开发者

Android static method plots background thread data nicely in real-time, but is it a good solution?

开发者 https://www.devze.com 2023-03-10 04:32 出处:网络
I\'ve been asking a series of evolving questions regarding my Android project that continually plots Bluetooth data in real-time. And I haven\'t done a very good job of asking questions.

I've been asking a series of evolving questions regarding my Android project that continually plots Bluetooth data in real-time. And I haven't done a very good job of asking questions.

So what I need to do is edit this question, clean it up, add important detail, and most importantly I need to add code fragments of relevant sections of code, especially sections I've hacked on quite a bit, and provide explanation about these sections of code. That way maybe I might get an answer to my question/concerns which are: Is my current solution an OK one? Is it going to hold up as I add new features?

Basically what I've already done is create a first version of my app by cobbling together some open source code Blueterm and OrientationSensor.

It's been suggested that I add a thread, a handler, a Service, or use Async Task, or AIDL, etc. But I have decided I don't want to modify or replace my existing solution unless I really should. Mainly I want to know if it's good enough to move forward and extend it to add other features.

By the way what I have previously referred to as BluetoothData is just bluetooth data: it's 16 bit data received from a remote Bluetooth device at the rate of 2 to 10 samples/second. My app is basically a data acquisition system that acquires/receives bluetooth data and plots it.

Here's a description of the Blueterm open source code I started with (see link above). Blueterm is basically a terminal emulator program that communicates over Bluetooth. It consists of several activities with Blueterm being the most important. It discovers, pairs, and connects with a remote Bluetooth device that supports SPP/RfComm. When connected I can use Blueterm to configure the remote device by sending it commands to turn on sampling, change the number of channels to sample (to one channel), change to format of the incoming data (I like comma separated data), etc

Here's a description of the OrientationSensorExample open source code I started with (see link above). It's basically an example application of the AnroidPlot library. The OrientationSensor activity implements SensorEventListener. This includes overriding o开发者_JAVA百科nSenorChanged() which is called whenever new orientation sensor data is taken, and it redraws the graph.

Having cobbled together these two open source projects (Blueterm and OrientationSensorExample) into one application (Blueterm) here's a description of how the overall application (Blueterm) works. When I start Blueterm the whole screen emulates a nice blue terminal. From the Options Menu I discover, pair with, connect to, and configure a remote bluetooth device as described above. Once I have configured the remote device, I go again to the Options Menu and select "Plot data" which launches the Plot activity. The terminal emulator goes away, and a nice scrolling real-time plot from the Plot activity shows up.

Here's how I did this. In onOptionsItemSelected() I added a case to launch the Plot activity as follows:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
    case R.id.connect:

        if (getConnectionState() == BluetoothSerialService.STATE_NONE) {
            // Launch the DeviceListActivity to see devices and do scan
            Intent serverIntent = new Intent(this, DeviceListActivity.class);
            startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE);
        }
        else
            if (getConnectionState() == BluetoothSerialService.STATE_CONNECTED) {
                mSerialService.stop();
                mSerialService.start();
            }
        return true;
    case R.id.preferences:
        doPreferences();
        return true;
    case R.id.menu_special_keys:
        doDocumentKeys();
        return true;
    case R.id.plot_data:
        doPlotData();
        return true;
    }
    return false;
}

private void doPlotData() {
    Intent plot_data = new Intent(this, com.vtrandal.bluesentry.Plot.class);
    startActivity(plot_data);
}

Then in the bluetooth background thread I added a call to update() to call plotData() as follows:

/**
 * Look for new input from the ptty, send it to the terminal emulator.
 */
private void update() {
    int bytesAvailable = mByteQueue.getBytesAvailable();
    int bytesToRead = Math.min(bytesAvailable, mReceiveBuffer.length);
    try {
        int bytesRead = mByteQueue.read(mReceiveBuffer, 0, bytesToRead);
        append(mReceiveBuffer, 0, bytesRead);

        //VTR use existing handler that calls update() to get data into plotting activity
        //OrientationSensor orientationSensor = new OrientationSensor();
        Plot.plotData(mReceiveBuffer, 0, bytesRead);

    } catch (InterruptedException e) {
        //VTR OMG their swallowing this exception
    }
}

Then in the Plot activity I basically cleaned house, removed "implements SensorEventListener" and some related methods and variables, and wrote plotData() to be called as shown above. Here's what plotData() and it's helper methods splitData() and nowPlotData() currently look like:

private static StringBuffer strData = new StringBuffer("");
public static void plotData(byte[] buffer, int base, int length) {

    Log.i("Entering: ", "plotData()");

    /*
    byte[] buffer = (byte[]) msg.obj;
    int base = msg.arg1;
    int length = msg.arg2;
    */

    for (int i = 0; i < length; i++) {
        byte b = buffer[base + i];
        try {
            if (true) {
                char printableB = (char) b;
                if (b < 32 || b > 126) {
                    printableB = ' ';
                }
                Log.w("Log_plotData", "'" + Character.toString(printableB)
                        + "' (" + Integer.toString(b) + ")");

                strData.append(Character.toString(printableB));
                if (b == 10)
                {
                    Log.i("End of line: ", "processBlueData()");
                    Log.i("strData", strData.toString());
                    splitData(strData);
                    strData = new StringBuffer("");
                }
            }
        } catch (Exception e) {
            Log.e("Log_plotData_exception", "Exception while processing character "
                    + Integer.toString(i) + " code "
                    + Integer.toString(b), e);
        }
    }

    Log.i("Leaving: ", "plotData()");
}

private static void splitData(StringBuffer strBuf) {
    String strDash = strBuf.toString().trim();
    String[] strDashSplit = strDash.split("-");
    for (int ndx = 0; ndx < strDashSplit.length; ndx++)
    {
        if (strDashSplit[ndx].length() > 0)
            Log.i("strDashSplit", ndx + ":" + strDashSplit[ndx]);
        String strComma = strDashSplit[ndx].trim();
        String[] strCommaSplit = strComma.split(",");
        for (int mdx = 0; mdx < strCommaSplit.length; mdx++)
        {
            if (strCommaSplit[mdx].length() > 0)
                Log.i("strCommaSplit", mdx + ":" + strCommaSplit[mdx]);
            if (mdx == 1)
            {
                int raw = Integer.parseInt(strCommaSplit[1],16);
                Log.i("raw", Integer.toString(raw));
                float rawFloat = raw;
                Log.i("rawFloat", Float.toString(rawFloat));
                float ratio = (float) (rawFloat/65535.0);
                Log.i("ratio", Float.toString(ratio));
                float voltage = (float) (5.0*ratio);
                Log.i("voltage", Float.toString(voltage));
                nowPlotData(voltage);
            }
        }
    }
}

public static void nowPlotData(float data) {

    // get rid the oldest sample in history:
    if (plotHistory.size() > HISTORY_SIZE) {
        plotHistory.removeFirst();
    }

    // add the latest history sample:
    plotHistory.addLast(data);

    // update the plot with the updated history Lists:
    plotHistorySeries.setModel(plotHistory, SimpleXYSeries.ArrayFormat.Y_VALS_ONLY);

    //VTR null pointer exception?
    if (plotHistoryPlot == null)
        Log.i("aprHistoryPlot", "null pointer exception");

    // redraw the Plots:
    plotHistoryPlot.redraw();
}

Time for a summary: I basically found the update() method in the background thread that was created by the Blueterm activity. The update() method essentially appends newly received bluetooth data to the screen buffer using the append() method. So, the background thread's update() method looked like a good place to call plotPlot(). So I designed plotData() to plot the data being passed to append(). This works as long plotData() is a static method. I would appreciate an explanation as to why plotData() seemingly must be static in order to work.

And again my overall question/concerns: Is my current solution an OK one? Is it going to hold up as I add new features?


I found the method in the background thread that writes BluetoothData to the Logcat. So I am leveraging this method to call a static method, plotData(BluetoothData), in the Plotting Activity. It works nicely plotting the incoming BluetoothData in real-time.

This story does not add up, or BluetoothData is misnamed.

In Android, to plot to the screen, you need an Activity instance, with whatever widget(s) you are plotting on. A plotData() method that does the plotting can be static, but somehow it needs the Activity instance. So, one of the following must be true:

  • BluetoothData contains an Activity instance (and hence is misnamed), or
  • plotData() takes more than the one parameter you have indicated, or
  • you are holding onto an Activity instance in a static data member (BAD BAD BAD BAD BAD), or
  • plotData() is not a static method, so you are actually calling it on an Activity instance

But, since you repeatedly decline to provide the source code, despite having asked several questions about the code, it is impossible for us to say which of these is really your problem.

Is it not thread safe? Do I have a deadlock issue? Is my solution a fragile one? Am I circumventing the Android OS? Am I lucky that it's working at all? Or is there a proper way to extend the existing design?

The first five of these have different answers depending upon which of the four bullets above reflects reality, and I really do not feel like writing out a 20-cell grid of answers. Your last question assumes that you have actually explained your "design", which you have not.


UPDATE

Some comments based upon the substantial revision to the question:

I would appreciate an explanation as to why plotData() seemingly must be static in order to work.

It probably doesn't have to be static.

Is my current solution an OK one?

Probably not.

Static methods are rarely a problem on their own. Static data is frequently a problem, due to lack of thread safety, memory leaks, and the like. Hence, your objective is to minimize or eliminate all static data members.

There are at least four static data members at play here, perhaps more. One is strData. This isn't too bad, in that you reset the static data member to a fresh StringBuffer on each plotData() call, so your memory leak is modest. However, if plotData() somehow were to be called on multiple threads simultaneously -- and, since I don't know your threading model, that's at least possible -- you will have problems, since you have no synchronization.

However, the far bigger problem is represented by the plotHistory, plotHistorySeries, and plotHistoryPlot static data members are. I have no idea what these objects are. However, by their name and your overall objective, it would appear that redraw() actually draws to the screen, which means that plotHistoryPlot is or holds some subclass of View that is the thing being plotted upon. This means you violated a cardinal rule of Android development:

Never put something that references a transient Context in a static data member

Here, an Activity represents a transient Context, "transient" because activities do go away, Context because it inherits from Context. Your statically-referenced View holds a reference back to its Activity. Hence, this Activity can never be garbage collected, which is bad for business, let alone any possible thread-safety issues.

Again, this is an educated guess, since I don't know what those static data members really are.

0

精彩评论

暂无评论...
验证码 换一张
取 消