危险的AsyncTask

来源:互联网 发布:路书制作软件 编辑:程序博客网 时间:2024/04/29 01:14

    危险的AsyncTask

  前几天修改项目,在于数据库同步的时候,发现同步数据一直下载不下来。然后跟踪log,最终发现,我定义的AsyncTask,的doInBackground一直没有执行。本来以为是代码移植(4.4移植到L)时候,framework的同事修改出现的bug,后来google了半天,才发现。原来真正的幕后真凶是AsyncTask。

  下面我通过分析源码,结合自身的惨痛教训来分析以下AsyncTask。

  在看源码之前,需要掌握以下几个知识。我都已经粘贴出链接,有兴趣的朋友可以根据链接自行学习


1  AtomicInteger   java线程用到的原子操作对应的类。具体可以参看对应的博客

blog.csdn.net/ufo2910628/article/details/39473059

2 BlockingQueue java多线程操作时需要的队列,也是为了线程同步的问题而出现地

www.cnblogs.com/jackyuj/archive/2010/11/24/1886553.html

3 volatile 同上,目的也是线程同步

www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html

4 ThreadPoolExecutor java的线程池操作想过对象

blog.csdn.net/historyasamirror/article/details/5961368


以上都是会在源码中出现的一些东东,可以看出都是和线程有关(因为AsyncTask本身就是多线程操作地)。这些其实就是AsyncTask的多线程操作本质啦。好了,下面进入源码部分

(或许会枯燥,可以看我的注释进行阅读)

    //cpu的数目
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;  //线程池大小    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;    //最大线程池大小    private static final int KEEP_ALIVE = 1; 
</pre><p></p><pre name="code" class="html">    //线程工厂,如果看不懂,可以先看上面的 ThreadPoolExecutor,然后再次阅读。其实这个就可以看作一个普通类,它会返回一个新的线程对象,其他现不考虑

       private static final ThreadFactory sThreadFactory = new ThreadFactory() { 

              private final AtomicInteger mCount = new AtomicInteger(1); 
              public Thread newThread(Runnable r) { 
                    return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); 
    } };

    //线程安全队列,他是为下面的线程池作的准备
       private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(128);
   //真正执行任务的线程池,可以参看上面链接。 他的参数包括线程数量,最大线程数量,产生线程需要的工厂等等。   //从这段代码和上面的分析就可以看出,这个ThreadPoolExecutor其实就是AsyncTask执行一步操作的主要执行人。注意,他是static类型地,
   //而且他的参数threadFactory和blockQueue也是static。   //看到这里应该就能明白一点,原来不管我用多少个AsyncTask,其实执行线程操作的‘能力者’就是这一个,如果它卡了,
   //其他的不管怎么new AsynTask也都是无济于事的。(这里就可以看出一些我那个bug的眉目了)

      /** * An {@link Executor} that can be used to execute tasks in parallel. */

     public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); 

  
   // SerialExecutor,他也是一个executor,但是前面有个Serial.表明他是序列性质地Executor.那么他和ThreadPoolExecutor啥关系呢?出现两个executor不矛盾吗?
   //其实他的异步线程操作时调用THREAD_POOL_EXECUTOR的异步操作,下面会继续分析

      /** * An {@link Executor} that executes tasks one at a time in serial * order. This serialization is global to a particular process. */ 

     public static final Executor SERIAL_EXECUTOR = new SerialExecutor();     ..............

   
   //hanle,熟悉Thread和Handle配合使用的朋友们一看就知道,这个八成就是AsyncTask用来对UI线程操作的Handle,
   //本身AysncTask就是对Thread与Handle的配合使用的封装
     private static final InternalHandler sHandler = new InternalHandler();
   
   //把上面的SerialExecutor设置成默认地Executor

       private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; 

   private final WorkerRunnable<Params, Result> mWorker; //暂时现不考虑
   private final FutureTask<Result> mFuture;//暂时先不考虑

   private volatile Status mStatus = Status.PENDING;//执行状态

     ok,上面就是部分属性。下面我从execute方法进入,一步步的跟踪代码

 
   //这部分是我的代码,new完task之后,直接调用execute,
   LoginTask task = new LoginTask(ctx, username, passwd, callback);   task.execute();                             好了,正式进入execute.
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {        
      //调用了executeOnExecutor方法,*注意*,看一下这里的参数sDefaultExecutor.他就是上面提到的SERIAL_EXECUTOR,序列化的executor。这个先记住,继续读

           return executeOnExecutor(sDefaultExecutor, params); 

       }

进入executeOnExecutor

 
 private volatile Status mStatus = Status.PENDING;//记着上面最后的属性mStatus吗?他不是static的。说明他属于每个AsyncTask的子类地,而不是全局地。每个子类有着不同的mStatus.

 public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,            Params... params) {        if (mStatus != Status.PENDING) {            switch (mStatus) {                case RUNNING://如果当前AsyncTask处于running                    throw new IllegalStateException("Cannot execute task:"                            + " the task is already running.");                case FINISHED://如果当前线程处于finish                    throw new IllegalStateException("Cannot execute task:"                            + " the task has already been executed "                            + "(a task can be executed only once)");            }        }
       mStatus = Status.RUNNING;       onPreExecute();//调用onPreExecute()方法,他是抽象地,是由子类来实现的。这种设计模式叫做“模板方法模式”,具体设计模式,我推荐看一下《大话设计模式》,个人很喜欢
       mWorker.mParams = params;//设置参数       exec.execute(mFuture);//调用execute()方法,注意传入的mFuture。oh yeah!总算找到execute方法了,希望他就是真正执行异步的方法       return this;
   }

    

进入exec.execute 要进入exec.execute,首先搞明白exec是个啥(废话),他是被传递进来的。仔细阅读前面,就知道,他就是sDefaultExecutor,也就是我前面一直提到的SerialExecutor。还有需要注意传递的参数mFuture。好了,下面继续,看这个SerialExecutor的真面目是个啥

 //在看SerialExecutor之前,我必须交代清除他的参数mFuture是个啥,否则用到他的地方,不好阐述。望见谅. //mFture属性是在构造方法中定义地。这样每个AsyncTask,在构造的时候,就必须构造出他来了。下面阅读源码 public AsyncTask() {      //前面提到过的mWork.从名字可以看出,这家伙或许就是真正的工作者        mWorker = new WorkerRunnable<Params, Result>() {            public Result call() throws Exception {                mTaskInvoked.set(true);                //设置线程的执行的优先级                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);                //noinspection unchecked                //调用postResult,注意他的参数,是我们非常熟悉的doInBackground方法,对,他就是子类重写的doInBackground方法。
                //也是模板设计模式。他调用的位置就是在mWork的call中,调用完成之后会调用postResult。                //姑且先不管postResult.先继续往下看,我估计读者如果写过AsyncTask和hanle Thread的应用,这里心理也会大体知道postResult的作用了。                return postResult(doInBackground(mParams));            }        };       //这个类内部传递了mWork作参数,然后重写了一个done方法,看done方法时候,如果去掉exeption,其实就是一个postResultInNotInvoked()方法被执行,光看这个方法,
       //感觉应该是在更新ui时候用到的,跟doInBackground关系不大。估计也是模板设计模式。              mFuture = new FutureTask<Result>(mWorker) {            @Override            protected void done() {                try {                    postResultIfNotInvoked(get());                } catch (InterruptedException e) {                    android.util.Log.w(LOG_TAG, e);                } catch (ExecutionException e) {                    throw new RuntimeException("An error occured while executing doInBackground()",                            e.getCause());                } catch (CancellationException e) {                    postResultIfNotInvoked(null);                }            }        };    }   //由于代码很多,我这里就不写FutureTask的源码了,直接说结论。如果想看代码的读者还是自行下载源码吧(sorry)   //原来FutureTask其实就是个Runnable.它的run方法中会调用mWork的call方法,从而调用了doInBackGround方法了。   //OH YEAH.总算找到我们doInBackground方法调用的地方了.  搞清楚了参数,现在回到  executeOnExecutor方法中继续分析(有忘记的,可以参看上面代码)  //就是这里开始研究mFutre的。我们知道了exec其实就是SERIAL_EXECUTOR.现在就进入SerialExecutor里面看看.还是记住mFutrue这个参数。//这个类是静态的,也就意味着全局独一分
 mWorker.mParams = params; exec.execute(mFuture);//就是这里开始研究mFutre的
<pre name="code" class="java">//我们知道了exec其实就是SERIAL_EXECUTOR.现在就进入SerialExecutor里面看看.
//这个类是静态的,也就意味着全局独一分(全局当然是只在当前的app中)
private static class SerialExecutor implements Executor {
        //一个进程安全队列          final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();        Runnable mActive;       //重要的execute方法,我们调用子类AsyncTask的execute方法的时候,最终会执行该方法。        public synchronized void execute(final Runnable r) {
            //向线程安全队列插入一个runnable            mTasks.offer(new Runnable() {                public void run() {                    try {
                        //从这里可以看出,顺序执行
                        //当run方法执行没有结束,下面的<pre name="code" class="java">                        // scheduleNext()不会只执行,所以如果第一个AsyncTask的run方法没有执行完毕,后面的所有
//AsyncTask的异步都不会执行 r.run(); } finally { scheduleNext(); } } }); if (mActive == null) { scheduleNext(); } } //调用真正的线程持THREAD_POOL_EXECUTOR执行操作,
//它会从线程安全队列mTasks中取出run,然后执行run操作 protected synchronized void scheduleNext() { if ((mActive = mTasks.poll()) != null) { THREAD_POOL_EXECUTOR.execute(mActive); } }

ok上面的讲解就是在执行doInBackground()方法的过程,最后也说明一下我的app为啥会出现doInBackground执行不出来的情况。我在程序刚开始开了一个推送服务,由于服务器的原因或许其他的一些原因,导致推送服务出现了问题,整个推送不了。而这个推送是用AsyncTask。这就造成了那个队列一直被阻塞,我后面就算new出在多的AsyncTask也都无济于事的。当然也是有解决办法地。

我们可以直接调用AsyncTask的 this.executeOnExecutor(exec, params)。而不是通过execute()让AsyncTask帮我们调用(因为他会默认的调用序列的后的executor).而里面传入的第一个参数executor可以通过task.THREAD_POOL_EXECUTOR直接得到,因为他是public static类型的嘛。然后params参数就可以随意自己写拉。这样就可以跳过默认executor的过滤从而直接执行线程操作

后面怎么修改进入ui线程我暂时就不写了,太多了。不过原理就是通过handle来设置。有兴趣的朋友可以参看源代码。好了,就说到这里,如果有什么不对的,欢迎大家提出来。一起讨论,谢谢。

                                             
0 0
原创粉丝点击