Android的UI是单线程的,所以对于运行时间长的程序必须
异步运行。实现异步任务的一个很方便的工具是
AsyncTask。它完全隐藏了运行任务的线程的很多详细信息。
以一个例子来说明AsyncTask:
一个非常简单的应用中,有需要初始化游戏引擎,当加载内容时,显示一些插播广告图形。假设,我们希望在用户等待游戏启动时,显示一个动画背景(类似于Windows Phone 8)上的加载程序的等待界面。
当用户在点击启动按钮以后,会执行多个初始化。
问题:如果在UI线程中执行远程服务调用初始化时,整个UI界面无法执行任何其他操作。
解决:使用AsyncTask解决这个问题,代码如下:
private final class AsyncInitGame extends AsyncTask
{
private final View root;
private final Game game;
private final TextView message;
private final Drawable bg;
public AsyncInitGame(View root, Drawable bg, Game game, TextView msg)
{
this.root = root;
this.bg = bg;
this.game = game;
this.message = msg;
}
//run on the UI thread
//1. 当UI线程调用任务的execute方法时,会首先调用该方法,这里要做的操作时该任务能够对其本身和环境执行初始化,在这个例子中是安装等待启动的背景动画
@Override protected void onPreExecute()
{
if(0 >= mInFlight++){
root.setBackgroundResouce(R.anim.dots);
((AnimationDrawable)root.getBackground()).start();
}
}
//runs on the UI thread
//3. 当doInBackground方法完成时,就删除背景线程,再在UI线程中调用onPostExecute方法。
@Override protected void onPostExecute(String msg){
if(0 >= --mInFlight){
((AndimationDrawable)root.getBackground()).stop();
root.setBackgroundDrawable(bg);
}
message.setText(msg);
}
//runs on a background thread
//2. 在onPreExecute方法完成后AsyncTask创建新的背景线程,并发执行doInBackground方法。
@Override protected String doInBackground(String... args){
return (1 != args.length) || (null == args[0])) ? null : game.initialize(args[0]);
}
}private final class AsyncInitGame extends AsyncTask
{
private final View root;
private final Game game;
private final TextView message;
private final Drawable bg;
public AsyncInitGame(View root, Drawable bg, Game game, TextView msg)
{
this.root = root;
this.bg = bg;
this.game = game;
this.message = msg;
}
//run on the UI thread
//1. 当UI线程调用任务的execute方法时,会首先调用该方法,这里要做的操作时该任务能够对其本身和环境执行初始化,在这个例子中是安装等待启动的背景动画
@Override protected void onPreExecute()
{
if(0 >= mInFlight++){
root.setBackgroundResouce(R.anim.dots);
((AnimationDrawable)root.getBackground()).start();
}
}
//runs on the UI thread
//3. 当doInBackground方法完成时,就删除背景线程,再在UI线程中调用onPostExecute方法。
@Override protected void onPostExecute(String msg){
if(0 >= --mInFlight){
((AndimationDrawable)root.getBackground()).stop();
root.setBackgroundDrawable(bg);
}
message.setText(msg);
}
//runs on a background thread
//2. 在onPreExecute方法完成后AsyncTask创建新的背景线程,并发执行doInBackground方法。
@Override protected String doInBackground(String... args){
return (1 != args.length) || (null == args[0])) ? null : game.initialize(args[0]);
}
}
假设AsyncTask的实现是正确的,我们单击按钮“启动”按钮只需要创建一个实例并调用它,如下所示:
((Button)findViewById(R.id.start)).setOnClickListener(
new View.OnClickListener(){
@Override public void onClick(View v){
new AsyncInitGame(root, bg, game, msg).execute("basic");
}
}
);//注:该文中的代码并不全面,只是为了阐述AsyncTask
doInBackground是类Game的代理(proxy)。
解释:
一般来说AsyncTask需要一组参数并返回一个结果。因为需要在线程之间传递该参数并返回结果,所以就需要一些握手机制用来确保线程安全性。
1. 通过参数传递调用execute方法来调用AsyncTask。
2. 当线程在后台执行时,这些参数最终通过AsyncTask机制传递给doInBackground方法的,doInBackground返回结果。
3. AsyncTask把该结果作为参数传递给doPostExecute方法,doPostExecute方法和最初的execute方法在同一个线程中运行。
AsyncTask不但会确保数据流安全也会确保类型安全。该抽象基类(AsyncTask)使用Java反省,使得实现能够制定任务参数和结果的类型,下面以一个例子来说明:
public class AsyncDBReq extends AsyncTask
{
@Override protected ResultSet doInBackground(PreparedStatement...q){
//implementation.....
}
@Override protected onPostExecute(ResultSet result){
//implementation
}
}
public class AsyncHttpReq extends AsyncTask
{
@Override protected HeepResponse doInBackground(HttpRequest.....req){
//implementaion.....
}
@Override protected void onPostExecute(HttpResponse result){
//implementaion
}
}
第一个类,AsyncDBReq实例的execute方法参数是一个或者多个PreparedStatement变量。
该类中AsyncDBReq实例的doInBackground方法会把这些PreparedStatement参数作为其参数,返回结果是ResultSet。onPostExecute方法会把该ResultSet作为其参数使用。
第二个类也是如此。
AsyncTask的一个实例只能运行一次!!!,第二次执行execute方法会抛出IllegalStateException一场。所以每个任务调用都需要一个新的实例。
虽然AsyncTask简化并行处理,但它有很强的限制
约束条件且无法自动验证这些条件。注意不要违反这些约束条件是非常有必须要的,
对于这些约束条件,最明显的是doInBackground方法,因为它是在另一个线程上执行的,
只能引用作用域内的变量!!!这样才是线程安全的
OK难点来了,如果实际使用中可能还是会发生下面两个这样的错误,所以,可能需要大量的练习来熟悉和理解了。
案例一:
//易犯错误一、
//....some clas
int mCOunt;
public void initButton1(Button button){
mCOunt = 0;
button.setonClickListener(
new View.OnClickListener(){
@SuppressWarnings("unchecked")
@Override public void onCLick(View v){
new AsyncTask(){
@Override protected Void doInBackground(Void...args){
mCount++; //!!! not thread safe!!
return null;
}
}.execute();
}});
}
这里在编译时不会产生编译错误,也没有运行警告,可能甚至在bug被触发时也不会立即失败,但该代码绝对是错误的。有两个不同的线程访问变量mCount,而这两个线程之间却没有执行同步。
鉴于这种情况,在本文的第一段代码中的mInFlight的访问执行同步时,你可能会感到奇怪。事实上它是正确的,
AsyncTask约束会确保onPreExecute方法和onPostExecute方法在同一个线程中执行,即execute方法被调用的线程。和mCount不同,mInFlight只有一个线程访问,不需要执行同步。
案例二:可能会导致最致命的的并发问题是在用完某个参数变量后,没有释放其引用。如下代码:
//易犯错误二、
public void initButton(Button button, final Map vals){
button.setOnClickListener(
new View.OnClickListener(){
@Override public void onClick(View v){
new AsyncTask
错误原因:
initButton的参数valse被并发引用,却没有执行同步!!!
当调用AsyncTask时,它作为参数传递给execute方法。syncTask框架可以确保当调用doInBackground方法时,该引用会正确地传递给后台线程。但是对于在initButton方法中所保存并使用的vals引用,却没有办法处理。调用vals.caear修改了在另一个线程上正在使用的状态,但没有执行同步。因此,不是线程安全的。
最佳解决办法:
确保AsyncTask的参数是不可变的。如果这些参数不可变,类似String、Integer或只包含final变量的POJO对象,那么它们都是线程安全的,不需要更多的操作。
要保证传递给AsyncTask的可变对象是程序安全的唯一办法是确保只有AsyncTask持有引用。
在案例二中参数vals是传递给initButton方法,我们完全无法保证它不存在悬空的引用(dangline references)。即使删掉代码vals.clear,也无法保证该代码正确,因为调用initButton方法的实例可能会保存其参数map的引用,它最终传递的是参数vals。使该代码正确的唯一方式是完全复制(深拷贝)map及其包含的对象!!!
最后还有AsyncTask还有一个方法没有使用到:
onProgressUpdate。
作用:使长时间运行的任务可以周期性安全地把状态返回给UI线程。
用一个例子来说明并结束本文吧,哎,打字不容易啊,妹子的电脑上没有eclipse,只能用editplus,木有智能提示伤不起的。
该例子说明了如何使用onProgressUpdate方法实现进度条,向用户显示游戏初始化进程还需要多久的时间。
public class AsyncTaskDemoWithProgress extends Activity{
private final class AsyncInit extends AsyncTask implements Game.InitProgressLIstener
{
private final View root;
private final Game game;
private final TextView message;
private final Drawable bg;
public AsyncInit(View root, Drawable bg, Game game, TextView msg){
this.root = root;
this.bg = bg;
this.game = game;
this.message = msg;
}
//run on the UI thread
//1. 当UI线程调用任务的execute方法时,会首先调用该方法
@Override protected void onPreExecute()
{
if(0 >= mInFlight++){
root.setBackgroundResouce(R.anim.dots);
((AnimationDrawable)root.getBackground()).start();
}
}
//runs on the UI thread
//3. 当doInBackground方法完成时,就删除背景线程,再在UI线程中调用onPostExecute方法。
@Override protected void onPostExecute(String msg){
if(0 >= --mInFlight){
((AndimationDrawable)root.getBackground()).stop();
root.setBackgroundDrawable(bg);
}
message.setText(msg);
}
//runs on its own thread
//2. 在onPreExecute方法完成后AsyncTask创建新的背景线程,并发执行doInBackground方法。
@Override protected String doInBackground(String... args){
return (1 != args.length) || (null == args[0])) ? null : game.initialize(args[0]);
}
//runs on its UI thread
@Override protected void onProgressUpdate(Integer... vals){
updateProgressBar(vals[0].intValue());
}
//runs on the UI thread
@Override public void onInitProgress(int pctComlete){
//为了正确地给UI线程发布进程状态,onInitProgress调用的是AsyncTask的publicProgress。
//AsyncTask处理UI线程的publicProgress调度细节,从而onProgressUpdate可以安全地使用View方法。
publicProgress(Integer.valueOf(pctComplete));
}
}
int mInFlight,mComplete;
/** @see android.app.Activity#onCreate(android.os.Bundle) */
@Override public void onCreate(Bundle state){
super.onCreate(state);
setContentView(R.layout.asyncdemoprogress);
final View root = findViewById(R.id.root);
final Drawable bg = root.getBackground();
final TextView msg = ((TextView)findViewById(R.id.msg));
final Game game = Game.newGame();
((Button)findViewById(R.id.start)).setOnClickListener(
new View.OnClickListener(){
@Override public void onCLick(View v){
mComplete=0;
new AsyncInit(root, bg, game, msg).execute("basic");
}});
}
void updateProgressBar(int progress){
int p = progress;
if(mComplete < p){
mComplete = p;
(ProgressBar)findViewById(R.id.progress)).setprogress(p);
}
}
}
.......嗯,其实还没结束,来个总结:
· Android UI线程是单线程的。为了熟练使用Android UI,开发人员必须对任务队列概念很熟悉。
· 为了保证UI的及时响应,需要运行的任务的执行时间超过几毫秒,或者需要好几百条指令,都不应该在UI线程中执行。
· 并发编程很棘手,容易犯错,并难以找出错误。
· AsyncTask是运行简单、异步任务的很便捷的工具。要记住的是doInBackground运行在另一个线程上。它不能写任何其他线程可见的状态,也不能读任何其他线程可写的状态,这也包括其参数!
· 不可改变的对象时在并线程之间传递信息的重要工具。
Mr.傅:学习笔记
欢迎转载,转载注明出处,谢谢
《Android程序设计》
引用说明:
Programming Android by Zigurd Mednieks, Laird Dornin, G.Blake Meike, and Masumi Nakamura. Copyright 2011 O'Reilly Media, Inc., 978-1-449-38969-7