Статьи

Android Barometer Logger: запись данных датчика

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

В этом продолжении последнего урока, Android Barometer Logger: сбор данных датчика , мы будем реализовывать код для регулярного считывания данных барометрического датчика и его постоянного хранения. Вы научитесь читать данные датчика и как планировать повторяющиеся события, чтобы приложение и его сервис не оставались запущенными.


В этом руководстве предполагается, что у вас есть базовые знания об Android и Java, что у вас установлены и работают все инструменты Android, и что вы можете загружать и тестировать приложения на устройстве Android. Мы будем использовать аппаратный датчик барометра в этом приложении. Если у вас нет устройства с этим датчиком, вы можете заменить его другим аналогичным датчиком для целей тестирования, но результаты или полезность могут быть не эквивалентны.

Возможно, вы видели поставщиков баз данных и контента в других руководствах; этот представляет быстрый, «быстрый и грязный» вариант для любителя или энтузиаста, желающего быстро записать некоторые данные в структурированную базу данных и затем легко отобразить их, все для Android.


Самый быстрый способ создать базу данных на Android с использованием SQLite – это использовать класс SQLiteOpenHelper. Записи данных, которые мы хотим записать, очень просты по структуре: временная метка и значение, представляющее давление в то время.


Схема для этой базы данных проста. Помимо двух обязательных столбцов данных для метки времени и значения давления, мы также создадим уникальный идентификатор, который будет полезен, если мы когда-либо расширяем схему или нам приходится работать с данными построчно. Вот определение схемы для класса SQLiteOpenHelper:

1
2
3
4
5
6
7
8
9
static final String TABLE_NAME = “table_sensor_data”;
static final String COL_ID = “_id”;
static final String COL_VALUE = “value”;
static final String COL_TIMESTAMP = “timestamp”;
 
private static final String DB_SCHEMA = “CREATE TABLE ” + TABLE_NAME + “(“
        + COL_ID + ” INTEGER PRIMARY KEY AUTOINCREMENT, ” + COL_TIMESTAMP
        + ” INTEGER NOT NULL, ” + COL_VALUE
        + ” REAL ” + “);”;

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


При отсутствии схемы, остальная часть SQLiteOpenHelperclass тривиальна:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class SensorDataHelper extends SQLiteOpenHelper {
    private static final String DEBUG_TAG = “SensorDataHelper”;
 
    private static final String DATABASE_NAME = “sensor_data.db”;
    private static final int DATABASE_VERSION = 1;
 
    // schema
    // ….
 
    SensorDataHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }
 
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(DB_SCHEMA);
    }
 
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        Log.w(DEBUG_TAG,
                “Warning: Dropping all tables; data migration not supported”);
        db.execSQL(“DROP TABLE IF EXISTS ” + TABLE_NAME);
        onCreate(db);
    }
 
}

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


Контент-провайдеры – это способ Android предоставлять структурированные данные с помощью курсоров, защищая при этом базовые данные. Данные приложения запрашиваются через поставщика контента, а не напрямую из базы данных приложения. Поскольку эта реализация использует SQLiteOpenHelper, задача создания провайдера контента проста.


В то время как полнофункциональный поставщик контента, который обрабатывает все операции с данными, такие как запросы, вставки, обновления и удаления, требует чуть больше работы, чем частичный, для целей регистрации все, что нам действительно нужно для реализации ввода, – это метод insert (). Для получения данных требуется реализация метода query (), который также использует вспомогательный класс URIMatcher. Вы всегда можете вернуться и реализовать другие функции поставщика контента позже, если ваши требования изменятся. Большая часть регистрации данных, тем не менее, требует полных, неизмененных записей.


Давайте реализуем контент-провайдера, который работает с нашими планами регистрации датчиков. Во-первых, настройте некоторые полезные константы и URIMatcher, чтобы помочь нам сопоставить URI с типом данных, которые они представляют:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public class SensorDataProvider extends ContentProvider {
    private static final String DEBUG_TAG = “TutListProvider”;
    private SensorDataHelper sensorDataHelper;
 
    public static final int SENSORDATA = 100;
    public static final int SENSORDATA_ID = 110;
 
     
    private static final String AUTHORITY = “com.mamlambo.barologger.SensorDataProvider”;
 
    private static final String BASE_PATH = “sensordata”;
 
     
    public static final Uri CONTENT_URI = Uri.parse(“content://” + AUTHORITY
            + “/” + BASE_PATH);
 
    public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE
            + “/barolog”;
    public static final String CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE
            + “/barolog”;
 
     
    private static final UriMatcher sURIMatcher = new UriMatcher(
            UriMatcher.NO_MATCH);
    static {
        sURIMatcher.addURI(AUTHORITY, BASE_PATH, SENSORDATA);
        sURIMatcher.addURI(AUTHORITY, BASE_PATH + “/#”, SENSORDATA_ID);
    }
//…
}
[sourcecode]
 
A consumer of this content provider will refer to the CONTENT_URI value.
 
<hr>
<h2><span>Step 3:
 
The insert() and query() methods are implemented using a simple mapping to helper methods from the SQLite database objects.
 
[sourcecode language=”java”]
@Override
public Uri insert(Uri uri, ContentValues values) {
    int uriType = sURIMatcher.match(uri);
    if (uriType != SENSORDATA) {
        throw new IllegalArgumentException(“Invalid URI for insert”);
    }
    SQLiteDatabase sqlDB = getDatabase(true);
    try {
        long newID = sqlDB.insertOrThrow(SensorDataHelper.TABLE_NAME, null,
                values);
        if (newID > 0) {
            Uri newUri = ContentUris.withAppendedId(uri, newID);
            getContext().getContentResolver().notifyChange(uri, null);
            return newUri;
        } else {
            throw new SQLException(“Failed to insert row into ” + uri);
        }
    } catch (SQLiteConstraintException e) {
        Log.w(DEBUG_TAG, “Warning: Ignoring constraint failure.”);
    }
    return null;
}

Большая часть того, что вы видите здесь – обработка ошибок. Основной вызов insertOrThrow () использует данные непосредственно из параметров в методе insert ().

А теперь метод query ():

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Override
public Cursor query(Uri uri, String[] projection, String selection,
        String[] selectionArgs, String sortOrder) {
 
    SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
    queryBuilder.setTables(SensorDataHelper.TABLE_NAME);
 
    int uriType = sURIMatcher.match(uri);
    switch (uriType) {
    case SENSORDATA_ID:
        queryBuilder.appendWhere(SensorDataHelper.COL_ID + “=”
                + uri.getLastPathSegment());
        break;
    case SENSORDATA:
        // no filter
        break;
    default:
        throw new IllegalArgumentException(“Unknown URI”);
    }
 
    Cursor cursor = queryBuilder.query(getDatabase(false), projection,
            selection, selectionArgs, null, null, sortOrder);
    cursor.setNotificationUri(getContext().getContentResolver(), uri);
    return cursor;
 
}

Единственное изменение в методе query () – добавление идентификатора в предложение where, если URI содержит путь к определенной записи. В противном случае параметры метода query () также передаются как есть вызову метода query (). Очень удобно.

Мы использовали вспомогательный метод для получения экземпляра объекта SQLiteDatabase. Это так, мы создаем объект SQLiteOpenHelper только тогда, когда это абсолютно необходимо. Это рекомендуется для улучшения общей производительности.

1
2
3
4
5
6
7
8
private SQLiteDatabase getDatabase(boolean writable) {
    if (sensorDataHelper == null) {
        sensorDataHelper = new SensorDataHelper(getContext());
    }
 
    return (writable ? sensorDataHelper.getWritableDatabase()
            : sensorDataHelper.getReadableDatabase());
}

Наконец, поставщик контента должен быть зарегистрирован как поставщик в файле манифеста приложения в теге <application>, например:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
<provider
    android:name=”com.mamlambo.barologger.SensorDataProvider”
    android:authorities=”com.mamlambo.barologger.SensorDataProvider” >
</provider>
[sourcecode]
 
The name field is the class name of the content provider and the authority should match the authority used in your content provider.
 
<hr>
<h2>Part C: Inserting Data</h2>
 
Back in the AsyncTask of the Service we implemented in the last tutorial, we now need to actually insert the data into the content provider as new sensor readings are taken.
 
[sourcecode language=”java”]
ContentValues values = new ContentValues();
values.put(SensorDataHelper.COL_VALUE, value);
values.put(SensorDataHelper.COL_TIMESTAMP, timestamp);
getContentResolver().insert(SensorDataProvider.CONTENT_URI,
                        values);

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


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

В качестве проблемы, реализовать этот код самостоятельно. Завершите реализацию поставщика контента, реализовав методы delete () и update (). Они нужны для вашего регистратора? Можно ли их исключить для обеспечения целостности данных?


Разработчики мобильных приложений Лорен Дарси и Шейн Кондер являются соавторами нескольких книг по разработке Android: углубленная книга по программированию под названием « Разработка беспроводных приложений для Android» (в третьем выпуске – в виде двухтомника) и « Sams Teach Yourself» – разработка приложений для Android за 24 часа . Когда они не пишут, они тратят свое время на разработку мобильного программного обеспечения в своей компании и оказание консультационных услуг. С ними можно связаться по электронной почте androidwirelessdev+mt@gmail.com , через их блог на androidbook.blogspot.com и в Twitter @androidwireless .

Купить разработку беспроводных приложений для Android, 3-е издание, том 1 Купить Sam's Teach Yourself для Android разработки приложений в течение 24 часов, 2-е издание Код Мамламбо в Код-Каньоне