动手学Android之十——异步任务

来源:互联网 发布:ios降级会清除数据吗 编辑:程序博客网 时间:2024/04/29 14:46

有些东西感觉懂了,其实关上书本和电脑,想想看,你还能想得起代码的样子吗?

         这节我们来讲一讲重头戏,异步任务,这是干嘛的呢?我们先来看个例子。

public class MainActivity extends Activity {private Button wasteTimeBtn = null;private Button operatorBtn = null;@Overrideprotected void onCreate(Bundle savedInstanceState) {// TODO Auto-generated method stubsuper.onCreate(savedInstanceState);setContentView(R.layout.main);wasteTimeBtn = (Button) findViewById(R.id.waste_time_btn);operatorBtn = (Button) findViewById(R.id.operator_btn);wasteTimeBtn.setOnClickListener(new OnClickListener() {public void onClick(View v) {// TODO Auto-generated method stubtry {Thread.sleep(5000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}});operatorBtn.setOnClickListener(new OnClickListener() {public void onClick(View v) {// TODO Auto-generated method stubToast.makeText(MainActivity.this, "Can you see me?", Toast.LENGTH_SHORT).show();}});}}

两个Button,当你点击“启动耗时操作”时,为了模拟耗时操作,线程Sleep五秒钟,你可以在自己手机上试一试,必须等5秒后才能点击第二个按钮,5秒内,程序没反应!

         等等,说好的列表下拉刷新呢?其实这个功能不是一个组件就能完成的,它要自己写代码,而在这之前,要打好一些基础,想看到那个的实现的同学继续关注我的博客哈,在这里为放了大家鸽子表示歉意!

         好了,我们看看刚刚那个丑陋不堪的例子,启动耗时操作后,硬要我们等5秒后才能点击第二个按钮,能不能把耗时操作放到后台去运行呢?你肯定想到了多线程。

wasteTimeBtn.setOnClickListener(new OnClickListener() {public void onClick(View v) {// TODO Auto-generated method stubnew Thread(new Runnable(){public void run() {// TODO Auto-generated method stubtry {Thread.sleep(5000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}).start();}});

         代码改动后,你再试试!一切ok,我们感谢多线程带来的好处。但是我们如果要每隔10秒弹出一个Toast,总共弹3次,该怎么办呢?这还不简单?

public void run() {// TODO Auto-generated method stubtry {for(int i=0;i<3;i++) {Toast.makeText(MainActivity.this, "我在另一个线程哦", Toast.LENGTH_SHORT).show();Thread.sleep(10000);}} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}

         不就OK了吗?但是我们运行下:点击启动耗时操作按钮



         程序Crash了。这个错误的具体原因这里暂不深究,以后会说,这里我们只要知道Toast必须在UI线程中才能使用,不仅仅是Toast,涉及到UI的东西都只能在UI线程中。那么什么是UI线程呢?我们的程序刚启动的时候,肯定会启动一个线程来展示我们的界面啦,这就是UI线程。而我们new出来的Thread,是另外一个线程,没有改变UI的功能,那刚刚的任务怎么完成呢?这就要用到我们的异步任务了。

package com.example.asynctask;import android.os.AsyncTask;public class MyAsyncTask extends AsyncTask<Params, Progress, Result> {@Overrideprotected Result doInBackground(Params... params) {// TODO Auto-generated method stubreturn null;}}

         异步任务AsyncTask是我们用的很多的一个类,android早就知道我们会有这种需求,它早就为我们提供了这种机制,我们来看看怎么用吧,首先,异步任务有三个东西要说下:Params、Progress和Result。我们看doInBackground方法,它的入参是Params... params,这是可变参数的写法,我们在这里可以传递任意多的Params对象,它的出参是Result,也许第一次见,你不太懂这是什么意思,我们来写明白点:

public class MyAsyncTask extends AsyncTask<Void, Integer, String> {@Overrideprotected String doInBackground(Void... params) {// TODO Auto-generated method stubreturn null;}}

         其实我们平时是这么用的,看到了吧,Params、Progress和Result其实代表的是三种类,我们可以自己定义的,这里Void类代表空,而doInBackground方法就像run方法,它会在另一个线程中运行,所以我们的需求可以这样写:

public class MyAsyncTask extends AsyncTask<Context, Integer, String> {@Overrideprotected String doInBackground(Context... contexts) {// TODO Auto-generated method stubtry {for(int i=0;i<3;i++) {Toast.makeText(contexts[0], "我在另一个线程哦", Toast.LENGTH_SHORT).show();Thread.sleep(10000);}} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}return null;}}

         这里的Contexts怎么传呢?

public void onClick(View v) {// TODO Auto-generated method stubnew MyAsyncTask().execute(MainActivity.this);}

         我们再来试下吧:



         怎么还是Crash?其实原因很简单,我说了doInBackground会在另一个线程中运行,而且我也说了,Toast只能在UI线程中,所以你懂了,那么怎么办呢?这里我们要用到中间的Progress这个参数了,也就是这里的Integer,我们这样写:

public class MyAsyncTask extends AsyncTask<Void, Integer, String> {private Context context = null;public MyAsyncTask(Context context) {// TODO Auto-generated constructor stubthis.context = context;}@Overrideprotected String doInBackground(Void... voids) {// TODO Auto-generated method stubtry {for(int i=0;i<3;i++) {publishProgress(i);Thread.sleep(10000);}} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}return null;}@Overrideprotected void onProgressUpdate(Integer... values) {// TODO Auto-generated method stubToast.makeText(context, "我在另一个线程哦,第" + values[0] + "次!", Toast.LENGTH_SHORT).show();super.onProgressUpdate(values);}}

         我们调用publishProgress函数,注意,参数是Progress,也就是这里的Integer,然后我们在onProgressUpdate中去用Toast,注意这次Context是通过构造函数传进来的,所以我们异步任务的调用也要改:

public void onClick(View v) {// TODO Auto-generated method stubnew MyAsyncTask(MainActivity.this).execute();}

         我们这次再看看


         哈哈,这次我们成功了,我们还得到一个结论:onProgressUpdate是在UI线程中运行的(不要被图片迷惑哈,“我在另一个线程哦,第2次!”,其实是在骗你哈,Toast永远在UI线程),这就是Android给我们提供的异步任务机制,当然,android考虑得没这么简单,它还给我们提供了onPreExecute和onPostExecute函数,看名字就知道,这两个函数,一个是在doInBackground前执行,一个在doInBackground后执行,需要说明的是,这两个函数都是在UI线程中执行的,也就是说,这里出现的四个函数,只有doInBackground是在新的线程执行,其他函数还是在UI线程中。为了证明我们的结论,我们在线程中打印出线程ID:首先在onCreate中:System.out.println("MainActivity onCreate : Thread ID" + Thread.currentThread().getId());,然后分别在异步任务的四个函数中添加类似的打印语句,看下效果:



我们看到,只有doInBackground的Thread ID和其他的不一样,证明上述结论是正确的。到这里,我们有个问题可以猜一猜:我们点击“启动耗时操作”按钮后,点返回键退出Activity,这时候还会不会弹出Toast呢?

         试验了一下,我们发现还是会的,但是我们的Activity已经Destroy了(还记得Activity的生命周期吧,不记得了去看第七节),为什么还会弹Toast呢?其实我们的Activity只存在于一个UI线程A中,而我们启动一个程序,当然是启动了一个进程,当一个进程中还有在运行的线程的时候,进程就不会退出,那么我们点击按钮启动了一个新的线程B,所以当UI线程A退出后,我们的进程中还有一个线程B在运行,进程不会退出,也就是程序没有结束,我们的线程B还可以继续运行。

         到这里,关于异步任务还有一个东西没说,相信你已经注意到了,就是Result参数,在这里是String参数,那么它是干嘛的呢?我们看我们的doInBackground方法,它返回一个String参数,而onPostExecute恰好接受一个String参数,你已经猜到,这个String参数就是doInBackground返回的参数,关于这点大家可以自己写程序试验,也可以参考我的例子。

         好了,这节的内容比较简单,大家回去要自己练习下哦,初学者还是不容易一下子掌握的,下面我们来总结下:

1、  UI线程中不要做耗时操作,会导致界面卡死

2、  非UI线程不能进行UI操作

3、  咋办呢?用异步任务AsyncTask,这里面有四个方法:doInBackground,onPreExecute,onPostExecute,onProgressUpdate,只有doInBackground在新线程中运行,所以耗时操作都放到doInBackground里面去

4、  三个参数,Params, Progress, Result,Params是doInBackground的入参,Progress是onProgressUpdate的入参,是通过在doInBackground中调用publishProgress触发的,Result是doInBackground的出参,也是onPostExecute的入参

这节的例子在http://download.csdn.net/detail/yeluoxiang/7359157,欢迎大家下载!



0 0
原创粉丝点击