弹出窗口导致内存泄漏



我有一个FragmentA。当我点击片段a中的一个按钮时,我会转到片段B。在FragmentB中,我有一个弹出窗口。PopupWindow有一个包含两个页面的ViewPager。

我从这个代码中得到了帮助-表情符号

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

这是我的问题:

场景1:当我在FragmentA时,内存图显示13MB的利用率。当我转到FragmentB而不显示PopupWindow时,内存图显示16MB,当我回到FragmentA时,它降到13MB。这很好。

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

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

请帮帮我。

这是我的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()之后,这个监听器也不会被释放。请帮帮我。

您确定CustomPopupWindow导致内存泄漏吗?在运行堆转储之前,你是否已经完成了垃圾收集,也许根本没有泄漏。。?当你回到碎片A时,它在碎片B中被称为onDestroy并弹出?

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

你能试试这个吗

 if (Build.VERSION.SDK_INT < 16) {
        v.getViewTreeObserver().removeGlobalOnLayoutListener(listener);
    } else {
        v.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
    }

最新更新