كيفية التعامل مع تغيير توجيه الشاشة عند إجراء حوار التقدم وخيط الخلفية نشط؟

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

سؤال

يقوم برنامجي ببعض نشاط الشبكة في مؤشر ترابط الخلفية. قبل البدء، فإنه ينبثق حوار تقدم. يتم رفض مربع الحوار على المعالج. يعمل كل هذا بشكل جيد، باستثناء متى يتغير توجيه الشاشة أثناء تشغيل مربع الحوار (ولم يسار مؤشر ترابط الخلفية). في هذه المرحلة، يتعطل التطبيق إما Deadlocks، أو الجمود، أو في مرحلة غريبة حيث لا يعمل التطبيق على الإطلاق حتى يتم قتل جميع المواضيع.

كيف يمكنني التعامل مع تغير توجيه الشاشة بأمان؟

يطابق نموذج التعليمات البرمجية أدناه تقريبا ما يفعله برنامجي الحقيقي:

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)

لقد حاولت استبعاد مربع حوار التقدم في OnsaaveInstanCestate، لكن هذا يمنع تعطل فوري. لا يزال موضوع الخلفية مستمرا، واجهة المستخدم في حالة رسم جزئيا. بحاجة إلى قتل التطبيق بأكمله قبل أن يبدأ العمل مرة أخرى.

هل كانت مفيدة؟

المحلول

عند تشغيل الاتجاهات، سيقوم Android بإنشاء طريقة عرض جديدة. من المحتمل أنك تحصل على تعطل لأن خيط الخلفية الخاص بك يحاول تغيير الحالة في القديم. (قد تواجه مشكلة لأن خيط الخلفية الخاص بك ليس في مؤشر ترابط UI)

أود أن أقترح جعل هذا مهاندلر متقلب وتحديثه عند تغيير الاتجاه.

نصائح أخرى

يحرر: لا يوصي مهندسو Google هذا النهج، كما هو موضح بواسطة Dianne Heakborn (AKA Hackbod.) في هذا stackoverflow post.. وبعد الدفع هذه المدونة post. للمزيد من المعلومات.


يجب عليك إضافة هذا إلى إعلان النشاط في البيان:

android:configChanges="orientation|screenSize"

لذلك يبدو

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

الأمر هو أن النظام يدمر النشاط عند حدوث تغيير في التكوين. يرى ضبط التكوينات.

لذا فإن وضع ذلك في ملف التكوين يتجنب النظام لتدمير نشاطك. بدلا من ذلك يستدعي onConfigurationChanged(Configuration) طريقة.

توصلت إلى حل صخري لهذه المشكلات التي تتفق مع "طريقة أندرويد" للأشياء. لدي كل عملياتي الطويلة قيد التشغيل باستخدام نمط IntentService.

وهذا هو، أن أنشطتي تبث النوايا، تقوم Intentservice بعمل العمل، وتحفيز البيانات في DB ثم البث لزج النوايا. الجزء اللزج مهم، بحيث تم إيقاف هذا النشاط خلال الوقت خلال الوقت بعد أن بدأ المستخدم في العمل ويشمل البث في الوقت الفعلي من Intentservice، لا يزال بإمكاننا الاستجابة والتقاط البيانات من نشاط المكالمات. ProgressDialogS يمكن أن تعمل مع هذا النمط بشكل جيد تماما مع onSaveInstanceState().

في الأساس، تحتاج إلى حفظ علامة لديك حوار تقدم يعمل في حزمة المثيل المحفوظة. لا احفظ كائن مربع حوار التقدم لأن هذا سيتسرب النشاط بأكمله. للحصول على مقبض مستمر لحوار التقدم، قمت بتخزينه كمرجع ضعيف في كائن التطبيق. عند تغيير الاتجاه أو أي شيء آخر يؤدي إلى إيقاف النشاط الإيقاف مؤقتا (مكالمة هاتفية، يقوم المستخدم بربط المنزل وما إلى ذلك) ثم استئناف، أقوم بإقالة مربع الحوار القديم وإعادة إنشاء حوار جديد في النشاط المنشئ حديثا.

للحوار التقدم غير المسمى، هذا سهل. للحصول على نمط شريط التقدم، يجب عليك وضع التقدم الأخير المعروف في الحزمة وأي المعلومات التي تستخدمها محليا في النشاط لتتبع التقدم المحرز. عند استعادة التقدم المحرز، ستستخدم هذه المعلومات لإعادة تفعيل شريط التقدم في نفس الحالة كما كان من قبل ثم تحديث بناء على الحالة الحالية للأشياء.

لتلخيص، وضع المهام الطويلة الجري في الانتهاء من القائمة إلى جانب الاستخدام الحكيم onSaveInstanceState() يسمح لك باستمرار بكفاءة تتبع مربعات الحوار واستعادتها ثم عبر أحداث دورة الحياة. البتات ذات الصلة من قانون النشاط أدناه. ستحتاج أيضا إلى المنطق في BroadCastReciver لمعالجة النوايا اللزجة بشكل مناسب، ولكن هذا يتجاوز نطاق هذا.

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) ثم ملء ذلك إلى واجهة المستخدم. يجب السماح بتناوب الشاشة في أي لحظة دون مفاجأة.

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 وأحصل على نتائج أسرع.

ما فعلته كان يخلق تخطيطا له سجل في ذلك.

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

ثم قم بعمل المهمة الطويلة في مؤشر ترابط، وعندما يكون ذلك قد انتهى عرض محتوى عرض المحتوى إلى التصميم الحقيقي الذي تريد استخدامه لهذا النشاط.

علي سبيل المثال:

mHandler.post(new Runnable(){

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

هذا ما فعلته، وقد وجدت أنه يدير بشكل أسرع من إظهار التقدم المحرز وهو أقل تدخلا ولديه نظرة أفضل في رأيي.

ومع ذلك، إذا كنت ترغب في استخدام ProgressDialog، فإن هذه الإجابة ليست لك.

اكتشفت حل لهذا أنني لم أر بعد في مكان آخر. يمكنك استخدام كائن تطبيق مخصص يعرف ما إذا كان لديك مهام خلفية تسير، بدلا من محاولة القيام بذلك في النشاط الذي يتم تدميره وإعادة إنشاؤه بتغيير الاتجاه. لقد بلوق حول هذا هنا.

سوف أسهم نهجي في التعامل مع مشكلة الدوران هذه. قد لا يكون هذا ذا صلة بالبرودة لأنه لا يستخدم AsyncTask, ، ولكن ربما سيجد الآخرون أنه مفيد. انها بسيطة جدا ولكن يبدو أن القيام بهذه المهمة بالنسبة لي:

لدي نشاط تسجيل الدخول مع متداخل AsyncTask دعا الطبقة BackgroundLoginTask.

في BackgroundLoginTask أنا لا أفعل أي شيء خارج عن المألوف باستثناء إضافة فحص فارغة عند الاتصال 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.

الحيلة هي إظهار / رفض مربع الحوار داخل ASYNCTASK أثناء OnPreexecute / OnPostExecute كالمعتاد، على الرغم من إنشاء / إظهار تغيير / إظهار مثيل جديد من مربع الحوار في النشاط وتمرير مرجعه إلى المهمة.

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 هذا يفعل كل الرفع الثقيل (طلبات برنامج التعاون الفني / الاستجابة، Unmarshalling)، View و Activity يمكن تدميرها وإعادة إنشاؤها دون تسرب نافذة أو فقدان البيانات. هذا يسمح بسلوك Android الموصى به، وهو ما هو تدمير النشاط على كل تغيير التكوين (على سبيل المثال. لكل تغيير اتجاه).

إنه أكثر تعقيدا بعض الشيء، لكنه أفضل طريقة لاستدعاء طلب الخادم والبيانات قبل / بعد المعالجة، إلخ.

قد تستخدم حتى الخاص بك Service في قائمة الانتظار لكل طلب إلى خادم، لذلك يجعلها سهلة وفعالة للتعامل مع هذه الأشياء.

دليل DEV لديه كامل الفصل على 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.حل الحل لأنه "حل الصخور الصخور لهذه المشكلات التي تتوافق مع "طريقة أندرويد" للأشياء"لكن استغرق بعض الوقت للبحث عن جميع العناصر المذكورة ووضعها معا. انتهى الأمر بهذا مختلف قليلا، وأعتقد أن أكثر أناقة، حل تم نشره هنا في مجمله.

يستخدم 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"

هذا هو الحل المقترح:

  • نقل ASYNCTASK أو الخيط إلى جزء محبوب، كما هو موضح هنا. وبعد أعتقد أنه من الممارسات الجيدة نقل جميع مكالمات الشبكة إلى شظايا. إذا كنت تستخدم شظايا بالفعل، فيمكن إجراء أحدهم مسؤولا عن المكالمات. خلاف ذلك، يمكنك إنشاء جزء فقط للقيام بذلك، حيث تقترح المقالة المرتبطة.
  • سيستخدم الجزء واجهة مستمع للإشارة إلى إكمال / فشل المهمة. لا داعي للقلق لتغيير الاتجاه هناك. سيكون لدى الجزء دائما الرابط الصحيح للنشاط الحالي ويمكن استئناف مربع حوار التقدم بأمان.
  • اجعل حوار التقدم المحرز عضوا في صفك. في الواقع يجب عليك القيام بذلك لجميع الحوار. في طريقة OnPause، يجب عليك إقالةها، وإلا فسوف تسرب نافذة على تغيير التكوين. يجب أن تبقى الدولة المزدحمة من خلال الجزء. عندما يتم إرفاق الجزء في النشاط، يمكنك طرح مربع حوار التقدم مرة أخرى، إذا كانت المكالمة لا تزال تعمل. أ void showProgressDialog() يمكن إضافة الطريقة إلى واجهة مستمع نشاط الشظية لهذا الغرض.

واجهت نفس الوضع. ما فعلته هو الحصول على مثيل واحد فقط لحوار التقدمي في التطبيق بأكمله.

أولا، أنشأت فئة 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();
        }
    }
}

كما أظهر في هذه الفئة، لدي مربع حوار التقدم كاسم. في كل مرة أحتاج فيها إلى إظهار مربع حوار التقدم، أحصل على مثيل فريد وإنشاء تقدم مسبق جديد.

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

اتمني ان يكون مفيدا.

هذا سؤال قديم جاء على الشريط الجانبي لسبب ما.

إذا كانت مهمة الخلفية تحتاج فقط إلى البقاء على قيد الحياة أثناء وجود النشاط في المقدمة، فإن الحل "الجديد" هو استضافة مؤشر ترابط الخلفية (أو، ويفضل ذلك، AsyncTask) في احتفظ بشتاف, ، كما هو موضح في هذا دليل المطور و العديد من سؤال وجواب.

شظية محتجزة على قيد الحياة إذا تم تدمير النشاط لتغيير التكوين، ولكن ليس عندما يتم تدمير النشاط في الخلفية أو المكدس الخلفي. لذلك، يجب أن تتطور مهمة الخلفية إذا 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. تحديث واجهة المستخدم مع النتائج في النهاية.

كانت المشكلة، عند تدوير الشاشة، فشل كل حل في الكتاب. حتى مع فئة ASYNCTASK، ما هي طريقة Android الصحيحة للتعامل مع هذه المواقف. عند تدوير الشاشة، يختفي السياق الحالي الذي يعمل فيه مؤشر ترابط البداية معه، ويبحث عن مربع الحوار الذي يظهر. كانت المشكلة دائما الحوار، بغض النظر عن عدد الحيل التي أضفتها إلى التعليمات البرمجية (تمرير سياقات جديدة لتشغيل مؤشرات الترابط، تحتفظ بإحتفاظ حالات الخيط عبر الدوران، إلخ ...). كانت تعقيد التعليمات البرمجية في النهاية كانت ضخمة دائما وكان هناك دائما شيء يمكن أن يحدث خطأ.

الحل الوحيد الذي عمل بالنسبة لي كان خدعة النشاط / الحوار. انها بسيطة والجريئية وهذا دليل الدوران:

  1. بدلا من إنشاء مربع حوار وتطلب إظهارها، قم بإنشاء نشاط تم تعيينه في البيان مع Android: Theme = "@ Android: Style / theme.dialog". لذلك، يبدو الأمر وكأنه مربع حوار.

  2. استبدال ShowDialog (DialOG_ID) مع StartivityForreSult (youractivitydialog، yourcode)؛

  3. استخدم InflactivityResult في نشاط الاتصال للحصول على النتائج من مؤسس مؤسس مؤسس (حتى الأخطاء) وتحديث UI.

  4. في "ActivityDialog"، استخدم مؤشرات الترابط أو ASYNCTASK لتنفيذ المهام الطويلة و OnretainNonConfigurationInstance لحفظ حالة "الحوار" عند تدوير الشاشة.

هذا سريع ويعمل بشكل جيد. ما زلت أستخدم مربعات الحوار لمهام أخرى و ASYNCTASK لشيء لا يحتاج إلى مربع حوار دائم على الشاشة. ولكن مع هذا السيناريو، أذهب دائما لنمط النشاط / الحوار.

ولم أحاول ذلك، لكن من الممكن حظر هذا النشاط / مربع الحوار من التدوير، عند تشغيل الخيط، تسرع الأمور، مع السماح بنشاط الاتصال بالتناوب.

هذه الأيام هناك طريقة أكثر تميزا بكثير للتعامل مع هذه الأنواع من القضايا. النهج النموذجي هو:

1. تأكد من أن بياناتك مفصولة بشكل صحيح من UI:

يجب أن يكون أي شيء في عملية خلفية في الاحتفاظ به Fragment (اضبط هذا مع Fragment.setRetainInstance(). وبعد يصبح هذا تخزين البيانات المستمر "حيث يتم الاحتفاظ بأي شيء يعتمد على أي شيء ترغب في الاحتفاظ به. بعد تغيير الحدث التوجيه، هذا Fragment ما زلت يمكن الوصول إليها في حالتها الأصلية من خلال FragmentManager.findFragmentByTag() اتصل (عند إنشاءه، يجب أن تعطيه علامة ليس معرف لأنها غير مرفقة ب View).

انظر تغيير وقت التشغيل التغييرات وضع دليل للحصول على معلومات حول القيام بذلك بشكل صحيح ولماذا هو الخيار الأفضل.

2. تأكد من أنك تتداخل بشكل صحيح وأمان بين معالجات الخلفية و UI:

يجب عليك أن يعكس عملية ربط الخاص بك. في الوقت الحالي تعلق عملية الخلفية الخاصة بك بنفسها View - بدلا من ذلك View يجب أن تعلق نفسها على عملية الخلفية. يجعل أكثر منطقية أليس كذلك؟ ال Viewيعتمد العمل على عملية الخلفية، في حين أن عملية الخلفية لا تعتمد على Viewهذا يعني تغيير الرابط إلى المعيار Listener واجهه المستخدم. قل لعمليتك (مهما كانت الطبقة - سواء كان ذلك AsyncTask, Runnable أو أيا كان) يحدد OnProcessFinishedListener, ، عند إجراء العملية، يجب أن تتصل بهذا المستمع إذا كان موجودا.

هذه إجابه هو وصف موجز لطيفة لكيفية القيام المستمعين المخصصين.

3. ربط واجهة المستخدم الخاصة بك في عملية البيانات كلما تم إنشاء واجهة المستخدم (بما في ذلك تغييرات التوجيه):

الآن يجب أن تقلق بشأن تتفاعل مهمة الخلفية مع كل ما لديك View هيكل. إذا كنت تتعامل مع تغييرات توجيهك على وجه صحيح (ليس configChanges اخترق الناس دائما)، ثم الخاص بك Dialog سيتم إنشاؤه بواسطة النظام. هذا مهم، وهذا يعني أنه على التوجيه التغيير، كل ما تبذلونه Dialogوتذكر أساليب دورة الحياة. لذلك في أي من هذه الأساليب (onCreateDialog عادة ما يكون مكانا جيدا)، يمكنك إجراء مكالمة مثل ما يلي:

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

انظر اتفاقية دورة حياة الشظية لتحديد مكان إعداد أفضل المستمع في تنفيذك الفردي.

هذا نهج عام لتوفير حلا قويا وكاملا للمشكلة العامة المطلوبة في هذا السؤال. ربما يكون هناك عدد قليل من القطع البسيطة المفقودة في هذه الإجابة اعتمادا على سيناريو الفردي الخاص بك، ولكن هذا هو أكثر النهج الصحيح لأحداث تغيير الاتجاه المناولة بشكل صحيح.

لقد وجدت وحل أسهل لمعالجة المواضيع عند تغيير الاتجاه. يمكنك فقط الاحتفاظ بالرجوع إلى نشاط ثابت إلى نشاطك / شظية وتحقق من ما إذا كان خالص قبل التصرف في UI. أقترح باستخدام محاولة التقاط أيضا:

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

كان التحدي هو الاحتفاظ بلقب الحوار ورسالة أثناء استدارة الشاشة أثناء إعادة تعيينها إلى السلسلة الفارغة الافتراضية، على الرغم من أن الحوار لا يزال يظهر

هناك 2 مناهج لحل هذا:

النهج الأول: اجعل النشاط يستخدم مربع الحوار للاحتفاظ بالحالة أثناء تغيير التكوين في ملف بيان:

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

يبدو أنه "سريع وقذرة للغاية" ليكون صحيحا لذا يرجى الإشارة إلى العيوب ولكن ما وجدته يعمل ...

ضمن طريقة OnPostExecute الخاصة بي ASYNCTASK، قمت ببساطة ملفوفة ".DISMISS" الحوار التقدمي في كتلة TRY / CATCK (مع الصيد الفارغ)، ثم تجاهل ببساطة الاستثناء الذي تم رفعه. يبدو خطأ في القيام به ولكن يبدو أنه لا توجد آثار سيئة (على الأقل لما أفعله لاحقا وهو ما هو بدء نشاط آخر يمر في نتيجة استعلام التشغيل الطويل الخاص بي كإضاحية)

أبسط والحل الأكثر مرونة هو استخدام 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();
        }
    }
}

الاستخدام في نشاط 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