踩坑-复用RemoteView导致内存泄漏总结

复用RemoteView导致内存泄漏总结

关于RemoteView

RemoteView是一个提供跨进程控制的View,主要用在通知栏或者小部件的开发上。例如音乐类APP自定义的通知栏样式就是通过RemoteView实现的。如果你之前没有听说过RemoteView,可以在这里简单了解一下:

https://www.jianshu.com/p/23041852bd85

RemoteView中可以使用的布局和控件是受限制的,能用的布局有:

  • AdapterViewFlipper
  • FrameLayout
  • GridLayout
  • GridView
  • LinearLayout
  • ListView
  • RelativeLayout
  • StackView
  • ViewFlipper

可以用的控件有:

  • AnalogClock
  • Button
  • Chronometer
  • ImageButton
  • ImageView
  • ProgressBar
  • TextClock
  • TextView

遇到的问题

最近开发了一个音乐类的App,播放音乐时会在通知栏常驻一个自定义样式的通知,其中通知栏有一个头像是通过setImageViewBitmap(int viewId, Bitmap bitmap)方法进行设置的:

1
2
3
4
5
6
RemoteViews mRemoteView;  // 全局变量

mRemoteView = new RemoteViews(Application.getInstance().getPackageName(), resId);

// 每次调用setImageViewBitmap来更新通知栏的头像
mRemoteView.setImageViewBitmap(R.id.cover, bitmap);

后来发现如果App连续放歌在3个小时左右时就会OOM崩掉,通过Profile检查内存后,发现是头像的bitmap没有销毁导致的,但是这里的bitmap对象每次使用完都会recycle掉,为什么还会内存泄漏呢?经过一番排查,发现是使用同一个RemoteVIew对象setImageViewBitmap(R.id.cover, bitmap)导致的。

RemoteView的源码中我们可以看到一个mActions变量,这是一个Action的列表:

1
2
3
4
5
/**
* An array of actions to perform on the view tree once it has been
* inflated
*/
private ArrayList<Action> mActions;

而Action是其内部定义的一个可序列化的类:

1
private abstract static class Action implements Parcelable 

通过跟踪setImageViewBitmap(int viewId, Bitmap bitmap)中bitmap的去向,发现最终调用了setBitmap()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public void setBitmap(int viewId, String methodName, Bitmap value) {
addAction(new BitmapReflectionAction(viewId, methodName, value));
}

private class BitmapReflectionAction extends Action {
int bitmapId;
Bitmap bitmap;
String methodName;

BitmapReflectionAction(int viewId, String methodName, Bitmap bitmap) {
this.bitmap = bitmap;
...
}
...

@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(TAG);
dest.writeInt(viewId);
dest.writeString(methodName);
dest.writeInt(bitmapId);
}

...
}

可以看出,每次setImageViewBitmap(),都会将Bitmap做成一个BitmapReflectionAction,并添加到mActions列表里,这里的BitmapReflectionAction是继承Action的一个可序列化的类,Bitmap在里面作为被序列化成了一组值最终存到了mActions列表中。在源码中,mActions列表只看到有添加操作,并没有看到remove或者clear操作,导致了内存泄漏。

解决

不要复用RemoteView,更新通知栏icon时,new一个新的RemoteView给NotificationCompat.Builder

参考

So internally RemoteViews is simply a set of actions that are “serialized” and sent to another process. Each time you make a call to something like setDouble(), you’re adding an additional action to RemoteViews’ internal list.

Because there isn’t a way of clearing these actions from a RemoteViews object, all of your successive setImageViewBitmap() calls, along with their Bitmaps, remain in the internal list, and are actually “serialized” and applied each time your send it. :( In this case it’s best to just create a new RemoteViews object every time.

https://github.com/rojdes/AngryDict/blob/master/app/src/main/java/me/rds/angrydictionary/widget/BinaryClockWidget.java

https://blog.csdn.net/u013989732/article/details/78501462

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×