为什么 viewPager 会导致内存泄漏?
why viewPager cause memory leak?
在我的应用程序中,我在 viewPager 中使用,它使用 FragmentStatePagerAdapter 调整片段。
每个片段包含 gridView 和 GridViewAdapter。
一切正常,但每次交换时,内存使用量都会增加很多并浪费内存。
为什么会发生,我该如何解决?
片段适配器class:
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import java.util.Calendar;
public class FragmentAdapter extends FragmentStatePagerAdapter {
private static final int NUM_ITEMS = 12;
public FragmentAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
switch (position) {
case Calendar.JANUARY:
return MonthFragment.newInstance(Calendar.JANUARY);
case Calendar.FEBRUARY:
return MonthFragment.newInstance(Calendar.FEBRUARY);
case Calendar.MARCH:
return MonthFragment.newInstance(Calendar.MARCH);
case Calendar.APRIL:
return MonthFragment.newInstance(Calendar.APRIL);
case Calendar.MAY:
return MonthFragment.newInstance(Calendar.MAY);
case Calendar.JUNE:
return MonthFragment.newInstance(Calendar.JUNE);
case Calendar.JULY:
return MonthFragment.newInstance(Calendar.JULY);
case Calendar.AUGUST:
return MonthFragment.newInstance(Calendar.AUGUST);
case Calendar.SEPTEMBER:
return MonthFragment.newInstance(Calendar.SEPTEMBER);
case Calendar.OCTOBER:
return MonthFragment.newInstance(Calendar.OCTOBER);
case Calendar.NOVEMBER:
return MonthFragment.newInstance(Calendar.NOVEMBER);
case Calendar.DECEMBER:
return MonthFragment.newInstance(Calendar.DECEMBER);
default:
throw new IllegalArgumentException("there is problem with position", new Throwable("NUM_ITEMS is not 12"));
}
}
@Override
public int getCount() {
return NUM_ITEMS;
}
}
MonthFragment class:
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.GridView;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
public class MonthFragment extends Fragment {
private static final String ARG_MONTH = "arg_month";
private int fragmentMonthId;
private GridView gridView;
private GridViewAdapter adapter;
private ArrayList<Date> monthDates;
public MonthFragment() {
// Required empty public constructor
}
public static MonthFragment newInstance(int monthId) {
MonthFragment fragment = new MonthFragment();
Bundle args = new Bundle();
args.putInt(ARG_MONTH, monthId);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
fragmentMonthId = getArguments().getInt(ARG_MONTH);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_month, container, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
gridView = view.findViewById(R.id.days);
monthDates = getDates();
adapter = new GridViewAdapter(getContext(), R.layout.day, 0, monthDates);
gridView.setAdapter(adapter);
}
private ArrayList<Date> getDates() {
ArrayList<Date> arrayList = new ArrayList<>();
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.DAY_OF_MONTH, 1);
calendar.set(Calendar.MONTH, fragmentMonthId);
calendar.set(Calendar.YEAR, calendar.get(Calendar.YEAR));
for (int i = 0; i < calendar.getMaximum(Calendar.DAY_OF_MONTH); i++) {
arrayList.add(calendar.getTime());
calendar.add(Calendar.DATE, 1);
}
return arrayList;
}
@Override
public void onDestroyView() {
super.onDestroyView();
monthDates.clear();
adapter = null;
gridView.setAdapter(null);
gridView = null;
}
}
GridViewAdapter class:
package com.example.kobimoshe.calendarwithpager;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class GridViewAdapter extends ArrayAdapter<Date> {
ArrayList<Date> datesList;
public GridViewAdapter(@NonNull Context context, int resource, int textViewResourceId, @NonNull List<Date> objects) {
super(context, resource, textViewResourceId, objects);
datesList = (ArrayList<Date>) objects;
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.day, parent, false);
}
TextView dateView = (TextView) convertView.findViewById(R.id.date);
dateView.setText(datesList.get(position).toString());
return convertView;
}
}
更新:
经过实验和研究,我假设两件事:
GC 不会删除片段内的元素,即使我在片段被销毁时删除了它们(我假设是因为当 onDestroyView 被调用时内存使用图没有减少) .
每次交换也会造成内存泄漏(我用 ViewPager 创建了一个新的,它使用 FragmentStatePagerAdapter 来设置空片段。每次在此应用程序中交换片段时,内存使用量都会增加)。
谁能给我解释一下为什么不能完全删除元素?如何修复代码以便在删除片段时清理它们?
即使片段以某种方式从中删除,保存在内存中。如何从内存中删除片段以节省恒定的内存大小?
检查你的引用,99% 的时间内存泄漏是由应该被垃圾收集但仍然存在的持久对象引起的。确保摆脱不再使用的对象。
此外,当您有大量片段并且在页面之间切换时会增加一些开销时,FragmentStatePagerAdapter
效果最佳。考虑尝试 FragmentPagerAdapter
,它更适合少量片段。正如已经提到的,你应该实施 viewholder pattern 因为你有一个视图列表。
我已成功解决问题
在片段 class 的 onDetach 方法中,我删除了所有子片段引用。然后,我调用 Runtime.getRuntime().gc()
以强制 GC 从未使用的元素中清理内存;
在我的应用程序中,我在 viewPager 中使用,它使用 FragmentStatePagerAdapter 调整片段。 每个片段包含 gridView 和 GridViewAdapter。
一切正常,但每次交换时,内存使用量都会增加很多并浪费内存。 为什么会发生,我该如何解决?
片段适配器class:
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import java.util.Calendar;
public class FragmentAdapter extends FragmentStatePagerAdapter {
private static final int NUM_ITEMS = 12;
public FragmentAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
switch (position) {
case Calendar.JANUARY:
return MonthFragment.newInstance(Calendar.JANUARY);
case Calendar.FEBRUARY:
return MonthFragment.newInstance(Calendar.FEBRUARY);
case Calendar.MARCH:
return MonthFragment.newInstance(Calendar.MARCH);
case Calendar.APRIL:
return MonthFragment.newInstance(Calendar.APRIL);
case Calendar.MAY:
return MonthFragment.newInstance(Calendar.MAY);
case Calendar.JUNE:
return MonthFragment.newInstance(Calendar.JUNE);
case Calendar.JULY:
return MonthFragment.newInstance(Calendar.JULY);
case Calendar.AUGUST:
return MonthFragment.newInstance(Calendar.AUGUST);
case Calendar.SEPTEMBER:
return MonthFragment.newInstance(Calendar.SEPTEMBER);
case Calendar.OCTOBER:
return MonthFragment.newInstance(Calendar.OCTOBER);
case Calendar.NOVEMBER:
return MonthFragment.newInstance(Calendar.NOVEMBER);
case Calendar.DECEMBER:
return MonthFragment.newInstance(Calendar.DECEMBER);
default:
throw new IllegalArgumentException("there is problem with position", new Throwable("NUM_ITEMS is not 12"));
}
}
@Override
public int getCount() {
return NUM_ITEMS;
}
}
MonthFragment class:
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.GridView;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
public class MonthFragment extends Fragment {
private static final String ARG_MONTH = "arg_month";
private int fragmentMonthId;
private GridView gridView;
private GridViewAdapter adapter;
private ArrayList<Date> monthDates;
public MonthFragment() {
// Required empty public constructor
}
public static MonthFragment newInstance(int monthId) {
MonthFragment fragment = new MonthFragment();
Bundle args = new Bundle();
args.putInt(ARG_MONTH, monthId);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
fragmentMonthId = getArguments().getInt(ARG_MONTH);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_month, container, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
gridView = view.findViewById(R.id.days);
monthDates = getDates();
adapter = new GridViewAdapter(getContext(), R.layout.day, 0, monthDates);
gridView.setAdapter(adapter);
}
private ArrayList<Date> getDates() {
ArrayList<Date> arrayList = new ArrayList<>();
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.DAY_OF_MONTH, 1);
calendar.set(Calendar.MONTH, fragmentMonthId);
calendar.set(Calendar.YEAR, calendar.get(Calendar.YEAR));
for (int i = 0; i < calendar.getMaximum(Calendar.DAY_OF_MONTH); i++) {
arrayList.add(calendar.getTime());
calendar.add(Calendar.DATE, 1);
}
return arrayList;
}
@Override
public void onDestroyView() {
super.onDestroyView();
monthDates.clear();
adapter = null;
gridView.setAdapter(null);
gridView = null;
}
}
GridViewAdapter class:
package com.example.kobimoshe.calendarwithpager;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class GridViewAdapter extends ArrayAdapter<Date> {
ArrayList<Date> datesList;
public GridViewAdapter(@NonNull Context context, int resource, int textViewResourceId, @NonNull List<Date> objects) {
super(context, resource, textViewResourceId, objects);
datesList = (ArrayList<Date>) objects;
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.day, parent, false);
}
TextView dateView = (TextView) convertView.findViewById(R.id.date);
dateView.setText(datesList.get(position).toString());
return convertView;
}
}
更新:
经过实验和研究,我假设两件事:
GC 不会删除片段内的元素,即使我在片段被销毁时删除了它们(我假设是因为当 onDestroyView 被调用时内存使用图没有减少) .
每次交换也会造成内存泄漏(我用 ViewPager 创建了一个新的,它使用 FragmentStatePagerAdapter 来设置空片段。每次在此应用程序中交换片段时,内存使用量都会增加)。
谁能给我解释一下为什么不能完全删除元素?如何修复代码以便在删除片段时清理它们? 即使片段以某种方式从中删除,保存在内存中。如何从内存中删除片段以节省恒定的内存大小?
检查你的引用,99% 的时间内存泄漏是由应该被垃圾收集但仍然存在的持久对象引起的。确保摆脱不再使用的对象。
此外,当您有大量片段并且在页面之间切换时会增加一些开销时,FragmentStatePagerAdapter
效果最佳。考虑尝试 FragmentPagerAdapter
,它更适合少量片段。正如已经提到的,你应该实施 viewholder pattern 因为你有一个视图列表。
我已成功解决问题
在片段 class 的 onDetach 方法中,我删除了所有子片段引用。然后,我调用 Runtime.getRuntime().gc()
以强制 GC 从未使用的元素中清理内存;