map-fragment icon indicating copy to clipboard operation
map-fragment copied to clipboard

Leaking map activity / problems with the back button

Open realdadfish opened this issue 12 years ago • 1 comments

Hi Inazaruk!

Based on your code I combined the two Fragment classes into one and based it off my own custom Fragment:

public abstract class ActivityHostFragment<T> extends ContentFragment<T> {

    private static final String KEY_STATE_BUNDLE = "localActivityManagerState";

    private static final String ACTIVITY_TAG = "hosted";

    private View activityView;

    private LocalActivityManager localActivityManager;

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

        Bundle state = null;
        if (savedInstanceState != null) {
            state = savedInstanceState.getBundle(KEY_STATE_BUNDLE);
        }

        localActivityManager = new LocalActivityManager(getActivity(), true);
        localActivityManager.dispatchCreate(state);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle state) {
        View fragmentView = super.onCreateView(inflater, container, state);

        ViewGroup containerView = getContainerView(fragmentView);
        activityView = startActivityAndGetView();
        containerView.addView(activityView);

        return fragmentView;
    }

    @Override
    public void onDestroyView() {
        ViewGroup containerView = getContainerView(getView());
        containerView.removeAllViews();
        activityView = null;
        super.onDestroyView();
    }

    private ViewGroup getContainerView(View fragmentView) {
        int containerId = getActivityContainerView();
        View containerView = fragmentView.findViewById(containerId);
        checkArgument(containerView != null, "container with ID " + containerId + " not found");
        checkArgument(containerView instanceof ViewGroup, "container with ID " + containerId + " is not a viewGroup");
        return (ViewGroup) containerView;
    }

    protected View startActivityAndGetView() {
        Intent intent = new Intent(getActivity(), getActivityClass());
        Window window = localActivityManager.startActivity(ACTIVITY_TAG, intent);
        View view = window != null ? window.getDecorView() : null;

        if (view != null) {
            ViewParent parent = view.getParent();
            if (parent != null) {
                ViewGroup v = (ViewGroup) parent;
                v.removeView(view);
            }

            view.setVisibility(View.VISIBLE);
            view.setFocusableInTouchMode(true);
            if (view instanceof ViewGroup) {
                ((ViewGroup) view).setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            }
        }
        return view;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putBundle(KEY_STATE_BUNDLE, localActivityManager.saveInstanceState());
    }

    @Override
    public void onResume() {
        super.onResume();
        localActivityManager.dispatchResume();
    }

    @Override
    public void onPause() {
        super.onPause();
        localActivityManager.dispatchPause(getActivity().isFinishing());
    }

    @Override
    public void onStop() {
        super.onStop();
        localActivityManager.dispatchStop();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        localActivityManager.dispatchDestroy(getActivity().isFinishing());
    }

    /**
     * Returns the view instance that has been embedded into this fragment's
     * view
     * 
     * @return
     */
    public View getActivityView() {
        return activityView;
    }

    /**
     * Returns the class of the activity that should be "hosted" in this
     * fragment
     * 
     * @return
     */
    protected abstract Class<? extends Activity> getActivityClass();

    /**
     * Returns the ID of the container that is part of the layout defined in
     * {@#getLayout()} in which the activity view should be
     * loaded into
     * 
     * @return
     */
    protected abstract int getActivityContainerView();
}

This works all fine as is, just that I encounter two major problems:

  • when my fragment is dismissed, recreated and then dismissed again Android's StrictMode tells me that I'm leaking my MapActivity that I return as getActivityClass() in a derived class; what is weird about that is that I could only reproduce it to leak up to 3 or 4 instances of the activity, so it seems to be cleaned, but at least two instances still remain (verified with HPROF dump in MAT)
  • the BACK button does not seem to work properly any longer. It takes sometimes up to five clicks on it to actually make it call my onBackPressed() handler in my main activity. I get many log messages from the InputEventConsistencyVerifier telling me about ACTION_UP but key was not down, originating from android.widget.ZoomButtonsController

Have you experienced anything like this in the past?

I know that there is a new API finally available, but I read of problems with it on older devices (< 3.0), so we decided to stick to the v1 API for now.

Thanks in advance! Thomas.

realdadfish avatar Jan 18 '13 11:01 realdadfish

Ok, actually forgot the second issue. I tracked it down being a problem with MapView.displayZoomControls(true); that I used previously to permanently show the zoom controls. This puts the focus on the zoom controls which seem to have their own key event handling as it seems. Removing that makes the BACK button work instantly as it is supposed to work. Still, the activity leaking is immanent...

realdadfish avatar Jan 18 '13 11:01 realdadfish