Android Developers Blogでも紹介されているように、Android Studio 1.5のstableバージョンがリリースされた。
特に、今回のリリースでMemory Profiler機能がついたというので早速試してみる。
In addition to the stability improvements and bug fixes, we’ve added a new feature to the memory profiler. It can now assist you in detecting some of the most commonly known causes of leaked activities.
最近は高性能な端末が出てきて、メモリ容量も多くなったとはいえ、メモリ管理はパフォーマンスに影響するため、いつの時代も大事なキーワード。
以前はheap dumpを取得して、EclipseのMemoryAnalyzerで見るという感じでやっていたが、AndroidStudioで完結できるということでだいぶ楽になりそう。
今一度実際に作業をしながらメモリの解放手順確認とともに、MemoryProfilerを使ってみる。
Leakするサンプルアプリ
とりあえず、強制的にメモリリークするような犬でリークするサンプルアプリを作って試してみる。
概要は以下のとおり
- FragmentにRecyclerViewを使って、Bitmap画像を読みこませる(50個程度)
- FABを押すと更に同じFragmentを生成する
- 画像をクリックすると、トップに戻り、FragmentのBackstackをすべてクリアする
下記要領で、Leakするようにして確認する。
- Bitmapのrecycleを呼ばない
- FragmentのonDestroyViewで、AdapterやListenerを開放しない
早速MemoryProfilerを使ってみる
手順
みるみるうちに増えていく(段々と上がっている部分で新たに犬リストのFragmentを生成している)
- 一旦トップまで戻って、強制的にGCをかける(MemoryMonitorの左側のトラックアイコンをクリック)
案の定、メモリは減らない
MemoryProfilerを使ってメモリを確認
-
同じくMemoryMonitorの左側の下から2番目(Heap Java Dump)を押してしばらく待つと、下記のようなプロファイルが生成される
-
この中で、Heap CountとかRetained Heapでソートをかけて、何がメモリを圧迫しているかを確認する(検索で怪しいクラスを探してもOK)
いましたね、Bitmap。それも大量に。
ちなみに、下記のようにBitmapのところで右クリックして、View Bitmapを選択すると実際の画像が見れる。
ほら!
あとは、RecyclerViewのAdapterインスタンスもいっぱいいた。
Leakを修正したアプリで再度MemoryProfilerを使う。
リーク修正方針は下記の通り
- ViewHolderで保持しているbitmapをrecycleする
- FragmentのonDestroyViewでadapterのリスナーを解放、recyclerViewからadapterをデタッチ
- 上記と同じ手順で操作し、GCをかけた後にMemoryProfilerを見てみる。
まずはBitmap
犬がいなくなった。
2つ残ってるのは、もともと入っているBitmapっぽい。
つづいてAdapterとListener
Heap Count 0。キレイ。
実際にGCかけた時はこんな感じだったので、これでもある程度のメモリ解放は把握できる。
今回使用したコード
今回試してみたのはこんな感じのソースコード
- Fragment
public class ImageListFragment extends Fragment implements ImageAdapter.OnImageAdapterListener {
private RecyclerView mRecyclerView;
private ImageAdapter mAdapter;
public ImageListFragment() {
}
public static ImageListFragment newInstance() {
return new ImageListFragment();
}
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAdapter = new ImageAdapter(getContext());
mAdapter.setOnImageAdapterListener(this);
}
@Nullable
@Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
return inflater.inflate(R.layout.list_layout, container, false);
}
@Override
public void onViewCreated(final View view, @Nullable final Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
mRecyclerView.setAdapter(mAdapter);
}
@Override
public void onDestroyView() {
// ここのコメントアウトを外して、メモリ解放する
// mAdapter.setOnImageAdapterListener(null);
// final int childrenCount = mRecyclerView.getChildCount();
// for (int i = 0; i < childrenCount; i++) {
// mAdapter.recycle((ImageAdapter.ImageViewHolder) mRecyclerView.findViewHolderForAdapterPosition(i));
// }
// mAdapter = null;
// mRecyclerView.setAdapter(null);
// mRecyclerView = null;
super.onDestroyView();
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public void onClickView() {
FragmentManager manager = getFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
List<Fragment> fragments = manager.getFragments();
for (final Fragment fragment : fragments) {
transaction.remove(fragment);
}
transaction.commit();
}
}
- Adapter
public class ImageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Context mContext;
private OnImageAdapterListener mOnImageAdapterListener;
public interface OnImageAdapterListener {
void onClickView();
}
public ImageAdapter(final Context context) {
mContext = context;
}
public void setOnImageAdapterListener(final OnImageAdapterListener listener) {
mOnImageAdapterListener = listener;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
final View view = View.inflate(mContext, R.layout.image_row, null);
return new ImageViewHolder(mContext, view);
}
@Override
public int getItemCount() {
return 50;
}
@Override
public int getItemViewType(final int position) {
return super.getItemViewType(position);
}
@Override
public long getItemId(final int position) {
return super.getItemId(position);
}
@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
}
public void recycle(final ImageViewHolder holder) {
// これを呼ぶとViewHolderのBitmapをrecycleする
if (holder != null) {
holder.clear();
}
}
public class ImageViewHolder extends RecyclerView.ViewHolder {
private ImageView mImageView;
private Bitmap mBitmap;
public ImageViewHolder(final Context context, final View view) {
super(view);
mImageView = (ImageView) view.findViewById(R.id.image_view);
mBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.test);
mImageView.setImageBitmap(mBitmap);
mImageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
if (mOnImageAdapterListener != null) {
mOnImageAdapterListener.onClickView();
}
}
});
}
public void clear() {
if (mBitmap != null && !mBitmap.isRecycled()) {
mBitmap.recycle();
mBitmap = null;
}
}
}
}