2011-06-04 30 views
1

我一直在问一系列关于我的Android项目的不断演变的问题,这些问题不断地实时绘制蓝牙数据。我在提问方面做得不是很好。所以我需要做的是编辑这个问题,清理它,添加重要的细节,最重要的是我需要添加相关代码段的代码片段,特别是我已经砍掉很多的段落,以及提供有关这些代码段的解释。这样,也许我可能会得到一个答案,我的问题是:我目前的解决方案是否可行?当我添加新功能时,它会持续吗?Android静态方法实时绘制后台线程数据很好,但它是一个很好的解决方案吗?

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

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

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

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

下面是我开始使用的OrientationSensorExample开放源代码的描述(请参阅上面的链接)。它基本上是AnroidPlot库的一个示例应用程序。 OrientationSensor活动实现SensorEventListener。这包括覆盖onSenorChanged(),无论何时取得新的方向传感器数据,都会调用onSenorChanged(),并重新绘制图形。

将这两个开放源代码项目(Blueterm和OrientationSensorExample)拼凑在一个应用程序(Blueterm)中后,这里描述了整个应用程序(Blueterm)的工作方式。当我启动Blueterm时,整个屏幕模拟一个漂亮的蓝色终端。从选项菜单中,我发现,配对,连接并配置远程蓝牙设备,如上所述。配置好远程设备后,我再次进入选项菜单并选择启动绘图活动的“绘图数据”。终端模拟器消失了,并且Plot活动出现了一个很好的滚动实时图。

以下是我如何做到这一点。在onOptionsItemSelected()我添加的情况下,推出绘图活动如下:

@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()看起来必须是静态的才能工作。

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

回答

6

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

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

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

  • BluetoothData包含Activity实例(因此是名不副实),或
  • plotData()花费比一个参数越多,你所指出的,或
  • 你持有到在静态数据成员(BAD BAD BAD BAD BAD)的Activity实例或
  • plotData()不是静态的方法,因此,实际上是调用它上的Activity
  • 实例

但是,由于您一再拒绝提供源代码,尽管询问了有关代码的几个问题,但我们无法说出其中哪些问题真的是您的问题。

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

这些答案的前五个有不同的答案,取决于上面的四个项目符号中哪一个反映了现实,我真的不想写出一个20格的答案网格。你最后的问题是假设你已经解释了你的“设计”,而你没有。


UPDATE

基于大量修改的问题的一些意见:

我相当喜欢的一个解释,为什么plotData()貌似必须是静态的,以工作。

它可能不一定是静态的。

我目前的解决方案是否可行?

可能不是。

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

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

但是,更大的问题是由静态数据成员plotHistory,plotHistorySeriesplotHistoryPlot表示。我不知道这些对象是什么。但是,从他们的名字和总体目标来看,redraw()实际上是绘制到屏幕上的,这意味着plotHistoryPlot是或正在绘制的东西是View的一些子类。这意味着你违反了Android开发的基本规则:

不要把一些静态数据成员引用了一个短暂的Context

这里,Activity表示瞬时Context,“短暂的”,因为活动干什么去了Context,因为它继承自Context。您静态参考的View持有其参考Activity。因此,这个Activity永远不会被垃圾收集,这对业务不利,更不用说任何可能的线程安全问题。

再一次,这是一个有教养的猜测,因为我不知道那些静态数据成员真的是什么。

+0

@CommonsWare我很抱歉没有发布源代码。我不记得任何人要求。但我没有发布链接到代码。它包含一个3300+行文件,我不确定任何人想看到的部分。这里是链接[Blueterm](http://pymasde.es/blueterm/)和[OrientatioinSensor](http://androidplot.com/wiki/OrientationSensorExample)。正如你所看到的,我对开源代码进行了黑客攻击。我将编辑我的问题以解释我对设计的理解,并且我将尝试发布源代码的相关部分。但在此之前,我应该解决您的其他意见。 – Vince 2011-06-04 19:05:17

+0

@CommonsWare我很抱歉,“这个故事没有加起来。“当我编辑我的问题时,我会纠正这个问题,但按照我看到的方式,这个开源代码由3个主要组件组成:蓝牙活动,由蓝牙活动创建的后台线程和绘图活动 – Vince 2011-06-04 19:23:18

+0

@Vince:诚然,没有人特别要求提供源代码,但是,你提出的问题是提供足够的信息来获得你想要的答案。如果你在StackOverflow中寻找答案,你会发现许多问题在源代码发布后关于他们的源代码是否“脆弱”等问题,你可以给我们提供的越多,获得更好答案的几率就越高 – CommonsWare 2011-06-04 19:28:57

相关问题