如果从片段中使用,GoogleApiClient 未连接

GoogleApiClient not connected if used from fragment

我今天发现了一个奇怪的行为。

我有我的 activity,它在 onStart() 中连接到 GoogleApiClient,在 onStop() 中断开连接

activity 使用 GridViewPager 来显示我的片段。为了通过数据层发送消息,我在 activity 和片段之间使用了一个回调接口。

如果我从 Activity 布局中的按钮调用 sendMessage(),它工作正常。如果 sendMessage() 由使用回调接口的片段执行,sendMessage() 显示 "not connected" Toast。

两种方式都调用了 Activity 中的相同方法,那么它的行为怎么可能不同呢?

需要说明的是,该问题仅在应用程序首次重启后出现。

Activity

public class WearPlex extends WearableActivity implements
    NavigationRemoteCallbacks,
    GoogleApiClient.ConnectionCallbacks,
    GoogleApiClient.OnConnectionFailedListener {

private List<Node> nodeList = new ArrayList<Node>();
private List<Fragment> fragmentList = new ArrayList<Fragment>();
private GoogleApiClient googleApiClient;

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

    setContentView(R.layout.activity_wear_plex);
    setAmbientEnabled();

    fragmentList.add(NavigationRemoteFragment.getInstance(this));

    GridViewPager mGridPager = (GridViewPager)findViewById(R.id.gridViewPager);
    mGridPager.setAdapter(new MainGridPageAdapter(getFragmentManager(), fragmentList));

    googleApiClient = new GoogleApiClient.Builder(this)
            .addApi(Wearable.API)
            .addConnectionCallbacks(this)
            .addOnConnectionFailedListener(this)
            .build();
}

@Override
protected void onStart() {
    super.onStart();
    googleApiClient.connect();
}

@Override
protected void onStop() {
    googleApiClient.disconnect();
    super.onStop();
}

@Override
public void onConnected(Bundle bundle) {
    Toast.makeText(this, "Connected", Toast.LENGTH_SHORT).show();

    nodeList.clear();
    Wearable.NodeApi.getConnectedNodes(googleApiClient).setResultCallback(new ResultCallback<NodeApi.GetConnectedNodesResult>() {
        @Override
        public void onResult(NodeApi.GetConnectedNodesResult nodes) {
            for (Node node : nodes.getNodes()) nodeList.add(node);
        }
    });
}

@Override
public void navigationRemoteSendCommand(String commandPath) {
    sendMessage(commandPath, null);
}

public void debugOnClick(View view) {
    sendMessage("/debug", null);
}

public void sendMessage(String path, byte[] data) {
    if (googleApiClient.isConnected()) {
        for (int i = 0; i < nodeList.size(); i++) {
            if (nodeList.get(i).isNearby()) {
                Toast.makeText(this, "Send message", Toast.LENGTH_SHORT).show();
                Wearable.MessageApi.sendMessage(googleApiClient, nodeList.get(i).getId(), path, data);
            }
        }
    } else {
        Toast.makeText(this, "Not connected", Toast.LENGTH_SHORT).show();
    }
}

@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
    Toast.makeText(this, "Connection failed", Toast.LENGTH_SHORT).show();
}

片段

public class NavigationRemoteFragment extends Fragment {

private static NavigationRemoteFragment navigationRemoteFragment = null;

private NavigationRemoteCallbacks callbackHandler = null;

private ImageButton navBtnCenter;

public static NavigationRemoteFragment getInstance(NavigationRemoteCallbacks handler) {
    if (navigationRemoteFragment == null) {
        navigationRemoteFragment = new NavigationRemoteFragment();
        navigationRemoteFragment.callbackHandler = handler;
    }

    return navigationRemoteFragment;
}

public NavigationRemoteFragment() {
    // Required empty public constructor
}

@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 v = inflater.inflate(R.layout.fragment_navigation_remote, container, false);

    navBtnCenter = (ImageButton)v.findViewById(R.id.navBtnCenter);

    navBtnCenter.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            callbackHandler.navigationRemoteSendCommand("/debug");
        }
    });

    return v;
}

}

回调接口

public interface NavigationRemoteCallbacks {
    public void navigationRemoteSendCommand(String commandPath);
}

为 MainGridPageAdapter 编辑 1 个代码

public class MainGridPageAdapter extends FragmentGridPagerAdapter {

private List<Fragment> fragmentList = null;

public MainGridPageAdapter(FragmentManager fm, List<Fragment> fragmentList) {
    super(fm);
    this.fragmentList = fragmentList;
}

@Override
public Fragment getFragment(int i, int i1) {
    if (i1 < fragmentList.size()) return fragmentList.get(i1);
    return null;
}

@Override
public int getRowCount() {
    return 1;
}

@Override
public int getColumnCount(int i) {
    return fragmentList.size();
}

您没有显示 MainGridPageAdapter 的代码,所以我不知道它是如何管理片段的。您提到问题发生在重新启动后。查看 WearPlex.onCreate() 中的代码,我怀疑问题是由包含对 activity.

的旧的、已销毁实例的引用的片段引起的

FragmentManager 的一个记录不详的行为是它在重新启动时保存其状态。这一点经常被忽视,导致重启后出现重复的片段实例。在主机 activity 的 onCreate() 方法中管理片段创建的正确模式是:

    if (savedInstanceState == null) {
        // Not a restart
        // Create a new instance of the fragment
        // Add it to the fragment manager
    } else {
        // Restart
        // The fragment manager has saved and restored the fragment instances
        // Use findFragmentById() to get the fragment if you need it
    }

您没有在 onCreate() 中使用 savedInstanceState 来测试重启。重新启动后,您是否看到比预期更多的碎片?如果是这样,则原始片段持有对已停止的旧 activity 的引用,并且具有断开连接的 GoogleApiClient。如果单击其中一个片段的 NavBtn,您将看到 "not connected" toast。

更新

问题是由您创建 NavigationRemoteFragment 新实例的方式引起的,特别是 static 成员 navigationRemoteFragment 的使用。重启后,当 activity 被重新创建时,代码调用 NavigationRemoteFragment.getInstance(this)getInstance() 发现 navigationRemoteFragment 不为空,因为它是静态的,并且不会创建新片段。返回的片段是旧片段,其中包含对旧 activity 的引用,该片段已停止并具有断开连接的 GoogleApiClient.

这可以通过使用 isDestroyed 方法并添加一些调试日志来确认:

@Override
public void navigationRemoteSendCommand(String commandPath) {
    if (isDestroyed()) {
        Log.w("TEST", "This is an old instance of the activity");
    }
    sendMessage(commandPath, null);
}