백그라운드 작업, 진행률 대화 상자, 방향 변경-100 % 작동하는 솔루션이 있습니까?


235

백그라운드 스레드에서 인터넷에서 일부 데이터를 다운로드하고 (을 사용합니다 AsyncTask) 다운로드하는 동안 진행률 대화 상자를 표시합니다. 방향이 변경되고 활동이 다시 시작된 다음 내 AsyncTask가 완료됩니다. progess 대화 상자를 닫고 새 활동을 시작하고 싶습니다. 그러나 dismissDialog를 호출하면 때때로 예외가 발생합니다 (아마도 활동이 파괴되었고 새로운 활동이 아직 시작되지 않았기 때문에).

이러한 종류의 문제를 처리하는 가장 좋은 방법은 무엇입니까 (사용자가 방향을 변경하더라도 작동하는 백그라운드 스레드에서 UI 업데이트)? Google 직원이 "공식 솔루션"을 제공 했습니까?


4
이 주제에 대한 내 블로그 게시물 이 도움이 될 수 있습니다. 구성 변경에서 장기 실행 작업을 유지하는 것입니다.
Alex Lockwood

1
질문 도 관련이 있습니다.
Alex Lockwood

그냥 FTR 여기에 관련 미스터리가 있습니다 .. stackoverflow.com/q/23742412/294884
Fattie

답변:


336

1 단계 : 당신의 확인 중첩 클래스, 또는 완전히 별도의 클래스, 그냥 내부 (비 정적 중첩) 클래스를.AsyncTaskstatic

2 단계 : 생성자와 setter를 통해 설정된 데이터 멤버를 통해 via를 AsyncTask유지합니다 Activity.

3 단계 : 생성시 생성자에 AsyncTask전류 Activity를 공급합니다 .

단계 # 4 : 에서, 원래의 진행중인 활동에서 분리 한 후을 onRetainNonConfigurationInstance()리턴하십시오 AsyncTask.

5 단계 : 에서이 아닌 onCreate()경우 클래스로 전송하고 세터를 호출하여 새 활동을 작업과 연결합니다.getLastNonConfigurationInstance()nullAsyncTask

6 단계 :의 활동 데이터 멤버를 참조하지 마십시오 doInBackground().

위의 레시피를 따르면 모든 것이 작동합니다. onProgressUpdate()그리고 onPostExecute()다음의 시작 onRetainNonConfigurationInstance()과 끝 사이에 일시 중단 onCreate()됩니다.

다음은 이 기술을 보여주는 샘플 프로젝트 입니다.

또 다른 방법은를 버리고 AsyncTask작업을으로 옮기는 것 IntentService입니다. 이것은 수행 할 작업이 길 수 있고 사용자가 활동 측면에서 수행하는 작업 (예 : 큰 파일 다운로드)에 관계없이 진행되어야하는 경우에 특히 유용합니다. 정렬 된 브로드 캐스트 Intent를 사용하여 활동이 수행중인 작업에 응답하도록하거나 (아직 포 그라운드에있는 Notification경우) 작업을 완료했는지 사용자에게 알리기 위해 a 를 올릴 수 있습니다. 이 패턴에 대한 자세한 내용 은 블로그 게시물 입니다.


8
이 일반적인 문제에 대한 귀하의 큰 답변에 감사드립니다! 철저히하기 위해 AsyncTask에서 활동을 분리 (널로 설정)해야하는 4 단계를 추가 할 수 있습니다. 이것은 샘플 프로젝트에서 잘 설명되어 있습니다.
Kevin Gaudin

3
그러나 활동 회원에게 액세스 권한이 필요한 경우 어떻게해야합니까?
유진

3
@Andrew : 정적 내부 클래스 또는 여러 객체를 보유하는 무언가를 만들어 반환합니다.
CommonsWare

11
onRetainNonConfigurationInstance()사용되지 않으며 권장되는 대안은을 사용하는 setRetainInstance()것이지만 객체를 반환하지는 않습니다. ? asyncTask로 구성 변경 을 처리 할 수 setRetainInstance()있습니까?
Indrek Kõue

10
@SYLARRR : 물론입니다. 이 Fragment홀드를 AsyncTask. 이 Fragment전화 setRetainInstance(true)자체에 따라 있습니다. 에 AsyncTask유일한 대화를하십시오 Fragment. 이제는 구성 변경시 Fragment활동이 있더라도 파괴되고 다시 작성되지 않으므로 AsyncTask구성 변경을 통해 유지됩니다.
CommonsWare

13

수락 된 답변은 매우 도움이되었지만 진행률 대화 상자가 없습니다.

다행히도 독자 여러분, 진행 대화 상자가있는 AsyncTask매우 포괄적이고 실제적인 예를 만들었습니다 !

  1. 회전이 작동하고 대화가 유지됩니다.
  2. 뒤로 버튼을 눌러 작업 및 대화 상자를 취소 할 수 있습니다 (이 동작을 원하는 경우).
  3. 조각을 사용합니다.
  4. 활동 아래의 조각 레이아웃은 장치가 회전 할 때 올바르게 변경됩니다.

허용되는 답변은 정적 클래스 (멤버가 아님)에 관한 것입니다. 그리고 그이 필요하다 AsyncTask를이 활동을 파괴에 메모리 누수가되는 외부 클래스의 인스턴스에 대한 (숨겨진) 포인터를 가지고 피하기 위해.
Bananeweizen

그래도 정적 멤버에 대해 왜 넣었는지 잘 모르겠습니다. 실제로도 사용했기 때문에 ... 이상합니다. 수정 된 답변.
Timmmm

링크를 업데이트 해 주시겠습니까? 나는 이것이 정말로 필요하다.
Romain Pellerin

죄송합니다. 내 웹 사이트를 복원하지 못했습니다. 곧 할게요! 그러나 그 동안 그것은 기본적으로이 답변의 코드와 동일합니다 : stackoverflow.com/questions/8417885/…
Timmmm

1
링크는 가짜입니다. 코드의 위치를 ​​나타내지 않고 쓸모없는 색인으로 이어집니다.
FractalBob

9

나는 매니페스트 파일을 편집하지 않고이 딜레마에 대한 해결책을 찾기 위해 일주일 동안 노력했습니다. 이 솔루션의 가정은 다음과 같습니다.

  1. 항상 진행률 대화 상자를 사용해야합니다
  2. 한 번에 하나의 작업 만 수행
  3. 전화가 회전하고 진행률 대화 상자가 자동으로 닫힐 때 작업을 지속해야합니다.

이행

이 게시물의 하단에있는 두 파일을 작업 공간에 복사해야합니다. 다음을 확인하십시오.

  1. 당신 Activity의 모든 확장해야합니다BaseActivity

  2. 에서 onCreate()super.onCreate()액세스해야하는 멤버를 초기화 한 후에서을 (를) 호출해야합니다 ASyncTask. 또한 getContentViewId()양식 레이아웃 ID를 제공하도록 대체 하십시오.

  3. 활동에 의해 관리되는 대화 상자를 작성하려면 onCreateDialog() 평소처럼 무시 하십시오.

  4. AsyncTasks를 만들기위한 정적 정적 내부 클래스 샘플은 아래 코드를 참조하십시오. mResult에 결과를 저장하여 나중에 액세스 할 수 있습니다.


final static class MyTask extends SuperAsyncTask<Void, Void, Void> {

    public OpenDatabaseTask(BaseActivity activity) {
        super(activity, MY_DIALOG_ID); // change your dialog ID here...
                                       // and your dialog will be managed automatically!
    }

    @Override
    protected Void doInBackground(Void... params) {

        // your task code

        return null;
    }

    @Override
    public boolean onAfterExecute() {
        // your after execute code
    }
}

마지막으로 새 작업을 시작하십시오.

mCurrentTask = new MyTask(this);
((MyTask) mCurrentTask).execute();

그게 다야! 이 강력한 솔루션이 누군가를 도울 수 있기를 바랍니다.

BaseActivity.java (가져 오기 구성)

protected abstract int getContentViewId();

public abstract class BaseActivity extends Activity {
    protected SuperAsyncTask<?, ?, ?> mCurrentTask;
    public HashMap<Integer, Boolean> mDialogMap = new HashMap<Integer, Boolean>();

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

        setContentView(getContentViewId());

        mCurrentTask = (SuperAsyncTask<?, ?, ?>) getLastNonConfigurationInstance();
        if (mCurrentTask != null) {
            mCurrentTask.attach(this);
            if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
                && mDialogMap.get((Integer) mCurrentTask.dialogId)) {
        mCurrentTask.postExecution();
            }
        }
    }

    @Override
    protected void onPrepareDialog(int id, Dialog dialog) {
    super.onPrepareDialog(id, dialog);

        mDialogMap.put(id, true);
    }

    @Override
    public Object onRetainNonConfigurationInstance() {
        if (mCurrentTask != null) {
            mCurrentTask.detach();

            if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
                && mDialogMap.get((Integer) mCurrentTask.dialogId)) {
                return mCurrentTask;
            }
        }

        return super.onRetainNonConfigurationInstance();
    }

    public void cleanupTask() {
        if (mCurrentTask != null) {
            mCurrentTask = null;
            System.gc();
        }
    }
}

SuperAsyncTask.java

public abstract class SuperAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
    protected BaseActivity mActivity = null;
    protected Result mResult;
    public int dialogId = -1;

    protected abstract void onAfterExecute();

    public SuperAsyncTask(BaseActivity activity, int dialogId) {
        super();
        this.dialogId = dialogId;
        attach(activity);
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        mActivity.showDialog(dialogId); // go polymorphism!
    }    

    protected void onPostExecute(Result result) {
        super.onPostExecute(result);
        mResult = result;

        if (mActivity != null &&
                mActivity.mDialogMap.get((Integer) dialogId) != null
                && mActivity.mDialogMap.get((Integer) dialogId)) {
            postExecution();
        }
    };

    public void attach(BaseActivity activity) {
        this.mActivity = activity;
    }

    public void detach() {
        this.mActivity = null;
    }

    public synchronized boolean postExecution() {
        Boolean dialogExists = mActivity.mDialogMap.get((Integer) dialogId);
        if (dialogExists != null || dialogExists) {
            onAfterExecute();
            cleanUp();
    }

    public boolean cleanUp() {
        mActivity.removeDialog(dialogId);
        mActivity.mDialogMap.remove((Integer) dialogId);
        mActivity.cleanupTask();
        detach();
        return true;
    }
}

4

Google 직원이 "공식 솔루션"을 제공 했습니까?

예.

솔루션은 단지 일부 코드 보다는 응용 프로그램 아키텍처 제안에 가깝습니다 .

그들은 응용 프로그램 상태에 관계없이 응용 프로그램이 서버와 동기화되어 작동 할 수있게 해주는 3 가지 디자인 패턴 을 제안 했습니다 (사용자가 응용 프로그램을 마치고 사용자가 화면을 변경하고 응용 프로그램이 종료되는 경우 가능한 모든 다른 상태에서도 작동합니다) 백그라운드 데이터 작업이 뒤섞 일 수 있습니다.

제안은 Virgil Dobjanschi의 Google I / O 2010Android REST 클라이언트 애플리케이션 연설 에서 설명됩니다 . 길이는 1 시간이지만 볼 가치가 있습니다.

그 기초는 네트워크 작업을 응용 프로그램에서 Service독립적으로 작동 하는 네트워크 작업으로 추상화하는 것 입니다 Activity. 당신이 데이터베이스 작업하는 경우, 사용 ContentResolver하고 Cursor당신에게 아웃 - 오브 - 박스 줄 것 옵저버 패턴 당신이 가져온 원격 데이터와 로컬 데이터베이스를 업데이트하면, 어떤이 추가적으로 로직없이 UI를 업데이트하는 것이 편리하다. 다른 모든 작업 후 코드는 콜백을 통해 실행됩니다 Service( ResultReceiver서브 클래스를 사용합니다).

어쨌든, 나의 설명은 실제로 매우 모호합니다. 당신은 분명히 연설을 봐야합니다.


2

Mark의 (CommonsWare) 답변은 실제로 방향 변경에 작동하지만 전화 통화의 경우와 같이 활동이 직접 파괴되면 실패합니다.

Application 개체를 사용하여 ASyncTask를 참조하면 방향 변경 및 드물게 파괴 된 Activity 이벤트를 처리 할 수 ​​있습니다.

여기에 문제와 해결책에 대한 훌륭한 설명이 있습니다 .

크레디트는이 사실을 알아 낸 라이언에게 완전히 간다.


1

4 년 후 Google은 Activity onCreate에서 setRetainInstance (true)를 호출하여 문제를 해결했습니다. 장치 회전 중에 활동 인스턴스를 유지합니다. 구형 Android를위한 간단한 솔루션도 있습니다.


1
사람들이 관찰하는 문제는 안드로이드가 회전, 키보드 확장 및 기타 이벤트에서 활동 클래스를 파괴하지만 비동기 작업은 여전히 ​​파괴 된 인스턴스에 대한 참조를 유지하고 UI 업데이트에 사용하려고 시도하기 때문에 발생합니다. 매니페스트 또는 실용적으로 활동을 파괴하지 않도록 Android에 지시 할 수 있습니다. 이 경우 비동기 작업 참조는 계속 유효하며 아무런 문제도 관찰되지 않습니다. 회전에는 뷰 다시로드 등의 추가 작업이 필요할 수 있으므로 활동을 유지하지 않는 것이 좋습니다. 그래서 당신은 결정합니다.
Singagirl

고마워, 상황에 대해서는 알았지 만 setRetainInstance ()에 대해서는 알지 못했습니다. 내가 이해하지 못하는 것은 Google이 질문에 제기 된 문제를 해결하기 위해 이것을 사용했다는 귀하의 주장입니다. 정보 출처를 연결할 수 있습니까? 감사.
jj_


onRetainNonConfigurationInstance ()이 함수는 순수하게 최적화라고하며 호출되는 함수에 의존해서는 안됩니다. <같은 출처에서 : developer.android.com/reference/android/app/…
Dhananjay M

0

활동 핸들러를 사용하여 모든 활동 조치를 호출해야합니다. 따라서 스레드가있는 경우 Runnable을 만들고 Activitie의 처리기를 사용하여 게시해야합니다. 그렇지 않으면 치명적인 예외로 인해 앱이 충돌하는 경우가 있습니다.


0

이것은 내 솔루션입니다 : https://github.com/Gotchamoh/Android-AsyncTask-ProgressDialog

기본적으로 단계는 다음과 같습니다.

  1. onSaveInstanceState작업이 아직 처리 중이면 작업을 저장하는 데 사용 합니다.
  2. 에서 onCreate가 저장된 경우 I 작업을 얻을.
  3. 에서 onPause나는 폐기 ProgressDialog그것은이 표시됩니다.
  4. 에서 onResume내가 보여 ProgressDialog작업이 계속 처리됩니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.