Comment gérer le changement d'orientation de l'écran lorsque le dialogue de progression et le thread d'arrière-plan sont actifs?
mon programme fait une certaine activité de réseau dans un fil de fond. Avant de commencer, il apparaît un dialogue de progrès. Le dialogue est rejeté sur le gestionnaire. Cela fonctionne très bien, sauf lorsque l'orientation de l'écran change pendant que la boîte de dialogue est activée (et que le thread d'arrière-plan est activé). À ce stade, l'application se bloque, ou de blocages, ou pénètre dans une étrange scène où l'application ne fonctionne pas du tout jusqu'à ce que tous les fils ont été tués.
comment gérer l'écran changement d'orientation avec élégance?
le code échantillon ci-dessous correspond à peu près à ce que fait mon vrai programme:
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();
}
};
}
"151920920 Pile":
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.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)
j'ai essayé de rejeter le dialogue de progrès dans onSaveInstanceState, mais cela empêche juste un accident immédiat. Le fil d'arrière-plan est toujours actif, et L'interface utilisateur est dans un état partiellement étiré. Besoin de tuer l'application entière avant qu'elle ne commence à fonctionner à nouveau.
26 réponses
lorsque vous changez d'orientation, Android va créer une nouvelle vue. Vous avez probablement des plantages parce que votre fil d'arrière-plan essaye de changer l'état de l'ancien. (Il peut également avoir des problèmes parce que votre fil d'arrière-plan n'est pas sur le fil UI)
je suggère de rendre ce mHandler volatile et de le mettre à jour quand l'orientation change.
modifier: les ingénieurs de Google ne recommandent pas cette approche, comme décrit par Dianne Hackborn (a. K. A. hackbod ) dans ce" 151980920 StackOverflow post 151970920". Consultez ce billet de blog pour plus d'informations.
vous devez ajouter ceci à la déclaration d'activité dans le manifeste:
android:configChanges="orientation|screenSize"
de sorte qu'il ressemble
<activity android:label="@string/app_name"
android:configChanges="orientation|screenSize|keyboardHidden"
android:name=".your.package">
La question est que le système détruit l'activité lors d'un changement de configuration se produit. Voir Changements De Configuration .
donc mettre cela dans le fichier de configuration évite au système de détruire votre activité. Il invoque plutôt la méthode onConfigurationChanged(Configuration)
.
j'ai trouvé une solution solide pour ces problèmes qui est conforme à la "manière Android" des choses. J'ai toutes mes opérations de longue date en utilisant le modèle IntentService.
Ce sont mes activités diffusion intentions, L'IntentService fait le travail, sauve les données dans le DB et puis diffuse des intentions collantes.
La partie collante est importante, de sorte que même si L'activité a été interrompue pendant le temps après que l'Utilisateur a initié le travailler et manquer la diffusion en temps réel de L'ItentService nous pouvons encore répondre et ramasser les données de l'activité d'appel.
ProgressDialogs
peut très bien fonctionner dans ce modèle avec onSaveInstanceSate()
.
Fondamentalement, vous devez enregistrer un drapeau que vous avez une boîte de dialogue de progression dans le paquet d'instance sauvegardé. Ne sauvegardez pas l'objet de dialogue progress, car il en résultera une fuite de L'ensemble de l'activité.
Pour avoir une poignée persistante pour le dialogue de progression, je le stocke comme une référence faible dans l'objet application.
Sur le changement d'orientation ou toute autre chose qui provoque la pause de L'activité (appel téléphonique, clics de l'utilisateur à la maison, etc) et puis reprendre, je rejette l'ancien dialogue et recréer un nouveau dialogue dans l'activité nouvellement créée.
Pour des dialogues de progrès indéfini c'est facile. Pour le style de barre de progrès, vous devez mettre le dernier progrès connu dans le paquet et quelle que soit l'information que vous êtes utiliser localement dans l'activité pour suivre les progrès.
Sur Restaurer la progression, vous utiliserez cette information pour relancer la barre de progression dans le même état qu'avant et ensuite mettre à jour en fonction de l'état actuel des choses.
Donc, pour résumer, mettre des tâches de longue durée dans un IntentService couplé avec l'utilisation juidcieuse de onSaveInstanceState()
vous permet de garder efficacement la trace des dialogues et restaurer ensuite à travers les événements du cycle de vie de l'activité. Les bits pertinents du code D'activité sont ci-dessous. Vous aurez également besoin de logique dans votre Diffusreceiver pour gérer les intentions collantes de manière appropriée, mais qui est au-delà de la portée de cette.
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));
}
}
j'ai rencontré le même problème. Mon activité doit analyser des données à partir d'une URL et elle est lente. Donc je crée un fil pour le faire puis montrer un progressdialog. J'ai laissé le thread poster un msg de nouveau à L'UI thread via Handler quand il est terminé. Dans Le Gestionnaire.handleMessage, j'obtiens l'objet de données (prêt maintenant) à partir du thread et je le remplis en UI. C'est donc très similaire à votre exemple.
après de nombreux essais et erreurs, il semble que j'ai trouvé une solution. Au moins maintenant je peux tourner l'écran à n'importe quel moment moment, avant ou après le fil est fait. Dans tous les tests, le dialogue est correctement fermé et tous les comportements sont comme prévu.
ce que j'ai fait est montré ci-dessous. Le but est de remplir mon modèle de données (mDataObject) puis de le remplir en UI. Devrait permettre la rotation de l'écran à tout moment sans surprise.
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;
public MyHandler(..., Handler h) {...; mHandler = h;} // constructor with handler param
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.
}
}
c'est ce qui marche pour moi. Je ne sais pas si c'est la méthode "correcte" telle que conçue par Android -- ils prétendent que " détruire / recréer l'activité pendant la rotation de l'écran " rend en fait les choses plus faciles, Donc je suppose que cela ne devrait pas être trop délicat.
Laissez-moi savoir si vous voyez un problème dans mon code. Comme je l'ai dit plus haut, Je ne sais pas vraiment s'il y a des effets secondaires.
ma solution a été d'étendre la classe ProgressDialog
pour obtenir mon propre MyProgressDialog
.
J'ai redéfini show()
et dismiss()
méthodes pour verrouiller l'orientation avant de montrer le Dialog
et déverrouiller en arrière quand Dialog
est rejetée. Ainsi, lorsque le Dialog
est affiché et que l'orientation du dispositif change, l'orientation de l'écran reste jusqu'à ce que dismiss()
soit appelé, puis l'orientation de l'écran change selon capteur de valeurs/appareil-orientation.
Voici mon code:
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);
}
}
le problème perçu à l'origine était que le code ne survivrait pas à un changement d'orientation à l'écran. Apparemment, cela a été "résolu" en demandant au programme de modifier lui-même l'orientation de l'écran, au lieu de laisser le cadre de L'UI le faire (en appelant onDestroy)).
je dirais que si le problème sous-jacent est que le programme ne survivra pas onDestroy (), alors la solution acceptée est juste une solution de contournement qui laisse le programme avec d'autres problèmes sérieux et les vulnérabilités. Rappelez-vous que le cadre Android précise que votre activité est à risque d'être détruite presque à tout moment en raison de circonstances indépendantes de votre volonté. Par conséquent, votre activité doit être capable de survivre à onDestroy() et à onCreate() pour n'importe quelle raison, pas seulement un changement d'orientation de l'écran.
si vous allez accepter de gérer les changements d'orientation de l'écran vous-même pour résoudre le problème de L'OP, vous devez vérifier que d'autres causes de onDestroy() n'entraînent pas la même erreur. Êtes-vous capable de faire cela? Dans la négative, je me demande si la réponse "acceptée" est vraiment très bonne.
j'ai fait face à ce même problème, et j'ai trouvé une solution qui n'implique pas l'utilisation du ProgressDialog et j'obtiens des résultats plus rapides.
ce que j'ai fait était de créer un layout qui a une barre de progression.
<?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>
puis dans la méthode onCreate faire la suivante
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.progress);
}
alors faire la longue tâche dans un thread, et quand ce est terminé ont un ensemble exécutable la vue de contenu à la mise en page réelle que vous voulez utiliser pour ce activité.
par exemple:
mHandler.post(new Runnable(){
public void run() {
setContentView(R.layout.my_layout);
}
});
C'est ce que j'ai fait, et j'ai trouvé qu'il fonctionne plus vite que montrer le ProgressDialog et il est moins intrusif et a un meilleur regard à mon avis.
cependant, si vous voulez utiliser le ProgressDialog, alors cette réponse n'est pas pour vous.
j'ai découvert une solution à cela que je n'ai pas encore vu ailleurs. Vous pouvez utiliser un objet d'application personnalisé qui sait si vous avez des tâches d'arrière-plan en cours, au lieu d'essayer de le faire dans l'activité qui est détruite et recréée sur le changement d'orientation. J'ai blogué à ce sujet dans ici .
je vais contribuer mon approche à traiter cette question de rotation. Cela n'est peut-être pas pertinent pour OP car il n'utilise pas AsyncTask
, mais peut-être que d'autres le trouveront utile. C'est assez simple, mais il semble faire le travail pour moi:
j'ai une activité de connexion avec une classe imbriquée AsyncTask
appelée BackgroundLoginTask
.
Dans mon BackgroundLoginTask
je ne fais pas tout ce qui sort de l'ordinaire, sauf à ajouter un null vérifier lors de l'appel de ProgressDialog
rejeter:
@Override
protected void onPostExecute(Boolean result)
{
if (pleaseWaitDialog != null)
pleaseWaitDialog.dismiss();
[...]
}
il s'agit de gérer le cas où la tâche de fond se termine alors que le Activity
n'est pas visible et, par conséquent, le dialogue de progression a déjà été rejeté par la méthode onPause()
.
ensuite, dans la classe de mes parents Activity
, je crée des poignées statiques globales pour ma classe AsyncTask
et ma classe ProgressDialog
(les AsyncTask
, étant imbriqués, peuvent accéder à ces variables):
private static BackgroundLoginTask backgroundLoginTask;
private static ProgressDialog pleaseWaitDialog;
cela sert deux buts: D'abord, cela permet à mon Activity
d'accéder toujours à l'objet AsyncTask
même à partir d'une nouvelle activité post-rotation. Deuxièmement, il permet à mon BackgroundLoginTask
d'accéder et de rejeter le ProgressDialog
même après une rotation.
ensuite, j'ajoute ceci à onPause()
, provoquant la disparition du dialogue de progression lorsque notre Activity
quitte le premier plan (empêchant ce vilain crash de" force close"):
if (pleaseWaitDialog != null)
pleaseWaitDialog.dismiss();
Enfin, j'ai ce qui suit dans ma onResume()
méthode:
if ((backgroundLoginTask != null) && (backgroundLoginTask.getStatus() == Status.RUNNING))
{
if (pleaseWaitDialog != null)
pleaseWaitDialog.show();
}
cela permet au Dialog
de réapparaître après le Activity
est recréé.
voici toute la classe:
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();
}
}
}
}
}
Je ne suis en aucun cas un développeur Android chevronné, donc n'hésitez pas à commenter.
déplacer la longue tâche à une classe séparée. Mettre en œuvre comme un objet-modèle observateur. Chaque fois que l'activité est créée, enregistrez-vous et fermez unregistrer avec la classe de tâches. La classe de tâche peut utiliser AsyncTask.
le truc est de montrer/rejeter le dialogue dans AsyncTask pendant onPreExecute/onPostExecute comme d'habitude, bien que dans le cas d'orientation-changement créer/montrer une nouvelle instance du dialogue dans l'activité et passer sa référence à la tâche.
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;
}
}
}
Je l'ai fait comme ceci:
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 {
}
}
}
}
vous pouvez également essayer et me faire savoir qu'il fonctionne pour vous ou non
si vous créez un background Service
qui fait tout le levage lourd (demandes/réponses tcp, démontage), les View
et Activity
peuvent être détruits et recréés sans perte de fenêtre ou de données. Cela permet le comportement recommandé Android, qui est de détruire une activité sur chaque changement de configuration (par exemple. pour chaque changement d'orientation).
Il est un peu plus complexe, mais il est la meilleure façon d'invoquer la requête du serveur, le pré/post-traitement des données,etc.
vous pouvez même utiliser votre Service
pour faire la file d'attente de chaque requête vers un serveur, donc il est facile et efficace de gérer ces choses.
Le dev guide a une pleine chapitre Services
.
j'ai une implémentation qui permet de détruire l'activité lors d'un changement d'orientation de l'écran, mais qui détruit tout de même le dialogue dans l'activité recréée avec succès.
J'utilise ...NonConfigurationInstance
pour attacher la tâche de fond à l'activité recréée.
Le cadre normal Android manipule recréer le dialogue lui-même, rien n'y est changé.
je sous-classé AsyncTask l'ajout d'un champ pour les "posséder", et une méthode de mise à jour de ce propriétaire.
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);
}
...
}
dans ma classe d'activité, j'ai ajouté un champ backgroundTask
faisant référence à la tâche de base" propriété", et j'ai mis à jour ce champ en utilisant onRetainNonConfigurationInstance
et 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;
}
...
}
Suggestions d'amélioration:
- effacer la référence
backgroundTask
dans l'activité après que la tâche est terminée de libérer toute mémoire ou d'autres ressources associées avec elle. - effacer le
ownerActivity
référence dans la tâche de fond avant la destruction de l'activité au cas où elle ne serait pas recréée immédiatement. - créer une interface et/ou une collection
BackgroundTask
pour permettre à différents types de tâches à exécuter à partir de la même activité propriétaire.
si vous maintenez deux layouts, tout le thread UI doit être terminé.
si vous utilisez AsynTask, alors vous pouvez facilement appeler .cancel()
méthode à l'intérieur onDestroy()
méthode de l'activité courante.
@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();
}
Pour AsyncTask, lire la suite de "Annulation d'une tâche" à la section ici .
mise à Jour: Ajout d'une condition pour vérifier le statut, car il ne peut être annulé que s'il est état en cours d'exécution. Notez également que L'AsyncTask ne peut être exécuté qu'une seule fois.
essayé de mettre en œuvre jfelectron solution de " rock-solid solution to these issues that conforms with the 'Android Way 'of things " mais il a fallu un certain temps de chercher et de mettre en place tous les éléments mentionnés. J'ai fini avec cette solution légèrement différente, et je pense que plus élégante, affichée ici dans son intégralité.
utilise un IntentService tiré d'une activité pour effectuer la tâche à long terme sur un thread séparé. Le service renvoie la diffusion sticky intentions à l'activité qui mettent à jour la boîte de dialogue. L'Activité utilise showDialog (), onCreateDialog() et onPrepareDialog () pour éliminer la nécessité de faire passer des données persistantes dans l'objet d'application ou le paquet savedincestate. Cela devrait fonctionner quelle que soit la façon dont votre demande est interrompue.
Classe D'Activité:
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);
}
}}
notices du Manifeste:
avant la section de demande:
uses-permission android:name="com.mindspiker.Tester.MyService.TEST"
uses-permission android:name="android.permission.BROADCAST_STICKY"
à l'intérieur de l'application de la section
service android:name=".MyService"
C'est la solution que je propose:
- déplacer L'AsyncTask ou le fil vers un Fragment retenu, comme expliqué ici . Je crois que c'est une bonne pratique de déplacer tous les appels réseau de fragments. Si vous utilisez déjà des fragments, l'un d'eux pourrait être responsable des appels. Sinon, vous pouvez créer un fragment juste pour faire la demande, comme l'article lié propose.
- le fragment utilisera un écouteur interface pour signaler l'achèvement/l'échec de la tâche. Vous n'avez pas à vous inquiéter pour les changements d'orientation. Le fragment aura toujours le lien correct avec le dialogue activité courante et progression peut être repris en toute sécurité.
- faites de votre dialogue de progrès un membre de votre classe. En fait, vous devriez faire cela pour tous les dialogues. Dans la méthode onPause vous devriez les rejeter, sinon vous laisserez échapper une fenêtre sur le changement de configuration. L'état occupé doit être conservé par le fragment. Lorsque le fragment est attaché à l'activité, vous pouvez relancer le dialogue de progression, si l'appel est toujours en cours. À cette fin, une méthode
void showProgressDialog()
peut être ajoutée à l'interface d'écoute fragment-activity.
j'ai tout essayé. Passé des journées à expérimenter. Je ne voulais pas empêcher l'activité de tourner. Mon scénario était:
- dialogue de progression montrant des informations dynamiques à l'utilisateur. E. g.: "La connexion au serveur...", De "téléchargement de données...", etc.
- Un thread de faire le lourd et de la mise à jour de la boîte de dialogue
- mise à Jour de l'INTERFACE utilisateur avec les résultats à la fin.
le problème était, en tournant l'écran, chaque solution sur le livre a échoué. Même avec la classe AsyncTask, qui est la bonne façon Android de traiter ces situations. Lors de la rotation de l'écran, le contexte actuel avec lequel le thread de départ fonctionne est parti, et cela perturbe le dialogue qui s'affiche. Le problème a toujours été la boîte de dialogue, peu importe le nombre de trucs que j'ai ajouté au code (passer de nouveaux contextes à des threads en cours d'exécution, conserver les états de threads à travers des rotations, etc...). Code la complexité à la fin était toujours énorme et il y avait toujours quelque chose qui pouvait mal tourner.
la seule solution qui a fonctionné pour moi était le tour de dialogue activité. C'est simple et génial et c'est tout la preuve de rotation:
-
au lieu de créer un dialogue et de demander de le montrer, créer une activité qui a été mis dans le manifeste avec android:theme=" @android:style/Theme.Dialogue." Donc, il ressemble à une boîte de dialogue.
-
remplacer showDialog (DIALOG_ID) par startActivityForResult (yourActivityDialog, yourCode);
-
utilisez onActivityResult dans L'activité d'appel pour obtenir les résultats du thread d'exécution (même les erreurs) et mettre à jour L'UI.
-
sur votre 'ActivityDialog', utilisez threads ou AsyncTask pour exécuter de longues tâches et onRetainNonConfigurationInstance pour sauvegarder l'état" dialog " lorsque la rotation de l'écran.
C'est rapide et fonctionne très bien. J'utilise encore les dialogues pour d'autres tâches et L'AsyncTask pour quelque chose qui ne nécessite pas un dialogue constant à l'écran. Mais avec ce scénario, j'opte toujours pour le modèle activité/dialogue.
et, je ne l'ai pas essayé, mais il est même possible de bloquer cette activité / dialogue de rotation, lorsque le thread est en cours d'exécution, accélérer les choses, tout en permettant L'activité d'appel à tourner.
de nos jours, il existe une façon beaucoup plus distincte de traiter ce genre de questions. L'approche typique est la suivante:
1. Assurez-vous que vos données sont correctement séparées de L'UI:
Tout ce qui est un processus de fond doit être dans une Fragment
conservée (mettez ceci avec Fragment.setRetainInstance()
. Cela devient votre "stockage de données persistantes" où rien de données que vous souhaitez conservé est conservé. Après l'événement de changement d'orientation, ce Fragment
sera toujours accessible dans son état d'origine par un appel FragmentManager.findFragmentByTag()
(quand vous le créez vous devez lui donner une étiquette pas une ID car il n'est pas attaché à un View
).
voir le Managing Runtime Changes guide développé pour des informations sur le fait de faire ceci correctement et pourquoi c'est la meilleure option.
2. Assurez-vous que vous interfacez correctement et en toute sécurité entre les processus de fond et votre UI:
Vous devez reverse votre processus de liaison. À l'heure actuelle, votre processus de background s'attache à un View
- au lieu de cela, votre View
devrait s'attacher au processus de background. Il fait plus de sens? L 'action du View
dépend du processus de fond, alors que le processus de fond ne dépend pas du View
.Cela signifie changer le lien vers une interface standard Listener
. Dire votre processus (quelle que soit sa classe - qu'il s'agisse d'un AsyncTask
, Runnable
ou autre) définit un OnProcessFinishedListener
, lorsque le processus est terminé, il devrait appeler cet auditeur s'il existe.
Ce réponse est une belle description concise de la façon de faire des modules d'écoute personnalisés.
3. Liez votre UI dans le processus de données à chaque fois que L'UI est créé (y compris les changements d'orientation):
maintenant tu dois t'inquiéter à propos de l'interfaçage de la tâche de fond avec quelle que soit votre structure actuelle View
. Si vous manipulez vos changements d'orientation correctement (pas le configChanges
les gens de hack recommandent toujours), alors votre Dialog
sera recréé par le système. Ceci est important, cela signifie que sur le changement d'orientation, toutes vos méthodes de cycle de vie Dialog
sont rappelées. Dans l'une de ces méthodes ( onCreateDialog
est généralement un bon endroit), vous pourriez faire un appel comme suit:
DataFragment f = getActivity().getFragmentManager().findFragmentByTag("BACKGROUND_TAG");
if (f != null) {
f.mBackgroundProcess.setOnProcessFinishedListener(new OnProcessFinishedListener() {
public void onProcessFinished() {
dismiss();
}
});
}
voir le Fragment lifecycle pour décider où définir l'auditeur correspond le mieux à votre implémentation individuelle.
il s'agit d'une approche générale pour fournir une solution robuste et complète au problème Générique posé dans cette question. Il y a probablement quelques petites pièces manquantes dans cette réponse en fonction de votre scénario individuel, mais c'est généralement la plus correcte approche pour bien gérer les événements de changement d'orientation.
j'ai fait face à la même situation. Ce que j'ai fait était d'obtenir une seule instance pour mon dialogue de progrès dans l'application entière.
D'abord, j'ai créé une classe DialogSingleton pour obtenir une seule instance (Singleton pattern)
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();
}
}
}
comme je le montre dans cette classe, j'ai le dialogue de progression comme attribut. Chaque fois que j'ai besoin de montrer un dialogue de progrès, j'obtiens l'instance unique et je crée un nouveau ProgressDialog.
DialogSingleton.GetInstance().DialogShow(this, "My title here!");
Quand j'en ai fini avec la tâche de fond, j'appelle de nouveau l'instance unique et je rejette son dialogue.
DialogSingleton.GetInstance().DialogDismiss(this);
je sauve l'état de la tâche de fond dans mes préférences partagées. Lorsque je tourne l'écran, je demande si j'ai une tâche en cours d'exécution pour cette activité: (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).
quand je commence à exécuter une tâche de fond:
preference.AddValue(IS_TASK_NAME_EXECUTED_KEY, true, "boolean");
DialogSingleton.GetInstance().DialogShow(this, "My title here!");
quand j'ai terminé une tâche de fond:
preference.AddValue(IS_TASK_NAME_EXECUTED_KEY, false, "boolean");
DialogSingleton.GetInstance().DialogDismiss(ActivityName.this);
I espérons que cela aide.
C'est une très vieille question qui m'est venue sur la barre latérale pour une raison quelconque.
si la tâche de fond n'a besoin de survivre que pendant que l'activité est à l'avant-plan, la" nouvelle "solution est d'héberger le fil d'arrière-plan (ou, de préférence, AsyncTask
) dans un fragment retenu , comme décrit dans ce developer guide et nombreuses questions et réponses .
un fragment conservé survit si l'activité est détruite pour un changement de configuration, mais et non lorsque l'activité est détruite dans la pile arrière. Par conséquent, la tâche de fond devrait toujours être interrompue si isChangingConfigurations()
est faux dans onPause()
.
je suis une nouvelle à android et j'ai essayé et ça a marché.
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);
}
}
semble beaucoup trop "rapide et sale" pour être vrai donc s'il vous plaît pointez les défauts, mais ce que j'ai trouvé a fonctionné...
dans la méthode onpostexécute de mon AsyncTask, j'ai simplement enveloppé le '.Rejeter ' pour le dialogue de progression dans un bloc try/catch (avec une capture vide) et ensuite simplement ignoré l'exception qui a été soulevée. (Au moins pour ce que je fais par la suite qui est de commencer une autre activité passant dans le résultat de ma requête en cours d'exécution comme un Extra)
la solution la plus simple et la plus souple consiste à utiliser un AsyncTask avec une référence statique à ProgressBar . Cela fournit une solution encapsulée et donc réutilisable aux problèmes de changement d'orientation. Cette solution m'a bien servi pour diverses tâches asynchrones, y compris les téléchargements sur internet , la communication avec Services , et les scans du système de fichiers. La solution a été bien testée sur plusieurs versions android et les modèles de téléphone. Une démo complète peut être trouvé ici avec un intérêt spécifique dans téléchargement.java
je présente ce qui suit comme un exemple de concept
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();
}
}
}
L'utilisation dans une activité Android est simple
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();
}
}
j'ai trouvé et la solution plus facile pour manipuler les fils en cas de changement d'orientation. Vous pouvez simplement garder une référence statique à votre activité/fragment et vérifier si elle est nulle avant d'agir sur l'ui. Je suggère d'utiliser un try catch aussi:
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;
}
}
si vous avez du mal à détecter les changements d'orientation d'un dialogue indépendant d'une référence D'activité , cette méthode fonctionne très bien. Je l'utilise parce que j'ai ma propre classe de dialogue qui peut être affichée dans plusieurs activités différentes donc je ne sais pas toujours dans quelle activité il est affiché. Avec cette méthode, vous n'avez pas besoin de changer le AndroidManifest, de vous soucier des références D'activité, et vous n'avez pas besoin d'un dialogue personnalisé (comme je l'ai fait). Vous n' cependant, vous avez besoin d'une vue de contenu personnalisée pour que vous puissiez détecter les changements d'orientation en utilisant cette vue particulière. Voici mon exemple:
Setup
public class MyContentView extends View{
public MyContentView(Context context){
super(context);
}
@Override
public void onConfigurationChanged(Configuration newConfig){
super.onConfigurationChanged(newConfig);
//DO SOMETHING HERE!! :D
}
}
Mise En Œuvre 1 - Boîte De Dialogue
Dialog dialog = new Dialog(context);
//set up dialog
dialog.setContentView(new MyContentView(context));
dialog.show();
La Mise En Oeuvre 2 - AlertDialog.Constructeur
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();
Implementation 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();