Введение
Год назад, на хабре публиковалась статья «Собираем показания датчиков с Android смартфона», где рассматривался способ получения данных с акселерометра (кстати говоря, есть пост более старый, в котором рассказывается все то же самое). Недавно передо мной была поставлена похожая задача. Необходимо было создать приложение (решил назвать его «Sensor Logger»), записывающее показания с акселерометра в файл в фоновом режиме. В данной статье постараюсь показать, как можно использовать сервисы и намерения, как работать с текстовыми файлами, а также каким образом отправлять данные из сервиса в Activity.
Рассказывать о снятии показаний с датчиков, классах
SensorEventListener и SensorManager не вижу особого смысла, т.к. привел выше две статьи, в которых подробно об этом говорится.Предполагается, что для читателя данный проект будет являться учебным, поскольку для меня он именно таким и являлся. До текущего момента я никогда не писал на Java и не работал с Android SDK.
Как оказалось, не все так просто
В приведенных выше статьях рассматривались примеры, когда показания датчиков снимались в главном Activity. Они оказались лишь отчасти применимыми к моей задаче. Ибо есть ложка дегтя: жизненный цикл Activity. Очевидно, что не удастся записывать в файл данные в фоновом режиме, если этим будет заниматься класс Activity, поэтому, прочитав ряд статей, было решено написать сервис, который и будет заниматься записью показаний датчиков.
Про сервисы в Android
В Android существует класс Service, который позволяет выполнять задачи приложения в фоновом режиме (стоит отметить, что сервис и Activity работают в одном потоке) в то время, когда телефон заблокирован, или приложение свернуто. Сервис не требует наличия UI, но он может передавать информацию в Activity (например для отображения) или другие сервисы.
Запуск сервиса, получение показаний с акселерометра, запись в файл
Рассмотрим процесс запуска сервиса (метод
onStartCommand)@SuppressLint("DefaultLocale") public class SensorLoggerService extends Service implements SensorEventListener { private SensorManager sm; private BufferedOutputStream outStream; private OutputStreamWriter sWriter; private String appDirString; private Calendar cal; private Long startTime; private String date_format = "yyyy-MM-dd_HH-mm-ss"; private Boolean first = true; @Override public int onStartCommand(Intent intent, int flags, int startId) { sm = (SensorManager) getSystemService(SENSOR_SERVICE); appDirString = intent.getStringExtra(MainActivity.APP_DIR); rec_start(); return super.onStartCommand(intent, flags, startId); } public void rec_start() { try { String filename = currentDateToString()+".txt"; File appDir = new File(appDirString); if(!appDir.exists()) appDir.mkdirs(); File file = new File(appDir, filename); outStream = new BufferedOutputStream(new FileOutputStream(file)); Toast.makeText(getApplicationContext(), appDirString+filename,Toast.LENGTH_SHORT).show(); sWriter = new OutputStreamWriter(outStream); Toast.makeText(getApplicationContext(), getString(R.string.record_started),Toast.LENGTH_SHORT).show(); sm.registerListener(this,sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), sm.SENSOR_DELAY_NORMAL); } catch (Throwable t1) { Toast.makeText(getApplicationContext(), "Exception: " + t1.toString(), Toast.LENGTH_LONG).show(); } }
При запуске сервиса происходит инициализация объекта типа
SensorManager (переменная sm). Данный класс в Android предназначен для работы с датчиками (об этом можно почитать в приведенных статьях во введении, а также в документации). На следующем этапе сервис получает имя директории (с помощью Intent — намерения), в которую необходимо записывать файлы с показаниями (в случае не существования таковой, программа создаст необходимую папку) и открывает для записи файл, именем которого являются дата и время запуска сервиса. За генерацию имени файла отвечает метод
currentDateToString, возвращающий дату и время в виде строки.@SuppressLint("SimpleDateFormat") public String currentDateToString() { SimpleDateFormat sdf = new SimpleDateFormat(date_format); cal = Calendar.getInstance(); return sdf.format(cal.getTime()); }
После открытия файла начинаем снимать показания с датчиков.
При изменении состояния какого-либо из датчиков вызывается метод
onSensorChanged, внутри которого идет проверка на то, на каком датчике произошло событие, и если это акселерометр, то показания, которые пришли, записываются в файл и оправляются в Activity приложения (создается новое намерение и вызывается функция sendBroadcast):@Override public void onSensorChanged(SensorEvent event) { if(event.sensor.getType()==Sensor.TYPE_ACCELEROMETER) writeData(event.values); } @SuppressLint("DefaultLocale") public void writeData(float values[]) { try{ cal = Calendar.getInstance(); if(first) { startTime = cal.getTimeInMillis(); first = false; } Long currentTime = cal.getTimeInMillis()-startTime; String data = Long.toString(currentTime)+String.format(" %f %f %f\n", values[0],values[1],values[2]); sWriter.write(data); Intent intent = new Intent(MainActivity.BROADCAST_ACTION); intent.putExtra(MainActivity.VAL1, values[0]); intent.putExtra(MainActivity.VAL2, values[1]); intent.putExtra(MainActivity.VAL3, values[2]); sendBroadcast(intent); } catch (Throwable t1) { Toast.makeText(getApplicationContext(), "Exception: " + t1.toString(), Toast.LENGTH_SHORT) .show(); } }
В Activity прием сообщений от сервиса реализован при помощи класса BroadcastReciever, который принимает намерения, отправленные с помощью функции
sendBroadcast. Рассмотрим метод rec_start из класса Activity:public final static String BROADCAST_ACTION = "SensorLoggerServiceRecieve"; public void rec_start() { Intent startServiceIntent = new Intent(this,SensorLoggerService.class) .putExtra(APP_DIR, appDirString); startService(startServiceIntent); rec=true; button_rec_off.setEnabled(true); button_rec_on.setEnabled(false); intentFlt = new IntentFilter(BROADCAST_ACTION); br = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { float val1 = intent.getFloatExtra(VAL1, 0); float val2 = intent.getFloatExtra(VAL2, 0); float val3 = intent.getFloatExtra(VAL3, 0); x_label.setText("X: "+String.valueOf(val1)); y_label.setText("Y: "+String.valueOf(val2)); z_label.setText("Z: "+String.valueOf(val3)); } }; registerReceiver(br, intentFlt); }
Объекты
button_rec_off, button_rec_on являются объектами класса Button, а x_label, y_label и z_label — TextView.В данном методе происходит запуск сервиса, инициализация BroadcastReciever и создание фильтра намерений (класс IntentFilter).
Стоит отметить, что в моем коде также предусмотрены методы для остановки процесса записи. Исходный код и *.apk можно скачать в репозитарии. Ссылка ниже.
Вместо заключения
В заключение всего вышесказанного приведу ссылки на ресурсы, которые помогли мне освоить азы разработки под Android:
- Проект startandroid.ru — огромное количество примеров кода с объяснениями
- Книга Рето Майера — в книге все объясняется доступно и понятно
- Документация по ServiceManager
Исходный код и *.apk выложил на репозитарий
