Android异步下载网络图片

项目中有时候需要获取网络上的图片,并下载下来到手机客户端显示。怎么做呢?

实现思路是:

 1:在UI线程中启动一个线程,让这个线程去下载图片。

 2:图片完成下载后发送一个消息去通知UI线程

 2:UI线程获取到消息后,更新UI。

 这里的UI线程就是主线程。

 这两个步骤涉及到一些知识点,即是:ProgressDialog,Handler,Thread/Runnable,URL,HttpURLConnection等等一系列东东的使用。

 现在让我们开始来实现这个功能吧!

 第一步:新建项目。

 第二步:设计好UI,如下所示

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

    <Button
        android:id="@+id/btnFirst"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="异步下载方式一" >
    </Button>

    <Button
        android:id="@+id/btnSecond"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="异步下载方式二" >
    </Button>

    <FrameLayout
        android:id="@+id/frameLayout"
        android:layout_width="fill_parent"
        android:layout_height="match_parent" >

        <ImageView
            android:id="@+id/image"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:padding="2dp"
            android:scaleType="centerInside" >
        </ImageView>

        <ProgressBar
            android:id="@+id/progress"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center" >
        </ProgressBar>
    </FrameLayout>

</LinearLayout>


第三步:获取UI相应View组件,并添加事件监听。

public class DownLoaderActivity extends Activity implements OnClickListener {
	private static final String params = "http://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/Hukou_Waterfall.jpg/800px-Hukou_Waterfall.jpg";
	private Button btnFirst, btnSecond;
	private ProgressBar progress;
	private FrameLayout frameLayout;
	private Bitmap bitmap = null;
	ProgressDialog dialog = null;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		btnFirst = (Button) this.findViewById(R.id.btnFirst);
		btnSecond = (Button) this.findViewById(R.id.btnSecond);
		progress = (ProgressBar) this.findViewById(R.id.progress);
		progress.setVisibility(View.GONE);
		frameLayout = (FrameLayout) this.findViewById(R.id.frameLayout);
		btnFirst.setOnClickListener(this);
		btnSecond.setOnClickListener(this);
	}
 


 

第四步:在监听事件中处理我们的逻辑,即是下载服务器端图片数据。

这里我们需要讲解一下了。

通常的我们把一些耗时的工作用另外一个线程来操作,比如,下载上传图片,读取大批量XML数据,读取大批量sqlite数据信息。为什么呢?答案大家都明白,用户体验问题。

在这里,首先我构造一个进度条对话框,用来显示下载进度,然后开辟一个线程去下载图片数据,下载数据完毕后,通知主UI线程去更新显示我们的图片。

Handler是沟通Activity 与Thread/runnable的桥梁。而Handler是运行在主UI线程中的,它与子线程可以通过Message对象来传递数据。具体代码如下:

/** 这里重写handleMessage方法,接受到子线程数据后更新UI **/
	private Handler handler = new Handler() {
		@Override
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case 1:
				// 关闭
				ImageView view = (ImageView) frameLayout
						.findViewById(R.id.image);
				view.setImageBitmap(bitmap);
				dialog.dismiss();
				break;
			}
		}
	};


我们在这里弹出进度对话框,使用HTTP协议来获取数据。

 

// 前台ui线程在显示ProgressDialog,
	// 后台线程在下载数据,数据下载完毕,关闭进度框
	@Override
	public void onClick(View view) {
		switch (view.getId()) {
		case R.id.btnFirst:
			dialog = ProgressDialog.show(this, "", "下载数据,请稍等 …", true, true);
			// 启动一个后台线程
			handler.post(new Runnable() {
				@Override
				public void run() {
					// 这里下载数据
					try {
						URL url = new URL(params);
						HttpURLConnection conn = (HttpURLConnection) url
								.openConnection();
						conn.setDoInput(true);
						conn.connect();
						InputStream inputStream = conn.getInputStream();
						bitmap = BitmapFactory.decodeStream(inputStream);
						Message msg = new Message();
						msg.what = 1;
						handler.sendMessage(msg);
					} catch (MalformedURLException e1) {
						e1.printStackTrace();
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			});
			break;
		}
	}


 

如此以来,你会发现很好的完成了我们的下载目标了,你可以把它应用到其他方面去,举一反三。

运行截图如下:

 

 

我们使用Handler、Thread/Runnable 、URL、HttpURLConnection等等来进行异步下载网络图片。

但是采用这种方式有一些缺点,如下:

  • 线程的开销较大,如果每个任务都要创建一个线程,那么程序的效率要低很多。
  • 线程无法管理,匿名线程创建并启动后就不受程序的控制了,如果有很多个请求发送,那么就会启动非常多的线程,系统将不堪重负。
  • 另外,前面已经看到,在新线程中更新UI还必须要引入handler,这让代码看上去非常臃肿。

     那么有没有比较更好好的实现方式呢?这个可以有!它就是AsyncTask

     AsyncTask的特点是任务在主UI线程之外运行,而回调方法是在主UI线程中,这就有效地避免了使用Handler带来的麻烦。 

     AsyncTask定义了三种泛型类型 Params,Progress和Result。
    • Params 启动任务执行的输入参数。
    • Progress 后台任务执行的百分比。
    • Result 后台执行任务返回的结果。

      当然,使用它还必须覆盖它的一些抽象方法方法

     doInBackground(Params...)        执行任务

     onPostExecute(Result)             返回任务执行的结果,通常更新UI

     onProgressUpdate (Progress... values) 进度更新

     注意:红色的是必须实现的。

     第一步:设计好UI,与上节一样

     第二步:也与上节一样。

     第三步:主要是实例化AsyncTask,并执行execute(Params)

     我们必须继承AsyncTask,并覆盖它的一些方法,我们这里主要是要获取网络图片,并保存为Bitmap,以便UI根据Bitmap来更新的。

     那么需要为AsyncTask设置返回的类型参数为String,Integer,Bitmap 类定义如下:

    public class MyASyncTask extends AsyncTask<String, Integer, Bitmap> {


    doInBackground(Params...)  方法中 ,接受String ....params,返回我们需要的Bitmap.当然我们这里是获取图片Bitmap所以要返回Bitmap

      如果你返回的需要是String或者其他复杂类型时候,需要修改类的定义参数类型为你需要返回的类型,当然接受参数也是根据你的请求需要改变。

     

    @Override
    	protected Bitmap doInBackground(String params) {
    		Bitmap bitmap = null;
    		try {
    			URL url = new URL(params[0]);
    			HttpURLConnection con = (HttpURLConnection) url.openConnection();
    			con.setDoInput(true);
    			con.connect();
    			InputStream inputStream = con.getInputStream();
    			bitmap = BitmapFactory.decodeStream(inputStream);
    			inputStream.close();
    		} catch (MalformedURLException e) {
    			e.printStackTrace();
    		} catch (IOException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		return bitmap;
    	}


     

     onPostExecute(Result) 中是请求获得结果后更新UI部分。你会看到他的参数就是我们类中的类型参数。代码如下:

    //执行获得图片数据后,更新UI:显示图片,隐藏进度条
    	@Override 
    	protected void onPostExecute(Bitmap Result){
    		ImageView imgView=(ImageView)this.viewGroup.getChildAt(0); 
    		imgView.setImageBitmap(Result);   
    		ProgressBar bar=(ProgressBar)this.viewGroup.getChildAt(1);  
    		bar.setVisibility(View.GONE);
    	}


     

    然后怎么用呢?在UI线程中执行吧:

    MyASyncTask yncTask=new MyASyncTask(this,frameLayout);
       yncTask.execute(params);

    运行结果与上节大同小异

     

     

     

    在第一节中,我们使用Handler、Thread/Runnable 、URL、HttpURLConnection等等来进行异步下载网络图片。然后第二节中换了AsyncTask方式。那么这一节我们再应用其他方式,这个方式与第一节有点雷同,感觉走了一圈回到原地,只是风景更加美丽。人生很多时候也是如此,从NULL中来再回到NULL中。

    那么它是谁呢?java.util.concurrent

    java.util.concurrent 是在并发编程中很常用的实用工具类。

    ExecutorService类:具有服务生命周期的Executors。

    Executors 类:执行器,将为你管理Thread 对象。

    我们知道这些是用来处理并发任务的,当然我们Demo只是请求一张图片而已,并不能体现并发,但是假设我们有一个ListView,里面每一项都需要一张网络图片显示呢?那么并发性就可以体现出来了:多个线程并发从网络下载图片。当然这个版本不会使用listView显示多个项图片,以后做个版本吧!研究下。

     思路是这样的:

    1:动态的创建N个线程,防在线程池中。

    2:系统从线程池中取出一个线程投入执行,线程池中若没有线程可用,其他任务只有先等待了,直到有新线程释放,才调用。

    如下有几个方法可以动态的指定多少个线程。

    newFixedThreadPool(int nThreads)  指定线程个数

    newCachedThreadPool()                 系统为每个任务都建立一个线程

    下面可以实现并发下载数据

     

    case R.id.btnThress:    
    	progress.setVisibility(View.VISIBLE);  
    	final Handler newhandler=new Handler();   
    	executorService.submit(new Runnable(){ 
    		@Override             
    		public void run() {  
    			try { 
    				URL newurl = new URL(params); 
    				HttpURLConnection conn = (HttpURLConnection)newurl.openConnection();      
    				conn.setDoInput(true); 
    				conn.connect();       
    				InputStream inputStream=conn.getInputStream();     
    				bitmap = BitmapFactory.decodeStream(inputStream); 
    				newhandler.post(new Runnable(){   
    					@Override                 
    					public void run() {    
    						ImageView view=(ImageView)frameLayout.findViewById(R.id.image); 
    						view.setImageBitmap(bitmap);  
    						}      
                     });     
    				} catch (MalformedURLException e) { 
    					e.printStackTrace(); 
    					} catch (IOException e) {  
    						e.printStackTrace(); 
    						}             
    					}          
    		});      
    	break;


    运行结果为:

     

     

     

     

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值