Fragmenty Androida. Zachowanie asynchronicznej funkcji podczas obracania ekranu lub zmiany konfiguracji

Pracuję nad aplikacją na smartfon / Tablet, używając tylko jednego APK i ładując zasoby w zależności od rozmiaru ekranu, najlepszym wyborem wydaje się użycie fragmentów przez ACL.

Ta aplikacja działa dobrze do tej pory jest tylko aktywność oparta. Jest to przykładowa Klasa obsługi asynchronicznych zadań i Progressdialogów w działaniach, aby działały nawet wtedy, gdy ekran jest obrócony lub gdy nastąpi zmiana konfiguracji w połowie komunikacji.

Nie będę Zmień manifest, aby uniknąć rekreacji działalności, istnieje wiele powodów, dla których nie chcę tego robić, ale głównie dlatego, że oficjalne dokumenty mówią, że nie polecam i udało mi się bez niego tak daleko, więc proszę nie polecać tej trasy.

public class Login extends Activity {

    static ProgressDialog pd;
    AsyncTask<String, Void, Boolean> asyncLoginThread;

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setContentView(R.layout.login);
        //SETUP UI OBJECTS
        restoreAsyncTask();
    }

    @Override
    public Object onRetainNonConfigurationInstance() {
        if (pd != null) pd.dismiss();
        if (asyncLoginThread != null) return (asyncLoginThread);
        return super.onRetainNonConfigurationInstance();
    }

    private void restoreAsyncTask();() {
        pd = new ProgressDialog(Login.this);
        if (getLastNonConfigurationInstance() != null) {
            asyncLoginThread = (AsyncTask<String, Void, Boolean>) getLastNonConfigurationInstance();
            if (asyncLoginThread != null) {
                if (!(asyncLoginThread.getStatus()
                        .equals(AsyncTask.Status.FINISHED))) {
                    showProgressDialog();
                }
            }
        }
    }

    public class LoginThread extends AsyncTask<String, Void, Boolean> {
        @Override
        protected Boolean doInBackground(String... args) {
            try {
                //Connect to WS, recieve a JSON/XML Response
                //Place it somewhere I can use it.
            } catch (Exception e) {
                return true;
            }
            return true;
        }

        protected void onPostExecute(Boolean result) {
            if (result) {
                pd.dismiss();
                //Handle the response. Either deny entry or launch new Login Succesful Activity
            }
        }
    }
}

Ten kod działa dobrze, mam około 10.000 użytkowników bez skargi, więc wydawało się logiczne, aby po prostu skopiować tę logikę do nowego projektu opartego na fragmentach, ale, oczywiście, to nie działa.

Oto LoginFragment:

public class LoginFragment extends Fragment {

    FragmentActivity parentActivity;
    static ProgressDialog pd;
    AsyncTask<String, Void, Boolean> asyncLoginThread;

    public interface OnLoginSuccessfulListener {
        public void onLoginSuccessful(GlobalContainer globalContainer);
    }

    public void onSaveInstanceState(Bundle outState){
        super.onSaveInstanceState(outState);
        //Save some stuff for the UI State
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setRetainInstance(true);
        //If I setRetainInstance(true), savedInstanceState is always null. Besides that, when loading UI State, a NPE is thrown when looking for UI Objects.
        parentActivity = getActivity();
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            loginSuccessfulListener = (OnLoginSuccessfulListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnLoginSuccessfulListener");
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        RelativeLayout loginLayout = (RelativeLayout) inflater.inflate(R.layout.login, container, false);
        return loginLayout;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        //SETUP UI OBJECTS
        if(savedInstanceState != null){
            //Reload UI state. Im doing this properly, keeping the content of the UI objects, not the object it self to avoid memory leaks.
        }
    }

    public class LoginThread extends AsyncTask<String, Void, Boolean> {
            @Override
            protected Boolean doInBackground(String... args) {
                try {
                    //Connect to WS, recieve a JSON/XML Response
                    //Place it somewhere I can use it.
                } catch (Exception e) {
                    return true;
                }
                return true;
            }

            protected void onPostExecute(Boolean result) {
                if (result) {
                    pd.dismiss();
                    //Handle the response. Either deny entry or launch new Login Succesful Activity
                }
            }
        }
    }
}

Nie mogę użyć onRetainNonConfigurationInstance() ponieważ musi być wywołana z działania, a nie z fragmentu, to samo dotyczy getLastNonConfigurationInstance(). Przeczytałem tu kilka podobnych pytań bez odpowiedzi.

Rozumiem, że może to wymagać trochę pracy, aby uporządkować te rzeczy we fragmentach, to powiedziawszy, chciałbym zachować tę samą podstawową logikę projektowania.

Jaki byłby właściwy sposób zachowania Asynctasku podczas zmiany konfiguracji, a jeśli nadal runing, Pokaż progresdialog, biorąc pod uwagę, że AsyncTask jest klasą wewnętrzną dla fragmentu i to sam Fragment wywołuje AsyncTask.execute ()?

Author: blindstuff, 2011-12-07

12 answers

Fragmenty mogą to znacznie ułatwić. Wystarczy użyć metody Fragment.setRetainInstance (boolean) , aby instancja fragmentu została zachowana podczas zmian konfiguracji. Należy pamiętać, że jest to zalecany zamiennik aktywności .onRetainnonConfigurationInstance () W dokumentach.

Jeśli z jakiegoś powodu naprawdę nie chcesz używać zachowanego fragmentu, możesz zastosować inne podejście. Zauważ, że każdy fragment ma unikalny identyfikator zwracany przez Fragment.getId () . Możesz również dowiedzieć się, czy fragment jest wybierany w celu zmiany konfiguracji za pomocą Fragment.getActivity ().isChangingConfigurations () . Tak więc, w punkcie, w którym zdecydujesz się zatrzymać swoją AsyncTask( w onStop () lub onDestroy () najprawdopodobniej), możesz na przykład sprawdzić, czy konfiguracja się zmienia i jeśli tak, trzymać ją w statycznym SparseArray pod identyfikatorem fragmentu, a następnie w OnCreate() lub onStart() spojrzeć, aby zobaczyć, czy masz AsyncTask w dostępna jest nieliczna tablica.

 75
Author: hackbod,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-18 06:57:39

Myślę, że spodoba ci się mój niezwykle wszechstronny i roboczy przykład opisany poniżej.

  1. rotacja działa, a okno dialogowe trwa.
  2. możesz anulować zadanie i okno dialogowe, naciskając przycisk Wstecz (jeśli chcesz tego zachowania).
  3. używa fragmentów.
  4. układ fragmentu pod aktywnością zmienia się prawidłowo, gdy urządzenie się obraca.
  5. jest kompletny kod źródłowy do pobrania i wstępnie skompilowany APK więc możesz zobaczyć jeśli zachowanie jest to, co chcesz.

Edit

Zgodnie z życzeniem Brada Larsona odtworzyłem większość linkowanego rozwiązania poniżej. Również od kiedy go opublikowałem, zostałem wskazany na AsyncTaskLoader. Nie jestem pewien, czy jest to całkowicie zastosowanie do tych samych problemów, ale powinieneś to sprawdzić i tak.

Używanie {[5] } z oknami postępu i obrotem urządzenia.

Rozwiązanie pracy!

W końcu mam wszystko do pracy. Mój kod ma następujące cechy:

  1. a Fragment którego układ zmienia się wraz z orientacją.
  2. AsyncTask w którym możesz wykonać jakąś pracę.
  3. A DialogFragment, który pokazuje postęp zadania na pasku postępu (nie tylko nieokreślonym spinnerze).
  4. rotacja działa bez przerywania zadania lub oddalania okna dialogowego.
  5. przycisk Wstecz odrzuca okno dialogowe i anuluje zadanie(można jednak dość łatwo zmienić to zachowanie).

Nie wydaje mi się, żeby ta kombinacja pracy można znaleźć wszędzie indziej.

Podstawowa idea jest następująca. Istnieje klasa MainActivity, która zawiera pojedynczy fragment - MainFragment. MainFragment ma różne układy dla orientacji poziomej i pionowej, a {[12] } jest false, dzięki czemu układ może się zmieniać. Oznacza to, że po zmianie orientacji urządzenia zarówno MainActivity, jak i MainFragment zostają całkowicie zniszczone i odtworzone.

Osobno mamy MyTask (Rozszerzony od AsyncTask), który wykonuje całą pracę. Nie możemy go przechowywać w MainFragment ponieważ to zostanie zniszczone, a Google wycofało się używając czegoś takiego jak setRetainNonInstanceConfiguration(). To nie zawsze jest dostępne i jest brzydki hack w najlepszym razie. Zamiast tego będziemy przechowywać MyTask w innym fragmencie, DialogFragment o nazwie TaskFragment. ten fragment będzie miał setRetainInstance() ustawione na true, więc gdy urządzenie obróci ten fragment nie zostanie zniszczony, a MyTask zostanie zachowany.

W końcu musimy powiedzieć TaskFragment komu poinformować, kiedy to się skończy, i robimy to używając setTargetFragment(<the MainFragment>) kiedy stwórz go. Gdy urządzenie jest obrócone, MainFragment jest zniszczone i powstaje nowa instancja, używamy FragmentManager, aby znaleźć okno dialogowe (na podstawie jego znacznika) i wykonać setTargetFragment(<the new MainFragment>). To wszystko.

Były dwie inne rzeczy, które musiałem zrobić: najpierw Anuluj zadanie, gdy okno dialogowe zostanie odrzucone, a następnie Ustaw komunikat o zwolnieniu na null, w przeciwnym razie okno dialogowe jest dziwnie odrzucane, gdy urządzenie jest obrócone.

Kod

Nie będę wymieniał układów, są dość oczywiste i można je znaleźć w projekcie pobierz poniżej.

Główna aktywność

To dość proste. Dodałem callback do tej aktywności, aby wiedział, kiedy zadanie jest zakończone, ale możesz tego nie potrzebować. Głównie chciałem tylko pokazać mechanizm wywołania zwrotnego fragment-activity, ponieważ jest całkiem zgrabny i być może wcześniej go nie widziałeś.
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

Jest długa, ale warta to!

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);
        }
    }

MyTask

    // 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();
        }
    }
}

Pobierz przykładowy projekt

Oto kod źródłowy i APK . Przepraszam, ADT nalegał na dodanie biblioteki wsparcia, zanim pozwoli mi zrobić projekt. Jestem pewien, że możesz go usunąć.

 66
Author: Timmmm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-09 17:52:00

Ostatniozamieścił artykuł opisujący sposób obsługi zmian konfiguracyjnych za pomocą zatrzymanych Fragment s. rozwiązuje problem utrzymywania AsyncTask przez zmianę rotacji.

TL;DR ma używać hosta AsyncTask Wewnątrz Fragment, wywoływać setRetainInstance(true) na Fragment, i raportować AsyncTask's postęp/wyniki z powrotem do Activity (lub jest to cel Fragment, jeśli zdecydujesz się użyć podejścia opisanego przez @Timmmm) poprzez zatrzymane Fragment.

 16
Author: Alex Lockwood,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-30 16:46:35

Moja pierwsza sugestia to unikać wewnętrznych Asynctasków , możesz przeczytać pytanie, które zadałem na ten temat i odpowiedzi: Android: zalecenia AsyncTask: Klasa prywatna czy klasa publiczna?

Potem zacząłem używać non-inner and... teraz widzę wiele korzyści.

Po drugie, zachowaj odniesienie do swojej AsyncTask w klasie Application - http://developer.android.com/reference/android/app/Application.html

Everytime you start an AsyncTask, ustaw go w aplikacji i po jej zakończeniu Ustaw NA null.

Kiedy rozpoczyna się fragment / aktywność, możesz sprawdzić, czy uruchomiona jest jakaś AsyncTask (sprawdzając, czy w aplikacji jest null, czy nie), a następnie ustawić odniesienie wewnątrz na cokolwiek chcesz (aktywność, fragment itp., abyś mógł wykonywać wywołania zwrotne).

To rozwiąże twój problem: Jeśli masz tylko 1 AsyncTask uruchomiony w dowolnym określonym czasie, możesz dodać proste odniesienie:
AsyncTask<?,?,?> asyncTask = null;

Else, have in the Zastosowanie Hashmapy z odniesieniami do nich.

Okno dialogowe postępu może podążać dokładnie tą samą zasadą.

 13
Author: neteinstein,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:17:47

Wymyśliłem metodę użycia Asynctaskloaderów do tego. Jest dość łatwy w użyciu i wymaga mniej napowietrznych IMO..

Zasadniczo tworzysz AsyncTaskLoader w ten sposób:

public class MyAsyncTaskLoader extends AsyncTaskLoader {
    Result mResult;
    public HttpAsyncTaskLoader(Context context) {
        super(context);
    }

    protected void onStartLoading() {
        super.onStartLoading();
        if (mResult != null) {
            deliverResult(mResult);
        }
        if (takeContentChanged() ||  mResult == null) {
            forceLoad();
        }
    }

    @Override
    public Result loadInBackground() {
        SystemClock.sleep(500);
        mResult = new Result();
        return mResult;
    }
}

Następnie w Twojej aktywności, która używa powyższego Asynctaskloadera po kliknięciu przycisku:

public class MyActivityWithBackgroundWork extends FragmentActivity implements LoaderManager.LoaderCallbacks<Result> {

    private String username,password;       
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        setContentView(R.layout.mylayout);
        //this is only used to reconnect to the loader if it already started
        //before the orientation changed
        Loader loader = getSupportLoaderManager().getLoader(0);
        if (loader != null) {
            getSupportLoaderManager().initLoader(0, null, this);
        }
    }

    public void doBackgroundWorkOnClick(View button) {
        //might want to disable the button while you are doing work
        //to prevent user from pressing it again.

        //Call resetLoader because calling initLoader will return
        //the previous result if there was one and we may want to do new work
        //each time
        getSupportLoaderManager().resetLoader(0, null, this);
    }   


    @Override
    public Loader<Result> onCreateLoader(int i, Bundle bundle) {
        //might want to start a progress bar
        return new MyAsyncTaskLoader(this);
    }


    @Override
    public void onLoadFinished(Loader<LoginResponse> loginLoader,
                               LoginResponse loginResponse)
    {
        //handle result
    }

    @Override
    public void onLoaderReset(Loader<LoginResponse> responseAndJsonHolderLoader)
    {
        //remove references to previous loader resources

    }
}

To wydaje się obsługiwać zmiany orientacji dobrze i twoje zadanie w tle będzie kontynuowane podczas obracania.

Kilka rzeczy do zapamiętania:

  1. Jeśli w onCreate ponownie podłączysz się do asynctaskloader zostanie wywołany z powrotem w onloadfinished () z poprzednim wynikiem (nawet jeśli wcześniej powiedziano ci, że żądanie zostało zakończone). To jest rzeczywiście dobre zachowanie przez większość czasu, ale czasami może być trudne do obsługi. Chociaż wyobrażam sobie, że istnieje wiele sposobów, aby poradzić sobie z tym, co zrobiłem, nazywałem ładowaczem.abandon() w onLoadFinished. Następnie dodałem check in onCreate, aby tylko ponownie podłączyć do ładowarki, jeśli nie był już porzucony. Jeśli ponownie potrzebujesz danych wynikowych, nie będziesz chciał żeby to zrobić. W większości przypadków potrzebujesz danych.

Mam więcej szczegółów na temat używania tego do połączeń http tutaj

 4
Author: Matt Wolfe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:11

Stworzyłem bardzo małą bibliotekę zadań open-source w tle, która jest w dużej mierze oparta na Marshmallow AsyncTask, ale z dodatkowymi funkcjami, takimi jak:

  1. automatyczne zachowywanie zadań podczas zmian konfiguracji;
  2. UI callback (słuchacze);
  3. Nie uruchamia się ponownie ani nie anuluje zadania, gdy urządzenie się obraca (podobnie jak Ładowarki);

Biblioteka wewnętrznie używa Fragment bez żadnego interfejsu użytkownika, który jest zachowany po zmianach konfiguracji (setRetainInstance(true)).

Znajdziesz go na Githubie: https://github.com/NeoTech-Software/Android-Retainable-Tasks

Najbardziej podstawowy przykład (wersja 0.2.0):

Ten przykład w pełni zachowuje zadanie, używając bardzo ograniczonej ilości kodu.

zadanie:

private class ExampleTask extends Task<Integer, String> {

    public ExampleTask(String tag){
        super(tag);
    }

    protected String doInBackground() {
        for(int i = 0; i < 100; i++) {
            if(isCancelled()){
                break;
            }
            SystemClock.sleep(50);
            publishProgress(i);
        }
        return "Result";
    }
}

aktywność:

public class Main extends TaskActivityCompat implements Task.Callback {

    @Override
    public void onClick(View view){
        ExampleTask task = new ExampleTask("activity-unique-tag");
        getTaskManager().execute(task, this);
    }

    @Override
    public Task.Callback onPreAttach(Task<?, ?> task) {
        //Restore the user-interface based on the tasks state
        return this; //This Activity implements Task.Callback
    }

    @Override
    public void onPreExecute(Task<?, ?> task) {
        //Task started
    }

    @Override
    public void onPostExecute(Task<?, ?> task) {
        //Task finished
        Toast.makeText(this, "Task finished", Toast.LENGTH_SHORT).show();
    }
}
 3
Author: Rolf ツ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-07 15:47:07

Moje podejście polega na użyciu wzorca projektowania delegacji, ogólnie rzecz biorąc, możemy wyizolować rzeczywistą logikę biznesową (odczyt danych z Internetu lub bazy danych lub w ogóle) z AsyncTask (delegator) do BusinessDAO (delegat), w Twoim AysncTask.metoda doInBackground (), deleguje rzeczywiste zadanie do BusinessDAO, a następnie implementuje mechanizm procesu singleton w BusinessDAO, tak aby wielokrotne wywołanie do BusinessDAO.doSomething() wywoła tylko jedno rzeczywiste zadanie uruchomione za każdym razem i czekające na zadanie wynik. Idea polega na zatrzymaniu delegata (np. BusinessDAO) podczas zmiany konfiguracji, zamiast delegatora (np. AsyncTask).

  1. Utwórz/zaimplementuj naszą własną aplikację, celem jest utworzenie / zainicjowanie BusinessDAO tutaj, aby cykl życia naszego BusinessDAO był ograniczony zakresem aplikacji, a nie zakresem aktywności, pamiętaj, że musisz zmienić AndroidManifest.xml do użycia:

    public class MyApplication extends android.app.Application {
      private BusinessDAO businessDAO;
    
      @Override
      public void onCreate() {
        super.onCreate();
        businessDAO = new BusinessDAO();
      }
    
      pubilc BusinessDAO getBusinessDAO() {
        return businessDAO;
      }
    
    }
    
  2. Nasza dotychczasowa aktywność / fragmentacja jest w większości niezmieniona, wciąż zaimplementuj AsyncTask jako klasę wewnętrzną i włącz AsyncTask.execute () z Activity / Fragement, różnica polega teraz na tym, że AsyncTask powierzy rzeczywiste zadanie BusinessDAO, więc podczas zmiany konfiguracji zostanie zainicjowana i wykonana druga AsyncTask i wywoła BusinessDAO.doSomething () drugi raz, jednak drugie wezwanie do BusinessDAO.doSomething() nie uruchomi nowego zadania, zamiast tego czeka na zakończenie bieżącego zadania:

    public class LoginFragment extends Fragment {
      ... ...
    
      public class LoginAsyncTask extends AsyncTask<String, Void, Boolean> {
        // get a reference of BusinessDAO from application scope.
        BusinessDAO businessDAO = ((MyApplication) getApplication()).getBusinessDAO();
    
        @Override
        protected Boolean doInBackground(String... args) {
            businessDAO.doSomething();
            return true;
        }
    
        protected void onPostExecute(Boolean result) {
          //Handle task result and update UI stuff.
        }
      }
    
      ... ...
    }
    
  3. Inside BusinessDAO, implementacja mechanizmu procesu Singletona, na przykład:

    public class BusinessDAO {
      ExecutorCompletionService<MyTask> completionExecutor = new ExecutorCompletionService<MyTask(Executors.newFixedThreadPool(1));
      Future<MyTask> myFutureTask = null;
    
      public void doSomething() {
        if (myFutureTask == null) {
          // nothing running at the moment, submit a new callable task to run.
          MyTask myTask = new MyTask();
          myFutureTask = completionExecutor.submit(myTask);
        }
        // Task already submitted and running, waiting for the running task to finish.
        myFutureTask.get();
      }
    
      // If you've never used this before, Callable is similar with Runnable, with ability to return result and throw exception.
      private class MyTask extends Callable<MyTask> {
        public MyAsyncTask call() {
          // do your job here.
          return this;
        }
      }
    
    }
    

Nie jestem w 100% pewien, czy to zadziała, ponadto przykładowy fragment kodu powinien być uważany za pseudokod. Próbuję tylko dać ci jakąś wskazówkę z poziomu projektowania. Wszelkie opinie i sugestie są mile widziane i doceniane.

 1
Author: yorkw,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-16 22:16:45

Jeśli ktoś znajdzie drogę do tego wątku, odkryłem, że czyste podejście polegało na uruchomieniu zadania asynchronicznego z app.Service (zaczęło się od START_STICKY), a następnie odtworzyć iterację nad uruchomionymi usługami, aby dowiedzieć się, czy usługa (a tym samym zadanie asynchroniczne) nadal działa;

    public boolean isServiceRunning(String serviceClassName) {
    final ActivityManager activityManager = (ActivityManager) Application.getContext().getSystemService(Context.ACTIVITY_SERVICE);
    final List<RunningServiceInfo> services = activityManager.getRunningServices(Integer.MAX_VALUE);

    for (RunningServiceInfo runningServiceInfo : services) {
        if (runningServiceInfo.service.getClassName().equals(serviceClassName)){
            return true;
        }
    }
    return false;
 }

Jeśli tak, Dodaj ponownie DialogFragment (lub cokolwiek innego), a jeśli nie, upewnij się, że okno dialogowe zostało odrzucone.

Jest to szczególnie istotne, jeśli korzystasz z bibliotek v4.support.*, ponieważ (w czasie pisanie) mają problemy z metodą setRetainInstance i wyświetlaniem stronicowania. ponadto, nie zachowując instancji, możesz odtworzyć swoją aktywność przy użyciu innego zestawu zasobów (tj. innego układu widoku dla nowej orientacji)

 1
Author: OceanLife,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-24 10:16:09

Możesz uczynić AsyncTask polem statycznym. Jeśli potrzebujesz kontekstu, powinieneś wysłać kontekst aplikacji. Pozwoli to uniknąć wycieków pamięci, w przeciwnym razie zachowasz odniesienie do całej swojej aktywności.

 0
Author: Boude,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-17 18:41:21

Piszę ten sam kod aby rozwiązać ten problem

Pierwszym krokiem jest wykonanie klasy aplikacji:

public class TheApp extends Application {

private static TheApp sTheApp;
private HashMap<String, AsyncTask<?,?,?>> tasks = new HashMap<String, AsyncTask<?,?,?>>();

@Override
public void onCreate() {
    super.onCreate();
    sTheApp = this;
}

public static TheApp get() {
    return sTheApp;
}

public void registerTask(String tag, AsyncTask<?,?,?> task) {
    tasks.put(tag, task);
}

public void unregisterTask(String tag) {
    tasks.remove(tag);
}

public AsyncTask<?,?,?> getTask(String tag) {
    return tasks.get(tag);
}
}
W AndroidManifest.xml
<application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme"
        android:name="com.example.tasktest.TheApp">

Kod w aktywności:

public class MainActivity extends Activity {

private Task1 mTask1;

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

    mTask1 = (Task1)TheApp.get().getTask("task1");

}

/*
 * start task is not running jet
 */
public void handletask1(View v) {
    if (mTask1 == null) {
        mTask1 = new Task1();
        TheApp.get().registerTask("task1", mTask1);
        mTask1.execute();
    } else
        Toast.makeText(this, "Task is running...", Toast.LENGTH_SHORT).show();

}

/*
 * cancel task if is not finished
 */
public void handelCancel(View v) {
    if (mTask1 != null)
        mTask1.cancel(false);
}

public class Task1 extends AsyncTask<Void, Void, Void>{

    @Override
    protected Void doInBackground(Void... params) {
        try {
            for(int i=0; i<120; i++) {
                Thread.sleep(1000);
                Log.i("tests", "loop=" + i);
                if (this.isCancelled()) {
                    Log.e("tests", "tssk cancelled");
                    break;
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    protected void onCancelled(Void result) {
        TheApp.get().unregisterTask("task1");
        mTask1 = null;
    }

    @Override
    protected void onPostExecute(Void result) {
        TheApp.get().unregisterTask("task1");
        mTask1 = null;
    }
}

}

Gdy zmienia się orientacja aktywności zmienna mTask jest wywoływana z kontekstu aplikacji. Po zakończeniu zadania zmienna jest ustawiona na null i usuwa z pamięci.

Dla mnie Wystarczy.
 0
Author: Kenumir,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-03 20:41:26

Spójrz na poniższy przykład , jak użyć zatrzymanego fragmentu do zachowania zadania w tle:

public class NetworkRequestFragment extends Fragment {

    // Declare some sort of interface that your AsyncTask will use to communicate with the Activity
    public interface NetworkRequestListener {
        void onRequestStarted();
        void onRequestProgressUpdate(int progress);
        void onRequestFinished(SomeObject result);
    }

    private NetworkTask mTask;
    private NetworkRequestListener mListener;

    private SomeObject mResult;

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        // Try to use the Activity as a listener
        if (activity instanceof NetworkRequestListener) {
            mListener = (NetworkRequestListener) activity;
        } else {
            // You can decide if you want to mandate that the Activity implements your callback interface
            // in which case you should throw an exception if it doesn't:
            throw new IllegalStateException("Parent activity must implement NetworkRequestListener");
            // or you could just swallow it and allow a state where nobody is listening
        }
    }

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

        // Retain this Fragment so that it will not be destroyed when an orientation
        // change happens and we can keep our AsyncTask running
        setRetainInstance(true);
    }

    /**
     * The Activity can call this when it wants to start the task
     */
    public void startTask(String url) {
        mTask = new NetworkTask(url);
        mTask.execute();
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        // If the AsyncTask finished when we didn't have a listener we can
        // deliver the result here
        if ((mResult != null) && (mListener != null)) {
            mListener.onRequestFinished(mResult);
            mResult = null;
        }
    }

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

        // We still have to cancel the task in onDestroy because if the user exits the app or
        // finishes the Activity, we don't want the task to keep running
        // Since we are retaining the Fragment, onDestroy won't be called for an orientation change
        // so this won't affect our ability to keep the task running when the user rotates the device
        if ((mTask != null) && (mTask.getStatus == AsyncTask.Status.RUNNING)) {
            mTask.cancel(true);
        }
    }

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

        // This is VERY important to avoid a memory leak (because mListener is really a reference to an Activity)
        // When the orientation change occurs, onDetach will be called and since the Activity is being destroyed
        // we don't want to keep any references to it
        // When the Activity is being re-created, onAttach will be called and we will get our listener back
        mListener = null;
    }

    private class NetworkTask extends AsyncTask<String, Integer, SomeObject> {

        @Override
        protected void onPreExecute() {
            if (mListener != null) {
                mListener.onRequestStarted();
            }
        }

        @Override
        protected SomeObject doInBackground(String... urls) {
           // Make the network request
           ...
           // Whenever we want to update our progress:
           publishProgress(progress);
           ...
           return result;
        }

        @Override
        protected void onProgressUpdate(Integer... progress) {
            if (mListener != null) {
                mListener.onRequestProgressUpdate(progress[0]);
            }
        }

        @Override
        protected void onPostExecute(SomeObject result) {
            if (mListener != null) {
                mListener.onRequestFinished(result);
            } else {
                // If the task finishes while the orientation change is happening and while
                // the Fragment is not attached to an Activity, our mListener might be null
                // If you need to make sure that the result eventually gets to the Activity
                // you could save the result here, then in onActivityCreated you can pass it back
                // to the Activity
                mResult = result;
            }
        }

    }
}
 0
Author: DeepakPanwar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-15 15:17:34

Zobacz Tutaj .

Istnieje rozwiązanie oparte narozwiązaniu Timmmm ' a .

Ale poprawiłem:

  • Teraz rozwiązanie można rozszerzyć - wystarczy tylko rozszerzyć FragmentAbleToStartTask

  • Możesz wykonywać kilka zadań jednocześnie.

    I moim zdaniem jest to tak proste, jak startActivityForResult i otrzymanie wyniku

  • Możesz również zatrzymać uruchomione zadanie i sprawdzić, czy dane zadanie jest running

Sorry for my English

 -1
Author: l3onid-clean-coder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:11