Как моделировать класс Android AsyncTask? - PullRequest
1 голос
/ 08 января 2012

Я решил изучить Scala / Play (на стороне сервера) и решил одновременно изучить Android (Client) разработку игр, чтобы оживить разработку.
У меня есть вопрос относительно того, каксделать красивый дизайн для HTTP-запроса в Android.
Из того, что я понял, лучший способ - делегировать HTTP-запрос классу, расширяющему абстрактный класс AsyncTask.
Вам нужно создать новое расширение?AsyncTask для каждой отдельной логики в методе doInBackground, который вы переопределяете?
Для меня не вполне естественно иметь класс для каждой логики запроса, а вместо этого инкапсулировать несколько согласованных методов в одном классе.

Я только начал немного играть, но меня не устраивает дизайн, так как мне не нравится мой дизайн объекта varargs в doInBackground(Object... params).
С этим дизайном я теряю безопасность типов иОбъект params далеко не интуитивен, а интуиция - это то, к чему я стремлюсь в своем коде.

Вот код, который я хочу улучшить.

public class GameActivity extends Activity {

    private class MyCellListener implements ICellListener {
        public void onCellSelected() {
            ServerProxy.postSelectedCell(row, col, player.getUser());    
            ...
            // ServerProxy.other();

public class ServerProxy extends AsyncTask<Object, Void, Void>{

    private static final String TAG = ServerProxy.class.getSimpleName();
    private static final String SERVER_ADDRESS = "http://127.0.0.1";

    // Prevent external instantiation
    private ServerProxy(){};

    public static void postSelectedCell(int row, int cell, User user){
         List<NameValuePair> postParameters = new ArrayList<NameValuePair>(3);
         postParameters.add(new BasicNameValuePair("row", String.valueOf(row)));
         postParameters.add(new BasicNameValuePair("cell", String.valueOf(cell)));
         postParameters.add(new BasicNameValuePair("userName", user.getUserName()));
         new ServerProxy().doInBackground("setSelectedCell" , postParameters);
    }

//    public static void postOther() {
//      new ServerProxy().doInBackground("other" , //some parameters); 
//    }

    /**
     * @param postParameters First object URL postfix<br/>
     * Second parameter is post parameters inform of {@code List<NameValuePair>}
     * @return null
     */
    @SuppressWarnings("unchecked")
    @Override
    protected Void doInBackground(Object... params) {

        HttpClient httpclient = new DefaultHttpClient();
        HttpPost httppost = new HttpPost(SERVER_ADDRESS +"/" + params[0]);
        httppost.getParams().setBooleanParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, false);

        try {
            httppost.setEntity(new UrlEncodedFormEntity((List<NameValuePair>) params[1]));
            httpclient.execute(httppost);
        } catch (ClientProtocolException e) {
         Log.e(TAG,e.toString());
        } catch (IOException e) {
         Log.e(TAG,e.toString());
        }
        return null;
    }
}

Ответы [ 2 ]

5 голосов
/ 08 января 2012

Я думаю, вы не понимаете, что делает AsyncTask. Вы не можете определить методы, такие как postLastName, postFirstName, postWh независимо от одного подкласса AsyncTask, и заставить его выполнять этот метод из потока пользовательского интерфейса. AsyncTask предназначен для облегчения выполнения фоновых заданий, которые должны обновлять пользовательский интерфейс, не заставляя вас обрабатывать потоки напрямую.

Для связи с сервером вы используете HttpClient, и вызов HttpClient.execute () будет блокироваться, пока ответ сервера не вернется. Это может занять много времени, особенно если сервер занят, умирает или отключается сотовая связь. Когда этот вызов занимает слишком много времени, вы не хотите, чтобы ваш пользовательский интерфейс перестал отвечать на запросы пользователя. Вы хотите показать вращающийся спиннер, чтобы пользователь знал, что что-то происходит. Если вы использовали код, который вы предоставили, ваш пользовательский интерфейс прекратил бы рисовать до тех пор, пока не будет возвращен вызов HttpClient.execute (), потому что вы вызываете его в потоке пользовательского интерфейса.

Чтобы обойти эту проблему, переместите этот вызов из потока пользовательского интерфейса в другой поток. Пусть этот поток ожидает ответа, затем уведомляет поток пользовательского интерфейса о завершении и использует поток пользовательского интерфейса для обновления пользовательского интерфейса новыми данными. Почему вы не можете позволить этому фоновому потоку обновлять интерфейс? Потому что это нарушило бы правило потоков Android, которое гласит, что ТОЛЬКО поток пользовательского интерфейса может обновлять пользовательский интерфейс.

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

Теперь ваша идея о том, чтобы несколько запросов проходили через один подкласс, не сработает, потому что AsyncTask связывает воедино две части выполнения сервисных вызовов: детали выполнения сервисного вызова для получения ответа и что с этим делать. ответ на обновление пользовательского интерфейса. Если вызывается метод postFirstName (), то, что вы делаете после того, как он возвращает этот ответ, вероятно, отличается от того, если вы вызывали postLastName (). И потому что это отличается, вы не можете просто определить ОДИН AsyncTask для всех этих различных вызовов. Вы можете поделиться кодом либо с помощью композиции, либо с помощью подклассов базового класса, но вам придется создавать подкласс для каждой уникальной операции, которую вы хотите выполнить на сервере. Так что думайте класс вместо метода для каждого вызова.

Вам не нужно использовать параметры, переданные в doInBackground (). Если у вас есть несколько типов параметров для передачи в AsyncTask. Передайте их в конструкторе. Поскольку вы не можете повторно использовать экземпляры AsyncTask (то есть не можете вызывать AsyncTask.execute () более одного раза за экземпляр). Их жизненный цикл ДОЛЖЕН быть реализован, выполнить () и бросить. Это означает, что передача ввода в конструкторе не повредит вашей способности использовать AsyncTask.

Я написал свою собственную версию AsyncTask, которая разделяет три метода обратного вызова: success () для обработки результата, возвращенного из doInBackground (); handleException () вызывается, когда любое исключение выдается из doInBackground (); и doFinally () вызывается независимо от doInBackground (), что-то возвращает или выдает исключение. По умолчанию AsyncTask onPostExecute () вызывается независимо от ошибки или успеха. Это будет выглядеть примерно так:

public class MyTask extends EnhancedAsyncTask<Param,Integer,MyResult> {
    MyParam1 param1;
    MyParam2 param2;

    public MyTask( MyParam1 param1, MyParam2 param2 ) {
        this.param1 = param;
        this.param2 = param2;
    }

    protected MyResult doInBackground( Param... params ) {
        // do server work here
        server.send( param1, param2 );
    }

    protected void success( MyResult result ) {
        // do Update of the UI
    }

    protected void handleException( Exception ex ) {
       // show an error here
    }
}

// Now to use this AsyncTask you would do something like this:

MyTask task = new MyTask( param1, param2 ).execute();

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

0 голосов
/ 08 января 2012

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

  • Вы не должны звонить doInBackground напрямую, как здесь

    new ServerProxy (). doInBackground ("setSelectedCell", postParameters);

    вместо этого он должен вызывать любую версию execute

Что касается дизайна, вы можете попробовать это

  • Вы можете создать класс ServerProxy и не расширять его с AsyncTask.Просто используйте его для инкапсуляции вашей логики.
  • И теперь вы можете создать ServerProxyAsync, который будет принимать ServerProxy в качестве композиции.
  • Теперь один вариант, который вы можете сделать, это

    открытый класс ServerProxy расширяет AsyncTask

    Здесь String может быть именем метода, который вы хотите вызвать в ServerProxy экземпляре, и вы можете использовать для этого отражение.

ПРИМЕЧАНИЕ: в этом тесте могут быть другие варианты, это только первая попытка решения проблемы

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...