Работа с датчиками в Android, или сервис для записи показаний с акселерометра

в 7:56, , рубрики: Песочница, Разработка под android, метки:

Введение

Год назад, на хабре публиковалась статья «Собираем показания датчиков с 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_labelTextView.
В данном методе происходит запуск сервиса, инициализация BroadcastReciever и создание фильтра намерений (класс IntentFilter).

Стоит отметить, что в моем коде также предусмотрены методы для остановки процесса записи. Исходный код и *.apk можно скачать в репозитарии. Ссылка ниже.

Вместо заключения

В заключение всего вышесказанного приведу ссылки на ресурсы, которые помогли мне освоить азы разработки под Android:

Исходный код и *.apk выложил на репозитарий

Автор: lex_t

Источник

  1. Vladimir:

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

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js