진행률 대화 상자 및 백그라운드 스레드가 활성화되었을 때 화면 방향 변경을 처리하는 방법은 무엇입니까?


524

내 프로그램은 백그라운드 스레드에서 일부 네트워크 활동을 수행합니다. 시작하기 전에 진행률 대화 상자가 나타납니다. 핸들러에서 대화 상자가 닫힙니다. 대화 상자가 켜져 있고 백그라운드 스레드가 진행되는 동안 화면 방향이 변경되는 경우를 제외하고는 모두 잘 작동합니다. 이 시점에서 앱은 다운되거나 교착 상태가되거나 모든 스레드가 종료 될 때까지 앱이 전혀 작동하지 않는 이상한 단계에 도달합니다.

화면 방향 변경을 정상적으로 처리하려면 어떻게해야합니까?

아래 샘플 코드는 실제 프로그램과 거의 일치합니다.

public class MyAct extends Activity implements Runnable {
    public ProgressDialog mProgress;

    // UI has a button that when pressed calls send

    public void send() {
         mProgress = ProgressDialog.show(this, "Please wait", 
                      "Please wait", 
                      true, true);
        Thread thread = new Thread(this);
        thread.start();
    }

    public void run() {
        Thread.sleep(10000);
        Message msg = new Message();
        mHandler.sendMessage(msg);
    }

    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            mProgress.dismiss();
        }
    };
}

스택:

E/WindowManager(  244): Activity MyAct has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@433b7150 that was originally added here
E/WindowManager(  244): android.view.WindowLeaked: Activity MyAct has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@433b7150 that was originally added here
E/WindowManager(  244):     at android.view.ViewRoot.<init>(ViewRoot.java:178)
E/WindowManager(  244):     at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:147)
E/WindowManager(  244):     at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:90)
E/WindowManager(  244):     at android.view.Window$LocalWindowManager.addView(Window.java:393)
E/WindowManager(  244):     at android.app.Dialog.show(Dialog.java:212)
E/WindowManager(  244):     at android.app.ProgressDialog.show(ProgressDialog.java:103)
E/WindowManager(  244):     at android.app.ProgressDialog.show(ProgressDialog.java:91)
E/WindowManager(  244):     at MyAct.send(MyAct.java:294)
E/WindowManager(  244):     at MyAct$4.onClick(MyAct.java:174)
E/WindowManager(  244):     at android.view.View.performClick(View.java:2129)
E/WindowManager(  244):     at android.view.View.onTouchEvent(View.java:3543)
E/WindowManager(  244):     at android.widget.TextView.onTouchEvent(TextView.java:4664)
E/WindowManager(  244):     at android.view.View.dispatchTouchEvent(View.java:3198)

onSaveInstanceState에서 진행률 대화 상자를 닫으려고했지만 즉시 충돌을 방지합니다. 백그라운드 스레드가 여전히 진행 중이며 UI가 부분적으로 그려진 상태입니다. 다시 작동하기 전에 전체 앱을 종료해야합니다.


1
당신이받은 답변을 고려할 때, 당신은 최선을 위해 수락 된 답변을 변경해야합니까?
rds


3
이 문제에 대한 훌륭한 설명과 가능한 해결책이 있습니다. http://blog.doityourselfandroid.com/2010/11/14/handling-progress-dialogs-and-screen-orientation-changes/ Lemme 를 통해 도움이되는지 확인하십시오.
arcamax

2
이 블로그 게시물 에서 화면 방향에 걸쳐 비동기 백그라운드 작업을 유지하는 방법에 대한 완전한 설명 있습니다. 확인 해봐!
Adrian Monk

매니페스트에서 android : configChanges = "orientation | screenSize"를 Activity로 설정하기 만하면 활동을 다시 만들기 위해 Android가 중지됩니다.
Jawad Zeb

답변:


155

방향을 전환하면 Android에서 새보기를 만듭니다. 백그라운드 스레드가 이전 스레드의 상태를 변경하려고 시도하여 충돌이 발생했을 수 있습니다. (배경 스레드가 UI 스레드에 없기 때문에 문제가있을 수도 있습니다)

mHandler를 휘발성으로 만들고 방향이 변경되면 업데이트하는 것이 좋습니다.


14
충돌 원인을 정확하게 지적했을 수 있습니다. 충돌을 제거했지만 여전히 방향 변경 이전의 상태로 UI를 안정적인 방식으로 복원하는 방법을 찾지 못했습니다. 그러나 당신의 대답은 저를 앞으로 나아갔습니다.
Heikki Toivonen

4
방향이 변경되면 활동에서 onStart를 가져와야합니다. 기본적으로 이전 데이터를 사용하여보기를 재구성해야합니다. 따라서 새로운 'onStart'를 얻을 때 진행률 표시 줄에서 숫자 상태 udpates를 요청하고 새보기를 다시 작성하는 것이 좋습니다. 새로운 활동을 얻었을 때 기억이 나지 않지만 문서를 통한 일부 사냥이 도움이 될 것입니다.
haseman

6
최근에 게임을 해본 결과 앱 방향이 바뀌면 새로운 활동을 얻는다는 사실을 전할 수 있습니다. (또한 새로운보기를 얻습니다) 이전보기를 업데이트하려고하면 이전보기에 잘못된 응용 프로그램 컨텍스트 (이전 활동)가 있기 때문에 예외가 발생합니다. myActivity.getApplicationContext ()를 전달 하여이 문제를 해결할 수 있습니다 활동 자체에 대한 포인터 대신.
haseman

1
누군가가 이런 맥락에서 휘발성의 사용 / 이점을 설명 할 수 있습니까
Jawad Zeb

2
@ Nepster 그래, 나도 그것에 대해 궁금했다. 누군가가 휘발성에 대해 설명하면 좋을 것입니다.
RestInPeace

261

편집 : Google 엔지니어는 이 StackOverflow 게시물 에서 Dianne Hackborn (일명 hackbod )에 설명 된 대로이 방법을 권장하지 않습니다 . 체크 아웃 이 블로그 게시물을 자세한 내용은.


매니페스트의 활동 선언에 이것을 추가해야합니다.

android:configChanges="orientation|screenSize"

그래서 그것은 보인다

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

중요한 것은 구성 변경이 발생하면 시스템이 활동을 파괴한다는 것입니다. 구성 변경을 참조하십시오 .

따라서 구성 파일에 넣으면 시스템이 활동을 파괴하지 않습니다. 대신 onConfigurationChanged(Configuration)메소드를 호출합니다 .


21
이것은 확실히 최고의 솔루션입니다. 레이아웃을 회전시키기 때문에 (처음에 예상되는 동작) 가로 방향 키보드가있는 전화기 때문에 android : configChanges = "orientation | keyboardHidden"을 입력하십시오.
nikib3ro

24
이것은 내가 기대하는 행동 인 것 같습니다. 그러나이 문서는 "레이아웃 파일을 포함한 모든 응용 프로그램 자원이 모든 구성 값에 따라 변경 될 수 있기 때문에 활동이 파괴되었음을 나타냅니다. 따라서 구성 변경을 처리하는 유일한 안전한 방법은 모든 자원을 다시 검색하는 것입니다." 그리고 이외에도 orientation구성을 변경 해야하는 더 많은 이유가 있습니다. keyboardHidden(위키 답변을 이미 편집했습니다.) uiMode(예를 들어 자동차 모드로 들어가거나 나가기; 야간 모드 변경) 등이 있습니다. 좋은 대답입니다.
rds

116
이것은 수용 가능한 해결책이 아닙니다. 그것은 단지 진정한 문제를 숨 깁니다.
rf43

18
작동하지만 Google에서는 권장하지 않습니다.
Ed Burnette

21
이 방법을 따르지 마십시오. DDosAttack이 완전히 맞습니다. 다운로드 또는 시간이 오래 걸리는 작업에 대한 진행률 대화 상자를 작성한다고 가정하십시오. 사용자는 해당 활동을 유지하지 않고 응시하지 않습니다. 홈 화면이나 게임 또는 전화 통화와 같은 다른 앱으로 전환하거나 리소스가 배가 고파서 활동이 중단 될 수 있습니다. 그러면 무엇? 당신은 깔끔한 작은 트릭으로 해결되지 않은 같은 오래된 문제에 직면하고 있습니다. 사용자가 다시 방문하면 활동이 다시 생성됩니다.
tiguchi

68

나는 'Android Way'의 것들과 일치하는 이러한 문제에 대한 견고한 솔루션을 생각해 냈습니다. IntentService 패턴을 사용하여 장기 실행 작업을 모두 수행했습니다.

즉, 내 활동은 의도를 방송하고, IntentService는 작업을 수행하고, 데이터를 DB에 저장 한 다음 고정 의도 를 방송 합니다. 고정 부분은 사용자가 작업을 시작한 후 일정 시간 동안 활동이 일시 중지되어 IntentService에서 실시간 브로드 캐스트를 놓친 경우에도 응답하고 호출 활동에서 데이터를 가져올 수 있도록 중요합니다. ProgressDialog는이 패턴으로 꽤 잘 작동 할 수 있습니다 onSaveInstanceState().

기본적으로 저장된 인스턴스 번들에서 진행률 대화 상자가 실행 중임을 나타내는 플래그를 저장해야합니다. 진행률 대화 상자 개체를 저장하면 전체 활동이 누출되므로 저장 하지 마십시오 . 진행률 대화 상자를 지속적으로 처리하기 위해 응용 프로그램 객체에 약한 참조로 저장합니다. 오리엔테이션 변경 또는 활동이 일시 중지되게하는 (전화 통화, 사용자가 집을 때리는 등) 그 다음에 이전 대화 상자를 닫고 새로 작성된 활동에서 새 대화 상자를 다시 작성합니다.

무한 진행 대화 상자의 경우 이것은 쉽습니다. 진행률 표시 줄 스타일의 경우 번들에 마지막으로 알려진 진행률과 진행 상황을 추적하기 위해 활동에서 로컬로 사용중인 모든 정보를 넣어야합니다. 진행률을 복원 할 때이 정보를 사용하여 이전과 동일한 상태로 진행률 표시 줄을 다시 생성 한 다음 현재 상태를 기반으로 업데이트합니다.

요약하자면, 장기 실행 작업을 신중하게 사용하여 IntentService에 넣으면 onSaveInstanceState()대화를 효율적으로 추적하고 활동 라이프 사이클 이벤트를 통해 복원 할 수 있습니다. 활동 코드의 관련 비트는 다음과 같습니다. Sticky 인 텐트를 적절하게 처리하려면 BroadcastReceiver에 로직이 필요하지만이 범위를 벗어납니다.

public void doSignIn(View view) {
    waiting=true;
    AppClass app=(AppClass) getApplication();
    String logingon=getString(R.string.signon);
    app.Dialog=new WeakReference<ProgressDialog>(ProgressDialog.show(AddAccount.this, "", logingon, true));
    ...
}

@Override
protected void onSaveInstanceState(Bundle saveState) {
    super.onSaveInstanceState(saveState);
    saveState.putBoolean("waiting",waiting);
}

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if(savedInstanceState!=null) {
        restoreProgress(savedInstanceState);    
    }
    ...
}

private void restoreProgress(Bundle savedInstanceState) {
    waiting=savedInstanceState.getBoolean("waiting");
    if (waiting) {
        AppClass app=(AppClass) getApplication();
        ProgressDialog refresher=(ProgressDialog) app.Dialog.get();
        refresher.dismiss();
        String logingon=getString(R.string.signon);
        app.Dialog=new WeakReference<ProgressDialog>(ProgressDialog.show(AddAccount.this, "", logingon, true));
    }
}

좋은 해결책으로 보인다
Derekyy

"IntentService 패턴을 사용하는 장기 실행 작업이 모두 있습니다." 더 당신이 볼 수에 대한, 참새와 많은 상용구 코드로 대포 밖으로 촬영하는 것처럼 때문 완벽한 해결책이 아니다 youtube.com/watch?v=NJsq0TU0qeg
카밀 Nekanowicz

28

나는 같은 문제를 만났다. 내 활동은 URL에서 일부 데이터를 구문 분석해야하며 속도가 느립니다. 그래서 스레드를 만들고 진행률 대화 상자를 표시합니다. 스레드 Handler가 끝나면 UI 스레드에 메시지를 다시 게시하도록 했습니다. 에Handler.handleMessage , 나는 스레드에서 데이터 오브젝트 (지금 준비)를 얻을 UI로 채 웁니다. 여러분의 예제와 매우 비슷합니다.

많은 시행 착오 끝에 해결책을 찾은 것처럼 보입니다. 적어도 지금은 스레드가 완료되기 전이나 후에 언제든지 화면을 회전시킬 수 있습니다. 모든 테스트에서 대화 상자가 올바르게 닫히고 모든 동작이 예상대로 수행됩니다.

내가 한 일은 아래에 나와 있습니다. 목표는 내 데이터 모델 ( mDataObject)을 채우고 UI에 채우는 것입니다. 놀랍게도 언제든지 화면 회전을 허용해야합니다.

class MyActivity {

    private MyDataObject mDataObject = null;
    private static MyThread mParserThread = null; // static, or make it singleton

    OnCreate() {
        ...
        Object retained = this.getLastNonConfigurationInstance();
        if(retained != null) {
            // data is already completely obtained before config change
            // by my previous self.
            // no need to create thread or show dialog at all
            mDataObject = (MyDataObject) retained;
            populateUI();
        } else if(mParserThread != null && mParserThread.isAlive()){
            // note: mParserThread is a static member or singleton object.
            // config changed during parsing in previous instance. swap handler
            // then wait for it to finish.
            mParserThread.setHandler(new MyHandler());
        } else {
            // no data and no thread. likely initial run
            // create thread, show dialog
            mParserThread = new MyThread(..., new MyHandler());
            mParserThread.start();
            showDialog(DIALOG_PROGRESS);
        }
    }

    // http://android-developers.blogspot.com/2009/02/faster-screen-orientation-change.html
    public Object onRetainNonConfigurationInstance() {
        // my future self can get this without re-downloading
        // if it's already ready.
        return mDataObject;
    }

    // use Activity.showDialog instead of ProgressDialog.show
    // so the dialog can be automatically managed across config change
    @Override
    protected Dialog onCreateDialog(int id) {
        // show progress dialog here
    }

    // inner class of MyActivity
    private class MyHandler extends Handler {
        public void handleMessage(msg) {
            mDataObject = mParserThread.getDataObject();
            populateUI();
            dismissDialog(DIALOG_PROGRESS);
        }
    }
}

class MyThread extends Thread {
    Handler mHandler;
    MyDataObject mDataObject;

    // constructor with handler param
    public MyHandler(..., Handler h) {
        ...
        mHandler = h;
    }

    public void setHandler(Handler h) { mHandler = h; } // for handler swapping after config change
    public MyDataObject getDataObject() { return mDataObject; } // return data object (completed) to caller

    public void run() {
        mDataObject = new MyDataObject();
        // do the lengthy task to fill mDataObject with data
        lengthyTask(mDataObject);
        // done. notify activity
        mHandler.sendEmptyMessage(0); // tell activity: i'm ready. come pick up the data.
    }
}

그것이 저에게 효과적입니다. 이것이 안드로이드가 디자인 한 "올바른"방법인지는 모르겠습니다.이 "스크린 회전 중 활동을 파괴 / 재 생성"하면 실제로 일이 더 쉬워 진다고 생각하므로 너무 까다로워서는 안된다고 생각합니다.

내 코드에 문제가 있으면 알려주십시오. 위에서 말했듯이 부작용이 있는지 실제로 알지 못합니다.


1
고마워요! 받는 힌트 onRetainNonConfigurationInstance()getLastNonConfigurationInstance()내 문제를 해결하는 데 도움을 주었다. 엄지 손가락!
스벤

15

원래 인식 된 문제는 코드가 화면 방향 변경에서 살아남지 못한다는 것입니다. 분명히 이것은 UI 프레임 워크에서 (onDestroy 호출을 통해) 대신 화면 방향 변경 자체를 프로그램이 처리하도록하여 "해결되었습니다".

기본 문제가 프로그램이 onDestroy ()에서 살아남지 못한다는 사실을 받아 들인다면 받아 들여진 해결책은 프로그램에 심각한 다른 문제와 취약점을 남기는 해결 방법 일뿐입니다. Android 프레임 워크는 구체적으로 통제 할 수없는 상황으로 인해 언제든지 활동이 거의 파괴 될 위험이 있음을 명시합니다. 따라서 활동은 화면 방향 변경 만이 아니라 어떤 이유로 든 onDestroy () 및 후속 onCreate ()에서 살아남을 수 있어야합니다.

OP의 문제를 해결하기 위해 화면 방향 변경 처리를 수락하려는 경우 onDestroy ()의 다른 원인으로 인해 동일한 오류가 발생하지 않는지 확인해야합니다. 당신은 이것을 할 수 있습니까? 그렇지 않다면, "허용 된"답변이 정말 좋은 답변인지 질문 할 것입니다.


14

내 솔루션은 ProgressDialog클래스 를 확장하여 내 자신의 것을 얻는 것이 었습니다 MyProgressDialog. 방향을 표시하기 전에 방향을 잠그고 다시 닫을 때 다시 잠금을 해제하는 방법 과 방법을
재정의 했습니다 . 그래서이 때, 도시 및 장치 변화의 방향이 화면의 나머지의 방향까지되고show()dismiss()DialogDialogDialogdismiss() 호출 되고 센서 값 / 장치 방향에 따라 화면 방향이 변경됩니다.

내 코드는 다음과 같습니다.

public class MyProgressDialog extends ProgressDialog {
private Context mContext;

public MyProgressDialog(Context context) {
    super(context);
    mContext = context;
}

public MyProgressDialog(Context context, int theme) {
    super(context, theme);
    mContext = context;
}

public void show() {
    if (mContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT)
        ((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    else
        ((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    super.show();
}

public void dismiss() {
    super.dismiss();
    ((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
}

}

8

나는이 같은 문제에 직면하여 ProgressDialog를 사용하지 않고 더 빠른 결과를 얻는 솔루션을 생각해 냈습니다.

내가 한 것은 ProgressBar가 포함 된 레이아웃을 만드는 것입니다.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ProgressBar
    android:id="@+id/progressImage"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    />
</RelativeLayout>

그런 다음 onCreate 메소드에서 다음을 수행하십시오.

public void onCreate(Bundle icicle) {
    super.onCreate(icicle);
    setContentView(R.layout.progress);
}

그런 다음 스레드에서 긴 작업을 수행하고 완료되면 Runnable이 컨텐츠보기를이 활동에 사용하려는 실제 레이아웃으로 설정하십시오.

예를 들면 다음과 같습니다.

mHandler.post(new Runnable(){

public void run() {
        setContentView(R.layout.my_layout);
    } 
});

이것이 내가 한 일이며, ProgressDialog를 표시하는 것보다 빠르게 실행되며 덜 방해 적이며 내 의견으로는 더 잘 보입니다.

그러나 ProgressDialog를 사용하려면이 대답이 적합하지 않습니다.


이 솔루션은 간단한 사용 사례에서는 우아하지만 단점이 있습니다. 전체 컨텐츠보기를 다시 빌드해야합니다. setContentView(R.layout.my_layout);충분하지 않다; 모든 리스너를 설정하고 데이터를 재설정해야합니다.
rds

@ rds 당신이 맞아요. 이것은 실제로 간단한 경우에 대한 해결책이거나, 뷰를 표시하기 전에 onCreate 메소드에서 약간의 리프팅을 수행해야하는 경우입니다.
Pzanno

나는 그것을 얻지 못한다. onCreate ()에서 리스너를 설정하는 대신 일반적으로하는 것처럼 run ()에서 리스너를 설정할 수 있습니다. 여기에 뭔가 빠졌습니까?
Code Poet

7

나는 아직 다른 곳에서는 보지 못한 해결책을 발견했습니다. 방향 변경시 파괴되고 다시 생성되는 활동에서 백그라운드 작업을 수행하는 대신 백그라운드 작업이 있는지 알고있는 사용자 지정 응용 프로그램 개체를 사용할 수 있습니다. 나는 이것에 대해 여기에 블로그 했다 .


1
사용자 정의 작성 Application은 일반적으로 글로벌 애플리케이션 상태를 유지하는 데 사용됩니다. 나는 그것이 효과가 없다고 말하지는 않지만 지나치게 복잡해 보입니다. 문서에서 "일반적으로 응용 프로그램을 서브 클래스 할 필요가 없습니다." 나는 sonxurxo의 대답을 주로 선호합니다.
rds

7

이 로테이션 문제를 처리하는 데 도움이 될 것입니다. 그가 사용하지 않기 때문에 이것은 OP와 관련이 없을 수 있습니다AsyncTask 있지만 다른 사람들이 유용 할 수도 있습니다. 꽤 간단하지만 나를 위해 일하는 것 같습니다.

AsyncTask라는 중첩 클래스 가있는 로그인 활동이 BackgroundLoginTask있습니다.

내에서 BackgroundLoginTask나는 호출을 해제 ProgressDialog할 때 null 검사를 추가하는 것을 제외하고는 평범하지 않은 일을하지 않습니다 .

@Override
protected void onPostExecute(Boolean result)
{    
if (pleaseWaitDialog != null)
            pleaseWaitDialog.dismiss();
[...]
}

백그라운드 작업이 완료된 상태에서 작업이 완료 Activity되어 진행률 대화 상자가 이미 onPause()메서드에 의해 해제 된 경우를 처리하기위한 것입니다.

다음으로 부모 Activity클래스에서 클래스에 대한 전역 정적 핸들을 만들고 AsyncTaskProgressDialog( AsyncTask중첩 된이 변수에 액세스 할 수 있음) :

private static BackgroundLoginTask backgroundLoginTask;
private static ProgressDialog pleaseWaitDialog;

이것은 두 가지 목적을 제공합니다. 첫째, 새로운 회전 후 활동에서도 Activity항상 AsyncTask객체에 액세스 할 수 있습니다 . 둘째, 그것은 내가 BackgroundLoginTask액세스하고 해제 할 수 있습니다ProgressDialog 회전 후에도 .

다음으로 이것을 추가 하여 전경을 떠날 onPause()때 진행 대화 상자가 사라지게 Activity합니다 (추악한 "강제 종료"충돌 방지).

    if (pleaseWaitDialog != null)
    pleaseWaitDialog.dismiss();

마지막으로 내 onResume()방법 에는 다음이 있습니다 .

if ((backgroundLoginTask != null) && (backgroundLoginTask.getStatus() == Status.RUNNING))
        {
           if (pleaseWaitDialog != null)
             pleaseWaitDialog.show();
        }

이렇게 Dialog하면가 다시 생성 된 후 Activity가 다시 나타납니다 .

전체 수업은 다음과 같습니다.

public class NSFkioskLoginActivity extends NSFkioskBaseActivity {
    private static BackgroundLoginTask backgroundLoginTask;
    private static ProgressDialog pleaseWaitDialog;
    private Controller cont;

    // This is the app entry point.
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (CredentialsAvailableAndValidated())
        {
        //Go to main menu and don't run rest of onCreate method.
            gotoMainMenu();
            return;
        }
        setContentView(R.layout.login);
        populateStoredCredentials();   
    }

    //Save current progress to options when app is leaving foreground
    @Override
    public void onPause()
    {
        super.onPause();
        saveCredentialsToPreferences(false);
        //Get rid of progress dialog in the event of a screen rotation. Prevents a crash.
        if (pleaseWaitDialog != null)
        pleaseWaitDialog.dismiss();
    }

    @Override
    public void onResume()
    {
        super.onResume();
        if ((backgroundLoginTask != null) && (backgroundLoginTask.getStatus() == Status.RUNNING))
        {
           if (pleaseWaitDialog != null)
             pleaseWaitDialog.show();
        }
    }

    /**
     * Go to main menu, finishing this activity
     */
    private void gotoMainMenu()
    {
        startActivity(new Intent(getApplicationContext(), NSFkioskMainMenuActivity.class));
        finish();
    }

    /**
     * 
     * @param setValidatedBooleanTrue If set true, method will set CREDS_HAVE_BEEN_VALIDATED to true in addition to saving username/password.
     */
    private void saveCredentialsToPreferences(boolean setValidatedBooleanTrue)
    {
        SharedPreferences settings = getSharedPreferences(APP_PREFERENCES, MODE_PRIVATE);
        SharedPreferences.Editor prefEditor = settings.edit();
        EditText usernameText = (EditText) findViewById(R.id.editTextUsername);
        EditText pswText = (EditText) findViewById(R.id.editTextPassword);
        prefEditor.putString(USERNAME, usernameText.getText().toString());
        prefEditor.putString(PASSWORD, pswText.getText().toString());
        if (setValidatedBooleanTrue)
        prefEditor.putBoolean(CREDS_HAVE_BEEN_VALIDATED, true);
        prefEditor.commit();
    }

    /**
     * Checks if user is already signed in
     */
    private boolean CredentialsAvailableAndValidated() {
        SharedPreferences settings = getSharedPreferences(APP_PREFERENCES,
                MODE_PRIVATE);
        if (settings.contains(USERNAME) && settings.contains(PASSWORD) && settings.getBoolean(CREDS_HAVE_BEEN_VALIDATED, false) == true)
         return true;   
        else
        return false;
    }

    //Populate stored credentials, if any available
    private void populateStoredCredentials()
    {
        SharedPreferences settings = getSharedPreferences(APP_PREFERENCES,
            MODE_PRIVATE);
        settings.getString(USERNAME, "");
       EditText usernameText = (EditText) findViewById(R.id.editTextUsername);
       usernameText.setText(settings.getString(USERNAME, ""));
       EditText pswText = (EditText) findViewById(R.id.editTextPassword);
       pswText.setText(settings.getString(PASSWORD, ""));
    }

    /**
     * Validate credentials in a seperate thread, displaying a progress circle in the meantime
     * If successful, save credentials in preferences and proceed to main menu activity
     * If not, display an error message
     */
    public void loginButtonClick(View view)
    {
        if (phoneIsOnline())
        {
        EditText usernameText = (EditText) findViewById(R.id.editTextUsername);
        EditText pswText = (EditText) findViewById(R.id.editTextPassword);
           //Call background task worker with username and password params
           backgroundLoginTask = new BackgroundLoginTask();
           backgroundLoginTask.execute(usernameText.getText().toString(), pswText.getText().toString());
        }
        else
        {
        //Display toast informing of no internet access
        String notOnlineMessage = getResources().getString(R.string.noNetworkAccessAvailable);
        Toast toast = Toast.makeText(getApplicationContext(), notOnlineMessage, Toast.LENGTH_SHORT);
        toast.show();
        }
    }

    /**
     * 
     * Takes two params: username and password
     *
     */
    public class BackgroundLoginTask extends AsyncTask<Object, String, Boolean>
    {       
       private Exception e = null;

       @Override
       protected void onPreExecute()
       {
           cont = Controller.getInstance();
           //Show progress dialog
           String pleaseWait = getResources().getString(R.string.pleaseWait);
           String commWithServer = getResources().getString(R.string.communicatingWithServer);
            if (pleaseWaitDialog == null)
              pleaseWaitDialog= ProgressDialog.show(NSFkioskLoginActivity.this, pleaseWait, commWithServer, true);

       }

        @Override
        protected Boolean doInBackground(Object... params)
        {
        try {
            //Returns true if credentials were valid. False if not. Exception if server could not be reached.
            return cont.validateCredentials((String)params[0], (String)params[1]);
        } catch (Exception e) {
            this.e=e;
            return false;
        }
        }

        /**
         * result is passed from doInBackground. Indicates whether credentials were validated.
         */
        @Override
        protected void onPostExecute(Boolean result)
        {
        //Hide progress dialog and handle exceptions
        //Progress dialog may be null if rotation has been switched
        if (pleaseWaitDialog != null)
             {
            pleaseWaitDialog.dismiss();
                pleaseWaitDialog = null;
             }

        if (e != null)
        {
         //Show toast with exception text
                String networkError = getResources().getString(R.string.serverErrorException);
                Toast toast = Toast.makeText(getApplicationContext(), networkError, Toast.LENGTH_SHORT);
            toast.show();
        }
        else
        {
            if (result == true)
            {
            saveCredentialsToPreferences(true);
            gotoMainMenu();
            }
            else
            {
            String toastText = getResources().getString(R.string.invalidCredentialsEntered);
                Toast toast = Toast.makeText(getApplicationContext(), toastText, Toast.LENGTH_SHORT);
            toast.show();
            } 
        }
        }

    }
}

나는 노련한 안드로이드 개발자가 아니므로 자유롭게 의견을 말하십시오.


1
흥미 롭습니다! 특히 AsyncTask를 사용하는 사람들에게는 더욱 그렇습니다. 방금 솔루션을 시험해 보았는데 대부분 효과가있는 것 같습니다. 한 가지 문제가 있습니다. ProgressDialog가 여전히 활성화되어있는 동안 ProgressDialog는 회전 후 약간 일찍 종료되는 것 같습니다. 나는 무슨 일이 일어나고 있고 어떻게 해결해야하는지 놀러 다닐 것입니다. 하지만 더 이상 그런 충돌이 일어나지 않습니다!
Scott Biggs

1
수정 사항을 찾았습니다. 여기서 문제는 정적 ProgressDialog입니다. 회전이 ProgressDialog를 중단하면 때로는 새 활동에서 다시 시작한 후 호출 된 .dismiss () 메서드를 가져옵니다. 각 Activity로 ProgressDialog를 만들면이 새로운 ProgressDialog가 이전 Activity와 함께 종료되지 않습니다. 또한 (가비지 수집을 돕기 위해) ProgressDialog가 닫힐 때마다 null로 설정되어 있는지 확인했습니다. 여기에 해결책이 있습니다! AsyncTask를 사용하는 사람들을 응원합니다!
Scott Biggs

4

긴 작업을 별도의 클래스로 이동하십시오. 피사체 관찰자 패턴으로 구현하십시오. 활동이 작성 될 때마다 등록하고 닫는 동안 태스크 클래스에 등록 취소하십시오. 작업 클래스는 AsyncTask를 사용할 수 있습니다.


1
어떻게 도움이 될지 모르겠습니다. 이것이 내가보고있는 문제를 어떻게 방지하는지 자세히 설명해 주시겠습니까?
Heikki Toivonen

1
Haseman이 말했듯이 백엔드가 UI 요소에 액세스하는 것을 방지하고 UI를 백엔드에서 분리 할 수 ​​있으며 백엔드는 별도 스레드에서 실행되며 화면 방향이 변경되고 상태 업데이트를 위해 백엔드 작업에 등록-등록 취소 후에도 계속 실행됩니다. . 내가 이것을 사용하여 해결 한 실제 예는 다운로드 작업이 있고 스레드가 생성 될 때마다 별도의 스레드로 이동했다는 것입니다.
Vinay

좋아, 나는이 문제를 재검토하고 있으며 여전히이 대답을 완전히 이해하지 못한다고 생각합니다. 화면 방향 변경 중에 중단하고 싶지 않은 장기 실행 네트워크 작업을 수행하기 위해 AsyncTask를 시작하는 주요 활동이 있다고 가정하십시오. 새 활동이 이전 활동으로 시작된 AsyncTask에 메시지를 보내는 방법을 알 수 없습니다. 코드 예제를 줄 수 있습니까?
Heikki Toivonen

@ Heikki, 내 구현이 의미하는 바입니까?
beetstra

4

트릭은 평상시와 같이 onPreExecute / onPostExecute 동안 AsyncTask 내에서 대화 상자를 표시 / 해제하는 것이지만 방향 변경의 경우 활동에서 대화 상자의 새 인스턴스를 작성 / 표시하고 해당 참조를 작업에 전달합니다.

public class MainActivity extends Activity {
    private Button mButton;
    private MyTask mTask = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        MyTask task = (MyTask) getLastNonConfigurationInstance();
        if(task != null){
            mTask = task;
            mTask.mContext = this;
            mTask.mDialog = ProgressDialog.show(this, "", "", true);        
        }

        mButton = (Button) findViewById(R.id.button1);
        mButton.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v){
                mTask = new MyTask(MainActivity.this);
                mTask.execute();
            }
        });
    }


    @Override
    public Object onRetainNonConfigurationInstance() {
        String str = "null";
        if(mTask != null){
            str = mTask.toString();
            mTask.mDialog.dismiss();
        }
        Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
        return mTask;
    }



    private class MyTask extends AsyncTask<Void, Void, Void>{
        private ProgressDialog mDialog;
        private MainActivity mContext;


        public MyTask(MainActivity context){
            super();
            mContext = context;
        }


        protected void onPreExecute() {
            mDialog = ProgressDialog.show(MainActivity.this, "", "", true);
        }

        protected void onPostExecute(Void result) {
            mContext.mTask = null;
            mDialog.dismiss();
        }


        @Override
        protected Void doInBackground(Void... params) {
            SystemClock.sleep(5000);
            return null;
        }       
    }
}

4

나는 이렇게했다 :

    package com.palewar;
    import android.app.Activity;
    import android.app.ProgressDialog;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;

    public class ThreadActivity extends Activity {


        static ProgressDialog dialog;
        private Thread downloadThread;
        final static Handler handler = new Handler() {

            @Override
            public void handleMessage(Message msg) {

                super.handleMessage(msg);

                dialog.dismiss();

            }

        };

        protected void onDestroy() {
    super.onDestroy();
            if (dialog != null && dialog.isShowing()) {
                dialog.dismiss();
                dialog = null;
            }

        }

        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);

            downloadThread = (Thread) getLastNonConfigurationInstance();
            if (downloadThread != null && downloadThread.isAlive()) {
                dialog = ProgressDialog.show(ThreadActivity.this, "",
                        "Signing in...", false);
            }

            dialog = ProgressDialog.show(ThreadActivity.this, "",
                    "Signing in ...", false);

            downloadThread = new MyThread();
            downloadThread.start();
            // processThread();
        }

        // Save the thread
        @Override
        public Object onRetainNonConfigurationInstance() {
            return downloadThread;
        }


        static public class MyThread extends Thread {
            @Override
            public void run() {

                try {
                    // Simulate a slow network
                    try {
                        new Thread().sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    handler.sendEmptyMessage(0);

                } finally {

                }
            }
        }

    }

시도해보고 그것이 당신에게 효과가 있는지 알려주세요.


onDestroy 코드는 개발자 페이지 에서 전혀 실행되지 않을 수 있습니다 . "위의 표에서"Killable "열에 유의
ilomambo

나는 확고하게 그 "onRetainNonConfigurationInstance ()는"이러한 경우에 사용하는 방법 ... 뉴욕의 작업이라고 생각
니틴 반살

Sachin Gurnani는 정적 선언을 사용하여 문제를 해결하므로 비슷한 문제에 직면하고 있습니다. stackoverflow.com/questions/12058774/…
Steven Du

2

당신은 배경 작성하는 경우 Service(비 정렬 화, TCP 요청 / 응답) 모든 무거운 리프팅을 수행의 ViewActivity창을 유출이나 데이터 손실없이 파괴되고 다시 생성 할 수 있습니다. 이를 통해 각 구성 변경 (예 : 각 방향 변경) 에 대한 활동삭제하는 Android 권장 동작이 허용 됩니다.

좀 더 복잡하지만 서버 요청, 데이터 사전 / 사후 처리 등을 호출하는 가장 좋은 방법입니다.

을 사용하여 Service각 요청을 서버에 대기 시킬 수도 있으므로 이러한 작업을 쉽고 효율적으로 처리 할 수 ​​있습니다.

개발자 안내서에는에 대한 전체 장이 있습니다Services .


A Service는 것보다 많은 작업 AsyncTask이지만 일부 상황에서는 더 나은 접근 방법이 될 수 있습니다. 반드시 더 좋은 것은 아닙니다. 그렇지 않습니까? 즉, 이것이 ProgressDialogmain에서 유출 된 문제를 어떻게 해결하는지 이해하지 못합니다 Activity. 어디에서 인스 턴싱 ProgressDialog합니까? 어디에서 닫습니까?
rds

2

화면 방향 변경시 활동을 파괴 할 수는 있지만 여전히 재 작성 된 활동의 대화 상자를 성공적으로 파괴하는 구현이 있습니다. 나는 사용한다...NonConfigurationInstance 재생성 활동에 백그라운드 작업을 첨부 할 수 있습니다. 일반적인 Android 프레임 워크는 대화 상자 자체의 재 작성을 처리하며 아무것도 변경되지 않습니다.

'소유'활동에 대한 필드를 추가하고이 소유자를 업데이트하는 메소드를 추가하여 AsyncTask를 서브 클래 싱했습니다.

class MyBackgroundTask extends AsyncTask<...> {
  MyBackgroundTask (Activity a, ...) {
    super();
    this.ownerActivity = a;
  }

  public void attach(Activity a) {
    ownerActivity = a;
  }

  protected void onPostExecute(Integer result) {
    super.onPostExecute(result);
    ownerActivity.dismissDialog(DIALOG_PROGRESS);
  }

  ...
}

내 활동 클래스에서 나는 필드가 추가 backgroundTask'소유'backgroundtask를 참조하고, 내가 사용이 필드를 업데이트 onRetainNonConfigurationInstance하고 getLastNonConfigurationInstance.

class MyActivity extends Activity {
  public void onCreate(Bundle savedInstanceState) {
    ...
    if (getLastNonConfigurationInstance() != null) {
      backgroundTask = (MyBackgroundTask) getLastNonConfigurationInstance();
      backgroundTask.attach(this);
    }
  }

  void startBackgroundTask() {
    backgroundTask = new MyBackgroundTask(this, ...);
    showDialog(DIALOG_PROGRESS);
    backgroundTask.execute(...);
  }

  public Object onRetainNonConfigurationInstance() {
    if (backgroundTask != null && backgroundTask.getStatus() != Status.FINISHED)
      return backgroundTask;
    return null;
  }
  ...
}

추가 개선을위한 제안 :

  • backgroundTask작업이 완료된 후 활동 에서 참조를 지우면 연관된 메모리 또는 기타 자원이 해제됩니다.
  • 클리어 ownerActivity활동이 즉시 재 작성되지 않을 경우 활동이 파괴되기 전에 백그라운드 참조를 지우십시오.
  • BackgroundTask동일한 소유 활동에서 다른 유형의 작업을 실행할 수 있도록 인터페이스 및 / 또는 컬렉션을 만듭니다 .

2

두 개의 레이아웃을 유지하면 모든 UI 스레드가 종료되어야합니다.

AsynTask를 사용하면 현재 활동의 .cancel()메소드 내부에서 onDestroy()메소드를 쉽게 호출 할 수 있습니다 .

@Override
protected void onDestroy (){
    removeDialog(DIALOG_LOGIN_ID); // remove loading dialog
    if (loginTask != null){
        if (loginTask.getStatus() != AsyncTask.Status.FINISHED)
            loginTask.cancel(true); //cancel AsyncTask
    }
    super.onDestroy();
}

AsyncTask에 대해서는 여기의 "작업 취소"섹션에서 자세한 내용을 읽으 십시오 .

업데이트 : 실행 상태에있는 경우에만 취소 할 수 있으므로 상태를 확인하기위한 조건이 추가되었습니다. 또한 AsyncTask는 한 번만 실행할 수 있습니다.


2

jfelectron 의 솔루션 은 "Android Way"에 부합 하는 " 이러한 문제에 대한 견고한 솔루션 "이기 때문에 구현하려고 시도했습니다. 했지만 언급 된 모든 요소를 ​​찾고 정리하는 데 시간이 걸렸습니다. 이것으로 약간 다른 결과가 나왔고 더 우아한 솔루션이 여기에 게시되었습니다.

활동에서 시작된 IntentService를 사용하여 별도의 스레드에서 장기 실행 태스크를 수행합니다. 이 서비스는 대화를 업데이트하는 활동에 고정 브로드 캐스트 의도를 다시 발생시킵니다. 활동은 showDialog (), onCreateDialog () 및 onPrepareDialog ()를 사용하여 애플리케이션 오브젝트 또는 savedInstanceState 번들에 지속적 데이터를 전달할 필요가 없습니다. 응용 프로그램이 중단 된 방식에 관계없이 작동합니다.

활동 클래스 :

public class TesterActivity extends Activity {
private ProgressDialog mProgressDialog;
private static final int PROGRESS_DIALOG = 0;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    Button b = (Button) this.findViewById(R.id.test_button);
    b.setOnClickListener(new OnClickListener() {
        public void onClick(View v) {
            buttonClick();
        }
    });
}

private void buttonClick(){
    clearPriorBroadcast();
    showDialog(PROGRESS_DIALOG);
    Intent svc = new Intent(this, MyService.class);
    startService(svc);
}

protected Dialog onCreateDialog(int id) {
    switch(id) {
    case PROGRESS_DIALOG:
        mProgressDialog = new ProgressDialog(TesterActivity.this);
        mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        mProgressDialog.setMax(MyService.MAX_COUNTER);
        mProgressDialog.setMessage("Processing...");
        return mProgressDialog;
    default:
        return null;
    }
}

@Override
protected void onPrepareDialog(int id, Dialog dialog) {
    switch(id) {
    case PROGRESS_DIALOG:
        // setup a broadcast receiver to receive update events from the long running process
        IntentFilter filter = new IntentFilter();
        filter.addAction(MyService.BG_PROCESS_INTENT);
        registerReceiver(new MyBroadcastReceiver(), filter);
        break;
    }
}

public class MyBroadcastReceiver extends BroadcastReceiver{
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.hasExtra(MyService.KEY_COUNTER)){
            int count = intent.getIntExtra(MyService.KEY_COUNTER, 0);
            mProgressDialog.setProgress(count);
            if (count >= MyService.MAX_COUNTER){
                dismissDialog(PROGRESS_DIALOG);
            }
        }
    }
}

/*
 * Sticky broadcasts persist and any prior broadcast will trigger in the 
 * broadcast receiver as soon as it is registered.
 * To clear any prior broadcast this code sends a blank broadcast to clear 
 * the last sticky broadcast.
 * This broadcast has no extras it will be ignored in the broadcast receiver 
 * setup in onPrepareDialog()
 */
private void clearPriorBroadcast(){
    Intent broadcastIntent = new Intent();
    broadcastIntent.setAction(MyService.BG_PROCESS_INTENT);
    sendStickyBroadcast(broadcastIntent);
}}

IntentService 클래스 :

public class MyService extends IntentService {

public static final String BG_PROCESS_INTENT = "com.mindspiker.Tester.MyService.TEST";
public static final String KEY_COUNTER = "counter";
public static final int MAX_COUNTER = 100;

public MyService() {
  super("");
}

@Override
protected void onHandleIntent(Intent intent) {
    for (int i = 0; i <= MAX_COUNTER; i++) {
        Log.e("Service Example", " " + i);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Intent broadcastIntent = new Intent();
        broadcastIntent.setAction(BG_PROCESS_INTENT);
        broadcastIntent.putExtra(KEY_COUNTER, i);
        sendStickyBroadcast(broadcastIntent);
    }
}}

매니페스트 파일 항목 :

신청 전 섹션 :

uses-permission android:name="com.mindspiker.Tester.MyService.TEST"
uses-permission android:name="android.permission.BROADCAST_STICKY"

내부 신청 섹션

service android:name=".MyService"

2

이것이 내가 제안한 해결책입니다.

  • 여기에 설명 된대로 AsyncTask 또는 스레드를 보유 된 조각으로 이동 하십시오 . 모든 네트워크 호출을 조각으로 옮기는 것이 좋습니다. 이미 프래그먼트를 사용하고 있다면 그 중 하나가 통화를 담당 할 수 있습니다. 그렇지 않으면 링크 된 기사에서 제안한대로 요청을 수행하기위한 프래그먼트를 만들 수 있습니다.
  • 프래그먼트는 리스너 인터페이스를 사용하여 작업 완료 / 실패를 알립니다. 방향 변경에 대해 걱정할 필요가 없습니다. 프래그먼트에는 항상 현재 활동에 대한 올바른 링크가 있으며 진행률 대화 상자를 안전하게 다시 시작할 수 있습니다.
  • 진행률 대화 상자를 수업의 구성원으로 만드십시오. 실제로 모든 대화 상자에서 그렇게해야합니다. onPause 메소드에서이를 해제해야합니다. 그렇지 않으면 구성 변경시 새 창이 나타납니다. 사용 중 상태는 조각에 의해 유지되어야합니다. 프래그먼트가 액티비티에 첨부되면 통화가 계속 실행중인 경우 진행률 대화 상자를 다시 표시 할 수 있습니다. void showProgressDialog()방법은 이러한 목적을 위해 단편 활동 청취자 인터페이스에 추가 될 수있다.

완벽한 솔루션이지만 왜이 답변이 다른 사람들로부터 어둡게되었는지 이해하지 못합니다 !!!
blackkara

2

나는 같은 상황에 직면했다. 내가 한 것은 전체 응용 프로그램에서 진행률 대화 상자에 대해 하나의 인스턴스 만 얻는 것입니다.

먼저 하나의 인스턴스 만 가져 오는 DialogSingleton 클래스를 만들었습니다 (Singleton 패턴)

public class DialogSingleton
{
    private static Dialog dialog;

    private static final Object mLock = new Object();
    private static DialogSingleton instance;

    private DialogSingleton()
    {

    }

    public static DialogSingleton GetInstance()
    {
        synchronized (mLock)
        {
            if(instance == null)
            {
                instance = new DialogSingleton();
            }

            return instance;
        }
    }

    public void DialogShow(Context context, String title)
    {
        if(!((Activity)context).isFinishing())
        {
            dialog = new ProgressDialog(context, 2);

            dialog.setCanceledOnTouchOutside(false);

            dialog.setTitle(title);

            dialog.show();
        }
    }

    public void DialogDismiss(Context context)
    {
        if(!((Activity)context).isFinishing() && dialog.isShowing())
        {
            dialog.dismiss();
        }
    }
}

이 클래스에서 보여 주듯이 진행률 대화 상자가 속성으로 표시됩니다. 진행률 대화 상자를 표시해야 할 때마다 고유 한 인스턴스를 가져 와서 새로운 ProgressDialog를 만듭니다.

DialogSingleton.GetInstance().DialogShow(this, "My title here!");

백그라운드 작업을 마치면 고유 한 인스턴스를 다시 호출하고 해당 대화 상자를 닫습니다.

DialogSingleton.GetInstance().DialogDismiss(this);

백그라운드 작업 상태를 공유 환경 설정에 저장합니다. 화면을 회전 시키면이 액티비티에 대한 작업이 실행 중인지 묻습니다. (onCreate)

if(Boolean.parseBoolean(preference.GetValue(IS_TASK_NAME_EXECUTED_KEY, "boolean").toString()))
{
    DialogSingleton.GetInstance().DialogShow(this, "Checking credentials!");
} // preference object gets the info from shared preferences (my own implementation to get and put data to shared preferences) and IS_TASK_NAME_EXECUTED_KEY is the key to save this flag (flag to know if this activity has a background task already running).

백그라운드 작업을 시작할 때 :

preference.AddValue(IS_TASK_NAME_EXECUTED_KEY, true, "boolean");

DialogSingleton.GetInstance().DialogShow(this, "My title here!");

백그라운드 작업 실행을 마치면 :

preference.AddValue(IS_TASK_NAME_EXECUTED_KEY, false, "boolean");

DialogSingleton.GetInstance().DialogDismiss(ActivityName.this);

도움이 되길 바랍니다.


2

이것은 몇 가지 이유로 사이드 바에 나타난 아주 오래된 질문입니다.

활동이 포 그라운드에있는 동안 백그라운드 태스크 만 생존해야하는 경우, "새로운"솔루션은 이 개발자 안내서수많은 Q & A에 설명 된대로 백그라운드 스레드 (또는 바람직하게는 AsyncTask)를 보유 된 단편 으로 호스트하는 것 입니다.

구성 변경으로 인해 활동이 파괴 된 경우 보유 단편이 유지되지만 백그라운드 또는 백 스택에서 활동이 파괴 된 경우에는 유지 되지 않습니다 . 따라서에서 isChangingConfigurations()false 인 경우 백그라운드 작업이 여전히 중단되어야합니다 onPause().


2

나는 안드로이드에서 더 신선하고 이것을 시도했고 효과가 있었다.

public class loadTotalMemberByBranch extends AsyncTask<Void, Void,Void> {
        ProgressDialog progressDialog = new ProgressDialog(Login.this);
        int ranSucess=0;
        @Override
        protected void onPreExecute() {
            // TODO Auto-generated method stub
            super.onPreExecute();
            progressDialog.setTitle("");    
            progressDialog.isIndeterminate();
            progressDialog.setCancelable(false);
            progressDialog.show();
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);

        }
        @Override
        protected Void doInBackground(Void... params) {
            // TODO Auto-generated method stub

            return null;
        }
        @Override
        protected void onPostExecute(Void result) {
            // TODO Auto-generated method stub
            super.onPostExecute(result);
            progressDialog.dismiss();
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
        }
}

1

나는 모든 것을 시도했다. 실험 일을 보냈다. 활동이 회전하는 것을 차단하고 싶지 않았습니다. 내 시나리오는 다음과 같습니다

  1. 사용자에게 동적 정보를 보여주는 진행 대화 상자. 예 : "서버에 연결 중 ...", "데이터 다운로드 중 ..."등
  2. 무거운 일을하고 대화 상자를 업데이트하는 스레드
  3. 마지막에 결과를 사용하여 UI를 업데이트합니다.

문제는 화면을 회전 할 때 책의 모든 솔루션이 실패했다는 것입니다. 이 상황을 처리하는 올바른 Android 방법 인 AsyncTask 클래스를 사용하더라도. 화면을 회전하면 시작 스레드가 작동하는 현재 컨텍스트가 사라지고 표시되는 대화 상자가 엉망이됩니다. 문제는 코드에 몇 가지 트릭을 추가했는지에 관계없이 항상 대화 상자였습니다 (실행중인 스레드에 새로운 컨텍스트 전달, 회전을 통해 스레드 상태 유지 등). 결국 코드 복잡성은 항상 크며 항상 잘못 될 수있는 것이있었습니다.

나를 위해 일한 유일한 솔루션은 활동 / 대화 트릭이었습니다. 간단하고 천재적이며 모든 회전 증거입니다.

  1. 대화 상자를 만들고 표시하는 대신 android : theme = "@ android : style / Theme.Dialog"를 사용하여 매니페스트에 설정된 활동을 만듭니다. 따라서 대화 상자처럼 보입니다.

  2. showDialog (DIALOG_ID)를 startActivityForResult (yourActivityDialog, yourCode)로 바꾸십시오.

  3. 호출 활동에서 onActivityResult를 사용하여 실행 스레드에서 결과를 가져오고 (오류조차 포함) UI를 업데이트하십시오.

  4. 'ActivityDialog'에서 스레드 또는 AsyncTask를 사용하여 긴 작업을 실행하고 onRetainNonConfigurationInstance를 사용하여 화면을 회전 할 때 "대화 상자"상태를 저장하십시오.

이것은 빠르며 잘 작동합니다. 나는 여전히 다른 작업에 대화 상자를 사용하고 화면에 일정한 대화 상자가 필요없는 AsyncTask를 사용합니다. 그러나이 시나리오에서는 항상 활동 / 대화 패턴으로 이동합니다.

그리고 시도하지는 않았지만 스레드가 실행 중일 때 활동 / 대화가 회전하는 것을 차단하고 호출하는 활동을 회전시키는 동안 속도를 높이는 것도 가능합니다.


매개 변수가 다음을 통해 허용되는 Intent것보다 더 제한적인을 통과해야한다는 점을 제외하고는 좋습니다.ObjectAsyncTask
rds

@Rui 작년 에도이 방법을 사용했습니다. 이것이 잘못 되었음에도 불구하고 이제 나에게 일어났다. 이것이이 문제를 '수정'하는 방법이라면 Google에 대화 상자가있는 이유는 무엇입니까? 내가 볼 수있는 문제는 ActivityA에서 ActivityB (Theme.Dialog)를 열면 ActivityA가 Activity 스택에서 아래로 이동하여 필요한 경우 OS가 죽일 준비가 된 것으로 표시됩니다. 따라서 프로세스를 오래 실행하고 일종의 가짜 진행률 '대화 상자'를 표시하고 너무 오래 걸리고 메모리가 부족한 경우 ActivityA가 종료되고 진행 완료시 다시 돌아올 것이 없습니다.
rf43

1

요즘에는 이러한 유형의 문제를 처리하는 훨씬 더 뚜렷한 방법이 있습니다. 일반적인 접근 방식은 다음과 같습니다.

1. 데이터가 UI에서 올바르게 분리되었는지 확인하십시오.

백그라운드 프로세스 인 항목은 모두 보유해야합니다 Fragment(로 설정하십시오 Fragment.setRetainInstance(). 이는 보유하려는 데이터가 유지되는 '지속적인 데이터 스토리지'가됩니다. 방향 변경 이벤트 Fragment후에도 원래의 상태로 액세스 할 수 있습니다. FragmentManager.findFragmentByTag()호출을 통해 상태를 만듭니다 (생성 할 때에 첨부되지 않은 태그를 ID 가 아닌 태그로 지정해야 함 View).

이 작업을 올바르게 수행하는 방법과 최상의 옵션 인 이유에 대한 정보는 런타임 변경 처리 개발 안내서를 참조하십시오 .

2. 백그라운드 프로세스와 UI간에 올 바르고 안전하게 인터페이스하고 있는지 확인하십시오.

연결 과정을 취소 해야합니다 . 현재 백그라운드 프로세스가 자체에 연결되는 View대신 백그라운드 프로세스에 연결 View해야합니다. 더 이해가 되나요? View백그라운드 프로세스에 의존하지 않는 반면의 동작은 백그라운드 프로세스에 의존하는 View표준 링크 변경 수단 .This Listener인터페이스. - 당신의 (그것이를인지 그것이 어떤 클래스 과정 말 AsyncTask, Runnable정의 또는 무엇이든)OnProcessFinishedListener 프로세스가 존재하는 경우는 그 리스너를 호출해야 할 때를.

답변 은 사용자 정의 리스너를 수행하는 방법에 대한 간결한 설명입니다.

3. UI가 생성 될 때마다 (방향 변경 포함) UI를 데이터 프로세스에 연결합니다.

이제 백그라운드 작업을 현재 View구조 와 인터페이스하는 것에 대해 걱정해야합니다 . 방향 변경을 올바르게 처리하는 경우 ( configChanges사람들이 항상 권장하는 것은 아님) Dialog시스템에서 다시 생성합니다. 이것은 중요합니다. 오리엔테이션 변경시 모든 Dialog라이프 사이클 방법이 호출됩니다. 따라서 이러한 방법 중 하나 ( onCreateDialog일반적으로 좋은 위치)에서 다음과 같이 전화를 걸 수 있습니다.

DataFragment f = getActivity().getFragmentManager().findFragmentByTag("BACKGROUND_TAG");
if (f != null) {
    f.mBackgroundProcess.setOnProcessFinishedListener(new OnProcessFinishedListener() {
        public void onProcessFinished() {
            dismiss();
        }
    });
 }

리스너 설정이 개별 구현에 가장 적합한 위치를 결정 하려면 단편 수명주기 를 참조하십시오 .

이것은이 질문에서 제기 된 일반적인 문제에 대한 강력하고 완전한 솔루션을 제공하기위한 일반적인 접근 방식입니다. 개별 시나리오에 따라이 답변에 몇 가지 사소한 부분이 누락되었을 수 있지만 이것은 일반적으로 방향 변경 이벤트를 올바르게 처리하는 가장 올바른 방법입니다.


1

방향 변경시 스레드를 처리하는 더 쉬운 솔루션을 찾았습니다. 활동 / 조각에 대한 정적 참조를 유지하고 ui에서 작동하기 전에 null인지 확인하십시오. 시도 캐치를 사용하는 것이 좋습니다.

 public class DashListFragment extends Fragment {
     private static DashListFragment ACTIVE_INSTANCE;

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

        ACTIVE_INSTANCE = this;

        new Handler().postDelayed(new Runnable() {
            public void run() {
                try {
                        if (ACTIVE_INSTANCE != null) {
                            setAdapter(); // this method do something on ui or use context
                        }
                }
                catch (Exception e) {}


            }
        }, 1500l);

    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        ACTIVE_INSTANCE = null;
    }


}

1

대화 상자의 방향 변경 이벤트를 감지하는 데 어려움을 겪고 있다면 활동 참조의 독립 이 방법이 흥미롭게 작동합니다. 여러 활동에 표시 할 수있는 자체 대화 상자 클래스가 있기 때문에이를 사용하므로 어떤 활동이 표시되는지 항상 알 수는 없습니다.이 방법을 사용하면 AndroidManifest를 변경할 필요가 없습니다. 활동 참조에 대해 걱정하십시오. 사용자 정의 대화 상자가 필요하지 않습니다. 그러나 해당 특정보기를 사용하여 방향 변경을 감지 할 수 있도록 사용자 정의 컨텐츠보기가 필요합니다. 내 예는 다음과 같습니다.

설정

public class MyContentView extends View{
    public MyContentView(Context context){
        super(context);
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig){
        super.onConfigurationChanged(newConfig);

        //DO SOMETHING HERE!! :D
    }
}

구현 1-대화

Dialog dialog = new Dialog(context);
//set up dialog
dialog.setContentView(new MyContentView(context));
dialog.show();

구현 2-AlertDialog.Builder

AlertDialog.Builder builder = new AlertDialog.Builder(context);
//set up dialog builder
builder.setView(new MyContentView(context));        //Can use this method
builder.setCustomTitle(new MycontentView(context)); // or this method
builder.build().show();

구현 3-ProgressDialog / AlertDialog

ProgressDialog progress = new ProgressDialog(context);
//set up progress dialog
progress.setView(new MyContentView(context));        //Can use this method
progress.setCustomTitle(new MyContentView(context)); // or this method
progress.show();

1

이것은 내가 직면했을 때의 해결책입니다 ProgressDialog. Fragment자식 이 아니므 로 구성 변경에 대한 대화 상자를 표시하기 위해 대신 사용자 정의 클래스 " ProgressDialogFragment"를 확장 할 수 있습니다 DialogFragment.

import androidx.annotation.NonNull;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.os.Bundle; 
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager;

 /**
 * Usage:
 * To display the dialog:
 *     >>> ProgressDialogFragment.showProgressDialogFragment(
 *              getSupportFragmentManager(), 
 *              "fragment_tag", 
 *              "my dialog title", 
 *              "my dialog message");
 *              
 * To hide the dialog
 *     >>> ProgressDialogFragment.hideProgressDialogFragment();
 */ 


public class ProgressDialogFragment extends DialogFragment {

    private static String sTitle, sMessage;
    private static ProgressDialogFragment sProgressDialogFragment;

    public ProgressDialogFragment() {
    }

    private ProgressDialogFragment(String title, String message) {
        sTitle = title;
        sMessage = message;
    }


    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return ProgressDialog.show(getActivity(), sTitle, sMessage);
    }

    public static void showProgressDialogFragment(FragmentManager fragmentManager, String fragmentTag, String title, String message) {
        if (sProgressDialogFragment == null) {
            sProgressDialogFragment = new ProgressDialogFragment(title, message);
            sProgressDialogFragment.show(fragmentManager, fragmentTag);

        } else { // case of config change (device rotation)
            sProgressDialogFragment = (ProgressDialogFragment) fragmentManager.findFragmentByTag(fragmentTag); // sProgressDialogFragment will try to survive its state on configuration as much as it can, but when calling .dismiss() it returns NPE, so we have to reset it on each config change
            sTitle = title;
            sMessage = message;
        }

    }

    public static void hideProgressDialogFragment() {
        if (sProgressDialogFragment != null) {
            sProgressDialogFragment.dismiss();
        }
    }
}

문제는 대화 상자가 여전히 표시되지만 기본 빈 문자열로 재설정 될 때 화면 회전 중에 대화 상자 제목 및 메시지를 유지하는 것이 었습니다.

이 문제를 해결하기위한 두 가지 방법이 있습니다.

첫 번째 접근 방식 : 매니페스트 파일에서 구성을 변경하는 동안 대화 상자를 사용하여 상태를 유지하는 활동을 작성하십시오.

android:configChanges="orientation|screenSize|keyboardHidden"

이 방법은 Google에서 선호하지 않습니다.

두 번째 방법 : 활동의에 onCreate()방법, 당신은 당신을 유지할 필요가 DialogFragment재건하여 ProgressDialogFragment(가) 경우 다음과 같이 제목 및 메시지와 함께 다시 savedInstanceState널되지 않습니다 :

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

 if (savedInstanceState != null) {
      ProgressDialogFragment saveProgressDialog = (ProgressDialogFragment) getSupportFragmentManager()
              .findFragmentByTag("fragment_tag");
      if (saveProgressDialog != null) {
          showProgressDialogFragment(getSupportFragmentManager(), "fragment_tag", "my dialog title", "my dialog message");
      }
  }
}

0

너무 '빠르고 더러운'사실 인 것처럼 보이므로 결함을 지적하십시오. 그러나 내가 발견 한 것은 ...

내 AsyncTask의 onPostExecute 메소드 내에서 진행 대화 상자의 '.dismiss'를 try / catch 블록 (빈 캐치 포함)으로 래핑 한 다음 발생한 예외를 무시했습니다. 잘못하는 것처럼 보이지만 나쁜 영향은 없습니다 (적어도 나중에 내가하고있는 일은 장기 실행 쿼리의 결과로 다른 활동을 추가로 시작하는 것입니다)


Android 플랫폼에서 창이 누출되었다고 말할 때 실제로 메모리 누출이 없다고 말하는가?
rds

내 진행률 대화 상자는 활동 클래스의 멤버 변수이므로 활동을 파괴하고 다시 만들 때 가비지 수집되고 누출이 없다고 가정했습니다. 내가 잘못?
Simon

예, 잘못되었다고 생각합니다. 당신이 말했듯이,에 Activity대한 참조가 있습니다 Dialog. 구성이 변경되면 첫 번째 Activity가 삭제되므로 모든 필드가로 설정됩니다 null. 그러나 저수준 WindowManagerDialog(아직 해고되지 않았기 때문에) 참조를 가지고 있습니다. 새로운 사용자 ActivityDialog(에서 preExecute()) 새로운 것을 만들려고 시도 하고 창 관리자는 치명적인 예외를 발생시켜 사용자가 그렇게하지 못하게합니다. 실제로 그렇게한다면 Dialog, 초기에 대한 참조를 유지하면서 깨끗하게 파괴 할 방법이 없을 것 Activity입니다. 내가 맞아?
rds

0

가장 간단하고 유연한 솔루션은 ProgressBar에 대한 정적 참조와 함께 AsyncTask 를 사용하는 것 입니다. 이것은 방향 변경 문제에 대한 캡슐화되어 재사용 가능한 솔루션을 제공합니다. 이 솔루션은 인터넷 다운로드, 서비스 와의 통신 및 파일 시스템 스캔을 포함한 다양한 비동기 작업에 유용했습니다 . 이 솔루션은 여러 안드로이드 버전과 전화 모델에서 잘 테스트되었습니다. 전체 데모는 DownloadFile.java에 대한 특정 관심과 함께 여기 에서 찾을 수 있습니다.

나는 개념적인 예로서 다음을 제시한다

public class SimpleAsync extends AsyncTask<String, Integer, String> {
    private static ProgressDialog mProgressDialog = null;
    private final Context mContext;

    public SimpleAsync(Context context) {
        mContext = context;
        if ( mProgressDialog != null ) {
            onPreExecute();
        }
    }

    @Override
    protected void onPreExecute() {
        mProgressDialog = new ProgressDialog( mContext );
        mProgressDialog.show();
    }

    @Override
    protected void onPostExecute(String result) {
        if ( mProgressDialog != null ) {
            mProgressDialog.dismiss();
            mProgressDialog = null;
        }
    }

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

    @Override
    protected String doInBackground(String... sUrl) {
        // Do some work here
        publishProgress(1);
        return null;
    }

    public void dismiss() {
        if ( mProgressDialog != null ) {
            mProgressDialog.dismiss();
        }
    }
}

안드로이드 활동에서의 사용법은 간단합니다

public class MainActivity extends Activity {
    DemoServiceClient mClient = null;
    DownloadFile mDownloadFile = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate( savedInstanceState );
        setContentView( R.layout.main );
        mDownloadFile = new DownloadFile( this );

        Button downloadButton = (Button) findViewById( R.id.download_file_button );
        downloadButton.setOnClickListener( new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mDownloadFile.execute( "http://www.textfiles.com/food/bakebred.txt");
            }
        });
    }

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

0

방향을 변경하면 Android가 해당 활동을 종료하고 새 활동을 작성합니다. Rx java와 함께 개조를 사용하는 것이 좋습니다. 어떤 핸들이 자동으로 충돌합니다.

개조 할 때이 방법을 사용하십시오.

.subscribeOn (Schedulers.io ()) .observeOn (AndroidSchedulers.mainThread ())

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.