- mongodb - 在 MongoDB mapreduce 中,如何展平值对象?
- javascript - 对象传播与 Object.assign
- html - 输入类型 ="submit"Vs 按钮标签它们可以互换吗?
- sql - 使用 MongoDB 而不是 MS SQL Server 的优缺点
当您在 Fragment
上调用 setRetainInstance(true)
时,究竟会发生什么?文档几乎不存在,这似乎是一个非常重要的功能。具体来说,我想知道这个序列(我编造的)有多少是真的:
- The user rotates the device.
- The fragment is detached from the
Activity
andFragment.onDetach()
is called.- The activity is destroyed;
Activity.onDestroy()
is called.- The
Activity
java object is deleted (when possible, by the GC).- A new
Activity
java object is created; its constructor, andonCreate()
are called.- In
Activity.onCreate()
we either havesetContentView(...)
which sets a layout containing a fragment, or we useFragmentTransaction
to add a fragment.- I'm really not sure about this, but I assume that android is smart enough to find the old fragment, and call
Fragment.onAttach()
to reattach it to the newActivity
- Next (or before? who knows?)
Activity.onResume()
is called.
那是正确的吗?即使我第一次明确使用 FragmentTransaction.add(new MyFragment(), ...)
,Android 是否足够智能以找到旧 fragment ?如果是这样,我该如何避免在 onCreate()
中添加 another fragment ?我需要这样做吗?:
if (getSupportFragmentManager().findFragmentByTag("foo") == null)
{
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(new FooFragment(), "foo").commit();
}
最佳答案
好吧,也许我对 Android 文档有点太苛刻了,因为它确实有一些有用的信息,但遗憾的是,这些信息都没有与 setRetainInstance()
链接。来自 the page about fragments
Note: Each fragment requires a unique identifier that the system can use to restore the fragment if the activity is restarted (and which you can use to capture the fragment to perform transactions, such as remove it). There are three ways to provide an ID for a fragment:
- Supply the android:id attribute with a unique ID.
- Supply the android:tag attribute with a unique string.
- If you provide neither of the previous two, the system uses the ID of the container view.
这强烈暗示如果您在 Activity.onCreated()
中执行 setContentView(R.layout.whatever)
并且该布局包含带有 setRetainInstance( true)
,然后在重新创建 Activity 时,将使用其 id 或标签再次搜索它。
其次,对于无 UI 的 fragment ,它声明
To add a fragment without a UI, add the fragment from the activity using add(Fragment, String) (supplying a unique string "tag" for the fragment, rather than a view ID). This adds the fragment, but, because it's not associated with a view in the activity layout, it does not receive a call to onCreateView(). So you don't need to implement that method.
文档链接到一个非常好的示例 - FragmentRetainInstance.java
为了您的方便,我在下面复制了该示例。它完全符合我推测的问题的答案 (if (...findFragmentByTag() == null) { ...
)。
最后,我创建了自己的测试 Activity 来准确查看调用了哪些函数。当您从纵向开始并旋转到横向时,它会输出此信息。代码如下。
(为了便于阅读,稍作修改。)
TestActivity@415a4a30: this()
TestActivity@415a4a30: onCreate()
TestActivity@415a4a30: Existing fragment not found.
TestFragment{41583008}: this() TestFragment{41583008}
TestFragment{41583008}: onAttach(TestActivity@415a4a30)
TestFragment{41583008}: onCreate()
TestFragment{41583008}: onCreateView()
TestFragment{41583008}: onActivityCreated()
TestActivity@415a4a30: onStart()
TestFragment{41583008}: onStart()
TestActivity@415a4a30: onResume()
TestFragment{41583008}: onResume()
<rotate device>
TestFragment{41583008}: onPause()
TestActivity@415a4a30: onPause()
TestFragment{41583008}: onStop()
TestActivity@415a4a30: onStop()
TestFragment{41583008}: onDestroyView()
TestFragment{41583008}: onDetach()
TestActivity@415a4a30: onDestroy()
TestActivity@415a3380: this()
TestFragment{41583008}: onAttach(TestActivity@415a3380)
TestActivity@415a3380: onCreate()
TestActivity@415a3380: Existing fragment found.
TestFragment{41583008}: onCreateView()
TestFragment{41583008}: onActivityCreated()
TestActivity@415a3380: onStart()
TestFragment{41583008}: onStart()
TestActivity@415a3380: onResume()
TestFragment{41583008}: onResume()
请注意,Android 文档是错误的:无 UI fragment 确实 收到了对 onCreateView()
的调用,但它可以自由返回 空
。
TestActivity
/TestFragment
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.concentriclivers.ss.R;
// An activity for understanding Android lifecycle events.
public class TestActivity extends Activity
{
private static final String TAG = TestActivity.class.getSimpleName();
public TestActivity()
{
super();
Log.d(TAG, this + ": this()");
}
protected void finalize() throws Throwable
{
super.finalize();
Log.d(TAG, this + ": finalize()");
}
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Log.d(TAG, this + ": onCreate()");
TextView tv = new TextView(this);
tv.setText("Hello world");
setContentView(tv);
if (getFragmentManager().findFragmentByTag("test_fragment") == null)
{
Log.d(TAG, this + ": Existing fragment not found.");
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.add(new TestFragment(), "test_fragment").commit();
}
else
{
Log.d(TAG, this + ": Existing fragment found.");
}
}
@Override
public void onStart()
{
super.onStart();
Log.d(TAG, this + ": onStart()");
}
@Override
public void onResume()
{
super.onResume();
Log.d(TAG, this + ": onResume()");
}
@Override
public void onPause()
{
super.onPause();
Log.d(TAG, this + ": onPause()");
}
@Override
public void onStop()
{
super.onStop();
Log.d(TAG, this + ": onStop()");
}
@Override
public void onDestroy()
{
super.onDestroy();
Log.d(TAG, this + ": onDestroy()");
}
public static class TestFragment extends Fragment
{
private static final String TAG = TestFragment.class.getSimpleName();
public TestFragment()
{
super();
Log.d(TAG, this + ": this() " + this);
}
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Log.d(TAG, this + ": onCreate()");
setRetainInstance(true);
}
@Override
public void onAttach(final Activity activity)
{
super.onAttach(activity);
Log.d(TAG, this + ": onAttach(" + activity + ")");
}
@Override
public void onActivityCreated(Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
Log.d(TAG, this + ": onActivityCreated()");
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
Log.d(TAG, this + ": onCreateView()");
return null;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState)
{
super.onViewCreated(view, savedInstanceState);
Log.d(TAG, this + ": onViewCreated()");
}
@Override
public void onDestroyView()
{
super.onDestroyView();
Log.d(TAG, this + ": onDestroyView()");
}
@Override
public void onDetach()
{
super.onDetach();
Log.d(TAG, this + ": onDetach()");
}
@Override
public void onStart()
{
super.onStart();
Log.d(TAG, this + ": onStart()");
}
@Override
public void onResume()
{
super.onResume();
Log.d(TAG, this + ": onResume()");
}
@Override
public void onPause()
{
super.onPause();
Log.d(TAG, this + ": onPause()");
}
@Override
public void onStop()
{
super.onStop();
Log.d(TAG, this + ": onStop()");
}
@Override
public void onDestroy()
{
super.onDestroy();
Log.d(TAG, this + ": onDestroy()");
}
}
}
FragmentRetainInstance.java
的源代码(从 API 16 开始):/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.apis.app;
import com.example.android.apis.R;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;
/**
* This example shows how you can use a Fragment to easily propagate state
* (such as threads) across activity instances when an activity needs to be
* restarted due to, for example, a configuration change. This is a lot
* easier than using the raw Activity.onRetainNonConfiguratinInstance() API.
*/
public class FragmentRetainInstance extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// First time init, create the UI.
if (savedInstanceState == null) {
getFragmentManager().beginTransaction().add(android.R.id.content,
new UiFragment()).commit();
}
}
/**
* This is a fragment showing UI that will be updated from work done
* in the retained fragment.
*/
public static class UiFragment extends Fragment {
RetainedFragment mWorkFragment;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_retain_instance, container, false);
// Watch for button clicks.
Button button = (Button)v.findViewById(R.id.restart);
button.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mWorkFragment.restart();
}
});
return v;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
FragmentManager fm = getFragmentManager();
// Check to see if we have retained the worker fragment.
mWorkFragment = (RetainedFragment)fm.findFragmentByTag("work");
// If not retained (or first time running), we need to create it.
if (mWorkFragment == null) {
mWorkFragment = new RetainedFragment();
// Tell it who it is working with.
mWorkFragment.setTargetFragment(this, 0);
fm.beginTransaction().add(mWorkFragment, "work").commit();
}
}
}
/**
* This is the Fragment implementation that will be retained across
* activity instances. It represents some ongoing work, here a thread
* we have that sits around incrementing a progress indicator.
*/
public static class RetainedFragment extends Fragment {
ProgressBar mProgressBar;
int mPosition;
boolean mReady = false;
boolean mQuiting = false;
/**
* This is the thread that will do our work. It sits in a loop running
* the progress up until it has reached the top, then stops and waits.
*/
final Thread mThread = new Thread() {
@Override
public void run() {
// We'll figure the real value out later.
int max = 10000;
// This thread runs almost forever.
while (true) {
// Update our shared state with the UI.
synchronized (this) {
// Our thread is stopped if the UI is not ready
// or it has completed its work.
while (!mReady || mPosition >= max) {
if (mQuiting) {
return;
}
try {
wait();
} catch (InterruptedException e) {
}
}
// Now update the progress. Note it is important that
// we touch the progress bar with the lock held, so it
// doesn't disappear on us.
mPosition++;
max = mProgressBar.getMax();
mProgressBar.setProgress(mPosition);
}
// Normally we would be doing some work, but put a kludge
// here to pretend like we are.
synchronized (this) {
try {
wait(50);
} catch (InterruptedException e) {
}
}
}
}
};
/**
* Fragment initialization. We way we want to be retained and
* start our thread.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Tell the framework to try to keep this fragment around
// during a configuration change.
setRetainInstance(true);
// Start up the worker thread.
mThread.start();
}
/**
* This is called when the Fragment's Activity is ready to go, after
* its content view has been installed; it is called both after
* the initial fragment creation and after the fragment is re-attached
* to a new activity.
*/
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Retrieve the progress bar from the target's view hierarchy.
mProgressBar = (ProgressBar)getTargetFragment().getView().findViewById(
R.id.progress_horizontal);
// We are ready for our thread to go.
synchronized (mThread) {
mReady = true;
mThread.notify();
}
}
/**
* This is called when the fragment is going away. It is NOT called
* when the fragment is being propagated between activity instances.
*/
@Override
public void onDestroy() {
// Make the thread go away.
synchronized (mThread) {
mReady = false;
mQuiting = true;
mThread.notify();
}
super.onDestroy();
}
/**
* This is called right before the fragment is detached from its
* current activity instance.
*/
@Override
public void onDetach() {
// This fragment is being detached from its activity. We need
// to make sure its thread is not going to touch any activity
// state after returning from this function.
synchronized (mThread) {
mProgressBar = null;
mReady = false;
mThread.notify();
}
super.onDetach();
}
/**
* API for our UI to restart the progress thread.
*/
public void restart() {
synchronized (mThread) {
mPosition = 0;
mThread.notify();
}
}
}
}
关于android - 进一步理解 setRetainInstance(true),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12640316/
我正在尝试使用 setRetainInstance(),但它似乎对我不起作用! =(。我将问题简化为最简单的版本: 我有一个带有 TextView 和 Button 的 fragment 。 Text
我正在使用无 View fragment 在我的 Activity 方向更改期间存储一些数据。它看起来大致是这样的: public class BoardActivity extends BaseAc
我有一个 DialogFragment 附加到 Activity,当屏幕旋转时,Activity 被销毁,DialogFragment 也被销毁,我想保留 DialogFragment 和我的Dial
假设我有一个 fragment A。它有一个实例变量 mViewPager 指向它的 ViewPager。 在 Fragment A 的 onCreate(Bundle) 中,我调用了 setReta
当您在 Fragment 上调用 setRetainInstance(true) 时,究竟会发生什么?文档几乎不存在,这似乎是一个非常重要的功能。具体来说,我想知道这个序列(我编造的)有多少是真的:
我正在使用一个具有 SearchActivity 的应用程序;搜索 Activity 只是“SearchFragment”的容器,“SearchFragment”在ActionBar中包含一个Sear
这让我受够了。无论我如何尝试,我都无法在设备旋转时保留 textview 的状态。 示例如下: activity_main.xml RotationFragmentDemo.
我用的是ListFragment 设置保留实例(真);不工作,我不知道为什么 savedInstanceState == null 我的资源 public class Fragment_Left
如果我有一个 Activity 承载一个带有 setRetainInstance(true) 的 Fragment 如果这个 Activity 在后台被杀死会发生什么,即另一个 Activity 在它
对所有 Fragment 使用 Fragment.setRetainInstance() 以摆脱处理 Fragment 是一种好习惯吗娱乐、保存实例状态等?为什么不呢? 最佳答案 是的,如果它们必须保
我试图在方向更改时保存一个 ListFragment 子类,因此我将 setRetainInstance(true) 添加到我的 onCreate 函数的末尾。我添加了一个 onSaveInstanc
背景 我有一个 fragment 的 Activity ,在创建时需要动画,但在方向改变时不需要。 fragment 被动态插入到布局中,因为它是抽屉导航式 Activity 的一部分。 问题 我想避
Here , Dianne 说通过 onRetainNonConfigurationInstance() 保留对象的旧方法现在已经过时了,因为您可以在配置更改时保留 Fragment 实例。 还有 h
我发现 Fragment#setRetainInstance(true) 令人困惑。这是从 Android Developer API 中提取的 Javadoc : public void setRe
我创建了一个包含4个片段的底部导航项目,并将setHasOptionsMenu(true)放在qponFragment的onCreate()中,以便在切换Fragments之后保持qponFragme
从文档中得知,setRetainInstance(boolean) 方法有助于在设备配置更改时保留所有 Activity 对象,但我阅读的大多数示例和文档都涉及 headless fragment 。
我通过替换在多个 fragment 之间切换。 fragment 也被添加到返回堆栈。 现在我希望在方向改变时不再创建 fragment 。我将 setRetainInstance(true) 放在它
我正在学习 Google 提供的高级 android 开发培训。这是 link .我遇到了这个:Added a static fragment in xml 并且在旋转设备时,所选单选按钮的状态将保留
我有一个包含单个子 fragment 的父 fragment , fragment 的 onAttach 和 onCreateView 方法都被记录下来用于调试目的。 当我以这种方式将父 fragme
如果我在这方面有任何错误,请纠正我。这是一个澄清问题,因为我没有看到它在任何地方明确写过。 在 Android 4 中,您可以在 Fragment 上调用 setRetainInstance(true
我是一名优秀的程序员,十分优秀!