草庐IT

android - aChartEngine:如何显示实时更新

coder 2023-12-23 原文

我目前正在使用 achartengine 来显示使用蓝牙接收的实时数据。 数据在特定线程上正确收集,并以 Bundle 的形式每隔 100 毫秒发送到我的主要 Activity 。

主要 Activity 包含来自 achartengine 库(LineChart 类)的 4 个图表,使用 GraphicalView 添加到其 View

总而言之,每隔 100 毫秒,我的主要 Activity 就会收到一个回调,并且

  • 从包中复制数据
  • 将其添加到 4 个不同的数据集 (XYMultipleSeriesDataset)
  • 在包含数据集的 4 个图表上调用 repaint()

调用重绘时,数据集不会更新,直到下一个包从另一个线程到达,即 100 毫秒后。我假设 100 毫秒应该足以重绘 View 。

我在这里发布了一个包含数据集和 View 的类的小版本, 以及实例化和使用它的主要 Activity 的一部分

/** This class contains the data and renderer used to draw a chart */
public class LineChartData 
{
public static enum Series {X, Y, Z};
public static final int MAX_NB_VALUES_PER_SERIE = 1000;

private XYMultipleSeriesDataset mDataset = new XYMultipleSeriesDataset();
private XYMultipleSeriesRenderer mRenderer = new XYMultipleSeriesRenderer();
private int[] mColors = new int[]{Color.RED, Color.GREEN, Color.BLUE};
private GraphicalView mChartView;

private boolean mFollow = true;
private boolean mTouchedDown = false;

public LineChartData(Context context, String title, String[] seriesNames)
{
    // Since we save only 3 colors, the programm will not support more than 3 series per chart
    for(int i = 0; i < seriesNames.length; ++i)
    {
        XYSeries serie = new XYSeries(seriesNames[i].toString());
        mDataset.addSeries(serie);
        XYSeriesRenderer serieRenderer = new XYSeriesRenderer();
        serieRenderer.setColor(mColors[i]);
        mRenderer.addSeriesRenderer(serieRenderer);
    }

    mRenderer.setMargins(new int[] { 20, 30, 0, 0 });
    mRenderer.setLegendHeight(50);
    //mRenderer.setShowLegend(true);
    mRenderer.setChartTitle(title);

    mRenderer.setMarginsColor(Color.WHITE);

    mRenderer.setXAxisMax(SettingsActivity.DEFAULT_DISPLAY_RANGE);
    mRenderer.setXAxisMin(0);

    mChartView = ChartFactory.getLineChartView(context, mDataset, mRenderer);


    mChartView.repaint();
}

public void addData(int serieId, double x, double y)
{
    mDataset.getSeriesAt(serieId).add(x, y);
}


public void repaint()
{
        mChartView.invalidate();
}

public void updateDisplayWindow(int displayRange)
{
    if(!mFollow)
    {
        return;
    }

    double maxX = mDataset.getSeries()[0].getMaxX();
    if(maxX > displayRange)
    {
        mRenderer.setXAxisMax(maxX);
        mRenderer.setXAxisMin(maxX-displayRange);
    }
    else
    {
        mRenderer.setXAxisMax(displayRange);
        mRenderer.setXAxisMin(0);
    }
}

public void deleteOldestValues()
{
    XYSeries[] series = mDataset.getSeries();

    for(XYSeries serie : series)
    {
        while(serie.getItemCount() > MAX_NB_VALUES_PER_SERIE)
        {
            serie.remove(0);
        }
    }
}
}

创建 4 个图表:

protected void onCreate(Bundle b)
{
    mAccelChartView    = new LineChartData(this, getString(R.string.Accel), new String[]
            {getString(R.string.xAxis), getString(R.string.zAxis), getString(R.string.zAxis)});
    mGyroChartView    = new LineChartData(this, getString(R.string.Gyro), new String[]
            {getString(R.string.xAxis), getString(R.string.zAxis), getString(R.string.zAxis)});
    mPressureChartView    = new LineChartData(this, getString(R.string.Pressure), new String[]
            {getString(R.string.pressureAxis)});
    mEcgChartView    = new LineChartData(this, getString(R.string.ECG), new String[]
            {getString(R.string.ecgAxis)});

    LinearLayout layoutAccel = (LinearLayout) findViewById(R.id.accelChart);

    LinearLayout layoutGyro = (LinearLayout) findViewById(R.id.gyroChart);

    LinearLayout layoutPressure = (LinearLayout) findViewById(R.id.pressureChart);

    LinearLayout layoutEcg = (LinearLayout) findViewById(R.id.ecgChart);

    layoutAccel.addView(mAccelChartView.getView(), 0);
    layoutGyro.addView(mGyroChartView.getView(), 0);
    layoutPressure.addView(mPressureChartView.getView(), 0);
    layoutEcg.addView(mEcgChartView.getView(), 0);
}

更新图表

private void updateViewUsingMessage(String action, Bundle newData) 
{   
    if(action.equals(BluetoothCollecterThread.MSG_UPDATE_CHART))
    {
        addFromBundle(newData, mLogger.isLogging());

        mAccelChartView.updateDisplayWindow(mDisplayRange);
        mAccelChartView.deleteOldestValues();

        mGyroChartView.updateDisplayWindow(mDisplayRange);
        mGyroChartView.deleteOldestValues();

        mPressureChartView.updateDisplayWindow(mDisplayRange);
        mPressureChartView.deleteOldestValues();

        mEcgChartView.updateDisplayWindow(mDisplayRange);
        mEcgChartView.deleteOldestValues();

        mAccelChartView.repaint();
        mGyroChartView.repaint();
        mPressureChartView.repaint();
        mEcgChartView.repaint();
    }
}

它可能看起来很大,但实际上很容易。我有两个问题,我不知道他们是否有联系: 首先,每秒数次,一张图表的第一个系列被绘制在另一张图表上(见下图)。我检查了我的代码,但找不到任何解决方案。此外,它似乎真的是随机发生的,所以它看起来更像是来自 achartengine 库的并发问题。

这带来了我的第二个问题。日志显示来自垃圾收集器的消息太多,尤其是 WAIT_FOR_CONCURRENT_GC 类型的消息被阻塞,这显然很糟糕。

我找到了这个链接:http://stackoverflow.com/questions/14187716/is-achartengine-ready-for-realtime-graphing建议内存分配问题仍然存在于 achartengine 中,但最新版本应该足以实时显示几个 1000 点的图形。即使我将数据更新设置为 1 秒而不是 100 毫秒,我仍然会遇到视觉故障

正确的行为:实时更新 4 个图表(两个首先是 3 个系列,最后两个是 1 个系列) http://s903.photobucket.com/user/bperreno/media/correct_zpsebd731f6.png.html?sort=3&o=1

错误:图表 2 中的红色系列显示在其他图表上 http://s903.photobucket.com/user/bperreno/media/wrong1_zpsd33eec83.png.html?sort=3&o=0

数据是实时移动的,但时不时会出现第二张图所示的故障,持续一两帧,然后就消失了。 有谁知道如何至少解决显示问题?也许希望能告诉我如何解决图表引擎过度使用垃圾回收的问题

非常感谢!

编辑 04.12.2013 我现在使用的是 achartengine 源而不是 .jar,所以我能够查看这些值。看到“setInScroll”不起作用后,我查看了代码。在 GraphicalView.onDraw 方法中,我比较了 setInScroll 为 true 或 false 时使用的值。

int top = mRect.top;
int left = mRect.left;
int width = mRect.width();
int height = mRect.getheight();
if(mRenderer.isInScroll())
{
    top = 0;
    left = 0;
    width = getMeasuredWidth();
    height = getMeasuredHeight();
}

来自 mRect 或 getMeasuredXXX() 的值始终相同。在这两种情况下,顶部和左侧都是零。所以(至少在这种情况下),inScroll = true 没有效果

最佳答案

好的,我正在回答我的问题:

经过多次测试,发现主要是charartengine lib操作过多导致的并发问题。

繁重的操作似乎包括

  • 删除一个点
  • 设置X最大显示值
  • 提神

所以我能做的最好的事情就是将 X 最大位置设置得更远,所以我只需要偶尔更新一次(例如每秒一次)

向库中添加一种允许一次删除多个点的方法也很有用。一个删除目前是 O(n),一个接一个地删除 m 个点因此是 O(nm)。

请注意,比我的 galaxy S2 更强大的设备(如 S4mini)可以毫无故障地处理显示。但是对于更多的图和更高的频率,效率仍然是一个问题。

4 个图表达到 10 FPS 并不是我所谓的实时

关于android - aChartEngine:如何显示实时更新,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20317244/

有关android - aChartEngine:如何显示实时更新的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  4. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  5. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  6. ruby-on-rails - Rails 编辑表单不显示嵌套项 - 2

    我得到了一个包含嵌套链接的表单。编辑时链接字段为空的问题。这是我的表格:Editingkategori{:action=>'update',:id=>@konkurrancer.id})do|f|%>'Trackingurl',:style=>'width:500;'%>'Editkonkurrence'%>|我的konkurrencer模型:has_one:link我的链接模型:classLink我的konkurrancer编辑操作:defedit@konkurrancer=Konkurrancer.find(params[:id])@konkurrancer.link_attrib

  7. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  8. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  9. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  10. ruby - 如何每月在 Heroku 运行一次 Scheduler 插件? - 2

    在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/

随机推荐