So guys, this is a big one
I'm currently working on an App which involves a big database creation.
Basicalls what it does is scanning the entire device for any kind of media file and adds it to the database (filtered by certain file extensions).
The way it currently does that is scanning through the device's folders with a recursive function, and every time it finds a file that is actually a file and not a directory AND matches the file extensions I picked it adds the file's path to a previously created ArrayList containing only strings.
This is implemented in an AsyncTask so it's done in the background.
As soon as that task finishes it passes the ArrayList on to another AsyncTask that is responsible for adding those files to the database. Out of simplicity resons I only took the file's path, its name and the title that is stored in its tags for every entry (simple sqlite stuff) and displayed the titles in a ListView in another activity over cursor adapters and so on (which isnt really an issue in my case).
Right now it workes pretty well, doesn't crash or return anything bad (I'm testing on a OnePlus One right now).
BUT I have a really big music library on my phone (around 5400 songs) which is nearly filling my entire storage. And out of some unknown reason to me, the scanning task i mentioned (with the ArrayList) stops at around 3000 files and passes the ArrayList on to the second task, which is obviously not what I'm aming at to have.
My guess is that there might be a big performance issue and using an ArrayList is not the best way to approach this. It might work with a number of files that is under 3000 but higher than that is also necessary...
So what would you guys suggest? Does my approach lack something or do I have to try something completely different?
You don't need scan your folders (and, probably, creating database of media files too), you can just use MediaStore class
The Media provider contains meta data for all available media on both internal and external storage devices.
For example, this piece of code will return all available audio playlists
private List<Playlist> getPlayListsList(){
ContentResolver resolver = getContentResolver();
Uri uri = MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI;
String[] columns = { _ID, NAME };
Cursor playLists = resolver.query(uri, columns, null, null, null);
List<Playlist> result = new ArrayList<>();
if (playLists == null) {
return result;
}
Cursor cursor;
for (boolean hasItem = playLists.moveToFirst(); hasItem; hasItem = playLists.moveToNext()) {
long id = playLists.getLong(playLists.getColumnIndex(_ID));
String name = playLists.getString(playLists.getColumnIndex(NAME));
cursor = resolver.query(MediaStore.Audio.Playlists.Members.getContentUri("external", id),
new String[]{MediaStore.Audio.Playlists.Members.TITLE, MediaStore.Audio.Playlists.Members.ARTIST,
MediaStore.Audio.Media.DATA, MediaStore.Audio.Playlists.Members.PLAY_ORDER}, null, null, null
);
int songsCount = cursor.getCount();
result.add( new Playlist(
id,
name,
songsCount));
cursor.close();
}
playLists.close();
return result;
}
Related
I'm trying to make a MediaBrowserService. In the past when I want to display all the music files actually on the device, I would use a ContentResolver and load a cursor from the MediaStore.
I can't figure out where I am supposed to do this in a MediaBrowserService.
There is the method: onLoadChildren
The examples I find look like this:
#Override
public void onLoadChildren(
#NonNull final String parentMediaId,
#NonNull final Result<List<MediaBrowserCompat.MediaItem>> result) {
result.sendResult(MusicLibrary.getMediaItems());
}
My question is, do I use my ContentResolver cursor within this method to return a list of MediaItems? Or is there some other internal method to use to go through the device and get all the music files?
Only examples I see online are streaming services which do an HTTP call to get a song list, or they just hard code fake songs to display for testing. I'm trying to allow the service to browse music files that are on the device itself.
I also tried to do the exactly same thing in my app, hitting the same wall as you. Finally I figured out how to do it using the Android's new Paging library. You do have to use a cursor with a ContentResolver at the service side in order to fetch the media items on the device, but in order to not iterate all the cursor's records just to return the children in the onLoadChildren() method, I use the paging library to return only the chunks the user is interested in this moment at the client/UI side. You can read about it in a post I wrote, where I also demonstrate the concept with code samples.
I am using a device with Android 5.0.1 and when execute the function:
Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
List<ResolveInfo> apps = manager.queryIntentActivities(mainIntent,0);
An exception of type TransactionTooLargeException is thrown ....
I cannot use the flag: MATCH_DEFAULT_ONLY because I can't be limited to only those activities that support the CATEGORY_DEFAULT.
Looks that the problem is related with the amount of data returned, like many related issues that I found here at stackoverflow ...
Is there a way to break this response? Or is there a query, equivalent, or a combination of flags that allow get the same results making more than one query?
I am asking because is not clear to me the meaning of flag = 0 in documentation: https://developer.android.com/reference/android/content/pm/PackageManager.html#queryIntentActivities(android.content.Intent, int)
May be I can query more than once, with different queries, and combine the results?
You could chunk your query by being a bit more specific with how you form your Intent, and do multiple queries rather than 1 lump sum query. Right now, you're querying basically every application (potentially multiple for each) on the device (since everything that can launch will have a android.app.action.Main action), which goes over your allocation of the ~1 MB shared buffer parcel limit causing the exception:
From the docs:
{ action=android.app.action.MAIN } matches all of the activities that can be used as top-level entry points into an application.
{ action=android.app.action.MAIN, category=android.app.category.LAUNCHER } is the actual intent used by the Launcher to populate its top-level list.
So, adding a category narrows the search results. If you need everything of action type "android.app.action.Main", use that as the action, then iterate over an additional set of categories ex sudo code:
List<ResolveInfo> activities = new ArrayList<ResolveInfo>()
String[] categories = new String[] { android.app.category.LAUNCHER, additional... }
for(String category: categories){
Intent mainIntent = new Intent(Intent.ACTION_MAIN)
mainIntent.addCategory(category)
List<ResolveInfo> matches = manager.queryIntentActivities(mainIntent, 0); //You might want to use a better #ResolveInfoFlag to narrow your ResolveInfo data
if(!matches.isEmpty()){
activities.addAll(matches)
}
}
You should also probably do a try catch, because when doing a MATCH_ALL, there's still no guarantee that the query won't be too large. There might be appropriate flags to help with this, but I'm not familiar with your use case.
I am programming Android using LibGDX.
I have big file and want to sraightforward access specific line on the file without going through all the lines and iterating them until i reach desired line.
I was
suggested
to use SQLite for this.
So I followed example and did this:
DatabaseCursor cursor;
cursor = mydb1.rawQuery("SELECT * FROM comments");
while (cursor.next()) {
cursor.getString(1);
}
Well it works , but that is iteration (looping)! how to do it directly?
thanx
as advised did this and it worked:
cursor = mydb1.rawQuery("SELECT * FROM comments WHERE comment='test recorddd' ");
I'm relatively new to Android and can't get this working.
So far I have an app which has one large circular red button. When this is pressed by the user it executes an operation (at the moment it displays 'Hello World').
Instead, I would like it to delete all photos on the Android device. (Yes, it is a strange thing to do, I know)
My questions:
Is this possible? I've had trouble getting it to work and can't find apps which have done this before.
The only Android device I have is a Nexus 7 tablet. I would like it to work on both Android phones and tablets. Is this also possible, as I understand that their directory structures vary?
Yes you can delete Images but I do not suspect you can do them all at once.
I recommend you query the Mediastore to get the _ID of all Images and then loop through the list and delete them. Here is some sample code but i'm sure it can be better optimized. This code is filesystem agnostic so it should work anywhere.
Code to get list of all items on Mediastore.Images
List<Long> mediaStoreIds = new ArrayList<Long>();
Cursor c = context.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new String[]{BaseColumns._ID}, null, null, null);
if (c != null) {
final int id = c.getColumnIndexOrThrow(BaseColumns._ID);
c.moveToFirst();
while (!c.isAfterLast()) {
Long mediaStoreId = c.getLong(id);
mediaStoreIds.add(mediaStoreId);
c.moveToNext();
}
c.close();
}
Then to delete, well you could do that right in the original loop or loop through the arraylist, but here is how to delete:
context.getContentResolver().delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, BaseColumns._ID + "=?", new String[]{Long.toString(mediaStoreIds.get(i))});
I'm working on a MP3 application in which I'd like to index the files on my SDCard. What is the best way to do it?
My Idea. Search for files when the application is started for the first time and register a broadcast receiver for the SDCard state change intents and search for the files whenever the intent is broadcasted.
But in this case the ANR would show up if my broadcast receiver doesn't return within 10 seconds.
Looking for better and failsafe ideas. Thanks.
Agree with Chris, MediaScanner finds music for you, populating the MediaStore database. Here's some code to look up a music entry:
final Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
final String[] cursor_cols = {
MediaStore.Audio.Media._ID,
MediaStore.Audio.Media.ARTIST,
MediaStore.Audio.Media.ALBUM,
MediaStore.Audio.Media.TITLE,
};
final String where = MediaStore.Audio.Media.IS_MUSIC + "=1";
final Cursor cursor = getContentResolver().query(uri, cursor_cols, where, null, null);
cursor.moveToNext();
final String artist = cursor.getString(_cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
final String album = cursor.getString(_cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
final String track = cursor.getString(_cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
doSomethingInteresting(artist, album, track);
The "data" field contains a handle you can use with MediaPlayer.
To avoid the ANR you need to do the search outside the UI thread. As SD cards can be big, you probably want to do it in a service rather than in your foreground activity so that the user can use their device for other things while the search is ongoing.
But android already finds and indexes supported media files, so you should see if you can leverage the built-in MediaScanner stuff.