声明:本文读者适合有一定android开发经验的程序员,文中肯定有不足之处,欢迎指正。
在android开发中,异步处理模块(控制器)占有很大比例,异步处理的稳定与否关乎整个应用的运行速度和用户体验,android开发不像web开发,web开发有各种各样的框架可以套用,使整个系统的架构看上去很明朗,因为笔者有代码混乱恐惧症,所以每当看到各种android实例,包括一些成熟应用的代码中,progress dialog满天飞,一个activity实例化n个oncliklistener,handler thread得哪儿放哪儿,笔者就忍不住用最快的时间关掉程序。在了解和使用了无数handler+thread组合后,我决定抛弃它们,使用android api提供的AsyncTask,虽然重构代码需要一定工作量,但我认为这是值得的。
什么是AsyncTask?简单地说,它是一个线程池。
如何使用AsyncTask并有效把它整合为架构的一部分?
首先我们需要新建一个继承AsyncTask的类,名字就叫MainAsyncTask,好了,就用它来处理乱七八糟的异步任务!MainAsyncTask需要继承一些父类方法,供系统回调(注意不能主动调用回调接口),AsyncTask中常用的回调方法如下:
onPreExecute(), 该方法将在执行实际的后台操作前被UI thread调用。可以在该方法中做一些准备工作,如在界面上显示一个进度条。
doInBackground(Params...), 将在onPreExecute 方法执行后马上执行,该方法运行在后台线程中。这里将主要负责执行那些很耗时的后台计算工作。可以调用 publishProgress方法来更新实时的任务进度。该方法是抽象方法,子类必须实现。
onProgressUpdate(Progress...),在publishProgress方法被调用后,UI thread将调用这个方法从而在界面上展示任务的进展情况,例如通过一个进度条进行展示。
onPostExecute(Result), 在doInBackground 执行完成后,onPostExecute 方法将被UI thread调用,后台的计算结果将通过该方法传递到UI thread.
好吧,让我们看看如何在主线程(UI线程)中调用吧。
1、activity页面调用
MainAsynTask asynTask = new MainAsynTask(this);
asynTask.execute(Constants.TASK_USER_LOGIN);
以LoginActivity为例,我们要执行的是一个登录任务,这里只需两行代码,execute
传入的是任务号(当然,这里可以传入多个参数,类型自便),this代表的activity上下文,至于如何执行,就交给万能的AsyncTask吧。
2、MainAsyncTask!
public class MainAsynTask extends AsyncTask<Object, Object, Object> implements Serializable{// 继承AsyncTask
AsyncTask定义了三种泛型类型 Params,Progress和Result,作为传入参数。
Params 是启动任务执行的输入参数,比如HTTP请求的URL。Progress 是后台任务执行的百分比。Result 是后台执行任务最终返回的结果,比如String。
上面代码是我定义的三个输入参数,为了兼容全定义成Object,第一个参数是任务号,用来区分处理任务,仍然沿用之前的int类型的TASK_ID,只不过放入Object中;第二个参数暂时没用,第三个参数是返回类型,定义为Object,在刷新UI线程时(即取得结果时)再强转成需要的类型。
3、MainAsyncTask的构造函数
public MainAsynTask(Context context) {
this.context = context;
baseActivity = (BaseActivity) this.getContext();
}
没错,context接收的参数就是调用时的activity上下文,方法体中利用多态思想实例化当前activity。
4、业务处理前要做的....
// 在 doInBackground(Params...)之前被调用,ui线程执行
@Override
protected void onPreExecute() {
baseActivity.setDialogDisplay(this);
}
这里是简单地显示一个等待框。
5、业务处理时
@Override
protected Object doInBackground(Object... params) {// 处理后台执行的任务,在后台线程执行
while (running) {
Log.e("执行任务中", "..........");
int taskId = (Integer)params[0];
switch (taskId) {
case Constants.TASK_USER_LOGIN:
User user = UHomeApplication.getInstance().getUser();
//其它业务代码
return true;
}
}
return null;
}
注意,params[0]就是我们传入的任务号。switch先判断不同的任务号然后再确定执行哪个任务。进入此方法就不能对UI线程进行操作了。
6、返回结果
@Override
protected void onPostExecute(Object result) {// 后台任务执行完之后被调用,在ui线程执行
if (result != null) {
baseActivity.setDialogDisappear(this);
baseActivity.refresh(result);
running = false;
} else {
baseActivity.setDialogDisappear(this);
Toast.makeText(context, "服务器无响应", Toast.LENGTH_LONG).show();
running = false;
}
}
传入参数result即上一步的return,我们可以对它进行分析和处理,,在这里可以对UI线程进行操作了。因为baseActivity已经被我们强转成了子类,所以,这里调用的refresh是子类实现的方法。
7、更新主线程(UI)
@Override
public void refresh(Object... args) {
super.refresh(args);
boolean flag = (Boolean) args[0];
if(flag){
//登录成功
}
}
上述代码在第一步中的activity中,refresh方法是继承自BaseActivity的。BaseActivity是一个自定义的类,内有常用的方法,供继承者调用,如refresh
附:部分BaseActivity代码
public class BaseActivity extends Activity {
//等待框
protected ProgressDialog dlg;
protected MainAsynTask mainAsyncTask;
//异步调用后activity接收结果并更新UI
public void refresh(Object... param) {
}
//activity初始化时执行代码,如检查网络、实例化控件等
public void prepareUI() {
};
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
Log.e("BaseActivity启动", "..................");
dlg = new ProgressDialog(this);
}
public void setDialogDisplay(MainAsynTask mainTask) {
Bundle bun = new Bundle();
bun.putSerializable("syc", mainTask);
this.onCreateDialog(1,bun);
}
public void setDialogDisappear(MainAsynTask mainTask) {
Bundle bun = new Bundle();
bun.putSerializable("syc", mainTask);
this.onCreateDialog(0,bun);
}
}
总结:
先前异步处理模块特点:使用handler+thread处理机制,缺点是代码冗余量大,业务处理晦涩难懂,thread(线程)运行有不可遇见的问题,需要另写大量的维护代码,handler对程序主线程的占用率也是惊人的,如果出现异常可能导致系统崩溃。优点是效率较高,无需另启线程池来维护,对于业务量少后台任务不复杂的应用来说这种机制是首选。
使用AsyncTask后特点:优点:AsyncTask是成熟的作品,可以很好解决一些线程安全问题,不用我们手动维护线程;代码比较整洁,业务易于理解。缺点:虽然AsyncTask在代码上比handler要轻量,而实际上要比handler更耗资源,因为AsyncTask底层是一个线程池,这也是开始没有使用它的原因,但是,如果异步任务的数据庞大,AsyncTask这种线程池结构的优势就体现出来了。另外,AsyncTask目前最多只能同时存在20个实例,不过只要能够做到用完就释放,这个不算是太大的问题。
还需解决的问题:
大家都知道,此框架中,如果activity要处理异步任务,需要继承BaseActivty,而有些应用中activity还会继承别的类,如tabhost,鉴于java单一继承机制,笔者认为上述框架还需修改,将BaseActivty抽象成接口即可,本文算是改进前的一篇技术预言吧。