Статический метод Android хорошо отображает данные фоновых потоков в режиме реального времени, но является ли это хорошим решением? - PullRequest
1 голос
/ 04 июня 2011

Я задавал ряд развивающихся вопросов, касающихся моего проекта Android, который постоянно отображает данные Bluetooth в режиме реального времени. И я не очень хорошо задавал вопросы.

Итак, что мне нужно сделать, это отредактировать этот вопрос, очистить его, добавить важные детали, и, самое главное, мне нужно добавить фрагменты кода соответствующих разделов кода, особенно разделов, которые я взломал довольно часто, и предоставить объяснение об этих разделах кода. Таким образом, возможно, я мог бы получить ответ на мой вопрос / проблемы, а именно: Является ли мое текущее решение приемлемым? Собирается ли это, пока я добавляю новые функции?

По сути, я уже создал первую версию своего приложения, собрав воедино некоторый открытый исходный код Blueterm и OrientationSensor .

Было предложено добавить поток, обработчик, службу, или использовать асинхронную задачу, или AIDL и т. Д. Но я решил, что не хочу изменять или заменять существующее решение, если мне это не нужно. В основном я хочу знать, достаточно ли это, чтобы двигаться вперед и расширить , чтобы добавить другие функции.

Кстати, то, что я ранее называл BluetoothData, это просто данные Bluetooth: это 16-битные данные, полученные с удаленного устройства Bluetooth со скоростью от 2 до 10 выборок в секунду. Мое приложение в основном представляет собой систему сбора данных, которая получает / получает данные Bluetooth и строит их на графике.

Вот описание открытого исходного кода Blueterm, с которого я начал (см. Ссылку выше). Blueterm - это в основном программа-эмулятор терминала, которая общается через Bluetooth. Он состоит из нескольких видов деятельности, причем Blueterm является наиболее важным. Он обнаруживает, связывает и соединяется с удаленным устройством Bluetooth, которое поддерживает SPP / RfComm. После подключения я могу использовать Blueterm для настройки удаленного устройства, посылая ему команды для включения выборки, изменения количества каналов для выборки (на один канал), изменения формата входных данных (мне нравятся данные, разделенные запятыми) и т. Д.

Вот описание открытого исходного кода OrientationSensorExample, с которого я начал (см. Ссылку выше). Это в основном пример приложения библиотеки AnroidPlot. Деятельность OrientationSensor реализует SensorEventListener. Это включает переопределение onSenorChanged (), которое вызывается всякий раз, когда берутся новые данные датчика ориентации, и перерисовывает график.

Объединив эти два проекта с открытым исходным кодом (Blueterm и OrientationSensorExample) в одно приложение (Blueterm), вот описание того, как работает все приложение (Blueterm). Когда я запускаю Blueterm, весь экран эмулирует красивый синий терминал. В открывшемся меню «Параметры» выполните сопряжение, подключитесь и настройте удаленное устройство Bluetooth, как описано выше. После того, как я настроил удаленное устройство, я снова захожу в меню параметров и выбираю «Данные графика», который запускает действие «График». Эмулятор терминала исчезает, и появляется хороший прокручиваемый график в реальном времени из действия 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);
}

Затем в фоновом потоке bluetooth я добавил вызов 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
    }
}

Затем в упражнении «Заговор» я в основном убрал дом, удалил «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();
}

Время для сводки: я в основном нашел метод update () в фоновом потоке, который был создан действием Blueterm. Метод update () по существу добавляет вновь полученные данные Bluetooth в экранный буфер с помощью метода append (). Таким образом, метод update () фонового потока выглядит как хорошее место для вызова plotPlot (). Поэтому я разработал plotData () для отображения данных, передаваемых в append (). Это работает, так как plotData () является статическим методом. Я был бы признателен за объяснение того, почему plotData (), по-видимому, должен быть статическим, чтобы работать.

И снова мой общий вопрос / проблемы: мое текущее решение в порядке? Собирается ли это, пока я добавляю новые функции?

1 Ответ

6 голосов
/ 04 июня 2011

Я нашел метод в фоновом потоке, который записывает BluetoothData в Logcat. Поэтому я использую этот метод для вызова статического метода plotData (BluetoothData) в операции Plotting. Он отлично работает при построении входящих BluetoothData в режиме реального времени.

Эта история не складывается, или BluetoothData назван неправильно.

В Android для вывода на экран вам необходим экземпляр Activity с любым виджетом, на котором вы строите графики. plotData() метод, который выполняет черчение, может быть static, но каким-то образом ему нужен Activity экземпляр . Итак, одно из следующего должно быть верным:

  • BluetoothData содержит экземпляр Activity (и, следовательно, ошибочно назван) или
  • plotData() принимает больше, чем указанный вами параметр, или
  • вы держите экземпляр Activity в статическом элементе данных (BAD BAD BAD BAD BAD) или
  • plotData() не является статическим методом, поэтому вы фактически вызываете его для Activity instance

Но, поскольку вы неоднократно отказываетесь предоставить исходный код, несмотря на то, что задали несколько вопросов о коде, мы не можем сказать, какой из них действительно является вашей проблемой.

Разве это не потокобезопасно? У меня есть проблема тупика? Мое решение хрупкое? Я обхожу ОС Android? Мне повезло, что это работает вообще? Или есть правильный способ расширить существующий дизайн?

Первые пять из них имеют разные ответы в зависимости от того, какая из четырех вышеупомянутых пуль отражает реальность, и мне действительно не хочется выписывать сетку из 20 ячеек ответов. Ваш последний вопрос предполагает, что вы на самом деле объяснили свой «дизайн», которого у вас нет.


UPDATE

Некоторые комментарии, основанные на существенном пересмотре вопроса:

Буду признателен за объяснение того, почему plotData (), по-видимому, должен быть статическим, чтобы работать.

Вероятно, оно не должно быть статичным.

Мое текущее решение в порядке?

Вероятно, нет.

Статические методы сами по себе редко являются проблемой. Статические данные часто являются проблемой из-за недостаточной безопасности потоков, утечек памяти и тому подобного. Следовательно, ваша цель состоит в том, чтобы свести к минимуму или устранить все элементы статических данных.

Здесь есть как минимум четыре статических члена данных, возможно, больше. Одним из них является strData. Это не слишком плохо, поскольку вы сбрасываете элемент статических данных на новый StringBuffer при каждом вызове plotData(), так что утечка вашей памяти является умеренной. Однако, если plotData() каким-то образом вызывать одновременно несколько потоков - и, поскольку я не знаю вашу модель потоков, это по крайней мере возможно - у вас будут проблемы, поскольку у вас нет синхронизации.

Однако гораздо большую проблему представляют статические элементы данных plotHistory, plotHistorySeries и plotHistoryPlot. Я понятия не имею, что это за объекты. Тем не менее, по их названию и вашей общей цели может показаться, что redraw() фактически рисует на экране, что означает, что plotHistoryPlot является или содержит некоторый подкласс View, то есть предмет, на котором строится график. Это означает, что вы нарушили основное правило разработки Android:

Никогда не помещайте то, что ссылается на переходный процесс Context, в статический элемент данных

Здесь Activity представляет кратковременный Context, «переходный», потому что действия уходят, Context, потому что он наследуется от Context. Ваш View со статической ссылкой хранит ссылку на его Activity. Следовательно, этот Activity никогда не может быть собран мусором, что плохо для бизнеса, не говоря уже о возможных проблемах с безопасностью потоков.

Опять же, это обоснованное предположение, поскольку я не знаю, что на самом деле представляют собой эти статические члены данных.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...