0

У меня есть FragmentA. Когда я нажимаю кнопку в FragmentA, я иду в FragmentB. В FragmentB у меня есть PopupWindow. В PopupWindow есть ViewPager с двумя страницами.Утечка памяти из-за PopupWindow

Я взял справку из этого кода - Emojicon

У меня есть 2 отдельные классы, View1 и View2, для взглядов на странице 1 и 2-ViewPager соответственно. Оба этих класса, View1 и View2, расширяют класс ViewBase родительского класса.

Вот моя проблема:

Сценарий 1: Когда я в Fragmenta график памяти показывает использование 13MB. Когда я перехожу к FragmentB, не показывая PopupWindow, график памяти показывает 16 МБ, и когда я вернусь к FragmentA, он снизится до 13 МБ. Это хорошо.

Сценарий 2: Когда я нахожусь в FragmentA, график памяти показывает использование 13 МБ. Когда я перехожу к FragmentB с показом PopupWindow, график памяти показывает 20 МБ, и когда я вернусь к FragmentA, он не дойдет до 13 МБ.

Я пробовал Eclipse MAT и кучу кучи, чтобы узнать проблему, но по-прежнему не помогаю. Я вижу в 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 здесь, но только необходимая часть.

Вот как я использую этот DemoPopupWindow в моем 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(); 
     } 
    }); 

В 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; 
} 
} 

EDIT СНОВА:

Я нашел этот вопрос, но я не знаю, как это исправить. Проблема заключается в mGlobalLayoutListener. Это удерживает ссылку на какое-то представление. Если я вообще не использую GlobalLayoutListener, экземпляр FragmentB удаляется из памяти.

Даже после вызова removeGlobalLayout() этот прослушиватель не освобождается.Пожалуйста, помогите мне.

+1

привет, вы можете указать код PopupWindow? спасибо – jeorfevre

+0

Пожалуйста, отредактируйте свой вопрос с частями вашего кода. Закрываете ли вы всплывающее окно, а вы вызываете его? – Rohan

+0

Да, у вас есть утечка памяти. Чтобы узнать, где нам нужен код. Также вы можете использовать LeakCanary. – BladeCoder

ответ

0

Как безопасно удалить GlobalLayoutListener? Осторожно, ваша версия Android, так как api устарел! :)

Вы можете попробовать это

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

Я уже увольняю popupWindow в onStop(). Тем не менее в памяти есть экземпляр DemoPopupWindow. –

+0

, если вы это сделаете, нам нужно посмотреть на выполнение кода. Я сомневаюсь, что решение находится в фрагментации desalocation, но я думаю, что что-то еще создано и не уволено. Не могли бы вы дать мне больше информации о View1 & View2? mViewPagerAdapter = новый ViewPagerAdapter ( Arrays.asList ( новый View1 (mContext, this), новый View2 (mContext, this) ) ); – jeorfevre

+0

Пожалуйста, уточните мой обновленный вопрос. –

0

вы уверены, CustomPopupWindow вызывающий утечку памяти? Вы сделали сборку мусора перед запуском кучи кучи, возможно, нет утечки вообще ...? Он называется onDestroy в FragmentB со всплывающим окном, когда вы возвращаетесь к фрагменту?

+0

Просто удалитеGlobalLayoutListener в методе onGlobalLayout .. в начале метода или на остановке. Но я все еще не думаю, что это утечка. – mmmatey

Смежные вопросы