Фрагменты Android. Сохранение AsyncTask во время поворота экрана или изменения конфигурации - PullRequest
84 голосов
/ 07 декабря 2011

Я работаю над приложением для смартфонов / планшетов, использую только один APK и загружаю ресурсы по мере необходимости в зависимости от размера экрана; наилучший выбор дизайна, похоже, заключается в использовании фрагментов через ACL.

Этоприложение работало нормально до сих пор, будучи только на основе деятельности.Это фиктивный класс того, как я обрабатываю AsyncTasks и ProgressDialogs в Деятельностях, чтобы заставить их работать, даже когда экран поворачивается или изменение конфигурации происходит во время связи.

Я не буду изменять манифест, чтобы избежать воссозданияДеятельности, есть много причин, почему я не хочу это делать, но в основном потому, что официальные документы говорят, что это не рекомендуется, и я обходился без него до сих пор, поэтому, пожалуйста, не рекомендуйте этот маршрут.

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

Этот код работает нормально, у меня около 10 000 пользователей без жалоб, поэтому было бы логично просто скопировать эту логику в новый дизайн на основе фрагментов, но, конечно, она не работает.

Вот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
                }
            }
        }
    }
}

Я не могу использовать onRetainNonConfigurationInstance(), поскольку он должен вызываться из действия, а не из фрагмента, то же самое относится и к getLastNonConfigurationInstance().Я читал некоторые подобные вопросы здесь без ответа.

Я понимаю, что для правильной организации всего этого по фрагментам может потребоваться работа, при этом я хотел бы сохранить ту же базовую логику проектирования.

Каким будет правильный способ сохранить AsyncTask во время изменения конфигурации, и, если он все еще работает, покажите progressDialog, принимая во внимание, что AsyncTask является внутренним классом для фрагмента и егоэто сам фрагмент, который вызывает AsyncTask.execute ()?

Ответы [ 12 ]

0 голосов
/ 24 апреля 2014

Посмотрите здесь .

Существует решение, основанное на решении Тиммма .

Но я его улучшил:

  • Теперь решение можно расширять - вам нужно только расширить FragmentAbleToStartTask

  • Вы можете продолжать выполнение нескольких задач одновременно.

    И, на мой взгляд, это так же просто, как startActivityForResult и получать результат

  • Вы также можете остановить запущенную задачу и проверить, выполняется ли конкретная задача

Извините за мой английский

0 голосов
/ 04 декабря 2013

Я пишу тот же код для решения этой проблемы

Первый шаг - сделать Класс приложения:

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

В 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">

Код в действии:

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

}

При изменении ориентации активности переменная mTask запускается из контекста приложения.Когда задача завершена, переменная устанавливается в ноль и удаляется из памяти.

Для меня этого достаточно.

...