Akshay Sharma Akshay Sharma - 5 months ago 10
Android Question

Threading - how to improve the performance of App

I am making a app which scans the list of songs from phone storage and store them in

ListView
. So, I created the class
SongsManager
which scans the songs from SD card and store them in
Array
.The code is given below.

public class SongsManager {
final String MEDIA_PATH = Environment.getExternalStorageDirectory() .getPath() + "/";
private ArrayList<HashMap<String, String>> songsList = new ArrayList<HashMap<String, String>>();
private String mp3Pattern = ".mp3";
private File directory;

// Constructor
public SongsManager() {
}

/** * Function to read all mp3 files and store the details in * ArrayList * */

public ArrayList<HashMap<String, String>> getPlayList() {
System.out.println(MEDIA_PATH);
if (MEDIA_PATH != null) {
File home = new File(MEDIA_PATH);
File[] listFiles = home.listFiles();
if (listFiles != null && listFiles.length > 0) {
for (File file : listFiles) {
System.out.println(file.getAbsolutePath());
if (file.isDirectory()) {
scanDirectory(file);
} else {
addSongToList(file);
}
}
}
}
return songsList;
}


private void scanDirectory(final File directory) {
if (directory != null) {
File[] listFiles = directory.listFiles();
if (listFiles != null && listFiles.length > 0) {
for (File file : listFiles) {
if (file.isDirectory()) {
scanDirectory(file);
} else {
addSongToList(file);
}
}
}
}
}


private void addSongToList(File song) {
if (song.getName().endsWith(mp3Pattern)) {
HashMap<String, String> songMap = new HashMap<String, String>();
songMap.put("songTitle", song.getName().substring(0, (song.getName().length() - 4)));
songMap.put("songPath", song.getPath());

// Adding each song to SongList
songsList.add(songMap);
}
}
}


When I run the app, app freezes for 2 or 3 seconds due to fetching songs from SD Card. When I open the logcat, it shows the error
The application may be doing too much work on its main thread
. So, I decided to improve the performance of application. I modified the class as following

public class SongsManager {

final String MEDIA_PATH = Environment.getExternalStorageDirectory() .getPath() + "/";
private ArrayList<HashMap<String, String>> songsList = new ArrayList<HashMap<String, String>>();
private String mp3Pattern = ".mp3";
private File directory;

// Constructor
public SongsManager() {
}

/** * Function to read all mp3 files and store the details in * ArrayList * */

public ArrayList<HashMap<String, String>> getPlayList() {
System.out.println(MEDIA_PATH);
if (MEDIA_PATH != null) {
File home = new File(MEDIA_PATH);
File[] listFiles = home.listFiles();
if (listFiles != null && listFiles.length > 0) {
for (File file : listFiles) {
System.out.println(file.getAbsolutePath());
if (file.isDirectory()) {
scanDirectory(file);
} else {
addSongToList(file);
}
}
}
}
return songsList;
}


private void scanDirectory(final File directory) {
Runnable r = new Runnable() {
@Override
public void run() {
if (directory != null) {
File[] listFiles = directory.listFiles();
if (listFiles != null && listFiles.length > 0) {
for (File file : listFiles) {
if (file.isDirectory()) {
scanDirectory(file);
} else {
addSongToList(file);
}
}
}
}
}
};
Thread thread = new Thread(r);
thread.start();
}


private void addSongToList(File song) {
if (song.getName().endsWith(mp3Pattern)) {
HashMap<String, String> songMap = new HashMap<String, String>();
songMap.put("songTitle", song.getName().substring(0, (song.getName().length() - 4)));
songMap.put("songPath", song.getPath());

// Adding each song to SongList
songsList.add(songMap);
}
}
}


After modifying the code, freezing problem solves but the fetching of songs not working properly. When I open the app, some songs are loaded and some are not. When I reopen the app, some more songs are loaded. This problem is occuring.
Here is my
Tab1 Class
code

public class Tab1 extends ListFragment {

private ListView lv;
// Songs list
public ArrayList<HashMap<String, String>> songsList = new ArrayList<HashMap<String, String>>();


public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View V = inflater.inflate(R.layout.tab1, container, false);

ArrayList<HashMap<String, String>> songsListData = new ArrayList<HashMap<String, String>>();
SongsManager plm = new SongsManager();
// get all songs from sdcard
this.songsList = plm.getPlayList();

// looping through playlist
for (int i = 0; i < songsList.size(); i++) {
// creating new HashMap
HashMap<String, String> song = songsList.get(i);
// adding HashList to ArrayList
songsListData.add(song);
}

// Adding menuItems to ListView
ListAdapter adapter = new SimpleAdapter(getContext(), songsListData,
R.layout.playlist_item, new String[]{"songTitle"}, new int[]{
R.id.songTitle});
setListAdapter(adapter);
return V;
}


}

Answer

consider using a AsyncTask instead of above implementation.. in your implementation there is no way to guarantee that all songs have been scanned after your plm.getPlaylist() is completed as it being done on several different threads. That's why you are not seeing all songs.

public class GetSongsAsyncTask extends AsyncTask<Void, Void, List<Hashmap<String, String>>> {
private final SongsManager mSongsManager;

public interface SongsReceivedListener {
    void onSongsReceived(List<HashMap<String, String>> songs);
}

private SongsReceivedListener mSongsReceivedListener;

public GetSongsAsyncTask(SongsReceivedListener mSongsReceivedListener) {
    this.mSongsReceivedListener = mSongsReceivedListener;
    mSongsManager = new SongsManager();
}

@Override
protected List<HashMap<String, String>> doInBackground(Void.. voids) {
    return mSongsManager.getPLayList();
}

@Override
protected void onPostExecute(List<HashMap<String, String>> v) {
    if(mSongsReceivedListener != null) {
        mSongsReceivedListener.onSongsReceived(v);
    }
}
}

Where your list fragment will implement SongsReceivedListener and call this AsyncTask like this

new GetSongsAsyncTask(this).execute(); 

your ListFragment will look like this

public class Tab1 extends ListFragment implements GetSongsAsyncTask.SongsReceivedListener{

private ListView lv; // Songs list public ArrayList> songsList = new ArrayList>(); public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); }

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View V = inflater.inflate(R.layout.tab1, container, false);
    new GetSongsAsyncTask(this).execute(); 
    return V;
}

@Override
void onSongsReceived(List<Hashmap<String, String>> songs) {
  if(getContext() == null)
   return;
  // looping through playlist
  for (int i = 0; i < songsList.size(); i++) {
      // creating new HashMap
      HashMap<String, String> song = songs.get(i);
      // adding HashList to ArrayList
      songsListData.add(song);
  }

  // Adding menuItems to ListView
  ListAdapter adapter = new SimpleAdapter(getContext(), songsListData,
          R.layout.playlist_item, new String[]{"songTitle"}, new int[]{
          R.id.songTitle});
  setListAdapter(adapter);
}

}