虽然使用线程能适用大部分异步的场景,但是代码看起来还是不够简练,在android平台,还有另外一种解决方案----AsyncTask。
首先直接上代码:
private class getRemoteDataTask extends AsyncTask<String, Integer, ArrayList<Hashtable<String, String>>> {
protected void onPostExecute(ArrayList<Hashtable<String, String>> dataList) {
this.cancel(false);
}
@Override
protected ArrayList<Hashtable<String, String>> doInBackground(String... params) {
ArrayList<Hashtable<String, String>> dataList = new ArrayList<Hashtable<String, String>>();
return dataList;
}
}
下面来解释一下,doInBackground方法类似于在线程中的run方法,这个方法是另一个后台线程中执行,然后执行完会通过回调机制执行 onPostExecute,神奇之处就在于,onPostExecute这个方法的执行权限又交回给activity的主进程中执行,也就是说 onPostExecute这个方法里面可以操作UI。于是就能实现异步读取数据,并且操作UI,是不是比起自己写线程方便很多?
然后我们来剖析一下,这种方案的几个不太容易理解的地方。
一、AsyncTask<String, Integer, ArrayList<Hashtable<String, String>>> 注意这里几个泛型的定义和意义,第一个代表该方法被调用时传递的参数类型,注意是参数的类型,而不是参数的个数,也就是说如果定义为String,那么, 该方法被调用时所传递的参数只能是String类型;然后第二个Integer,资料上说是线程后台执行的百分比,不过我没彻底弄清楚这个的含义,欢迎大 家指教,然后泛型里的第三个ArrayList<Hashtable<String, String>,这个和该类的两个方法都有密切联系,该泛型是指后台执行返回的结果的类型。
二、doInBackground这个方法是在后台执行的,然后它的返回结果将提供给onPostExecute,所 以,doInBackground的返回值必须和AsyncTask类的第三个泛型定义一致,然后onPostExecute的参数类型必须和 doInBackground的返回类型一致。
三、前边我们都一直在解释AsyncTask这个类,但是怎么调用呢?其实很简单
new getRemoteDataTask().execute(String... params);
注意,execute方法里传递的参数的类型必须和AsyncTask第一个泛型的类型一致,然后这个方法传递的参数将在
doInBackground(String... params)
这里面起作用。
第四,AsyncTask还有很多方法,我们仅仅分析了其中两个,它被继承后,必须要重写的一个方法是doInBackground,因为doInBackground的返回值将在onPostExecute中被用来更新UI,所以我们姑且认为这两个方法是最重要。
第五、AsyncTask的实例必须在UI线程中被调用、 execute(String...params)必须在UI线程中调用、 不要手动调用onPreExecute(), onPostExecute(Result), doInBackground(Params...), onProgressUpdate(Progress...)等方法、 AsyncTask只执行一次。
一个小例子
有一个ListView的小例子,一开始List中没有内容,通过一个AsyncTask逐步在List中加入条目。
1)XML文件:简单的ListView布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout ... ...>
<ListView android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</LinearLayout>
2)例子代码
public class Chapter15Test3 extends ListActivity{
//这里是List Item内容,在这个例子中,将在后台任务中逐个加入
private static String[] items={"lorem", "ipsum", "dolor","sit", "amet", "consectetuer","adipiscing", "elit", "morbi","vel", "ligula", "vitae","arcu", "aliquet", "mollis","etiam", "vel", "erat","placerat", "ante","porttitor", "sodales","pellentesque", "augue","purus"};
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.chapter_8_test2);
//在这个例子中,我们一开始并没有导入items的数据,注意item数据为新建的ArrayList,即无内容
setListAdapter(new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,new ArrayList<String>()));
//步骤5:创建后台任务的对象,并通过execute()启动后台线程,调用doInBackground()的代码,execute中的参数类型为参数1,这里我们不需要传递任何内容
new AddStringTask().execute();
}
//步骤1:创建AsyncTask子类,参数1是Void的范式类型,参数2是String的范式类型,参数3是Void 。其中参数1:向后台任务的执行方法传递参数的类型;参数2:在后台任务执行过程中,要求主UI线程处理中间状态,通常是一些UI处理中传递的参数类型;参数3:后台任务执行完返回时的参数类型。
private class AddStringTask extends AsyncTask<Void, String,Void>{
//我们加入一个检测信息的方法,打印当前在哪个线程执行的信息
private void printInfo(String info){
Log.d("WEI", info + " : Tread is " + Thread.currentThread().getName());
}
//步骤2:实现抽象方法doInBackground(),代码将在后台线程中执行,由execute()触发,由于这个例子并不需要传递参数,使用Void...,具体书写方式为范式书写
protected Void/*参数3*/ doInBackground(Void...params/*参数1*/) {
for(String item : items){
//步骤3:通知UI主线程执行相关的操作(在onProgressUpdate中定义)
publishProgress(item/*参数2*/);
printInfo("doInBackgound " + item);
SystemClock.sleep(200);
}
return null;
}
//步骤3:定义收到pushProgress()触发后,在UI主线程执行的内容,在本例,将item加入list中。方法中的参数为范式方式,实质为数组,由于我们只传递了item一个String,要获取,为values[0]
protected void onProgressUpdate(String... values/*参数2*/) {
printInfo("onProgressUpdate get param " + values[0]);
((ArrayAdapter<String>)getListAdapter()).add(values[0]);
}
//步骤4:定义后台进程执行完后的处理,本例,采用Toast
protected void onPostExecute(Void result/*参数3*/) {
printInfo("onPostExecute");
Toast.makeText(Chapter15Test3.this, "Done!", Toast.LENGTH_SHORT).show();
}
}
}
我们根据printInfo跟踪各部分代码在哪里执行:doInBackground在后台线程执行,onProgressUpdate()和onPostExecute()在UI主线程执行。main就是UI主线程,而AsyncTask #1为后台线程,名字不一样。
需要注意
虽然Android提供后台任务方便我们处理,是否使用后台任务,以及如何使用后台任务,我们要注意下面的内容:
可能在执行后台线程处理中,用户和UI之间存在交互,这些交换可能会对后台任务有重要的影响,因此需要通知后台线程,Android提供很多的类来处理,封装在java.util.concurrent包中,帮助与后台线程的安全通信。可能在执行后台线程处理中,我们的Activity就已经被kill了,例如有一个电话过来,然后发给短信,查看号码本。这时系统可能将你的activity踢走,接着后面我们会学习Activity的生命周期,了解相关的情况。在编程中,出现这种情况,只要有可能,需要将后台进程关闭。可能在执行后台线程处理中,出现某种错误,例如后台在下载URL,而网络连接中断了。这种情况下关闭后台进程可能是最好的处理。此外后台任务是消耗CPU和内存,是有代价的,我们应该确保它处理的时候更为有效。