【问题标题】:Custom view isn't being restored after adding it to a layout from AsyncTask's onPostExecute()从 AsyncTask 的 onPostExecute() 将自定义视图添加到布局后,未恢复自定义视图
【发布时间】:2014-01-30 22:30:14
【问题描述】:

我编写了一个相当大的自定义视图,它覆盖了onSaveInstanceState()onRestoreInstanceState(Parcelable state)

我想用我的自定义视图填充一个 LinearLayout,所以我编写了以下代码:

public class MainActivity extends Activity {

    private LinearLayout    mRootLayout;

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

        mRootLayout = (LinearLayout) findViewById(R.id.root_layout);

        int i;

        // Test: adding 10 instances of MyCustomView.
        for (i = 0; i < 10; ++i) {
            MyCustomView cv = new MyCustomView(this);

            // I set an ID for this view so that onSaveInstanceState() and
            // onRestoreInstanceState(Parcelable state) will be called
            // automatically.
            cv.setId(++i);

            mRootLayout.addView(cv);
        }
    }

    // ...
}

它工作正常 - mRootLayout 确实被填充了 10 个 MyCustomView 实例,并且每个 MyCustomView 实例在例如屏幕旋转之后被正确恢复。

我注意到由于MyCustomView 相当大,我的代码在 UI 线程上很重。

为了解决这个问题并减少 UI 线程的工作量,我决定使用自定义 AsyncTask,它将在 doInBackground() 中创建一个 MyCustomView 的实例并将其添加到主布局(mRootLayout ) 在onPostExecute()

以下代码是我自定义的AsyncTask:

private class LoadMyCustomViewTask extends AsyncTask<Void, Void, MyCustomView> {

    private Context         mContext;
    private LinearLayout    mLayoutToPopulate;
    private int             mId;

    public LoadMyCustomViewTask(Context context, LinearLayout layout, int id) {
        mContext = context;
        mLayoutToPopulate = layout;
        mId = id;
    }

    @Override
    protected MyCustomView doInBackground(Void... params) {

        MyCustomView cv = new MyCustomView(mContext);

        return cv;
    }

    @Override
    protected void onPostExecute(MyCustomView result) {

        result.setId(mId);
        mLayoutToPopulate.addView(result);
    }
}

MainActivity我使用如下:

public class MainActivity extends Activity {

    private LinearLayout    mRootLayout;

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

        mRootLayout = (LinearLayout) findViewById(R.id.root_layout);

        int i;

        for (i = 0; i < 10; ++i) {

            new LoadMyCustomViewTask(this, mRootLayout, ++i).execute();
        }
    }
    // ...
}

这段代码也可以,但只有一个问题 - MyCustomView 根本没有被恢复。

出于调试目的,我在MyCustomViewonSaveInstanceState()onRestoreInstanceState(Parcelable state) 中放置了一个Log.d(...),我注意到onRestoreInstanceState(Parcelable state) 没有被调用。

您知道为什么当我使用 AsyncTask 填充 mRootLayout 时没有调用 onRestoreInstanceState(Parcelable state),但是当我在界面线程?

谢谢。

编辑:我发布了MyCustomViewonSaveInstanceState()onRestoreInstanceState() 方法

@Override
protected Parcelable onSaveInstanceState() {
    debug("onSaveInstanceState()");
    Bundle state = new Bundle();
    state.putParcelable(_BUNDLE_KEY_PARENT_STATE, super.onSaveInstanceState());
    state.putBooleanArray(_BUNDLE_KEY_CLICKED_VIEWS, mClickedViews);
    return state;
}

@Override
protected void onRestoreInstanceState(Parcelable state) {
    debug("onRestoreInstanceState(Parcelable state)");
    if (state instanceof Bundle) {
        Bundle bundle = (Bundle) state;
        mClickedViews = bundle.getBooleanArray(_BUNDLE_KEY_CLICKED_VIEWS);
        state = bundle.getParcelable(_BUNDLE_KEY_PARENT_STATE);
    }

    super.onRestoreInstanceState(state);
}

【问题讨论】:

    标签: java android multithreading android-asynctask


    【解决方案1】:

    视图状态恢复从根视图开始,并向下移动到当时附加的所有子视图。这可以在ViewGroup.dispatchRestoreInstanceState 方法中看到。这意味着,只有在调用 Activity.onRestoreInstanceState 时它们是视图层次结构的一部分时,Android 才能恢复您的视图。

    使用AsyncTask,您可以异步创建视图,然后安排它们稍后在主循环器空闲时添加。考虑到生命周期,Android 只让你的AsyncTask.onPostExecuteActivity.onStartActivity.onRestoreInstanceStateActivity.onResume 等被调用后运行。您的视图被添加到布局中为时已晚,无法进行自动恢复。

    如果您将日志语句添加到上述方法以及您的AsyncTask.onPostExecute,您将能够看到实际的排序/时间安排。以下代码在Activity.onRestoreInstanceState 之后运行,即使这一切都发生在主线程上,仅仅是因为调度:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
    
        new Handler().post(new Runnable() {
            @Override
            public void run() {
                Log.i("TAG", "when does this run?");
            }
        });
    
        ...
    }
    

    【讨论】:

    • 你让我很开心,先生。非常感谢您的详细解答!
    • @nylon100 如果有帮助,请奖励赏金 :)
    • 啊,对不起。我以为我接受答案后你会得到奖励。
    【解决方案2】:

    闻起来像是错误的观察...在后台线程上创建视图不应影响您的活动生命周期。

    也就是说,在后台线程上使用 View 对象做任何事情都是不可以的,我很惊讶你用这种方法能走到这一步。所有视图代码都应该快速并避免阻塞。如果您有长时间运行的工作要做,那么将该工作分离到后台线程中,将该复杂计算的结果发布到主线程,并将所有实际的视图/演示内容保留在它所属的主线程上。

    【讨论】:

    • 鲁本您好,感谢您的回答。创建一千多个 MyCustomView 实例是一项长期工作,我决定将其分为两个任务 - 创建和演示。 MyCustomView 的创建是在一个单独的线程中完成的,因为这是更大的任务。该任务正在onPostExectue() 中执行,因为它必须在 UI 线程中完成。 MyCustomView 获取 MainActivity 的一个实例,该实例负责保存该视图并将其显示给用户。其实我看不出问题出在哪里。您能否详细说明一下 - 为什么这是“不可以”?​​span>
    • 对不起,我的意思是:“presentation 任务正在onPostExecute()...中执行”
    • 除非您实际上同时显示 1000 个视图,这似乎不太可能,否则您不需要创建那么多。也许您应该更详细地描述应用程序和自定义视图的目的,并且可以建议更好的方法。至于'no-no',视图应该只由主线程访问/更新,因为这是 Android 确保 UI 一致的方式,而不需要每个视图上的每个方法都需要同步锁定(这将是非常昂贵和缓慢的事情下降很多)。换句话说,如果只有一个线程负责,UI 会更快更好。
    • MyCustomView 是我目前开发的大型应用程序中的一个小块。提供有关其功能的更多详细信息会错过重点,只会让您感到困惑。不过,我可以更具体地说,它是一个 6X10 按钮矩阵,具有相当复杂的嵌入式功能。我实际上使用填充简单列表视图的自定义适配器解决了这个问题。我在这里提出的问题仍然存在,因为我真的不明白为什么会发生这种情况。在单独的线程中创建 MyCustomView 时,是否有我想念的隐藏部分?
    【解决方案3】:

    我记得之前读过onSaveInstanceState()/onRestoreInstanceState() 是如何工作的,这不是“固定情况规则”的事情。由于我暂时找不到它的引用,我将尝试用我自己的话来解释它:

    基本上,取决于调用这些方法的一个因素是设备剩余的资源。当第二个Activity 获得焦点并且第一个由于缺乏资源而被杀死时,这两种方法都会起作用。具体来说,onRestoreInstanceState 应该在 Activity 被杀死并重新启动时触发,因此它会获得以前的状态。

    虽然您尚未发布您的 MyCustomView 实现,但我的猜测是,当您完全在主 UI 线程上执行此操作时,您将涉及一些使 MainActivity 失去焦点并且一旦 @987654327 @ 被创建,它需要恢复它的状态。在单独的线程中执行此操作(如 AsyncTask 所做的那样)会并行创建那些 Views,因此您的主 Activity 不会失去焦点,因此不会被杀死,因此不会调用这些方法.

    结束这一点,如果这些方法并非总是被调用,请不要担心,因为它们不必每次都在需要时调用,这并不意味着出现问题。

    【讨论】:

    • 您好 NKN,感谢您的回答。我更新了代码以包含MyCustomViewonSaveInstanceState()onRestoreInstanceState()。我想再次提一下,它们可以很好地协同工作,问题仅在于在后台线程中创建 MyCustomView 时(然而,它必须在onPostExecute()添加)。您的回答很有趣,但我正在寻找不完全在 UI 线程中创建 MyCustomView 时所涉及的 特定 操作。
    【解决方案4】:

    我建议您将 SDK 源连接到您的 IDE,并使用调试器完成与 ViewonRestoreInstanceState() 相关的整个过程。由于我没有您的代码,因此查看来源我只能猜测可能出了什么问题,但据我所见,这可能与问题有关:

    1) 尝试为您生成的每个视图设置一个 ID

    2) 尝试使用 Fragment 作为视图的宿主(而不是 MainActivity)。

    【讨论】:

    • 你好,德米德。我已经尝试了两者。为每个孩子设置了一个 id,并将MainActivity 替换为ListFragment,但问题仍然存在。是的,我将使用调试器完成它。谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多