Поток пользовательского интерфейса Android перестает обновляться из других потоков

Я пишу приложение для Android, которое подключается к устройству Bluetooth, считывает данные, отправленные с устройства, добавляет их в график AChartEngine, и отображает данные в TextView.

Мой код Bluetooth очень похож на поточную реализацию в примере кода BluetoothChat (он поставляется с SDK). Я вижу в LogCat, что цикл ConnectedThread выполняется и, следовательно, получает новые данные, но мой TextView перестает обновляться после 7 строк и график периодически приостанавливается (не говоря уже о том, что он только периодически реагирует на взаимодействие). В LogCat не отображаются ошибки. Кроме того, если я удалю график, проблема с TextView сохраняется.

Почему мой поток пользовательского интерфейса не работает при обновлении из других моих потоков?


Ниже приведены соответствующие части моего кода. Каждая строка, отправленная по Bluetooth, принимается в ConnectedThread и отправляется в BluetoothController.addToGraph(), который затем запускает NewPoints AsyncTask из класса viewer.

private class ConnectedThread extends Thread {
    public ConnectedThread(BluetoothSocket socket, String socketType) { ... } // Initialize input and output streams here

    public void run() {
        while (true) {
            Log.i(TAG, "READ mConnectedThread");
            // Read from the InputStream
            byte[] buffer = new byte[1024];
            bytes = mmInStream.read(buffer);

            // Send the obtained bytes to the UI Activity
            mHandler.obtainMessage(BluetoothController.MESSAGE_READ, bytes, -1, buffer)
                    .sendToTarget();
            Log.i(TAG, "LOOPEND mConnectedThread");
        }
    }
}
public class BluetoothController extends Activity {
    private viewer plotter;
    public static final int MESSAGE_READ = 2;
    // The Handler that gets information back from the BluetoothClass
    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_READ:
                    byte[] readBuf = (byte[]) msg.obj;
                    // construct a string from the valid bytes in the buffer
                    String readMessage = new String(readBuf, 0, msg.arg1);
                    addToGraph(readMessage);
                    break;
            }
        }
    };

    protected void addToGraph(String result) {
        // process the string, create doubles x and y that correspond to a point (x,y)
        plotter.new NewPoints().execute(x, y);
    }
}
public class viewer extends Activity {
    // initialize graph, etc.

    @Override
    protected void onResume() {
        // Create handlers for textview
        textHandler = new Handler();

        // Set scrolling for textview
        myTextView.setMovementMethod(new ScrollingMovementMethod());

    protected class NewPoints extends AsyncTask<Double, Void, Void> {
        @Override
        protected Void doInBackground(Double... values) {
            mCurrentSeries.add(values[0], values[1]); // x, y

            if (mChartView != null) {
                mChartView.repaint();
            }

            final Double[] messages = values;
            textHandler.post(new Runnable() {
                @Override
                public void run() {
                    myTextView.append("(" + messages[0].toString() + ", " + messages[1].toString() + ") \n");
                }
            });

            return null;
        }
    }
}

Что дает? Если потребуется больше кода, дайте мне знать.


person Maxim Zaslavsky    schedule 31.07.2012    source источник
comment
Я не видел полного исходника. Просто мысль. Может ошибаться. Не следует ли использовать сон в потоке подключения?   -  person san    schedule 01.08.2012


Ответы (3)


кажется мне обратным... ваш AsyncTask обновляет ваш textview и currentseries, но AsyncTask следует использовать для длительных задач, таких как связь с другими устройствами/сетями. Ваш поток пользовательского интерфейса должен выполнять обновление текстового представления, а у вас все наоборот.

doInBackground должен содержать код для связи с вашим устройством BlueTooth.

person Martin    schedule 01.08.2012
comment
Спасибо за Ваш ответ! Не могли бы вы добавить пример кода, демонстрирующий, как это должно быть сделано правильно? - person Maxim Zaslavsky; 01.08.2012

Конечно, вот один из API Dropbox, который показывает, как реализовать индикатор выполнения, пока ваша задача выполняет свою коммуникационную работу в фоновом режиме. Вам придется изменить это для своих гнусных целей, но это отличный пример фоновых задач.

 /**
 * Here we show uploading a file in a background thread, trying to show
 * typical exception handling and flow of control for an app that uploads a
 * file
 */
public class UploadFile extends AsyncTask<Void, Long, Boolean> {

    private DropboxAPI<?> mApi;
    private File mFile;

    private long mFileLen;
    private UploadRequest mRequest;
    private Context mContext;
    private final ProgressDialog mDialog;

    private String mErrorMsg;



public UploadFile(Context context, DropboxAPI<?> api, File file) {
    // We set the context this way so we don't accidentally leak activities
    mContext = context.getApplicationContext();

    mFileLen = file.length();
    mApi = api;
    mFile = file;

    mDialog = new ProgressDialog(context);
    mDialog.setMax(100);
    mDialog.setMessage("Uploading " + file.getName());
    mDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
    mDialog.setProgress(0);
    mDialog.setButton(Dialog.BUTTON_POSITIVE ,(CharSequence) "Cancel", new Dialog.OnClickListener() {
        public void onClick(DialogInterface dialog, int which) {
            // This will cancel the putFile operation
            mRequest.abort();
        }
    });
    mDialog.show();
}

@Override
protected Boolean doInBackground(Void... params) {
    try {
        // By creating a request, we get a handle to the putFile operation,
        // so we can cancel it later if we want to
        FileInputStream fis = new FileInputStream(mFile);
        String path = mFile.getName();
        mRequest = mApi.putFileOverwriteRequest(path, fis, mFile.length(),
                new ProgressListener() {
            @Override
            public long progressInterval() {
                // Update the progress bar every half-second or so
                return 500;
            }

            @Override
            public void onProgress(long bytes, long total) {
                publishProgress(bytes);
            }
        });

        if (mRequest != null) {
            mRequest.upload();
            return true;
        }

    } catch (DropboxUnlinkedException e) {
        // This session wasn't authenticated properly or user unlinked
        mErrorMsg = "This app wasn't authenticated properly.";
    } catch (DropboxFileSizeException e) {
        // File size too big to upload via the API
        mErrorMsg = "This file is too big to upload";
    } catch (DropboxPartialFileException e) {
        // We canceled the operation
        mErrorMsg = "Upload canceled";
    } catch (DropboxServerException e) {
        // Server-side exception.  These are examples of what could happen,
        // but we don't do anything special with them here.
        if (e.error == DropboxServerException._401_UNAUTHORIZED) {
            // Unauthorized, so we should unlink them.  You may want to
            // automatically log the user out in this case.
        } else if (e.error == DropboxServerException._403_FORBIDDEN) {
            // Not allowed to access this
        } else if (e.error == DropboxServerException._404_NOT_FOUND) {
            // path not found (or if it was the thumbnail, can't be
            // thumbnailed)
        } else if (e.error == DropboxServerException._507_INSUFFICIENT_STORAGE) {
            // user is over quota
        } else {
            // Something else
        }
        // This gets the Dropbox error, translated into the user's language
        mErrorMsg = e.body.userError;
        if (mErrorMsg == null) {
            mErrorMsg = e.body.error;
        }
    } catch (DropboxIOException e) {
        // Happens all the time, probably want to retry automatically.
        mErrorMsg = "Network error.  Try again.";
    } catch (DropboxParseException e) {
        // Probably due to Dropbox server restarting, should retry
        mErrorMsg = "Dropbox error.  Try again.";
    } catch (DropboxException e) {
        // Unknown error
        mErrorMsg = "Unknown error.  Try again.";
    } catch (FileNotFoundException e) {
    }
    return false;
}

@Override
protected void onProgressUpdate(Long... progress) {
    int percent = (int)(100.0*(double)progress[0]/mFileLen + 0.5);
    mDialog.setProgress(percent);
}

@Override
protected void onPostExecute(Boolean result) {
    mDialog.dismiss();
    if (result) {
        showToast("File successfully uploaded");
    } else {
        showToast(mErrorMsg);
    }
}
person Martin    schedule 01.08.2012

Я также пишу подобное приложение, используя achartengine и AsynkTask. Вы должны управлять bluetooth в doInBackground, и когда вы получаете новые данные, вызовите publishProgress, чтобы обновить ваш пользовательский интерфейс (TextView и Achartengine) в методе onProgressUpdate. doInBackground НИКОГДА не должен обновлять пользовательский интерфейс, странно, что он действительно работает для вас! Если вы рисуете данные в реальном времени с «низким» обновлением, это сработает. Если нет, я бы порекомендовал реализовать часть Bluetooth как услугу и передавать ваши данные в действие, которое управляет и обновляет пользовательский интерфейс. Если вы получаете много данных, вы обнаружите, что отправка данных через широковещательные рассылки ограничивает вашу пропускную способность, поскольку вам нужно сделать класс данных Parcelable, а это ужасно медленно, и вы скоро столкнетесь с ограничениями в архитектуре связывания Android, которая используется на «Localmanager.sendbroadcast». ". Наиболее эффективным способом связи со службой, который я нашел, было использование обработчиков.

Если вы планируете быстро строить графики в реальном времени с помощью achartengine, вы должны сначала проверить один из моих вопросов здесь о проблемах, которые вы найдете позже: Готов ли achartengine к построению графиков в реальном времени?

person Usul    schedule 09.01.2013