Клиент-серверное взаимодействие под Android
в деталях
Кирилл Зотин
Android dev lead
23.04.2011
Введение
• Специфика мобильных приложений:
Document-ориентированая модель
VS
Database-ориентированная модель
• Преимущества родных приложений над мобильными версиями сайтов
Document-ориентированая модель данных
Database-ориентированная модель данных
Преимущества над мобильными версиями сайтов.
• Тесная интеграция с платформой
• Улучшение поведения платформы
• Фоновая работа
• Более высокая скорость работы
• "родной" пользовательский интерфейс
Типичная реализация
О проблеме
• OS в любой момент может остановить процесс
• Данные не всегда сохранены
Правильная реализация1: Service API
Правильная реализация2: Content Provider API
Network-MVC
Правильная реализация3: Content Provider + SyncAdapter
Tips & Tricks
• Выполняйте запросы к БД не в UI потоке
• Используйте транзакции
• Делайте код хорошо читаемым
• Используйте Gzip сжатие
• Android 3.0: Loaders
• Удобный класс IntentService
Выполняйте запросы к БД не в UI потоке
• Используйте (Notifying)AsyncQueryHandler
public class YourActivity extends Activity implements AsyncQueryListener {
@Override
public void onQueryComplete(int token, Object cookie,
final Cursor cursor) {
startManagingCursor(cursor);
. . .
}
}
Используйте транзакции
@Override
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws OperationApplicationException {
final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
db.beginTransaction();
try {
final int numOperations = operations.size();
final ContentProviderResult[] results = new ContentProviderResult[numOperations];
for (int i = 0; i < numOperations; i++) {
results[i] = operations.get(i).apply(this, results, i);
}
db.setTransactionSuccessful();
return results;
} finally {
db.endTransaction();
}
Группируйте детали запросов в одном месте
Плохо Cursor mCursor = query(true,
DATABASE_TABLE_EMAIL,
new String[] {
KEY_ROWID, KEY_EMAIL_NAME
},
KEY_ROWID + "=" + rowId,
null, null, null, null, null);
if (mCursor.moveToFirst()) {
long id = mCursor.getLong(
mCursor.getColumnIndexOrThrow(KEY_ROWID));
String name = mCursor.getString(
mCursor.getColumnIndexOrThrow(KEY_EMAIL_NAME));
}
Хорошо interface ReviewsQuery { final String[] PROJECTION = new String[] { Reviews._ID, Reviews.TITLE, Reviews.SUBTITLE, Reviews.IMG_URL_SMALL, Reviews.ARTICLE_ID }; int ID = 0; int TITLE = 1; int SUBTITLE = 2; int IMG_URL = 3; int ARTICLE_ID = 4; String DEFAULT_SORT = Reviews.DATE + " desc"; } ... startAsyncQuery(QUERY_REVIEWS_ITEMS, null, Reviews.CONTENT_URI, ReviewsQuery.PROJECTION, selection, null, ReviewsQuery.DEFAULT_SORT); … final String url =
getCursor().getString(ReviewsQuery.IMG_URL);
Используйте сжатие Gzip
HttpGet request = new HttpGet(uri);
request.addHeader("Accept-Encoding", "gzip");
HttpResponse response = getHttpClient().execute(request);
Header contentEncoding = response.getFirstHeader("Content-Encoding");
if (contentEncoding != null &&
contentEncoding.getValue().equalsIgnoreCase("gzip")) {
is = new GZIPInputStream(is);
}
Honeycomb API: CursorLoader
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getLoaderManager().initLoader(0, null, this);
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
final String eventId = getArguments().getString(EXTRA_EVENT_ID);
return new CursorLoader(getActivity(),
Events.CONTENT_URI,
EventQuery.PROJECTION,
EventQuery.SELECTION, new String[] {eventId}, null);
}
Использование IntentService
• Для обработки Intent стартует Service
• Запрос обрабатывается в отдельном потоке
• Параллельные запросы кладутся в одну очередь.
Выводы
• Не обрабатывайте сетевые операции в Activity, используйте Service
• Сохраняйте данные часто
• Все запросы к БД – в отдельный поток
• Минимизируйте трафик сетевых операций
• Используйте локальный кэш данных