모범 사례 : 방향 변경 중 AsyncTask


151

AsyncTask 다른 스레드에서 복잡한 작업을 실행하는 것이 좋습니다.

그러나 방향 변경이나 다른 구성 변경이있는 동안에는 AsyncTask전류 Activity가 파괴되고 다시 시작됩니다. 그리고 인스턴스 AsyncTask가 해당 활동에 연결되면 실패하고 "강제 종료"메시지 창이 나타납니다.

따라서 이러한 오류를 피하고 AsyncTask가 실패하지 않도록 일종의 "모범 사례"를 찾고 있습니다.

내가 지금까지 본 것은 :

  • 방향 변경을 사용하지 않도록 설정하십시오 (이를 처리하는 방법이 확실하지 않음).
  • 작업을 유지하고 새 활동 인스턴스로 업데이트 onRetainNonConfigurationInstance
  • Activity가 삭제 되면 작업을 취소 하고 다시 Activity생성 되면 다시 시작하면됩니다 .
  • 활동 인스턴스 대신 애플리케이션 클래스에 태스크 바인딩
  • "shelves"프로젝트에서 사용되는 일부 메소드 (onRestoreInstanceState를 통해)

일부 코드 예제 :

화면 회전 중 Android AsyncTask, 1 부2 부

ShelvesActivity.java

문제를 가장 잘 해결하고 구현하기 쉬운 최선의 접근 방법을 찾도록 도와 줄 수 있습니까? 이 문제를 올바르게 해결하는 방법을 모르기 때문에 코드 자체도 중요합니다.


중복이 있습니다. 이 stackoverflow.com/questions/4584015/…를 확인하십시오 .
TeaCupApp

이것은 Mark Murphy의 블로그에서 ... AsyncTask 및 ScreenRotation이 도움이 될 수 있습니다 ... link
Gopal

이 게시물은 오래된 게시물이지만 IMO는 훨씬 더 쉽고 좋은 방법입니다.
DroidDev

문서가 왜 그렇게 사소한 상황에 대해 말하지 않는지 궁금합니다.
Sreekanth Karumanaghat

답변:


140

이 문제를 해결 하는 데 사용 하지 마십시오android:configChanges . 이것은 매우 나쁜 습관입니다.

어느 쪽도 사용 하지 마십시오Activity#onRetainNonConfigurationInstance() . 이것은 덜 모듈 Fragment식이며 기반 응용 프로그램에는 적합하지 않습니다 .

retained를 사용하여 구성 변경을 처리하는 방법을 설명하는 내 기사읽을 수 있습니다 Fragment. AsyncTask회전 변화를 잘 유지하는 문제를 해결합니다 . 당신은 기본적으로 호스트 할 필요가 AsyncTask내부 a를 Fragment, 전화 setRetainInstance(true)상의를 Fragment하고,보고 AsyncTask그것의 위로의 진행 / 결과를 Activity유지 관통 Fragment.


26
좋은 생각이지만 모든 사람이 조각을 사용하는 것은 아닙니다. Fragments가 옵션이되기 오래 전에 작성된 많은 레거시 코드가 있습니다.
Scott Biggs

14
@ScottBiggs Fragments는 지원 라이브러리를 통해 Android 1.6까지 계속 사용할 수 있습니다. 그리고 지원 라이브러리 Fragments를 사용하는 데 문제가있는 여전히 활발하게 사용되고있는 일부 레거시 코드의 예를들 수 있습니까? 솔직히 그게 문제라고 생각하지 않기 때문입니다.
Alex Lockwood

4
@tactoth 99.9 %의 사람들이 더 이상 사용하지 않기 때문에 제 대답에서 이러한 문제를 해결해야 할 필요성을 느끼지 못했습니다 TabActivity. 솔직히 말해서, 우리가 왜 이것에 대해 이야기하고 있는지 잘 모르겠습니다 ... 모두가 Fragments가 갈 길이 라고 동의합니다 . :)
Alex Lockwood

2
중첩 된 조각에서 AsyncTask를 호출해야하는 경우 어떻게합니까?
Eduardo Naveda

3
@AlexLockwood- "모두 조각이 갈 길에 동의합니다." Squared의 Devs는 동의하지 않을 것입니다!
JBeckton

36

나는 보통 AsyncTasks가 .onPostExecute () 콜백에서 방송 인 텐트를 발생 시켜서 이것을 해결하므로 직접 시작한 활동을 수정하지 않습니다. 활동은 동적 인 BroadcastReceivers로 이러한 브로드 캐스트를 청취하고 그에 따라 작동합니다.

이런 식으로 AsyncTask는 결과를 처리하는 특정 Activity 인스턴스를 신경 쓸 필요가 없습니다. 그들은 끝났을 때 그냥 "소리를 지르며"활동이 그 시간이 다가 오면 (활동 중이고 집중되어 / 재개 된 상태에 있음) 작업 결과에 관심이 있으면 처리됩니다.

런타임은 브로드 캐스트를 처리해야하므로 오버 헤드가 조금 더 발생하지만 보통은 상관 없습니다. 기본 시스템 너비 대신 LocalBroadcastManager를 사용하면 속도가 약간 빨라집니다.


6
답변에 예를 추가 할 수 있다면 더 도움이 될 것입니다
Sankar V

1
나는 이것이 활동과 프래그먼트들 사이에 적은 연결을 제공하는 솔루션이라고 생각한다
Roger Garzon Nieto

7
이것은 솔루션의 일부 일 수 있지만 방향 변경 후 AsyncTask의 문제가 해결되지 않는 것 같습니다.
miguel

4
당신이 운이 좋지 않고 방송 중에 활동이 없다면 어떻게해야합니까? (즉, 당신은 중간 회전)

24

를 사용하여 Fragment(사용자가 화면을 회전 할 때와 같이) 런타임 구성 변경을 처리 하는 AsyncTask의 또 다른 예는 다음과 같습니다 setRetainInstance(true). 결정 (정기적으로 업데이트 된) 진행률 표시 줄도 보여줍니다.

이 예는 부분적으로 공식 문서, 구성 변경 중 객체 유지 에 기반합니다 .

이 예제에서 백그라운드 스레드가 필요한 작업은 인터넷에서 UI로 이미지를로드하는 것입니다.

Alex Lockwood는 "Retained Fragment"를 사용하여 AsyncTasks로 런타임 구성 변경을 처리하는 것이 가장 좋습니다. onRetainNonConfigurationInstance()Lint, Android Studio에서 더 이상 사용되지 않습니다. 공식 문서를 사용하여 우리를 경고 android:configChanges에서 구성 변경 자신을 취급 , ...

구성 변경을 직접 처리하면 시스템이 자동으로 대체 리소스를 적용하지 않기 때문에 대체 리소스를 사용하기가 훨씬 어려워 질 수 있습니다. 이 기술은 구성 변경으로 인한 재시작을 피해야 할 경우 최후의 수단으로 고려해야하며 대부분의 응용 프로그램에는 권장되지 않습니다.

그런 다음 백그라운드 스레드에 AsyncTask를 사용해야하는지 여부에 대한 문제가 있습니다.

AsyncTask공식 참조는 경고합니다 ...

AsyncTasks는 짧은 작업 (최대 몇 초)에 이상적으로 사용되어야합니다. 스레드를 장기간 계속 실행해야하는 경우 java.util.concurrent pacakge에서 제공하는 다양한 API를 사용하는 것이 좋습니다. Executor, ThreadPoolExecutor 및 FutureTask.

또는 서비스, 로더 (CursorLoader 또는 AsyncTaskLoader 사용) 또는 컨텐츠 제공자를 사용하여 비동기 작업을 수행 할 수 있습니다.

나는 게시물의 나머지 부분을 나눕니다.

  • 절차; 과
  • 위의 절차에 대한 모든 코드.

절차

  1. 기본 AsyncTask를 활동의 내부 클래스로 시작하십시오 (내부 클래스 일 필요는 없지만 편리 할 것입니다). 이 단계에서 AsyncTask는 런타임 구성 변경을 처리하지 않습니다.

    public class ThreadsActivity extends ActionBarActivity {
    
        private ImageView mPictureImageView;
    
        private class LoadImageFromNetworkAsyncTask
                              extends AsyncTask<String, Void, Bitmap> {
    
            @Override
            protected Bitmap doInBackground(String... urls) {
                return loadImageFromNetwork(urls[0]);
            }
    
            @Override
            protected void onPostExecute(Bitmap bitmap) {
                mPictureImageView.setImageBitmap(bitmap);
            }
        }
    
        /**
         * Requires in AndroidManifext.xml
         *  <uses-permission android:name="android.permission.INTERNET" />
         */
        private Bitmap loadImageFromNetwork(String url) {
            Bitmap bitmap = null;
            try {
                bitmap = BitmapFactory.decodeStream((InputStream)
                                              new URL(url).getContent());
            } catch (Exception e) {
                e.printStackTrace();
            }
            return bitmap;
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_threads);
    
            mPictureImageView =
                (ImageView) findViewById(R.id.imageView_picture);
        }
    
        public void getPicture(View view) {
            new LoadImageFromNetworkAsyncTask()
                .execute("http://i.imgur.com/SikTbWe.jpg");
        }
    
    }
  2. Fragement 클래스를 확장하고 자체 UI가없는 중첩 클래스 RetainedFragment를 추가하십시오. 이 Fragment의 onCreate 이벤트에 setRetainInstance (true)를 추가하십시오. 데이터를 설정하고 얻는 절차를 제공하십시오.

    public class ThreadsActivity extends Activity {
    
        private ImageView mPictureImageView;
        private RetainedFragment mRetainedFragment = null;
        ...
    
        public static class RetainedFragment extends Fragment {
    
            private Bitmap mBitmap;
    
            @Override
            public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
    
                // The key to making data survive
                // runtime configuration changes.
                setRetainInstance(true);
            }
    
            public Bitmap getData() {
                return this.mBitmap;
            }
    
            public void setData(Bitmap bitmapToRetain) {
                this.mBitmap = bitmapToRetain;
            }
        }
    
        private class LoadImageFromNetworkAsyncTask
                        extends AsyncTask<String, Integer,Bitmap> {
        ....
  3. 가장 바깥 쪽 Activity 클래스의 onCreate ()에서 RetainedFragment를 처리하십시오. 이미 존재하는 경우 참조하십시오 (활동이 다시 시작되는 경우). 존재하지 않는 경우 작성하여 추가하십시오. 그런 다음 이미 존재하는 경우 RetainedFragment에서 데이터를 가져와 해당 데이터로 UI를 설정하십시오.

    public class ThreadsActivity extends Activity {
    
        ...
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_threads);
    
            final String retainedFragmentTag = "RetainedFragmentTag";
    
            mPictureImageView =
                      (ImageView) findViewById(R.id.imageView_picture);
            mLoadingProgressBar =
                    (ProgressBar) findViewById(R.id.progressBar_loading);
    
            // Find the RetainedFragment on Activity restarts
            FragmentManager fm = getFragmentManager();
            // The RetainedFragment has no UI so we must
            // reference it with a tag.
            mRetainedFragment =
              (RetainedFragment) fm.findFragmentByTag(retainedFragmentTag);
    
            // if Retained Fragment doesn't exist create and add it.
            if (mRetainedFragment == null) {
    
                // Add the fragment
                mRetainedFragment = new RetainedFragment();
                fm.beginTransaction()
                    .add(mRetainedFragment, retainedFragmentTag).commit();
    
            // The Retained Fragment exists
            } else {
    
                mPictureImageView
                    .setImageBitmap(mRetainedFragment.getData());
            }
        }
  4. UI에서 AsyncTask 시작

    public void getPicture(View view) {
        new LoadImageFromNetworkAsyncTask().execute(
                "http://i.imgur.com/SikTbWe.jpg");
    }
  5. 확정 진행률 표시 줄 추가 및 코딩 :

    • UI 레이아웃에 진행률 표시 줄을 추가하십시오.
    • Activity oncreate ()에서 참조를 얻으십시오.
    • 프로세스의 시작과 끝에서 볼 수 있고 보이지 않게하십시오.
    • onProgressUpdate에서 UI에보고 할 진행률을 정의하십시오.
    • AsyncTask 2nd Generic 매개 변수를 Void에서 진행 업데이트를 처리 할 수있는 유형 (예 : 정수)으로 변경하십시오.
    • doInBackground ()의 일반 지점에서 publishProgress.

위의 절차에 대한 모든 코드

활동 레이아웃.

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.mysecondapp.ThreadsActivity">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin">

        <ImageView
            android:id="@+id/imageView_picture"
            android:layout_width="300dp"
            android:layout_height="300dp"
            android:background="@android:color/black" />

        <Button
            android:id="@+id/button_get_picture"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentStart="true"
            android:layout_below="@id/imageView_picture"
            android:onClick="getPicture"
            android:text="Get Picture" />

        <Button
            android:id="@+id/button_clear_picture"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignBottom="@id/button_get_picture"
            android:layout_toEndOf="@id/button_get_picture"
            android:layout_toRightOf="@id/button_get_picture"
            android:onClick="clearPicture"
            android:text="Clear Picture" />

        <ProgressBar
            android:id="@+id/progressBar_loading"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/button_get_picture"
            android:progress="0"
            android:indeterminateOnly="false"
            android:visibility="invisible" />

    </RelativeLayout>
</ScrollView>

서브 클래 싱 된 AsyncTask 내부 클래스; 런타임 구성 변경을 처리하는 서브 클래 싱 된 RetainedFragment 내부 클래스 (예 : 사용자가 화면을 회전 할 때); 일정한 간격으로 업데이트되는 확정 진행 막대. ...

public class ThreadsActivity extends Activity {

    private ImageView mPictureImageView;
    private RetainedFragment mRetainedFragment = null;
    private ProgressBar mLoadingProgressBar;

    public static class RetainedFragment extends Fragment {

        private Bitmap mBitmap;

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

            // The key to making data survive runtime configuration changes.
            setRetainInstance(true);
        }

        public Bitmap getData() {
            return this.mBitmap;
        }

        public void setData(Bitmap bitmapToRetain) {
            this.mBitmap = bitmapToRetain;
        }
    }

    private class LoadImageFromNetworkAsyncTask extends AsyncTask<String,
            Integer, Bitmap> {

        @Override
        protected Bitmap doInBackground(String... urls) {
            // Simulate a burdensome load.
            int sleepSeconds = 4;
            for (int i = 1; i <= sleepSeconds; i++) {
                SystemClock.sleep(1000); // milliseconds
                publishProgress(i * 20); // Adjust for a scale to 100
            }

            return com.example.standardapplibrary.android.Network
                    .loadImageFromNetwork(
                    urls[0]);
        }

        @Override
        protected void onProgressUpdate(Integer... progress) {
            mLoadingProgressBar.setProgress(progress[0]);
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            publishProgress(100);
            mRetainedFragment.setData(bitmap);
            mPictureImageView.setImageBitmap(bitmap);
            mLoadingProgressBar.setVisibility(View.INVISIBLE);
            publishProgress(0);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_threads);

        final String retainedFragmentTag = "RetainedFragmentTag";

        mPictureImageView = (ImageView) findViewById(R.id.imageView_picture);
        mLoadingProgressBar = (ProgressBar) findViewById(R.id.progressBar_loading);

        // Find the RetainedFragment on Activity restarts
        FragmentManager fm = getFragmentManager();
        // The RetainedFragment has no UI so we must reference it with a tag.
        mRetainedFragment = (RetainedFragment) fm.findFragmentByTag(
                retainedFragmentTag);

        // if Retained Fragment doesn't exist create and add it.
        if (mRetainedFragment == null) {

            // Add the fragment
            mRetainedFragment = new RetainedFragment();
            fm.beginTransaction().add(mRetainedFragment,
                                      retainedFragmentTag).commit();

            // The Retained Fragment exists
        } else {

            mPictureImageView.setImageBitmap(mRetainedFragment.getData());
        }
    }

    public void getPicture(View view) {
        mLoadingProgressBar.setVisibility(View.VISIBLE);
        new LoadImageFromNetworkAsyncTask().execute(
                "http://i.imgur.com/SikTbWe.jpg");
    }

    public void clearPicture(View view) {
        mRetainedFragment.setData(null);
        mPictureImageView.setImageBitmap(null);
    }
}

이 예제에서 실제 작동하는 라이브러리 함수 (위에서 명시 적 패키지 접두사 com.example.standardapplibrary.android.Network로 참조) ...

public static Bitmap loadImageFromNetwork(String url) {
    Bitmap bitmap = null;
    try {
        bitmap = BitmapFactory.decodeStream((InputStream) new URL(url)
                .getContent());
    } catch (Exception e) {
        e.printStackTrace();
    }
    return bitmap;
}

백그라운드 작업에 필요한 권한을 AndroidManifest.xml에 추가하십시오 ...

<manifest>
...
    <uses-permission android:name="android.permission.INTERNET" />

AndroidManifest.xml에 활동 추가 ...

<manifest>
...
    <application>
        <activity
            android:name=".ThreadsActivity"
            android:label="@string/title_activity_threads"
            android:parentActivityName=".MainActivity">
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="com.example.mysecondapp.MainActivity" />
        </activity>

큰. 이것에 대해 블로그를 작성해야합니다.
Akh

2
@AKh. 내 대답이 Stackoverflow에서 너무 많은 공간을 차지한다고 제안합니까?
John Bentley

1
나는 그가 당신이 멋진 답변을 가지고 있다는 것을 의미한다고 생각하고 블로그를 작성해야합니다! =) @JohnBentley
Sandy D.

@ SandyD.yesterday 긍정적 인 해석에 감사드립니다. 나는 그녀 또는 그가 그것을 의도했으면 좋겠다.
존 벤틀리

나는 또한 그것이 멋진 대답이라고 생각했고 나는 그것을 그렇게 해석했습니다. 이와 같은 매우 완전한 답변은 훌륭합니다!
LeonardoSibela

3

최근에 좋은 해결책을 찾았 습니다 . RetainConfiguration을 통해 태스크 오브젝트 저장을 기반으로합니다. 내 관점에서 볼 때, 솔루션은 매우 우아하며 사용하기 시작했습니다. 기본 작업에서 비동기 작업을 중첩하면됩니다.


이 흥미로운 답변에 대단히 감사합니다. 관련 질문에 언급 된 것 외에도 좋은 해결책입니다.
caw

5
불행히도이 솔루션은 더 이상 사용되지 않는 방법을 사용합니다.
Damien

3

@Alex Lockwood 답변과 @William & @quickdraw mcgraw 답변을 기반 으로이 게시물에 대한 mcgraw 답변 : 활동 / 조각이 일시 중지 될 때 처리기 메시지를 처리하는 방법 , 나는 일반적인 해결책을 썼습니다.

이렇게하면 회전이 처리되고 비동기 작업 실행 중에 활동이 백그라운드로 진행되면 활동이 다시 시작되면 콜백 (onPreExecute, onProgressUpdate, onPostExecute & onCancelled)을 수신하므로 IllegalStateException이 발생하지 않습니다 ( 처리기 처리 방법 참조) 활동 / 조각이 일시 중지되었을 때의 메시지 ).

AsyncTask (예 : AsyncTaskFragment <Params, Progress, Result>)와 같은 일반 인수 유형을 사용하는 것이 좋지만 신속하게 처리 할 수는 없었으며 현재 시간이 없습니다. 누구든지 개선을 원한다면 자유롭게 느끼십시오!

코드:

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Message;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;

public class AsyncTaskFragment extends Fragment {

    /* ------------------------------------------------------------------------------------------ */
    // region Classes & Interfaces

    public static abstract class Task extends AsyncTask<Object, Object, Object> {

        private AsyncTaskFragment _fragment;

        private void setFragment(AsyncTaskFragment fragment) {

            _fragment = fragment;
        }

        @Override
        protected final void onPreExecute() {

            // Save the state :
            _fragment.setRunning(true);

            // Send a message :
            sendMessage(ON_PRE_EXECUTE_MESSAGE, null);
        }

        @Override
        protected final void onPostExecute(Object result) {

            // Save the state :
            _fragment.setRunning(false);

            // Send a message :
            sendMessage(ON_POST_EXECUTE_MESSAGE, result);
        }

        @Override
        protected final void onProgressUpdate(Object... values) {

            // Send a message :
            sendMessage(ON_PROGRESS_UPDATE_MESSAGE, values);
        }

        @Override
        protected final void onCancelled() {

            // Save the state :
            _fragment.setRunning(false);

            // Send a message :
            sendMessage(ON_CANCELLED_MESSAGE, null);
        }

        private void sendMessage(int what, Object obj) {

            Message message = new Message();
            message.what = what;
            message.obj = obj;

            Bundle data = new Bundle(1);
            data.putString(EXTRA_FRAGMENT_TAG, _fragment.getTag());
            message.setData(data);

            _fragment.handler.sendMessage(message);
        }
    }

    public interface AsyncTaskFragmentListener {

        void onPreExecute(String fragmentTag);
        void onProgressUpdate(String fragmentTag, Object... progress);
        void onCancelled(String fragmentTag);
        void onPostExecute(String fragmentTag, Object result);
    }

    private static class AsyncTaskFragmentPauseHandler extends PauseHandler {

        @Override
        final protected void processMessage(Activity activity, Message message) {

            switch (message.what) {

                case ON_PRE_EXECUTE_MESSAGE : { ((AsyncTaskFragmentListener)activity).onPreExecute(message.getData().getString(EXTRA_FRAGMENT_TAG)); break; }
                case ON_POST_EXECUTE_MESSAGE : { ((AsyncTaskFragmentListener)activity).onPostExecute(message.getData().getString(EXTRA_FRAGMENT_TAG), message.obj); break; }
                case ON_PROGRESS_UPDATE_MESSAGE : { ((AsyncTaskFragmentListener)activity).onProgressUpdate(message.getData().getString(EXTRA_FRAGMENT_TAG), ((Object[])message.obj)); break; }
                case ON_CANCELLED_MESSAGE : { ((AsyncTaskFragmentListener)activity).onCancelled(message.getData().getString(EXTRA_FRAGMENT_TAG)); break; }
            }
        }
    }

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Attributes

    private Task _task;
    private AsyncTaskFragmentListener _listener;
    private boolean _running = false;

    private static final String EXTRA_FRAGMENT_TAG = "EXTRA_FRAGMENT_TAG";
    private static final int ON_PRE_EXECUTE_MESSAGE = 0;
    private static final int ON_POST_EXECUTE_MESSAGE = 1;
    private static final int ON_PROGRESS_UPDATE_MESSAGE = 2;
    private static final int ON_CANCELLED_MESSAGE = 3;

    private AsyncTaskFragmentPauseHandler handler = new AsyncTaskFragmentPauseHandler();

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Getters

    public AsyncTaskFragmentListener getListener() { return _listener; }
    public boolean isRunning() { return _running; }

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Setters

    public void setTask(Task task) {

        _task = task;
        _task.setFragment(this);
    }

    public void setListener(AsyncTaskFragmentListener listener) { _listener = listener; }
    private void setRunning(boolean running) { _running = running; }

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Fragment lifecycle

    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }

    @Override
    public void onResume() {

        super.onResume();
        handler.resume(getActivity());
    }

    @Override
    public void onPause() {

        super.onPause();
        handler.pause();
    }

    @Override
    public void onAttach(Activity activity) {

        super.onAttach(activity);
        _listener = (AsyncTaskFragmentListener) activity;
    }

    @Override
    public void onDetach() {

        super.onDetach();
        _listener = null;
    }

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Utils

    public void execute(Object... params) {

        _task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
    }

    public void cancel(boolean mayInterruptIfRunning) {

        _task.cancel(mayInterruptIfRunning);
    }

    public static AsyncTaskFragment getRetainedOrNewFragment(AppCompatActivity activity, String fragmentTag) {

        FragmentManager fm = activity.getSupportFragmentManager();
        AsyncTaskFragment fragment = (AsyncTaskFragment) fm.findFragmentByTag(fragmentTag);

        if (fragment == null) {

            fragment = new AsyncTaskFragment();
            fragment.setListener( (AsyncTaskFragmentListener) activity);
            fm.beginTransaction().add(fragment, fragmentTag).commit();
        }

        return fragment;
    }

    // endregion
    /* ------------------------------------------------------------------------------------------ */
}

PauseHandler가 필요합니다.

import android.app.Activity;
import android.os.Handler;
import android.os.Message;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Message Handler class that supports buffering up of messages when the activity is paused i.e. in the background.
 *
 * /programming/8040280/how-to-handle-handler-messages-when-activity-fragment-is-paused
 */
public abstract class PauseHandler extends Handler {

    /**
     * Message Queue Buffer
     */
    private final List<Message> messageQueueBuffer = Collections.synchronizedList(new ArrayList<Message>());

    /**
     * Flag indicating the pause state
     */
    private Activity activity;

    /**
     * Resume the handler.
     */
    public final synchronized void resume(Activity activity) {
        this.activity = activity;

        while (messageQueueBuffer.size() > 0) {
            final Message msg = messageQueueBuffer.get(0);
            messageQueueBuffer.remove(0);
            sendMessage(msg);
        }
    }

    /**
     * Pause the handler.
     */
    public final synchronized void pause() {
        activity = null;
    }

    /**
     * Store the message if we have been paused, otherwise handle it now.
     *
     * @param msg   Message to handle.
     */
    @Override
    public final synchronized void handleMessage(Message msg) {
        if (activity == null) {
            final Message msgCopy = new Message();
            msgCopy.copyFrom(msg);
            messageQueueBuffer.add(msgCopy);
        } else {
            processMessage(activity, msg);
        }
    }

    /**
     * Notification message to be processed. This will either be directly from
     * handleMessage or played back from a saved message when the activity was
     * paused.
     *
     * @param activity  Activity owning this Handler that isn't currently paused.
     * @param message   Message to be handled
     */
    protected abstract void processMessage(Activity activity, Message message);
}

샘플 사용법 :

public class TestActivity extends AppCompatActivity implements AsyncTaskFragmentListener {

    private final static String ASYNC_TASK_FRAGMENT_A = "ASYNC_TASK_FRAGMENT_A";
    private final static String ASYNC_TASK_FRAGMENT_B = "ASYNC_TASK_FRAGMENT_B";

    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        Button testButton = (Button) findViewById(R.id.test_button);
        final AsyncTaskFragment fragment = AsyncTaskFragment.getRetainedOrNewFragment(TestActivity.this, ASYNC_TASK_FRAGMENT_A);

        testButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                if(!fragment.isRunning()) {

                    fragment.setTask(new Task() {

                        @Override
                        protected Object doInBackground(Object... objects) {

                            // Do your async stuff

                            return null;
                        }
                    });

                    fragment.execute();
                }
            }
        });
    }

    @Override
    public void onPreExecute(String fragmentTag) {}

    @Override
    public void onProgressUpdate(String fragmentTag, Float percent) {}

    @Override
    public void onCancelled(String fragmentTag) {}

    @Override
    public void onPostExecute(String fragmentTag, Object result) {

        switch (fragmentTag) {

            case ASYNC_TASK_FRAGMENT_A: {

                // Handle ASYNC_TASK_FRAGMENT_A
                break;
            }
            case ASYNC_TASK_FRAGMENT_B: {

                // Handle ASYNC_TASK_FRAGMENT_B
                break;
            }
        }
    }
}

3

로더를 사용할 수 있습니다. 여기에서 문서 확인


2
로더는 이제 Android API 28에서 더 이상 사용되지 않습니다 (링크에서 알 수 있듯이).
수밋

로더는 더 이상 사용되지 않으며, 변경 한 것만 호출합니다
EdgeDev

2

조각을 피하고 싶은 사람들을 위해 onRetainCustomNonConfigurationInstance () 및 일부 배선을 사용하여 방향 변경에서 실행되는 AsyncTask를 유지할 수 있습니다 .

이 메소드는 사용되지 않는 onRetainNonConfigurationInstance () 대신 사용할 수 있습니다.

이 솔루션은 자주 언급되지 않는 것 같습니다. 나는 설명하기 위해 간단한 실행 예제를 작성했습니다.

건배!

public class MainActivity extends AppCompatActivity {

private TextView result;
private Button run;
private AsyncTaskHolder asyncTaskHolder;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    result = (TextView) findViewById(R.id.textView_result);
    run = (Button) findViewById(R.id.button_run);
    asyncTaskHolder = getAsyncTaskHolder();
    run.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            asyncTaskHolder.execute();
        }
    });
}

private AsyncTaskHolder getAsyncTaskHolder() {
    if (this.asyncTaskHolder != null) {
        return asyncTaskHolder;
    }
    //Not deprecated. Get the same instance back.
    Object instance = getLastCustomNonConfigurationInstance();

    if (instance == null) {
        instance = new AsyncTaskHolder();
    }
    if (!(instance instanceof ActivityDependant)) {
        Log.e("", instance.getClass().getName() + " must implement ActivityDependant");
    }
    return (AsyncTaskHolder) instance;
}

@Override
//Not deprecated. Save the object containing the running task.
public Object onRetainCustomNonConfigurationInstance() {
    return asyncTaskHolder;
}

@Override
protected void onStart() {
    super.onStart();
    if (asyncTaskHolder != null) {
        asyncTaskHolder.attach(this);
    }
}

@Override
protected void onStop() {
    super.onStop();
    if (asyncTaskHolder != null) {
        asyncTaskHolder.detach();
    }
}

void updateUI(String value) {
    this.result.setText(value);
}

interface ActivityDependant {

    void attach(Activity activity);

    void detach();
}

class AsyncTaskHolder implements ActivityDependant {

    private Activity parentActivity;
    private boolean isRunning;
    private boolean isUpdateOnAttach;

    @Override
    public synchronized void attach(Activity activity) {
        this.parentActivity = activity;
        if (isUpdateOnAttach) {
            ((MainActivity) parentActivity).updateUI("done");
            isUpdateOnAttach = false;
        }
    }

    @Override
    public synchronized void detach() {
        this.parentActivity = null;
    }

    public synchronized void execute() {
        if (isRunning) {
            Toast.makeText(parentActivity, "Already running", Toast.LENGTH_SHORT).show();
            return;
        }
        isRunning = true;
        new AsyncTask<Void, Integer, Void>() {

            @Override
            protected Void doInBackground(Void... params) {
                for (int i = 0; i < 100; i += 10) {
                    try {
                        Thread.sleep(500);
                        publishProgress(i);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                return null;
            }

            @Override
            protected void onProgressUpdate(Integer... values) {
                if (parentActivity != null) {
                    ((MainActivity) parentActivity).updateUI(String.valueOf(values[0]));
                }
            }

            @Override
            protected synchronized void onPostExecute(Void aVoid) {
                if (parentActivity != null) {
                    ((MainActivity) parentActivity).updateUI("done");
                } else {
                    isUpdateOnAttach = true;
                }
                isRunning = false;
            }
        }.execute();
    }
}

0

작업이 실행되는 동안 활동 일시 중지 및 레크리에이션 문제를 해결할 수있는 라이브러리 를 구현했습니다 .

AsmykPleaseWaitTask및 을 구현해야합니다 AsmykBasicPleaseWaitActivity. 화면을 회전하고 응용 프로그램간에 전환하더라도 활동 및 백그라운드 작업이 제대로 작동합니다.


-9

빠른 해결 방법 (권장하지 않음)

활동이 자체적으로 파괴되고 생성되는 것을 피하려면 매니페스트 파일에서 활동을 선언하는 것입니다. android : configChanges = "orientation | keyboardHidden | screenSize

  <activity
        android:name=".ui.activity.MyActivity"
        android:configChanges="orientation|keyboardHidden|screenSize"
        android:label="@string/app_name">

문서 에서 언급했듯이

화면 방향이 변경되었습니다. 사용자가 장치를 회전했습니다.

참고 : 애플리케이션이 API 레벨 13 이상 (minSdkVersion 및 targetSdkVersion 속성으로 선언 된대로)을 대상으로하는 경우 장치가 세로 방향과 가로 방향간에 전환 될 때도 변경되므로 "screenSize"구성도 선언해야합니다.


1
이것은 피하는 것이 가장 좋습니다. developer.android.com/guide/topics/resources/… "참고 : 구성 변경을 직접 처리하면 시스템이 자동으로 대체 리소스를 적용하지 않기 때문에 대체 리소스를 사용하기가 훨씬 어려워 질 수 있습니다.이 기술은 마지막으로 고려해야합니다. "구성 변경으로 인한 재시작을 피해야하며 대부분의 응용 프로그램에는 권장되지 않습니다."
David
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.