安卓网络编程学习之HTTP(S)

这里学习使用的API为Android原生API:HttpUrlConnection。我们平常使用中更多的是使用第三方框架例如OKHttp,Volley等,但是像Volley也是封装了访问网络的一些操作,底层用的还是HttpUrlConnection,而OkHttp和HttpUrlConnection则使用socket实现了网络连接,只是OkHttp比HttpUrlConnection来说功能更强大。当然这里先不说其他的第三方框架如何,先了解http一些基础知识,使用原生API进行学习。

Http 概念:hypertext transfer protocol 超文本传输协议,TCP/IP协议的一个应用层协议,用于定义WEB浏览器与WEB服务器之间交换数据的过程。客户端连上web服务器后,若想获得web服务器中的某个web资源,需遵守一定的通讯格式,Http协议用于定义客户端与web服务器通讯的格式。

1.Http通信流程

这里就以经常会问到的浏览器输入url:www.baidu.com后会发生什么为例,当然现在百度的请求都改为了Https,这里考虑最简单的情况。

1.1DNS解析

DNS解析的最终目的是查找域名对应的ip地址。

  • 1.请求发起后,浏览器有自己的缓存,因此先查找浏览器有没有相应的DNS缓存记录,没有的话,调用系统函数获取系统缓存记录。
  • 2.接上一步,查看系统的hosts文件里有没有对应的域名规则,有的话就直接使用hosts文件里面的ip地址。以前常用的fq方法大致就是修改hosts文件加上google.com的对应规则。
  • 3.如果第2步没有找到,会继续到路由器上,路由器也有DNS缓存。
  • 4.如果上面都没找到,那么这个解析url的DNS请求会先发送到本地DNS(域名分布系统)服务器,一般由运营商提供。本地DNS服务器去查询自己的缓存,有的话返回。
  • 5.第4步没有找到的话,本地DNS服务器将这个DNS请求继续向根DNS服务器发送,根DNS服务器本身并没有记录域名与ip的对应关系,而是告诉本地DNS服务器,你到.com域服务器上查询,并给出.com域服务器的地址。
  • 6.本地DNS服务器就把这个DNS请求发送到.com域服务器上,.com域服务器收到DNS请求之后,和根DNS服务器一样也不会直接返回域名和IP地址的对应关系,而是告诉本地DNS服务器,去www.baidu.com域名的域服务器上查询。
  • 7.最后,本地DNS服务器向www.baidu.com域名的解析服务器发出请求,就可以收到这个域名对应的IP地址。本地DNS服务器收到后把IP地址返回给用户,还要把这个对应关系保存在缓存中,以备下次别的用户查询时,可以直接返回结果,加快网络访问。

之后的步骤可以参考下图(图片来源),主机A相当于我们的PC,浏览器处于应用层,主机B为百度的服务器,总的来说就是我们的请求从A的应用层到A的物理层,通过物理线路传到B的物理层,这中间会涉及到各种路由协议等等,B收到之后又从下至上层层解包,接着响应数据又顺着应用层->传输层->网络层->网络层->传输层->应用层的顺序返回到A,A应用层的浏览器根据获得的响应渲染响应页面。 重要的几个步骤如下:
在这里插入图片描述

1.2建立TCP连接

Http本质上是通过TCP传送数据的(原因就是我们常说的TCP是面向连接的可靠的,可以进行差错控制等,而UDP是无连接的,不可靠的),因此在发送接收http请求之前,先要建立TCP连接。这里就用的是上篇文章里面提到的三次握手。

1.3Http请求

TCP连接建立后,应用层的http请求加上TCP头部-包括源端口号、目的端口号(http为80)和用于校验数据完整性的序号等,继续向下层传输到网络层,网络层在数据包上加上IP头部(见下图),包含了原IP与目的IP,之后就是加上数据链路层的帧头,中间的细节省略掉,继续向下传输到底层物理层,底层通过物理线路传输到百度服务器。服务器收到后又层层解包,服务器网络层收到数据包后,解析出IP头部,识别数据部分,并将解开的数据包向上传输到传输层,传输层获得数据包后,就解析出TCP头部,识别端口,将解开的数据包向上传输到应用层,应用层HTTP解析请求头和请求体,根据用户请求返回相应资源接着响应数据又顺着应用层->传输层->网络层->网络层->传输层->应用层的顺序返回到PC,PC应用层的浏览器根据获得的响应渲染响应页面。
在这里插入图片描述

1.4断开TCP连接

数据传输完成后,为了避免客户端与服务器资源占用和损耗,当双方没有请求或响应传递了,任意一方都可以发起关闭请求,用到的就是四次挥手。Http 1.1版本增加了长连接功能,在Http数据头部加上Connection:Keep-Alive,表示TCP一直保持连接,可以提高资源加载速度。

1.5浏览器渲染

这里考虑简单的HTML、CSS资源,浏览器通过解析HTML,生成DOM树,解析CSS,生成CSS规则树,然后将DOM树和CSS规则树结合生成渲染树,之后通过Layout可以计算出每个元素具体的宽高颜色位置,结合起来开始绘制,最后显示在屏幕新页面中。

Http协议在通信的过程中是以明文方式发送数据,不提供任何方式的数据加密,如果攻击者截取了浏览器和服务器之间的传输报文,就可以直接读懂其中的信息,因此,Http协议不适合传输一些敏感信息,比如:信用卡号、密码等支付信息。在这样的情况下,Https应用而生,Https可以将数据加密传输,保证数据传输的安全。这里学习一下Https的通信过程,参考iispring博文

2.Https

Https协议简单来说由Http协议和SSL/TLS协议构成。Http协议前面已经说过,它扮演的角色就是传输数据,而SSL/TLS是负责加密解密等安全处理的模块,需要用它对数据进行加密和解密,加密后的数据也是通过Http传输的,所以Https的核心在SSL/TLS上面。

SSL的全称是Secure Sockets Layer,即安全套接层协议,是为网络通信提供安全及数据完整性的一种安全协议。SSL协议在1994年被Netscape发明,后来各个浏览器均支持SSL,其最新的版本是3.0 。
TLS的全称是Transport Layer Security,即安全传输层协议,最新版本的TLS(Transport Layer Security,传输层安全协议)是IETF(Internet Engineering Task Force,Internet工程任务组)制定的一种新的协议,它建立在SSL 3.0协议规范之上,是SSL 3.0的后续版本。在TLS与SSL3.0之间存在着显著的差别,主要是它们所支持的加密算法不同,所以TLS与SSL3.0不能互操作。虽然TLS与SSL3.0在加密算法上不同,但是在我们理解HTTPS的过程中,我们可以把SSL和TLS看做是同一个协议。

通信的过程图如下图所示:
在这里插入图片描述
借用iispring博文中的一句简短的介绍就是:HTTPS为了兼顾安全与效率,同时使用了对称加密和非对称加密。数据是被对称加密传输的,对称加密过程需要客户端的一个密钥,为了确保能把该密钥安全传输到服务器端,采用非对称加密对对称加密所使用的密钥进行加密传输。具体步骤如下:

  • 1.客户端或者这里说浏览器向服务器发起HTTPS请求,连接到百度服务器的443端口,请求携带了浏览器支持的加密算法和哈希算法。
  • 2.服务器收到请求,选择浏览器支持的加密算法和哈希算法。
  • 3.服务器将数字证书返回给浏览器,这里的数字证书可以是向某个权威机构申请的,也可以是自制的。
    这里解释一下数字证书:第3步的实质其实是服务器将自己的公钥发送给浏览器以方便之后非对称加密用。我们知道服务器端有自己的公钥和私钥,服务器端着私钥自己保密不泄漏,但是公钥在发送给浏览器的时候很可能会被黑客中途篡改,这就存在安全问题即浏览器不知道这个公钥是自己想请求的服务器的还是黑客的,于是数字证书就应运而生。数字证书简单点说就是找了个可靠的权威的第三方担保人CA,CA也有自己的私钥和公钥,它用自己的私钥对服务器的公钥进行非对称加密,只有用CA公开的公钥才能解密,这样就能保证服务器公钥的可靠性。加密完之后,得到的密文再加上证书的有效期、颁发给、颁发者等信息,构成了了数字证书。当然如果是自制的认证机构,可能浏览器就认为是不可靠的,得让客户端去信任相应的跟证书。简单来说,数字证书就是为了解决公钥的可靠性问题。
  • 4.浏览器验证服务器数字证书的合法性,这一部分是浏览器内置的TLS完成的,如果发现发现数字证书有问题,那么HTTPS传输就无法继续,具体的验证方法如下:
  • 4.1. 首先浏览器会从内置的证书列表中索引,找到服务器下发证书对应的机构,如果没有找到相应机构,此时就会提示用户该证书是不是由权威机构颁发,无法信任。如果查到了对应的机构,则取出该机构颁发的公钥。
  • 4.2. 用这个对应机构的证书公钥去解密,能够解密成功的话说明该数字证书就是由该CA的私钥签发的。解密的内容包括网站的网址、网站的公钥、证书的有效期等。浏览器会先验证证书签名的合法性,还要验证浏览器当前访问的服务器的域名是与数字证书中提供的“颁发给”这一项吻合,还要检查数字证书是否过期等。这些都通过认证时,浏览器就可以安全使用证书中的网站公钥进行之后的非对称加密了。
  • 4.3. 已经验证服务器公钥是可靠的之后,浏览器生成一个随机值R,作为传输数据时的对称加密密钥,并利用4.2中得到的服务器公钥对这个R进行非对称加密。因为传输数据量一般较大,非对称加密计算复杂耗时太多,肯定会造成延迟,因此量大数据适合采用对称加密。
  • 5.浏览器将非对称加密后的密钥密文发送给服务器。到这里就已经完成了Https的握手过程。
  • 6.服务器接收到客户端发来的密文之后,用自己的私钥对其进行非对称解密,解密之后的明文就是共享的对称密钥R,然后用对称密钥R对数据例如网页内容进行对称加密,这样数据就变成了密文。
  • 7.然后服务器将加密后的密文发送给浏览器。
  • 8.浏览器收到密文后,用共享的对称密钥R对密文进行对称解密,得到服务器发送的数据。

3.Http版本

这里简单学习下Http 1.0、1.1和最新的2.0三个版本的区别。参考一只好奇的茂博文

先看下Http 1.0和1.1的区别

这里挑几个重要的点来说:

  • 1.节约宽带

  • Http1.0中不支持断点续传,例如客户端只是需要某个资源的一部分,但是服务器却会将整个资源送过来。Http1.1则在请求头引入了RANGE头域,它允许只请求资源的某个部分,即返回码是206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。这点我们在前面AsyncTask实现文件下载的时候学习过,计算出已存在文件的长度,之后再下载的话就直接请求这个长度之后的文件资源,而不用从头再下载整个文件。
    在这里插入图片描述

  • 2.长连接
    Http 1.0规定客户端与服务器只能保持短暂的连接,客户端的每次请求都需要与服务器建立一个TCP连接,服务器完成请求处理后立即断开TCP连接,服务器不跟踪每个用户也不记录过去的请求。举个例子比如一个网页中包含很多图像url地址或者包含JS、CSS规则等,那么每次请求和响应都需要通过TCP三次握手建立一个单独的连接(耗时),每次连接传输一个文档或者图像等,传输完成后就四次挥手断开连接,上一次和下一次请求完全是分离的,这必不可免的会造成耗时。为了解决这个问题,Http1.0需要使用connection:keep-alive参数来告知服务器端要建立一个TCP长连接,一次请求完成后不会断开连接。Http 1.1则默认支持长连接,默认开启Connection: keep-alive,如果使用http1.1协议不希望建立长连接,就则需要在request或者response的header中指明connection的值为close。
    http1.0不指明keep-alive时,每次请求都要建立一次连接:在这里插入图片描述
    http1.1默认开启长连接:
    在这里插入图片描述

  • 3.Host头处理
    在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此不支持Host请求头字段,客户端无法使用主机头名来明确表示要访问服务器上的哪个WEB站点,这样就无法使用WEB服务器在同一个IP地址和端口号上配置多个虚拟WEB站点。在HTTP 1.1中增加Host请求头字段后,客户端可以使用主机头名来明确表示要访问服务器上的哪个WEB站点,实现了一台服务器上可以在同一个IP地址和端口号上使用不同的主机名来创建多个虚拟WEB站点。

Http 2.0相比Http1.x

Http 2.0相比Http1.x多出一些新的特性:

  • 1.多路复用 (Multiplexing)
    Http 2.0使用了多路复用,允许同一个连接并发处理多个请求即连接共享,一个请求对应一个id,这样一个连接上可以有多个请求,每个连接的请求可以随机的混杂在一起,接收方可以根据请求的 id将请求再归属到各自不同的服务端请求里面。连接共享如下图所示:
    在这里插入图片描述

  • 2.二进制分帧
    Http 1.x的解析是基于文本,基于文本协议的格式解析存在缺陷,因为文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只有0和1组合。基于这种考虑Http 2.0的协议解析采用二进制格式,在应用层和传输层之间增加一个二进制分帧层,在二进制分帧层里面将所有传输的信息分割为更小的消息和帧(frame),并对它们采用二进制格式的编码 ,实现方便且健壮。

  • 3.服务端推送
    服务端推送可以把客户端可能会用到的资源伴随着网页内容一起发送到客户端,省去了客户端重复请求的步骤,非常合适加载静态资源,可以极大地提升静态资源的加载速度。与普通的客户端请求对比图如下:
    普通客户端请求:在这里插入图片描述
    采用服务端推送:
    在这里插入图片描述

  • 4.头部(Header)压缩
    Http 1.x不支持header数据的压缩,但是header中带有大量信息,每次都要重复发送会耗时。Http 2.0使用HPACK算法对header的数据进行压缩,数据体积变小,在网络上传输就会更快。

4.GET与POST

GET与POST是我们最常见的两种数据请求方式,都是用来发送数据的,只是发送机制不一样,GET安全性非常低,Post安全性较高, 但是执行效率却比Post方法好,一般获取查询数据的时候用GET,数据增删改的时候用POST。主要区别如下:

  • 1.GET把参数包含在URL中,对所有人可见,POST通过request body传递参数,数据不会显示在 URL 中。
  • 2.受URL长度限制,Get传送的数据量较小(2k);Post传送的数据量较大,一般被默认为不受限制。
  • 3.后退或者刷新页面时,GET请求无害,而POST请求数据会被重新提交。
  • 4.GET请求会缓存数据,而POST请求不会缓存数据。
  • 5.GET请求限制数据类型为ASCII 字符,POST请求无限制,允许二进制数据。
  • 6.GET请求会把http header和data一并发送出去,而POST请求根据浏览器或者框架的不同,会将header和data分开发送,先发送header,服务器响应100 continue,再发送data。

从上面也可以看到GET请求的速度比POST请求块,主要的几点原因也在上面提到了:

  • 1.POST需要在请求的body部分包含数据,所以会多了几个数据描述部分的首部字段(如content-type)。
  • 2.POST请求可能会将header和data分开发送。
  • 3.get会将数据缓存起来,而post不会

5.Session和Cookie

参考知乎

Session: 由于Http是无状态的协议,所以服务端需要记录用户的状态时就需要用某种机制来识具体的用户,这个机制就是Session。典型的场景比如购物车,当你点击下单按钮时,由于Http协议无状态,所以并不知道是哪个用户操作的,所以服务端要为特定的用户创建了特定的Session,用用于标识并且跟踪用户,这样才知道用户进行了什么操作例如购物车里面有几本书。Session是保存在服务端的,有一个唯一标识。

Cookie是实现Session机制的一种常用形式。 举例来说: 一般发送登陆请求后,服务器通过Set-Cookie响应头,返回一个Cookie,浏览器默认保存这个Cookie, 后续访问相关页面的时候会带上这个Cookie,通过Cookie请求头标识用户信息完成访问,如果没Cookie或者 Cookie过期,就提示用户没登陆,登陆超时,访问需要登陆之类的信息。

用户第一次请求服务器的时候,服务器根据用户提交的相关信息,创建创建对应的 Session ,请求返回时将此 Session 的唯一标识信息 SessionID 返回给浏览器,浏览器接收到服务器返回的 SessionID 信息后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名。
当用户第二次访问服务器的时候,请求会自动判断此域名下是否存在 Cookie 信息,如果存在自动将 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作。如果客户端的浏览器禁用了 Cookie ,会使用一种叫做URL重写的技术来进行会话跟踪,即每次Http交互,URL后面都会被附加上一个诸如 sid=xxxxx 这样的参数,服务端据此来识别用户。

Cookie 和 Session 不同

  • 作用范围不同,Cookie 保存在客户端(浏览器),Session 保存在服务器端。
  • 存取方式的不同,Cookie 只能保存 ASCII,Session 可以存任意数据类型,一般情况下我们可以在 Session 中保持一些常用变量信息,比如说 UserId 等。
  • 有效期不同,Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能,Session 一般失效时间较短,客户端关闭或者Session 超时都会失效。
  • 隐私策略不同,Cookie 存储在客户端,比较容易遭到不法获取;Session 存储在服务端,安全性相对 Cookie 要好一些。 存储大小不同, 单个 Cookie保存的数据不能超过 4K,Session 可存储数据远高于 Cookie

6.Demo练习

前面学习了相关知识,这里使用原生API进行练习,请求方式为GET。

GET请求获取数据

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
    <TextView
        android:id="@+id/tvmenu"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="加载菜单"
        android:clickable="true"
        android:gravity="center"
        android:textSize="20sp" />

    <ImageView
        android:id="@+id/mpicture"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone"/>

    <ScrollView
        android:id="@+id/scroll"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone">
        <TextView
            android:id="@+id/scrolltxt"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </ScrollView>

    <WebView
        android:id="@+id/mwebview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone"/>

</LinearLayout>

menu文件:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- checkableBehavior的可选值由三个:single设置为单选,all为多选,none为普通选项 -->
    <group android:checkableBehavior="none">
        <item android:id="@+id/pic" android:title="@string/query_pic"/>
        <item android:id="@+id/code" android:title="@string/query_code"/>
        <item android:id="@+id/loadview" android:title="@string/loadview"/>
    </group>

</menu>

数据请求类:

public class GetData {
    //定义类方法
    public static byte[] getImage(String param) throws Exception {
        URL url = new URL(param);
        HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
        conn.setConnectTimeout(5000);//超时时间为5s
        conn.setRequestMethod("GET");
        if(conn.getResponseCode() == 200) {
            InputStream is = conn.getInputStream();
            byte[] bt = StreamTobyte.read(is);
            is.close();
            return bt;
        }else {
            throw new RuntimeException("请求失败");
        }
    }

    public static String getHtml(String param) throws Exception {
        URL url = new URL(param);
        HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
        conn.setConnectTimeout(5000);//超时时间为5s
        conn.setRequestMethod("GET");
        if(conn.getResponseCode()==200){
            InputStream is = conn.getInputStream();
            byte[] bt = StreamTobyte.read(is);
            String html = new String(bt,"UTF-8");
            return html;
        }else {
            throw new RuntimeException("请求失败");
        }
    }

}

输入流转换为二进制数组类:

public class StreamTobyte {
    //定义类方法 将输入流转换为二进制数组
    public static byte[] read(InputStream inStream) throws Exception{
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = 0;
        while((len = inStream.read(buffer)) != -1)
        {
            outStream.write(buffer,0,len);
        }
        inStream.close();
        return outStream.toByteArray();
    }
}

MainActivity:

public class MainActivity extends AppCompatActivity {
    private TextView textView;
    private ImageView imageView;
    private ScrollView scrollView;
    private TextView scrolltextview;
    private WebView webView;

    private Bitmap bitmap;
    private String html;
    private long exitTime = 0;
    private final static String picurl = "https://ww2.sinaimg.cn/large/7a8aed7bgw1evshgr5z3oj20hs0qo0vq.jpg";
    private final static String htmlurl = "https://www.baidu.com";

    private Handler handler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case 0x001:
                    hideAllWidget();
                    imageView.setVisibility(View.VISIBLE);
                    imageView.setImageBitmap(bitmap);
                    Toast.makeText(MainActivity.this,"图片加载完成",Toast.LENGTH_SHORT).show();
                    break;
                case 0x002:
                    hideAllWidget();
                    scrollView.setVisibility(View.VISIBLE);
                    scrolltextview.setText(html);
                    Toast.makeText(MainActivity.this,"html代码加载完成",Toast.LENGTH_SHORT).show();
                    break;
                case 0x003:
                    hideAllWidget();
                    webView.setVisibility(View.VISIBLE);
                    webView.setWebViewClient(new WebViewClient() {
                        //设置在webView点击打开的新网页在当前界面显示,而不跳转到新的浏览器中
                        @Override
                        public boolean shouldOverrideUrlLoading(WebView view, String url) {
                            view.loadUrl(url);
                            return true;
                        }
                    });
                    webView.getSettings().setJavaScriptEnabled(true);  //设置WebView属性,运行执行js脚本
                    webView.loadUrl(htmlurl); //调用loadUrl方法为WebView加入链接
                    Toast.makeText(MainActivity.this,"网页加载完成",Toast.LENGTH_SHORT).show();
                    break;

            }
        }
    };

    private void hideAllWidget() {
        //INVISIBLE属性界面会保留view控件所占有的空间,GONE属性界面则不保留view控件所占有的空间
        imageView.setVisibility(View.GONE);
        scrollView.setVisibility(View.GONE);
        webView.setVisibility(View.GONE);
    }

    @Override
    public void onBackPressed() {
        //重写回退按钮的时间,当用户点击回退按钮,webView.canGoBack()判断网页是否能后退,可以则goback()
        //如果不可以连续点击两次退出App,否则弹出提示Toast
        if (webView.canGoBack()) {
            webView.goBack();
        } else {
            if ((System.currentTimeMillis() - exitTime) > 2000) {
                Toast.makeText(getApplicationContext(), "再按一次退出程序",
                        Toast.LENGTH_SHORT).show();
                exitTime = System.currentTimeMillis();
            } else {
                super.onBackPressed();
            }

        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bindviews();
        registerForContextMenu(textView);
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
        super.onCreateContextMenu(menu, v, menuInfo);
        MenuInflater inflater = new MenuInflater(this);
        inflater.inflate(R.menu.menu_context,menu);
    }

    @Override
    public boolean onContextItemSelected(@NonNull MenuItem item) {
        //菜单的点击事件
        switch (item.getItemId()){
            case R.id.pic:
                //UI线程中不能进行网络操作
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                         try{
                             byte[] pic = GetData.getImage(picurl);
                             bitmap = BitmapFactory.decodeByteArray(pic, 0, pic.length);
                         }catch (Exception e){
                             e.printStackTrace();
                         }
                         handler.sendEmptyMessage(0x001);
                    }
                }).start();
                break;
            case R.id.code:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            html = GetData.getHtml(htmlurl);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        handler.sendEmptyMessage(0x002);
                    }
                }).start();
                break;
            case R.id.loadview:
                handler.sendEmptyMessage(0x003);
                break;
        }
        return true;
    }

    private void bindviews() {
        textView = findViewById(R.id.tvmenu);
        imageView = findViewById(R.id.mpicture);
        scrollView = findViewById(R.id.scroll);
        scrolltextview = findViewById(R.id.scrolltxt);
        webView = findViewById(R.id.mwebview);
    }
}

效果图如下,长按加载菜单文本:
在这里插入图片描述
点击请求图片:
在这里插入图片描述
点击请求html代码:
在这里插入图片描述
点击加载webview:
在这里插入图片描述
搜索后:
在这里插入图片描述
回退:
在这里插入图片描述
再次回退:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_42011443/article/details/107009017