0

I have a FragmentA. When I click on a button in FragmentA I go to FragmentB. In FragmentB I have a PopupWindow. The PopupWindow have a ViewPager with two pages.

I took help from this code - Emojicon

I have 2 separate classes, View1 and View2, for the views at page 1 and 2 of the ViewPager respectively. Both these classes, View1 and View2, extends a parent class ViewBase.

Here is my problem:

Scenario 1: When I am at FragmentA the memory graph shows 13MB utilization. When I go to FragmentB without showing PopupWindow the memory graph shows 16MB and when I come back to FragmentA it comes down to 13MB. This is good.

Scenario 2: When I am at FragmentA the memory graph shows 13MB utilization. When I go to FragmentB with showing PopupWindow the memory graph shows 20MB and when I come back to FragmentA it doesn't come down to 13MB.

I have tried Eclipse MAT and Heap dump to find out the issue but still no help. I can see in the MAT that FragmentB is still in memory when I come back to FragmentA holding the instances of PopupWindow, View1 and View2. None of them are released. FragmentB should not be in memory.

Please help me out.

Here is my DemoPopupWindow.java

public class DemoPopupWindow extends PopupWindow {

// Views
private TabLayout mTabLayout;
private CustomViewPager mViewPager;
private PagerAdapter mViewPagerAdapter;
private RelativeLayout mLayout;
private View mRootView;

// Variables
private int mGreyColor, mPrimaryColor;
private OnSoftKeyboardOpenCloseListener onSoftKeyboardOpenCloseListener;
private int keyBoardHeight = 0;
private Boolean pendingOpen = false;
private Boolean isOpened = false;
private Context mContext;

ViewTreeObserver.OnGlobalLayoutListener mGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        Rect r = new Rect();
        mRootView.getWindowVisibleDisplayFrame(r);

        int screenHeight = mRootView.getRootView().getHeight();
        int heightDifference = screenHeight - (r.bottom);
        if (heightDifference > 100) {
            keyBoardHeight = heightDifference;
            setSize(WindowManager.LayoutParams.MATCH_PARENT, keyBoardHeight);
            if (isOpened == false) {
                if (onSoftKeyboardOpenCloseListener != null)
                    onSoftKeyboardOpenCloseListener.onKeyboardOpen(keyBoardHeight);
            }
            isOpened = true;
            if (pendingOpen) {
                showAtBottom();
                pendingOpen = false;
            }
        } else {
            isOpened = false;
            if (onSoftKeyboardOpenCloseListener != null)
                onSoftKeyboardOpenCloseListener.onKeyboardClose();
        }
    }
};

/**
 * Constructor
 * @param rootView
 * @param mContext
 */
public DemoPopupWindow(View rootView, Context mContext){
    super(mContext);
    this.mContext = mContext;
    this.mRootView = rootView;

    Resources resources = mContext.getResources();
    View customView = createCustomView(resources);

    setContentView(customView);
    setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
    setSize((int) mContext.getResources().getDimension(R.dimen.keyboard_height), WindowManager.LayoutParams.MATCH_PARENT);

}

/**
 * Set keyboard close listener
 * @param listener
 */
public void setOnSoftKeyboardOpenCloseListener(OnSoftKeyboardOpenCloseListener listener){
    this.onSoftKeyboardOpenCloseListener = listener;
}

/**
 * Show PopupWindow
 */
public void showAtBottom(){
    showAtLocation(mRootView, Gravity.BOTTOM, 0, 0);
}

/**
 * Show PopupWindow at bottom
 */
public void showAtBottomPending(){
    if(isKeyBoardOpen())
        showAtBottom();
    else
        pendingOpen = true;
}

/**
 * Check whether keyboard is open or not
 * @return
 */
public Boolean isKeyBoardOpen(){
    return isOpened;
}

/**
 * Set soft keyboard size
 */
public void setSizeForSoftKeyboard(){
    mRootView.getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
}

/**
 * Remove global layout listener
 */
public void removeGlobalListener() {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
        mRootView.getViewTreeObserver().removeGlobalOnLayoutListener(mGlobalLayoutListener);
    } else {
        mRootView.getViewTreeObserver().removeOnGlobalLayoutListener(mGlobalLayoutListener);
    }
}

/**
 * Set PopupWindow size
 * @param width
 * @param height
 */
public void setSize(int width, int height){
    keyBoardHeight = height;
    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, keyBoardHeight);
    mLayout.setLayoutParams(params);
    setWidth(width);
    setHeight(height);
}

/**
 * Create PopupWindow View
 * @return
 */
private View createCustomView(Resources resources) {
    LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
    View view = inflater.inflate(R.layout.popup, null, false);

    mViewPager = (CustomViewPager) view.findViewById(R.id.pager);
    mLayout = (RelativeLayout) view.findViewById(R.id.layout);

    mViewPagerAdapter = new ViewPagerAdapter(
            Arrays.asList(
                    new View1(mContext, this),
                    new View2(mContext, this)
            )
    );
    mViewPager.setAdapter(mViewPagerAdapter);

    mPrimaryColor = resources.getColor(R.color.color_primary);
    mGreyColor = resources.getColor(R.color.grey_color);

    mTabLayout = (TabLayout) view.findViewById(R.id.tabs);
    mTabLayout.addTab(mTabLayout.newTab());
    mTabLayout.addTab(mTabLayout.newTab());
    mTabLayout.setupWithViewPager(mViewPager);

    return view;
}

/**
 * ViewPager Adapter
 */
private static class ViewPagerAdapter extends PagerAdapter {
    private List<ViewBase> views;

    public ViewPagerAdapter(List<ViewBase> views) {
        super();
        this.views = views;
    }

    @Override
    public int getCount() {
        return views.size();
    }


    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        View v = views.get(position).mRootView;
        ((ViewPager)container).addView(v, 0);
        return v;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object view) {
        ((ViewPager)container).removeView((View)view);
    }

    @Override
    public boolean isViewFromObject(View view, Object key) {
        return key == view;
    }
}

/**
 * Soft keyboard open close listener
 */
public interface OnSoftKeyboardOpenCloseListener{
    void onKeyboardOpen(int keyBoardHeight);
    void onKeyboardClose();
}
}

Please note that I haven't pasted complete PopupWindow class here but only the necessary part.

Here is how I am using this DemoPopupWindow in my FragmentB

mPopupWindow = new DemoPopupWindow(mLayout, getActivity());
    mPopupWindow.setSizeForSoftKeyboard();


    // If the text keyboard closes, also dismiss the PopupWindow
    mPopupWindow.setOnSoftKeyboardOpenCloseListener(new DemoPopupWindow.OnSoftKeyboardOpenCloseListener() {

        @Override
        public void onKeyboardOpen(int keyBoardHeight) {

        }

        @Override
        public void onKeyboardClose() {
            if (mPopupWindow.isShowing())
                mPopupWindow.dismiss();
        }
    });

In FragmentB onDestroy I am calling this method to remove GlobalLayoutListener

mPopupWindow.removeGlobalListener();

I have a button in FragmentB to show and dismiss PopupWindow.

Here is my ViewBase.java

public class ViewBase {

public View mRootView;
DemoPopupWindow mPopup;
private Context mContext;

public ViewBase (Context context, DemoPopupWindow popup) {
    mContext = context;
    mPopup = popup;
}

public ViewBase () {
}
}

Here is my View1

public class View1 extends ViewBase{

// Views
public View mRootView;
DemoPopupWindow mPopup;
private LinearLayout mLayoutText;

// Variables
private Context mContext;
private List<String> mText;

/**
 * Constructor
 */
public View1(Context context, DemoPopupWindow popup) {
    super(context, popup);

    LayoutInflater inflater = (LayoutInflater) context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
    mPopup = popup;
    mRootView = inflater.inflate(R.layout.fragment_view1, null);
    mContext = context;

    // Set parent class rootview
    super.mRootView = mRootView;

    registerViews(mRootView);
    registerListeners();

    populateText();
}

/**
 * Register all the views
 * @param view
 */
private void registerViews(View view) {
    mLayoutText = (LinearLayout) view.findViewById(R.id.view1_layout);
    mText = TextManager.getInstance().getText();
}

/**
 * Populate text
 */
private void populateText() {
    int length = mText.size();
    for(int i=0; i<length; i++) {
        addNewText(mText.get(i).getText());
    }
}

/**
 * Add new text
 * @param text
 */
private void addNewText(final String text) {
    TextView textView = createTextView(text);
    mLayoutText.addView(textView);
    textView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // Do something
        }
    });
}

/**
 * Create textview
 * @param text
 * @return
 */
private TextView createTextView(final String text) {
    TextView textView = new TextView(mContext);
    FlowLayout.LayoutParams params = new FlowLayout.LayoutParams(FlowLayout.LayoutParams.WRAP_CONTENT, 40);
    params.setMargins(4, 4, 0, 0);
    textView.setLayoutParams(params);
    textView.setClickable(true);
    textView.setGravity(Gravity.CENTER);
    textView.setPadding(10, 0, 10, 0);
    textView.setText(text);
    textView.setTextSize(20);
    return textView;
}
}

EDIT AGAIN:

I have found the issue but I dont know how to fix it. The problem is with mGlobalLayoutListener. This is holding the reference of some view. If I don't use GlobalLayoutListener at all then the FragmentB instance is getting removed from the memory.

Even after calling removeGlobalLayout(), this listener is not getting released. Please help me out.

Nitesh Kumar
  • 5,370
  • 5
  • 37
  • 54

2 Answers2

0

How to remove safely GlobalLayoutListener ? Caution of your Android version, since api is deprecated! :)

Can you try this

 if (Build.VERSION.SDK_INT < 16) {
        v.getViewTreeObserver().removeGlobalOnLayoutListener(listener);
    } else {
        v.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
    }
jeorfevre
  • 2,286
  • 1
  • 17
  • 27
  • I am already dismissing the popupWindow in onStop(). Still there is an instance of DemoPopupWindow in memory. – Nitesh Kumar Nov 13 '15 at 19:13
  • if you do so, we'll need to look at your code execution. I doubt that the solution is in fragment desalocation, but I think that something else is created and not dismissed. Can you please give me more infos on View1 & View2 ? mViewPagerAdapter = new ViewPagerAdapter( Arrays.asList( new View1(mContext, this), new View2(mContext, this) ) ); – jeorfevre Nov 13 '15 at 19:29
  • please share in github your project, because do not see the mistake as it ! With whole project will try to reproduice this bug. – jeorfevre Nov 14 '15 at 00:52
  • I cannot share the project on github as its a huge and private project. Anyway thanks for your support. – Nitesh Kumar Nov 14 '15 at 06:25
  • I have found the issuebut dont know how to fix. Please look at my question again. If I dont add the GlobalLayoutListener then there is no instance if FragmentB in memory. – Nitesh Kumar Nov 14 '15 at 09:45
  • I have already tried that. But still no help. If I remove everything written inside onGlobalLayout() of mGlobalLayoutListener then FragmentB instance is released from the memory. I guess the code written in mGlobalLayoutListener is making some view/variable hold the reference. – Nitesh Kumar Nov 14 '15 at 12:26
  • there is something that we don't get, provide your code please, I'll take a deeper look to it. (share a github project, in private, my github pseudo is https://github.com/jeorfevre – jeorfevre Nov 15 '15 at 13:50
0

are you sure CustomPopupWindow is causing you memory leak? Have you done garbage collection before running heap dump, maybe there is no leak at all..? It's called onDestroy in FragmentB with popup when you goes back to fragmentA?

mmmatey
  • 666
  • 8
  • 15
  • Just removeGlobalLayoutListener in onGlobalLayout method..on start of method or on stop.. But I still don't think that is leak. – mmmatey Nov 14 '15 at 20:15