RecyclerView:重新加载数据而不阻塞UI线程

时间:2018-10-04 14:33:13

标签: android android-recyclerview

我正在开发一个应用程序,该应用程序扫描您的手机中的MP3,然后将它们全部放入列表中,并由recyclerview处理,以便您滚动浏览并播放它们。

在设法获得recyclerview之前,我设法使所有这些功能都起作用,但是每次打开该应用程序时,加载许多歌曲(在我的手机700+上)花费了大约30秒。当然这是不能接受的。因此,现在所有内容都由recyclerview控制,但我只是不知道如何填充重载适配器,因此它不会一次重载,而是一步一步地重载。这就是ATM的外观:

重新加载适配器:

public class MySimpleItemLoader
{
    public List<MP3object> mP3Objects { get; private set; }
    public bool IsBusy { get; set; }
    public int CurrentPageValue { get; set; }
    public bool CanLoadMoreItems { get; private set; }

    public MySimpleItemLoader(List<MP3object> mp3)
    {
        this.mP3Objects = mp3;
    }

    public void LoadMoreItems(int itemsPerPage)
    {
        IsBusy = true;
        for (int i = CurrentPageValue; i < CurrentPageValue + itemsPerPage; i++)
        {
            mP3Objects.Add(new MP3object() { AlbumName = string.Format("This is item {0:0000}", i) });
        }

        CanLoadMoreItems = true;
        CurrentPageValue = mP3Objects.Count;
        IsBusy = false;
    }


}

我的主要活动,我在其中加载了所有mp3的列表(需要很长的时间):

    private void PopulateMP3List(List<string> content)
    {
       mp3 = new List<MP3object>();        
       foreach (string obj in content)
       {
           WriteMetaDataToFileList(obj);        
       }
    }



    void WriteMetaDataToFileList(string obj)
    {
        reader.SetDataSource(obj);

        //Write Mp3 as object to global list
        MP3object ob = new MP3object();
        {
            if(reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyTitle) != "" && reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyTitle) != null)
            {
                ob.SongName = reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyTitle);
            }
            else
            {
                ob.SongName = Resources.GetString(Resource.String.Unknown);
            }

            if (reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyArtist) != "" && reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyArtist) != null)
            {
                ob.ArtistName = reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyArtist);
            }
            else
            {
                ob.ArtistName = Resources.GetString(Resource.String.Unknown);
            }

            if (reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyAlbum) != "" && reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyAlbum) != null)
            {
                ob.AlbumName = reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyAlbum);
            }
            else
            {
                ob.AlbumName = Resources.GetString(Resource.String.Unknown);
            }

            if (reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyYear) != "" && reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyYear) != null)
            {
                ob.Year = reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyYear);
            }
            else
            {
                ob.Year = Resources.GetString(Resource.String.Unknown);
            }

            if (reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyYear) != "" && reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyYear) != null)
            {
                ob.Year = reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyYear);
            }
            else
            {
                ob.Year = Resources.GetString(Resource.String.Unknown);
            }

            ob.Mp3Uri = obj; // can never be unknown!

            ob.DurationInSec = int.Parse(reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyDuration)) / 1000; // can never be unknown, div by 1000 to get sec not millis
        }
        mp3.Add(ob);

    }

    public List<string> ReturnPlayableMp3(bool sdCard)
    {
        List<string> res = new List<string>();
        string phyle;
        string path1 = null;

        if(sdCard) // get mp3 from SD card
        {
            string baseFolderPath = "";

            try
            {
                bool getSDPath = true;

                Context context = Application.Context;
                Java.IO.File[] dirs = context.GetExternalFilesDirs(null);

                foreach (Java.IO.File folder in dirs)
                {
                    bool IsRemovable = Android.OS.Environment.InvokeIsExternalStorageRemovable(folder);
                    bool IsEmulated = Android.OS.Environment.InvokeIsExternalStorageEmulated(folder);

                    if (getSDPath ? IsRemovable && !IsEmulated : !IsRemovable && IsEmulated)
                        baseFolderPath = folder.Path;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("GetBaseFolderPath caused the following exception: {0}", ex);
            }

            string xy = baseFolderPath.Remove(18); // This is result after this, but this hard coded solution could be a problem on different phones.: "/storage/05B6-2226/Android/data/Media_Player.Media_Player/files"

            path1 = xy;
            // path to SD card and MUSIC "/storage/05B6-2226/"
        }
        else // get Mp3 from internal storage
        {
                path1 = Android.OS.Environment.ExternalStorageDirectory.ToString();
        }

        var mp3Files = Directory.EnumerateFiles(path1, "*.mp3", SearchOption.AllDirectories);

        foreach (string currentFile in mp3Files)
        {
            phyle = currentFile;
            res.Add(phyle);
        }

        return res;
    }

我将如何转换它,以便加载不会发生在主要活动中,而是需要在回收视图重新加载适配器中的何处进行?

我真的需要你的帮助。

非常感谢:)

1 个答案:

答案 0 :(得分:2)

我认为您没有正确理解RecyclerView的用法。 RecyclerView的职责是在屏幕上显示您的项目,仅此而已。
RecyclerView内部加载的任何数据大多会导致UI延迟和滚动。

要正确处理,您需要在RecyclerView的适配器之外加载数据,然后通知它来投影更改。而且,如果您有很多数据需要花费很多时间来加载,则可以以较小的步骤进行操作,并在每个步骤之后更新RecyclerView。假设您以每步100首的速度加载700首歌曲。

这是一些示例伪代码,可为您提供一些思路。因此,不要指望它真正起作用。

val audioFiles = mutableListOf<String>()
Observable.create{ e ->
    for{
        val offset = audioFiles.size
        // load your files here
        if(audioFiles.size % 100){
            e.onNext(offset)
        }
    }
    e.onComplete()
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe{ offset ->
    mAdapter.notifyItemRangeInserted(offset, offset+100)
}