Введение
Год назад, на хабре публиковалась статья «Собираем показания датчиков с 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 %fn",
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 выложил на репозитарий
Автор: lex_t
Здравствуйте lex_t !
Статья Ваша понравилась.Я недавно столкнулся с тем.что сделать измерительный прибор из смартфона в лоб -никак не выходит.Нельзя оставлять сенсоры в Activity.Но я не программист (увы). поэтому возможно задам дурацкие вопросы:
1). В тексте два раза определен метод rec_start().Тут описка или я чего-то не понимаю?
2). Листинг всей программы (чтобы Вас не грузить) не удалось скачать-репозитарий кричит err.404
Буду благодарен за ответ, Владимир, Орёл.