Android Worker thread freezes Main thread - java

I am working on an app that implements a custom ListView that contains images. I was able to get the listview to download and cache the images, but I would like to move the downloading onto a separate thread. To do so, I created a CachedLogoDownloader class who's goal is to asynchronously download the images and save them in memory to be accessed later by the custom Adapter class.
The problem is that the thread holds up the entire UI. Even when the code in the run() was just a "thread.sleep()" the UI stopped whenever the request for a logo image occurred. Similarly, placing an infinite loop in the run() caused the program to hang indefinitely.
class CachedLogoDownloader{
private HashMap<String, Bitmap> cache = new HashMap<String, Bitmap>();
Context context;
ArrayList<String> FIDs;
Thread runner;
public CachedLogoDownloader(Context inContext){
this.context = inContext;
//list of company logos (by FID) to be downloaded
FIDs = new ArrayList<String>();
//asynchronous downloader thread (single thread for lower-end devices. Shouldn't be much slower to get the images, though)
runner = new Thread(new Runnable() {
public void run() {
for(String FID:FIDs){
Log.d(Cloud.DEBUG_TAG, "Icon Runnable for: " + FID);
Bitmap tempImage = Cloud.lookupIcon(context, FID);
cache.put(FID, tempImage);
FIDs.remove(FID);
}
}
});
}
public Bitmap getLogo(Company aCompany){
String currentFID = aCompany.getFID();
//if the image has already been cached, return the cached image
if(cache.containsKey(currentFID))
{
return cache.get(currentFID);
}
else
{
//add the logo to the list of logos to be downloaded
FIDs.add(currentFID);
//if the downloader thread completed (or hasn't started yet) make it download stuff.
if(!runner.isAlive()){
runner.setPriority(Thread.MIN_PRIORITY);
runner.run();
}
//return null for now (until next time, whent the image will be in the cache!.)
return null;
}
}
}

runner.run();
That is not starting a separate thread, but just executing the contents of the run method on the calling thread. You probably want
runner.start();
Also, you cannot start a Thread more than once, so
if(!runner.isAlive()){
this check is probably not very useful. Start the thread when you need the image (or when you start the application), and then just wait for it to do its work. If you need more complex logic, such as thread pooling or error handling, you should probably just use a library for asynchronous image loading.

Related

Multi-threading in Android

I'm new to Android and Java. I'm trying to download 1000 plus images. I don't want to do that serially in a UI thread as that will be slow. Hence, I implemented multi-threading using thread and runnable in the below manner.
The for-loop will be called 1000 plus times. So is it an efficient way of achieving it? Will the OS manage the thread pool by its own?
private void syncS3Data() {
tStart = System.currentTimeMillis();
try {
for (final AWSSyncFile f : awsSyncData.getFiles()) {
new Thread(new Runnable() {
#Override
public void run() {
beginDownload(f);
}
}).start();
}
} catch (Exception ex) {
progressDialog.dismiss();
showMessage("Error:" + ex.getStackTrace().toString());
}
}
for Sure you can't do that in MainThread (UI Thread) because if you did, the application will not be responding.. and then it will be killed by system, you can use AsyncTask class in order to do what do you need but i prefer to use intentservice
However you have to use Intentservice it's a worker thread (long operation) but be noted intentservice will not execute anything until it finish the current task, if you need to download it in parallel then you have to use Service it works with UI Thread so you need asyncTask in order to perform the action but make sure of calling stopSelf() unlike intentService it will be stopped once it finish
Instead of creating threads for each download, create one thread and use that for downloading all images.
You can use AsyncTask Refer: https://developer.android.com/reference/android/os/AsyncTask.html
private class DownloadFilesTask extends AsyncTask<SomeObject, Integer, Long> {
protected Long doInBackground(SomeObject... objs) {
for (final AWSSyncFile f : obj.getFiles()) {
beginDownload(f);
}
}
protected void onPostExecute(Long result) {
//Task Completed
}
new DownloadFilesTask().execute(someObj);
I had developed an e-commerce app before and have encountered a similar problem in which I had to download some 200+ images for each category.The way I did it was using a loop within an AsyncTask and after each download was completed the image was displayed at the relevant place using the onProgessUpdate() function.I can't share the actual code,so i will give a skeleton example.
public class DownloadImages extends AsyncTask<String,String,String>
{
File image;
protected String doInBackground(String... params)
{
//download the image here and lets say its stored in the variable file
//call publishProgress() to run onProgressUpdate()
}
protected void onProgressUpdate(String... values)
{
//use the image in variable file to update the UI
}
}

Setting text on TextView from background thread anomaly

I've just started playing with Android Concurrency/Loopers/Handers, and I have just faced with strange anomaly. The code below doesn't block me from setting text on TextView from different Thread.
TextView tv;
Handler backgroundHandler;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.sample_text);
Runnable run = new Runnable() {
#Override
public void run() {
Looper.prepare();
backgroundHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
String text = (String) msg.obj;
tv.setText(Thread.currentThread().getName() + " " + text);
}
};
Looper.loop();
}
};
Thread thread = new Thread(run);
thread.setName("Background thread");
thread.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message message = backgroundHandler.obtainMessage();
message.obj = "message from UI";
backgroundHandler.sendMessage(message);
}
And guess what happen
But, when I sleep background thread for a while
backgroundHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String text = (String) msg.obj;
tv.setText(Thread.currentThread().getName() + " " + text);
}
};
it does throw exception as I expected
07-03 18:54:40.506 5996-6025/com.stasbar.tests E/AndroidRuntime: FATAL EXCEPTION: Background thread
Process: com.stasbar.tests, PID: 5996
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7275)
Can someone explain me what happened ? Why I was able to set text from different thread ?
From the very first try, your code is wrong because you created Handler on different thread. This leads to looper/handler is run on different thread. Based on your comment, I guess you know this issue and you want to understand why exception doesn't throw on first try but second.
You should beware this: Accessing UI Element on different thread except from UI Thread causes undefined behavior. This means:
Sometime you see it works.
sometime you don't. You will meet exception as you have seen.
That means Accessing UI Element on different thread isn't always 100% reproducible.
Why you should access all UI Elements only on UI Thread? Because processing UI Element (change internal state, draw to screen ...) is a complex process and need to synchronized between related parties. For example, you call TextView#setText(String) on 2 fragments that both visible on screen. Android doesn't do this concurrently but pushing all jobs into an UI message queue and do that sequentially. This is also true not only from your application viewpoint but also from whole Android system perspective. Updating from status bar that called by system, updating from your app that called by your application always push actions to same UI Message queue before processing.
When you access and modify UI elements on different thread, you broke that process. That means maybe two threads might access and modify an element at same state and same time. As the result, you will meet race condition at some time. That when error occurs.
Explaining for your situation is hard because not enough data for analyzing. But there are some few reasons:
At your first try, TextView haven't displayed on screen yet. So different thread was able to make change on TextView. But on your second try, you sleep for 1 second. At this time, all view has rendered and displayed successfully on screen, so exception threw. You can try Thread.sleep(0) and hopefully your code doesn't crash.
This is will happen in some situations but hard to guess why. That by some chance, both your thread and ui thread accesses same lock object, exception threw.
You can read more about thread issue here Android Thread
Explicit references
Many tasks on non-main threads have the end goal
of updating UI objects. However, if one of these threads accesses an
object in the view hierarchy, application instability can result: If a
worker thread changes the properties of that object at the same time
that any other thread is referencing the object, the results are
undefined.
Hope this help you.
Only the UI Thread can make edits to UI items. In other words, you can not make user interface edits from background threads.
So, instead of tv.setText(Thread.currentThread().getName() + " " + text);, use the following code inside your backgroundHandler :-
runOnUiThread(new Runnable() {
public void run() {
tv.setText(Thread.currentThread().getName() + " " + text);
}
});

JavaFx Progress Indicator freezes when I run another thread

So I have been trying to implement a progress indicator with no luck. I am not sure I understand managing threads with JavaFx very well, despite having read a bit about the Platform.RunLater and Tasks. So here is my use case.
My program allows users to connect to a database and look at some of the schemas and other objects in the database. Sometimes connecting to a large database and pulling up all its tables and info takes a while, so I would like to show a progress indicator. I am not trying to update the progress at all I would just like to make the progress indicator visible at a value of -1 while the process is running to pull everything from the database. Ideally I will have a progress indicator loaded in from an FXML file invisible. When I start the process of pulling info from the database I would like to make it visible.
When trying to make my progress visible it never showed up, so I decide to start out having it visible and making it invisible, just to see what happens. The progress indicator rotated nicely when I opened the program up, but as soon as I try to connect to the database it stopped rotating and just froze. I assume this is what happens when I try to make it visible too which is why it was never showing up.
The following is my current code, I would appreciate any detailed help with explanations so I can understand what is going on. Thanks
from the method that is doing most of the work.
//make progress indicator visible
pi.setVisible(true);
// separate non-FX thread
ExtractorThread t = new ExtractorThread();
t.setCp(cp);
t.start();
//Wait until the thread is done
try{
t.join();
}
catch(Exception e){
e.printStackTrace();
}
//Retrieve the dbextractor from the thread
DbExtractor dbe = t.getDbe();
//move on to the next page in the application
this.caster.goToDataSource(c, cp, dbe);
The ExtractorThread which does the work.
private class ExtractorThread extends Thread{
private ConnectionProperties cp;
private DbExtractor dbe;
public void run() {
dbe = new DbExtractor(cp);
try {
dbe.extract();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public DbExtractor getDbe() {
return dbe;
}
public void setCp(ConnectionProperties cp) {
this.cp = cp;
}
}
If I am supposed to use the Platform.RunLater I am not sure where to use it or why. Any help would be appreciated, thanks!
Use the javafx.concurrent API. Extend Task instead of Thread:
private class ExtractorThread extends Task<DbExtractor>{
private ConnectionProperties cp;
public DbExtractor call() throws Exception {
dbe = new DbExtractor(cp);
dbe.extract();
return dbe;
}
public void setCp(ConnectionProperties cp) {
this.cp = cp;
}
}
Then do:
//make progress indicator visible
pi.setVisible(true);
// separate non-FX thread
final ExtractorThread t = new ExtractorThread();
t.setCp(cp);
t.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
public void handle(WorkerStateEvent event) {
DbExtractor dbExtractor = t.getValue();
this.caster.goToDataSource(c, cp, dbe);
}
});
t.setOnFailed(...); // similarly, to handle exceptions
new Thread(t).start();
I don't code JavaFX, and so I can't give you chapter and verse, but this line:
t.join();
will block the calling code until the background thread is through. Don't do this. Instead use some type of listener to get notified when the background thread finishes. If this were Swing, I'd use a PropertyChangeListener added to a SwingWorker to notify me when the background thread was done. I think that you can still use a PropertyChangeListener to do a similar thing with with JavaFX, but I cannot tell you if this would represent the canonical solution.
Also, don't extend Thread but instead implement Runnable. This won't fix your problem but is basic Java common sense.

Send thread to background

I am trying to implement a music player.
I wrote a class which extends from Thread and overwrote its Start()-Method to play a random song.
Playing a song works, but I want to send that thread to the background, which doesn't work:
File file = new File("song.mp3");
PlayEngine plengine = new PlayEngine(); //This class extends from Thread
plengine.Play(file); //This just sets the file to play in a variable
plengine.Start(); //And this finally plays the file itself
System.out.println("Next task:"); // I don't get to this point. Only when the song has finished.
As you can see in the code above, I'd like to go to the printed line right after launching the thread.
It is not recommended to extend Thread - Have your PlayEngine implement Runnable instead, and override the run method:
class PlayEngine implements Runnable {
private final File file;
PlayEngine(File file) {
this.file = file;
}
#Override
public void run() {
//do your stuff here
play(file);
}
}
Then start the tread with:
PlayEngine plengine = new PlayEngine(file);
Thread t = new Thread(plengine);
t.start();
System.out.println("Next task:");
and Next task should print immediately. In your example, you seem to be calling the long running method play in the main thread, which explains why it does not return immediately.
overwrote its Start()
I suspect you overriden Thread.start() which will never work. Either override Thread.run() or supply your own instance of Runnable to thread.
I think you should pring the log PlayEngine's run method very first. Moreover It appears you have written the playback code in start method(which runs in main thread) instead of run method. To get the playback done in background put the code in start in run method by overriding that.

Single Android Thread for Multiple Jobs

I would like to have an application which either loads or saves data through a HTTP request, however the data must interact with the UI thread. Ideally, I would like a single thread to use an IF statement on a message to determine if the request is to "load" or "save".
What would be the simplest way of doing this with the smallest amount of code?
Also, do instances of Handlers run on individual threads?
EDIT: This is the code I am using now:
Handler doStuff = new Handler(){
#Override
public void handleMessage(Message msg){
if(msg.what == 1){
// Load all the information.
// Get the ID from sharedPrefs
SharedPreferences details= getSharedPreferences("details", 0);
String ID = patDetails.getString("id", "error");
// Load up the ID from HTTP
String patInfo = httpInc.getURLContent("info.php?no="+AES.encrypt("387gk3hjbo8sgslksjho87s", ID));
// Separate all the details
patientInfo = patInfo.split("~");
}
if(msg.what == 2){
// Save the data
}
}
};
Eclipse halts the debugging and displays, "Source not found" for StrictMode.class
I suppose it's because it's using the Main thread to access the internet although it's running in individual threads.
Any idea.
Handlers do run on individual threads. Check that link. You should also check out AsyncTask.
I would propose submitting the jobs as Runnable to a single-threaded ExecutorService:
public class SomeClass {
private ExecutorService execService = Executors.newSingleThreadExecutor();
public void doSomething() {
final String someUiData = // retrieve data from UI
execService.submit(new Runnable() {
#Override
public void run() {
// so something time-consuming, which will be executed asynchronously from the UI thread
// you can also access someUiData here...
}
});
}
}
This way, the UI thread will not block whereas you can easily submit a different Runnable for different operations and the ExecutorService will completely take care of keeping it async.
Edit: If you need to interact with the UI, do so before becoming asynchronous and keep the result in final variables.

Categories

Resources