Android视图在屏幕方向改变时被重置。

11 浏览
0 Comments

Android视图在屏幕方向改变时被重置。

我的程序在后台线程中进行一些网络活动。在开始之前,它会弹出一个进度对话框。对话框在handler中被关闭。这一切都运行良好,除了在对话框弹出时(并且后台线程正在运行)屏幕方向发生变化的情况下。此时,应用程序要么崩溃,要么死锁,要么进入一种奇怪的状态,直到所有线程都被终止才能正常工作。\n我该如何优雅地处理屏幕方向的改变?\n下面的示例代码大致与我的真实程序相匹配:\n

public class MyAct extends Activity implements Runnable {
    public ProgressDialog mProgress;
    // UI有一个按钮,按下按钮调用send方法
    public void send() {
         mProgress = ProgressDialog.show(this, "请稍候", 
                      "请稍候", 
                      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();
        }
    };
}

\n堆栈:\n

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

\n我尝试在onSaveInstanceState中关闭进度对话框,但这只是防止了立即崩溃。后台线程仍在运行,UI处于部分绘制状态。需要在应用程序开始工作之前将整个应用程序关闭。

0
0 Comments

在Android中,当屏幕方向改变时,视图会被重置。这个问题的出现原因是因为屏幕方向改变时,Activity会被销毁并重新创建,导致视图被重置。解决这个问题的方法是在Activity生命周期中保存和恢复视图的状态。

为了解决这个问题,可以使用IntentService模式来处理长时间运行的操作。在这个模式下,Activity发送广播意图,IntentService执行工作,将数据保存在数据库中,然后发送“sticky”意图。这里的“sticky”意图很重要,即使Activity在用户发起工作后的一段时间内被暂停,并且错过了来自IntentService的实时广播,我们仍然可以从调用Activity中响应和获取数据。ProgressDialog可以很好地与这种模式配合使用,并且可以使用onSaveInstanceState()方法来保存进度对话框的状态。

基本上,你需要在保存实例状态的bundle中保存一个标志,表示进度对话框正在运行。不要保存进度对话框对象,因为这样会导致整个Activity泄漏。为了持久地持有进度对话框的句柄,我将其作为弱引用存储在应用程序对象中。在屏幕方向改变或其他导致Activity暂停(电话呼叫、用户按下Home键等)然后恢复的情况下,我会关闭旧的对话框并在新创建的Activity中重新创建一个新的对话框。

对于无限进度对话框,这很容易实现。对于进度条样式,你需要将最后已知的进度放入bundle中,并将你在Activity中使用的任何本地信息保存在活动中以跟踪进度。在恢复进度时,你将使用这些信息以与之前相同的状态重新生成进度条,然后根据当前状态进行更新。

为了总结,将长时间运行的任务放入IntentService中,并巧妙地使用onSaveInstanceState()方法,可以有效地跟踪对话框并在Activity生命周期事件中恢复它们。下面是相关的Activity代码。你还需要在BroadcastReceiver中添加逻辑来适当处理Sticky意图,但这超出了本文的范围。

public void doSignIn(View view) {
    waiting=true;
    AppClass app=(AppClass) getApplication();
    String logingon=getString(R.string.signon);
    app.Dialog=new WeakReference(ProgressDialog.show(AddAccount.this, "", logingon, true));
    ...
}
protected void onSaveInstanceState(Bundle saveState) {
    super.onSaveInstanceState(saveState);
    saveState.putBoolean("waiting",waiting);
}
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.show(AddAccount.this, "", logingon, true));
    }
}

“我将所有的长时间运行的操作都使用IntentService模式。”这并不是一个完美的解决方案,因为它会带来大量样板代码。更多的信息可以在youtube.com/watch?v=NJsq0TU0qeg上观看。

0
0 Comments

Android屏幕方向切换时,会创建一个新的视图。您可能会遇到崩溃,因为后台线程正在尝试更改旧视图的状态。解决方法是将mHandler声明为volatile,并在屏幕方向切换时更新它。

您可能已经找到了崩溃的原因。我解决了崩溃问题,但我还没有找到可靠的方法将UI恢复到屏幕方向改变之前的状态。但是您的答案让我有所进展,所以我将其作为答案。

当屏幕方向改变时,您的活动应该会收到onStart回调。在这种情况下,您需要使用旧数据重新配置视图。建议在收到新的“onStart”回调时请求进度条的数值状态更新,并在收到更新时重新构建一个新的视图。我记不清楚是否还会收到新的活动,但是查阅文档应该能帮助您找到答案。

最近我尝试过这个功能,我可以告诉您,当应用程序更改方向时,您会得到一个新的活动(也会得到一个新的视图)。如果您尝试更新旧视图,则会抛出异常,因为旧视图具有无效的应用程序上下文(旧活动)。您可以通过传递myActivity.getApplicationContext()而不是指向活动本身的指针来解决这个问题。

有人能解释一下在这种情况下volatile的用法/好处吗?

是的,我也想知道。如果有人能解释一下volatile的用法,那将非常好。

0
0 Comments

在Android中,当屏幕方向改变时,视图会被重置。这个问题的出现原因是系统在配置发生变化时会销毁活动,并重新创建活动。为了避免系统销毁活动,可以在清单文件中的活动声明中添加android:configChanges="orientation|screenSize"。这样系统就不会销毁活动,而是调用onConfigurationChanged(Configuration)方法。这是解决这个问题的最佳方法,因为它只是简单地旋转布局。需要注意的是,如果手机具有横向键盘,还需要添加keyboardHidden。尽管有其他配置改变的原因,但这个方法仍然是一个常见的解决方案。此外,还有其他一些解决方法,但这个方法是最简单和最有效的。尽管Google工程师不推荐使用这种方法,但在某些情况下,它仍然是一个很好的解决方案。

0