问题描述
我一直在问关于我的Android项目,不断绘制蓝牙数据实时一系列不断变化的问题。我没有做过提出问题的一个很好的工作。
所以我需要做的就是修改这个问题,把它清理干净,加入重要的细节,而且最重要的是我需要补充的code相关章节code片段,尤其是部分我砍死的相当多,并提供解释一下code这些部分。这样,也许我可能会回答我的问题/顾虑它们是:是我目前的解决方案是确定的?难道要托起我添加新的功能?
基本上我已经做是拼凑一些开源$ C $ C 创建我的应用程序的第一个版本Blueterm 和 OrientationSensor 。
它已经建议我加入一个线程,处理程序,服务,或使用异步任务,或者AIDL等,但我已经决定,我不希望修改或替换我现有的解决方案,除非我真的应该。主要是我想知道它是否足够好继续前进,延长的它来添加其他功能。
顺便说我有什么previously称BluetoothData只是蓝牙数据:这是从一个远程蓝牙设备在2〜10次/秒的速率接收到的16位数据。我的应用程序基本上是一个数据采集系统,采集/蓝牙接收数据,并绘制它。
下面是的Blueterm开源$ C $ C,我开始了说明(见以上链接)。 Blueterm基本上是通信通过蓝牙终端仿真程序。它由与Blueterm是最重要的几项活动。它发现,对,并与支持SPP / RFCOMM远程蓝牙设备连接。当连接我可以用Blueterm通过发送命令来打开取样配置远程设备,换台的数量样品(一个信道),切换到输入数据的格式(我喜欢用逗号分隔的数据)等
下面是的OrientationSensorExample开源$ C $ C,我开始了说明(见以上链接)。它基本上是AnroidPlot库的一个示例应用程序。该OrientationSensor活动实现SensorEventListener。这包括覆盖onSenorChanged()被称为每当新的方向传感器数据是,它重绘图形。
说完这两个开源项目(Blueterm和OrientationSensorExample)拼凑成一个应用程序(Blueterm)这里有一个如何在整个应用程序(Blueterm)工作的描述。当我开始Blueterm整个屏幕模拟一个不错的蓝色端。从选项菜单中我发现,配对,连接,且如上所述配置远程蓝牙设备。有一次,我已经配置了遥控器,我又来了选项菜单,然后选择绘图数据会启动绘图活动。终端仿真器消失,并从情节活动一个很好的滚动实时曲线显示了。
下面是我如何做到这一点。在onOptionsItemSelected()我添加了一个情况下,启动绘图活动如下:
@覆盖
公共布尔onOptionsItemSelected(菜单项项){
开关(item.getItemId()){
案例R.id.connect:
如果(getConnectionState()== BluetoothSerialService.STATE_NONE){
//启动DeviceListActivity看到设备,也扫描
意图serverIntent =新的意图(这一点,DeviceListActivity.class);
startActivityForResult(serverIntent,REQUEST_CONNECT_DEVICE);
}
其他
如果(getConnectionState()== BluetoothSerialService.STATE_CONNECTED){
mSerialService.stop();
mSerialService.start();
}
返回true;
案例R.id. preferences:
做preferences();
返回true;
案例R.id.menu_special_keys:
doDocumentKeys();
返回true;
案例R.id.plot_data:
doPlotData();
返回true;
}
返回false;
}
私人无效doPlotData(){
意图plot_data =新的意图(这一点,com.vtrandal.bluesentry.Plot.class);
startActivity(plot_data);
}
然后在蓝牙后台线程我添加了一个调用的update()调用plotData()如下:
/ **
*寻找新的输入从ptty的,将其发送到终端仿真器。
* /
私人无效更新(){
INT方bytesAvailable = mByteQueue.getBytesAvailable();
INT bytesToRead = Math.min(方bytesAvailable,mReceiveBuffer.length);
尝试 {
INT读取动作= mByteQueue.read(mReceiveBuffer,0,bytesToRead);
追加(mReceiveBuffer,0,读取动作);
// VTR利用现有处理程序调用Update()来获取数据到绘制活动
// OrientationSensor orientationSensor =新OrientationSensor();
Plot.plotData(mReceiveBuffer,0,读取动作);
}赶上(InterruptedException异常E){
// VTR OMG自己吞咽这个异常
}
}
然后在绘图活动我基本上打扫房子,删除实现SensorEventListener和一些相关的方法和变量,并写了plotData()被调用,如上图所示。下面是plotData()和它的辅助方法splitData()和nowPlotData()目前是这样的:
私有静态的StringBuffer strData是=新的StringBuffer();
公共静态无效plotData(byte []的缓冲区,诠释基地,INT的长度){
Log.i(输入,plotData());
/ *
byte []的缓冲区=(字节[])msg.obj;
INT基准= msg.arg1;
INT长度= msg.arg2;
* /
的for(int i = 0; I<长度;我++){
字节B =缓冲[基地+ I];
尝试 {
如果(真){
焦炭printableB =(焦炭)B:
如果(B< 32 || B> 126){
printableB ='';
}
Log.w(Log_plotData,'+ Character.toString(printableB)
+'(+ Integer.toString(B)+));
strData.append(Character.toString(printableB));
如果(B == 10)
{
Log.i(行结束:,processBlueData());
Log.i(strData是,strData.toString());
splitData(strData是);
strData是=新的StringBuffer();
}
}
}赶上(例外五){
Log.e(Log_plotData_exception,异常处理时的性格
+ Integer.toString(我)+code
+ Integer.toString(B),E);
}
}
Log.i(离开,plotData());
}
私有静态无效splitData(StringBuffer的STRBUF){
字符串strDash = strBuf.toString()修剪()。
串[] strDashSplit = strDash.split( - );
对于(INT NDX = 0; NDX< strDashSplit.length; NDX ++)
{
如果(strDashSplit [NDX] .length()大于0)
Log.i(strDashSplit,NDX +:+ strDashSplit [NDX]);
字符串strComma = strDashSplit [NDX] .trim();
串[] strCommaSplit = strComma.split(,);
对于(INT MDX = 0; MDX< strCommaSplit.length; MDX ++)
{
如果(strCommaSplit [MDX] .length()大于0)
Log.i(strCommaSplit,MDX +:+ strCommaSplit [MDX]);
如果(MDX == 1)
{
INT原始=的Integer.parseInt(strCommaSplit [1],16);
Log.i(原始,Integer.toString(原始));
浮rawFloat =原料;
Log.i(rawFloat,Float.toString(rawFloat));
浮动率=(浮点)(rawFloat / 65535.0);
Log.i(比率,Float.toString(比));
浮充电压=(浮点)(5.0 *比率);
Log.i(电压,Float.toString(电压));
nowPlotData(电压);
}
}
}
}
公共静态无效nowPlotData(浮点数据){
//摆脱最古老的样本中的历史:
如果(plotHistory.size()> HISTORY_SIZE){
plotHistory.removeFirst();
}
//添加最新的历史样本:
plotHistory.addLast(数据);
//更新与更新历史记录列表的情节:
plotHistorySeries.setModel(plotHistory,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY);
// VTR空指针异常?
如果(plotHistoryPlot == NULL)
Log.i(aprHistoryPlot,空指针异常);
//重绘图:
plotHistoryPlot.redraw();
}
时间的总结:我基本上发现是由Blueterm活动创建的后台线程的update()方法。更新()方法本质上追加新接收到的蓝牙数据使用append()方法屏幕缓冲区。因此,后台线程的update()方法看起来像一个好地方调用plotPlot()。所以我设计plotData()来绘制传递的数据追加()。这个工作,只要plotData()是一个静态方法。我想AP preciate解释为什么plotData()貌似必须是静态的,为了工作。
和我的一次全面的问题/担忧:是我目前的解决方案是确定的?难道要托起我添加新的功能?
这个故事并没有加起来,或 BluetoothData code>是名不副实的。
在Android上,绘制到屏幕上,你需要一个活动
实例,与任何部件()你正在策划的。 A plotData()
方法,并且绘制可静态
,但不知何故,它需要的活动
的实例的。因此,下列情况之一必须是真实的:
-
BluetoothData code>包含一个
活动
实例(因此是名不副实),或 -
plotData()
的时间超过了一个参数,你曾表示,或 - 您持有到一个
活动
例如在静态数据成员(糟糟糟糟的BAD),或 -
plotData()
不是一个静态方法,所以你实际上是调用它的活动
实例
不过,既然你多次拒绝提供源$ C $ C,尽管已经问了好几个问题,关于code,这是不可能的,我们说这这些确实是你的问题。
前五这些都取决于这四个子弹上面反映现实不同的答案,而且我真的不觉得自己写出答案的20单元格。你的最后一个问题假定您实际上已经说明你的设计,而你没有。
更新
基于大量修改的问题的一些意见:
这可能不一定是静态的。
也许不是。
静态方法是很少对自己的问题。静态的数据的是经常有问题,由于缺少线程安全,内存泄漏等的。因此,你的目标是最大限度地减少或消除所有静态数据成员。
有在这里打球至少四个静态数据成员,或许更。一个是 strData是
。这不是的太的不好,在你重新设置静态数据成员到一个新的的StringBuffer
每个 plotData()
通话,让你的内存泄漏是适度的。但是,如果 plotData()
不知何故要同时被多个线程 - 而且,因为我不知道你的线程模型,这至少是可能 - 你会有问题,因为你没有同步。
不过,远更大的问题是由 plotHistory
, plotHistorySeries
和<$ psented重新$ P code> plotHistoryPlot 静态数据成员。我不知道这些对象。然而,他们的名字和你的总体目标,它会出现重绘()
竟吸引到屏幕上,这意味着 plotHistoryPlot
是或持有查看
是在被绘制的东西一些子类。这意味着你违反了Android开发的一个重要的规则:
永远不要放东西,它引用一个短暂的上下文
的静态数据成员
这里,活动
重presents短暂上下文
,短暂的,因为活动不走,上下文
,因为它从上下文继承
。您的静态参考查看
持有引用恢复到活动
。因此,这个活动
永远不能被垃圾回收,这是对企业不利,更不用说任何可能的线程安全问题。
再次,这是一个受过教育的猜测,因为我不知道那些静态数据成员真的。
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 onSenorChanged() 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?
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 anActivity
instance (and hence is misnamed), orplotData()
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 anActivity
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.
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:
It probably doesn't have to be static.
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.
这篇关于Android的静态方法地块后台线程的数据很好地进行实时,但它是一个很好的解决方案?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!