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

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

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

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

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가 부분적으로 그려진 상태입니다. 다시 작동하기 전에 전체 앱을 종료해야합니다.



답변

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

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


답변

편집 : 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)메소드를 호출합니다 .


답변

나는 ‘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));
    }
}


답변

나는 같은 문제를 만났다. 내 활동은 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.
    }
}

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

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


답변

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

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

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


답변

내 솔루션은 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);
}

}


답변

나는이 같은 문제에 직면하여 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를 사용하려면이 대답이 적합하지 않습니다.