#android #graph #scichart
#Android #График #scichart
Вопрос:
Я пишу приложение для построения графиков в реальном времени с использованием Scichart для Android. Я использовал
FastLineRenderableSeries как оболочка для моих рядов данных
Но мне интересно, какие другие методы с Android SciChart существуют для максимизации скорости построения графиков?
В частности, я заметил снижение производительности при использовании IXyDataSeries и увеличении размера оси x до 100 000 пунктов с 10 000. Скорость построения графиков остается стабильно высокой, пока я не добавлю около 90 000 точек в свои IXyDataSeries.
Спасибо, ребята. Я новичок в stackoverflow … скорее механик, чем специалист по CS.
Вот мой класс graphFragment, который принимает данные датчика UDP в виде строки, объединяет их и добавляет в IXyDataSeries.
public class GraphFragment extends Fragment {
//Various fields...
//UDP Settings
private UdpClient client;
private String hostname;
private int remotePort;
private int localPort;
//Use to communicate with UDPDataClass
private Handler handler;
private boolean listenerExists = false;
private int xBound = 100000; //**Graphing Slows if xBound is TOO large**
private int yBound = 5000;
private boolean applyBeenPressed = false;
private GraphDataSource dataSource; //Gets data from UDPDataClass
private SciChartSurface plotSurface; //Graphing Surface
protected final SciChartBuilder sciChartBuilder = SciChartBuilder.instance();
//Data Series containers
//Perhaps it would be better to use XyySeries here?
private final IXyDataSeries<Double, Double> dataSeriesSensor1 = sciChartBuilder.newXyDataSeries(Double.class, Double.class).build();
private final IXyDataSeries<Double, Double> dataSeriesSensor2 = sciChartBuilder.newXyDataSeries(Double.class, Double.class).build();
private final IXyDataSeries<Double, Double> dataSeriesSensor3 = sciChartBuilder.newXyDataSeries(Double.class, Double.class).build();
private final IXyDataSeries<Double, Double> dataSeriesSensor4 = sciChartBuilder.newXyDataSeries(Double.class, Double.class).build();
private final IXyDataSeries<Double, Double> dataSeriesSensor5 = sciChartBuilder.newXyDataSeries(Double.class, Double.class).build();
private final IXyDataSeries<Double, Double> dataSeriesSensor6 = sciChartBuilder.newXyDataSeries(Double.class, Double.class).build();
private ArrayList<IXyDataSeries<Double,Double>> dataSeriesList = new ArrayList<>(Arrays.asList(dataSeriesSensor1,dataSeriesSensor2,dataSeriesSensor3,dataSeriesSensor4, dataSeriesSensor5, dataSeriesSensor6));
private ArrayList<Double> xCounters = new ArrayList<>(Arrays.asList(0.0,0.0,0.0,0.0,0.0,0.0));
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View frag = inflater.inflate(R.layout.graph_fragment, container, false);
plotSurface = (SciChartSurface) frag.findViewById(R.id.dynamic_plot);
dataSource = new GraphDataSource(); //Run the data handling on a separate thread
dataSource.start();
UpdateSuspender.using(plotSurface, new Runnable() {
@Override
public void run() {
final NumericAxis xAxis = sciChartBuilder.newNumericAxis().withVisibleRange(0,xBound).build();
final NumericAxis yAxis = sciChartBuilder.newNumericAxis().withVisibleRange(0,yBound).build();
//These are wrappers for the series we will add the data to...It contains the formatting
final FastLineRenderableSeries rs1 = sciChartBuilder.newLineSeries().withDataSeries(dataSeriesSensor1).withStrokeStyle(ColorUtil.argb(0xFF, 0x40, 0x83, 0xB7)).build(); //Light Blue Color
final FastLineRenderableSeries rs2 = sciChartBuilder.newLineSeries().withDataSeries(dataSeriesSensor2).withStrokeStyle(ColorUtil.argb(0xFF, 0xFF, 0xA5, 0x00)).build(); //Light Pink Color
final FastLineRenderableSeries rs3 = sciChartBuilder.newLineSeries().withDataSeries(dataSeriesSensor3).withStrokeStyle(ColorUtil.argb(0xFF, 0xE1, 0x32, 0x19)).build(); //Orange Red Color
final FastLineRenderableSeries rs4 = sciChartBuilder.newLineSeries().withDataSeries(dataSeriesSensor4).withStrokeStyle(ColorUtil.argb(0xFF, 0xFF, 0xFF, 0xFF)).build(); //White color
final FastLineRenderableSeries rs5 = sciChartBuilder.newLineSeries().withDataSeries(dataSeriesSensor5).withStrokeStyle(ColorUtil.argb(0xFF, 0xFF, 0xFF, 0x99)).build(); //Light Yellow color
final FastLineRenderableSeries rs6 = sciChartBuilder.newLineSeries().withDataSeries(dataSeriesSensor6).withStrokeStyle(ColorUtil.argb(0xFF, 0xFF, 0x99, 0x33)).build(); //Light Orange color
Collections.addAll(plotSurface.getXAxes(), xAxis);
Collections.addAll(plotSurface.getYAxes(), yAxis);
Collections.addAll(plotSurface.getRenderableSeries(), rs1, rs2, rs3, rs4, rs5, rs6);
}
});
return frag;
}
//This class receives the UDP sensor data as messages to its handler
//Then it splices the data
//Adds the data to the IXySeries
//Then the UpdateSuspender updates the graph
//New data arrives approx every 50 ms (around 20x a second)
//Graphing slows when xAxis is increased to ~100,000
//X data is only counters...Only care about Y data
public class GraphDataSource extends Thread{
public void run(){
Looper.prepare();
//Get Data from UDP Data Class when its available
handler = new Handler(){
public void handleMessage(Message msg){
String sensorData = msg.getData().getString("data"); //Data receiveds
if(dataValid(sensorData)){
sensorData = sensorData.replaceAll("\s", "");
final String[] dataSplit = sensorData.split(","); //split the data at the commas
UpdateSuspender.using(plotSurface, new Runnable() { //This updater graphs the values
@Override
public void run() {
spliceDataAndAddData(dataSplit);
}
});
}
}
};
Looper.loop();
}
/**
*
* @param data string of the udp data
* @return true if the data isn't corrupted..aka the correct length
*/
private boolean dataValid(String data){
return ((data.length() == 1350));
}
/**
*
* @param dataSplit String[] of the entire data
* Adds the each sensor data to the IXySeries representing the data
*/
private void spliceDataAndAddData(String[] dataSplit){
addToSensorSeries(dataSplit, 1);
addToSensorSeries(dataSplit, 2);
addToSensorSeries(dataSplit, 3);
addToSensorSeries(dataSplit, 4);
addToSensorSeries(dataSplit, 5);
addToSensorSeries(dataSplit, 6);
}
/**
*
* @param dataSplit data to split into individual sensor array
* must contain only string representations of numbers
* @param sensorSeriesNumber which sensors to collect the data points of
* Adds the data to the corresponding IXySeries
*/
private void addToSensorSeries(String[] dataSplit, int sensorSeriesNumber){
sensorSeriesNumber -= 1; //Adds each value individually to the series
double xcounter = xCounters.get(sensorSeriesNumber);
int i = sensorSeriesNumber;
int dataSize = dataSplit.length - 1;
String num = "";
while(true){
if(i < 6){ //This is the base case...add the first set of data
num = dataSplit[i];
try {
if(xcounter > xBound){
xcounter = 0;
dataSeriesList.get(sensorSeriesNumber).clear();
}
dataSeriesList.get(sensorSeriesNumber).append(xcounter, Double.parseDouble(num)); //appends every number...
}catch (Exception e){
//Corrupt data
}
}else if((i) <= dataSize amp;amp; i >= 6){ //Will start to get hit after the second time
num = dataSplit[i];
try {
if(xcounter > xBound){
xcounter = 0;
dataSeriesList.get(sensorSeriesNumber).clear();
}
dataSeriesList.get(sensorSeriesNumber).append(xcounter, Double.parseDouble(num));
}catch (Exception e){
//Corrupt data
}
}else{
break;
}
xcounter ;
i = 6;
}
xCounters.set(sensorSeriesNumber,xcounter);
}
}
Комментарии:
1. Обычно при задании вопросов по stackoverflow принято приводить пример кода, чтобы люди могли понять, что вы уже пробовали, в чем может быть проблема, и, возможно, определить, почему это не сработало.
2. Привет @Dr.ABT спасибо за ответ. Я добавил пример кода выше. Я попытался вывести свой код из примера Performance demo Android. Я использую IXyDataSeries с серией FastLineRenderable. Я считываю данные в свой класс fragment, используя структуру обработчика в Android, и объединяю полученные данные, а затем добавляю их в IXyDataSeries. После этого я использую UpdateSuspender для пересчета точек. Построение графиков работает хорошо, пока я не увеличу размер оси X до 100 000 точек или около того, и оно замедляется только после добавления около 90 000 точек. Если у вас есть какие-либо идеи, которые могли бы помочь
3. Это довольно странно, потому что мы протестировали график с точностью до миллиона точек или около того на современных устройствах Android. Вопрос: Демонстрирует ли демонстрация производительности Android замедление? scichart.com/android-chart-realtime-performance-demo также смотрите scichart.com /… где вы можете нажать, чтобы добавить 100 тысяч или 1 миллион очков
4. … и из кода xBound просто влияет на xAxis.VisibleRange, верно?
5. Да, это просто влияет на xAxis.VisibleRange. Я использую HTC One 2013 года для тестирования. Демонстрация производительности Android работает на нем великолепно. Я бы не удивился, если бы мой код был ужасно неэффективным <- не программист по своей природе
Ответ №1:
Я посмотрел на ваш код и не уверен, что мы можем что-то с этим сделать. Ваш пример содержит 6 XyDataSeries с диапазоном значений от 0 до 100000, что дает 600 000 точек на экране, что довольно хорошо для примера в реальном времени на HTC One. В демонстрации производительности SciChart вы можете увидеть использование только 3 экземпляров XyDataSeries, что позволяет получать больше очков в каждой серии
Раскрытие информации: я являюсь ведущим разработчиком в команде SciChart Android
Но я думаю, вы можете получить несколько дополнительных кадров в секунду, добавив некоторые оптимизации в свой код. Основная проблема в диаграммах в реальном времени заключается в коде, который обновляет диаграмму — он вызывается очень часто, поэтому, если вы создаете некоторые объекты во время обновления и не сохраняете их, это может вызвать проблемы из-за GC в Android (передача GC происходит медленно, и она может приостановить все потоки приложения, пока GC собирает все данныенеиспользуемые объекты). Итак, я бы посоветовал вам сделать следующее:
- Я бы посоветовал увеличить размер кучи в вашем приложении: больше памяти у приложения — меньше GC, который он выполняет, если вы используете память эффективно.
- Постарайтесь уменьшить количество упаковок / распаковок и выделять меньше объектов в коде, который вызывается часто (например, обновления рядов данных). По сути, вам нужно забыть о создании каких-либо объектов в обратных вызовах, которые обновляют ряды данных. В вашем коде я заметил несколько мест, где происходит упаковка / распаковка. Этот код вызывается каждую секунду, и некоторые методы вызываются в цикле, поэтому эффект упаковки / распаковки может существенно повлиять на производительность вашего приложения:
dataSeriesList.get(sensorSeriesNumber).append(xcounter, Double.parseDouble(num));
double xcounter = xCounters.get(sensorSeriesNumber);
xCounters.set(sensorSeriesNumber,xcounter);
Я бы посоветовал вам использовать append override, который принимает значения IV. Использование append, которое принимает IValues, позволяет избежать ненужной упаковки / распаковки примитивных типов, когда вы добавляете много данных очень часто.
- Также я бы посоветовал использовать Float или Integer, если вам действительно не нужно Double при создании XyDataSeries. Это потенциально позволяет сократить потребление памяти вдвое (8 байт для хранения double против 4 байт для хранения int / float), и в результате приложение имеет больше свободной памяти, что позволяет выполнять GC реже.
Надеюсь, это вам поможет.
Комментарии:
1. Привет, Юра! Большое спасибо! Я реализовал ваш совет и только что протестировал построение графиков для 6 датчиков с 1 000 000 точек каждый (1 миллион!), И график остается отзывчивым! Спасибо! Я думаю, что исправление распаковки и использование переопределения добавления были тем, что это сделало, но я реализовал все ваши предложения. Спасибо! Scichart потрясающий. 🙂