Nexus 6 设备和模拟器之间的不同后台行为
Different backstack behavior between Nexus 6 device and emulator
我正在开发一个应用程序,它支持 Fragment
,带有 SearchView
小部件和 RecyclerView
来呈现搜索结果,将用户发送到 FragmentActivity
显示选择的详细信息。所有这些都工作正常,但我看到 Nexus 6 模拟器和实际设备之间在后台堆栈方面的行为不一致。在模拟器中,一切都按我预期的那样工作,如果在详细信息 FragmentActivity 上按下后退按钮,用户将被带回搜索结果片段。在实际的 Nexus 6 设备上,用户被一路带回到 AppCompatActivity
,其中包含我的应用程序的菜单(这个 activity 使用支持 FragmentManager
,它将它管理的片段添加到后栈):
private void replaceFragment(Fragment supportFragment) {
String fragmentName = supportFragment.getClass().getSimpleName();
supportFragmentManager.beginTransaction()
.addToBackStack(fragmentName)
.replace(R.id.frame_menu_container, supportFragment, fragmentName)
.commit();
}
将用户从 Fragment 发送到 FragmentActivity 的代码只是一个具有额外功能的意图:
Intent intent = new Intent(getActivity(), PlayerDetailsActivity.class);
intent.putExtra(TransientPlayer.BUNDLE_KEY, transientPlayer);
startActivity(intent);
我还没有对 backstack 做任何特别的事情——这是默认行为。
这可能是什么问题?我已经阅读了一些有关后台堆栈的信息,但我还没有看到任何有关在您从 FragmentActivy 返回到不使用 FragmentManager 的 Fragment 时手动向后台堆栈添加内容的信息。
编辑 1:这是布局 XML,其中定义了 frame_menu_container
:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
<FrameLayout
android:id="@+id/frame_menu_container"
android:layout_below="@id/toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="@layout/fragment_home">
</FrameLayout>
</RelativeLayout>
编辑 2:好的,这是我的高级活动和片段的文字描述:
NavigationMenuActivity
,它扩展了 AppCompatActivity
。这是所有片段根据需要换入和换出的地方,使用 SupportFragmentManager
并且我有 replaceFragment()
方法。
我有以下片段,所有片段要么显示静态信息,进行 REST 调用以检索和显示数据,要么允许用户发送反馈。所有扩展 android.support.v4.app.Fragment
,其中 none 具有用户可以访问的其他片段或活动。
- 主页片段
- 日历片段
- StandingsFragment
- PlayerSearchFragment(见下文)
- 关于片段
- FeedbackFragment
这些片段的唯一功能例外是 PlayerSearchFragment
,它也扩展了 android.support.v4.app.Fragment
。此片段包含一个 RecyclerView
,当用户想要搜索玩家时,它会显示 REST 调用的结果。当返回结果并且用户从列表中选择一个项目时,它们将被发送到扩展 FragmentActivity
的 PlayerDetailsActivity
。
PlayerDetailsActivity
使用 FragmentTabHost
视图,其中包含可以查看的关于玩家的不同类型的信息。这是此路径下的 "end of the line" - 用户无法进入其他片段或活动。这就是问题所在。当我在这个 activity 上点击后退按钮时,我的意图是让用户返回到 PlayerSearchFragment
片段(搜索结果),在这种情况下,如果我在Nexus 6 模拟器,但如果我使用的是实际的 Nexus 6 设备[=,它们会一直回到 NavigationMenuActivity
activity 76=]。
我在 PlayerDetailsActivity
中有以下方法,当按下“后退”按钮时调用该方法,但它始终显示零条目:
@Override
public void onBackPressed() {
FragmentManager fragmentManager = getSupportFragmentManager();
int count = fragmentManager.getBackStackEntryCount();
Log.i(TAG, "Backstack : There are " + count + " entries");
for (int i = 0; i > count; i++) {
FragmentManager.BackStackEntry backStackEntry = fragmentManager.getBackStackEntryAt(i);
Log.i(TAG, String.format("Backstack : id=%d, name=%s, shortTitle=%s, breadcrumbTitle=%s",
backStackEntry.getId(),
backStackEntry.getName(),
backStackEntry.getBreadCrumbShortTitle(),
backStackEntry.getBreadCrumbTitle()));
}
super.onBackPressed();
}
我希望所有设备的体验都一样,无论是硬件还是模拟器。
我想你想在 replace(R.id.frame_menu_container, supportFragment, fragmentName)
之后调用 addToBackStack(fragmentName)
。
告诉我发生了什么。
首先,根据您的情况,您不需要将事务添加到 BackStack,因为它仅在您在同一 activity 中的片段之间导航时使用。
现在你的另一点是当你向后按时你没有得到你的片段是你点击两次。单击一次可删除您的第二个 activity,而另一个单击可删除添加到您的后台堆栈中的片段。至于你的信息 Fragment 没有 backpress 回调,他们依赖 activity onbackPressed() 方法
我尝试 运行 此代码,它 运行 在我的 Nexus 5 设备上正常运行。
虽然我附上了我正在使用的代码
public class MainActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
replaceFragment(new FragmentA());
}
private void replaceFragment(Fragment supportFragment) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.frame_menu_container, supportFragment
.commit();
}
}
FragmentA 在这里:
public class FragmentA extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_a, null);
view.findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getActivity(), AnotherActivity.class);
startActivity(intent);
}
});
return view;
}
}
另一个 Activity 就像:
public class AnotherActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.another_act);
}
}
我认为你应该不会遇到任何问题。
如果您仍然遇到问题,请告诉我您的设备名称并在此处附上代码,以便我进行测试并通知您。
好吧,试试这个,希望能让你摆脱这个错误。
这个答案给了你一个解决方法,但不是真正发生的事情
@Override
public void onBackPressed() {
FragmentManager fragmentManager = getSupportFragmentManager();
int count = fragmentManager.getBackStackEntryCount();
Log.i(TAG, "Backstack : There are " + count + " entries");
if(count > 0){
fragmentManager.popBackStack(); //or popBackStackImmediate();
for (int i = 0; i > count; i++) {
FragmentManager.BackStackEntry backStackEntry = fragmentManager.getBackStackEntryAt(i);
Log.i(TAG, String.format("Backstack : id=%d, name=%s, shortTitle=%s, breadcrumbTitle=%s",
backStackEntry.getId(),
backStackEntry.getName(),
backStackEntry.getBreadCrumbShortTitle(),
backStackEntry.getBreadCrumbTitle()));
}
}else{
super.onBackPressed();
}
}
虽然我仍然不知道为什么我不能让它按照我希望的方式工作,但我确实有一个解决方法,它确实可以很好地满足我需要做的事情。基本上,我覆盖 PlayerDetailsActivity
中的向上和返回导航回调,并使用意图(带有数据)显式将用户发送回 NavigationMenuActivity
。我在意图中发送的数据基本上只是一个布尔值,它告诉我我是否来自 PlayerDetailsActivity
,如果是,则显示该片段而不是 Home 片段,如下所示:
@Override
public void onBackPressed() {
sendBackToMenu();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
sendBackToMenu();
finish();
return true;
}
return false;
}
private void sendBackToMenu() {
Intent backToMenuIntent = new Intent(this, NavigationMenuActivity.class);
backToMenuIntent.putExtra("FROM_PLAYER_DETAILS", true);
startActivity(backToMenuIntent);
}
以上更改是在我将 class 更改为从 AppCompatActivity
扩展并在 PlayerDetailsActivity.onCreate()
中添加以下内容以激活向上导航的 ActionBar 之后:
ActionBar actionBar = getSupportActionBar();
assert actionBar != null;
actionBar.setTitle(R.string.player_details);
actionBar.setDisplayHomeAsUpEnabled(true);
此外,在 NavigationMenuActivity.onCreate()
我这样做:
if (getIntent().getBooleanExtra("FROM_PLAYER_DETAILS", false))
replaceFragment(new PlayerSearchFragment());
else
replaceFragment(new HomeFragment());
我不知道这是否是最 'appropriate' 的方法,但它运行良好并且可以满足我在少数硬件设备和 Nexus 4/5/6 模拟器上的需要。
我正在开发一个应用程序,它支持 Fragment
,带有 SearchView
小部件和 RecyclerView
来呈现搜索结果,将用户发送到 FragmentActivity
显示选择的详细信息。所有这些都工作正常,但我看到 Nexus 6 模拟器和实际设备之间在后台堆栈方面的行为不一致。在模拟器中,一切都按我预期的那样工作,如果在详细信息 FragmentActivity 上按下后退按钮,用户将被带回搜索结果片段。在实际的 Nexus 6 设备上,用户被一路带回到 AppCompatActivity
,其中包含我的应用程序的菜单(这个 activity 使用支持 FragmentManager
,它将它管理的片段添加到后栈):
private void replaceFragment(Fragment supportFragment) {
String fragmentName = supportFragment.getClass().getSimpleName();
supportFragmentManager.beginTransaction()
.addToBackStack(fragmentName)
.replace(R.id.frame_menu_container, supportFragment, fragmentName)
.commit();
}
将用户从 Fragment 发送到 FragmentActivity 的代码只是一个具有额外功能的意图:
Intent intent = new Intent(getActivity(), PlayerDetailsActivity.class);
intent.putExtra(TransientPlayer.BUNDLE_KEY, transientPlayer);
startActivity(intent);
我还没有对 backstack 做任何特别的事情——这是默认行为。
这可能是什么问题?我已经阅读了一些有关后台堆栈的信息,但我还没有看到任何有关在您从 FragmentActivy 返回到不使用 FragmentManager 的 Fragment 时手动向后台堆栈添加内容的信息。
编辑 1:这是布局 XML,其中定义了 frame_menu_container
:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
<FrameLayout
android:id="@+id/frame_menu_container"
android:layout_below="@id/toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="@layout/fragment_home">
</FrameLayout>
</RelativeLayout>
编辑 2:好的,这是我的高级活动和片段的文字描述:
NavigationMenuActivity
,它扩展了 AppCompatActivity
。这是所有片段根据需要换入和换出的地方,使用 SupportFragmentManager
并且我有 replaceFragment()
方法。
我有以下片段,所有片段要么显示静态信息,进行 REST 调用以检索和显示数据,要么允许用户发送反馈。所有扩展 android.support.v4.app.Fragment
,其中 none 具有用户可以访问的其他片段或活动。
- 主页片段
- 日历片段
- StandingsFragment
- PlayerSearchFragment(见下文)
- 关于片段
- FeedbackFragment
这些片段的唯一功能例外是 PlayerSearchFragment
,它也扩展了 android.support.v4.app.Fragment
。此片段包含一个 RecyclerView
,当用户想要搜索玩家时,它会显示 REST 调用的结果。当返回结果并且用户从列表中选择一个项目时,它们将被发送到扩展 FragmentActivity
的 PlayerDetailsActivity
。
PlayerDetailsActivity
使用 FragmentTabHost
视图,其中包含可以查看的关于玩家的不同类型的信息。这是此路径下的 "end of the line" - 用户无法进入其他片段或活动。这就是问题所在。当我在这个 activity 上点击后退按钮时,我的意图是让用户返回到 PlayerSearchFragment
片段(搜索结果),在这种情况下,如果我在Nexus 6 模拟器,但如果我使用的是实际的 Nexus 6 设备[=,它们会一直回到 NavigationMenuActivity
activity 76=]。
我在 PlayerDetailsActivity
中有以下方法,当按下“后退”按钮时调用该方法,但它始终显示零条目:
@Override
public void onBackPressed() {
FragmentManager fragmentManager = getSupportFragmentManager();
int count = fragmentManager.getBackStackEntryCount();
Log.i(TAG, "Backstack : There are " + count + " entries");
for (int i = 0; i > count; i++) {
FragmentManager.BackStackEntry backStackEntry = fragmentManager.getBackStackEntryAt(i);
Log.i(TAG, String.format("Backstack : id=%d, name=%s, shortTitle=%s, breadcrumbTitle=%s",
backStackEntry.getId(),
backStackEntry.getName(),
backStackEntry.getBreadCrumbShortTitle(),
backStackEntry.getBreadCrumbTitle()));
}
super.onBackPressed();
}
我希望所有设备的体验都一样,无论是硬件还是模拟器。
我想你想在 replace(R.id.frame_menu_container, supportFragment, fragmentName)
之后调用 addToBackStack(fragmentName)
。
告诉我发生了什么。
首先,根据您的情况,您不需要将事务添加到 BackStack,因为它仅在您在同一 activity 中的片段之间导航时使用。
现在你的另一点是当你向后按时你没有得到你的片段是你点击两次。单击一次可删除您的第二个 activity,而另一个单击可删除添加到您的后台堆栈中的片段。至于你的信息 Fragment 没有 backpress 回调,他们依赖 activity onbackPressed() 方法
我尝试 运行 此代码,它 运行 在我的 Nexus 5 设备上正常运行。 虽然我附上了我正在使用的代码
public class MainActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
replaceFragment(new FragmentA());
}
private void replaceFragment(Fragment supportFragment) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.frame_menu_container, supportFragment
.commit();
}
}
FragmentA 在这里:
public class FragmentA extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_a, null);
view.findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getActivity(), AnotherActivity.class);
startActivity(intent);
}
});
return view;
}
}
另一个 Activity 就像:
public class AnotherActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.another_act);
}
}
我认为你应该不会遇到任何问题。
如果您仍然遇到问题,请告诉我您的设备名称并在此处附上代码,以便我进行测试并通知您。
好吧,试试这个,希望能让你摆脱这个错误。
这个答案给了你一个解决方法,但不是真正发生的事情
@Override
public void onBackPressed() {
FragmentManager fragmentManager = getSupportFragmentManager();
int count = fragmentManager.getBackStackEntryCount();
Log.i(TAG, "Backstack : There are " + count + " entries");
if(count > 0){
fragmentManager.popBackStack(); //or popBackStackImmediate();
for (int i = 0; i > count; i++) {
FragmentManager.BackStackEntry backStackEntry = fragmentManager.getBackStackEntryAt(i);
Log.i(TAG, String.format("Backstack : id=%d, name=%s, shortTitle=%s, breadcrumbTitle=%s",
backStackEntry.getId(),
backStackEntry.getName(),
backStackEntry.getBreadCrumbShortTitle(),
backStackEntry.getBreadCrumbTitle()));
}
}else{
super.onBackPressed();
}
}
虽然我仍然不知道为什么我不能让它按照我希望的方式工作,但我确实有一个解决方法,它确实可以很好地满足我需要做的事情。基本上,我覆盖 PlayerDetailsActivity
中的向上和返回导航回调,并使用意图(带有数据)显式将用户发送回 NavigationMenuActivity
。我在意图中发送的数据基本上只是一个布尔值,它告诉我我是否来自 PlayerDetailsActivity
,如果是,则显示该片段而不是 Home 片段,如下所示:
@Override
public void onBackPressed() {
sendBackToMenu();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
sendBackToMenu();
finish();
return true;
}
return false;
}
private void sendBackToMenu() {
Intent backToMenuIntent = new Intent(this, NavigationMenuActivity.class);
backToMenuIntent.putExtra("FROM_PLAYER_DETAILS", true);
startActivity(backToMenuIntent);
}
以上更改是在我将 class 更改为从 AppCompatActivity
扩展并在 PlayerDetailsActivity.onCreate()
中添加以下内容以激活向上导航的 ActionBar 之后:
ActionBar actionBar = getSupportActionBar();
assert actionBar != null;
actionBar.setTitle(R.string.player_details);
actionBar.setDisplayHomeAsUpEnabled(true);
此外,在 NavigationMenuActivity.onCreate()
我这样做:
if (getIntent().getBooleanExtra("FROM_PLAYER_DETAILS", false))
replaceFragment(new PlayerSearchFragment());
else
replaceFragment(new HomeFragment());
我不知道这是否是最 'appropriate' 的方法,但它运行良好并且可以满足我在少数硬件设备和 Nexus 4/5/6 模拟器上的需要。