PopupWindow引起的内存泄漏

时间:2015-11-13 15:10:50

标签: android android-fragments memory memory-leaks android-popupwindow

我有一个FragmentA。当我点击FragmentA中的一个按钮时,我会去FragmentB。在FragmentB中我有一个PopupWindow。 PopupWindow有一个有两页的ViewPager。

我从这段代码中获得了帮助 - Emojicon

我有两个单独的类View1和View2,分别用于ViewPager第1页和第2页的视图。这两个类View1和View2都扩展了父类ViewBase。

这是我的问题:

场景1:当我在FragmentA时,内存图显示13MB利用率。当我在没有显示PopupWindow的情况下访问FragmentB时,内存图显示为16MB,当我回到FragmentA时,它降至13MB。这很好。

场景2:当我在FragmentA时,内存图显示13MB利用率。当我通过显示PopupWindow去FragmentB时,内存图显示20MB,当我回到FragmentA时,它不会降到13MB。

我已经尝试过Eclipse MAT和Heap转储来找出问题,但仍然没有帮助。我可以在MAT中看到FragmentB仍然在内存中,当我回到FragmentA时,持有PopupWindow,View1和View2的实例。他们都没有被释放。 FragmentB不应该在内存中。

请帮帮我。

这是我的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();
}
}

请注意,我还没有在这里粘贴完整的PopupWindow课程,只是必要的部分。

以下是我在FragmentB中使用此DemoPopupWindow的方法

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();
        }
    });

在FragmentB onDestroy中,我调用此方法删除GlobalLayoutListener

mPopupWindow.removeGlobalListener();

我在FragmentB中有一个按钮来显示和关闭PopupWindow。

这是我的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 () {
}
}

这是我的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;
}
}

再次编辑:

我发现了这个问题,但我不知道如何修复它。问题出在mGlobalLayoutListener上。这是一些观点的参考。如果我根本不使用GlobalLayoutListener,那么FragmentB实例将从内存中删除。

即使在调用removeGlobalLayout()之后,这个监听器也没有被释放。请帮帮我。

2 个答案:

答案 0 :(得分:0)

如何安全地删除GlobalLayoutListener? 注意你的Android版本,因为api已被弃用! :)

你能试试吗

for i in list:
    try:
        #look for even or odd
    except:
        break

答案 1 :(得分:0)

你确定CustomPopupWindow会导致内存泄漏吗?你是否在运行堆转储之前完成了垃圾收集,也许根本没有泄漏..? 当你回到fragmentA时,它会在FragmentB中使用弹出窗口调用onDestroy吗?