本文出自 “双门M” 简书博客,转载或引用请注明出处。
安卓手机当然是需要上网的,上网的话无非就涉及到网络协议。
以前啊,我们上网访问的WAP网络,这个网络是中国运营商给我们打造的一个巨型局域网,后来3g来了,后来4g来了,从这3g开始,我们真的算互联网了(科学上网长城不算,算进来不是欺负人呢,还怎么说下去啊)。
说起网络请求协议,那肯定很复杂的一套的东西,我们只要知道,现在普遍采用的网络协议的是HTTP1.1就好啦,然后顺带提一下,还有个Https,简单来说,就是https比http安全(既然安全,肯定操作起来就麻烦),不过这个不管我们的是,关于这个网络协议就是随便说说,有兴趣的自行百度,百度一下“http”、“抓包”、“请求头”、“响应头”之类的东西。不怎么清楚可以继续下面的学习滴。
安卓的网络框架一大堆,比如
* 谷歌官方2013年推出的 Volley
* android-async-http
* okHttp
等等,这些都是开发中非常著名的网络框架。
不过当让啦,万变不离其中,这些框架说到底就是把安卓原生的 HttpClient 和 HttpURLConnection 该封装的封装,该提取的提取,然后能成一套好用的框架在经过无数人的开源的修改历练打造而成的。
- HttpClient 安卓2.2以前用的很嗨,但是由于android studio已经不支持和高版本上使用起来不如的 HttpURLConnection 好用的原因,我们稍微这里说过就好,不对其进行继续说明
- HttpURLConnection 2.2以前他也有,但是有bug,但是2.3以后慢慢就好用了,人们也用得嗨了,android studio支持,所以我们主要讲话这个家伙。
好啦,正式开始。
一、HttpURLConnection
1、Http协议工作原理
Http协议工作原理特别的简单,就是客户端向服务器发出一条HTTP请求,服务器收到请求之后会返回一些数据给客户端,然后客户端再对这些数据进行解析和处理就可以了。
android http协议,大概就是这么回事,网上临时找的图片
我们可以简单理解为,客户端(此时是我们的手机浏览器或者说手机软件)输入了网址(涉及到主机名啊,域名啊什么鬼东西的可以自定百度何可忽略,说好接地气嘛),通过这个网址就可以找到服务器,然后服务器通过地址一看,就知道你要什么东西,就返回数据给你。
我们通过网络怎么传输东西?我们都知道计算式的所有数据是0和1的数据,这些数据我们可以想象成小水滴,小水滴的传输我们需要一个管道,从我们这里流出去的,叫做输出流,可从别人那里流过来的,叫做输入流
流的概念应该是学语言继续必须接触到的,java的输入流和输出流有很多种,什么文件的啊,字符数组的啊,对于流,我们的只要明确一个概念就好,
* 判断输入流还是输出流 :都是相对自身而言的。
接下来我们HttpURLConnection还是从网上到处都有的 “图片查看器” 和 “源码查看器” 来进行。两个小例子。
大概要涉及到的东西这么几个,如果明白了那就是看API的事情了
* 安卓的耗时操作的逻辑不能写在主线程,必须写在子线程(I/O流操作肯定是耗时,肯定得写在子线程)
* 子线程在安卓2.3以后不能更新UI界面,(更新就报错),子线程想要更新界面必须结合Handler或者AsyncTask等才可以,说到底就是更新UI这些操作通过Handler或者AsyncTask绕一绕然后交还给主线程自己去做
* I/O流的简单操作(I就是input,O就是output,勿嫌弃,谁叫我们说好了接地气 )
2、图片查看器
以下是最简单图片下载的demo(有bug,只能在安卓2,3以下运行,写这个只是为了演示简单 HttpURLConnection 的使用,后面会有修改版)
HttpURLConnection最简易的使用方法:
- 1.利用 URL 类的实例接收url路径
- 2.通过路径得到一个连接 http的打开连接
- 3.判断服务器给我们返回的状态信息。
- 4.利用链接成功的 conn 得到输入流
- 5.ImageView设置Bitmap,用BitMap工厂解析输入流
package com.example.amqr.simple;
import android.app.Activity;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class MainActivity extends Activity {
private EditText mEtUrlPath;
private ImageView mIvShow;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mEtUrlPath = (EditText) findViewById(R.id.mEtUrlPath);
mIvShow = (ImageView) findViewById(R.id.mIvShow);
}
/**
* 点击查看网络上的图片
*
* @param view
*
*
* // 这是最简单的网络图片加载,这份代码只能在安卓2.3以下运行
* 这份代码有有以下问题
* 1 没有开子线程执行网络下载,可能会挂掉,但是2.3不会...
* 2 没有利用子线程然后在handler切换到主线程更新UI
*
*/
public void click(View view) {
String path = mEtUrlPath.getText().toString().trim();// http://www.baidu.com/aa.png
System.out.println("图片不存在,获取数据生成缓存");
// 通过http请求把图片获取下来。
try {
// 1.声明访问的路径, url 网络资源 http ftp rtsp
URL url = new URL(path);
// 2.通过路径得到一个连接 http的连接
HttpURLConnection conn = (HttpURLConnection) url
.openConnection();
// 3.判断服务器给我们返回的状态信息。
// 200 成功 302 从定向 404资源没找到 5xx 服务器内部错误
int code = conn.getResponseCode();
if (code == 200) {
// 4.利用链接成功的 conn 得到输入流
InputStream is = conn.getInputStream();// png的图片
// 5. ImageView设置Bitmap,用BitMap工厂解析输入流
mIvShow.setImageBitmap(BitmapFactory.decodeStream(is));
} else {
// 请求失败
Toast.makeText(this, "请求失败", Toast.LENGTH_SHORT).show();
}
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "发生异常,请求失败", Toast.LENGTH_SHORT).show();
}
}
}
布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#66ff0000"
tools:context=".MainActivity" >
<EditText
android:text="http://www.baidu.com/img/bd_logo1.png"
android:id="@+id/mEtUrlPath"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入网络图片的路径" />
<Button
android:onClick="click"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="查看" />
<ImageView
android:id="@+id/mIvShow"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
4.0以上就不行了
上面演示完了最简单的 HttpURLConnection 的用法,但是上面那种用法是存在严重bug的,接下我们附上一个相对完善的例子:
实现:子线程执行耗时操作+利用Handler让让主线程更新UI+简单图片缓存
修改后的图片查看器
改动的只有java代码。xml布局文件不需要改动
package com.example.amqr.simple;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.util.Base64;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class MainActivity extends Activity {
private static final int MSG_NEW_PIC = 2;
private static final int MSG_CACHE_PIC = 1;
private static final int ERROR = 3;
private static final int EXCEPTION = 4;
private EditText mEtUrlPath;
private ImageView mIvShow;
//1.在主线程里面声明消息处理器 handler
private Handler handler = new Handler(){
//处理消息的
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_CACHE_PIC:
//3.处理消息 运行在主线程
Bitmap bitmap = (Bitmap) msg.obj;
mIvShow.setImageBitmap(bitmap);
System.out.println("(不用下载)缓存图片");
break;
case MSG_NEW_PIC:
Bitmap bitmap2 = (Bitmap) msg.obj;
mIvShow.setImageBitmap(bitmap2);
System.out.println("新下载(还没有缓存)下载的图片");
break;
case ERROR:
Toast.makeText(MainActivity.this, "请求失败", Toast.LENGTH_SHORT).show();
break;
case EXCEPTION:
Toast.makeText(MainActivity.this, "发生异常,请求失败", Toast.LENGTH_SHORT).show();
break;
}
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mEtUrlPath = (EditText) findViewById(R.id.mEtUrlPath);
mIvShow = (ImageView) findViewById(R.id.mIvShow);
}
/**
* 点击查看网络上的图片
*
* @param view
*/
public void click(View view) {
final String path = mEtUrlPath.getText().toString().trim();// http://www.baidu.com/aa.png
if (TextUtils.isEmpty(path)) {
Toast.makeText(this, "图片路径不能为空", Toast.LENGTH_SHORT).show();
return;
}
new Thread(){
public void run() {
getImage(path);
};
}.start();
}
/**
* 获取图片
* @param path
*/
private void getImage(String path) {
File file = new File(getCacheDir(), Base64.encodeToString(
path.getBytes(), Base64.DEFAULT));
if (file.exists() && file.length() > 0) {
System.out.println("图片存在,拿缓存");
Bitmap bitmap = BitmapFactory.decodeFile(file
.getAbsolutePath());
//更新ui 不能写在子线程
//iv.setImageBitmap(bitmap);
Message msg = new Message();//声明消息
msg.what = MSG_CACHE_PIC;
msg.obj = bitmap;//设置数据
handler.sendMessage(msg);//让handler帮我们发送数据
} else {
System.out.println("图片不存在,获取数据生成缓存");
// 通过http请求把图片获取下来。
try {
// 1.声明访问的路径, url 网络资源 http ftp rtsp
URL url = new URL(path);
// 2.通过路径得到一个连接 http的连接
HttpURLConnection conn = (HttpURLConnection) url
.openConnection();
// 3.判断服务器给我们返回的状态信息。
// 200 成功 302 从定向 404资源没找到 5xx 服务器内部错误
int code = conn.getResponseCode();
if (code == 200) {
InputStream is = conn.getInputStream();// png的图片
FileOutputStream fos = new FileOutputStream(file);
byte[] buffer = new byte[1024];
int len = -1;
while ((len = is.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
is.close();
fos.close();
Bitmap bitmap = BitmapFactory.decodeFile(file
.getAbsolutePath());
//更新ui ,不能写在子线程
Message msg = new Message();
msg.obj = bitmap;
msg.what = MSG_NEW_PIC;
handler.sendMessage(msg);
//iv.setImageBitmap(bitmap);
} else {
// 请求失败
//土司更新ui,不能写在子线程
//Toast.makeText(this, "请求失败", 0).show();
Message msg = new Message();
msg.what = ERROR;
handler.sendMessage(msg);
}
} catch (Exception e) {
e.printStackTrace();
//土司不能写在子线程
//Toast.makeText(this, "发生异常,请求失败", 0).show();
Message msg = new Message();
msg.what = EXCEPTION;
handler.sendMessage(msg);
}
}
}
}
现在4.0以上就可以正常加载图片了,2.3当然也是没有问题了,贴上4.0的图片,2,3的就算了不贴了,2.3可以显示。
图片加载的就说完了,接下来说 源码加载。
3、源码查看器
直接上代码了:
小小区别就是把利用conn得到输入流转成字节数组流,然后字节数组流转成字符串,这样就可以看到源码了
代码:
package com.example.amqr.httpurltest;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class MainActivity extends Activity {
protected static final int SUCCESS = 1;
protected static final int ERROR = 2;
private EditText et_path;
private TextView tv_result;
private Handler handler = new Handler(){
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case SUCCESS:
String text = (String) msg.obj;
tv_result.setText(text);
break;
case ERROR:
Toast.makeText(MainActivity.this, "请求失败", Toast.LENGTH_SHORT).show();
break;
}
};
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_path = (EditText) findViewById(R.id.et_path);
tv_result = (TextView) findViewById(R.id.tv_result);
}
/**
* 查看源文件的点击事件
* @param view
*/
public void click(View view){
final String path = et_path.getText().toString().trim();
//访问网络,把html源文件下载下来
new Thread(){
public void run() {
try {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");//声明请求方式 默认get
//conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Linux; U; Android 2.3.3; zh-cn; sdk Build/GRI34) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1 MicroMessenger/6.0.0.57_r870003.501 NetType/internet");
int code = conn.getResponseCode();
if(code ==200){
InputStream is = conn.getInputStream();
String result = StreamTools.readStream(is);
Message msg = Message.obtain();//减少消息创建的数量
msg.obj = result;
msg.what = SUCCESS;
handler.sendMessage(msg);
}
} catch (Exception e) {
Message msg = Message.obtain();//减少消息创建的数量
msg.what = ERROR;
handler.sendMessage(msg);
e.printStackTrace();
}
};
}.start();
}
}
xml布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" >
<EditText
android:id="@+id/et_path"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="http://www.bai.com" />
<Button
android:onClick="click"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="查看源码" />
<ScrollView
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TextView
android:id="@+id/tv_result"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</ScrollView>
</LinearLayout>
输入流转字符串工具类代码
package com.example.amqr.httpurltest;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* 流的工具类
* @author Administrator
*
*/
public class StreamTools {
/**
* 把输入流的内容转换成字符串
* @param is
* @return null解析失败, string读取成功
*/
public static String readStream(InputStream is) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = -1;
while( (len = is.read(buffer))!=-1){
baos.write(buffer, 0, len);
}
is.close();
String temptext = new String(baos.toByteArray());
if(temptext.contains("charset=gb2312")){//解析meta标签
return new String(baos.toByteArray(),"gb2312");
}else{
return new String(baos.toByteArray(),"utf-8");
}
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
运行gif图
完。