AsyncTask가 실제로 개념적으로 결함이 있습니까? 아니면 뭔가 빠졌습니까? 다른 해결책을 생각해 냈습니다.

나는이 문제를 몇 달 동안 조사했으며 다른 해결책을 생각해 냈습니다. 모두 큰 해킹이기 때문에 만족스럽지 않습니다. 디자인에 결함이있는 클래스가 프레임 워크에 만들었고 아무도 그것에 대해 이야기하고 있지 않다는 것을 여전히 믿을 수 없으므로 뭔가 빠진 것 같아요.

문제는입니다 AsyncTask. 문서에 따르면

“스레드 및 / 또는 핸들러를 조작 할 필요없이 백그라운드 작업을 수행하고 UI 스레드에 결과를 게시 할 수 있습니다.”

그런 다음 예제는에서 예제 showDialog()메소드가 어떻게 호출 되는지 계속 보여줍니다 onPostExecute(). 그러나 대화 상자를 표시하려면 항상 유효한 참조가 필요하고 AsyncTask 는 컨텍스트 객체에 대한 강력한 참조를 보유해서는 안되기 때문에 이것은 전적으로 나에게 부여 된 것처럼 보입니다 .Context

그 이유는 분명합니다. 활동이 파괴되어 작업을 시작하면 어떻게됩니까? 예를 들어 화면을 뒤집었기 때문에 항상 이런 일이 발생할 수 있습니다. 태스크를 만든 컨텍스트에 대한 참조를 유지한다면, 당신은 단지 쓸모 컨텍스트 개체 (윈도우가 파괴 된 것이며에 들고하지 않는 모든 UI 상호 작용 예외와 함께 실패합니다!), 당신은도를 만드는 위험 메모리 누수.

내 논리가 여기에 결함이 없다면, 이것은 다음과 같이 번역됩니다. onPostExecute()문맥에 액세스 할 수없는 경우이 메소드가 UI 스레드에서 실행되는 것이 좋기 때문에 전혀 쓸모가 없습니다. 여기서 의미있는 일은 할 수 없습니다.

한 가지 해결 방법은 컨텍스트 인스턴스를 AsyncTask가 아니라 인스턴스로 전달하는 것 Handler입니다. 작동 : 처리기는 컨텍스트와 작업을 느슨하게 바인딩하므로 누출 위험없이 메시지를 교환 할 수 있습니다 (오른쪽?). 그러나 그것은 AsyncTask의 전제, 즉 처리기를 귀찮게 할 필요가 없다는 것이 잘못되었음을 의미합니다. 동일한 스레드에서 메시지를 보내고 받고 있기 때문에 Handler를 학대하는 것처럼 보입니다 (UI 스레드에서 메시지를 작성하고 UI 스레드에서 실행되는 onPostExecute ()에서 메시지를 전송 함).

그 해결 방법을 사용하더라도 상황이 파괴 될 때 발생하는 작업에 대한 기록없다는 문제가 여전히 있습니다 . 즉, 컨텍스트를 다시 만들 때 (예 : 화면 방향 변경 후) 작업을 다시 시작해야합니다. 이것은 느리고 낭비입니다.

이것에 대한 나의 해결책 ( Droid-Fu 라이브러리에서 구현 됨 )은 WeakReference고유 한 응용 프로그램 객체의 구성 요소 이름에서 현재 인스턴스 로 의 매핑을 유지하는 것입니다 . AsyncTask가 시작될 때마다 해당 맵에 호출 컨텍스트를 기록하고 모든 콜백에서 해당 맵핑에서 현재 컨텍스트 인스턴스를 가져옵니다. 당신은 오래된 컨텍스트 인스턴스를 참조하지 않을 것을이 보장하지만 그리고 당신이 거기에 의미있는 UI 작업을 할 수 있도록 항상 콜백에 유효한 컨텍스트에 액세스 할 수 있습니다. 참조가 약하고 주어진 구성 요소의 인스턴스가 더 이상 존재하지 않으면 지워지기 때문에 누출되지 않습니다.

여전히 복잡한 해결 방법이며 일부 Droid-Fu 라이브러리 클래스를 서브 클래스 화해야하므로이 방법이 상당히 방해가됩니다.

이제 나는 단순히 알고 싶어합니다. 방금 큰 무언가가 누락되었거나 AsyncTask가 실제로 완전히 결함이 있습니까? 당신의 경험은 어떻게 작동합니까? 이 문제를 어떻게 해결 했습니까?

입력 해 주셔서 감사합니다.



답변

이런 식으로 어떻습니까 :

class MyActivity extends Activity {
    Worker mWorker;

    static class Worker extends AsyncTask<URL, Integer, Long> {
        MyActivity mActivity;

        Worker(MyActivity activity) {
            mActivity = activity;
        }

        @Override
        protected Long doInBackground(URL... urls) {
            int count = urls.length;
            long totalSize = 0;
            for (int i = 0; i < count; i++) {
                totalSize += Downloader.downloadFile(urls[i]);
                publishProgress((int) ((i / (float) count) * 100));
            }
            return totalSize;
        }

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

        @Override
        protected void onPostExecute(Long result) {
            if (mActivity != null) {
                mActivity.showDialog("Downloaded " + result + " bytes");
            }
        }
    }

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

        mWorker = (Worker)getLastNonConfigurationInstance();
        if (mWorker != null) {
            mWorker.mActivity = this;
        }

        ...
    }

    @Override
    public Object onRetainNonConfigurationInstance() {
        return mWorker;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mWorker != null) {
            mWorker.mActivity = null;
        }
    }

    void startWork() {
        mWorker = new Worker(this);
        mWorker.execute(...);
    }
}

답변

그 이유는 분명합니다. 활동이 파괴되어 작업을 시작하면 어떻게됩니까?

수동에서 활동 해제 AsyncTask에서을 onDestroy(). 새 활동을 AsyncTaskin에 수동으로 다시 연결하십시오 onCreate(). 정적 내부 클래스 또는 표준 Java 클래스와 10 줄의 코드가 필요합니다.


답변

것 같습니다 AsyncTask조금있다 만보다 개념적으로 결함 . 호환성 문제로도 사용할 수 없습니다. Android 문서는 다음과 같습니다.

처음 소개되었을 때 AsyncTasks는 단일 백그라운드 스레드에서 순차적으로 실행되었습니다. DONUT부터는 여러 작업을 병렬로 수행 할 수있는 스레드 풀로 변경되었습니다. HONEYCOMB을 시작하면 병렬 실행으로 인한 일반적인 응용 프로그램 오류를 피하기 위해 단일 스레드에서 작업이 다시 실행됩니다. 병렬 실행을 정말로 원한다면 executeOnExecutor(Executor, Params...) 이 메소드 버전을THREAD_POOL_EXECUTOR ; 그러나 사용에 대한 경고는 주석을 참조하십시오.

모두 executeOnExecutor()하고 THREAD_POOL_EXECUTOR있습니다 API 레벨 11에 추가 (안드로이드 3.0.x의, 벌집).

AsyncTask, 두 개의 파일을 다운로드하기 위해 두 개의 파일 을 만들면 첫 번째 파일이 완료 될 때까지 두 번째 다운로드가 시작되지 않습니다. 두 서버를 통해 채팅하고 첫 번째 서버가 다운 된 경우 첫 번째 서버에 대한 연결 시간이 초과되기 전에 두 번째 서버에 연결하지 않습니다. 물론 새로운 API11 기능을 사용하지 않으면 코드가 2.x와 호환되지 않습니다.

그리고 2.x와 3.0+를 모두 목표로 삼으려면 정말 까다로워집니다.

또한 문서 는 다음 과 같이 말합니다.

주의 : 작업자 스레드를 사용할 때 발생할 수있는 또 다른 문제는 런타임 구성 변경 (예 : 사용자가 화면 방향을 변경하는 경우)으로 인해 활동이 예기치 않게 다시 시작된다는 것입니다. 될 수 있습니다 . 이러한 재시작 중 하나를 수행하는 동안 작업을 유지하는 방법과 활동이 소멸 될 때 작업을 올바르게 취소하는 방법을 보려면 선반 샘플 응용 프로그램의 소스 코드를 참조하십시오.


답변

아마 우리는 구글을 포함한 모든이, 오용되는 AsyncTask으로부터 MVC 관점 입니다.

활동은 컨트롤러 이므로 컨트롤러가 보기 보다 오래 지속될 수있는 작업을 시작해서는 안됩니다 . 즉, AsyncTasks는 Activity 라이프 사이클에 바인딩되지 않은 클래스의 Model 에서 사용해야합니다 . 활동은 회전시 파괴됩니다. ( View 와 관련 하여 일반적으로 android.widget.Button에서 파생 된 클래스를 프로그래밍하지는 않지만 가능합니다. 일반적으로 View 에 대해 수행하는 유일한 작업 은 xml입니다.)

다시 말해, 활동의 메소드에 AsyncTask 파생어를 배치하는 것은 잘못입니다. OTOH, 활동에서 AsyncTasks를 사용하지 않아야하는 경우 AsyncTask는 매력을 잃습니다. 예전에는 빠르고 쉬운 해결책으로 광고되었습니다.


답변

AsyncTask의 컨텍스트에 대한 참조로 메모리 누수 위험이 있다는 것은 확실하지 않습니다.

그것들을 구현하는 일반적인 방법은 Activity의 메소드 중 하나의 범위 내에서 새로운 AsyncTask 인스턴스를 만드는 것입니다. 따라서 활동이 파괴되면 AsyncTask가 완료되면 도달 할 수 없으며 가비지 수집이 가능합니까? 따라서 AsyncTask 자체가 멈추지 않기 때문에 활동에 대한 참조는 중요하지 않습니다.


답변

활동에 대한 WeekReference를 유지하는 것이 더 강력합니다.

public class WeakReferenceAsyncTaskTestActivity extends Activity {
    private static final int MAX_COUNT = 100;

    private ProgressBar progressBar;

    private AsyncTaskCounter mWorker;

    @SuppressWarnings("deprecation")
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_async_task_test);

        mWorker = (AsyncTaskCounter) getLastNonConfigurationInstance();
        if (mWorker != null) {
            mWorker.mActivity = new WeakReference<WeakReferenceAsyncTaskTestActivity>(this);
        }

        progressBar = (ProgressBar) findViewById(R.id.progressBar1);
        progressBar.setMax(MAX_COUNT);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_async_task_test, menu);
        return true;
    }

    public void onStartButtonClick(View v) {
        startWork();
    }

    @Override
    public Object onRetainNonConfigurationInstance() {
        return mWorker;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mWorker != null) {
            mWorker.mActivity = null;
        }
    }

    void startWork() {
        mWorker = new AsyncTaskCounter(this);
        mWorker.execute();
    }

    static class AsyncTaskCounter extends AsyncTask<Void, Integer, Void> {
        WeakReference<WeakReferenceAsyncTaskTestActivity> mActivity;

        AsyncTaskCounter(WeakReferenceAsyncTaskTestActivity activity) {
            mActivity = new WeakReference<WeakReferenceAsyncTaskTestActivity>(activity);
        }

        private static final int SLEEP_TIME = 200;

        @Override
        protected Void doInBackground(Void... params) {
            for (int i = 0; i < MAX_COUNT; i++) {
                try {
                    Thread.sleep(SLEEP_TIME);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Log.d(getClass().getSimpleName(), "Progress value is " + i);
                Log.d(getClass().getSimpleName(), "getActivity is " + mActivity);
                Log.d(getClass().getSimpleName(), "this is " + this);

                publishProgress(i);
            }
            return null;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            if (mActivity != null) {
                mActivity.get().progressBar.setProgress(values[0]);
            }
        }
    }

}

답변

onPause()소유 활동 의 메소드를 대체하고 AsyncTask거기에서 메소드를 취소 하지 않는 이유는 무엇 입니까?