Android SDK中AsyncTask的doInBackground方法未运行(子类)

90

截至2012年2月15日,我还没有找到一个好的解释或原因来说明为什么这不能工作。最接近解决方案的是使用传统的线程方法,但是为什么在Android SDK中包含一个看起来不能工作的类呢?

晚上好,SO!

我有一个AsyncTask子类:

// ParseListener had a callback which was called when an item was parsed in a
// RSS-xml, but as stated further down it is not used at all right now.
private class xmlAsync extends AsyncTask<String, RSSItem, Void> implements ParseListener

这样执行:

xmlAsync xmlThread = new xmlAsync();

xmlThread.execute("http://www.nothing.com");

现在这个子类遇到了一个小错误。之前它进行了一些XML解析,但当我注意到它的doInBackground()没有被调用时,我逐行削减代码,最终只保留以下内容:

@Override
protected Void doInBackground(String... params) 
{
    Log.v(TAG, "doInBackground");
        return null;
}

出于某种原因,它没有记录任何内容。不过,我添加了这个:

@Override
protected void onPreExecute() 
{
        Log.v(TAG, "onPreExecute");
        super.onPreExecute();
}

在执行线程时确实记录了那行代码。所以某种方式调用了onPreExecute()但没有调用doInBackground()。我同时在后台运行另一个异步任务,它可以正常工作。

我目前在模拟器上运行此应用程序,SDK版本为15,使用Eclipse和Mac OS X 10.7.2,距离北极非常近。

编辑:

@Override
    protected void onProgressUpdate(RSSItem... values) {

        if(values[0] == null)
        {
                            // activity function which merely creates a dialog
            showInputError();
        }
        else
        {

            Log.v(TAG, "adding "+values[0].toString());
            _tableManager.addRSSItem(values[0]);
        }


        super.onProgressUpdate(values);
    }
_tableManager.addRSSItem()函数会向SQLiteDatabase添加一行数据,并使用该活动的上下文进行初始化。publishProgress()由接口ParseListener的回调函数调用。然而,由于在doInBackground()中我仅做log.v操作,因此我认为这个功能不必要。 编辑2:好的,为了更加清楚,这是另一个AsyncTask,在同一个活动中执行并正常工作。
private class dbAsync extends AsyncTask<Void, RSSItem, Void>
{
    Integer prevCount;
    boolean run;

    @Override
    protected void onPreExecute() {
        run = true;
        super.onPreExecute();
    }

    @Override
    protected Void doInBackground(Void... params) {
        // TODO Auto-generated method stub
        run = true;
        prevCount = 0;

        while(run)
        {
            ArrayList<RSSItem> items = _tableManager.getAllItems();

            if(items != null)
            {
                if(items.size() > prevCount)
                {
                    Log.v("db Thread", "Found new item(s)!");
                    prevCount = items.size();

                    RSSItem[] itemsArray = new RSSItem[items.size()];

                    publishProgress(items.toArray(itemsArray));
                }
            }               

            SystemClock.sleep(5000);
        }

        return null;
    }

    @Override
    protected void onProgressUpdate(RSSItem... values) {

        ArrayList<RSSItem> list = new ArrayList<RSSItem>();

        for(int i = 0; i < values.length; i++)
        {
            list.add(i, values[i]);
        }

        setItemsAndUpdateList(list);

        super.onProgressUpdate(values);
    }

    @Override
    protected void onCancelled() {
        run = false;

        super.onCancelled();
    }
}

编辑3:

叹气,抱歉我不太擅长提问。以下是Tasks的初始化:

xmlAsync _xmlParseThread;
dbAsync _dbLookup;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

_dbLookup = new dbAsync();
_dbLookup.execute();

_xmlParseThread = new xmlAsync();       
_xmlParseThread.execute("http://www.nothing.com", null);
}

任务完成前活动是否可能已经结束? - Paul Nikonowicz
很不可能。如果那样的话,我的另一个线程就不会运行了吧?而且我现在只有一个活动。 - SeruK
2
我的应用程序也有同样的问题 - doInBackground 要么没有被调用,要么被延迟了很长时间。这是我的有限观察:完全相同的代码在 Android 2.3.3 智能手机和模拟器上无缺陷地运行,但在 Android 4.0.3 平板电脑和一些 Android 4.x.x 模拟器上出现了这个问题。很容易得出结论,这个问题是在较新版本的 Android 中引入的。 - Hong
抱歉,但我忘了提到这个问题只发生在一个Activity的第二个AsyncTask中。第一个AsyncTask总是正常工作的。 - Hong
Hong,你试过Matthieu的答案吗?我现在大多数时间都不涉及Android游戏了,已经有一段时间没用它了,所以我不能确定他的答案是否有效。如果对你没有用,那么也许我接受他的答案是错误的... - SeruK
我有完全相同的问题,我花了一整天的时间在这上面,但是我找不到解决方法。当我注意到doInBackground中的登录未显示时,我真的很惊讶。我也有一个活动和两个AsyncTask。第一个任务(文件下载)阻塞了第二个任务(使用来自数据库的数据更新UI)。 - S.Thiongane
9个回答

160
你应该查看这个答案:https://dev59.com/LGkv5IYBdhLWcg3wzkHa#10406894,以及它所包含的指向Google组的链接。
我曾经遇到过类似的问题,仍然不清楚为什么代码没起作用,但我按照下面的方式修改了我的代码,问题就解决了:
ASyncTask<Void,Void,Void> my_task = new ASyncTask<Void,Void,Void>() { ... };
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
    my_task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null);
else
    my_task.execute((Void[])null);

1
@Matthieu先生:真的非常感谢您!!!我为这个问题苦苦思索了几个小时,而您的解决方案让一切都像魔法般地运行起来!非常感谢您提供如此出色的答案。真希望我能给您多个赞!! - Swayam
这应该在官方文档中。这里没有提到 http://developer.android.com/guide/components/processes-and-threads.html - 124697
@Walmart_Hobo,你的链接已经失效了。 - joao2fast4u
3
@joao2fast4u http://web.archive.org/web/20131219080911/http://www.jayway.com/2012/11/28/is-androids-asynctask-executing-tasks-serially-or-concurrently - Andrey
@zionpi,这些更改使得多个ASyncTask可以并行运行。我的理解是,当您认为它没有运行时,问题实际上是您已经有另一个任务在后台运行且未完成... - Matthieu
显示剩余9条评论

108

Matthieu的解决方案对大多数人都有效,但有些人可能会遇到问题;除非深入研究这里或网络上提供的许多链接,例如Anders Göransson的解释。

AsyncTask().execute();的行为在Android版本中已经发生了变化。在甜甜圈 (Android:1.6 API:4) 之前,任务被串行执行,从甜甜圈姜饼 (Android:2.3 API:9),任务被并行执行; 自蜂巢 (Android:3.0 API:11)起,执行被切换回顺序执行;但是为了实现并行执行,增加了一个新的方法AsyncTask().executeOnExecutor(Executor)

在顺序处理中,所有异步任务都在单个线程中运行,因此必须等待前一个任务结束。如果需要立即执行代码,则需要在单独的线程中并行处理任务。

使用AsyncTask时,Donut和Honeycomb版本之间不支持串行执行,而在Donut之前不支持并行执行。

对于Donut之后的并行处理:检查Build版本,根据此使用.execute()或.executeOnExecutor()方法。以下代码可帮助...

AsyncTask<Void,Void,Void> myTask = new AsyncTask<Void,Void,Void>() { ... }; // ... your AsyncTask code goes here
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB)
    myTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else
    myTask.execute();

注意: 函数 .executeOnExecutor() 会检查项目的 targetSdkVersion 是否小于或等于 HONEYCOMB_MR1 (Android:2.1 API:7),如果是,则强制执行程序为 THREAD_POOL_EXECUTOR(该执行程序在Honeycomb之后以顺序方式运行任务)。
如果您没有定义 targetSdkVersion,则 minSdkVersion 将自动被视为 targetSdkVersion
因此,在Honeycomb之后并行运行AsyncTask时,不能将 targetSdkVersion 留空。


1
非常好的回答。虽然Matthieu的回答也没有错,但我接受这个回答,因为你添加了许多重要信息。 - SeruK
@Nashe 非常感谢。这真的非常有帮助。我为这个问题奋斗了3天。再次感谢 :) - user3755008
4
嘿@ Nashe,我的问题有点尴尬。 在今天之前,我一直在AsyncTask上使用.execute()方法,代码完全正常工作。 但是今天我遇到了问题-控制权不会进入doInBackground()方法。 尽管您提供的解决方案有效,但我很困惑为什么之前没有解决方案也可以工作。 我现在使用的设备组和以前相同。 - Ravi Sisodia
为什么应该是这样?为什么它不按照预期工作? :( - Nikolay R
@RaviSisodia 可能你在项目中添加了另一个线程... 我也遇到过同样的情况,昨天一切正常,今天只有一个线程没有工作... 当我在整体上进行分析时,我明白我有更多的任务在做某些事情,而异步等待空间开始... - Fernando Carvalho
你救了我的一天!我因为在PagerAdapter的instantiateItem方法中使用AsyncTask下载图像而遇到了奇怪的问题。如果我在页面之间快速滑动,"doInBackground"方法就会停止被调用。你的解决方案解决了这个问题,太棒了! - Leandro Silva

9
您可以通过两种方式实现此目标: 方式1:
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB) // Above Api Level 13
  {
      asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
  }
else // Below Api Level 13
  {
      asyncTask.execute();
  }

如果方式1不适合您,请尝试方式2方式2:
int mCorePoolSize = 60;
int mMaximumPoolSize = 80;
int mKeepAliveTime = 10;
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>(mMaximumPoolSize);
Executor mCustomThreadPoolExecutor = new ThreadPoolExecutor(mCorePoolSize, mMaximumPoolSize, mKeepAliveTime, TimeUnit.SECONDS, workQueue);
asyncTask.executeOnExecutor(mCustomThreadPoolExecutor);

希望这可以帮助到您。

超级厉害,它正在工作,但是使用AsyncTask.THREAD_POOL_EXECUTOR的下一个后续异步任务失败了,所以我想我需要在整个项目中更改为CustomExecutor :( - kumar
@vinu,我建议你使用常见的异步任务和执行AsyncTask的常见方法。希望这能帮到你。 - Hiren Patel
2
嘿,这帮了我很多!谢谢! - Justin Ebby

6
我遇到了同样的问题:在第一个AsyncTask上调用“execute”后无法执行第二个AsyncTask,因为doInBackground只对第一个AsyncTask起作用。
为了回答为什么会发生这种情况,请参考此答案(根据SDK不同有不同的行为)。
但是,在你的情况下,可以通过使用executeOnExecutor(从3.0版本开始可用,在我的4.0.3版本中有效)来避免这个障碍,但要注意线程池大小和排队的限制。
你可以尝试像这样做:
xmlAsync _xmlParseThread;
dbAsync _dbLookup;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

_dbLookup = new dbAsync();
_dbLookup.execute();

_xmlParseThread = new xmlAsync();       
_xmlParseThread.executeOnExecutor(_dbLookup.THREAD_POOL_EXECUTOR
 ,"http://www.nothing.com", null);
}

针对您的更新问题:它在文档中有解释。基本上,只是为了避免多线程可能带来的所有问题,例如干扰等等...


5
我想知道一件事,这可能会解决你的问题:你在哪里实例化了你的类,并调用了execute()方法?如果你阅读AsyncTask的文档,会发现这两个操作都需要在主UI线程上执行。如果你在某个其他线程中创建对象并调用execute方法,则onPreExecute可能会被触发,但是我不确定,在后台线程将不会被创建和执行。
如果你从后台线程或某些在主UI线程之外进行的操作中创建AsyncTask的实例,你可以考虑使用方法:Activity.runOnUiThread(Runnable) 你需要访问正在运行的Activity的实例来调用该方法,但它将允许你从一些不在UI线程上运行的代码中运行代码。
希望这有意义。如果需要更多帮助,请告诉我。 David

在你的回答中添加一条信息,这个线程有一个有趣的答案:https://dev59.com/d2855IYBdhLWcg3w6Y1q。 - manjusg
感谢您提供的出色答案!我点赞是因为我认为这可能是一个常见的AsyncTask初学者问题。然而,对于这个问题来说,它并不完全正确。这两个类都是在活动的onCreate()方法中在主UI线程上实例化的。在这个项目中,我只有一个活动。 - SeruK
@manjusg 我一直认为这可能与AsyncTask不稳定有关,尤其是在同时运行多个任务时。如果是这样,为什么呢? - SeruK
我不太清楚SO在连续三次发布帖子方面的政策,但我在另一个帖子中找到了这个链接... http://foo.jasonhudgins.com/2010/05/limitations-of-asynctask.html“AsyncTask使用一个静态内部工作队列,其硬编码限制为10个元素。”这可能证明了一些东西,但我只有两个AsyncTask子类的实例!我真的很想避免使用正常的线程方法,因为最终会有很多解析工作。 - SeruK

2

Android太残酷了!我简直不敢相信,它的实现如此脆弱,一天一个样。有一天是单线程,下一天就是5个,再下一天就是128个。

总之,这里有一个几乎可以替代原始AsyncTask的解决方案。你甚至可以像原来一样调用它并取名为AsyncTask,但为了避免混淆,我们称其为ThreadedAsyncTask。你需要调用executeStart()而不是execute,因为execute()是final方法。

/**
 * @author Kevin Kowalewski
 *
 */
public abstract class ThreadedAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> { 
    public AsyncTask<Params, Progress, Result> executeStart(Params... params){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){
            return executePostHoneycomb(params);
        }else{
            return super.execute(params);
        }
    }


    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private AsyncTask<Params, Progress, Result> executePostHoneycomb(Params... params){
        return super.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params); 
    }
}

等等,你的意思不是有些版本的Android将异步操作限制在一个线程上吧?那太愚蠢了。(我有一段时间没有接触Android开发了。: )) - SeruK
是的,在Android 3.0+上,如果您不使用AsyncTask.THREAD_POOL_EXECUTOR,则只会得到一个线程池。自己尝试一下,在其中一个doInBackground中睡眠两个AsyncTask。根据Android AsyncTask文档:“从HONEYCOMB开始,任务在单个线程上执行,以避免由并行执行引起的常见应用程序错误。” - Kevin Parker

1
我知道这个帖子可能已经很晚了,但是后来的安卓模拟器为什么无法运行它是有原因的。当asynctask被引入时,安卓只允许你一次运行一个,然后在某个时候(我不确定是哪个版本),他们允许你同时运行多个asynctasks,这导致了很多应用程序出现问题,所以在Honeycomb+中,他们恢复到只允许一次运行一个asynctask,除非你手动更改线程池。希望这能为人们澄清一些事情。

0

根据Matthieu的回答,下面是一个帮助类,可以根据SDK版本正确执行您的AsyncTask,以避免在应用程序中重复代码:

import android.annotation.SuppressLint;
import android.os.AsyncTask;
import android.os.Build;

public class AsyncTaskExecutor<Params, Progress, Result> {

  @SuppressLint("NewApi")
  public AsyncTask<Params, Progress, Result> execute(final AsyncTask<Params, Progress, Result> asyncTask, final Params... params){
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){
      return asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
    }  else{
      return asyncTask.execute(params);
    }
  }

}

使用示例:

public class MyTask extends AsyncTask<Void, Void, List<String>> {

...

final MyTask myTask = new MyTask();
new AsyncTaskExecutor<Void, Void, List<String>>().execute(myTask);

0

我认为是SDK的问题。我曾经遇到过同样的问题,将目标SDK从15更改为11后,一切都完美地解决了。

使用SDK15时,即使AsyncTask.Status正在运行,doInBackground也永远不会被调用。我认为这与UI线程有关。


我既不能否认也不能确认,因为我现在没有时间测试它。我只能说的是,我当时使用的是SDK 15,所以很有可能。 - SeruK

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接