Vin*_*nce 1 static multithreading android real-time android-activity
我一直在询问有关我的Android项目的一系列不断变化的问题,这些问题会不断地实时绘制蓝牙数据.而且我在提问方面做得不是很好.
所以我需要做的是编辑这个问题,清理它,添加重要的细节,最重要的是我需要添加相关代码段的代码片段,特别是我已经黑了很多的部分,并提供有关这些的解释代码段.那样也许我可以得到一个答案:我的问题/关注点是:我目前的解决方案是否合适?是否会在我添加新功能时保持不变?
基本上我已经完成的是通过拼凑一些开源代码Blueterm和OrientationSensor来创建我的应用程序的第一个版本.
有人建议我添加一个线程,一个处理程序,一个服务,或使用异步任务,或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()看起来必须是静态的才能工作.
我的整体问题/疑虑:我目前的解决方案是否合适?是否会在我添加新功能时保持不变?
我在后台线程中找到了将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永远不会被垃圾收集,这对业务不利,更不用说任何可能的线程安全问题了.
同样,这是一个有根据的猜测,因为我不知道那些静态数据成员到底是什么.