Слезаем с велосипедов: опыт использования open source в...
-
Upload
yandex -
Category
Technology
-
view
624 -
download
0
description
Transcript of Слезаем с велосипедов: опыт использования open source в...
Слезаем с велосипедов: Опыт использования open source в Android
Дмитрий Кунин, старший разработчик Яндекс.Музыки для Android
Boilerplate Top 22
Views management
Boilerplate Top 4
Boilerplate Top 4: Views management
5
Button happyButton = (Button)findViewById(R.id.the_button); happyButton.setText("Not ok"); happyButton.setOnClickListener( new View.OnClickListener() {
@Override public void onClick(View view) { handleUserNotOk(); } });
Boilerplate Top 4: Views management
6
Button happyButton = (Button)findViewById(R.id.the_button); happyButton.setText("Not ok"); happyButton.setOnClickListener( new View.OnClickListener() {
@Override public void onClick(View view) { handleUserNotOk(); } }); // из 8 строк полезных только 2, а это 25%
Views management: Butterknife
Boilerplate Top 4
Butterknife
• Декларативная замена findViewById
• Декларативное задание OnClickListener
• Мульти-сеттеры
• можно подружить с proguard
• и все это compile time
• использовать и интегрировать очень просто
Butterknife: http://jakewharton.github.io/butterknife/ 8
Декларативная замена findViewById
9
@InjectView(R.id.title) TextView title; !
… !
ButterKnife.inject(this); title.setText("Либа моей мечты");
Декларативное задание OnClickListener
10
!
@OnClick(R.id.submit) public void submit() { server.sendForm(); }
!
@OnClick(R.id.sayhi) public void sayHi(Button button) { button.setText("Hello!"); }
можно подружить с proguard
11
# Butterknife -dontwarn butterknife.internal.** -keep class butterknife.** { *; } -keep class **$$ViewInjector { *; } !
-keepclasseswithmembernames class * { @butterknife.InjectView <fields>; } -keepclasseswithmembernames class * { @butterknife.OnClick <methods>; } https://github.com/JakeWharton/butterknife/pull/117
и все это compile time
12
public void inject(ExampleActivity activity) { activity.subtitle = (android.widget.TextView) activity.findViewById(2130968578); activity.footer = (android.widget.TextView) activity.findViewById(2130968579); activity.title = (android.widget.TextView) activity.findViewById(2130968577); }
Butterknife
http://jakewharton.github.io/butterknife/
14
compile 'com.jakewharton:butterknife:6.0.0'
Database management
Boilerplate top 3
Очень простая модель
*Здесь и далее модификаторы доступа, акцессоры и мутаторы не указаны простоты ради. То же самое относится к проверкам на null.
16
class Photo { long id; String name; long lat; long lon; String pathToFile; }
Boilerplate Top 3.1: SqliteOpenHelper
17
public void onCreate(SQLiteDatabase db) { db.execSQL("create table photos ( id integer primary key autoincrement, name text, lat integer, lon integer, pathToFile text);"); } public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// crazy update stuff }
Boilerplate Top 3.1: SqliteOpenHelper
18
public void onCreate(SQLiteDatabase db) { db.execSQL("create table photos ( id integer primary key autoincrement, name text, lat integer, lon integer, pathToFile text);"); } public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// crazy update stuff }
Boilerplate Top 3.2: Query
19
public Cursor getPhotoById(long id) { return database.query ( "photos", // table, null, // columns, "id =?", // selection, new String[] { String.valueOf(id) } // selectionArgs, null, // groupBy null, // having null, // orderBy); }
Boilerplate Top 3.3: Conversion
20
public Photo cursorToPhoto(Cursor cursor) { Photo photo = new Photo(); photo.id = cursor.getLong(0); photo.name = cursor.getString(1); photo.lat = cursor.getLong(2); photo.lon = cursor.getLong(3); photo.path = cursor.getString(4); return photo; }
Boilerplate Top 3.3: Conversion
21
public Photo cursorToPhoto(Cursor cursor) { Photo photo = new Photo(); photo.id = cursor.getLong(0); photo.name = cursor.getString(1); photo.lat = cursor.getLong(2); photo.lon = cursor.getLong(3); photo.path = cursor.getString(4); return photo; } // что если поменяется порядок столбцов?
Boilerplate Top 3.3: Conversion
22
public Photo cursorToPhoto(Cursor cursor) { Photo photo = new Photo(); photo.id = cursor.getLong(0); photo.name = cursor.getString(1); photo.lat = cursor.getLong(2); photo.lon = cursor.getLong(3); photo.path = cursor.getString(4); return photo; } // что если поменяется порядок столбцов? // где-то еще нужно менеджить курсор
Boilerplate Top 3.3: Conversion
23
public Photo cursorToPhoto(Cursor cursor) { Photo photo = new Photo(); photo.id = cursor.getLong(0); photo.name = cursor.getString(1); photo.lat = cursor.getLong(2); photo.lon = cursor.getLong(3); photo.path = cursor.getString(4); return photo; } // что если поменяется порядок столбцов? // где-то еще нужно менеджить курсор // не забывайте править каждый раз при смене модели
Database management: Cupboard
Boilerplate top 3
Cupboard
• Нет необходимости в наследовании от классов библиотеки
• Создавалась специально для Android
• Не менеджит отношения между объектами
• Автоматически генерирует и обновляет таблицы
• Прозрачные трансформации POJO -> ContentValues, Cursor -> POJO
Cupboard: https://bitbucket.org/qbusict/cupboard 25
Нет необходимости в наследовании от классов библиотеки
26
class Photo { Long _id; String name; long lat; long lon; String pathToFile; }
Автоматически генерирует и обновляет таблицы
27
static { cupboard().register(Photo.class); } !
@Override public void onCreate(SQLiteDatabase db) { cupboard().withDatabase(db).createTables(); } !
@Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
cupboard().withDatabase(db).upgradeTables(); }
Трансформации POJO -> ContentValues, Cursor -> POJO
28
!
Photo photo = … cupboard.withDatabase(db).put(photo);
!
!
long photoId = … photo = cupboard.withDatabase(db).get(Photo.class, photoId);
Делать нужные выборки очень просто
29
List<Photo> kittyPhotos = cupboard().withDatabase(db) .query(Photo.class) .withSelection("name like %?%", "kitty") .list();
Кастомные конверсии
30
public interface FieldConverter<T> { T fromCursorValue(Cursor cursor, int columnIndex); !
void toContentValue(T value, String key, ContentValues values); !
ColumnType getColumnType(); !
}
Cupboard
https://bitbucket.org/qbusict/cupboard
31
compile 'nl.qbusict:cupboard:2.0.1'
Image loading
Boilerplate Top 2
Чего мы хотим?
• асинхронности
• кеширование в память
• трансформации (crop, scale, round, desaturate etc.)
• анимации
• bitmap pooling
• placeholder image
• кеширование на диск
Boilerplate Top 2: Image loading 33
Чего мы хотим?
• асинхронности
• кеширование в память
• трансформации (crop, scale, round, desaturate etc.)
• анимации
• bitmap pooling
• placeholder image
• кеширование на диск
Boilerplate Top 2: Image loading 34
Чего мы хотим?
• асинхронности
• кеширование в память
• трансформации (crop, scale, round, desaturate etc.)
• анимации
• bitmap pooling
• placeholder image
• кеширование на диск
Boilerplate Top 2: Image loading 34
Что такое Bitmap Pooling?// вот так далвик справляется с выделением памяти для битмапов
GC_FOR_ALLOC freed 1105K, 14% free 10933K/12615K, paused 33ms, total 34ms
GC_FOR_ALLOC freed 337K, 13% free 11055K/12615K, paused 25ms, total 26ms
GC_FOR_ALLOC freed 278K, 14% free 10951K/12615K, paused 24ms, total 24ms
GC_CONCURRENT freed 633K, 11% free 11317K/12615K, paused 16ms+3ms, total 79ms
WAIT_FOR_CONCURRENT_GC blocked 16ms
WAIT_FOR_CONCURRENT_GC blocked 15ms
Boilerplate Top 2: Image loading 35
Что такое Bitmap Pooling?// вот так далвик справляется с выделением памяти для битмапов
GC_FOR_ALLOC freed 1105K, 14% free 10933K/12615K, paused 33ms, total 34ms
GC_FOR_ALLOC freed 337K, 13% free 11055K/12615K, paused 25ms, total 26ms
GC_FOR_ALLOC freed 278K, 14% free 10951K/12615K, paused 24ms, total 24ms
GC_CONCURRENT freed 633K, 11% free 11317K/12615K, paused 16ms+3ms, total 79ms
WAIT_FOR_CONCURRENT_GC blocked 16ms
WAIT_FOR_CONCURRENT_GC blocked 15ms
Boilerplate Top 2: Image loading 36
на один фрейм у нас 1000/60 = 16мсек
Image loading: Glide
Boilerplate top 2
Glide
• Простой интерфейс
• Работает асинхронно
• А может работать синхронно
• Кеширует в память
• Кеширует на диск
• Анимации
• Трансформации
Glide: https://github.com/bumptech/glide 38
Простой интерфейс
39
Glide.with(applicationContext) .load(imageUrl) .crossfade() .into(imageView);
Поддерживает блокирующую загрузку
40
Bitmap bmp = Glide.with(applicationContext) .load(yourUrl) .asBitmap() .centerCrop() .into(500, 500) .get() // блокирует поток до завершения загрузки
Кеширование
41
public class AwesomeApp extends Application { @Override public void onCreate() { Glide.setup(new GlideBuilder(this) .setDiskCache(…) // LRU, 250MB .setMemoryCache(…) // LRU, 40% RAM ); } }
Кастомизация цели загрузки
42
public interface Target<R> extends LifecycleListener { !
public void onLoadStarted(Drawable placeholder); public void onLoadFailed(Exception e, Drawable errorDrawable); public void onResourceReady(R resource, GlideAnimation<? super R> glideAnimation); public void onLoadCleared(Drawable placeholder); public void getSize(SizeReadyCallback cb); public void setRequest(Request request); public Request getRequest(); !
}
43
public interface BitmapPool { public void setSizeMultiplier(float sizeMultiplier); !
public boolean put(Bitmap bitmap); !
public Bitmap get(int width, int height, Bitmap.Config config); !
public Bitmap getDirty(int width, int height, Bitmap.Config config); public void clearMemory(); !
public void trimMemory(int level); } // НЕ панацея!
Glide
https://github.com/bumptech/glide
44
compile 'com.github.bumptech.glide:glide:3.3.1'
//optional
compile 'com.github.bumptech.glide:okhttp-integration:1.0.+'
compile 'com.squareup.okhttp:okhttp:2.0.+'
Взаимодействие с сервером
Boilerplate Top 1
Чего мы хотим?
• манипулировать параметрами запроса
• удобно обрабатывать ошибки
• ставить нужные заголовки
• как можно меньше думать о http
• незаметно парсить ответы
• а главное, чтобы все было асинхронно
Boilerplate Top 1: Client-Server Interaction 46
Взаимодействие с сервером: Retrofit
Boilerplate top 1
Retrofit
• Декларативное описание серверного (REST?) API
• Гибкое задание параметров запроса
• Автоматические конверсии параметров запросов/ответов
• Манипуляция заголовками
• Синхронно/Асинхронно/Реактивно
Retrofit: http://square.github.io/retrofit/ 48
Декларативное описание серверного API
49
public interface PhotosService { !
@GET("/photos/list") List<Photos> photos(@Query("sort") String sortBy); !
@Multipart @POST("/{user}/photos/add") Photo addPhoto(@Path("user") String user, @Body Photo photo, @Part TypeFile imageFile); }
Декларативное описание серверного API
50
public interface PhotosService { !
@GET("/photos/list") List<Photos> photos(@Query("sort") String sortBy); !
@Multipart @POST("/{user}/photos/add") Photo addPhoto(@Path("user") String user, @Body Photo photo, @Part TypeFile imageFile); }
Декларативное описание серверного API
51
public interface PhotosService { !
@GET("/photos/list") List<Photos> photos(@Query("sort") String sortBy); !
@Multipart @POST("/{user}/photos/add") Photo addPhoto(@Path("user") String user, @Body Photo photo, @Part TypeFile imageFile); }
Декларативное описание серверного API
52
public interface PhotosService { !
@GET("/photos/list") List<Photos> photos(@Query("sort") String sortBy); !
@Multipart @POST("/{user}/photos/add") Photo addPhoto(@Path("user") String user, @Body Photo photo, @Part TypeFile imageFile); }
Декларативное описание серверного API
53
public interface PhotosService { !
@GET("/photos/list") List<Photos> photos(@Query("sort") String sortBy); !
@Multipart @POST("/{user}/photos/add") Photo addPhoto(@Path("user") String user, @Body Photo photo, @Part TypeFile imageFile); }
Использование
54
RestAdapter restAdapter = new RestAdapter.Builder() .setEndpoint("https://api.example.com") .build(); !
PhotoService photoService = restAdapter.create(PhotoService.class); !
// теперь можно использовать сервис List<Photos> photos = photoService.photos("popular");
Гибкое задание параметров запроса
55
PhotoService photoService = … !
// /photos/list List<Photos> photos = photoService.photos(null); !
… !
// /photos/list?sort="rating" List<Photos> bestPhotos = photoService.photos("rating");
Конвертация параметров запросов/ответов
56
!
// По-умолчанию объект типа Photo будет сериализован в // JSON при помощи GSON при запросе и десериализован из ответа !
Photo photo = photoService.addPhoto(userId, newPhoto, imageFile);
Конвертация параметров запросов/ответов
57
// Конверторы задают преобразования TypedValue -> Object и обратно. public interface Converter { !
Object fromBody(TypedInput body, Type type); TypedOutput toBody(Object object); }
Конвертация параметров запросов/ответов: Retrofit Converters
https://github.com/square/retrofit/tree/master/retrofit-converters
Доступные конверторы:
• Jackson
• Protobuf
• SimpleXml
• Wire
58
Манипуляция заголовками (статические)
59
@Headers( { "Accept: application/json", "User-Agent: Agent-007" }; @GET("/photos/list") List<Photo> photos();
Манипуляция заголовками (динамические)
60
@Headers( { "Accept: application/json", "User-Agent: Agent-007" }; @GET("/photos/list") List<Photo> photos(); !
@GET("/photos/{user}/list") List<Photo> photos(@Header("Authorization") String authorization, @Path("user") String user); !
Перехват запросов
61
RequestInterceptor requestInterceptor = new RequestInterceptor() { @Override public void intercept(RequestFacade request) { request.addHeader("Authorization", UserData.getAuthToken()); } };
Перехват запросов
62
RequestInterceptor requestInterceptor = new RequestInterceptor() { @Override public void intercept(RequestFacade request) { request.addHeader("Authorization", UserData.getAuthToken()); } }; // Будет перехватывать ВСЕ запросы RestAdapter restAdapter = new RestAdapter.Builder() .setEndpoint("…") .setRequestInterceptor(requestInterceptor) .build();
Синхронно
63
public interface PhotosService { !
@GET("/photos/list") List<Photos> photos(@Query("sort") String sortBy);
}
Асинхронно
64
public interface PhotosService { !
@GET("/photos/list") void photos(@Query("sort") String sortBy, Callback<List<Photo>> callback); }
!
}
Асинхронно
65
public interface PhotosService { !
@GET("/photos/list") void photos(@Query("sort") String sortBy, Callback<List<Photo>> callback); }
} !
public interface Callback<T> { void success(T t, Response response); void failure(RetrofitError error); }
Асинхронно в паре с Robospice
66
// https://github.com/stephanenicolas/robospice/wiki/Retrofit-module !
public abstract class SpiceRequest<RESULT> implements Comparable<SpiceRequest<RESULT>> {
public abstract RESULT loadDataFromNetwork() throws Exception; } !
public interface RequestListener<RESULT> { void onRequestFailure(SpiceException spiceException); void onRequestSuccess(RESULT result); } spiceManager.execute(request, listener);
Асинхронно в паре с Robospice
67
// https://github.com/stephanenicolas/robospice/wiki/Retrofit-module !
public abstract class SpiceRequest<RESULT> implements Comparable<SpiceRequest<RESULT>> {
public abstract RESULT loadDataFromNetwork() throws Exception; } !
public interface RequestListener<RESULT> { void onRequestFailure(SpiceException spiceException); void onRequestSuccess(RESULT result); } spiceManager.execute(request, listener);
Асинхронно в паре с Robospice
68
// https://github.com/stephanenicolas/robospice/wiki/Retrofit-module !
public abstract class SpiceRequest<RESULT> implements Comparable<SpiceRequest<RESULT>> {
public abstract RESULT loadDataFromNetwork() throws Exception; } !
public interface RequestListener<RESULT> { void onRequestFailure(SpiceException spiceException); void onRequestSuccess(RESULT result); } spiceManager.execute(request, listener);
Асинхронно в паре с Robospice
69
// https://github.com/stephanenicolas/robospice/wiki/Retrofit-module !
public abstract class SpiceRequest<RESULT> implements Comparable<SpiceRequest<RESULT>> {
public abstract RESULT loadDataFromNetwork() throws Exception; } !
public interface RequestListener<RESULT> { void onRequestFailure(SpiceException spiceException); void onRequestSuccess(RESULT result); } spiceManager.execute(request, listener);
Реактивно
70
public interface PhotosService { !
@GET("/photos/list") Observable<List<Photos>> photos(@Query("sort") String sortBy); !
}
Реактивно
71
photoService.photos("popular") .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Action1<List<Photo>>() {
@Override public void call(List<Photo> list) { // обработка результата запроса } });
Реактивно
72
photoService.photos("popular") .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Action1<List<Photo>>() {
@Override public void call(List<Photo> list) { // обработка результата запроса }
});
Реактивно
73
photoService.photos("popular") .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Action1<List<Photo>>() {
@Override public void call(List<Photo> list) { // обработка результата запроса } });
74
photoService.photos("popular") .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<List<Photo>>() { public void onCompleted() { … } public void onError(Throwable e) { … } !
public void onNext(List<Photo> list) { … } !
});
Retrofit
http://square.github.io/retrofit/
75
compile 'com.squareup.retrofit:retrofit:1.7.0'
compile 'com.squareup.okhttp:okhttp:2.0.0'
compile 'com.squareup.okhttp:okhttp-urlconnection:2.0.0'
// optional
compile 'com.netflix.rxjava:rxjava-android:0.20.5'
Полезные ссылки:
• Демо: https://github.com/d-kunin/yac2014
• http://gradleplease.appspot.com/
• http://androidweekly.net/
• https://github.com/JakeWharton/u2020
Спасибо за внимание!
_platon
https://www.facebook.com/dev.kunin 78
Дмитрий Кунин
старший разработчик Яндекс.Музыки для Android
Контакты