我的程序在背景线程中进行一些网络活动。在开始之前,它弹出了一个进度对话框。对话在处理程序上被驳回。这一切都很好,除非在对话框启动时屏幕方向更改(背景线程正在运行)。此时,该应用程序要么崩溃,要么僵局,要么进入一个怪异的阶段,直到所有线程都被杀死之前,该应用程序根本不起作用。

如何处理屏幕方向如何优雅地更改?

下面的示例代码大致匹配我的真实程序:

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

    // UI has a button that when pressed calls send

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

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

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

堆:

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

我试图驳回Onsaveinstancestate中的进度对话,但这只是防止立即崩溃。背景线程仍在继续,UI处于部分绘制状态。需要在整个应用程序再次开始工作之前杀死整个应用程序。

有帮助吗?

解决方案

当您切换方向时,Android将创建一个新视图。您可能会崩溃,因为您的背景线程正在尝试更改旧状态。 (因为您的背景线程不在UI线程上,也可能会遇到麻烦)

我建议在方向变化时使Mhandler挥发性易变并进行更新。

其他提示

编辑: Google工程师不推荐这种方法,如Dianne Hackborn(aka)所述 hackbodStackoverflow帖子. 。查看 这篇博客文章 了解更多信息。


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

android:configChanges="orientation|screenSize"

所以看起来像

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

问题在于,当配置发生更改时,系统会破坏活动。看 configurationChanges.

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

我为这些问题提出了一个岩石固定的解决方案,这些解决方案符合“ Android方式”。我使用意见服务模式进行了所有长期运行的操作。

也就是说,我的活动播放意图,意图服务可以完成工作,将数据保存在数据库中,然后广播 意图。粘性部分很重要,因此,即使在用户启动工作后的时间内暂停了活动,并错过了从IntentService中进行实时广播,我们仍然可以响应并从调用活动中获取数据。 ProgressDialogS可以很好地使用这种模式 onSaveInstanceState().

基本上,您需要保存一个标志,该标志在保存的实例捆绑包中运行了一个进度对话框。 不要 保存进度对话框对象,因为这会泄漏整个活动。为了对“进度对话”进行持续的句柄,我将其作为弱参考存储在应用程序对象中。在方向更改或其他任何导致活动暂停的任何事物(电话,用户击中家园等),然后恢复,我解散了旧的对话框,并在新创建的活动中重新创建了一个新的对话框。

对于不确定的进度对话,这很容易。对于Progress Bar样式,您必须将最后一个已知的进度放在捆绑包中,以及您在活动中本地使用的任何信息,以跟踪进度。在恢复进度时,您将使用此信息与以前相同的状态重新产生进度栏,然后根据事物的当前状态更新。

因此,总而言之,将长期运行的任务置于意外服务,并明智地使用 onSaveInstanceState() 让您有效地跟踪对话框并恢复,然后在整个活动生命周期事件中进行恢复。活动代码的相关位在下面。您还需要在广播员中需要逻辑来适当处理粘性意见,但这超出了此范围。

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

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

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

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

我遇到了同样的问题。我的活动需要从URL中解析一些数据,并且很慢。因此,我创建一个线程来做到这一点,然后显示一个进度对话框。我让线程通过 Handler 完成后。在 Handler.handleMessage, ,我从线程中获取数据对象(立即准备就绪)并将其填充到UI。因此,这与您的示例非常相似。

经过大量的反复试验,看起来我找到了解决方案。至少现在,我可以随时在螺纹完成之前或之后旋转屏幕。在所有测试中,对话框都正确闭合,并且所有行为均为预期。

我所做的如下所示。目标是填写我的数据模型(mDataObject),然后将其填充到UI。应随时允许屏幕旋转毫无意外。

class MyActivity {

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

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

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

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

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

class MyThread extends Thread {
    Handler mHandler;
    MyDataObject mDataObject;

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

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

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

那就是对我有用的。我不知道这是否是Android设计的“正确”方法 - 他们声称这种“销毁/重现屏幕旋转期间的活动”实际上使事情变得更容易,所以我想这不应该太棘手。

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

最初的感知问题是,该代码无法在屏幕方向更改中幸存下来。显然,这是通过让程序处理屏幕方向更改本身来“解决”的,而不是让UI框架执行此操作(通过调用OnDestroy)。

我会认为,如果根本问题是该计划无法在OnDestroy()中幸存下来,那么公认的解决方案只是一种解决方法,使该计划遇到了严重的其他问题和脆弱性。请记住,Android框架专门指出,由于您的控制情况,您的活动几乎在任何时候都有被摧毁的风险。因此,由于任何原因,您的活动必须能够在ondestroy()和随后的ongreate()中生存,而不仅仅是屏幕取向更改。

如果您要接受处理屏幕方向改变自己以解决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>

然后在ongreate方法中执行以下操作

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,那么此答案不适合您。

我发现了我在其他地方尚未见过的解决方案。您可以使用一个自定义的应用程序对象,该对象知道您是否有背景任务,而不是尝试在销毁和重新创建方向更改的活动中执行此操作。我在此博客 这里.

我将为处理此轮换问题做出贡献。这可能与OP不使用,因为他不使用 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();
            } 
        }
        }

    }
}

我绝不是经验丰富的Android开发人员,因此请随时发表评论。

将长任务移至单独的班级。将其作为主题观察者模式实现。每当创建活动寄存器时,并在与任务类关闭时关闭时。任务类可以使用asynctask。

诀窍是在OnPreeXecute/Onpostexecute中像往常一样在异步箱中显示/解开对话框,尽管如果在Active中进行方向变化 - 变化创建/显示对话框中的新实例,并将其引用到任务中。

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

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

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

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


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



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


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


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

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


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

我这样做了:

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

    public class ThreadActivity extends Activity {


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

            @Override
            public void handleMessage(Message msg) {

                super.handleMessage(msg);

                dialog.dismiss();

            }

        };

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

        }

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

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

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

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

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


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

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

                } finally {

                }
            }
        }

    }

您也可以尝试让我知道它适合您是否有效

如果您创建背景 Service 这可以完成所有繁重的举重(TCP请求/响应,删除), ViewActivity 可以在不泄漏窗口或丢失数据的情况下破坏和重新创建。这允许Android推荐的行为,即 破坏每个配置更改上的活动 (例如,对于每个方向更改)。

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

您甚至可以使用您的 Service 将每个请求排队到服务器,因此可以轻松有效地处理这些内容。

开发指南有一个完整的 一章 Services.

我有一个实现,可以在屏幕方向更改上破坏活动,但仍然成功地破坏了重新创建活动中的对话框。我用 ...NonConfigurationInstance 将背景任务附加到重新创建的活动中。普通的Android框架处理重新创建对话框本身,那里没有任何更改。

我对“拥有”活动的字段添加了一个字段,并将其分类为“拥有”活动,并添加了更新此所有者的方法。

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 参考“拥有”的背景任务,我使用 onRetainNonConfigurationInstancegetLastNonConfigurationInstance.

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,请在“取消任务”部分中阅读更多信息 这里.

更新:添加条件以检查状态,因为仅在运行状态时才能取消状态。另请注意,异步只能执行一次。

试图实施 jfelectron的解决方案是因为它是岩石固定的解决方案解决了与事物的“ Android”方式符合的问题“但是花了一些时间抬头抬起头来,把所有提到的元素放在一起。最终以这种略有不同的速度,我认为在这里发布了更优雅的解决方案。

使用从活动发射的意向服务来执行单独线程上的长期运行任务。该服务向更新对话框的活动发射了粘性广播意见。该活动使用ShowDialog(),onCreatedialog()和onpreparedialog()消除了在应用程序对象或SavedInstancestate Bundle中传递持久数据的需求。无论您的应用程序如何中断,这都应该起作用。

活动类:

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

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

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

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

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

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

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

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

意识服务类:

public class MyService extends IntentService {

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

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

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

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

清单文件条目:

申请部分:

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

内部申请部分

service android:name=".MyService"

这是我建议的解决方案:

  • 如解释 这里. 。我相信将所有网络调用转移到碎片是一种好习惯。如果您已经在使用片段,则可以使其中一个负责呼叫。否则,您可以像链接的文章所建议的那样创建一个用于执行请求的片段。
  • 片段将使用侦听器接口来发出任务完成/失败的信号。您不必担心那里的方向更改。片段将始终具有与当前活动的正确链接,并且可以安全地恢复进度对话框。
  • 使您的进度对话成为班级的成员。实际上,您应该为所有对话做到这一点。在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();
        }
    }
}

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

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

完成背景任务后,我再次致电唯一实例并解散其对话框。

DialogSingleton.GetInstance().DialogDismiss(this);

我将背景任务状态保存在我的共享首选项中。当我旋转屏幕时,我询问是否有执行此活动的任务:( ongreate)

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

我在Android中是个新鲜事物,我尝试了这一点,并且奏效了。

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

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

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

我已经尝试了一切。花了几天的实验。我不想阻止活动旋转。我的情况是:

  1. 进度对话框向用户显示动态信息。例如:“连接到服务器...”,“下载数据...”,等。
  2. 线程进行重型工作并更新对话框
  3. 最终更新UI的UI。

问题是,在旋转屏幕时,本书上的每个解决方案都失败了。即使使用异步类别,这是处理这种情况的正确方法。旋转屏幕时,启动线程正在使用的当前上下文已经消失,并且与所显示的对话框混乱。问题始终是对话框,无论我添加了多少个技巧(将新上下文传递到运行线程,通过旋转等线程状态等线等等)。最后,代码复杂性总是很大,总会出现问题。

对我有用的唯一解决方案是活动/对话技巧。这很简单而天才,这都是旋转的证明:

  1. 与其创建对话框并要求展示它,不如创建一个用Android中设置的活动:theme =“@android:style/theme.dialog”。因此,它看起来像是对话框。

  2. 用StartActivityForresult(YourActivityDialog,yourscode)替换Showdialog(dialog_id);

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

  4. 在您的“ ActivationDialog”上,使用线程或异步性执行长任务并在旋转屏幕时保存“对话框”状态。

这很快,正常工作。我仍然将对话框用于其他任务,并将异步性用于不需要在屏幕上恒定对话框的内容。但是,在这种情况下,我总是选择活动/对话模式。

而且,我没有尝试过,但是甚至有可能阻止该活动/对话框旋转,线程运行时,加速事物,同时允许调用活动旋转。

如今,有一种更明显的方法来处理这些类型的问题。典型的方法是:

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

任何是背景过程都应保留的 Fragment (用 Fragment.setRetainInstance(). 。这将成为您的“持续数据存储”,其中保留了您想要保留的任何基于数据的东西。定向更改事件之后,此 Fragment 仍然可以通过A的原始状态访问 FragmentManager.findFragmentByTag() 致电(创建它时,应该给它一个标签 没有ID 因为它没有附着在 View).

看到 处理运行时更改 开发了有关正确执行此操作以及为什么是最佳选择的信息指南。

2.确保您正确可安全地在背景过程和UI之间进行安全接口:

你必须 撤销 您的链接过程。目前,您的背景过程将自己附加到 View - 而不是你 View 应该将自己附加到背景过程中。这更有意义吗?这 View的操作取决于背景过程,而背景过程不取决于 View。这意味着将链接更改为标准 Listener 界面。说您的过程(无论是什么课 - 是否是 AsyncTask, Runnable 或其他)定义 OnProcessFinishedListener, ,完成该过程后,如果存在该过程,则应调用该过程。

这个 回答 是关于如何进行自定义听众的精美描述。

3.只要创建UI(包括方向更改),将UI链接到数据过程中:

现在您必须担心将背景任务与当前 View 结构是。如果您要处理方向更改 适当地 (不是 configChanges 黑客总是推荐),然后您 Dialog 将由系统重新创建。这很重要,这意味着在方向变化,所有 Dialog召回了生命周期方法。因此,在任何这些方法中onCreateDialog 通常是一个好地方),您可以按以下电话打个电话:

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

看到 碎片生命周期 为了确定设置侦听器最适合您个人实施的位置。

这是为在这个问题中提出的通用问题提供强大而完整的解决方案的一般方法。根据您的个人情况,这个答案可能缺少一些小部分,但这通常是正确处理方向更改事件的最正确方法。

我发现了方向更改时,可以更轻松地处理线程。您只需对您的活动/片段进行静态引用,并在作用在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- walldialog.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();
        }
    }
}

挑战是将对话框标题和消息保留在屏幕旋转时,将它们重置为默认的空字符串,尽管对话框仍显示

有两种解决此问题的方法:

第一种方法: 使使用对话框在Contract更改中保留状态的活动在清单文件中保留状态:

android:configChanges="orientation|screenSize|keyboardHidden"

Google并非首选此方法。

第二种方法:关于活动的 onCreate() 方法,您需要保留您的 DialogFragment 通过重建 ProgressDialogFragment 同样,标题和消息如下 savedInstanceState 不是null:

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

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

似乎太“快速又脏了”,以至于无法正确,所以请指出缺陷,但我发现有效的是...

在我的异步函数的OnPostExecute方法中,我只是将“ .dismiss”包裹在try/Catch块中的进度对话框(带有空的捕获量),然后简单地忽略了提出的异常。似乎做错了,但似乎没有任何不良影响(至少对于我后来所做的事情,这是在我长期运行的查询结果中开始另一个活动,

最简单,最灵活的解决方案是使用 异型 静态引用 进度条. 。这为方向变更问题提供了一个包封的,因此可以重复使用的解决方案。该解决方案为我提供了不同的异步任务,包括互联网下载,与 服务, 和文件系统扫描。该解决方案已在多个Android版本和电话模型上进行了很好的测试。可以找到一个完整的演示 这里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