使用com.google.android.gms.maps.MapFragment的另一个片段复制ID,标记null或父ID

her*_*ann 325 android illegalargumentexception android-maps android-fragments

我有一个带有三个标签的应用程序.

每个选项卡都有自己的布局.xml文件.main.xml有自己的地图片段.这是应用程序首次启动时显示的那个.

除了在标签之间切换时,一切正常.如果我尝试切换回地图片段选项卡,我会收到此错误.切换到其他选项卡之间和之间的工作正常.

这可能有什么问题?

这是我的主类和我的main.xml,以及我使用的相关类(你也会在底部找到错误日志)

主要班级

package com.nfc.demo;

import android.app.ActionBar;
import android.app.ActionBar.Tab;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.widget.Toast;

public class NFCDemoActivity extends Activity {

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ActionBar bar = getActionBar();
        bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
        bar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);

        bar.addTab(bar
                .newTab()
                .setText("Map")
                .setTabListener(
                        new TabListener<MapFragment>(this, "map",
                                MapFragment.class)));
        bar.addTab(bar
                .newTab()
                .setText("Settings")
                .setTabListener(
                        new TabListener<SettingsFragment>(this, "settings",
                                SettingsFragment.class)));
        bar.addTab(bar
                .newTab()
                .setText("About")
                .setTabListener(
                        new TabListener<AboutFragment>(this, "about",
                                AboutFragment.class)));

        if (savedInstanceState != null) {
            bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
        }
        // setContentView(R.layout.main);

    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("tab", getActionBar().getSelectedNavigationIndex());
    }

    public static class TabListener<T extends Fragment> implements
            ActionBar.TabListener {
        private final Activity mActivity;
        private final String mTag;
        private final Class<T> mClass;
        private final Bundle mArgs;
        private Fragment mFragment;

        public TabListener(Activity activity, String tag, Class<T> clz) {
            this(activity, tag, clz, null);
        }

        public TabListener(Activity activity, String tag, Class<T> clz,
                Bundle args) {
            mActivity = activity;
            mTag = tag;
            mClass = clz;
            mArgs = args;

            // Check to see if we already have a fragment for this tab,
            // probably from a previously saved state. If so, deactivate
            // it, because our initial state is that a tab isn't shown.
            mFragment = mActivity.getFragmentManager().findFragmentByTag(mTag);
            if (mFragment != null && !mFragment.isDetached()) {
                FragmentTransaction ft = mActivity.getFragmentManager()
                        .beginTransaction();
                ft.detach(mFragment);
                ft.commit();
            }
        }

        public void onTabSelected(Tab tab, FragmentTransaction ft) {
            if (mFragment == null) {
                mFragment = Fragment.instantiate(mActivity, mClass.getName(),
                        mArgs);
                ft.add(android.R.id.content, mFragment, mTag);
            } else {
                ft.attach(mFragment);
            }
        }

        public void onTabUnselected(Tab tab, FragmentTransaction ft) {
            if (mFragment != null) {
                ft.detach(mFragment);
            }
        }

        public void onTabReselected(Tab tab, FragmentTransaction ft) {
            Toast.makeText(mActivity, "Reselected!", Toast.LENGTH_SHORT)
                         .show();
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

main.xml中

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <fragment
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/mapFragment"
        android:name="com.google.android.gms.maps.MapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>
Run Code Online (Sandbox Code Playgroud)

相关类(MapFragment.java)

package com.nfc.demo;

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class MapFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);
        return inflater.inflate(R.layout.main, container, false);
    }

    public void onDestroy() {
        super.onDestroy();
    }
}
Run Code Online (Sandbox Code Playgroud)

错误

android.view.InflateException: Binary XML file line #7: 
     Error inflating class fragment
   at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:704)
   at android.view.LayoutInflater.rInflate(LayoutInflater.java:746)
   at android.view.LayoutInflater.inflate(LayoutInflater.java:489)
   at android.view.LayoutInflater.inflate(LayoutInflater.java:396)
   at com.nfc.demo.MapFragment.onCreateView(MapFragment.java:15)
   at android.app.Fragment.performCreateView(Fragment.java:1695)
   at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:885)
   at android.app.FragmentManagerImpl.attachFragment(FragmentManager.java:1255)
   at android.app.BackStackRecord.run(BackStackRecord.java:672)
   at android.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1435)
   at android.app.FragmentManagerImpl$1.run(FragmentManager.java:441)
   at android.os.Handler.handleCallback(Handler.java:725)
   at android.os.Handler.dispatchMessage(Handler.java:92)
   at android.os.Looper.loop(Looper.java:137)
   at android.app.ActivityThread.main(ActivityThread.java:5039)
   at java.lang.reflect.Method.invokeNative(Native Method)
   at java.lang.reflect.Method.invoke(Method.java:511)
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
   at dalvik.system.NativeStart.main(Native Method)

Caused by: java.lang.IllegalArgumentException: 
     Binary XML file line #7: Duplicate id 0x7f040005, tag null, or 
     parent id 0xffffffff with another fragment for 
     com.google.android.gms.maps.MapFragment
   at android.app.Activity.onCreateView(Activity.java:4722)
   at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:680)
   ... 19 more
Run Code Online (Sandbox Code Playgroud)

Vid*_*erg 398

Matt建议的答案是有效的,但它会导致地图被重新创建并重新绘制,这并不总是令人满意的.经过大量的反复试验,我发现了一个适合我的解决方案:

private static View view;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    if (view != null) {
        ViewGroup parent = (ViewGroup) view.getParent();
        if (parent != null)
            parent.removeView(view);
    }
    try {
        view = inflater.inflate(R.layout.map, container, false);
    } catch (InflateException e) {
        /* map is already there, just return view as it is */
    }
    return view;
}
Run Code Online (Sandbox Code Playgroud)

为了更好的衡量,这里的"map.xml"(R.layout.map)与R.id.mapFragment(android:id ="@ + id/mapFragment"):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mapLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <fragment xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/mapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        class="com.google.android.gms.maps.SupportMapFragment" />
</LinearLayout>
Run Code Online (Sandbox Code Playgroud)

我希望这有帮助,但我不能保证它没有任何不利影响.

编辑:存在一些不利影响,例如退出应用程序并再次启动时.由于应用程序不一定完全关闭(但只是在后台进入休眠状态),因此我提交的先前代码在重新启动应用程序时将失败.我已经将代码更新为适合我的东西,无论是进出地图还是退出并重新启动应用程序,我对try-catch位都不太满意,但它似乎运行得很好.在查看堆栈跟踪时,我发现我可以检查映射片段是否在FragmentManager中,不需要try-catch块,代码已更新.

更多编辑:事实证明,你需要那个try-catch.毕竟,检查地图片段结果不是很好.Blergh.

  • 这是一个错误的答案-1!您正在使用静态修改器为View泄漏活动.这个问题的根本原因可能是另一个泄露的活动,它不能被垃圾收集,因为你保持一个强引用指向它.如果抛出InflateException,您将使用具有已销毁活动的上下文的视图!更好地在您的应用程序中找到其他内存泄漏,这将解决所有问题. (23认同)
  • 不要这样做!它会导致内存泄漏.发生这种情况的唯一原因是因为您正在从另一个片段中的XML中扩展片段.你不应该这样做!您应该使用ChildFragmentManager并在onViewCreated()中添加片段! (4认同)
  • 这对我来说也是+1.您没有义务使用静态参考视图 (2认同)

Jus*_*ler 269

问题是你不应该做的事情.你不应该在其他片段内膨胀片段.来自Android的文档:

注意:当布局包含<fragment>时,您无法将布局扩展为片段.仅在动态添加到片段时才支持嵌套片段.

虽然您可以通过此处提供的黑客来完成任务,但我强烈建议您不要这样做.当你试图给包含另一个片段的片段的布局充气时,不可能确定这些黑客会处理每个新的Android操作系统所做的事情.

将片段添加到另一个片段的唯一Android支持方式是通过子片段管理器中的事务.

只需将XML布局更改为空容器(如果需要,添加ID):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mapFragmentContainer"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
</LinearLayout>
Run Code Online (Sandbox Code Playgroud)

然后在Fragment onViewCreated(View view, @Nullable Bundle savedInstanceState)方法中:

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    FragmentManager fm = getChildFragmentManager();
    SupportMapFragment mapFragment = (SupportMapFragment) fm.findFragmentByTag("mapFragment");
    if (mapFragment == null) {
        mapFragment = new SupportMapFragment();
        FragmentTransaction ft = fm.beginTransaction();
        ft.add(R.id.mapFragmentContainer, mapFragment, "mapFragment");
        ft.commit();
        fm.executePendingTransactions();
    }
    mapFragment.getMapAsync(callback);
}
Run Code Online (Sandbox Code Playgroud)

  • 有关以编程方式创建地图片段并初始化地图的示例,请参阅http://stackoverflow.com/questions/13733299/initialize-mapfragment-programmatically-with-maps-api-v2. (4认同)
  • 根据doc,使用`SupportMapFragment.newInstance();`https://developers.google.com/maps/documentation/android-api/map (2认同)

Mat*_*att 176

我有同样的问题,并能够通过手动删除类MapFragmentonDestroy()方法来解决它Fragment.以下代码可以工作并引用MapFragmentXML中的ID:

@Override
public void onDestroyView() {
    super.onDestroyView();
    MapFragment f = (MapFragment) getFragmentManager()
                                         .findFragmentById(R.id.map);
    if (f != null) 
        getFragmentManager().beginTransaction().remove(f).commit();
}
Run Code Online (Sandbox Code Playgroud)

如果您不MapFragment手动删除它,它将会四处闲逛,这样就不会花费大量资源来重新创建/再次显示地图视图.似乎保持底层MapView对于在选项卡之间来回切换是很好的,但是当在片段中使用时,这种行为导致MapViewMapFragment具有相同ID的每个新的上创建复制.解决方案是手动删除MapFragment每次片段膨胀时重新创建底层映射.

我在另一个答案[ 1 ]中也注意到了这一点.

  • 这工作,但如果我旋转我的屏幕,我的应用程序崩溃与此异常:引起:java.lang.IllegalStateException:onSaveInstanceState后无法执行此操作 (23认同)
  • commitAllowingStateLoss将避免IllegalStateException异常. (10认同)
  • 对我来说int无法正常工作.应用程序崩溃了onDestroy (7认同)

小智 22

这是我的答案:

1,创建如下的布局xml:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/map_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
Run Code Online (Sandbox Code Playgroud)

2,在Fragment类中,以编程方式添加谷歌地图.

import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

/**
 * A simple {@link android.support.v4.app.Fragment} subclass. Activities that
 * contain this fragment must implement the
 * {@link MapFragment.OnFragmentInteractionListener} interface to handle
 * interaction events. Use the {@link MapFragment#newInstance} factory method to
 * create an instance of this fragment.
 * 
 */
public class MapFragment extends Fragment {
    // TODO: Rename parameter arguments, choose names that match
    private GoogleMap mMap;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_map, container, false);
        SupportMapFragment mMapFragment = SupportMapFragment.newInstance();
        mMap = mMapFragment.getMap();
        FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
        transaction.add(R.id.map_container, mMapFragment).commit();
        return view;
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        Log.d("Attach", "on attach");
    }

    @Override
    public void onDetach() {
        super.onDetach();
    }
} 
Run Code Online (Sandbox Code Playgroud)

  • `mMapFragment.getMap();`返回`null`.知道为什么吗? (2认同)

Des*_*Lua 10

  1. 正如@Justin Breitfeller所提到的,@ Vidar Wahlberg解决方案是一个黑客攻击,可能在未来的Android版本中无效.
  2. @Vidar Wahlberg执行黑客攻击是因为其他解决方案可能"导致地图重新创建并重新绘制,这并不总是令人满意".可以通过维护旧的地图片段来防止地图重绘,而不是每次都创建新的实例.
  3. @Matt解决方案对我不起作用(IllegalStateException)
  4. 正如@Justin Breitfeller所引用的那样,"当布局包含a时,你无法将布局扩展为片段.只有在动态添加到片段时才支持嵌套片段."

我的解决方案

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,                              Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_map_list, container, false);

    // init
    //mapFragment = (SupportMapFragment)getChildFragmentManager().findFragmentById(R.id.map);
    // don't recreate fragment everytime ensure last map location/state are maintain
    if (mapFragment == null) {
        mapFragment = SupportMapFragment.newInstance();
        mapFragment.getMapAsync(this);
    }
    FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
    // R.id.map is a layout
    transaction.replace(R.id.map, mapFragment).commit();

    return view;
}
Run Code Online (Sandbox Code Playgroud)

  • 谢谢@Desmond您的解决方案对我来说完美无瑕.您唯一需要记住的是不在布局中创建地图.此解决方案中的地图创建嵌入在代码中,因此将<fragment android:name ="com.google.android.gms.maps.SupportMapFragment">更改为例如<LinearLayout id ="@ + id/map /> (2认同)

Dig*_*ale 7

全局声明SupportMapFragment对象

    private SupportMapFragment mapFragment;
Run Code Online (Sandbox Code Playgroud)

在onCreateView()方法中,将下面的代码放入

mapFragment = (SupportMapFragment) getChildFragmentManager()
            .findFragmentById(R.id.map);
 mapFragment.getMapAsync(this);
Run Code Online (Sandbox Code Playgroud)

在onDestroyView()下面的代码中

@Override
public void onDestroyView() {
   super.onDestroyView();

    if (mapFragment != null)
        getFragmentManager().beginTransaction().remove(mapFragment).commit();
}
Run Code Online (Sandbox Code Playgroud)

在您的xml文件中,将以下代码放入

 <fragment
    android:id="@+id/map"
    android:name="com.abc.Driver.fragment.FragmentHome"
    class="com.google.android.gms.maps.SupportMapFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    />
Run Code Online (Sandbox Code Playgroud)

上面的代码解决了我的问题,并且工作正常