Android学习之——并发编程:AsyncTask和UI线程

        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方法在同一个线程中运行。
Android学习之——并发编程:AsyncTask和UI线程_第1张图片
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 < 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

你可能感兴趣的:(Android)