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

StackOverflow https://stackoverflow.com/questions/1111980

문제

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

화면 방향 변경을 어떻게 적절하게 처리할 수 있나요?

아래 샘플 코드는 실제 프로그램이 수행하는 작업과 대략적으로 일치합니다.

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 엔지니어는 Dianne Hackborn(a.k.a. 해킹) 이에 StackOverflow 게시물.확인해 보세요 이 블로그 게시물 자세한 내용은.


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

android:configChanges="orientation|screenSize"

그래서 그것은 다음과 같습니다

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

문제는 구성이 변경되면 시스템이 활동을 파괴한다는 것입니다.보다 구성변경.

따라서 이를 구성 파일에 넣으면 시스템이 활동을 파괴하는 것을 방지할 수 있습니다.대신에 다음을 호출합니다. onConfigurationChanged(Configuration) 방법.

저는 이러한 문제에 대해 'Android 방식'을 준수하는 견고한 솔루션을 생각해냈습니다.IntentService 패턴을 사용하여 장기 실행 작업을 모두 수행했습니다.

즉, 내 활동은 의도를 브로드캐스트하고 IntentService는 작업을 수행하며 데이터를 DB에 저장한 다음 브로드캐스트합니다. 어려운 의도.사용자가 작업을 시작한 후 일정 시간 동안 활동이 일시 중지되어 IntentService의 실시간 브로드캐스트를 놓친 경우에도 호출 활동에서 응답하고 데이터를 선택할 수 있도록 끈적한 부분이 중요합니다. ProgressDialogs는 이 패턴을 아주 잘 사용할 수 있습니다. 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의 일부 데이터를 구문 분석해야 하는데 속도가 느립니다.그래서 이를 수행하기 위한 스레드를 만든 다음 진행률 대화 상자를 표시합니다.스레드가 다음을 통해 메시지를 UI 스레드에 다시 게시하도록 허용합니다. Handler 끝나면.~ 안에 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.
    }
}

그것이 나에게 효과적인 것입니다.이것이 Android에서 설계한 "올바른" 방법인지는 모르겠습니다. 그들은 "화면 회전 중 활동 삭제/재생성"이 실제로 작업을 더 쉽게 만든다고 주장하므로 너무 까다로워서는 안 될 것 같습니다.

내 코드에 문제가 있으면 알려주세요.위에서 말했듯이 부작용이 있는지는 잘 모르겠습니다.

원래 인지된 문제는 화면 방향 변경 시 코드가 유지되지 않는다는 것이었습니다.분명히 이 문제는 UI 프레임워크가 (onDestroy 호출을 통해) 수행하도록 하는 대신 프로그램이 화면 방향 변경 자체를 처리하도록 함으로써 "해결"되었습니다.

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

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

내 해결책은 ProgressDialog 내 것을 얻기 위한 수업 MyProgressDialog.
나는 재정의했다 show() 그리고 dismiss() 표시하기 전에 방향을 잠그는 방법 Dialog 그리고 다시 잠금을 해제하면 Dialog 해고됩니다.그래서 언제 Dialog 이 표시되고 장치의 방향이 변경되면 화면의 방향은 다음까지 유지됩니다. dismiss() 호출되면 센서 값/장치 방향에 따라 화면 방향이 변경됩니다.

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

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를 사용하려는 경우 이 답변은 적합하지 않습니다.

나는 아직 다른 곳에서는 보지 못한 이에 대한 해결책을 발견했습니다.방향 변경 시 소멸되고 다시 생성되는 활동에서 이 작업을 수행하는 대신 백그라운드 작업이 진행 중인지 아는 사용자 정의 애플리케이션 개체를 사용할 수 있습니다.나는 이것에 대해 블로그에 올렸습니다. 여기.

나는 이 회전 문제를 처리하는 방법에 기여할 것입니다.OP가 사용하지 않기 때문에 이는 OP와 관련이 없을 수 있습니다. AsyncTask, 하지만 다른 사람들도 유용하다고 생각할 수도 있습니다.그것은 매우 간단하지만 나에게 맞는 일인 것 같습니다.

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

BackgroundLoginTask 호출 시 null 검사를 추가하는 것 외에는 특별한 작업을 수행하지 않습니다. ProgressDialog님의 해고:

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

이는 백그라운드 작업이 종료되는 동안 처리하기 위한 것입니다. Activity 표시되지 않으므로 진행 대화 상자가 이미 닫혔습니다. onPause() 방법.

다음으로 부모님의 Activity 클래스에서 전역 정적 핸들을 만듭니다. AsyncTask 수업과 나의 ProgressDialog (그만큼 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();
            } 
        }
        }

    }
}

나는 결코 노련한 안드로이드 개발자가 아니므로 자유롭게 의견을 말해주세요.

긴 작업을 별도의 클래스로 이동하세요.주체-관찰자 패턴으로 구현하세요.활동이 생성될 때마다 등록하고 종료하는 동안 작업 클래스 등록을 취소합니다.태스크 클래스는 AsyncTask를 사용할 수 있습니다.

요령은 평소와 같이 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;
        }       
    }
}

나는 이것을 이렇게 했다:

    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 {

                }
            }
        }

    }

시도해 보고 그것이 효과가 있는지 여부를 알려주실 수도 있습니다.

배경을 만들면 Service 이는 모든 무거운 작업(TCP 요청/응답, 역마샬링)을 수행합니다. View 그리고 Activity 창 누출이나 데이터 손실 없이 파괴되고 다시 생성될 수 있습니다.이는 Android 권장 동작을 허용합니다. 구성이 변경될 때마다 활동을 삭제합니다. (예:각 방향 변경에 대해).

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

당신은 당신의 Service 각 요청을 서버에 대기열에 추가하므로 이러한 작업을 쉽고 효율적으로 처리할 수 있습니다.

개발자 가이드에는 전체 내용이 있습니다. Services.

화면 방향 변경 시 활동이 삭제되도록 허용하지만 다시 생성된 활동의 대화 상자는 성공적으로 삭제되는 구현이 있습니다.나는 사용한다 ...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 '소유된' 백그라운드 작업을 참조하고 다음을 사용하여 이 필드를 업데이트합니다. 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 동일한 소유 활동에서 다양한 유형의 작업을 실행할 수 있도록 하는 인터페이스 및/또는 컬렉션입니다.

두 개의 레이아웃을 유지하는 경우 모든 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는 한 번만 실행할 수 있습니다.

구현을 시도했습니다. jfelectron의 솔루션은 "'Android 방식'을 준수하는 이러한 문제에 대한 견고한 솔루션"하지만 언급된 모든 요소를 ​​찾아 정리하는 데 시간이 좀 걸렸습니다.이것은 약간 다른 결과로 끝났으며 여기에 전체 솔루션이 게시되어 더 우아하다고 생각합니다.

활동에서 실행된 IntentService를 사용하여 별도의 스레드에서 장기 실행 작업을 수행합니다.서비스는 대화 상자를 업데이트하는 활동에 고정 브로드캐스트 인텐트를 다시 실행합니다.활동은 showDialog(), onCreateDialog() 및 onPrepareDialog()를 사용하여 애플리케이션 객체 또는 saveInstanceState 번들에 영구 데이터를 전달할 필요가 없도록 합니다.이는 애플리케이션이 어떻게 중단되더라도 작동해야 합니다.

활동 클래스:

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);
}}

인텐트서비스 클래스:

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"

제가 제안한 솔루션은 다음과 같습니다.

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

나는 같은 상황에 직면했다.내가 한 일은 전체 애플리케이션에서 진행 대화 상자에 대해 단 하나의 인스턴스만 얻은 것입니다.

먼저 하나의 인스턴스만 가져오기 위해 DialogSingleton 클래스를 만들었습니다(싱글톤 패턴).

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);

공유 기본 설정에 백그라운드 작업 상태를 저장합니다.화면을 회전할 때 이 활동에 대해 실행 중인 작업이 있는지 묻습니다.(생성 시)

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);

도움이 되었으면 좋겠습니다.

어떤 이유에서인지 사이드바에 올라온 아주 오래된 질문입니다.

활동이 포그라운드에 있는 동안에만 백그라운드 작업이 유지되어야 하는 경우 "새로운" 솔루션은 백그라운드 스레드를 호스팅하는 것입니다(또는 바람직하게는 AsyncTask) 안에 보유된 조각, 여기에 설명된 대로 개발자 가이드 그리고 수많은 Q&A.

구성 변경으로 인해 활동이 삭제된 경우 보유된 프래그먼트는 유지되지만 ~ 아니다 활동이 백그라운드 또는 백 스택에서 소멸될 때.따라서 다음과 같은 경우에도 백그라운드 작업을 중단해야 합니다. isChangingConfigurations() 거짓이다 onPause().

나는 안드로이드에 더 익숙하고 이것을 시도했고 효과가 있었습니다.

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. 사용자에게 동적 정보를 표시하는 진행 대화 상자입니다.예:"서버에 연결하는 중...", "데이터 다운로드 중..." 등
  2. 무거운 작업을 수행하고 대화 상자를 업데이트하는 스레드
  3. 마지막에 결과로 UI를 업데이트합니다.

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

나에게 도움이 된 유일한 해결책은 Activity/Dialog 트릭이었습니다.간단하고 천재적이며 모든 회전 방지 기능이 있습니다.

  1. 대화 상자를 만들고 표시하도록 요청하는 대신 android:theme="@android:style/Theme.Dialog"를 사용하여 매니페스트에 설정된 활동을 만듭니다.그래서 그것은 단지 대화처럼 보입니다.

  2. showDialog(DIALOG_ID)를 startActivityForResult(yourActivityDialog, yourCode)로 바꿉니다.

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

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

이것은 빠르고 잘 작동합니다.나는 여전히 다른 작업에는 대화 상자를 사용하고 화면에 지속적인 대화 상자가 필요하지 않은 작업에는 AsyncTask를 사용합니다.하지만 이 시나리오에서는 항상 Activity/Dialog 패턴을 사용합니다.

그리고 시도하지는 않았지만 스레드가 실행 중일 때 해당 활동/대화 상자가 회전하는 것을 차단하여 작업 속도를 높이는 동시에 호출 활동이 회전하도록 허용하는 것도 가능합니다.

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

1.데이터가 UI에서 적절하게 분리되었는지 확인하세요.

백그라운드 프로세스인 모든 항목은 유지되어야 합니다. Fragment (이것을 설정 Fragment.setRetainInstance().이는 보관하려는 모든 데이터 기반이 보관되는 '지속적 데이터 저장소'가 됩니다.오리엔테이션 변경 이벤트 이후에는 Fragment 다음을 통해 원래 상태로 계속 액세스할 수 있습니다. FragmentManager.findFragmentByTag() 호출(만들 때 태그를 지정해야 함) 아이디가 아님 에 붙어 있지 않기 때문에 View).

참조 런타임 변경 처리 이를 올바르게 수행하는 방법과 이것이 최선의 선택인 이유에 대한 정보를 제공하는 가이드가 개발되었습니다.

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

당신은해야합니다 뒤집다 귀하의 연결 프로세스.현재 백그라운드 프로세스가 다음 프로세스에 연결됩니다. View - 대신 당신의 View 백그라운드 프로세스에 연결되어야 합니다.더 의미가 있지 않나요?그만큼 View의 작업은 백그라운드 프로세스에 의존하는 반면, 백그라운드 프로세스는 백그라운드 프로세스에 의존하지 않습니다. View.이것은 링크를 표준으로 변경하는 것을 의미합니다. Listener 상호 작용.프로세스를 말하세요(클래스가 무엇이든 상관없음). AsyncTask, Runnable 또는 무엇이든) OnProcessFinishedListener, 프로세스가 완료되면 해당 리스너가 있으면 호출해야 합니다.

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

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

이제 현재 작업과 백그라운드 작업을 인터페이스하는 것에 대해 걱정해야 합니다. View 구조는.방향 변경을 처리하는 경우 제대로 (그렇지 않다 configChanges hack 사람들은 항상 추천합니다) Dialog 시스템에 의해 다시 생성됩니다.이는 중요합니다. 이는 방향 변경 시 모든 Dialog의 수명주기 방법이 회상됩니다.따라서 이러한 방법 중 하나에서(onCreateDialog 일반적으로 좋은 장소입니다.) 다음과 같이 호출할 수 있습니다.

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

참조 조각 수명주기 리스너 설정이 개별 구현에 가장 적합한 위치를 결정합니다.

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

방향이 변경될 때 스레드를 처리하는 더 쉬운 솔루션을 찾았습니다.활동/조각에 대한 정적 참조를 유지하고 UI에서 작업하기 전에 null인지 확인할 수 있습니다.try catch도 사용하는 것이 좋습니다.

 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;
    }


}

대화상자의 방향 변경 이벤트를 감지하는 데 어려움을 겪고 있는 경우 활동 참조와 무관, 이 방법은 매우 잘 작동합니다.나는 여러 다른 활동에 표시될 수 있는 자체 대화 상자 클래스가 있어서 어떤 활동에 표시되는지 항상 알 수 없기 때문에 이것을 사용합니다.이 방법을 사용하면 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();

내가 직면했을 때 이것이 나의 해결책입니다.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 null이 아닙니다:

@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");
      }
  }
}

사실이라고 하기에는 너무 '빠르고 더러워' 보이는 것 같으니 결함을 지적해 주세요. 하지만 제가 찾은 결과는...

내 AsyncTask의 onPostExecute 메서드 내에서 진행률 대화 상자의 '.dismiss'를 try/catch 블록(빈 catch 포함)으로 래핑한 다음 발생한 예외를 무시했습니다.잘못된 것 같지만 나쁜 영향은 없는 것 같습니다(적어도 이후에 수행하는 작업의 경우 장기 실행 쿼리의 결과를 추가 항목으로 전달하는 다른 활동을 시작하는 것입니다).

가장 간단하고 유연한 솔루션은 다음을 사용하는 것입니다. 비동기태스크 에 대한 정적 참조를 사용하여 진행 표시 줄.이는 방향 변경 문제에 대한 캡슐화되어 재사용 가능한 솔루션을 제공합니다.이 솔루션은 인터넷 다운로드, 통신 등 다양한 비동기 작업에 큰 도움이 되었습니다. 서비스, 파일 시스템 스캔.이 솔루션은 여러 안드로이드 버전과 휴대폰 모델에서 잘 테스트되었습니다.전체 데모를 찾을 수 있습니다 여기 특별한 관심을 가지고 다운로드File.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();
        }
    }
}

Android 활동에서의 사용법은 간단합니다.

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();
    }
}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top