展会信息港展会大全

Android学习之——并发编程:AsyncTask和UI线程
来源:互联网   发布日期:2016-01-14 12:40:32   浏览:1294次  

导读:Android的UI是单线程的,所以对于运行时间长的程序必须异步运行。实现异步任务的一个很方便的工具是AsyncTask。它完全隐藏了运行任务的线程的很多详细信息。 以一个例子...

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, Void, Void>(){

@Override protected Void doInBackground(Map...params){

//implementation, uses the params Map

}

}.execute(vals);

vals.clear();//this is not thread safe!!!!

}});

}错误原因: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 .......嗯,其实还没结束,来个总结:

· 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

赞助本站

人工智能实验室

相关热词: android开发

AiLab云推荐
推荐内容
展开

热门栏目HotCates

Copyright © 2010-2024 AiLab Team. 人工智能实验室 版权所有    关于我们 | 联系我们 | 广告服务 | 公司动态 | 免责声明 | 隐私条款 | 工作机会 | 展会港