Статический метод Android хорошо отображает данные фонового потока в режиме реального времени, но хорошее ли это решение?

#android #multithreading #static #android-activity #real-time

#Android #многопоточность #статический #android-активность #в режиме реального времени

Вопрос:

Я задавал ряд возникающих вопросов относительно моего проекта 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);
}
  

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
    }
}
  

Затем в процессе построения графика я в основном почистил дом, удалил «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:

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

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

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

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

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

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

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


Обновить

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

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

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

Подходит ли мое текущее решение?

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

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

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

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

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

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

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

Комментарии:

1. @CommonsWare Прошу прощения за то, что не опубликовал исходный код. Я не помню, чтобы кто-нибудь просил об этом. Но я разместил ссылки на код. Он содержит файл объемом более 3300 строк, и я не уверен, какой раздел кто-либо хотел бы увидеть. Вот ссылки снова на Blueterm и OrientatioinSensor . Как вы можете видеть, я взламываю открытый исходный код. Я отредактирую свой вопрос, чтобы объяснить, что я понимаю о дизайне, и постараюсь опубликовать соответствующие разделы исходного кода. Но перед этим я должен обратиться к другому вашему комментарию.

2. @CommonsWare Мне жаль, что «эта история не складывается». Я исправлю это, когда отредактирую свой вопрос. Но, на мой взгляд, этот открытый исходный код состоит из 3 основных компонентов: действия Bluetooth, фонового потока, созданного действием Bluetooth, и действия построения графика.

3. @Vince: Правда, никто специально не запрашивал исходный код. Однако ваша задача, задавая вопросы, предоставлять достаточно информации, чтобы получить ответы, которые вы ищете. Если вы поищете в StackOverflow, вы увидите, что многие, многие вопросы публикуют исходный код, когда у них возникают вопросы о том, является ли их исходный код «хрупким» и так далее. Чем больше вы можете нам сообщить, тем выше ваши шансы получить лучшие ответы.

4. @Vince: К вашему сведению, ни в одном коде, на который вы ссылались в предыдущем комментарии, нет plotData() метода AFAICT.

5. @CommonsWare, вы правы, plotData () там нет. Ссылки относятся к открытому исходному коду, который я часто взламывал. Я должен опубликовать соответствующие части кода в том виде, в каком он выглядит сейчас. Извините, что трачу ваше время. Я это исправлю.