Android SDK AsyncTask doInBackground не працює (підклас)


90

Станом на 15.02.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 () більш-менш додає рядок до бази даних SQLiteData, ініціалізованої контекстом діяльності. опублікуватиProgress () викликається зворотним викликом 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();
    }
}

EDIT 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

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. До того, як " Пончик" (Android: 1.6 API: 4) завдання виконувались послідовно, від " Пончика" до " Пряника" (Android: 2.3 API: 9), завдання виконувались паралельно; з тих пір, як Honeycomb (Android: 3.0 API: 11) виконання було повернуто назад до послідовного; AsyncTask().executeOnExecutor(Executor)однак новий метод був доданий для паралельного виконання.

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

З 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();

NOTE:Функція .executeOnExecutor()має перевірку, якщо targetSdkVersionпроект менше або дорівнює HONEYCOMB_MR1(Android: 2.1 API: 7), тоді вона змушує бути виконавцем THREAD_POOL_EXECUTOR(який виконує Завдання послідовно в стільниковій стільниковій мережі).
Якщо ви не визначили, targetSdkVersionтоді minSdkVersionавтоматично вважається targetSdkVersion.
Отже, паралельно запускаючи AsyncTask на стільниковій стільниковій мережі, ви не можете залишити targetSdkVersionпорожнім.


1
Дуже хороша відповідь. Хоча Matthieu's не помиляється, я приймаю це, оскільки ви додаєте купу важливої ​​інформації.
SeruK

@Nashe Велике спасибі. Це справді дуже корисно. Я боровся з цим самим питанням протягом 3 днів. Ще раз спасибі :)

Врятував мій день! Бажаю, щоб цю відповідь було легше знайти.
zjk

4
Привіт @Nashe, моя проблема трохи незручна. До сьогодні я використовував метод .execute () на AsyncTask, і код працював ідеально. Але сьогодні у мене проблема - елемент керування не переходить у метод doInBackground (). Хоча рішення, яке ви надаєте, працює, я здивований, як він працював раніше без рішення. Я використовую однаковий набір Пристроїв раніше і зараз.
Ravi Sisodia

Чому так повинно бути? Чому це не працює відповідно до очікувань? :(
Nikolay R

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

це у мене спрацювало зараз, але я хотів би зрозуміти, чому. до того він зупинився. одна річ, яку я роблю, - це запуск нового асинктаску всередині onPostExecute того самого асинктаску (тобто я його називаю рекурсивно), можливо, це пов'язано з проблемою?
steveh

Я думаю , що це повинно дати вам все пояснення вам потрібно: commonsware.com/blog/2012/04/20 / ...
Матьє

1
@Matthieu: Сер, я справді не можу вам подякувати !!! Я годинами бився головою про цю проблему, і ваше рішення змусило все працювати як шарм! Щиро дякую за фантастичну відповідь. Я хотів би дати більше, ніж один голос !!
Сваям,


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, я пропоную вам використовувати загальне завдання асинхронізації та загальний метод для виконання 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);
}

Для вашого запитання щодо оновлення: це пояснюється в документації В основному лише для того, щоб уникнути всіх проблем, які можуть виникати в результаті багатопоточності, як-от взаємодія ....


5

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

Якщо ви створюєте екземпляр вашого AsyncTask з фонового потоку або якоїсь іншої операції, що не відбувається в основному потоці інтерфейсу користувача, ви можете розглянути можливість використання методу: Activity.runOnUiThread (Runnable)

Для виклику цього методу вам знадобиться доступ до екземпляра вашої запущеної діяльності, але це дозволить вам запускати код у потоці інтерфейсу користувача з іншого коду, який не працює в потоці інтерфейсу користувача.

Сподіваюся, що це має сенс. Повідомте мене, чи можу я допомогти більше.

Девід


додаючи до вашої відповіді, у цій темі є цікава відповідь stackoverflow.com/questions/4080808/… .
manjusg

Дякуємо за чудову відповідь! Я доповнив це, оскільки вважаю, що це може бути типовою проблемою для початківців AsyncTask. На жаль, це не зовсім правильна відповідь на це питання. Обидва класи екземпляруються в операції onCreate (), що виконується в основному потоці інтерфейсу користувача. У цьому проекті я маю лише одну діяльність.
SeruK

@manjusg Я весь час вважав, що це пов’язано з тим, що AsyncTask нестабільний, можливо, надто багато, коли одночасно запускається кілька. Якщо так, то чому?
SeruK

Я насправді не знаю, яку політику SO так швидко публікує тричі поспіль, але я знайшов це в іншому потоці ... foo.jasonhudgins.com/2010/05/limitations-of-asynctask.html "AsyncTask використовує статична внутрішня черга з жорстко закодованим обмеженням до 10 елементів. " Це може щось довести, але я просто маю два екземпляри підкласів AsyncTask! Я б дуже хотів уникати використання звичайних методів різьблення, оскільки з часом буде проведено багато розбору в дере.
SeruK

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. :))
SeruK

Так, на Android 3.0+, якщо ви не використовуєте AsyncTask.THREAD_POOL_EXECUTOR, ви отримуєте лише пул потоків з одного. Спробуйте самі, два AsyncTask і просто поспіть у своєму doInBackground. З документації Android AsyncTask: "Починаючи з HONEYCOMB, завдання виконуються в одному потоці, щоб уникнути типових помилок додатків, викликаних паралельним виконанням."
Kevin Parker

1

Я знаю, що це може запізнитись для потоку, але є причина, чому це не буде працювати на пізніших емуляторах Android. Коли asynctask був введений, андроїд дозволяв вам запускати лише по одній, то десь пізніше, я не впевнений, яку версію, вони дозволили запускати кілька асинктазків одночасно, це спричинило проблеми у багатьох додатках, і тому в Honeycomb + вони повернулися до дозволяючи одночасно запускати один асинктаск. Якщо ви вручну не змінюєте пул потоків. Сподівання, що прояснить одну чи дві речі для людей.


0

я думаю це його sdk. у мене була та сама проблема, і після зміни цільового sdk з 15 на 11 все працює ідеально.

з sdk15, навіть незважаючи на те, що AsyncTask.Status РОБІТЬ, 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);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.