Статьи

Реальные приложения для Android, использующие механизм персистентности db4o (часть 2)

Это вторая статья из серии статей, целью которой было показать разработчикам, как db4o (база данных с открытым исходным кодом, использующая современные объектно-ориентированные языки, системы и образ мышления) используется в нескольких проектах Android, чтобы избежать всех ловушек и неприятностей, связанных с объектами. реляционное отображение, получая выгоду от краткого и прямого пути развития доменной модели, которая, в конце концов, приводит к более быстрым и простым обновлениям для пользователей.

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

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

В первой статье этой серии мы рассмотрели некоторые аспекты проекта DyCaPo , системы, которая облегчает возможность водителям и пассажирам проводить одноразовые матчи во время поездки близко к времени отправления. На этот раз мы рассмотрим QuiteSleep , проект Android от César Valiente, который работает на db4o и имеет открытый исходный код и доступен на рынке Android.

Основной задачей QuiteSleep является отключение вызовов от контактов, которые ранее были заблокированы в течение определенного периода времени. После завершения отклоненного вызова QuiteSleep вернет телефон в нормальное рабочее состояние (восстановив предыдущие настройки громкости и вибрации). Кроме того, приложение позволяет отправлять уведомления заблокированным абонентам по электронной почте или SMS.

По словам Сезара, добавить db4o в проект было просто: вы просто добавляете jar-файл db4o, как любая другая внешняя библиотека в Eclipse (даже для проекта Android). Как только это будет сделано, полная функциональность базы данных db4o станет доступной для приложения Android. Во время разработки в проекте использовалась версия db4o db4o-7.12.132.14217 . Когда вы загрузите db4o для Java, вы увидите, что существует довольно много jar-файлов. Это потому, что (начиная с версии 7.7) db4o был разделен, так что вы можете добавить именно то, что вам нужно в вашем проекте и уменьшить занимаемую библиотеку площадь. Если вы выберете файл jar, имя которого заканчивается на «-all.jar», вы добавите версию, в которой есть все (другие файлы jar, связанные с db4o, не понадобятся).

В QuiteSleep db4o работает в режиме встроенного клиента / сервера, который является своего рода гибридом между чисто встроенной опцией и сетевым C / S. Когда db4o выступает в качестве встроенного сервера, все коммуникации являются внутренними и быстрыми (сетевое взаимодействие не происходит), но, в отличие от простого встроенного режима, многие локальные клиенты не могут одновременно подключиться к базе данных (т. Е. Разные клиенты могут запускаться по-разному). транзакции на БД). В этих приложениях сервер представлен как одиночный, и к нему подключается один или несколько клиентов.

Сервер базы данных

Сервер базы данных настроен, выполнив следующие действия:

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

	/**
* This object is for get only one Singleton object and create ONLY in of
* this class mode.
*/
private static ServerDDBB SINGLETON = null;

/**
* * This object is the server DDBB file properly said
*/
private static ObjectServer server = null;

private ServerDDBB() {
try {
if (QSLog.DEBUG_I)
QSLog.i(CLASS_NAME, "Before to open the DDBB");
ServerConfiguration configuration = Db4oClientServer
.newServerConfiguration();
configuration.common().allowVersionUpdates(true);
configuration.common().activationDepth(DEEP);
server = Db4oClientServer.openServer(configuration,
getDDBBFile(DDBB_FILE), 0);
if (server == null)
if (QSLog.DEBUG_W)
QSLog.w(CLASS_NAME, "Server Null!!!!");
} catch (Exception e) {
if (QSLog.DEBUG_E)
QSLog.e(CLASS_NAME, ExceptionUtils.exceptionTraceToString(e
.toString(), e.getStackTrace()));
}
}

2. Единственный экземпляр сервера базы данных создается здесь при получении первого запроса клиента базы данных. После создания экземпляр сервера доступен как одиночный и будет обрабатывать новые клиентские запросы в соответствии с потребностями приложения.

	/**
* Create the server instance if the server doesn’t create before
*/
private synchronized static void createInstance() {
if (server == null)
SINGLETON = new ServerDDBB();
}

/**
* Get the server DDBB instance, if the singleton object doesn’t crea
* before, we create for use it. * @return The server object
*
* @see ObjectsServer
*/
public static ObjectServer getServer() {
if (SINGLETON == null)
createInstance();
return server;
}


Клиент базы данных

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

	protected ObjectContainer clientDDBB;
protected Selects selects;
protected Inserts inserts;
protected Updates updates;
protected Deletes deletes;

/**
* This function return the ObjectContainer used like as clientDDBB for
* the application.
* @return The ObjectContainer used like as clientDDBB
* @see ObjectContainer
*/
public ObjectContainer getObjectContainer() {
return clientDDBB;
}

/**
* Empty constructor, get de client and use it.
*/
public ClientDDBB() {
try {
synchronized (SEMAPHORE) {
clientDDBB = ServerDDBB.getServer().openClient();
selects = new Selects(clientDDBB);
inserts = new Inserts(clientDDBB);
updates = new Updates(clientDDBB);
deletes = new Deletes(clientDDBB);
}
} catch (Exception e) {
if (QSLog.DEBUG_E)
QSLog.e(CLASS_NAME, ExceptionUtils.exceptionTraceToString(e
.toString(), e.getStackTrace()));
}
}

Как видно из кода, клиент БД работает с ObjectContainer, полученным путем вызова сервера БД (в статическом классе ServerDDBB). Получив контейнер, мы можем использовать его для создания сопутствующих объектов, которые выполняют такие операции, как операции выбора, вставки, обновления и удаления, которые позволяют нам работать с БД.

 В качестве дополнения мы также можем создавать экземпляры клиентов БД, передавая внешний ObjectContainer или даже изменяя сервер БД по умолчанию. Эти альтернативы не используются в приложении, но включены как полезности для разработчиков.

Как только мы получим клиент БД, они несут ответственность за выполнение всех общих операций с базой данных (выбор, вставка, обновление и удаление). Давайте посмотрим, как создается объект, который обрабатывает «выбор», и как отправить запрос для получения всех контактов в базе данных. Процедура создания объектов, которые обрабатывают вставки, обновления и удаления, идентична.

	private ObjectContainer db;

/**
* Constructor
* @param db
*/
public Selects(ObjectContainer db) {
this.db = db;
}

/**
* Select all Contact objects from the DDBB and return it. * @return All
* Contact objects from the DDBB
*
* @see List
*/
public List selectAllContacts() {
try {
synchronized (SEMAPHORE) {
Query query = db.query();
query.constrain(Contact.class);
// Ordered by name (first A....last Z)
query.descend(DDBBValues.CONTACT_NAME).orderAscending();
List contactList = query.execute();
return contactList;
}
} catch (Exception e) {
if (QSLog.DEBUG_E)
QSLog.e(CLASS_NAME, ExceptionUtils.exceptionTraceToString(e
.toString(), e.getStackTrace()));
return null;
}
}

Как видите, мы передаем ObjectContainer в клиенте БД конструктору классов, чтобы мы могли строить запросы и получать необходимые данные. запросы, используемые в коде, называются запросами SODA , один из трех типов запросов, которые в настоящее время поддерживает db4o.

Жизненный цикл приложения

В течение жизненного цикла приложения есть несколько точек доступа к данным (запросы, вставки, обновления и т. Д.). Давайте посмотрим, как db4o используется QuiteSleep во время нормальной работы.

Управление контактами

Когда QuiteSleep загружается впервые, он выполняет, пожалуй, самую важную операцию с данными в приложении: он синхронизирует все контакты с db4o.

/**
* Sync contacts from the ddbb SQLite to the DB4O ddbb
*/
private void syncContacts() {

try {

if (context != null) {

// int insertContact = 0;
ClientDDBB clientDDBB = new ClientDDBB();

// get all contacts from de ddbb
Cursor contactCursor = context.getContentResolver().query(
ContactsContract.Contacts.CONTENT_URI, null, null,
null, null);

if (QSLog.DEBUG_D)
QSLog.d(CLASS_NAME, "Num contacts: "
+ contactCursor.getCount());

// Whule startCursor has content
while (contactCursor.moveToNext()) {

String contactName = contactCursor
.getString(contactCursor
.getColumnIndex(ContactsContract.Data.DISPLAY_NAME));

String contactId = contactCursor.getString(contactCursor
.getColumnIndex(ContactsContract.Contacts._ID));

String hasPhone = contactCursor
.getString(contactCursor
.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER));

// If the used contact has at least one phone number, we
// insert it in the db4o ddbb, else not insert.
if (hasPhone.equals("1")) {

// Create the db4o contact object.
Contact contact = new Contact(contactId, contactName);

// insert the contact object
clientDDBB.getInserts().insertContact(contact);

insertContact++;

// where clausule
String where = ContactsContract.Data.CONTACT_ID
+ " = "
+ contactId
+ " AND "
+ ContactsContract.Data.MIMETYPE
+ " = '"
+ ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE
+ "'";

Cursor phonesCursor = context.getContentResolver()
.query(ContactsContract.Data.CONTENT_URI, null,
where, null, null);

if (QSLog.DEBUG_D)
QSLog.d(CLASS_NAME, "count: "
+ phonesCursor.getCount());

List phonesList = new ArrayList(
phonesCursor.getCount());

// While the phonesCursor has content
while (phonesCursor.moveToNext()) {

String phoneNumber = phonesCursor
.getString(phonesCursor
.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));

phonesList.add(phoneNumber);

if (QSLog.DEBUG_D)
QSLog.d(CLASS_NAME, "phone: " + phoneNumber);
}
// close the phones cursor
phonesCursor.close();

// create the phones object asociated to contacts
createPhoneObjects(contact, phonesList, clientDDBB);

// Get all mail from the contact
Cursor mailCursor = context
.getContentResolver()
.query(
ContactsContract.CommonDataKinds.Email.CONTENT_URI,
null,
ContactsContract.CommonDataKinds.Email.CONTACT_ID
+ " = " + contactId, null, null);

if (mailCursor.getCount() > 0) {

List mailsList = new ArrayList(
mailCursor.getColumnCount());
// While the emailsCursor has content
while (mailCursor.moveToNext()) {

// This would allow you get several email
// addresses
String emailAddress = mailCursor
.getString(mailCursor
.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA));

if (QSLog.DEBUG_D)
QSLog.d(CLASS_NAME, "email: "
+ emailAddress);
mailsList.add(emailAddress);
}

// Create the mail objects asociated to every
// contact
createMailObjects(contact, mailsList, clientDDBB);
}
// close the email cursor
mailCursor.close();

}// end hasPhone

// create the Schedule object if not have been created
// previously
createSchedule(clientDDBB);

// Commit the transaction
clientDDBB.commit();

}// end contact cursor
}// end context!=null
else
// return -1;
insertContact = -1;

} catch (Exception e) {
if (QSLog.DEBUG_E)
QSLog.e(CLASS_NAME, ExceptionUtils.exceptionTraceToString(e
.toString(), e.getStackTrace()));
insertContact = -1;
}
}

Приложение извлекает через поставщика контента список контактов пользователя и отфильтровывает все контакты, у которых нет связанного номера телефона (они бесполезны для QuiteSleep). Контакты с номерами телефонов затем сохраняются в db4o. После этого db4o становится единственной точкой доступа к данным для приложения.

Эта операция синхронизации выполняется в фоновом режиме с помощью потоков Java. Диалоги Android используются для уведомления пользователя о том, что процесс происходит, и в то же время предотвращают взаимодействие с пользователем (этот процесс крайне важен для правильной работы приложения).

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

Контакт

Этот класс представляет типичный контакт (с полным именем и уникальным идентификатором контакта). Он также имеет логический атрибут, который показывает, находится ли этот вызывающий объект в заблокированном состоянии в приложении.

Телефон

Этот класс представляет номер телефона, связанный с контактом. Он имеет прямую ссылку на контактный объект, описанный выше. Он также предоставляет логический атрибут, указывающий, включен ли этот номер телефона для получения SMS-уведомлений приложением.

Почта

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

Этими объектами манипулируют во время таких операций, как добавление заблокированных контактов, редактирование контактной информации и разблокировка контактов.

Во время операции блокировки контакта в базе данных создается новый тип объекта:

Banned

Этот объект создается, когда мы помечаем контакт как заблокированный. Внутренне эта операция связывает объект Contact с объектом Banned и объектом Schedule (последний указывает, когда именно контакт должен быть заблокирован, как описано ниже).

Каждый раз, когда мы удаляем состояние блокировки контакта, запрещенный объект удаляется из базы данных, а объекты «Контакт» и «Расписание» остаются нетронутыми (
каскадное удаление не используется).

public int deleteAllBanned() {

try {

Query query = db.query();
query.constrain(Banned.class);
List bannedList = query.execute();
int deletes = 0;

if (bannedList != null)
for (int i = 0; i < bannedList.size(); i++) {
Banned banned = bannedList.get(i);
db.delete(banned);
deletes++;
}

return deletes;

} catch (Exception e) {
if (QSLog.DEBUG_E)
QSLog.e(CLASS_NAME, ExceptionUtils.exceptionTraceToString(e
.toString(), e.getStackTrace()));
return -1;
}
}

Управление временем

Когда приложение создает все объекты, связанные с контактами (объекты типов «Контакт», «Телефон», «Почта» и т. Д.), И устанавливает для них заблокированное состояние, следующим шагом является определение активного периода времени для заблокированных контактов.

Для поддержки этого приложения используются 3 параметра:

  1. startTime и startFormatTime (время применения состояния блокировки) с типами Date и String соответственно.
  2. endTime y endFormatTime (время для удаления состояния блокировки), также с типами Date и String соответственно.
  3. понедельник, вторник, среда, четверг, пятница, суббота и воскресенье (дни недели), все логические значения.

When any of these parameters is defined by the user, a Schedule object is created based on this information and saved in the database. The Schedule object is created only once and is never deleted (only updated when the schedule details are changed by the user).

As we mentioned before Scheduled objects are referenced by Banned objects.

Service Management

Once all the contact related data together with blocking information has been processed the app configures the notifications service (both SMS and e-mail) and other minor services. when this is the dome the main service loop is started and the app is ready to be used.

At this point the Settings object is created and stored in the database with parameters such as:

  • quiteSleepServiceState: true when QuiteSleep is up and running and has been activated
  • E-mail and SMS configuration attributes: such as recipient, body, subject, etc together with a boolean value that determines whether each type of notification is active

Event History

In this phase the user can access information about the recorded activities while the application was running. The available information consists of the contact name, phone number of the originating call, call date and time, number of messages (SMS, e-mail) that were sent.

In order to support this kind of logging a CallLog object is made persistent in db4o for every blocked call. A CallLog object references a specific contact and phone number combined with the specific date and time of the blocked call.

 

	public void silentIncomingCall() {

try {

/*
* Put the mobile phone in silent mode (sound+vibration) Here i put
* this for silent all incoming call for salomonic decision that
* silent all calls for later if the contact is banned let this
* silent mode and if the contact isn't banned put in normal mode. I
* use this because sometimes a ring second sound when an incoming
* call.
*/
// putRingerModeSilent();

ClientDDBB clientDDBB = new ClientDDBB();

String phoneNumberWhithoutDashes = TokenizerUtils
.tokenizerPhoneNumber(incomingCallNumber, null);

Contact contactBanned = clientDDBB.getSelects()
.selectBannedContactForPhoneNumber(
phoneNumberWhithoutDashes);

// If the contact is in the banned list
if (contactBanned != null) {

if (QSLog.DEBUG_D)
QSLog.d(CLASS_NAME, "Contact: "
+ contactBanned.getContactName() + "\t isBanned: "
+ contactBanned.isBanned());

// create the CallLog object for log calls.
CallLog callLog = new CallLog();

// check if the call is in the interval time
boolean isInInterval = checkSchedule(callLog, clientDDBB);

if (isInInterval) {

// Put the mobile phone in silent mode (sound+vibration)
putRingerModeSilent();

/*
* Check if the mail service is running, if it is true
* create a SendMail object for try to send one or more
* email to the contact with the incoming number
*/
sendMail(incomingCallNumber, callLog, clientDDBB);

/*
* Check if the sms service is running, if it is true create
* a SendSMS object for try to send a SMS to the contact
* with the incoming number
*/
sendSMS(incomingCallNumber, callLog, clientDDBB);

// get the nomOrder for the new CallLog
int numOrder = clientDDBB.getSelects().countCallLog();
if (QSLog.DEBUG_D)
QSLog.d(CLASS_NAME, "CallLog numOrder: " + numOrder);

// Set the parameters and save it
callLog.setPhoneNumber(phoneNumberWhithoutDashes);
callLog.setContact(contactBanned);
callLog.setNumOrder(numOrder + 1);

clientDDBB.getInserts().insertCallLog(callLog);
clientDDBB.commit();
clientDDBB.close();

}
// If the call isn't in the interval time
else {
if (QSLog.DEBUG_D)
QSLog.d(CLASS_NAME, "No est√° en el intervalo");
// putRingerModeNormal();
clientDDBB.close();
}
}
// If the incoming call number isn't of the any banned contact
else {
if (QSLog.DEBUG_D)
QSLog.d(CLASS_NAME, "ContactBanned == NULL!!!!");
// putRingerModeNormal();
clientDDBB.close();
}

} catch (Exception e) {
if (QSLog.DEBUG_E)
QSLog.e(CLASS_NAME, ExceptionUtils.exceptionTraceToString(e
.toString(), e.getStackTrace()));
}
}

 

Application Behavior

Now let’s take a look at how the application behaves and how the described objects work together and are made persistent when there’s an incoming call.

Incoming Call

When there’s an incoming call Android throws a «broadcast intent», a signal that alerts apps the call should be handled. QuiteSleep implements a «broadcast receiver» that listens to this broadcast message. Once the message is intercepted QuiteSleep performs a series of checks such as whether the phone is already muted and if the app itself is in active state.

If these checks are passed a background process is launched via an Android service that checks whether the incoming call number belongs to a blocked contact. This is when the CallLog object is created and stored in the database to later have a record for the call.

If the call was effectively silenced according to the specified day and time frame the independent threads are created to handle notifications (SMS or e-mail). This is performed before making the CallLog object persistent on db4o to keep a record of sent notifications.

Call Termination

Once a call has been silienced a hang-up event (another broadcast intent) will follow which is intercepted by the app via a different broadcast receiver. The wrap-up actions are then the following:

  • Check whether the phone is in mute mode via the app (if it’s not this is an unrelated hang up event that shouldn’t be handled)
  • Check whether the QuiteSleep service is active
  • Check whether a hang-up is already being processed by the app

If all the checks above are positive a final background thread handles restoring the phone to it’s original state to allow for the normal handling of incoming calls.

Conclusion

In QuiteSleep db4o is up to the task of dealing with all persistence operations in the app and makes persistence related code simpler.

In César Valiente’s own words:

db4o is an excellent option for Android apps that don’t need to use the complexity of relational databases, with a few lines of code you can create a complete data model for your app, using queries, inserts, deletes, updates and index configurations…  and more importantly, the class model in your app is the data model in your object database!!

 Still not using an object database? What are you waiting for? =)