WebView的基本用法
- 简易适配
<WebView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/wv">
</WebView>
- 代码
public class MainActivity extends Activity {
private WebView webview;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
webview=(WebView)findViewById(R.id.wv);
webview.getSettings().setJavaScriptEnabled(true); //让WebView支持JavaScript脚本
webview.setWebViewClient(new WebViewClient(){ //配置WebViewClient。WebViewClient是一个事件接口,通过自己实现的WebViewClient,可响应各种渲染事件。
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) { //当有新的URL加载到WebView(比如点击了某个链接),该方法会决定下一步的行动。
webview.loadUrl(url);
return true;
};
});
webview.loadUrl("http://www.baidu.com");
}
}
这里重写shouldOverrideUrlLoading,返回值上要注意下。
如果提供了 WebViewClient 对象且shouldOverrideUrlLoading 方法返回 true,则主机应用处理URL,如我们需要针对点击事件添加额外控制,也就需要传说中的自定义 WebViewClient去实现;
如果提供了 WebViewClient 对象且shouldOverrideUrlLoading 方法返回 false,则当前 WebView 处理URL;
而如果没有new WebViewClient,则 WebView 会请求 Activity 管理者选择合适的 URL 处理方式,一般情况就是启动浏览器来加载URL;
所以如果仅需要控制在 WebView 内加载新的 URL ,只需要下面一段代码即可:
WebView myWebView = (WebView) findViewById(R.id.wv);
myWebView.setWebViewClient(new WebViewClient());//默认返回false
- 不要忘记权限
<uses-permission android:name="android.permission.INTERNET" />
使用HTTP协议访问网络
HTTP协议的工作原理简易理解就是:
客户端向服务器发出了一条HTTP请求,服务器收到请求之后会返回一些数据给客户端,然后客户端再对这些数据进行解析和处理就可以了。
本来Android上发送HTTP请求的方式一般有两种,但是随着时代的进步,HttpClient由于许多问题最终被 Google所摒弃。现在最新的Android6.0 SDK上已经删除了HttpClient的相关类。
- 关于HttpClient与HttpURLConnection的一些介绍可以继续查看郭神的博客 Android访问网络,使用HttpURLConnection还是HttpClient?
- 现在一般都是用HttpURLConnection,以下用一个简单的网络图片查看器作为例子
简单的网络图片查看器
URL url = new URL(address);
//获取连接对象,并没有建立连接
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//设置连接和读取超时
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
//设置请求方法,注意必须大写
conn.setRequestMethod("GET");
//建立连接,发送get请求
//conn.connect();
//getResponseCode()本来就会去建立连接,所以conn.connect()可注释
//然后获取响应吗,200说明请求成功
if (conn.getResponseCode() == 200) {
.......
}
常用的HTTP请求方法有两种,GET和POST
- GET表示希望从服务器那里获取数据
- POST表示希望提交数据给服务器
服务器的图片是以流的形式返回给浏览器的
//拿到服务器返回的输入流
InputStream is = conn.getInputStream();
//把流里的数据读取出来,并构造成图片
Bitmap bm = BitmapFactory.decodeStream(is);
- 把图片设置为ImageView的显示内容
ImageView iv = (ImageView) findViewById(R.id.iv);
iv.setImageBitmap(bm);//只能在主线程中执行
- 添加权限
<uses-permission android.permission.INTERNET/>
主线程不能被阻塞
- 在Android中,主线程被阻塞会导致应用不能刷新ui界面,不能响应用户操作,用户体验将非常差
- 主线程阻塞时间过长,系统会抛出ANR异常
- ANR:Application Not Response;应用无响应
- 任何耗时操作都不可以写在主线程
- 因为网络交互属于耗时操作,如果网速很慢,代码会阻塞,所以网络交互的代码不能运行在主线程
只有主线程能刷新ui
- 刷新ui的代码只能运行在主线程,运行在子线程是没有任何效果的
- 如果需要在子线程中刷新ui,使用消息队列机制
Handler用途
- 当主线程需要子线程完成之后再执行的操作,并且这些操作不能在子线程中完成,例如修改UI子类的
消息队列
- Looper(消息轮询器)一旦发现Message Queue中有消息,就会把消息取出,然后把消息扔给Handler(消息处理器)对象,Handler会调用自己的handleMessage方法来处理这条消息
- handleMessage方法运行在主线程
- 主线程创建时,消息队列和轮询器对象就会被创建,但是消息处理器对象,需要使用时,自行创建
- 总结:只要消息队列有消息,handleMessenger方法就会被调用
//消息队列
static Handler handler = new Handler(){
//主线程中有一个消息轮询器looper,不断检测消息队列中是否有新消息,如果发现有新消息,自动调用此方法,注意此方法是在主线程中运行的
public void handleMessage(android.os.Message msg) {
}
};
- 在子线程使用handler往消息队列里发消息
//创建消息对象
Message msg = new Message();// Message msg = handler.obtainMessage();
//消息的obj属性可以赋值任何对象,通过这个属性可以携带数据
msg.obj = bm;
//what属性相当于一个标签,用于区分出不同的消息,从而运行不能的代码
msg.what = 1;
//发送消息
handler.sendMessage(msg);
- 通过switch语句区分不同的消息
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
//如果是1,说明属于请求成功的消息
case 1:
ImageView iv = (ImageView) findViewById(R.id.iv);
Bitmap bm = (Bitmap) msg.obj;
iv.setImageBitmap(bm);
break;
case 2:
Toast.makeText(MainActivity.this, "请求失败", 0).show();
break;
}
}
加入缓存图片的功能
- 把服务器返回的流里的数据读取出来,然后通过文件输入流写至本地文件
//1.拿到服务器返回的输入流
InputStream is = conn.getInputStream();
//2.把流里的数据读取出来,并构造成图片
//File file = new File(getCacheDir(), getFileName(path));
FileOutputStream fos = new FileOutputStream(file);
byte[] b = new byte[1024];//1024,每次读取1K
int len = 0;
while((len = is.read(b)) != -1){
fos.write(b, 0, len);
}
- 创建bitmap对象的代码改成
Bitmap bm = BitmapFactory.decodeFile(file.getAbsolutePath());
- 每次发送请求前检测一下在缓存中是否存在同名图片,如果存在,则读取缓存
//1.确定网站
final String path = "";
final File file = new File(getCacheDir(), getFileName(path));
//2.判断,缓存中是否存在该文件
if (file.exists()) {
//如果缓存存在,从缓存读取图片
Bitmap bm = BitmapFactory.decodeFile(file.getAbsolutePath());
iv.setImageBitmap(bm);
}else{
//如果缓存不存在,从网络下载
......
}
- 获取文件名
public String getFileName(String String){
int index = path.lastIndexOf("/");
return path.substring(index + 1);
}
Html源文件查看器
- 发送GET请求
URL url = new URL(path);
//获取连接对象
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//设置连接属性
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
//建立连接,获取响应吗
if(conn.getResponseCode() == 200){
......
}
- 获取服务器返回的流,从流中把html源码读取出来
byte[] b = new byte[1024];
int len = 0;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while((len = is.read(b)) != -1){
//把读到的字节先写入字节数组输出流中存起来
bos.write(b, 0, len);
}
//把字节数组输出流中的内容转换成字符串
//默认使用utf-8
text = new String(bos.toByteArray());
乱码的处理
- 乱码的出现是因为服务器和客户端码表不一致导致
//手动指定码表
text = new String(bos.toByteArray(), "gbk");
获取xml文件并且解析
List<News> newsList;
private void getNewsInfo(){
Thread t = new Thread(){
@Override
public void run(){
String path = "....news.xml";
try{
URL url = new URl(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setReadTimeout(5000);
conn.setConnectTimeout(5000);
//发送http GET请求,获取相应码
if (conn.getResponseCode() == 200 ){
InputStream is = conn.getInputStream();
//使用pull解析器,解析这个流
parseNewsXml(is);
}
} catch (Exception e) {
e.printStackTrace();
}
}
};
t.start();
}
private void parseNewsXml(InputStream is){
XmlPullParser xp = Xml.newPullParser();
try{
xp.setInput(is, "utf-8");
//对节点的事件类型进行判断,就可以知道当前节点是什么节点
int type = xp.getEventType();
News news = null;
while(type != XmlPullParser.END_DOCUMENT){
switch (type) {
case XmlPullParser.START_TAG:
if ("newslist".equals(xp.getName())) {
newsList = new ArrayList<News>;
}else if ("news".equals(xp.getName())) {
news = new News();
}else if ("title".equals(xp.getName())) {
String title = xp.nextText();
news.setTitle(title);
}
break;
case XmlPullParser.END_TAG:
if ("news".equals(xp.getName())) {
newsList.add(news);
}
break;
}
//解析完当前节点后,把指针移动到下一个节点,并返回它的事件类型
type = xp.next();
}
} catch (Exception e){
e.printStackTrace();
}
}
提交数据
GET方式提交数据
- get方式提交的数据是直接拼接在url的末尾
final String path = "http://localhost/Web/servlet/CheckLogin?name=" + name + "&pass=" + pass;
- 发送get请求,代码和之前一样
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setReadTimeout(5000);
conn.setConnectTimeout(5000);
if(conn.getResponseCode() == 200){
}
- 浏览器在发送请求携带数据时会对数据进行URL编码,我们写代码时也需要为中文进行URL编码
String path = "http://localhost/Web/servlet/CheckLogin?name=" + URLEncoder.encode(name) + "&pass=" + pass;
POST方式提交数据
- post提交数据是用流写给服务器的
协议头中多了两个属性
- Content-Type: application/x-www-form-urlencoded,描述提交的数据的mimetype
- Content-Length: 32,描述提交的数据的长度
发送post请求
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setReadTimeout(5000);
conn.setConnectTimeout(5000);
//给请求头添加post多出来的两个属性
String data = "name=" + URLEncoder.encode(name) + "&pass=" + pass;
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setRequestProperty("Content-Length", data.length() + "");
conn.setDoOutput(true);
//拿到输出流
OutputStream os = conn.getOutputStream();
os.write(data.getBytes());
if(conn.getResponseCode() == 200){
}
- 设置允许打开post请求的流
conn.setDoOutput(true);
- 获取连接对象的输出流,往流里写要提交给服务器的数据
OutputStream os = conn.getOutputStream();
os.write(data.getBytes());
多线程下载
原理:服务器CPU分配给每条线程的时间片相同,服务器带宽平均分配给每条线程,所以客户端开启的线程越多,就能抢占到更多的服务器资源
确定每条线程下载多少数据
- 发送http请求至下载地址
String path = "http://localhost:8080/XXX.exe";
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(5000);
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
- 获取文件总长度,然后创建长度一致的临时文件
if(conn.getResponseCode() == 200){
//获得服务器流中数据的长度
int length = conn.getContentLength();
//创建一个临时文件存储下载的数据
RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rwd");
//设置临时文件的大小
raf.setLength(length);
raf.close();
}
- 确定线程下载多少数据
//计算每个线程下载多少数据
int blockSize = length / THREAD_COUNT;
计算每条线程下载数据的开始位置和结束位置
for(int id = 1; id <= 3; id++){
//计算每个线程下载数据的开始位置和结束位置
int startIndex = (id - 1) * blockSize;
int endIndex = id * blockSize - 1;
if(id == THREAD_COUNT){
endIndex = length;
}
//开启线程,按照计算出来的开始结束位置开始下载数据
new DownLoadThread(startIndex, endIndex, id).start();
}
再次发送请求至下载地址,请求开始位置至结束位置的数据
String path = "http://localhost:8080/XXX.exe";
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(5000);
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
//向服务器请求部分数据
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
conn.connect();
- 下载请求到的数据,存放至临时文件中
//请求部分数据,相应码是206
if(conn.getResponseCode() == 206){
InputStream is = conn.getInputStream();
RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rwd");
//指定从哪个位置开始存放数据
raf.seek(startIndex);
byte[] b = new byte[1024];
int len;
while((len = is.read(b)) != -1){
raf.write(b, 0, len);
}
raf.close();
}
带断点续传的多线程下载
- 定义一个int变量记录每条线程下载的数据总长度,然后加上该线程的下载开始位置,得到的结果就是下次下载时,该线程的开始位置,把得到的结果存入缓存文件
//用来记录当前线程总的下载长度
int total = 0;
while((len = is.read(b)) != -1){
raf.write(b, 0, len);
total += len;
//每次下载都把新的下载位置写入缓存文本文件
RandomAccessFile raf2 = new RandomAccessFile(threadId + ".txt", "rwd");
raf2.write((startIndex + total + "").getBytes());
raf2.close();
}
- 下次下载开始时,先读取缓存文件中的值,得到的值就是该线程新的开始位置
FileInputStream fis = new FileInputStream(file);
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
String text = br.readLine();
int newStartIndex = Integer.parseInt(text);
//把读到的值作为新的开始位置
startIndex = newStartIndex;
fis.close();
- 三条线程都下载完毕之后,删除缓存文件
RUNNING_THREAD--;
synchronized (path){
if(RUNNING_THREAD == 0){
for(int i = 0; i <= ThreadCount; i++){
File f = new File(i + ".txt");
f.delete();
}
finishedThread = 0 ;
}
}
用进度条显示下载进度
- 设置style
<ProgressBar
android:layout_width="match_paren"
android:layout_height="wrap_content"
style="@android:style/Widget.ProgressBar.Horizontal"/>
- 拿到下载文件总长度时,设置进度条的最大值
//设置进度条的最大值
pb.setMax(length);
进度条需要显示三条线程的整体下载进度,所以三条线程每下载一次,就要把新下载的长度加入进度条
定义一个int全局变量,记录三条线程的总下载长度
int progress;
刷新进度条
while((len = is.read(b)) != -1){
raf.write(b, 0, len);
//把当前线程本次下载的长度加到进度条里
progress += len;
pb.setProgress(progress);
}
- 每次断点下载时,从新的开始位置开始下载,进度条也要从新的位置开始显示,在读取缓存文件获取新的下载开始位置时,也要处理进度条进度
FileInputStream fis = new FileInputStream(file);
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
String text = br.readLine();
int newStartIndex = Integer.parseInt(text);
//新开始位置减去原本的开始位置,得到已经下载的数据长度
int alreadyDownload = newStartIndex - startIndex;
//把已经下载的长度设置入进度条
progress += alreadyDownload;
添加文本框显示百分比进度
tv.setText((long)progress * 100 / pb.getMax() + "%");
xUtils的HttpUtils的使用
- xUtils,它是在aFinal基础上进行重构和扩展的框架,相比aFinal有很大的改善。
- xUtils中的HttpUtils本身就支持多线程断点续传,使用起来非常的方便。
- 创建HttpUtils对象
HttpUtils http = new HttpUtils();
- 下载文件
http.download(url, //下载请求的网址
target, //下载的数据保存路径和文件名
true, //是否开启断点续传
true, //如果服务器响应头中包含了文件名,那么下载完毕后自动重命名
new RequestCallBack<File>() {//侦听下载状态
//下载成功此方法调用
@Override
public void onSuccess(ResponseInfo<File> arg0) {
tv.setText("下载成功" + arg0.result.getPath());
}
//下载失败此方法调用,比如文件已经下载、没有网络权限、文件访问不到,方法传入一个字符串参数告知失败原因
@Override
public void onFailure(HttpException arg0, String arg1) {
tv.setText("下载失败" + arg1);
}
//在下载过程中不断的调用,用于刷新进度条
@Override
public void onLoading(long total, long current, boolean isUploading) {
super.onLoading(total, current, isUploading);
//设置进度条总长度
pb.setMax((int) total);
//设置进度条当前进度
pb.setProgress((int) current);
tv_progress.setText(current * 100 / total + "%");
}
});
解析网络数据
解析XML格式数据
- Pull解析方式
- PULL 的工作原理:
XML pull提供了开始元素和结束元素。当某个元素开始时,可以调用parser.nextText从XML文档中提取所有字符数据。当解析到一个文档结束时,自动生成EndDocument事件。 - 得到XmlPullParser对象,XmlPullParserFactory.newInstance.newPullParser();
- 设置XML数据,xmlPullParser.setInput(new StringReader(xmlData));
- 调用xmlPullParser的getEventType()和next()解析事件,nextText()获取结点的内容。读取到xml的声明返回 START_DOCUMENT; 结束返回 END_DOCUMENT ; 开始标签返回 START_TAG;结束标签返回 END_TAG; 文本返回 TEXT。
- PULL 的工作原理:
public void pullxml(InputStream in) throws Exception {
// 由android.util.Xml创建一个XmlPullParser实例
XmlPullParser parser = Xml.newPullParser();
// 设置输入流 并指明编码方式
parser.setInput(in, "UTF-8");
// 产生第一个事件
int eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
switch (eventType) {
// 判断当前事件是否为文档开始事件
case XmlPullParser.START_DOCUMENT:
list = new ArrayList<Book>();// 初始化list集合
break;
// 判断当前事件是否为标签元素开始事件
case XmlPullParser.START_TAG:
if (parser.getName().equals("book")) { // 判断开始标签元素是否是book
book = new Book();
} else if (parser.getName().equals("id")) {
eventType = parser.next();
// 得到book标签的属性值,并设置book的id
book.setId(Integer.parseInt(parser.getText()));
} else if (parser.getName().equals("name")) { // 判断开始标签元素是否是book
eventType = parser.next();
book.setName(parser.getText());
} else if (parser.getName().equals("price")) { // 判断开始标签元素是否是price
eventType = parser.next();
book.setPrice(Float.parseFloat(parser.getText()));
}
break;
// 判断当前事件是否为标签元素结束事件
case XmlPullParser.END_TAG:
if (parser.getName().equals("book")) { // 判断结束标签元素是否是book
list.add(book); // 将book添加到books集合
book = null;
}
break;
}
// 进入下一个元素并触发相应事件
eventType = parser.next();
}
return list;
}
}
- SAX解析方式
- 其实SAX跟pull差不多,就是把解析具体结点的操作封装成一个DefaultHandler类。首先要自定义一个类继承自DefaultHandler。
- 获取XMLReader实例,SAXParserFactroy.newInstance.newSAXParser().getXMLReader();
- 将自定义的MyHandler实例设置到XMLReader中
- 调用xmlreader的parser开始解析,xmlReader.parse(new InputSource(new StringReader(xmlData)));
public class MyHandler extends DefaultHandler {
@Override
public void startDocument() throws SAXException {
//开始XML解析的时候调用
super.startDocument();
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
//开始解析某个结点的时候调用。
super.startElement(uri, localName, qName, attributes);
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
//获取结点内容时调用
super.characters(ch, start, length);
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
//完成解析某个结点的时候调用
super.endElement(uri, localName, qName);
}
@Override
public void endDocument() throws SAXException {
//完成整个XML解析的时候调用
super.endDocument();
}
}
解析JSON格式数据
- 使用JSONObject
- 把jsonData转化成JSONArray
- 从JSONArray取出JSONObject
- 从JSONObject中取出字段
private void parseJASONWithJSONObject(String jsonData){
try {
//把jsonData转化成JSONArray
JSONArray jsonArray=new JSONArray(jsonData);
for(int i=0;i<jsonArray.length();i++){
//从JSONArray取出JSONObject
JSONObject jsonObject=jsonArray.getJSONObject(i);
//从JSONObject中取出字段
String id=jsonObject.getString("id");
String name=jsonObject.getString("name");
String version=jsonObject.getString("version");
}
} catch (Exception e) {
e.printStackTrace();
}
}
- 使用GSON解析json数据
GsonUserGuide
网络编程的笔记整理到这里告一段落,接下来继续整理其他Android笔记,(^__^) 谢谢观看~