You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android中RecyclerView加载900首歌曲耗时过长的优化方案咨询

优化Android歌曲加载速度的几个关键方案

Hey there! Loading 900 songs shouldn't take 9-10 seconds—let's dig into the bottlenecks in your code and fix them to get that load time way down.

1. 移除同步网络请求(最大的性能杀手)

Your biggest issue right now is probably the getAlbumArtFromInternet call inside the cursor loop. Doing a synchronous network request for every single song blocks the background thread until each request completes—with 900 songs, this adds massive overhead that's almost certainly causing the long load time.

Instead, move album art loading to your SongAdapter using an image loading library like Glide or Picasso. These libraries handle async loading, caching, and placeholder images automatically:

  • In your SongInfoModel, store just the album ID/artist/title instead of the pre-fetched URI.
  • In your Adapter's ViewHolder, load the art when binding the item:
// Example with Glide (Java implementation)
Glide.with(itemView.getContext())
    .load(getLocalAlbumArtUri(albumId)) // Try local art first
    .placeholder(R.drawable.default_album_art)
    .error(Glide.with(itemView.getContext())
        .load(getInternetAlbumArtUrl(artist, title))) // Async fallback to internet
    .into(albumArtImageView);

This way, album art loads in the background after your song list is already displayed, so the RecyclerView populates instantly.

2. 优化Cursor解析效率

Your cursor loop is doing unnecessary repetitive work that adds up:

  • Precompute column indices: Calling cursor.getColumnIndex() 9 times per song (900 songs = 8100 calls) is inefficient. Get all indices once before the loop:
// Inside the background thread, after confirming cursor is not null
int idCol = cursor.getColumnIndex(MediaStore.Audio.Media._ID);
int titleCol = cursor.getColumnIndex(MediaStore.Audio.Media.TITLE);
int artistCol = cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST);
// ... repeat this for all columns you need to fetch

Then use these precomputed indices in your loop instead of calling getColumnIndex every time.

  • Initialize your list with fixed capacity: When creating SongList, use new ArrayList<>(cursor.getCount()) to avoid expensive resizing as the list grows. The cursor's count tells you exactly how many songs you'll add, so pre-allocating space eliminates reallocation overhead.

  • Simplify the cursor loop: You don't need moveToFirst() + do-while—just use while (cursor.moveToNext()) which is cleaner and slightly more efficient.

3. 替换过时的AsyncTask

AsyncTask is deprecated and has limitations with thread pool management. Switch to ExecutorService (for Java) or Coroutines (for Kotlin) for better control over background threading.

Here's a Java example using ExecutorService:

private void queryForSongsInTheBg(final Uri uri, final String selection, final String[] whereVal) {
    ProgressBar progressBar = getView().findViewById(R.id.querySongsProgressBar);
    progressBar.setVisibility(View.VISIBLE);

    ExecutorService executor = Executors.newSingleThreadExecutor();
    Handler mainHandler = new Handler(Looper.getMainLooper());

    executor.execute(() -> {
        // Your cursor query and optimized data parsing goes here
        
        mainHandler.post(() -> {
            progressBar.setVisibility(View.GONE);
            songAdapter = new SongAdapter(getContext(), SongList, listener);
            recyclerView.setAdapter(songAdapter);
        });
    });
}

4. RecyclerView基础优化

Make sure your RecyclerView is configured for maximum performance:

  • Call recyclerView.setHasFixedSize(true) if the size of your RecyclerView doesn't change after loading (most music apps fit this case). This tells the RecyclerView it doesn't need to re-measure items every time data changes.
  • Ensure your Adapter's onCreateViewHolder only handles inflating the layout and finding views—no heavy processing should happen here.
  • Use RecyclerView.RecycledViewPool if you have multiple RecyclerViews (like in tabbed interfaces) to reuse ViewHolders across them.

修改后的核心代码示例

Here's how your optimized background processing code would look:

// Inside the background thread (ExecutorService runnable)
String[] projection = {
    MediaStore.Audio.Media._ID,
    MediaStore.Audio.Media.TITLE,
    MediaStore.Audio.Media.ARTIST,
    MediaStore.Audio.Media.ALBUM,
    MediaStore.Audio.Media.DURATION,
    MediaStore.Audio.Media.DATA,
    MediaStore.Audio.Media.ALBUM_ID,
    MediaStore.Audio.Media.DATE_ADDED,
    MediaStore.Audio.Media.YEAR
};

Cursor cursor = getActivity().getContentResolver().query(uri, projection, selection, whereVal, null);
if (cursor != null) {
    // Precompute all column indices once
    int idCol = cursor.getColumnIndex(MediaStore.Audio.Media._ID);
    int titleCol = cursor.getColumnIndex(MediaStore.Audio.Media.TITLE);
    int artistCol = cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST);
    int albumCol = cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM);
    int durationCol = cursor.getColumnIndex(MediaStore.Audio.Media.DURATION);
    int dataCol = cursor.getColumnIndex(MediaStore.Audio.Media.DATA);
    int albumIdCol = cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM_ID);
    int dateAddedCol = cursor.getColumnIndex(MediaStore.Audio.Media.DATE_ADDED);
    int yearCol = cursor.getColumnIndex(MediaStore.Audio.Media.YEAR);

    // Initialize list with fixed capacity
    SongList = new ArrayList<>(cursor.getCount());

    while (cursor.moveToNext()) {
        long id = cursor.getLong(idCol);
        String name = cursor.getString(titleCol);
        String artist = cursor.getString(artistCol);
        String album = cursor.getString(albumCol);
        Long duration = cursor.getLong(durationCol);
        String data = cursor.getString(dataCol);
        Long albumId = cursor.getLong(albumIdCol);
        String date_added = cursor.getString(dateAddedCol);
        Integer year = cursor.getInt(yearCol);

        // Only handle local album art here—internet load goes to Adapter
        String albumArtUri = String.valueOf(ContentUris.withAppendedId(Uri.parse("content://media/external/audio/albumart"), albumId));
        String checkAlbumArt = getCoverArtPath(albumId);
        if (checkAlbumArt == null || checkAlbumArt.isEmpty()) {
            albumArtUri = null; // Flag to trigger internet load in Adapter
        }

        SongInfoModel s = new SongInfoModel(id, name, artist, date_added, album, year, duration, data, albumId, albumArtUri);
        SongList.add(s);
    }
    cursor.close();
}

内容的提问来源于stack exchange,提问作者Rektirino

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:43:24