Android SDK AsyncTask doInBackground не работает (подкласс)

90

По состоянию на 15 февраля 2012 г. мне еще предстоит найти ни хорошее объяснение, ни причину, почему это не работает. Наиболее близким к решению является использование традиционного подхода Thread , но тогда зачем включать класс, который (кажется, не работает) в Android SDK?

Даже так!

У меня есть подкласс 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 () . У меня одновременно работает еще одна AsyncTask в фоновом режиме, которая работает нормально.

В настоящее время я запускаю приложение на эмуляторе 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 () более или менее добавляет строку в базу данных SQLite, инициализированную контекстом действия. publishProgress () вызывается обратным вызовом Interface ParseListener. Однако, поскольку я даже не делаю ничего, кроме log.v в doInBackground (), я сначала обнаружил, что это не нужно даже поднимать.

РЕДАКТИРОВАТЬ 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:

Вздох, извини, я не умею задавать вопросы. Но вот инициализация Задач.

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);
}
СеруК
источник
Возможно ли, что действие завершается до завершения задачи?
Пол Никонович
Очень маловероятно. В этом случае другой мой поток не будет работать правильно? А у меня сейчас только одно занятие.
SeruK 02
2
У моего приложения такая же проблема - doInBackground либо не вызывается, либо вызывается с очень большой задержкой. Вот мое ограниченное наблюдение: точно такой же код безупречно работает на смартфоне Android 2.3.3 и эмуляторе Android 2.3.3, но имеет эту проблему на планшете Android 4.0.3 и нескольких эмуляторах Android 4.xx. Очень заманчиво заключить, что эта проблема появилась в более новых версиях Android.
Гонконг,
Извините, но я забыл упомянуть, что эта проблема возникает только со второй AsyncTask действия. Первая AsyncTask всегда работает нормально.
Гонконг
Хонг, ты пробовал ответить Матье? Я в основном не пользуюсь банкоматом для игр для Android и какое-то время с ним не работал, поэтому не могу сказать, действительно ли его ответы работают. Если это не для вас, то, может быть, я плохо принял его ответ ...
SeruK

Ответы:

107

Решение Матье подойдет для большинства, но у некоторых могут возникнуть проблемы; если не копаться во многих ссылках, предоставленных здесь или в Интернете, как объяснение Андерса Йоранссона . Я пытаюсь обобщить некоторые другие чтения прямо здесь и быстро объяснить решение, если executeOnExecutor все еще работает в одном потоке ...

Поведение AsyncTask().execute();изменилось в версиях Android. До Donut (Android: 1.6 API: 4) задачи выполнялись последовательно, от Donut до Gingerbread (Android: 2.3 API: 9) задачи выполнялись параллельно; поскольку Honeycomb (Android: 3.0 API: 11) выполнение было переключено обратно на последовательное; AsyncTask().executeOnExecutor(Executor)однако был добавлен новый метод для параллельного выполнения.

При последовательной обработке все задачи Async выполняются в одном потоке и, следовательно, должны ждать до завершения предыдущей задачи. Если вам нужно выполнить код немедленно, вам нужно, чтобы задачи обрабатывались параллельно в отдельных потоках.

В AsyncTask последовательное выполнение недоступно между версиями Donut и Honeycomb, а параллельное выполнение недоступно в версиях Donut.

Для параллельной обработки после Donut: проверьте версию сборки и на основе этого используйте метод .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();

NOTE:Функция .executeOnExecutor()проверяет, targetSdkVersionменьше ли проект или равен HONEYCOMB_MR1(Android: 2.1 API: 7), а затем заставляет исполнителя быть THREAD_POOL_EXECUTOR(который последовательно запускает задачи в post Honeycomb).
Если вы не определили, targetSdkVersionто minSdkVersionавтоматически считается targetSdkVersion.
Следовательно, для параллельного запуска вашей AsyncTask на post Honeycomb вы не можете оставить targetSdkVersionпустым.

Наше
источник
1
Очень хороший ответ. Хотя Матье не ошибается, я принимаю это, поскольку вы добавляете кучу важной информации.
SeruK
@Nashe Большое спасибо. Это действительно очень полезно. Я боролся с этой же проблемой 3 дня.
Спас мой день! Желаю, чтобы этот ответ было легче найти.
zjk
4
Привет, @Nashe, моя проблема немного неловкая. До сегодняшнего дня я использовал метод .execute () в AsyncTask, и код работал отлично. Но сегодня у меня возникла проблема - управление не входит в метод doInBackground (). Хотя предложенное вами решение работает, я озадачен, как раньше оно работало без решения. Раньше и сейчас я использую один и тот же набор устройств.
Рави Сисодия
Почему так должно быть? Почему не работает согласно ожиданиям? :(
Николай Р
160

Вы должны проверить этот ответ: https://stackoverflow.com/a/10406894/347565 и ссылку на группы 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);
Матье
источник
Я очень плохо оглядывался на этот пост; Я давно ушел из проекта. Я просто приму это как ответ, поскольку люди, кажется, говорят, что это работает.
SeruK
это сработало для меня только сейчас, но я хотел бы понять, почему. он работал нормально с исполнением до того, как остановился. одна вещь, которую я делаю, - это запуск новой asynctask внутри onPostExecute той же asynctask (т.е. я вызываю ее рекурсивно), может быть, это связано с проблемой?
Стивен
Я думаю, это должно дать вам все необходимые объяснения: commonsware.com/blog/2012/04/20/…
Matthieu
1
@Matthieu: Сэр, я действительно не могу вас отблагодарить !!! Я часами ломал голову над этой проблемой, и ваше решение заставило все работать как шарм! Большое спасибо за фантастический ответ. Хотел бы я дать больше одного голоса !!
Swayam
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);

Надеюсь, что это поможет вам.

Хирен Патель
источник
супер, чувак, он работает, но следующие последующие задачи Async, которые используют AsyncTask.THREAD_POOL_EXECUTOR, терпят неудачу, поэтому мне нужно изменить CustomExecutor во всем проекте, я думаю :(
kumar
@vinu, я предлагаю вам использовать общую задачу async и общий метод для выполнения AsyncTask. Надеюсь, что это поможет вам.
Hiren Patel
2
Эй, это мне очень помогло! Благодарность!
Джастин
6

У меня была такая же проблема: не могу выполнить вторую AsyncTask после того, как я вызвал «выполнить» для первой: doInBackground вызывается только для первой.

Чтобы ответить, почему это происходит, проверьте этот ответ (разное поведение в зависимости от 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);
}

Для вашего вопроса об обновлении: это объяснено в документации. В основном, чтобы избежать всех проблем, которые могут возникнуть из-за многопоточности, такой как взаимодействие ....

code7amza
источник
5

Одна вещь, которую я хотел бы знать, и это может действительно решить вашу проблему, - это где вы создаете экземпляр своего класса и вызываете метод execute ()? Если вы читаете документацию по AsyncTask, обе эти операции должны выполняться в основном потоке пользовательского интерфейса. Если вы создаете свой объект и вызываете execute из какого-то другого потока, то onPreExecute может сработать, я не уверен на 100% здесь, но фоновый поток не будет создан и выполнен.

Если вы создаете экземпляр своей AsyncTask из фонового потока или какую-либо другую операцию, не выполняемую в основном потоке пользовательского интерфейса, вы можете рассмотреть возможность использования метода: Activity.runOnUiThread (Runnable)

Для вызова этого метода вам понадобится доступ к экземпляру вашего запущенного Activity, но он позволит вам запускать код в потоке пользовательского интерфейса из другого кода, который не выполняется в потоке пользовательского интерфейса.

Надеюсь, это имеет смысл. Дай мне знать, если я смогу помочь.

Дэвид

Дэвид С. Сент-Клер
источник
добавив к вашему ответу, в этой ветке есть интересный ответ stackoverflow.com/questions/4080808/… .
manjusg 03
Спасибо за отличный ответ! Я увеличил это, потому что думаю, что это может быть обычная проблема для начинающих с AsyncTask. Увы, это не совсем правильный ответ на этот вопрос. Оба класса создаются в onCreate () операции, выполняемой в основном потоке пользовательского интерфейса. В этом проекте у меня только одно мероприятие.
SeruK 03
@manjusg Я все время считал, что это как-то связано с нестабильностью AsyncTask, возможно, даже более того, когда несколько запускаются одновременно. Если да, то почему?
SeruK 03
Я действительно не знаю, какие политики SO использует для быстрой публикации три раза подряд, но я нашел это в другом потоке ... foo.jasonhudgins.com/2010/05/limitations-of-asynctask.html "AsyncTask использует статическая внутренняя рабочая очередь с жестко заданным пределом в 10 элементов ". Это может что-то доказать, но у меня всего два экземпляра подклассов AsyncTask! Я бы очень хотел избежать использования обычных методов многопоточности, поскольку в конечном итоге в dere будет выполняться много анализа.
SeruK 03 фев.12,
2

Android жесток! Я не могу поверить в это, какая беспорядочная реализация, которая меняется со дня на день. Сегодня это один поток, на следующий - 5, другой - 128.

В любом случае, это почти замена стандартной AsyncTask. Вы даже можете назвать его AsyncTask, если хотите, но, чтобы избежать путаницы, он называется ThreadedAsyncTask. Вам нужно вызвать executeStart () вместо execute, потому что execute () является окончательным.

/**
 * @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 3.0+, если вы не используете AsyncTask.THREAD_POOL_EXECUTOR, вы получаете только пул потоков из одного. Попробуйте сами, две AsyncTask и просто спите в одной doInBackground. Из документации Android AsyncTask: «Начиная с HONEYCOMB, задачи выполняются в одном потоке, чтобы избежать распространенных ошибок приложений, вызванных параллельным выполнением».
Кевин Паркер
1

Я знаю, что это может быть очень поздно для обсуждения, но есть причина, по которой он не будет работать в более поздних эмуляторах Android. Когда была представлена ​​asynctask, Android позволял запускать только по одному, а когда-то позже, я не уверен, в какой версии они позволяли запускать сразу несколько asynctasks, это вызывало проблемы во многих приложениях, поэтому в Honeycomb + они вернулись только к позволяя запускать одну асинхронную задачу за раз. Если вы вручную не измените пул потоков. Надеюсь, что это прояснит кое-что для людей.

Fre AkyTurtle-Records
источник
0

Я думаю, что это SDK. У меня была такая же проблема, и после изменения целевого sdk с 15 на 11 все работает отлично.

с sdk15, даже если AsyncTask.Status находится в состоянии RUNNING, doInBackground никогда не вызывается. Я действительно думаю, что это как-то связано с потоком пользовательского интерфейса.

фоб
источник
Я не могу ни опровергнуть, ни подтвердить, потому что сейчас у меня нет времени проверять это. Все, что я могу сказать, это то, что я использовал SDK 15, так что это очень вероятно.
SeruK
0

Основываясь на ответе Матье, ниже вспомогательный класс для AsyncTaskправильного выполнения в зависимости от версии SDK, чтобы избежать дублирования кода в вашем приложении:

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);
LG
источник