Java 原生API进行网络请求

先了解底层,才能更好地上手Android网络请求框架。

一、android使用Java原生API进行网络请求的步骤

  1. 声明网络权限
<uses-permission android:name="android.permission.INTERNET"/>
  1. 使用HttpURLConnection进行网络请求,子线程中请求数据,android不允许在Main-Thread中执行耗时操作,防止ANR。对于Java网络编程不熟悉的可以转到这是属于你的频道
URL url;
// 使用URLConnection的衍生类 HttpURLConnection
HttpURLConnection connection;
try {
    url = new URL("https://www.sunofbeach.net/content/content/moment/list/1153952789488054272/1");
    connection = (HttpURLConnection) url.openConnection();
    // 设置请求参数
    connection.setRequestMethod("GET");
    connection.setConnectTimeout(1000);
    connection.setRequestProperty("accept", "*/*");
    connection.setRequestProperty("connection", "keep-alive");
    connection.setRequestProperty("Accept-Language", "zh-CN,zh");
    // 开始连接,也可以不设置,当获取流的时候,会建立连接
    connection.connect();
    // 获取响应码,HttpURLConnection特有方法
    int responseCode = connection.getResponseCode();
    Log.d(TAG, "responseCode-->" + responseCode);
    if (responseCode == HttpURLConnection.HTTP_OK) {
        InputStream in = connection.getInputStream();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in));
        String res = bufferedReader.readLine();
        Log.d(TAG, "responseData: "+res);
        bufferedReader.close();
    }
} catch (IOException e) {
    e.printStackTrace();
}

对于https请求以上程序是没有问题,但是对于http请求android版本的不同对网络请求的行为进行了相应的变更,对于android8.0(API27)以上,是不允许http请求的,因为http请求消息是不经过加密处理的即明文传输(cleartextTraffic),目的为了更好的保证应用程序的安全,保证用户隐私。但是Android8.0(opera)以下是可以直接进行http请求的,详情信息请移步应用安全性最佳做法

https由底层的SSL(安全套接层)所支撑,保证了应用程序间进行通信的安全性。但互联网并非所有的链接都是https,android对于http请求的安全性是如何进行处理的,请看如下解决方案。

二、android高版本http网络请求的处理

  1. 降维度,即降低API版本,targetSdkVersion <= 27显然这不太合乎常规,机器是要进化的,高版本兼容低版本,特殊情况,但这也绝非不可。
  2. Manifest.xml的application标签中添加android:usesCleartextTraffic="true"最方便也是最有效的方案。世界上没有绝对安全的系统,这种方案带来的缺陷也显而易见。
  3. 添加网络配置文件network_security_config.xml,比较安全的方案。因为在配置文件中可以指定,信任那些站点,还可以信任某个域以及子域。
    步骤:res下新建xml目录,目录下新建network_security_config.xml,配置如下内容,然后再Manifest.xml的application标签中添加android:networkSecurityConfig="@xml/network_security_config"
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <!--应用范围内定义-->
    <base-config cleartextTrafficPermitted="true"/>
    <!--网络范围内定义-->
    <!--<domain-config>
        <domain includeSubdomains="true">example.com</domain>
        <domain-config>
        </domain-config>
    </domain-config>-->
</network-security-config>

reminder:对于开发中网络请求的问题,我们应该想到是不是请求权限的问题,http的原因,https不用考 虑太多;比如以后即将用到的OKHttp框架,还是正在当下的Glide图片加载框架,Glide.load(url)图片没有加 载成功,是不是http请求所导致等thinking and study

三、网络请求后回显数据的处理

现在开发请求后端接口,返回的数据大多都是JSON格式的数据了吧,如果还生活在传递xml格式的石器时代,那么雨我无瓜。那么我们拿到后台数据之后,要干什么,如何去做?无非就是下面两点:

  1. 后台数据格式化(JSON >> JavaBean)
    如果后台数据量很大,JSON转JavaBean是一个很繁琐无脑的工作,这里推荐插件GsonFormat,类似于Mybatis-Generator、EasyCode等逆向工程插件。安装步骤这里就不在介绍了,plugins >> MarketExplore >> GsonFormat >> download,很好,一如既往的安装失败!在这给出离线安装jar包,拿去不客气密码:enjh。安装成功后,在所在类直接一顿快捷键*[alt + insert]* 就可以看到GsonFormt选项了,将json格式的数据复制到里面然后根据需要字段生成对应的类即可。
    JSON >> JavaBean:
    1.1)添加Gson依赖implementation 'com.google.code.gson:gson:2.8.5'如果是在project structure下app里面搜索添加的依赖,因为搜索之后会有多个版本,如果依赖下载失败,那么可以尝试一下其他版本。
    1.2)Gson的使用,如果添加依赖成功,但是Gson没有该类,rebuild尝试一下!
Gson gson = new Gson();
JavaBean bean = gson.fromJson(JSONData, JavaBean.class);
  1. 更新UI
    android里面子线程不可以直接更新UI,要想更新UI两种方法:
    2.1) runOnUIThread
runOnUiThread(new Runnable() {
    @Override
    public void run() {
        // 请求数据完成,装载数据,更新UI 
        ...
    }
});

2.2)使用Handler

四、请求图片数据的处理

了解了使用java api进行网络请求json格式数据的过程之后,图片数据的处理过程与文本格式稍微有所不同,但是传输过程中还是变成了字节流进行的传输,底层还是01010…,因为图片分辨率不同导致图片所占的内存大小也就有所差异,对于大图片要适当的进行处理防止OOM(Out Of Memory),所以我们可以使用BitmapFactory.Options.inSampleSize按照一定的采样率加载缩小后的图片,将缩小后的图片在ImageView中显示,这样就能降低内存占用,在一定程度上避免OOM,提高bitma加载时候的性能。在做项目中,应该都会有缩略图,比如一个商品列表,不可能上来就请求原图,这么多商品图片被请求,不崩才怪呢!所以说,缩略图是一种解决方案,当用户点击了某个商品之后,进入商品详情图,这时候请求商品原图给用户是一种很好的方式。

  1. 请求一张图片,主要是底层IO流的操作,还有借助BitmapFactory.decodeStream(InputStream in)实例化Bitmap对象。
URL url = new URL("image url");

HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(1000);
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "keep-alive");
connection.setRequestProperty("Accept-Language", "zh-CN,zh");

if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
    InputStream in = connection.getInputStream();
    final Bitmap bitmap = BitmapFactory.decodeStream(in);
    // 更新UI
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            ImageView imageView = findViewById(R.id.image_view);
            imageView.setImageBitmap(bitmap);
        }
    });

}
  1. Android图片采样缩放对大图处理,防止OOM
    假设通过imageView来显示图片,很多时候ImageView并没有图片的原始尺寸那么大,这时候把整张图片加载进来后再设给ImageView是没有必要的,因为ImagView并没有办法显示原始的图片。
    2.1)BitmapFactory.Options.inSampleSize(采样率)
inSampleSize = 1 //采样后图片大小=原始图片大小
inSampleSize = 2 //那么采样后图片宽高均为原始图片的1/2,像素为原图的1/4,占有的内存大小为原图的1/4。
例如:一张的图片占有内存1024*1024*4=4M,用采样率为2采样后内存占用为512*512*4=1M。

inSampleSize是必须大于1的整数才有效果,小与1就相当于1,并且同时作用于宽高,所以缩放后的图片大小以采样率的2次方形式递减.根据最新的官方文档,inSampleSize的取值应该总是为2的指数,若给系统的inSampleSize不为2的指数,那么系统会向下取整并且选择一个最接近2的指数来代替,不过经过验证,这个结论并不是在所有的Android版本上都成立。

2.2)获取采样率

1、将BitmapFactory.Option的inJustDecodeBound参数设为true,加载图片,这个时候图片并没有加载进内存,仅仅是去解析图片原始宽高信息而已。

2、从BitmapFactory.Option取出图片的原始宽高信息,对应于outWidth,outHeight参数。

3、根据采样率的规则和目标原始View的所需大小计算出采样率inSampleSize。

4、将BitmapFactory.Option的inJustDecodeBound参数设为false,重新加载图片,这时候图片才真正被载进内存。

在没有进行采样压缩之前的图片

// 下面将一张6.04MB的图片加载内存,运行后
// 抛出java.lang.RuntimeException: Canvas: trying to draw too large(336063168bytes) bitmap.
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.large);
ImageView imageView = findViewById(R.id.image_view);
imageView.setImageBitmap(bitmap);

经过采样压缩处理的图片

ImageView imageView = findViewById(R.id.image_view);
int height = imageView.getMeasuredHeight();
int width = imageView.getMeasuredWidth();
Log.d(TAG, "控件的宽高 " + "宽:" + width + " 高:" + height);
BitmapFactory.Options options = new BitmapFactory.Options();
// 图片不加载进内存,只解析图片宽高等信息 --> options
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.large, options);
int inSampleSize = 1;
int scaleX,scaleY;//缩放比
int outWidth = options.outWidth;
int outHeight = options.outHeight;
Log.d(TAG, "原始图片大小 " + "宽:" + outWidth + " 高:" + outHeight);
if (outWidth > width || outHeight > height) {
    scaleX = outWidth / width;
    scaleY = outHeight / height;
    inSampleSize = scaleX > scaleY ? scaleX : scaleY;
    // 设置新的缩放比
    options.inSampleSize = inSampleSize;
}
Log.d(TAG, "缩放比:" + inSampleSize);
// 将接下来的图片加载内存
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.large, options);
imageView.setImageBitmap(bitmap);

// log --->
// 控件的宽高 宽:1080 高:788
// 原始图片大小 宽:4032 高:3024
// 缩放比:3