Android网络 | URL和URLConnection详解及其实战案例

URL和URLConnection

  • URL(Uniform Resource Locator)对象代表统一资源定位器
    指向互联网“资源”指针
    这里的资源可以是简单的文件或目录
    也可以是对更为复杂的对象引用
    例如对数据库搜索引擎查询

  • 通常情况而言,
    URL可以由协议名、主机、端口和资源组成,
    满足如下的格式
    protocol://host:port/resourceName

  • 例如下面就是一个合法的URL地址:
    http://www.oneedu.cn/Index.htm

  • 在Android系统中可以通过URL获取网络资源,
    其中的URLConnectionHTTPURLConnection
    是最为常用的两种方式。

URL类详解

  • 在JDK中还提供了一个URI(Uniform Resource Identifiers)类,
    其实例代表一个统一资源标识符
    Java的URI不能用于定位任何资源
    它的唯一作用就是解析

  • 与此对应的是,
    URL包含一个可打开到达该资源输入流
    因此我们可以将URL理解成URI的特例

  • 类URL中,
    提供了多个可以创建URL对象的构造器
    一旦获得了URL对象之后,
    可以调用下面的方法来访问该URL对应的资源

    • String getFile():获取此URL的资源名

    • String getHost():获取此URL的主机名

    • String getPath():获取此URL的路径部分

    • int getPort():获取此URL的端口号

    • String getProtocol():获取此URL的协议名称

    • String getQuery():获取此URL的查询字符串部分

    • URLConnection openConnection()
      返回一个URLConnection对象
      它表示到URL所引用的远程对象的连接

    • InputStream openStream()
      打开与此 URL 的连接
      并返回一个用于读取该 URL 资源InputStream

  • URL中,
    可以使用方法openConnection()返回一个URLConnection对象,
    该对象表示应用程序URL之间的通信链接

  • 应用程序可以通过URLConnection实例
    向此URL发送请求
    并读取URL引用的资源
    创建一个和URL连接
    并发送请求;

  • 读取此URL引用的资源的步骤:

    • (1)通过调用URL对象openConnection()方法来创建URLConnection对象。
    • (2)设置URLConnection参数普通请求属性
    • (3)如果只是发送 Get 方式请求,使用方法 connect
      建立和远程资源之间的实际连接即可;
      如果需要发送Post方式请求,
      需要获取URLConnection实例对应的输出流来发送请求参数
    • (4)远程资源变为可用
      程序可以访问远程资源的头字段或通过输入流
      读取远程资源的数据


  • 建立和远程资源的实际连接之前
    可以通过如下方法来设置请求头字段
    • setAllowUserInteraction:设置该URLConnection的allowUserInteraction请求头字段的值。

    • setDoInput:设置该URLConnection的doInput请求头字段的值。

    • setDoOutput:设置该URLConnection的doOutput请求头字段的值。

    • setIfModifiedSince:设置该URLConnection的ifModifiedSince请求头字段的值。

    • setUseCaches:设置该URLConnection的useCaches请求头字段的值。
      除此之外,还可以使用如下方法来设置或增加通用头字段。

    • setRequestProperty(String key, String value):设置该URLConnection的key请求头字段的值为value。

    • addRequestProperty(String key, String value):为该URLConnection的key请求头字段的增加value值,该方法并不会覆盖原请求头字段的值,而是将新值追加到原请求头字段中。


  • 当发现远程资源可以使用后,
    使用如下方法访问头字段和内容
    • Object getContent():获取该URLConnection的内容。
    • String getHeaderField(String name):获取指定响应头字段的值。
    • getInputStream():返回该URLConnection对应的输入流,用于获取URLConnection响应的内容。
    • getOutputStream():返回该URLConnection对应的输出流,用于向URLConnection发送请求参数。
    • getHeaderField:根据响应头字段来返回对应的值。
      因为在程序中需要经常访问某些头字段,所以Java为我们提供了如下方法来访问特定响应头字段的值。
    • getContentEncoding:获取content-encoding响应头字段的值。
    • getContentLength:获取content-length响应头字段的值。
    • getContentType:获取content-type响应头字段的值。
    • getDate():获取date响应头字段的值。
    • getExpiration():获取expires响应头字段的值。
    • getLastModified():获取last-modified响应头字段的值。
案例1:InetAddress的简单用法:
public class UseInetAddress {

    public UseInetAddress() {
        // TODO Auto-generated constructor stub
    }

    public static void main(String[] args)
            throws Exception
        {
            //根据主机名来获取对应的InetAddress实例
            InetAddress ip = InetAddress.getByName("www.sohu.com");
            
            //判断是否可达
            System.out.println("sohu是否可达:" + ip.isReachable(2000));
            
            //获取该InetAddress实例的IP字符串
            System.out.println(ip.getHostAddress());
            
            //根据原始IP地址来获取对应的InetAddress实例
            InetAddress local = InetAddress.getByAddress(new byte[]{127,0,0,1});            
            System.out.println("本机是否可达:" + local.isReachable(5000));
            
            //获取该InetAddress实例对应的全限定域名
            System.out.println(local.getCanonicalHostName());
        }

}

运行效果:
sohu是否可达:true
14.18.240.22
本机是否可达:true
127.0.0.1
凌川江雪阁是否可达:true
47.100.78.251
案例2:普通字符和MIME字符的转换

注意:

  • encode编码;decode解码/译码;

  • 编码和解码所用的编码标准(UTF-8/GBK)要一样!
    比方说,某一个普通String,
    encode用的标准是UTF-8,
    那编码出来的码在decode时,
    用的标准也要是UTF-8,方可译码,
    否则用GBK是无法解码的!

public class URLDecodery {

    public static void main(String[] args) 
            throws Exception
    {
        //将application/x-www-form-urlencoded MIME字符串
        //转换成普通字符串
        String keyWord = URLDecoder.decode(
          "%CE%CA%CA%C0%BC%E4%C7%E9%CE%AA%BA%CE%CE%EF", "GBK");
        System.out.println(keyWord);
        
        //将普通字符串转换成
        //application/x-www-form-urlencoded MIME字符串
        String urlStr = URLEncoder.encode("直教人生死相许" , "GBK");
        System.out.println(urlStr);
            
        keyWord = URLDecoder.decode("%E7%8B%97%E7%8B%97%E6%90%9E%E7%AC%91", "UTF-8");
        System.out.println(keyWord);
        
    }

}

运行结果:

问世间情为何物
%D6%B1%BD%CC%C8%CB%C9%FA%CB%C0%CF%E0%D0%ED
狗狗搞笑



HttpURLConnection详解

主要分四个功能实现:

  1. 从Internet获取网页
    需要先发送请求,
    然后将网页以流的形式读回来:

(1)创建一个URL对象:

    URL url = new URL("http://www.sohu.com");

(2)利用HttpURLConnection对象从网络中获取网页数据:

    HttpURLConnection conn = (HttpURLConnection) url.openConnection();

(3)设置连接超时:

    conn.setConnectTimeout(6* 1000);

(4)对响应码进行判断:

    if (conn.getResponseCode() != 200) throw new RuntimeException("请求url失败");

(5)得到网络返回的输入流:

    InputStream is = conn.getInputStream();

接着可以用bufferReader读取数据;

  1. 从Internet获取文件

(1)~(5)同上
(6)写出得到的文件流:

    outStream.write(buffer, 0, len);
  1. 向Internet发送请求参数

(1)将地址和参数存到byte数组中:

    byte[] data = params.toString().getBytes();

(2)创建URL对象:

URL realUrl = new URL(requestUrl);

(3)用HttpURLConnection对象向网络地址发送请求:

HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection();

(4)设置容许输出:

conn.setDoOutput(true);

(5)设置不使用缓存:

conn.setUseCaches(false);

(6)设置使用Post的方式发送:

conn.setRequestMethod("POST");

(7)设置维持长连接:

conn.setRequestProperty("Connection", "Keep-Alive");

(8)设置文件字符集:

conn.setRequestProperty("Charset", "UTF-8");

(9)设置文件长度:

conn.setRequestProperty("Content-Length", String.valueOf(data.length));

(10)设置文件类型:

conn.setRequestProperty("Content-Type","application/x-www-form-urlencoded");

(11)最后以流的方式输出。

在实现此功能时,
在发送Post请求时必须设置允许输出。
建议不要使用缓存,避免出现不应该出现的问题。
在开始就用HttpURLConnection对象的setRequestProperty()设置,
即生成HTML文件头。

当然,具体的还可以参考郭神的写法:

HttpURLConnection

OKHttp

  1. 向Internet发送XML数据
    可参考其他博客,这里不再赘述

注意
使用Android中的HttpUrlConnection时,有个地方需要注意一下,
就是如果程序中有跳转,并且跳转有外部域名的跳转,
那么非常容易超时并抛出域名无法解析的异常(Host Unresolved),
建议做跳转处理的时候不要使用它自带的方法设置成为自动跟随跳转,
最好自己做处理,以防出现异常。
这个问题模拟器上面看不出来,只有真机上面能看出来。

案例1:在Android手机屏幕中显示网络中的图片

  • 在日常应用中,
    我们经常不需要将网络中的图片 保存到手机中
    而只是在网络浏览一下即可。

    这里用 HttpURLConnection 打开连接,
    即可获取连接数据了。
    在本实例中,
    使用HttpURLConnection方法来连接获取网络数据
    获取的数据InputStream的方式保存内存中。

注意:
这里必须把网络请求这个耗时操作放在子线程
否则可能会阻塞主线程,造成报错!
(各种乱起八糟的错误,
IDE待会儿什么v4和v7组件库版本不匹配的错误都给你搬出来。。。)

主要思路是:
子线程中进行网络请求
具体的网络请求操作如上所述
(这里用的是 HttpURLConnection去连接远程资源
实际开发中可以尝试集成第三方库),
请求成功
把得到的资源在子线程编码(decodeStream())成bitmap
接着把bitmap转交到主线程进行UI更新即可完成!

  • 方式一:直接用runOnUiThread()bitmap转交到主线程进行UI更新
public class GetImageActivity extends AppCompatActivity {

    private Button mButton1;
    private TextView mTextView1;
    private ImageView mImageView1;

    String uriPic = "http://www.baidu.com/img/baidu_sylogo1.gif";

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_get_image);

        mButton1 = (Button) findViewById(R.id.myButton1);
        mTextView1 = (TextView) findViewById(R.id.myTextView1);
        mImageView1 = (ImageView) findViewById(R.id.myImageView1);

        mButton1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                /* 设置Bitmap在ImageView中 */
                getURLBitmap();

            }
        });
    }

    public void getURLBitmap()
    {
        new Thread(new Runnable() {
            @Override
            public void run() {
                URL imageUrl = null;
                Bitmap bitmap = null;

                try {
                    /* new URL对象将网址传入 */
                    imageUrl = new URL(uriPic);
                } catch (MalformedURLException e)
                {
                    e.printStackTrace();
                }

                try {
                    /* 取得连接 */
                    HttpURLConnection conn = (HttpURLConnection) (imageUrl != null ? imageUrl.openConnection() : null);
                    if (conn != null) {
                        conn.connect();
                    }

                    /* 取得返回的InputStream */
                    InputStream is = null;
                    if (conn != null) {
                        is = conn.getInputStream();
                    }

            /* !!!!!!!!!!!
            将InputStream变成Bitmap
            !!!!!!!!!!!!!*/
                    bitmap = BitmapFactory.decodeStream(is);

                    showImage(bitmap);

                    /* 关闭InputStream */
                    if (is != null) {
                        is.close();
                    }

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    private void showImage(final Bitmap bitmap) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mImageView1.setImageBitmap(bitmap);
                mTextView1.setText("");
            }
        });
    }
}

对应的xml布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:background="#FFFFFF"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/myTextView1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/app_name"/>

    <Button
        android:id="@+id/myButton1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="获取网络上的图片" />

    <ImageView
        android:id="@+id/myImageView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        tools:ignore="ContentDescription" />

</LinearLayout>

运行结果:
  • 方式二:使用handle消息机制bitmap转交到主线程进行UI更新
public class GetImageActivityTwo extends AppCompatActivity {

    ImageView iv_show;
    EditText et_path;
    String path;

    @SuppressLint("HandlerLeak")
    Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {

            Bitmap bitmap = (Bitmap) msg.obj;
            iv_show.setImageBitmap(bitmap);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_get_image_two);
        //寻找相应控件
        et_path = findViewById(R.id.et_path);
        iv_show = findViewById(R.id.iv_show);
    }

    public void click(View v){
        new Thread(){
            Message message = Message.obtain();
            @Override
            public void run() {

                File file = new File(getCacheDir(),"test.png");
                if(file.exists() && file.length()>=0){
                    //如果要缓存
//                    System.out.print("本地缓存");
//                    Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
//                    message.obj = bitmap;
//                    handler.sendMessage(message);
                }

                else{

                    path = et_path.getText().toString().trim();
                    try {
                        URL url = new URL(path);
                        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                        conn.setRequestMethod("GET");//设置请求方法
                        conn.setConnectTimeout(5000);//设置超时时间
                        InputStream in = conn.getInputStream();//拿到服务器返回的输出流
                        Bitmap bitmap = BitmapFactory.decodeStream(in);
                        message.obj = bitmap;
                        message.what = 2;
                        handler.sendMessage(message);//发送消息

                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }
}

xml布局:

<?xml version="1.0" encoding="utf-8"?>
<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=".UI.GetImageActivityTwo">

    <EditText
        android:id="@+id/et_path"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="http://www.baidu.com/img/baidu_sylogo1.gif"
        android:hint="请输入图片地址" />

    <Button
        android:onClick="click"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="查看" />

    <ImageView
        android:id="@+id/iv_show"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</LinearLayout>

运行结果:





  • 参考《精通Android网络开发》
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容