歌曲图像可以更快吗?
Posted
技术标签:
【中文标题】歌曲图像可以更快吗?【英文标题】:Can the song image be gotten faster? 【发布时间】:2020-02-21 18:20:24 【问题描述】:我正在开发音乐播放器,它可以在片段上显示来自本地设备的所有歌曲和相应歌曲的图像。
当我启动应用程序需要很长时间才能加载时,我注意到它是 getImagesForSongs() 处理速度很慢
为什么慢?有没有更好的方法,我该怎么做?
My All Song 片段类
import android.annotation.SuppressLint;
import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.mikeinvents.musicbaze.R;
import com.mikeinvents.musicbaze.adapters.SongAdapter;
import com.mikeinvents.musicbaze.data_model.SongModel;
import com.mikeinvents.musicbaze.ui.MainActivity;
import java.util.ArrayList;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* A simple @link Fragment subclass.
*/
public class AllSongsFragment extends Fragment
RecyclerView recyclerView;
ArrayList<SongModel> song;
ArrayList<Bitmap> bitmaps;
MediaMetadataRetriever mediaMetadataRetriever;
static byte[] rawArt;
static Bitmap art;
static BitmapFactory.Options bfo;
public AllSongsFragment()
// Required empty public constructor
@Override
public void onStart()
super.onStart();
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
View rootView = inflater.inflate(R.layout.fragment_all_songs, container, false);
// setRetainInstance(true);
recyclerView = rootView.findViewById(R.id.song_recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
new MyAsyncTask().execute();
return rootView;
private void loadSongList()
ContentResolver contentResolver = Objects.requireNonNull(getActivity()).getContentResolver();
Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
String selection = MediaStore.Audio.Media.IS_MUSIC;
String sortOrder = MediaStore.Audio.Media.TITLE + " ASC";
Cursor cursor = contentResolver.query(uri,null,selection,null,sortOrder);
if(cursor != null && cursor.moveToFirst())
song = new ArrayList<>();
//get columns
do
String data = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA));
String title = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.TITLE));
String album = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM));
String artist = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST));
String timeDuration = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DURATION));
//convert time
int duration = Integer.parseInt(timeDuration);
@SuppressLint("DefaultLocale") String time = String.format("%02d:%02d",
TimeUnit.MILLISECONDS.toMinutes(duration),
TimeUnit.MILLISECONDS.toSeconds(duration) -
TimeUnit.MILLISECONDS
.toSeconds(TimeUnit.MILLISECONDS.toMinutes(duration)));
//save to list
song.add(new SongModel(data, title, artist, album, time));
Log.i(MainActivity.TAG, " Added: "+"data = "+data+" title = "+title+
" album = "+album+" artist: "+artist+" duration = "+time);
while (cursor.moveToNext());
if(cursor !=null)
cursor.close();
private void getImagesForSongs()
ContentResolver contentResolver = Objects.requireNonNull(getActivity()).getContentResolver();
Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
String selection = MediaStore.Audio.Media.IS_MUSIC;
String sortOrder = MediaStore.Audio.Media.TITLE + " ASC";
Cursor cursor = contentResolver.query(uri,null,selection,null,sortOrder);
if(cursor != null && cursor.moveToFirst())
bitmaps = new ArrayList<>();
do
String data = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA));
//get song image
mediaMetadataRetriever = new MediaMetadataRetriever();
mediaMetadataRetriever.setDataSource(data);
Log.i(MainActivity.TAG, "Data source has been set");
rawArt = mediaMetadataRetriever.getEmbeddedPicture();
Log.i(MainActivity.TAG, "Raw art has been gotten");
bfo = new BitmapFactory.Options();
art = decodeSampledBitmapFromResource(100,100);
//save to list
bitmaps.add(art);
while (cursor.moveToNext());
if (cursor != null)
cursor.close();
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight)
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 4;
if (height > reqHeight || width > reqWidth)
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth)
inSampleSize *= 2;
Log.i(MainActivity.TAG,"Calculated in sample size");
return inSampleSize;
public static Bitmap decodeSampledBitmapFromResource(int reqWidth, int reqHeight)
// First decode with inJustDecodeBounds=true to check dimensions
bfo.inJustDecodeBounds = true;
if(rawArt != null)
BitmapFactory.decodeByteArray(rawArt,0,rawArt.length,bfo);
Log.i(MainActivity.TAG, "Gotten embedded picture");
// Calculate inSampleSize
calculateInSampleSize(bfo,reqWidth,reqHeight);
// Decode bitmap with inSampleSize set
bfo.inJustDecodeBounds = false;
return BitmapFactory.decodeByteArray(rawArt,0,rawArt.length,bfo);
Log.i(MainActivity.TAG, "Gotten null embedded picture");
return null;
private class MyAsyncTask extends AsyncTask<Void, Void, Void>
ProgressDialog pd;
@Override
protected void onPreExecute()
super.onPreExecute();
pd = new ProgressDialog(getContext());
pd.setTitle("Please wait...");
pd.setMessage("Fetching songs");
pd.setCancelable(false);
pd.show();
@Override
protected Void doInBackground(Void... voids)
getImagesForSongs();
loadSongList();
return null;
@Override
protected void onPostExecute(Void aVoid)
super.onPostExecute(aVoid);
pd.dismiss();
SongAdapter songAdapter = new SongAdapter(getContext(), song, bitmaps);
Log.i(MainActivity.TAG,"Initialized adapter");
recyclerView.setAdapter(songAdapter);
Log.i(MainActivity.TAG,"Adapter has been set");
我的 SongAdapter 类
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.TextView;
import android.widget.Toast;
import com.bumptech.glide.Glide;
import com.mikeinvents.musicbaze.R;
import com.mikeinvents.musicbaze.data_model.SongModel;
import com.mikeinvents.musicbaze.ui.MainActivity;
import java.util.ArrayList;
public class SongAdapter extends RecyclerView.Adapter<SongAdapter.ViewHolder>
private Context ctx;
private ArrayList<SongModel> mSong;
private ArrayList<Bitmap> mBitmap;
public SongAdapter(Context ct, ArrayList<SongModel> song, ArrayList<Bitmap> bitmap)
ctx = ct;
mSong = song;
mBitmap = bitmap;
@NonNull
@Override
public SongAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i)
LayoutInflater inflater = LayoutInflater.from(ctx);
View view = inflater.inflate(R.layout.song_row,viewGroup,false);
return new ViewHolder(view);
@Override
public void onBindViewHolder(@NonNull SongAdapter.ViewHolder viewHolder, int i)
SongModel song = mSong.get(i);
viewHolder.songTitle.setText(song.getTitle());
viewHolder.songArtist.setText(song.getArtist());
viewHolder.songAlbum.setText(song.getAlbum());
viewHolder.songTime.setText(song.getSongTime());
// Glide.with(ctx).load(mBitmap.get(i)).fallback(R.drawable.mojo_logo).into(viewHolder.songImage);
if(mBitmap.get(i) == null)
viewHolder.songImage.setImageResource(R.drawable.mojo_logo);
else if(mBitmap.get(i) !=null)
viewHolder.songImage.setImageBitmap(mBitmap.get(i));
viewHolder.moreOptions.setOnClickListener(v ->
PopupMenu popupMenu = new PopupMenu(ctx,v);
popupMenu.setOnMenuItemClickListener(item ->
switch (item.getItemId())
case R.id.more_options_action_share:
Toast.makeText(ctx,"Implement Share Option", Toast.LENGTH_SHORT).show();
return true;
case R.id.more_options_action_add_fav:
Toast.makeText(ctx,"Implement Favorite Option", Toast.LENGTH_SHORT).show();
return true;
case R.id.more_options_action_delete:
Toast.makeText(ctx,"Implement Delete Option", Toast.LENGTH_SHORT).show();
return true;
default:
return false;
);
popupMenu.inflate(R.menu.more_options_popup);
popupMenu.show();
);
@Override
public int getItemCount()
return mSong.size();
public class ViewHolder extends RecyclerView.ViewHolder
ImageView songImage, moreOptions;
TextView songTitle, songArtist, songAlbum, songTime;
public ViewHolder(@NonNull View itemView)
super(itemView);
songImage = itemView.findViewById(R.id.song_row_song_image);
moreOptions = itemView.findViewById(R.id.song_row_more_options);
songTitle = itemView.findViewById(R.id.song_row_name_of_song);
songArtist = itemView.findViewById(R.id.song_row_artist_name);
songAlbum = itemView.findViewById(R.id.song_row_album_name);
songTime = itemView.findViewById(R.id.song_row_song_time);
Log.i(MainActivity.TAG,"Views in song_row layout initialized");
谢谢。
【问题讨论】:
你在哪里获取歌曲图片? 从本地设备 【参考方案1】:不确定是否有具体修复,但可以采取一些措施来加快加载速度。
位图可能会很慢,因此如果可以获取 png 图像,这将加快处理速度。在 decodeSampledBitmapFromResource 方法中,你运行
BitmapFactory.decodeByteArray(rawArt,0,rawArt.length,bfo);
两次,这似乎是不必要的,但也许我错过了一些东西。
此外,您可以延迟加载图像,而不是一次加载每首歌曲的所有图像,您可以在 onBindViewHolder 中获取图像。这样每张图片也会被并行异步获取。
【讨论】:
谢谢,但我有点困惑。additionally you could lazyload images
是的。应该这样做。以上是关于歌曲图像可以更快吗?的主要内容,如果未能解决你的问题,请参考以下文章
ImageMagick 是覆盖图像的最快方法吗?我怎样才能做得更快,或者有没有我不知道的更快的技术?