Filling in Android listView in parallel threads

Author: Eugeniy Marilev
Date of publication: 2014-05-06 10:44:40

In this article, we will focus on working with the GUI in parallel threads. This has already been said in previous articles, but this one will focus on the fully efficient example and its description.

Let us assume that we are developing an application that will do a parallel search in multiple search engines, such as Google and Yandex. We will design the system so that it can be easily expanded.

Make a base class model for the search engines - SearchModel:

public abstract class SearchModel {
 
    private String query; 
 
    public SearchModel() {
        init();
    }
 
    protected void init() {
    }
 
    public void setQuery(String query) {
        this.query = query;
    }
 
    public String getQuery() {
        return query;
    }
 
    public String toString() {
        return "Search system";
    }
 
    public abstract ArrayList getResults() throws IOException;
}

And the class for the search results — SearchResultsItem:

public class SearchResultsItem {
 
     public enum SearchResultsItemType {
         GOOGLE {
             public int getIconResource() {
                 return R.drawable.ic_google_logo;
             }
         },
         YANDEX {
             public int getIconResource() {
                 return R.drawable.ic_yandex_logo;
             }
         };
 
         public abstract int getIconResource();
    }
 
    private SearchResultsItemType type;
    private String url;
    private String title;
 
    public String getUrl() {
        return url;
    }
 
    public String getTitle() {
        return title;
    }
 
    public SearchResultsItemType getType() {
        return type;
    }
 
    public void setUrl(String url) {
        this.url = url;
    }
 
    public void setTitle(String title) {
        this.title = title;
    }
 
    public void setType(SearchResultsItemType type) {
        this.type = type;
    }
 
    public String toString() {
        return "SearchResultItem[url:" + url +",title:" + title + "]";
    }
}

The implementation of the heirs that will search various search engines is up to you, dear readers. I will say only that you will need to do two search patterns to cover our problem: GoogleSearchModel, YandexSearchModel.

Next, we need a definition of Runnable in order to be able to run a search in parallel threads:

public class SearchThread implements Runnable {
 
    private SearchModel model;
    private ArrayList resultsBuffer;
 
    public SearchThread(SearchModel model, ArrayList resultsBuffer) {
        setModel(model);
        setResultsBuffer(resultsBuffer);
    }
 
    public void setModel(SearchModel model) {
        this.model = model;
    }
 
    public SearchModel getModel() {
        return model;
    }
 
    public void setResultsBuffer(ArrayList resultsBuffer) {
        this.resultsBuffer = resultsBuffer;
    }
 
    public ArrayList getResultsBuffer() {
        return resultsBuffer;
    }
 
    public void run() {
        try {
            int pagesCount = 10;
            int i = 0;
            while (i < pagesCount) {
                ArrayList results = model.getResults();
                for (SearchResultsItem item : results) {
                    synchronized (resultsBuffer) {
                        resultsBuffer.add(item);
                    }
                }
                ++i;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        Thread.currentThread().interrupt();
    }
}

Please pay attention to the class of «SearchThread», namely, the constructor and the root method «run». We will pass a reference to an external buffer that will shape your search results, inserting elements in the above buffer will be synchronized for the parallel threads. «SearchThread» method of «Run» class takes a few iterations of a search using a search pattern, and inserts the results into a temporary buffer.

Since it was previously found that the parallel threads can not directly access the elements of the interface (GUI), which are created and run in the main thread of the application, we will create an intermediate entity that will be created and run in the GUI thread, and thus control the buffer of search results. We call that entity, for example, «MultipleSearch»:

public class MultipleSearch implements Runnable {
 
    private String query;
    private ArrayList threads;
    private Handler processResultsHandler;
    private HashMap searchMap;
    public ArrayList items;
 
    public MultipleSearch(Handler processResultsHandler) {
        this.processResultsHandler = processResultsHandler;
        init();
    }
 
    protected void init() {
        threads = new ArrayList();
        items = new ArrayList();
    }
 
    public void setQuery(String query) {
        this.query = query;
    }
 
    public String getQuery() {
        return query;
    }
 
    public void setSearchMap(HashMap searchMap) {
        this.searchMap = searchMap;
    }
 
    public HashMap getSearchMap() {
        return searchMap;
    }
 
    public void run() {
        threads.clear();
        items.clear();
 
        for (String key : searchMap.keySet()) {
            SearchModel model = searchMap.get(key);
            model.setQuery(query);
            Thread thread = new Thread(new SearchThread(model, items), key);
            threads.add(thread);
            thread.start();
        }
 
        listenResults();
    }
 
    protected void listenResults() {
        while (threads.size() > 0) {
            Message msg = new Message();
            msg.obj = this;
            processResultsHandler.sendMessage(msg);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }   
            for (int i = 0; i < threads.size(); ++i) {
                Thread thread = threads.get(i);
                if (!thread.isAlive()) {
                    threads.remove(i);
                }
            }
        }       
    }
}

The purpose of this entity in the start-up and synchronization of parallel searching threads for the list of search patterns to be transferred when «setSearchMap» is called. As you can see, the method of «run» starts for each search model a separate thread, and goes into a “waiting for results”-mode. Method «listenResults» in the process puts the current thread for a specified time asleep, and sends the message, that search results need to be checked, to the main flow through the «processResultsHandler».

Object handler, of course, is to be created in the main stream, and to write the code in the handler that checks for new data in the collection of search results and, in case of presence of the mentioned, adds them to the listView, and then clears the collection:

public class AndroidTestProjectActivity extends Activity {
 
    private Button searchBtn;
    private EditText searchText;
    private SearchResultsListView resultsView;
    private MultipleSearch finder;
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        instance = this;
        init();
    }
 
    protected void onSearch(String query) {
        HashMap map = new HashMap();
        map.put("googleSearch", new GoogleSearchModel());
        map.put("yandexSearch", new YandexSearchModel());
 
        ((SearchResultsAdapter)resultsView.getAdapter()).clear();
        finder.setQuery(query);
        finder.setSearchMap(map);
        Thread thread = new Thread(finder, "search#" + query);
        thread.start();
    }
 
    protected void onSearchButtonClick(View v) {
        String query = searchText.getText().toString();
        if (query.length() > 0) {
            onSearch(query);
        } else {
            CharSequence error = getResources().getString(R.string.search_message_empty);
            searchText.setError(error);
        }
    }
 
    private void init() {
        searchBtn = (Button) findViewById(R.id.search_btn);
        searchBtn.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                onSearchButtonClick(v);
            }
        });
 
        searchText = (EditText) findViewById(R.id.search_text);
        resultsView = (SearchResultsListView)findViewById(R.id.search_results_listview);
 
        finder = new MultipleSearch(new Handler() {
            @Override
            public void handleMessage(Message msg) {
                synchronized (finder.items) {
                    SearchResultsAdapter adapter = (SearchResultsAdapter)resultsView.getAdapter();
                    for (SearchResultsItem item : finder.items) {
                        adapter.add(item);
                    }
                    finder.items.clear();
                }
            }
        });
    }
}

In the last example, we pay attention to the «init»-method of main activity, to the lines, where the «finder»is initialized, in particular. We pass it the object of an anonymous class, in which, in fact, processing the search results is determined.

The given example in this article may be far from a perfect solution, but it allows the parallel search without freezing of graphical interface for the search, which was to be achieved ... Try it - it works :-)

Article comments
Comments:
No results found.
Only logged users can leave comments.