Как реализовать ProgressDialog, когда Activity запрашивает SoapObject из веб-службы?

Я знаю, что вопросы ProgressDialog with Threads задавались много раз, но ни одно из решений не работает для моего проекта. В основном я хочу сделать следующее: 1) когда пользователь нажимает кнопку, действие отправляет запрос аутентификации на сервер 2) пока это делается, отображается ProgressDialog 3) когда приходит ответ, я хочу отклонить ProgressDialog и возвращаемый объект, который будет прочитан и интерпретирован действием

Если я: 1) задаю поток для обновления поля приложения ответом, следующий метод (который находится вне потока) выдает NPE при доступе к полю 2) если я включаю следующий метод в поток, второй метод выдает «java.lang.RuntimeException: невозможно создать обработчик внутри потока, который не вызвал Looper.prepare ()»

Извините за длинный текст, но я полностью теряюсь из-за этого... Мой код выглядит примерно так:

public class XXX extends Activity implements OnClickListener {

// (...)
private SoapObject returnObject;
private String response;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    // (...)
        authProgressDialog = ProgressDialog.show(XXX.this, "", "Authenticating...", true, false);
        new Thread(new Runnable() {
            @Override
            public void run() {
                authenticate(); // method that calls the API via SOAP
                authenticateReal(); // method that handles the response
            }
        }).start();

        mHandler = new Handler() {
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                    case 10:
                        authProgressDialog.dismiss();
                        break;
                }
            }
        };
    }
}

public void authenticate() {
    // API stuff (...)
    AndroidHttpTransport aht = new AndroidHttpTransport(URL);
    try {
        aht.call(SOAP_ACTION, soapEnvelope);
        returnObject = (SoapObject) soapEnvelope.getResponse();
        response = returnObject.getProperty("ResponseStatus").toString();
    }
    catch (Exception e) {
        e.printStackTrace();
    }
    finally {
        mHandler.sendEmptyMessage(10);
    }
}

// Method that needs to access returnObject and reponse objects and
// it is here where the NPE's or other exceptions are thrown
public void authenticateReal() {
// (...)
}

person TomaszRykala    schedule 23.01.2011    source источник
comment
Я мог упустить что-то совершенно фундаментальное о тредах здесь, потому что я никогда не читал их в достаточной мере, тс... извините за нубский вопрос. Содержимое authenticationReal() может быть легко передано в блок try функции autenticate().   -  person TomaszRykala    schedule 23.01.2011


Ответы (4)


Вам лучше использовать AsyncTask (это способ Android):

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    new TheTask().execute();
}

private class TheTask extends AsyncTask<Void, Void, Void>{

    @Override
    protected void onPreExecute() {
        authProgressDialog = ProgressDialog.show(XXX.this, "", "Authenticating...", true, false);
    }

    @Override
    protected Void doInBackground(Void... params) {
        authenticate(); // method that calls the API via SOAP
        authenticateReal(); // method that handles the response
        return null;
    }

    @Override
    protected void onPostExecute(Void result) {
        authProgressDialog.dismiss();
    }
}

Между прочим... Эта презентация показалась мне очень полезной (в ней рассказывается о приложениях REST, но вы можете применить ту же концепцию к другим типам приложений): Разработка клиентских приложений REST для Android

person Cristian    schedule 23.01.2011
comment
Большое спасибо, приятель. Это решило проблему :) - person TomaszRykala; 24.01.2011
comment
Именно то, что мне было нужно!! Спасибо!! Без этого диалоговое окно даже не будет отображаться до тех пор, пока не будет выполнено выполнение, потому что оно асинхронно! - person bluediapente; 31.03.2011

Для вашей последней проблемы поставьте "Looper.prepare();" в ваш метод run().

        @Override
        public void run() {
            Looper.prepare();
            authenticate(); // method that calls the API via SOAP
            authenticateReal(); // method that handles the response
        }

Вы проверили, правильно ли работает ваш ответ в вашем методе аутентификации()? (используя LogCat для отображения ответа)

В противном случае лучше используйте AsyncTask (как было предложено).

person Rainer    schedule 23.01.2011
comment
Спасибо за ваш ценный комментарий. Я также пробовал ваше решение, но мой класс чище, когда вместо него используется ASyncTask. - person TomaszRykala; 24.01.2011

Если вы действительно хотите использовать Thread вместо AsyncTask, вы можете попробовать это следующим образом:

public class XXX extends Activity implements OnClickListener {
...
    private static int HANDLER_MESSAGE_AUTH_REQUEST_COMPLETE = 10;
...
    private void performAuthentication(){
        authProgressDialog = ProgressDialog.show(XXX.this, "", "Authenticating...", true, false);
        Thread backgroundThread = new Thread() {
            @Override
            public void run() {
                authenticate();
            }
        }
        backgroundThread.start();
    }

    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch(msg.what){
            case HANDLER_MESSAGE_AUTH_REQUEST_COMPLETE:
                authProgressDialog.dismiss();
                break;
            }
        }
    }

    private void authenticate(){
        // existing authenticate code goes here
        ...
        Message msg = Message.obtain();
        msg.what = HANDLER_MESSAGE_AUTH_REQUEST_COMPLETE;
        handler.sendMessage(msg);
        // existing authenticateReal code goes here
        ...
    }
}

Из вашего кода не совсем понятно, где переменная mHandler присваивается new Handler(). Чтобы было ясно, в моем коде это должно быть поле private на самом class. Вам также понадобится некоторая обработка ошибок в вашем методе authenticate(), чтобы отправить сообщение о закрытии диалогового окна, если вы обнаружите ошибку.

person dave.c    schedule 23.01.2011
comment
Привет, да, Handler тоже было частным полем, я просто по ошибке пропустил его из своего кода. Я уверен, что ваше решение могло сработать для меня, но ответ ASyncTask решил мою проблему. Кажется, что ASyncTask предпочтительнее. Большое спасибо за ваш ответ. - person TomaszRykala; 24.01.2011

Как уже писали другие, AsyncTask — это способ продолжить.

НО: AsyncTask и Threads имеют некоторые подводные камни для элементов пользовательского интерфейса:

Если вы измените ориентацию телефона и не установите android:configChanges="keyboardHidden|orientation" для своей активности (это означает, что вы должны самостоятельно обрабатывать onConfigChange) в Manifest.xml, изменение ориентации уничтожит и воссоздаст вашу активность и ContentView, а также отключит ProgressDialog от видимого окна. (он связан со старым). Android НЕ собирается убивать ваш Thread или AsyncTask. Это также не убьет его, если действие будет уничтожено. Эти фоновые задачи продолжаются до тех пор, пока они не будут выполнены.

Попытка уволить () ваш ранее созданный ProgressDialog выдает исключение после того, как ContentView был уничтожен, поскольку он больше не является частью вашего окна. попробуйте/поймайте МНОГО в ситуациях, когда вы делаете что-то отдельное (асинхронное). Все, на что вы, возможно, полагаетесь, могло исчезнуть или замениться чем-то другим, когда onPostExecute() снова вызывается. В конце концов подумайте о регистрации каждого ASyncTask, который вы запускаете в некотором массиве в своей деятельности, и попробуйте отменить() их в своей Activity.onDestroy().

person Oliver    schedule 23.01.2011