0

I am writing an Android app that connects to a Bluetooth device, reads data sent from the device, adds it to an AChartEngine graph, and displays the data in a TextView.

My Bluetooth code is quite similar to the threaded implementation in the BluetoothChat sample code (it comes with the SDK). I can see in LogCat that the ConnectedThread loop is executing and thus getting new data, but my TextView stops updating after 7 lines and the graph pauses intermittently (not to mention that it only responds to interaction intermittently). There aren't any errors being shown in LogCat. Also, if I remove the graph, the problem with the TextView persists.

Why is my UI thread not working when updated from my other threads?


Below are the relevant parts of my code. Each string sent over Bluetooth is received in ConnectedThread and sent to BluetoothController.addToGraph(), which then runs the NewPoints AsyncTask from the viewer class.

private class ConnectedThread extends Thread {
    public ConnectedThread(BluetoothSocket socket, String socketType) { ... } // Initialize input and output streams here

    public void run() {
        while (true) {
            Log.i(TAG, "READ mConnectedThread");
            // Read from the InputStream
            byte[] buffer = new byte[1024];
            bytes = mmInStream.read(buffer);

            // Send the obtained bytes to the UI Activity
            mHandler.obtainMessage(BluetoothController.MESSAGE_READ, bytes, -1, buffer)
                    .sendToTarget();
            Log.i(TAG, "LOOPEND mConnectedThread");
        }
    }
}
public class BluetoothController extends Activity {
    private viewer plotter;
    public static final int MESSAGE_READ = 2;
    // The Handler that gets information back from the BluetoothClass
    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_READ:
                    byte[] readBuf = (byte[]) msg.obj;
                    // construct a string from the valid bytes in the buffer
                    String readMessage = new String(readBuf, 0, msg.arg1);
                    addToGraph(readMessage);
                    break;
            }
        }
    };

    protected void addToGraph(String result) {
        // process the string, create doubles x and y that correspond to a point (x,y)
        plotter.new NewPoints().execute(x, y);
    }
}
public class viewer extends Activity {
    // initialize graph, etc.

    @Override
    protected void onResume() {
        // Create handlers for textview
        textHandler = new Handler();

        // Set scrolling for textview
        myTextView.setMovementMethod(new ScrollingMovementMethod());

    protected class NewPoints extends AsyncTask<Double, Void, Void> {
        @Override
        protected Void doInBackground(Double... values) {
            mCurrentSeries.add(values[0], values[1]); // x, y

            if (mChartView != null) {
                mChartView.repaint();
            }

            final Double[] messages = values;
            textHandler.post(new Runnable() {
                @Override
                public void run() {
                    myTextView.append("(" + messages[0].toString() + ", " + messages[1].toString() + ") \n");
                }
            });

            return null;
        }
    }
}

What gives? If more code is necessary, let me know.

Maxim Zaslavsky
  • 17,787
  • 30
  • 107
  • 173
  • I have not seen the complete source. Just a thought. Could be wrong. Should you not using sleep in your connection thread? – san Aug 01 '12 at 03:47

3 Answers3

2

seems backwards to me... your AsyncTask is updating your textview and currentseries, but AsyncTask should be used to for long running tasks like communicating with other devices/networks. Your UI thread should be doing the updating of the textview and you've got it the other way around

doInBackground should contain the code to talk to your BlueTooth device

Martin
  • 4,711
  • 4
  • 29
  • 37
1

Sure, here's one that comes from the Dropbox api and shows how to implement the progress bar while your task is doing it's communication work in the background. You'll have to modify this for your own nefarious purposes, but it's a great example of background tasks.

 /**
 * Here we show uploading a file in a background thread, trying to show
 * typical exception handling and flow of control for an app that uploads a
 * file
 */
public class UploadFile extends AsyncTask<Void, Long, Boolean> {

    private DropboxAPI<?> mApi;
    private File mFile;

    private long mFileLen;
    private UploadRequest mRequest;
    private Context mContext;
    private final ProgressDialog mDialog;

    private String mErrorMsg;



public UploadFile(Context context, DropboxAPI<?> api, File file) {
    // We set the context this way so we don't accidentally leak activities
    mContext = context.getApplicationContext();

    mFileLen = file.length();
    mApi = api;
    mFile = file;

    mDialog = new ProgressDialog(context);
    mDialog.setMax(100);
    mDialog.setMessage("Uploading " + file.getName());
    mDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
    mDialog.setProgress(0);
    mDialog.setButton(Dialog.BUTTON_POSITIVE ,(CharSequence) "Cancel", new Dialog.OnClickListener() {
        public void onClick(DialogInterface dialog, int which) {
            // This will cancel the putFile operation
            mRequest.abort();
        }
    });
    mDialog.show();
}

@Override
protected Boolean doInBackground(Void... params) {
    try {
        // By creating a request, we get a handle to the putFile operation,
        // so we can cancel it later if we want to
        FileInputStream fis = new FileInputStream(mFile);
        String path = mFile.getName();
        mRequest = mApi.putFileOverwriteRequest(path, fis, mFile.length(),
                new ProgressListener() {
            @Override
            public long progressInterval() {
                // Update the progress bar every half-second or so
                return 500;
            }

            @Override
            public void onProgress(long bytes, long total) {
                publishProgress(bytes);
            }
        });

        if (mRequest != null) {
            mRequest.upload();
            return true;
        }

    } catch (DropboxUnlinkedException e) {
        // This session wasn't authenticated properly or user unlinked
        mErrorMsg = "This app wasn't authenticated properly.";
    } catch (DropboxFileSizeException e) {
        // File size too big to upload via the API
        mErrorMsg = "This file is too big to upload";
    } catch (DropboxPartialFileException e) {
        // We canceled the operation
        mErrorMsg = "Upload canceled";
    } catch (DropboxServerException e) {
        // Server-side exception.  These are examples of what could happen,
        // but we don't do anything special with them here.
        if (e.error == DropboxServerException._401_UNAUTHORIZED) {
            // Unauthorized, so we should unlink them.  You may want to
            // automatically log the user out in this case.
        } else if (e.error == DropboxServerException._403_FORBIDDEN) {
            // Not allowed to access this
        } else if (e.error == DropboxServerException._404_NOT_FOUND) {
            // path not found (or if it was the thumbnail, can't be
            // thumbnailed)
        } else if (e.error == DropboxServerException._507_INSUFFICIENT_STORAGE) {
            // user is over quota
        } else {
            // Something else
        }
        // This gets the Dropbox error, translated into the user's language
        mErrorMsg = e.body.userError;
        if (mErrorMsg == null) {
            mErrorMsg = e.body.error;
        }
    } catch (DropboxIOException e) {
        // Happens all the time, probably want to retry automatically.
        mErrorMsg = "Network error.  Try again.";
    } catch (DropboxParseException e) {
        // Probably due to Dropbox server restarting, should retry
        mErrorMsg = "Dropbox error.  Try again.";
    } catch (DropboxException e) {
        // Unknown error
        mErrorMsg = "Unknown error.  Try again.";
    } catch (FileNotFoundException e) {
    }
    return false;
}

@Override
protected void onProgressUpdate(Long... progress) {
    int percent = (int)(100.0*(double)progress[0]/mFileLen + 0.5);
    mDialog.setProgress(percent);
}

@Override
protected void onPostExecute(Boolean result) {
    mDialog.dismiss();
    if (result) {
        showToast("File successfully uploaded");
    } else {
        showToast(mErrorMsg);
    }
}
Martin
  • 4,711
  • 4
  • 29
  • 37
0

I'm coding a similar app using achartengine and AsynkTask too. You should manage bluetooth in doInBackground and when you receive new data call publishProgress to update your UI (TextView and Achartengine) on the onProgressUpdate method. doInBackground should NEVER update UI, it's wierd that it actually sort of works for you! If you're graphing realtime data with a "low" refresh that will work. If not I'd recommend implementing the bluetooth part as a service and broadcast your data to an activity which manages and updates the UI. If you receive a lot of data you will find that sending data through broadcasts limits your throughput as you have to make your data class Parcelable and that's terribly slow and you'll soon hit limits with Android's binder arquitecture which is used on "Localmanager.sendbroadcast". The most efficient way I found to communicate with the service was through the use of handlers.

If you plan to do fast real-time graphing with achartengine you should first check one of my questions here regarding problems you'll find later on: Is achartengine ready for realtime graphing?

Community
  • 1
  • 1
Usul
  • 123
  • 1
  • 7