简体   繁体   中英

RecyclerView: Reloading the data without blocking the UI thread

I am working on an app that scans your phone for MP3 and then puts them all into a list, handled by a recyclerview for you to scroll through them and play them.

I managed to get all of that to work before I had the recyclerview , but the loading of the many songs (on my phone 700+) took about 30 seconds each time, the app was open. That is ofcourse unacceptable. So now everything is being controlled by a recyclerview , but I just don't know how to fill the reload adapter so that it doesn't reload all at once but step by step. This is what it looks ATM:

The reload adapter:

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;
    }


}

My Main Activity, where I load a list with all the mp3 (taking to long):

    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;
    }

How would I convert this, so that the loading happens not in the main activity, but where it is neede inside the recycler view reload adapter?

I really need your help.

Thanks so much :)

I don't think you understood the RecyclerView usage correctly. The RecyclerView has the responsibility of displaying your items on the screen and nothing more.
Any data loading inside the RecyclerView mostly results in laggy UIs and scrolling.

To put it right you need to load your data outside of RecyclerView 's adapter and then notify it to project the changes. And if you have a lot data which takes a lot of time to load you can do it in smaller steps and update the RecyclerView after each step. Say for example you load your 700 songs in 100 song per step.

This is some sample-pseudo-code to give you some ideas. So don't expect it to actually work.

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)
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM