【问题标题】:OutOfMemoryError using FirebaseRecyclerViewAdapterOutOfMemoryError 使用 FirebaseRecyclerViewAdapter
【发布时间】:2015-12-31 18:44:50
【问题描述】:

我正在构建一个用于播放有声读物的安卓应用。有声读物元数据(标题、作者等)使用以下结构存储在 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);
}

现在这段代码通常可以工作(我可以看到屏幕上显示的值),但是在很短的时间后,应用程序由于内存不足错误而崩溃:

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 对象。不幸的是,我不知道如何克服这个问题。为了仅列出有声读物,我不需要它们对应的有声读物轨道,但是有声读物轨道是有声读物的子元素,它们也被转换为 Java 对象。 另一方面,我还尝试使用 ref.limitToFirst(2) 来限制显示的有声读物的数量。该应用程序仍然会崩溃,但需要更多时间才能崩溃。

您是否发现我的代码存在任何可能导致问题的问题?或者是否有可能只从 Firebase 数据库中选择我需要的值,跳过例如有声书的曲目?

谢谢!

【问题讨论】:

    标签: java android firebase out-of-memory nosql


    【解决方案1】:

    这个问题确实很可能是由于您为每本书加载了大量不必要的数据。特别是在移动设备上,您应该只加载要向用户显示的数据。由于 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 下(您称之为“每本书的单独哈希”),但在不同的顶级节点下。本书本身的数据在audioBooks下,曲目列表在authBookTracks下。

    现在您可以分别加载一本书的信息或该书的曲目:

    ref.child("audioBooks").child(bookId).addValueEventListener(...
    

    ref.child("audioBookTracks").child(bookId).addChildEventListener(...
    

    您可能可以对AudioBook Java 类进行建模以保留其tracks 成员而不对其进行序列化(使用Jackson 注释)。但是您也可以将一本书及其曲目列表视为恰好具有相同 ID 的独立顶级实体(这就是它在数据库中的建模方式)。

    【讨论】:

    • 非常感谢!我按照您建议的方式更改了我的数据模型,现在它可以正常工作了。
    猜你喜欢
    • 2016-05-24
    • 2016-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-14
    • 1970-01-01
    相关资源
    最近更新 更多