Ich denke, Sie werden mein äußerst umfassendes und funktionierendes Beispiel genießen, das unten aufgeführt ist.
- Die Drehung funktioniert und der Dialog bleibt erhalten.
- Sie können die Aufgabe und den Dialog abbrechen, indem Sie die Zurück-Taste drücken (wenn Sie dieses Verhalten wünschen).
- Es werden Fragmente verwendet.
- Das Layout des Fragments unter der Aktivität ändert sich ordnungsgemäß, wenn sich das Gerät dreht.
- Es gibt einen vollständigen Quellcode-Download und eine vorkompilierte APK, damit Sie sehen können, ob das Verhalten Ihren Wünschen entspricht .
Bearbeiten
Wie von Brad Larson angefordert, habe ich den größten Teil der unten aufgeführten verknüpften Lösung reproduziert. Auch seit ich es gepostet habe, wurde ich darauf hingewiesen AsyncTaskLoader
. Ich bin mir nicht sicher, ob es für dieselben Probleme vollständig anwendbar ist, aber Sie sollten es trotzdem überprüfen.
Verwenden AsyncTask
mit Fortschrittsdialogen und Gerätedrehung.
Eine funktionierende Lösung!
Ich habe endlich alles zum Arbeiten. Mein Code hat folgende Funktionen:
- A,
Fragment
dessen Layout sich mit der Ausrichtung ändert.
- Ein,
AsyncTask
in dem Sie etwas arbeiten können.
- A,
DialogFragment
das den Fortschritt der Aufgabe in einem Fortschrittsbalken anzeigt (nicht nur ein unbestimmter Spinner).
- Die Drehung funktioniert, ohne die Aufgabe zu unterbrechen oder den Dialog zu schließen.
- Die Schaltfläche "Zurück" schließt den Dialog und bricht die Aufgabe ab (Sie können dieses Verhalten jedoch recht einfach ändern).
Ich glaube nicht, dass eine Kombination aus Arbeitsfähigkeit irgendwo anders zu finden ist.
Die Grundidee ist wie folgt. Es gibt eine MainActivity
Klasse, die ein einzelnes Fragment enthält - MainFragment
. MainFragment
hat unterschiedliche Layouts für die horizontale und vertikale Ausrichtung und setRetainInstance()
ist falsch, damit sich das Layout ändern kann. Dies bedeutet , dass , wenn die Vorrichtung Orientierung geändert wird, die beide MainActivity
und MainFragment
werden vollständig zerstört und neu erstellt.
Separat haben wir MyTask
(erweitert von AsyncTask
) die die ganze Arbeit erledigt. Wir können es nicht speichern, MainFragment
da dies zerstört wird und Google mit so etwas wie veraltet ist setRetainNonInstanceConfiguration()
. Das ist sowieso nicht immer verfügbar und bestenfalls ein hässlicher Hack. Stattdessen werden wir MyTask
in einem anderen Fragment speichern , einem DialogFragment
aufgerufenen TaskFragment
. Dieses Fragment wird hat setRetainInstance()
auf true gesetzt, so dass das Gerät dieses Fragment dreht nicht zerstört wird, und MyTask
wird beibehalten.
Schließlich müssen TaskFragment
wir demjenigen mitteilen , der informiert werden soll, wenn es fertig ist, und das tun wir, setTargetFragment(<the MainFragment>)
wenn wir es erstellen. Wenn das Gerät gedreht und MainFragment
zerstört wird und eine neue Instanz erstellt wird, verwenden wir das FragmentManager
, um den Dialog (basierend auf seinem Tag) zu finden und zu tun setTargetFragment(<the new MainFragment>)
. Das wars so ziemlich.
Ich musste noch zwei weitere Dinge tun: erstens die Aufgabe abbrechen, wenn der Dialog geschlossen wird, und zweitens die Entlassungsnachricht auf null setzen, andernfalls wird der Dialog seltsamerweise geschlossen, wenn das Gerät gedreht wird.
Der Code
Ich werde die Layouts nicht auflisten, sie sind ziemlich offensichtlich und Sie finden sie im Projekt-Download unten.
Hauptaktivität
Das ist ziemlich einfach. Ich habe dieser Aktivität einen Rückruf hinzugefügt, damit sie weiß, wann die Aufgabe abgeschlossen ist, aber das brauchen Sie möglicherweise nicht. Hauptsächlich wollte ich nur den Rückrufmechanismus für Fragmentaktivitäten zeigen, weil er ziemlich ordentlich ist und Sie ihn vielleicht vorher noch nicht gesehen haben.
public class MainActivity extends Activity implements MainFragment.Callbacks
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public void onTaskFinished()
{
// Hooray. A toast to our success.
Toast.makeText(this, "Task finished!", Toast.LENGTH_LONG).show();
// NB: I'm going to blow your mind again: the "int duration" parameter of makeText *isn't*
// the duration in milliseconds. ANDROID Y U NO ENUM?
}
}
MainFragment
Es ist lang, aber es lohnt sich!
public class MainFragment extends Fragment implements OnClickListener
{
// This code up to onDetach() is all to get easy callbacks to the Activity.
private Callbacks mCallbacks = sDummyCallbacks;
public interface Callbacks
{
public void onTaskFinished();
}
private static Callbacks sDummyCallbacks = new Callbacks()
{
public void onTaskFinished() { }
};
@Override
public void onAttach(Activity activity)
{
super.onAttach(activity);
if (!(activity instanceof Callbacks))
{
throw new IllegalStateException("Activity must implement fragment's callbacks.");
}
mCallbacks = (Callbacks) activity;
}
@Override
public void onDetach()
{
super.onDetach();
mCallbacks = sDummyCallbacks;
}
// Save a reference to the fragment manager. This is initialised in onCreate().
private FragmentManager mFM;
// Code to identify the fragment that is calling onActivityResult(). We don't really need
// this since we only have one fragment to deal with.
static final int TASK_FRAGMENT = 0;
// Tag so we can find the task fragment again, in another instance of this fragment after rotation.
static final String TASK_FRAGMENT_TAG = "task";
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// At this point the fragment may have been recreated due to a rotation,
// and there may be a TaskFragment lying around. So see if we can find it.
mFM = getFragmentManager();
// Check to see if we have retained the worker fragment.
TaskFragment taskFragment = (TaskFragment)mFM.findFragmentByTag(TASK_FRAGMENT_TAG);
if (taskFragment != null)
{
// Update the target fragment so it goes to this fragment instead of the old one.
// This will also allow the GC to reclaim the old MainFragment, which the TaskFragment
// keeps a reference to. Note that I looked in the code and setTargetFragment() doesn't
// use weak references. To be sure you aren't leaking, you may wish to make your own
// setTargetFragment() which does.
taskFragment.setTargetFragment(this, TASK_FRAGMENT);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
return inflater.inflate(R.layout.fragment_main, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState)
{
super.onViewCreated(view, savedInstanceState);
// Callback for the "start task" button. I originally used the XML onClick()
// but it goes to the Activity instead.
view.findViewById(R.id.taskButton).setOnClickListener(this);
}
@Override
public void onClick(View v)
{
// We only have one click listener so we know it is the "Start Task" button.
// We will create a new TaskFragment.
TaskFragment taskFragment = new TaskFragment();
// And create a task for it to monitor. In this implementation the taskFragment
// executes the task, but you could change it so that it is started here.
taskFragment.setTask(new MyTask());
// And tell it to call onActivityResult() on this fragment.
taskFragment.setTargetFragment(this, TASK_FRAGMENT);
// Show the fragment.
// I'm not sure which of the following two lines is best to use but this one works well.
taskFragment.show(mFM, TASK_FRAGMENT_TAG);
// mFM.beginTransaction().add(taskFragment, TASK_FRAGMENT_TAG).commit();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (requestCode == TASK_FRAGMENT && resultCode == Activity.RESULT_OK)
{
// Inform the activity.
mCallbacks.onTaskFinished();
}
}
TaskFragment
// This and the other inner class can be in separate files if you like.
// There's no reason they need to be inner classes other than keeping everything together.
public static class TaskFragment extends DialogFragment
{
// The task we are running.
MyTask mTask;
ProgressBar mProgressBar;
public void setTask(MyTask task)
{
mTask = task;
// Tell the AsyncTask to call updateProgress() and taskFinished() on this fragment.
mTask.setFragment(this);
}
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// Retain this instance so it isn't destroyed when MainActivity and
// MainFragment change configuration.
setRetainInstance(true);
// Start the task! You could move this outside this activity if you want.
if (mTask != null)
mTask.execute();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_task, container);
mProgressBar = (ProgressBar)view.findViewById(R.id.progressBar);
getDialog().setTitle("Progress Dialog");
// If you're doing a long task, you probably don't want people to cancel
// it just by tapping the screen!
getDialog().setCanceledOnTouchOutside(false);
return view;
}
// This is to work around what is apparently a bug. If you don't have it
// here the dialog will be dismissed on rotation, so tell it not to dismiss.
@Override
public void onDestroyView()
{
if (getDialog() != null && getRetainInstance())
getDialog().setDismissMessage(null);
super.onDestroyView();
}
// Also when we are dismissed we need to cancel the task.
@Override
public void onDismiss(DialogInterface dialog)
{
super.onDismiss(dialog);
// If true, the thread is interrupted immediately, which may do bad things.
// If false, it guarantees a result is never returned (onPostExecute() isn't called)
// but you have to repeatedly call isCancelled() in your doInBackground()
// function to check if it should exit. For some tasks that might not be feasible.
if (mTask != null) {
mTask.cancel(false);
}
// You don't really need this if you don't want.
if (getTargetFragment() != null)
getTargetFragment().onActivityResult(TASK_FRAGMENT, Activity.RESULT_CANCELED, null);
}
@Override
public void onResume()
{
super.onResume();
// This is a little hacky, but we will see if the task has finished while we weren't
// in this activity, and then we can dismiss ourselves.
if (mTask == null)
dismiss();
}
// This is called by the AsyncTask.
public void updateProgress(int percent)
{
mProgressBar.setProgress(percent);
}
// This is also called by the AsyncTask.
public void taskFinished()
{
// Make sure we check if it is resumed because we will crash if trying to dismiss the dialog
// after the user has switched to another app.
if (isResumed())
dismiss();
// If we aren't resumed, setting the task to null will allow us to dimiss ourselves in
// onResume().
mTask = null;
// Tell the fragment that we are done.
if (getTargetFragment() != null)
getTargetFragment().onActivityResult(TASK_FRAGMENT, Activity.RESULT_OK, null);
}
}
Meine Aufgabe
// This is a fairly standard AsyncTask that does some dummy work.
public static class MyTask extends AsyncTask<Void, Void, Void>
{
TaskFragment mFragment;
int mProgress = 0;
void setFragment(TaskFragment fragment)
{
mFragment = fragment;
}
@Override
protected Void doInBackground(Void... params)
{
// Do some longish task. This should be a task that we don't really
// care about continuing
// if the user exits the app.
// Examples of these things:
// * Logging in to an app.
// * Downloading something for the user to view.
// * Calculating something for the user to view.
// Examples of where you should probably use a service instead:
// * Downloading files for the user to save (like the browser does).
// * Sending messages to people.
// * Uploading data to a server.
for (int i = 0; i < 10; i++)
{
// Check if this has been cancelled, e.g. when the dialog is dismissed.
if (isCancelled())
return null;
SystemClock.sleep(500);
mProgress = i * 10;
publishProgress();
}
return null;
}
@Override
protected void onProgressUpdate(Void... unused)
{
if (mFragment == null)
return;
mFragment.updateProgress(mProgress);
}
@Override
protected void onPostExecute(Void unused)
{
if (mFragment == null)
return;
mFragment.taskFinished();
}
}
}
Laden Sie das Beispielprojekt herunter
Hier ist der Quellcode und die APK . Entschuldigung, das ADT bestand darauf, die Support-Bibliothek hinzuzufügen, bevor ich ein Projekt erstellen konnte. Ich bin sicher, Sie können es entfernen.