写点什么

Android 网络请求心路历程,面试安卓工程师会问到那些问题

用户头像
Android架构
关注
发布于: 刚刚

if (conn != null) {conn.disconnect();}}


return null;}


private static String getStringFromInputStream(InputStream is)throws IOException {ByteArrayOutputStream os = new ByteArrayOutputStream();// 模板代码 必须熟练 byte[] buffer = new byte[1024];int len = -1;while ((len = is.read(buffer)) != -1) {os.write(buffer, 0, len);}is.close();String state = os.toString();// 把流中的数据转换成字符串,采用的编码是 utf-8(模拟器默认编码)os.close();return state;}}


注意网络权限!被坑了多少次。


<uses-permission android:name="android.permission.INTERNET"/>

同步 &异步

这 2 个概念仅存在于多线程编程中。


android 中默认只有一个主线程,也叫 UI 线程。因为 View 绘制只能在这个线程内进行。


所以如果你阻塞了(某些操作使这个线程在此处运行了 N 秒)这个线程,这期间 View 绘制将不能进行,UI 就会卡。所以要极力避免在 UI 线程进行耗时操作。


网络请求是一个典型耗时操作。


通过上面的 Utils 类进行网络请求只有一行代码。


NetUtils.get("http://www.baidu.com");//这行代码将执行几百毫秒。


如果你这样写


@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);String response = Utils.get("http://www.baidu.com");}


就会死。。


这就是同步方式。直接耗时操作阻塞线程直到数据接收完毕然后返回。Android 不允许的。


异步方式:


//在主线程 new 的 Handler,就会在主线程进行后续处理。private Handler handler = new Handler();private TextView textView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);textView = (TextView) findViewById(R.id.text);new Thread(new Runnable() {@Overridepublic void run() {//从网络获取数据 final String response = NetUtils.get("http://www.baidu.com");//向 Handler 发送处理操作 handler.post(new Runnable() {@Overridepublic void run() {//在 UI 线程更新 UItextView.setText(response);}});}}).start();}


在子线程进行耗时操作,完成后通过 Handler 将更新 UI 的操作发送到主线程执行。这就叫异步。Handler 是一个 Android 线程模型中重要的东西,与网络无关便不说了。关于 Handler 不了解就先去 Google 一下。


关于Handler原理一篇不错的文章


但这样写好难看。异步通常伴随者他的好基友回调


这是通过回调封装的 Utils 类。


public class AsynNetUtils {public interface Callback{void onResponse(String response);}


public static void get(final String url, final Callback callback){final Handler handler = new Handler();new Thread(new Runnable() {@Overridepublic void run() {final String response = NetUtils.get(url);handler.post(new Runnable() {@Overridepublic void run() {callback.onResponse(response);}});}}).start();}


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


public static void post(final String url, final String content, final Callback callback){final Handler handler = new Handler();new Thread(new Runnable() {@Overridepublic void run() {final String response = NetUtils.post(url,content);handler.post(new Runnable() {@Overridepublic void run() {callback.onResponse(response);}});}}).start();}}


然后使用方法。


private TextView textView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);textView = (TextView) findViewById(R.id.webview);AsynNetUtils.get("http://www.baidu.com", new AsynNetUtils.Callback() {@Overridepublic void onResponse(String response) {textView.setText(response);}});


是不是优雅很多。


嗯,一个蠢到哭的网络请求方案成型了。


愚蠢的地方有很多:


  • 每次都 new Thread,new Handler 消耗过大

  • 没有异常处理机制

  • 没有缓存机制

  • 没有完善的 API(请求头,参数,编码,拦截器等)与调试模式

  • 没有 Https

HTTP 缓存机制

缓存对于移动端是非常重要的存在。


  • 减少请求次数,减小服务器压力.

  • 本地数据读取速度更快,让页面不会空白几百毫秒。

  • 在无网络的情况下提供数据。


缓存一般由服务器控制(通过某些方式可以本地控制缓存,比如向过滤器添加缓存控制信息)。通过在请求头添加下面几个字端:


Request



Response



正式使用时按需求也许只包含其中部分字段。


客户端要根据这些信息储存这次请求信息。


然后在客户端发起请求的时候要检查缓存。遵循下面步骤:



浏览器缓存机制


注意服务器返回 304 意思是数据没有变动滚去读缓存信息。 曾经年轻的我为自己写的网络请求框架添加完善了缓存机制,还沾沾自喜,直到有一天我看到了下面 2 个东西。(/TДT)/

Volley&OkHttp

Volley&OkHttp 应该是现在最常用的网络请求库。用法也非常相似。都是用构造请求加入请求队列的方式管理网络请求。


先说 Volley:


Volley 可以通过这个库进行依赖.


Volley 在 Android 2.3 及以上版本,使用的是 HttpURLConnection,而在 Android 2.2 及以下版本,使用的是 HttpClient。


Volley 的基本用法,网上资料无数,这里推荐郭霖大神的博客


Volley 存在一个缓存线程,一个网络请求线程池(默认 4 个线程)。


Volley 这样直接用开发效率会比较低,我将我使用 Volley 时的各种技巧封装成了一个库 RequestVolly.


我在这个库中将构造请求的方式封装为了函数式调用。维持一个全局的请求队列,拓展一些方便的 API。


不过再怎么封装 Volley 在功能拓展性上始终无法与 OkHttp 相比。


Volley 停止了更新,而 OkHttp 得到了官方的认可,并在不断优化。


因此我最终替换为了 OkHttp


OkHttp 用法见这里


很友好的 API 与详尽的文档。


这篇文章也写的很详细了。


OkHttp 使用 Okio进行数据传输。都是Square家的。


但并不是直接用 OkHttp。Square 公司还出了一个 Retrofit 库配合 OkHttp 战斗力翻倍。

Retrofit&RestAPI

Retrofit极大的简化了网络请求的操作,它应该说只是一个 Rest API 管理库,它是直接使用 OKHttp 进行网络请求并不影响你对 OkHttp 进行配置。毕竟都是Square公司出品。


RestAPI 是一种软件设计风格。


服务器作为资源存放地。客户端去请求 GET,PUT, POST,DELETE 资源。并且是无状态的,没有 session 的参与。


移动端与服务器交互最重要的就是 API 的设计。比如这是一个标准的登录接口。



Paste_Image.png


你们应该看的出这个接口对应的请求包与响应包大概是什么样子吧。


请求方式,请求参数,响应数据,都很清晰。


使用 Retrofit 这些 API 可以直观的体现在代码中。



Paste_Image.png


然后使用 Retrofit 提供给你的这个接口的实现类 就能直接进行网络请求获得结构数据。


注意 Retrofit2.0 相较 1.9 进行了大量不兼容更新。google 上大部分教程都是基于 1.9 的。这里有个 2.0 的教程。


教程里进行异步请求是使用 Call。Retrofit 最强大的地方在于支持RxJava。就像我上图中返回的是一个 Observable。RxJava 上手难度比较高,但用过就再也离不开了。Retrofit+OkHttp+RxJava 配合框架打出成吨的输出,这里不再多说。


网络请求学习到这里我觉得已经到顶了。。

网络图片加载优化

对于图片的传输,就像上面的登录接口的 avatar 字段,并不会直接把图片写在返回内容里,而是给一个图片的地址。需要时再去加载。


如果你直接用 HttpURLConnection 去取一张图片,你办得到,不过没优化就只是个 BUG 不断 demo。绝对不能正式使用。 注意网络图片有些特点:


  1. 它永远不会变

  2. 一个链接对应的图片一般永远不会变,所以当第一次加载了图片时,就应该予以永久缓存,以后就不再网络请求。


  • 它很占内存

  • 一张图片小的几十 k 多的几 M 高清无码。尺寸也是 64*64 到 2k 图。你不能就这样直接显示到 UI,甚至不能直接放进内存。

  • 它要加载很久

  • 加载一张图片需要几百 ms 到几 m。这期间的 UI 占位图功能也是必须考虑的。


说说我在上面提到的RequestVolley里做的图片请求处理(没错我做了,这部分的代码可以去 github 里看源码)。

三级缓存

网上常说三级缓存--服务器,文件,内存。不过我觉得服务器不算是一级缓存,那就是数据源嘛。


  • 内存缓存

  • 首先内存缓存使用 LruCache。LRU 是 Least Recently Used 近期最少使用算法,这里确定一个大小,当 Map 里对象大小总和大于这个大小时将使用频率最低的对象释放。我将内存大小限制为进程可用内存的 1/8.

  • 内存缓存里读得到的数据就直接返回,读不到的向硬盘缓存要数据。

  • 硬盘缓存

  • 硬盘缓存使用DiskLruCache。这个类不在 API 中。得复制使用。

  • 看见 LRU 就明白了吧。我将硬盘缓存大小设置为 100M。


@Overridepublic void putBitmap(String url, Bitmap bitmap) {put(url, bitmap);//向内存 Lru 缓存存放数据时,主动放进硬盘缓存里 try {Editor editor = mDiskLruCache.edit(hashKeyForDisk(url));bitmap.compress(Bitmap.CompressFormat.JPEG, 100, editor.newOutputStream(0));editor.commit();} catch (IOException e) {e.printStackTrace();}}


//当内存 Lru 缓存中没有所需数据时,调用创造。@Overrideprotected Bitmap create(String url) {//获取 keyString key = hashKeyForDisk(url);//从硬盘读取数据 Bitmap bitmap = null;try {DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);if(snapShot!=null){bitmap = BitmapFactory.decodeStream(snapShot.getInputStream(0));}} catch (IOException e) {e.printStackTrace();}return bitmap;}


DiskLruCache 的原理不再解释了(我还解决了它存在的一个 BUG,向 Log 中添加的数据增删记录时,最后一条没有输出,导致最后一条缓存一直失效。)


  • 硬盘缓存也没有数据就返回空,然后就向服务器请求数据。


这就是整个流程。 但我这样的处理方案还是有很多局限。


  • 图片未经压缩处理直接存储使用

  • 文件操作在主线程

  • 没有完善的图片处理 API


以前也觉得这样已经足够好直到我遇到下面俩。

Fresco&Glide

不用想也知道它们都做了非常完善的优化,重复造轮子的行为很蠢。


Fresco是 Facebook 公司的黑科技。光看功能介绍就看出非常强大。使用方法官方博客说的够详细了。


真三级缓存,变换后的 BItmap(内存),变换前的原始图片(内存),硬盘缓存。


在内存管理上做到了极致。对于重度图片使用的 APP 应该是非常好的。


它一般是直接使用SimpleDraweeView来替换ImageView,呃~侵入性较强,依赖上它 apk 包直接大 1M。代码量惊人。


所以我更喜欢Glide,作者是 bumptech。这个库被广泛的运用在 google 的开源项目中,包括 2014 年 google I/O 大会上发布的官方 app。


这里有详细介绍。直接使用 ImageView 即可,无需初始化,极简的 API,丰富的拓展,链式调用都是我喜欢的。


丰富的拓展指的就是这个


另外我也用过 Picasso。API 与 Glide 简直一模一样,功能略少,且有半年未修复的 BUG。

图片管理方案

再说说图片存储。不要存在自己服务器上面,徒增流量压力,还没有图片处理功能。


推荐七牛阿里云存储(没用过其它 π__π )。它们都有很重要的一项图片处理。在图片 Url 上加上参数来对图片进行一些处理再传输。

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
Android网络请求心路历程,面试安卓工程师会问到那些问题