用HttpURLConnection即时访问网络图片的小Demo

今天写了一个用HttpURLConnection即时访问网络图片的小Demo,是拿了一本比较久的参考书,直接照着代码敲的,但是出现了各种问题。
书本的源代码如下:

String picStr="http://www.shixiu.net/d/file/p/2bc22002a6a61a7c5694e7e641bf1e6e.jpg";
/*MainActivity onCreate()*/
mImageView= (ImageView) findViewById(R.id.mImageView); 
Button button= (Button) findViewById(R.id.button1);
button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mImageView.setImageBitmap(getURLBitmap());

            }
        });

在布局文件创建一个开关Button和ImageView,并创建了Button的点击事件。并在主线程(注意是在主线程中)中写了从网上下载图片的getURLBitmap()函数。

public Bitmap getURLBitmap(){
 URL imgUrl=null;

        try {
            /*new URL对象将网址传入*/
            imgUrl=new URL(picStr);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        try {
            /*取得联机*/
            HttpURLConnection conn= (HttpURLConnection) imgUrl.openConnection();
            conn.connect();
            /*取得回传的InputStream*/
            InputStream is=conn.getInputStream();
            /*将InputStream转化为Bitmap*/
             final Bitmap bitmap= BitmapFactory.decodeStream(is);
             /*关闭InputStream*/
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bitmap;

}

一切都是照着书上敲的,理论上是没什么问题,但是运行的时候程序出现错误。android.os.NetworkOnMainThreadException 出现这样的异常。在网上一查知道,在Android4.0中访问网络不能在主程序中进行!!!!!!这就是出现这样的错误。

那怎么解决呢,即把网络访问的程序写成一个线程,在主线程中访问这个线程即可。

以下为创建的网络访问的线程源码:

package com.example.zhang.httpurlconnectiondemo;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.widget.ImageView;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

/**
 * Created by zhang on 2015/12/25.
 */
public class HttpThread extends Thread{
    private ImageView mImageView;
    String picStr="http://www.shixiu.net/d/file/p/2bc22002a6a61a7c5694e7e641bf1e6e.jpg";

    public HttpThread(ImageView mImageView){
        this.mImageView=mImageView;
    }

    @Override
    public void run() {
        URL imgUrl=null;

        try {
            /*new URL对象将网址传入*/
            imgUrl=new URL(picStr);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        try {
            /*取得联机*/
            HttpURLConnection conn= (HttpURLConnection) imgUrl.openConnection();
            conn.connect();
            /*取得回传的InputStream*/
            InputStream is=conn.getInputStream();
            /*将InputStream转化为Bitmap*/
            final Bitmap bitmap= BitmapFactory.decodeStream(is);
            //在此处做图片的展示,即更新视图
            mImageView.setImageBitmap(bitmap);

            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

以上为网络访问的线程,在MainActivity中Button的点击事件中

button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new HttpThread(mImageView).start();

            }
        });

我在想这样应该完美解决了吧,运行了一下,等待奇迹发生。但是并没有。出现了Only the original thread that created a view hierarchy can touch its views的异常,在博客上查了一下,原因在于我在子线程中更新了视图。分析:android系统中的视图组件并不是线程安全的,如果要更新视图,必须要在主线程中更新,不能在子线程中执行更新操作。

要想解决此方法,即在子线程中写代码,通知主线程进行更新的操作,这时候我们需要Handler对象。以下为网络访问线程的代码,其中加入了Handler对象,并且用Handler操作提醒主线程。

package com.example.zhang.httpurlconnectiondemo;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.widget.ImageView;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

/**
 * Created by zhang on 2015/12/25.
 */
public class HttpThread extends Thread{
    private ImageView mImageView;
    private Handler handler;
    String picStr="http://www.shixiu.net/d/file/p/2bc22002a6a61a7c5694e7e641bf1e6e.jpg";
//创建构造函数
    public HttpThread(ImageView mImageView,Handler handler){
        this.mImageView=mImageView;
        this.handler=handler;
    }

    @Override
    public void run() {
        URL imgUrl=null;

        try {
            /*new URL对象将网址传入*/
            imgUrl=new URL(picStr);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        try {
            /*取得联机*/
            HttpURLConnection conn= (HttpURLConnection) imgUrl.openConnection();
            conn.connect();
            /*取得回传的InputStream*/
            InputStream is=conn.getInputStream();
            /*将InputStream转化为Bitmap*/
            final Bitmap bitmap= BitmapFactory.decodeStream(is);
           //用Handler post()方法通知主线程
            handler.post(new Runnable() {
                @Override
                public void run() {
                    mImageView.setImageBitmap(bitmap);
                }
            });

            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }



}

在主线程中也创建Handler对象,来接收提示的消息

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        mImageView= (ImageView) findViewById(R.id.mImageView);
        final Handler handler=new Handler();


        Button button= (Button) findViewById(R.id.button1);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new HttpThread(mImageView,handler).start();

            }
        });

此时跑一下代码就成功了
这里写图片描述

现在来解释一下上面出现的问题。此处大部分出自此博客:http://blog.csdn.net/woaieillen/article/details/6837887

通过上面的方式,我们基本解决了线程安全的问题,把复杂的任务交给子线程去做,然后子线程通过handler对象告知主线程,由主线程更新视图,这个过程消息机制起到了重要的作用。

下面我们来熟悉一下android中的消息机制。
熟悉Windows的朋友也知道Windows程序是消息驱动的,并且有全局的消息循环系统。Google参考了Windows的消息循环机制,也在Android中实现了消息循环机制。Android通过Looper、Handler来实现消息循环机制。
Android的消息循环是针对线程的,每个线程都有自己的消息队列和消息循环。
Android系统中的Looper负责管理线程的消息队列和消息循环。通过Looper.myLooper()得到当前线程的Looper对象,通过Looper.getMainLooper()得到当前进程的主线程的Looper对象.。

前面提到Andriod的消息队列和消息循环都是针对具体线程的,一个线程可以存在一个消息队列和消息循环,特定线程的消息只能分发给本线程,不能跨线程和跨进程通讯。但是在创建过程中是没有消息队列和消息循环的,如果想让工作线程有消息队列和消息循环,就需要在线程中先调用Looper.prepare()来创建消息队列,然后调用Looper.loop进入消息循环。下面是创建的工作线程:

class WorkThread extends Thread{
    public Handler mHandler;

    public void run(){
         Looper.prepare();//创建消息队列

         mHandler=new Handler(){
            public void handleMassage(Massage msg){
               //处理收到的消息
            }
         };
         Looper.loop();
    }
}

这样一来,我们创建的工作线程就有消息的处理机制了。
那么,为什么我们上面写的Demo中没有看到Looper.prepare()和Looper.loop()的调用呢?原因在于,我们的Mainactivity是一个UI线程,运行在主线程中,Android系统会在启动时创建一个消息队列和消息循环。
前面提到最多的就是消息队列(MessageQueue)和消息循环(Looper),但是我们看到每个地方都有Hanlder的存在,他是做什么的呢?Handler的作用是把消息加入到特定的Looper管理的消息队列中,并分发和处理该消息队列中的消息。构造Handler的时候可以指定一个Looper对象,如果不指定,则利用当前线程的Looper对象创建。下面是Handler的两个构造方法:

/**  
     * Default constructor associates this handler with the queue for the  
     * current thread.  
     *  
     * If there isn't one, this handler won't be able to receive messages.  
     */  
    public Handler() {  
        if (FIND_POTENTIAL_LEAKS) {  
            final Class<? extends Handler> klass = getClass();  
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&  
                    (klass.getModifiers() & Modifier.STATIC) == 0) {  
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +  
                    klass.getCanonicalName());  
            }  
        }  

        mLooper = Looper.myLooper();  
        if (mLooper == null) {  
            throw new RuntimeException(  
                "Can't create handler inside thread that has not called Looper.prepare()");  
        }  
        mQueue = mLooper.mQueue;  
        mCallback = null;  
    }  

/**  
     * Use the provided queue instead of the default one.  
     */  
    public Handler(Looper looper) {  
        mLooper = looper;  
        mQueue = looper.mQueue;  
        mCallback = null;  
    }  

下面是消息机制中的几个重要的成员关系图:
这里写图片描述
这里写图片描述

一个Activity中可以创建出多个工作线程,如果这些线程把他们的消息放入Activity主线程的消息队列中,那么消息就会在主线程中处理了。因为主线程一般负责视图组件的更新操作,对于不是线程安全的视图来说,这种方式能够很好的实现视图的更新。
那么子线程是如何把消息放入主线程的消息队列当中呢?只要Handler对象以主线程的Looper创建,那么调用Handler的sendMessage方法,系统就会把消息放入主线程的消息队列,并且将会在调用handleMessage方法时处理主线程队列的消息

对于子线程访问主线程的Handler对象,你可能会问,多个子线程访问主线程的Handler对象,发送消息和处理消息的过程中会不会出现数据不一致呢?答案是Handler对象是不会出现问题,因为Handler的对象管理的Looper对象是线程安全的,不管是添加消息到消息队列还是从消息队列中读取消息是同步保护的,所以不会出现数据不一致现象。

深入理解Android消息处理机制对于应用程序开发非常重要,也可以让我们对线程同步有更加深刻的认识。

上面对于消息处理机制主要来自这篇博客:http://blog.csdn.net/woaieillen/article/details/6837887

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值