如何简化安卓网络调用:介绍 volley 库

dmiy9070 7年前
   <p>生存在一个由互联网驱动的世界之中,移动应用需要从它们的产品后端(例如,从数据库)还有像 非死book 和 推ter 这样的第三方资源那里共享和接收信息。这些交互经常是通过 RESTful API 来进行了。当请求的数量增长的时候,请求发起的方式对于开发部门而言会变得越来越值得重视, 因为你获取数据的方法会实实在在的影响到一个应用的用户体验。</p>    <p>本文我想要从API层面来分享一下我在Android开发中使用网络库的一些经验。我会先从基础的同步和异步编程开始讲起,并且会涵盖复杂的 Android 线程问题。然后我们会深入到 AsyncTask 模块当中去, 理解其架构流程并且阅读示例代码以了解其具体实现。我也会聊到 AsyncTask 库的局限性,并向各位介绍 Android  Volley ,它可以作为发起异步网络调用的一种更好的方法。之后我们会深入去了解一下 Volley 的架构并用代码示例来向大家介绍一下其中那些有价值的功能特性。</p>    <p>说到这儿你还能提起劲头吗? 把 Android 网络编程这块搞定可是能带你成为一名应用开发老手滴哦。</p>    <p><em>注意</em> : 其它一些具备网络开发能力的 Android 库不在本文涵盖的内容之列, 包括  Retrofit ,  OkHttp 。建议你自行去对他们进行一下了解。</p>    <h3>编程方法简言之就是: 同步和异步</h3>    <p>“等下,老妈,我来了。” Jason 这样说着, 仍然坐在沙发上,等着他的女朋友回消息,一小时前他联系过她的。“你应该在等女朋友回消息的时候打扫一下你的房间。” Jason 的妈妈回应他道,语带讽刺。她的建议不是很明显吗? 这样的场景跟同步和异步 HTTP 请求的情况很像。让我们来瞧瞧。</p>    <p>同步请求的行为像 Jason,保持等待,直到有响应从服务器发过来。同步请求会阻塞界面,增加计算的时间,并且使得应用失去响应(也不总是这样 — 有时候停止等待没有意义,比如银行交易)。更聪明的一个办法正如 Jason 的老妈所建议的。在异步的世界中,当客户端向服务端发起了一次请求, 服务端会将该请求分派给事件处理器,注册一个回调然后就转向下一个请求。当有了响应以后,客户端就会响应该结果。这是一种更棒的方法,因为异步请求可以各个任务独立执行。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/817554b983fb313d4f5302d2bfaaf08e.png"> </p>    <p>上图展示了两种编程方法之间在客户端-服务器模型中的区别。在 Android 中, UI 线程也常被称作主线程, 基于的是跟同步编程一样的理念。</p>    <h3><a href="/misc/goto?guid=4959746989808788315" rel="nofollow,noindex">Android 线程</a></h3>    <p><a href="/misc/goto?guid=4959746989898685944" rel="nofollow,noindex">它们是什么,如何运作</a> ? </p>    <p>线程是由操作系统所管理的指令的集合。多个线程会运行在一个进程之下 (类似于 Android 中的 Linux 进程) 并且共享诸如内存这样的资源。在 Android 中, 当应用运行起来的时候,系统会为整个应用程序创建一个执行线程, 被称作 “主” 线程 (或者说 UI 线程)。主线程工作在一个单线程模型之中。它负责处理 UI 组件(绘制事件)事件的派发工作, 以及通过 UI 工具集与组件进行交互,比如 View.OnClickListener(), 并且对系统事件做出相应,比如 onKeyLongPress()。</p>    <p>UI 线程运行在一个无线循环之中,并且对消息队列进行监视以检测是否需要更新 UI。让我们来看一个示例。但用户触摸了一个按钮,UI线程会将触摸事件派发给某个组件, 该组件是能对其按压状态进行设置,并且向消息队列发送一个请求。UI线程会从消息队列取出该请求,然后通知组件执行动作 — 在该场景中,组件会重绘自己以表明按钮被按下去了。如果你对深入了解UI线程感兴趣的话,应该去读一读关于 Looper ,   MessageQueue 以及  Handler 类的相关内容, 就是它们完成了我们示例中所谈论的任务。如你所预料的,UI线程肩负着许多的职责,比如:</p>    <ul>     <li> <p>启动一个 activity;</p> </li>     <li> <p>执行一个服务;</p> </li>     <li> <p>响应系统回调。</p> </li>    </ul>    <p><a href="/misc/goto?guid=4959746989982107403" rel="nofollow,noindex">为什么不能阻塞UI线程</a></p>    <p>当你在思考这个问题的时候,你的单线程UI线程执行着所有的工作来响应用户的交互。因为所有的事情都发生在UI线程身上,所以诸如数据库查询以及网络调用这样的耗时操作就会把 UI 阻塞掉。UI线程会将事件派发给 UI 组件。应用的表现不佳,用户会感觉应用卡死了。如果这些任务花掉的时间让UI线程被阻塞4至5秒钟的话,Android 会抛出一个 “ Application Not Responding ” (ANR) 错误。这样一个对用户不怎么友好的 Android 应用,就别想得到什么好评了,也许随之而来的还有糟糕的评分和匆匆忙忙的卸载。</p>    <p>利用主线程来处理长耗时任务会把事情搞砸。如果UI线程是非阻塞的,那么你的应用将总是能持续响应用户事件。这就是为什么如果你的应用程序需要发起网络调用,而调用需要在运行于后台的工作线程来执行,而不是主线程。你可以使用一个 Java 的 HTTP 客户端库来通过网络发送和接收数据, 但是网络调用本身应该由一个工作线程来负责执行。等等,在Android里面还有另外一个问题: 线程安全。</p>    <p><a href="/misc/goto?guid=4959746990070053728" rel="nofollow,noindex">线程安全,还有它为什么如此重要! </a></p>    <p>Android UI 工具集是非 线程安全 的。如果工作线程(执行了发起网络调用的任务)更新了 Android UI 工具集, 就可能导致未定义且非预期的行为发生。这样的问题追踪起来困难且费时间。单线程模型可以确保在同一时刻 UI 不会被不同的线程所修改。因此,如果我们得用一张来自于网络的图片来更新 ImageView 的话,工作线程会在一个独立的线程中执行网络操作,而 ImageView 会由 UI 线程来更新。这样的话,有了 UI 线程提供必要的同步机制,就确保了操作是线程安全的。这样也能让 UI 线程总是保持非阻塞状态,因为实际的任务都发生在后台工作者线程中。</p>    <p>总而言之,在 Android 开发中要遵循如下两条简单的规则:</p>    <ul>     <li> <p>不要阻塞 UI 线程。</p> </li>     <li> <p>UI 工具集不能直接通过一个非 UI 线程的工作线程被更新。</p> </li>    </ul>    <p><a href="/misc/goto?guid=4959746990152450311" rel="nofollow,noindex">有关 Android 服务的一个注意事项</a></p>    <p>当说到要向一个“activity”发起请求的时候,你会碰到 Android 的“ 服务 ”。服务就是一个应用组件,它可以在后台执行长耗时操作,不需要应用处理活动状态,或者甚至是在执行时用户已经切换到了另外一个应用。例如,服务能让你很好地在后台播放音乐或者下载内容。如果你选择使用一个服务的话,它默认还是在主线程里面运行,因此你就需要在服务中创建一个新的线程来处理阻塞操作。如果你需要在主线程的外部执行操作,而用户此时正在同应用进行交互的话,最好就是利用像 AsyncTask 或者 Volley 这样的网络库。</p>    <p>在工作线程中执行任务挺好的,不过要是你的应用开始要执行复杂的网络操作的话,用工作线程维护起来就困难了。</p>    <h3><a href="/misc/goto?guid=4959746990236032997" rel="nofollow,noindex">AsyncTask 是个坑</a></h3>    <p>现在已经相当清楚了,我们应该使用一种稳定的 HTTP 客户端库,并且要确保网络任务是使用工作线程在后台来实现的 — 其实就是要使用非 UI 线程。</p>    <p>Android 有个资源能帮助我们实现异步地处理网络调用。 AsyncTask 是一个可以让我们在用户界面上执行异步操作的模块。</p>    <p><a href="/misc/goto?guid=4959746990308933425" rel="nofollow,noindex">异步能给我们什么? </a></p>    <p>AsyncTask 会在一个工作线程中执行所有的阻塞操作, 比如网络调用,并且在完成以后对结果进行发布。UI 线程会获取到这些结果并据此对用户界面进行更新。</p>    <p><img src="https://simg.open-open.com/show/ab3479a4fdca376d27f152f24960b24a.png"></p>    <p>Android上的异步任务 </p>    <p>如下是我使用 AsyncTask 实现一个异步工作线程的步骤:</p>    <ol>     <li> <p>定义一个 AsyncTask 的子类实现 onPreExecute() 方法, 它会创建一条提示消息,提示说网络调用即将发生。</p> </li>     <li> <p>实现 doInBackground(Params...) 方法。顾名思义,doInBackground 就是发起网络调用并保护主线自由之身的那个工作线程。</p> </li>     <li> <p>因为工作线程不能直接更新 UI,所以我实现了自己的 postExecute(Result) 方法, 它会传递来自于网络调用操作的结果并且在 UI 线程中运行,如此用户界面就可以被安全地修改了。</p> </li>     <li> <p>后台任务的执行进度可以使用 publishProgress() 方法从工作线程那里发布出来,并且使用 onProgressUpdate(Progress...) 方法在UI线程上进行更新。这些方法并没有在示例代码中实现,不过用起来相当直接明了。</p> </li>     <li> <p>最后,从 UI 线程使用 execute() 方法将异步任务调用起来。</p> </li>    </ol>    <p>注意: execute() 和 postExecute() 两者都运行于 UI 线程之上,因此 doInBackground() 是一个非 UI 的工作线程。</p>    <p> </p>    <p>来自:https://www.oschina.net/translate/introducing-the-volley-http-library-to-simplify-networking-in-android</p>    <p> </p>