Android学习笔记-网络编程

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

网络编程的笔记整理到这里告一段落,接下来继续整理其他Android笔记,(^__^) 谢谢观看~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值