android 线程(AsyncTask+Handler)
android的线程是一个很重要的知识点!对用户的操作体验至关重要
一.什么是线程呢??
给个通俗易懂的一个解释吧!进程好比是一个游泳池,在游泳池当中有很多的水管,每一个水管就相当于是一个线程。而其中有一根主要的水管,其他一些小的水管是给主水管提供辅助帮助,来完成对
泳池进水,出水的任务的。在android线程当中有一个UI线程(主线程),我们自己可以创建一些子线程来帮助主线程进行UI更新等操作。
二.为什么要创建线程呢?
(1)是为了提高用户的操作体验避免产生ANR现象
如果所有的操作都放在一个主线程当中,那只有执行完这一步才能执行下一步操作,如果当前操作很耗时(网络等原因),那应用就会一直"卡"在这一步操作当中,用户只有一直等待。等待时间过长
给用户的体验月糟糕,应用的效益就会越低,以至于到最后没有人会用了。
(2)对于网络耗时操作的异步处理
主线程当中的事件处理不能太耗时,否则后续事件无法再5秒内得到响应,就会弹出ANR对话框。
通常在主线程当中会执行如下一些方法:
a.Activity的生命周期方法, OnCreate(),OnStart(), OnResume()等
b.事件处理方法,例如onClick()、onItemClick()等
通常Android基类中以on开头的方法是在Main线程被回调的
对于网络耗时操作就要开辟新的线程来进行处理,并与主线程进行交互。让用户不用等待结果的情况下,可以进行进行操作(如发送图片给服务器用户只管发送,什么时候服务器能接收到就是网络问题了)
三.子线程与主线程的通讯
(1)android提供了一个辅助类AsyncTask。可以将完成一些耗时的事件,并将事件处理后的结果返回给主线程。但AsyncTask只适合创建单一的线程,创建多线程会比较复杂而且会产生很多重复代码。
下面就对AsyncTask做个简要的讲解:
API是这样解释AsyncTask:This class allows to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers
一个异步任务的执行一般包括以下几个步骤:
1.execute(Params... params),执行一个异步任务,需要我们在代码中调用此方法,触发异步任务的执行。
2.onPreExecute(),在execute(Params... params)被调用后立即执行,一般用来在执行后台任务前对UI做一些标记。
3.doInBackground(Params... params),在onPreExecute()完成后立即执行,用于执行较为费时的操作,此方法将接收输入参数和返回计算结果。在执行过程中可以调用publishProgress(Progress... values)来更新进度信息。
4.onProgressUpdate(Progress... values),在调用publishProgress(Progress... values)时,此方法被执行,直接将进度信息更新到UI组件上。
5.onPostExecute(Result result),当后台操作结束时,此方法将会被调用,计算结果将做为参数传递到此方法中,直接将结果显示到UI组件上。
在使用的时候,有几点需要格外注意:
1.异步任务的实例必须在UI线程中创建。
2.execute(Params... params)方法必须在UI线程中调用。
3.不要手动调用onPreExecute(),doInBackground(Params... params),onProgressUpdate(Progress... values),onPostExecute(Result result)这几个方法。
4.不能在doInBackground(Params... params)中更改UI组件的信息。
5.一个任务实例只能执行一次,如果执行第二次将会抛出异常
意思是:这个类允许执行一些后台操作并将结果返回给UI线程而不用去创建线程和handler
AsyncTsk下载网络图片:
public class MainActivity extends Activity { private ImageView image; private ProgressDialog dialog; private Button button; private String imageUrl = "http://img2.3lian.com/img2009/08/3lian14/173.jpg"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); image = (ImageView) findViewById(R.id.imageID); button = (Button) findViewById(R.id.button1); dialog = new ProgressDialog(MainActivity.this); dialog.setTitle("图片下载中"); dialog.setMessage("请稍后"); button.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { new MyAsyncTask().execute(imageUrl); } }); } /** * 这里的String参数对应AsyncTask中的第一个参数 这里的Bitmap返回值对应AsyncTask的第三个参数 * 该方法并不运行在UI线程当中,主要用于异步操作,所有在该方法中不能对UI当中的空间进行设置和修改 * 但是可以调用publishProgress方法触发onProgressUpdate对UI进行操作 * */ class MyAsyncTask extends AsyncTask<String, Void, Bitmap> { /** * 完成耗时操作,注意这里不能直接操作UI */ @Override protected Bitmap doInBackground(String... params) { HttpClient httpClient = new DefaultHttpClient(); Bitmap bitmap = null; HttpGet httpGet = new HttpGet(params[0]); System.out.println(params[0]); try { HttpResponse response = httpClient.execute(httpGet); System.out.println(response.getStatusLine().getStatusCode()); if (response.getStatusLine().getStatusCode() == 200) { HttpEntity entity = response.getEntity(); // 将图片装换成字节流 byte[] data = EntityUtils.toByteArray(entity); bitmap = BitmapFactory .decodeByteArray(data, 0, data.length); } } catch (Exception e) { } return bitmap; } /** * 耗时操作执行前所执行的 */ @Override protected void onPreExecute() { super.onPreExecute(); dialog.show(); } /** * 耗时操作执行后所执行的,主要执行更新UI的操作 */ @Override protected void onPostExecute(Bitmap result) { super.onPostExecute(result); dialog.dismiss(); // 进行UI更新 image.setImageBitmap(result); } } }
对应的R.layout.activity_main.xml布局文件比较简单,这里就不给出来了
要注意是否打开了网络权限!!!
运行效果:
(2)利用Handler来创建线程下载图片
Handler当中涉及到的一些主要类:
Handler:就是在android当中发送message和接收message,通过接收到的message来完成与主线程的交互
Looper:负责管理MessageQueue和消息循环的
Message:就相当于是货车,装载各种信息
MessageQueue:消息队列,Message就是在MessageQueue里面进行先进先出的循环,保存有待线程处理的消息。
他们之间的关系是:在其他线程当中调用Handler.sendMessage()方法,将Message添加到主线程当中的MessageQueue当中去,
主线程的Looper从消息队列当中提取Handler发送过来的Message,回调Handler的handlerMessage()方法进行消息处理,来更新UI
下面用几个例子来说明Hnadler多线程的利用:
在利用Handler创建多线程之前,来说明一下Runnable:Handler利用Runnable不一定就开辟了新线程。
Runnable是一个接口,不是一个线程,但开辟新线程的时候要继承这个接口。但当我们用的匿名内部内类是在主线程当中的,那new Runnable就没有开辟新线程
下面这个例子就没有开新线程:
1)Handler+Runnable
这个例子是没有开新的线程而是在主线程当中下载的图片,运行程序后会发现,三张图片要同时被加载完成后才一起显示,这就造成了主界面的线程堵塞。所以这种模式不建议使用
主要代码:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final String url1 = "http://img2.3lian.com/img2009/08/3lian14/173.jpg"; final String url2 = "http://down.tutu001.com/d/file/20100528/5cbd29cade6126e6c35112d18e_560.jpg"; final String url3 = "http://down.tutu001.com/d/file/20100528/93ed3168a11ad23a6ee676ecc9_560.jpg"; (MainActivity.this.findViewById(R.id.button1)) .setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { getImage(url1, R.id.imageView1); getImage(url2, R.id.imageView2); getImage(url3, R.id.imageView3); } }); } Handler handler = new Handler(); public void getImage(final String url, final int ImageID) { handler.post(new Runnable() { @Override public void run() { HttpClient httpClient = new DefaultHttpClient(); HttpGet httpGet = new HttpGet(url); try { HttpResponse response = httpClient.execute(httpGet); if (response.getStatusLine().getStatusCode() == 200) { HttpEntity entity = response.getEntity(); byte[] data = EntityUtils.toByteArray(entity); Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); ImageView image11 = (ImageView) MainActivity.this .findViewById(ImageID); image11.setImageBitmap(bitmap); } } catch (Exception e) { Toast.makeText(MainActivity.this, "网络问题", Toast.LENGTH_LONG) .show(); System.out.println("网络问题"); } } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } }
**运行了这段代码后,会发现三张图片是一起出现的
2)Handler+Thread+Message 实现多线程的异步加载图片
public class MainActivity extends Activity { final String url1 = "http://img2.3lian.com/img2009/08/3lian14/173.jpg"; final String url2 = "http://down.tutu001.com/d/file/20100528/5cbd29cade6126e6c35112d18e_560.jpg"; final String url3 = "http://down.tutu001.com/d/file/20100528/93ed3168a11ad23a6ee676ecc9_560.jpg"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ((Button)MainActivity.this.findViewById(R.id.button1)).setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { getImage(url1, R.id.imageView1); getImage(url2, R.id.imageView2); getImage(url3, R.id.imageView3); } }); } // 进入了主线程的Looper进行UI更改 Handler myHandler = new Handler() { @Override public void handleMessage(Message msg) { if (msg != null) { ImageView image = (ImageView) findViewById(msg.what); image.setImageBitmap((Bitmap) msg.obj); } }; }; // 异步加载图片方法 public void getImage(final String url, final int imageID) { // 开线程获取网络图片 new Thread() { @Override public void run() { HttpClient httpClient = new DefaultHttpClient(); HttpGet httpget = new HttpGet(url); try { HttpResponse response = httpClient.execute(httpget); if (response.getStatusLine().getStatusCode() == 200) { HttpEntity entity = response.getEntity(); byte[] data = EntityUtils.toByteArray(entity); Bitmap bm = BitmapFactory.decodeByteArray(data, 0, data.length); Message message = myHandler.obtainMessage(); message.obj = bm; message.what = imageID; myHandler.sendMessage(message); // ImageView imageview = (ImageView) // findViewById(imageID); // imageview.setImageBitmap(bm); } } catch (Exception e) { } } }.start(); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } }
调用getImage()方法,开辟属于自己的线程,将得到的图片信息保存在Message当中,发送给Handler进行UI 的更新。之间发送给Handler进行处理时不分先后的。
这段代码的效果就是三个ImageView加载自己的图片,相互之间没有联系的。这就形成了异步。