Android: передать функцию функции AsyncTask

Я новичок в android и очень привык к веб-разработке. в javascript, когда вы хотите выполнить асинхронную задачу, вы передаете функцию в качестве аргумента (обратный вызов):

http.get('www.example.com' , function(response){
   //some code to handle response
});

Мне было интересно, можем ли мы сделать то же самое с android AsyncTask, передаем ссылку функции на метод onPostExecute(), и он запустит ее.

любые предложения?

Ответы

Ответ 1

Да, концепция обратных вызовов также существует на Java. В Java вы определяете обратный вызов следующим образом:

public interface TaskListener {
    public void onFinished(String result);
}

Внутри AsyncTask часто бывают такие определения слушателей:

public class ExampleTask extends AsyncTask<Void, Void, String> {

    public interface TaskListener {
        public void onFinished(String result);
    }

    ...
}

И полная реализация обратного вызова в AsyncTask будет выглядеть так:

public class ExampleTask extends AsyncTask<Void, Void, String> {

    public interface TaskListener {
        public void onFinished(String result);
    }

    // This is the reference to the associated listener
    private final TaskListener taskListener;

    public ExampleTask(TaskListener listener) {
        // The listener reference is passed in through the constructor
        this.taskListener = listener;
    }

    @Override
    protected String doInBackground(Void... params) {
        return doSomething();
    }

    @Override
    protected void onPostExecute(String result) {
        super.onPostExecute(result);

        // In onPostExecute we check if the listener is valid
        if(this.taskListener != null) {

            // And if it is we call the callback function on it.
            this.taskListener.onFinished(result);
        }
    }
}

onPostExecute() вызывается, как только заканчивается фоновое задание. Вы можете использовать все это так:

ExampleTask task = new ExampleTask(new ExampleTask.TaskListener() {
    @Override
    public void onFinished(String result) {
        // Do Something after the task has finished
    }
});

task.execute();

Или вы можете полностью определить TaskListener следующим образом:

ExampleTask.TaskListener listener = new ExampleTask.TaskListener() {
    @Override
    public void onFinished(String result) {
        // Do Something after the task has finished
    }
};

ExampleTask task = new ExampleTask(listener);    
task.execute();

Или вы можете подклассом TaskListener следующим образом:

public class ExampleTaskListener implements TaskListener {

    @Override
    public void onFinished(String result) {

    }
}

И затем используйте его следующим образом:

ExampleTask task = new ExampleTask(new ExampleTaskListener());    
task.execute();

Конечно, вы можете просто переопределить метод onPostExecute() AsyncTask, но это не рекомендуется и в большинстве случаев на самом деле довольно плохая практика. Например, вы можете сделать это:

ExampleTask task = new ExampleTask() {
    @Override
    public void onPostExecute(String result) {
        super.onPostExecute(result);

        // Your code goes here
    }
};

Это будет работать так же хорошо, как и реализация выше с помощью отдельного интерфейса прослушивателя, но есть несколько проблем с этим:

Прежде всего вы можете сломать ExampleTask все вместе. Все сводится к вызову super.onPostExecute() выше. Если вы как разработчик переопределите onPostExecute(), как указано выше, и забудьте включить супервызов или просто удалить его по любой причине, что оригинальный метод onPostExecute() в ExampleTask больше не будет вызываться. Например, реализация всего прослушивателя с помощью TaskListener внезапно перестанет работать, поскольку вызов обратного вызова реализован в onPostExecute(). Вы также можете разбить TaskListener многими другими способами, неосознанно или невольно влияя на состояние ExampleTask, чтобы он больше не работал.

Если вы посмотрите на то, что на самом деле происходит, когда вы переопределяете метод, подобный этому, становится намного понятнее, что происходит. Переопределяя onPostExecute(), вы создаете новый подкласс ExampleTask. Это было бы точно так же, как это делать:

public class AnotherExampleTask extends ExampleTask {

    @Override
    public void onPostExecute(String result) {
        super.onPostExecute(result);

        // Your code goes here
    }
}

Все это просто скрывается за языковой функцией, называемой анонимными классами. Внезапно переопределение метода, подобного этому, кажется не таким чистым и быстрым, не делает этого?

Подводя итог:

  • Переопределение такого метода фактически создает новый подкласс. Вы не просто добавляете обратный вызов, вы изменяете, как работает этот класс, и можете бессознательно сломать так много вещей.
  • Отладка таких ошибок может быть намного больше, чем просто боль в **. Потому что внезапно ExampleTask может бросать Exceptions или просто не работать больше без видимых причин, потому что вы никогда не изменяли его код.
  • Каждый класс должен обеспечивать реализацию прослушивателя в тех местах, где он подходит и предназначен. Конечно, вы можете просто добавить их позже, переопределив onPostExecute(), но это всегда очень опасно. Даже @flup с его 13-кратной репутацией забыл включить вызов super.onPostExecute() в свой ответ, представьте, что может сделать другой, как опытный разработчик!
  • Маленькая абстракция никогда никому не повредит. Написание конкретных слушателей может быть немного больше кода, но это гораздо лучшее решение. Код будет более чистым, более читаемым и намного более удобным. Использование ярлыков, таких как переопределение onPostExecute(), по существу приносит в жертву качество кода для небольшого удобства. Это никогда не будет хорошей идеей, а просто вызовет проблемы в конечном итоге.

Ответ 2

В Java функции меньше гражданина первого класса, чем в JavaScript. AsyncTask предоставляет обратный вызов как метод в классе, который вы должны переопределить.

См. Сделать HTTP-запрос с android для подкласса AsyncTask с реализацией doInBackground, которая делает веб-запрос.

Если вы хотите выполнять несколько HTTP-запросов с различными обратными вызовами, вы можете переопределить RequestTask и реализовать onPostExecute с различными реализациями обратного вызова. Вы можете использовать анонимный класс для имитации закрытия, которое обычно использует обратный вызов JavaScript:

new RequestTask(){
    @Override
    public void onPostExecute(String result) {
        // Implementation has read only access to 
        // final variables in calling scope. 
    }
}.execute("http://stackoverflow.com");

Как показывает Xaver, вы также можете создать полномасштабный интерфейс для слушателя. Это кажется мне полезным только в том случае, если вы хотите реализовать пару функций по умолчанию onPostExecute и выбрать одну из этих реализаций по умолчанию для определенного вызова.