Share
Embed
[Full Version] 최신 AndroiodX 체크
"최신 AndroiodX 체크" 발표 중에 미처 발표하지 못한 내용이 포함된 풀 버전을 공개합니다.
pluulove (노현석)
December 01, 2019
Transcript
150 slides
-
୭न AndroidX Google Developer Experts for Android GDG Korea
Android Organizer Noh HyeonSeok -
Index • Introduction AndroidX • Android Components • AndroidX in
Android Dev Summit • Migration -
Question?! 1. AndroidXܳ ٜযࠌ? 2. Jetpackਸ ঌҊ? 3. Support Libraryо
ӝরդ? 4. Android Architecture Components? 5. ٜ ରਸ ঌҊ? -
Jetpack == AndroidX - androidx.* ী ח ۄ࠳۞ܻٜ - Jetpack
Libraries - (ҳ) Support Libraries -
AndroidX Android Jetpack ղীࢲ ۄ࠳۞ܻܳ ѐߊ, పझ, ಁః, ߡ
ҙܻ, दೞחؘ ࢎਊೞח য়ࣗझ ۽ંੑפ. https://developer.android.com/jetpack/androidx -
Updated packaging • Android java ࣗझܳ ಁః߹۽ ܻಂష݂ ▪ Android.<feature>.ClassName
• Maven ▪ androidx.<feature>:<feature>-<sub-feature> ▪ ਃೠ ӝמ߹ ߓನ • -v7, -v4 ಁఃݺ ઁѢ -
AndroidX Component - Total 70ѐ (2019.11.29) - Support, UI -
Benchmark, Migration … etc -
Activity & Fragment & Navigation - Activity 1.0.0(Stable) , 1.1.0-rc02(RC)
- Fragment 1.1.0(Stable) , 1.2.0-rc02(RC) - Navigation 2.1.0(Stable) , 2.2.0-rc02(RC) -
class OldFragment : Fragment() { // Crazy fun backStart() {
requireActivity().onBackPressed() } } җѢ Fragmentীࢲ onBackPressed // Crazy fun backStart() { requireActivity().onBackPressed() } // Safe override fun onAttach(context: Context) { super.onAttach(context) backListener = context as? BackListener backListener?.onBackPressed() } -
class MainFragment : Fragment() { private val dispatcher by lazy
{ requireActivity().onBackPressedDispatcher } private val backPressedCallback = object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { // TODO } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) dispatcher.addCallback(this, backPressedCallback) } } Fragmentীࢲ onBackPressed ܻ Back Pressed Flag -
// ComponentActivity.java public class ComponentActivity { private final OnBackPressedDispatcher mOnBackPressedDispatcher
= new OnBackPressedDispatcher(new Runnable() { @Override public void run() { // fallbackOnBackPressed ComponentActivity.super.onBackPressed(); } }); @Override @MainThread public void onBackPressed() { mOnBackPressedDispatcher.onBackPressed(); } } // OnBackPressedDispatcher.java public final class OnBackPressedDispatcher { @MainThread public void onBackPressed() { Iterator<OnBackPressedCallback> iterator = mOnBackPressedCallbacks.descendingIterator(); while (iterator.hasNext()) { OnBackPressedCallback callback = iterator.next(); if (callback.isEnabled()) { callback.handleOnBackPressed(); return; } } if (mFallbackOnBackPressed != null) { mFallbackOnBackPressed.run(); } } } ComponentActivity / BackPressedDispatcher ܴ -
// ComponentActivity.java public class ComponentActivity { private final OnBackPressedDispatcher mOnBackPressedDispatcher
= new OnBackPressedDispatcher(new Runnable() { @Override public void run() { // fallbackOnBackPressed ComponentActivity.super.onBackPressed(); } }); @Override @MainThread public void onBackPressed() { mOnBackPressedDispatcher.onBackPressed(); } } // OnBackPressedDispatcher.java public final class OnBackPressedDispatcher { @MainThread public void onBackPressed() { Iterator<OnBackPressedCallback> iterator = mOnBackPressedCallbacks.descendingIterator(); while (iterator.hasNext()) { OnBackPressedCallback callback = iterator.next(); if (callback.isEnabled()) { callback.handleOnBackPressed(); return; } } if (mFallbackOnBackPressed != null) { mFallbackOnBackPressed.run(); } } } ComponentActivity / BackPressedDispatcher ܴ -
// ComponentActivity.java public class ComponentActivity { private final OnBackPressedDispatcher mOnBackPressedDispatcher
= new OnBackPressedDispatcher(new Runnable() { @Override public void run() { // fallbackOnBackPressed ComponentActivity.super.onBackPressed(); } }); @Override @MainThread public void onBackPressed() { mOnBackPressedDispatcher.onBackPressed(); } } ComponentActivity / BackPressedDispatcher ܴ // OnBackPressedDispatcher.java public final class OnBackPressedDispatcher { @MainThread public void onBackPressed() { Iterator<OnBackPressedCallback> iterator = mOnBackPressedCallbacks.descendingIterator(); while (iterator.hasNext()) { OnBackPressedCallback callback = iterator.next(); if (callback.isEnabled()) { callback.handleOnBackPressed(); return; } } if (mFallbackOnBackPressed != null) { mFallbackOnBackPressed.run(); } } } -
// ComponentActivity.java public class ComponentActivity { private final OnBackPressedDispatcher mOnBackPressedDispatcher
= new OnBackPressedDispatcher(new Runnable() { @Override public void run() { // fallbackOnBackPressed ComponentActivity.super.onBackPressed(); } }); @Override @MainThread public void onBackPressed() { mOnBackPressedDispatcher.onBackPressed(); } } // OnBackPressedDispatcher.java public final class OnBackPressedDispatcher { @MainThread public void onBackPressed() { Iterator<OnBackPressedCallback> iterator = mOnBackPressedCallbacks.descendingIterator(); while (iterator.hasNext()) { OnBackPressedCallback callback = iterator.next(); if (callback.isEnabled()) { callback.handleOnBackPressed(); return; } } if (mFallbackOnBackPressed != null) { mFallbackOnBackPressed.run(); } } } ComponentActivity / BackPressedDispatcher ܴ -
// ComponentActivity.java public class ComponentActivity { private final OnBackPressedDispatcher mOnBackPressedDispatcher
= new OnBackPressedDispatcher(new Runnable() { @Override public void run() { // fallbackOnBackPressed ComponentActivity.super.onBackPressed(); } }); @Override @MainThread public void onBackPressed() { mOnBackPressedDispatcher.onBackPressed(); } } // OnBackPressedDispatcher.java public final class OnBackPressedDispatcher { @MainThread public void onBackPressed() { Iterator<OnBackPressedCallback> iterator = mOnBackPressedCallbacks.descendingIterator(); while (iterator.hasNext()) { OnBackPressedCallback callback = iterator.next(); if (callback.isEnabled()) { callback.handleOnBackPressed(); return; } } if (mFallbackOnBackPressed != null) { mFallbackOnBackPressed.run(); } } } ComponentActivity / BackPressedDispatcher ܴ -
ViewModel ୡӝച lifecycle-viewmodel private val viewModel by lazy { ViewModelProviders.of(this).get<MainViewModel>()
} This constructor was deprecated in API level 1.1.0. This class should not be directly instantiated -
ViewModel ୡӝച lifecycle-viewmodel val vm: VM = ViewModelProvider(this, factory).get(VM::class.java) val
vm: VM = ViewModelProvider(this).get(VM::class.java) public ViewModelProvider(@NonNull ViewModelStoreOwner owner) { this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory() : NewInstanceFactory.getInstance()); } -
Support Coroutines in ViewModel After lifecycle-viewmodel-ktx 2.1.0 val ViewModel.viewModelScope: CoroutineScope
get() { val scope: CoroutineScope? = this.getTag(JOB_KEY) if (scope != null) { return scope } return setTagIfAbsent(JOB_KEY, CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)) } To be continue… -
#AndroidDevSummit @Composable fun FilteredList(contacts: List<Contact>, filterText: String = "") {
val state = +state { filterText } VerticalScroller { Column { TextField(filterText, onValueChange = { state.value = it }) contacts.filter { it.contains(state.value) }.forEach { Row { Image(it.photo) Text(it.name) } } } } } -
#AndroidDevSummit • Simplify • Fix what’s broken • No more
cheating • Material + Animations • Compatibility Jetpack Compose -
#AndroidDevSummit Android Studio Live preview, Apply Changes Compose Compiler Plugin
Code generation extensions kotlinc Upstream Kotlin compiler (1.4) Compose UI Foundation Standard layouts, interactions Compose UI Core Input, Measure, Layout, Drawing Compose Runtime Tree management, Effects Compose UI Material Surface, Buttons, Tabs, Themes Compose Build time (development host) Runtime (on device) -
#AndroidDevSummit Compose UI Foundation Standard layouts, interactions Compose UI Core
Input, Measure, Layout, Drawing Compose UI Material Surface, Buttons, Tabs, Themes BUTTON Gestures, accessibility, & other internals -
Jetpack Compose • I/O 19, Unbundled • Announced Android Studio
4.0 Canary 1 in Android Dev Summit • Last Release 0.1.0-dev02 -
#AndroidDevSummit Using FragmentScenario @Test fun testEventFragment() { val scenario =
launchFragmentInContainer<MyFragment>() onView(withId(R.id.refresh)).perform(click()) scenario.onFrament { fragment -> // Check that the fragment handled the click correctly. } } -
#AndroidDevSummit Using FragmentScenario @Test fun testEventMoveToCreatedFragment() { val scenario =
launchFragmentInContainer<MyFragment>() scenario.moveToState(State.CREATED) } @Test fun testEventFragment() { val scenario = launchFragmentInContainer<MyFragment>() scenario.recreate() } -
#AndroidDevSummit Using FragmentFactory private class MyFactory() : FragmentFactory() { override
fun instantiate( classLoader: ClassLoader, className: String ) = when (className) { MyFragment::class.java.name -> MyFragment(...) else -> super.instantiate(classLoader, className) } } override fun onCreate(savedInstanceState: Bundle?) { supportFragmentManager.fragmentFactory = MyFactory() super.onCreate(savedInstanceState) } -
FragmentContainerView extends FrameLayout • Fragmentਊ Container ◦ AddView => IllegalStateException
• <fragment> / <FrameLayout>ܳ • Z-index ޙઁ ೧Ѿ • 3о সਸ ೯ ◦ ࢜۽ Fragment ੋझఢझ ࢤࢿ ◦ Fragment.onInflate(Context, AttributeSet, Bundle) ഐ ◦ FragmentTransactionਸ प೯ೞৈ FragmentManagerী ୶о -
// FirstFragment, SecondFragment parentFragmentManager .beginTransaction() .setCustomAnimations( R.anim.nav_enter, R.anim.nav_exit ) .replace(R.id.container,
/**...*/ ) .addToBackStack(null) .commit() FrameLayout Not desired result -
ViewModel ୡӝച // activity-ktx val activityVM : ActivityViewModel by viewModels()
// fragment-ktx val fragmentViewModel : MyViewModel by viewModels() val activityViewModel : MyViewModel by activityViewModels() // navigation-ktx val navGraphViewModel: MyViewModel by navGraphViewModels(R.id.main) -
ROOM #AndroidDevSummit • RxJava/Coroutines ਗ • Raw ௪ܻ ਗ •
AndroidX ݃Ӓۨ࣌ • Full Text Search • Views • SQLiteী ೠ ୶࢚ച ҅க ઁҕ • ஹੌ ఋী Query • Migration ए • పझೞӝ ए • Last 2.2.2(Stable) -
#AndroidDevSummit Async queres Query Type Observable Read One-Shot Read /
Write Lifecycle RxJava Coroutines LiveData Publisher, Observable, Flowable Flowable N/A Single, Maybe, Completale suspend fun New! -
Flow 101 - Kotlin coroutine primitive - Asynchronous - Cold
stream • transform stream (e.g. map, filter) • consume data (e.g. collect, single, toList) -
Flow Example fun getDogsToPet(limit: Int = -1): Flow<Dog> = flow
{ var i = 0 while (limit < 0 || i > limit) { emit(getRandomDog()) i++ } } -
ୡӝ DB ؘఠ ࢸ (җѢ) 1. Database ৌӝ 2. Schema
Ѩૐ 3. Database File ਫ਼Ә 4. Thread زӝച 5. Content ࠂࢎ 6. Database ײӝ #AndroidDevSummit -
#AndroidDevSummit • Easy to get off main thread • Minimal
boilerplate • Structured concurrency = managed jobs Coroutines -
#AndroidDevSummit class MyActivity : Activity { override fun onCreate(state: Bundle?)
{ super.onCreate(state) lifecycleScope.launch { // Run } lifecycleScope.launchWhenResumed { // Run } } } Activities & Fragments lifecycleScope.launchWhenResumed { // Run } -
#AndroidDevSummit ViewModel + LiveData class MainActivityViewModel : ViewModel { private
val _result = MutableLiveData<String>() val result = LiveData<String> = _result init { viewModelScope.launch { val computationResult = doComputation() _result.value = computationResult } } } -
#AndroidDevSummit liveData coroutine builder class MyViewModel { val result =
liveData { emit(doComputation()) } } After lifecycle 2.2 liveData(Dispatchers.IO) { emit(LOADING.STRING) emitSource(dataSource.fetchWeather()) } 1 2 LiveData -
emit N Values val currentWeather: LiveData<String> = dataSource.fetchWeather() LiveData →
LiveData Flow → LiveData val currentWeatherFlow: LiveData<String> = liveData { dataSource.fetchWeatherFlow().collect { emit(it) } } -
emit N Values LiveData → LiveData Flow → LiveData val
currentWeather: LiveData<String> = dataSource.fetchWeather() val currentWeatherFlow: LiveData<String> = dataSource.fetchWeatherFlow().asLiveData() -
emit 1 + emit N Values val currentWeather: LiveData<String> =
liveData { emit(LOADING_STRING) emitSource(dataSource.fetchWeather()) } LiveData → LiveData Flow → LiveData val currentWeatherFlow: LiveData<String> = liveData { emit(LOADING_STRING) emitSource( dataSource.fetchWeatherFlow().asLiveData() ) } -
emit 1 + emit N Values val currentWeather: LiveData<String> =
liveData { emit(LOADING_STRING) emitSource(dataSource.fetchWeather()) } LiveData → LiveData Flow → LiveData val currentWeatherFlow: LiveData<String> = dataSource.fetchWeatherFlow() .onStart { emit(LOADING_STRING) } .asLiveData() -
Suspend transformation val currentWeather: LiveData<String> = dataSource.fetchWeather().switchMap { liveData {
emit(heavyTransformation(it)) } } LiveData → LiveData -
Suspend transformation val currentWeather: LiveData<String> = dataSource.fetchWeather().switchMap { liveData {
emit(heavyTransformation(it)) } } LiveData → LiveData val currentWeatherFlow: LiveData<String> = dataSource.fetchWeatherFlow() .map { heavyTransformation(it) } .asLiveData() } Flow → LiveData -
#AndroidDevSummit +{API} New Capabilities • Tap to focus • Zoom
control • Device rotation info • Flash availability • And a lot more! -
#AndroidDevSummit HDR Extensions • Expanding footprint with additional manufactures •
Two lines of code enables Extensions across all of them! -
#AndroidDevSummit // Set up use case // Step 1. Config
val imageCapture = ImageCaptureConfig.Builder() .setTargetResolution(Size(600, 800)) .build() // Step 2. Bind CameraX.bindToLifecycle(this, imageCapture) // Step 3. Act imageCapture.takePicture(...) -
#AndroidDevSummit Beta 2019֙ 12ਘ !! • Stable API • Higher
code quality • Read for production -
What is benchmarking? - ٘ ࢿמ ஏ - ஏػ Ѿҗ۽
ѐࢶ ߈ - ۽Ӓ۔ प೯ Improving App Performance with Benchmarking (Google I/O ’19) Source : https://www.youtube.com/watch?v=ZffMCJdA5Qc -
Source : https://www.youtube.com/watch?v=ZffMCJdA5Qc Benchmark @Test fun myFirstBenchmark() { val worker
= TestListenableWorkerBuilder<MyWorker>(context).build() val COUNT = 5 // Small iteration count val start = System.nanoTime() // Measuring immediately for (i in 0..COUNT) { worker.doWork() } // Includes outliers! val elapsed = (System.nanoTime() - start) Log.d("Benchmark", "Time taken was $elapsed ns") } -
Source : https://www.youtube.com/watch?v=ZffMCJdA5Qc Benchmark @Test fun myFirstBenchmark() { val worker
= TestListenableWorkerBuilder<MyWorker>(context).build() val COUNT = 5 // Small iteration count val start = System.nanoTime() // Measuring immediately for (i in 0..COUNT) { worker.doWork() } // Includes outliers! val elapsed = (System.nanoTime() - start) Log.d("Benchmark", "Time taken was $elapsed ns") } -
Source : https://www.youtube.com/watch?v=ZffMCJdA5Qc Benchmark @Test fun myFirstBenchmark() { val worker
= TestListenableWorkerBuilder<MyWorker>(context).build() val COUNT = 5 // Small iteration count val start = System.nanoTime() // Measuring immediately for (i in 0..COUNT) { worker.doWork() } // Includes outliers! val elapsed = (System.nanoTime() - start) Log.d("Benchmark", "Time taken was $elapsed ns") } -
Source : https://www.youtube.com/watch?v=ZffMCJdA5Qc Benchmark @get:Rule val benchmarkRule = BenchmarkRule() @Test
fun myFirstBenchmark() { val worker = TestListenableWorkerBuilder<MyWorker>(context).build() val COUNT = 5 // Small iteration count val start = System.nanoTime() // Measuring immediately for (i in 0..COUNT) { worker.doWork() } // Includes outliers! val elapsed = (System.nanoTime() - start) Log.d("Benchmark", "Time taken was $elapsed ns") } -
Source : https://www.youtube.com/watch?v=ZffMCJdA5Qc Benchmark @get:Rule val benchmarkRule = BenchmarkRule() @Test
fun myFirstBenchmark() { val worker = TestListenableWorkerBuilder<MyWorker>(context).build() benchmarkRule.measureRepeated { worker.doWork() } } -
RecyclerView Benchmark @get:Rule val activityRule = ActivityTestRule(MainActivity::class.java) @get:Rule val benchmarkRule
= BenchmarkRule() @UiThreadTest @Test fun simpleScroll() { val recyclerView = activityRule.activity.recyclerView benchmarkRule.measureRepeated { val heightOfLastItem = recyclerView.getLastChild().height recyclerView.scrollBy(0, heightOfLastItem) } } https://github.com/android/performance-samples/blob/master/BenchmarkSample/benchmark/ src/androidTest/java/com/example/benchmark/RecyclerViewBenchmark.kt -
@get:Rule val activityRule = ActivityTestRule(MainActivity::class.java) @get:Rule val benchmarkRule = BenchmarkRule()
@UiThreadTest @Test fun simpleScroll() { val recyclerView = activityRule.activity.recyclerView benchmarkRule.measureRepeated { val heightOfLastItem = recyclerView.getLastChild().height recyclerView.scrollBy(0, heightOfLastItem) } } https://github.com/android/performance-samples/blob/master/BenchmarkSample/benchmark/ src/androidTest/java/com/example/benchmark/RecyclerViewBenchmark.kt RecyclerView Benchmark -
RecyclerView Benchmark @get:Rule val activityRule = ActivityTestRule(MainActivity::class.java) @get:Rule val benchmarkRule
= BenchmarkRule() @UiThreadTest @Test fun simpleScroll() { val recyclerView = activityRule.activity.recyclerView benchmarkRule.measureRepeated { val heightOfLastItem = recyclerView.getLastChild().height recyclerView.scrollBy(0, heightOfLastItem) } } https://github.com/android/performance-samples/blob/master/BenchmarkSample/benchmark/ src/androidTest/java/com/example/benchmark/RecyclerViewBenchmark.kt -
RecyclerView Benchmark @get:Rule val activityRule = ActivityTestRule(MainActivity::class.java) @get:Rule val benchmarkRule
= BenchmarkRule() @UiThreadTest @Test fun simpleScroll() { val recyclerView = activityRule.activity.recyclerView benchmarkRule.measureRepeated { val heightOfLastItem = recyclerView.getLastChild().height recyclerView.scrollBy(0, heightOfLastItem) } } https://github.com/android/performance-samples/blob/master/BenchmarkSample/benchmark/ src/androidTest/java/com/example/benchmark/RecyclerViewBenchmark.kt -
RecyclerView Benchmark @get:Rule val activityRule = ActivityTestRule(MainActivity::class.java) @get:Rule val benchmarkRule
= BenchmarkRule() @UiThreadTest @Test fun simpleScroll() { val recyclerView = activityRule.activity.recyclerView benchmarkRule.measureRepeated { val heightOfLastItem = recyclerView.getLastChild().height recyclerView.scrollBy(0, heightOfLastItem) } } https://github.com/android/performance-samples/blob/master/BenchmarkSample/benchmark/ src/androidTest/java/com/example/benchmark/RecyclerViewBenchmark.kt -
RecyclerView Benchmark @get:Rule val activityRule = ActivityTestRule(MainActivity::class.java) @get:Rule val benchmarkRule
= BenchmarkRule() @UiThreadTest @Test fun simpleScroll() { val recyclerView = activityRule.activity.recyclerView benchmarkRule.measureRepeated { val heightOfLastItem = recyclerView.getLastChild().height recyclerView.scrollBy(0, heightOfLastItem) } } https://github.com/android/performance-samples/blob/master/BenchmarkSample/benchmark/ src/androidTest/java/com/example/benchmark/RecyclerViewBenchmark.kt -
RecyclerView Benchmark @get:Rule val activityRule = ActivityTestRule(MainActivity::class.java) @get:Rule val benchmarkRule
= BenchmarkRule() @UiThreadTest @Test fun simpleScroll() { val recyclerView = activityRule.activity.recyclerView benchmarkRule.measureRepeated { val heightOfLastItem = recyclerView.getLastChild().height recyclerView.scrollBy(0, heightOfLastItem) } } https://github.com/android/performance-samples/blob/master/BenchmarkSample/benchmark/ src/androidTest/java/com/example/benchmark/RecyclerViewBenchmark.kt -
RecyclerView Benchmark @get:Rule val activityRule = ActivityTestRule(MainActivity::class.java) @get:Rule val benchmarkRule
= BenchmarkRule() @UiThreadTest @Test fun simpleScroll() { val recyclerView = activityRule.activity.recyclerView benchmarkRule.measureRepeated { val heightOfLastItem = recyclerView.getLastChild().height recyclerView.scrollBy(0, heightOfLastItem) } } https://github.com/android/performance-samples/blob/master/BenchmarkSample/benchmark/ src/androidTest/java/com/example/benchmark/RecyclerViewBenchmark.kt Without Time runWithTimingDisabled(…) -
CardView vs CardView Support (28.0.0-alpha01) AndroidX (1.0.0) 45 Removals, 44
Additions - JavaDoc : 38 lines - Package/Import : 5 lines - Empty Line : 1 lines Package name݅ ܴ -
Better package management ؕ Library ܻ࠙ => ؊ ࡅܲ
ܾܻૉ - Google Play Services - Firebase - ButterKnife - Mockito2 - SQL Delight - New libraries -
FIRST backup backup backup backup backup backup backup backup backup
backup backup backup backup backup backup backup backup backup backup backup backup backup backup backup backup backup backup backup backup backup -
Migration • Menu Bar Refactor > Migrate to AndroidX ࢶఖ
• External Librariesী AndroidX ֢द Opt 1. Gradle Sync Opt 2. Invalidate Caches / Restart • 100% ߸ജ উؼ ࣻ ਵ۽ ѐੋਵ۽ ࣻز উ -
Targeting API level 28 or lower dependencies { ... implementation
'com.android.support:appcompat-v7:28.0.0' implementation 'android.arch.lifecycle:extensions:1.1.1' ... } -
Targeting API level 29+ dependencies { ... implementation 'com.android.support:appcompat-v7:28.0.0' implementation
'android.arch.lifecycle:extensions:1.1.1' ... } -
gradle.properties # AndroidX package structure to make it clearer which
packages are bundled with the # Android operating system, and which are packaged with your app's APK android.useAndroidX=true # Automatically convert third-party libraries to use AndroidX android.enableJetifier=tru https://developer.android.com/studio/command-line/jetifier -
Jetifier AndroidX ಁఃܳ ࢎਊೡ ࣻ ب۾ Support Libraryܳ ݃Ӓۨ࣌ Migration
ߑߨ • Android Studio ղ Bundle۽ ػ Plugin ࢎਊ • ݃Ӓۨ࣌ https://developer.android.com/studio/command-line/jetifier -
Migration to AndroidX • Support Libraryח Deprecate • ઃо ೧ঠೡ
স ؊ ࢚ ইפ • Release Android 10 (2019.09.03) • ࢲ࠺झ ਃ ױ݈ Android 10 সؘ दӝ -
Stable Activity AppCompat Lifecycle Navigation Room Work Autofill Benchmark Biometric
Coordinator Layout Fragment Lifecycle ViewModel-SavedState Navigation RecyclerView ViewPager2 ConstraintLayout RC Beta Alpha Ads Browser Camera Car Compose Security