Android: How to list mp3 albums - android

I want to try develop simple android music player.
I have this method below to list album title, but it gives duplicate albums list.
public void getAlbumList() {
//query external audio
Activity a=getActivity();
ContentResolver musicResolver = a.getContentResolver();
Uri musicUri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
//String[] projection = null;
//String sortOrder = null;
String selectionMimeType = MediaStore.Files.FileColumns.MIME_TYPE + "=?";
String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension("mp3");
String[] selectionArgsMp3 = new String[]{ mimeType };
Cursor musicCursor = musicResolver.query(musicUri, null, selectionMimeType, selectionArgsMp3, null);
//iterate over results if valid
if(musicCursor!=null && musicCursor.moveToFirst()){
//get columns
int atitleColumn = musicCursor.getColumnIndex
(android.provider.MediaStore.Audio.Media.ALBUM);
int idColumn = musicCursor.getColumnIndex
(android.provider.MediaStore.Audio.Media.ALBUM_ID);
//int bitmap = musicCursor.getColumnIndex
// (android.provider.MediaStore.Audio.Albums.ALBUM_ART);
//add songs to list
while (musicCursor.moveToNext()){
long thisId = musicCursor.getLong(idColumn);
String thisaTitle = musicCursor.getString(atitleColumn);
albumList.add(new Album(thisId, thisaTitle));
}
}
}
I have tried How to use MediaStore query to get Artists without duplicates? but it doesn't work.
Thanks in advance.

add this to your Album class
#Override
public boolean equals(Object obj) {
if (obj instanceof Album) {
Album album = (Album) obj;
//here we compare if the 2 album objects are same or not (edit code as needed)
return (album.getaTitle().equals(this.aTitle) && album.getId == this.Id);
} else {
return false;
}
}
#Override
public int hashCode() {
int hashcode = 0;
hashcode = this.Id*20;
hashcode += this.aTitle.hashCode();
//here we generate the album object hashcode using Id and artistname
return hashcode;
}
then instead of adding your objects to a list, use this :
HashSet<Album> albumList = new HashSet<Album>();
albumList.add(new Album(thisId, thisaTitle));
//use simple for each loop to iterate
for(Album album:albumList){
System.out.println(album);
}
A hashset aromatically handles duplications

Related

Perform slow task on lots of data in RecyclerView

I am building a music player that gets the BPM ID3v2 tag from all mp3 files on a device and allows sorting and filtering by BPM. To get the tag, I am using the mp3agic library. It turns out that this tag fetching takes some time for each song, and performing the fetch when adding each song to my list of songs renders the player without function for a while. I am not sure if I should use an AsyncTask on the entire list when it is done building or an AsyncTask for each item to retrieve its BPM and insert it into the song or even using some other, faster method of getting the BPM value. Can anyone offer any guidance?
Here is my code to get the BPM:
public int getBpmFromId(long id) {
int bpm = -1;
Uri uri = Uri.withAppendedPath(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, Long.toString(id));
try {
Mp3File file = new Mp3File(getRealPathFromURI(getApplicationContext(), uri));
if(file.hasId3v2Tag()) {
ID3v2 id3v2Tag = file.getId3v2Tag();
bpm = id3v2Tag.getBPM();
Log.d("MP3AGIC", "Got BPM for track: " + id + ": " + bpm);
}
} catch (Exception e) {
e.printStackTrace();
}
return bpm;
}
and here is the code I am using to build my list of songs:
public void getSongList() {
ContentResolver musicResolver = getContentResolver();
Uri musicUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
String selectionMimeType = MediaStore.Files.FileColumns.MIME_TYPE + "=?";
String selection = MediaStore.Audio.Media.IS_MUSIC + " != 0 AND " + selectionMimeType;
String sortOrder = null;
String[] projection = null;
String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension("mp3");
String[] selectionArgsMp3 = new String[] { mimeType };
Cursor musicCursor = musicResolver.query(musicUri, projection, selection, selectionArgsMp3, sortOrder);
if(musicCursor != null && musicCursor.moveToFirst()) {
int idColumn = musicCursor.getColumnIndex(MediaStore.Audio.Media._ID);
int titleColumn = musicCursor.getColumnIndex(MediaStore.Audio.Media.TITLE);
int artistColumn = musicCursor.getColumnIndex(MediaStore.Audio.Media.ARTIST);
do {
long id = musicCursor.getLong(idColumn);
String title = musicCursor.getString(titleColumn);
String artist = musicCursor.getString(artistColumn);
//bpm processing
int bpm = getBpmFromId(id);
songList.add(new Song(id, title, artist));
} while (musicCursor.moveToNext());
}
if(musicCursor != null)
musicCursor.close();
}
EDIT: It seems that what might be taking so long is getting the file path from the Uri, which is done in this method:
public String getRealPathFromURI(Context context, Uri contentUri) {
Cursor cursor = null;
try {
String[] proj = { MediaStore.Images.Media.DATA };
cursor = context.getContentResolver().query(contentUri, proj, null, null, null);
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
return cursor.getString(column_index);
} finally {
if (cursor != null) {
cursor.close();
}
}
}
I'm not sure how, or if I can, optimize this.
The decision to use one asynctask for the entire list or use the list as a queue for many asynctasks depends on your desire for concurrency.
If you plan to parallelize the tag-getting, then use multiple asynctasks (this would be good if the operation involves a lot of disk IO rather than CPU time).
Otherwise if you plan to execute them in sequence, then you should use only one asynctask. There is no need to create, then destroy tens/hundreds of background threads. This is inefficient.

Android <unknown> Genre in MediaStore

Is there a way to query for genres in MediaStore and also find songs that don't have a genre set?
This code returns all songs that have a genre set but skips those that don't have a genre.
String[] proj1 = new String[]{
MediaStore.Audio.Genres.NAME,
MediaStore.Audio.Genres._ID
};
ContentResolver cr = activity.getContentResolver();
genreCursor = cr.query(MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI, proj1, null, null, null);
if (genreCursor.moveToFirst()) {
while (genreCursor.moveToNext()) {
int index = genreCursor.getColumnIndexOrThrow(MediaStore.Audio.Genres.NAME);
String genre = genreCursor.getString(index);
index = genreCursor.getColumnIndexOrThrow(MediaStore.Audio.Genres._ID);
long genreId = Long.parseLong(genreCursor.getString(index));
Uri uri = MediaStore.Audio.Genres.Members.getContentUri("external", genreId);
Cursor tempCursor = cr.query(uri, projection, null, null, null);
if (tempCursor.moveToFirst()) {
while (tempCursor.moveToNext()) {
index = tempCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE);
String title = tempCursor.getString(index);
index = tempCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST);
String artist = tempCursor.getString(index);
index = tempCursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM);
String album = tempCursor.getString(index);
songs.add(new Song(artist, title, album, genre));
}
tempCursor.close();
}
}
I am looking for a way to find those songs that have an unknown genre set. Is the only possibility to query all songs and then cross-check them with those that have a genre set to have a list of all songs with the genre for those that have it set?
Songs with unknown genre will have genre as "unknown". So, you'll be getting the the list of all the songs on the device.
I am using this code to scan all the songs with genres and store it in SQLitedatabase. Hope this will help you and this code also return genres.
int index;
int genres;
long genreId;
int count;
Uri uri;
Cursor genrecursor;
Cursor musicCursor;
db = new DBhelper(getApplicationContext()).getReadableDatabase();
String[] proj1 = {MediaStore.Audio.Genres.NAME, MediaStore.Audio.Genres._ID};
genrecursor=getApplicationContext().getContentResolver().query(MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI,proj1,null, null, null);
if(genrecursor.moveToFirst())
{
do{
genres = genrecursor.getColumnIndexOrThrow(MediaStore.Audio.Genres.NAME);
System.out.println("GENRE NAME: "+genrecursor.getString(genres));
System.out.println("======================================");
index = genrecursor.getColumnIndexOrThrow(MediaStore.Audio.Genres._ID);
genreId=Long.parseLong(genrecursor.getString(index));
uri = MediaStore.Audio.Genres.Members.getContentUri("external", genreId);
musicCursor = getApplicationContext().getContentResolver().query(uri, null, null,null,null);
System.out.println("Total Songs: "+musicCursor.getCount());
if(musicCursor.moveToFirst())
{
int titleColumn = musicCursor.getColumnIndex
(android.provider.MediaStore.Audio.Media.TITLE);
int idColumn = musicCursor.getColumnIndex
(android.provider.MediaStore.Audio.Media._ID);
int artistColumn = musicCursor.getColumnIndex
(android.provider.MediaStore.Audio.Media.ARTIST);
int albumId = musicCursor.getColumnIndex
(MediaStore.Audio.Media.ALBUM_ID);
int data = musicCursor.getColumnIndex(MediaStore.Audio.Media.DATA);
int albumkey = musicCursor.getColumnIndex(MediaStore.Audio.Media.ALBUM_KEY);
int albumName = musicCursor.getColumnIndex(MediaStore.Audio.Media.ALBUM);
int artwork = musicCursor.getColumnIndex(MediaStore.Audio.Albums.ALBUM_ART);
do{
long thisId = musicCursor.getLong(idColumn);
String thisTitle = musicCursor.getString(titleColumn);
String thisArtist = musicCursor.getString(artistColumn);
long thisalbumId = musicCursor.getLong(albumId);
String thisdata = musicCursor.getString(data);
String AlbumKey = musicCursor.getString(albumkey);
String thisAlbum = musicCursor.getString(albumName);
String thisGenres = genrecursor.getString(genres);
ContentValues value = new ContentValues();
value.put("songtitle", thisTitle);
value.put("songartist", thisArtist);
value.put("songpath", thisdata);
value.put("songalbum", thisAlbum);
value.put("songgenres", thisGenres);
try {
db.insertOrThrow("songListRecord", null, value);
}
catch (SQLiteConstraintException e)
{
}
}while(musicCursor.moveToNext());
}
System.out.println("======================================");
}while(genrecursor.moveToNext());
}

How to get songs and other media from an Album ID?

I want to get songs and other MEDIA details from Album Id. All I have is Album Id and I tried many solutions but none of them succeed.
My code snippet:
ContentResolver contentResolver = getContentResolver();
Uri mediaUri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, albumId);
Log.wtf("SKJDBKJ", mediaUri.toString());
Cursor mediaCursor = contentResolver.query(mediaUri, null, null, null, null);
// if the cursor is null.
if(mediaCursor != null && mediaCursor.moveToFirst())
{
Log.wtf("DSJK", "entered cursor");
//get Columns
int titleColumn = mediaCursor.getColumnIndex(MediaStore.Audio.Albums.ALBUM);
int idColumn = mediaCursor.getColumnIndex(MediaStore.Audio.Media._ID);
int artistColumn = mediaCursor.getColumnIndex(MediaStore.Audio.Media.ARTIST);
// Store the title, id and artist name in Song Array list.
do
{
long thisId = mediaCursor.getLong(idColumn);
String thisTitle = mediaCursor.getString(titleColumn);
String thisArtist = mediaCursor.getString(artistColumn);
Log.wtf("Title", thisTitle);
// Add the info to our array.
songArrayList.add(new Song(thisId, thisTitle, thisArtist));
}
while (mediaCursor.moveToNext());
// For best practices, close the cursor after use.
mediaCursor.close();
}
Log for mediaUri returns path to current album, e.g. : content://media/external/audio/media/41.
Someone tell me how do I do it?
I have done it by myself.You can write simple sqlite query for 'selection' string on contentResolver.query .This example can show you how to get songs by album Id. I think this is best way.
ArrayList<Song> songs = new ArrayList<>();
String selection = "is_music != 0";
if (albumId > 0) {
selection = selection + " and album_id = " + albumId;
}
String[] projection = {
MediaStore.Audio.Media.TITLE,
MediaStore.Audio.Media.ARTIST,
MediaStore.Audio.Media.DATA,
MediaStore.Audio.Media.DISPLAY_NAME,
MediaStore.Audio.Media.DURATION,
MediaStore.Audio.Media.ALBUM,
MediaStore.Audio.Media.ALBUM_ID
};
final String sortOrder = MediaStore.Audio.AudioColumns.TITLE + " COLLATE LOCALIZED ASC";
Cursor cursor = null;
try {
Uri uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
cursor = getActivity().getContentResolver().query(uri, projection, selection, null, sortOrder);
if (cursor != null) {
cursor.moveToFirst();
int position = 1;
while (!cursor.isAfterLast()) {
Song song = new Song();
song.setTitle(cursor.getString(0));
song.setDuration(cursor.getLong(4));
song.setArtist(cursor.getString(1));
song.setPath(cursor.getString(2));
song.setPosition(position);
song.setAlbumId(cursor.getLong(6));
cursor.moveToNext();
}
}
} catch (Exception e) {
Log.e("Media", e.toString());
} finally {
if (cursor != null) {
cursor.close();
}
}
I have figured it out myself after a LOT of trial and errors. I don't know if it's the best and safest way to do so, but as far as it's working I am happy.
I changed my code a bit and compared Album IDs. Here's the snippet:
ContentResolver contentResolver = getContentResolver();
Uri mediaUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
Log.wtf("SKJDBKJ", mediaUri.toString());
Cursor mediaCursor = contentResolver.query(mediaUri, null, null, null, null);
// if the cursor is null.
if(mediaCursor != null && mediaCursor.moveToFirst())
{
Log.wtf("DSJK", "entered cursor");
//get Columns
int titleColumn = mediaCursor.getColumnIndex(MediaStore.Audio.Media.TITLE);
int idColumn = mediaCursor.getColumnIndex(MediaStore.Audio.Media._ID);
int artistColumn = mediaCursor.getColumnIndex(MediaStore.Audio.Media.ARTIST);
int albumId = mediaCursor.getColumnIndex(MediaStore.Audio.Media.ALBUM_ID);
// Store the title, id and artist name in Song Array list.
do
{
long thisId = mediaCursor.getLong(idColumn);
long thisalbumId = mediaCursor.getLong(albumId);
String thisTitle = mediaCursor.getString(titleColumn);
String thisArtist = mediaCursor.getString(artistColumn);
// Add the info to our array.
if(this.albumId == thisalbumId)
{
Log.wtf("SAME2SAME", String.valueOf(thisalbumId));
Log.wtf("SAME2SAME", String.valueOf(this.albumId));
songArrayList.add(new Song(thisId, thisTitle, thisArtist));
}
}
while (mediaCursor.moveToNext());
// For best practices, close the cursor after use.
mediaCursor.close();
}
I changed:
Albums to Media in MediaStore.Audio.Audio.xxx.
Got the album Id of Media.
Compared that to album Id I receive from bundle extras.
Only add those songs in the arrayList.
I guess this'll be the way for Artists too.
when you put the cursor.movenext function in to the while loop, the first line of
MediaStore database will be passed without saving the data because the function move the cursor and then check so its better to make a value instead of putting the movenext function into the while.
try {
Projection = new String[]{
MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.ALBUM
, MediaStore.Audio.Media.ARTIST,MediaStore.Audio.Media.DATA, MediaStore.Audio.Media.SIZE};
Cursor cursor = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, Projection, null, null, null);
cursor.moveToFirst();
for (int i = 0; i < 5; i++) {
columns[i] = cursor.getColumnIndex(Projection[i]);
}
Music tempMusic = new Music();
while (next) {
tempMusic.setName(cursor.getString(columns[0]));
tempMusic.setAlbum(cursor.getString(columns[1]));
tempMusic.setArtist(cursor.getString(columns[2]));
tempMusic.setUri(cursor.getString(columns[3]));
tempMusic.setSize(cursor.getDouble(columns[4]));
if(!cursor.moveToNext())
{
next=false;
}
musicss.add(tempMusic);
tempMusic = new Music();
}
public Bitmap getAlbumArt(Long albumId) {
Bitmap albumArt = null;
try {
final Uri AlbumArtUri = Uri.parse("content://media/external/audio/albumart");
Uri uri = ContentUris.withAppendedId(AlbumArtUri, albumId);
ParcelFileDescriptor pfd = GlobalSongList.getInstance().getContentResolver().openFileDescriptor(uri, "r");
if (pfd != null) {
FileDescriptor fd = pfd.getFileDescriptor();
albumArt = BitmapFactory.decodeFileDescriptor(fd);
}
} catch (Exception e) {
}
return albumArt;
}
ALBUM_ID is a field in the MediaStore and you can query it in the same way you query for title,artist,etc.
public Long getAlbumId(String id)
{
Cursor musicCursor;
String[] mProjection = {MediaStore.Audio.Media._ID, MediaStore.Audio.Media.ALBUM_ID};
String[] mArgs = {id};
musicCursor = musicResolver.query(musicUri, mProjection, MediaStore.Audio.Media._ID + " = ?", mArgs, null);
musicCursor.moveToFirst();
Long albumId=musicCursor.getLong(musicCursor.getColumnIndex(MediaStore.Audio.Media.ALBUM_ID));
return albumId;
}
This is how I did.
Works perfect.
String selection= MediaStore.Audio.Media.IS_MUSIC+"!=0";
String[] projection={
MediaStore.Audio.Media._ID,
MediaStore.Audio.Media.DATA,
MediaStore.Audio.Media.TITLE,
MediaStore.Audio.Media.ARTIST,
MediaStore.Audio.Media.ALBUM,
MediaStore.Audio.Albums.ALBUM_ID,
MediaStore.Audio.Media.DURATION
};
Cursor cursor=getActivity().managedQuery(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,projection,selection,null,"title asc");
List<String> data=new ArrayList<>();
List<String> songs=new ArrayList<>();
List<String> artists=new ArrayList<>();
List<String> albums=new ArrayList<>();
List<String> album_id=new ArrayList<>();
List<String> durations=new ArrayList<>();
cursor.moveToFirst();
while (cursor.moveToNext()){
if(cursor.getString(5).equals(mAlbumId)) {
data.add(cursor.getString(1));
songs.add(cursor.getString(2));
artists.add(cursor.getString(3));
albums.add(cursor.getString(4));
album_id.add(mAlbumId);
durations.add(cursor.getString(6));
}
}

Getting album art for all music files

I am trying to fetch the album art of all the songs present on the phone. I am using MediaStore to fetch all the songs title,artist etc. How should I fetch album art ? I tried using MediaMetaDataRetriever but getting confused how to use it for multiple files. Can anyone please tweak this code?
Activity class:
public void getSongList() {
// retrieve song info
ContentResolver musicResolver = getContentResolver();
Uri musicUri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
Cursor musicCursor = musicResolver.query(musicUri, null, null, null,
null);
metaRetriver.setDataSource(MainActivity.this,musicUri); // now how to loop over this
if (musicCursor != null && musicCursor.moveToFirst()) {
// get columns
int titleColumn = musicCursor.getColumnIndex(MediaColumns.TITLE);
int idColumn = musicCursor.getColumnIndex(BaseColumns._ID);
int artistColumn = musicCursor.getColumnIndex(AudioColumns.ARTIST);
// add songs to list
do {
long thisId = musicCursor.getLong(idColumn);
String thisTitle = musicCursor.getString(titleColumn);
String thisArtist = musicCursor.getString(artistColumn);
songList.add(new Song(thisId, thisTitle, thisArtist));
} while (musicCursor.moveToNext());
}
Once you have the album id, which you can get from that same cursor, you can query a different URI for the cover art path. see below for an example of approximately how I do it:
private static String getCoverArtPath(Context context, long androidAlbumId) {
String path = null;
Cursor c = context.getContentResolver().query(
MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
new String[]{MediaStore.Audio.Albums.ALBUM_ART},
MediaStore.Audio.Albums._ID + "=?",
new String[]{Long.toString(androidAlbumId)},
null);
if (c != null) {
if (c.moveToFirst()) {
path = c.getString(0);
}
c.close();
}
return path;
}
You could get a Map of all album art by id using something like this (untested)
private static Map<Long, String> getCoverArtPaths(Context context) {
String HashMap<Long, String> map = new HashMap<Long, String>();
Cursor c = context.getContentResolver().query(
MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
new String[]{MediaStore.Audio.Albums._ID, MediaStore.Audio.Albums.ALBUM_ART},
null,
null,
null);
if (c != null) {
for (c.moveToFirst(); !c.isAfterLast(); c.moveToNext()) {
map.add(c.getLong(0), c.getString(1));
}
c.close();
}
// returns a mapping of Album ID => art file path
return map;
}
I got it working like this. Hope it helps someone :)
public void getSongList() {
// retrieve song info
ContentResolver musicResolver = getContentResolver();
Uri musicUri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
Cursor musicCursor = musicResolver.query(musicUri, null, null, null,
null);
if (musicCursor != null && musicCursor.moveToFirst()) {
// get columns
int titleColumn = musicCursor.getColumnIndex(MediaColumns.TITLE);
int idColumn = musicCursor.getColumnIndex(BaseColumns._ID);
int artistColumn = musicCursor.getColumnIndex(AudioColumns.ARTIST);
int column_index = musicCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA);
// add songs to list
do {
long thisId = musicCursor.getLong(idColumn);
String pathId = musicCursor.getString(column_index);
Log.d(this.getClass().getName(), "path id=" + pathId);
metaRetriver.setDataSource(pathId);
try {
art = metaRetriver.getEmbeddedPicture();
Options opt = new Options();
opt.inSampleSize = 2;
songImage = BitmapFactory .decodeByteArray(art, 0, art.length,opt);
}
catch (Exception e)
{ imgAlbumArt.setBackgroundColor(Color.GRAY);
}
String thisTitle = musicCursor.getString(titleColumn);
String thisArtist = musicCursor.getString(artistColumn);
songList.add(new Song(thisId, thisTitle, thisArtist,songImage));
// if(songImage!=null)
// {
// songImage.recycle();
// }
} while (musicCursor.moveToNext());
}
For Fast rendering
If you are using recylerview, listview to render song list and it is making slow due to getting album art from song path then you can first check if ImageView has already background image set then don't process anything. It will reduce lot of processing and make scrolling fast even if song list is very large. I was facing same problem. I just did same as mentioned and recyclerview rendering with album art became smooth otherwise it was getting stuck during scroll.
My be this will help someone.
My code for recyclerview:
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
try {
if (holder instanceof MusicAdapter.MusicViewHolder) {
MusicAdapter.MusicViewHolder vh = (MusicAdapter.MusicViewHolder) holder;
vh.tvTitle.setText(musicList.get(position).title.toString());
vh.tvArtistAndAblum.setText(musicList.get(position).artist.toString() + " | " + musicList.get(position).album.toString());
Drawable background = vh.ivMusicIcon.getBackground();
if(background == null) {
String pathId = musicList.get(position).path;
MediaMetadataRetriever metaRetriver = new MediaMetadataRetriever();
metaRetriver.setDataSource(pathId);
try {
byte[] art = metaRetriver.getEmbeddedPicture();
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inSampleSize = 2;
Bitmap songImage = BitmapFactory.decodeByteArray(art, 0, art.length,opt);
BitmapDrawable ob = new BitmapDrawable(context.getResources(), songImage);
vh.ivMusicIcon.setBackground(ob);
}
catch (Exception e)
{
vh.ivMusicIcon.setImageResource(R.drawable.compact_disc);
}
}
} else if (holder instanceof FooterViewHolder) {
FooterViewHolder vh = (FooterViewHolder) holder;
}
} catch (Exception e) {
e.printStackTrace();
}
}

How load all the contacts with minimum time in Android

In my project getting contacts is taking a long time to load.
What are ways to reduce the time of getting contacts
Assume there are 1000 contacts in my phone.
Right now it is taking more than 2 minutes to load all the contacts
How can I reduce the time to load contacts ?
Any Thoughts?
I referred to the the following link when programming the initial method.
http://www.coderzheaven.com/2011/06/13/get-all-details-from-contacts-in-android/
BETTER SOLUTION HERE.....
private static final String[] PROJECTION = new String[] {
ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER
};
.
.
.
ContentResolver cr = getContentResolver();
Cursor cursor = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, PROJECTION, null, null, null);
if (cursor != null) {
try {
final int nameIndex = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
final int numberIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
String name, number;
while (cursor.moveToNext()) {
name = cursor.getString(nameIndex);
number = cursor.getString(numberIndex);
}
} finally {
cursor.close();
}
}
CHEERS...:)
Total time will depend upon what fields you are trying to access from the Contacts table.
Accessing less field means less looping , less processing and hence faster results.
Also to speed up your contacts fetch operation you can use the ContentProvideClient instead of calling query on ContentResolver every time. This will make you query the specific table rather than querying first for the required ContentProvider and then to table.
Create an instance of ContentProviderClient
ContentResolver cResolver=context.getContextResolver();
ContentProviderClient mCProviderClient = cResolver.acquireContentProviderClient(ContactsContract.Contacts.CONTENT_URI);
Then reuse this mCProviderClient to get Contacts(data from any ContentProvider) data on your call.
For example in following method, I am accessing only one field.
private ArrayList<String> fetchContactsCProviderClient()
{
ArrayList<String> mContactList = null;
try
{
Cursor mCursor = mCProviderClient.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
if (mCursor != null && mCursor.getCount() > 0)
{
mContactList = new ArrayList<String>();
mCursor.moveToFirst();
while (!mCursor.isLast())
{
String displayName = mCursor.getString(mCursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME));
mContactList.add(displayName);
mCursor.moveToNext();
}
if (mCursor.isLast())
{
String displayName = mCursor.getString(mCursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME));
mContactList.add(displayName);
}
}
mCursor.close();
}
catch (RemoteException e)
{
e.printStackTrace();
mContactList = null;
}
catch (Exception e)
{
e.printStackTrace();
mContactList = null;
}
return mContactList;
}
Load Contact faster like other apps doing.
I have tested this code with multiple contacts its working fine and faster like other apps within 500 ms (within half second or less) I am able to load 1000+ contacts.
Total time will depend upon what fields you are trying to access from the Contacts table.
Mange your query according to your requirement do not access unwanted fields. Accessing less field means less looping , less processing and hence faster results.
Accessing right table in contact it also help to reduce contact loading time.
Query Optimization to load contact more faster use projection
String[] projection = {
ContactsContract.Data.MIMETYPE,
ContactsContract.Data.CONTACT_ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.PHOTO_URI,
ContactsContract.Contacts.STARRED,
ContactsContract.RawContacts.ACCOUNT_TYPE,
ContactsContract.CommonDataKinds.Contactables.DATA,
ContactsContract.CommonDataKinds.Contactables.TYPE
};
Selection and selection argument
String selection = ContactsContract.Data.MIMETYPE + " in (?, ?)" + " AND " /*+ ContactsContract.Contacts.IN_VISIBLE_GROUP + " = '" + 1 + "' AND "*/ +
ContactsContract.Data.HAS_PHONE_NUMBER + " = '" + 1 + "'";
String[] selectionArgs = {
ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE,
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE,
};
To order contacts alphabetically use following code
try {
Collections.sort(listview_address, new Comparator<ContactBook>() {
#Override
public int compare(ContactBook lhs, ContactBook rhs) {
return lhs.name.toUpperCase().compareTo(rhs.name.toUpperCase());
}
});
} catch (Exception e) {
e.printStackTrace();
}
Following is complete source code
public void initeContacts() {
List<ContactBook> listview_address = new LinkedList<ContactBook>();
SparseArray<ContactBook> addressbook_array = null;
{
addressbook_array = new SparseArray<ContactBook>();
long start = System.currentTimeMillis();
String[] projection = {
ContactsContract.Data.MIMETYPE,
ContactsContract.Data.CONTACT_ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.PHOTO_URI,
ContactsContract.Contacts.STARRED,
ContactsContract.RawContacts.ACCOUNT_TYPE,
ContactsContract.CommonDataKinds.Contactables.DATA,
ContactsContract.CommonDataKinds.Contactables.TYPE
};
String selection = ContactsContract.Data.MIMETYPE + " in (?, ?)" + " AND " /*+ ContactsContract.Contacts.IN_VISIBLE_GROUP + " = '" + 1 + "' AND "*/ +
ContactsContract.Data.HAS_PHONE_NUMBER + " = '" + 1 + "'";
String[] selectionArgs = {
ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE,
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE,
};
String sortOrder = ContactsContract.Contacts.SORT_KEY_ALTERNATIVE;
Uri uri = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {
uri = ContactsContract.CommonDataKinds.Contactables.CONTENT_URI;
} else {
uri = ContactsContract.Data.CONTENT_URI;
}
// we could also use Uri uri = ContactsContract.Data.CONTENT_URI;
// we could also use Uri uri = ContactsContract.Contact.CONTENT_URI;
Cursor cursor = getActivity().getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder);
final int mimeTypeIdx = cursor.getColumnIndex(ContactsContract.Data.MIMETYPE);
final int idIdx = cursor.getColumnIndex(ContactsContract.Data.CONTACT_ID);
final int nameIdx = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
final int dataIdx = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Contactables.DATA);
final int photo = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Contactables.PHOTO_URI);
final int typeIdx = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Contactables.TYPE);
final int account_type = cursor.getColumnIndex(ContactsContract.RawContacts.ACCOUNT_TYPE);
while (cursor.moveToNext()) {
int contact_id = cursor.getInt(idIdx);
String photo_uri = cursor.getString(photo);
String contact_name = cursor.getString(nameIdx);
String contact_acc_type = cursor.getString(account_type);
int contact_type = cursor.getInt(typeIdx);
String contact_data = cursor.getString(dataIdx);
ContactBook contactBook = addressbook_array.get(contact_id);
/* if (contactBook == null) {
//list contact add to avoid duplication
//load All contacts fro device
//to add contacts number with name add one extra veriable in ContactBook as number and pass contact_data this give number to you (contact_data is PHONE NUMBER)
contactBook = new ContactBook(contact_id, contact_name, getResources(), photo_uri, contact_acc_type, "phone number");
addressbook_array.put(contact_id, contactBook);
listview_address.add(contactBook);
}*/
String Contact_mimeType = cursor.getString(mimeTypeIdx);
//here am checking Contact_mimeType to get mobile number asociated with perticular contact and email adderess asociated
if (Contact_mimeType.equals(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)) {
if (contactBook != null) {
contactBook.addEmail(contact_type, contact_data);
}
} else {
if (contactBook == null) {
//list contact add to avoid duplication
//load All contacts fro device
//to add contacts number with name add one extra veriable in ContactBook as number and pass contact_data this give number to you (contact_data is PHONE NUMBER)
contactBook = new ContactBook(contact_id, contact_name, getResources(), photo_uri, contact_acc_type, "phone number");
addressbook_array.put(contact_id, contactBook);
listview_address.add(contactBook);
}
// contactBook.addPhone(contact_type, contact_data);
}
}
cursor.close();
try {
Collections.sort(listview_address, new Comparator<ContactBook>() {
#Override
public int compare(ContactBook lhs, ContactBook rhs) {
return lhs.name.toUpperCase().compareTo(rhs.name.toUpperCase());
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
You can use following code in above code that I have commented .It club the the single contact with its multiple number.To get all number associated with single contact use array in Object class.
if (contactBook == null) {
//irst contact add to avoid duplication
//load All contacts fro device
contactBook = new ContactBook(contact_id, contact_name, getResources(), photo_uri, contact_acc_type, "");
addressbook_array.put(contact_id, contactBook);
listview_address.add(contactBook);
}
String Contact_mimeType = cursor.getString(mimeTypeIdx);
//here am checking Contact_mimeType to get mobile number asociated with perticular contact and email adderess asociated
if (Contact_mimeType.equals(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)) {
contactBook.addEmail(contact_type, contact_data);
} else {
contactBook.addPhone(contact_type, contact_data);
}
Object class
public class ContactBook {
public int id;
public Resources res;
public String name;
public String photo;
public String contact_acc_type;
public SparseArray<String> emails;
public SparseArray<String> phones;
/* public LongSparseArray<String> emails;
public LongSparseArray<String> phones;*/
public String header = "";
public ContactBook(int id, String name, Resources res, String photo, String contact_acc_type, String header) {
this.id = id;
this.name = name;
this.res = res;
this.photo = photo;
this.contact_acc_type = contact_acc_type;
this.header = header;
}
#Override
public String toString() {
return toString(false);
}
public String toString(boolean rich) {
//testing method to check ddata
SpannableStringBuilder builder = new SpannableStringBuilder();
if (rich) {
builder.append("id: ").append(Long.toString(id))
.append(", name: ").append("\u001b[1m").append(name).append("\u001b[0m");
} else {
builder.append(name);
}
if (phones != null) {
builder.append("\n\tphones: ");
for (int i = 0; i < phones.size(); i++) {
int type = (int) phones.keyAt(i);
builder.append(ContactsContract.CommonDataKinds.Phone.getTypeLabel(res, type, ""))
.append(": ")
.append(phones.valueAt(i));
if (i + 1 < phones.size()) {
builder.append(", ");
}
}
}
if (emails != null) {
builder.append("\n\temails: ");
for (int i = 0; i < emails.size(); i++) {
int type = (int) emails.keyAt(i);
builder.append(ContactsContract.CommonDataKinds.Email.getTypeLabel(res, type, ""))
.append(": ")
.append(emails.valueAt(i));
if (i + 1 < emails.size()) {
builder.append(", ");
}
}
}
return builder.toString();
}
public void addEmail(int type, String address) {
//this is the array in object class where i am storing contact all emails of perticular contact (single)
if (emails == null) {
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
emails = new SparseArray<String>();
emails.put(type, address);
/*} else {
//add emails to array below Jelly bean //use single array list
}*/
}
}
public void addPhone(int type, String number) {
//this is the array in object class where i am storing contact numbers of perticular contact
if (phones == null) {
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
phones = new SparseArray<String>();
phones.put(type, number);
/* } else {
//add emails to array below Jelly bean //use single array list
}*/
}
}}
For loading the contacts with mininum time the optimum solution is to use the concept of projection and selection argument while querying the cursor for contacts.
this can be done in following way
void getAllContacts() {
long startnow;
long endnow;
startnow = android.os.SystemClock.uptimeMillis();
ArrayList arrContacts = new ArrayList();
Uri uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
String selection = ContactsContract.Contacts.HAS_PHONE_NUMBER;
Cursor cursor = ctx.getContentResolver().query(uri, new String[]{ContactsContract.CommonDataKinds.Phone.NUMBER, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone._ID, ContactsContract.Contacts._ID}, selection, null, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC");
cursor.moveToFirst();
while (cursor.isAfterLast() == false) {
String contactNumber = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
String contactName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
int phoneContactID = cursor.getInt(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone._ID));
int contactID = cursor.getInt(cursor.getColumnIndex(ContactsContract.Contacts._ID));
Log.d("con ", "name " + contactName + " " + " PhoeContactID " + phoneContactID + " ContactID " + contactID)
cursor.moveToNext();
}
cursor.close();
cursor = null;
endnow = android.os.SystemClock.uptimeMillis();
Log.d("END", "TimeForContacts " + (endnow - startnow) + " ms");
}
With above method it took 400ms(less than second) to load contacts where as in normall way it was taking 10-12 sec.
For details imformation this post might help as i took help from it
http://www.blazin.in/2016/02/loading-contacts-fast-from-android.html
If your time increases with your data, then you are probably running a new query to fetch phones/emails for every contact. If you query for the phone/email field using ContactsContract.CommonDataKinds.Phone.NUMBER, then you will just retrieve 1 phone per contact.
The solution is to project the fields and join them by contact id.
Here is my solution in Kotlin (extracting id, name, all phones and emails):
val projection = arrayOf(
ContactsContract.Data.MIMETYPE,
ContactsContract.Data.CONTACT_ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Contactables.DATA
)
val selection = "${ContactsContract.Data.MIMETYPE} in (?, ?)"
val selectionArgs = arrayOf(
ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE,
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
val contacts = applicationContext
.contentResolver
.query(ContactsContract.Data.CONTENT_URI, projection, selection, selectionArgs, null)
.run {
if (this == null) {
throw IllegalStateException("Cursor null")
}
val contactsById = mutableMapOf<String, LocalContact>()
val mimeTypeField = getColumnIndex(ContactsContract.Data.MIMETYPE)
val idField = getColumnIndex(ContactsContract.Data.CONTACT_ID)
val nameField = getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)
val dataField = getColumnIndex(ContactsContract.CommonDataKinds.Contactables.DATA)
while (moveToNext()) {
val mimeType = getString(mimeTypeField)
val id = getString(idField)
var contact = contactsById[id]
if (contact == null) {
val name = getString(nameField)
contact = LocalContact(id = id, fullName = name, phoneNumbers = listOf(), emailAddresses = listOf())
}
val data = getString(dataField)
when(getString(mimeTypeField)) {
ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE ->
contact = contact.copy(emailAddresses = contact.emailAddresses + data)
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE ->
contact = contact.copy(phoneNumbers = contact.phoneNumbers + data)
}
contactsById[id] = contact
}
close()
contactsById.values.toList()
}
And for reference, my LocalContact model:
data class LocalContact(
val id: String,
val fullName: String?,
val phoneNumbers: List<String>,
val emailAddresses: List<String>
)
I think this is a better solution:
public ContentValues getAllContacts() {
ContentValues contacts = new ContentValues();
ContentResolver cr = getContentResolver();
Cursor cur = cr.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
if (cur != null && cur.getCount() > 0) {
while (cur.moveToNext()) {
String id = cur.getString(cur.getColumnIndex(ContactsContract.Contacts._ID));
String name = cur.getString(cur.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
if (cur.getInt(cur.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER)) > 0) {
Cursor pCur = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?", new String[]{id}, null);
if (pCur != null) {
while (pCur.moveToNext()) {
String phoneNo = pCur.getString(pCur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
contacts.put(phoneNo, name);
}
pCur.close();
}
}
}
cur.close();
}
return contacts;
}
for use it you need to call this lines once:
ContentValues contacts = new ContentValues();
contacts = getAllContacts();
and when you want to get contact name by number, just use:
String number = "12345";
String name = (String) G.contacts.get(number);
this algorithm is a bit faster...

Categories

Resources