суббота, 9 марта 2013 г.

AsyncTask. Максимальное количество одновременных потоков.

Вступление

В настоящее время операционная система Android переживает бурное развитие, что в то же время негативно сказывается на постоянстве результата работы одних и тех же классов и методов в разных версиях этой системы. Одним из примеров такого непостоянства может служить класс асинхронных задач AsyncTask.

Асинхронные задачи - это потоки, выполняемые параллельно с основным потоком, в котором работает пользовательский интерфейс приложения, и никак не синхронизированные с ним. Это значит, что поток пользовательского интерфейса, запустив такую асинхронную задачу, не знает, когда в него вернётся результат её работы. Плюсом такой архитектуры является постоянная доступность пользовательского интерфейса для взаимодействия. Он не подвисает, ни замирает, ожидая окончания работы какой-либо длительной задачи, а продолжает работать.

Минусом такого подхода является сложность в возврате результата работы асинхронной задачи в пользовательский интерфейс. Поскольку интерфейс не знает, когда асинхронный поток закончит работу, поток должен сам производить вывод результата своей работы в интерфейс, как только этот результат у него будет готов. Кроме того, он должен иметь возможность периодически сообщать интерфейсу о ходе своей работы (например, двигать прогрессбар).

Всё это осуществляется соответствующими классами (Thread, ThreadPoolExecutor, Runnable) и приёмами программирования, но выглядит относительно сложно, поскольку во избежание конфликтов потоков просто так нельзя из них манипулировать интерфейсом. Приходится прибегать к дополнительным конструкциям. Вот в этом случае и помогает AsyncTask.

Класс AsyncTask является обёрткой над классами потоков и облегчает работу с ними. Класс содержит несколько переопределяемых методов, из которых обязательным для переопределения является метод doInBackground(), содержимое которого, собственно, и будет выполняться в отдельном потоке. Также достаточно часто используемыми являются методы onPreExecute(), onProgressUpdate() и onPostExecute(), вызываемые перед запуском потока, по команде из потока, и после окончания работы потока соответственно, из которых, собственно, и происходит манипулирование интерфейсом (мы ведь помним, что непосредственно из потока манипулировать интерфейсом нельзя).


Особенности работы AsyncTask в разных версиях Android.

До версии Android 1.5 (т.е. до API 3) AsyncTask вообще назывался UserTask. Вплоть до версии 1.6 (т.е. до API 4) его поведение было следующим. При создании и запуске нескольких объектов класса AsyncTask на выполнение, они все выполняли переопределённое содержимое своего метода onPreExecute(), выполняемого перед запуском потока, а их методы doInBackground() вставали в очередь. В каждый момент времени выполнялся только один метод doInBackground(), а остальные ждали завершения работы предыдущего.

Таким образом, фактически существовал один длинный поток, представляющий собой несколько мелких потоков, выстроившихся в очередь. Размер такой очереди был ограничен только наличием свободной памяти в телефоне.
Такой вариант последовательной работы был во многих случаях удобен, поскольку всегда было известно, что результаты работы появятся в той же последовательности, в которой потоки были запущены. Но это было не очень правильно с точки зрения многопоточности как таковой.

Начиная с версии 1.6 (API 4) логика работы AsyncTask была изменена. Теперь первые 5 потоков стали запускаться параллельно, и только начиная с 6-го, они начинали становиться в очередь. Если очередь, ограниченная 10 потоками, заканчивалась (5 запущенных потоков +10 в очереди), 16-й и каждый последующий потоки начинали выполняться параллельно остальным запущенным, а те 10 продолжали ждать своей очереди, но уже могли рассчитывать на места любых из уже работающих параллельно потоков (пяти и более). Максимальное ограничение количества одновременно работающих потоков — 128. Таким образом, можно запустить 138 потоков (128 + 10 в очереди). При превышении этого количества вызывается исключение RejectedExecutionException.

Помимо вышеперечисленных, в AsyncTask появился ещё один внутренний параметр, влияющий на его поведение. Это интервал, в течение которого резервируется место для выполнения новых параллельных потоков после завершения работы дополнительных параллельных потоков. То есть, места 6-го и последующих параллельно выполнявшихся потоков (если таковые были) не сокращаются снова до пяти, а предоставляются потокам из очереди или вновь появившимся потокам, если они успели появиться в течение этого интервала. До версии Android 2.3 (до API 9) этот интервал был равен 10 секундам, а начиная с версии 2.3 (API 9) — 1 секунда.

Практика программирования показала, что выгода от использования параллельности затмевалась валом ошибок, допускаемых программистами при реализации своих многопоточных алгоритмов, и, начиная с версии Android 3.0 (API 11), разработчики вернули прежнее поведение AsyncTask, хотя и добавили помимо стандартного метода execute() новый метод executeOnExecutor(), позволяющий всё же реализовывать прежнее параллельное поведение.

Возможно, важную роль в отказе от параллельного выполнения потоков в Android сыграли какие-то не совсем удобные для использования многопоточности особенности системы, а может быть, дело в низкой квалификации большого процента программистов, ринувшихся на новую платформу. Так или иначе, но при выполнении метода execute() запущенные потоки объектов AsyncTask снова вставали в очередь, которая теперь ограничивалась 128 потоками.

Всё это даёт разное поведение программ, активно использующих множественные вызовы потоков. Если один из потоков застревает, другие просто не выполняются. И это, однозначно, плохо. Конечно, в новых версиях можно использовать метод executeOnExecutor() с параметром AsyncTask.THREAD_POOL_EXECUTOR для параллельного выполнения или с параметром AsyncTask.SERIAL_EXECUTOR для последовательного выполнения потоков аналогично методу execute(). Но как быть, если требуется совместимость программы со старыми версиями, где этого метода ещё не было?


Свой класс AsyncTask

Можно написать свой класс AsyncTask, а точнее, взять его из новых версий Android и, если вы оставляете совместимость с Android 2.2 (API 8) и младше, убрать оттуда возможность последовательного выполнения потоков, поскольку он использует метод ArrayDeque, появившийся только в Android 2.3 (API 9), или же, использовать эту возможность в версиях Android с 2.3 до 3.0, где ранее её не было:

package com.example.testasynctask;

/**
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//import java.util.ArrayDeque;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import android.os.Handler;
import android.os.Message;
import android.os.Process;

/**
 * ### I delete this comments as it make the answer too long to submit ###
 */
public abstract class AsyncTask<Params, Progress, Result> {
    private static final String LOG_TAG = "AsyncTask";

    private static final int CORE_POOL_SIZE = 10;
    private static final int MAXIMUM_POOL_SIZE = 128;
    private static final int KEEP_ALIVE = 1;

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

    /**
     * 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);

    /**
     * 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();

    private static final int MESSAGE_POST_RESULT = 0x1;
    private static final int MESSAGE_POST_PROGRESS = 0x2;

    private static final InternalHandler sHandler = new InternalHandler();

    //    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
    private static volatile Executor sDefaultExecutor = THREAD_POOL_EXECUTOR;
    private final WorkerRunnable<Params, Result> mWorker;
    private final FutureTask<Result> mFuture;

    private volatile Status mStatus = Status.PENDING;

    private final AtomicBoolean mTaskInvoked = new AtomicBoolean();

    //    private static class SerialExecutor implements Executor {
    //        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
    //        Runnable mActive;
    //
    //        public synchronized void execute(final Runnable r) {
    //            mTasks.offer(new Runnable() {
    //                public void run() {
    //                    try {
    //                        r.run();
    //                    } finally {
    //                        scheduleNext();
    //                    }
    //                }
    //            });
    //            if (mActive == null) {
    //                scheduleNext();
    //            }
    //        }
    //
    //        protected synchronized void scheduleNext() {
    //            if ((mActive = mTasks.poll()) != null) {
    //                THREAD_POOL_EXECUTOR.execute(mActive);
    //            }
    //        }
    //    }

    /**
     * Indicates the current status of the task. Each status will be set only once
     * during the lifetime of a task.
     */
    public enum Status {
        /**
         * Indicates that the task has not been executed yet.
         */
        PENDING,
        /**
         * Indicates that the task is running.
         */
        RUNNING,
        /**
         * Indicates that {@link AsyncTask#onPostExecute} has finished.
         */
        FINISHED,
    }

    /** @hide Used to force static handler to be created. */
    public static void init() {
        sHandler.getLooper();
    }

    /** @hide */
    public static void setDefaultExecutor(Executor exec) {
        sDefaultExecutor = exec;
    }

    /**
     * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
     */
    public AsyncTask() {
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);

                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                return postResult(doInBackground(mParams));
            }
        };

        mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {
                try {
                    final Result result = get();

                    postResultIfNotInvoked(result);
                } 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);
                } catch (Throwable t) {
                    throw new RuntimeException("An error occured while executing "
                            + "doInBackground()", t);
                }
            }
        };
    }

    private void postResultIfNotInvoked(Result result) {
        final boolean wasTaskInvoked = mTaskInvoked.get();
        if (!wasTaskInvoked) {
            postResult(result);
        }
    }

    private Result postResult(Result result) {
        Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();
        return result;
    }

    /**
     * Returns the current status of this task.
     * 
     * @return The current status.
     */
    public final Status getStatus() {
        return mStatus;
    }

    /**
     * Override this method to perform a computation on a background thread. The
     * specified parameters are the parameters passed to {@link #execute} by the caller of this task.
     * 
     * This method can call {@link #publishProgress} to publish updates
     * on the UI thread.
     * 
     * @param params The parameters of the task.
     * 
     * @return A result, defined by the subclass of this task.
     * 
     * @see #onPreExecute()
     * @see #onPostExecute
     * @see #publishProgress
     */
    protected abstract Result doInBackground(Params... params);

    /**
     * Runs on the UI thread before {@link #doInBackground}.
     * 
     * @see #onPostExecute
     * @see #doInBackground
     */
    protected void onPreExecute() {
    }

    /**
     * <p>
     * Runs on the UI thread after {@link #doInBackground}. The specified result is the value returned by {@link #doInBackground}.
     * </p>
     * 
     * <p>
     * This method won't be invoked if the task was cancelled.
     * </p>
     * 
     * @param result The result of the operation computed by {@link #doInBackground}.
     * 
     * @see #onPreExecute
     * @see #doInBackground
     * @see #onCancelled(Object)
     */
    @SuppressWarnings({ "UnusedDeclaration" })
    protected void onPostExecute(Result result) {
    }

    /**
     * Runs on the UI thread after {@link #publishProgress} is invoked.
     * The specified values are the values passed to {@link #publishProgress}.
     * 
     * @param values The values indicating progress.
     * 
     * @see #publishProgress
     * @see #doInBackground
     */
    @SuppressWarnings({ "UnusedDeclaration" })
    protected void onProgressUpdate(Progress... values) {
    }

    /**
     * <p>
     * Runs on the UI thread after {@link #cancel(boolean)} is invoked and {@link #doInBackground(Object[])} has finished.
     * </p>
     * 
     * <p>
     * The default implementation simply invokes {@link #onCancelled()} and ignores the result. If you write your own implementation, do not call <code>super.onCancelled(result)</code>.
     * </p>
     * 
     * @param result The result, if any, computed in {@link #doInBackground(Object[])}, can be null
     * 
     * @see #cancel(boolean)
     * @see #isCancelled()
     */
    @SuppressWarnings({ "UnusedParameters" })
    protected void onCancelled(Result result) {
        onCancelled();
    }

    /**
     * <p>
     * Applications should preferably override {@link #onCancelled(Object)}. This method is invoked by the default implementation of {@link #onCancelled(Object)}.
     * </p>
     * 
     * <p>
     * Runs on the UI thread after {@link #cancel(boolean)} is invoked and {@link #doInBackground(Object[])} has finished.
     * </p>
     * 
     * @see #onCancelled(Object)
     * @see #cancel(boolean)
     * @see #isCancelled()
     */
    protected void onCancelled() {
    }

    /**
     * Returns <tt>true</tt> if this task was cancelled before it completed
     * normally. If you are calling {@link #cancel(boolean)} on the task,
     * the value returned by this method should be checked periodically from {@link #doInBackground(Object[])} to end the task as soon as possible.
     * 
     * @return <tt>true</tt> if task was cancelled before it completed
     * 
     * @see #cancel(boolean)
     */
    public final boolean isCancelled() {
        return mFuture.isCancelled();
    }

    /**
     * <p>
     * Attempts to cancel execution of this task. This attempt will fail if the task has already completed, already been cancelled, or could not be cancelled for some other reason. If successful, and this task has not started when <tt>cancel</tt> is
     * called, this task should never run. If the task has already started, then the <tt>mayInterruptIfRunning</tt> parameter determines whether the thread executing this task should be interrupted in an attempt to stop the task.
     * </p>
     * 
     * <p>
     * Calling this method will result in {@link #onCancelled(Object)} being invoked on the UI thread after {@link #doInBackground(Object[])} returns. Calling this method guarantees that {@link #onPostExecute(Object)} is never invoked. After invoking
     * this method, you should check the value returned by {@link #isCancelled()} periodically from {@link #doInBackground(Object[])} to finish the task as early as possible.
     * </p>
     * 
     * @param mayInterruptIfRunning <tt>true</tt> if the thread executing this
     *        task should be interrupted; otherwise, in-progress tasks are allowed
     *        to complete.
     * 
     * @return <tt>false</tt> if the task could not be cancelled,
     *         typically because it has already completed normally; <tt>true</tt> otherwise
     * 
     * @see #isCancelled()
     * @see #onCancelled(Object)
     */
    public final boolean cancel(boolean mayInterruptIfRunning) {
        return mFuture.cancel(mayInterruptIfRunning);
    }

    /**
     * Waits if necessary for the computation to complete, and then
     * retrieves its result.
     * 
     * @return The computed result.
     * 
     * @throws CancellationException If the computation was cancelled.
     * @throws ExecutionException If the computation threw an exception.
     * @throws InterruptedException If the current thread was interrupted
     *         while waiting.
     */
    public final Result get() throws InterruptedException, ExecutionException {
        return mFuture.get();
    }

    /**
     * Waits if necessary for at most the given time for the computation
     * to complete, and then retrieves its result.
     * 
     * @param timeout Time to wait before cancelling the operation.
     * @param unit The time unit for the timeout.
     * 
     * @return The computed result.
     * 
     * @throws CancellationException If the computation was cancelled.
     * @throws ExecutionException If the computation threw an exception.
     * @throws InterruptedException If the current thread was interrupted
     *         while waiting.
     * @throws TimeoutException If the wait timed out.
     */
    public final Result get(long timeout, TimeUnit unit) throws InterruptedException,
            ExecutionException, TimeoutException {
        return mFuture.get(timeout, unit);
    }

    /**
     * Executes the task with the specified parameters. The task returns
     * itself (this) so that the caller can keep a reference to it.
     * 
     * <p>
     * Note: this function schedules the task on a queue for a single background thread or pool of threads depending on the platform version. When first introduced, AsyncTasks were executed serially on a single background thread. Starting with
     * {@link android.os.Build.VERSION_CODES#DONUT}, this was changed to a pool of threads allowing multiple tasks to operate in parallel. After {@link android.os.Build.VERSION_CODES#HONEYCOMB}, it is planned to change this back to a single thread to
     * avoid common application errors caused by parallel execution. If you truly want parallel execution, you can use the {@link #executeOnExecutor} version of this method with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for
     * warnings on its use.
     * 
     * <p>
     * This method must be invoked on the UI thread.
     * 
     * @param params The parameters of the task.
     * 
     * @return This instance of AsyncTask.
     * 
     * @throws IllegalStateException If {@link #getStatus()} returns either {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
     */
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }

    /**
     * Executes the task with the specified parameters. The task returns
     * itself (this) so that the caller can keep a reference to it.
     * 
     * <p>
     * This method is typically used with {@link #THREAD_POOL_EXECUTOR} to allow multiple tasks to run in parallel on a pool of threads managed by AsyncTask, however you can also use your own {@link Executor} for custom behavior.
     * 
     * <p>
     * <em>Warning:</em> Allowing multiple tasks to run in parallel from a thread pool is generally <em>not</em> what one wants, because the order of their operation is not defined. For example, if these tasks are used to modify any state in common
     * (such as writing a file due to a button click), there are no guarantees on the order of the modifications. Without careful work it is possible in rare cases for the newer version of the data to be over-written by an older one, leading to
     * obscure data loss and stability issues. Such changes are best executed in serial; to guarantee such work is serialized regardless of platform version you can use this function with {@link #SERIAL_EXECUTOR}.
     * 
     * <p>
     * This method must be invoked on the UI thread.
     * 
     * @param exec The executor to use. {@link #THREAD_POOL_EXECUTOR} is available as a
     *        convenient process-wide thread pool for tasks that are loosely coupled.
     * @param params The parameters of the task.
     * 
     * @return This instance of AsyncTask.
     * 
     * @throws IllegalStateException If {@link #getStatus()} returns either {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
     */
    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
        if (mStatus != Status.PENDING) {
            switch (mStatus) {
                case RUNNING:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task is already running.");
                case FINISHED:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task has already been executed "
                            + "(a task can be executed only once)");
            }
        }

        mStatus = Status.RUNNING;

        onPreExecute();

        mWorker.mParams = params;
        exec.execute(mFuture);

        return this;
    }

    /**
     * Convenience version of {@link #execute(Object...)} for use with
     * a simple Runnable object.
     */
    public static void execute(Runnable runnable) {
        sDefaultExecutor.execute(runnable);
    }

    /**
     * This method can be invoked from {@link #doInBackground} to
     * publish updates on the UI thread while the background computation is
     * still running. Each call to this method will trigger the execution of {@link #onProgressUpdate} on the UI thread.
     * 
     * {@link #onProgressUpdate} will note be called if the task has been
     * canceled.
     * 
     * @param values The progress values to update the UI with.
     * 
     * @see #onProgressUpdate
     * @see #doInBackground
     */
    protected final void publishProgress(Progress... values) {
        if (!isCancelled()) {
            sHandler.obtainMessage(MESSAGE_POST_PROGRESS,
                    new AsyncTaskResult<Progress>(this, values)).sendToTarget();
        }
    }

    private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }

    private static class InternalHandler extends Handler {
        @SuppressWarnings({ "unchecked", "RawUseOfParameterizedType" })
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult result = (AsyncTaskResult) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // There is only one result
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }

    private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
        Params[] mParams;
    }

    @SuppressWarnings({ "RawUseOfParameterizedType" })
    private static class AsyncTaskResult<Data> {
        final AsyncTask mTask;
        final Data[] mData;

        AsyncTaskResult(AsyncTask task, Data... data) {
            mTask = task;
            mData = data;
        }
    }
}
Сам код небольшой, основной объём занимают комментарии. После добавления класса в проект просто удалите или закомментируйте в вашем коде импорт: import android.os.AsyncTask; и новый класс будет использоваться вместо штатного.

Кроме всего прочего, теперь мы можем сами менять поведение AsyncTask путём манипулирования его константами прямо в коде класса AsyncTask:
    private static final int CORE_POOL_SIZE = 5;
    private static final int MAXIMUM_POOL_SIZE = 128;
    private static final int KEEP_ALIVE = 1;
Можем увеличить количество изначально одновременно выполняющихся потоков с 5 до, например, 10. Можем увеличить максимально возможное количество параллельно выполняющихся потоков (хотя это вряд ли нам может пригодиться), а также время резервирования места под новые потоки. Кроме того, мы можем изменить размер очереди с 10 на любое другое число в строке:
private static final BlockingQueue<Runnable> sPoolWorkQueue =
   new LinkedBlockingQueue<Runnable>(10);

Простейшее приложение для тестирования работы потоков

Для наглядного изучения поведения потоков можно написать следующее приложение. Создадим приложение Android с активити, содержащей одну кнопку, которой мы будем запускать новые потоки:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/LinearLayout1"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <Button
        android:id="@+id/button1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="onClick"
        android:text="Добавить новый поток" />

</LinearLayout>
В коде обработаем нажатие на кнопку. По нажатию будем создавать и запускать новый поток, который будет создавать новое TextView, устанавливать его в Layout и в течение заданного времени выдавать в него каждую секунду увеличивающееся на единицу число, после чего TextView будет уничтожаться:
package com.example.testasynctask;

import android.app.Activity;
//import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.TextView;

public class MainActivity extends Activity implements OnClickListener { // Наследуем класс Activity и добавим интерфейс OnClickListener  
    LinearLayout linearLayout;                                          // Переменная для хранения ссылки на LinearLayout

    // Создадим активити
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);                     // Вызовем родительский класс создания активити
        setContentView(R.layout.activity_main);                 // Создадим интерфейс согласно разметке в xml-файле
        linearLayout = (LinearLayout) findViewById(R.id.LinearLayout1); // Найдём в интерфейсе LinearLayout  
    }                                                                   // для добавления в него в дальнейшем TextView

    // Переопределим метод интерфейса OnClickListener для обработки нажатия кнопки
    @Override
    public void onClick(View view) {
        (new AsyncTask<Integer, Integer, Void>() {              // Создадим новый объект AsyncTask по классу, описанному прямо здесь 
            TextView textView;                                  // Переменная для хранения ссылки на созданный TextView

            // Выполняется в потоке интерфейса сразу после запуска AsyncTask перед выполнением потока
            @Override
            protected void onPreExecute() {
                super.onPreExecute();                           // Вызовем родительский метод
                textView = new TextView(MainActivity.this);     // Создадим TextView
                // Создадим объект для хранения параметров разметки TextView и зададим их
                LayoutParams lpView = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
                textView.setLayoutParams(lpView);               // Присвоим параметры TextView
                linearLayout.addView(textView);                 // Добавим созданное TextView в LinearLayout
                textView.setText("...");                        // Выведем многоточие в TextView
            }

            // Выполняется в отдельном потоке
            @Override
            protected Void doInBackground(Integer... arg) {     
                for (int i = arg[0]; i >= 0; i--) {             // Цикл с количеством итераций, заданным при запуске потока
                    publishProgress(i);                         // Запустить метод onProgressUpdate, выводящий число i в TextView
                    try {
                        Thread.sleep(1000);                     // Задержка в 1000 миллисекунд
                    } catch (InterruptedException e) {
                    }
                }
                return null;
            }

            // Выполняется в потоке интерфейса по команде publishProgress(), дающейся в отдельном потоке
            @Override
            protected void onProgressUpdate(Integer... values) {
                super.onProgressUpdate(values);                 // Вызовем родительский метод
                textView.setText(values[0].toString());         // Вывести число i в TextView
            }

            // Выполняется в потоке интерфейса после окончания работы отдельного потока
            @Override
            protected void onPostExecute(Void arg) {
                super.onPostExecute(arg);                       // Вызовем родительский метод
                linearLayout.removeView(textView);              // Удалим TextView из интерфейса
            }

        }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, 20);   // Выполнить объект асинхронного потока в параллельном режиме,
    }                                                               // передав ему число 20 в качестве параметра количества циклов
}
Добавим в приложение нашу реализацию класса AsyncTask, запустим его, и будем нажимать на кнопку. Мы увидим, как после каждого нажатия под кнопкой будет появляться новая строчка с бегущим числом. Числа в новых строчках будут бежать, пока количество строчек не достигнет константы количества одновременно запущенных потоков CORE_POOL_SIZE, по умолчанию равняющейся 5. Затем начнут появляться строчки с многоточиями — это потоки, стоящие в очереди. Их будет 10 штук. Затем снова начнут появляться строчки с бегущими цифрами, потому что очередь переполнена. Всё так, как и было описано мной ранее.

Комментариев нет:

Отправить комментарий