Frodo's Blog
熟悉的歌谣里,藏着童话的影子。
2016-06-21T02:36:19.203Z
http://frodoking.github.io/
Frodo
awangyun8@gmail.com
Hexo
Github Android Client
http://frodoking.github.io/2016/06/07/android-github-client/
2016-06-07T08:27:28.000Z
2016-06-21T02:36:19.203Z
<p>前一段时间找了很久Github在Android端上的App,但是无果。同时为了验证个人开源的App框架<a href="https://github.com/frodoking/App-Architecture.git" target="_blank" rel="external">App-Architecture</a>,因此,花了一些时间编写了这个应用。另外个人相信针对拥有众多开发者的Github开发一个好的App是很有用的。</p>
<a id="more"></a>
<h2 id="GithubAndroidClient"><a href="#GithubAndroidClient" class="headerlink" title="GithubAndroidClient"></a>GithubAndroidClient</h2><p>This project is a Github client on Android platform, This App drawing is based on the design of Github Web page(H5).</p>
<h2 id="Feature"><a href="#Feature" class="headerlink" title="Feature"></a>Feature</h2><ol>
<li>account (Auth2.0/Sign in/Sign out)</li>
<li>explore (showcases/repos)</li>
<li>user (profile/followers/following)</li>
<li>repo (detail/contents)</li>
<li>events, notification, issues</li>
<li>trending</li>
</ol>
<p>there are many features not yet implemented, you can contribute to it if you are interested in this project.</p>
<h2 id="API"><a href="#API" class="headerlink" title="API"></a>API</h2><ol>
<li><a href="https://developer.github.com/v3/" target="_blank" rel="external">Github Developer</a></li>
<li><a href="https://octicons.github.com/" target="_blank" rel="external">Octions</a></li>
</ol>
<h2 id="About-Open-Source"><a href="#About-Open-Source" class="headerlink" title="About Open Source"></a>About Open Source</h2><h4 id="IDE"><a href="#IDE" class="headerlink" title="IDE"></a>IDE</h4><p>Android studio 2.0</p>
<h4 id="Third-Party-Project"><a href="#Third-Party-Project" class="headerlink" title="Third Party Project"></a>Third Party Project</h4><ol>
<li><a href="https://github.com/frodoking/App-Architecture.git" target="_blank" rel="external">App-Architecture</a><br> app architecture on android platform.</li>
<li><a href="https://github.com/falnatsheh/MarkdownView" target="_blank" rel="external">MarkdownView</a><br> MarkdownView is an Android webview with the capablity of loading Markdown text or file and display it as HTML, it uses MarkdownJ and extends Android webview.</li>
</ol>
<h2 id="Author-frodoking"><a href="#Author-frodoking" class="headerlink" title="Author(frodoking)"></a>Author(frodoking)</h2><ul>
<li>Email: awangyun8@gmail.com</li>
<li>Blog:<a href="http://frodoking.github.io/">http://frodoking.github.io/</a></li>
</ul>
<h2 id="Apk-v1-0-x-snapshots"><a href="#Apk-v1-0-x-snapshots" class="headerlink" title="Apk (v1.0.x snapshots)"></a>Apk (v1.0.x snapshots)</h2><blockquote>
<ul>
<li><a href="https://github.com/frodoking/GithubAndroidClient/releases/download/v1.0/github-v1.0.1-snapshots-release.apk" target="_blank" rel="external">github-v1.0.1-snapshots-release.apk</a></li>
<li><a href="https://github.com/frodoking/GithubAndroidClient/releases/download/v1.0/github-v1.0.2-snapshots-release.apk" target="_blank" rel="external">github-v1.0.2-snapshots-release.apk</a></li>
<li><a href="https://github.com/frodoking/GithubAndroidClient/releases/download/v1.0/github-v1.0.3-snapshots-release.apk" target="_blank" rel="external">github-v1.0.3-snapshots-release.apk</a></li>
</ul>
</blockquote>
<h2 id="App-Master-Drawing"><a href="#App-Master-Drawing" class="headerlink" title="App Master Drawing"></a>App Master Drawing</h2><p><img src="http://frodoking.github.io/img/github-client/github-home.png" width="240" height="427"><br><img src="http://frodoking.github.io/img/github-client/github-drawer.png" width="240" height="427"><br><img src="http://frodoking.github.io/img/github-client/github-profile.png" width="240" height="427"><br><img src="http://frodoking.github.io/img/github-client/github-notifications.png" width="240" height="427"><br><img src="http://frodoking.github.io/img/github-client/github-issues.png" width="240" height="427"><br><img src="http://frodoking.github.io/img/github-client/github-events.png" width="240" height="427"></p>
<p><img src="http://frodoking.github.io/img/github-client/github-explore.png" width="240" height="427"><br><img src="http://frodoking.github.io/img/github-client/github-showcases.png" width="240" height="427"></p>
<p><img src="http://frodoking.github.io/img/github-client/github-repo.png" width="240" height="427"><br><img src="http://frodoking.github.io/img/github-client/github-repo-issues.png" width="240" height="427"><br><img src="http://frodoking.github.io/img/github-client/github-repo-pulse.png" width="240" height="427"><br><img src="http://frodoking.github.io/img/github-client/github-repo-contents.png" width="240" height="427"></p>
<p>前一段时间找了很久Github在Android端上的App,但是无果。同时为了验证个人开源的App框架<a href="https://github.com/frodoking/App-Architecture.git">App-Architecture</a>,因此,花了一些时间编写了这个应用。另外个人相信针对拥有众多开发者的Github开发一个好的App是很有用的。</p>
Android Weekly 值得收藏的项
http://frodoking.github.io/2016/03/01/android-weekly/
2016-03-01T03:30:27.000Z
2016-03-01T03:30:29.760Z
<p><strong> 第193期 </strong></p>
<ol>
<li><a href="http://saulmm.github.io/avoding-android-cold-starts" target="_blank" rel="external">Avoiding cold starts on Android</a> </li>
</ol>
<p><strong> 第192期 </strong></p>
<ol>
<li><a href="http://www.schibsted.pl/2016/02/hood-okhttps-cache/" target="_blank" rel="external">What’s under the hood of the OkHttp’s cache?</a> </li>
</ol>
<p><strong> 第191期 </strong></p>
<ol>
<li><a href="https://medium.com/@workingkills/10-things-didn-t-know-about-android-s-service-component-a2880b74b2b3#.eu5ls3cic" target="_blank" rel="external">10 things you didn’t know about Android’s Service component</a></li>
</ol>
<p><strong> 第190期 </strong></p>
<ol>
<li><a href="http://riggaroo.co.za/fixing-memory-leaks-in-android-outofmemoryerror/" target="_blank" rel="external">Fixing Memory Leaks in Android - OutOfMemoryError</a></li>
</ol>
<p><strong> 第189期 </strong></p>
<ol>
<li><a href="https://medium.com/@v.danylo/implementing-video-playback-in-a-scrolled-list-listview-recyclerview-d04bc2148429#.mzto2r2u2" target="_blank" rel="external">Implementing video playback in a scrolled list </a><a id="more"></a>
</li>
</ol>
<p><strong> 第182期 </strong></p>
<ol>
<li><a href="https://medium.com/ribot-labs/android-application-architecture-8b6e34acda65#.io0zhw6cm" target="_blank" rel="external">Android Application Architecture</a></li>
</ol>
<p><strong> 第181期 </strong></p>
<ol>
<li><a href="https://www.simform.com/blog/top-resources-to-learn-android" target="_blank" rel="external">Top resources to learn Android</a></li>
</ol>
<p><strong> 第179期 </strong></p>
<ol>
<li><a href="http://www.grokkingandroid.com/rxjavas-side-effect-methods/" target="_blank" rel="external">RxJava’s Side Effect Methods</a></li>
</ol>
<p><strong> 第178期 </strong></p>
<ol>
<li><a href="http://pguardiola.com/blog/clean-architecture-part-1/" target="_blank" rel="external">What is all this Clean Architecture jibber-jabber about? - Part 1</a>;</li>
<li><a href="http://pguardiola.com/blog/clean-architecture-part-2/" target="_blank" rel="external">What is all this Clean Architecture jibber-jabber about? - Part 2</a>;</li>
<li><a href="https://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html" target="_blank" rel="external">The Clean Architecture</a>.</li>
</ol>
<p><strong> 第177期 </strong></p>
<ol>
<li><a href="http://saulmm.github.io/mastering-coordinator/" target="_blank" rel="external">Mastering the Coordinator Layout</a>;</li>
<li><a href="https://medium.com/@etiennelawlor/the-complete-guide-to-creating-an-android-library-46628b7fc879#.4jkgten5o" target="_blank" rel="external">The Complete Guide to Creating and Publishing an Android Library</a>;</li>
<li><a href="https://medium.com/@bherbst/fragment-transitions-with-shared-elements-7c7d71d31cbb#.ssvqlcm43" target="_blank" rel="external">Fragment transitions with shared elements</a>;</li>
<li><a href="https://yalantis.com/blog/video-recording-app-development-how-we-built-instagram-for-videos/" target="_blank" rel="external">Video Recording App Development: How We Built Instagram for Videos</a>;</li>
<li><a href="https://github.com/PhilJay/MPAndroidChart" target="_blank" rel="external">MPAndroidChart (github.com)</a>.</li>
</ol>
<p><strong> 第174期 </strong></p>
<ol>
<li><a href="https://futurestud.io/blog/retrofit-2-upgrade-guide-from-1-9/" target="_blank" rel="external">Retrofit 2 — Upgrade Guide from 1.9</a>;</li>
<li><a href="https://medium.com/@Macarse/lazy-loading-dex-files-d41f6f37df0e" target="_blank" rel="external">Lazy Loading Dex files</a>.</li>
</ol>
<p><strong> 第173期 </strong></p>
<ol>
<li><a href="https://code.facebook.com/posts/1480969635539475?refid=8&_ft_=qid.6200742327944805904:mf_story_key.6249203789055394671:eligibleForSeeFirstBumping.1&__tn__=H" target="_blank" rel="external">Optimizing Android bytecode with Redex</a>;</li>
<li><a href="http://blog.tunebrains.com/2015/10/02/gradle-multi-flavors-signing.html" target="_blank" rel="external">Sign multiple flavors with different keystores</a>;</li>
<li><a href="https://blog.stylingandroid.com/data-binding-part-1/" target="_blank" rel="external">Data Binding – Part 1</a>;</li>
<li><a href="https://blog.stylingandroid.com/data-binding-part-2/" target="_blank" rel="external">Data Binding – Part 2</a>;</li>
<li><a href="https://blog.stylingandroid.com/data-binding-part-3/" target="_blank" rel="external">Data Binding – Part 3</a>;</li>
<li><a href="https://blog.stylingandroid.com/data-binding-part-4/" target="_blank" rel="external">Data Binding – Part 4</a>;</li>
<li><a href="https://blog.stylingandroid.com/data-binding-part-5/" target="_blank" rel="external">Data Binding – Part 5</a>.</li>
</ol>
<p><strong> 第172期 </strong></p>
<ol>
<li><a href="http://blog.udinic.com/2015/09/15/speed-up-your-app" target="_blank" rel="external">Speed up your app</a>;</li>
<li><a href="https://medium.com/ribot-labs/approaching-android-with-mvvm-8ceec02d5442" target="_blank" rel="external">Approaching Android with MVVM</a>;</li>
<li><a href="https://medium.com/android-news/prefmatters-using-custom-views-in-android-to-improve-performance-part-1-4dc9bdd75396" target="_blank" rel="external">PerfMatters using custom Views in Android to improve performance — Part 1</a>;</li>
<li><a href="https://medium.com/android-news/perfmatters-introduction-to-custom-viewgroups-to-improve-performance-part-2-f14fbcd47c" target="_blank" rel="external">PerfMatters introduction to custom ViewGroups to improve performance — Part 2</a>.</li>
</ol>
<p><strong> 第168期 </strong></p>
<ol>
<li><a href="https://speakerdeck.com/rock3r/tools-of-the-trade-droidcon-nyc-2015" target="_blank" rel="external">Tools of the Trade</a>;</li>
<li><a href="https://speakerdeck.com/udinic/speed-up-your-app-droidcon-nyc-2015" target="_blank" rel="external">Speed up your app</a>;</li>
<li><a href="http://www.slideshare.net/AnnyceDavis/develop-maintainable-apps-droidcon-2015" target="_blank" rel="external">Develop Maintainable Apps</a>;</li>
<li><a href="https://medium.com/android-news/android-architecture-2f12e1c7d4db" target="_blank" rel="external">Android Architecture</a>;</li>
<li><a href="https://speakerdeck.com/dlew/using-styles-and-themes-without-going-crazy-1" target="_blank" rel="external">Using styles and themes without going crazy</a>;</li>
<li><a href="https://speakerdeck.com/jakewharton/simple-http-with-retrofit-2-droidcon-nyc-2015" target="_blank" rel="external">Simple HTTP with Retrofit 2</a>.</li>
</ol>
<p><strong> InfoQ 2015年9月30日 </strong></p>
<ol>
<li>AndFix:dexposed框架功能虽然强大,但是由于它的性能和兼容性(不支持ART),很多朋友都决定不再研究此框架。AndFix是一个Android App的在线热补丁框架。使用此框架,我们能够在不重复发版的情况下,在线修改App中的Bug。AndFix也就是 “Android Hot-Fix”的缩写。AndFix支持Android 2.3到6.0版本,并且支持ARM与X86系统架构的设备,完美支持Dalvik与ART的Runtime。</li>
</ol>
<p><strong> 第193期 </strong></p>
<ol>
<li><a href="http://saulmm.github.io/avoding-android-cold-starts">Avoiding cold starts on Android</a> </li>
</ol>
<p><strong> 第192期 </strong></p>
<ol>
<li><a href="http://www.schibsted.pl/2016/02/hood-okhttps-cache/">What’s under the hood of the OkHttp’s cache?</a> </li>
</ol>
<p><strong> 第191期 </strong></p>
<ol>
<li><a href="https://medium.com/@workingkills/10-things-didn-t-know-about-android-s-service-component-a2880b74b2b3#.eu5ls3cic">10 things you didn’t know about Android’s Service component</a></li>
</ol>
<p><strong> 第190期 </strong></p>
<ol>
<li><a href="http://riggaroo.co.za/fixing-memory-leaks-in-android-outofmemoryerror/">Fixing Memory Leaks in Android - OutOfMemoryError</a></li>
</ol>
<p><strong> 第189期 </strong></p>
<ol>
<li><a href="https://medium.com/@v.danylo/implementing-video-playback-in-a-scrolled-list-listview-recyclerview-d04bc2148429#.mzto2r2u2">Implementing video playback in a scrolled list </a>
令人尖叫的架构
http://frodoking.github.io/2015/11/11/screaming-architecture/
2015-11-11T07:19:38.000Z
2015-11-11T09:09:50.019Z
<p>原文:<a href="https://blog.8thlight.com/uncle-bob/2011/09/30/Screaming-Architecture.html" target="_blank" rel="external">https://blog.8thlight.com/uncle-bob/2011/09/30/Screaming-Architecture.html</a></p>
<p>想像一下,你正在看一栋大楼的蓝图。建筑师为你准备了一份修建大楼的计划文档,那你从这些计划文档中能知道些什么呢?<br>如果你看的计划书是针对单个家庭住户的话,那么你可能会看到一个前门,一个通向客厅或者是餐厅的门厅。靠近饭厅很可能离厨房有一段很短的距离。也许厨房的旁边是扇子区,也可能是一个卧室。当你看着这些计划,你毫无疑问的就会想到你正在看的是一套房子。该架构就叫做“房子”。</p>
<p>或者,如果你在一座图书馆的架构时,在这个图书馆里,你很可能看到一个宏伟的入口,一个出入检查区域,阅读区,小型会议室和一排排用于放书的书架。这种架构就叫做“图书馆”。</p>
<p>因此,你的应用架构重点是什么呢?当你看到顶层的目录结构和在最高级别的包的源文件时,重点是医疗保健系统,或者是会计系统,或者是库存管理系统?还是Rails, 或者 Spring/Hibernate, 或者ASP呢?<br><a id="more"></a></p>
<h2 id="架构的主题"><a href="#架构的主题" class="headerlink" title="架构的主题"></a>架构的主题</h2><p>回过头来去读Ivar Jacobson关于软件架构一书《面向对象软件工程(Object Oriented Software Engineering)》。这本书的副标题是:一种用例驱动方法(A use case driven approach)。在这本书里Ivar明确指出,软件架构是支持系统用例的结构。就像一栋房子或者是一座图书馆的计划主要关注的是这些建筑的用例,因此,一个软件应用也应该重点关注应用本身的用例。</p>
<p>架构并不是框架(也不应该是)。架构不应该由框架提供。框架是要使用的工具,而架构并不是。如果你的架构是基于框架,那么就不能根据你的用例。</p>
<h2 id="架构的目的"><a href="#架构的目的" class="headerlink" title="架构的目的"></a>架构的目的</h2><p>好的架构主要关心用例的原因是可以让软件设计师能够安全的描述结构来支持这些用例,而不用关心框架,工具和工程环境。再回到房子的计划上,建筑设计师首先关心的是确保这个房子是可使用的,而不是确保这个房子是用什么样的砖来修建。事实上,建筑设计师必须精心设计计划来确保房主可以决定使用砖、石或者是雪松的情况得到满足。</p>
<p>一个优秀的软件架构允许可以推迟框架、数据库、web服务器和其他环境问题以及工具的约定。不需要立即决定用Rails、Spring、Hibernate、Tomcat或者MySql,当然直到最后的项目。一个好的架构可以很容易地改变这些主意的决定。并且强调用例,从外部考虑中来让他们解耦。</p>
<h2 id="Web工程又是什么?"><a href="#Web工程又是什么?" class="headerlink" title="Web工程又是什么?"></a>Web工程又是什么?</h2><p>Web工程是一个架构吗?交付在网络上的你的系统真的就决定了你的系统架构吗?当然不是!Web只是一种展示或者发布的机制,并且你的应用架构也应该这么认为。事实上,你的应用通过Web来展示只是一个细节,不能决定你的系统结构。并且,你的应用通过Web这些事情你应该延后考虑。所以,你的系统应该忽略该如何来发布该项目的事情。你可以作为一个控制台应用程序、Web应用程序或者客户端应用程序,甚至是Web服务应用程序,对于基本架构无需过分复杂和变动。</p>
<h2 id="框架只是工具"><a href="#框架只是工具" class="headerlink" title="框架只是工具"></a>框架只是工具</h2><p>框架是非常强大和非常有用的。框架开发者往往很相信他们的框架,他们写如何利用自己框架的例子就能说明这一点。谁写的框架,其他一些写框架的开发者也对框架深信不疑。他们展示使用该框架的方式。通常情况下,这就是一个无所不包,无所不在,框架至上的思维。当然这不是你想要采取的立场。</p>
<p>当我们带着一双疲惫的眼睛看着每个框架时。我们抱以怀疑态度可能会有帮助,但是代价又怎样呢?我应该如何使用它,我该如何保护自己的框架免受它的影响。我该如何维护我的架构所强调的测试用例?如何防止自身的框架被这样的架构接管呢? </p>
<h2 id="可测试架构"><a href="#可测试架构" class="headerlink" title="可测试架构"></a>可测试架构</h2><p>如果你的系统架构是基于用例的,如果你保持你的所有框架在同一个公平等级上。那么你应该能够不用任何替代框架来完成所有用例的单元测试。您应该不需要为了运行你的测试运行Web服务器。你不需要连接数据库来运行你的测试。您的业务对象应该是普通对象,不依赖与框架、数据库或其他复杂逻辑。你的用例对象应该和你的业务对象保持一致。所有的测试用例再一起也是可以立即可测试的,跟框架无关。</p>
<h2 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h2><p>你的架构应该告诉读者这是一个系统,而不是你在系统中使用的框架。如果你构建一个医疗保健系统,当新的程序员看到该系统的源码是,他们的第一映像应该是“这就是一个医疗保健系统”。这些新的程序员应该先知道所有的系统用例,而不是先知道系统是如何实现的。他们可能过来说,“我们看到一些东西像是Model,但是View和Controller在哪儿呢?”,而你应该说,“噢,在这个时候你不需要去关心这些细节,在后面我们将会展示给你”。</p>
<p>原文:<a href="https://blog.8thlight.com/uncle-bob/2011/09/30/Screaming-Architecture.html">https://blog.8thlight.com/uncle-bob/2011/09/30/Screaming-Architecture.html</a></p>
<p>想像一下,你正在看一栋大楼的蓝图。建筑师为你准备了一份修建大楼的计划文档,那你从这些计划文档中能知道些什么呢?<br>如果你看的计划书是针对单个家庭住户的话,那么你可能会看到一个前门,一个通向客厅或者是餐厅的门厅。靠近饭厅很可能离厨房有一段很短的距离。也许厨房的旁边是扇子区,也可能是一个卧室。当你看着这些计划,你毫无疑问的就会想到你正在看的是一套房子。该架构就叫做“房子”。</p>
<p>或者,如果你在一座图书馆的架构时,在这个图书馆里,你很可能看到一个宏伟的入口,一个出入检查区域,阅读区,小型会议室和一排排用于放书的书架。这种架构就叫做“图书馆”。</p>
<p>因此,你的应用架构重点是什么呢?当你看到顶层的目录结构和在最高级别的包的源文件时,重点是医疗保健系统,或者是会计系统,或者是库存管理系统?还是Rails, 或者 Spring/Hibernate, 或者ASP呢?<br>
Android Glide源码解析
http://frodoking.github.io/2015/10/10/android-glide/
2015-10-10T03:03:19.000Z
2016-03-09T10:09:27.886Z
<h2 id="功能介绍"><a href="#功能介绍" class="headerlink" title="功能介绍"></a>功能介绍</h2><p>使用文章介绍以及和Picasso的对比分析请参考<a href="http://inthecheesefactory.com/blog/get-to-know-glide-recommended-by-google/en" target="_blank" rel="external">Introduction to Glide, Image Loader Library for Android, recommended by Google</a></p>
<p>由于这篇文章使用glide的老版本,因此有些使用方法可能不太一致了。<br>本文基于github上Glide最新代码4.0.0版本做解析。<br>最基本的使用方式如下:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">Glide.with(this)</span><br><span class="line"> .asDrawable()</span><br><span class="line"> .load("http://i6.topit.me/6/5d/45/1131907198420455d6o.jpg")</span><br><span class="line"> .apply(fitCenterTransform(this))</span><br><span class="line"> .apply(placeholderOf(R.drawable.skyblue_logo_wechatfavorite_checked))</span><br><span class="line"> .into(imageView);</span><br></pre></td></tr></table></figure></p>
<p>Glide使用了现在非常流行的流氏编码方式,方便了开发者的使用,简明、扼要。<br>接下来主要对上面这一段流氏操作做拆分。<br><a id="more"></a></p>
<h3 id="Glide-主入口"><a href="#Glide-主入口" class="headerlink" title="Glide 主入口"></a>Glide 主入口</h3><p>这个类有点像门脸模式的统一代理入口,不过实际作用在4.0.0中很多功能都被放到后面的其他类中,此类关注的点就很少了。虽然整个libray的所有需要的组建都在这个类中,但是实际也只是一个统一初始化的地方。</p>
<h3 id="RequestManager(Glide-with-…-)"><a href="#RequestManager(Glide-with-…-)" class="headerlink" title="RequestManager(Glide.with(…))"></a>RequestManager(Glide.with(…))</h3><p>这个类主要用于管理和启动Glide的所有请求,可以使用activity,fragment或者连接生命周期的事件去智能的停止,启动,和重启请求。也可以检索或者通过实例化一个新的对象,或者使用静态的Glide去利用构建在Activity和Fragment生命周期处理中。它的方法跟你的Fragment和Activity的是同步的。</p>
<h3 id="RequestBuilder"><a href="#RequestBuilder" class="headerlink" title="RequestBuilder"></a>RequestBuilder</h3><p>通用类,可以处理设置选项,并启动负载的通用资源类型。</p>
<p>在这个类中,主要是应用请求的很多选项(如下的选项从字面都能看出具体的用处,在ImageView控件中经常也能看到,另外之前版本可不是这么使用的):<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">public final class RequestOptions extends BaseRequestOptions<RequestOptions> {</span><br><span class="line"></span><br><span class="line"> private static RequestOptions skipMemoryCacheTrueOptions;</span><br><span class="line"> private static RequestOptions skipMemoryCacheFalseOptions;</span><br><span class="line"> private static RequestOptions fitCenterOptions;</span><br><span class="line"> private static RequestOptions centerCropOptions;</span><br><span class="line"> private static RequestOptions circleCropOptions;</span><br><span class="line"> private static RequestOptions noTransformOptions;</span><br><span class="line"> private static RequestOptions noAnimationOptions;</span><br><span class="line"> </span><br><span class="line"> // ...省略...</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>RequestBuilder<transcodetype> transition(TransitionOptions transitionOptions){} 这个方法主要是用于加载对象从占位符(placeholder)或者缩略图(thumbnail)到真正对象加载完成的转场动画。</transcodetype></p>
<p>RequestBuilder<transcodetype> load(…){}多太方法中,这里可以加载很多类型的数据对象,可以是String,Uri,File,resourceId,byte[]这些。当然这些后面对应的编码方式也是不一样的。</transcodetype></p>
<p>Target<transcodetype> into(…){}这个方法是触发Request真正启动的地方,在上边的示例中最后就是调用这个方法发起请求。</transcodetype></p>
<p>不得不说的registry域,这个域挂载了很多元件,该注册器中囊括了模块加载器(ModelLoader)、编码器(Encoder)、资源解码器(ResourceDecoder)、资源编码器(ResourceEncoder)、数据回转器(DataRewinder)、转码器(Transcoder)。这些都是Glide在对资源编解码中既是基础又是核心功能。</p>
<h2 id="代码结构"><a href="#代码结构" class="headerlink" title="代码结构"></a>代码结构</h2><p>这里主要列举一下一些重要的组件以及他们的结构关系:<br><strong>ModelLoader</strong><br><img src="http://frodoking.github.io/img/glide/Glide_ModelLoaderDiagram.png" alt="ModelLoader"></p>
<p><strong>DataFetcher</strong><br><img src="http://frodoking.github.io/img/glide/Glide_DataFetcherDiagram.png" alt="DataFetcher"></p>
<p><strong>Target</strong><br><img src="http://frodoking.github.io/img/glide/Glide_TargetDiagram.png" alt="Target"></p>
<p><strong>Resource</strong><br><img src="http://frodoking.github.io/img/glide/Glide_ResourceDiagram.png" alt="Resource"></p>
<p><strong>ResourceTransformation</strong><br><img src="http://frodoking.github.io/img/glide/Glide_ResourceTransformationDiagram.png" alt="ResourceTransformation"></p>
<p><strong>Pool</strong><br><img src="http://frodoking.github.io/img/glide/Glide_PoolDiagram.png" alt="Pool"></p>
<p><strong>Cache</strong><br><img src="http://frodoking.github.io/img/glide/Glide_CacheDiagram.png" alt="Cache"></p>
<p><strong>Decoder</strong><br><img src="http://frodoking.github.io/img/glide/Glide_DecoderDiagram.png" alt="Decoder"></p>
<p><strong>Encoder</strong><br><img src="http://frodoking.github.io/img/glide/Glide_EncoderDiagram.png" alt="Encoder"><br>把这些组件代码结构列举出来主要是为了让读者和使用者一目了然的看到自己需要的一些功能。</p>
<h2 id="执行流程"><a href="#执行流程" class="headerlink" title="执行流程"></a>执行流程</h2><h3 id="1、根据不同版本的Fragment创建RequestManagerFragment或者SupportRequestManagerFragment,并加入到对应的FragmentManager中。这两种Fragment是不带有任何界面的,主要是用于同步生命周期。具体实现如下:"><a href="#1、根据不同版本的Fragment创建RequestManagerFragment或者SupportRequestManagerFragment,并加入到对应的FragmentManager中。这两种Fragment是不带有任何界面的,主要是用于同步生命周期。具体实现如下:" class="headerlink" title="1、根据不同版本的Fragment创建RequestManagerFragment或者SupportRequestManagerFragment,并加入到对应的FragmentManager中。这两种Fragment是不带有任何界面的,主要是用于同步生命周期。具体实现如下:"></a>1、根据不同版本的Fragment创建RequestManagerFragment或者SupportRequestManagerFragment,并加入到对应的FragmentManager中。这两种Fragment是不带有任何界面的,主要是用于同步生命周期。具体实现如下:</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line">public static RequestManager with(Context context) {</span><br><span class="line"> RequestManagerRetriever retriever = RequestManagerRetriever.get();</span><br><span class="line"> return retriever.get(context);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">// RequestManagerRetriever.get(...) </span><br><span class="line">@TargetApi(Build.VERSION_CODES.HONEYCOMB)</span><br><span class="line"> public RequestManager get(Activity activity) {</span><br><span class="line"> if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {</span><br><span class="line"> return get(activity.getApplicationContext());</span><br><span class="line"> } else {</span><br><span class="line"> assertNotDestroyed(activity);</span><br><span class="line"> android.app.FragmentManager fm = activity.getFragmentManager();</span><br><span class="line"> return fragmentGet(activity, fm, null);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> @TargetApi(Build.VERSION_CODES.HONEYCOMB)</span><br><span class="line"> RequestManager fragmentGet(Context context, android.app.FragmentManager fm,</span><br><span class="line"> android.app.Fragment parentHint) {</span><br><span class="line"> RequestManagerFragment current = getRequestManagerFragment(fm, parentHint);</span><br><span class="line"> RequestManager requestManager = current.getRequestManager();</span><br><span class="line"> if (requestManager == null) {</span><br><span class="line"> requestManager =</span><br><span class="line"> new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());</span><br><span class="line"> current.setRequestManager(requestManager);</span><br><span class="line"> }</span><br><span class="line"> return requestManager;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)</span><br><span class="line"> RequestManagerFragment getRequestManagerFragment(</span><br><span class="line"> final android.app.FragmentManager fm, android.app.Fragment parentHint) {</span><br><span class="line"> RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);</span><br><span class="line"> if (current == null) {</span><br><span class="line"> current = pendingRequestManagerFragments.get(fm);</span><br><span class="line"> if (current == null) {</span><br><span class="line"> current = new RequestManagerFragment();</span><br><span class="line"> current.setParentFragmentHint(parentHint);</span><br><span class="line"> pendingRequestManagerFragments.put(fm, current);</span><br><span class="line"> fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();</span><br><span class="line"> handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return current;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<h3 id="2、创建一个RequestBuilder,并添加一个DrawableTransitionOptions类型的转场动画"><a href="#2、创建一个RequestBuilder,并添加一个DrawableTransitionOptions类型的转场动画" class="headerlink" title="2、创建一个RequestBuilder,并添加一个DrawableTransitionOptions类型的转场动画"></a>2、创建一个RequestBuilder,并添加一个DrawableTransitionOptions类型的转场动画</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">public RequestBuilder<Drawable> asDrawable() {</span><br><span class="line"> return as(Drawable.class).transition(new DrawableTransitionOptions());</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<h3 id="3、加载对象(model域)"><a href="#3、加载对象(model域)" class="headerlink" title="3、加载对象(model域)"></a>3、加载对象(model域)</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">public RequestBuilder<TranscodeType> load(@Nullable Object model) {</span><br><span class="line"> return loadGeneric(model);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line">private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {</span><br><span class="line"> this.model = model;</span><br><span class="line"> isModelSet = true;</span><br><span class="line"> return this;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<h3 id="4、装载对象(包含请求的发起点)。"><a href="#4、装载对象(包含请求的发起点)。" class="headerlink" title="4、装载对象(包含请求的发起点)。"></a>4、装载对象(包含请求的发起点)。</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">public <Y extends Target<TranscodeType>> Y into(@NonNull Y target) {</span><br><span class="line"> Util.assertMainThread();</span><br><span class="line"> Preconditions.checkNotNull(target);</span><br><span class="line"> if (!isModelSet) {</span><br><span class="line"> throw new IllegalArgumentException("You must call #load() before calling #into()");</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> Request previous = target.getRequest();</span><br><span class="line"></span><br><span class="line"> if (previous != null) {</span><br><span class="line"> requestManager.clear(target);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> requestOptions.lock();</span><br><span class="line"> Request request = buildRequest(target);</span><br><span class="line"> target.setRequest(request);</span><br><span class="line"> requestManager.track(target, request);</span><br><span class="line"></span><br><span class="line"> return target;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p>一般而言,大部分使用者都是用来装载图片的,因此都会调用如下这个方法:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">public Target<TranscodeType> into(ImageView view) {</span><br><span class="line"> Util.assertMainThread();</span><br><span class="line"> Preconditions.checkNotNull(view);</span><br><span class="line"></span><br><span class="line"> if (!requestOptions.isTransformationSet()</span><br><span class="line"> && requestOptions.isTransformationAllowed()</span><br><span class="line"> && view.getScaleType() != null) {</span><br><span class="line"> if (requestOptions.isLocked()) {</span><br><span class="line"> requestOptions = requestOptions.clone();</span><br><span class="line"> }</span><br><span class="line"> switch (view.getScaleType()) {</span><br><span class="line"> case CENTER_CROP:</span><br><span class="line"> requestOptions.optionalCenterCrop(context);</span><br><span class="line"> break;</span><br><span class="line"> case FIT_CENTER:</span><br><span class="line"> case FIT_START:</span><br><span class="line"> case FIT_END:</span><br><span class="line"> requestOptions.optionalFitCenter(context);</span><br><span class="line"> break;</span><br><span class="line"> //$CASES-OMITTED$</span><br><span class="line"> default:</span><br><span class="line"> // Do nothing.</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> return into(context.buildImageViewTarget(view, transcodeClass));</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p>
<p>这里针对ImageView的填充方式做了筛选并对应设置到requestOptions上。最终的是通过ImageView和转码类型(transcodeClass)创建不通过的Target(例如Bitmap对应的BitmapImageViewTarget和Drawable对应的DrawableImageViewTarget)</p>
<p><strong>4.1 Request的创建buildRequest(target)。</strong><br>在Request的创建中会针对是否有缩略图来创建不同尺寸的请求,缩略图方法可以使用RequestBuilder.thumbnail(…)方法来添加上。<br>Glide中的Request都是使用了SingleRequest类,当然缩略图采用的是ThumbnailRequestCoordinator类:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">private Request obtainRequest(Target<TranscodeType> target,</span><br><span class="line"> BaseRequestOptions<?> requestOptions, RequestCoordinator requestCoordinator,</span><br><span class="line"> TransitionOptions<?, ? super TranscodeType> transitionOptions, Priority priority,</span><br><span class="line"> int overrideWidth, int overrideHeight) {</span><br><span class="line"> requestOptions.lock();</span><br><span class="line"></span><br><span class="line"> return SingleRequest.obtain(</span><br><span class="line"> context,</span><br><span class="line"> model,</span><br><span class="line"> transcodeClass,</span><br><span class="line"> requestOptions,</span><br><span class="line"> overrideWidth,</span><br><span class="line"> overrideHeight,</span><br><span class="line"> priority,</span><br><span class="line"> target,</span><br><span class="line"> requestListener,</span><br><span class="line"> requestCoordinator,</span><br><span class="line"> context.getEngine(),</span><br><span class="line"> transitionOptions.getTransitionFactory());</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p>
<p>比较值得推崇的是SingleRequest.obtain写法,个人认为比new关键字更简洁明了吧。</p>
<p>target.setRequest(request)也是一个比较值得注意的地方,如果target是ViewTarget,那么request会被设置到View的tag上。这样其实是有一个好处,每一个View有一个自己的Request,如果有重复请求,那么都会先去拿到上一个已经绑定的Request,并且从RequestManager中清理回收掉。这应该是去重的功能。</p>
<p><strong>4.2 requestManager.track(target, request)</strong><br>这个方法非常的复杂,主要用于触发请求、编解码、装载和缓存这些功能。下面就一步一步来看吧:</p>
<p><strong>4.2.1 缓存target,并启动Request</strong><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">void track(Target<?> target, Request request) {</span><br><span class="line"> targetTracker.track(target);</span><br><span class="line"> requestTracker.runRequest(request);</span><br><span class="line"> }</span><br><span class="line"> /**</span><br><span class="line"> * Starts tracking the given request.</span><br><span class="line"> */</span><br><span class="line"> public void runRequest(Request request) {</span><br><span class="line"> requests.add(request); //添加内存缓存</span><br><span class="line"> if (!isPaused) {</span><br><span class="line"> request.begin(); // 开始</span><br><span class="line"> } else {</span><br><span class="line"> pendingRequests.add(request); // 挂起请求</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p>
<p>继续看一下SingleRequest中的begin方法:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">@Override</span><br><span class="line"> public void begin() {</span><br><span class="line"> stateVerifier.throwIfRecycled();</span><br><span class="line"> startTime = LogTime.getLogTime();</span><br><span class="line"> // 如果model空的,那么是不能执行的。 这里的model就是前面提到的RequestBuilder中的model</span><br><span class="line"> if (model == null) {</span><br><span class="line"> if (Util.isValidDimensions(overrideWidth, overrideHeight)) {</span><br><span class="line"> width = overrideWidth;</span><br><span class="line"> height = overrideHeight;</span><br><span class="line"> }</span><br><span class="line"> // Only log at more verbose log levels if the user has set a fallback drawable, because</span><br><span class="line"> // fallback Drawables indicate the user expects null models occasionally.</span><br><span class="line"> int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;</span><br><span class="line"> onLoadFailed(new GlideException("Received null model"), logLevel);</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> status = Status.WAITING_FOR_SIZE;</span><br><span class="line"> // 如果当前的View尺寸已经加载获取到了,那么就会进入真正的加载流程。</span><br><span class="line"> if (Util.isValidDimensions(overrideWidth, overrideHeight)) {</span><br><span class="line"> onSizeReady(overrideWidth, overrideHeight);</span><br><span class="line"> } else {</span><br><span class="line"> // 反之,当前View还没有画出来,那么是没有尺寸的。</span><br><span class="line"> // 这里会调用到ViewTreeObserver.addOnPreDrawListener。</span><br><span class="line"> // 等待View的尺寸都ok,才会继续</span><br><span class="line"> target.getSize(this);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 如果等待和正在执行状态,那么当前会加载占位符Drawable</span><br><span class="line"> if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)</span><br><span class="line"> && canNotifyStatusChanged()) {</span><br><span class="line"> target.onLoadStarted(getPlaceholderDrawable());</span><br><span class="line"> }</span><br><span class="line"> if (Log.isLoggable(TAG, Log.VERBOSE)) {</span><br><span class="line"> logV("finished run method in " + LogTime.getElapsedMillis(startTime));</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p>
<p>接下来是target.getSize(this)方法。这里主要说一下尺寸未加载出来的情况(ViewTarget.java):<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line">void getSize(SizeReadyCallback cb) {</span><br><span class="line"> int currentWidth = getViewWidthOrParam();</span><br><span class="line"> int currentHeight = getViewHeightOrParam();</span><br><span class="line"> if (isSizeValid(currentWidth) && isSizeValid(currentHeight)) {</span><br><span class="line"> cb.onSizeReady(currentWidth, currentHeight);</span><br><span class="line"> } else {</span><br><span class="line"> // We want to notify callbacks in the order they were added and we only expect one or two</span><br><span class="line"> // callbacks to</span><br><span class="line"> // be added a time, so a List is a reasonable choice.</span><br><span class="line"> if (!cbs.contains(cb)) {</span><br><span class="line"> cbs.add(cb);</span><br><span class="line"> }</span><br><span class="line"> if (layoutListener == null) {</span><br><span class="line"> final ViewTreeObserver observer = view.getViewTreeObserver();</span><br><span class="line"> layoutListener = new SizeDeterminerLayoutListener(this);</span><br><span class="line"> // 绘画之前加入尺寸的监听。这一点我想大部分Android开发同学应该都知道。</span><br><span class="line"> // 接下来在看看系统触发该Listener时target又干了些什么。</span><br><span class="line"> observer.addOnPreDrawListener(layoutListener);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">private static class SizeDeterminerLayoutListener implements ViewTreeObserver</span><br><span class="line"> .OnPreDrawListener {</span><br><span class="line"> // 注意这里是弱引用</span><br><span class="line"> private final WeakReference<SizeDeterminer> sizeDeterminerRef;</span><br><span class="line"></span><br><span class="line"> public SizeDeterminerLayoutListener(SizeDeterminer sizeDeterminer) {</span><br><span class="line"> sizeDeterminerRef = new WeakReference<>(sizeDeterminer);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> @Override</span><br><span class="line"> public boolean onPreDraw() {</span><br><span class="line"> if (Log.isLoggable(TAG, Log.VERBOSE)) {</span><br><span class="line"> Log.v(TAG, "OnGlobalLayoutListener called listener=" + this);</span><br><span class="line"> }</span><br><span class="line"> SizeDeterminer sizeDeterminer = sizeDeterminerRef.get();</span><br><span class="line"> if (sizeDeterminer != null) {</span><br><span class="line"> // 通知SizeDeterminer去重新检查尺寸,并触发后续操作。</span><br><span class="line"> // SizeDeterminer有点像工具类,又作为尺寸回调的检测接口</span><br><span class="line"> sizeDeterminer.checkCurrentDimens();</span><br><span class="line"> }</span><br><span class="line"> return true;</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p>
<p>ok,继续回到SingleRequest.onSizeReady方法,主要就是Engine发起load操作<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line">public void onSizeReady(int width, int height) {</span><br><span class="line"> stateVerifier.throwIfRecycled();</span><br><span class="line"> if (Log.isLoggable(TAG, Log.VERBOSE)) {</span><br><span class="line"> logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));</span><br><span class="line"> }</span><br><span class="line"> if (status != Status.WAITING_FOR_SIZE) {</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"> status = Status.RUNNING;</span><br><span class="line"></span><br><span class="line"> float sizeMultiplier = requestOptions.getSizeMultiplier();</span><br><span class="line"> this.width = Math.round(sizeMultiplier * width);</span><br><span class="line"> this.height = Math.round(sizeMultiplier * height);</span><br><span class="line"></span><br><span class="line"> if (Log.isLoggable(TAG, Log.VERBOSE)) {</span><br><span class="line"> logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));</span><br><span class="line"> }</span><br><span class="line"> loadStatus = engine.load(</span><br><span class="line"> glideContext,</span><br><span class="line"> model,</span><br><span class="line"> requestOptions.getSignature(),</span><br><span class="line"> this.width,</span><br><span class="line"> this.height,</span><br><span class="line"> requestOptions.getResourceClass(),</span><br><span class="line"> transcodeClass,</span><br><span class="line"> priority,</span><br><span class="line"> requestOptions.getDiskCacheStrategy(),</span><br><span class="line"> requestOptions.getTransformations(),</span><br><span class="line"> requestOptions.isTransformationRequired(),</span><br><span class="line"> requestOptions.getOptions(),</span><br><span class="line"> requestOptions.isMemoryCacheable(),</span><br><span class="line"> this);</span><br><span class="line"> if (Log.isLoggable(TAG, Log.VERBOSE)) {</span><br><span class="line"> logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p>
<p>特别的,所有的操作都是来之唯一一个Engine,它的创建是来至于Glide的初始化。如果有需要修改缓存配置的同学可以继续看一下diskCacheFactory的创建:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">if (engine == null) {</span><br><span class="line"> engine = new Engine(memoryCache, diskCacheFactory, diskCacheExecutor, sourceExecutor);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p>
<p>继续看一下Engine.load的详细过程:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br></pre></td><td class="code"><pre><span class="line">public <R> LoadStatus load(</span><br><span class="line"> GlideContext glideContext,</span><br><span class="line"> Object model,</span><br><span class="line"> Key signature,</span><br><span class="line"> int width,</span><br><span class="line"> int height,</span><br><span class="line"> Class<?> resourceClass,</span><br><span class="line"> Class<R> transcodeClass,</span><br><span class="line"> Priority priority,</span><br><span class="line"> DiskCacheStrategy diskCacheStrategy,</span><br><span class="line"> Map<Class<?>, Transformation<?>> transformations,</span><br><span class="line"> boolean isTransformationRequired,</span><br><span class="line"> Options options,</span><br><span class="line"> boolean isMemoryCacheable,</span><br><span class="line"> ResourceCallback cb) {</span><br><span class="line"> Util.assertMainThread();</span><br><span class="line"> long startTime = LogTime.getLogTime();</span><br><span class="line"></span><br><span class="line"> // 创建key,这是给每次加载资源的唯一标示。</span><br><span class="line"> EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,</span><br><span class="line"> resourceClass, transcodeClass, options);</span><br><span class="line"></span><br><span class="line"> // 通过key查找缓存资源 (PS 这里的缓存主要是内存中的缓存,切记,可以查看MemoryCache)</span><br><span class="line"> EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);</span><br><span class="line"> if (cached != null) {</span><br><span class="line"> // 如果有,那么直接利用当前缓存的资源。</span><br><span class="line"> cb.onResourceReady(cached, DataSource.MEMORY_CACHE);</span><br><span class="line"> if (Log.isLoggable(TAG, Log.VERBOSE)) {</span><br><span class="line"> logWithTimeAndKey("Loaded resource from cache", startTime, key);</span><br><span class="line"> }</span><br><span class="line"> return null;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 这是一个二级内存的缓存引用,很简单用了一个Map<Key, WeakReference<EngineResource<?>>>装载起来的。</span><br><span class="line"> // 这个缓存主要是谁来放进去呢? 可以参考上面一级内存缓存loadFromCache方法。</span><br><span class="line"> EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);</span><br><span class="line"> if (active != null) {</span><br><span class="line"> cb.onResourceReady(active, DataSource.MEMORY_CACHE);</span><br><span class="line"> if (Log.isLoggable(TAG, Log.VERBOSE)) {</span><br><span class="line"> logWithTimeAndKey("Loaded resource from active resources", startTime, key);</span><br><span class="line"> }</span><br><span class="line"> return null;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 根据key获取缓存的job。</span><br><span class="line"> EngineJob current = jobs.get(key);</span><br><span class="line"> if (current != null) {</span><br><span class="line"> current.addCallback(cb); // 给当前job添加上回调Callback</span><br><span class="line"> if (Log.isLoggable(TAG, Log.VERBOSE)) {</span><br><span class="line"> logWithTimeAndKey("Added to existing load", startTime, key);</span><br><span class="line"> }</span><br><span class="line"> return new LoadStatus(cb, current);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 创建job</span><br><span class="line"> EngineJob<R> engineJob = engineJobFactory.build(key, isMemoryCacheable);</span><br><span class="line"> DecodeJob<R> decodeJob = decodeJobFactory.build(</span><br><span class="line"> glideContext,</span><br><span class="line"> model,</span><br><span class="line"> key,</span><br><span class="line"> signature,</span><br><span class="line"> width,</span><br><span class="line"> height,</span><br><span class="line"> resourceClass,</span><br><span class="line"> transcodeClass,</span><br><span class="line"> priority,</span><br><span class="line"> diskCacheStrategy,</span><br><span class="line"> transformations,</span><br><span class="line"> isTransformationRequired,</span><br><span class="line"> options,</span><br><span class="line"> engineJob);</span><br><span class="line"> jobs.put(key, engineJob);</span><br><span class="line"> engineJob.addCallback(cb);</span><br><span class="line"> // 放入线程池,执行</span><br><span class="line"> engineJob.start(decodeJob);</span><br><span class="line"></span><br><span class="line"> if (Log.isLoggable(TAG, Log.VERBOSE)) {</span><br><span class="line"> logWithTimeAndKey("Started new load", startTime, key);</span><br><span class="line"> }</span><br><span class="line"> return new LoadStatus(cb, engineJob);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p>
<p>上面有一些值得注意的地方:</p>
<blockquote>
<ol>
<li>内存缓存:在Glide中默认是LruResourceCache。当然你也可以自定义;</li>
<li>为何要两级内存缓存(loadFromActiveResources)。个人理解是一级缓存采用LRU算法进行缓存,并不能保证全部能命中,添加二级缓存提高命中率之用;</li>
<li>EngineJob和DecodeJob各自职责:EngineJob充当了管理和调度者,主要负责加载和各类回调通知;DecodeJob是真正干活的劳动者,这个类实现了Runnable接口。</li>
</ol>
</blockquote>
<p>下面来看看DecodeJob是如何执行的:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line">private void runWrapped() {</span><br><span class="line"> switch (runReason) {</span><br><span class="line"> case INITIALIZE:</span><br><span class="line"> // 初始化 获取下一个阶段状态</span><br><span class="line"> stage = getNextStage(Stage.INITIALIZE);</span><br><span class="line"> currentGenerator = getNextGenerator();</span><br><span class="line"> // 运行</span><br><span class="line"> runGenerators();</span><br><span class="line"> break;</span><br><span class="line"> case SWITCH_TO_SOURCE_SERVICE:</span><br><span class="line"> runGenerators();</span><br><span class="line"> break;</span><br><span class="line"> case DECODE_DATA:</span><br><span class="line"> decodeFromRetrievedData();</span><br><span class="line"> break;</span><br><span class="line"> default:</span><br><span class="line"> throw new IllegalStateException("Unrecognized run reason: " + runReason);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line">// 这里的阶段策略首先是从resource中寻找,然后再是data,,再是source</span><br><span class="line">private Stage getNextStage(Stage current) {</span><br><span class="line"> switch (current) {</span><br><span class="line"> case INITIALIZE: </span><br><span class="line"> // 根据定义的缓存策略来回去下一个状态</span><br><span class="line"> // 缓存策略来之于RequestBuilder的requestOptions域</span><br><span class="line"> // 如果你有自定义的策略,可以调用RequestBuilder.apply方法即可</span><br><span class="line"> // 详细的可用缓存策略请参看DiskCacheStrategy.java</span><br><span class="line"> return diskCacheStrategy.decodeCachedResource()</span><br><span class="line"> ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);</span><br><span class="line"> case RESOURCE_CACHE:</span><br><span class="line"> return diskCacheStrategy.decodeCachedData()</span><br><span class="line"> ? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);</span><br><span class="line"> case DATA_CACHE:</span><br><span class="line"> return Stage.SOURCE;</span><br><span class="line"> case SOURCE:</span><br><span class="line"> case FINISHED:</span><br><span class="line"> return Stage.FINISHED;</span><br><span class="line"> default:</span><br><span class="line"> throw new IllegalArgumentException("Unrecognized stage: " + current);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">// 根据Stage找到数据抓取生成器。</span><br><span class="line">private DataFetcherGenerator getNextGenerator() {</span><br><span class="line"> switch (stage) {</span><br><span class="line"> case RESOURCE_CACHE:</span><br><span class="line"> // 产生含有降低采样/转换资源数据缓存文件的DataFetcher。</span><br><span class="line"> return new ResourceCacheGenerator(decodeHelper, this);</span><br><span class="line"> case DATA_CACHE:</span><br><span class="line"> // 产生包含原始未修改的源数据缓存文件的DataFetcher。</span><br><span class="line"> return new DataCacheGenerator(decodeHelper, this);</span><br><span class="line"> case SOURCE:</span><br><span class="line"> // 生成使用注册的ModelLoader和加载时提供的Model获取源数据规定的DataFetcher。</span><br><span class="line"> // 根据不同的磁盘缓存策略,源数据可首先被写入到磁盘,然后从缓存文件中加载,而不是直接返回。</span><br><span class="line"> return new SourceGenerator(decodeHelper, this);</span><br><span class="line"> case FINISHED:</span><br><span class="line"> return null;</span><br><span class="line"> default:</span><br><span class="line"> throw new IllegalStateException("Unrecognized stage: " + stage);</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p>
<p>经过很多流程,最后来到了发起实际请求的地方SourceGenerator.startNext()方法:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"> public boolean startNext() {</span><br><span class="line"> if (dataToCache != null) {</span><br><span class="line"> Object data = dataToCache;</span><br><span class="line"> dataToCache = null;</span><br><span class="line"> cacheData(data);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {</span><br><span class="line"> return true;</span><br><span class="line"> }</span><br><span class="line"> sourceCacheGenerator = null;</span><br><span class="line"></span><br><span class="line"> loadData = null;</span><br><span class="line"> boolean started = false;</span><br><span class="line">// 查找ModelLoader</span><br><span class="line"> while (!started && hasNextModelLoader()) {</span><br><span class="line"> loadData = helper.getLoadData().get(loadDataListIndex++);</span><br><span class="line"> if (loadData != null</span><br><span class="line"> && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())</span><br><span class="line"> || helper.hasLoadPath(loadData.fetcher.getDataClass()))) {</span><br><span class="line"> started = true;</span><br><span class="line"> 根据model的fetcher加载数据</span><br><span class="line"> loadData.fetcher.loadData(helper.getPriority(), this);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return started;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p>
<p>这里的Model必须是实现了GlideModule接口的,fetcher是实现了DataFetcher接口。有兴趣同学可以继续看一下integration中的okhttp和volley工程。Glide主要采用了这两种网络libray来下载图片。</p>
<p><strong>4.2.2 数据下载完成后的缓存处理SourceGenerator.onDataReady</strong><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">public void onDataReady(Object data) {</span><br><span class="line"> DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();</span><br><span class="line"> if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {</span><br><span class="line"> dataToCache = data;</span><br><span class="line"> // We might be being called back on someone else's thread. Before doing anything, we should</span><br><span class="line"> // reschedule to get back onto Glide's thread.</span><br><span class="line"> cb.reschedule();</span><br><span class="line"> } else {</span><br><span class="line"> cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,</span><br><span class="line"> loadData.fetcher.getDataSource(), originalKey);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>有些小伙伴可能看不太明白为什么就一个dataToCache = data就完了…其实cb.reschedule()很重要,这里的cb就是DecodeJob.reschedule():<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">public void reschedule() {</span><br><span class="line"> runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;</span><br><span class="line"> callback.reschedule(this);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p>
<p>这里又有一个Callback,继续追踪,这里的Callback接口是定义在DecodeJob内的,而实现是在外部的Engine中(这里会用线程池重新启动当前job,那为什么要这样做呢?源码中的解释是为了不同线程的切换,因为下载都是借用第三方网络库,而实际的编解码是在Glide自定义的线程池中进行的):<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">public void reschedule(DecodeJob<?> job) {</span><br><span class="line"> if (isCancelled) {</span><br><span class="line"> MAIN_THREAD_HANDLER.obtainMessage(MSG_CANCELLED, this).sendToTarget();</span><br><span class="line"> } else {</span><br><span class="line"> sourceExecutor.execute(job);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>接下来继续DecodeJob.runWrapped()方法。这个时候的runReason是SWITCH_TO_SOURCE_SERVICE,因此直接执行runGenerators(),这里继续执行SourceGenerator.startNext()方法,值得注意的dataToCache域,因为上一次执行的时候是下载,因此再次执行的时候内存缓存已经存在,因此直接缓存数据cacheData(data):<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">private void cacheData(Object dataToCache) {</span><br><span class="line"> long startTime = LogTime.getLogTime();</span><br><span class="line"> try {</span><br><span class="line">// 根据不同的数据获取注册的不同Encoder</span><br><span class="line"> Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);</span><br><span class="line"> DataCacheWriter<Object> writer =</span><br><span class="line"> new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());</span><br><span class="line"> originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());</span><br><span class="line"> // 这里的DiskCache实现是Engine中LazyDiskCacheProvider提供的DiskCacheAdapter。</span><br><span class="line"> helper.getDiskCache().put(originalKey, writer);</span><br><span class="line"> if (Log.isLoggable(TAG, Log.VERBOSE)) {</span><br><span class="line"> Log.v(TAG, "Finished encoding source to cache"</span><br><span class="line"> + ", key: " + originalKey</span><br><span class="line"> + ", data: " + dataToCache</span><br><span class="line"> + ", encoder: " + encoder</span><br><span class="line"> + ", duration: " + LogTime.getElapsedMillis(startTime));</span><br><span class="line"> }</span><br><span class="line"> } finally {</span><br><span class="line"> loadData.fetcher.cleanup();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">// 创建针对缓存的Generator</span><br><span class="line"> sourceCacheGenerator =</span><br><span class="line"> new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p>
<p>继续回到SourceGenerator.startNext()方法,这个时候已经有了sourceCacheGenerator,那么直接执行DataCacheGenerator.startNext()方法:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">public boolean startNext() {</span><br><span class="line"> while (modelLoaders == null || !hasNextModelLoader()) {</span><br><span class="line"> sourceIdIndex++;</span><br><span class="line"> if (sourceIdIndex >= cacheKeys.size()) {</span><br><span class="line"> return false;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> Key sourceId = cacheKeys.get(sourceIdIndex);</span><br><span class="line"> Key originalKey = new DataCacheKey(sourceId, helper.getSignature());</span><br><span class="line"> cacheFile = helper.getDiskCache().get(originalKey);</span><br><span class="line"> if (cacheFile != null) {</span><br><span class="line"> this.sourceKey = sourceId;</span><br><span class="line"> modelLoaders = helper.getModelLoaders(cacheFile);</span><br><span class="line"> modelLoaderIndex = 0;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> loadData = null;</span><br><span class="line"> boolean started = false;</span><br><span class="line"> // 这里会通过model寻找注册过的ModelLoader</span><br><span class="line"> while (!started && hasNextModelLoader()) {</span><br><span class="line"> ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);</span><br><span class="line"> loadData =</span><br><span class="line"> modelLoader.buildLoadData(cacheFile, helper.getWidth(), helper.getHeight(),</span><br><span class="line"> helper.getOptions());</span><br><span class="line"> // 通过FileLoader继续加载数据</span><br><span class="line"> if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {</span><br><span class="line"> started = true;</span><br><span class="line"> loadData.fetcher.loadData(helper.getPriority(), this);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return started;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p>
<p>这里的ModelLoader跟之前提到过的Register的模块加载器(ModelLoader)对应是modelLoaderRegistry域,具体执行的操作是Registry.getModelLoaders(…)方法如下:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">public <Model> List<ModelLoader<Model, ?>> getModelLoaders(Model model) {</span><br><span class="line"> List<ModelLoader<Model, ?>> result = modelLoaderRegistry.getModelLoaders(model);</span><br><span class="line"> if (result.isEmpty()) {</span><br><span class="line"> throw new NoModelLoaderAvailableException(model);</span><br><span class="line"> }</span><br><span class="line"> return result;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p>
<p>继续回到DataCacheGenerator.startNext()方法,找到了ModelLoader,这里笔者跟踪到的是FileLoader类(FileFetcher.loadData(…)方法):<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">public void loadData(Priority priority, DataCallback<? super Data> callback) {</span><br><span class="line"> // 读取文件数据</span><br><span class="line"> try {</span><br><span class="line"> data = opener.open(file);</span><br><span class="line"> } catch (FileNotFoundException e) {</span><br><span class="line"> if (Log.isLoggable(TAG, Log.DEBUG)) {</span><br><span class="line"> Log.d(TAG, "Failed to open file", e);</span><br><span class="line"> }</span><br><span class="line"> //失败</span><br><span class="line"> callback.onLoadFailed(e);</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"> // 成功</span><br><span class="line"> callback.onDataReady(data);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p>
<p><strong>4.2.3 装载流程</strong><br>回调通知这里就不打算多讲了,主要线路如下:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">-->DataCacheGenerator.onDataReady</span><br><span class="line"> -->SourceGenerator.onDataFetcherReady</span><br><span class="line"> -->DecodeJob.onDataFetcherReady</span><br><span class="line"> -->DecodeJob.decodeFromRetrievedData</span><br><span class="line"> -->DecodeJob.notifyEncodeAndRelease</span><br><span class="line"> -->DecodeJob.notifyComplete</span><br><span class="line"> -->EngineJob.onResourceReady</span><br></pre></td></tr></table></figure></p>
<p>Debug流程图:<br><img src="http://frodoking.github.io/img/glide/Glide_Decode_Exe.png" alt="装载流程Debug流程图"><br>需要说明的就是在EngineJob中有一个Handler叫MAIN_THREAD_HANDLER。为了实现在主UI中装载资源的作用,ok继续上边的流程:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">-->EngineJob.handleResultOnMainThread</span><br><span class="line"> -->SingleRequest.onResourceReady</span><br><span class="line"> -->ImageViewTarget.onResourceReady</span><br><span class="line"> -->ImageViewTarget.setResource</span><br><span class="line"> -->ImageView.setImageDrawable/ImageView.setImageBitmap</span><br></pre></td></tr></table></figure></p>
<p>Debug流程图2:<br><img src="http://frodoking.github.io/img/glide/Glide_Response_Exe.png" alt="装载流程Debug流程图2"><br>数据的装载过程中有一个很重要的步骤就是decode,这个操作发生在DecodeJob.decodeFromRetrievedData的时候,继续看代码:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">private void decodeFromRetrievedData() {</span><br><span class="line"> if (Log.isLoggable(TAG, Log.VERBOSE)) {</span><br><span class="line"> logWithTimeAndKey("Retrieved data", startFetchTime,</span><br><span class="line"> "data: " + currentData</span><br><span class="line"> + ", cache key: " + currentSourceKey</span><br><span class="line"> + ", fetcher: " + currentFetcher);</span><br><span class="line"> }</span><br><span class="line"> Resource<R> resource = null;</span><br><span class="line"> try {</span><br><span class="line"> resource = decodeFromData(currentFetcher, currentData, currentDataSource);</span><br><span class="line"> } catch (GlideException e) {</span><br><span class="line"> e.setLoggingDetails(currentAttemptingKey, currentDataSource);</span><br><span class="line"> exceptions.add(e);</span><br><span class="line"> }</span><br><span class="line"> if (resource != null) {</span><br><span class="line"> notifyEncodeAndRelease(resource, currentDataSource);</span><br><span class="line"> } else {</span><br><span class="line"> runGenerators();</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p>
<p>这中间发生了很多转换主要流程:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">-->DecodeJob.decodeFromData</span><br><span class="line">-->DecodeJob.decodeFromFetcher</span><br><span class="line">-->DecodeJob.runLoadPath</span><br><span class="line"> -->LoadPath.load</span><br><span class="line"> -->LoadPath.loadWithExceptionList</span><br><span class="line"> -->LoadPath.decode</span><br><span class="line"> -->LoadPath.decodeResource</span><br><span class="line"> -->LoadPath.decodeResourceWithList</span><br><span class="line"> -->ResourceDecoder.handles</span><br><span class="line"> -->ResourceDecoder.decode</span><br></pre></td></tr></table></figure></p>
<p>这里讲到了decode,那么encode发生在什么时候呢?直接通过Encoder接口调用发现,在数据缓存的时候才会触发编码。具体调用在DiskLruCacheWrapper和DataCacheWriter中。一些值得参考的写法例如BitmapEncoder对Bitmap的压缩处理。</p>
<h2 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h2><p>最近看开源库Glide关注度一直比较高,因此打算一探究竟。 由于时间比较紧,因此一些应该有的时序图没有画,这里也只能简单用箭头代替。不过个人认为整体执行流程已经表达清楚了。</p>
<blockquote>
<ol>
<li>总体来说代码写的挺漂亮的,单从使用者角度来说入手是比较容易的。</li>
<li>源码使用了大量的工厂方法来创建对象,就像String.valueof(…)方法一样,这也体现编码的优雅。</li>
<li>不过想要对这个库进行改造,可能并非易事,笔者在跟踪代码的过程中发现很多地方有Callback这样的接口,来来回回查找几次很容易就晕头转向了。。。</li>
<li>另外一个感觉难受的地方就是构造方法带入参数太多,就拿SingleRequest来说就是12个构造参数。</li>
<li>单例的使用感觉还是有些模糊,就比如GlideContext,有些时候通过Glide.get(context).getGlideContext()获取,而有些类中采用构造传入。个人觉得既然让Glide作为单例,那么还这样传入参数是不是有点多余?代码的编写都是可以折中考虑,不过如果整个项目拟定好了一个规则的话,我想最好还是遵循它。另外再吐槽一下单例,很多开发人员喜欢用单例,如果你是有代码洁癖的开发者,那么你肯定很讨厌这样,单例很容易造成代码的散落和结构不清晰。</li>
</ol>
</blockquote>
<h2 id="思考"><a href="#思考" class="headerlink" title="思考"></a>思考</h2><p>源码的解析只是把最重要的加载流程走了一遍,有一些比较细节的地方没有关注,如果你有需要,可以自己跟着这个主线debug一下就能查找到。</p>
<blockquote>
<ol>
<li>为何要使用额外的无界面的Fragment?</li>
<li>如果开发者要使用这个libray作为图片加载库,而且项目本身对App的内存占用和Size都是有要求的话,那么Register是否有过重的嫌疑?</li>
</ol>
</blockquote>
<h2 id="功能介绍"><a href="#功能介绍" class="headerlink" title="功能介绍"></a>功能介绍</h2><p>使用文章介绍以及和Picasso的对比分析请参考<a href="http://inthecheesefactory.com/blog/get-to-know-glide-recommended-by-google/en">Introduction to Glide, Image Loader Library for Android, recommended by Google</a></p>
<p>由于这篇文章使用glide的老版本,因此有些使用方法可能不太一致了。<br>本文基于github上Glide最新代码4.0.0版本做解析。<br>最基本的使用方式如下:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">Glide.with(this)</span><br><span class="line"> .asDrawable()</span><br><span class="line"> .load("http://i6.topit.me/6/5d/45/1131907198420455d6o.jpg")</span><br><span class="line"> .apply(fitCenterTransform(this))</span><br><span class="line"> .apply(placeholderOf(R.drawable.skyblue_logo_wechatfavorite_checked))</span><br><span class="line"> .into(imageView);</span><br></pre></td></tr></table></figure></p>
<p>Glide使用了现在非常流行的流氏编码方式,方便了开发者的使用,简明、扼要。<br>接下来主要对上面这一段流氏操作做拆分。<br>
App项目架构解决方案
http://frodoking.github.io/2015/09/15/android-app-framework/
2015-09-15T08:54:54.000Z
2016-03-09T10:09:45.007Z
<p>App-Architecture是一个关于移动应用一整套架构的解决方案开源项目。主要目的是整合流行开发模式结合自己本人的工作经验形成的一整套App快速开发解决方案。本套解决方案的app工程和simple工程主要基于Android实现。framework工程实现了主要架构,主要目的是抛开平台相关性。</p>
<a id="more"></a>
<p>项目主要包括两大部分</p>
<h2 id="Framework层"><a href="#Framework层" class="headerlink" title="Framework层"></a>Framework层</h2><p>依托以java级别,主要提供底层API框架接口,提供一种编程思想同时从平台中剥离出来</p>
<ol>
<li>CacheSystem 缓存系统</li>
<li>Configuration 基础应用配置</li>
<li>Context 接管App层级上下文</li>
<li>NetworkInteractor 网络模块,主要负责网络检测以及网络请求</li>
<li>FileSystem 文件系统</li>
<li>Database 数据库</li>
<li>Theme 主题</li>
<li>Scene 场景功能</li>
<li>ModelFactory 业务工厂(实现IModel功能的所有业务操作类)</li>
<li>PluginManager 插件化系统,主要对可扩展类的考虑。在一些特殊的系统中,可能存在ChildSystem级别的系统功能。需要继承PluginChildSystem来实现</li>
<li>LogCollector 日志收集系统,针对所有日志做处理(开关,打印,本地保持,上传server等功能)</li>
</ol>
<h2 id="App层"><a href="#App层" class="headerlink" title="App层"></a>App层</h2><p>依赖于Framework Library。实现基于Android平台下的一系列接口</p>
<ol>
<li>主要接管Activity和Fragment中的架构,采用了MVVM的方式来解放UI(最近受到IOS的MVC架构所启发,Android的View概念被弱化,Activity和Fragment被强化导致很多同学认为这两者就是UI上的事情。其实不是这样,Fragment应该类似于IOS中的UIViewController才对。因此本框架通过UIView来强化Android的UI概念。用Fragment来做为UIViewController。)</li>
<li>App全局只有一个入口启动MainActivity同时也是唯一的一个Activity。MainActivity继承了FragmentContainerActivity,因此他是Fragment容器</li>
<li>所有页面都是Fragment实现,包括启动页面。主要利用support.v4包的FragmentManager来管理整个Fragment堆栈实现页面切换功能</li>
<li>页面请求都采用线程池执行Task的方式来完成,回调使用了Rxjava的订阅/消费的观察者模式完成</li>
</ol>
<h2 id="架构图"><a href="#架构图" class="headerlink" title="架构图"></a>架构图</h2><p><img src="http://frodoking.github.io/img/App-Architecture.png" alt="架构图"></p>
<h2 id="Simple工程"><a href="#Simple工程" class="headerlink" title="Simple工程"></a>Simple工程</h2><ol>
<li>simplecloudentity是服务器定义的基础数据结构</li>
<li>simpleentity是本地App需要的数据结构。这样定义的目的是为了App数据结构的组织不完全依赖Server</li>
<li>Simple工程数据主要参考<a href="https://github.com/OpenSource-Frodo/philm" target="_blank" rel="external">philm</a>工程</li>
</ol>
<h2 id="注"><a href="#注" class="headerlink" title="注"></a>注</h2><p>由于整个工程项目开发是一个很耗时的迭代过程,所以有考虑不全面的地方希望引用同学继续补充(贡献分支<a href="https://github.com/frodoking/GradleAndroid-App-Framework/tree/branch_contributors" target="_blank" rel="external">branch_contributors</a>)。在补充过程中尽量通知到作者本人。希望有兴趣的同学加入进来,把这个工程完善得更好。</p>
<p><strong>依赖注入和事件总线思想在本项目里是排斥的,原因很简单,不仅仅从性能方面的考虑,同时:</strong></p>
<ol>
<li>依赖让代码指向混乱同时也给开发者造成结构的不清晰</li>
<li>事件总线思想很容易造成滥用的现象,就像广播一样,没有目的的注册和广播很容易导致内存泄露发生</li>
</ol>
<h2 id="第三方依赖"><a href="#第三方依赖" class="headerlink" title="第三方依赖"></a>第三方依赖</h2><ol>
<li>基础库guava</li>
<li>网络库okhttp、retrofit</li>
<li>图片库picasso(后期考虑glide)</li>
<li>事件传递机制的Rxjava</li>
<li>内存泄露检测库leakcanary</li>
</ol>
<h2 id="关于作者-frodoking"><a href="#关于作者-frodoking" class="headerlink" title="关于作者(frodoking)"></a>关于作者(frodoking)</h2><ul>
<li>Email: awangyun8@gmail.com</li>
<li>个人技术Blog:<a href="http://frodoking.github.io/">http://frodoking.github.io/</a></li>
</ul>
<p>App-Architecture是一个关于移动应用一整套架构的解决方案开源项目。主要目的是整合流行开发模式结合自己本人的工作经验形成的一整套App快速开发解决方案。本套解决方案的app工程和simple工程主要基于Android实现。framework工程实现了主要架构,主要目的是抛开平台相关性。</p>
ReactiveX--响应式编程
http://frodoking.github.io/2015/09/08/reactivex/
2015-09-08T06:47:15.000Z
2015-09-14T04:14:02.023Z
<p>在许多软件编程任务中,你或多或少期待你的指令将会按照你已经写好的顺序,依次增量执行和完成。但在ReactiveX,很多指令可以通过“观察者”并行执行,其结果将以任意顺序被捕获。你定义了一种“可观察的形式“的检索和转换数据机制而不是调用方法,然后订阅观察者给它,每当之前定义好的机制已经准备好了,这些机制就会触发常设的哨兵去捕获并反馈结果。</p>
<p>这种方法的优点是,当你有一大堆的任务是不相互依赖,你就可以同时执行他们,而不是等待每一个来启动下一个前完成,这样你的整个任务包只需要花最长的任务时间。</p>
<p>有很多属于来描述异步编程和设计模型。本文将使用下列术语:一个观察者(observer)订阅可观察到的(Observable)。可观察到的(Observable)通过调用观察者的方法来发射项目或通知给它的所有观察者(observer)。</p>
<p>观察者有些时候也被称作是订阅者,观看者,响应者。因此这样的模式通常就叫做响应模式。</p>
<p>在很多存在UI操作的地方,UI上的操作不应该等待耗时执行程序的完成而阻塞。在一般编程模式下,都会采用异步线程+回调的方式完成这样的交互操作。不过当回调层次越来越多的时候,那代码可维护性将变得很麻烦。因此ReactiveX最出色的地方就是将多个操作过程按照自定义顺序组合完成最终结果,在每次一的操作中只需要关心业务逻辑本身的执行即可。<br><a id="more"></a><br>这些话描述起来比较生硬,一些简单的使用介绍可以见如下站点:</p>
<blockquote>
<ul>
<li><a href="http://blog.csdn.net/lzyzsd/article/details/41833541" target="_blank" rel="external">深入浅出RxJava 一、基础篇</a>;</li>
<li><a href="http://blog.csdn.net/lzyzsd/article/details/44094895" target="_blank" rel="external">深入浅出RxJava 二、操作符</a>;</li>
<li><a href="http://blog.csdn.net/lzyzsd/article/details/44891933" target="_blank" rel="external">深入浅出RxJava 三、响应式的好处</a>;</li>
<li><a href="http://blog.csdn.net/lzyzsd/article/details/45033611" target="_blank" rel="external">深入浅出RxJava 四、在Android中使用响应式编程</a>;</li>
</ul>
</blockquote>
<p>本文主要针对实现过程做梳理和剖析,因此基础部分不在做过多阐述。</p>
<p>OK,接下来讲讲ReactiveX中几个比较重要的概念</p>
<hr style="border:1px dashed gray; height:1px">
<p><strong><center>Observable 可观察的对象</center></strong></p>
<hr style="border:1px dashed gray; height:1px">
<p>在ReactiveX中,一个观察者observer订阅到一个可观察的对象Observable。无论是某一个还是多个Observable执行,这些观察者都会做出响应。这样的模式有利于并发操作,因为这样不需要去等待Observable去广播,但是它创建了一个观察者形式的哨兵,此哨兵在今后的任何时间里随时准备做适当的响应,Observable也会做出这样的响应。</p>
<p>下面这这张图很好的说明了什么是Observables和observers,以及他们之间的转换关系<br><img src="http://frodoking.github.io/img/rxjava/usecase/Observable.png" alt="Observables与Observables转换图"></p>
<p><strong>关于Observers的创建</strong><br>下面是采用了伪代码来展示Observers的实现过程:</p>
<ul>
<li>同步方式:</li>
</ul>
<ol>
<li>调用一个方法</li>
<li>用一个变量存储方法返回值</li>
<li>使用这个变量作为一个新的值做其他事情</li>
</ol>
<p>例如:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">// 写一个回调方法,并且指定到 `returnVal`</span><br><span class="line">returnVal = someMethod(itsParameters);</span><br><span class="line">// 使用returnVal做新的事情</span><br></pre></td></tr></table></figure></p>
<ul>
<li>异步方式:</li>
</ul>
<ol>
<li>定义一个方法,此方法是做一些事情并带有来之于异步调用的返回值;这个方法也是observer的一部分</li>
<li>定义异步调用自身作为一个Observable</li>
<li>通过订阅的方式连接observer到Observable(这个过程也是初始化Observable的actions)</li>
<li>执行你的业务;每当调用返回,observer的方法将会操作它自身返回值,这里的返回值是通过Observable广播</li>
</ol>
<p>例如:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">// 定义但是不执行, 订阅者的onNext方法</span><br><span class="line">// (这个例子中observer只有一个onNext方法)</span><br><span class="line">def myOnNext = { it -> do something useful with it };</span><br><span class="line">// 定义但是不执行的Observable</span><br><span class="line">def myObservable = someObservable(itsParameters);</span><br><span class="line">// 发起订阅并执行</span><br><span class="line">myObservable.subscribe(myOnNext);</span><br><span class="line">// 继续执行相应的业务逻辑</span><br></pre></td></tr></table></figure></p>
<p><strong>onNext, onCompleted, and onError</strong><br>订阅方法就是展示了observer如何连接到Observable。oberver实现了下列方法的一些子集:</p>
<ul>
<li>onNext<br>每当Observable广播数据时将会调用该方法。这个方法将会被作为Observable的一个广播项目参数被发送</li>
<li>onError<br>Observable调用此方法表示它内部已经发生异常数据或者发生一些其他错误。这样停止观察,并且也不会做将来的调用onNext或者onCompleted。该onError方法作为它的参数来指示了错误的原因。</li>
<li>onCompleted<br>Observable在已经调用了onNext方法作为最后的时间,如果没有遇到任何错误,那么该方法将会被调用</li>
</ul>
<p>通过Observable的定义,它可能调用onNext零次或者很多次,并且接下来的调用可能是onCompleted或者onError方法,但是不是同时调用,这都是最终才会被调用。在调用过程中,onNext通常称作任务的执行,而onCompleted或者onError被称作任务的结果通知</p>
<p>下面是一个subscribe调用例子:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">def myOnNext = { item -> /* 任务执行 */ };</span><br><span class="line">def myError = { throwable -> /* 失败时的响应 */ };</span><br><span class="line">def myComplete = { /* 成功后的响应 */ };</span><br><span class="line">def myObservable = someMethod(itsParameters);</span><br><span class="line">myObservable.subscribe(myOnNext, myError, myComplete);</span><br><span class="line">// 继续执行相应的业务逻辑</span><br></pre></td></tr></table></figure></p>
<p>更多相关信息也可以参考</p>
<ul>
<li><a href="http://www.introtorx.com/Content/v1.0.10621.0/02_KeyTypes.html#IObserver" target="_blank" rel="external">Introduction to Rx: IObserver</a></li>
</ul>
<p><strong>Unsubscribing取消订阅</strong><br>在ReactiveX实现中,有一个特殊的observer接口是Subscriber,这个接口中有一个unsubscribe方法。当你调用此方法,表示订阅者不在对当前任何被订阅的Observables。如果没有其他observer,那么当前的Observables就会选择停止对新数据的广播。</p>
<p>退订结果将会通过应用于哪些之前观察者订阅了的Observable的操作连来联级返回。这个操作将会导致整个连接链上的每一个环节都停止发送动作。这个过程虽然不能保证立即发生,但是,在没有观察者仍然观察这些回调数据的时候,Observable是有可能试图去发送或者广播数据的。</p>
<p><strong>Observables的“冷”与“热”</strong><br>Observable具体在什么时候发送他的数据队列?这依赖于Observable。一个“热”的Observable可能随着它的创建就会立即发送回调数据,哪些之后订阅到Observable的任何observer也可以立即发起对观察队列的监听。另一方面,一个“冷”Observable就会等待,直到一个观察者observer订阅它之前开始发送动作,所以这样就能保证观察者从一开始就能看到整个序列。</p>
<p><strong>Composition</strong><br>Observables和observers只是ReactiveX的一个开始。通过对标准的观察者模式的稍微扩展,更好的去处理了事件序列,而不是单个回调。<br>真正的核心就是“无扩展”,操作符允许你去转换,合并,操作以及同发送序列被Observables一起发送。也就是说操作符和操作结果都是可发送,可传递的。<br>ReactiveX的操作符允许你以声明的方式一起构成异步序列,同时还保持着回调函数的高效率, 但没有嵌套的回调处理程序通常与异步系统相关的问题。</p>
<p>这里罗列一下Observable中定义的一些主要功能点:</p>
<ol>
<li>Observable的创建<br>Create, Defer, Empty/Never/Throw, From, Interval, Just, Range, Repeat, Start, and Timer</li>
<li>Observable发送项目的转换<br>Buffer, FlatMap, GroupBy, Map, Scan, and Window</li>
<li>Observable过滤<br>Debounce, Distinct, ElementAt, Filter, First, IgnoreElements, Last, Sample, Skip, SkipLast, Take, and TakeLast</li>
<li>Observable合并<br>And/Then/When, CombineLatest, Join, Merge, StartWith, Switch, and Zip</li>
<li>错误处理操作符<br>Catch and Retry</li>
<li>实用工具操作符<br>Delay, Do, Materialize/Dematerialize, ObserveOn, Serialize, Subscribe, SubscribeOn, TimeInterval, Timeout, Timestamp, and Using</li>
<li>条件和布尔运算符<br>All, Amb, Contains, DefaultIfEmpty, SequenceEqual, SkipUntil, SkipWhile, TakeUntil, and TakeWhile</li>
<li>数学和聚集操作符<br>Average, Concat, Count, Max, Min, Reduce, and Sum</li>
<li>转换操作符<br>To</li>
<li>可连接到Observable的操作符<br>Connect, Publish, RefCount, and Replay</li>
</ol>
<p>下面来详细说一下ReactiveX的操作符知识 </p>
<hr style="border:1px dashed gray; height:1px">
<p><strong><center>Operators 操作符</center></strong></p>
<hr style="border:1px dashed gray; height:1px">
<p>ReactiveX有一些列的操作集合,但是在不同的语言上表现都是大相径庭的。在一些特殊的语言可能还会有特定的定义操作符。</p>
<p><strong> 链式操作符 </strong><br>大多数操作符操作一个Observable并且返回一个Observable。这样允许开发人员以一个链式的方式一个接一个的执行操作符。在可修改的链式中每一个操作结果Observable都是来之于上一个操作,这里的操作也就是定义的operator。<br>这里有一些类似于构造器Builder模式,该模式描述了一个含有一系方法的特定类通过操作方法来操作具有相同功能的类的每一项。这个模式也允许你以类似的方式去链式操作方法。在Builder模式中,操作方法出现的顺序在链式中可能不是那么重要,但是在Observable中的操作符顺序就很重要。</p>
<p>Observable操作符链不会依赖于原来的Observable去操作原始的链,但他们会反过来操作,每一个在Observable上的正在操作的operator都是上一个操作立即产生的。</p>
<p>我们也可以自己选择自定义操作符,具体如何实现可以参考<a href="http://reactivex.io/documentation/implement-operator.html" target="_blank" rel="external">Implementing Your Own Operators</a>。</p>
<p>上面在Observable中罗列过简单的功能点,下面罗列一下按照类别划分的操作符以及各自的功能:</p>
<p><strong> 创建Observables </strong><br>创建新的Observables的操作符</p>
<blockquote>
<ul>
<li>Create——通过调用observer方法编程从头创建一个Observable</li>
<li>Defer——不立即创建Observable,直到observer触发订阅动作。此方法为每一个observer创建一个新的Observable</li>
<li>Empty/Never/Throw——为非常精确和有限的行为创建Observables</li>
<li>From——将其他对象或数据结构转换成一个Observable</li>
<li>Interval——创建一个具有发出一个整数序列间隔为一个特定的时间间隔的Observable</li>
<li>Just——把一个对象或一组对象转换成一个Observable,同时该Observable发送这样的对象</li>
<li>Range——创建一个Observable,发送一系列连续的整数</li>
<li>Repeat——创建一个Observable,发送一个特定的项目或项目重复序列</li>
<li>Start——创建一个Observable,发送一个函数的返回值</li>
<li>Timer——创建一个Observable,在一个给定的一段时间延迟后发送一个对象或者项目</li>
</ul>
</blockquote>
<p><strong> 转换Observables </strong><br>转换被一个Observable发送的项目的操作符</p>
<blockquote>
<ul>
<li>Buffer——定期收集从Observable中发出的数据到集合中,并且发送这些集合而不是发送一次</li>
<li>FlatMap——将一个Observable发送的数据或者项目转换到Observables中,然后把这些数据压缩成一个单个的Observable</li>
<li>GroupBy——拆分一个Observable成多个Observable组,并且每个组发送的数据会租床成一个不同的发送数据组,当然这些发送数据时来至于原始的Observable。这些分组都是通过划分key来实现</li>
<li>Map——转换一个Observable发送的每个数据或者项目映射到一个函数上</li>
<li>Scan——应用一个函数给一个Observable发送出来的每一想数据,并且是按照顺序发送每个连续值</li>
<li>Window——定期细分条目从一个Observable到Observable的windows,并且发送结果是这些windows而不是一次发送原始的数据或者项目</li>
</ul>
</blockquote>
<p><strong> 过滤Observables </strong><br>过滤被Observable发送的项目的操作符</p>
<blockquote>
<ul>
<li>Debounce——如果Observable在一个特定时间间隔过去后没有发送其他数据或者项目,那么它只发送一个数据或者项目</li>
<li>Distinct——该Observable不可以发送重复的数据</li>
<li>ElementAt——只发送被Observable发送的某一个元素</li>
<li>Filter——一个Observable只发送通过来特定测试描述语的匹配项</li>
<li>First——只发出第一项,或第一项符合条件的项</li>
<li>IgnoreElements——不发送任何数据,但是必须反馈它的中断通知</li>
<li>Last——只发送最后一项</li>
<li>Sample——发出Observables周期时间间隔内最新的项</li>
<li>Skip——跳过发送前几项</li>
<li>SkipLast——跳过发送后几项</li>
<li>Take——仅仅发送前几项</li>
<li>TakeLast——仅仅发送后几项</li>
</ul>
</blockquote>
<p><strong> 合并Observables </strong><br>将多个Observables合并成单个的Observable的操作符</p>
<blockquote>
<ul>
<li>And/Then/When——通过Pattern和Plan媒介将两个或者多个Observables发送的数据或项目合并成集合</li>
<li>CombineLatest——当某一项数据由两个Observables发送时,通过一个特殊的函数来合并每一个Observable发送的项,并且最终发送数据是该函数的结果</li>
<li>Join——合并两个Observables发送的结果数据。其中两个Observable的结果遵循如下规则:每当一个Observable在定义的数据窗口中发送一个数据都是依据另外一个Observable发送的数据。</li>
<li>Merge——通过合并多个Observables发送的结果数据将多个Observables合并成一个</li>
<li>StartWith——在Observable源开始发送数据项目之前发送一个指定的项目序列</li>
<li>Switch——转换一个Observable,并且发送Observables到一个单个Observable,这个单个的Observable发送的项目就是转换之前的Observables最近发送的项目</li>
<li>Zip——通过特定的函数合并多个Observable的结果,并且对于每个组合都发出单独的项目数据,这些数据就是之前定义的合并函数</li>
</ul>
</blockquote>
<p><strong> 错误处理操作符 </strong><br>错误处理操作符主要用于帮助来之于一个Observable里的错误通知的恢复功能</p>
<blockquote>
<ul>
<li>Catch——从OnError方法通知中恢复持续的没有错误的序列</li>
<li>Retry——如果一个源Observable发送一个onError通知,重新订阅给它,希望它将没有错误的执行完成</li>
</ul>
</blockquote>
<p><strong> 实用工具操作符 </strong><br>一个实用的操作符工具箱</p>
<blockquote>
<ul>
<li>Delay——按照一个特定量及时的将Observable发送的结果数据向前推移</li>
<li>Do——注册一个事件去监听Observable生命周期</li>
<li>Materialize/Dematerialize——代表发送出来的项目数据或者通知,或相反过程</li>
<li>ObserveOn——指定一个observer将会观察这个Observable的调度</li>
<li>Serialize——强制一个Observable去做序列化调用</li>
<li>Subscribe——操作可观测的排放和通知</li>
<li>SubscribeOn——指定一个Observable在被订阅的时候应该使用的调度</li>
<li>TimeInterval——转换一个Observable的发送项目到另一个项目,在这些发送项之间,此项目具有指示这些发送的时间开销功能</li>
<li>Timeout——镜像源Observable,但如果某段时间过后没有任何通知发出将会发出一个错误通知</li>
<li>Timestamp——给一个Observable发送的每一个项目附加一个时间戳</li>
<li>Using——创建一个一次性的资源,这个资源就像Observable一样有相同的寿命</li>
</ul>
</blockquote>
<p><strong> 条件和布尔运算操作符 </strong><br>评估一个或者多个Observables或者被Observables发送的项目的操作符</p>
<blockquote>
<ul>
<li>All——确定发出的所有项目满足某些标准</li>
<li>Amb——给定两个或两个以上的Observable来源,从只有第一个可见发出一个项目发送所有的项目数据</li>
<li>Contains——决定是否Observable发出一个特定的项</li>
<li>DefaultIfEmpty——发送项从Observable源,或者如果Observable源没有任何发送内容,那么将会发送一个默认的项</li>
<li>SequenceEqual——确定两个Observables发出相同的序列条目</li>
<li>SkipUntil——丢弃Observable发出的项,直到第二个Observable发出一项</li>
<li>SkipWhile——丢弃Observable发出的项,直到指定的条件变成了false</li>
<li>TakeUntil——在第二个Observable发送一项或者终止之后,丢弃Observable发出的项</li>
<li>TakeWhile——在指定的条件变成了false之后,丢弃Observable发出的项</li>
</ul>
</blockquote>
<p><strong> 数学和聚集操作符 </strong></p>
<blockquote>
<ul>
<li>操作一个被Observable发送出来的一整个项目序列操作符</li>
<li>Average——计算一个Observable发送所有结果的平均值,并且发送这个值</li>
<li>Concat——发送两个或两个以上Observables没有交叉的值</li>
<li>Count——计算Observable源发出的项目数据数量,只发出这个值</li>
<li>Max——确定,发送最大值项</li>
<li>Min——确定,发送最小值项</li>
<li>Reduce——应用一个函数给一个Observable发送的项,并且发送该函数的结果</li>
<li>Sum——计算Observable发送的所有数据的求和,并且发送这个求和结果</li>
</ul>
</blockquote>
<p><strong> 转换操作符 </strong></p>
<blockquote>
<ul>
<li>To——将一个Observable转换到另一个对象或数据结构</li>
</ul>
</blockquote>
<p><strong> 可连接到Observable的操作符 </strong><br>指定Observables有更多精确控制订阅动态的操作符</p>
<blockquote>
<ul>
<li>Connect——定义一个可连接的Observable发送项目数据给它的订阅者</li>
<li>Publish——把一个普通的Observable转化为一个可连接的Observable(向下转换)</li>
<li>RefCount——把一个可连接的Observable转化成一个看起来就行一个普通的Observable(向上转换)</li>
<li>Replay——确保所有的Observables能看到所有发送的相同的项目数据序列,及时是在Observable已经开始发送后才订阅的</li>
</ul>
</blockquote>
<hr style="border:1px dashed gray; height:1px">
<p><strong><center>Single</center></strong></p>
<hr style="border:1px dashed gray; height:1px">
<p>由于Single是Observable的一个衍生变体,因此这里就不再做介绍。有兴趣的同学可以查看<a href="http://reactivex.io/documentation/single.html" target="_blank" rel="external">ReactiveX–Single文档</a></p>
<hr style="border:1px dashed gray; height:1px">
<p><strong><center>Subject</center></strong></p>
<hr style="border:1px dashed gray; height:1px">
<p>一个Subject是一种桥梁或者也可以叫做代理,一个Subject在ReactiveX的实现中既是一个observer也是一个Observable。因为它本身是一个observer,它能订阅到一个或者多个Observables中,同时它也是一个Observable,他通过重新发送项目数据,能遍历它所有的observers,同时,它也能发送新的项目数据。<br>因为一个Subject订阅到一个Observable时,这将会触发Observable开始发送他的项目数据(当然这里的操作必须是定义Observable为“冷的”)。</p>
<p>这里还有一些其他介绍可以参考:</p>
<blockquote>
<ul>
<li><a href="http://davesexton.com/blog/post/To-Use-Subject-Or-Not-To-Use-Subject.aspx" target="_blank" rel="external">To Use or Not to Use Subject</a> from Dave Sexton’s blog</li>
<li><a href="http://www.introtorx.com/Content/v1.0.10621.0/02_KeyTypes.html#Subject" target="_blank" rel="external">Introduction to Rx: Subject</a></li>
<li><a href="http://rxwiki.wikidot.com/101samples#toc44" target="_blank" rel="external">101 Rx Samples: ISubject<t> and ISubject<t1,t2></t1,t2></t></a></li>
<li><a href="http://akarnokd.blogspot.hu/2015/06/subjects-part-1.html" target="_blank" rel="external">Advanced RxJava: Subject</a> by Dávid Karnok</li>
<li><a href="http://xgrommx.github.io/rx-book/content/getting_started_with_rxjs/subjects.html" target="_blank" rel="external">Using Subjects</a> by Dennis Stoyanov</li>
</ul>
</blockquote>
<p><strong> 各种不同的Subject类型 </strong><br>这里有四种不同类型的Subject来满足于特定的使用场景。 <strong><font color="#FF0000" size="3">注意:<u>下面的示例图中每一条带有向右箭头横线都是一个单向过程,蓝色的subscribe()方法表示每一次订阅触发执行函数。每一次的订阅触发即图中蓝色箭头</u>。</font> </strong> </p>
<p><strong> AsyncSubject </strong><br><img src="http://frodoking.github.io/img/rxjava/usecase/S.AsyncSubject.png" alt="AsyncSubject执行流程图"><br>只有在源Observable完成之后,一个AsyncSubject将会发送由源Observable发送的最后一个值。(如果源Observable并没有发送任何值,那么AsyncSubject在完成的时候也不会发送任何值)<br><img src="http://frodoking.github.io/img/rxjava/usecase/S.AsyncSubject.e.png" alt="AsyncSubject执行中断流程图"><br>AsyncSubject将会发送相同的最终值给接下来的observers。但是,如果源Observable因为错误而中断,AsyncSubject并不会发送任何值,但是会传递来之于源Observable的错误通知。</p>
<p>通俗的来讲,异步的Subject在每次触发subscribe()方法发送项目的时候,只有在源Observable结束后才会发送源发送的结果。</p>
<p>更详细介绍参考</p>
<blockquote>
<ul>
<li><a href="http://www.introtorx.com/Content/v1.0.10621.0/02_KeyTypes.html#AsyncSubject" target="_blank" rel="external">Introduction to Rx: AsyncSubject</a></li>
</ul>
</blockquote>
<p><strong> BehaviorSubject </strong><br><img src="http://frodoking.github.io/img/rxjava/usecase/S.BehaviorSubject.png" alt="BehaviorSubject执行流程图"><br>当一个observer订阅到一个BehaviorSubject上时,通过发送当源Observable发送的最近项目数据,这个observer将会被触发执行。并且它会继续发送源Observable后续发送的项。<br><img src="http://frodoking.github.io/img/rxjava/usecase/S.BehaviorSubject.e.png" alt="BehaviorSubject执行中断流程图"><br>但是,如果源Observable发生错误而中断,BehaviorSubject将不会发送任何数据给随后的observers。不过,来之于源Observable的错误通知任然会传递。</p>
<p>更详细介绍参考</p>
<blockquote>
<ul>
<li><a href="http://www.introtorx.com/Content/v1.0.10621.0/02_KeyTypes.html#BehaviorSubject" target="_blank" rel="external">Introduction to Rx: BehaviorSubject</a></li>
</ul>
</blockquote>
<p><strong> PublishSubject </strong><br><img src="http://frodoking.github.io/img/rxjava/usecase/S.PublishSubject.png" alt="PublishSubject执行流程图"><br>PublishSubject的主要职责就是将源Observable发送的所有数据发送给随后一个已经订阅了的observer。<br>值得注意的就是,一个PublishSubject可能在创建的时候立即发送项目数据,不过在Subject的创建和observer订阅到这个Subject的这段时间中,一个或者多个发送项目数据可能存在丢失的风险。如果你要确保传送所有的源Observable发送项,你可以使用Observable的Create方式来构建,以便你能手动重新构建“冷的”Observable行为。或者你也可以使用ReplaySubject。<br><img src="http://frodoking.github.io/img/rxjava/usecase/S.PublishSubject.e.png" alt="PublishSubject执行中断流程图"><br>如果源Observable发生错误而中断,PublishSubject将不会发送任何项给接下来的observer。不过,来之于源Observable的错误通知任然会传递。</p>
<p><strong> ReplaySubject </strong><br><img src="http://frodoking.github.io/img/rxjava/usecase/S.ReplaySubject.png" alt="ReplaySubject执行流程图"><br>ReplaySubject发送过去源Observable发送的所有项目数据给任意的observer,不管observer在什么时候订阅。<br>一旦replay缓冲项逐渐增长超过了一个固定值后,ReplaySubject将会丢弃旧的项。或者给已经发送的数据指定一个有效时间,在失效过后就会扔掉。<br>如果你使用ReplaySubject作为一个observer,必须确保不会再多线程中调用onNext方法,因为这可能导致乱序调用,这是违反了Observable定义规则的。并且会创建一个有歧义的Subject去replay。<br>更详细介绍参考</p>
<blockquote>
<ul>
<li><a href="http://www.introtorx.com/Content/v1.0.10621.0/02_KeyTypes.html#ReplaySubject" target="_blank" rel="external">Introduction to Rx: ReplaySubject</a></li>
</ul>
</blockquote>
<hr style="border:1px dashed gray; height:1px">
<p><strong><center>Scheduler 调度</center></strong></p>
<hr style="border:1px dashed gray; height:1px">
<p>如果你想在多线程中使用Observable的联级操作链,你可以在特殊的Schedulers上去制定这些操作链去操作。<br>在ReactiveX中Observable操作符将Scheduler作为一个变量,这些操作在一个特定的Scheduler上做一些操作或者所有的工作。<br>默认情况下,你应用操作链到Observable上做一些事情的时候,这将会通知它的observers,在同一个线程中它的Subscribe方法将会被调起。根据Observable应有的操作,定义一个不同的Scheduler,SubscribeOn操作就会改变这些行为。ObserveOn操作指定一个不同的Scheduler,这个Scheduler主要用于Observable去发送通知给它自身的observers上。<br>如下图所示,SubscribeOn操作指派哪一个Observable线程将会开始操作,操作链中的所有操作都可以被调起。另一方面,在操作出现的地方,ObserveOn会影响下面的Observable将使用的线程。对于这样的原因,在Observable操作链中,你可以在多个点多次调用ObserveOn方法,这样来确保多线程上的这些操作的执行。<br><img src="http://frodoking.github.io/img/rxjava/usecase/schedulers.png" alt="Scheduler执行流程图"></p>
<h2 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h2><p>由于这篇文章大部分来至ReactiveX原始文档,加上一些的个人理解形成。有一些翻译或者个人理解会有一定的偏差,再后续会继续修正。如果阅读到这篇文章的同学发现有不妥当的地方,还请回复指出,谢谢。</p>
<p>另外,后面有时间打算针对Rxjava做一下源码上的分析。</p>
<p>在许多软件编程任务中,你或多或少期待你的指令将会按照你已经写好的顺序,依次增量执行和完成。但在ReactiveX,很多指令可以通过“观察者”并行执行,其结果将以任意顺序被捕获。你定义了一种“可观察的形式“的检索和转换数据机制而不是调用方法,然后订阅观察者给它,每当之前定义好的机制已经准备好了,这些机制就会触发常设的哨兵去捕获并反馈结果。</p>
<p>这种方法的优点是,当你有一大堆的任务是不相互依赖,你就可以同时执行他们,而不是等待每一个来启动下一个前完成,这样你的整个任务包只需要花最长的任务时间。</p>
<p>有很多属于来描述异步编程和设计模型。本文将使用下列术语:一个观察者(observer)订阅可观察到的(Observable)。可观察到的(Observable)通过调用观察者的方法来发射项目或通知给它的所有观察者(observer)。</p>
<p>观察者有些时候也被称作是订阅者,观看者,响应者。因此这样的模式通常就叫做响应模式。</p>
<p>在很多存在UI操作的地方,UI上的操作不应该等待耗时执行程序的完成而阻塞。在一般编程模式下,都会采用异步线程+回调的方式完成这样的交互操作。不过当回调层次越来越多的时候,那代码可维护性将变得很麻烦。因此ReactiveX最出色的地方就是将多个操作过程按照自定义顺序组合完成最终结果,在每次一的操作中只需要关心业务逻辑本身的执行即可。<br>
Java并发控制机制
http://frodoking.github.io/2015/07/19/java-concurrent-mechanism/
2015-07-19T02:10:06.000Z
2015-09-14T06:32:06.884Z
<p>因文章很快被人转载到一些其他网站,所以本人在此声明:<br><strong>转载请标明转载出处:<a href="http://frodoking.github.io/2015/07/19/java-concurrent-mechanism/">http://frodoking.github.io/2015/07/19/java-concurrent-mechanism/</a> </strong></p>
<p>在一般性开发中,笔者经常看到很多同学在对待java并发开发模型中只会使用一些基础的方法。比如volatile,synchronized。像Lock和atomic这类高级并发包很多人并不经常使用。我想大部分原因都是来之于对原理的不属性导致的。在繁忙的开发工作中,又有谁会很准确的把握和使用正确的并发模型呢?</p>
<p>所以最近基于这个思想,本人打算把并发控制机制这部分整理成一篇文章。既是对自己掌握知识的一个回忆,也是希望这篇讲到的类容能帮助到大部分开发者。</p>
<p>并行程序开发不可避免地要涉及多线程、多任务的协作和数据共享等问题。在JDK中,提供了多种途径实现多线程间的并发控制。比如常用的:内部锁、重入锁、读写锁和信号量。<br><a id="more"></a></p>
<h2 id="Java内存模型"><a href="#Java内存模型" class="headerlink" title="Java内存模型"></a>Java内存模型</h2><p>在java中,每一个线程有一块工作内存区,其中存放着被所有线程共享的主内存中的变量的值的拷贝。当线程执行时,它在自己的工作内存中操作这些变量。<br>为了存取一个共享的变量,一个线程通常先获取锁定并且清除它的工作内存区,这保证该共享变量从所有线程的共享内存区正确地装入到线程的工作内存区,当线程解锁时保证该工作内存区中变量的值协会到共享内存中。</p>
<p>当一个线程使用某一个变量时,不论程序是否正确地使用线程同步操作,它获取的值一定是由它本身或者其他线程存储到变量中的值。例如,如果两个线程把不同的值或者对象引用存储到同一个共享变量中,那么该变量的值要么是这个线程的,要么是那个线程的,共享变量的值不会是由两个线程的引用值组合而成。</p>
<p>一个变量时Java程序可以存取的一个地址,它不仅包括基本类型变量、引用类型变量,而且还包括数组类型变量。保存在主内存区的变量可以被所有线程共享,但是一个线程存取另一个线程的参数或者局部变量时不可能的,所以开发人员不必担心局部变量的线程安全问题。至于内存模型中线程工作内存与主内存的交互请关注<a href="http://www.cnblogs.com/nexiyi/p/java_memory_model_and_thread.html" target="_blank" rel="external">http://www.cnblogs.com/nexiyi/p/java_memory_model_and_thread.html</a>, 这里就不再做过多介绍。</p>
<h2 id="volatile变量–多线程间可见"><a href="#volatile变量–多线程间可见" class="headerlink" title="volatile变量–多线程间可见"></a>volatile变量–多线程间可见</h2><p>由于每个线程都有自己的工作内存区,因此当一个线程改变自己的工作内存中的数据时,对其他线程来说,可能是不可见的。为此,可以使用volatile关键字破事所有线程军读写内存中的变量,从而使得volatile变量在多线程间可见。<br>声明为volatile的变量可以做到如下保证:</p>
<blockquote>
<p>1、其他线程对变量的修改,可以及时反应在当前线程中;<br>2、确保当前线程对volatile变量的修改,能及时写回到共享内存中,并被其他线程所见;<br>3、使用volatile声明的变量,编译器会保证其有序性。</p>
</blockquote>
<h2 id="同步关键字synchronized"><a href="#同步关键字synchronized" class="headerlink" title="同步关键字synchronized"></a>同步关键字synchronized</h2><p>同步关键字synchronized是Java语言中最为常用的同步方法之一。在JDK早期版本中,synchronized的性能并不是太好,值适合于锁竞争不是特别激烈的场合。在JDK6中,synchronized和非公平<br>锁的差距已经缩小。更为重要的是,synchronized更为简洁明了,代码可读性和维护性比较好。<br>锁定一个对象的方法:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">public synchronized void method(){}</span><br><span class="line">``` </span><br><span class="line"></span><br><span class="line">当method()方法被调用时,调用线程首先必须获得当前对象所,若当前对象锁被其他线程持有,这调用线程会等待,犯法结束后,对象锁会被释放,以上方法等价于下面的写法:</span><br></pre></td></tr></table></figure>
<p>public void method(){<br> synchronized(this){<br> // do something …<br> }<br>}<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">其次,使用synchronized还可以构造同步块,与同步方法相比,同步块可以更为精确控制同步代码范围。一个小的同步代码非常有离与锁的快进快出,从而使系统拥有更高的吞吐量。</span><br></pre></td></tr></table></figure></p>
<pre><code>public void method(Object o){
// before
synchronized(o){
// do something ...
}
// after
}
</code></pre><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">synchronized也可以用于static函数:</span><br></pre></td></tr></table></figure>
<pre><code>public synchronized static void method(){}
</code></pre><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">这个地方一定要注意,synchronized的锁是加在**当前Class对象**上,因此,所有对该方法的调用,都必须获得Class对象的锁。</span><br><span class="line"></span><br><span class="line">虽然synchronized可以保证对象或者代码段的线程安全,但是仅使用synchronized还是不足以控制拥有复杂逻辑的线程交互。为了实现多线程间的交互,还需要使用Object对象的wait()和notify()方法。</span><br><span class="line">典型用法:</span><br></pre></td></tr></table></figure>
<pre><code>synchronized(obj){
while(<?>){
obj.wait();
// 收到通知后,继续执行。
}
}
</code></pre><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">在使用wait()方法前,需要获得对象锁。在wait()方法执行时,当前线程或释放obj的独占锁,供其他线程使用。</span><br><span class="line">当等待在obj上线程收到obj.notify()时,它就能重新获得obj的独占锁,并继续运行。注意了,notify()方法是**随机唤起**等待在当前对象的某一个线程。</span><br><span class="line">下面是一个阻塞队列的实现:</span><br></pre></td></tr></table></figure>
<pre><code>public class BlockQueue{
private List list = new ArrayList();
public synchronized Object pop() throws InterruptedException{
while (list.size()==0){
this.wait();
}
if (list.size()>0){
return list.remove(0);
} else{
return null;
}
}
public synchronized Object put(Object obj){
list.add(obj);
this.notify();
}
}
</code></pre><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">**synchronized配合wait()、notify()**应该是Java开发者必须掌握的基本技能。</span><br><span class="line"></span><br><span class="line">## Reentrantlock重入锁</span><br><span class="line">Reentrantlock称为重入锁。它比synchronized拥有更加强大的功能,它可以中断、可定时。在高并发的情况下,它比synchronized有明显的性能优势。</span><br><span class="line">Reentrantlock提供了公平和非公平两种锁。公平锁是对锁的获取是先进先出,而非公平锁是可以插队的。当然从性能上分析,非公平锁的性能要好得多。因此,在无特殊需要,应该优选非公平锁,但是synchronized提供锁业不是绝对公平的。Reentrantlock在构造的时候可以指定锁是否公平。</span><br><span class="line">在使用重入锁时,一定要在程序最后释放锁。一般释放锁的代码要写在finally里。否则,如果程序出现异常,Loack就永远无法释放了。synchronized的锁是JVM最后自动释放的。</span><br><span class="line">经典使用方式如下:</span><br></pre></td></tr></table></figure>
<pre><code>try {
if (lock.tryLock(5, TimeUnit.SECONDS)) { //如果已经被lock,尝试等待5s,看是否可以获得锁,如果5s后仍然无法获得锁则返回false继续执行
// lock.lockInterruptibly();可以响应中断事件
try {
//操作
} finally {
lock.unlock();
}
}
} catch (InterruptedException e) {
e.printStackTrace(); //当前线程被中断时(interrupt),会抛InterruptedException
}
</code></pre><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">Reentrantlock提供了非常丰富的锁控制功能,灵活应用这些控制方法,可以提高应用程序的性能。不过这里并非是极力推荐使用Reentrantlock。重入锁算是JDK中提供的高级开发工具。这里有一篇文章专门针对**[ReentrantLock和synchronized两种锁定机制的对比](http://blog.csdn.net/fw0124/article/details/6672522)**。</span><br><span class="line"></span><br><span class="line">## ReadWriteLock读写锁</span><br><span class="line">读写分离是一种非常常见的数据处理思想。在sql中应该算是必须用到的技术。ReadWriteLock是在JDK5中提供的读写分离锁。读写分离锁可以有效地帮助减少锁竞争,以提升系统性能。读写分离使用场景主要是如果在系统中,读操作次数远远大于写操作。使用方式如下:</span><br></pre></td></tr></table></figure>
<pre><code>private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private Lock readLock = readWriteLock.readLock();
private Lock writeLock = readWriteLock.writeLock();
public Object handleRead() throws InterruptedException {
try {
readLock.lock();
Thread.sleep(1000);
return value;
}finally{
readLock.unlock();
}
}
public Object handleRead() throws InterruptedException {
try {
writeLock.lock();
Thread.sleep(1000);
return value;
}finally{
writeLock.unlock();
}
}
</code></pre><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">## Condition对象</span><br><span class="line">Conditiond对象用于协调多线程间的复杂协作。主要与锁相关联。通过Lock接口中的newCondition()方法可以生成一个与Lock绑定的Condition实例。Condition对象和锁的关系就如用Object.wait()、Object.notify()两个函数以及synchronized关键字一样。</span><br><span class="line">这里可以把ArrayBlockingQueue的源码摘出来看一下:</span><br></pre></td></tr></table></figure>
<p>public class ArrayBlockingQueue<e> extends AbstractQueue<e><br> implements BlockingQueue<e>, java.io.Serializable {</e></e></e></p>
<pre><code>/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition(); // 生成与Lock绑定的Condition
notFull = lock.newCondition();
}
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
insert(e);
} finally {
lock.unlock();
}
}
private void insert(E x) {
items[putIndex] = x;
putIndex = inc(putIndex);
++count;
notEmpty.signal(); // 通知
}
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) // 如果队列为空
notEmpty.await(); // 则消费者队列要等待一个非空的信号
return extract();
} finally {
lock.unlock();
}
}
private E extract() {
final Object[] items = this.items;
E x = this.<E>cast(items[takeIndex]);
items[takeIndex] = null;
takeIndex = inc(takeIndex);
--count;
notFull.signal(); // 通知put() 线程队列已有空闲空间
return x;
}
// other code
</code></pre><p>}<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">## Semaphore信号量</span><br><span class="line">信号量为多线程协作提供了更为强大的控制方法。信号量是对锁的扩展。无论是内部锁synchronized还是重入锁ReentrantLock,一次都允许一个线程访问一个资源,而信号量却可以指定多个线程同时访问某一个资源。从构造函数可以看出:</span><br><span class="line"></span><br><span class="line">``` </span><br><span class="line">public Semaphore(int permits) {}</span><br><span class="line">public Semaphore(int permits, boolean fair){} // 可以指定是否公平</span><br><span class="line">``` </span><br><span class="line"></span><br><span class="line">permits指定了信号量的准入书,也就是同时能申请多少个许可。当每个线程每次只申请一个许可时,这就相当于指定了同时有多少个线程可以访问某一个资源。这里罗列一下主要方法的使用:</span><br><span class="line"></span><br><span class="line">> * public void acquire() throws InterruptedException {} //尝试获得一个准入的许可。若无法获得,则线程会等待,知道有线程释放一个许可或者当前线程被中断。</span><br><span class="line">* public void acquireUninterruptibly(){} // 类似于acquire(),但是不会响应中断。</span><br><span class="line">* public boolean tryAcquire(){} // 尝试获取,如果成功则为true,否则false。这个方法不会等待,立即返回。</span><br><span class="line">* public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException {} // 尝试等待多长时间</span><br><span class="line">* public void release() //用于在现场访问资源结束后,释放一个许可,以使其他等待许可的线程可以进行资源访问。</span><br><span class="line"></span><br><span class="line">下面来看一下JDK文档中提供使用信号量的实例。这个实例很好的解释了如何通过信号量控制资源访问。</span><br></pre></td></tr></table></figure></p>
<p>public class Pool {<br> private static final int MAX_AVAILABLE = 100;<br> private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);</p>
<pre><code>public Object getItem() throws InterruptedException {
available.acquire();
// 申请一个许可
// 同时只能有100个线程进入取得可用项,
// 超过100个则需要等待
return getNextAvailableItem();
}
public void putItem(Object x) {
// 将给定项放回池内,标记为未被使用
if (markAsUnused(x)) {
available.release();
// 新增了一个可用项,释放一个许可,请求资源的线程被激活一个
}
}
// 仅作示例参考,非真实数据
protected Object[] items = new Object[MAX_AVAILABLE]; // 用于对象池复用对象
protected boolean[] used = new boolean[MAX_AVAILABLE]; // 标记作用
protected synchronized Object getNextAvailableItem() {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (!used[i]) {
used[i] = true;
return items[i];
}
}
return null;
}
protected synchronized boolean markAsUnused(Object item) {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (item == items[i]) {
if (used[i]) {
used[i] = false;
return true;
} else {
return false;
}
}
}
return false;
}
</code></pre><p>}<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">此实例简单实现了一个对象池,对象池最大容量为100。因此,当同时有100个对象请求时,对象池就会出现资源短缺,未能获得资源的线程就需要等待。当某个线程使用对象完毕后,就需要将对象返回给对象池。此时,由于可用资源增加,因此,可以激活一个等待该资源的线程。</span><br><span class="line"></span><br><span class="line">## ThreadLocal线程局部变量</span><br><span class="line">在刚开始接触ThreadLocal,笔者很难理解这个线程局部变量的使用场景。当现在回过头去看,ThreadLocal是一种多线程间并发访问变量的解决方案。与synchronized等加锁的方式不同,ThreadLocal完全不提供锁,而使用了以空间换时间的手段,为每个线程提供变量的独立副本,以保障线程安全,因此它不是一种数据共享的解决方案。</span><br><span class="line">ThreadLocal是解决线程安全问题一个很好的思路,ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本,由于Key值不可重复,每一个“线程对象”对应线程的“变量副本”,而到达了线程安全。</span><br><span class="line">特别值得注意的地方,从性能上说,ThreadLocal并不具有绝对的又是,在并发量不是很高时,也行加锁的性能会更好。但作为一套与锁完全无关的线程安全解决方案,在高并发量或者所竞争激烈的场合,使用ThreadLocal可以在一定程度上减少锁竞争。</span><br><span class="line">下面是一个ThreadLocal的简单使用:</span><br></pre></td></tr></table></figure></p>
<p>public class TestNum {<br> // 通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值<br> private static ThreadLocal<integer> seqNum = new ThreadLocal<integer>() {<br> public Integer initialValue() {<br> return 0;<br> }<br> };</integer></integer></p>
<pre><code>// 获取下一个序列值
public int getNextNum() {
seqNum.set(seqNum.get() + 1);
return seqNum.get();
}
public static void main(String[] args) {
TestNum sn = new TestNum();
//3个线程共享sn,各自产生序列号
TestClient t1 = new TestClient(sn);
TestClient t2 = new TestClient(sn);
TestClient t3 = new TestClient(sn);
t1.start();
t2.start();
t3.start();
}
private static class TestClient extends Thread {
private TestNum sn;
public TestClient(TestNum sn) {
this.sn = sn;
}
public void run() {
for (int i = 0; i < 3; i++) {
// 每个线程打出3个序列值
System.out.println("thread[" + Thread.currentThread().getName() + "] --> sn["
+ sn.getNextNum() + "]");
}
}
}
</code></pre><p>}<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">输出结果:</span><br></pre></td></tr></table></figure></p>
<p>thread[Thread-0] –> sn[1]<br>thread[Thread-1] –> sn[1]<br>thread[Thread-2] –> sn[1]<br>thread[Thread-1] –> sn[2]<br>thread[Thread-0] –> sn[2]<br>thread[Thread-1] –> sn[3]<br>thread[Thread-2] –> sn[2]<br>thread[Thread-0] –> sn[3]<br>thread[Thread-2] –> sn[3]<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">输出的结果信息可以发现每个线程所产生的序号虽然都共享同一个TestNum实例,但它们并没有发生相互干扰的情况,而是各自产生独立的序列号,这是因为ThreadLocal为每一个线程提供了单独的副本。</span><br><span class="line"></span><br><span class="line">## 锁的性能和优化</span><br><span class="line"></span><br><span class="line">“锁”是最常用的同步方法之一。在平常开发中,经常能看到很多同学直接把锁加很大一段代码上。还有的同学只会用一种锁方式解决所有共享问题。显然这样的编码是让人无法接受的。特别的在高并发的环境下,激烈的锁竞争会导致程序的性能下降德更加明显。因此合理使用锁对程序的性能直接相关。</span><br><span class="line">** 1、线程的开销 **</span><br><span class="line">在多核情况下,使用多线程可以明显提高系统的性能。但是在实际情况中,使用多线程的方式会额外增加系统的开销。相对于单核系统任务本身的资源消耗外,多线程应用还需要维护额外多线程特有的信息。比如,线程本身的元数据,线程调度,线程上下文的切换等。</span><br><span class="line">** 2、减小锁持有时间 **</span><br><span class="line">在使用锁进行并发控制的程序中,当锁发生竞争时,单个线程对锁的持有时间与系统性能有着直接的关系。如果线程持有锁的时间很长,那么相对地,锁的竞争程度也就越激烈。因此,在程序开发过程中,应该尽可能地减少对某个锁的占有时间,以减少线程间互斥的可能。比如下面这一段代码:</span><br></pre></td></tr></table></figure></p>
<p>public synchronized void syncMehod(){<br> beforeMethod();<br> mutexMethod();<br> afterMethod();<br>}<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">此实例如果只有mutexMethod()方法是有同步需要的,而在beforeMethod(),和afterMethod()并不需要做同步控制。如果beforeMethod(),和afterMethod()分别是重量级的方法,则会花费较长的CPU时间。在这个时候,如果并发量较大时,使用这种同步方案会导致等待线程大量增加。因为当前执行的线程只有在执行完所有任务后,才会释放锁。</span><br><span class="line">下面是优化后的方案,只在必要的时候进行同步,这样就能明显减少线程持有锁的时间,提高系统的吞吐量。代码如下:</span><br></pre></td></tr></table></figure></p>
<p>public void syncMehod(){<br> beforeMethod();<br> synchronized(this){<br> mutexMethod();<br> }<br> afterMethod();<br>}<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">** 3、减少锁粒度 ** </span><br><span class="line">减小锁粒度也是一种削弱多线程锁竞争的一种有效手段,这种技术典型的使用场景就是ConcurrentHashMap这个类。在普通的HashMap中每当对集合进行add()操作或者get()操作时,总是获得集合对象的锁。这种操作完全是一种同步行为,因为锁是在整个集合对象上的,因此,在高并发时,激烈的锁竞争会影响到系统的吞吐量。</span><br><span class="line">如果看过源码的同学应该知道HashMap是数组+链表的方式做实现的。ConcurrentHashMap在HashMap的基础上将整个HashMap分成若干个段(Segment),每个段都是一个子HashMap。如果需要在增加一个新的表项,并不是将这个HashMap加锁,二十搜线根据hashcode得到该表项应该被存放在哪个段中,然后对该段加锁,并完成put()操作。这样,在多线程环境中,如果多个线程同时进行写入操作,只要被写入的项不存在同一个段中,那么线程间便可以做到真正的并行。具体的实现希望读者自己花点时间读一读ConcurrentHashMap这个类的源码,这里就不再做过多描述了。</span><br><span class="line">** 4、锁分离 ** </span><br><span class="line">在前面提起过ReadWriteLock读写锁,那么读写分离的延伸就是锁的分离。同样可以在JDK中找到锁分离的源码LinkedBlockingQueue。</span><br></pre></td></tr></table></figure></p>
<p>public class LinkedBlockingQueue<e> extends AbstractQueue<e><br> implements BlockingQueue<e>, java.io.Serializable {<br> /<em>* Lock held by take, poll, etc </em>/<br> private final ReentrantLock takeLock = new ReentrantLock();</e></e></e></p>
<pre><code>/** Wait queue for waiting takes */
private final Condition notEmpty = takeLock.newCondition();
/** Lock held by put, offer, etc */
private final ReentrantLock putLock = new ReentrantLock();
/** Wait queue for waiting puts */
private final Condition notFull = putLock.newCondition();
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly(); // 不能有两个线程同时读取数据
try {
while (count.get() == 0) { // 如果当前没有可用数据,一直等待put()的通知
notEmpty.await();
}
x = dequeue(); // 从头部移除一项
c = count.getAndDecrement(); // size减1
if (c > 1)
notEmpty.signal(); // 通知其他take()操作
} finally {
takeLock.unlock(); // 释放锁
}
if (c == capacity)
signalNotFull(); // 通知put()操作,已有空余空间
return x;
}
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
// Note: convention in all put/take/etc is to preset local var
// holding count negative to indicate failure unless set.
int c = -1;
Node<E> node = new Node(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly(); // 不能有两个线程同时put数据
try {
/*
* Note that count is used in wait guard even though it is
* not protected by lock. This works because count can
* only decrease at this point (all other puts are shut
* out by lock), and we (or some other waiting put) are
* signalled if it ever changes from capacity. Similarly
* for all other uses of count in other wait guards.
*/
while (count.get() == capacity) { // 队列满了 则等待
notFull.await();
}
enqueue(node); // 加入队列
c = count.getAndIncrement();// size加1
if (c + 1 < capacity)
notFull.signal(); // 如果有足够空间,通知其他线程
} finally {
putLock.unlock();// 释放锁
}
if (c == 0)
signalNotEmpty();// 插入成功后,通知take()操作读取数据
}
// other code
</code></pre><p>}<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">这里需要说明一下的就是,take()和put()函数是相互独立的,它们之间不存在锁竞争关系。只需要在take()和put()各自方法内部分别对takeLock和putLock发生竞争。从而,削弱了锁竞争的可能性。</span><br><span class="line"></span><br><span class="line">** 5、锁粗化 ** </span><br><span class="line">上面说到的减小锁时间和粒度,这样做就是为了满足每个线程持有锁的时间尽量短。但是,在粒度上应该把握一个度,如果对用一个锁不停地进行请求、同步和释放,其本身也会消耗系统宝贵的资源,反而加大了系统开销。</span><br><span class="line">我们需要知道的是,虚拟机在遇到一连串连续的对同一锁不断进行请求和释放的操作时,便会把所有的锁操作整合成对锁的一次请求,从而减少对锁的请求同步次数,这样的操作叫做锁的粗化。下面是一段整合实例演示:</span><br></pre></td></tr></table></figure></p>
<p>public void syncMehod(){<br> synchronized(lock){<br> method1();<br> }<br> synchronized(lock){<br> method2();<br> }<br>}<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">JVM整合后的形式:</span><br></pre></td></tr></table></figure></p>
<p>public void syncMehod(){<br> synchronized(lock){<br> method1();<br> method2();<br> }<br>}<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">因此,这样的整合给我们开发人员对锁粒度的把握给出了很好的演示作用。</span><br><span class="line"></span><br><span class="line">## 无锁的并行计算</span><br><span class="line">上面花了很大篇幅在说锁的事情,同时也提到过锁是会带来一定的上下文切换的额外资源开销,在高并发时,”锁“的激烈竞争可能会成为系统瓶颈。因此,这里可以使用一种非阻塞同步方法。这种无锁方式依然能保证数据和程序在高并发环境下保持多线程间的一致性。</span><br><span class="line"></span><br><span class="line">** 1、非阻塞同步/无锁 **</span><br><span class="line">非阻塞同步方式其实在前面的ThreadLocal中已经有所体现,每个线程拥有各自独立的变量副本,因此在并行计算时,无需相互等待。这里笔者主要推荐一种更为重要的、基于比较并交换(Compare And Swap)CAS算法的无锁并发控制方法。</span><br><span class="line">CAS算法的过程:它包含3个参数CAS(V,E,N)。V表示要更新的变量,E表示预期值,N表示新值。仅当V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后CAS返回当前V的真实值。CAS操作时抱着乐观的态度进行的,它总是认为自己可以成功完成操作。当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余俊辉失败。失败的线程不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。基于这样的原理,CAS操作及时没有锁,也可以发现其他线程对当前线程的干扰,并且进行恰当的处理。</span><br><span class="line">在这里笔者引入一篇讲得很不错的文章**[非阻塞同步算法与CAS(Compare and Swap)无锁算法](http://www.cnblogs.com/Mainz/p/3546347.html)**。</span><br><span class="line"></span><br><span class="line">** 2、原子量操作 **</span><br><span class="line">JDK的java.util.concurrent.atomic包提供了使用无锁算法实现的原子操作类,代码内部主要使用了底层native代码的实现。有兴趣的同学可以继续跟踪一下native层面的代码。这里就不贴表层的代码实现了。</span><br><span class="line">下面主要以一个例子来展示普通同步方法和无锁同步的性能差距:</span><br></pre></td></tr></table></figure></p>
<p>public class TestAtomic {<br> private static final int MAX_THREADS = 3;<br> private static final int TASK_COUNT = 3;<br> private static final int TARGET_COUNT = 100 * 10000;<br> private AtomicInteger acount = new AtomicInteger(0);<br> private int count = 0;</p>
<pre><code>synchronized int inc() {
return ++count;
}
synchronized int getCount() {
return count;
}
public class SyncThread implements Runnable {
String name;
long startTime;
TestAtomic out;
public SyncThread(TestAtomic o, long startTime) {
this.out = o;
this.startTime = startTime;
}
@Override
public void run() {
int v = out.inc();
while (v < TARGET_COUNT) {
v = out.inc();
}
long endTime = System.currentTimeMillis();
System.out.println("SyncThread spend:" + (endTime - startTime) + "ms" + ", v=" + v);
}
}
public class AtomicThread implements Runnable {
String name;
long startTime;
public AtomicThread(long startTime) {
this.startTime = startTime;
}
@Override
public void run() {
int v = acount.incrementAndGet();
while (v < TARGET_COUNT) {
v = acount.incrementAndGet();
}
long endTime = System.currentTimeMillis();
System.out.println("AtomicThread spend:" + (endTime - startTime) + "ms" + ", v=" + v);
}
}
@Test
public void testSync() throws InterruptedException {
ExecutorService exe = Executors.newFixedThreadPool(MAX_THREADS);
long startTime = System.currentTimeMillis();
SyncThread sync = new SyncThread(this, startTime);
for (int i = 0; i < TASK_COUNT; i++) {
exe.submit(sync);
}
Thread.sleep(10000);
}
@Test
public void testAtomic() throws InterruptedException {
ExecutorService exe = Executors.newFixedThreadPool(MAX_THREADS);
long startTime = System.currentTimeMillis();
AtomicThread atomic = new AtomicThread(startTime);
for (int i = 0; i < TASK_COUNT; i++) {
exe.submit(atomic);
}
Thread.sleep(10000);
}
</code></pre><p>}<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">测试结果如下:</span><br></pre></td></tr></table></figure></p>
<p>testSync():<br>SyncThread spend:201ms, v=1000002<br>SyncThread spend:201ms, v=1000000<br>SyncThread spend:201ms, v=1000001</p>
<p>testAtomic():<br>AtomicThread spend:43ms, v=1000000<br>AtomicThread spend:44ms, v=1000001<br>AtomicThread spend:46ms, v=1000002<br>```</p>
<p>相信这样的测试结果将内部锁和非阻塞同步算法的性能差异体现的非常明显。因此笔者更推荐直接视同atomic下的这个原子类。</p>
<h2 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h2><p>终于把想表达的这些东西整理完成了,其实还有一些想CountDownLatch这样的类没有讲到。不过上面的所讲到的绝对是并发编程中的核心。也许有些读者朋友能在网上看到很多这样的知识点,但是个人还是觉得知识只有在对比的基础上才能找到它合适的使用场景。因此,这也是笔者整理这篇文章的原因,也希望这篇文章能帮到更多的同学。</p>
<p>因文章很快被人转载到一些其他网站,所以本人在此声明:<br><strong>转载请标明转载出处:<a href="http://frodoking.github.io/2015/07/19/java-concurrent-mechanism/">http://frodoking.github.io/2015/07/19/java-concurrent-mechanism/</a> </strong></p>
<p>在一般性开发中,笔者经常看到很多同学在对待java并发开发模型中只会使用一些基础的方法。比如volatile,synchronized。像Lock和atomic这类高级并发包很多人并不经常使用。我想大部分原因都是来之于对原理的不属性导致的。在繁忙的开发工作中,又有谁会很准确的把握和使用正确的并发模型呢?</p>
<p>所以最近基于这个思想,本人打算把并发控制机制这部分整理成一篇文章。既是对自己掌握知识的一个回忆,也是希望这篇讲到的类容能帮助到大部分开发者。</p>
<p>并行程序开发不可避免地要涉及多线程、多任务的协作和数据共享等问题。在JDK中,提供了多种途径实现多线程间的并发控制。比如常用的:内部锁、重入锁、读写锁和信号量。<br>
Android统一风格 —— 主题
http://frodoking.github.io/2015/07/01/android-theme/
2015-07-01T09:25:47.000Z
2015-09-14T06:38:41.036Z
<p>接触Android是从UI开始的,笔者第一份工作是在金山软件做WPS Office。当时开发主要是负责整个演示PPT这边的UI风格开发和维护,当时的开发要求非常严格。要求所有控件采用App的总体风格,不过尽管这样,一个上百人的开发团队。并不能保证所有的人都能做出一样的风格,总是会存在这里或者那里的细小差别。就拿简单的文本框来说,文字排版、大小、颜色、字体、内边距和外边距等等,在不同的层级中都是有不同的要求。如果稍不注意,在后面的调整中都很费劲。<br><a id="more"></a><br>当时做法其实是从一开始都要求统一风格,尽量使用公共控件。这样从一开始就觉得这样挺好,不过也没感受到没这样做的坏处。</p>
<p>当笔者去年跳槽后,相继都去过一些公司。都发现一个共有问题,风格严重不统一、编码不规范。xml布局全部都是一个标签一个标签堆,几乎看不到共用风格,比如说尺寸、颜色都是明码,只想说这样的代码维护成本得多高啊。很多同学觉得一个App不就是很多Activity堆起来的嘛,而且Activity已经被Android自身封装得很好,没必要把层次划得太细。而且更严重的是每个人都在自己的页面中完成所有的业务处理,这种编码能力笔者只能表示无语,本人最后把问题给他们抛出来后就离开了。</p>
<p>本人在这里必须说明一下,这种思想是非常错误的:</p>
<blockquote>
<p>1、风格不统一,那么你会浪费更多的时间去写xml布局。同时你并不能保证每个页面真的统一了;<br>2、Android App不是Activity的堆积。本人经常给身边的同事或者朋友说,Android只是一层皮,真正体现个人能力的应该是如果做到你这一套app解决方案能与平台无关后也能采用,这就上升到基础架构;</p>
</blockquote>
<p>最近项目相对有一些空余时间,因此很有必要把这一部分内容说一说。因为在网上并没有看到一些系统的做法。也希望这篇文章能让读者意识到风格统一编码的重要性。</p>
<p>下面主要说一说笔者风格统一走过的路:</p>
<blockquote>
<p>1、早期风格统一的做法主要是在style中把每一种风格定义好,然后再layout中进行一个引用;<br>2、现在的做法是将系统控件上升到Application层面,比如android:textViewStyle、android:editTextStyle这类标签;</p>
</blockquote>
<h2 id="Application层面风格统一"><a href="#Application层面风格统一" class="headerlink" title="Application层面风格统一"></a>Application层面风格统一</h2><p>在Application层面上做风格统一,一般只能针对系统的控件。主要使用方式是在manifest的application标签下写入:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><application android:theme="@style/AppTheme" ></span><br></pre></td></tr></table></figure></p>
<p>这里的AppTheme就是自定义的</p>
<p>为了区分style,建议在values文件夹中建立一个theme.xml文件。下面主要讲讲EditText的统一风格如何实现<br>首先需要在theme.xml中建立一份name为AppTheme的style标签<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><style name="AppTheme" parent="@android:style/Theme"></span><br><span class="line"></style></span><br></pre></td></tr></table></figure></p>
<p>注意一下parent的继承有非常多可选项,笔者建议大家选择最基础的Theme,这个风格基本上包含了App的所有系统控件默认风格;<br>接下来需要针对可编辑文本框做风格,那首先需要建立一个EditText的style标签:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><style name="EditTextTheme" parent="@android:style/Widget.EditText"></span><br><span class="line"> <item name="android:autoText">false</item></span><br><span class="line"> <item name="android:capitalize">none</item></span><br><span class="line"> <item name="android:scrollHorizontally">true</item></span><br><span class="line"> <item name="android:singleLine">true</item></span><br><span class="line"> <item name="android:padding">10dp</item></span><br><span class="line"> <item name="android:textColor">@color/table_text</item></span><br><span class="line"> <item name="android:textCursorDrawable">@drawable/edit_text_cursor</item></span><br><span class="line"> <item name="android:textSize">20sp</item></span><br><span class="line"> <item name="android:background">@drawable/edit_text_background</item></span><br><span class="line"> </style></span><br></pre></td></tr></table></figure></p>
<p>同样建议先集成默认风格,再写入需要修改的风格。比如文字大小、文字颜色和文本框背景等等。<br>在AppTheme中的引用如下:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><style name="AppTheme" parent="@android:style/Theme"></span><br><span class="line"> <item name="android:editTextStyle">@style/EditTextTheme</item></span><br><span class="line"></style></span><br></pre></td></tr></table></figure></p>
<p>ok,这就算是完成了对EditText的风格集成。上边的代码演示其实很简单,但是要坚持做好并非一件容易事;<br>下边给出笔者自己整理的一份主题风格控件,主要包括:</p>
<blockquote>
<ul>
<li>Dialog</li>
<li>TextView</li>
<li>EditText</li>
<li>Button</li>
<li>ImageButton</li>
<li>RadioButton</li>
<li>CheckBox</li>
<li>Spinner</li>
<li>ScrollView</li>
<li>GridView</li>
<li>ListView</li>
<li>ProgressView</li>
</ul>
</blockquote>
<p>其中,最为重要的应该是可编辑框,单选,多选以及列表风格。有多少同学能保证带有滚动条的列表都是一样的风格呢?<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br></pre></td><td class="code"><pre><span class="line"><resources></span><br><span class="line"></span><br><span class="line"> <!-- Base application theme. --></span><br><span class="line"> <style name="AppBaseTheme" parent="@android:style/Theme"></span><br><span class="line"> <item name="android:background">@drawable/main_background</item></span><br><span class="line"> <item name="android:windowNoTitle">true</item></span><br><span class="line"> </style></span><br><span class="line"></span><br><span class="line"> <style name="AppTheme" parent="AppBaseTheme"></span><br><span class="line"> <item name="android:dialogTheme">@style/DialogTheme</item></span><br><span class="line"> <item name="android:textViewStyle">@style/TextViewTheme</item></span><br><span class="line"> <item name="android:editTextStyle">@style/EditTextTheme</item></span><br><span class="line"> <item name="android:buttonStyle">@style/ButtonTheme</item></span><br><span class="line"> <item name="android:imageButtonStyle">@style/ImageButtonTheme</item></span><br><span class="line"> <item name="android:radioButtonStyle">@style/RadioButtonTheme</item></span><br><span class="line"> <item name="android:checkboxStyle">@style/CheckboxTheme</item></span><br><span class="line"> <item name="android:spinnerStyle">@style/SpinnerTheme</item></span><br><span class="line"> <item name="android:scrollViewStyle">@style/ScrollViewTheme</item></span><br><span class="line"> <item name="android:gridViewStyle">@style/GridViewTheme</item></span><br><span class="line"> <item name="android:listViewStyle">@style/ListViewTheme</item></span><br><span class="line"> <item name="android:popupWindowStyle">@style/PopupWindowTheme</item></span><br><span class="line"> <item name="android:progressBarStyle">@style/ProgressBarTheme</item></span><br><span class="line"> <item name="android:progressBarStyleHorizontal">@style/ProgressBarHorizontalTheme</item></span><br><span class="line"> </style></span><br><span class="line"></span><br><span class="line"> <style name="DialogTheme" parent="@android:style/Theme.Dialog"></span><br><span class="line"> <item name="android:windowFrame">@null</item></span><br><span class="line"> <item name="android:windowNoTitle">true</item></span><br><span class="line"> <item name="android:windowIsFloating">true</item></span><br><span class="line"> <item name="android:windowContentOverlay">@null</item></span><br><span class="line"> </style></span><br><span class="line"></span><br><span class="line"> <style name="TextViewTheme" parent="@android:style/Widget.TextView"></span><br><span class="line"> <item name="android:textSize">14sp</item></span><br><span class="line"> <item name="android:textColor">@color/table_text_light</item></span><br><span class="line"> <item name="android:layout_height">wrap_content</item></span><br><span class="line"> <item name="android:layout_width">wrap_content</item></span><br><span class="line"> </style></span><br><span class="line"></span><br><span class="line"> <style name="EditTextTheme" parent="@android:style/Widget.EditText"></span><br><span class="line"> <item name="android:autoText">false</item></span><br><span class="line"> <item name="android:capitalize">none</item></span><br><span class="line"> <item name="android:scrollHorizontally">true</item></span><br><span class="line"> <item name="android:singleLine">true</item></span><br><span class="line"> <item name="android:padding">10dp</item></span><br><span class="line"> <item name="android:textColor">@color/table_text</item></span><br><span class="line"> <item name="android:textCursorDrawable">@drawable/edit_text_cursor</item></span><br><span class="line"> <item name="android:textSize">20sp</item></span><br><span class="line"> <item name="android:background">@drawable/edit_text_background</item></span><br><span class="line"> </style></span><br><span class="line"></span><br><span class="line"> <style name="ButtonTheme" parent="@android:style/Widget.Button"></span><br><span class="line"> <item name="android:paddingLeft">@dimen/margin_middle</item></span><br><span class="line"> <item name="android:paddingRight">@dimen/margin_middle</item></span><br><span class="line"> <item name="android:textSize">16sp</item></span><br><span class="line"> <item name="android:textColor">@color/text_light_selector</item></span><br><span class="line"> <item name="android:background">@drawable/background_button_rectangle</item></span><br><span class="line"> </style></span><br><span class="line"></span><br><span class="line"> <style name="ImageButtonTheme" parent="@android:style/Widget.ImageButton"></span><br><span class="line"> </style></span><br><span class="line"></span><br><span class="line"> <style name="RadioButtonTheme" parent="@android:style/Widget.CompoundButton.RadioButton"></span><br><span class="line"> <item name="android:button">@drawable/radiobutton</item></span><br><span class="line"> <item name="android:paddingLeft">@dimen/margin_small</item></span><br><span class="line"> </style></span><br><span class="line"></span><br><span class="line"> <style name="CheckboxTheme" parent="@android:style/Widget.CompoundButton.CheckBox"></span><br><span class="line"> <item name="android:button">@drawable/checkbox</item></span><br><span class="line"> <item name="android:paddingLeft">@dimen/margin_small</item></span><br><span class="line"> </style></span><br><span class="line"></span><br><span class="line"> <style name="SpinnerTheme"></span><br><span class="line"> <item name="android:indeterminate">true</item></span><br><span class="line"> <item name="android:indeterminateDrawable">@drawable/spinner</item></span><br><span class="line"> <item name="android:indeterminateDuration">2000</item></span><br><span class="line"> <item name="android:indeterminateOnly">true</item></span><br><span class="line"> </style></span><br><span class="line"></span><br><span class="line"> <style name="ScrollViewTheme" parent="@android:style/Widget.ScrollView"></span><br><span class="line"> <item name="android:scrollbarTrackVertical">@drawable/scrollbar_vertical_track</item></span><br><span class="line"> <item name="android:scrollbarThumbVertical">@drawable/scrollbar_vertical_thumb</item></span><br><span class="line"> <item name="android:scrollbarTrackHorizontal">@drawable/scrollbar_vertical_track</item></span><br><span class="line"> <item name="android:scrollbarThumbHorizontal">@drawable/scrollbar_vertical_thumb</item></span><br><span class="line"> <item name="android:scrollbarStyle">outsideOverlay</item></span><br><span class="line"> <item name="android:scrollbarSize">12dp</item></span><br><span class="line"> </style></span><br><span class="line"></span><br><span class="line"> <style name="GridViewTheme" parent="@android:style/Widget.GridView"></span><br><span class="line"> <item name="android:listSelector">@drawable/list_item_background</item></span><br><span class="line"> <item name="android:scrollbarTrackVertical">@drawable/scrollbar_vertical_track</item></span><br><span class="line"> <item name="android:scrollbarThumbVertical">@drawable/scrollbar_vertical_thumb</item></span><br><span class="line"> <item name="android:scrollbarTrackHorizontal">@drawable/scrollbar_vertical_track</item></span><br><span class="line"> <item name="android:scrollbarThumbHorizontal">@drawable/scrollbar_vertical_thumb</item></span><br><span class="line"> <item name="android:scrollbarStyle">outsideOverlay</item></span><br><span class="line"> <item name="android:scrollbarSize">12dp</item></span><br><span class="line"> </style></span><br><span class="line"></span><br><span class="line"> <style name="ListViewTheme" parent="@android:style/Widget.ListView"></span><br><span class="line"> <item name="android:dividerHeight">2px</item></span><br><span class="line"> <item name="android:divider">@drawable/bootstrap_divider</item></span><br><span class="line"> <item name="android:cacheColorHint">@android:color/transparent</item></span><br><span class="line"> <item name="android:listSelector">@drawable/list_item_background</item></span><br><span class="line"></span><br><span class="line"> <item name="android:scrollbarTrackVertical">@drawable/scrollbar_vertical_track</item></span><br><span class="line"> <item name="android:scrollbarThumbVertical">@drawable/scrollbar_vertical_thumb</item></span><br><span class="line"> <item name="android:scrollbarTrackHorizontal">@drawable/scrollbar_vertical_track</item></span><br><span class="line"> <item name="android:scrollbarThumbHorizontal">@drawable/scrollbar_vertical_thumb</item></span><br><span class="line"> <item name="android:scrollbarStyle">outsideOverlay</item></span><br><span class="line"> <item name="android:scrollbarSize">12dp</item></span><br><span class="line"> </style></span><br><span class="line"></span><br><span class="line"> <style name="PopupWindowTheme" parent="@android:style/Widget.PopupWindow"></span><br><span class="line"> </style></span><br><span class="line"></span><br><span class="line"> <style name="ProgressBarTheme" parent="@android:style/Widget.ProgressBar"></span><br><span class="line"> </style></span><br><span class="line"></span><br><span class="line"> <style name="ProgressBarHorizontalTheme" parent="@android:style/Widget.ProgressBar.Horizontal"></span><br><span class="line"> <item name="android:progressDrawable">@drawable/progressbar_horizontal</item></span><br><span class="line"> </style></span><br><span class="line"></span><br><span class="line"></resources></span><br></pre></td></tr></table></figure></p>
<p>希望看到的同学能结合<a href="/2015/03/22/android-ui-design/">UI的整体设计思路(避免臃肿的UI)</a>这篇文章</p>
<p>#UI事儿虽小,但是很有必要。毕竟UI是一个非常需要时间的工作。</p>
<p>接触Android是从UI开始的,笔者第一份工作是在金山软件做WPS Office。当时开发主要是负责整个演示PPT这边的UI风格开发和维护,当时的开发要求非常严格。要求所有控件采用App的总体风格,不过尽管这样,一个上百人的开发团队。并不能保证所有的人都能做出一样的风格,总是会存在这里或者那里的细小差别。就拿简单的文本框来说,文字排版、大小、颜色、字体、内边距和外边距等等,在不同的层级中都是有不同的要求。如果稍不注意,在后面的调整中都很费劲。<br>
OKHttp源码解析-ConnectionPool对Connection重用机制&Http/Https/SPDY协议选择
http://frodoking.github.io/2015/06/29/android-okhttp-connectionpool-http1-x-http2-x/
2015-06-29T03:08:46.000Z
2015-09-14T06:45:52.978Z
<p>因文章很快被人转载到一些其他网站,所以本人在此声明:<br><strong>转载请标明转载出处:<a href="http://frodoking.github.io/2015/06/29/android-okhttp-connectionpool-http1-x-http2-x/">http://frodoking.github.io/2015/06/29/android-okhttp-connectionpool-http1-x-http2-x/</a> </strong></p>
<p>距离上一次的<a href="/2015/03/12/android-okhttp/">OKHttp源码解析</a>过去快3月了。最近一直在忙工作上的事情,另外也再尝试一门新的语言Go。所以一直没花很多心思在Android这边。最近看到一些网友建议把okhttp的连接池对Connection的重用维护机制以及HTTP和SPDY协议如何得到区分这两个核心内容做深入的分析。<br>因此,这几天就打算好好说一说这块儿的实现方式。SPDY既是http1.x的增强版也是http2.x的过渡版本,虽然现在很多都直接切入到http2.0,不过SPDY的应用仍然值得关注。<br><a id="more"></a></p>
<h2 id="ConnectionPool对Connection的重用机制"><a href="#ConnectionPool对Connection的重用机制" class="headerlink" title="ConnectionPool对Connection的重用机制"></a>ConnectionPool对Connection的重用机制</h2><p>从上一篇文章的HttpEngine.connect()说起,在这个方法中有connection = nextConnection();这是Connection创建或者重用的起点。那我们先来看看nextConnection()方法:</p>
<p><strong>HttpEngine.java</strong></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">public final class HttpEngine {</span><br><span class="line"> // ...省略.....</span><br><span class="line"> /**</span><br><span class="line"> * Returns the next connection to attempt.</span><br><span class="line"> *</span><br><span class="line"> * @throws java.util.NoSuchElementException if there are no more routes to attempt.</span><br><span class="line"> */</span><br><span class="line"> private Connection nextConnection() throws IOException {</span><br><span class="line"> Connection connection = createNextConnection();</span><br><span class="line"> Internal.instance.connectAndSetOwner(client, connection, this, networkRequest);</span><br><span class="line"> return connection;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> private Connection createNextConnection() throws IOException {</span><br><span class="line"> ConnectionPool pool = client.getConnectionPool();</span><br><span class="line"></span><br><span class="line"> // Always prefer pooled connections over new connections.</span><br><span class="line"> // 这里表示先从连接池中选拔一个已经缓存过的Connection</span><br><span class="line"> // 先通过连接池内部的get方法获取(下面代码再展开)</span><br><span class="line"> for (Connection pooled; (pooled = pool.get(address)) != null; ) {</span><br><span class="line"> // 匹配GET方法,判断当前命中的Connection是否是可读取的,这里SPDY类型连接默认是true,</span><br><span class="line"> // 而http1.x通过判断socket是否已经关闭来作为是否可读取判断依据</span><br><span class="line"> if (networkRequest.method().equals("GET") || Internal.instance.isReadable(pooled)) {</span><br><span class="line"> return pooled;</span><br><span class="line"> }</span><br><span class="line"> // 如果不满足可循环使用,当然就是关闭当前的连接</span><br><span class="line"> pooled.getSocket().close();</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> // 新开一个Connection</span><br><span class="line"> Route route = routeSelector.next();</span><br><span class="line"> return new Connection(pool, route);</span><br><span class="line"> }</span><br><span class="line"> // ...省略.....</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><strong>ConnectionPool.java</strong></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line">public final class ConnectionPool {</span><br><span class="line"> //executor 的内部构建方式贴出来,希望读者也能注意到可定制线程池的使用。定制化的差别还是很大的,这里主要使用了LinkedBlockingQueue。</span><br><span class="line"> private Executor executor = new ThreadPoolExecutor(</span><br><span class="line"> 0 /* corePoolSize */, 1 /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,</span><br><span class="line"> new LinkedBlockingQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));</span><br><span class="line"> // 内部一个Connection的缓存列表,主要用于可循环使用连接的缓存作用。 </span><br><span class="line"> private final LinkedList<Connection> connections = new LinkedList<>();</span><br><span class="line"> // ...省略.....</span><br><span class="line"> </span><br><span class="line"> /** Returns a recycled connection to {@code address}, or null if no such connection exists. */</span><br><span class="line"> 返回一个循环使用的Connection</span><br><span class="line"> public synchronized Connection get(Address address) {</span><br><span class="line"> Connection foundConnection = null;</span><br><span class="line"> for (ListIterator<Connection> i = connections.listIterator(connections.size());</span><br><span class="line"> i.hasPrevious(); ) {</span><br><span class="line"> Connection connection = i.previous();</span><br><span class="line"> if (!connection.getRoute().getAddress().equals(address)</span><br><span class="line"> || !connection.isAlive()</span><br><span class="line"> || System.nanoTime() - connection.getIdleStartTimeNs() >= keepAliveDurationNs) {</span><br><span class="line"> continue;</span><br><span class="line"> }</span><br><span class="line"> i.remove();</span><br><span class="line"> // 如果不是spdy连接。</span><br><span class="line"> if (!connection.isSpdy()) {</span><br><span class="line"> try {</span><br><span class="line"> // 通过反射 -- 这是Android平台下的适配 主要是反射到“android.net.TrafficStats.Socket.class”,</span><br><span class="line"> // 在这下边会有tagSocket和untagSocket方法,如果想了解详细的情况,建议再对照Platform这个类仔细研究一下</span><br><span class="line"> // // Non-null on Android 4.0+.</span><br><span class="line"> // private final Method trafficStatsTagSocket;</span><br><span class="line"> // private final Method trafficStatsUntagSocket;</span><br><span class="line"> Platform.get().tagSocket(connection.getSocket());</span><br><span class="line"> } catch (SocketException e) {</span><br><span class="line"> Util.closeQuietly(connection.getSocket());</span><br><span class="line"> // When unable to tag, skip recycling and close</span><br><span class="line"> Platform.get().logW("Unable to tagSocket(): " + e);</span><br><span class="line"> continue;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> // 命中可循环使用Connection</span><br><span class="line"> foundConnection = connection;</span><br><span class="line"> break;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 这里针对SPDY的Connection的重用,添加到队列头部</span><br><span class="line"> if (foundConnection != null && foundConnection.isSpdy()) {</span><br><span class="line"> connections.addFirst(foundConnection); // Add it back after iteration.</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> return foundConnection;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> void recycle(Connection connection) {</span><br><span class="line"> // ...省略.....</span><br><span class="line"> addConnection(connection);</span><br><span class="line"> // ...省略.....</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> void share(Connection connection) {</span><br><span class="line"> // ...省略.....</span><br><span class="line"> addConnection(connection);</span><br><span class="line"> // ...省略.....</span><br><span class="line"> }</span><br><span class="line"> // ...省略.....</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在代码段中重点标注了一下get方法来命中缓存的可循环使用的Connection,这里单独说一下这些Connection是什么时候被放入到缓存池中的:<br>1、recycle方法<br>这个recycle方法主要是针对Http1.x协议的Connection<br>2、share方法<br>在HttpEngine的nextConection()方法中,当创建完成了Connection后会执行Internal.instance.connectAndSetOwner(client, connection, this, networkRequest);<br>而这个方法最后执行时Connection.connectAndSetOwner(xxx)方法</p>
<p><strong>Connection.java</strong><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">// ...省略.....</span><br><span class="line"> /**</span><br><span class="line"> * Connects this connection if it isn't already. This creates tunnels, shares</span><br><span class="line"> * the connection with the connection pool, and configures timeouts.</span><br><span class="line"> */</span><br><span class="line"> void connectAndSetOwner(OkHttpClient client, Object owner, Request request) throws IOException {</span><br><span class="line"> setOwner(owner);</span><br><span class="line"></span><br><span class="line"> if (!isConnected()) {</span><br><span class="line"> // 对请求头部的处理</span><br><span class="line"> Request tunnelRequest = tunnelRequest(request);</span><br><span class="line"> // 发起连接请求</span><br><span class="line"> connect(client.getConnectTimeout(), client.getReadTimeout(),</span><br><span class="line"> client.getWriteTimeout(), tunnelRequest);</span><br><span class="line"> if (isSpdy()) {</span><br><span class="line"> client.getConnectionPool().share(this);</span><br><span class="line"> }</span><br><span class="line"> client.routeDatabase().connected(getRoute());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> setTimeouts(client.getReadTimeout(), client.getWriteTimeout());</span><br><span class="line"> }</span><br><span class="line"> // ...省略.....</span><br></pre></td></tr></table></figure></p>
<p>owner作用标示当前连接是谁持有,如果是spdy的连接、属于连接池或者被丢弃那么owner都是null的。在循环使用这里是很有用的。<br>另外说一点tunnelRequest(request)方法,这里主要是为request通过HTTP proxy创建一个TLS的管道,这里牵涉到加解密的一些问题。<br>如果对网络编程比较熟悉的同学应该一看就非常明白这个方法对请求头部的关键字处理。这里就不详细展开了。<br>回到上边的代码,如果是SPDY的连接,这个Connection就会被共享,那么就会被缓存下来。</p>
<p>再简单说一下HttpEngine的Connection真正发起重用的地方,HttpEngine.releaseConnection()。每一个HttpEngine对应一个Transport接口,而Transport接口分HttpTransport和SpdyTransport。<br>在释放连接的时候会通过各自的Transport执行canReuseConnection(),如果可以重用,那么将状态置为idle状态,同时将连接放入到连接池。<br>SPDYTransport默认是可以重用的,而HttpTransport则需要判断request和Response的状态以及连接是否关闭来决定。<br>ok,连接的缓存就讲到这里吧。</p>
<h2 id="Connection对Http-Https-SPDY协议的选择"><a href="#Connection对Http-Https-SPDY协议的选择" class="headerlink" title="Connection对Http/Https/SPDY协议的选择"></a>Connection对Http/Https/SPDY协议的选择</h2><p>关于协议的选择,到底是走http1.x还是走http2.x的spdy,主要得从HttpEngine的Transport接口选择说起。<br>任何与网络相关的,当然第一入口就是发起请求。在HttpEngine.sendRequest()方法中可以看到Transport创建的身影Internal.instance.newTransport(connection, this);<br>通过跟踪,绕了大半圈回到Connection.newTransport方法:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"> /** Returns the transport appropriate for this connection. */</span><br><span class="line">Transport newTransport(HttpEngine httpEngine) throws IOException {</span><br><span class="line"> return (spdyConnection != null)</span><br><span class="line"> ? new SpdyTransport(httpEngine, spdyConnection)</span><br><span class="line"> : new HttpTransport(httpEngine, httpConnection);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这里是通过spdyConnection是否为空来作为判断依据。那好,继续跟踪这个域到底是怎么创建的:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">private void upgradeToTls(Request tunnelRequest, int readTimeout, int writeTimeout)</span><br><span class="line"> throws IOException {</span><br><span class="line"> Platform platform = Platform.get();</span><br><span class="line"> // ...省略.....</span><br><span class="line"> try {</span><br><span class="line"> // Force handshake. This can throw!</span><br><span class="line"> sslSocket.startHandshake();</span><br><span class="line"></span><br><span class="line"> String maybeProtocol;</span><br><span class="line"> if (route.connectionSpec.supportsTlsExtensions()</span><br><span class="line"> && (maybeProtocol = platform.getSelectedProtocol(sslSocket)) != null) {</span><br><span class="line"> protocol = Protocol.get(maybeProtocol); // Throws IOE on unknown.</span><br><span class="line"> }</span><br><span class="line"> } finally {</span><br><span class="line"> platform.afterHandshake(sslSocket);</span><br><span class="line"> }</span><br><span class="line"> // ...省略.....</span><br><span class="line"> if (protocol == Protocol.SPDY_3 || protocol == Protocol.HTTP_2) {</span><br><span class="line"> sslSocket.setSoTimeout(0); // SPDY timeouts are set per-stream.</span><br><span class="line"> spdyConnection = new SpdyConnection.Builder(route.address.getUriHost(), true, socket)</span><br><span class="line"> .protocol(protocol).build();</span><br><span class="line"> spdyConnection.sendConnectionPreface();</span><br><span class="line"> } else {</span><br><span class="line"> httpConnection = new HttpConnection(pool, this, socket);</span><br><span class="line"> }</span><br><span class="line"> // ...省略.....</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>中间的各种加密以及握手操作这里都省略,因为我们最想看到具体体现根据不同协议创建不同连接的地方。<br>另外特意贴出了一段关于Protocol的获取方法。通过强行发起握手,感知不同平台支持的协议。有兴趣同学可以更加深入了解一下源码内部给出的Android和JdkWithJettyBootPlatform这两个类。</p>
<p>upgradeToTls会被Connection.connect方法调起,而connect方法被上一节说连接缓存的connectAndSetOwner方法调用。这是不是就全部串联起来了呢?</p>
<p>由于visio密钥过期,导致没法画时序图,就采用下边的一个执行箭头表示吧:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">HttpEngine</span><br><span class="line">--> HttpEngine.sendRequest()</span><br><span class="line"> |--> HttpEngine.connect()</span><br><span class="line"> |--> HttpEngine.nextConnection()</span><br><span class="line"> | --> HttpEngine.createNextConnection()</span><br><span class="line"> | --> ConnectionPool.get(address)</span><br><span class="line"> | --> Connection.connectAndSetOwner()</span><br><span class="line"> | -->ConnectionPool.share()</span><br><span class="line"> | --> Connection.connect()</span><br><span class="line"> | --> Connection.upgradeToTls()</span><br><span class="line"> |--> Connection.newTransport()</span><br><span class="line"></span><br><span class="line">http1.x的reuse过程</span><br><span class="line">--> HttpEngine.releaseConnection()</span><br><span class="line"> --> HttpTransport.releaseConnectionOnIdle()</span><br><span class="line"> --> HttpConnection.poolOnIdle()</span><br><span class="line"> --> ConnectionPool.recycle()</span><br></pre></td></tr></table></figure></p>
<h2 id="关于http1-x和spdy协议的一些对比:"><a href="#关于http1-x和spdy协议的一些对比:" class="headerlink" title="关于http1.x和spdy协议的一些对比:"></a>关于http1.x和spdy协议的一些对比:</h2><p>SPDY(读作“SPeeDY”)是Google开发的基于TCP的应用层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验。<br>SPDY并不是一种用于替代HTTP的协议,而是对HTTP协议的增强。新协议的功能包括降低延迟、数据流的多路复用、请求优先级、HTTP报头压缩以及安全强制性使用 TLS。这个从上边的源码也能看到协议的选择就有判断。<br>详细的区别这里不做进一步讨论,后面有时间个人觉得还是有必要再多深入了解一下这方面实现。由于google的推动作用,现在http2.x的已经得到很多浏览器的支持。</p>
<p>在infoq上有一篇关于<a href="http://www.infoq.com/cn/news/2015/02/https-spdy-http2-comparison/" target="_blank" rel="external">HTTPS、SPDY和HTTP/2的性能比较</a>的文章。有需要的同学可以去看看吧。</p>
<p>因文章很快被人转载到一些其他网站,所以本人在此声明:<br><strong>转载请标明转载出处:<a href="http://frodoking.github.io/2015/06/29/android-okhttp-connectionpool-http1-x-http2-x/">http://frodoking.github.io/2015/06/29/android-okhttp-connectionpool-http1-x-http2-x/</a> </strong></p>
<p>距离上一次的<a href="/2015/03/12/android-okhttp/">OKHttp源码解析</a>过去快3月了。最近一直在忙工作上的事情,另外也再尝试一门新的语言Go。所以一直没花很多心思在Android这边。最近看到一些网友建议把okhttp的连接池对Connection的重用维护机制以及HTTP和SPDY协议如何得到区分这两个核心内容做深入的分析。<br>因此,这几天就打算好好说一说这块儿的实现方式。SPDY既是http1.x的增强版也是http2.x的过渡版本,虽然现在很多都直接切入到http2.0,不过SPDY的应用仍然值得关注。<br>
Retrofit源码解析
http://frodoking.github.io/2015/05/16/android-retrofit/
2015-05-16T03:59:24.000Z
2015-09-14T06:44:22.983Z
<p>之前花了一段时间整理过一篇文章<a href="/2015/03/12/android-okhttp/">OKHttp源码解析</a>。所以今天打算把一个包装工具Retrofit做一下源码解析。</p>
<p>Retrofit和Java领域的ORM概念类似,ORM把结构化数据转换为Java对象,而Retrofit把REST API返回的数据转化为Java对象方便操作。同时还封装了网络代码的调用。这个网络代码默认采用了OKHttp的方式。<br><a id="more"></a></p>
<h3 id="Retrofit使用"><a href="#Retrofit使用" class="headerlink" title="Retrofit使用"></a>Retrofit使用</h3><p>这一节主要使用源码内部的一个实例来展示Retrofit的使用<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">public class GitHubClient {</span><br><span class="line"> private static final String API_URL = "https://api.github.com";</span><br><span class="line"></span><br><span class="line"> static class Contributor {</span><br><span class="line"> String login;</span><br><span class="line"> int contributions;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> interface GitHub {</span><br><span class="line"> @GET("/repos/{owner}/{repo}/contributors")</span><br><span class="line"> List<Contributor> contributors(</span><br><span class="line"> @Path("owner") String owner,</span><br><span class="line"> @Path("repo") String repo</span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public static void main(String... args) {</span><br><span class="line"> // Create a very simple REST adapter which points the GitHub API endpoint.</span><br><span class="line"> RestAdapter restAdapter = new RestAdapter.Builder()</span><br><span class="line"> .setEndpoint(API_URL)</span><br><span class="line"> .build();</span><br><span class="line"></span><br><span class="line"> // Create an instance of our GitHub API interface.</span><br><span class="line"> GitHub github = restAdapter.create(GitHub.class);</span><br><span class="line"></span><br><span class="line"> // Fetch and print a list of the contributors to this library.</span><br><span class="line"> List<Contributor> contributors = github.contributors("square", "retrofit");</span><br><span class="line"> for (Contributor contributor : contributors) {</span><br><span class="line"> System.out.println(contributor.login + " (" + contributor.contributions + ")");</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<blockquote>
<ul>
<li>定义一个REST API接口。该接口定义了一个函数listRepos , 该函数会通过HTTP GET请求去访问服务器的/users/{user}/repos路径并把返回的结果封装为List<repo> Java对象返回。其中URL路径中的{user}的值为listRepos函数中的参数user的取值。然后通过RestAdapter类来生成一个GitHubService接口的实现;</repo></li>
<li>获取接口的实现,调用接口函数来和服务器交互;</li>
</ul>
</blockquote>
<h3 id="RestAdapter-Builder-构建器模式"><a href="#RestAdapter-Builder-构建器模式" class="headerlink" title="RestAdapter.Builder 构建器模式"></a>RestAdapter.Builder 构建器模式</h3><p>Builder模式主要出发点是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。<br>使用场景:经常在构造器中装配的域非常多、同时不同场景下需要初始化的域(或者传入的域)不一样的时候。这样的好处就是按需构造,非常灵活。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * Build a new {@link RestAdapter}.</span><br><span class="line"> * <p></span><br><span class="line"> * Calling {@link #setEndpoint} is required before calling {@link #build()}. All other methods</span><br><span class="line"> * are optional.</span><br><span class="line"> */</span><br><span class="line"> public static class Builder {</span><br><span class="line"> private Endpoint endpoint;</span><br><span class="line"> private OkHttpClient client;</span><br><span class="line"> private Executor callbackExecutor;</span><br><span class="line"> private RequestInterceptor requestInterceptor;</span><br><span class="line"> private Converter converter;</span><br><span class="line"> private ErrorHandler errorHandler;</span><br><span class="line"></span><br><span class="line"> // ....省略...</span><br><span class="line"> </span><br><span class="line"> /** Create the {@link RestAdapter} instances. */</span><br><span class="line"> public RestAdapter build() {</span><br><span class="line"> if (endpoint == null) {</span><br><span class="line"> throw new IllegalArgumentException("Endpoint may not be null.");</span><br><span class="line"> }</span><br><span class="line"> ensureSaneDefaults();</span><br><span class="line"> return new RestAdapter(endpoint, client, callbackExecutor, requestInterceptor, converter,</span><br><span class="line"> errorHandler);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> private void ensureSaneDefaults() {</span><br><span class="line"> if (converter == null) {</span><br><span class="line"> converter = Platform.get().defaultConverter();</span><br><span class="line"> }</span><br><span class="line"> if (client == null) {</span><br><span class="line"> client = Platform.get().defaultClient();</span><br><span class="line"> }</span><br><span class="line"> if (callbackExecutor == null) {</span><br><span class="line"> callbackExecutor = Platform.get().defaultCallbackExecutor();</span><br><span class="line"> }</span><br><span class="line"> if (errorHandler == null) {</span><br><span class="line"> errorHandler = ErrorHandler.DEFAULT;</span><br><span class="line"> }</span><br><span class="line"> if (requestInterceptor == null) {</span><br><span class="line"> requestInterceptor = RequestInterceptor.NONE;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p>在RestAdapter需要指定url根地址、采用的网络客户端、回调线程池、请求拦截器、返回数据格式器和错误处理。这些参数在Builder中得到了接管,不过值得注意的是RestAdapter不应该持有Builder(之前曾经看到过一些开发同学这样干过)。参数在在builder中都创建了默认值(默认自适应平台,默认返回数据JSON格式化,默认Error处理方式以及请求拦截器),默认值是提高代码健壮性的一中方式,这是一个非常好的习惯。留给使用的只需要指定endpoint就可以工作了。</p>
<h3 id="RestAdapter-create-代理模式"><a href="#RestAdapter-create-代理模式" class="headerlink" title="RestAdapter.create 代理模式"></a>RestAdapter.create 代理模式</h3><p>很多同学在开发中或多或少是遇到过代理,而实际使用我想肯定不多。感觉也不是那么好用。在上面的实例中,RestAdapter.create很好的展现出了java中对代理的支持与实现应用。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">public <T> T create(Class<T> service) {</span><br><span class="line"> Utils.validateServiceClass(service);</span><br><span class="line"> return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },</span><br><span class="line"> new RestHandler(getMethodInfoCache(service)));</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p>
<p>Proxy.newProxyInstance使用这里不做介绍,重点说说实现了InvocationHandler接口的RestHandler<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"> private class RestHandler implements InvocationHandler {</span><br><span class="line"> private final Map<Method, MethodInfo> methodDetailsCache;</span><br><span class="line"></span><br><span class="line"> RestHandler(Map<Method, MethodInfo> methodDetailsCache) {</span><br><span class="line"> this.methodDetailsCache = methodDetailsCache;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> @SuppressWarnings("unchecked") //</span><br><span class="line"> @Override public Object invoke(Object proxy, Method method, final Object[] args)</span><br><span class="line"> throws Throwable {</span><br><span class="line"> // If the method is a method from Object then defer to normal invocation.</span><br><span class="line"> if (method.getDeclaringClass() == Object.class) {</span><br><span class="line"> return method.invoke(this, args);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> MethodInfo methodInfo = getMethodInfo(methodDetailsCache, method);</span><br><span class="line"> Request request = createRequest(methodInfo, args);</span><br><span class="line"> switch (methodInfo.executionType) {</span><br><span class="line"> case SYNC:</span><br><span class="line"> return invokeSync(methodInfo, request);</span><br><span class="line"> case ASYNC:</span><br><span class="line"> invokeAsync(methodInfo, request, (Callback) args[args.length - 1]);</span><br><span class="line"> return null; // Async has void return type.</span><br><span class="line"> case RX:</span><br><span class="line"> return invokeRx(methodInfo, request);</span><br><span class="line"> default:</span><br><span class="line"> throw new IllegalStateException("Unknown response type: " + methodInfo.executionType);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>当调用起github.contributors(“square”, “retrofit”)这个方法的时候,会触发RestHandler的拦截。下面一步一步来看看在拦截的地方做了什么:<br>在第一步如果是构造器方法则返回应该不难理解,这里主要说方法的拆分与缓存(getMethodInfo(methodDetailsCache, method)):<br><strong>1、根据当前方法获取缓存中解析的方法信息,如果有就不用再去解析方法,反之重新创建方法信息</strong><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">static MethodInfo getMethodInfo(Map<Method, MethodInfo> cache, Method method) {</span><br><span class="line"> synchronized (cache) {</span><br><span class="line"> MethodInfo methodInfo = cache.get(method);</span><br><span class="line"> if (methodInfo == null) {</span><br><span class="line"> methodInfo = new MethodInfo(method);</span><br><span class="line"> cache.put(method, methodInfo);</span><br><span class="line"> }</span><br><span class="line"> return methodInfo;</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p>
<p><strong>2、根据当前方法解析方法信息</strong><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br></pre></td><td class="code"><pre><span class="line">final class MethodInfo {</span><br><span class="line"></span><br><span class="line"> enum ExecutionType {</span><br><span class="line"> ASYNC,</span><br><span class="line"> RX,</span><br><span class="line"> SYNC</span><br><span class="line"> }</span><br><span class="line"> enum RequestType {</span><br><span class="line"> /** No content-specific logic required. */</span><br><span class="line"> SIMPLE,</span><br><span class="line"> /** Multi-part request body. */</span><br><span class="line"> MULTIPART,</span><br><span class="line"> /** Form URL-encoded request body. */</span><br><span class="line"> FORM_URL_ENCODED</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> MethodInfo(Method method) {</span><br><span class="line"> this.method = method;</span><br><span class="line"> executionType = parseResponseType();</span><br><span class="line"></span><br><span class="line"> parseMethodAnnotations();</span><br><span class="line"> parseParameters();</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> /** Loads {@link #responseObjectType}. */</span><br><span class="line"> private ExecutionType parseResponseType() {</span><br><span class="line"> // Synchronous methods have a non-void return type.</span><br><span class="line"> // Observable methods have a return type of Observable.</span><br><span class="line"> Type returnType = method.getGenericReturnType();</span><br><span class="line"></span><br><span class="line"> // Asynchronous methods should have a Callback type as the last argument.</span><br><span class="line"> Type lastArgType = null;</span><br><span class="line"> Class<?> lastArgClass = null;</span><br><span class="line"> Type[] parameterTypes = method.getGenericParameterTypes();</span><br><span class="line"> if (parameterTypes.length > 0) {</span><br><span class="line"> Type typeToCheck = parameterTypes[parameterTypes.length - 1];</span><br><span class="line"> lastArgType = typeToCheck;</span><br><span class="line"> if (typeToCheck instanceof ParameterizedType) {</span><br><span class="line"> typeToCheck = ((ParameterizedType) typeToCheck).getRawType();</span><br><span class="line"> }</span><br><span class="line"> if (typeToCheck instanceof Class) {</span><br><span class="line"> lastArgClass = (Class<?>) typeToCheck;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> boolean hasReturnType = returnType != void.class;</span><br><span class="line"> boolean hasCallback = lastArgClass != null && Callback.class.isAssignableFrom(lastArgClass);</span><br><span class="line"></span><br><span class="line"> // Check for invalid configurations.</span><br><span class="line"> if (hasReturnType && hasCallback) {</span><br><span class="line"> throw methodError("Must have return type or Callback as last argument, not both.");</span><br><span class="line"> }</span><br><span class="line"> if (!hasReturnType && !hasCallback) {</span><br><span class="line"> throw methodError("Must have either a return type or Callback as last argument.");</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> if (hasReturnType) {</span><br><span class="line"> if (Platform.HAS_RX_JAVA) {</span><br><span class="line"> Class rawReturnType = Types.getRawType(returnType);</span><br><span class="line"> if (RxSupport.isObservable(rawReturnType)) {</span><br><span class="line"> returnType = RxSupport.getObservableType(returnType, rawReturnType);</span><br><span class="line"> responseObjectType = getParameterUpperBound((ParameterizedType) returnType);</span><br><span class="line"> return ExecutionType.RX;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> responseObjectType = returnType;</span><br><span class="line"> return ExecutionType.SYNC;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> lastArgType = Types.getSupertype(lastArgType, Types.getRawType(lastArgType), Callback.class);</span><br><span class="line"> if (lastArgType instanceof ParameterizedType) {</span><br><span class="line"> responseObjectType = getParameterUpperBound((ParameterizedType) lastArgType);</span><br><span class="line"> return ExecutionType.ASYNC;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> throw methodError("Last parameter must be of type Callback<X> or Callback<? super X>.");</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> /** Loads {@link #requestMethod} and {@link #requestType}. */</span><br><span class="line"> private void parseMethodAnnotations() {</span><br><span class="line"> for (Annotation methodAnnotation : method.getAnnotations()) {</span><br><span class="line"> Class<? extends Annotation> annotationType = methodAnnotation.annotationType();</span><br><span class="line"> if (annotationType == DELETE.class) {</span><br><span class="line"> parseHttpMethodAndPath("DELETE", ((DELETE) methodAnnotation).value(), false);</span><br><span class="line"> } else if (annotationType == GET.class) {</span><br><span class="line"> parseHttpMethodAndPath("GET", ((GET) methodAnnotation).value(), false);</span><br><span class="line"> } else if (annotationType == HEAD.class) {</span><br><span class="line"> parseHttpMethodAndPath("HEAD", ((HEAD) methodAnnotation).value(), false);</span><br><span class="line"> } else if (annotationType == PATCH.class) {</span><br><span class="line"> parseHttpMethodAndPath("PATCH", ((PATCH) methodAnnotation).value(), true);</span><br><span class="line"> } else if (annotationType == POST.class) {</span><br><span class="line"> parseHttpMethodAndPath("POST", ((POST) methodAnnotation).value(), true);</span><br><span class="line"> } else if (annotationType == PUT.class) {</span><br><span class="line"> parseHttpMethodAndPath("PUT", ((PUT) methodAnnotation).value(), true);</span><br><span class="line"> } else if (annotationType == HTTP.class) {</span><br><span class="line"> HTTP http = (HTTP) methodAnnotation;</span><br><span class="line"> parseHttpMethodAndPath(http.method(), http.path(), http.hasBody());</span><br><span class="line"> } else if (annotationType == Headers.class) {</span><br><span class="line"> String[] headersToParse = ((Headers) methodAnnotation).value();</span><br><span class="line"> if (headersToParse.length == 0) {</span><br><span class="line"> throw methodError("@Headers annotation is empty.");</span><br><span class="line"> }</span><br><span class="line"> headers = parseHeaders(headersToParse);</span><br><span class="line"> } else if (annotationType == Multipart.class) {</span><br><span class="line"> if (requestType != RequestType.SIMPLE) {</span><br><span class="line"> throw methodError("Only one encoding annotation is allowed.");</span><br><span class="line"> }</span><br><span class="line"> throw new UnsupportedOperationException("Multipart shall return!");</span><br><span class="line"> //requestType = RequestType.MULTIPART;</span><br><span class="line"> } else if (annotationType == FormUrlEncoded.class) {</span><br><span class="line"> if (requestType != RequestType.SIMPLE) {</span><br><span class="line"> throw methodError("Only one encoding annotation is allowed.");</span><br><span class="line"> }</span><br><span class="line"> throw new UnsupportedOperationException("Form URL encoding shall return!");</span><br><span class="line"> //requestType = RequestType.FORM_URL_ENCODED;</span><br><span class="line"> } else if (annotationType == Streaming.class) {</span><br><span class="line"> if (responseObjectType != Response.class) {</span><br><span class="line"> throw methodError(</span><br><span class="line"> "Only methods having %s as data type are allowed to have @%s annotation.",</span><br><span class="line"> Response.class.getSimpleName(), Streaming.class.getSimpleName());</span><br><span class="line"> }</span><br><span class="line"> isStreaming = true;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> if (requestMethod == null) {</span><br><span class="line"> throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");</span><br><span class="line"> }</span><br><span class="line"> if (!requestHasBody) {</span><br><span class="line"> if (requestType == RequestType.MULTIPART) {</span><br><span class="line"> throw methodError(</span><br><span class="line"> "Multipart can only be specified on HTTP methods with request body (e.g., @POST).");</span><br><span class="line"> }</span><br><span class="line"> if (requestType == RequestType.FORM_URL_ENCODED) {</span><br><span class="line"> throw methodError("FormUrlEncoded can only be specified on HTTP methods with request body "</span><br><span class="line"> + "(e.g., @POST).");</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> /**</span><br><span class="line"> * Loads {@link #requestParamAnnotations}. Must be called after {@link #parseMethodAnnotations()}.</span><br><span class="line"> */</span><br><span class="line"> private void parseParameters() {</span><br><span class="line"> Type[] methodParameterTypes = method.getGenericParameterTypes();</span><br><span class="line"></span><br><span class="line"> Annotation[][] methodParameterAnnotationArrays = method.getParameterAnnotations();</span><br><span class="line"> int count = methodParameterAnnotationArrays.length;</span><br><span class="line"> if (executionType == ExecutionType.ASYNC) {</span><br><span class="line"> count -= 1; // Callback is last argument when not a synchronous method.</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> Annotation[] requestParamAnnotations = new Annotation[count];</span><br><span class="line"></span><br><span class="line"> boolean gotField = false;</span><br><span class="line"> boolean gotPart = false;</span><br><span class="line"> boolean gotBody = false;</span><br><span class="line"></span><br><span class="line"> for (int i = 0; i < count; i++) {</span><br><span class="line"> Type methodParameterType = methodParameterTypes[i];</span><br><span class="line"> Annotation[] methodParameterAnnotations = methodParameterAnnotationArrays[i];</span><br><span class="line"> if (methodParameterAnnotations != null) {</span><br><span class="line"> for (Annotation methodParameterAnnotation : methodParameterAnnotations) {</span><br><span class="line"> Class<? extends Annotation> methodAnnotationType =</span><br><span class="line"> methodParameterAnnotation.annotationType();</span><br><span class="line"></span><br><span class="line"> if (methodAnnotationType == Path.class) {</span><br><span class="line"> String name = ((Path) methodParameterAnnotation).value();</span><br><span class="line"> validatePathName(i, name);</span><br><span class="line"> } else if (methodAnnotationType == Query.class) {</span><br><span class="line"> // Nothing to do.</span><br><span class="line"> } else if (methodAnnotationType == QueryMap.class) {</span><br><span class="line"> if (!Map.class.isAssignableFrom(Types.getRawType(methodParameterType))) {</span><br><span class="line"> throw parameterError(i, "@QueryMap parameter type must be Map.");</span><br><span class="line"> }</span><br><span class="line"> } else if (methodAnnotationType == Header.class) {</span><br><span class="line"> // Nothing to do.</span><br><span class="line"> } else if (methodAnnotationType == Field.class) {</span><br><span class="line"> if (requestType != RequestType.FORM_URL_ENCODED) {</span><br><span class="line"> throw parameterError(i, "@Field parameters can only be used with form encoding.");</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> gotField = true;</span><br><span class="line"> } else if (methodAnnotationType == FieldMap.class) {</span><br><span class="line"> if (requestType != RequestType.FORM_URL_ENCODED) {</span><br><span class="line"> throw parameterError(i, "@FieldMap parameters can only be used with form encoding.");</span><br><span class="line"> }</span><br><span class="line"> if (!Map.class.isAssignableFrom(Types.getRawType(methodParameterType))) {</span><br><span class="line"> throw parameterError(i, "@FieldMap parameter type must be Map.");</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> gotField = true;</span><br><span class="line"> } else if (methodAnnotationType == Part.class) {</span><br><span class="line"> if (requestType != RequestType.MULTIPART) {</span><br><span class="line"> throw parameterError(i, "@Part parameters can only be used with multipart encoding.");</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> gotPart = true;</span><br><span class="line"> } else if (methodAnnotationType == PartMap.class) {</span><br><span class="line"> if (requestType != RequestType.MULTIPART) {</span><br><span class="line"> throw parameterError(i,</span><br><span class="line"> "@PartMap parameters can only be used with multipart encoding.");</span><br><span class="line"> }</span><br><span class="line"> if (!Map.class.isAssignableFrom(Types.getRawType(methodParameterType))) {</span><br><span class="line"> throw parameterError(i, "@PartMap parameter type must be Map.");</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> gotPart = true;</span><br><span class="line"> } else if (methodAnnotationType == Body.class) {</span><br><span class="line"> if (requestType != RequestType.SIMPLE) {</span><br><span class="line"> throw parameterError(i,</span><br><span class="line"> "@Body parameters cannot be used with form or multi-part encoding.");</span><br><span class="line"> }</span><br><span class="line"> if (gotBody) {</span><br><span class="line"> throw methodError("Multiple @Body method annotations found.");</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> requestObjectType = methodParameterType;</span><br><span class="line"> gotBody = true;</span><br><span class="line"> } else {</span><br><span class="line"> // This is a non-Retrofit annotation. Skip to the next one.</span><br><span class="line"> continue;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> if (requestParamAnnotations[i] != null) {</span><br><span class="line"> throw parameterError(i,</span><br><span class="line"> "Multiple Retrofit annotations found, only one allowed: @%s, @%s.",</span><br><span class="line"> requestParamAnnotations[i].annotationType().getSimpleName(),</span><br><span class="line"> methodAnnotationType.getSimpleName());</span><br><span class="line"> }</span><br><span class="line"> requestParamAnnotations[i] = methodParameterAnnotation;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> if (requestParamAnnotations[i] == null) {</span><br><span class="line"> throw parameterError(i, "No Retrofit annotation found.");</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> if (requestType == RequestType.SIMPLE && !requestHasBody && gotBody) {</span><br><span class="line"> throw methodError("Non-body HTTP method cannot contain @Body or @TypedOutput.");</span><br><span class="line"> }</span><br><span class="line"> if (requestType == RequestType.FORM_URL_ENCODED && !gotField) {</span><br><span class="line"> throw methodError("Form-encoded method must contain at least one @Field.");</span><br><span class="line"> }</span><br><span class="line"> if (requestType == RequestType.MULTIPART && !gotPart) {</span><br><span class="line"> throw methodError("Multipart method must contain at least one @Part.");</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> this.requestParamAnnotations = requestParamAnnotations;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>这一段代码有点多,本打算只截取最重要的地方,不过后来发现还是全部展示出来最有好的说服力。<br>在最开始实例中定义了一个API接口,采用了Annotation注解的方式定义了每一个网络请求的方式(GET/POST,相对路径,在路径中的请求参数,方法参数中的请求参数)。</p>
<blockquote>
<ol>
<li>解析执行类型,在这里代码采用了检测最后一个参数是否是Callback类型做判断,如果最后一个是Callback类型参数,那么采用异步的方式,反之采用同步。另外在JAVA平台的时候还会根据返回类型来判断是否符合RX方式。</li>
<li>解析Annotation注解(重点解析GET/POST,相对路径,在路径中的请求参数)。标签非常的多(GET,POST,PUT,DELETE和HEAD),主要是指定每一个作用域的意图。更值得一提的是在相对路径中可以采用“xxx?a=1&b=2”的方式带入参数,也可以使用@Path,@Query,@Body,@Field来表示。</li>
<li>在请求参数的解析方法中,根据这些不同的标注来返回当前的请求方式是普通请求、multi-part还是form形式。</li>
</ol>
</blockquote>
<p>在最后的invoke调用中只需要根据当前方法拿到解析出来的方法信息执行对应的网络请求即可。</p>
<blockquote>
<p>1.同步直接返回数据<br>2.异步这加入异步调用队列,采用Callback返回<br>3.RX方式(省略…)</p>
</blockquote>
<p>专门再看一下Retrofit源码,主要是个人觉得采用了注解+反射代理的方式可以非常灵活的将复杂的业务逻辑进行拆分解耦。同时从代码结构上来看也会非常的清晰。这都是值得开发者学习的地方。</p>
<p>之前花了一段时间整理过一篇文章<a href="/2015/03/12/android-okhttp/">OKHttp源码解析</a>。所以今天打算把一个包装工具Retrofit做一下源码解析。</p>
<p>Retrofit和Java领域的ORM概念类似,ORM把结构化数据转换为Java对象,而Retrofit把REST API返回的数据转化为Java对象方便操作。同时还封装了网络代码的调用。这个网络代码默认采用了OKHttp的方式。<br>
从Eclipse到Intellij
http://frodoking.github.io/2015/05/10/ide-from-eclipse-to-intellij/
2015-05-10T13:39:23.000Z
2015-09-14T06:51:32.566Z
<p>由于本人最近项目做过一次从eclipse到intellij切换的分享会,本来打算把相关的技术文档发给组员就可以完事。但是发现很多同学在切换的过程中仍然感觉到很难受,所以我打算把这次分享会的内容以文章的形式呈现出来。给更多的同学使用。</p>
<p>此文章是个人工作中的一次总结,当看到网上很多ctrl+c,ctrl+v的文章的时候。个人觉得还不如自己总结一下整个流程来得真实。这也是写本文章的一个动机。</p>
<h2 id="前提"><a href="#前提" class="headerlink" title="前提"></a>前提</h2><p>在我们公司将软件工程师简称RD(Research Development)。所以我们从事着一份富有创造力和探究精神的职业。我们秉承软件工程师 ≠ 码农的职业路线,在科学技术的道路上不断探索、创造和前行着。我相信跟我同样职业的大部分人都有着自己“高傲”的一面,遇到问题解决问题以及不服的劲头经常出现。我是一个有着代码洁癖的人,以前遇到一个很好的架构师对方法和{}之间的空隙都有着严格的要求。当时的我是那么的不理解,但是当代码量越来越大的时候,好的编码格式就体现的是那么的明显。工作中好的东西需要我们有敢于尝试的勇气,这样对于我们提升自己我驱动的效率会高很多。所以很希望同行的同学们都保持一种高质量的创造水准,一起同行吧。。。<br><a id="more"></a><br>这里扯的有点多,不过也正是因为有问题的出现,新的探究就会萌芽。最后发展推广到成熟与运用。</p>
<p>文章主要侧重点是Android studio,不过个人更愿意大家能接触到intellij IDEA这个编译器上。</p>
<h2 id="为何要做迁移?"><a href="#为何要做迁移?" class="headerlink" title="为何要做迁移?"></a>为何要做迁移?</h2><p><strong>不得不说的Intellij</strong></p>
<p>intellij号称是最专业的IDE工具。下面这些总结都是本人在实际工作中深有体会的一些值得说明的优点:</p>
<blockquote>
<ol>
<li>自动完成的特性(由于非常快速的索引,这种支持速度要快上几个数量级)</li>
<li>强大的插件化工具集成(例如像版本控制git,svn)</li>
<li>代码的检测功能(自动检测unused/null,代码可精简写法,多个try catch的合并提示,String的拼接自动提示,toString的warp包装提示)</li>
<li>可以根据选择的地方来判断新建文件类型(同时还提供不同模板,这一条eclipse绝对是做不到的)</li>
<li>IntelliJ的调试器(“Smart step into”,如果一行中存在多个方法调用,我就可以选择进入到哪个方法中了)</li>
<li>重构功能(Android特定重构和快速修复)</li>
<li>工程目录与maven更相符合</li>
<li>更加强大的快捷键(alt+enter)</li>
<li>show history功能对每一个本地操作都可以追溯到</li>
<li>terminal 集成</li>
<li>个人非常喜欢黑色经典主题Darcula(语法高亮)</li>
<li>学习成本,其实IntelliJ是要比Eclipse低的,至少省去了很多配置插件、理清依赖、处理问题的功夫,同时设置也比Eclipse要简单不少</li>
</ol>
</blockquote>
<p><strong>Android studio独有特性</strong></p>
<blockquote>
<ol>
<li>采用最流行的Gradle构建工程(支持ProGuard和应用签名功能)</li>
<li>提示(比如左侧的图片、颜色和标签,右侧的警告)</li>
<li>UI设计(自带布局编辑器,text与实时的设计界面效果review,还能针对不同屏幕尺寸做效果切换,自带布局编辑器,可以让你拖放UI组件,并在多个屏幕配置上预览布局,等等。)</li>
<li>提示工具更好地对程序性能、可用性、版本兼容性和其问题进行版本捕捉;</li>
<li>analyze inspecting code (lint 去除无用资源)</li>
</ol>
</blockquote>
<h2 id="如何做迁移?"><a href="#如何做迁移?" class="headerlink" title="如何做迁移?"></a>如何做迁移?</h2><p>下面侧重讲一下Android studio如何做迁移这个事情</p>
<blockquote>
<ul>
<li>导入工程,直接import</li>
<li>新建工程,直接New Project,或者 New Library Project</li>
<li>如果说工程中存在*iml文件(这样的文件类似于 .classpath文件),后续直接open就ok</li>
</ul>
</blockquote>
<p><strong>自动化工具的演进</strong></p>
<blockquote>
<ol>
<li>make</li>
<li>ant Ant是第一个“现代”构建工具,主要的不足是用XML作为脚本编写格式。 XML,本质上是层次化的,并不能很好地贴合Ant过程化编程的初衷。Ant的另外一个问题是,除非是很小的项目,否则它的XML文件很快就大得无法管理。Ant的主要优点在于对构建过程的控制上。</li>
<li>maven Maven具备从网络上自动下载依赖的能力(Ant后来通过Ivy也具备了这个功能),这一点革命性地改变了我们开发软件的方式。但是,依赖管理不能很好地处理相同库文件不同版本之间的冲突</li>
<li>gradle 它具有Ant的强大和灵活,又有Maven的生命周期管理且易于使用。Gradle样板文件的代码很少,这是因为它的DSL被设计用于解决特定的问题:贯穿软件的生命周期,从编译,到静态检查,到测试,直到打包和部署。</li>
</ol>
</blockquote>
<p><strong>Gradle</strong><br>Gradle是以Groovy为基础,面向java应用,基于DSL(Domain Specific Language)语法的自动化构建工具。是Google引入,替换ant和maven的新工具,其依赖兼容maven(mavenCentral())和ivy(jcenter())。<br>使用gradle的目的:</p>
<blockquote>
<ul>
<li>更容易重用资源和代码</li>
<li>可以更容易创建不同的版本的程序,多个类型的apk包</li>
<li>更容易配置,扩展</li>
<li>更好的IDE集成</li>
</ul>
</blockquote>
<p><strong>Android Studio</strong><br>从工程的整体结构上来讲,Android studio采用了gradle工具来构建整个工程。脚本主要涵盖了如下五个方面:</p>
<blockquote>
<ul>
<li>Properties</li>
<li>Signing</li>
<li>Flavors</li>
<li>Build Types</li>
<li>Dependencies</li>
</ul>
</blockquote>
<p><strong>项目构成</strong><br>在最外层构建一个公共脚本,包括一些基本的构建工具版本信息和整个项目的编码方式等等。下边这段脚本是本人在项目中的运用:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">ext {</span><br><span class="line"> compileSdkVersion = 21</span><br><span class="line"> buildToolsVersion = System.env.CUSTOM_BUILDTOOLS != null ? System.env.CUSTOM_BUILDTOOLS : '21.1.2'</span><br><span class="line">}</span><br><span class="line">buildscript {</span><br><span class="line"> def gradleVersion = System.env.CUSTOM_GRADLE != null ? System.env.CUSTOM_GRADLE : '1.0.1'</span><br><span class="line"> repositories {</span><br><span class="line"> if (System.env.CUSTOM_REPO != null) {</span><br><span class="line"> maven { url System.env.CUSTOM_REPO }</span><br><span class="line"> } else {</span><br><span class="line"> mavenCentral()</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> dependencies {</span><br><span class="line"> classpath "com.android.tools.build:gradle:$gradleVersion"</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">allprojects {</span><br><span class="line">repositories {</span><br><span class="line"> if (System.env.CUSTOM_REPO != null) {</span><br><span class="line"> maven { url System.env.CUSTOM_REPO }</span><br><span class="line"> } else {</span><br><span class="line"> mavenCentral()</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> tasks.withType(JavaCompile) {</span><br><span class="line"> options.encoding = "UTF-8"</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">apply plugin: 'android-reporting'</span><br></pre></td></tr></table></figure></p>
<p>ext主要是定义工程非正式属性,像编译工程使用的sdk版本,构建工具gradle版本。<br>buildscript主要是定义项目使用的库地址,这里一般有两种选择mavenCentral()和jcenter()。</p>
<p><strong>Libray工程构建</strong><br>Library工程只需要简单配置:</p>
<blockquote>
<ol>
<li>插件</li>
<li>构建工具</li>
<li>依赖(可以添加本地jar、也可以添加远程依赖jar、还可以添加library工程)</li>
</ol>
</blockquote>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">apply plugin: 'com.android.library'</span><br><span class="line"></span><br><span class="line">android {</span><br><span class="line"> compileSdkVersion rootProject.ext.compileSdkVersion</span><br><span class="line"> buildToolsVersion rootProject.ext.buildToolsVersion</span><br><span class="line"></span><br><span class="line"> defaultConfig {</span><br><span class="line"> minSdkVersion 8</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">dependencies {</span><br><span class="line"> compile fileTree(dir: 'libs', include: ['*.jar'])</span><br><span class="line"> compile 'com.google.code.gson:gson:2.3.1'</span><br><span class="line"> compile project(":tuancorelibrary")</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><strong>App工程</strong><br>在app工程中比较重要的是对android属性的配置<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">defaultConfig {</span><br><span class="line"> applicationId "com.baidu.nuomi.sale"</span><br><span class="line"> minSdkVersion 8</span><br><span class="line"> targetSdkVersion 21</span><br><span class="line"> versionCode 15</span><br><span class="line"> versionName "2.0.5"</span><br><span class="line"> multiDexEnabled false</span><br><span class="line"></span><br><span class="line"> ndk {</span><br><span class="line"> moduleName "MyJNI"</span><br><span class="line"> ldLibs "log", "z", "m"</span><br><span class="line"> abiFilters "armeabi", "mips", "x86"</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p>
<p>ndk是很多同学容易搞不懂的地方。其实这个地方主要是配置我们工程下src->main->jni下的c代码打包成so并随着apk发布。<br>另外,第三方的so包应该放在src->main->jniLibs 下面的armeabi、x86对应文件夹中,这样就可以随着apk发布了。</p>
<p>签名与混淆<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">signingConfigs {</span><br><span class="line"> debug {</span><br><span class="line"> }</span><br><span class="line"> release {</span><br><span class="line"> storeFile file("androidkey.keystore")</span><br><span class="line"> storePassword "Showmethemoney"</span><br><span class="line"> keyAlias "bnsale"</span><br><span class="line"> keyPassword "Showmethemoney"</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">buildTypes {</span><br><span class="line"> release {</span><br><span class="line"> signingConfig signingConfigs.release</span><br><span class="line"> minifyEnabled true //实现了自动清除无用资源的功能,还能够移除项目中引用的libraries的无用资源</span><br><span class="line"> zipAlignEnabled true</span><br><span class="line"> shrinkResources true</span><br><span class="line"> buildConfigField "boolean", "ISDEBUG", "false"</span><br><span class="line"> proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’ //混淆</span><br><span class="line"> }</span><br><span class="line"> debug {</span><br><span class="line"> shrinkResources false</span><br><span class="line"> signingConfig signingConfigs.debug</span><br><span class="line"> buildConfigField "boolean", "ISDEBUG", "true"</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>多渠道打包<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">productFlavors { //就是可定义的产品特性</span><br><span class="line"> wandoujia {</span><br><span class="line"> }</span><br><span class="line"> 91 {</span><br><span class="line"> }</span><br><span class="line"> 360 {</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">productFlavors.all { flavor -> //此处name 就是flavor中定义的sale_rd。此处是一个遍历所有特性替换渠道号</span><br><span class="line"> flavor.manifestPlaceholders = [BaiduMobAd_CHANNEL_VALUE: name] </span><br><span class="line"> //百度统计中采用这样的方式处理 android:value="${BaiduMobAd_CHANNEL_VALUE}"</span><br><span class="line">}</span><br><span class="line">applicationVariants.all { variant -></span><br><span class="line"> def Date currentTime = new Date();</span><br><span class="line"> def java.text.SimpleDateFormat formatter = new java.text.SimpleDateFormat("yyyyMMddHHmmss");</span><br><span class="line"> def String dateString = formatter.format(currentTime);</span><br><span class="line"> variant.outputs.each { output -> //为每个不同的输出包重新打包,修改名称,可以采用java的方式写</span><br><span class="line"> def String tmpName</span><br><span class="line"> def String fileName</span><br><span class="line"> if (variant.name.contains("Release")) {</span><br><span class="line"> tmpName = variant.name.replace("Release", "");</span><br><span class="line"> fileName = "saleapp_${tmpName}_${defaultConfig.versionName}_${dateString}_release";</span><br><span class="line"> } else {</span><br><span class="line"> tmpName = variant.name.replace("Debug", "");</span><br><span class="line"> fileName = "saleapp_${tmpName}_${defaultConfig.versionName}_${dateString}_debug";</span><br><span class="line"> }</span><br><span class="line"> output.outputFile = file("$project.buildDir/${fileName}.apk")</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>由于脚本采用了groovy,所以内部可以使用java的代码。在上面这段代码中本人重点用了Date来对apk重新定义,这一点在多渠道打包中是非常有用的。</p>
<h2 id="Gradle自定义Task"><a href="#Gradle自定义Task" class="headerlink" title="Gradle自定义Task"></a>Gradle自定义Task</h2><p>由于我们在开发中经常遇到一些不常见的需求,那么我们需要自定义一些Task来达到我们的需求</p>
<p>例如项目还无法完全迁移的时候,我们可以采用ant引用原始打包方式来执行打包。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ant.importBuild ‘build.xml’ //使用ant原始打包方式</span><br></pre></td></tr></table></figure></p>
<p>自定义任务<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">task releaseJar(type: Copy) { //使用task重命名包</span><br><span class="line"> from( 'build/bundles/release') </span><br><span class="line"> into( 'build/libs') </span><br><span class="line"> include('classes.jar') </span><br><span class="line"> rename('calsses.jar', 'superlog' + VERSION_NAME + '.jar') </span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>调取系统命令行做更多事情<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">task testJavaExec(type: JavaExec){ //使用java 命令执行main方法</span><br><span class="line"> main = 'Hello'</span><br><span class="line"> args '-d',"build"</span><br><span class="line"> args '-p', "nice"</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<h2 id="键位映射"><a href="#键位映射" class="headerlink" title="键位映射"></a>键位映射</h2><p>下面列出了一些个人认为比较使用的一些快捷键。很多同学说自己去改这个key-map,不过个人不是很认同,当你改了过后你会发现你越改越乱。</p>
<blockquote>
<ol>
<li>高亮所有相同变量ctrl+shift+F7</li>
<li>在方法和内部类之间跳转<br>alt+↑/↓</li>
<li>类文件结构弹窗<br>ctrl+F12</li>
<li>方法调用层级弹窗<br>ctrl+alt+H</li>
<li>定义快速查找<br>ctrl+shift+i(能看到当前代码快的快照)</li>
<li>收缩或者展开代码快<br>ctrl+shift+ +/-</li>
<li>书签,保存你的代码现场<br>F11</li>
<li>带字母或者数字的标签<br>ctrl+F11</li>
<li>展开标签<br>shift+F11</li>
<li>查找菜单选项<br>ctrl+shift+A</li>
<li>代码行级移动<br>alt+shift+↑/↓</li>
<li>删除行<br>ctrl+Y</li>
<li>行复制<br>ctrl+D</li>
<li>扩大或者缩小选择范围<br>ctrl+W/ctrl+shift+W</li>
<li>包裹代码段<br>ctrl+alt+T</li>
<li>查询最近编辑文件<br>ctrl+E</li>
<li>代码模块<br>ctrl+J (自动生成像单例一样的简单代码块)</li>
<li>方法整体移动<br>ctrl+shift+↑/↓</li>
<li>代码补全<br>ctrl+shift+Enter</li>
<li>回到上一次最后编辑位置<br>ctrl+shift+backspace</li>
<li>代码行合并<br>ctrl+shift+J</li>
<li>操作当前文件信息<br>alt+F1</li>
<li>移除包裹代码 (移除代码结构中的包裹代码,比如 if 语句, while 循环, 或者 try/catch 语句)<br>ctrl+shift+delete </li>
</ol>
</blockquote>
<h2 id="Intellij-Gradle可以做得更多"><a href="#Intellij-Gradle可以做得更多" class="headerlink" title="Intellij + Gradle可以做得更多"></a>Intellij + Gradle可以做得更多</h2><p>笔者之前做了一个后台服务器的项目,发现gradle在项目管理上是如此的得心应手。所以单独拿一个section来说说Intellij和Gradle结合的另一些好的地方</p>
<ol>
<li>多环境配置<br>先抛开studio这个编译器,在正常的开发中一般都会涉及到开发环境和上线环境。<br>那么我们用gradle是如何做到这件事情的呢?来看一下本人自己的一段脚本:<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">def env = System.getProperty("env") ?: "dev"</span><br><span class="line">sourceSets {</span><br><span class="line"> main {</span><br><span class="line"> output.resourcesDir = "${buildDir}/classes/main"</span><br><span class="line"> java {</span><br><span class="line"> resources {</span><br><span class="line"> srcDirs = ["src/main/resources/public", "src/main/resources/$env"]</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
</li>
</ol>
<p>此段脚本可以很灵活的切换当前环境是dev还是publish(这里是dev的)。</p>
<ol>
<li><p>强大的插件化友好支持<br>做服务器的同学应该对war包非常的熟悉。如果在eclipse上直接运行工程的话需要配置需要运行服务器的插件,而这个配置过程其实是一个很痛苦的过程。而使用如下的方式将让我们从繁琐的配置中解脱出来:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">apply plugin: 'java'</span><br><span class="line">apply plugin: 'groovy'</span><br><span class="line">apply plugin: 'war'</span><br><span class="line">apply plugin: 'jetty'</span><br></pre></td></tr></table></figure>
</li>
<li><p>脚本整理<br>当工程越来越复杂的时候,脚本规整是一件很重要的事情。gradle中可以使用List来规整包依赖。可以使用apply调用多个gradle脚本文件达到拆分脚本的目的。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"> List spring = ['org.springframework:spring-jdbc:4.1.3.RELEASE',</span><br><span class="line"> 'org.springframework:spring-context-support:4.1.3.RELEASE']</span><br><span class="line">List utils = ['org.tuckey:urlrewritefilter:4.0.4',</span><br><span class="line"> 'org.apache.httpcomponents:httpclient:4.3.6',</span><br><span class="line"> 'com.googlecode.xmemcached:xmemcached:2.0.0',</span><br><span class="line"> 'org.aspectj:aspectjtools:1.8.4',</span><br><span class="line"> 'org.freemarker:freemarker:2.3.21',</span><br><span class="line"></span><br><span class="line"> 'c3p0:c3p0:0.9.1.2']</span><br><span class="line"></span><br><span class="line">dependencies {</span><br><span class="line"> compile project(':core')</span><br><span class="line"> providedCompile 'javax.servlet:servlet-api:2.5'</span><br><span class="line"></span><br><span class="line"> runtime 'javax.servlet:jstl:1.2'</span><br><span class="line"> compile spring, utils</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">apply "custom_build.gradle"</span><br></pre></td></tr></table></figure>
</li>
</ol>
<p>笔者看过spring的源码已经切入采用gradle构建,不过脚本的书写还不够专业。很多冗长的书写方式看起来非常的早够。也希望使用的同学注意自己的脚本质量。</p>
<p>由于本人最近项目做过一次从eclipse到intellij切换的分享会,本来打算把相关的技术文档发给组员就可以完事。但是发现很多同学在切换的过程中仍然感觉到很难受,所以我打算把这次分享会的内容以文章的形式呈现出来。给更多的同学使用。</p>
<p>此文章是个人工作中的一次总结,当看到网上很多ctrl+c,ctrl+v的文章的时候。个人觉得还不如自己总结一下整个流程来得真实。这也是写本文章的一个动机。</p>
<h2 id="前提"><a href="#前提" class="headerlink" title="前提"></a>前提</h2><p>在我们公司将软件工程师简称RD(Research Development)。所以我们从事着一份富有创造力和探究精神的职业。我们秉承软件工程师 ≠ 码农的职业路线,在科学技术的道路上不断探索、创造和前行着。我相信跟我同样职业的大部分人都有着自己“高傲”的一面,遇到问题解决问题以及不服的劲头经常出现。我是一个有着代码洁癖的人,以前遇到一个很好的架构师对方法和{}之间的空隙都有着严格的要求。当时的我是那么的不理解,但是当代码量越来越大的时候,好的编码格式就体现的是那么的明显。工作中好的东西需要我们有敢于尝试的勇气,这样对于我们提升自己我驱动的效率会高很多。所以很希望同行的同学们都保持一种高质量的创造水准,一起同行吧。。。<br>
事件总线 —— otto的bus和eventbus对比分析
http://frodoking.github.io/2015/03/30/android-eventbus-otto-analysis/
2015-03-30T07:07:30.000Z
2015-09-14T06:50:07.533Z
<p>因文章很快被人转载到一些其他网站,所以本人在此声明:<br><strong>转载请标明转载出处:<a href="http://frodoking.github.io/2015/03/30/android-eventbus-otto-analysis/">http://frodoking.github.io/2015/03/30/android-eventbus-otto-analysis/</a> </strong></p>
<p>##出现场景<br>为了简化并且更加高质量的在Activities, Fragments, Threads, Services等等之间的通信,同时解决组建之间的高耦合还能继续高效的通信。事件总线设计出现了。<br>总线,在计算机组成原理中遇到过io总线。总线的思路就是负责传递某种object到指定的地方。<br>在Android内置的Intent和BroadcastReceiver就是采用了类似事件总线的设计思路。这两者都可以起到跟事件总线类似的效果。注册广播接收器和单纯发一个intent就可以唤起其他组件,提醒其他组件更新,这是非常方便的,同时也是本文提到的两个开源方案所做不到的。但也有不好地方,它们内部的实现都需要 IPC,单从传递效率上来讲,可能并不太适合上层的组件间通信。本文章主要讨论的app内部组件间的通信。<br><a id="more"></a></p>
<p>##基本用法对比</p>
<p><strong>EventBus三步骤</strong><br>1.定义 events:<br><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">public class MessageEvent { /* Additional fields if needed */ }</span><br></pre></td></tr></table></figure></p>
<p>2.注册订阅者:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">eventBus.register(this);</span><br><span class="line">public void onEvent(AnyEventType event) {/* Do something */};</span><br></pre></td></tr></table></figure></p>
<p>3.发布时间events:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">eventBus.post(event);</span><br></pre></td></tr></table></figure></p>
<p><strong>Otto四步骤</strong><br>1.初始化bus<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Bus bus = new Bus(); </span><br><span class="line">Bus bus2 = new Bus(ThreadEnforcer.MAIN);//主线程</span><br></pre></td></tr></table></figure></p>
<p>这里可以指定@Subscribe和@Produce标注的回调方法所运行的线程,默认是在MainThread中执行。如果不关心在哪个线程执行,可以使用ThreadEnforcer.ANY,甚至可以使用自己实现的ThreadEnforcer接口。<br>2.订阅事件<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">@Subscribe public void answerAvailable(AnswerAvailableEvent event) {</span><br><span class="line"> // TODO: React to the event somehow!</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>注意subscribe方法接收的参数类型需要和post参数的类型一致或者是post参数类型的父类。<br>3.发布事件<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">bus.post(new AnswerAvailableEvent(42));</span><br></pre></td></tr></table></figure></p>
<p>bus.register(this);<br>一旦调用了register方法,Otto就会通过反射去寻找所有带有@Subscribe或者@Produce注解的方法,并将这些方法缓存下来。只有在调用了register之后,该类里面标注了@Subscribe或者@Produce的方法才会在适当的时候被调用。另外,当不需要订阅事件的时候,可以调用unregister来取消订阅。<br>4.生产者<br>有时候当订阅某个事件的时候,希望能够获取当前的一个值,比如订阅位置变化事件的时候,希望能拿到当前的位置信息。Otto中@Produce正是扮演了这么一个生产者的角色。@Produce也是用于方法,并且这个方法的参数必须为空,返回值是你要订阅的事件的类型。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">@Produce public AnswerAvailableEvent produceAnswer() { </span><br><span class="line"> // Assuming 'lastAnswer' exists. </span><br><span class="line"> return new AnswerAvailableEvent(this.lastAnswer); </span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>使用标签Produce之后,也需要调用bus.register()。调用了register方法之后,所有之前订阅AnswerAvailableEvent事件的方法都会被执行一次,参数就是produceAnswer方法的返回值,之后任何新的订阅了AnswerAvailableEvent事件的方法,也都会立即调用produceAnswer方法。</p>
<p>##观察者模式<br><strong>概述</strong><br>观察者模式有时被称作发布/订阅模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。</p>
<p><strong>解决的问题</strong><br>将一个系统分割成一个一些类相互协作的类有一个不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便。观察者就是解决这类的耦合关系的。</p>
<p><strong>模式中的角色</strong></p>
<blockquote>
<ul>
<li>抽象主题(Subject):它把所有观察者对象的引用保存到一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。</li>
<li>具体主题(ConcreteSubject):将有关状态存入具体观察者对象;在具体主题内部状态改变时,给所有登记过的观察者发出通知。</li>
<li>抽象观察者(Observer):为所有的具体观察者定义一个接口,在得到主题通知时更新自己。</li>
<li>具体观察者(ConcreteObserver):实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题状态协调。</li>
</ul>
</blockquote>
<p>再来看看观察者模式的类图<br><img src="http://frodoking.github.io/img/android/the_observer_pattern.png" alt="观察者模式的类图"><br>下面是本人关于该模式的具体实现代码<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line">public interface Subject {</span><br><span class="line"> void attach(Observer obs);</span><br><span class="line"> void detach(Observer obs);</span><br><span class="line"> void notifyObserver();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">public class ConcreteSubject implements Subject {</span><br><span class="line"></span><br><span class="line"> private Vector<Observer> obsVector = new Vector<Observer>();</span><br><span class="line"></span><br><span class="line"> @Override</span><br><span class="line"> public void attach(Observer obs) {</span><br><span class="line"> obsVector.add(obs);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> @Override</span><br><span class="line"> public void detach(Observer obs) {</span><br><span class="line"> obsVector.remove(obs);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> @Override</span><br><span class="line"> public void notifyObserver() {</span><br><span class="line"> for (Observer o : obsVector) {</span><br><span class="line"> o.update();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public Enumeration<Observer> observers() {</span><br><span class="line"> return obsVector.elements();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public void change() {</span><br><span class="line"> notifyObserver();</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">public interface Observer {</span><br><span class="line"> void update();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">public class ConcreteObserver implements Observer {</span><br><span class="line"></span><br><span class="line"> @Override</span><br><span class="line"> public void update() {</span><br><span class="line"> System.out.println("收到通知,并进行处理");</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">public class Client {</span><br><span class="line"> public static void main(String args[]) {</span><br><span class="line"> Subject subject = new ConcreteSubject();</span><br><span class="line"></span><br><span class="line"> Observer observer = new ConcreteObserver();</span><br><span class="line"></span><br><span class="line"> subject.attach(observer);</span><br><span class="line"> subject.notifyObserver();</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">``` </span><br><span class="line"></span><br><span class="line">##源码实现方式对比</span><br><span class="line">整体描述完了eventbus和otto的使用方式以及采用的观察者模式后,在这里重点分析一下这两个项目是如何实现各自的订阅事件、发布事件以及多环境下的切换问题(这里切换主要是主线程还是非主线程以及同步与异步问题)</span><br><span class="line"></span><br><span class="line">**事件订阅分析对比**</span><br><span class="line">1、eventbus</span><br><span class="line">在这里重点讲一下eventbus如何实现订阅的关键地方(EventBus.getDefault().register(this))</span><br><span class="line"></span><br><span class="line">先来看一个非常重要的工具类**SubscriberMethodFinder.findSubscriberMethods(Class<?> subscriberClass)**</span><br></pre></td></tr></table></figure></p>
<p>//工具类,查找订阅者下的所有方法<br>class SubscriberMethodFinder {<br> private static final String ON_EVENT_METHOD_NAME = “onEvent”;//这个字段非常的重要<br> List<subscribermethod> findSubscriberMethods(Class<?> subscriberClass) {<br> String key = subscriberClass.getName();<br> List<subscribermethod> subscriberMethods;<br> synchronized (methodCache) {<br> subscriberMethods = methodCache.get(key);<br> }<br> if (subscriberMethods != null) {<br> return subscriberMethods;<br> }<br> subscriberMethods = new ArrayList<subscribermethod>();<br> Class<?> clazz = subscriberClass;<br> HashSet<string> eventTypesFound = new HashSet<string>();<br> StringBuilder methodKeyBuilder = new StringBuilder();<br> while (clazz != null) {<br> String name = clazz.getName();<br> if (name.startsWith(“java.”) || name.startsWith(“javax.”) || name.startsWith(“android.”)) {<br> // Skip system classes, this just degrades performance<br> break;<br> }</string></string></subscribermethod></subscribermethod></subscribermethod></p>
<pre><code> // Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again)
//得到所有的方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
String methodName = method.getName();
//查看当前查找的class内部所有以onEvent字段开始的方法
if (methodName.startsWith(ON_EVENT_METHOD_NAME)) {
int modifiers = method.getModifiers();
//是否是public且非static和abstract方法,是否是一个参数。如果都复合,才进入封装的部分。
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 1) {
String modifierString = methodName.substring(ON_EVENT_METHOD_NAME.length());
ThreadMode threadMode;
//根据方法的后缀,来确定threadMode,threadMode是个枚举类型:就四种情况。
if (modifierString.length() == 0) {
threadMode = ThreadMode.PostThread;
//主线程(UI线程采用handler更新机制)
} else if (modifierString.equals("MainThread")) {
threadMode = ThreadMode.MainThread;
//后台线程池(队列式的一个接一个)
} else if (modifierString.equals("BackgroundThread")) {
threadMode = ThreadMode.BackgroundThread;
//后台线程池(异步的,跟background共享线程池)
} else if (modifierString.equals("Async")) {
threadMode = ThreadMode.Async;
} else {
if (skipMethodVerificationForClasses.containsKey(clazz)) {
continue;
} else {
throw new EventBusException("Illegal onEvent method, check for typos: " + method);
}
}
Class<?> eventType = parameterTypes[0];
methodKeyBuilder.setLength(0);
methodKeyBuilder.append(methodName);
methodKeyBuilder.append('>').append(eventType.getName());
String methodKey = methodKeyBuilder.toString();
if (eventTypesFound.add(methodKey)) {
// Only add if not already found in a sub class
//将method, threadMode, eventType传入构造了:new SubscriberMethod(method, threadMode, eventType)。添加到List,最终放回。
subscriberMethods.add(new SubscriberMethod(method, threadMode, eventType));
}
}
} else if (!skipMethodVerificationForClasses.containsKey(clazz)) {
Log.d(EventBus.TAG, "Skipping method (not public, static or abstract): " + clazz + "."
+ methodName);
}
}
}
//扫描所有的父类,不仅仅是当前类。
clazz = clazz.getSuperclass();
}
if (subscriberMethods.isEmpty()) {
throw new EventBusException("Subscriber " + subscriberClass + " has no public methods called "
+ ON_EVENT_METHOD_NAME);
} else {
synchronized (methodCache) {
methodCache.put(key, subscriberMethods);
}
return subscriberMethods;
}
}
</code></pre><p>}<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">EventBus注册方法</span><br></pre></td></tr></table></figure></p>
<pre><code>private synchronized void register(Object subscriber, boolean sticky, int priority) {
//找到需要订阅的方法
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass());
for (SubscriberMethod subscriberMethod : subscriberMethods) {
//发起订阅
subscribe(subscriber, subscriberMethod, sticky, priority);
}
}
// Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority) {
Class<?> eventType = subscriberMethod.eventType;
//根据subscriberMethod.eventType,去subscriptionsByEventType去查找一个CopyOnWriteArrayList<Subscription>,如果没有则创建。
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
//把传入的参数封装成了一个:Subscription(subscriber, subscriberMethod, priority)
//这里的subscriptionsByEventType是个Map,key:eventType ; value:CopyOnWriteArrayList<Subscription>
//这个Map其实就是EventBus存储方法的地方
Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority);
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<Subscription>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
// Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again)
// subscriberMethod.method.setAccessible(true);
//添加newSubscription;并且是按照优先级添加的。
//可以看到,优先级越高,会插到在当前List的前面。
int size = subscriptions.size();
for (int i = 0; i <= size; i++) {
if (i == size || newSubscription.priority > subscriptions.get(i).priority) {
subscriptions.add(i, newSubscription);
break;
}
}
//根据subscriber存储它所有的eventType; 依然是map;key:subscriber ,value:List<eventType> ;主要用于isRegister的判断。
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<Class<?>>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
//判断sticky;如果为true,从stickyEvents中根据eventType去查找有没有stickyEvent,
if (sticky) {
Object stickyEvent;
synchronized (stickyEvents) {
stickyEvent = stickyEvents.get(eventType);
}
//如果有则立即发布去执行。stickyEvent其实就是我们post时的参数。
if (stickyEvent != null) {
// If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)
// --> Strange corner case, which we don't take care of here.
postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper());
}
}
}
</code></pre><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">2、otto</span><br><span class="line">同样看一下otto的关于订阅的关键点(new Bus().register(Object obj))</span><br><span class="line"></span><br><span class="line">类似于eventbus,otto也有一个非常重要的工具类</span><br><span class="line">**AnnotatedHandlerFinder.findAllSubscribers(Object listener)** </span><br><span class="line">**AnnotatedHandlerFinder.findAllProducers(Object listener)**</span><br><span class="line">在下面的代码中主要以订阅者方法为重点展开</span><br></pre></td></tr></table></figure>
<p>/<em>* This implementation finds all methods marked with a {@link Subscribe} annotation. </em>/<br>//这个方法实现了基于java的注解方式实现查找当前listener所有被标记了Subscribe的方法<br> static Map<class<?>, Set<eventhandler>> findAllSubscribers(Object listener) {<br> Class<?> listenerClass = listener.getClass();<br> Map<class<?>, Set<eventhandler>> handlersInMethod = new HashMap<class<?>, Set<eventhandler>>();</eventhandler></class<?></eventhandler></class<?></eventhandler></class<?></p>
<pre><code>//检查cache中是否已经存在加入内存中的class
if (!SUBSCRIBERS_CACHE.containsKey(listenerClass)) {
loadAnnotatedMethods(listenerClass);
}
Map<Class<?>, Set<Method>> methods = SUBSCRIBERS_CACHE.get(listenerClass);
if (!methods.isEmpty()) {
for (Map.Entry<Class<?>, Set<Method>> e : methods.entrySet()) {
Set<EventHandler> handlers = new HashSet<EventHandler>();
for (Method m : e.getValue()) {
//为每个方法添加一个Handler,对event的处理的一个包装
handlers.add(new EventHandler(listener, m));
}
//按照class为key,handler为value存入
handlersInMethod.put(e.getKey(), handlers);
}
}
return handlersInMethod;
</code></pre><p> }<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">订阅者和生产者注入</span><br></pre></td></tr></table></figure></p>
<p> public void register(Object object) {<br> if (object == null) {<br> throw new NullPointerException(“Object to register must not be null.”);<br> }<br> enforcer.enforce(this);</p>
<pre><code>//查找生产者方法
Map<Class<?>, EventProducer> foundProducers = handlerFinder.findAllProducers(object);
for (Class<?> type : foundProducers.keySet()) {
final EventProducer producer = foundProducers.get(type);
EventProducer previousProducer = producersByType.putIfAbsent(type, producer);
//checking if the previous producer existed
if (previousProducer != null) {
throw new IllegalArgumentException("Producer method for type " + type
+ " found on type " + producer.target.getClass()
+ ", but already registered by type " + previousProducer.target.getClass() + ".");
}
Set<EventHandler> handlers = handlersByType.get(type);
if (handlers != null && !handlers.isEmpty()) {
for (EventHandler handler : handlers) {
dispatchProducerResultToHandler(handler, producer);
}
}
}
</code></pre><p>//查找所有的订阅者方法<br> Map<class<?>, Set<eventhandler>> foundHandlersMap = handlerFinder.findAllSubscribers(object);<br> for (Class<?> type : foundHandlersMap.keySet()) {<br> //按照type为class的key查找handler<br> Set<eventhandler> handlers = handlersByType.get(type);<br> //如果没有那么就创建新的集合并存入到内存<br> if (handlers == null) {<br> //concurrent put if absent<br> Set<eventhandler> handlersCreation = new CopyOnWriteArraySet<eventhandler>();<br> handlers = handlersByType.putIfAbsent(type, handlersCreation);<br> if (handlers == null) {<br> handlers = handlersCreation;<br> }<br> }<br> //做一个检查是否注册进去了<br> final Set<eventhandler> foundHandlers = foundHandlersMap.get(type);<br> if (!handlers.addAll(foundHandlers)) {<br> throw new IllegalArgumentException(“Object already registered.”);<br> }<br> }</eventhandler></eventhandler></eventhandler></eventhandler></eventhandler></class<?></p>
<pre><code>//针对生产者的一个Dispatch 结果功能
for (Map.Entry<Class<?>, Set<EventHandler>> entry : foundHandlersMap.entrySet()) {
Class<?> type = entry.getKey();
EventProducer producer = producersByType.get(type);
if (producer != null && producer.isValid()) {
Set<EventHandler> foundHandlers = entry.getValue();
for (EventHandler foundHandler : foundHandlers) {
//查看是否可用,不可用即跳出
if (!producer.isValid()) {
break;
}
if (foundHandler.isValid()) {
//可用则分发结果
dispatchProducerResultToHandler(foundHandler, producer);
}
}
}
}
</code></pre><p> }<br>```</p>
<p>对比<br>从上边的源码可以很明显看出,事件订阅的处理差别<br>1、eventbus是采用反射的方式对整个注册的类的所有方法进行扫描来完成注册;<br>2、otto采用了注解的方式完成注册;<br>3、共同的地方缓存所有注册并有可用性的检测。同时可以移除注册;<br>4、注册的共同点都是采用method方法进行一个集成。</p>
<p><strong>事件发布</strong><br>在发布的地方,其实差异性不大。都采用了遍历当前的注册表,通过key找到当前注册列表,然后发起Dispatch,调用method.inovke(xxx)方法完成通知。</p>
<p>事件发布不一样的地方eventbus采用了四种线程模式</p>
<blockquote>
<ul>
<li>PostThread //直接反射调用,在当前的线程直接调用该方法</li>
<li>MainThread //通过Handler去发送消息,然后执行</li>
<li>BackgroundThread //如果当前非UI线程,则直接调用;如果是UI线程,则将任务加入到后台的一个队列,最终由Eventbus中的一个线程池去调用(一个接着一个去调用,中间使用了一个布尔型变量handlerActive进行控制)</li>
<li>Async //将任务加入到后台的一个队列,最终由Eventbus中的一个线程池去调用;线程池与BackgroundThread用的是同一个(动态控制并发)</li>
</ul>
</blockquote>
<p>在otto更多使用场景应该就是在主线程中,因为它内部没有异步线程的场景。(也许是它自身的定位不一样,它就是为了解决UI的通信机制。所以出发点就是轻量级)<br>在代码中主要体现这一特色的地方就是在接口ThreadEnforcer以及内部的实现域ANY和MAIN。在MAIN内部有一个是否是主线程的检查,而ANY不做任何检查的事情。</p>
<p><strong>为何不重复造轮子,这里给出了目前个人看到最详细的 <a href="http://codekk.com/open-source-project-analysis/detail/Android/Trinea/EventBus%20%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90" target="_blank" rel="external">EventBus 源码解析</a>的连接。</strong></p>
<p>##开发者角度使用对比<br>1、otto中从源码角度看,要在基类中注册事件是一件比较麻烦的事情。而Evenbus就比较友好(有网友反应如果父类中注册了总线,那么子类中必须实现一个onEvent*方法,否则程序就会崩掉。由于时间问题没进行验证这一点);<br>2、订阅的事件参数问题,eventbus对多参数不会抛出异常。而otto只允许接收一个参数,否则抛出RuntimeException;(其实这一点作为开源项目对代码的质量还是挺重要的)<br>3、从个人使用的角度来看,个人更加喜欢otto的特性。因为我只会用otto来简化UI的通信,其他的我并不需要;<br>4、另外,用java注解的方式来显示的标记订阅方法和生产者方法这非常的友好。至少对刚使用的开发者而言,能够清晰看到代码的思路;<br>5、不过对不同需求的人群来说,eventbus拓展能力和使用场景更加丰富。如果你的项目通信比较多,而且很复杂的时候;<br>6、eventbus定义必须onEvent开始的方法感觉还是挺别扭;<br>7、eventbut是不使用注解是因为注解在2.3之前的系统上会变得缓慢(这一点还需要求证一下);<br>8、在eventbus中有一个比较难受的地方是:在一个订阅者类中如果有两个同参数类型的接收函数,并且都要执行在主线程,那如何命名呢?由于EventBus只根据事件参数类型来判断接收函数,因此会导致两个函数都会被执行。这当然对开发者来说比较难受了,不过github上已经有人提出采用添加tag的方式来做标记扩展(<a href="https://github.com/bboyfeiyu/AndroidEventBus" target="_blank" rel="external">AndroidEventBus</a>)<br>9、使用otto时候,Bus对象只有作为单例共享的时候才足够高效。</p>
<p>##结束语<br>任何一个框架都有自己的设计初衷,开发者必须明白每个框架的出发点才能更好的运用。也希望每个开发者能从自己项目的角度出发,只有适合自己的才是最好的。过于追求反而会适得其反。</p>
<p>分析源码在于掌握当前项目的整体思路和应用范围的一个调研,个人更倾向于把关键点说出来而非说把所有细节全部描绘清楚。</p>
<p>因文章很快被人转载到一些其他网站,所以本人在此声明:<br><strong>转载请标明转载出处:<a href="http://frodoking.github.io/2015/03/30/android-eventbus-otto-analysis/">http://frodoking.github.io/2015/03/30/android-eventbus-otto-analysis/</a> </strong></p>
<p>##出现场景<br>为了简化并且更加高质量的在Activities, Fragments, Threads, Services等等之间的通信,同时解决组建之间的高耦合还能继续高效的通信。事件总线设计出现了。<br>总线,在计算机组成原理中遇到过io总线。总线的思路就是负责传递某种object到指定的地方。<br>在Android内置的Intent和BroadcastReceiver就是采用了类似事件总线的设计思路。这两者都可以起到跟事件总线类似的效果。注册广播接收器和单纯发一个intent就可以唤起其他组件,提醒其他组件更新,这是非常方便的,同时也是本文提到的两个开源方案所做不到的。但也有不好地方,它们内部的实现都需要 IPC,单从传递效率上来讲,可能并不太适合上层的组件间通信。本文章主要讨论的app内部组件间的通信。<br>
一种更清晰的Android架构
http://frodoking.github.io/2015/03/28/android-a-more-clear-architecture/
2015-03-28T15:00:20.000Z
2015-09-14T04:18:05.348Z
<blockquote>
<ul>
<li>原文链接 : <a href="http://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/" target="_blank" rel="external">Architecting Android…The clean way?</a></li>
<li>译者 : <a href="https://www.github.com/bboyfeiyu" target="_blank" rel="external">Mr.Simple & Sophie.Ping</a></li>
</ul>
</blockquote>
<p>过去几个月以来,通过在Tuenti网站上与@pedro_g_s和@flipper83(安卓开发两位大牛)进行友好讨论之后,我决定写这篇关于架构安卓应用的文章。 </p>
<p>我写这篇文章的目的是想把我在过去几个月体悟到的小方法以及在调查和应用中学到的有用的东西分享给大家。<br><a id="more"></a></p>
<h2 id="入门指南"><a href="#入门指南" class="headerlink" title="入门指南"></a>入门指南</h2><p>大家都知道要写一款精品软件是有难度且很复杂的:不仅要满足特定要求,而且软件还必须具有稳健性,可维护、可测试性强,并且能够灵活适应各种发展与变化。这时候,“清晰架构”就应运而生了,这一架构在开发任何软件应用的时候用起来非常顺手。</p>
<p>这个思路很简单:简洁架构 意味着产品系统中遵循一系列的习惯原则:</p>
<blockquote>
<ul>
<li>框架独立性</li>
<li>可测试</li>
<li>UI独立性</li>
<li>数据库独立性</li>
<li>任何外部代理模块的独立性 </li>
</ul>
</blockquote>
<p><img src="https://camo.githubusercontent.com/dd69e725f30c30031dea279adc5a9d09ea3432f2/687474703a2f2f6665726e616e646f63656a61732e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031342f30392f636c65616e5f617263686974656374757265312e706e67" alt="arch"></p>
<p>我们并不要求一定要用四环结构(如图所示),这只是一个示例图解,但是要考虑的是依赖项规则:源码依赖项只能向内指向,内环里的所有项不能了解外环所发生的东西。 </p>
<p>以下是更好地理解和熟悉本方法的一些相关词汇: </p>
<blockquote>
<ul>
<li>Entities:是指一款应用的业务对象</li>
<li>Use cases:是指结合数据流和实体中的用例,也称为Interactor</li>
<li>Interface Adapters: 这一组适配器,是负责以最合理的格式转换用例(use cases)和实体(entities)之间的数据,表现层(Presenters )和控制层(Controllers),就属于这一块的。</li>
<li>Frameworks and Drivers: 这里是所有具体的实现了:比如:UI,工具类,基础框架,等等。</li>
</ul>
</blockquote>
<p>想要更具体,更生动丰富的解释,可以参考<a href="http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html" target="_blank" rel="external">这篇文章</a>或者<a href="https://vimeo.com/43612849" target="_blank" rel="external">这个视频</a>。</p>
<h2 id="场景"><a href="#场景" class="headerlink" title="场景"></a>场景</h2><p>我会设置一个简单的场景来开始:创建一个简单的小app,app中显示从云端获取的一个朋友或用户列表。当点击其中任何一个时,会打开一个新的窗口,显示该用户的详细信息。这里我放了一段视频,大家看看<a href="http://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/" target="_blank" rel="external">这个视频 (需翻墙)</a>大概就可以对我所描述的东西了解个大概了。 </p>
<h2 id="Android应用架构"><a href="#Android应用架构" class="headerlink" title="Android应用架构"></a>Android应用架构</h2><p>这一对象遵循关注分离原则,也就是通过业务规则让内环操作对外环事物一无所知,这样一来,在测试时它们就不会依赖任何的外部元素了。<br>要达到这个目的,我的建议就是把一个项目分成三个层次,每个层次拥有自己的目的并且各自独立于堆放运作。<br>值得一提的是,每一层次使用其自有的数据模型以达到独立性的目的(大家可以看到,在代码中需要一个数据映射器来完成数据转换。如果你不想把你的模型和整个应用交叉使用,这是你要付出的代价)。 </p>
<p>以下是图解,大家感受下:<br><img src="http://fernandocejas.com/wp-content/uploads/2014/09/clean_architecture_android.png" alt="schema"></p>
<p>注:我并没有使用任何的外部库(除了用于json数据句法分析的gson和用于测试的junit, mockito, robolectric和espresso以外)。原因是它可以使这个示例更清晰。总之,在存储磁盘数据时,记得加上ORM、依赖注入框架或者你熟悉的任何工具或库,这些都会带来很大帮助。(记住:重复制造轮子可不是明智的选择)</p>
<h2 id="表现层-Presentation-Layer"><a href="#表现层-Presentation-Layer" class="headerlink" title="表现层 (Presentation Layer)"></a>表现层 (Presentation Layer)</h2><p>表现层在此,表现的是与视图和动画相关的逻辑。这里仅用了一个Model View Presenter(下文简称MVP),但是大家也可以用MVC或MVVM等模式。这里我不再赘述细节,但是需要强调的是,这里的fragment和activity都是View,其内部除了UI逻辑以外没有其他逻辑,这也是所有渲染的东西发生的地方。<br>本层次的Presenter由多个interactor(用例)组成,用于完成Android UI线程以外的新线程的工作,并借助渲染到view中的数据callback函数来返回。<br><img src="http://fernandocejas.com/wp-content/uploads/2014/09/clean_architecture_mvp.png" alt="mvp"> </p>
<p>如果你需要一个使用MVP和MVVM的<a href="https://github.com/pedrovgs/EffectiveAndroidUI/" target="_blank" rel="external">Effective Android UI</a>典型案例,可以参考我朋友Pedro Gómez的文章。</p>
<h2 id="领域层-Domain-Layer"><a href="#领域层-Domain-Layer" class="headerlink" title="领域层 (Domain Layer)"></a>领域层 (Domain Layer)</h2><p>这里的业务规则是指所有在本层发生的逻辑。对于Android项目来说,大家还可以看到所有的interactor(用例)实施。这一层是纯粹的java模块,没有任何的Android依赖性。当涉及到业务对象时,所有的外部组件都使用接口。<br><img src="http://fernandocejas.com/wp-content/uploads/2014/09/clean_architecture_domain.png" alt="domain"> </p>
<h2 id="数据层-Data-Layer"><a href="#数据层-Data-Layer" class="headerlink" title="数据层 (Data Layer)"></a>数据层 (Data Layer)</h2><p>应用所需的所有数据都来自这一层中的UserRepository实现(接口在领域层)。这一实现采用了<a href="http://martinfowler.com/eaaCatalog/repository.html" target="_blank" rel="external">Repository Pattern</a>,主要策略是通过一个工厂根据一定的条件选取不同的数据来源。<br>比如,通过ID获取一个用户时,如果这个用户在缓存中已经存在,则硬盘缓存数据源会被选中,否则会通过向云端发起请求获取数据,然后存储到硬盘缓存。<br>这一切背后的原理是由于原始数据对于客户端是透明的,客户端并不关心数据是来源于内存、硬盘还是云端,它需要关心的是数据可以正确地获取到。<br><img src="http://fernandocejas.com/wp-content/uploads/2014/09/clean_architecture_data.png" alt="data"> </p>
<ul>
<li>注:在代码方面,出于学习目的,我通过文件系统和Android preference实现了一个简单、原始的硬盘缓存。请记住,如果已经存在了能够完成这些工作的库,就不要重复制造轮子。</li>
</ul>
<h2 id="错误处理"><a href="#错误处理" class="headerlink" title="错误处理"></a>错误处理</h2><p>这是一个长期待解决的讨论话题,如果大家能够分享各自的解决方案,那真真是极好的。<br>我的策略是使用回调,这样的话,如果数据仓库发生了变化,回调有两个方法:onResponse()和onError(). onError方法将异常信息封装到一个ErrorBundle对象中: 这种方法的难点在于这其中会存在一环扣一环的回调链,错误会沿着这条回调链到达展示层。因此会牺牲一点代码的可读性。另外,如果出现错误,我本来可以通过事件总线系统抛出事件,但是这种实现方式类似于使用C语言的goto语法。在我看来,当你订阅多个事件时,如果不能很好的控制,你可能会被弄得晕头转向。</p>
<h2 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h2><p>关于测试方面,我根据不同的层来选择不同的方法: </p>
<blockquote>
<ul>
<li>展示层 ( Presentation Layer) : 使用android instrumentation和 espresso进行集成和功能测试</li>
<li>领域层 ( Domain Layer) : 使用JUnit和Mockito进行单元测试;</li>
<li>数据层 ( Data Layer) : 使用Robolectric ( 因为依赖于Android SDK中的类 )进行集成测试和单元测试。</li>
</ul>
</blockquote>
<h2 id="代码展示"><a href="#代码展示" class="headerlink" title="代码展示"></a>代码展示</h2><p>我猜你现在在想,扯了那么久的淡,代码究竟在哪里呢? 好吧,这就是你可以找到上述解决方案的<a href="https://github.com/android10/Android-CleanArchitecture" target="_blank" rel="external">github链接</a>。还要提一点,在文件夹结构方面,不同的层是通过以下不同的模块反应的: </p>
<blockquote>
<ul>
<li>presentation: 展示层的Android模块</li>
<li>domain: 一个没有android依赖的java模块</li>
<li>data: 一个数据获取来源的android模块。</li>
<li>data-test: 数据层测试,由于使用Robolectric 存在一些限制,所以我得再独立的java模块中使用。</li>
</ul>
</blockquote>
<h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>正如 Bob大叔 所说:“Architecture is About Intent, not Frameworks” ,我非常同意这个说法,当然了,有很多不同的方法做不同的事情(不同的实现方法),我很确定,你每天(像我一样)会面临很多挑战,但是遵循这些方法,可以确保你的应用会: </p>
<blockquote>
<ul>
<li>易维护 Easy to maintain</li>
<li>易测试 Easy to tes.</li>
<li>高内聚 Very cohesive.</li>
<li>低耦合 Decoupled. </li>
</ul>
</blockquote>
<p>最后,我强烈推荐你去实践一下,并且分享你的经验。也许你会找到更好的解决方案:我们都知道,不断提升自己是一件件非常好的事。我希望这篇文章对你有所帮助,欢迎拍砖。</p>
<h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><blockquote>
<ul>
<li><a href="https://github.com/android10/Android-CleanArchitecture" target="_blank" rel="external">https://github.com/android10/Android-CleanArchitecture</a></li>
<li><a href="http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html" target="_blank" rel="external">The clean architecture by Uncle Bob</a></li>
<li><a href="http://www.infoq.com/news/2013/07/architecture_intent_frameworks" target="_blank" rel="external">Architecture is about Intent, not Frameworks</a></li>
<li><a href="http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter" target="_blank" rel="external">Model View Presenter</a></li>
<li><a href="http://martinfowler.com/eaaCatalog/repository.html" target="_blank" rel="external">Repository Pattern by Martin Fowler</a></li>
<li><a href="http://www.slideshare.net/PedroVicenteGmezSnch/" target="_blank" rel="external">Android Design Patterns Presentation</a></li>
</ul>
</blockquote>
<blockquote>
<ul>
<li>原文链接 : <a href="http://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/">Architecting Android…The clean way?</a></li>
<li>译者 : <a href="https://www.github.com/bboyfeiyu">Mr.Simple & Sophie.Ping</a></li>
</ul>
</blockquote>
<p>过去几个月以来,通过在Tuenti网站上与@pedro_g_s和@flipper83(安卓开发两位大牛)进行友好讨论之后,我决定写这篇关于架构安卓应用的文章。 </p>
<p>我写这篇文章的目的是想把我在过去几个月体悟到的小方法以及在调查和应用中学到的有用的东西分享给大家。<br>
UI的整体设计思路(避免臃肿的UI)
http://frodoking.github.io/2015/03/22/android-ui-design/
2015-03-22T01:30:07.000Z
2015-09-14T06:41:50.716Z
<p>一直打算把工作中关于Android的UI相关事情写一写。由于时间紧张,拖延到今天。<br>本文打算从基本代码的整理到UI的整体设计详细系统的讲述一下。</p>
<h2 id="XML相关UI的整理"><a href="#XML相关UI的整理" class="headerlink" title="XML相关UI的整理"></a>XML相关UI的整理</h2><p>由于本人很早就在开始使用Android studio,对xml格式的layout整理相当得心应手。<br>很多开发初期的同学在不熟悉xml布局情况下,总是喜欢为了实现一个效果而不计较布局的层次。比如:<br><a id="more"></a></p>
<blockquote>
<p>1、本来只需要一层的效果,而使用了两层甚至三层<br>2、一些特定的布局方式理解也不深刻,针对LinearLayout、RelativeLayout和Fragment这三种的组合使用场景不够熟练<br>3、本来一个TextView可以完成的图片+文字效果,使用了LinearLayout+TextView+ImageView<br>4、一些通用的View效果可以选择自定义View和ViewGroup来实现(例如所有按钮的点击变灰情况,可以设置View以及ViewGroup下子View的Alpa来达到目的)<br>5、原本可以用代码中的Shape完成纯色或者渐变色的Drawable,而使用了图片代替(这无疑是增加apk的包大小)</p>
</blockquote>
<p>上面这些问题在这里只是提出来,对应解决方式可以使用Google官网上提供的HierarchyViewer工具来做进一步的检测;</p>
<p>再来说说xml中代码复用的问题,先来看一看如下这段布局对比</p>
<p>老版本代码<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br></pre></td><td class="code"><pre><span class="line"><?xml version="1.0" encoding="UTF-8"?></span><br><span class="line"><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"</span><br><span class="line"> android:layout_width="match_parent"</span><br><span class="line"> android:layout_height="match_parent"</span><br><span class="line"> android:background="@color/gray2" ></span><br><span class="line"></span><br><span class="line"> <RelativeLayout</span><br><span class="line"> android:id="@+id/layout_top"</span><br><span class="line"> android:layout_width="match_parent"</span><br><span class="line"> android:layout_height="wrap_content"</span><br><span class="line"> android:background="@color/white" ></span><br><span class="line"></span><br><span class="line"> <RelativeLayout</span><br><span class="line"> android:id="@+id/setting_check_update"</span><br><span class="line"> android:layout_width="match_parent"</span><br><span class="line"> android:layout_height="64dp"</span><br><span class="line"> android:background="@drawable/bg_item_list"</span><br><span class="line"> android:paddingLeft="12dp"</span><br><span class="line"> android:paddingRight="12dp" ></span><br><span class="line"></span><br><span class="line"> <TextView</span><br><span class="line"> android:id="@+id/setting_check_update_title"</span><br><span class="line"> android:layout_width="wrap_content"</span><br><span class="line"> android:layout_height="wrap_content"</span><br><span class="line"> android:layout_centerVertical="true"</span><br><span class="line"> android:text="@string/setting_check_update"</span><br><span class="line"> android:textColor="@color/black"</span><br><span class="line"> android:textSize="16sp" /></span><br><span class="line"></span><br><span class="line"> <TextView</span><br><span class="line"> android:id="@+id/version_str"</span><br><span class="line"> android:layout_width="wrap_content"</span><br><span class="line"> android:layout_height="wrap_content"</span><br><span class="line"> android:layout_marginLeft="10dp"</span><br><span class="line"> android:layout_toRightOf="@id/setting_check_update_title"</span><br><span class="line"> android:layout_alignBottom="@id/setting_check_update_title"</span><br><span class="line"> /></span><br><span class="line"> </span><br><span class="line"> <ImageView</span><br><span class="line"> android:layout_width="wrap_content"</span><br><span class="line"> android:layout_height="wrap_content"</span><br><span class="line"> android:layout_alignParentRight="true"</span><br><span class="line"> android:layout_centerVertical="true"</span><br><span class="line"> android:contentDescription="@null"</span><br><span class="line"> android:src="@drawable/arrow_right" /></span><br><span class="line"> </RelativeLayout></span><br><span class="line"></span><br><span class="line"> <View</span><br><span class="line"> android:id="@+id/line"</span><br><span class="line"> android:layout_width="match_parent"</span><br><span class="line"> android:layout_height="1px"</span><br><span class="line"> android:layout_below="@id/setting_check_update"</span><br><span class="line"> android:layout_marginLeft="12dp"</span><br><span class="line"> android:background="@color/divider_line_color" /></span><br><span class="line"></span><br><span class="line"> <RelativeLayout</span><br><span class="line"> android:id="@+id/setting_about_me"</span><br><span class="line"> android:layout_width="match_parent"</span><br><span class="line"> android:layout_height="64dp"</span><br><span class="line"> android:layout_below="@id/line"</span><br><span class="line"> android:background="@drawable/bg_item_list"</span><br><span class="line"> android:paddingLeft="12dp"</span><br><span class="line"> android:paddingRight="12dp" ></span><br><span class="line"></span><br><span class="line"> <TextView</span><br><span class="line"> android:layout_width="wrap_content"</span><br><span class="line"> android:layout_height="wrap_content"</span><br><span class="line"> android:layout_centerVertical="true"</span><br><span class="line"> android:text="@string/setting_about_me"</span><br><span class="line"> android:textColor="@color/black"</span><br><span class="line"> android:textSize="16sp" /></span><br><span class="line"></span><br><span class="line"> <ImageView</span><br><span class="line"> android:layout_width="wrap_content"</span><br><span class="line"> android:layout_height="wrap_content"</span><br><span class="line"> android:layout_alignParentRight="true"</span><br><span class="line"> android:layout_centerVertical="true"</span><br><span class="line"> android:contentDescription="@null"</span><br><span class="line"> android:src="@drawable/arrow_right" /></span><br><span class="line"> </RelativeLayout></span><br><span class="line"></span><br><span class="line"> <View</span><br><span class="line"> android:id="@+id/line2"</span><br><span class="line"> android:layout_width="match_parent"</span><br><span class="line"> android:layout_height="1px"</span><br><span class="line"> android:layout_below="@id/setting_about_me"</span><br><span class="line"> android:layout_marginLeft="12dp"</span><br><span class="line"> android:background="@color/divider_line_color" /></span><br><span class="line"></span><br><span class="line"> <RelativeLayout</span><br><span class="line"> android:id="@+id/setting_about_service"</span><br><span class="line"> android:layout_width="match_parent"</span><br><span class="line"> android:layout_height="64dp"</span><br><span class="line"> android:layout_below="@id/line2"</span><br><span class="line"> android:background="@drawable/bg_item_list"</span><br><span class="line"> android:paddingLeft="12dp"</span><br><span class="line"> android:paddingRight="12dp" ></span><br><span class="line"></span><br><span class="line"> <TextView</span><br><span class="line"> android:layout_width="wrap_content"</span><br><span class="line"> android:layout_height="wrap_content"</span><br><span class="line"> android:layout_centerVertical="true"</span><br><span class="line"> android:text="@string/setting_about_service"</span><br><span class="line"> android:textColor="@color/black"</span><br><span class="line"> android:textSize="16sp" /></span><br><span class="line"></span><br><span class="line"> <ImageView</span><br><span class="line"> android:layout_width="wrap_content"</span><br><span class="line"> android:layout_height="wrap_content"</span><br><span class="line"> android:layout_alignParentRight="true"</span><br><span class="line"> android:layout_centerVertical="true"</span><br><span class="line"> android:contentDescription="@null"</span><br><span class="line"> android:src="@drawable/arrow_right" /></span><br><span class="line"> </RelativeLayout></span><br><span class="line"></span><br><span class="line"> <View</span><br><span class="line"> android:id="@+id/line3"</span><br><span class="line"> android:layout_width="match_parent"</span><br><span class="line"> android:layout_height="1px"</span><br><span class="line"> android:layout_below="@id/setting_about_service"</span><br><span class="line"> android:layout_marginLeft="12dp"</span><br><span class="line"> android:background="@color/divider_line_color" /></span><br><span class="line"></span><br><span class="line"> <RelativeLayout</span><br><span class="line"> android:id="@+id/setting_baiduband_qr"</span><br><span class="line"> android:layout_width="match_parent"</span><br><span class="line"> android:layout_height="64dp"</span><br><span class="line"> android:layout_below="@id/line3"</span><br><span class="line"> android:background="@drawable/bg_item_list"</span><br><span class="line"> android:paddingLeft="12dp"</span><br><span class="line"> android:paddingRight="12dp" ></span><br><span class="line"></span><br><span class="line"> <TextView</span><br><span class="line"> android:layout_width="wrap_content"</span><br><span class="line"> android:layout_height="wrap_content"</span><br><span class="line"> android:layout_centerVertical="true"</span><br><span class="line"> android:text="@string/setting_baiduband_qr"</span><br><span class="line"> android:textColor="@color/black"</span><br><span class="line"> android:textSize="16sp" /></span><br><span class="line"></span><br><span class="line"> <ImageView</span><br><span class="line"> android:layout_width="wrap_content"</span><br><span class="line"> android:layout_height="wrap_content"</span><br><span class="line"> android:layout_alignParentRight="true"</span><br><span class="line"> android:layout_centerVertical="true"</span><br><span class="line"> android:contentDescription="@null"</span><br><span class="line"> android:src="@drawable/arrow_right" /></span><br><span class="line"> </RelativeLayout></span><br><span class="line"></span><br><span class="line"> <View</span><br><span class="line"> android:id="@+id/line4"</span><br><span class="line"> android:layout_width="match_parent"</span><br><span class="line"> android:layout_height="1px"</span><br><span class="line"> android:layout_below="@id/setting_baiduband_qr"</span><br><span class="line"> android:layout_marginLeft="12dp"</span><br><span class="line"> android:background="@color/divider_line_color" /></span><br><span class="line"></span><br><span class="line"> <RelativeLayout</span><br><span class="line"> android:id="@+id/setting_monitor"</span><br><span class="line"> android:layout_width="match_parent"</span><br><span class="line"> android:layout_height="64dp"</span><br><span class="line"> android:layout_below="@id/line4"</span><br><span class="line"> android:background="@drawable/bg_item_list"</span><br><span class="line"> android:paddingLeft="12dp"</span><br><span class="line"> android:paddingRight="12dp" ></span><br><span class="line"></span><br><span class="line"> <TextView</span><br><span class="line"> android:layout_width="wrap_content"</span><br><span class="line"> android:layout_height="wrap_content"</span><br><span class="line"> android:layout_centerVertical="true"</span><br><span class="line"> android:text="@string/monitor_mode"</span><br><span class="line"> android:textColor="@color/black"</span><br><span class="line"> android:textSize="16sp" /></span><br><span class="line"></span><br><span class="line"> <ImageView</span><br><span class="line"> android:layout_width="wrap_content"</span><br><span class="line"> android:layout_height="wrap_content"</span><br><span class="line"> android:layout_alignParentRight="true"</span><br><span class="line"> android:layout_centerVertical="true"</span><br><span class="line"> android:contentDescription="@null"</span><br><span class="line"> android:src="@drawable/arrow_right" /></span><br><span class="line"> </RelativeLayout></span><br><span class="line"> </RelativeLayout></span><br><span class="line"></span><br><span class="line"> <View</span><br><span class="line"> android:layout_width="match_parent"</span><br><span class="line"> android:layout_height="1px"</span><br><span class="line"> android:layout_below="@id/layout_top"</span><br><span class="line"> android:background="@color/divider_line_color" /></span><br><span class="line"></span><br><span class="line"> <FrameLayout</span><br><span class="line"> android:layout_width="match_parent"</span><br><span class="line"> android:layout_height="56dp"</span><br><span class="line"> android:layout_alignParentBottom="true"</span><br><span class="line"> android:paddingBottom="8dp"</span><br><span class="line"> android:paddingLeft="8dp"</span><br><span class="line"> android:paddingRight="8dp" ></span><br><span class="line"></span><br><span class="line"> <TextView</span><br><span class="line"> android:id="@+id/setting_logout"</span><br><span class="line"> android:layout_width="match_parent"</span><br><span class="line"> android:layout_height="48dp"</span><br><span class="line"> android:background="@drawable/my_btn_2_bg_selector"</span><br><span class="line"> android:gravity="center"</span><br><span class="line"> android:text="@string/setting_logout"</span><br><span class="line"> android:textColor="@drawable/text_red_white_selector"</span><br><span class="line"> android:textSize="16sp" /></span><br><span class="line"> </FrameLayout></span><br><span class="line"></span><br><span class="line"></RelativeLayout></span><br></pre></td></tr></table></figure></p>
<p>改造过后的版本<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br></pre></td><td class="code"><pre><span class="line"><?xml version="1.0" encoding="UTF-8"?></span><br><span class="line"><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"</span><br><span class="line"> style="@style/LayoutMMStyle"></span><br><span class="line"></span><br><span class="line"> <ScrollView android:layout_width="match_parent" android:layout_height="match_parent"</span><br><span class="line"> android:background="@color/page_bg"></span><br><span class="line"> <LinearLayout</span><br><span class="line"> style="@style/LayoutMWStyle"</span><br><span class="line"> android:layout_height="0dp"</span><br><span class="line"> android:layout_weight="1"></span><br><span class="line"></span><br><span class="line"> <RelativeLayout</span><br><span class="line"> android:id="@+id/setting_check_update"</span><br><span class="line"> style="@style/ItemStyle"></span><br><span class="line"></span><br><span class="line"> <TextView</span><br><span class="line"> android:id="@+id/setting_check_update_title"</span><br><span class="line"> style="@style/TextViewStyle"</span><br><span class="line"> android:layout_centerVertical="true"</span><br><span class="line"> android:text="@string/setting_check_update"/></span><br><span class="line"></span><br><span class="line"> <TextView</span><br><span class="line"> android:id="@+id/version_str"</span><br><span class="line"> style="@style/TextViewStyle"</span><br><span class="line"> android:layout_marginLeft="10dp"</span><br><span class="line"> android:layout_toRightOf="@id/setting_check_update_title"</span><br><span class="line"> android:layout_alignBottom="@id/setting_check_update_title"/></span><br><span class="line"></span><br><span class="line"> <ImageView</span><br><span class="line"> style="@style/ImageViewStyle"</span><br><span class="line"> android:layout_alignParentRight="true"</span><br><span class="line"> android:layout_centerVertical="true"</span><br><span class="line"> android:src="@drawable/arrow_right"/></span><br><span class="line"> </RelativeLayout></span><br><span class="line"></span><br><span class="line"> <View style="@style/LineStyle"/></span><br><span class="line"></span><br><span class="line"> <RelativeLayout</span><br><span class="line"> android:id="@+id/setting_about_me"</span><br><span class="line"> style="@style/ItemStyle"></span><br><span class="line"></span><br><span class="line"> <TextView</span><br><span class="line"> style="@style/TextViewStyle"</span><br><span class="line"> android:layout_centerVertical="true"</span><br><span class="line"> android:text="@string/setting_about_me"/></span><br><span class="line"></span><br><span class="line"> <ImageView</span><br><span class="line"> style="@style/ImageViewStyle"</span><br><span class="line"> android:layout_alignParentRight="true"</span><br><span class="line"> android:layout_centerVertical="true"</span><br><span class="line"> android:src="@drawable/arrow_right"/></span><br><span class="line"> </RelativeLayout></span><br><span class="line"></span><br><span class="line"> <View style="@style/LineStyle"/></span><br><span class="line"></span><br><span class="line"> <RelativeLayout</span><br><span class="line"> android:id="@+id/setting_about_service"</span><br><span class="line"> style="@style/ItemStyle"></span><br><span class="line"></span><br><span class="line"> <TextView</span><br><span class="line"> style="@style/TextViewStyle"</span><br><span class="line"> android:layout_centerVertical="true"</span><br><span class="line"> android:text="@string/setting_about_service"/></span><br><span class="line"></span><br><span class="line"> <ImageView</span><br><span class="line"> style="@style/ImageViewStyle"</span><br><span class="line"> android:layout_alignParentRight="true"</span><br><span class="line"> android:layout_centerVertical="true"</span><br><span class="line"> android:src="@drawable/arrow_right"/></span><br><span class="line"> </RelativeLayout></span><br><span class="line"></span><br><span class="line"> <View style="@style/LineStyle"/></span><br><span class="line"></span><br><span class="line"> <RelativeLayout</span><br><span class="line"> android:id="@+id/setting_baiduband_qr"</span><br><span class="line"> style="@style/ItemStyle"></span><br><span class="line"></span><br><span class="line"> <TextView</span><br><span class="line"> style="@style/TextViewStyle"</span><br><span class="line"> android:layout_centerVertical="true"</span><br><span class="line"> android:text="@string/setting_baiduband_qr"/></span><br><span class="line"></span><br><span class="line"> <ImageView</span><br><span class="line"> style="@style/ImageViewStyle"</span><br><span class="line"> android:layout_alignParentRight="true"</span><br><span class="line"> android:layout_centerVertical="true"</span><br><span class="line"> android:src="@drawable/arrow_right"/></span><br><span class="line"> </RelativeLayout></span><br><span class="line"></span><br><span class="line"> <View style="@style/LineStyle"/></span><br><span class="line"></span><br><span class="line"> <RelativeLayout</span><br><span class="line"> android:id="@+id/setting_monitor"</span><br><span class="line"> style="@style/ItemStyle"></span><br><span class="line"></span><br><span class="line"> <TextView</span><br><span class="line"> style="@style/TextViewStyle"</span><br><span class="line"> android:layout_centerVertical="true"</span><br><span class="line"> android:text="@string/monitor_mode"/></span><br><span class="line"></span><br><span class="line"> <ImageView</span><br><span class="line"> style="@style/ImageViewStyle"</span><br><span class="line"> android:layout_alignParentRight="true"</span><br><span class="line"> android:layout_centerVertical="true"</span><br><span class="line"> android:src="@drawable/arrow_right"/></span><br><span class="line"> </RelativeLayout></span><br><span class="line"> <View style="@style/LineStyle"/></span><br><span class="line"> </LinearLayout></span><br><span class="line"> </ScrollView></span><br><span class="line"></span><br><span class="line"> <TextView</span><br><span class="line"> android:id="@+id/setting_logout"</span><br><span class="line"> style="@style/ButtonStyle"</span><br><span class="line"> android:layout_marginBottom="@dimen/margin_xxlarge"</span><br><span class="line"> android:text="@string/setting_logout"</span><br><span class="line"> android:layout_gravity="bottom"/></span><br><span class="line"></span><br><span class="line"></FrameLayout></span><br></pre></td></tr></table></figure></p>
<p>从上边的对比可以明显看出,使用style方式将大大简化代码量。另外尺寸问题更加规范化。<br><strong>特别提醒一下,如果你的app页面越来越多,这时候app的整体风格是最难控制的。那么统一dimension和style是app风格统一的保证</strong><br>关于style,一般通过app的主题就可以完成对基本组建的风格定制作用(像Button,TextView,RadioButton… …)<br>再说一下加载问题,如果当前view比较复杂,建议使用ViewStub来实现懒加载,使用include来简化文件大小并提供代码的可复用性。另外在view添加上尽量考虑merge标签来避免view层次过重。</p>
<p>整理UI的风格统一其实是一件很不容易的事情。如果业务越来越多,风格分散化会越来越严重的。同时维护风格也会是一个不小的开销。所以请善待UI。</p>
<p>关于UI的高级设计请读者关注本人的另一篇文章(<a href="/2014/12/21/android-fragment-lifecycle/">关于Fragment和Activity对比中的一些理解</a>)</p>
<p>一直打算把工作中关于Android的UI相关事情写一写。由于时间紧张,拖延到今天。<br>本文打算从基本代码的整理到UI的整体设计详细系统的讲述一下。</p>
<h2 id="XML相关UI的整理"><a href="#XML相关UI的整理" class="headerlink" title="XML相关UI的整理"></a>XML相关UI的整理</h2><p>由于本人很早就在开始使用Android studio,对xml格式的layout整理相当得心应手。<br>很多开发初期的同学在不熟悉xml布局情况下,总是喜欢为了实现一个效果而不计较布局的层次。比如:<br>
狗日的青春-贰佰
http://frodoking.github.io/2015/03/19/life-the-fucking-youth/
2015-03-19T03:38:16.000Z
2015-09-14T06:51:57.742Z
<p><img src="http://frodoking.github.io/img/android/life-the-fucking-youth.jpg" alt="狗日的青春"><br><a id="more"></a></p>
<blockquote>
<p>我站在大雨淹没秋天的桥上<br>就像那年第一次看见你一样<br>时间是扇颠沛流离的大门<br>平凡的我们注定孤独一生<br>日子一天一天就这样过去<br>那些荒诞的时光都已经忘记<br>想起那些慢慢变的陌生的朋友<br>一回头 青春都喂了狗<br>喝醉的时候我又想起你<br>想起陪你度过的每一个夜晚<br>你走的时候我都没有留你<br>选择和谁一起度过余生是你的权利</p>
</blockquote>
<p>日子一天一天就这样过去<br>那些荒诞的时光都已经忘记<br>想起那些慢慢变的陌生的朋友<br>一回头 青春都喂了狗<br>喝醉的时候我又想起你<br>想起陪你度过的每一个夜晚<br>你走的时候我都没有留你<br>选择和谁一起度过余生是你的权利<br>一个人的时候我又怕想起你<br>怕你还能不能和从前一样<br>我们的人生竟是如此相同<br>流干了理想的血都来不及歌颂<br>日子一天一天就这样过去<br>那些荒诞的傻逼的时光都不该忘记<br>想起那些慢慢失去联系的朋友</p>
<h2 id="一回头-青春都喂了狗"><a href="#一回头-青春都喂了狗" class="headerlink" title="一回头 青春都喂了狗"></a>一回头 青春都喂了狗</h2><embed src="http://www.xiami.com/widget/0_1772618337/singlePlayer.swf" type="application/x-shockwave-flash" width="257" height="33" wmode="transparent">
<p><img src="http://frodoking.github.io/img/android/life-the-fucking-youth.jpg" alt="狗日的青春"><br>
OKHttp源码解析
http://frodoking.github.io/2015/03/12/android-okhttp/
2015-03-12T09:00:50.000Z
2015-09-14T06:47:57.698Z
<p>因文章很快被人转载到一些其他网站,所以本人在此声明:<br><strong>转载请标明转载出处:<a href="http://frodoking.github.io/2015/03/12/android-okhttp/">http://frodoking.github.io/2015/03/12/android-okhttp/</a> </strong></p>
<p>Android为我们提供了两种HTTP交互的方式:HttpURLConnection 和 Apache HTTP Client,虽然两者都支持HTTPS,流的上传和下载,配置超时,IPv6和连接池,已足够满足我们各种HTTP请求的需求。但更高效的使用HTTP可以让您的应用运行更快、更节省流量。而OkHttp库就是为此而生。</p>
<p>OkHttp是一个高效的HTTP库:</p>
<blockquote>
<ul>
<li>支持 SPDY ,共享同一个Socket来处理同一个服务器的所有请求</li>
<li>如果SPDY不可用,则通过连接池来减少请求延时</li>
<li>无缝的支持GZIP来减少数据流量</li>
<li>缓存响应数据来减少重复的网络请求</li>
</ul>
</blockquote>
<p>会从很多常用的连接问题中自动恢复。如果您的服务器配置了多个IP地址,当第一个IP连接失败的时候,OkHttp会自动尝试下一个IP。OkHttp还处理了代理服务器问题和SSL握手失败问题。</p>
<p>使用 OkHttp 无需重写您程序中的网络代码。OkHttp实现了几乎和java.net.HttpURLConnection一样的API。如果您用了 Apache HttpClient,则OkHttp也提供了一个对应的okhttp-apache 模块。</p>
<p>OKHttp源码位置<a href="https://github.com/square/okhttp" target="_blank" rel="external">https://github.com/square/okhttp</a><br><a id="more"></a></p>
<p>##使用</p>
<p>简单使用代码<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">private final OkHttpClient client = new OkHttpClient();</span><br><span class="line"> </span><br><span class="line">public void run() throws Exception {</span><br><span class="line"> Request request = new Request.Builder()</span><br><span class="line"> .url("https://api.github.com/repos/square/okhttp/issues")</span><br><span class="line"> .header("User-Agent", "OkHttp Headers.java")</span><br><span class="line"> .addHeader("Accept", "application/json; q=0.5")</span><br><span class="line"> .addHeader("Accept", "application/vnd.github.v3+json")</span><br><span class="line"> .build();</span><br><span class="line"> </span><br><span class="line"> Response response = client.newCall(request).execute();</span><br><span class="line"> if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);</span><br><span class="line"> </span><br><span class="line"> System.out.println("Server: " + response.header("Server"));</span><br><span class="line"> System.out.println("Date: " + response.header("Date"));</span><br><span class="line"> System.out.println("Vary: " + response.headers("Vary"));</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>在这里使用不做详细介绍,<a href="http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0106/2275.html" target="_blank" rel="external">推荐一篇关于OKHttp的详细使用教程</a>,下面转入源码的分析。</p>
<p>##总体设计<br><img src="http://frodoking.github.io/img/android/okhttp_instructure.png" alt="OKHttp总体设计图"><br>上面是OKHttp总体设计图,主要是通过Diapatcher不断从RequestQueue中取出请求(Call),根据是否已缓存调用Cache或Network这两类数据获取接口之一,从内存缓存或是服务器取得请求的数据。该引擎有同步和异步请求,同步请求通过Call.execute()直接返回当前的Response,而异步请求会把当前的请求Call.enqueue添加(AsyncCall)到请求队列中,并通过回调(Callback)的方式来获取最后结果。</p>
<p>接下来会介绍一些比较重要的类,另外一些基础IO方面的内容主要来之iohttp这个包。这些类的解释大部分来至文档介绍本身,所以在此不会翻译成中文,本人觉得英语原文更能准确表达它自身的作用。</p>
<p>##OKHttp中重要的类</p>
<p><strong>1.Route.java</strong><br>The concrete route used by a connection to reach an abstract origin server.<br>When creating a connection the client has many options:</p>
<blockquote>
<ul>
<li>HTTP proxy: a proxy server may be explicitly configured for the client. Otherwise the {@linkplain java.net.ProxySelector proxy selector} is used. It may return multiple proxies to attempt.</li>
<li>IP address: whether connecting directly to an origin server or a proxy, opening a socket requires an IP address. The DNS server may return multiple IP addresses to attempt.</li>
<li>TLS configuration: which cipher suites and TLS versions to attempt with the HTTPS connection.</li>
</ul>
</blockquote>
<p>Each route is a specific selection of these options.<br>其实就是对地址的一个封装类,但是很重要。</p>
<p><strong>2.Platform.java</strong><br>Access to platform-specific features.</p>
<blockquote>
<ul>
<li>Server name indication (SNI): Supported on Android 2.3+.</li>
<li>Session Tickets: Supported on Android 2.3+.</li>
<li>Android Traffic Stats (Socket Tagging): Supported on Android 4.0+.</li>
<li>ALPN (Application Layer Protocol Negotiation): Supported on Android 5.0+. The APIs were present in Android 4.4, but that implementation was unstable.</li>
</ul>
</blockquote>
<p>Supported on OpenJDK 7 and 8 (via the JettyALPN-boot library).<br>这个类主要是做平台适应性,针对Android2.3到5.0后的网络请求的适配支持。同时,在这个类中能看到针对不同平台,通过java反射不同的class是不一样的。</p>
<p><strong>3.Connnection.java</strong><br>The sockets and streams of an HTTP, HTTPS, or HTTPS+SPDY connection. May be used for multiple HTTP request/response exchanges. Connections may be direct to the origin server or via a proxy.<br>Typically instances of this class are created, connected and exercised automatically by the HTTP client. Applications may use this class to monitor HTTP connections as members of a ConnectionPool.<br>Do not confuse this class with the misnamed HttpURLConnection, which isn’t so much a connection as a single request/response exchange.<br>Modern TLS<br>There are tradeoffs when selecting which options to include when negotiating a secure connection to a remote host. Newer TLS options are quite useful:</p>
<blockquote>
<ul>
<li>Server Name Indication (SNI) enables one IP address to negotiate secure connections for multiple domain names.</li>
<li>Application Layer Protocol Negotiation (ALPN) enables the HTTPS port (443) to be used for different HTTP and SPDY protocols.</li>
</ul>
</blockquote>
<p>Unfortunately, older HTTPS servers refuse to connect when such options are presented. Rather than avoiding these options entirely, this class allows a connection to be attempted with modern options and then retried without them should the attempt fail.</p>
<p><strong>4.ConnnectionPool.java</strong><br>Manages reuse of HTTP and SPDY connections for reduced network latency. HTTP requests that share the same Address may share a Connection. This class implements the policy of which connections to keep open for future use.<br>The system-wide default uses system properties for tuning parameters:</p>
<blockquote>
<ul>
<li>http.keepAlive true if HTTP and SPDY connections should be pooled at all. Default is true.</li>
<li>http.maxConnections maximum number of idle connections to each to keep in the pool. Default is 5.</li>
<li>http.keepAliveDuration Time in milliseconds to keep the connection alive in the pool before closing it. Default is 5 minutes. This property isn’t used by HttpURLConnection.</li>
</ul>
</blockquote>
<p>The default instance doesn’t adjust its configuration as system properties are changed. This assumes that the applications that set these parameters do so before making HTTP connections, and that this class is initialized lazily.</p>
<p><strong>5.Request.java</strong><br>An HTTP request. Instances of this class are immutable if their body is null or itself immutable.(Builder模式)</p>
<p><strong>6.Response.java</strong><br>An HTTP response. Instances of this class are not immutable: the response body is a one-shot value that may be consumed <strong>only once</strong>. All other properties are immutable.</p>
<p><strong>7.Call.java</strong><br>A call is a request that has been prepared for execution. A call can be canceled. As this object represents a single request/response pair (stream), <strong>it cannot be executed twice</strong>.</p>
<p><strong>8.Dispatcher.java</strong><br>Policy on when async requests are executed.</p>
<p>Each dispatcher uses an <strong>ExecutorService</strong> to run calls internally. If you supply your own executor, it should be able to run configured maximum number of calls concurrently.</p>
<p><strong>9.HttpEngine.java</strong><br>Handles <strong>a single HTTP request/response pair</strong>. Each HTTP engine follows this<br>lifecycle:</p>
<blockquote>
<ul>
<li>It is created.</li>
<li>The HTTP request message is sent with sendRequest(). Once the request is sent it is an error to modify the request headers. After sendRequest() has been called the request body can be written to if it exists.</li>
<li>The HTTP response message is read with readResponse(). After the response has been read the response headers and body can be read. All responses have a response body input stream, though in some instances this stream is empty.</li>
</ul>
</blockquote>
<p>The request and response may be served by the HTTP response <strong>cache</strong>, by the <strong>network</strong>, or by <strong>both</strong> in the event of a conditional GET.</p>
<p><strong>10.Internal.java</strong><br>Escalate internal APIs in {@code com.squareup.okhttp} so they can be used from OkHttp’s implementation packages. The only implementation of this interface is in {@link com.squareup.okhttp.OkHttpClient}.</p>
<p><strong>11.Cache.java</strong><br>Caches HTTP and HTTPS responses to the filesystem so they may be reused, saving time and bandwidth.</p>
<p><strong>Cache Optimization</strong><br>To measure cache effectiveness, this class tracks three statistics:</p>
<blockquote>
<ul>
<li>Request Count: the number of HTTP requests issued since this cache was created.</li>
<li>Network Count: the number of those requests that required network use.</li>
<li>Hit Count: the number of those requests whose responses were served by the cache.</li>
</ul>
</blockquote>
<p>Sometimes a request will result in a conditional cache hit. If the cache contains a stale copy of the response, the client will issue a conditional GET. The server will then send either the updated response if it has changed, or a short ‘not modified’ response if the client’s copy is still valid. Such responses increment both the network count and hit count.<br>The best way to improve the cache hit rate is by configuring the web server to return cacheable responses. Although this client honors all <a href="http://www.ietf.org/rfc/rfc2616.txt" target="_blank" rel="external">HTTP/1.1 (RFC 2068)</a> cache headers, it doesn’t cache partial responses.</p>
<p><strong>Force a Network Response</strong><br>In some situations, such as after a user clicks a ‘refresh’ button, it may be necessary to skip the cache, and fetch data directly from the server. To force a full refresh, add the {@code no-cache} directive:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">connection.addRequestProperty("Cache-Control", "no-cache")</span><br></pre></td></tr></table></figure></p>
<p>If it is only necessary to force a cached response to be validated by the server, use the more efficient {@code max-age=0} instead:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">connection.addRequestProperty("Cache-Control", "max-age=0");</span><br></pre></td></tr></table></figure></p>
<p><strong>Force a Cache Response</strong><br>Sometimes you’ll want to show resources if they are available immediately, but not otherwise. This can be used so your application can show something while waiting for the latest data to be downloaded. To restrict a request to locally-cached resources, add the {@code only-if-cached} directive:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">try {</span><br><span class="line"> connection.addRequestProperty("Cache-Control", "only-if-cached");</span><br><span class="line"> InputStream cached = connection.getInputStream();</span><br><span class="line"> // the resource was cached! show it</span><br><span class="line"> } catch (FileNotFoundException e) {</span><br><span class="line"> // the resource was not cached</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p>
<p>This technique works even better in situations where a stale response is better than no response. To permit stale cached responses, use the {@code max-stale} directive with the maximum staleness in seconds:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale</span><br><span class="line">connection.addRequestProperty("Cache-Control", "max-stale=" + maxStale);</span><br></pre></td></tr></table></figure></p>
<p><strong>12.OkHttpClient.java</strong><br>Configures and creates HTTP connections. Most applications can use a single OkHttpClient for all of their HTTP requests - benefiting from a shared response cache, thread pool, connection re-use, etc.</p>
<p>Instances of OkHttpClient are intended to be fully configured before they’re shared - once shared they should be treated as immutable and can safely be used to concurrently open new connections. If required, threads can call <strong>clone</strong> to make a shallow copy of the OkHttpClient that can be safely modified with further configuration changes.</p>
<p>##请求流程图<br>下面是关于OKHttp的请求流程图<br><img src="http://frodoking.github.io/img/android/okhttp_request_process.png" alt="OKHttp的请求流程图"></p>
<p>##详细类关系图<br>由于整个设计类图比较大,所以本人将从核心入口client、cache、interceptor、网络配置、连接池、平台适配性…这些方面来逐一进行分析源代码的设计。<br>下面是核心入口OkHttpClient的类设计图<br><img src="http://frodoking.github.io/img/android/okhttp_okhttpclient_class.png" alt="OkHttpClient的类设计图"><br>从OkHttpClient类的整体设计来看,它采用门面模式来。client知晓子模块的所有配置以及提供需要的参数。client会将所有从客户端发来的请求委派到相应的子系统去。<br>在该系统中,有多个子系统、类或者类的集合。例如上面的cache、连接以及连接池相关类的集合、网络配置相关类集合等等。每个子系统都可以被客户端直接调用,或者被门面角色调用。子系统并不知道门面的存在,对于子系统而言,门面仅仅是另外一个客户端而已。同时,OkHttpClient可以看作是整个框架的上下文。<br>通过类图,其实很明显反应了该框架的几大核心子系统;路由、连接协议、拦截器、代理、安全性认证、连接池以及网络适配。从client大大降低了开发者使用难度。同时非常明了的展示了该框架在所有需要的配置以及获取结果的方式。</p>
<p>在接下来的几个Section中将会结合子模块核心类的设计,从该框架的整体特性上来分析这些模块是如何实现各自功能。以及各个模块之间是如何相互配合来完成客户端各种复杂请求。</p>
<p>##同步与异步的实现<br>在发起请求时,整个框架主要通过Call来封装每一次的请求。同时Call持有OkHttpClient和一份HttpEngine。而每一次的同步或者异步请求都会有Dispatcher的参与,不同的是:</p>
<blockquote>
<ul>
<li>同步<br>Dispatcher会在同步执行任务队列中记录当前被执行过得任务Call,同时在当前线程中去执行Call的getResponseWithInterceptorChain()方法,直接获取当前的返回数据Response;</li>
<li>异步<br>首先来说一下Dispatcher,Dispatcher内部实现了懒加载无边界限制的线程池方式,同时该线程池采用了SynchronousQueue这种阻塞队列。SynchronousQueue每个插入操作必须等待另一个线程的移除操作,同样任何一个移除操作都等待另一个线程的插入操作。因此此队列内部其 实没有任何一个元素,或者说容量是0,严格说并不是一种容器。由于队列没有容量,因此不能调用peek操作,因为只有移除元素时才有元素。显然这是一种快速传递元素的方式,也就是说在这种情况下元素总是以最快的方式从插入者(生产者)传递给移除者(消费者),这在多任务队列中是最快处理任务的方式。对于高频繁请求的场景,无疑是最适合的。<br>异步执行是通过Call.enqueue(Callback responseCallback)来执行,在Dispatcher中添加一个封装了Callback的Call的匿名内部类Runnable来执行当前的Call。这里一定要注意的地方这个AsyncCall是Call的匿名内部类。AsyncCall的execute方法仍然会回调到Call的getResponseWithInterceptorChain方法来完成请求,同时将返回数据或者状态通过Callback来完成。</li>
</ul>
</blockquote>
<p>接下来继续讲讲Call的getResponseWithInterceptorChain()方法,这里边重点说一下拦截器链条的实现以及作用。</p>
<p>##拦截器有什么作用<br>先来看看Interceptor本身的文档解释:观察,修改以及可能短路的请求输出和响应请求的回来。通常情况下拦截器用来添加,移除或者转换请求或者回应的头部信息。<br>拦截器接口中有intercept(Chain chain)方法,同时返回Response。所谓拦截器更像是AOP设计的一种实现。下面来看一个okhttp源码中的一个引导例子来说明拦截器的作用。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">public final class LoggingInterceptors {</span><br><span class="line"> private static final Logger logger = Logger.getLogger(LoggingInterceptors.class.getName());</span><br><span class="line"> private final OkHttpClient client = new OkHttpClient();</span><br><span class="line"></span><br><span class="line"> public LoggingInterceptors() {</span><br><span class="line"> client.networkInterceptors().add(new Interceptor() {</span><br><span class="line"> @Override public Response intercept(Chain chain) throws IOException {</span><br><span class="line"> long t1 = System.nanoTime();</span><br><span class="line"> Request request = chain.request();</span><br><span class="line"> logger.info(String.format("Sending request %s on %s%n%s",</span><br><span class="line"> request.url(), chain.connection(), request.headers()));</span><br><span class="line"> Response response = chain.proceed(request);</span><br><span class="line"></span><br><span class="line"> long t2 = System.nanoTime();</span><br><span class="line"> logger.info(String.format("Received response for %s in %.1fms%n%s",</span><br><span class="line"> request.url(), (t2 - t1) / 1e6d, response.headers()));</span><br><span class="line"> return response;</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public void run() throws Exception {</span><br><span class="line"> Request request = new Request.Builder()</span><br><span class="line"> .url("https://publicobject.com/helloworld.txt")</span><br><span class="line"> .build();</span><br><span class="line"></span><br><span class="line"> Response response = client.newCall(request).execute();</span><br><span class="line"> response.body().close();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public static void main(String... args) throws Exception {</span><br><span class="line"> new LoggingInterceptors().run();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>返回信息<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">三月 19, 2015 2:11:29 下午 com.squareup.okhttp.recipes.LoggingInterceptors$1 intercept</span><br><span class="line">信息: Sending request https://publicobject.com/helloworld.txt on Connection{publicobject.com:443, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA protocol=http/1.1}</span><br><span class="line">Host: publicobject.com </span><br><span class="line">Connection: Keep-Alive</span><br><span class="line">Accept-Encoding: gzip</span><br><span class="line">User-Agent: </span><br><span class="line"></span><br><span class="line">三月 19, 2015 2:11:30 下午 com.squareup.okhttp.recipes.LoggingInterceptors$1 intercept</span><br><span class="line">信息: Received response for https://publicobject.com/helloworld.txt in 275.9ms</span><br><span class="line">Server: nginx/1.4.6 (Ubuntu)</span><br><span class="line">Date: Thu, 19 Mar 2015 06:08:50 GMT</span><br><span class="line">Content-Type: text/plain</span><br><span class="line">Content-Length: 1759</span><br><span class="line">Last-Modified: Tue, 27 May 2014 02:35:47 GMT</span><br><span class="line">Connection: keep-alive</span><br><span class="line">ETag: "5383fa03-6df"</span><br><span class="line">Accept-Ranges: bytes</span><br><span class="line">OkHttp-Selected-Protocol: http/1.1</span><br><span class="line">OkHttp-Sent-Millis: 1426745489953</span><br><span class="line">OkHttp-Received-Millis: 1426745490198</span><br></pre></td></tr></table></figure></p>
<p>从这里的执行来看,拦截器主要是针对Request和Response的切面处理。<br>那再来看看源码到底在什么位置做的这个处理呢?为了更加直观的反应执行流程,本人截图了一下执行堆栈<br><img src="http://frodoking.github.io/img/android/okhttp_interceptor_running_stack.png" alt="OKHttp总体设计图"><br>另外如果还有同学对Interceptor比较敢兴趣的可以去源码的simples模块看看GzipRequestInterceptor.java针对HTTP request body的一个zip压缩。</p>
<p>在这里再多说一下关于Call这个类的作用,在Call中持有一个HttpEngine。每一个不同的Call都有自己独立的HttpEngine。在HttpEngine中主要是各种链路和地址的选择,还有一个Transport比较重要</p>
<p>##缓存策略<br>在OkHttpClient内部暴露了有Cache和InternalCache。而InternalCache不应该手动去创建,所以作为开发使用者来说,一般用法如下:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line">public final class CacheResponse {</span><br><span class="line"> private static final Logger logger = Logger.getLogger(LoggingInterceptors.class.getName());</span><br><span class="line"> private final OkHttpClient client;</span><br><span class="line"></span><br><span class="line"> public CacheResponse(File cacheDirectory) throws Exception {</span><br><span class="line"> logger.info(String.format("Cache file path %s",cacheDirectory.getAbsoluteFile()));</span><br><span class="line"> int cacheSize = 10 * 1024 * 1024; // 10 MiB</span><br><span class="line"> Cache cache = new Cache(cacheDirectory, cacheSize);</span><br><span class="line"></span><br><span class="line"> client = new OkHttpClient();</span><br><span class="line"> client.setCache(cache);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public void run() throws Exception {</span><br><span class="line"> Request request = new Request.Builder()</span><br><span class="line"> .url("http://publicobject.com/helloworld.txt")</span><br><span class="line"> .build();</span><br><span class="line"></span><br><span class="line"> Response response1 = client.newCall(request).execute();</span><br><span class="line"> if (!response1.isSuccessful()) throw new IOException("Unexpected code " + response1);</span><br><span class="line"></span><br><span class="line"> String response1Body = response1.body().string();</span><br><span class="line"> System.out.println("Response 1 response: " + response1);</span><br><span class="line"> System.out.println("Response 1 cache response: " + response1.cacheResponse());</span><br><span class="line"> System.out.println("Response 1 network response: " + response1.networkResponse());</span><br><span class="line"></span><br><span class="line"> Response response2 = client.newCall(request).execute();</span><br><span class="line"> if (!response2.isSuccessful()) throw new IOException("Unexpected code " + response2);</span><br><span class="line"></span><br><span class="line"> String response2Body = response2.body().string();</span><br><span class="line"> System.out.println("Response 2 response: " + response2);</span><br><span class="line"> System.out.println("Response 2 cache response: " + response2.cacheResponse());</span><br><span class="line"> System.out.println("Response 2 network response: " + response2.networkResponse());</span><br><span class="line"></span><br><span class="line"> System.out.println("Response 2 equals Response 1? " + response1Body.equals(response2Body));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public static void main(String... args) throws Exception {</span><br><span class="line"> new CacheResponse(new File("CacheResponse.tmp")).run();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>返回信息<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">信息: Cache file path D:\work\workspaces\workspaces_intellij\workspace_opensource\okhttp\CacheResponse.tmp</span><br><span class="line">Response 1 response: Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}</span><br><span class="line">Response 1 cache response: null</span><br><span class="line">Response 1 network response: Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}</span><br><span class="line">Response 2 response: Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}</span><br><span class="line">Response 2 cache response: Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}</span><br><span class="line">Response 2 network response: null</span><br><span class="line">Response 2 equals Response 1? true</span><br><span class="line"></span><br><span class="line">Process finished with exit code 0</span><br></pre></td></tr></table></figure></p>
<p>上边这一段代码同样来之于simple代码CacheResponse.java,反馈回来的数据重点看一下缓存日志。第一次是来至网络数据,第二次来至缓存。<br>那在这一节重点说一下整个框架的缓存策略如何实现的。 </p>
<p>在这里继续使用上一节中讲到的运行堆栈图。从Call.getResponse(Request request, boolean forWebSocket)执行Engine.sendRequest()和Engine.readResponse()来详细说明一下。</p>
<p><strong>sendRequest()</strong><br>此方法是对可能的Response资源进行一个预判,如果需要就会开启一个socket来获取资源。如果请求存在那么就会为当前request添加请求头部并且准备开始写入request body。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line">public void sendRequest() throws IOException {</span><br><span class="line"> if (cacheStrategy != null) {</span><br><span class="line"> return; // Already sent.</span><br><span class="line"> }</span><br><span class="line"> if (transport != null) {</span><br><span class="line"> throw new IllegalStateException();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> //填充默认的请求头部和事务。</span><br><span class="line"> Request request = networkRequest(userRequest);</span><br><span class="line"></span><br><span class="line"> //下面一行很重要,这个方法会去获取client中的Cache。同时Cache在初始化的时候会去读取缓存目录中关于曾经请求过的所有信息。</span><br><span class="line"> InternalCache responseCache = Internal.instance.internalCache(client);</span><br><span class="line"> Response cacheCandidate = responseCache != null? responseCache.get(request): null;</span><br><span class="line"></span><br><span class="line"> long now = System.currentTimeMillis();</span><br><span class="line"> //缓存策略中的各种配置的封装</span><br><span class="line"> cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();</span><br><span class="line"> networkRequest = cacheStrategy.networkRequest;</span><br><span class="line"> cacheResponse = cacheStrategy.cacheResponse;</span><br><span class="line"></span><br><span class="line"> if (responseCache != null) {</span><br><span class="line"> //记录当前请求是来至网络还是命中了缓存</span><br><span class="line"> responseCache.trackResponse(cacheStrategy);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> if (cacheCandidate != null && cacheResponse == null) {</span><br><span class="line"> closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> if (networkRequest != null) {</span><br><span class="line"> // Open a connection unless we inherited one from a redirect.</span><br><span class="line"> if (connection == null) {</span><br><span class="line"> //连接到服务器、重定向服务器或者通过一个代理Connect to the origin server either directly or via a proxy.</span><br><span class="line"> connect();</span><br><span class="line"> }</span><br><span class="line"> //通过Connection创建一个SpdyTransport或者HttpTransport</span><br><span class="line"> transport = Internal.instance.newTransport(connection, this);</span><br><span class="line"> ...</span><br><span class="line"> } else {</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p>
<p><strong>readResponse()</strong><br>此方法发起刷新请求头部和请求体,解析HTTP回应头部,并且如果HTTP回应体存在的话就开始读取当前回应头。在这里有发起返回存入缓存系统,也有返回和缓存系统进行一个对比的过程。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line">public void readResponse() throws IOException {</span><br><span class="line"> ...</span><br><span class="line"> Response networkResponse;</span><br><span class="line"></span><br><span class="line"> if (forWebSocket) {</span><br><span class="line"> ...</span><br><span class="line"> } else if (!callerWritesRequestBody) {</span><br><span class="line"> // 这里主要是看当前的请求body,其实真正请求是在这里发生的。</span><br><span class="line"> // 在readNetworkResponse()方法中执行transport.finishRequest()</span><br><span class="line"> // 这里可以看一下该方法内部会调用到HttpConnection.flush()方法</span><br><span class="line"> networkResponse = new NetworkInterceptorChain(0, networkRequest).proceed(networkRequest);</span><br><span class="line"> } else {</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line"> //对Response头部事务存入事务管理中</span><br><span class="line"> receiveHeaders(networkResponse.headers());</span><br><span class="line"></span><br><span class="line"> // If we have a cache response too, then we're doing a conditional get.</span><br><span class="line"> if (cacheResponse != null) {</span><br><span class="line"> //检查缓存是否可用,如果可用。那么就用当前缓存的Response,关闭网络连接,释放连接。</span><br><span class="line"> if (validate(cacheResponse, networkResponse)) {</span><br><span class="line"> userResponse = cacheResponse.newBuilder()</span><br><span class="line"> .request(userRequest)</span><br><span class="line"> .priorResponse(stripBody(priorResponse))</span><br><span class="line"> .headers(combine(cacheResponse.headers(), networkResponse.headers()))</span><br><span class="line"> .cacheResponse(stripBody(cacheResponse))</span><br><span class="line"> .networkResponse(stripBody(networkResponse))</span><br><span class="line"> .build();</span><br><span class="line"> networkResponse.body().close();</span><br><span class="line"> releaseConnection();</span><br><span class="line"></span><br><span class="line"> // Update the cache after combining headers but before stripping the</span><br><span class="line"> // Content-Encoding header (as performed by initContentStream()).</span><br><span class="line"> // 更新缓存以及缓存命中情况</span><br><span class="line"> InternalCache responseCache = Internal.instance.internalCache(client);</span><br><span class="line"> responseCache.trackConditionalCacheHit();</span><br><span class="line"> responseCache.update(cacheResponse, stripBody(userResponse));</span><br><span class="line"> // unzip解压缩response</span><br><span class="line"> userResponse = unzip(userResponse);</span><br><span class="line"> return;</span><br><span class="line"> } else {</span><br><span class="line"> closeQuietly(cacheResponse.body());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> userResponse = networkResponse.newBuilder()</span><br><span class="line"> .request(userRequest)</span><br><span class="line"> .priorResponse(stripBody(priorResponse))</span><br><span class="line"> .cacheResponse(stripBody(cacheResponse))</span><br><span class="line"> .networkResponse(stripBody(networkResponse))</span><br><span class="line"> .build();</span><br><span class="line"></span><br><span class="line"> //发起缓存的地方</span><br><span class="line"> if (hasBody(userResponse)) {</span><br><span class="line"> maybeCache();</span><br><span class="line"> userResponse = unzip(cacheWritingResponse(storeRequest, userResponse));</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p>
<p>##HTTP连接的实现方式(说说连接池)<br>外部网络请求的入口都是通过Transport接口来完成。该类采用了桥接模式将HttpEngine和HttpConnection来连接起来。因为HttpEngine只是一个逻辑处理器,同时它也充当了请求配置的提供引擎,而HttpConnection是对底层处理Connection的封装。</p>
<p>OK现在重点转移到HttpConnection(一个用于发送HTTP/1.1信息的socket连接)这里。主要有如下的生命周期:</p>
<blockquote>
<p>1、发送请求头;<br>2、打开一个sink(io中有固定长度的或者块结构chunked方式的)去写入请求body;<br>3、写入并且关闭sink;<br>4、读取Response头部;<br>5、打开一个source(对应到第2步的sink方式)去读取Response的body;<br>6、读取并关闭source;</p>
</blockquote>
<p>下边看一张关于连接执行的时序图:<br><img src="http://frodoking.github.io/img/android/okhttp_connection_lifecycle.png" alt="OKHttp连接执行时序图"><br>这张图画得比较简单,详细的过程以及连接池的使用下面大致说明一下:</p>
<blockquote>
<p>1、连接池是暴露在client下的,它贯穿了Transport、HttpEngine、Connection、HttpConnection和SpdyConnection;在这里目前默认讨论HttpConnection;<br>2、ConnectionPool有两个构建参数是maxIdleConnections(最大空闲连接数)和keepAliveDurationNs(存活时间),另外连接池默认的线程池采用了Single的模式(源码解释是:一个用于清理过期的多个连接的后台线程,最多一个单线程去运行每一个连接池);<br>3、发起请求是在Connection.connect()这里,实际执行是在HttpConnection.flush()这里进行一个刷入。这里重点应该关注一下sink和source,他们创建的默认方式都是依托于同一个socket:<br> this.source = Okio.buffer(Okio.source(socket));<br> this.sink = Okio.buffer(Okio.sink(socket));<br> 如果再进一步看一下io的源码就能看到:<br> Source source = source((InputStream)socket.getInputStream(), (Timeout)timeout);<br> Sink sink = sink((OutputStream)socket.getOutputStream(), (Timeout)timeout);<br> 这下我想大家都应该明白这里到底是真么回事儿了吧?<br> 相关的sink和source还有相应的细分,如果有兴趣的朋友可以继续深入看一下,这里就不再深入了。不然真的说不完了。。。</p>
</blockquote>
<p>其实连接池这里还是有很多值得细看的地方,由于时间有限,到这里已经花了很多时间搞这事儿了。。。</p>
<p>##重连机制<br>这里重点说说连接链路的相关事情。说说自动重连到底是如何实现的。<br>照样先来看看下面的这个自动重连机制的实现方式时序图<br><img src="http://frodoking.github.io/img/android/okhttp_connection_reset.png" alt="OKHttp重连执行时序图"></p>
<p>同时回到Call.getResponse()方法说起<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">Response getResponse(Request request, boolean forWebSocket) throws IOException {</span><br><span class="line"> ...</span><br><span class="line"> while (true) { // 自动重连机制的循环处理</span><br><span class="line"> if (canceled) {</span><br><span class="line"> engine.releaseConnection();</span><br><span class="line"> return null;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> try {</span><br><span class="line"> engine.sendRequest();</span><br><span class="line"> engine.readResponse();</span><br><span class="line"> } catch (IOException e) {</span><br><span class="line"> //如果上一次连接异常,那么当前连接进行一个恢复。</span><br><span class="line"> HttpEngine retryEngine = engine.recover(e, null);</span><br><span class="line"> if (retryEngine != null) {</span><br><span class="line"> engine = retryEngine;</span><br><span class="line"> continue;//如果恢复成功,那么继续重新请求</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // Give up; recovery is not possible.如果不行,那么就中断了</span><br><span class="line"> throw e;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> Response response = engine.getResponse();</span><br><span class="line"> Request followUp = engine.followUpRequest();</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p>
<p>相信这一段代码能让同学们清晰的看到自动重连机制的实现方式,那么我们来看看详细的步骤:</p>
<blockquote>
<p>1、HttpEngine.recover()的实现方式是通过检测RouteSelector是否还有更多的routes可以尝试连接,同时会去检查是否可以恢复等等的一系列判断。如果可以会为重新连接重新创建一份新的HttpEngine,同时把相应的链路信息传递过去;<br>2、当恢复后的HttpEngine不为空,那么替换当前Call中的当前HttpEngine,执行while的continue,发起下一次的请求;<br>3、再重点强调一点HttpEngine.sendRequest()。这里之前分析过会触发connect()方法,在该方法中会通过RouteSelector.next()再去找当前适合的Route。多说一点,next()方法会传递到nextInetSocketAddress()方法,而此处一段重要的执行代码就是network.resolveInetAddresses(socketHost)。这个地方最重要的是在Network这个接口中有一个对该接口的DEFAULT的实现域,而该方法通过工具类InetAddress.getAllByName(host)来完成对数组类的地址解析。<br> 所以,多地址可以采用[“<a href="http://aaaaa","https://bbbbbb"]的方式来配置。" target="_blank" rel="external">http://aaaaa","https://bbbbbb"]的方式来配置。</a></p>
</blockquote>
<p>##Gzip的使用方式<br>在源码引导RequestBodyCompression.java中我们可以看到gzip的使用身影。通过拦截器对Request 的body进行gzip的压缩,来减少流量的传输。<br>Gzip实现的方式主要是通过GzipSink对普通sink的封装压缩。在这个地方就不再贴相关代码的实现。有兴趣同学对照源码看一下就ok。</p>
<p>强大的Interceptor设计应该也算是这个框架的一个亮点。</p>
<p>##安全性<br>连接安全性主要是在HttpEngine.connect()方法。上一节油讲到地址相关的选择,在HttpEngine中有一个静态方法createAddress(client, networkRequest),在这里通过获取到OkHttpClient中关于SSLSocketFactory、HostnameVerifier和CertificatePinner的配置信息。而这些信息大部分采用默认情况。这些信息都会在后面的重连中作为对比参考项。</p>
<p>同时在Connection.upgradeToTls()方法中,有对SSLSocket、SSLSocketFactory的创建活动。这些创建都会被记录到ConnectionSpec中,当发起ConnectionSpec.apply()会发起一些列的配置以及验证。</p>
<p>建议有兴趣的同学先了解java的SSLSocket相关的开发再来了解本框架中的安全性,会更能理解一些。</p>
<p>##平台适应性<br>讲了很多,终于来到了平台适应性了。Platform是整个平台适应的核心类。同时它封装了针对不同平台的三个平台类Android和JdkWithJettyBootPlatform。<br>代码实现在Platform.findPlatform中<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line">private static Platform findPlatform() {</span><br><span class="line"> // Attempt to find Android 2.3+ APIs.</span><br><span class="line"> try {</span><br><span class="line"> try {</span><br><span class="line"> Class.forName("com.android.org.conscrypt.OpenSSLSocketImpl");</span><br><span class="line"> } catch (ClassNotFoundException e) {</span><br><span class="line"> // Older platform before being unbundled.</span><br><span class="line"> Class.forName("org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl");</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> OptionalMethod<Socket> setUseSessionTickets</span><br><span class="line"> = new OptionalMethod<>(null, "setUseSessionTickets", boolean.class);</span><br><span class="line"> OptionalMethod<Socket> setHostname</span><br><span class="line"> = new OptionalMethod<>(null, "setHostname", String.class);</span><br><span class="line"> Method trafficStatsTagSocket = null;</span><br><span class="line"> Method trafficStatsUntagSocket = null;</span><br><span class="line"> OptionalMethod<Socket> getAlpnSelectedProtocol = null;</span><br><span class="line"> OptionalMethod<Socket> setAlpnProtocols = null;</span><br><span class="line"></span><br><span class="line"> // Attempt to find Android 4.0+ APIs.</span><br><span class="line"> try {</span><br><span class="line"> //流浪统计类</span><br><span class="line"> Class<?> trafficStats = Class.forName("android.net.TrafficStats");</span><br><span class="line"> trafficStatsTagSocket = trafficStats.getMethod("tagSocket", Socket.class);</span><br><span class="line"> trafficStatsUntagSocket = trafficStats.getMethod("untagSocket", Socket.class);</span><br><span class="line"></span><br><span class="line"> // Attempt to find Android 5.0+ APIs.</span><br><span class="line"> try {</span><br><span class="line"> Class.forName("android.net.Network"); // Arbitrary class added in Android 5.0.</span><br><span class="line"> getAlpnSelectedProtocol = new OptionalMethod<>(byte[].class, "getAlpnSelectedProtocol");</span><br><span class="line"> setAlpnProtocols = new OptionalMethod<>(null, "setAlpnProtocols", byte[].class);</span><br><span class="line"> } catch (ClassNotFoundException ignored) {</span><br><span class="line"> }</span><br><span class="line"> } catch (ClassNotFoundException | NoSuchMethodException ignored) {</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> return new Android(setUseSessionTickets, setHostname, trafficStatsTagSocket,</span><br><span class="line"> trafficStatsUntagSocket, getAlpnSelectedProtocol, setAlpnProtocols);</span><br><span class="line"> } catch (ClassNotFoundException ignored) {</span><br><span class="line"> // This isn't an Android runtime.</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // Find Jetty's ALPN extension for OpenJDK.</span><br><span class="line"> try {</span><br><span class="line"> String negoClassName = "org.eclipse.jetty.alpn.ALPN";</span><br><span class="line"> Class<?> negoClass = Class.forName(negoClassName);</span><br><span class="line"> Class<?> providerClass = Class.forName(negoClassName + "$Provider");</span><br><span class="line"> Class<?> clientProviderClass = Class.forName(negoClassName + "$ClientProvider");</span><br><span class="line"> Class<?> serverProviderClass = Class.forName(negoClassName + "$ServerProvider");</span><br><span class="line"> Method putMethod = negoClass.getMethod("put", SSLSocket.class, providerClass);</span><br><span class="line"> Method getMethod = negoClass.getMethod("get", SSLSocket.class);</span><br><span class="line"> Method removeMethod = negoClass.getMethod("remove", SSLSocket.class);</span><br><span class="line"> return new JdkWithJettyBootPlatform(</span><br><span class="line"> putMethod, getMethod, removeMethod, clientProviderClass, serverProviderClass);</span><br><span class="line"> } catch (ClassNotFoundException | NoSuchMethodException ignored) {</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> return new Platform();</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p>
<p>这里采用了JAVA的反射原理调用到class的method。最后在各自的平台调用下发起invoke来执行相应方法。详情请参看继承了Platform的Android类。<br>当然要做这两种的平台适应,必须要知道当前平台在内存中相关的class地址以及相关方法。</p>
<p>##总结<br>1、从整体结构和类内部域中都可以看到OkHttpClient,有点类似与安卓的ApplicationContext。看起来更像一个单例的类,这样使用好处是统一。但是如果你不是高手,建议别这么用,原因很简单:逻辑牵连太深,如果出现问题要去追踪你会有深深地罪恶感的;<br>2、框架中的一些动态方法、静态方法、匿名内部类以及Internal的这些代码相当规整,每个不同类的不同功能能划分在不同的地方。很值得开发者学习的地方;<br>3、从平台的兼容性来讲,也是很不错的典范(如果你以后要从事API相关编码,那更得好好注意对兼容性的处理);<br>4、由于时间不是很富裕,所以本人对细节的把握还是不够,这方面还得多多努力;<br>5、对于初学网络编程的同学来说,可能一开始学习都是从简单的socket的发起然后获取响应开始的。因为没有很好的场景能让自己知道网络编程到底有多么的重要,当然估计也没感受到网络编程有多么的难受。我想这是很多刚入行的同学们的一种内心痛苦之处;<br>6、不足的地方是没有对SPDY的方式最详细跟进剖析(手头还有工作的事情,后面如果有时间再补起来吧)。</p>
<p>##结束语<br>很早前都打算花点时间好好来看一个值得学习的框架,今天终于算是弄得差不多了。我相信从框架的前期使用、到代码的介入、再到源码分模块的剖析、最后到整理成文章。我想这都是一个很好的学习和成长的过程。</p>
<p>希望看到这篇文章的同学能做出评价,并且给出一些好的剖析点。</p>
<p>我也是一个普普通通的编码人,只是内心多了一点点不“安分” ^.^。</p>
<p>##后续<br>最近看到一些网友建议把okhttp的连接池对Connection的重用维护机制以及HTTP和SPDY协议如何得到区分这两部分内容做深入的分析<br>有需要的同学请移步:<a href="/2015/06/29/android-okhttp-connectionpool-http1-x-http2-x/">OKHttp源码解析-ConnectionPool对Connection重用机制&Http/Https/SPDY协议选择</a></p>
<p>因文章很快被人转载到一些其他网站,所以本人在此声明:<br><strong>转载请标明转载出处:<a href="http://frodoking.github.io/2015/03/12/android-okhttp/">http://frodoking.github.io/2015/03/12/android-okhttp/</a> </strong></p>
<p>Android为我们提供了两种HTTP交互的方式:HttpURLConnection 和 Apache HTTP Client,虽然两者都支持HTTPS,流的上传和下载,配置超时,IPv6和连接池,已足够满足我们各种HTTP请求的需求。但更高效的使用HTTP可以让您的应用运行更快、更节省流量。而OkHttp库就是为此而生。</p>
<p>OkHttp是一个高效的HTTP库:</p>
<blockquote>
<ul>
<li>支持 SPDY ,共享同一个Socket来处理同一个服务器的所有请求</li>
<li>如果SPDY不可用,则通过连接池来减少请求延时</li>
<li>无缝的支持GZIP来减少数据流量</li>
<li>缓存响应数据来减少重复的网络请求</li>
</ul>
</blockquote>
<p>会从很多常用的连接问题中自动恢复。如果您的服务器配置了多个IP地址,当第一个IP连接失败的时候,OkHttp会自动尝试下一个IP。OkHttp还处理了代理服务器问题和SSL握手失败问题。</p>
<p>使用 OkHttp 无需重写您程序中的网络代码。OkHttp实现了几乎和java.net.HttpURLConnection一样的API。如果您用了 Apache HttpClient,则OkHttp也提供了一个对应的okhttp-apache 模块。</p>
<p>OKHttp源码位置<a href="https://github.com/square/okhttp">https://github.com/square/okhttp</a><br>
依赖注入思路以及如何选择(Dagger、RoboGuice和ButterKnife)
http://frodoking.github.io/2015/02/10/android-dependency-injection-struction/
2015-02-10T08:17:51.000Z
2015-05-10T13:36:47.609Z
<p>在开发程序的时候,会用到各种对象,很多对象在使用之前都需要进行初始化。例如你要操作一个SharedPreference,你需要调用getSharedPreferences(String name,int mode)来获取一个对象,然后才能使用它。而如果这个对象会在多个Activity中被使用,你就需要在每个使用的场景中都写下同样的代码。这不仅麻烦,而且增加了出错的可能。依赖注入让你不需要初始化对象。换句话说,任何对象声明完了就能直接用。</p>
<p>当涉及到Android上的依赖注入(DI.dependency injection)类库的时候,存在不少的选择,但怎么知道哪一个最适合你呢?下面将针对目前比较流行的三种依赖注入框架(Dagger、RoboGuice和ButterKnife)做进一步的对比和分析。<br><a id="more"></a></p>
<h3 id="Dagger"><a href="#Dagger" class="headerlink" title="Dagger"></a>Dagger</h3><p>dagger是使用依赖注入的方式,使用Annotation给需要注入的对象做标记,通过inject()方法自动注入所有对象,从而完成自动的初始化。<br>dagger特别适合用在低端设备上,因为它没有采取反射而使用了预编译技术,因为基于反射的DI非常占用资源和耗时。Dagger或许不是最理想的依赖注入框架,但它可能是最高效的。</p>
<p><a href="http://www.codekk.com/open-source-project-analysis/detail/Android/%E6%89%94%E7%89%A9%E7%BA%BF/Dagger%20%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90" target="_blank" rel="external">Dagger源码解析</a><br>另外还有针对注解方式的具体实现方式分析<br><a href="http://codekk.com/open-source-project-analysis/detail/Android/Trinea/%E5%85%AC%E5%85%B1%E6%8A%80%E6%9C%AF%E7%82%B9%E4%B9%8BJava%20%E6%B3%A8%E8%A7%A3%20Annotation" target="_blank" rel="external">Java Annotation</a></p>
<h3 id="RoboGuice"><a href="#RoboGuice" class="headerlink" title="RoboGuice"></a>RoboGuice</h3><p>待续。。。</p>
<h3 id="ButterKnife"><a href="#ButterKnife" class="headerlink" title="ButterKnife"></a>ButterKnife</h3><p>待续。。。</p>
<p>由于后面对注入思想的热衷程度的下降,此文章暂时不做更新。。。</p>
<p>在开发程序的时候,会用到各种对象,很多对象在使用之前都需要进行初始化。例如你要操作一个SharedPreference,你需要调用getSharedPreferences(String name,int mode)来获取一个对象,然后才能使用它。而如果这个对象会在多个Activity中被使用,你就需要在每个使用的场景中都写下同样的代码。这不仅麻烦,而且增加了出错的可能。依赖注入让你不需要初始化对象。换句话说,任何对象声明完了就能直接用。</p>
<p>当涉及到Android上的依赖注入(DI.dependency injection)类库的时候,存在不少的选择,但怎么知道哪一个最适合你呢?下面将针对目前比较流行的三种依赖注入框架(Dagger、RoboGuice和ButterKnife)做进一步的对比和分析。<br>
MVC和MVP在app中的对比分析以及实际应用
http://frodoking.github.io/2015/02/01/android-mvc-mvp-analysis/
2015-02-01T07:01:23.000Z
2015-09-14T06:34:36.167Z
<p>为了解决逻辑处理和UI视图的松散耦合,MVC和MVP的架构模式在很多App中使用比较广泛。</p>
<p>那什么是MVP呢?它又和我们常常听到的MVC有什么关系了以及区别呢?</p>
<p><img src="http://frodoking.github.io/img/android/mvp_mvc_img.png" alt="MVP和MVC架构图"><br><a id="more"></a></p>
<p>MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地方:Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示。作为一种新的模式,MVP与MVC有着一个重大的区别:在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会从直接Model中读取数据而不是通过 Controller。</p>
<p>在MVC里,View是可以直接访问Model的!从而,View里会包含Model信息,不可避免的还要包括一些业务逻辑。 在MVC模型里,更关注的Model的不变,而同时有多个对Model的不同显示,及View。所以,在MVC模型里,Model不依赖于View,但是View是依赖于Model的。不仅如此,因为有一些业务逻辑在View里实现了,导致要更改View也是比较困难的,至少那些业务逻辑是无法重用的。</p>
<h3 id="MVP如何解决MVC的问题?"><a href="#MVP如何解决MVC的问题?" class="headerlink" title="MVP如何解决MVC的问题?"></a>MVP如何解决MVC的问题?</h3><p>在MVP里,Presenter完全把Model和View进行了分离,主要的程序逻辑在Presenter里实现。而且,Presenter与具体的View是没有直接关联的,而是通过定义好的接口进行交互,从而使得在变更View时候可以保持Presenter的不变,即重用! 不仅如此,我们还可以编写测试用的View,模拟用户的各种操作,从而实现对Presenter的测试–而不需要使用自动化的测试工具。 我们甚至可以在Model和View都没有完成时候,就可以通过编写Mock Object(即实现了Model和View的接口,但没有具体的内容的)来测试Presenter的逻辑。 在MVP里,应用程序的逻辑主要在Presenter来实现,其中的View是很薄的一层。因此就有人提出了Presenter First的设计模式,就是根据User Story来首先设计和开发Presenter。在这个过程中,View是很简单的,能够把信息显示清楚就可以了。在后面,根据需要再随便更改View,而对Presenter没有任何的影响了。 如果要实现的UI比较复杂,而且相关的显示逻辑还跟Model有关系,就可以在View和Presenter之间放置一个Adapter。由这个 Adapter来访问Model和View,避免两者之间的关联。而同时,因为Adapter实现了View的接口,从而可以保证与Presenter之间接口的不变。这样就可以保证View和Presenter之间接口的简洁,又不失去UI的灵活性。 在MVP模式里,View只应该有简单的Set/Get的方法,用户输入和设置界面显示的内容,除此就不应该有更多的内容,绝不容许直接访问Model–这就是与MVC很大的不同之处。</p>
<p>MVP的优点:</p>
<blockquote>
<p>1、模型与视图完全分离,我们可以修改视图而不影响模型;<br>2、可以更高效地使用模型,因为所有的交互都发生在一个地方——Presenter内部;<br>3、我们可以将一个Presenter用于多个视图,而不需要改变Presenter的逻辑。这个特性非常的有用,因为视图的变化总是比模型的变化频繁;<br>4、如果我们把逻辑放在Presenter中,那么我们就可以脱离用户接口来测试这些逻辑(单元测试)。</p>
</blockquote>
<h3 id="使用方法"><a href="#使用方法" class="headerlink" title="使用方法"></a>使用方法</h3><p>1、建立bean<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">public class UserBean {</span><br><span class="line"> private String mFirstName;</span><br><span class="line"> private String mLastName;</span><br><span class="line"></span><br><span class="line"> public UserBean(String firstName, String lastName) {</span><br><span class="line"> this. mFirstName = firstName;</span><br><span class="line"> this. mLastName = lastName;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public String getFirstName() {</span><br><span class="line"> return mFirstName;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public String getLastName() {</span><br><span class="line"> return mLastName;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>2、建立model接口(处理业务逻辑,这里指数据读写)<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">public interface IUserModel {</span><br><span class="line"> void setID(int id);</span><br><span class="line"></span><br><span class="line"> void setFirstName(String firstName);</span><br><span class="line"></span><br><span class="line"> void setLastName(String lastName);</span><br><span class="line"></span><br><span class="line"> int getID();</span><br><span class="line"></span><br><span class="line"> UserBean load(int id);// 通过id读取user信息,返回一个UserBean</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>3、建立view接口(更新ui中的view状态),这里列出需要操作当前view的方法<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">public interface IUserView {</span><br><span class="line"> int getID();</span><br><span class="line"></span><br><span class="line"> String getFristName();</span><br><span class="line"></span><br><span class="line"> String getLastName();</span><br><span class="line"></span><br><span class="line"> void setFirstName(String firstName);</span><br><span class="line"></span><br><span class="line"> void setLastName(String lastName);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>4、建立presenter(主导器,通过iView和iModel接口操作model和view),activity可以把所有逻辑给presenter处理,这样java逻辑就从手机的activity中分离出来<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">public class UserPresenter {</span><br><span class="line"> private IUserView mUserView;</span><br><span class="line"> private IUserModel mUserModel;</span><br><span class="line"></span><br><span class="line"> public UserPresenter(IUserView view) {</span><br><span class="line"> mUserView = view;</span><br><span class="line"> mUserModel = new UserModel();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public void saveUser( int id, String firstName, String lastName) {</span><br><span class="line"> mUserModel.setID(id);</span><br><span class="line"> mUserModel.setFirstName(firstName);</span><br><span class="line"> mUserModel.setLastName(lastName);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public void loadUser( int id) {</span><br><span class="line"> UserBean user = mUserModel.load(id);</span><br><span class="line"> mUserView.setFirstName(user.getFirstName()); // 通过调用IUserView的方法来更新显示</span><br><span class="line"> mUserView.setLastName(user.getLastName());</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<h3 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h3><p>MVP主要解决就是把逻辑层抽出来成P层,要是遇到需求逻辑上的更改就可以只需要修改P层了或者遇到逻辑上的大概我们可以直接从写一个P也可以,很多开发人员把所有的东西都写在了Activity/Fragment里面这样一来遇到频繁改需求或者逻辑越来越复杂的时候,Activity/Fragment里面就会出现过多的混杂逻辑导致出错,所以MVP模式对于APP来对控制逻辑和UI的解耦来说是一个不错的选择!</p>
<p>在这里多说一下,其实MVP只是一个总体的解决方案。在V和P之间其实我们还可以采用事件总线的方案来解决这种高耦合的情况。本人在另外一篇文章中将会讲到<a href="/2015/03/30/android-eventbus-otto-analysis/">事件总线(otto的bus和eventbus的一个对比分析)</a></p>
<p>为了解决逻辑处理和UI视图的松散耦合,MVC和MVP的架构模式在很多App中使用比较广泛。</p>
<p>那什么是MVP呢?它又和我们常常听到的MVC有什么关系了以及区别呢?</p>
<p><img src="http://frodoking.github.io/img/android/mvp_mvc_img.png" alt="MVP和MVC架构图"><br>
关于Fragment和Activity对比中的一些理解
http://frodoking.github.io/2014/12/21/android-fragment-lifecycle/
2014-12-21T07:43:27.000Z
2015-09-14T06:20:08.669Z
<h3 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h3><p>自从Android 3.0中引入fragments 的概念,根据词海的翻译可以译为:碎片、片段。其目的是为了解决不同屏幕分辩率的动态和灵活UI设计。大屏幕如平板小屏幕如手机,平板电脑的设计使得其有更多的空间来放更多的UI组件,而多出来的空间存放UI使其会产生更多的交互,从而诞生了fragments 。</p><p></p>
<p>fragments 的设计不需要你来亲自管理view hierarchy 的复杂变化,通过将Activity 的布局分散到frament 中,可以在运行时修改activity 的外观,并且由activity 管理的back stack 中保存些变化。当一个片段指定了自身的布局时,它能和其他片段配置成不同的组合,在活动中为不同的屏幕尺寸修改布局配置(小屏幕可能每次显示一个片段,而大屏幕则可以显示两个或更多)。</p><p></p>
<p>Fragment必须被写成可重用的模块。因为fragment有自己的layout,自己进行事件响应,拥有自己的生命周期和行为,所以你可以在多个activity中包含同一个Fragment的不同实例。这对于让你的界面在不同的屏幕尺寸下都能给用户完美的体验尤其重要。</p><p></p>
<a id="more"></a>
<h3 id="Fragment优点"><a href="#Fragment优点" class="headerlink" title="Fragment优点"></a>Fragment优点</h3><blockquote>
<ul>
<li>Fragment可以使你能够将activity分离成多个可重用的组件,每个都有它自己的生命周期和UI。</li>
<li>Fragment可以轻松得创建动态灵活的UI设计,可以适应于不同的屏幕尺寸。从手机到平板电脑。</li>
<li>Fragment是一个独立的模块,紧紧地与activity绑定在一起。可以运行中动态地移除、加入、交换等。</li>
<li>Fragment提供一个新的方式让你在不同的安卓设备上统一你的UI。</li>
<li>Fragment 解决Activity间的切换不流畅,轻量切换。</li>
<li>Fragment 替代TabActivity做导航,性能更好。</li>
<li>Fragment 在4.2.版本中新增嵌套fragment使用方法,能够生成更好的界面效果。</li>
<li>Fragment做局部内容更新更方便,原来为了到达这一点要把多个布局放到一个activity里面,现在可以用多Fragment来代替,只有在需要的时候才加载Fragment,提高性能。</li>
<li>可以从startActivityForResult中接收到返回结果,但是View不能。</li>
</ul>
</blockquote>
<h3 id="Fragment和Activity生命周期对比"><a href="#Fragment和Activity生命周期对比" class="headerlink" title="Fragment和Activity生命周期对比"></a>Fragment和Activity生命周期对比</h3><p>先让我们来看一下下面这一张Fragment和Activity的完整的生命周期的流程对比图</p>
<p><img src="http://frodoking.github.io/img/android/complete_android_fragment_lifecycle.png" alt="Fragment和Activity的完整的生命周期的流程对比图"></p>
<p>从这张图来看生命周期对比非常的明确。因此在这个地方不在做非常详细的对比和解释。</p>
<p>下面重点来说一下getActivity()经常null的情况,在Fragment里边有attach和dettach方法。其实这样拿到的activity都不是为null的,而且Fragment的大部分生命周期中都是可以拿到宿主Activity的。那为何会出现空的情况呢?</p>
<p>现在对Fragment的管理类FragmentManager做一下全面的分析:</p>
<p>Android采用了FragmentManager对所有创建的Fragment进行了按照tag方式的缓存机制,每次生命周期的执行如果系统存在相同tag的Fragment时候,这个Fragment会被重新利用。但是如果使用者不知道这样的机制的话,那么获取到当前绑定的Activity很有可能是一个过期被回收的类。(这样的场景在内存吃紧的情况下是必定出现的。)</p>
<p>最正确的做法代码如下:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">public Object instantiateItem(ViewGroup container, int position) {</span><br><span class="line"> if(this.mCurTransaction == null) {</span><br><span class="line"> this.mCurTransaction = this.mFragmentManager.beginTransaction();</span><br><span class="line"> }</span><br><span class="line"> String name = getItem(position).getClass().getCanonicalName();</span><br><span class="line"> Fragment fragment = this.mFragmentManager.findFragmentByTag(name);</span><br><span class="line"> if(fragment != null) {</span><br><span class="line"> this.mCurTransaction.attach(fragment);</span><br><span class="line"> } else {</span><br><span class="line"> fragment = this.getItem(position);</span><br><span class="line"> this.mCurTransaction.add(container.getId(), fragment, name);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> if(fragment != this.mCurrentPrimaryItem) {</span><br><span class="line"> fragment.setMenuVisibility(false);</span><br><span class="line"> fragment.setUserVisibleHint(false);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> return fragment;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p>
<p>这是笔者在做fragment+viewpager的时候遇到的问题,这段代码是针对adapter的重写。</p>
<p>Fragment页面相互切换的生命周期对比–场景演示<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line">切换到该Fragment</span><br><span class="line">11-29 14:26:35.095: D/AppListFragment(7649): onAttach</span><br><span class="line">11-29 14:26:35.095: D/AppListFragment(7649): onCreate</span><br><span class="line">11-29 14:26:35.095: D/AppListFragment(7649): onCreateView</span><br><span class="line">11-29 14:26:35.100: D/AppListFragment(7649): onActivityCreated</span><br><span class="line">11-29 14:26:35.120: D/AppListFragment(7649): onStart</span><br><span class="line">11-29 14:26:35.120: D/AppListFragment(7649): onResume</span><br><span class="line"></span><br><span class="line">屏幕灭掉:</span><br><span class="line">11-29 14:27:35.185: D/AppListFragment(7649): onPause</span><br><span class="line">11-29 14:27:35.205: D/AppListFragment(7649): onSaveInstanceState</span><br><span class="line">11-29 14:27:35.205: D/AppListFragment(7649): onStop</span><br><span class="line"></span><br><span class="line">屏幕解锁</span><br><span class="line">11-29 14:33:13.240: D/AppListFragment(7649): onStart</span><br><span class="line">11-29 14:33:13.275: D/AppListFragment(7649): onResume</span><br><span class="line"></span><br><span class="line">切换到其他Fragment:</span><br><span class="line">11-29 14:33:33.655: D/AppListFragment(7649): onPause</span><br><span class="line">11-29 14:33:33.655: D/AppListFragment(7649): onStop</span><br><span class="line">11-29 14:33:33.660: D/AppListFragment(7649): onDestroyView</span><br><span class="line"></span><br><span class="line">切换回本身的Fragment:</span><br><span class="line">11-29 14:33:55.820: D/AppListFragment(7649): onCreateView</span><br><span class="line">11-29 14:33:55.825: D/AppListFragment(7649): onActivityCreated</span><br><span class="line">11-29 14:33:55.825: D/AppListFragment(7649): onStart</span><br><span class="line">11-29 14:33:55.825: D/AppListFragment(7649): onResume</span><br><span class="line"></span><br><span class="line">回到桌面</span><br><span class="line">11-29 14:34:26.590: D/AppListFragment(7649): onPause</span><br><span class="line">11-29 14:34:26.880: D/AppListFragment(7649): onSaveInstanceState</span><br><span class="line">11-29 14:34:26.880: D/AppListFragment(7649): onStop</span><br><span class="line"></span><br><span class="line">回到应用</span><br><span class="line">11-29 14:36:51.940: D/AppListFragment(7649): onStart</span><br><span class="line">11-29 14:36:51.940: D/AppListFragment(7649): onResume</span><br><span class="line"></span><br><span class="line">退出应用</span><br><span class="line">11-29 14:37:03.020: D/AppListFragment(7649): onPause</span><br><span class="line">11-29 14:37:03.155: D/AppListFragment(7649): onStop</span><br><span class="line">11-29 14:37:03.155: D/AppListFragment(7649): onDestroyView</span><br><span class="line">11-29 14:37:03.165: D/AppListFragment(7649): onDestroy</span><br><span class="line">11-29 14:37:03.165: D/AppListFragment(7649): onDetach</span><br><span class="line"></span><br><span class="line">比Activity多了一些生命周期,完整和Activity对接上</span><br></pre></td></tr></table></figure></p>
<p>一个完善的app应该考虑好自己Fragment内部每个生命周期必须干的事情,比如onCreateView()和onViewCreated()都是不一样的。他们代表的view是否真的加载到当前app中。所以不同阶段Fragment的生命周期体现的状态时很不一样的。</p>
<h3 id="Fragment和Activity、Fragment和Fragment之间通信"><a href="#Fragment和Activity、Fragment和Fragment之间通信" class="headerlink" title="Fragment和Activity、Fragment和Fragment之间通信"></a>Fragment和Activity、Fragment和Fragment之间通信</h3><p>官方文档推荐使用回调的方式来进行Fragment和Activity、Fragment和Fragment之间通信。但是从笔者个人开发经验来讲我更倾向于使用<a href="https://github.com/frodoking/EventBus.git" target="_blank" rel="external">EventBus</a>。</p>
<h3 id="关于采用Fragment来逐步替代Activity的一些建议"><a href="#关于采用Fragment来逐步替代Activity的一些建议" class="headerlink" title="关于采用Fragment来逐步替代Activity的一些建议"></a>关于采用Fragment来逐步替代Activity的一些建议</h3><blockquote><p>1、使用Activity做一个容器,在每次启动新页面时候直接将启动的Fragment装入到Activity容器中,这样在Manifest中只需要注册这个Activity容器就ok了。(但是带来的问题:activity暂用了没有必要的内存)</p>
<p>2、采用一个MainActivity+FragmentManager的方式来对所有的Fragment进行一个集中管理。这种解决方案从性能和代码量来讲都是最好的。但是可能各种页面切换的时候逻辑维护要求比较高。</p>
</blockquote>
<p>另外,本人贴一下关于本人在实际项目中的一种解决方案<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">/**</span><br><span class="line"> * 负责装载Fragment的容器</span><br><span class="line"> * @author Frodo</span><br><span class="line"> */</span><br><span class="line">public class LoaderActivity extends BaseActivity {</span><br><span class="line"></span><br><span class="line"> private FrameLayout rootView;</span><br><span class="line"> private Fragment rootFragment;</span><br><span class="line"></span><br><span class="line"> @Override</span><br><span class="line"> protected void onCreate(Bundle savedInstanceState) {</span><br><span class="line"> super.onCreate(savedInstanceState);</span><br><span class="line"></span><br><span class="line"> rootView = new FrameLayout(this);</span><br><span class="line"> rootView.setLayoutParams(new ViewGroup.LayoutParams(</span><br><span class="line"> ViewGroup.LayoutParams.MATCH_PARENT,</span><br><span class="line"> ViewGroup.LayoutParams.MATCH_PARENT));</span><br><span class="line"> rootView.setId(getRootViewID());</span><br><span class="line"> setContentView(rootView);</span><br><span class="line"></span><br><span class="line"> Uri uri = getIntent().getData();</span><br><span class="line"> if (uri == null) {</span><br><span class="line"> setError(400, null);</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> String fragmentName = uri.getFragment();</span><br><span class="line"> if (fragmentName == null) {</span><br><span class="line"> setError(401, null);</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> if (savedInstanceState != null) {</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> try {</span><br><span class="line"> rootFragment = (Fragment) getClassLoader().loadClass(fragmentName).newInstance();</span><br><span class="line"> } catch (Exception e) {</span><br><span class="line"> setError(402, e);</span><br><span class="line"> Log.e("loader", "load fragment failed", e);</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> FragmentManager fm = getSupportFragmentManager();</span><br><span class="line"> FragmentTransaction ft = fm.beginTransaction();</span><br><span class="line"> ft.replace(getRootViewID(), rootFragment);</span><br><span class="line">// ft.add(getRootViewID(), rootFragment);</span><br><span class="line"> ft.commit();</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> /**</span><br><span class="line"> * 提供根布局的ID</span><br><span class="line"> * </span><br><span class="line"> * @return</span><br><span class="line"> */</span><br><span class="line"> protected int getRootViewID() {</span><br><span class="line"> return android.R.id.primary;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> public Fragment getRootFragment() {</span><br><span class="line"> return rootFragment;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> /**</span><br><span class="line"> * 加载页面遇到错误时候的处理</span><br><span class="line"> * </span><br><span class="line"> * @param errorCode</span><br><span class="line"> * @param e</span><br><span class="line"> */</span><br><span class="line"> protected void setError(int errorCode, Exception e) {</span><br><span class="line"> rootView.removeAllViews();</span><br><span class="line"> TextView text = new TextView(this);</span><br><span class="line"> text.setText("载入页面失败 (" + (errorCode > 0 ? errorCode : -1) + ")");</span><br><span class="line"> if (BuildConfig.DEBUG) {</span><br><span class="line"> if (e != null) {</span><br><span class="line"> text.append("\n");</span><br><span class="line"> text.append(e.toString());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> text.setLayoutParams(new FrameLayout.LayoutParams(</span><br><span class="line"> ViewGroup.LayoutParams.WRAP_CONTENT,</span><br><span class="line"> ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));</span><br><span class="line"> rootView.addView(text);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>那么在实际使用中通过如下方式完成页面的切换,所有页面采用Fragment的方式。在manifist文件中只需要注册很少的activity即可。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("btm://login"));</span><br><span class="line">intent.putExtra(LoginFragmentSale.SPLASH_LOGIN, true);</span><br><span class="line">startActivity(intent);</span><br></pre></td></tr></table></figure></p>
<p>当然通过schema方式,必须有一个注册地方。代码如下:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">public class MappingManager {</span><br><span class="line"></span><br><span class="line"> public MyMappingManager(Context ctx) {</span><br><span class="line"> super(ctx);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> @Override</span><br><span class="line"> protected MappingSpec read() {</span><br><span class="line"> List<PageSpec> pages = new ArrayList<PageSpec>();</span><br><span class="line"></span><br><span class="line"> // ********* 新增scheme支持在此添加项 *********</span><br><span class="line"> // 依次为host、fragment(可选)、activity(可选)、是否需要login</span><br><span class="line"></span><br><span class="line"> // 登录页面</span><br><span class="line"> pages.add(new PageSpec("login", LoginFragment.class, null, false));</span><br><span class="line"> </span><br><span class="line"> // other页面 。。。</span><br><span class="line"> </span><br><span class="line"> MappingSpec mapping = new MappingSpec(BULoaderActivity.class, pages.toArray(new PageSpec[pages.size()]));</span><br><span class="line"> return mapping;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>这里的read()方法是在Application中发起调用注册的。</p>
<h2 id="发现还是有同学不清楚我们到底怎么做的。因此,下面再详细说一下这个过程:"><a href="#发现还是有同学不清楚我们到底怎么做的。因此,下面再详细说一下这个过程:" class="headerlink" title="发现还是有同学不清楚我们到底怎么做的。因此,下面再详细说一下这个过程:"></a>发现还是有同学不清楚我们到底怎么做的。因此,下面再详细说一下这个过程:</h2><p>首先,btm前缀会在manifest中定义:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><activity</span><br><span class="line"> android:name=".RedirectActivity"</span><br><span class="line"> android:label="redirect"</span><br><span class="line"> android:screenOrientation="portrait" ></span><br><span class="line"> <intent-filter></span><br><span class="line"> <action android:name="android.intent.action.VIEW" /></span><br><span class="line"></span><br><span class="line"> <category android:name="android.intent.category.DEFAULT" /></span><br><span class="line"> <category android:name="android.intent.category.BROWSABLE" /></span><br><span class="line"></span><br><span class="line"> <data android:scheme="btm" /></span><br><span class="line"> </intent-filter></span><br><span class="line"> </activity></span><br></pre></td></tr></table></figure></p>
<p>这样就可以接收到所有btm开头的scheme了。再来看看RedirectActivity中具体做跳转的代码:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">protected void doRedirect() {</span><br><span class="line"> Intent orig = getIntent();</span><br><span class="line"> Intent intent = new Intent(orig.getAction(), orig.getData());</span><br><span class="line"> intent.putExtras(orig);</span><br><span class="line"> intent = urlMap(intent);</span><br><span class="line"> try {</span><br><span class="line"> // 避免进入死循环</span><br><span class="line"> List<ResolveInfo> l = getPackageManager().queryIntentActivities(</span><br><span class="line"> intent, 0);</span><br><span class="line"> if (l.size() == 1) {</span><br><span class="line"> ResolveInfo ri = l.get(0);</span><br><span class="line"> if (getPackageName().equals(ri.activityInfo.packageName)) {</span><br><span class="line"> if (getClass().getName().equals(ri.activityInfo.name)) {</span><br><span class="line"> throw new Exception("infinite loop");</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> } else if (l.size() > 1) {</span><br><span class="line"> // should not happen, do we allow this?</span><br><span class="line"> }</span><br><span class="line"> startActivity(intent);</span><br><span class="line"> finish();</span><br><span class="line"> } catch (Exception e) {</span><br><span class="line"> setError(402, e);</span><br><span class="line"> Log.e("app", "unable to redirect " + getIntent(), e);</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p>
<p>urlMap是非常重要的scheme分发映射点。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line">public Intent urlMap(Intent intent) {</span><br><span class="line"> do {</span><br><span class="line"> Uri uri = intent.getData();</span><br><span class="line"> if (uri == null) {</span><br><span class="line"> break;</span><br><span class="line"> }</span><br><span class="line"> if (uri.getScheme() == null</span><br><span class="line"> || !PRIMARY_SCHEME.equals(uri.getScheme())) {</span><br><span class="line"> break;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> MappingManager mManager = mappingManager();</span><br><span class="line"> if (mManager == null) {</span><br><span class="line"> break;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> MappingSpec mSpec = mManager.mappingSpec();</span><br><span class="line"> if (mSpec == null) {</span><br><span class="line"> break;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> String host = uri.getHost();</span><br><span class="line"> if (TextUtils.isEmpty(host))</span><br><span class="line"> break;</span><br><span class="line"> host = host.toLowerCase();</span><br><span class="line"></span><br><span class="line"> PageSpec page = mSpec.getPage(host);</span><br><span class="line"> if (page == null) {</span><br><span class="line"> Log.w("loader", "host (" + host</span><br><span class="line"> + ") Can't find the page in mapping.");</span><br><span class="line"> break;</span><br><span class="line"> }</span><br><span class="line"> Class<?> fragment = page.fragment;</span><br><span class="line"></span><br><span class="line"> intent.putExtra("_login", page.login);</span><br><span class="line"></span><br><span class="line"> Class<?> defaultLoader = mSpec.loader;</span><br><span class="line"> Class<?> loader = null;</span><br><span class="line"> if (page.activity != null) {</span><br><span class="line"> loader = page.activity;</span><br><span class="line"></span><br><span class="line"> } else if (defaultLoader != null) {// defaultLoader is always null</span><br><span class="line"> loader = defaultLoader;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> if (loader != null) {</span><br><span class="line"> intent.setClass(this, loader);</span><br><span class="line"></span><br><span class="line"> } else {</span><br><span class="line"> intent.setClass(this, LoaderActivity.class);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> String query = uri.getQuery();</span><br><span class="line"></span><br><span class="line"> uri = Uri.parse(String.format("%s://%s?%s#%s", uri.getScheme(),</span><br><span class="line"> host, query, fragment == null ? "" : fragment.getName()));</span><br><span class="line"> intent.setData(uri);</span><br><span class="line"></span><br><span class="line"> } while (false);</span><br><span class="line"></span><br><span class="line"> return intent;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p>
<p>在这里会有一个很关键的地方就是intent.setClass(this, LoaderActivity.class),通过这样可以加载Fragment承载的Activity。<br>另外这样做大伙儿可能觉得比较繁琐,但是这个通过Uri的方式来做的意义就是比如当你的应用需要像系统打电话,发短信这种功能的时候。这将是非常好的实现方式。</p>
<p>OK,这篇文章连续补了几次。该写的应该都写出来了,也感谢各位的关注。</p>
<p>Have fun!!!</p>
<h3 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h3><p>自从Android 3.0中引入fragments 的概念,根据词海的翻译可以译为:碎片、片段。其目的是为了解决不同屏幕分辩率的动态和灵活UI设计。大屏幕如平板小屏幕如手机,平板电脑的设计使得其有更多的空间来放更多的UI组件,而多出来的空间存放UI使其会产生更多的交互,从而诞生了fragments 。</p></p>
<p>fragments 的设计不需要你来亲自管理view hierarchy 的复杂变化,通过将Activity 的布局分散到frament 中,可以在运行时修改activity 的外观,并且由activity 管理的back stack 中保存些变化。当一个片段指定了自身的布局时,它能和其他片段配置成不同的组合,在活动中为不同的屏幕尺寸修改布局配置(小屏幕可能每次显示一个片段,而大屏幕则可以显示两个或更多)。</p></p>
<p>Fragment必须被写成可重用的模块。因为fragment有自己的layout,自己进行事件响应,拥有自己的生命周期和行为,所以你可以在多个activity中包含同一个Fragment的不同实例。这对于让你的界面在不同的屏幕尺寸下都能给用户完美的体验尤其重要。</p></p>
关于Android中App的整体解决方案的一些思考
http://frodoking.github.io/2014/10/01/android-app-whole-solution/
2014-10-01T03:13:20.000Z
2015-09-14T06:40:28.598Z
<p>从一开始做Android的时候就发现,一个app有很多个窗体(或者页面)堆积而成。由于Android自身对MVC的结构抽离得十分清晰,对布局,数据控制,逻辑处理都进行了一定的分离,大大方便了整个开发流程以及降低了开发成本。<br>但是由此而导致一定的问题,很多初学者开发一个app经常会落入只是重复的写activity的固定思维中。这样的结果就是一个app随着业务量的上升,重复劳动越来越厚重。同时也会伴随越来越多的逻辑错乱无序,或者有些逻辑根本无法实现。面对复杂业务的时候只能是用一个坑来填补另外一个坑。这对app来说本身就是一种灾难。<br>因此,我打算从这几年的工作经验中来对一个成熟app应该具有的整体架构以及基本知识点进行一个梳理,同时也希望从这些基本的分析中会给广大app开发者一些思考。</p>
<a id="more"></a>
<h3 id="App原始开发方式"><a href="#App原始开发方式" class="headerlink" title="App原始开发方式"></a>App原始开发方式</h3><p>一个activity+view+自身逻辑处理。ok完成当前页面的处理;<br>从这样的简单工作来看,当前页面的UI和逻辑控制以及数据都是存在activity中。那么也就是说这实际上是一个数据、UI和逻辑强关联的关系。<br>如果当我另外一个页面需要上一个页面的数据的时候,很多开发者选用了intent传输的方式,但是如果要在下一个页面去修改上一个页面的数据的时候。你们又会怎么样来实现呢?<br>曾经我遇到过有这样的处理方式:</p>
<blockquote>
<p>1.采用static的方式来保存这些数据;<br>2.采用sp的方式长久持有这些数据。</p>
</blockquote>
<p>是的,这些方式都可以达到这样的业务需求。不过static方式会消耗更多的内存;sp的方式长久持有临时数据,经常出现问题的表现就是重启,如果未删除某些数据那么后面得到的数据当然也就错乱了。<br>因此,为自己的app做一个细致的规划是很有必要的。</p>
<p>app不是说非做一个很复杂很NB的框架,从app对内存以及性能上的更高要求。其实一个适中的架构对app来说是不错的选择。切忌过度设计,假如让一个本身业务就很简单的app使用各种高级框架完成本来就很简单的一个业务逻辑,那无疑这是一个过度设计。(不过很多人喜欢这么干,觉得很NB的设计就非得死板硬套)</p>
<p>好了言归正传</p>
<hr>
<p>在接下来的章节中我会持续写一写一个成熟app的整体解决方案(包括整体框架结构,基本知识以及第三方对比以及选择)。</p>
<blockquote>
<p>1.<a href="/2015/02/01/android-mvc-mvp-analysis/">MVC和MVP在app中的对比分析以及实际应用</a>;<br>2.<a href="/2015/03/22/android-ui-design/">UI的整体设计思路(避免臃肿的UI)</a>;<br>3.<a href="/2015/05/16/android-retrofit/">OKHttp源码解析</a>;<br>4.<a href="/2015/06/29/android-okhttp-connectionpool-http1-x-http2-x/">OKHttp源码解析-ConnectionPool对Connection重用机制&Http/Https/SPDY协议选择</a>;<br>5.<a href="/2015/05/16/android-retrofit/">Retrofit源码解析</a>;<br>6.<a href="/2015/05/10/ide-from-eclipse-to-intellij/">从Eclipse到Intellij</a>;<br>7.<a href="/2015/07/01/android-theme/">Android统一风格 —— 主题</a>;<br>8.<a href="/2015/02/10/android-dependency-injection-struction/">依赖注入思路以及如何选择(Dagger、RoboGuice和ButterKnife)</a>;<br>9.<a href="/2015/03/30/android-eventbus-otto-analysis/">事件总线(otto的bus和eventbus对比分析)</a>;<br>10.数据存储(包括缓存);<br>11.插件化解决思路(一些动态加载框架的分析);<br>12.app的自我保护方案(如何做到防止数据接口的泄漏和逆向编译,保护自己的核心代码);</p>
</blockquote>
<p>从一开始做Android的时候就发现,一个app有很多个窗体(或者页面)堆积而成。由于Android自身对MVC的结构抽离得十分清晰,对布局,数据控制,逻辑处理都进行了一定的分离,大大方便了整个开发流程以及降低了开发成本。<br>但是由此而导致一定的问题,很多初学者开发一个app经常会落入只是重复的写activity的固定思维中。这样的结果就是一个app随着业务量的上升,重复劳动越来越厚重。同时也会伴随越来越多的逻辑错乱无序,或者有些逻辑根本无法实现。面对复杂业务的时候只能是用一个坑来填补另外一个坑。这对app来说本身就是一种灾难。<br>因此,我打算从这几年的工作经验中来对一个成熟app应该具有的整体架构以及基本知识点进行一个梳理,同时也希望从这些基本的分析中会给广大app开发者一些思考。</p>
概述
http://frodoking.github.io/2014/09/09/home-page/
2014-09-09T08:02:47.000Z
2016-03-09T09:56:44.503Z
<p>此博客属于个人工作和学习中一些心得体会,仅属于个人看法。同时也是一种对知识总结的方式。</p>
<h1 id="知识点列表"><a href="#知识点列表" class="headerlink" title="知识点列表"></a>知识点列表</h1><h3 id="Java调优"><a href="#Java调优" class="headerlink" title="Java调优"></a>Java调优</h3><ul>
<li>JVM调优总结(一)– 一些概念</li>
<li>JVM调优总结(二)– 一些概念</li>
<li>Java锁机制:synchronized、Lock、Condition</li>
</ul>
<h3 id="C-C"><a href="#C-C" class="headerlink" title="C/C++"></a>C/C++</h3><ul>
<li>C/C++基础学习 </li>
</ul>
<h3 id="Linux"><a href="#Linux" class="headerlink" title="Linux"></a>Linux</h3><ul>
<li>Linux编程<a id="more"></a>
<h3 id="并发"><a href="#并发" class="headerlink" title="并发"></a>并发</h3></li>
<li>Java并发</li>
</ul>
<h3 id="Cache"><a href="#Cache" class="headerlink" title="Cache"></a>Cache</h3><ul>
<li>缓存框架简介</li>
<li>MemoryCache</li>
</ul>
<h3 id="大数据"><a href="#大数据" class="headerlink" title="大数据"></a>大数据</h3><ul>
<li>hadoop</li>
<li>hive</li>
<li>hbase</li>
<li>scala</li>
<li>spark</li>
</ul>
<h3 id="服务器"><a href="#服务器" class="headerlink" title="服务器"></a>服务器</h3><ul>
<li>ssh框架</li>
<li>web框架</li>
<li>服务器调优</li>
</ul>
<h3 id="Android"><a href="#Android" class="headerlink" title="Android"></a>Android</h3><ul>
<li><a href="/2014/10/01/android-app-whole-solution/">关于Android中App的整体解决方案的一些思考</a></li>
<li><a href="/2014/12/21/android-fragment-lifecycle/">Fragment和Activity对比</a></li>
<li>App性能优化技巧</li>
<li><a href="/2015/03/12/android-okhttp/">OKHttp源码解析</a></li>
</ul>
<h3 id="Spring"><a href="#Spring" class="headerlink" title="Spring"></a>Spring</h3><ul>
<li>Spring整体框架说明</li>
</ul>
<h3 id="架构设计"><a href="#架构设计" class="headerlink" title="架构设计"></a>架构设计</h3><ul>
<li>设计模式</li>
</ul>
<h3 id="职业规划"><a href="#职业规划" class="headerlink" title="职业规划"></a>职业规划</h3><ul>
<li>浅谈程序员的职业路线</li>
</ul>
<h3 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h3><ul>
<li><a href="/2015/03/19/life-the-fucking-youth/">狗日的青春</a></li>
</ul>
<hr>
<p>欢迎评价或者回复问题到 <a href="http://awangyun8@gmail.com" target="_blank" rel="external">Email:awangyun8@gmail.com</a></p>
<p>此博客属于个人工作和学习中一些心得体会,仅属于个人看法。同时也是一种对知识总结的方式。</p>
<h1 id="知识点列表"><a href="#知识点列表" class="headerlink" title="知识点列表"></a>知识点列表</h1><h3 id="Java调优"><a href="#Java调优" class="headerlink" title="Java调优"></a>Java调优</h3><ul>
<li>JVM调优总结(一)– 一些概念</li>
<li>JVM调优总结(二)– 一些概念</li>
<li>Java锁机制:synchronized、Lock、Condition</li>
</ul>
<h3 id="C-C"><a href="#C-C" class="headerlink" title="C/C++"></a>C/C++</h3><ul>
<li>C/C++基础学习 </li>
</ul>
<h3 id="Linux"><a href="#Linux" class="headerlink" title="Linux"></a>Linux</h3><ul>
<li>Linux编程