Android静态方法可以很好地实时绘制后台线程数据,但它是一个很好的解决方案吗?

Vin*_*nce 1 static multithreading android real-time android-activity

我一直在询问有关我的Android项目的一系列不断变化的问题,这些问题会不断地实时绘制蓝牙数据.而且我在提问方面做得不是很好.

所以我需要做的是编辑这个问题,清理它,添加重要的细节,最重要的是我需要添加相关代码段的代码片段,特别是我已经黑了很多的部分,并提供有关这些的解释代码段.那样也许我可以得到一个答案:我的问题/关注点是:我目前的解决方案是否合适?是否会在我添加新功能时保持不变?

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

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

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

这是我开始使用的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);
}
Run Code Online (Sandbox Code Playgroud)

然后在蓝牙背景线程中我添加了一个调用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
    }
}
Run Code Online (Sandbox Code Playgroud)

然后在Plot活动中我基本上清理了house,删除了"实现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();
}
Run Code Online (Sandbox Code Playgroud)

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

我的整体问题/疑虑:我目前的解决方案是否合适?是否会在我添加新功能时保持不变?

Com*_*are 6

我在后台线程中找到了将BluetoothData写入Logcat的方法.所以我利用这种方法在绘图活动中调用静态方法plotData(BluetoothData).它可以很好地实时绘制输入的蓝牙数据.

这个故事并没有加起来,或者BluetoothData被误解了.

在Android中,要绘制到屏幕上,您需要一个Activity实例,以及您正在绘制的任何小部件.一plotData(),做绘图可法static,但不知何故,它需要的Activity 实例.因此,必须满足以下条件之一:

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

但是,由于您反复拒绝提供源代码,尽管已经询问了几个关于代码的问题,但我们不可能说出哪些是您的问题.

它不是线程安全的吗?我有死锁问题吗?我的解决方案是脆弱的吗?我是否规避了Android操作系统?我很幸运,它在工作吗?或者是否有适当的方式来扩展现有设计?

其中前五个有不同的答案取决于上面四个子弹中的哪一个反映现实,我真的不想写出一个20格的答案网格.你的上一个问题假设你实际上已经解释了你的"设计",你没有.


UPDATE

一些评论基于对问题的实质性修订:

我希望得到一个解释,为什么plotData()看起来必须是静态的才能工作.

它可能不一定是静态的.

我目前的解决方案是否合适?

可能不是.

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

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

然而,更大的远问题是由表示plotHistory,plotHistorySeries以及plotHistoryPlot静态数据成员是.我不知道这些对象是什么.然而,根据他们的名字和你的总体目标,它似乎redraw()实际上绘制到屏幕上,这意味着它plotHistoryPlot是或者保留了一些子类,View即绘制的东西.这意味着您违反了Android开发的基本规则:

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

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

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