Android学习笔记-网络编程
来源:互联网 发布:mac keeper劫持 编辑:程序博客网 时间:2024/06/05 02:47
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笔记,(^__^) 谢谢观看~
- Android学习笔记-网络编程
- Android学习笔记-网络编程
- Android学习笔记:网络编程-基础篇
- Android学习笔记 day04 _ 网络编程
- android网络编程学习笔记(一)
- Android学习笔记网络编程(1、基本概念)
- Android学习笔记——网络编程
- 网络编程学习笔记
- 学习笔记--网络编程
- 网络编程学习笔记
- 学习笔记--网络编程
- 学习笔记---网络编程
- 网络编程学习笔记
- 网络编程 学习笔记
- 网络编程学习笔记,
- 网络编程学习笔记
- 网络编程 学习笔记
- Android学习笔记:Android网络编程的理解和总结
- 软件开发过程
- android 开发工具
- strtusAction中的获取request
- Bootstrap学习笔记
- 函数编程的学习归纳(增)
- Android学习笔记-网络编程
- java的io流相关类使用
- iOS开发 兼容OC和C语言
- 基于S3C6410的ARM11学习(五) 核心初始化之关闭看门狗
- 以文件流的形式生成验证码
- PeekInputStream
- 【三层架构】对于三层架构的认识和总结
- validate插件获取表单中某一项的验证情况
- XMLhttpRequest 请求 XML,JSON ,POJO 数据