Como lidar com a alteração da orientação da tela quando a caixa de diálogo de progresso e o thread de segundo plano estão ativos?

524

Meu programa realiza alguma atividade de rede em um encadeamento em segundo plano. Antes de iniciar, aparece uma caixa de diálogo de progresso. A caixa de diálogo é descartada no manipulador. Isso tudo funciona bem, exceto quando a orientação da tela muda enquanto a caixa de diálogo está aberta (e a linha de fundo está em andamento). Nesse momento, o aplicativo trava, entra em conflito ou entra em um estágio estranho em que o aplicativo não funciona até que todos os threads tenham sido eliminados.

Como lidar com a mudança de orientação da tela normalmente?

O código de exemplo abaixo corresponde aproximadamente ao que meu programa real faz:

public class MyAct extends Activity implements Runnable {
    public ProgressDialog mProgress;

    // UI has a button that when pressed calls send

    public void send() {
         mProgress = ProgressDialog.show(this, "Please wait", 
                      "Please wait", 
                      true, true);
        Thread thread = new Thread(this);
        thread.start();
    }

    public void run() {
        Thread.sleep(10000);
        Message msg = new Message();
        mHandler.sendMessage(msg);
    }

    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            mProgress.dismiss();
        }
    };
}

Pilha:

E/WindowManager(  244): Activity MyAct has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@433b7150 that was originally added here
E/WindowManager(  244): android.view.WindowLeaked: Activity MyAct has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@433b7150 that was originally added here
E/WindowManager(  244):     at android.view.ViewRoot.<init>(ViewRoot.java:178)
E/WindowManager(  244):     at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:147)
E/WindowManager(  244):     at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:90)
E/WindowManager(  244):     at android.view.Window$LocalWindowManager.addView(Window.java:393)
E/WindowManager(  244):     at android.app.Dialog.show(Dialog.java:212)
E/WindowManager(  244):     at android.app.ProgressDialog.show(ProgressDialog.java:103)
E/WindowManager(  244):     at android.app.ProgressDialog.show(ProgressDialog.java:91)
E/WindowManager(  244):     at MyAct.send(MyAct.java:294)
E/WindowManager(  244):     at MyAct$4.onClick(MyAct.java:174)
E/WindowManager(  244):     at android.view.View.performClick(View.java:2129)
E/WindowManager(  244):     at android.view.View.onTouchEvent(View.java:3543)
E/WindowManager(  244):     at android.widget.TextView.onTouchEvent(TextView.java:4664)
E/WindowManager(  244):     at android.view.View.dispatchTouchEvent(View.java:3198)

Tentei descartar a caixa de diálogo de progresso no onSaveInstanceState, mas isso apenas impede uma falha imediata. O encadeamento em segundo plano ainda está em andamento e a interface do usuário está no estado parcialmente desenhado. Precisa matar o aplicativo inteiro antes que ele comece a funcionar novamente.

Heikki Toivonen
fonte
1
Considerando as respostas que recebeu, você deve alterar a resposta aceita em favor dos melhores, não deve?
Rd
Veja também uma pergunta anterior stackoverflow.com/questions/456211/…
rds 21/03
3
Tudo, tenho uma ótima explicação e possíveis soluções para esse problema. Acesse http://blog.doityourselfandroid.com/2010/11/14/handling-progress-dialogs-and-screen-orientation-changes/ Deixe-me saber se isso ajudou.
Arcamax # 30/11
2
Há uma explicação bastante completa sobre como reter tarefas em segundo plano assíncronas nas orientações da tela nesta postagem do blog . Confira!
Adrian Monk
Basta definir android: configChanges = "orientação | screensize" na Atividade em manifest.It vai parar android para recriar sua actividade
Zar E Ahmer

Respostas:

155

Quando você muda de orientação, o Android cria uma nova visualização. Você provavelmente está tendo falhas porque o thread de segundo plano está tentando alterar o estado do antigo. (Também pode estar com problemas porque o encadeamento em segundo plano não está no encadeamento da interface do usuário)

Eu sugeriria tornar esse mHandler volátil e atualizá-lo quando a orientação mudar.

haseman
fonte
14
Você pode ter identificado o motivo do acidente. Eu me livrei da falha, mas ainda não descobri como restaurar a interface do usuário para o estado em que estava antes da mudança de orientação de maneira confiável. Mas sua resposta me levou adiante, premiando-a como resposta.
Heikki Toivonen
4
Você deve obter um onStart em sua atividade quando a orientação mudar. Essencialmente, você deve reconfigurar a exibição usando os dados antigos. Portanto, sugiro solicitar atualizações de status numérico na barra de progresso e reconstruir uma nova exibição quando você obtiver esse novo 'onStart'. Não me lembro de imediato se você receber uma nova atividade, mas alguma busca na documentação deve ajudar.
HASEMAN
6
Tendo brincado com isso recentemente, posso transmitir que você recebe uma nova atividade quando seu aplicativo altera a orientação. (Você também obtém uma nova visualização) Se você tentar atualizar a visualização antiga, receberá uma exceção, pois a visualização antiga possui um contexto de aplicativo inválido (sua atividade antiga). Você pode contornar isso passando em myActivity.getApplicationContext () em vez de um ponteiro para a própria atividade.
21411 haseman
1
Alguém pode explicar o uso / benefício da volátil neste contexto
Zar E Ahmer
2
@ Nepster Sim, eu estava pensando sobre isso também. Seria ótimo se alguém explicasse sobre o volátil.
RestInPeace
261

Edit: Os engenheiros do Google não recomendam essa abordagem, conforme descrito por Dianne Hackborn (também conhecido como hackbod ) neste post do StackOverflow . Verificação de saída esta postagem no blog para obter mais informações.


Você deve adicionar isso à declaração de atividade no manifesto:

android:configChanges="orientation|screenSize"

então parece

<activity android:label="@string/app_name" 
        android:configChanges="orientation|screenSize|keyboardHidden" 
        android:name=".your.package">

O problema é que o sistema destrói a atividade quando ocorre uma alteração na configuração. Vejo ConfigurationChanges .

Portanto, colocar isso no arquivo de configuração evita que o sistema destrua sua atividade. Em vez disso, chama o onConfigurationChanged(Configuration)método

sonxurxo
fonte
21
Esta é definitivamente a melhor solução; simplesmente gira o layout (o comportamento que você espera em primeiro lugar). Só não se esqueça de colocar android: configChanges = "orientação | keyboardHidden" (por causa de telefones que têm teclado paisagem)
nikib3ro
24
Este parece ser o comportamento que eu espero. No entanto, a documentação indica que a atividade é destruída "porque qualquer recurso de aplicativo, incluindo arquivos de layout, pode mudar com base em qualquer valor de configuração. Portanto, a única maneira segura de lidar com uma alteração na configuração é recuperar todos os recursos". Além disso orientation, existem muitas outras razões para a configuração mudar: keyboardHidden(já editei a resposta da wiki), uiMode(por exemplo, entrar ou sair do modo carro; mudar o modo noturno), etc. Agora me pergunto se isso é realmente uma boa resposta
Rd #
116
Esta não é uma solução aceitável. Apenas mascara a verdadeira questão.
Rf43 #
18
Funciona, mas não é recomendado pelo Google.
precisa
21
Por favor, não siga esta abordagem aqui. DDosAttack está completamente certo. Imagine que você está criando uma caixa de diálogo de progresso para um download ou outra coisa que leva muito tempo. Como usuário, você não permanecerá nessa atividade e a encarará. Você mudaria para a tela inicial ou para outro aplicativo, como um jogo ou uma ligação telefônica, ou algo mais com fome de recursos que acabará por destruir sua atividade. E o que então? Você está enfrentando o mesmo problema antigo, que NÃO foi resolvido com esse pequeno truque. A atividade será recriada novamente quando o usuário voltar.
Tiguchi 01/10/12
68

Eu vim com uma solução sólida para esses problemas que está em conformidade com o 'Modo Android'. Eu tenho todas as minhas operações de longa duração usando o padrão IntentService.

Ou seja, minhas atividades intenções de transmissão, o IntentService faz o trabalho, salva os dados no banco de dados e, em seguida, transmite pegajosas intenções. A parte complicada é importante, de modo que, mesmo que a Atividade tenha sido interrompida durante o período após o usuário iniciar o trabalho e perca a transmissão em tempo real do IntentService, ainda podemos responder e coletar os dados da Atividade que está chamando. ProgressDialogs podem trabalhar muito bem com esse padrão onSaveInstanceState().

Basicamente, você precisa salvar um sinalizador que possui uma caixa de diálogo de progresso em execução no pacote configurável da instância salva. Não salve o objeto de diálogo de progresso, pois isso vazará toda a atividade. Para ter um identificador persistente da caixa de diálogo de progresso, eu a armazeno como uma referência fraca no objeto de aplicativo. Na mudança de orientação ou qualquer outra coisa que faça com que a Atividade seja pausada (telefonema, o usuário chega em casa etc.) e, em seguida, continuo, eu rejeito a caixa de diálogo antiga e recrio uma nova caixa de diálogo na Atividade recém-criada.

Para diálogos de progresso indefinidos, isso é fácil. Para o estilo da barra de progresso, você deve colocar o último progresso conhecido no pacote e as informações que estiver usando localmente na atividade para acompanhar o progresso. Ao restaurar o progresso, você usará essas informações para gerar novamente a barra de progresso no mesmo estado de antes e depois atualizar com base no estado atual das coisas.

Então, para resumir, colocar tarefas de longa execução em um IntentService, juntamente com o uso criterioso de, onSaveInstanceState()permite acompanhar de maneira eficiente os diálogos e restaurar, em seguida, os eventos do ciclo de vida da Atividade. Os bits relevantes do código de atividade estão abaixo. Você também precisará de lógica no seu BroadcastReceiver para lidar com as intenções fixas, mas isso está além do escopo.

public void doSignIn(View view) {
    waiting=true;
    AppClass app=(AppClass) getApplication();
    String logingon=getString(R.string.signon);
    app.Dialog=new WeakReference<ProgressDialog>(ProgressDialog.show(AddAccount.this, "", logingon, true));
    ...
}

@Override
protected void onSaveInstanceState(Bundle saveState) {
    super.onSaveInstanceState(saveState);
    saveState.putBoolean("waiting",waiting);
}

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if(savedInstanceState!=null) {
        restoreProgress(savedInstanceState);    
    }
    ...
}

private void restoreProgress(Bundle savedInstanceState) {
    waiting=savedInstanceState.getBoolean("waiting");
    if (waiting) {
        AppClass app=(AppClass) getApplication();
        ProgressDialog refresher=(ProgressDialog) app.Dialog.get();
        refresher.dismiss();
        String logingon=getString(R.string.signon);
        app.Dialog=new WeakReference<ProgressDialog>(ProgressDialog.show(AddAccount.this, "", logingon, true));
    }
}
jfelectron
fonte
parece uma boa solução
Derekyy
"Eu tenho todas as minhas operações de longa duração usando o padrão IntentService." Esta não é uma solução perfeita, porque é como a atirar para fora do canhão em pardais e um código muito clichê, por mais que você pode assistir youtube.com/watch?v=NJsq0TU0qeg
Kamil Nekanowicz
28

Eu encontrei o mesmo problema. Minha atividade precisa analisar alguns dados de um URL e é lenta. Então, crio um thread para fazer isso e mostro uma caixa de diálogo de progresso. Deixei o tópico postar uma mensagem de volta no tópico da interface do usuário via Handlerquando terminar. Em Handler.handleMessage, pego o objeto de dados (pronto agora) do encadeamento e o preencho na interface do usuário. Portanto, é muito parecido com o seu exemplo.

Após várias tentativas e erros, parece que encontrei uma solução. Pelo menos agora eu posso girar a tela a qualquer momento, antes ou depois do término da discussão. Em todos os testes, a caixa de diálogo é fechada corretamente e todos os comportamentos são esperados.

O que eu fiz é mostrado abaixo. O objetivo é preencher meu modelo de dados ( mDataObject) e preenchê-lo na interface do usuário. Deve permitir a rotação da tela a qualquer momento sem surpresa.

class MyActivity {

    private MyDataObject mDataObject = null;
    private static MyThread mParserThread = null; // static, or make it singleton

    OnCreate() {
        ...
        Object retained = this.getLastNonConfigurationInstance();
        if(retained != null) {
            // data is already completely obtained before config change
            // by my previous self.
            // no need to create thread or show dialog at all
            mDataObject = (MyDataObject) retained;
            populateUI();
        } else if(mParserThread != null && mParserThread.isAlive()){
            // note: mParserThread is a static member or singleton object.
            // config changed during parsing in previous instance. swap handler
            // then wait for it to finish.
            mParserThread.setHandler(new MyHandler());
        } else {
            // no data and no thread. likely initial run
            // create thread, show dialog
            mParserThread = new MyThread(..., new MyHandler());
            mParserThread.start();
            showDialog(DIALOG_PROGRESS);
        }
    }

    // http://android-developers.blogspot.com/2009/02/faster-screen-orientation-change.html
    public Object onRetainNonConfigurationInstance() {
        // my future self can get this without re-downloading
        // if it's already ready.
        return mDataObject;
    }

    // use Activity.showDialog instead of ProgressDialog.show
    // so the dialog can be automatically managed across config change
    @Override
    protected Dialog onCreateDialog(int id) {
        // show progress dialog here
    }

    // inner class of MyActivity
    private class MyHandler extends Handler {
        public void handleMessage(msg) {
            mDataObject = mParserThread.getDataObject();
            populateUI();
            dismissDialog(DIALOG_PROGRESS);
        }
    }
}

class MyThread extends Thread {
    Handler mHandler;
    MyDataObject mDataObject;

    // constructor with handler param
    public MyHandler(..., Handler h) {
        ...
        mHandler = h;
    }

    public void setHandler(Handler h) { mHandler = h; } // for handler swapping after config change
    public MyDataObject getDataObject() { return mDataObject; } // return data object (completed) to caller

    public void run() {
        mDataObject = new MyDataObject();
        // do the lengthy task to fill mDataObject with data
        lengthyTask(mDataObject);
        // done. notify activity
        mHandler.sendEmptyMessage(0); // tell activity: i'm ready. come pick up the data.
    }
}

Isso é o que funciona para mim. Não sei se esse é o método "correto", conforme projetado pelo Android - eles afirmam que essa "atividade de destruir / recriar durante a rotação da tela" facilita as coisas, então acho que não deve ser muito complicado.

Deixe-me saber se você encontrar um problema no meu código. Como dito acima, eu realmente não sei se há algum efeito colateral.

samsonsu
fonte
1
Muito obrigado! A dica onRetainNonConfigurationInstance()e getLastNonConfigurationInstance()me ajudou a resolver meu problema. Afirmativo!
sven
15

O problema percebido original era que o código não sobreviveria a uma alteração na orientação da tela. Aparentemente, isso foi "resolvido", fazendo com que o programa lide com a orientação da tela, mudando a si próprio, em vez de permitir que a estrutura da interface do usuário faça isso (chamando onDestroy)).

Eu diria que, se o problema subjacente é que o programa não sobreviverá ao Destroy (), a solução aceita é apenas uma solução alternativa que deixa o programa com outros problemas e vulnerabilidades sérios. Lembre-se de que a estrutura do Android afirma especificamente que sua atividade corre o risco de ser destruída quase a qualquer momento devido a circunstâncias fora de seu controle. Portanto, sua atividade deve ser capaz de sobreviver a onDestroy () e subsequente onCreate () por qualquer motivo, não apenas uma alteração na orientação da tela.

Se você aceitar as alterações na orientação da tela para resolver o problema do OP, será necessário verificar se outras causas de onDestroy () não resultam no mesmo erro. Você é capaz de fazer isso? Caso contrário, eu perguntaria se a resposta "aceita" é realmente muito boa.

gymshoe
fonte
14

Minha solução foi estender a ProgressDialogaula para obter a minha MyProgressDialog.
I redefinido show()e dismiss()métodos para bloquear a orientação antes de mostrar o Dialoge desbloqueá-lo de volta quando Dialogé indeferido. Portanto, quando Dialogé mostrado e a orientação do dispositivo muda, a orientação da tela permanece até que dismiss()seja chamada, e a orientação da tela muda de acordo com os valores do sensor / orientação do dispositivo.

Aqui está o meu código:

public class MyProgressDialog extends ProgressDialog {
private Context mContext;

public MyProgressDialog(Context context) {
    super(context);
    mContext = context;
}

public MyProgressDialog(Context context, int theme) {
    super(context, theme);
    mContext = context;
}

public void show() {
    if (mContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT)
        ((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    else
        ((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    super.show();
}

public void dismiss() {
    super.dismiss();
    ((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
}

}
Oli
fonte
8

Enfrentei esse mesmo problema e encontrei uma solução que não era invulgar usando o ProgressDialog e obtive resultados mais rápidos.

O que fiz foi criar um layout que contenha uma ProgressBar.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ProgressBar
    android:id="@+id/progressImage"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    />
</RelativeLayout>

Em seguida, no método onCreate, faça o seguinte

public void onCreate(Bundle icicle) {
    super.onCreate(icicle);
    setContentView(R.layout.progress);
}

Em seguida, execute a tarefa longa em um encadeamento e, quando terminar, um Runnable defina a exibição do conteúdo para o layout real que você deseja usar para esta atividade.

Por exemplo:

mHandler.post(new Runnable(){

public void run() {
        setContentView(R.layout.my_layout);
    } 
});

Foi o que fiz e descobri que ele roda mais rápido do que mostrar o ProgressDialog e é menos invasivo e tem uma aparência melhor na minha opinião.

No entanto, se você deseja usar o ProgressDialog, essa resposta não é para você.

Pzanno
fonte
Essa solução é elegante em um caso de uso simples, mas possui desvantagens. Você precisa recriar a exibição de conteúdo completo. setContentView(R.layout.my_layout);não é suficiente; você precisa definir todos os ouvintes, redefinir dados etc.
rds 10/03/11
@rds você está certo. Isso é realmente apenas uma solução para um caso simples, ou se você precisar fazer algum trabalho pesado no método onCreate antes de exibir sua exibição.
Pzanno 11/03/11
Eu não entendo direito. Em vez de configurar os ouvintes no onCreate (), como normalmente fazemos, poderíamos configurá-los na execução (). Estou faltando alguma coisa aqui?
Código Poet
7

Eu descobri uma solução para isso que ainda não vi em outro lugar. Você pode usar um objeto de aplicativo personalizado que saiba se você tem tarefas em segundo plano, em vez de tentar fazer isso na atividade que é destruída e recriada na mudança de orientação. Eu escrevi sobre isso aqui .

Heikki Toivonen
fonte
1
A criação de um costume Applicationé normalmente usada para manter um estado global do aplicativo. Não digo que não funcione, mas parece complicado demais. No documento "Normalmente não há necessidade de subclassificar Aplicativo.". Eu prefiro a resposta de sonxurxo.
Rd
7

Contribuirei com minha abordagem para lidar com esse problema de rotação. Isso pode não ser relevante para o OP, pois ele não está usando AsyncTask, mas talvez outros achem útil. É bem simples, mas parece fazer o trabalho para mim:

Eu tenho uma atividade de logon com uma AsyncTaskclasse aninhada chamada BackgroundLoginTask.

No meu, BackgroundLoginTaskeu não faço nada fora do comum, exceto para adicionar uma verificação nula ao cancelar a chamada ProgressDialog:

@Override
protected void onPostExecute(Boolean result)
{    
if (pleaseWaitDialog != null)
            pleaseWaitDialog.dismiss();
[...]
}

Isso é para lidar com o caso em que a tarefa em segundo plano termina enquanto Activitynão está visível e, portanto, a caixa de diálogo de progresso já foi descartada pelo onPause()método.

Em seguida, na minha Activityclasse pai , crio identificadores estáticos globais para minha AsyncTaskclasse e minha ProgressDialog(a AsyncTask, sendo aninhada, pode acessar essas variáveis):

private static BackgroundLoginTask backgroundLoginTask;
private static ProgressDialog pleaseWaitDialog;

Isso serve para dois propósitos: primeiro, ele permite que eu Activitysempre acesse o AsyncTaskobjeto mesmo a partir de uma nova atividade pós-rotacionada. Segundo, ele permite BackgroundLoginTaskacessar e descartar o ProgressDialogmesmo após uma rotação.

Em seguida, adiciono isso a onPause(), fazendo com que a caixa de diálogo de progresso desapareça quando Activitysairmos do primeiro plano (impedindo o feio travamento "forçar fechamento"):

    if (pleaseWaitDialog != null)
    pleaseWaitDialog.dismiss();

Finalmente, tenho o seguinte no meu onResume()método:

if ((backgroundLoginTask != null) && (backgroundLoginTask.getStatus() == Status.RUNNING))
        {
           if (pleaseWaitDialog != null)
             pleaseWaitDialog.show();
        }

Isso permite Dialogque reapareça após a Activityrecriação.

Aqui está toda a turma:

public class NSFkioskLoginActivity extends NSFkioskBaseActivity {
    private static BackgroundLoginTask backgroundLoginTask;
    private static ProgressDialog pleaseWaitDialog;
    private Controller cont;

    // This is the app entry point.
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (CredentialsAvailableAndValidated())
        {
        //Go to main menu and don't run rest of onCreate method.
            gotoMainMenu();
            return;
        }
        setContentView(R.layout.login);
        populateStoredCredentials();   
    }

    //Save current progress to options when app is leaving foreground
    @Override
    public void onPause()
    {
        super.onPause();
        saveCredentialsToPreferences(false);
        //Get rid of progress dialog in the event of a screen rotation. Prevents a crash.
        if (pleaseWaitDialog != null)
        pleaseWaitDialog.dismiss();
    }

    @Override
    public void onResume()
    {
        super.onResume();
        if ((backgroundLoginTask != null) && (backgroundLoginTask.getStatus() == Status.RUNNING))
        {
           if (pleaseWaitDialog != null)
             pleaseWaitDialog.show();
        }
    }

    /**
     * Go to main menu, finishing this activity
     */
    private void gotoMainMenu()
    {
        startActivity(new Intent(getApplicationContext(), NSFkioskMainMenuActivity.class));
        finish();
    }

    /**
     * 
     * @param setValidatedBooleanTrue If set true, method will set CREDS_HAVE_BEEN_VALIDATED to true in addition to saving username/password.
     */
    private void saveCredentialsToPreferences(boolean setValidatedBooleanTrue)
    {
        SharedPreferences settings = getSharedPreferences(APP_PREFERENCES, MODE_PRIVATE);
        SharedPreferences.Editor prefEditor = settings.edit();
        EditText usernameText = (EditText) findViewById(R.id.editTextUsername);
        EditText pswText = (EditText) findViewById(R.id.editTextPassword);
        prefEditor.putString(USERNAME, usernameText.getText().toString());
        prefEditor.putString(PASSWORD, pswText.getText().toString());
        if (setValidatedBooleanTrue)
        prefEditor.putBoolean(CREDS_HAVE_BEEN_VALIDATED, true);
        prefEditor.commit();
    }

    /**
     * Checks if user is already signed in
     */
    private boolean CredentialsAvailableAndValidated() {
        SharedPreferences settings = getSharedPreferences(APP_PREFERENCES,
                MODE_PRIVATE);
        if (settings.contains(USERNAME) && settings.contains(PASSWORD) && settings.getBoolean(CREDS_HAVE_BEEN_VALIDATED, false) == true)
         return true;   
        else
        return false;
    }

    //Populate stored credentials, if any available
    private void populateStoredCredentials()
    {
        SharedPreferences settings = getSharedPreferences(APP_PREFERENCES,
            MODE_PRIVATE);
        settings.getString(USERNAME, "");
       EditText usernameText = (EditText) findViewById(R.id.editTextUsername);
       usernameText.setText(settings.getString(USERNAME, ""));
       EditText pswText = (EditText) findViewById(R.id.editTextPassword);
       pswText.setText(settings.getString(PASSWORD, ""));
    }

    /**
     * Validate credentials in a seperate thread, displaying a progress circle in the meantime
     * If successful, save credentials in preferences and proceed to main menu activity
     * If not, display an error message
     */
    public void loginButtonClick(View view)
    {
        if (phoneIsOnline())
        {
        EditText usernameText = (EditText) findViewById(R.id.editTextUsername);
        EditText pswText = (EditText) findViewById(R.id.editTextPassword);
           //Call background task worker with username and password params
           backgroundLoginTask = new BackgroundLoginTask();
           backgroundLoginTask.execute(usernameText.getText().toString(), pswText.getText().toString());
        }
        else
        {
        //Display toast informing of no internet access
        String notOnlineMessage = getResources().getString(R.string.noNetworkAccessAvailable);
        Toast toast = Toast.makeText(getApplicationContext(), notOnlineMessage, Toast.LENGTH_SHORT);
        toast.show();
        }
    }

    /**
     * 
     * Takes two params: username and password
     *
     */
    public class BackgroundLoginTask extends AsyncTask<Object, String, Boolean>
    {       
       private Exception e = null;

       @Override
       protected void onPreExecute()
       {
           cont = Controller.getInstance();
           //Show progress dialog
           String pleaseWait = getResources().getString(R.string.pleaseWait);
           String commWithServer = getResources().getString(R.string.communicatingWithServer);
            if (pleaseWaitDialog == null)
              pleaseWaitDialog= ProgressDialog.show(NSFkioskLoginActivity.this, pleaseWait, commWithServer, true);

       }

        @Override
        protected Boolean doInBackground(Object... params)
        {
        try {
            //Returns true if credentials were valid. False if not. Exception if server could not be reached.
            return cont.validateCredentials((String)params[0], (String)params[1]);
        } catch (Exception e) {
            this.e=e;
            return false;
        }
        }

        /**
         * result is passed from doInBackground. Indicates whether credentials were validated.
         */
        @Override
        protected void onPostExecute(Boolean result)
        {
        //Hide progress dialog and handle exceptions
        //Progress dialog may be null if rotation has been switched
        if (pleaseWaitDialog != null)
             {
            pleaseWaitDialog.dismiss();
                pleaseWaitDialog = null;
             }

        if (e != null)
        {
         //Show toast with exception text
                String networkError = getResources().getString(R.string.serverErrorException);
                Toast toast = Toast.makeText(getApplicationContext(), networkError, Toast.LENGTH_SHORT);
            toast.show();
        }
        else
        {
            if (result == true)
            {
            saveCredentialsToPreferences(true);
            gotoMainMenu();
            }
            else
            {
            String toastText = getResources().getString(R.string.invalidCredentialsEntered);
                Toast toast = Toast.makeText(getApplicationContext(), toastText, Toast.LENGTH_SHORT);
            toast.show();
            } 
        }
        }

    }
}

Eu não sou um desenvolvedor Android experiente, então fique à vontade para comentar.

Anders
fonte
1
Interessante! Especialmente para aqueles que usam o AsyncTask. Acabei de experimentar sua solução, e ela parece funcionar principalmente. Há um problema: o ProgressDialog parece terminar um pouco depois de uma rotação, enquanto o ProgressDialog ainda está ativo. Vou brincar para ver exatamente o que está acontecendo e como remediá-lo. Mas não estou mais recebendo essas falhas!
Scott Biggs
1
Encontrei uma correção. Parece que o problema aqui é o ProgressDialog estático. Quando as rotações interrompem o ProgressDialog, ele às vezes recebe o método .dismiss () chamado depois que é reiniciado na nova atividade. Ao criar o ProgressDialog criado com cada atividade, garantimos que esse novo ProgressDialog não seja eliminado junto com a antiga atividade. Também assegurei que o ProgressDialog seja definido como nulo sempre que for descartado (para ajudar na coleta de lixo). Portanto, temos uma solução aqui! Felicidades para quem usa o AsyncTask!
22812 Scott Scott
4

Mova a tarefa longa para uma classe separada. Implemente-o como um padrão de observador de assunto. Sempre que a atividade é criada, registre-se e, ao fechar, cancele o registro com a classe de tarefa. A classe de tarefa pode usar AsyncTask.

Vinay
fonte
1
Não vejo como isso ajudaria. Você poderia explicar com mais detalhes como isso evita os problemas que estou vendo.
Heikki Toivonen
1
Como Haseman disse que evita que o back-end acesse os elementos da interface do usuário e podemos separá-la do back-end, o back-end é executado em um segmento separado e continua a ser executado mesmo depois que a tela é reorientada e Register-UnRegister with the Backend para atualizações de status . O exemplo real que resolvi usando isso é que tenho uma Tarefa de Download, que a mudei para um thread separado, sempre que o thread é criado, eu me registro e desisto.
Vinay
Ok, estou revisando esse problema e acho que ainda não entendi completamente essa resposta. Suponha que tenhamos a atividade principal iniciar um AsyncTask para executar uma operação de rede demorada que não queremos interromper durante a alteração da orientação da tela. Não vejo como a nova atividade pode enviar uma mensagem para o AsyncTask iniciada pela atividade antiga. Você pode dar um exemplo de código?
Heikki Toivonen
@ Heikki, minha implementação está abaixo do que você quer dizer?
precisa saber é o seguinte
4

O truque é mostrar / descartar a caixa de diálogo no AsyncTask durante onPreExecute / onPostExecute, como de costume, embora, no caso de mudança de orientação, crie / mostre uma nova instância da caixa de diálogo na atividade e passe sua referência para a tarefa.

public class MainActivity extends Activity {
    private Button mButton;
    private MyTask mTask = null;

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

        MyTask task = (MyTask) getLastNonConfigurationInstance();
        if(task != null){
            mTask = task;
            mTask.mContext = this;
            mTask.mDialog = ProgressDialog.show(this, "", "", true);        
        }

        mButton = (Button) findViewById(R.id.button1);
        mButton.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v){
                mTask = new MyTask(MainActivity.this);
                mTask.execute();
            }
        });
    }


    @Override
    public Object onRetainNonConfigurationInstance() {
        String str = "null";
        if(mTask != null){
            str = mTask.toString();
            mTask.mDialog.dismiss();
        }
        Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
        return mTask;
    }



    private class MyTask extends AsyncTask<Void, Void, Void>{
        private ProgressDialog mDialog;
        private MainActivity mContext;


        public MyTask(MainActivity context){
            super();
            mContext = context;
        }


        protected void onPreExecute() {
            mDialog = ProgressDialog.show(MainActivity.this, "", "", true);
        }

        protected void onPostExecute(Void result) {
            mContext.mTask = null;
            mDialog.dismiss();
        }


        @Override
        protected Void doInBackground(Void... params) {
            SystemClock.sleep(5000);
            return null;
        }       
    }
}
n224576
fonte
4

Eu fiz assim:

    package com.palewar;
    import android.app.Activity;
    import android.app.ProgressDialog;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;

    public class ThreadActivity extends Activity {


        static ProgressDialog dialog;
        private Thread downloadThread;
        final static Handler handler = new Handler() {

            @Override
            public void handleMessage(Message msg) {

                super.handleMessage(msg);

                dialog.dismiss();

            }

        };

        protected void onDestroy() {
    super.onDestroy();
            if (dialog != null && dialog.isShowing()) {
                dialog.dismiss();
                dialog = null;
            }

        }

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

            downloadThread = (Thread) getLastNonConfigurationInstance();
            if (downloadThread != null && downloadThread.isAlive()) {
                dialog = ProgressDialog.show(ThreadActivity.this, "",
                        "Signing in...", false);
            }

            dialog = ProgressDialog.show(ThreadActivity.this, "",
                    "Signing in ...", false);

            downloadThread = new MyThread();
            downloadThread.start();
            // processThread();
        }

        // Save the thread
        @Override
        public Object onRetainNonConfigurationInstance() {
            return downloadThread;
        }


        static public class MyThread extends Thread {
            @Override
            public void run() {

                try {
                    // Simulate a slow network
                    try {
                        new Thread().sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    handler.sendEmptyMessage(0);

                } finally {

                }
            }
        }

    }

Você também pode tentar e me avise que funciona para você ou não

Sachin Gurnani
fonte
O código onDestroy pode não ser executado, na página do desenvolvedor : "Observe a coluna" Matável "na tabela acima - para os métodos marcados como matáveis, após esse método retornar o processo que hospeda a atividade que pode ser matada pelo sistema a qualquer momento , sem outra linha de seu código a ser executado "
ilomambo
Acredito firmemente que "onRetainNonConfigurationInstance ()" é o método a ser utilizado para tais casos ... trabalho nyc
Nitin Bansal
Estou enfrentando um problema semelhante, como Sachin Gurnani faz usando declaração estática para corrigir meu problema. stackoverflow.com/questions/12058774/…
Steven Du
2

Se você criar um plano de fundo Serviceque faça todo o trabalho pesado (solicitações / resposta tcp, desserialização), o Viewe Activitypoderá ser destruído e recriado sem vazar janela ou perder dados. Isso permite o comportamento recomendado pelo Android, que é destruir uma Atividade em cada alteração na configuração (por exemplo, para cada alteração na orientação).

É um pouco mais complexo, mas é a melhor maneira de chamar solicitação de servidor, pré / pós-processamento de dados, etc.

Você pode até usar o seu Servicepara enfileirar cada solicitação em um servidor, tornando mais fácil e eficiente lidar com essas coisas.

O guia do desenvolvedor tem um capítuloServices completo .

acardenas89
fonte
A Serviceé mais trabalho que um, AsyncTaskmas pode ser uma abordagem melhor em algumas situações. Não é necessariamente melhor, é? Dito isto, não entendo como isso resolve o problema do ProgressDialogque vazou do principal Activity. Onde você instancia ProgressDialog? Onde você o dispensa?
Rd
2

Eu tenho uma implementação que permite que a atividade seja destruída em uma alteração na orientação da tela, mas ainda destrói o diálogo na atividade recriada com êxito. Eu uso ...NonConfigurationInstancepara anexar a tarefa em segundo plano à atividade recriada. A estrutura normal do Android lida com a recriação da própria caixa de diálogo, nada é alterado lá.

Subclassifiquei o AsyncTask adicionando um campo para a atividade 'proprietária' e um método para atualizar esse proprietário.

class MyBackgroundTask extends AsyncTask<...> {
  MyBackgroundTask (Activity a, ...) {
    super();
    this.ownerActivity = a;
  }

  public void attach(Activity a) {
    ownerActivity = a;
  }

  protected void onPostExecute(Integer result) {
    super.onPostExecute(result);
    ownerActivity.dismissDialog(DIALOG_PROGRESS);
  }

  ...
}

Na minha classe de atividade, adicionei um campo backgroundTaskreferente à tarefa de fundo 'de propriedade' e atualizo esse campo usando onRetainNonConfigurationInstancee getLastNonConfigurationInstance.

class MyActivity extends Activity {
  public void onCreate(Bundle savedInstanceState) {
    ...
    if (getLastNonConfigurationInstance() != null) {
      backgroundTask = (MyBackgroundTask) getLastNonConfigurationInstance();
      backgroundTask.attach(this);
    }
  }

  void startBackgroundTask() {
    backgroundTask = new MyBackgroundTask(this, ...);
    showDialog(DIALOG_PROGRESS);
    backgroundTask.execute(...);
  }

  public Object onRetainNonConfigurationInstance() {
    if (backgroundTask != null && backgroundTask.getStatus() != Status.FINISHED)
      return backgroundTask;
    return null;
  }
  ...
}

Sugestões para melhorias adicionais:

  • Limpe a backgroundTaskreferência na atividade após a conclusão da tarefa para liberar qualquer memória ou outros recursos associados a ela.
  • Limpe a ownerActivityreferência na tarefa em segundo plano antes que a atividade seja destruída, caso não seja recriada imediatamente.
  • Crie uma BackgroundTaskinterface e / ou coleção para permitir que diferentes tipos de tarefas sejam executados a partir da mesma atividade proprietária.
beetstra
fonte
2

Se você mantiver dois layouts, todo o thread da interface do usuário deve ser encerrado.

Se você usar o AsynTask, poderá chamar facilmente o .cancel()método dentro do onDestroy()método da atividade atual.

@Override
protected void onDestroy (){
    removeDialog(DIALOG_LOGIN_ID); // remove loading dialog
    if (loginTask != null){
        if (loginTask.getStatus() != AsyncTask.Status.FINISHED)
            loginTask.cancel(true); //cancel AsyncTask
    }
    super.onDestroy();
}

Para o AsyncTask, leia mais na seção "Cancelando uma tarefa" aqui .

Atualização: condição adicionada para verificar o status, pois só pode ser cancelado se estiver no estado de execução. Observe também que o AsyncTask pode ser executado apenas uma vez.

Vikas
fonte
2

Tentou implementar a solução da jfelectron porque é uma " solução sólida para esses problemas que está em conformidade com o 'Modo Android' ", mas levou algum tempo para procurar e reunir todos os elementos mencionados. Acabei com essa solução um pouco diferente, e acho mais elegante, postada aqui na íntegra.

Usa um IntentService acionado a partir de uma atividade para executar a tarefa de execução longa em um encadeamento separado. O serviço lança de volta intenções de transmissão persistentes para a atividade que atualiza a caixa de diálogo. A Atividade usa showDialog (), onCreateDialog () e onPrepareDialog () para eliminar a necessidade de passar dados persistentes no objeto de aplicativo ou no pacote salvoInstanceState. Isso deve funcionar, independentemente de como seu aplicativo seja interrompido.

Classe de atividade:

public class TesterActivity extends Activity {
private ProgressDialog mProgressDialog;
private static final int PROGRESS_DIALOG = 0;

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

    Button b = (Button) this.findViewById(R.id.test_button);
    b.setOnClickListener(new OnClickListener() {
        public void onClick(View v) {
            buttonClick();
        }
    });
}

private void buttonClick(){
    clearPriorBroadcast();
    showDialog(PROGRESS_DIALOG);
    Intent svc = new Intent(this, MyService.class);
    startService(svc);
}

protected Dialog onCreateDialog(int id) {
    switch(id) {
    case PROGRESS_DIALOG:
        mProgressDialog = new ProgressDialog(TesterActivity.this);
        mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        mProgressDialog.setMax(MyService.MAX_COUNTER);
        mProgressDialog.setMessage("Processing...");
        return mProgressDialog;
    default:
        return null;
    }
}

@Override
protected void onPrepareDialog(int id, Dialog dialog) {
    switch(id) {
    case PROGRESS_DIALOG:
        // setup a broadcast receiver to receive update events from the long running process
        IntentFilter filter = new IntentFilter();
        filter.addAction(MyService.BG_PROCESS_INTENT);
        registerReceiver(new MyBroadcastReceiver(), filter);
        break;
    }
}

public class MyBroadcastReceiver extends BroadcastReceiver{
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.hasExtra(MyService.KEY_COUNTER)){
            int count = intent.getIntExtra(MyService.KEY_COUNTER, 0);
            mProgressDialog.setProgress(count);
            if (count >= MyService.MAX_COUNTER){
                dismissDialog(PROGRESS_DIALOG);
            }
        }
    }
}

/*
 * Sticky broadcasts persist and any prior broadcast will trigger in the 
 * broadcast receiver as soon as it is registered.
 * To clear any prior broadcast this code sends a blank broadcast to clear 
 * the last sticky broadcast.
 * This broadcast has no extras it will be ignored in the broadcast receiver 
 * setup in onPrepareDialog()
 */
private void clearPriorBroadcast(){
    Intent broadcastIntent = new Intent();
    broadcastIntent.setAction(MyService.BG_PROCESS_INTENT);
    sendStickyBroadcast(broadcastIntent);
}}

Classe IntentService:

public class MyService extends IntentService {

public static final String BG_PROCESS_INTENT = "com.mindspiker.Tester.MyService.TEST";
public static final String KEY_COUNTER = "counter";
public static final int MAX_COUNTER = 100;

public MyService() {
  super("");
}

@Override
protected void onHandleIntent(Intent intent) {
    for (int i = 0; i <= MAX_COUNTER; i++) {
        Log.e("Service Example", " " + i);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Intent broadcastIntent = new Intent();
        broadcastIntent.setAction(BG_PROCESS_INTENT);
        broadcastIntent.putExtra(KEY_COUNTER, i);
        sendStickyBroadcast(broadcastIntent);
    }
}}

Entradas de arquivo de manifesto:

antes da seção de aplicação:

uses-permission android:name="com.mindspiker.Tester.MyService.TEST"
uses-permission android:name="android.permission.BROADCAST_STICKY"

seção de aplicação interna

service android:name=".MyService"
MindSpiker
fonte
2

Esta é a minha solução proposta:

  • Mova o AsyncTask ou Thread para um fragmento retido, conforme explicado aqui . Acredito que é uma boa prática mover todas as chamadas de rede para fragmentos. Se você já estiver usando fragmentos, um deles poderá ser responsabilizado pelas chamadas. Caso contrário, você pode criar um fragmento apenas para fazer a solicitação, como propõe o artigo vinculado.
  • O fragmento usará uma interface de ouvinte para sinalizar a conclusão / falha da tarefa. Você não precisa se preocupar com mudanças de orientação lá. O fragmento sempre terá o link correto para a atividade atual e o diálogo de progresso poderá ser retomado com segurança.
  • Faça do seu diálogo de progresso um membro da sua classe. Na verdade, você deve fazer isso em todas as caixas de diálogo. No método onPause, você deve descartá-los, caso contrário, você vazará uma janela na alteração da configuração. O estado ocupado deve ser mantido pelo fragmento. Quando o fragmento está anexado à atividade, você pode exibir a caixa de diálogo de progresso novamente, se a chamada ainda estiver em execução. Um void showProgressDialog()método pode ser adicionado à interface do ouvinte de atividade de fragmento para esse fim.
kgiannakakis
fonte
Solução perfeita, mas não entendo por que essa resposta é ofuscada por outras pessoas !!!
blackkara
2

Eu enfrentei a mesma situação. O que fiz foi obter apenas uma instância para o meu diálogo de progresso em todo o aplicativo.

Primeiro, criei uma classe DialogSingleton para obter apenas uma instância (padrão Singleton)

public class DialogSingleton
{
    private static Dialog dialog;

    private static final Object mLock = new Object();
    private static DialogSingleton instance;

    private DialogSingleton()
    {

    }

    public static DialogSingleton GetInstance()
    {
        synchronized (mLock)
        {
            if(instance == null)
            {
                instance = new DialogSingleton();
            }

            return instance;
        }
    }

    public void DialogShow(Context context, String title)
    {
        if(!((Activity)context).isFinishing())
        {
            dialog = new ProgressDialog(context, 2);

            dialog.setCanceledOnTouchOutside(false);

            dialog.setTitle(title);

            dialog.show();
        }
    }

    public void DialogDismiss(Context context)
    {
        if(!((Activity)context).isFinishing() && dialog.isShowing())
        {
            dialog.dismiss();
        }
    }
}

Como mostro nesta classe, tenho o diálogo de progresso como atributo. Sempre que preciso mostrar uma caixa de diálogo de progresso, obtenho a instância exclusiva e crio um novo ProgressDialog.

DialogSingleton.GetInstance().DialogShow(this, "My title here!");

Quando termino a tarefa em segundo plano, chamo novamente a instância exclusiva e encerro sua caixa de diálogo.

DialogSingleton.GetInstance().DialogDismiss(this);

Eu salvo o status da tarefa em segundo plano nas minhas preferências compartilhadas. Ao girar a tela, pergunto se tenho uma tarefa em execução para esta atividade: (onCreate)

if(Boolean.parseBoolean(preference.GetValue(IS_TASK_NAME_EXECUTED_KEY, "boolean").toString()))
{
    DialogSingleton.GetInstance().DialogShow(this, "Checking credentials!");
} // preference object gets the info from shared preferences (my own implementation to get and put data to shared preferences) and IS_TASK_NAME_EXECUTED_KEY is the key to save this flag (flag to know if this activity has a background task already running).

Quando começo a executar uma tarefa em segundo plano:

preference.AddValue(IS_TASK_NAME_EXECUTED_KEY, true, "boolean");

DialogSingleton.GetInstance().DialogShow(this, "My title here!");

Quando eu terminar de executar uma tarefa em segundo plano:

preference.AddValue(IS_TASK_NAME_EXECUTED_KEY, false, "boolean");

DialogSingleton.GetInstance().DialogDismiss(ActivityName.this);

Espero que ajude.

deinier
fonte
2

Essa é uma pergunta muito antiga que surgiu na barra lateral por algum motivo.

Se a tarefa em segundo plano precisar sobreviver apenas enquanto a atividade estiver em primeiro plano, a "nova" solução será hospedar o encadeamento em segundo plano (ou, de preferência, AsyncTask) em um fragmento retido , conforme descrito neste guia do desenvolvedor e em várias perguntas e respostas .

Um fragmento retido sobrevive se a atividade for destruída por uma alteração na configuração, mas não quando a atividade for destruída em segundo plano ou na pilha traseira. Portanto, a tarefa em segundo plano ainda deve ser interrompida se isChangingConfigurations()for falsa onPause().

Kevin Krumwiede
fonte
2

Eu sou um novato no Android e tentei isso e deu certo.

public class loadTotalMemberByBranch extends AsyncTask<Void, Void,Void> {
        ProgressDialog progressDialog = new ProgressDialog(Login.this);
        int ranSucess=0;
        @Override
        protected void onPreExecute() {
            // TODO Auto-generated method stub
            super.onPreExecute();
            progressDialog.setTitle("");    
            progressDialog.isIndeterminate();
            progressDialog.setCancelable(false);
            progressDialog.show();
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);

        }
        @Override
        protected Void doInBackground(Void... params) {
            // TODO Auto-generated method stub

            return null;
        }
        @Override
        protected void onPostExecute(Void result) {
            // TODO Auto-generated method stub
            super.onPostExecute(result);
            progressDialog.dismiss();
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
        }
}
Cloy
fonte
1

Eu tentei TUDO. Passou dias experimentando. Eu não queria impedir que a atividade girasse. Meu cenário foi:

  1. Uma caixa de diálogo de progresso mostrando informações dinâmicas para o usuário. Por exemplo: "Conectando ao servidor ...", "Fazendo download de dados ..." etc.
  2. Um tópico fazendo as coisas pesadas e atualizando a caixa de diálogo
  3. Atualizando a interface do usuário com os resultados no final.

O problema foi que, ao girar a tela, todas as soluções do livro falharam. Mesmo com a classe AsyncTask, que é a maneira correta do Android de lidar com essas situações. Ao girar a tela, o Contexto atual com o qual o thread inicial está trabalhando desaparece e isso atrapalha a caixa de diálogo exibida. O problema sempre foi o Diálogo, não importa quantos truques eu adicionei ao código (passando novos contextos para os threads em execução, mantendo os estados dos threads através de rotações, etc ...). A complexidade do código no final sempre foi enorme e sempre havia algo que poderia dar errado.

A única solução que funcionou para mim foi o truque Activity / Dialog. É simples e genial e é tudo à prova de rotação:

  1. Em vez de criar um Diálogo e pedir para mostrá-lo, crie uma Atividade que foi definida no manifesto com android: theme = "@ android: style / Theme.Dialog". Então, parece apenas um diálogo.

  2. Substitua showDialog (DIALOG_ID) por startActivityForResult (yourActivityDialog, yourCode);

  3. Use onActivityResult na atividade de chamada para obter os resultados do encadeamento em execução (mesmo os erros) e atualizar a interface do usuário.

  4. Em seu 'ActivityDialog', use threads ou AsyncTask para executar tarefas longas e onRetainNonConfigurationInstance para salvar o estado de "diálogo" ao girar a tela.

Isso é rápido e funciona bem. Ainda uso diálogos para outras tarefas e o AsyncTask para algo que não requer um diálogo constante na tela. Mas com esse cenário, eu sempre busco o padrão Atividade / Diálogo.

E não tentei, mas é possível impedir que a Atividade / Diálogo gire, quando o thread estiver em execução, acelerando as coisas, enquanto permite que a Atividade que está chamando gire.

Rui
fonte
Bom, exceto que os parâmetros devem ser passados ​​através de um Intent, que é mais restritivo do que o Objectpermitido porAsyncTask
rds
@Rui Eu também usei esse método no ano passado. Agora me ocorreu que isso também está incorreto. Por que o Google ainda teria um diálogo se esse era o caminho para "corrigir" esse problema? O problema que vejo é que, se você abrir o ActivityB (Theme.Dialog) do ActivityA, o ActivityA será movido para baixo na pilha de atividades e, portanto, será marcado como pronto para ser eliminado pelo sistema operacional, se necessário. Portanto, se você tiver um longo processo de execução e estiver mostrando algum tipo de "diálogo" de progresso falso, e demorou muito e a memória ficou baixa ... A ActivityA é interrompida e não há nada para voltar quando o progresso é concluído.
Rf43 #
1

Atualmente, existe uma maneira muito mais distinta de lidar com esses tipos de problemas. A abordagem típica é:

1. Verifique se seus dados estão separados adequadamente da interface do usuário:

Qualquer coisa que seja um processo em segundo plano deve ser mantida Fragment(defina-a com Fragment.setRetainInstance(). Isso se torna seu 'armazenamento de dados persistente', onde são mantidos os dados com base nos quais você deseja reter. Após o evento de mudança de orientação, Fragmentele ainda estará acessível em seu original estado através de uma FragmentManager.findFragmentByTag()chamada (quando você a cria, deve atribuir a ela uma tag, não um ID, pois não está anexado a View).

Consulte o guia desenvolvido Handling Runtime Changes para obter informações sobre como fazer isso corretamente e por que é a melhor opção.

2. Verifique se você está interagindo corretamente e com segurança entre os processos em segundo plano e sua interface do usuário:

Você deve reverter seu processo de vinculação. No momento, seu processo em segundo plano se anexa a um View- em vez disso, você Viewdeve se anexar ao processo em segundo plano. Faz mais sentido, certo? A Viewação da depende do processo em segundo plano, enquanto o processo em segundo plano não depende do View. Isso significa alterar o link para uma Listenerinterface padrão . Diga que seu processo (seja qual for a classe - seja uma AsyncTask, Runnableseja qual for) define umOnProcessFinishedListener , quando o processo é feito ele deve chamar esse ouvinte se ele existir.

Esta resposta é uma boa descrição concisa de como fazer ouvintes personalizados.

3. Vincule sua interface do usuário ao processo de dados sempre que a interface do usuário for criada (incluindo alterações de orientação):

Agora você deve se preocupar com a interface da tarefa em segundo plano com a Viewestrutura atual . Se você estiver lidando com as mudanças de orientação corretamente (e não as configChangespessoas sempre recomendam), você Dialogserá recriado pelo sistema. Isso é importante, significa que, na mudança de orientação, todos Dialogos métodos do ciclo de vida do seu são recuperados. Portanto, em qualquer um desses métodos ( onCreateDialoggeralmente é um bom lugar), você pode fazer uma chamada da seguinte maneira:

DataFragment f = getActivity().getFragmentManager().findFragmentByTag("BACKGROUND_TAG");
if (f != null) {
    f.mBackgroundProcess.setOnProcessFinishedListener(new OnProcessFinishedListener() {
        public void onProcessFinished() {
            dismiss();
        }
    });
 }

Consulte o ciclo de vida do fragmento para decidir onde a configuração do ouvinte se encaixa melhor na sua implementação individual.

Essa é uma abordagem geral para fornecer uma solução robusta e completa para o problema genérico solicitado nesta pergunta. Provavelmente faltam algumas partes menores nessa resposta, dependendo do cenário individual, mas essa geralmente é a abordagem mais correta para lidar adequadamente com os eventos de mudança de orientação.

BT
fonte
1

Eu encontrei uma solução mais fácil de lidar com tópicos quando a orientação muda. Você pode apenas manter uma referência estática à sua atividade / fragmento e verificar se é nula antes de agir na interface do usuário. Sugiro usar um try catch também:

 public class DashListFragment extends Fragment {
     private static DashListFragment ACTIVE_INSTANCE;

     @Override
     public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ACTIVE_INSTANCE = this;

        new Handler().postDelayed(new Runnable() {
            public void run() {
                try {
                        if (ACTIVE_INSTANCE != null) {
                            setAdapter(); // this method do something on ui or use context
                        }
                }
                catch (Exception e) {}


            }
        }, 1500l);

    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        ACTIVE_INSTANCE = null;
    }


}
sagits
fonte
1

Se você está com dificuldades para detectar eventos de mudança de orientação de um diálogo INDEPENDENTE DE UMA REFERÊNCIA DE ATIVIDADE , esse método funciona muito bem. Eu uso isso porque tenho minha própria classe de diálogo que pode ser mostrada em várias atividades diferentes, portanto nem sempre sei em qual atividade está sendo exibida. Com esse método, você não precisa alterar o AndroidManifest, preocupe-se com as referências de atividade, e você não precisa de uma caixa de diálogo personalizada (como eu tenho). Você precisa, no entanto, de uma exibição de conteúdo personalizada para poder detectar as alterações de orientação usando essa exibição específica. Aqui está o meu exemplo:

Configuração

public class MyContentView extends View{
    public MyContentView(Context context){
        super(context);
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig){
        super.onConfigurationChanged(newConfig);

        //DO SOMETHING HERE!! :D
    }
}

Implementação 1 - Diálogo

Dialog dialog = new Dialog(context);
//set up dialog
dialog.setContentView(new MyContentView(context));
dialog.show();

Implementação 2 - AlertDialog.Builder

AlertDialog.Builder builder = new AlertDialog.Builder(context);
//set up dialog builder
builder.setView(new MyContentView(context));        //Can use this method
builder.setCustomTitle(new MycontentView(context)); // or this method
builder.build().show();

Implementação 3 - ProgressDialog / AlertDialog

ProgressDialog progress = new ProgressDialog(context);
//set up progress dialog
progress.setView(new MyContentView(context));        //Can use this method
progress.setCustomTitle(new MyContentView(context)); // or this method
progress.show();
kpninja12
fonte
1

Esta é minha solução quando a enfrentei: ProgressDialognão é uma Fragmentcriança, portanto minha classe personalizada " ProgressDialogFragment" pode se estender DialogFragmentpara manter a caixa de diálogo mostrada para alterações na configuração.

import androidx.annotation.NonNull;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.os.Bundle; 
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager;

 /**
 * Usage:
 * To display the dialog:
 *     >>> ProgressDialogFragment.showProgressDialogFragment(
 *              getSupportFragmentManager(), 
 *              "fragment_tag", 
 *              "my dialog title", 
 *              "my dialog message");
 *              
 * To hide the dialog
 *     >>> ProgressDialogFragment.hideProgressDialogFragment();
 */ 


public class ProgressDialogFragment extends DialogFragment {

    private static String sTitle, sMessage;
    private static ProgressDialogFragment sProgressDialogFragment;

    public ProgressDialogFragment() {
    }

    private ProgressDialogFragment(String title, String message) {
        sTitle = title;
        sMessage = message;
    }


    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return ProgressDialog.show(getActivity(), sTitle, sMessage);
    }

    public static void showProgressDialogFragment(FragmentManager fragmentManager, String fragmentTag, String title, String message) {
        if (sProgressDialogFragment == null) {
            sProgressDialogFragment = new ProgressDialogFragment(title, message);
            sProgressDialogFragment.show(fragmentManager, fragmentTag);

        } else { // case of config change (device rotation)
            sProgressDialogFragment = (ProgressDialogFragment) fragmentManager.findFragmentByTag(fragmentTag); // sProgressDialogFragment will try to survive its state on configuration as much as it can, but when calling .dismiss() it returns NPE, so we have to reset it on each config change
            sTitle = title;
            sMessage = message;
        }

    }

    public static void hideProgressDialogFragment() {
        if (sProgressDialogFragment != null) {
            sProgressDialogFragment.dismiss();
        }
    }
}

O desafio era manter o título e a mensagem da caixa de diálogo durante a rotação da tela, conforme redefinidos para a sequência vazia padrão, embora a caixa de diálogo ainda seja exibida

Existem 2 abordagens para resolver isso:

Primeira abordagem: faça a atividade que utiliza o diálogo para reter o estado durante a alteração de configuração no arquivo de manifesto:

android:configChanges="orientation|screenSize|keyboardHidden"

Essa abordagem não é preferida pelo Google.

Segunda abordagem: no onCreate()método da atividade , você deve reter o seu DialogFragment, reconstruindo o ProgressDialogFragmentnovamente com o título e a mensagem da seguinte forma, se savedInstanceStatenão for nulo:

@Override
protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_deal);

 if (savedInstanceState != null) {
      ProgressDialogFragment saveProgressDialog = (ProgressDialogFragment) getSupportFragmentManager()
              .findFragmentByTag("fragment_tag");
      if (saveProgressDialog != null) {
          showProgressDialogFragment(getSupportFragmentManager(), "fragment_tag", "my dialog title", "my dialog message");
      }
  }
}
Zain
fonte
0

Parece muito "rápido e sujo" para ser verdade, por favor, aponte as falhas, mas o que eu achei funcionou foi ...

No método onPostExecute do meu AsyncTask, simplesmente agrupei o '.dismiss' da caixa de diálogo de progresso em um bloco try / catch (com um catch vazio) e simplesmente ignorei a exceção que foi gerada. Parece errado, mas parece que não há efeitos negativos (pelo menos para o que estou fazendo posteriormente, que é iniciar outra atividade que passa no resultado da minha consulta de longa duração como Extra)

Simon
fonte
Você está dizendo que realmente não há vazamento de memória quando as plataformas Android informam que a janela vazou?
Rds
Minha caixa de diálogo de progresso é uma variável de membro da classe de atividade, portanto, presumi que quando a atividade for destruída e recriada, ela será coletada como lixo e não haverá vazamento. Estou errado?
28411 Simon
Sim, acho que está errado. Como você diz, o Activitytem uma referência ao Dialog. Quando a configuração é alterada, a primeira Activityé destruída, o que significa que todos os campos estão definidos como null. Mas o nível inferior WindowManagertambém tem uma referência ao Dialog(já que ainda não foi descartado). O novo Activitytenta criar um novo Dialog(in preExecute()) e o gerenciador de janelas gera uma exceção fatal, impedindo que você faça isso. De fato, se o fizer, não haveria maneira de destruir de maneira limpa a Dialogportanto mantendo uma referência à inicial Activity. Estou certo?
rds 29/03
0

A solução mais simples e flexível é usar um AsyncTask com uma referência estática ao ProgressBar . Isso fornece uma solução encapsulada e, portanto, reutilizável para problemas de mudança de orientação. Essa solução me serviu bem para diversas tarefas assíncronas, incluindo downloads da Internet, comunicação com os Serviços e verificações do sistema de arquivos. A solução foi bem testada em várias versões do Android e modelos de telefone. Uma demonstração completa pode ser encontrada aqui com interesse específico em DownloadFile.java

Apresento o seguinte como exemplo de conceito

public class SimpleAsync extends AsyncTask<String, Integer, String> {
    private static ProgressDialog mProgressDialog = null;
    private final Context mContext;

    public SimpleAsync(Context context) {
        mContext = context;
        if ( mProgressDialog != null ) {
            onPreExecute();
        }
    }

    @Override
    protected void onPreExecute() {
        mProgressDialog = new ProgressDialog( mContext );
        mProgressDialog.show();
    }

    @Override
    protected void onPostExecute(String result) {
        if ( mProgressDialog != null ) {
            mProgressDialog.dismiss();
            mProgressDialog = null;
        }
    }

    @Override
    protected void onProgressUpdate(Integer... progress) {
        mProgressDialog.setProgress( progress[0] );
    }

    @Override
    protected String doInBackground(String... sUrl) {
        // Do some work here
        publishProgress(1);
        return null;
    }

    public void dismiss() {
        if ( mProgressDialog != null ) {
            mProgressDialog.dismiss();
        }
    }
}

O uso em uma atividade do Android é simples

public class MainActivity extends Activity {
    DemoServiceClient mClient = null;
    DownloadFile mDownloadFile = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate( savedInstanceState );
        setContentView( R.layout.main );
        mDownloadFile = new DownloadFile( this );

        Button downloadButton = (Button) findViewById( R.id.download_file_button );
        downloadButton.setOnClickListener( new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mDownloadFile.execute( "http://www.textfiles.com/food/bakebred.txt");
            }
        });
    }

    @Override
    public void onPause() {
        super.onPause();
        mDownloadFile.dismiss();
    }
}
Derrick J Wippler
fonte
0

Quando você muda as orientações, o Android elimina essa atividade e cria uma nova atividade. Eu sugiro usar o retrofit com o Rx java. que lida com falhas automaticamente.

Use esse método ao atualizar a chamada.

.subscribeOn (Schedulers.io ()) .observeOn (AndroidSchedulers.mainThread ())

priti
fonte