OutOfMemoryError 使用 FirebaseRecyclerViewAdapter
OutOfMemoryError using FirebaseRecyclerViewAdapter
我正在构建一个 android 应用程序来播放有声读物。有声读物元数据(标题、作者等)使用以下结构存储在 Firebase 数据库中:
root
--- audioBooks
--- <individual hash for each book>
--- author: "Ray Bradbury"
--- title: "Fahrenheit 451"
--- finished: false
--- key: <individual hash for each book>
--- cover
--- b64Cover: <b64-encoded image>
--- backgroundColor: -14473711
--- ...
--- tracks
--- 0
--- title: "Track 1"
--- duration: 361273
--- finished: false
--- currentPosition: 12345
--- ...
--- 1
--- ...
--- currentTrack
--- title: "Track 1"
--- duration: 361273
--- finished: false
--- currentPosition: 12345
--- ...
我有三个模型 类 用于 AudioBook、AudioBookTrack 和 AudioBookCover(删除了 setter 和 getter 以获得更好的概览)。
public class AudioBook {
private String title;
private String author;
private int duration;
private boolean finished;
private String key;
private AudioBookCover cover;
private ArrayList<AudioBookTrack> tracks;
private AudioBookTrack currentTrack;
public AudioBook() {
}
public AudioBook(String title, String author, boolean finished, String key, AudioBookCover cover, ArrayList<AudioBookTrack> tracks, AudioBookTrack currentTrack) {
this.title = title;
this.author = author;
this.finished = finished;
this.key = key;
this.cover = cover;
this.tracks = tracks;
this.currentTrack = currentTrack;
}
}
public class AudioBookTrack {
private String title;
private String album;
private String author;
private String filePath;
private int duration;
private int currentPosition;
private int index;
private boolean finished;
private String key;
public AudioBookTrack() {
}
public AudioBookTrack(String title, String album, String author, String filePath, int duration, int currentPosition, int index, boolean finished, String key) {
this.title = title;
this.album = album;
this.author = author;
this.filePath = filePath;
this.duration = duration;
this.currentPosition = currentPosition;
this.index = index;
this.finished = finished;
this.key = key;
}
}
public class AudioBookCover {
private String b64Cover;
private int backgroundColor;
private int primaryColor;
private int secondaryColor;
private int detailColor;
public AudioBookCover() {
}
public AudioBookCover(String b64Cover, int backgroundColor, int primaryColor, int secondaryColor, int detailColor) {
this.b64Cover = b64Cover;
this.backgroundColor = backgroundColor;
this.primaryColor = primaryColor;
this.secondaryColor = secondaryColor;
this.detailColor = detailColor;
}
}
使用 Firebase 数据库中的数据并将其转换为模型 类 没有问题。但是,我现在想使用 FirebaseRecyclerViewAdapter 列出 RecyclerView 中的所有有声读物。这是在片段的 onCreateView() 方法中执行的代码:
mAudioBooksList = (RecyclerView) view.findViewById(R.id.audiobooks_list);
mAudioBooksList.setHasFixedSize(true);
ref = new Firebase(Constants.FIREBASE_URL).child("audioBooks");
mAdapter = new FirebaseRecyclerViewAdapter<AudioBook, AudioBooksViewHolder>(AudioBook.class, R.layout.audiobook_grid_item, AudioBooksViewHolder.class, ref) {
@Override
public void populateViewHolder(AudioBooksViewHolder audioBooksViewHolder, AudioBook audioBook) {
audioBooksViewHolder.author.setText(audioBook.getAuthor());
audioBooksViewHolder.title.setText(audioBook.getTitle());
try {
byte[] byteArray = Base64.decode(audioBook.getCover().getB64Cover());
Bitmap coverBitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length);
audioBooksViewHolder.cover.setImageBitmap(coverBitmap);
}
catch(IOException e) {
Log.e(TAG, "Could not decode album cover: " + e.getMessage());
}
}
};
mAudioBooksList.setAdapter(mAdapter);
这是 ViewHolder:
private static class AudioBooksViewHolder extends RecyclerView.ViewHolder {
TextView author;
TextView title;
ImageView cover;
public AudioBooksViewHolder(View itemView) {
super(itemView);
author = (TextView)itemView.findViewById(R.id.audiobook_grid_author);
title = (TextView)itemView.findViewById(R.id.audiobook_grid_title);
cover = (ImageView) itemView.findViewById(R.id.audiobook_grid_cover);
}
现在这段代码通常可以工作(我可以看到屏幕上显示的值),但是在很短的时间之后,应用程序由于 out-of-memory 错误而崩溃:
Uncaught exception in Firebase runloop (2.4.0). Please report to support@firebase.com
java.lang.OutOfMemoryError: Failed to allocate a 35116844 byte allocation with 16773184 free bytes and 32MB until OOM
at java.lang.StringFactory.newStringFromChars(Native Method)
at java.lang.AbstractStringBuilder.toString(AbstractStringBuilder.java:629)
at java.lang.StringBuilder.toString(StringBuilder.java:663)
...
我在物理 Nexus 5(没有模拟器)上测试应用程序。
我猜这是因为我的嵌套模型(AudioBooks 中的 AudioBookTracks)导致了数百个 Java objects。不幸的是我不知道如何克服这个问题。对于仅列出有声读物,我不需要它们相应的有声读物曲目,但是由于有声读物曲目是有声读物的 child 元素,因此它们也会转换为 Java objects .
另一方面,我还尝试使用 ref.limitToFirst(2) 来限制显示的有声读物的数量。然后应用程序仍然崩溃,但需要更多时间才能崩溃。
您是否发现我的代码存在任何可能导致该问题的问题?或者是否有可能只 select 我需要的来自 Firebase 数据库的值,跳过例如有声书曲目?
谢谢!
这个问题确实很可能是因为您加载了很多关于每本书的不必要数据。特别是在移动设备上,您应该只加载要向用户显示的数据。由于 Firebase 始终加载整个节点,因此您必须 建模 您的数据,以便您可以仅加载您需要的数据。
解决方法是将曲目与有声读物的其他信息分开。这称为 denormalizing the data,是 NoSQL 数据库中非常常见的操作。
非规范化的数据将存储为:
root
--- audioBooks
--- <individual hash for each book>
--- author: "Ray Bradbury"
--- title: "Fahrenheit 451"
--- finished: false
--- key: <individual hash for each book>
--- cover
--- b64Cover: <b64-encoded image>
--- backgroundColor: -14473711
--- ...
--- audioBookTracks
--- <individual hash for each book>
--- 0
--- title: "Track 1"
--- duration: 361273
--- finished: false
--- currentPosition: 12345
--- ...
--- 1
--- ...
--- currentTrack
--- title: "Track 1"
--- duration: 361273
--- finished: false
--- currentPosition: 12345
--- ...
关于一本书的信息以及曲目存储在相同的 id(你称之为 "individual hash for each book")下,但在不同的 top-level 节点下。关于书籍本身的数据在 audioBooks
下,而曲目列表在 authBookTracks
.
下
现在您可以单独加载一本书的信息或该书的曲目:
ref.child("audioBooks").child(bookId).addValueEventListener(...
或
ref.child("audioBookTracks").child(bookId).addChildEventListener(...
您可以对 AudioBook
Java class 建模以保留其 tracks
成员而不对其进行序列化(使用 Jackson 注释)。但您也可以将一本书及其曲目列表视为单独的 top-level 实体,它们恰好具有相同的 ID(这就是它在数据库中的建模方式)。
我正在构建一个 android 应用程序来播放有声读物。有声读物元数据(标题、作者等)使用以下结构存储在 Firebase 数据库中:
root
--- audioBooks
--- <individual hash for each book>
--- author: "Ray Bradbury"
--- title: "Fahrenheit 451"
--- finished: false
--- key: <individual hash for each book>
--- cover
--- b64Cover: <b64-encoded image>
--- backgroundColor: -14473711
--- ...
--- tracks
--- 0
--- title: "Track 1"
--- duration: 361273
--- finished: false
--- currentPosition: 12345
--- ...
--- 1
--- ...
--- currentTrack
--- title: "Track 1"
--- duration: 361273
--- finished: false
--- currentPosition: 12345
--- ...
我有三个模型 类 用于 AudioBook、AudioBookTrack 和 AudioBookCover(删除了 setter 和 getter 以获得更好的概览)。
public class AudioBook {
private String title;
private String author;
private int duration;
private boolean finished;
private String key;
private AudioBookCover cover;
private ArrayList<AudioBookTrack> tracks;
private AudioBookTrack currentTrack;
public AudioBook() {
}
public AudioBook(String title, String author, boolean finished, String key, AudioBookCover cover, ArrayList<AudioBookTrack> tracks, AudioBookTrack currentTrack) {
this.title = title;
this.author = author;
this.finished = finished;
this.key = key;
this.cover = cover;
this.tracks = tracks;
this.currentTrack = currentTrack;
}
}
public class AudioBookTrack {
private String title;
private String album;
private String author;
private String filePath;
private int duration;
private int currentPosition;
private int index;
private boolean finished;
private String key;
public AudioBookTrack() {
}
public AudioBookTrack(String title, String album, String author, String filePath, int duration, int currentPosition, int index, boolean finished, String key) {
this.title = title;
this.album = album;
this.author = author;
this.filePath = filePath;
this.duration = duration;
this.currentPosition = currentPosition;
this.index = index;
this.finished = finished;
this.key = key;
}
}
public class AudioBookCover {
private String b64Cover;
private int backgroundColor;
private int primaryColor;
private int secondaryColor;
private int detailColor;
public AudioBookCover() {
}
public AudioBookCover(String b64Cover, int backgroundColor, int primaryColor, int secondaryColor, int detailColor) {
this.b64Cover = b64Cover;
this.backgroundColor = backgroundColor;
this.primaryColor = primaryColor;
this.secondaryColor = secondaryColor;
this.detailColor = detailColor;
}
}
使用 Firebase 数据库中的数据并将其转换为模型 类 没有问题。但是,我现在想使用 FirebaseRecyclerViewAdapter 列出 RecyclerView 中的所有有声读物。这是在片段的 onCreateView() 方法中执行的代码:
mAudioBooksList = (RecyclerView) view.findViewById(R.id.audiobooks_list);
mAudioBooksList.setHasFixedSize(true);
ref = new Firebase(Constants.FIREBASE_URL).child("audioBooks");
mAdapter = new FirebaseRecyclerViewAdapter<AudioBook, AudioBooksViewHolder>(AudioBook.class, R.layout.audiobook_grid_item, AudioBooksViewHolder.class, ref) {
@Override
public void populateViewHolder(AudioBooksViewHolder audioBooksViewHolder, AudioBook audioBook) {
audioBooksViewHolder.author.setText(audioBook.getAuthor());
audioBooksViewHolder.title.setText(audioBook.getTitle());
try {
byte[] byteArray = Base64.decode(audioBook.getCover().getB64Cover());
Bitmap coverBitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length);
audioBooksViewHolder.cover.setImageBitmap(coverBitmap);
}
catch(IOException e) {
Log.e(TAG, "Could not decode album cover: " + e.getMessage());
}
}
};
mAudioBooksList.setAdapter(mAdapter);
这是 ViewHolder:
private static class AudioBooksViewHolder extends RecyclerView.ViewHolder {
TextView author;
TextView title;
ImageView cover;
public AudioBooksViewHolder(View itemView) {
super(itemView);
author = (TextView)itemView.findViewById(R.id.audiobook_grid_author);
title = (TextView)itemView.findViewById(R.id.audiobook_grid_title);
cover = (ImageView) itemView.findViewById(R.id.audiobook_grid_cover);
}
现在这段代码通常可以工作(我可以看到屏幕上显示的值),但是在很短的时间之后,应用程序由于 out-of-memory 错误而崩溃:
Uncaught exception in Firebase runloop (2.4.0). Please report to support@firebase.com
java.lang.OutOfMemoryError: Failed to allocate a 35116844 byte allocation with 16773184 free bytes and 32MB until OOM
at java.lang.StringFactory.newStringFromChars(Native Method)
at java.lang.AbstractStringBuilder.toString(AbstractStringBuilder.java:629)
at java.lang.StringBuilder.toString(StringBuilder.java:663)
...
我在物理 Nexus 5(没有模拟器)上测试应用程序。 我猜这是因为我的嵌套模型(AudioBooks 中的 AudioBookTracks)导致了数百个 Java objects。不幸的是我不知道如何克服这个问题。对于仅列出有声读物,我不需要它们相应的有声读物曲目,但是由于有声读物曲目是有声读物的 child 元素,因此它们也会转换为 Java objects . 另一方面,我还尝试使用 ref.limitToFirst(2) 来限制显示的有声读物的数量。然后应用程序仍然崩溃,但需要更多时间才能崩溃。
您是否发现我的代码存在任何可能导致该问题的问题?或者是否有可能只 select 我需要的来自 Firebase 数据库的值,跳过例如有声书曲目?
谢谢!
这个问题确实很可能是因为您加载了很多关于每本书的不必要数据。特别是在移动设备上,您应该只加载要向用户显示的数据。由于 Firebase 始终加载整个节点,因此您必须 建模 您的数据,以便您可以仅加载您需要的数据。
解决方法是将曲目与有声读物的其他信息分开。这称为 denormalizing the data,是 NoSQL 数据库中非常常见的操作。
非规范化的数据将存储为:
root
--- audioBooks
--- <individual hash for each book>
--- author: "Ray Bradbury"
--- title: "Fahrenheit 451"
--- finished: false
--- key: <individual hash for each book>
--- cover
--- b64Cover: <b64-encoded image>
--- backgroundColor: -14473711
--- ...
--- audioBookTracks
--- <individual hash for each book>
--- 0
--- title: "Track 1"
--- duration: 361273
--- finished: false
--- currentPosition: 12345
--- ...
--- 1
--- ...
--- currentTrack
--- title: "Track 1"
--- duration: 361273
--- finished: false
--- currentPosition: 12345
--- ...
关于一本书的信息以及曲目存储在相同的 id(你称之为 "individual hash for each book")下,但在不同的 top-level 节点下。关于书籍本身的数据在 audioBooks
下,而曲目列表在 authBookTracks
.
现在您可以单独加载一本书的信息或该书的曲目:
ref.child("audioBooks").child(bookId).addValueEventListener(...
或
ref.child("audioBookTracks").child(bookId).addChildEventListener(...
您可以对 AudioBook
Java class 建模以保留其 tracks
成员而不对其进行序列化(使用 Jackson 注释)。但您也可以将一本书及其曲目列表视为单独的 top-level 实体,它们恰好具有相同的 ID(这就是它在数据库中的建模方式)。