写点什么

Android 网络请求心路历程 (1),2021Android 开发现状分析

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

int responseCode = conn.getResponseCode();// 调用此方法就不必再使用 conn.connect()方法 if (responseCode == 200) {


InputStream is = conn.getInputStream();String response = getStringFromInputStream(is);return response;} else {throw new NetworkErrorException("response status is "+responseCode);}


} catch (Exception e) {e.printStackTrace();} finally {if (conn != null) {conn.disconnect();// 关闭连接}}


return null;}


public static String get(String url) {HttpURLConnection conn = null;try {// 利用 string url 构建 URL 对象 URL mURL = new URL(url);conn = (HttpURLConnection) mURL.openConnection();


conn.setRequestMethod("GET");conn.setReadTimeout(5000);conn.setConnectTimeout(10000);


int responseCode = conn.getResponseCode();if (responseCode == 200) {


InputStream is = conn.getInputStream();String response = getStringFromInputStream(is);return response;} else {throw new NetworkErrorException("response status is "+responseCode);}


} catch (Exception e) {e.printStackTrace();} finally {


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();}


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进行数据传输。都


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


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) {//获取 key

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
Android网络请求心路历程(1),2021Android开发现状分析