JavaFX FX application Thread Issue - multithreading

I'm doing a desktop application and I'm performing a heavy task in background. I want a progress bar to be updated. My program works and I can see the progress bar here isn't my problem. My problem is that I use 2 tasks that I run in 2 thread in order to make both the update of the progress bar and the heavy task. My question is : Is there a better way to do in oder to avoid the error "Exception in thread "Thread-5" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-5".
Of course I already check on Internet and I always find : better use Platform.runLater. Ok but in both new thread I need attribute of my class, eg I can't access for example "this.myAttribute" when I use Platform.runLater((new Runnable()...)). Is RunLater the solution and I can't see it ?
Here is a bunch of code, the method setConnection is called in JavaFX thread, and I create 2 other. One for progressbar, the other for my task :
#FXML
private void setConnection() {
try {
this.onOffButton.setSelected(false);
if (!this.hubModel.isConnected()) {
this.progressBar.progressProperty().unbind();
#SuppressWarnings("unchecked")
OperationTask progressBarOperationTask = new OperationTask(this) {
#Override
public Void call() {
HubController hubController = (HubController) this.getHubController();
hubController.getProgressBar().setVisible(true);
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(25);
} catch (InterruptedException e) {
Thread.interrupted();
break;
}
updateProgress(i + 1, 100);
}
hubController.getProgressBar().setVisible(false);
return null;
}
};
this.progressBar.progressProperty().bind(progressBarOperationTask.progressProperty());
Thread timeThread = new Thread(progressBarOperationTask);
timeThread.setDaemon(true);
timeThread.start();
}
#SuppressWarnings("unchecked")
OperationTask connectionOperationTask = new OperationTask(this) {
#Override
protected Object call() throws Exception {
HubController hubController = (HubController) this.getHubController();
if (hubController.getUserID().getText().equals("") || hubController.getUserPW().getText().equals("")) {
hubController.getCommentBottom().setText("Please enter a user name and a password.");
hubController.getOnOffButton().setSelected(false);
} else {
hubController.getHubModel().setIdUser(hubController.getUserID().getText());
hubController.getHubModel().setPwUser(hubController.getUserPW().getText());
String comment = hubController.getHubModel().setConnection();
if (!comment.equals("Connection established.")) {
hubController.getOnOffButton().setSelected(false);
}
if (hubController.getHubModel().isConnected()) {
hubController.getConnectionStatus().setText("Connected");
hubController.getConnectionStatus().setStyle("-fx-font-weight: bold");
String commentProject = hubController.getHubModel().getAllProjects();
if (commentProject.equals("")) {
TextFields.bindAutoCompletion(hubController.getCloneAndMoveController().getNewProjectNameTextField(), hubController.getHubModel().getProjectsList());
} else {
comment = commentProject;
}
hubController.getOnOffButton().setSelected(true);
} else {
hubController.getConnectionStatus().setText("Not connected");
hubController.getConnectionStatus().setStyle("-fx-font-weight: regular");
}
hubController.getCommentBottom().setText(comment);
}
return null;
}
};
Thread connectionThread = new Thread(connectionOperationTask);
connectionThread.setDaemon(true);
connectionThread.start();
} catch (Exception e) {
e.printStackTrace();
}
}
Moreover if you see something that could be improved, I would appreciate (I'm new with java)
Thank you.

You can access your object from Platform.runLater(). New Runnable which you create for it has access to this instance of your object. See in an example:
private String myAttribute = "hello";
#Override
public void randomMethod() {
//...
Platform.runLater(new Runnable() {
#Override
public void run() {
System.out.println(myAttribute);
}
});
}

Related

Java FX new modal Window in Task

My problem is that I can not start a new modal window in Task, it just does not go out. I do not understand how it is possible to derive not just an allert, but any modal window from Task. Translated by Google =)
Task<Void> task = new Task<Void>() {
#Override
public Void call() throws ApiException,ClientException,InterruptedException {
int i = 0;
for ( i = 0; i < bufferLenght; i++){
try {
...some code
}catch(ApiCaptchaException e) {
...get capcha
captchaSid = e.getSid();
captchaImg = e.getImage();
System.out.println( captchaSid);
}
System.out.println(captchaSid);
if (captchaSid != null) {
System.out.println("gg");
Alert alert = new Alert(AlertType.INFORMATION);
alert.setTitle("Test Connection");
//here he is stuck
alert.setHeaderText("Results:");
alert.setContentText("Connect to the database successfully!");
alert.showAndWait();
System.out.println("gg3");
if(i<bufferLenght-1) {
Thread.sleep(2000);
}
}
return null;
}
};
new Thread(task).start();
You must create and show new windows on the FX Application Thread. You can schedule code to execute on the FX Application Thread by submitting it to Platform.runLater(...). If you need your background thread to wait for the user to dismiss the Alert, you can use a CompletableFuture, as in this question:
Task<Void> task = new Task<Void>() {
#Override
protected Void call() throws Exception {
// ...
CompletableFuture.runAsync(() -> {
Alert alert = new Alert(AlertType.INFORMATION);
alert.setTitle("Test Connection");
alert.setHeaderText("Results:");
alert.setContentText("Connect to the database successfully!");
alert.showAndWait();
}, Platform::runLater).join();
// ...
}
};
If your alert is returning a value which you need, use supplyAsync(...) instead and return the value from the lambda expression. You can then assign that value to the result of join():
Task<Void> task = new Task<Void>() {
#Override
protected Void call() throws Exception {
// ...
String result = CompletableFuture.supplyAsync(() -> {
Alert alert = new Alert(AlertType.INFORMATION);
alert.setTitle("Test Connection");
alert.setHeaderText("Results:");
alert.setContentText("Connect to the database successfully!");
alert.showAndWait();
// presumably here you want to return a string
// depending on the alert...
return "" ;
}, Platform::runLater).join();
// ...
}
};

JavaFX Concurrency and Tasks (running threads in Task)

I am new to JavaFx/Concurrency so I read the tutorial over at Concurrency in JavaFX but I am still a little confused about the implementation of background threads in a JavaFX Gui.
I'm trying to write a small GUI that interfaces with some serial devices (using JSSC-2.8) and that updates the GUI based on the responses from those devices. But, there's a lag between when the message is written and when the device responds, and using Thread.sleep() for an arbitrary amount of time wasn't a reliable way for me program it. So instead I want to use wait() and notify() methods from the concurrency package (with all the appropriate synchronizations), but I am not sure how to implement it. What I initially did is create another Thread, inside the Task, that would write the messages and wait for the responses, and using some bindings, would update the GUI. I've included my code at the end. Here is a short form of the pseudocode I am trying to implement:
start Task:
connect to serial devices
synchronized loop:
send messages
wait() for event to fire
notify()
But what's been happening is, as soon as I call the wait(), the entire application idles and then when notify() is called (after the response fires and event), it doesn't continue where it left off in the recipe() loop, or the startTdk() loop for that matter, it's just idle. Have I implements the threads wrong? When I am calling the wait(), is it a possibility that I cause the EventDispatch or JavaFX Application Thread to pause?
I hope the question is clear, if there are any clarifications needed I can update the post.
public class OmicronRecipe extends Service<String> implements Runnable{
private final String SEPERATOR=";";
private final Tdk tdk;
private final Pvci pvci;
private final SimpleStringProperty data = new SimpleStringProperty("");
private final Float MAX_V = 26.0f,UHV=1e-8f;
private boolean isTdkOn=false, isPvciOn=false;
private String power;
private Float temp,press,maxT, setT;
private int diffMaxT,diffP,diffPow, diffT, index=0;
public OmicronRecipe(){
tdk = new Tdk("COM4");
pvci = new Pvci("COM5");
}
private synchronized void recipe(){
while (true){
try {
sendMessages();
data.set(power+SEPERATOR+temp+SEPERATOR+press);
calcDiffs();
if (diffPow < 0){
if(diffMaxT < 0){
if(diffT < 0){
if (diffP < 0){
if(!rampPow()){
//Max Power reached
}
}else{
//Wait for pressure drop
}
}
}else{
//Wait until quit
}
}else{
//Max power reached
}
Thread.sleep(5000);
} catch (InterruptedException ex) {
Logger.getLogger(OmicronRecipe.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
private synchronized boolean rampPow(){
boolean isRamped=false;
Float setPow = tdk.getSetPow(index), curPow;
setT = tdk.getSetT(index);
curPow = Float.parseFloat(power);
if(curPow.compareTo(setPow) < 0){
do{
curPow += 0.1f;
tdk.sendMessage("PV "+curPow+"\r");
try {
wait();
} catch (InterruptedException ex) {
Logger.getLogger(OmicronRecipe.class.getName()).log(Level.SEVERE, null, ex);
}
curPow = Float.parseFloat(power);
}while(curPow.compareTo(setPow) < 0);
index++;
isRamped=true;
}
return isRamped;
}
public synchronized boolean connect(){
if(!isTdkOn && !isPvciOn){
isTdkOn = tdk.connect();
isPvciOn = pvci.connect();
}
return isTdkOn && isPvciOn;
}
public synchronized boolean disconnect(){
if(tdk!=null && pvci !=null){
isTdkOn = tdk.disconnect();
isPvciOn = pvci.disconnect();
}
return !isTdkOn && !isPvciOn;
}
public synchronized StringProperty getData(){
return data;
}
public void setMaxT(Float maxT){
this.maxT = maxT;
}
private synchronized void calcDiffs(){
Float pow = Float.parseFloat(power);
diffPow = pow.compareTo(MAX_V);
diffMaxT = temp.compareTo(maxT);
diffT = temp.compareTo(100f);
diffP = press.compareTo(UHV);
}
private synchronized void setListeners(){
tdk.getLine().addListener((ov,t, t1)-> {
synchronized (this){
System.out.println("New Power: "+t1);
power = t1;
this.notify();
}
});
pvci.getLine().addListener((ov,t,t1) ->{
synchronized (this){
String[] msg = t1.split(SEPERATOR);
if(msg.length == 2){
switch(msg[0]){
case "temperature":
System.out.println("Temperaute");
temp = Float.parseFloat(msg[1]);
break;
case "pressure":
System.out.println("Pressure");
press = Float.parseFloat(msg[1]);
break;
default:
System.out.println("Nothing; Something went wrong");
break;
}
}
this.notify();
}
});
}
private synchronized void sendMessages(){
try {
tdk.sendMessage("PV?\r");
this.wait();
pvci.sendMessage("temperature");
this.wait();
pvci.sendMessage("pressure");
this.wait();
} catch (InterruptedException ex) {
Logger.getLogger(OmicronRecipe.class.getName()).log(Level.SEVERE, null, ex);
}
}
private synchronized boolean startTdk(){
boolean isOut=false;
if(isTdkOn){
try {
tdk.sendMessage("ADR 06\r");
this.wait();
System.out.println("Power: "+power);
if(power.equals("OK")){
tdk.sendMessage("OUT?\r");
this.wait();
if(power.equals("OFF")){
tdk.sendMessage("OUT ON\r");
this.wait();
isOut = power.equals("ON");
}
else{
isOut = power.equals("ON");
}
}
} catch (InterruptedException ex) {
Logger.getLogger(OmicronRecipe.class.getName()).log(Level.SEVERE, null, ex);
}
}
return isOut;
}
#Override
protected Task<String> createTask() {
return new Task<String>() {
#Override
protected String call() throws IOException{
new Thread(new OmicronRecipe()).start();
return "";
}
};
}
#Override
public void run() {
if (connect()){
setListeners();
if(startTdk()){
recipe();
}
}
}
}
I won't include the Pvci class, because it just a copy of the Tdk class but with specific message sequences to talk with that machine.
public class Tdk {
private SerialPort tdkPort;
private final String portName;
private StringBuilder sb = new StringBuilder("");;
private final StringProperty line = new SimpleStringProperty("");
private final HashMap<Float,Float> calibMap;
private ArrayList<Float> list ;
private boolean isEnd=false;
public Tdk(String portName){
this.portName = portName;
System.out.println("TDK at "+portName);
calibMap = new HashMap();
setMap();
}
public synchronized boolean connect(){
tdkPort = new SerialPort(portName);
try {
System.out.println("Connecting");
tdkPort.openPort();
tdkPort.setParams(9600,
SerialPort.DATABITS_8,
SerialPort.STOPBITS_1,
SerialPort.PARITY_NONE);
tdkPort.setEventsMask(SerialPort.MASK_RXCHAR);
tdkPort.addEventListener(event -> {
if(event.isRXCHAR()){
if(event.getPortName().equals(portName)){
try {
if(!isEnd){
int[] str = tdkPort.readIntArray();
if(str!=null)
hexToString(str);
}
if(isEnd){
System.out.println("Here: "+sb.toString());
isEnd=false;
String d = sb.toString();
sb = new StringBuilder("");
line.setValue(d);
}
} catch (SerialPortException e) {
Logger.getLogger(Tdk.class.getName()).log(Level.SEVERE, null, e);
}
}
}
});
} catch (SerialPortException e) {
Logger.getLogger(Tdk.class.getName()).log(Level.SEVERE, null, e);
}
return tdkPort !=null && tdkPort.isOpened();
}
public synchronized boolean disconnect(){
if(tdkPort!=null) {
try {
tdkPort.removeEventListener();
if (tdkPort.isOpened())
tdkPort.closePort();
} catch (SerialPortException e) {
Logger.getLogger(Tdk.class.getName()).log(Level.SEVERE, null, e);
}
System.out.println("Disconnecting");
}
return tdkPort.isOpened();
}
public synchronized void sendMessage(String message){
try {
tdkPort.writeBytes(message.getBytes());
} catch (SerialPortException e) {
Logger.getLogger(Tdk.class.getName()).log(Level.SEVERE, null, e);
}
}
private void setMap(){
calibMap.put(1.0f, 25.0f);
calibMap.put(7.0f, 125.0f);
calibMap.put(9.8f, 220.0f);
list = new ArrayList(calibMap.keySet());
}
public Float getSetPow(int index){
return list.get(index);
}
public Float getSetT(int index){
return calibMap.get(list.get(index));
}
public synchronized StringProperty getLine(){
return line;
}
private synchronized void hexToString(int[] hexVal){
for(int i : hexVal){
if(i != 13){
sb.append((char)i);
}else{
isEnd=true;
}
}
System.out.println("Turning: "+Arrays.toString(hexVal)+" to String: "+sb.toString()+" End: "+isEnd);
}
Freeze
Your UI freezes most probably because you are waiting on the FX Apllication Thread, to solve this there are different approaches:
JavaFX Application Thread
You can delegate some work to the FX Application Thread, therefore see Platform.runLater
Not everything can be run on this thread, but for example, in your DeviceController, you can wait until the message appears and then call Platform.runLater() and update the field (you should therefor oc hand the field over to the controller).
DataBinding
What you are describing can also be realised with DataBinding.
With this you could define a SimpleStringProperty, which is bound to your UI Label (.bind() Method). If the controller must fire its message you can set the StringProperty and the UI will update itself.
The scenario you described could be used like this:
start Task:
connect to serial devices
synchronized loop:
send messages
wait() for event to fire
**updateDate the DataBounded fields**
We are taught that, Concurrency notify/wait
Concurrency on level wait()/notify() is very low level. You should try to work with higher level synchronisation methods or helpers (where people have already solved your problems :))

Replace a TableView with a ProgressIndicator within VBox JavaFX

I have a TableView associated with some data, and once i hit a run button i perform some processing on that data. Each row of data is handled in a seperate thread, and while those threads are running i want a ProgressInducator to replace the table within its vbox.
In the attached code:
If I stop where is says "WORKS IF STOP HERE" - table is replaced with pi.
If I continue waiting for the threads to join - no replacing.
What am I missing?
runButton.setOnAction(
new EventHandler<ActionEvent>() {
#Override
public void handle(final ActionEvent e) {
List<Thread> threadList = new ArrayList<Thread>();
int threadCounter = 0;
final ProgressIndicator pi = new ProgressIndicator(threadCounter);
vbox.getChildren().clear();
vbox.getChildren().addAll(pi);
for (ProductInTable product : data) {
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
try
{
product.calculate();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
});
threadList.add(thread);
thread.start();
}
int x = threadList.size();
/** WORKS IF STOP HERE **/
// wait for all threads to end
for (Thread t : threadList) {
try {
t.join();
threadCounter++;
pi.setProgress(threadCounter / x);
} catch (InterruptedException interE) {
interE.printStackTrace();
}
}
/** DOESNT WORKS IF STOP HERE **/
Thread.join() blocks execution until the thread is completed. Since you are calling this on the FX Application Thread, you block that thread until all your worker threads finish. This means the UI is unable to update until those threads are complete.
A better approach is probably to represent each computation with a task, and update a counter of complete tasks back on the FX Application Thread using setOnSucceeded. Something like:
runButton.setOnAction(
new EventHandler<ActionEvent>() {
#Override
public void handle(final ActionEvent e) {
final ProgressIndicator pi = new ProgressIndicator(threadCounter);
vbox.getChildren().clear();
vbox.getChildren().addAll(pi);
final int numTasks = data.size();
// only access from FX Application thread:
final IntegerProperty completedTaskCount = new SimpleIntegerProperty(0);
pi.progressProperty().bind(completedTaskCount.divide(1.0*numTasks));
completedTaskCount.addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> obs, Number oldValue, Number newValue) {
if (newValue.intValue() >= numTasks) {
// hide progress indicator and show table..
}
}
});
for (final ProductInTable product : data) {
Task<Void> task = new Task<Void>() {
#Override
public Void call() {
try
{
product.calculate();
} catch (IOException ioe) {
ioe.printStackTrace();
}
return null ;
}
});
task.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent event) {
completedTaskCount.set(completedTaskCount.get()+1);
}
});
new Thread(task).start();
}
}
});
If you potentially have a large number of items here, you should use some kind of ExecutorService instead to avoid creating too many threads:
ExecutorService exec = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors()); // for example...
and then replace
new Thread(task).start();
with
exec.submit(task);

Using thread and asyncTask

I am pretty new on android and i have problem with asyncTask and threads.
how can i use AsyncTask in this code?
when i using like this productIdList comes null.That's why i want to use AsyncTask. I think using AsyncTask could work.
thanks in advance.
public ArrayList<String> getProductData() {
final ArrayList<String> productIdList = new ArrayList<String>();
new Thread(new Runnable() {
public void run() {
HttpClient httpclient= new DefaultHttpClient();
GeneralConstans GC = new GeneralConstans();
// Products will be stated in memory
HttpPost httpget = new HttpPost(GC.UrlConstants);
HttpResponse response;
String result = null;
try {
HttpContext ctx = new BasicHttpContext();
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(
2);
httpget.setEntity(new UrlEncodedFormEntity(nameValuePairs,
"UTF-8"));
response = httpclient.execute(httpget, ctx);
HttpEntity resEntity = response.getEntity();
if (resEntity != null) {
result = EntityUtils.toString(resEntity);
JSONArray arr = new JSONArray(result);
Gson gson = new Gson();
if (arr.length() > 0) {
for (int j = 0; j < arr.length(); j++) {
Product p = gson.fromJson(arr.getString(j),
Product.class);
productIdList.add(p.toString());
}
}
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (SocketException e) {
/*if (checkAbortStatus(e.getMessage()) == true) {
handler.sendEmptyMessage(0);
}*/
} catch (IOException e) {
/*if (checkAbortStatus(e.getMessage()) == true) {
handler.sendEmptyMessage(0);
}*/
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
}
private Handler handler = new Handler() {
#Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
}).start();
return productIdList;
A Async task implicitly moves methods and commands away from the main thread, as the MAIN thread should run all tasks.
create a new class,
public class <NAME OF CLASS> extends AsyncTask<Void, String, String>
the extends part basically extends (inherits in c#) taking in some parameters, these parameters relate to the 3 overrided methods you are going to utilise.
onPreExecute - this is a kind of pre doing things method, i personally dont need it code i've written (i'm still new to android myself)
onDoInBackgound - this is the main part of the AsyncTask, this is where all your method will go, this is where all the logic will happen. This is exactly what it says on the tin, it does everything in the background on the other thread.
onPostExecute - when the onDoInBackground is finished it will run the OnPostExecute method, i usually have a String return on the onDoInBackgroun method, which ensures it progresses to the onPostExecute as i found sometimes without it it didnt quite progress.
then in the postExecute method you tell it what you want to do once all the logic is done, e.g you could have a listener on the Main thread in which you call that listener from the AysncTask i.e listener.onSuccess(results) in the postExecute Method, which will return you to the original thread.
Hope this helps

Blackberry device hang when downloading image online

Every time when my apps went to the layout that download image online, the device will hang and need to wait the download finish only can movable.
I did some researched. They recommend download it in another Thread. However, I not understand how to implement the download function in another Thread.
Here is my code to call the download image function.
Main.getUiApplication().invokeLater(new Runnable() {
public void run() {
for (j = 0; j < imagepath.length; j++) {
if (!imagepath[j].toString().equals("no picture")
&& Config_GlobalFunction.isConnected()) {
loader = new Util_LazyLoader(imagepath[j],
new Util_BitmapDowloadListener() {
public void ImageDownloadCompleted(
Bitmap bmp) {
imagebitmap[j] = bmp;
invalidate();
}
});
loader.run();
}
}
}
}, 500, false);
And the lazyloader
public class Util_LazyLoader implements Runnable {
String url = null;
Util_BitmapDowloadListener listener = null;
public Util_LazyLoader(String url, Util_BitmapDowloadListener listener) {
this.url = url;
this.listener = listener;
}
public void run() {
Bitmap bmpImage = getImageFromWeb(url);
listener.ImageDownloadCompleted(bmpImage);
}
private Bitmap getImageFromWeb(String url) {
HttpConnection connection = null;
InputStream inputStream = null;
EncodedImage bitmap;
byte[] dataArray = null;
try {
connection = (HttpConnection) (new ConnectionFactory())
.getConnection(url + Database_Webservice.ht_params)
.getConnection();
int responseCode = connection.getResponseCode();
if (responseCode == HttpConnection.HTTP_OK) {
inputStream = connection.openDataInputStream();
dataArray = IOUtilities.streamToBytes(inputStream);
}
} catch (Exception ex) {
} finally {
try {
inputStream.close();
connection.close();
} catch (Exception e) {
}
}
if (dataArray != null) {
bitmap = EncodedImage.createEncodedImage(dataArray, 0,
dataArray.length);
return bitmap.getBitmap();
} else {
return null;
}
}
}
I need help on it as I not familiar in networking.
So, the Util_LazyLoader is already well written to support background image downloads, because it implements the Runnable interface. You can start the download like this:
Util_LazyLoader loader =
new Util_LazyLoader(imagepath[j],
new Util_BitmapDowloadListener() {
public void ImageDownloadCompleted(final Bitmap bmp) {
UiApplication.getUiApplication().invokeLater(new Runnable() {
public void run() {
imagebitmap[j] = bmp;
invalidate();
}
});
}
});
Thread backgroundWorker = new Thread(loader);
backgroundWorker.start();
instead of directly calling the loader.run() method yourself.
A Runnable class is just one that has a run() method. You give your Runnable loader object to a new Thread and tell it to start(). This will cause that Thread to execute the run() method in another thread, instead of the UI thread. As long as you don't run network operations on the UI thread, your app should not appear to the user to be frozen.
Note: in your original code, you have this:
Main.getUiApplication().invokeLater(new Runnable() {
public void run() {
You probably don't need that at all. If that code is being run from the main (UI) thread, then all that's doing is telling the app to invoke that locally-defined run() method, also on the UI thread. You do pass a 500 millisecond delay as well. Maybe you need that (?). If you just want it to run right away, though, get rid of the code above (invokeLater(new Runnable() { public void run() { ...). Just use the code I posted (at the top of this answer) to create the backgroundWorker and then call its start() method.
Also, take note of two things in my implementation:
1. I used the UiApplication.invokeLater() method once the bitmap has been received. After the network operation completes, the UI must be updated. But that should not be done on the background thread. So, you create a Runnable to run on the background thread, and then once the download is complete, you create another Runnable to update the UI:
public void run() {
imagebitmap[j] = bmp;
invalidate();
}
2. Because I create another Runnable, and use the bmp variable inside that Runnable, I must declare it as a final parameter. The compiler requires you to do that. Another option would be to use the event lock directly, instead of invokeLater():
public void ImageDownloadCompleted(Bitmap bmp) {
synchronized(UiApplication.getEventLock()) {
imagebitmap[j] = bmp;
invalidate();
}
}
Either should work for you.

Resources