2015-12-31 14 views
3

我正在構建一個用於播放音頻書籍的Android應用程序。使用FirebaseRecyclerViewAdapter的OutOfMemoryError

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 
     --- ... 

我有聲書,AudioBookTrack和AudioBookCover爲更好地瞭解三個模型類(去除getter和setter方法:有聲讀物元數據(標題,作者等)用以下結構存儲在數據庫火力地堡)。

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 [email protected] 
    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(無仿真器應用程序)。 我想這是因爲我的嵌套模型(AudioBookTracks裏面的AudioBooks),導致數百個Java對象。不幸的是我不知道如何解決這個問題。只列出音頻書籍,我不需要他們相應的音頻書籍音軌,但是音頻書籍音軌是音頻書籍的子元素,它們也被轉換爲Java對象。 另一方面,我也嘗試通過使用ref.limitToFirst(2)來限制正在顯示的有聲讀物的數量。該應用程序仍然崩潰,但它需要更多的時間。

您是否看到任何可能導致問題的代碼問題?或者是否有可能只從Firebase數據庫中選擇我需要的值,例如跳過有聲書的軌道?

謝謝!

回答

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(這就是它在數據庫中建模的方式)。

+0

非常感謝!我按照你的建議改變了我的數據模型,現在它工作正常。 – BGoll