Android中的UI线程详解
(一)什么是UI线程?
Android在启动应用程序的时候,会为应用创建一个Main线程,这个线程负责将事件分派给相应用户接口的widget,其中包括drawing事件。除了事件分派之外,Main线程还负责应用与Android UI组件(例如, android.widget 和android.view 包)进行交互,因此Main 线程有时候也被称为UI线程。
(二)为什么会出现ANR(Application Not Response)?
Android不会为每个组件实例创建单独的线程。运行于同一进程的所有组件均在 UI 线程(Main)中实例化,并且对每个组件的系统调用均由该线程进行分派。这样一来,响应系统回调的方法(例如,报告用户操作的 onKeyDown() 或生命周期回调方法)始终在进程的 UI 线程中运行。举个例子来说,当用户触摸屏幕上的按钮时,应用的 UI 线程会将触摸事件分派给Widget,而Widget反过来又设置其按下状态,并将无效请求发布到事件队列中。UI 线程从队列中取消该请求并通知Widget进行重新绘制。
那么,如果应用在响应用户的操作的时候,在UI线程中执行了大量的耗时操作,比方说网络访问或数据库查询。这样做的后果势必会阻塞整个 UI。一旦UI线程被阻塞,将无法分派任何事件,包括绘图事件。
如果 UI 线程被阻塞超过几秒钟时间(目前大约是 5 秒钟),就会出现我们常说的ANR。
(三)解决在worker线程中访问UI资源的问题?
你可能会想,既然ANR是由于在UI线程中执行大量耗时的操作引起的,那么我们在主线程中新建一个worker线程问题不就解决了么?事实上,这种方式的确能够解决一些问题,但是对于UI来说就不是那么灵光了。不要忘了,Android UI toolkit并不是线程安全的,这也就意味着你不能在worker线程中来管理UI,也就是我们平常所说的不能在线程中更新UI。
至此,我们其实可以总结出两条适用于Android单线程模型的规则:
- 不要阻塞UI线程;
- 不要在UI线程之外访问Androd UI toolkit。
针对不能再线程中更新UI的问题,Android提供了三种在线程中更新UI的方式来解决这一问题:
- Activity.runOnUiThread(Runnable)
- View.post(Runnable)
- View.postDelayed(Runnable, long)
我们以View.post为例
public void onClick(View v) { new Thread(new Runnable() { public void run() { final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png"); //在此处来更新UI mImageView.post(new Runnable() { public void run() { mImageView.setImageBitmap(bitmap); } }); } }).start();}
但是随着代码复杂度的提升,再用此种方法来解决问题势必要启动更多的线程,从而使代码难以管理。要通过工作线程处理更复杂的交互,可以考虑在工作线程中使用 Handler 处理来自 UI 线程的消息。当然,最好的解决方案或许是扩展 AsyncTask 类,此类简化了与 UI 进行交互所需执行的工作线程任务。下面我们分别来看一下这两种方式的的具体使用形式:
使用Handler来解决线程中更新UI的问题
private BitMap bitmap;public static final int UPDATE_IMG=1;private Handler handler=new Handler(){ public void handleMessage(Message msg){ switch(msg.what){ case UPDATE_IMG: //在主线程中更新UI mImageView.setImageBitmap(bitmap); break; } }};public void onClick(View v) { new Thread(new Runnable() { public void run() { bitmap = loadImageFromNetwork("http://example.com/image.png"); Message message = new Message(); message.what=UPDATE_IMG; handler.sendMessage(message);//将消息发送至主线程 } }).start();}
使用AsyncTask来解决在线程中更新UI的问题
AsyncTask的出现就是为了简化在在线程中更新UI,有了这个类就不必显式的调用handler和Treads了。AsyncTask的使用非常简单主要分为四个主要的步骤:
- onPreExecute(), 这个方法会在 UI线程 中调用,因此这个方法也通常用于在Task开启之前准备Task需要的启动信息。例如展示一个progress bar。
- doInBackground(Params…), 其中Params是异步任务的参数。在onPreExecute()执行完成之后,这个方法会在后台线程中迅速被调用,主要用于进行后台一些比较耗时的计算。doInBackground(Params…)方法返回的计算结果会在最后一步中调用。此外,在这一步中还可以调用publishProgress(Progress…)来展示其中的过程单元,Progress…参数的值会转发到UI线程中的onProgressUpdate(Progress…) 方法。
- onProgressUpdate(Progress…), 在 publishProgress(Progress…)之后在UI线程调用. 即使此时后台的运算仍然在执行,这个方法可以用多种UI形式用户界面上展示后台进度。比如,用一个进度条和文本的形式来显示后台的下载进度。
- onPostExecute(Result), 其中Result是后台线程的运行结果。这个方法会在后台线程的计算工作完成之后再UI线程中调用。因此此方法也可进行相关的UI操作。比如,提醒后台的执行结果,关闭对话框。
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> { protected Long doInBackground(URL... urls) { int count = urls.length; long totalSize = 0; for (int i = 0; i < count; i++) { totalSize += Downloader.downloadFile(urls[i]); publishProgress((int) ((i / (float) count) * 100)); // Escape early if cancel() is called if (isCancelled()) break; } return totalSize; } protected void onProgressUpdate(Integer... progress) { setProgressPercent(progress[0]); } protected void onPostExecute(Long result) { showDialog("Downloaded " + result + " bytes"); } }
更多相关文章
- ubuntu 编译 Android(安卓)出现的若干错误及解决方法
- android连续调用setVisibility(View.VISIBLE)和setVisibility(Vi
- android用jdbc多线程操作sqlite小结
- android sqlite SQLiteDatabase 操作大全 不看后悔!必收藏!看后精
- Android多线程(三)HandlerThread源码原理解析
- [置顶] android ANR
- android 数据存储之 SharedPreference
- Android单元测试之Robolectric
- XposedHook:hook敏感函数