Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

Android Architecture Components

Android Architecture Components

Google I/O 2017 extended Android Codelabs
Android Architecture Components 발표자료

pluulove (노현석)

August 01, 2017
Tweet

More Decks by pluulove (노현석)

Other Decks in Programming

Transcript

  1. Data L i f e c y c l e

    ۄ੉೐ࢎ੉௿੄ ߷
  2. class MyActivity extends AppCompatActivity {
 private MyLocationListener myLocationListener;
 
 public

    void onCreate(...) {
 myLocationListener = new MyLocationListener(this, (location) -> {
 // update UI
 });
 }
 
 public void onStart() {
 super.onStart();
 myLocationListener.start();
 }
 
 public void onStop() {
 super.onStop();
 myLocationListener.stop();
 }
 } ࢤݺ઱ӝ ҙ۲ ੌ߈੸ੋ Android ࢸ҅ • onStart()ীࢲ द੘ • onStop()ীࢲ ઙܐ
  3. Lifecycle ࢤݺ ઱ӝ ੉߮౟੄ ৌѢഋҗ ೣԋ അ੤ ࢤݺ ઱ӝ ࢚క੄

    ৌѢഋਸ ࢎਊೞৈ ҙ۲ ҳࢿ ਃࣗী ؀ೠ ࢤݺ ઱ӝ ࢚కܳ ୶੸
  4. LifecycleOwner Lifecycle ё୓ܳ ߈ജೞח ױੌ ੋఠಕ੉झ ݫࢲ٘ী ઱ࢳਸ ୶оೣਵ۽ॄ ҳࢿ

    ਃࣗ੄ ࢤݺ ઱ӝ ੉߮౟ܳ ݽפఠ݂ೡ ࣻ ੓ח ੋఠಕ੉झ LifecycleObserver
  5. public class LifecycleActivity extends FragmentActivity implements LifecycleRegistryOwner {
 private final

    LifecycleRegistry mRegistry = new LifecycleRegistry(this); 
 @Override
 public LifecycleRegistry getLifecycle() {
 return mRegistry;
 }
 } 
 
 public class LifecycleFragment extends Fragment implements LifecycleRegistryOwner {
 LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
 
 @Override
 public LifecycleRegistry getLifecycle() {
 return mLifecycleRegistry;
 }
 }
  6. will be deprecated LifecycleActivity LifecycleFragment Architecture Components ח അ੤ ঌ౵

    ߡ੹ Fragmentҗ AppCompatActivity۽ח ҳഅ ࠛоמ Lifecycles ೐۽ં౟о ୹दغݶ Support Library Fragment৬ Activityо LifecycleOwner ੋఠಕ ੉झܳ ҳഅೠ׮.
  7. public class MyObserver implements LifecycleObserver {
 public MyObserver(Lifecycle lifecycle) {


    // Starts lifecycle observation
 lifecycle.addObserver(this);
 ...
 }
 
 // Annotated methods called when the associated lifecycle goes through these events
 @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
 public void onResume() {
 }
 @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
 public void onPause() {
 }
 }
 MyObserver observer = new MyObserver(aLifecycleOwner.getLifecycle()); 1 2 4 3
  8. implements LifecycleObserver
 @OnLifecycleEvent(Lifecycle.Event.ON_START) APT ח GenericLifecycleObserver / ReflectiveGenericLifecycleObserver (ղࠗ ௿ېझ

    ਊ) ী ೯زਸ ੹׳ Lifecycle#State ߸ച ١ਸ LifecycleObserverী Dispatch Lifecycle Flow APTী ੄೧ 
 
 ${Activity_name}
 _${LifecycleObserver_name}
 _LifecycleAdapter 
 
 ௿ېझо ࢤࢿؽ
  9. ViewModel UI ҙ۲ ؘ੉ఠܳ ੷੢ೞҊ ҙܻೞৈ ؘ੉ఠо ച ݶ ഥ੹җ

    э਷ ҳࢿ ߸҃ਸ Ѽ٧ ࣻ ੓ب۾ ࢸ҅غ ঻णפ׮.
  10. ViewModel ଼੐ UI ؘ੉ఠ ҙܻ Activity / Fragment ߂ աݠ૑

    জ ࢎ੉੄ ాन ୊ܻ Configuration ߸҃ী ؀਽ೞח ؘ੉ఠ ੷ ੢ Activity / Fragment ۽ࠗఠ ة݀
  11. The lifecycle of a ViewModel : https://developer.android.com/topic/libraries/architecture/viewmodel.html ViewModel Scope ViewModelਸ

    о૑ח Activityо ઙ ܐغח ҃਋, ೐ۨ੐ਕ௼о ViewModel੄ onClearedܳ ഐ୹ೣ
  12. https://github.com/googlecodelabs/android-lifecycles/blob/master/app/src/main/java/com/example/android/lifecycles/step2/ChronometerViewModel.java public class ChronometerViewModel extends ViewModel {
 
 @Nullable
 private

    Long startDate;
 
 @Nullable
 public Long getStartDate() {
 return startDate;
 }
 
 public void setStartDate(final long startDate) {
 this.startDate = startDate;
 }
 }
  13. https://github.com/googlecodelabs/android-lifecycles/blob/master/app/src/main/java/com/example/android/lifecycles/step2/ChronoActivity2.java public class ChronoActivity2 extends LifecycleActivity {
 
 @Override
 protected

    void onCreate(Bundle savedInstanceState) {
 ...
 ChronometerViewModel chronometerViewModel
 = ViewModelProviders.of(this).get(ChronometerViewModel.class);
 ...
 }
 } ViewModelProviders.of(this) ܳ ખ ؊ Ө੉ ଺ইࠁݶ, ViewModel਷ UI (Activity / Fragment)৬ োѾغয ੓਺ਸ ঌ ࣻ ੓णפ׮.
  14. ViewModelProviders ViewModelStore੄ ਬ౰ܻ౭ ௿ېझ ղࠗ੸ਵ۽ ViewModelStoreܳ ଵઑೞৈ ViewModel੄ ӝઓ ੋ

    झఢझܳ ߈ജ, ઓ੤ೞ૑ ঋח ҃਋ ViewModel ੋझఢझܳ ࢜۽ ੘ࢿ೤פ׮.
  15. android.arch.lifecycle.ViewModelStores.java public class ViewModelStore {
 
 private final HashMap<String, ViewModel>

    mMap = new HashMap<>();
 
 final void put(String key, ViewModel viewModel) {
 ViewModel oldViewModel = mMap.get(key);
 if (oldViewModel != null) {
 oldViewModel.onCleared();
 }
 mMap.put(key, viewModel);
 }
 
 final ViewModel get(String key) {
 return mMap.get(key);
 }
 
 public final void clear() {
 for (ViewModel vm : mMap.values()) {
 vm.onCleared();
 }
 mMap.clear();
 }
 }
  16. android.arch.lifecycle.AndroidViewModel.java public class AndroidViewModel extends ViewModel {
 private Application mApplication;


    
 public AndroidViewModel(Application application) {
 mApplication = application;
 }
 
 /**
 * Return the application.
 */
 public <T extends Application> T getApplication() {
 return (T) mApplication;
 }
 }
  17. ViewModel ઁড Viewী ૒੽ ੽Ӕ X Drawable ١ Resource о૗

    X Activity / Fragment о૗ X ઙܐद੄ ୊ܻח onCleared()ী ӝࣿ
  18. LiveData ч (Value Holder)ਸ ࠁਬೞҊ ч੄ ߸҃ ࢎ೦ਸ ҙ଴ ೡ

    ࣻ੓ח ҳࢿ ਃࣗੑפ׮. ViewHolder੄ ؘ੉ఠܳ ਬ૑ೞب۾ ࢸ҅
  19. LiveData ч (Value Holder)ਸ ࠁਬೞҊ ч੄ ߸҃ ࢎ೦ਸ ҙ଴ ೡ

    ࣻ੓ח ҳࢿ ਃࣗੑפ׮. ViewHolder੄ ؘ੉ఠܳ ਬ૑ೞب۾ ࢸ҅ Observableೠ DataHolder
  20. LiveData ੢੼ ೦࢚ ୭न ؘ੉ఠ ੸੺ೠ ҳࢿ ߸҃ ݫݽܻ ־ࣻ

    হ਺ (Lifecycle) Lifecycle੉ উ੹ೡदী݅ ੹׳ ч ߸҃ ୓௼ܳ ਤೠ ߹ب ҳഅ X
  21. Room ੢੼ Object Mapping Library ਗद SQL ௪ܻ੄ ஹ౵ੌ ఋ੐

    Ѩૐ SQL ௪ܻ৬ Java ؘ੉ఠ ё୓ ࢎ੉ܳ ߸ജೞח boilerplate codeܳ ઁѢ ழࢲ ژח ۽؊ ੘ࢿ ೙ਃ হ਺ ߈ജ ૑ਗೣ ؘ੉ఠ ৔ࣘച ઁҕ
  22. https://github.com/googlecodelabs/android-persistence/blob/master/app/src/main/java/com/example/android/persistence/codelab/db/User.java @Entity
 public class User {
 @PrimaryKey public String id;


    public String name;
 public String lastName;
 public int age; @Ignore
 Bitmap picture; } Entity • @PrimaryKey ӝࠄః ૑੿ ◦ autoGenerate ◦ primaryKeys ◦ tableName • @Ignore ૑੿ೠ ೙٘ח Column ࢤࢿ୊ܻܳ ೞ૑ ঋ ਺ • setter/getterܳ ੉ਊ೧ ೙٘ ੽Ӕ • @ColumnInfo ߹ب ૑੿ оמ
  23. https://github.com/googlecodelabs/android-persistence/blob/master/app/src/main/java/com/example/android/persistence/codelab/db/Loan.java @Entity(foreignKeys = {
 @ForeignKey(entity = Book.class,
 parentColumns = "id",

    childColumns = "book_id"),
 @ForeignKey(entity = User.class,
 parentColumns = "id", childColumns = "user_id")})
 @TypeConverters(DateConverter.class)
 public class Loan {
 public @PrimaryKey String id;
 public Date startTime;
 public Date endTime;
 @ColumnInfo(name="book_id")
 public String bookId;
 @ColumnInfo(name="user_id")
 public String userId;
 } Foreign Key
  24. https://github.com/googlesamples/android-architecture-components/blob/178fe541643adb122d2a8925cf61a21950a4611c/BasicSample/app/src/main/java/com/example/ android/persistence/db/converter/DateConverter.java public class DateConverter {
 @TypeConverter
 public static Date

    toDate(Long timestamp) {
 return timestamp == null ? null : new Date(timestamp);
 }
 
 @TypeConverter
 public static Long toTimestamp(Date date) {
 return date == null ? null : date.getTime();
 }
 } TypeConverter ੑ۱ ఋੑ DB ীࢲ੄ ఋੑ ୹۱ ఋੑ Date Long Date
  25. https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/vo/ Repo.java @Entity(primaryKeys = {"name", "owner_login"})
 public class Repo {


    public final int id;
 @SerializedName("name")
 public final String name;
 @SerializedName("full_name")
 public final String fullName;
 ...
 @SerializedName("owner")
 @Embedded(prefix = "owner_")
 public final Owner owner;
 ...
 } Nested Objects public class Owner {
 @SerializedName("login")
 public final String login;
 @SerializedName("url")
 public final String url;
 ...
 }
  26. Nested Objects ѐ୓ী ೞਤ ೙٘۽ ࠙೧ೞৈ ё୓ܳ ಴അ Room਷ Entityр੄

    ҙ҅ܳ য়࠳ં౟۽ ಴അ ࠛ оמ RDBܳ ਤೠ ۨ੉য੉ݶࢲ Object-relational mapping ੉ ইצ ੉ਬ class A { 
 @OneToOne // ૑ਗ ࠛо
 public B b;
 }
  27. https://github.com/googlecodelabs/android-persistence/blob/master/app/src/main/java/com/example/android/persistence/codelab/db/UserDao.java @Dao
 public interface UserDao {
 @Query("select * from user")


    List<User> loadAllUsers();
 
 @Query("select * from user where name = :firstName and lastName = :lastName")
 List<User> findByNameAndLastName(String firstName, String lastName);
 
 @Delete
 void deleteUser(User user);
 
 @Insert(onConflict = IGNORE)
 void insertOrReplaceUsers(User... users);
 ...
 } Data Access Object (Dao)
  28. https://github.com/googlecodelabs/android-persistence/blob/master/app/src/main/java/com/example/android/persistence/codelab/db/UserDao.java @Dao
 public interface UserDao {
 @Query("select * from user")


    List<User> loadAllUsers();
 
 @Query("select * from user where name = :firstName and lastName = :lastName")
 List<User> findByNameAndLastName(String firstName, String lastName);
 
 @Delete
 void deleteUser(User user);
 
 @Insert(onConflict = IGNORE)
 void insertOrReplaceUsers(User... users);
 ...
 } Data Access Object (Dao)
  29. public List<User> findByNameAndLastName(String firstName, String lastName) {
 final String _sql

    = "select * from user where name = ? and lastName = ?";
 final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 2);
 int _argIndex = 1;
 if (firstName == null) {
 _statement.bindNull(_argIndex);
 } else {
 _statement.bindString(_argIndex, firstName);
 }
 _argIndex = 2;
 if (lastName == null) {
 _statement.bindNull(_argIndex);
 } else {
 _statement.bindString(_argIndex, lastName);
 } UserDao_Impl implements UserDao (1/3)
  30. final Cursor _cursor = __db.query(_statement);
 try {
 final int _cursorIndexOfId

    = _cursor.getColumnIndexOrThrow("id");
 final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
 final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
 final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("age");
 final List<User> _result = new ArrayList<User>(_cursor.getCount());
 while(_cursor.moveToNext()) {
 final User _item;
 _item = new User();
 _item.id = _cursor.getString(_cursorIndexOfId);
 _item.name = _cursor.getString(_cursorIndexOfName);
 _item.lastName = _cursor.getString(_cursorIndexOfLastName);
 _item.age = _cursor.getInt(_cursorIndexOfAge);
 _result.add(_item);
 } return _result; UserDao_Impl implements UserDao (2/3)
  31. https://github.com/googlecodelabs/android-persistence/blob/master/app/src/main/java/com/example/android/persistence/codelab/db/AppDatabase.java // Create Database
 @Database(entities = {User.class, Book.class, Loan.class}, version

    = 1)
 public abstract class AppDatabase extends RoomDatabase {
 public abstract UserDao userModel();
 public abstract BookDao bookModel();
 public abstract LoanDao loanModel();
 ...
 } Database
  32. public class AppDatabase_Impl extends AppDatabase {
 private volatile UserDao _userDao;


    private volatile BookDao _bookDao;
 private volatile LoanDao _loanDao;
 
 protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration configuration) {
 // Create, Drop, Migration … etc
 } @Override protected InvalidationTracker createInvalidationTracker() { ... }
 @Override public UserDao userModel() { … }
 @Override public BookDao bookModel() { … }
 @Override public LoanDao loanModel() { … }
 } AppDatabase_Impl extends AppDatabase (1/3) // ప੉࠶ ߸҃ਸ х૑
  33. protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration configuration) {
 final SupportSQLiteOpenHelper.Callback _openCallback = new

    RoomOpenHelper(configuration, new RoomOpenHelper.Delegate() {
 public void createAllTables(SupportSQLiteDatabase _db) { … }
 public void dropAllTables(SupportSQLiteDatabase _db) { … }
 protected void onCreate(SupportSQLiteDatabase _db) { … }
 public void onOpen(SupportSQLiteDatabase _db) { … }
 protected void validateMigration(SupportSQLiteDatabase _db) { … }
 }, "..."); AppDatabase_Impl extends AppDatabase (2/3)
  34. final SupportSQLiteOpenHelper.Configuration _sqliteConfig = SupportSQLiteOpenHelper.Configuration.builder(configuration.context)
 .name(configuration.name)
 .version(1)
 .callback(_openCallback)
 .build();
 final

    SupportSQLiteOpenHelper _helper = configuration.sqliteOpenHelperFactory.create(_sqliteConfig);
 return _helper;
 } AppDatabase_Impl extends AppDatabase (3/3)
  35. https://developer.android.com/reference/android/arch/persistence/room/Room.html // File
 Builder<T> databaseBuilder (Context context,
 Class<T> klass, String

    name)
 // Memory Builder<T> inMemoryDatabaseBuilder (Context context,
 Class<T> klass) Database Generate Options
  36. Invalidation Tracker ۨ௏٘ ߸҃ী ؀ೠ ୶੸ InvalidationTracker ௿ېझо ప੉࠶ ߸҃ਸ

    Ѩࢎೣ Room ীࢲ ࢎਊߑߨ LiveData RxJava2 Flowable/Publisher DAOীࢲ ਤ ೦ݾਸ ࢎਊ
  37. Invalidation Tracker We create an in memory table with (version,

    table_id) where version is an auto-increment primary key and a table_id (hardcoded int from initialization). ObservedTableTracker tracks list of tables we should be watching (e.g. adding triggers for). Before each beginTransaction, RoomDatabase invokes InvalidationTracker to sync trigger states. After each endTransaction, RoomDatabase invokes InvalidationTracker to refresh invalidated tables. Each update on one of the observed tables triggers an insertion into this table, hence a new version. Unfortunately, we cannot override the previous row because sqlite uses the conflict resolution of the outer query (the thing that triggered us) so we do a cleanup as we sync instead of letting SQLite override the rows. https://sqlite.org/lang_createtrigger.html: An ON CONFLICT clause may be specified as part of an UPDATE or INSERT action within the body of the trigger. However if an ON CONFLICT clause is specified as part of the statement causing the trigger to fire, then conflict handling policy of the outer statement is used instead.
  38. Database migration Migration ௿ېझ ੉ਊ startVersion, endVersion ૑੿ ୭न ߡ੹ਵ۽

    ੉੹ೞӝী ୽࠙ೠ ݃੉Ӓۨ੉࣌ ઁҕೞ૑ ঋח ҃ ਋ ؘ੉ఠ߬੉झܳ ੤੘ࢿೞ޲۽ ݽٚ ؘ੉ఠ ࣚप
  39. https://developer.android.com/topic/libraries/architecture/room.html Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name")
 .addMigrations(MIGRATION_1_2, MIGRATION_2_3).build();
 
 static final Migration

    MIGRATION_1_2 = new Migration(1, 2) {
 @Override
 public void migrate(SupportSQLiteDatabase database) {
 database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, "
 + "`name` TEXT, PRIMARY KEY(`id`))");
 }
 };
 static final Migration MIGRATION_2_3 = new Migration(2, 3) {
 @Override
 public void migrate(SupportSQLiteDatabase database) {
 database.execSQL("ALTER TABLE Book " + " ADD COLUMN pub_year INTEGER");
 }
 };
  40. https://developer.android.com/topic/libraries/architecture/room.html#db-migration // build.gradle
 android {
 ...
 defaultConfig {
 ...
 javaCompileOptions

    {
 annotationProcessorOptions {
 arguments = ["room.schemaLocation":
 "$projectDir/schemas".toString()]
 }
 }
 }
 ...
 } Testing migrations (1) ݢ੷ ؘ੉ఠ߬੉झ झః݃ܳ ղࠁղঠ೤פ׮. Room਷ ஹ౵ੌद ؘ੉ఠ߬੉झ੄ झః݃ ੿ࠁ ܳ JSON ౵ੌਸ ࢤࢿೞ޲۽, build.gradle ౵ ੌীࢲ room.schemaLocation Annotation ೐۽ࣁࢲ ࣘࢿਸ ࢸ੿ೠ׮.
  41. https://developer.android.com/topic/libraries/architecture/room.html#db-migration // build.gradle
 android { ...
 sourceSets {
 androidTest.assets.srcDirs +=

    files("$projectDir/schemas".toString())
 }
 ...
 dependencies {
 androidTestCompile "android.arch.persistence.room:testing"
 }
 } Testing migrations (2) ‘android.arch.persistence.ro om:testing’ ਸ ઙࣘࢿী ୶оೞ Ҋ, ղࠁմ झః݃ ౵ੌ (JSON) ਸ asset ಫ؊ী ߓ஖ೠ׮.
  42. https://developer.android.com/topic/libraries/architecture/room.html#db-migration @RunWith(AndroidJUnit4.class)
 public class MigrationTest {
 private static final String

    TEST_DB = "migration-test";
 
 @Rule
 public MigrationTestHelper helper;
 
 public MigrationTest() {
 helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
 MigrationDb.class.getCanonicalName(),
 new FrameworkSQLiteOpenHelperFactory());
 }
 …
 } Testing migrations (3) MigrationTestHelper ௿ېझܳ ੉ਊ೧ࢲ झః݃ ౵ੌਸ ੍যٜੋ׮.
  43. https://developer.android.com/topic/libraries/architecture/room.html#db-migration @RunWith(AndroidJUnit4.class)
 public class MigrationTest {
 @Test
 public void migrate1To2()

    throws IOException {
 // DB۽ ૑੿ೞח Schema Versionਵ۽ ੘ࢿ
 SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1);
 
 // Daoח ୭न Schemaী ੄ઓೞ޲۽ ࢎਊ ࠛоמ
 // Ӓېࢲ ࣻ੘সਵ۽ ؘ੉ఠܳ ֍যঠೠ׮.
 db.execSQL(...);
 
 // Migrationী ؀࠺ೞৈ dbܳ ײח׮
 db.close();
 ... Testing migrations (4)
  44. https://developer.android.com/topic/libraries/architecture/room.html#db-migration ...
 // ਗೞח ߡ੹ী ؀ೠ ݃੉Ӓۨ੉࣌ ೐۽ࣁझܳ ઁҕ
 db

    = helper.runMigrationsAndValidate(TEST_DB, 2, true, MIGRATION_1_2);
 
 // MigrationTestHelperо झః݃ ߸҃ ࢎ೦ਸ ੗زਵ۽ ഛੋ
 // ؘ੉ఠо ৢ߄ܰѱ ੉੹غ঻ח૑ ഛੋ೧ળ׮ // SELECT ޙ ١ਸ ੉ਊ೧ࢲ ೠߣ ؊ Ѩૐೞח Ѫਸ ୶ୌ
 }
 } Testing migrations (5)
  45. https://developer.android.com/topic/libraries/architecture/room.html Entites DAO Primary key @Insert, @Update, @Delete Passing a

    collection of arguments Querying multiple tables Indices and uniqueness @Query Observable queries type converters Relationships Passing parameters into the query RxJava Nested objects Returning subsets of columns Direct cursor access
  46. ଵҊ উ٘۽੉٘ ইఃఫ୊ о੉٘৬ ইఃఫ୊ ஹಌք౟ী ؀೧ ഛੋ೧ ࠁࣁਃ. Android

    Architecture Components Introduction to Android Architecture Components Android Architecture Components android-architecture-components΀κϸϜϰΧϜϯ΀᤼ᅻ΀ॲሑ΀ᇥΞΤ฼͸ΑΝ [Android Architecture Components] Lifecycle, LiveData and ViewModel ᓮḑ [Android Architecture Components] - Room ᓮḑ
  47. Thank you, reviewer - ӣకഐ (ҳӖ ௏ܻই) - ੿थ਌ (Android

    GDE) - ӂకജ (ঌ૑ೖ௏ܻই) - ੉थ޹ (٘ۄ݃ঙஹಌפ)