代码之家  ›  专栏  ›  技术社区  ›  Heikki Toivonen

当进度对话框和后台线程处于活动状态时,如何处理屏幕方向的更改?

  •  501
  • Heikki Toivonen  · 技术社区  · 17 年前

    我的程序在后台线程中执行一些网络活动。在开始之前,它会弹出一个进度对话框。该对话框在处理程序上被取消。这一切都可以正常工作,除非在对话框打开时屏幕方向发生变化(背景线程正在运行)。此时,应用程序要么崩溃,要么死锁,要么进入一个奇怪的阶段,在所有线程都被杀死之前,应用程序根本无法工作。

    如何优雅地处理屏幕方向的变化?

    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处于部分绘制状态。需要在重新开始工作之前杀死整个应用程序。

    26 回复  |  直到 12 年前
        1
  •  263
  •   Community Mohan Dere    9 年前

    编辑: hackbod )在这个 StackOverflow post this blog post 了解更多信息。


    您必须将其添加到清单中的活动声明中:

    android:configChanges="orientation|screenSize"
    

    看来

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

    问题是,当配置发生更改时,系统会销毁活动。看见 ConfigurationChanges

    因此,将其放在配置文件中可以避免系统破坏您的活动。相反,它调用 onConfigurationChanged(Configuration) 方法

        2
  •  156
  •   haseman    17 年前

    当您切换方向时,Android将创建一个新视图。您可能会崩溃,因为您的后台线程正试图更改旧线程的状态。(它也可能有问题,因为您的后台线程不在UI线程上)

    我建议让mHandler不稳定,并在方向改变时更新它。

        3
  •  69
  •   neonblitzer MakePeaceGreatAgain    7 年前

    我为这些问题提出了一个坚如磐石的解决方案,它符合“安卓方式”。我所有的长期运行操作都使用IntentService模式。

    也就是说,我的活动广播意图,意图服务完成工作,将数据保存在数据库中,然后广播 意图。粘性部分很重要,因此即使活动在用户启动工作后暂停,并且错过了来自IntentService的实时广播,我们仍然可以响应并从调用活动中拾取数据。 ProgressDialog s可以很好地与此模式配合使用 onSaveInstanceState()

    基本上,您需要保存一个标志,表明您在保存的实例包中运行了一个进度对话框。 保存进度对话框对象,因为这将泄漏整个活动。为了获得进度对话框的持久句柄,我将其作为弱引用存储在应用程序对象中。在方向改变或其他任何导致活动暂停(电话、用户点击主页等)然后继续的情况下,我会关闭旧对话框,并在新创建的活动中重新创建一个新对话框。

    对于不确定进度对话框,这很容易。对于进度条样式,您必须将最后一次已知的进度放在bundle中,以及您在活动中本地使用的任何信息,以跟踪进度。在恢复进度时,您将使用此信息在与以前相同的状态下重新生成进度条,然后根据当前状态进行更新。

    总之,将长时间运行的任务放入IntentService,并明智地使用 onSaveInstanceState() 允许您高效地跟踪对话框,然后跨活动生命周期事件进行恢复。活动代码的相关位如下所示。您还需要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));
        }
    }
    
        4
  •  28
  •   neonblitzer MakePeaceGreatAgain    7 年前

    Handler Handler.handleMessage

    经过反复试验,我似乎找到了解决办法。至少现在我可以在线程完成之前或之后随时旋转屏幕。在所有测试中,对话框正确关闭,所有行为都符合预期。

    我所做的如下所示。目标是填充我的数据模型( 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设计的“正确”方法——他们声称这种“在屏幕旋转期间销毁/重新创建活动”实际上让事情变得更容易,所以我想这应该不会太棘手。

    如果您看到我的代码中有问题,请告诉我。如上所述,我真的不知道是否有任何副作用。

        5
  •  15
  •   gymshoe    15 年前

    最初的问题是,代码在屏幕方向改变后无法生存。显然,这是通过让程序自己处理屏幕方向的变化而不是让UI框架(通过调用onDestroy)来“解决”的。

    我认为,如果潜在的问题是程序无法在onDestroy()中生存,那么公认的解决方案只是一种变通方法,它会使程序存在严重的其他问题和漏洞。请记住,Android框架明确指出,由于您无法控制的情况,您的活动几乎随时都有被破坏的风险。因此,无论出于何种原因,您的活动必须能够在onDestroy()和随后的onCreate()中生存,而不仅仅是屏幕方向的更改。

    如果要自己处理屏幕方向更改以解决OP的问题,则需要验证onDestroy()的其他原因是否不会导致相同的错误。你能做到这一点吗?如果不是,我会质疑“被接受”的答案是否真的很好。

        6
  •  14
  •   piotrek1543    5 年前

    我的解决办法是延长期限 ProgressDialog 让我自己去上课 MyProgressDialog .

    我重新定义了 show() dismiss() Dialog 并在需要时将其解锁 对话 他被解雇了。

    对话 调用,然后屏幕方向根据传感器值/设备方向改变。

    这是我的密码:

    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);
        }
    }
    
        7
  •  8
  •   Pzanno    16 年前

    我也遇到了同样的问题,我想出了一个解决方案,这个解决方案不涉及使用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运行得更快,侵入性更小,在我看来,它的外观更好。

        8
  •  7
  •   Heikki Toivonen    16 年前

    我发现了一个在其他地方还没有看到的解决方案。您可以使用一个自定义应用程序对象,该对象知道您是否正在执行后台任务,而不是尝试在方向更改时被销毁和重新创建的活动中执行此操作。我在博客上写了这件事 here .

        9
  •  7
  •   Catalina    11 年前

    我将贡献我的方法来处理这个轮换问题。这可能与OP无关,因为他没有使用 AsyncTask ,但也许其他人会发现它很有用。这很简单,但似乎对我很有用:

    我有一个带有嵌套 班名 BackgroundLoginTask

    在我的 背景调查 我不会做任何不寻常的事情,除了在调用时添加一个空检查 ProgressDialog

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

    Activity 不可见,因此,进度对话框已被 onPause() 方法

    其次,在我的父母身上 活动 类中创建全局静态句柄 异步任务 进度对话框 (修订) ,被嵌套,可以访问以下变量):

    private static BackgroundLoginTask backgroundLoginTask;
    private static ProgressDialog pleaseWaitDialog;
    

    这有两个目的:第一,它允许我的 活动 要始终访问 异步任务 对象,即使是来自新的后旋转活动。第二,它允许我的 背景调查 进度对话框 即使在旋转之后。

    onPause() ,使进度对话框在 正在离开前景(防止丑陋的“强制关闭”崩溃):

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

    onResume() 方法:

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

    Dialog 复现 活动

    以下是整个课程:

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

    我绝不是一个经验丰富的Android开发者,所以请随意评论。

        10
  •  4
  •   Vinay    17 年前

    将长任务移动到单独的类中。将其实现为主题观察者模式。每当创建活动时,注册并在关闭时注销任务类。任务类可以使用AsyncTask。

        11
  •  4
  •   n224576    15 年前

    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;
            }       
        }
    }
    
        12
  •  4
  •   7bluephoenix    13 年前

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

    你也可以试着让我知道它是否适合你

        13
  •  3
  •   kgiannakakis    12 年前

    • 如前所述,将异步任务或线程移动到保留片段 here . 我认为将所有网络调用移动到片段是一种很好的做法。如果您已经在使用片段,可以让其中一个片段负责调用。否则,您可以按照链接文章的建议,创建一个片段来执行请求。
    • 该片段将使用侦听器接口发出任务完成/失败的信号。你不必担心方向的改变。片段将始终具有指向当前活动的正确链接,并且可以安全地恢复“进度”对话框。
    • void showProgressDialog() 方法可以为此目的添加到片段活动侦听器接口。
        14
  •  2
  •   rds    15 年前

    Service 这就完成了所有繁重的工作(tcp请求/响应、解组) View Activity 可以在不泄漏窗口或丢失数据的情况下销毁和重新创建。这允许Android推荐的行为,即 destroy an Activity on each configuration change

    它有点复杂,但它是调用服务器请求、数据预/后处理等的最佳方式。

    服务 将每个请求排队到服务器,这样就可以轻松高效地处理这些事情。

    chapter on Services .

        15
  •  2
  •   beetstra    15 年前

    我有一个实现,它允许在屏幕方向更改时销毁活动,但仍然成功地销毁重新创建的活动中的对话框。 ...NonConfigurationInstance 将后台任务附加到重新创建的活动。 正常的Android框架处理重新创建对话框本身,没有任何更改。

    我将AsyncTask子类化,为“拥有”活动添加了一个字段,以及一个更新此所有者的方法。

    class MyBackgroundTask extends AsyncTask<...> {
      MyBackgroundTask (Activity a, ...) {
        super();
        this.ownerActivity = a;
      }
    
      public void attach(Activity a) {
        ownerActivity = a;
      }
    
      protected void onPostExecute(Integer result) {
        super.onPostExecute(result);
        ownerActivity.dismissDialog(DIALOG_PROGRESS);
      }
    
      ...
    }
    

    在我的活动课上,我添加了一个字段 backgroundTask 引用“拥有的”backgroundtask,我使用 onRetainNonConfigurationInstance getLastNonConfigurationInstance

    class MyActivity extends Activity {
      public void onCreate(Bundle savedInstanceState) {
        ...
        if (getLastNonConfigurationInstance() != null) {
          backgroundTask = (MyBackgroundTask) getLastNonConfigurationInstance();
          backgroundTask.attach(this);
        }
      }
    
      void startBackgroundTask() {
        backgroundTask = new MyBackgroundTask(this, ...);
        showDialog(DIALOG_PROGRESS);
        backgroundTask.execute(...);
      }
    
      public Object onRetainNonConfigurationInstance() {
        if (backgroundTask != null && backgroundTask.getStatus() != Status.FINISHED)
          return backgroundTask;
        return null;
      }
      ...
    }
    

    进一步改进的建议:

    • 背景任务 任务完成后,在活动中引用以释放与其关联的任何内存或其他资源。
    • 清除 ownerActivity 在活动被销毁之前在backgroundtask中引用,以防无法立即重新创建该活动。
    • 创建一个 BackgroundTask
        16
  •  2
  •   Vikas    15 年前

    如果您维护两个布局,所有UI线程都应该终止。

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

    here .

    更新:

        17
  •  2
  •   Lucifer phtrivier    14 年前

    试图实施 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"
    
        18
  •  2
  •   deinier    10 年前

    我也面临同样的情况。我所做的是在整个应用程序中只为我的进度对话框获取一个实例。

    首先,我创建了一个DialogSingleton类,只获取一个实例(Singleton模式)

    public class DialogSingleton
    {
        private static Dialog dialog;
    
        private static final Object mLock = new Object();
        private static DialogSingleton instance;
    
        private DialogSingleton()
        {
    
        }
    
        public static DialogSingleton GetInstance()
        {
            synchronized (mLock)
            {
                if(instance == null)
                {
                    instance = new DialogSingleton();
                }
    
                return instance;
            }
        }
    
        public void DialogShow(Context context, String title)
        {
            if(!((Activity)context).isFinishing())
            {
                dialog = new ProgressDialog(context, 2);
    
                dialog.setCanceledOnTouchOutside(false);
    
                dialog.setTitle(title);
    
                dialog.show();
            }
        }
    
        public void DialogDismiss(Context context)
        {
            if(!((Activity)context).isFinishing() && dialog.isShowing())
            {
                dialog.dismiss();
            }
        }
    }
    

    正如我在这个类中所展示的,我将进度对话框作为属性。每次需要显示进度对话框时,我都会获得唯一的实例并创建一个新的ProgressDialog。

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

    当我完成后台任务时,我再次调用unique实例并关闭其对话框。

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

    我希望有帮助。

        19
  •  2
  •   Community Mohan Dere    9 年前

    如果后台任务只需要在活动处于前台时生存,那么“新”解决方案是托管后台线程(或者,最好是, AsyncTask )在一个 保留碎片 ,如本节所述 developer guide numerous Q&As .

    如果活动因配置更改而被销毁,则保留的片段仍然存在,但是 当活动在后台或后台堆栈中被销毁时。因此,如果出现以下情况,后台任务仍应中断: isChangingConfigurations() onPause() .

        20
  •  2
  •   ssuperczynski Pravesh Jain    10 年前

    我是安卓系统的新手,我试过这个,效果不错。

    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);
            }
    }
    
        21
  •  1
  •   Rui    16 年前

    1. 向用户显示动态信息的进度对话框。例如:“连接到服务器…”、“下载数据…”等。
    2. 执行繁重任务并更新对话框的线程
    3. 最后用结果更新UI。

    问题是,当旋转屏幕时,书上的每个解决方案都失败了。即使使用AsyncTask类,这也是Android处理这种情况的正确方法。旋转屏幕时,开始线程正在使用的当前上下文将消失,这会使显示的对话框混乱。问题总是对话框,不管我在代码中添加了多少技巧(将新上下文传递给正在运行的线程,通过旋转保持线程状态等等)。最后的代码复杂度总是很高,而且总有可能出错。

    唯一对我有效的解决方案是活动/对话技巧。它很简单,很有天赋,而且都是防旋转的:

    1. 不要创建一个对话框并要求显示它,而是创建一个在清单中用android:theme=“@android:style/theme.Dialog”设置的活动。所以,它看起来就像一个对话。

    2. 将showDialog(DIALOG_ID)替换为startActivityForResult(yourActivityDialog,yourCode);

    3. 在调用活动中使用onActivityResult从执行线程(甚至错误)获取结果并更新UI。

    4. 在“ActivityDialog”上,使用线程或AsyncTask执行长任务,并在旋转屏幕时使用OnRetainonConfiguration实例保存“dialog”状态。

    这是快速和工作良好。对于其他任务,我仍然使用对话框,而对于不需要在屏幕上显示恒定对话框的任务,我仍然使用AsyncTask。但在这种情况下,我总是选择活动/对话模式。

    而且,我没有尝试过,但它甚至可以阻止活动/对话框在线程运行时旋转,从而加快速度,同时允许调用活动旋转。

        22
  •  1
  •   Community Mohan Dere    9 年前

    现在有一种更独特的方式来处理这些类型的问题。典型的方法是:

    1.确保您的数据与UI正确分离:

    任何属于后台进程的内容都应该保存在一个 Fragment (将此设置为 Fragment.setRetainInstance() . 这将成为您的“持久数据存储”,您希望保留的任何基于数据的内容都将保存在其中。在方向更改事件之后,此 仍然可以通过 FragmentManager.findFragmentByTag() call(当您创建它时,您应该给它一个标记 不是身份证 因为它没有连接到 View ).

    Handling Runtime Changes

    2.确保后台进程和UI之间的接口正确且安全:

    你必须 颠倒 看法 -取而代之的是你的 应将自身附加到后台进程。这更有意义,对吧?这个 的操作依赖于后台进程,而后台进程不依赖于 看法 。这意味着将链接更改为标准 Listener 界面说出你的过程(无论它是什么类-是否是 AsyncTask Runnable 或其他)定义了 OnProcessFinishedListener ,当进程完成时,它应该调用该侦听器(如果存在)。

    answer

    现在,您必须担心后台任务与当前任务的接口 看法 结构是。如果您正在处理方向更改 正确地 (不是 configChanges 黑客总是推荐),然后你的 Dialog 将由系统重新创建。这很重要,这意味着在方向改变时,你所有的 重新调用的生命周期方法。所以在任何一种方法中( onCreateDialog 通常是一个好地方),您可以执行以下操作:

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

    Fragment lifecycle 用于确定侦听器的设置最适合您的个人实现。

        23
  •  1
  •   Renato Probst    10 年前

    我找到了一个更简单的解决方案来处理方向改变时的线程。您可以只保留对活动/片段的静态引用,并在对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;
        }
    
    
    }
    
        24
  •  1
  •   kpninja12    10 年前

    如果您正在努力检测对话框的方向更改事件 独立于活动引用

    安装程序

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

    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();
    
        25
  •  1
  •   Zain    7 年前

    这是我面对它时的解决方案: 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"
    

    第二种方法: onCreate() 方法,您需要保留 对话片段 通过重建 进展对话片段 再次与标题&如果 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");
          }
      }
    }
    
        26
  •  0
  •   Simon    15 年前

    看起来太“快速和肮脏”了,所以请指出缺陷,但我发现有效的是。。。

    在AsyncTask的onPostExecute方法中,我只是将进程对话框的“.discouse”包装在一个try/catch块中(带有一个空的catch),然后简单地忽略引发的异常。这样做似乎是错误的,但似乎没有不良影响(至少对于我随后正在做的事情,即启动另一个活动,将我长期运行的查询结果作为额外结果传入)

        27
  •  0
  •   Derrick J Wippler    13 年前

    最简单、最灵活的解决方案是使用 AsyncTask 静态引用 ProgressBar . 这为方向更改问题提供了一个封装的、因而可重用的解决方案。这个解决方案很好地解决了各种异步任务,包括internet下载、与服务器通信 Services ,以及文件系统扫描。该解决方案已经在多个android版本和手机型号上进行了良好测试。可以找到完整的演示 here 对……特别感兴趣 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();
        }
    }
    
        28
  •  0
  •   priti    6 年前

    当你改变方向时,Android会终止该活动并创建新的活动。 我建议使用Rx java进行改装。它会自动崩溃。

    改型调用时使用这些方法。