之前做过一个Android采集心电图数据的程序,那才是真正的多线程,之前写的小程序:比如下载个文件,从socket接受大一点的数据流然后在ui上更新进度,我都感觉这就叫做多线程了,其实这啥都不算,用个handler就解决问题了。而当你采集的时候情况就不同了,首先你要从硬件驱动中读取数据,另外数据需要缓存,缓存的同时还要将数据发送到远程服务器,另外还得将数据进行跳帧处理,以方便设备的屏幕上显示起来不那么卡,还要不断的更新ui界面上的绘图。起初的时候对这一连串的多线程真的是弄得手忙脚乱,后来才发现更新ui界面原来不只有handler一种方式,还有其他的,下面就总结如下:
1.利用Looper更新UI界面
这就是我们常用的handler方式
在Main主线程中新开一个线程,该线程负责数据的更新,然后将更新后的数据放在Message里面,然后通过Handler传递给相应的UI进行更新。
public class MainActivity extends Activity { private Button mButton; private TextView mText; @SuppressLint("HandlerLeak") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mButton = (Button)this.findViewById(R.id.button); mText = (TextView)this.findViewById(R.id.text); final Handler handler = new Handler(){ @Override public void handleMessage(Message msg){ super.handleMessage(msg); if(msg.what == 1){ mText.setText("更新后"); } } }; mText.setText("更新前"); final Thread thread = new Thread(new Runnable(){ @Override public void run() { Message message = new Message(); message.what = 1; handler.sendMessage(message); } }); mButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { thread.start(); } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } }
Handler的工作机制。
Handler的作用就是两个:在新启动的线程中发送消息和在主线程中获取和处理消息。像是上面例子中的Handler就包含了这两个方面:我们在新启动的线程thread中调用Handler的sendMessage()方法来发送消息。发送给谁呢?从代码中可以看到,就发送给主线程创建的Handler中的handleMessage()方法处理。这就是回调的方式:我们只要在创建Handler的时候覆写handleMessage()方法,然后在新启动的线程发送消息时自动调用该方法。
2.AsyncTask利用线程任务异步更新UI界面
AsyncTask的原理和Handler很接近,都是通过往主线程发送消息来更新主线程的UI,这种方式是异步的,所以就叫AsyncTask。使用AsyncTask的场合像是下载文件这种会严重阻塞主线程的任务就必须放在异步线程里面:
1 public class MainActivity extends Activity { 2 private Button mButton; 3 private ImageView mImageView; 4 private ProgressBar mProgressBar; 5 6 @SuppressLint("HandlerLeak") 7 @Override 8 protected void onCreate(Bundle savedInstanceState) { 9 super.onCreate(savedInstanceState); 10 setContentView(R.layout.activity_main); 11 12 mButton = (Button) this.findViewById(R.id.button); 13 mImageView = (ImageView) this.findViewById(R.id.image); 14 mProgressBar = (ProgressBar) this.findViewById(R.id.progressBar); 15 mButton.setOnClickListener(new OnClickListener() { 16 17 @Override 18 public void onClick(View v) { 19 AsyncTaskThread thread = new AsyncTaskThread(); 20 thread.execute("http://g.search2.alicdn.com/img/bao/uploaded/i4/" 21 + "i4/12701024275153897/T1dahpFapbXXXXXXXX_!!0-item_pic.jpg_210x210.jpg"); 22 } 23 }); 24 } 25 26 class AsyncTaskThread extends AsyncTask<String, Integer, Bitmap> { 27 28 @Override 29 protected Bitmap doInBackground(String... params) { 30 publishProgress(0); 31 HttpClient client = new DefaultHttpClient(); 32 publishProgress(30); 33 HttpGet get = new HttpGet(params[0]); 34 final Bitmap bitmap; 35 try { 36 HttpResponse response = client.execute(get); 37 bitmap = BitmapFactory.decodeStream(response.getEntity() 38 .getContent()); 39 } catch (Exception e) { 40 return null; 41 } 42 publishProgress(100); 43 return bitmap; 44 } 45 46 protected void onProgressUpdate(Integer... progress) { 47 mProgressBar.setProgress(progress[0]); 48 } 49 50 protected void onPostExecute(Bitmap result) { 51 if (result != null) { 52 Toast.makeText(MainActivity.this, "成功获取图片", Toast.LENGTH_LONG) 53 .show(); 54 mImageView.setImageBitmap(result); 55 } else { 56 Toast.makeText(MainActivity.this, "获取图片失败", Toast.LENGTH_LONG) 57 .show(); 58 } 59 } 60 61 protected void onPreExecute() { 62 mImageView.setImageBitmap(null); 63 mProgressBar.setProgress(0); 64 } 65 66 protected void onCancelled() { 67 mProgressBar.setProgress(0); 68 } 69 } 70 71 @Override 72 public boolean onCreateOptionsMenu(Menu menu) { 73 // Inflate the menu; this adds items to the action bar if it is present. 74 getMenuInflater().inflate(R.menu.main, menu); 75 return true; 76 } 77 }
AsyncTask是为了方便编写后台线程与UI线程交互的辅助类,它的内部实现是一个线程池,每个后台任务会提交到线程池中的线程执行,然后通过向UI线程的Handler传递消息的方式调用相应的回调方法实现UI界面的更新。
AsyncTask的构造方法有三个模板参数:Params(传递给后台任务的参数类型),Progress(后台计算执行过程中,进度单位(progress units)的类型,也就是后台程序已经执行了百分之几)和Result(后台执行返回的结果的类型)。
1 protected Bitmap doInBackground(String... params) { 2 publishProgress(0); 3 HttpClient client = new DefaultHttpClient(); 4 publishProgress(30); 5 HttpGet get = new HttpGet(params[0]); 6 final Bitmap bitmap; 7 try { 8 HttpResponse response = client.execute(get); 9 bitmap = BitmapFactory.decodeStream(response.getEntity() 10 .getContent()); 11 } catch (Exception e) { 12 return null; 13 } 14 publishProgress(100); 15 return bitmap; 16 }
params是一个可变参数列表,publishProgress()中的参数就是Progress,同样是一个可变参数列表,它用于向UI线程提交后台的进度,这里我们一开始设置为0,然后在30%的时候开始获取图片,一旦获取成功,就设置为100%。中间的代码用于下载和获取网上的图片资源
protected void onProgressUpdate(Integer... progress) { mProgressBar.setProgress(progress[0]); }
onProgressUpdate()方法用于更新进度条的进度。
1 protected void onPostExecute(Bitmap result) { 2 if (result != null) { 3 Toast.makeText(MainActivity.this, "成功获取图片", Toast.LENGTH_LONG).show(); 4 mImageView.setImageBitmap(result); 5 } else { 6 Toast.makeText(MainActivity.this, "获取图片失败", Toast.LENGTH_LONG).show(); 7 } 8 }
onPostExecute()方法用于处理Result的显示,也就是UI的更新。
protected void onPreExecute() { mImageView.setImageBitmap(null); mProgressBar.setProgress(0); } protected void onCancelled() { mProgressBar.setProgress(0); }
这两个方法主要用于在执行前和执行后清空图片和进度。
最后我们只需要调用AsyncTask的execute()方法并将Params参数传递进来进行。完整的流程是这样的:
UI线程执行onPreExecute()方法把ImageView的图片和ProgressBar的进度清空,然后后台线程执行doInBackground()方法,千万不要在这个方法里面更新UI,因为此时是在另一条线程上,在使用publishProgress()方法的时候会调用onProgressUpdate()方法更新进度条,最后返回result---Bitmap,当后台任务执行完成后,会调用onPostExecute()方法来更新ImageView。
AsyncTask本质上是一个静态的线程池,由它派生出来的子类可以实现不同的异步任务,但这些任务都是提交到该静态线程池中执行,执行的时候通过调用doInBackground()方法执行异步任务,期间会通过Handler将相关的信息发送到UI线程中,但神奇的是,并不是调用UI线程中的回调方法,而是AsyncTask本身就有一个Handler的子类InternalHandler会响应这些消息并调用AsyncTask中相应的回调方法。从上面的代码中我们也可以看到,UI的ProgressBar的更新是在AsyncTask的onProgressUpdate(),而ImageView是在onPostExecute()方法里。这是因为InternalHandler其实是在UI线程里面创建的,所以它能够调用相应的回调方法来更新UI。
AsyncTask就是专门用来处理后台任务的,而且它针对后台任务的五种状态提供了五个相应的回调接口,使得我们处理后台任务变得非常方便。
如果只是普通的UI更新操作,像是不断更新TextView这种动态的操作,可以使用Handler,但如果是涉及到后台操作,像是下载任务,然后根据后台任务的进展来更新UI,就得使用AsyncTask,但如果前者我们就使用AsyncTask,那真的是太大材小用了!!
3.利用Runnable更新UI界面
剩下的方法都是围绕着Runnable对象来更新UI。
一些组件本身就有提供方法来更新自己,像是ProgressBar本身就有一个post()方法,只要我们传进一个Runnable对象,就能更新它的进度。只要是继承自View的组件,都可以利用post()方法,而且我们还可以使用postDelay()方法来延迟执行该Runnable对象。android的这种做法就真的是让人称道了,至少我不用为了一个ProgressBar的进度更新就写出一大堆难懂的代码出来。
还有另一种利用Runnable的方式:Activity.runOnUiThread()方法。这名字实在是太直白了!!使用该方法需要新启一个线程:
1 class ProgressThread extends Thread { 2 @Override 3 public void run() { 4 super.run(); 5 while (mProgress <= 100) { 6 runOnUiThread(new Runnable() { 7 8 @Override 9 public void run() { 10 mProgressBar.setProgress(mProgress); 11 mProgress++; 12 } 13 }); 14 try { 15 Thread.sleep(100); 16 } catch (InterruptedException e) { 17 18 19 } 20 } 21 } 22 }
4.总结
1.如果只是单纯的想要更新UI而不涉及到多线程的话,使用View.post()就可以了;
2.需要另开线程处理数据以免阻塞UI线程,像是IO操作或者是循环,可以使用Activity.runOnUiThread();
3.如果需要传递状态值等信息,像是蓝牙编程中的socket连接,就需要利用状态值来提示连接状态以及做相应的处理,就需要使用Handler + Thread的方式;
4.如果是后台任务,像是下载任务等,就需要使用AsyncTask。