[android] Android 조각. 화면 회전 또는 구성 변경 중 AsyncTask 유지

저는 스마트 폰 / 태블릿 앱에서 작업 중이며 하나의 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 명의 사용자가 있으므로이 로직을 새로운 Fragment Based Design에 복사하는 것이 논리적으로 보였지만 물론 작동하지 않습니다.

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()Fragment가 아닌 Activity에서 호출해야하므로 사용할 수 없습니다 getLastNonConfigurationInstance(). 나는 여기에서 답이없는 유사한 질문을 읽었습니다.

나는이 물건을 조각으로 올바르게 구성하기 위해 약간의 작업이 필요할 수 있음을 이해합니다. 즉, 동일한 기본 디자인 논리를 유지하고 싶습니다.

구성 변경 중에 AsyncTask를 유지하는 적절한 방법은 무엇이며, 여전히 실행중인 경우 AsyncTask가 Fragment의 내부 클래스이고 AsyncTask.execute를 호출하는 것은 Fragment 자체임을 고려하여 progressDialog를 표시합니다. ()?



답변

조각은 실제로 이것을 훨씬 쉽게 만들 수 있습니다. Fragment.setRetainInstance (boolean) 메서드를 사용하여 구성 변경시에도 조각 인스턴스를 유지합니다. 이것은 문서에서 Activity.onRetainnonConfigurationInstance () 에 대한 권장 대체입니다 .

어떤 이유로 유지 된 조각을 사용하고 싶지 않은 경우 취할 수있는 다른 접근 방식이 있습니다. 각 조각에는 Fragment.getId ()에 의해 반환 된 고유 식별자가 있습니다. 또한 Fragment.getActivity (). isChangingConfigurations ()를 통해 구성 변경을 위해 조각이 해체되고 있는지 확인할 수 있습니다 . 따라서 AsyncTask (onStop () 또는 onDestroy ()에서)를 중지하기로 결정한 시점에서 예를 들어 구성이 변경되는지 확인하고, 그렇다면 조각의 식별자 아래에있는 정적 SparseArray에 고정 할 수 있습니다. 그런 다음 onCreate () 또는 onStart ()에서 사용 가능한 희소 배열에 AsyncTask가 있는지 확인하십시오.


답변

아래에 자세히 설명 된 매우 포괄적이고 실제적인 예제를 즐길 수있을 것입니다.

  1. 회전이 작동하고 대화가 유지됩니다.
  2. 뒤로 버튼을 눌러 작업 및 대화 상자를 취소 할 수 있습니다 (이 동작을 원할 경우).
  3. 조각을 사용합니다.
  4. 활동 아래에있는 조각의 레이아웃은 장치가 회전 할 때 올바르게 변경됩니다.
  5. 완전한 소스 코드 다운로드와 사전 컴파일 된 APK가 있으므로 원하는 동작인지 확인할 수 있습니다.

편집하다

Brad Larson의 요청에 따라 아래 링크 된 솔루션의 대부분을 재현했습니다. 또한 게시 한 이후로 지적되었습니다 AsyncTaskLoader. 동일한 문제에 완전히 적용 할 수 있을지 모르겠지만 어쨌든 확인해야합니다.

사용 AsyncTask진행률 대화 상자 및 장치 회전과 함께 .

작동하는 솔루션!

드디어 일할 모든 것을 얻었습니다. 내 코드에는 다음과 같은 기능이 있습니다.

  1. Fragment방향에 따라 레이아웃이 변경되는 입니다.
  2. AsyncTask 을 할 수있는 곳.
  3. DialogFragment진행률 표시 줄에서 작업의 진행 (다만 불확실한 회)를 표시한다.
  4. 회전은 작업을 중단하거나 대화 상자를 닫지 않고 작동합니다.
  5. 뒤로 버튼은 대화 상자를 닫고 작업을 취소합니다 (이 동작은 매우 쉽게 변경할 수 있음).

일 함의 조합은 다른 곳에서는 찾을 수 없다고 생각합니다.

기본 아이디어는 다음과 같습니다. MainActivity단일 조각을 포함 하는 클래스가 있습니다 MainFragment. MainFragment가로 및 세로 방향에 대한 레이아웃이 다르며 레이아웃 setRetainInstance()이 변경 될 수 있도록 false입니다. 즉, 장치 방향이 변경되면 MainActivityMainFragment 완전히 파괴되고 다시 생성됩니다.

별도로 모든 작업을 수행하는 MyTask(에서 확장 AsyncTask)이 있습니다. 그것은 MainFragment파괴 될 것이기 때문에 우리는 그것을 저장할 수 없으며 Google은 setRetainNonInstanceConfiguration(). 어쨌든 항상 사용할 수있는 것은 아니며 기껏해야 추악한 해킹입니다. 대신 라는 MyTask다른 조각에 저장 합니다. 단편 것이다 한 참으로 설정되므로 장치의 회전으로이 단편은 파괴되지 않고,DialogFragmentTaskFragmentsetRetainInstance()MyTask 유지된다.

마지막으로 우리 TaskFragment는 그것이 끝났을 때 누구에게 알릴 것인지를 알려줄 필요 setTargetFragment(<the MainFragment>)가 있습니다. 그리고 우리는 그것을 만들 때 그것을 사용하여 그렇게 합니다. 장치가 회전되고 MainFragment가 파괴되고 새 인스턴스가 생성되면을 사용 FragmentManager하여 대화 상자 (태그 기반)를 찾고setTargetFragment(<the new MainFragment>) . 그게 다야.

다른 두 가지 작업이 필요했습니다. 먼저 대화 상자가 닫힐 때 작업을 취소하고 두 번째로 닫기 메시지를 null로 설정합니다. 그렇지 않으면 장치가 회전 할 때 대화 상자가 이상하게 닫힙니다.

코드

나는 레이아웃을 나열하지 않을 것입니다. 그들은 매우 분명하며 아래 프로젝트 다운로드에서 찾을 수 있습니다.

주요 활동

이것은 매우 간단합니다. 이 활동에 콜백을 추가하여 작업이 언제 완료되는지 알 수 있지만 필요하지 않을 수도 있습니다. 주로 단편 활동 콜백 메커니즘을 보여주고 싶었습니다. 매우 깔끔하고 이전에는 보지 못했을 수도 있기 때문입니다.

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

길지만 그만한 가치가 있습니다!

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

예제 프로젝트 다운로드

다음은 소스 코드APK 입니다. 죄송합니다. ADT는 제가 프로젝트를 만들 수 있기 전에 지원 라이브러리를 추가해야한다고 주장했습니다. 제거 할 수 있다고 확신합니다.


답변

최근 유지 된 Fragments를 사용하여 구성 변경을 처리하는 방법을 설명하는 기사게시했습니다 . 유지 문제를 해결합니다.AsyncTask회전 변경 멋지게 .

TL; DR은 AsyncTask내부에서 호스트 를 사용하고을 Fragment호출 setRetainInstance(true)하고 의 진행 상황 / 결과를 다시 Fragment보고 (또는 @Timmmm에서 설명하는 접근 방식을 사용하기로 선택한 경우 대상 )를 통해 .AsyncTaskActivityFragmentFragment


답변

내 첫 번째 제안은 내부 AsyncTasks피하는 것입니다. 이에 대해 질문 한 질문과 답변을 읽을 수 있습니다. Android : AsyncTask Recommendation : private class or public class?

그 후 나는 비 내부를 사용하기 시작했고 … 이제 많은 이점을 볼 수 있습니다.

두 번째는 Application클래스 에서 실행중인 AsyncTask에 대한 참조를 유지하는 것입니다-http : //developer.android.com/reference/android/app/Application.html

AsyncTask를 시작할 때마다 응용 프로그램에서 설정하고 완료되면 null로 설정합니다.

프래그먼트 / 액티비티가 시작되면 AsyncTask가 실행 중인지 (응용 프로그램에서 null인지 아닌지 확인하여) 확인한 다음 원하는 항목 (액티비티, 프래그먼트 등)을 참조하여 콜백을 수행 할 수 있습니다.

이렇게하면 문제가 해결됩니다. 결정된 시간에 1 개의 AsyncTask 만 실행중인 경우 간단한 참조를 추가 할 수 있습니다.

AsyncTask<?,?,?> asyncTask = null;

그렇지 않으면 Aplication에서 HashMap을 참조하십시오.

진행률 대화 상자는 똑같은 원칙을 따를 수 있습니다.


답변

이를 위해 AsyncTaskLoaders를 사용하는 방법을 생각해 냈습니다. 사용하기 매우 쉽고 오버 헤드가 적은 IMO가 필요합니다.

기본적으로 다음과 같이 AsyncTaskLoader를 만듭니다.

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

그런 다음 버튼을 클릭 할 때 위의 AsyncTaskLoader를 사용하는 활동에서 다음을 수행합니다.

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

    }
}

이것은 방향 변경을 잘 처리하는 것으로 보이며 회전하는 동안 백그라운드 작업이 계속됩니다.

참고할 몇 가지 사항 :

  1. onCreate에서 asynctaskloader에 다시 연결하면 이전 결과와 함께 onLoadFinished ()에서 다시 호출됩니다 (이미 요청이 완료되었다고 말했더라도). 이것은 실제로 대부분의 경우 좋은 동작이지만 때로는 처리하기가 까다로울 수 있습니다. 나는 이것을 처리하는 많은 방법이 있다고 생각하지만 내가 한 일은 onLoadFinished에서 loader.abandon ()이라고 불렀습니다. 그런 다음 아직 포기하지 않은 경우에만 로더에 다시 연결하도록 onCreate 체크인을 추가했습니다. 결과 데이터가 다시 필요하면 그렇게하고 싶지 않을 것입니다. 대부분의 경우 데이터를 원합니다.

여기 에 http 호출에 이것을 사용하는 방법에 대한 자세한 내용이 있습니다.


답변

저는 Marshmallow를 기반으로 AsyncTask하지만 다음과 같은 추가 기능이 있는 매우 작은 오픈 소스 백그라운드 작업 라이브러리를 만들었습니다 .

  1. 구성 변경시 작업을 자동으로 유지합니다.
  2. UI 콜백 (리스너)
  3. 장치가 회전 할 때 작업을 다시 시작하거나 취소하지 않습니다 (로더처럼).

라이브러리는 내부적으로 Fragment사용자 인터페이스없이를 사용하며 이는 구성 변경 ( setRetainInstance(true))에 걸쳐 유지 됩니다.

GitHub에서 찾을 수 있습니다 : https://github.com/NeoTech-Software/Android-Retainable-Tasks

가장 기본적인 예 (버전 0.2.0) :

이 예제는 매우 제한된 양의 코드를 사용하여 작업을 완전히 유지합니다.

직무:

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

활동:

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


답변

내 접근 방식은 위임 디자인 패턴을 사용하는 것입니다. 일반적으로 AysncTask.doInBackground () 메서드에서 AsyncTask (위임자)에서 BusinessDAO (대리자)로 실제 비즈니스 논리 (인터넷 또는 데이터베이스에서 데이터 읽기 등)를 분리 할 수 ​​있습니다. , 실제 작업을 BusinessDAO에 위임 한 다음 BusinessDAO에서 단일 프로세스 메커니즘을 구현하여 BusinessDAO.doSomething ()을 여러 번 호출하면 매번 실행되고 작업 결과를 기다리는 하나의 실제 작업이 트리거됩니다. 아이디어는 위임자 (예 : AsyncTask) 대신 구성 변경 중에 위임 (예 : BusinessDAO)을 유지하는 것입니다.

  1. 자체 애플리케이션을 생성 / 구현합니다. 목적은 여기에서 BusinessDAO를 생성 / 초기화하는 것이므로 BusinessDAO의 수명주기가 활동 범위가 아닌 애플리케이션 범위가되도록합니다. MyApplication을 사용하려면 AndroidManifest.xml을 변경해야합니다.

    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. 기존 Activity / Fragement는 대부분 변경되지 않았으며 여전히 AsyncTask를 내부 클래스로 구현하고 Activity / Fragement의 AsyncTask.execute ()를 포함합니다. 차이점은 이제 AsyncTask가 실제 작업을 BusinessDAO에 위임하므로 구성 변경 중에 두 번째 AsyncTask 초기화 및 실행되고 BusinessDAO.doSomething ()을 두 번째로 호출하지만 BusinessDAO.doSomething ()에 대한 두 번째 호출은 새 실행 작업을 트리거하지 않고 대신 현재 실행중인 작업이 완료 될 때까지 기다립니다.

    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. BusinessDAO 내부에서 단일 프로세스 메커니즘을 구현합니다. 예를 들면 다음과 같습니다.

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

이것이 작동하는지 100 % 확신하지 못합니다. 또한 샘플 코드 스 니펫을 의사 코드로 간주해야합니다. 디자인 수준에서 약간의 단서를 제공하려고합니다. 피드백이나 제안을 환영합니다.