화면 회전 중에 AsyncTask를 처리하는 방법은 무엇입니까?


88

인스턴스 상태를 저장하는 방법이나 화면 회전 중에 파괴되는 내 활동을 처리하는 방법에 대해 많이 읽었습니다.

많은 가능성이있는 것 같지만 AsyncTask의 결과를 검색하는 데 가장 적합한 방법을 찾지 못했습니다.

나는 단순히 다시 시작 isFinishing()되고 활동 의 메소드를 호출하는 AsyncTasks가 있으며 활동이 완료되면 아무것도 업데이트하지 않습니다.

문제는 실패하거나 성공할 수있는 웹 서비스에 대한 요청을 수행하는 하나의 작업이 있고 작업을 다시 시작하면 사용자에게 재정적 손실이 발생한다는 것입니다.

이 문제를 어떻게 해결 하시겠습니까? 가능한 솔루션의 장단점은 무엇입니까?


1
여기에서 내 대답을 참조 하십시오 . 실제로 도움 되는 것에 대한이 정보를setRetainInstance(true) 찾을 수도 있습니다 .
Timmmm

내가 할 일은 단순히 asyncTask가 수행하는 처리를 스레드에서 수행하는 로컬 서비스를 구현하는 것입니다. 결과를 표시하려면 데이터를 활동에 브로드 캐스트하십시오. 이제 활동은 데이터 표시만을 담당 하며 화면 회전으로 인해 처리가 중단 되지 않습니다 .
누군가 어딘가에

AsyncTask 대신 AsyncTaskLoader를 사용하는 것은 어떻습니까 ??
Sourangshu Biswas 2015

답변:


6

내 첫 번째 제안은 화면 회전 (기본 동작)에서 실제로 활동을 재설정해야하는지 확인하는 것입니다. 회전에 문제가있을 때마다이 속성을 <activity>AndroidManifest.xml의 내 태그에 추가 했으며 괜찮 았습니다.

android:configChanges="keyboardHidden|orientation"

이상해 보이지만 그것이 당신의 onConfigurationChanged()방법에 전달하는 것은 당신이 하나를 제공하지 않으면 레이아웃을 다시 측정하는 것 외에는 아무것도하지 않습니다. 이는 대부분의 시간 동안 회전을 처리하는 완벽하게 적절한 방법 인 것 같습니다 .


5
그러나 그러면 활동이 레이아웃을 변경하지 못합니다. 따라서 사용자가 자신의 필요가 아닌 애플리케이션에 지정된 특정 방향으로 기기를 사용하도록합니다.
Janusz

77
이 기술을 사용하면 구성 특정 리소스를 쉽게 사용할 수 없습니다. 예를 들어 레이아웃, 드로어 블 또는 문자열 또는 세로 및 가로에서 다른 것을 원할 경우 기본 동작을 원할 것입니다. 구성 변경을 재정의하는 것은 매우 특정한 경우 (게임, 웹 브라우저 등)에서만 수행해야하며, 자신을 제한하고 있기 때문에 게으름이나 편리함으로 인해 수행되지 않아야합니다.
Romain Guy

38
그게 다야, 로맹. "레이아웃이나 드로어 블 또는 문자열 또는 세로 및 가로에서 다른 것을 원하면 기본 동작을 원할 것입니다."라고 생각하는 것보다 훨씬 드문 사용 사례라고 생각합니다. "매우 특정한 경우"라고 부르는 것은 제가 믿는 대부분의 개발자입니다. 모든 차원에서 작동하는 상대 레이아웃을 사용하는 것이 가장 좋은 방법이며 그렇게 어렵지는 않습니다. 게으름에 대한 이야기는 매우 잘못된 것이며, 이러한 기술은 개발 시간을 줄이는 것이 아니라 사용자 경험을 개선하는 것입니다.
Jim Blackler

2
나는 이것이 LinearLayout에 완벽하게 작동한다는 것을 알았지 만 RelativeLayout을 사용할 때 가로 모드로 전환 할 때 레이아웃을 올바르게 다시 그리지 않습니다 (적어도 N1에서는 아님). 이 질문을 참조하십시오 : stackoverflow.com/questions/2987049/…
JohnRock 2010 년

9
나는 Romain에 동의합니다 (그는 그가 말하는 것을 알고 OS를 개발합니다). 애플리케이션을 태블릿으로 포팅하고 싶을 때 UI가 늘어 났을 때 끔찍하게 보이면 어떻게됩니까? 이 답변의 접근 방식을 취하면이 게으른 해킹을 사용했기 때문에 전체 솔루션을 다시 코딩해야합니다.
Austyn Mahoney 2011 년

46

code.google.com/p/shelvesAsyncTask 에서 s 및 방향 변경을 처리하는 방법을 확인할 수 있습니다 . 이를 수행하는 방법에는 여러 가지가 있습니다.이 앱에서 선택한 방법은 현재 실행중인 작업을 취소하고 상태를 저장하고 새 작업 이 생성 될 때 저장된 상태로 새 작업을 시작하는 것입니다. 쉽게 할 수 있고 잘 작동하며 사용자가 앱을 떠날 때 작업을 중지하는 데 도움이됩니다.Activity

당신은 또한 onRetainNonConfigurationInstance()당신 AsyncTask을 새로운 것에 전달할 수 있습니다 Activity( Activity이 방법으로 이전을 유출하지 않도록 조심하십시오 .)


1
나는 책을 검색 인터럽트 중에 및 회전을 시도하고 나에게 회전하지 않을 때보다 결과가 나쁘지 제공
max4ever을

1
해당 코드에서 AsyncTask의 단일 사용법을 찾을 수 없습니다. 비슷한 모양의 UserTask 클래스가 있습니다. 이 프로젝트가 AsyncTask보다 이전입니까?
devconsole

7
AsyncTask는 UserTask에서 왔습니다. 원래 내 앱을 위해 UserTask를 작성했고 나중에 AsyncTask로 바꿨습니다. 이름이 바뀌 었다는 점에 대해 죄송합니다.
Romain Guy

@RomainGuy 안녕하세요, 잘 지내시기 바랍니다. 귀하의 코드에 따르면 첫 번째 작업이 취소되었지만 성공적으로 취소되지는 않았지만 요청이 서버로 전송됩니다. 이유를 모르겠습니다. 이 문제를 해결할 수있는 방법이 있는지 알려주세요.
iamcrypticcoder 2014 년

10

이것은 내가 안드로이드와 관련하여 본 가장 흥미로운 질문입니다 !!! 사실 저는 지난 몇 달 동안 이미 해결책을 찾고있었습니다. 아직 해결되지 않았습니다.

조심하세요.

android:configChanges="keyboardHidden|orientation"

물건으로는 충분하지 않습니다.

AsyncTask가 실행되는 동안 사용자가 전화를받는 경우를 고려하십시오. 요청이 이미 서버에서 처리 중이므로 AsyncTask가 응답을 기다리고 있습니다. 이 순간에는 전화 앱이 포 그라운드로 들어 왔기 때문에 앱이 백그라운드로 전환됩니다. OS는 백그라운드에 있기 때문에 활동을 종료 할 수 있습니다.


6

Android에서 제공하는 Singleton에서 현재 AsyncTask에 대한 참조를 항상 유지하지 않는 이유는 무엇입니까?

태스크가 시작될 때마다 PreExecute 또는 빌더에서 다음을 정의합니다.

((Application) getApplication()).setCurrentTask(asyncTask);

완료 될 때마다 null로 설정합니다.

이렇게하면 항상 특정 논리에 적합한 onCreate 또는 onResume과 같은 작업을 수행 할 수있는 참조가 있습니다.

this.asyncTaskReference = ((Application) getApplication()).getCurrentTask();

null이면 현재 실행중인 항목이 없음을 알 수 있습니다!

:-)


작동할까요? 누구든지 이것을 테스트 했습니까? 전화 통화 중단이 발생하거나 새 활동으로 이동 한 다음 다시 돌아가도 작업이 시스템에 의해 여전히 종료됩니까?
로버트

6
Application인스턴스에는 자체 수명주기가 있습니다. OS에 의해 종료 될 수도 있으므로이 솔루션은 재현하기 어려운 버그를 유발할 수 있습니다.
Vit Khudenko 2011 년

7
나는 생각했다 : 응용 프로그램이 죽으면 전체 응용 프로그램이 죽을 것입니다 (따라서 모든 AsyncTask도 마찬가지입니다)?
manmal

모든 비동기 작업없이 애플리케이션을 종료 할 수 있다고 생각합니다 (매우 드뭅니다). 그러나 @Arhimed는 각 비동기 작업의 시작과 끝에서 수행하기 쉬운 몇 가지 확인을 통해 버그를 피할 수 있습니다.
neteinstein 2011



3

내 관점 onRetainNonConfigurationInstance에서는 현재 Activity 객체에서 분리하고 방향 변경 후 새로운 Activity 객체에 바인딩하여 asynctask를 저장하는 것이 좋습니다 . 여기서 AsyncTask 및 ProgressDialog로 작업하는 방법에 대한 아주 좋은 예를 찾았습니다.


2

Android : 구성 변경이있는 백그라운드 처리 / 비동기 작업

백그라운드 프로세스 중에 비동기 작동 상태를 유지하려면 조각의 도움을받을 수 있습니다.

다음 단계를 참조하십시오.

1 단계 : 헤더없는 조각을 만들고 백그라운드 작업을 말하고 그 안에있는 개인 비동기 작업 클래스를 추가합니다.

2 단계 (선택적 단계) : 활동 위에 로딩 커서를 놓으려면 아래 코드를 사용하십시오.

3 단계 : 기본 활동에서 1 단계에서 정의한 BackgroundTaskCallbacks 인터페이스를 구현합니다.

class BackgroundTask extends Fragment {
public BackgroundTask() {

}

// Add a static interface 

static interface BackgroundTaskCallbacks {
    void onPreExecute();

    void onCancelled();

    void onPostExecute();

    void doInBackground();
}

private BackgroundTaskCallbacks callbacks;
private PerformAsyncOpeation asyncOperation;
private boolean isRunning;
private final String TAG = BackgroundTask.class.getSimpleName();

/**
 * Start the async operation.
 */
public void start() {
    Log.d(TAG, "********* BACKGROUND TASK START OPERATION ENTER *********");
    if (!isRunning) {
        asyncOperation = new PerformAsyncOpeation();
        asyncOperation.execute();
        isRunning = true;
    }
    Log.d(TAG, "********* BACKGROUND TASK START OPERATION EXIT *********");
}

/**
 * Cancel the background task.
 */
public void cancel() {
    Log.d(TAG, "********* BACKGROUND TASK CANCEL OPERATION ENTER *********");
    if (isRunning) {
        asyncOperation.cancel(false);
        asyncOperation = null;
        isRunning = false;
    }
    Log.d(TAG, "********* BACKGROUND TASK CANCEL OPERATION EXIT *********");
}

/**
 * Returns the current state of the background task.
 */
public boolean isRunning() {
    return isRunning;
}

/**
 * Android passes us a reference to the newly created Activity by calling
 * this method after each configuration change.
 */
public void onAttach(Activity activity) {
    Log.d(TAG, "********* BACKGROUND TASK ON ATTACH ENTER *********");
    super.onAttach(activity);
    if (!(activity instanceof BackgroundTaskCallbacks)) {
        throw new IllegalStateException(
                "Activity must implement the LoginCallbacks interface.");
    }

    // Hold a reference to the parent Activity so we can report back the
    // task's
    // current progress and results.
    callbacks = (BackgroundTaskCallbacks) activity;
    Log.d(TAG, "********* BACKGROUND TASK ON ATTACH EXIT *********");
}

public void onCreate(Bundle savedInstanceState) {
    Log.d(TAG, "********* BACKGROUND TASK ON CREATE ENTER *********");
    super.onCreate(savedInstanceState);
    // Retain this fragment across configuration changes.
    setRetainInstance(true);
    Log.d(TAG, "********* BACKGROUND TASK ON CREATE EXIT *********");
}

public void onDetach() {
    super.onDetach();
    callbacks = null;
}

private class PerformAsyncOpeation extends AsyncTask<Void, Void, Void> {
    protected void onPreExecute() {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON PRE EXECUTE ENTER *********");
        if (callbacks != null) {
            callbacks.onPreExecute();
        }
        isRunning = true;
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON PRE EXECUTE EXIT *********");
    }

    protected Void doInBackground(Void... params) {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > DO IN BACKGROUND ENTER *********");
        if (callbacks != null) {
            callbacks.doInBackground();
        }
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > DO IN BACKGROUND EXIT *********");
        return null;
    }

    protected void onCancelled() {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON CANCEL ENTER *********");
        if (callbacks != null) {
            callbacks.onCancelled();
        }
        isRunning = false;
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON CANCEL EXIT *********");
    }

    protected void onPostExecute(Void ignore) {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON POST EXECUTE ENTER *********");
        if (callbacks != null) {
            callbacks.onPostExecute();
        }
        isRunning = false;
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON POST EXECUTE EXIT *********");
    }
}

public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    setRetainInstance(true);
}

public void onStart() {
    super.onStart();
}

public void onResume() {
    super.onResume();
}

public void onPause() {
    super.onPause();
}

public void onStop() {
    super.onStop();
}

public class ProgressIndicator extends Dialog {

public ProgressIndicator(Context context, int theme) {
    super(context, theme);
}

private ProgressBar progressBar;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.progress_indicator);
    this.setCancelable(false);
    progressBar = (ProgressBar) findViewById(R.id.progressBar);
    progressBar.getIndeterminateDrawable().setColorFilter(R.color.DarkBlue, android.graphics.PorterDuff.Mode.SCREEN);
}

@Override
public void show() {
    super.show();
}

@Override
public void dismiss() {
    super.dismiss();
}

@Override
public void cancel() {
    super.cancel();
}

public class MyActivity extends FragmentActivity implements BackgroundTaskCallbacks,{

private static final String KEY_CURRENT_PROGRESS = "current_progress";

ProgressIndicator progressIndicator = null;

private final static String TAG = MyActivity.class.getSimpleName();

private BackgroundTask task = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(//"set your layout here");
    initialize your views and widget here .............



    FragmentManager fm = getSupportFragmentManager();
    task = (BackgroundTask) fm.findFragmentByTag("login");

    // If the Fragment is non-null, then it is currently being
    // retained across a configuration change.
    if (task == null) {
        task = new BackgroundTask();
        fm.beginTransaction().add(task, "login").commit();
    }

    // Restore saved state
    if (savedInstanceState != null) {
        Log.i(TAG, "KEY_CURRENT_PROGRESS_VALUE ON CREATE :: "
                + task.isRunning());
        if (task.isRunning()) {
            progressIndicator = new ProgressIndicator(this,
                    R.style.TransparentDialog);
            if (progressIndicator != null) {
                progressIndicator.show();
            }
        }
    }
}

@Override
protected void onPause() {
    // TODO Auto-generated method stub
    super.onPause();

}

@Override
protected void onSaveInstanceState(Bundle outState) {
    // save the current state of your operation here by saying this 

    super.onSaveInstanceState(outState);
    Log.i(TAG, "KEY_CURRENT_PROGRESS_VALUE ON SAVE INSTANCE :: "
            + task.isRunning());
    outState.putBoolean(KEY_CURRENT_PROGRESS, task.isRunning());
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();
    }
    progressIndicator = null;
}


private void performOperation() {

            if (!task.isRunning() && progressIndicator == null) {
                progressIndicator = new ProgressIndicator(this,
                        R.style.TransparentDialog);
                progressIndicator.show();
            }
            if (task.isRunning()) {
                task.cancel();
            } else {
                task.start();
            }
        }


@Override
protected void onDestroy() {
    super.onDestroy();
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();
    }
    progressIndicator = null;
}

@Override
public void onPreExecute() {
    Log.i(TAG, "CALLING ON PRE EXECUTE");
}

@Override
public void onCancelled() {
    Log.i(TAG, "CALLING ON CANCELLED");
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();

}

public void onPostExecute() {
    Log.i(TAG, "CALLING ON POST EXECUTE");
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();
        progressIndicator = null;
    }
}

@Override
public void doInBackground() {
    // put your code here for background operation
}

}


1

고려해야 할 한 가지는 AsyncTask의 결과를 작업을 시작한 활동에만 사용할 수 있는지 여부입니다. 그렇다면 Romain Guy의 대답 이 가장 좋습니다. 이 응용 프로그램의 다른 활동에 사용할 수 있어야합니다 경우에 onPostExecute당신은 사용할 수 있습니다 LocalBroadcastManager.

LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent("finished"));

또한 활동이 일시 중지 된 동안 브로드 캐스트가 전송 될 때 활동이 상황을 올바르게 처리하는지 확인해야합니다.


1

게시물을보십시오 . 이 게시물에는 AsyncTask가 장기 실행 작업을 수행하고 하나의 샘플 애플리케이션에서 화면 회전이 모두 발생할 때 메모리 누수가 포함됩니다. 샘플 앱은 Source forge 에서 사용할 수 있습니다.


0

내 솔루션.

제 경우에는 동일한 컨텍스트를 가진 AsyncTasks 체인이 있습니다. 활동은 첫 번째 활동에만 액세스했습니다. 실행중인 작업을 취소하려면 다음을 수행했습니다.

public final class TaskLoader {

private static AsyncTask task;

     private TaskLoader() {
         throw new UnsupportedOperationException();
     }

     public static void setTask(AsyncTask task) {
         TaskLoader.task = task;
     }

    public static void cancel() {
         TaskLoader.task.cancel(true);
     }
}

작업 doInBackground():

protected Void doInBackground(Params... params) {
    TaskLoader.setTask(this);
    ....
}

활동 onStop()또는 onPause():

protected void onStop() {
    super.onStop();
    TaskLoader.cancel();
}

0
@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    final AddTask task = mAddTask;
    if (task != null && task.getStatus() != UserTask.Status.FINISHED) {
        final String bookId = task.getBookId();
        task.cancel(true);

        if (bookId != null) {
            outState.putBoolean(STATE_ADD_IN_PROGRESS, true);
            outState.putString(STATE_ADD_BOOK, bookId);
        }

        mAddTask = null;
    }
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
    if (savedInstanceState.getBoolean(STATE_ADD_IN_PROGRESS)) {
        final String id = savedInstanceState.getString(STATE_ADD_BOOK);
        if (!BooksManager.bookExists(getContentResolver(), id)) {
            mAddTask = (AddTask) new AddTask().execute(id);
        }
    }
}

0

android : configChanges = "keyboardHidden | orientation | screenSize"를 추가 할 수도 있습니다.

당신의 매니페스트 예제에 도움이되기를 바랍니다.

 <application
    android:name=".AppController"
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:configChanges="keyboardHidden|orientation|screenSize"
    android:theme="@style/AppTheme">
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.