【问题标题】:Android static method plots background thread data nicely in real-time, but is it a good solution?Android静态方法可以很好地实时绘制后台线程数据,但它是一个好的解决方案吗?
【发布时间】:2011-06-04 15:19:47
【问题描述】:

我一直在就我的 Android 项目提出一系列不断演变的问题,该项目不断实时绘制蓝牙数据。而且我在提问方面做得不是很好。

所以我需要做的是编辑这个问题,清理它,添加重要的细节,最重要的是我需要添加相关代码部分的代码片段,特别是我已经破解了很多的部分,并提供关于这些代码部分的解释。这样,也许我可能会得到我的问题/疑虑的答案:我当前的解决方案是否可行?当我添加新功能时它会持续吗?

基本上我已经完成的是通过将一些开源代码BluetermOrientationSensor 拼凑在一起来创建我的应用程序的第一个版本。

有人建议我添加一个线程、一个处理程序、一个服务,或者使用 Async Task 或 AIDL 等。但我决定我不想修改或替换我现有的解决方案,除非我真的应该这样做。主要是我想知道它是否足以继续前进并扩展它以添加其他功能。

顺便说一下,我之前所说的 BluetoothData 只是蓝牙数据:它是以 2 到 10 个样本/秒的速率从远程蓝牙设备接收的 16 位数据。我的应用程序基本上是一个数据采集系统,它获取/接收蓝牙数据并绘制它。

这是我开始使用的 Blueterm 开源代码的描述(请参阅上面的链接)。 Blueterm 基本上是一个通过蓝牙进行通信的终端仿真程序。它由几个活动组成,Blueterm 是最重要的。它发现、配对和连接支持 SPP/RfComm 的远程蓝牙设备。连接后,我可以使用 Blueterm 通过发送命令来配置远程设备以打开采样、更改要采样的通道数(到一个通道)、更改输入数据的格式(我喜欢逗号分隔的数据)等

这里是我开始使用的 OrientationSensorExample 开源代码的描述(参见上面的链接)。它基本上是 AnroidPlot 库的示例应用程序。 OrientationSensor 活动实现 SensorEventListener。这包括覆盖 onSenorChanged(),每当获取新的方向传感器数据时都会调用它,它会重绘图形。

将这两个开源项目(Blueterm 和 OrientationSensorExample)拼凑到一个应用程序 (Blueterm) 中,下面是对整个应用程序 (Blueterm) 工作原理的描述。当我启动 Blueterm 时,整个屏幕模拟了一个漂亮的蓝色终端。从选项菜单中,我发现、配对、连接和配置远程蓝牙设备,如上所述。配置远程设备后,我再次转到选项菜单并选择“绘图数据”,这将启动绘图活动。终端模拟器消失了,一个漂亮的来自 Plot 活动的滚动实时图出现了。

我是这样做的。在 onOptionsItemSelected() 我添加了一个案例来启动 Plot 活动,如下所示:

@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);
}

然后在蓝牙后台线程中我添加了对update()的调用来调用plotData()如下:

/**
 * 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
    }
}

然后在Plot活动中我基本打扫了屋子,去掉了“implements SensorEventListener”和一些相关的方法和变量,写了plotData()来调用如上图。以下是 plotData() 及其辅助方法 splitData() 和 nowPlotData() 目前的样子:

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();
}

总结时间:我基本上在 Blueterm 活动创建的后台线程中找到了 update() 方法。 update() 方法本质上是使用 append() 方法将新接收的蓝牙数据附加到屏幕缓冲区。因此,后台线程的 update() 方法看起来是调用 plotPlot() 的好地方。所以我设计了 plotData() 来绘制传递给 append() 的数据。只要 plotData() 是一个静态方法,它就可以工作。我希望能解释一下为什么 plotData() 看起来必须是静态的才能工作。

我的总体问题/顾虑是:我当前的解决方案是否可行?当我添加新功能时它会持续吗?

【问题讨论】:

    标签: android multithreading static android-activity real-time


    【解决方案1】:

    我在后台线程中找到了将 BluetoothData 写入 Logcat 的方法。因此,我利用此方法在绘图活动中调用静态方法 plotData(BluetoothData)。它可以很好地实时绘制传入的 BluetoothData。

    这个故事没有加起来,或者BluetoothData被误命名了。

    在 Android 中,要绘制到屏幕上,您需要一个 Activity 实例,以及您要绘制的任何小部件。进行绘图的plotData() 方法可以是static,但不知何故它需要Activity 实例。因此,以下其中一项必须为真:

    • BluetoothData 包含一个 Activity 实例(因此命名错误),或者
    • plotData() 接受的参数不止一个,或者
    • 您在静态数据成员 (BAD BAD BAD BAD BAD) 中持有 Activity 实例,或者
    • plotData() 不是静态方法,因此您实际上是在 Activity 实例上调用它

    但是,由于您一再拒绝提供源代码,尽管就代码提出了几个问题,但我们无法确定哪些问题确实是您的问题。

    它不是线程安全的吗?我有死锁问题吗?我的解决方案是脆弱的吗?我在绕过安卓操作系统吗?我很幸运它正在工作吗?或者有没有适当的方法来扩展现有的设计?

    其中前五个有不同的答案,具体取决于上面四个项目符号中的哪一个反映了现实,我真的不想写出一个由 20 个单元格组成的答案网格。您的最后一个问题假设您实际上已经解释了您的“设计”,而您还没有。


    更新

    一些基于对问题的实质性修改的cmet:

    我希望能解释一下为什么 plotData() 看起来必须是静态的才能工作。

    它可能不必是静态的。

    我目前的解决方案可以吗?

    可能不会。

    静态方法本身很少有问题。由于缺乏线程安全、内存泄漏等原因,静态数据经常是个问题。因此,您的目标是最小化或消除所有静态数据成员。

    这里至少有四个静态数据成员,也许更多。一个是strData。这并不是糟糕,因为您在每个plotData() 调用上将静态数据成员重置为新的StringBuffer,因此您的内存泄漏是适度的。但是,如果要以某种方式同时在多个线程上调用 plotData() - 而且,由于我不知道您的线程模型,这至少是可能的 - 您将遇到问题,因为您没有同步。

    但是,plotHistoryplotHistorySeriesplotHistoryPlot 静态数据成员代表了更大的问题。我不知道这些对象是什么。但是,根据它们的名称和您的总体目标,redraw() 似乎实际上绘制到屏幕上,这意味着plotHistoryPlot 是或拥有View 的某个子类,这是正在绘制的东西。这意味着您违反了 Android 开发的基本规则:

    切勿将引用瞬态Context 的内容放入静态数据成员中

    这里,Activity 表示瞬态Context,“瞬态”是因为活动确实消失了,Context 因为它继承自 Context。您的静态引用的View 保留了对其Activity 的引用。因此,这个Activity 永远不会被垃圾回收,这对业务不利,更不用说任何可能的线程安全问题。

    同样,这是一个有根据的猜测,因为我不知道那些静态数据成员到底是什么。

    【讨论】:

    • @CommonsWare 很抱歉没有发布源代码。我不记得有人要求过。但我确实发布了代码的链接。它包含一个超过 3300 行的文件,我不确定有人想看哪个部分。这里又是BluetermOrientatioinSensor 的链接。如您所见,我正在破解开源代码。我将编辑我的问题以解释我对设计的理解,并尝试发布源代码的相关部分。但在此之前,我应该向你们的另一位 cmets 讲话。
    • @CommonsWare 很抱歉“这个故事没有加起来”。当我编辑我的问题时,我会更正这一点。但在我看来,这个开源代码由 3 个主要组件组成:蓝牙 Activity、蓝牙 Activity 创建的后台线程和绘图 Activity。
    • @Vince:没错,没有人专门要求源代码。但是,您提出问题的工作是提供足够的信息来获得您寻求的答案。如果你在 StackOverflow 上闲逛,你会看到很多很多的问题发布在源代码上,当他们对他们的源代码是否“脆弱”等有疑问时。您可以提供的越多,您获得更好答案的几率就越高。
    • @Vince:仅供参考,您在之前的评论中链接的代码都没有plotData() 方法,AFAICT。
    • @CommonsWare,你是正确的 plotData() 不存在。这些链接是我一直在破解的开源代码。我应该按照现在的样子发布代码的相关部分。很抱歉浪费您的时间。我会解决这个问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多