我想关于这个话题已经有不少前辈讨论过了。今天算是一次学习总结吧。android
在android的设计思想中,为了确保用户顺滑的操做体验。一 些耗时的任务不可以在UI线程中运行,像访问网络就属于这类任务。所以咱们必需要从新开启一个后台线程运行这些任务。然而,每每这些任务最终又会直接或者 间接的须要访问和控制UI控件。例如访问网络获取数据,而后须要将这些数据处理显示出来。就出现了上面所说的状况。本来这是在正常不过的现象了,可是 android规定除了UI线程外,其余线程都不能够对那些UI控件访问和操控。为了解决这个问题,因而就引出了咱们今天的话题。Android中后台线 程如何与UI线程交互。数组
据我所知android提供了如下几种方法,用于实现后台线程与UI线程的交互。网络
一、handleride
二、Activity.runOnUIThread(Runnable)函数
三、View.Post(Runnable)工具
四、View.PostDelayed(Runnabe,long)oop
五、AsyncTaskpost
方法一:handler学习
handler是android中专门用来在线程之间传递信息类的工具。this
要讲明handler的用法很是简单,可是我在这里会少量深刻的讲一下handler的运行机制。
为了可以让handler在线程间传递消息,咱们还须要用到几个类。他们是looper,messageQueue,message。
这 里说的looper可不是前段时间的好莱坞大片环形使者,他的主要功能是为特定单一线程运行一个消息环。一个线程对应一个looper。一样一个 looper对应一个线程。这就是所谓的特定单一。通常状况下,在一个线程建立时他自己是不会生产他特定单一的looper的(主线程是个特例)。所以我 们须要手动的把一个looper与线程相关联。其方法只需在须要关联的looper的线程中调用Looper.prepare。以后咱们再调用 Looper.loop启动looper。
说了这么多looper的事情,到底这个looper有什么用哪。其实以前咱们已经说到了,他是 为线程运行一个消息环。具体的说,在咱们将特定单一looper与线程关联的时候,looper会同时生产一个messageQueue。他是一个消息队 列,looper会不停的从messageQuee中取出消息,也就是message。而后线程就会根据message中的内容进行相应的操做。
那 么messageQueue中的message是从哪里来的哪?那就要提到handler了。在咱们建立handler的时候,咱们须要与特定的 looper绑定。这样经过handler咱们就能够把message传递给特定的looper,继而传递给特定的线程。在这里,looper和 handler并不是一一对应的。一个looper能够对应多个handler,而一个handler只能对应一个looper(忽然想起了一夫多妻制,呵 呵)。这里补充一下,handler和looper的绑定,是在构建handler的时候实现的,具体查询handler的构造函数。
在我 们建立handler并与相应looper绑定以后,咱们就能够传递message了。咱们只须要调用handler的sendMessage函数,将 message做为参数传递给相应线程。以后这个message就会被塞进looper的messageQueue。而后再被looper取出来交给线程 处理。
这 里要补充说一下message,虽然咱们能够本身建立一个新的message,可是更加推荐的是调用handler的obtainMessage方法来获 取一个message。这个方法的做用是从系统的消息池中取出一个message,这样就能够避免message建立和销毁带来的资源浪费了(这也就是算 得上重复利用的绿色之举了吧)。
忽然发现有一点很重要的地方没有讲到,那就是线程从looper收到message以后他是如何作出响应的 嘞。其实原来线程所须要作出何种响应须要咱们在咱们自定义的handler类中的handleMessage重构方法中编写。以后才是以前说的建立 handler并绑定looper。
好吧说的可能哟点乱,总结一下利用handler传递信息的方法。
假设A线程要传递信息给B线程,咱们须要作的就是
一、在B线程中调用Looper.prepare和Looper.loop。(主线程不须要)
二、 编写Handler类,重写其中的handleMessage方法。
三、建立Handler类的实例,并绑定looper
四、调用handler的sentMessage方法发送消息。
到这里,咱们想handler的运行机制我应该是阐述的差很少了吧,最后再附上一段代码,供你们参考。
1 public class MyHandlerActivity extends Activity { 2 TextView textView; 3 MyHandler myHandler; 4 5 protected void onCreate(Bundle savedInstanceState) { 6 super.onCreate(savedInstanceState); 7 setContentView(R.layout.handlertest); 8 9 //实现建立handler并与looper绑定。这里没有涉及looper与 //线程的关联是由于主线程在建立之初就已有looper 10 myHandler=MyHandler(MyHandlerActivitythis.getMainLooper()); 11 textView = (textView) findViewById(R.id.textView); 12 13 MyThread m = new MyThread(); 14 new Thread(m).start(); 15 } 16 17 18 class MyHandler extends Handler { 19 public MyHandler() { 20 } 21 22 public MyHandler(Looper L) { 23 super(L); 24 } 25 26 // 必须重写这个方法,用于处理message 27 @Override 28 public void handleMessage(Message msg) { 29 // 这里用于更新UI 30 Bundle b = msg.getData(); 31 String color = b.getString("color"); 32 MyHandlerActivity.this.textView.setText(color); 33 } 34 } 35 36 class MyThread implements Runnable { 37 public void run() { 38 //从消息池中取出一个message 39 Message msg = myHandler.obtainMessage(); 40 //Bundle是message中的数据 41 Bundle b = new Bundle(); 42 b.putString("color", "个人"); 43 msg.setData(b); 44 //传递数据 45 myHandler.sendMessage(msg); // 向Handler发送消息,更新UI 46 } 47 }
方法二:Activity.runOnUIThread(Runnable)
这个方法至关简单,咱们要作的只是如下几步
一、编写后台线程,这回你能够直接调用UI控件
二、建立后台线程的实例
三、调用UI线程对应的Activity的runOnUIThread方法,将后台线程实例做为参数传入其中。
注意:无需调用后台线程的start方法
方法三:View.Post(Runnable)
该方法和方法二基本相同,只是在后台线程中能操控的UI控件被限制了,只能是指定的UI控件View。方法以下
一、编写后台线程,这回你能够直接调用UI控件,可是该UI控件只能是View
二、建立后台线程的实例
三、调用UI控件View的post方法,将后台线程实例做为参数传入其中。
方法四:View.PostDelayed(Runnabe,long)
该方法是方法三的补充,long参数用于制定多少时间后运行后台进程
方法五:AsyncTask
AsyncTask是一个专门用来处理后台进程与UI线程的工具。经过AsyncTask,咱们能够很是方便的进行后台线程和UI线程之间的交流。
那么AsyncTask是如何工做的哪。
AsyncTask拥有3个重要参数
一、Params
二、Progress
三、Result
Params是后台线程所需的参数。在后台线程进行做业的时候,他须要外界为其提供必要的参数,就好像是一个用于下载图片的后台进程,他须要的参数就是图片的下载地址。
Progress是后台线程处理做业的进度。依旧上面的例子说,就是下载图片这个任务完成了多少,是20%仍是60%。这个数字是由Progress提供。
Result是后台线程运行的结果,也就是须要提交给UI线程的信息。按照上面的例子来讲,就是下载完成的图片。
AsyncTask还拥有4个重要的回调方法。
一、onPreExecute
二、doInBackground
三、onProgressUpdate
四、onPostExecute
onPreExecute运行在UI线程,主要目的是为后台线程的运行作准备。当他运行完成后,他会调用doInBackground方法。
doInBackground 运行在后台线程,他用来负责运行任务。他拥有参数Params,而且返回Result。在后台线程的运行当中,为了可以更新做业完成的进度,须要在 doInbackground方法中调用PublishProgress方法。该方法拥有参数Progress。经过该方法能够更新Progress的数 据。而后当调用完PublishProgress方法,他会调用onProgressUpdate方法用于更新进度。
onProgressUpdate运行在UI线程,主要目的是用来更新UI线程中显示进度的UI控件。他拥有Progress参数。在doInBackground中调用PublishProgress以后,就会自动调onProgressUpdate方法
onPostExecute运行在UI线程,当doInBackground方法运行完后,他会调用onPostExecute方法,并传入Result。在onPostExecute方法中,就能够将Result更新到UI控件上。
明白了上面的3个参数和4个方法,你要作的就是
一、编写一个继承AsyncTask的类,并声明3个参数的类型,编写4个回调方法的内容。
二、而后在UI线程中建立该类(必须在UI线程中建立)。
三、最后调用AsyncTask的execute方法,传入Parmas参数(一样必须在UI线程中调用)。
这样就大功告成了。
另外值得注意的2点就是,千万不要直接调用那四个回调方法。还有就是一个AsyncTask实例只能执行一次,不然就出错哦。
以上是AsyncTask的基本用法,更加详细的内容请参考android官方文档。最后附上一段代码,供你们参考。
1 private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> 2 //在这里声明了Params、Progress、Result参数的类型 3 { 4 //由于这里不须要使用onPreExecute回调方法,因此就没有加入该方法 5 6 //后台线程的目的是更具URL下载数据 7 protected Long doInBackground(URL... urls) { 8 int count = urls.length;//urls是数组,不止一个下载连接 9 long totalSize = 0;//下载的数据 10 for (int i = 0; i < count; i++) { 11 //Download是用于下载的一个类,和AsyncTask无关,你们能够忽略他的实现 12 totalSize += Downloader.downloadFile(urls[i]); 13 publishProgress((int) ((i / (float) count) * 100));//更新下载的进度 14 // Escape early if cancel() is called 15 if (isCancelled()) break; 16 } 17 return totalSize; 18 } 19 20 //更新下载进度 21 protected void onProgressUpdate(Integer... progress) { 22 setProgressPercent(progress[0]); 23 } 24 25 //将下载的数据更新到UI线程 26 protected void onPostExecute(Long result) { 27 showDialog("Downloaded " + result + " bytes"); 28 } 29 } 30
有了上面的这个类,接下你要作的就是在UI线程中建立实例,并调用execute方法,传入URl参数就能够了。
这上面的5种方法各有优势。可是究其根本,其实后面四种方法都是基于handler方法的包装。在通常的情形下后面四种彷佛更值得推荐。可是当情形比较复杂,仍是推荐使用handler。