Upgrade to Pro — share decks privately, control downloads, hide ads and more …

다시 살펴보는 AndroidX

다시 살펴보는 AndroidX

DroidKnights 2021에서 발표한 "다시 살펴보는 AndroidX" 자료 입니다.

354271902cd8ba2762d05b251dfa0f84?s=128

pluulove (노현석)

September 25, 2021
Tweet

Transcript

  1. ׮द ࢓ಝࠁח AndroidX ֢അࢳ @pluulove (KakaoBank)

  2. History View Compatibility Code Compatibility Etc 01 02 03 04

    INDEX
  3. ѐߊ੗о Ҋಿ૕ জਸ ࣚऔѱ ੘ࢿೡ ࣻ ੓ب۾ ೧઱ח ۄ੉࠳۞ܻ, بҳ,

    о੉٘ܳ ݽইك ઁಿ Jetpack AndroidX Jetpack੉ ୶ҳೞח о੉٘ۄੋਸ प୓ചೠ ۄ੉࠳۞ܻ
  4. • ೒ۖಬ ܾܻૉ৬ ܻ࠙ (= unbundled) • ߡӒ ࣻ੿ •

    ࢜۽਍ ӝמ بੑ • Ҋಿ૕੄ জਸ औѱ ੘ࢿ Jetpack&AndroidX੄ о஖
  5. History 01

  6. None
  7. https://google-developer-training.github.io/android-developer-fundamentals-course-concepts-v2 জ ѐߊਸ ਤೠ ۄ੉࠳۞ܻ ݽ਺. ಴ળ Android ೐ۨ੐ਕ௼ী ࠽٘غ૑

    ঋ਷ ৈ۞ ӝמਸ ઁҕೞݴ ੉੹ ੢஖ী ؀ೠ ੉੹ ߡ੹җ੄ ഐജࢿਸ ઁҕ. জী ੉۞ೠ ۄ੉࠳۞ܻܳ ನೣೞৈ ೧׼ ۄ੉࠳۞ܻ੄ ӝמਸ ా೤. Support Library Revision 1.March 2011
  8. Compatibility API ߡ੹ਸ Ҋ޹ೞ૑݈Ҋ, ਗೞח ز੘ী ૘઺ ViewCompat.animate() support-v4/v13 Added

    in API level 12 (Android 3.0) v4/v7/v13 ਷ ۄ੉࠳۞ܻ ࢎਊद ୭ࣗߡ੹ਸ ੄޷
  9. AppCompat Theme Backports UI Toolkit - UI ഐജࢿ ۨ੉য -

    ೐ۨ੐ਕ௼ܳ ୭न ࢚క۽ ਬ૑ - ࢜܂ѱ ୶оػ ӝמ/ࣘࢿਸ ଻਋ח ৉ೡ appcompat support-v4/v13
  10. Hight-level components recycler view appcompat support-v4/v13 ղࠗ ۄ੉࠳۞ܻ੄ ઙࣘࢿ੉ ೙ਃೠ

    ࢜۽਍ ۄ੉࠳۞ܻ
  11. Revision 26 ୭ࣗ SDK ߡ੹ਸ 
 Android 4.0 (API level

    14) 1.0.0-alpha01 AndroidX History Lesson https://developer.android.com/topic/libraries/support-library/rev-archive Revision 1/3 support-v4 support-v13 Revision 23 VectorDrawable Revision 21 cardview-v7 recyclerview-v7 palette-v7 leanback-v17 2011 2013 2014 2015 2017 2018 Revision 18 gridlayout-v7 appcompat-v7 mediarouter-v7 Revision 24.2.0 ୭ࣗ SDK ߡ੹ਸ 
 Android 2.3 (API level 9)
  12. Support Libraries Version (2017 ~ ) - com.android.support + support-v4

    + Fragments + NotificationCompat* + LocalBroadcastManager* + ViewPager* + PagerTabStrip* + DrawerLayout* + AccessibilityEventCompat + DatabaseUtilsCompat + ConnectivityManagerCompat + LruCache* + etc + support-v13 + appcompat-v7 + cardview-v7 ୭ࣗ ૑ਗ ߡ੹੉ ׮ܴ पઁח minSdkVersion=“14” ղࠗܳ ౵ঈೞӝ য۰਑ п ۄ੉࠳۞ܻ੄ ૑ਗ ೦ݾ੉ ঌӝ য۰਑ ױੌ সؘ੉౟ ࠛоמ ੌࠗ ӝמ݅ সؘ੉౟ೞ؊ۄب 
 ۄ੉࠳۞ܻ ੹୓੄ ߡ੹੉ ৢۄх
  13. View Compatibility 02

  14. View Compatibility ҳ ߡ੹ীࢲ ࢜܂ѱ ୶оغח 
 API৬੄ ഐജࢿ •

    AppCompat • Material Design • Emoji • etc Compat Standalone ׮নೠ ߡ੹ীࢲ ױة੸ਵ۽ 
 ࢎਊೡ ࣻ ੓ח UI ۄ੉࠳۞ܻ • CardView • Constraintlayout • etc
  15. Button? AppCompatButton? MaterialButton? Themeী ৔ೱ ߉਺ LayoutInflater

  16. Theme based properties ӝࠄ Buttonী হח ࣘࢿ

  17. LayoutInflater • ۨ੉ইਓ XML ౵ੌਸ ೧׼ View ѐ୓۽ ੋझఢझച •

    Activity.getLayoutIn fl ater() ഑਷ Context#getSystemService ܳ ా೧ࢲ ঳਺
  18. class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } } LayoutInflater Create view AppCompatActivity #setContentView AppCompatDelegateImpl #setContentView LayoutIn fl ater #in fl ate create XmlResourceParer & in fl ate view LayoutIn fl ater #tryCreateView AppCompatDelegateImpl #onCreateView tryCreateViewח জীࢲח ࢎਊ ࠛо XMLਸ ాೠ AttributeSet݅ ૑ਗ
  19. class AppCompatDelegateImpl extends AppCompatDelegate implements … { @Override public View

    createView(View parent, fi nal String name, @NonNull Context context, @NonNull AttributeSet attrs) { if (mAppCompatViewIn fl ater == null) { TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme); String viewIn fl aterClassName = a.getString(R.styleable.AppCompatTheme_viewIn fl aterClass); if (viewIn fl aterClassName == null) { mAppCompatViewIn fl ater = new AppCompatViewIn fl ater(); } else { try { Class<?> viewIn fl aterClass = Class.forName(viewIn fl aterClassName); mAppCompatViewIn fl ater = (AppCompatViewIn fl ater) viewIn fl aterClass.getDeclaredConstructor() .newInstance(); } catch (Throwable t) { mAppCompatViewIn fl ater = new AppCompatViewIn fl ater(); } } } … } create XmlResourceParer & in fl ate view LayoutIn fl ater #tryCreateView AppCompatDelegateImpl #onCreateView
  20. class AppCompatDelegateImpl extends AppCompatDelegate implements … { @Override public View

    createView(View parent, fi nal String name, @NonNull Context context, @NonNull AttributeSet attrs) { if (mAppCompatViewIn fl ater == null) { TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme); String viewIn fl aterClassName = a.getString(R.styleable.AppCompatTheme_viewIn fl aterClass); if (viewIn fl aterClassName == null) { mAppCompatViewIn fl ater = new AppCompatViewIn fl ater(); } else { try { Class<?> viewIn fl aterClass = Class.forName(viewIn fl aterClassName); mAppCompatViewIn fl ater = (AppCompatViewIn fl ater) viewIn fl aterClass.getDeclaredConstructor() .newInstance(); } catch (Throwable t) { mAppCompatViewIn fl ater = new AppCompatViewIn fl ater(); } } } … } create XmlResourceParer & in fl ate view LayoutIn fl ater #tryCreateView AppCompatDelegateImpl #onCreateView <?xml version="1.0" encoding="utf-8"?> <resources xmlns:tools="http://schemas.android.com/tools"> <style name="Base.V14.Theme.MaterialComponents.Light" parent="Base.V14.Theme.MaterialComponents.Light.Bridge"> <item name="viewIn fl aterClass">com.google.android.material.theme.MaterialComponentsViewIn fl ater</item> <!-- more --> </style> </resources> ప݃ী ੿੄ػ ViewInflater Load
  21. public class AppCompatViewIn fl ater { fi nal View createView(View

    parent, fi nal String name, …) { switch (name) { case "TextView": view = createTextView(context, attrs); verifyNotNull(view, name); break; case "ImageView": view = createImageView(context, attrs); verifyNotNull(view, name); break; case "Button": view = createButton(context, attrs); verifyNotNull break; … } return view; } ੉ܴী ݒடغח पઁ View ࢤࢿ
  22. public class MaterialComponentsViewIn fl ater extends AppCompatViewIn fl ater {

    @NonNull @Override protected AppCompatButton createButton(@NonNull Context context, @NonNull AttributeSet attrs) { return new MaterialButton(context, attrs); } @NonNull @Override protected AppCompatTextView createTextView(Context context, AttributeSet attrs) { return new MaterialTextView(context, attrs); } … } Material Design Themeীࢲ Button਷ MaterialButtonী ݒடؽ … } return view; } ੉ܴী ݒடغח पઁ View ࢤࢿ
  23. https://developer.android.com/reference/androidx/appcompat/app/AppCompatViewInflater • AppCompat ࢎਊ द, ۨ੉ইਓ ౵ੌী ࢎਊػ ਤઇٜਸ ഛ੢

    ѐ֛ • Android Widgetਸ ੗زਵ۽ ׮ܲ Widgetਵ۽ ؀୓ೞחؘ ࢎਊ • ప݃ܳ ా೧ࢲ ஂٙ (Default ViewIn fl ater : AppCompatViewIn fl ater) • ৘) Button ▶ AppCompatButton, MaterialButton ਵ۽ ؀୓ AppCompatViewInflater Android Widget ࢤࢿ ؀ܻ੗
  24. • জ ੹୓ী زੌೠ ٣੗ੋ ஹನք౟ ੸ਊ • XML ੘ࢿदীח

    Baseә੄ UI Component۽ ੘ࢿ • पઁ ੸ਊ਷ CustomViewܳ ੸ਊ Custom ViewInflater ੸ਊ
  25. <style name="Theme.DroidKnights2021Sample.Material.Sample"> <item name="viewIn fl aterClass">com.droidknights2021.sample.theme.SampleViewIn fl ater</item> </style> class

    SampleViewIn fl ater : MaterialComponentsViewIn fl ater() { override fun createButton(context: Context, attrs: AttributeSet): AppCompatButton { return DroidButton(context, attrs) } } class DroidButton @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null ) : MaterialButton(context, attrs) { init { setBackgroundColor(0xFFEC407A.toInt()) setIconResource(R.drawable.ic_android) iconTint = ColorStateList.valueOf(Color.WHITE) if (isInEditMode && text.isNullOrEmpty()) { text = "TEST" } } } Custom ViewInflater Sample Theme AppCompatViewInflater DroidButton TEST Material + DroidButton
  26. Custom ViewInflater Sample Theme ࢶఖ ഛੋ

  27. Custom ViewInflater Sample Layout Inspector

  28. Code Compatibility 03

  29. ٣߄੉झ੄ OS ߡ੹җ ೞ٘ਝয ١ী ৔ೱਸ ߉਺ • API ୶о

    ߂ Deprecated (ex : getColor, getDrawable) • OS ز੘੄ ߸҃ ؀਽ (ex : runtime-permissions) • ౠ੿ ߡ੹ ߡӒ ؀਽ ࠛоೖೠ ࠙ӝ୊ܻо ೙ਃ Configuration
  30. ContextCompat Drawableਸ оઉয়ח ߑߨ public static Drawable getDrawable(@NonNull Context context,

    @DrawableRes int id) { if (Build.VERSION.SDK_INT >= 21) { return Api21Impl.getDrawable(context, id); } else if (Build.VERSION.SDK_INT >= 16) { return context.getResources().getDrawable(id); } else { … } } https://developer.android.com/reference/androidx/core/content/ContextCompat#getDrawable(android.content.Context, %20int) context.getDrawable(id) ߡ੹݃׮ ୶оػ ੸੺ೠ API ࢎਊਸ ؀਽
  31. WindowInsetsCompat WindowInsets ૑ਗ + ࢜۽਍ OSী ୶оغח Window ؀਽ public

    class WindowInsetsCompat { @NonNull public static fi nal WindowInsetsCompat CONSUMED; static { if (SDK_INT >= 30) { CONSUMED = Impl30.CONSUMED; } else { CONSUMED = Impl.CONSUMED; } } } public WindowInsetsCompat( @Nullable fi nal WindowInsetsCompat src) { if (src != null) { fi nal Impl srcImpl = src.mImpl; if (SDK_INT >= 30 && srcImpl instanceof Impl30) { mImpl = new Impl30(this, (Impl30) srcImpl); } else if (SDK_INT >= 29 && srcImpl instanceof Impl29) { mImpl = new Impl29(this, (Impl29) srcImpl); } else if (SDK_INT >= 28 && srcImpl instanceof Impl28) { mImpl = new Impl28(this, (Impl28) srcImpl); } else if (SDK_INT >= 21 && srcImpl instanceof Impl21) { mImpl = new Impl21(this, (Impl21) srcImpl); } else if (SDK_INT >= 20 && srcImpl instanceof Impl20) { mImpl = new Impl20(this, (Impl20) srcImpl); } else { mImpl = new Impl(this); } srcImpl.copyWindowDataInto(this); } else { // Ideally src would be @NonNull, oh well. mImpl = new Impl(this); } } https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:core/core/src/main/java/androidx/ core/view/WindowInsetsCompat.java
  32. public class ViewCompat { public static void setElevation(@NonNull View view,

    fl oat elevation) { if (Build.VERSION.SDK_INT >= 21) { Api21Impl.setElevation(view, elevation); } } } ݽف ੸ਊغח Ѫ਷ ইפ׮ https://developer.android.com/reference/androidx/core/view/ViewCompat#setElevation(android.view.View,%20float) View#setElevation਷ API level 21ࠗఠ ૑ਗ
  33. • Activity/Fragmentীࢲ ਽׹чਸ ੹׳߉ח APIо ߸҃ؽ • (Add) Activity 1.2.0-alpha02

    / Fragment 1.3.0-alpha02 • (Deprecated) Activity 1.2.0-alpha04 / Fragment 1.3.0-alpha04 New Result API
  34. Deprecated Deprecated class SampleActivity : AppCompatActivity() { private val request_second_code

    = 100 private fun startSecondView() { val intent = Intent(this, ResultSecondActivity::class.java) startActivityForResult(intent, request_second_code) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == request_second_code) { // do action } } } New Result API with deprecated Activity 1.2.0-alpha02 / Fragment 1.3.0-alpha02
  35. class SampleActivity : AppCompatActivity() { private val requestActivity = registerForActivityResult(

    StartActivityForResult() ) { activityResult -> // do action } fun startSample() { // create intent requestActivity.launch(intent) } } Sample StartActivityForResult New Result API [ ✔︎ ] call startActivityForResult [ ✔︎ ] delegate onActivityResult [ ✔︎ ] call ActivityResultCallback
  36. import androidx.activity.result.registerForActivityResult class SampleActivity : AppCompatActivity() { private val requestPermission

    = registerForActivityResult( RequestPermission() ) { isGranted -> toast("Location granted: $isGranted") } private val requestLocation = registerForActivityResult( RequestPermission(), ACCESS_FINE_LOCATION ) { isGranted -> toast("Location granted: $isGranted") } } Sample Permission New Result API private fun sample1() { requestPermission.launch(ACCESS_FINE_LOCATION) } private fun sample2() { requestLocation.launch() } registerForActivityResult ktx registerForActivityResult [ ✔︎ ] call checkSelfPermission [ ✔︎ ] call requestPermissions [ ✔︎ ] delegate onRequestPermissionsResult [ ✔︎ ] call ActivityResultCallback
  37. ActivityResult Contract class SampleActivity : AppCompatActivity() { private val requestActivity

    = registerForActivityResult( StartActivityForResult() ) { activityResult -> // do action } } public static fi nal class StartActivityForResult extends ActivityResultContract<Intent, ActivityResult> { public static fi nal String EXTRA_ACTIVITY_OPTIONS_BUNDLE = "androidx.activity.result.contract.extra.ACTIVITY_OPTIONS_BUNDLE"; public StartActivityForResult() { } @NonNull public Intent createIntent(@NonNull Context context, @NonNull Intent input) { return input; } @NonNull public ActivityResult parseResult(int resultCode, @Nullable Intent intent) { return new ActivityResult(resultCode, intent); } } fun startSample() { // create intent requestActivity.launch(intent) } 1 2 3 class ActivityResultContract<I, O> { // … } ActivityResultContract StartActivityForResult Sample
  38. ActivityResult Contract ࢎ੹ ੿੄ػ ActivityResult • StartActivityForResult • StartIntentSenderForResult •

    RequestMultiplePermissions • RequestPermission • TakePicturePreview • TakePicture • TakeVideo • CaptureVideo • PickContact • GetContent • GetMultipleContents • OpenDocument • OpenMultipleDocuments • OpenDocumentTree • CreateDocument https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:activity/activity/src/main/java/androidx/ activity/result/contract/ActivityResultContracts.java
  39. ӝઓ ۽૒ ൔ੸ public class ComponentActivity extends androidx.core.app.ComponentActivity { private

    fi nal ActivityResultRegistry mActivityResultRegistry = new ActivityResultRegistry() { @Override public <I, O> void onLaunch( fi nal int requestCode, @NonNull ActivityResultContract<I, O> contract, I input, @Nullable ActivityOptionsCompat options) { // .. if (ACTION_REQUEST_PERMISSIONS.equals(intent.getAction())) { // requestPermissions path // .. ActivityCompat.requestPermissions(activity, permissions, requestCode); } else if (ACTION_INTENT_SENDER_REQUEST.equals(intent.getAction())) { // startIntentSenderForResult path // .. ActivityCompat.startIntentSenderForResult(/** ... */) } else { // startActivityForResult path ActivityCompat.startActivityForResult(activity, intent, requestCode, optionsBundle); } } }; https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:activity/ activity/src/main/java/androidx/activity/ComponentActivity.java
  40. ӝઓ ۽૒ ൔ੸ class ActivityResultContracts private constructor() { class RequestPermission

    : ActivityResultContract<String, Boolean>() { // .. override fun getSynchronousResult( context: Context, input: String ): SynchronousResult<Boolean>? { val granted = ContextCompat.checkSelfPermission( context, input ) == PackageManager.PERMISSION_GRANTED return if (granted) { SynchronousResult(true) } else { // proceed with permission request null } } } } https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:activity/ activity/src/main/java/androidx/activity/result/contract/ActivityResultContracts.kt
  41. ӝઓ ۽૒ ൔ੸ public class ComponentActivity extends androidx.core.app.ComponentActivity { private

    fi nal ActivityResultRegistry mActivityResultRegistry = new ActivityResultRegistry() { // .. }; @Deprecated protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { if (!mActivityResultRegistry.dispatchResult(requestCode, resultCode, data)) { super.onActivityResult(requestCode, resultCode, data); } } @Deprecated public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (!mActivityResultRegistry.dispatchResult(requestCode, Activity.RESULT_OK, /** ... */ )) { if (Build.VERSION.SDK_INT >= 23) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); } } } } https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:activity/ activity/src/main/java/androidx/activity/ComponentActivity.java
  42. Generate Request Code public abstract class ActivityResultRegistry { // Use

    upper 16 bits for request codes private static fi nal int INITIAL_REQUEST_CODE_VALUE = 0x00010000; private Random mRandom = new Random(); private int generateRandomNumber() { int number = mRandom.nextInt((Integer.MAX_VALUE - INITIAL_REQUEST_CODE_VALUE) + 1) + INITIAL_REQUEST_CODE_VALUE; while (mRcToKey.containsKey(number)) { number = mRandom.nextInt((Integer.MAX_VALUE - INITIAL_REQUEST_CODE_VALUE) + 1) + INITIAL_REQUEST_CODE_VALUE; } return number; } } https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:activity/activity/src/main/java/ androidx/activity/result/ActivityResultRegistry.java Range 65536 ~ Int.MAX_VALUE
  43. ComponentActivity FragmentActivity Fragment startActivityForResult() @Deprecated @SuppressWarnings 
 (“deprecation”) @Deprecated onActivityResult()

    @Deprecated @SuppressWarnings 
 (“deprecation”) @Deprecated requestPermissions() - - @Deprecated onRequestPermissionsResult() @Deprecated @SuppressWarnings 
 (“deprecation”) @Deprecated Deprecated
  44. Etc 04

  45. activity, appcompat, autofill, benchmark, biometric, constraintlayout, coordinatorlayout, core, datastore, drawerlayout,

    dynamicanimation, emoji, exifnterface, fragment, hilt, lifeclycle, navigation, paging, palettes, preference, print,recyclerview, room, slide, slidingpanelayout, startup, sqlite, swiperefreshlayout, test, transition, vectordrawable, viewpager, webkit, work camera, compose, enterprise, recommendation browser, leanback, media2 security, tvprovider, wear wear compose https://androidx.tech/ minSdkVersion
  46. • ઁઑࢎ ഑਷ ۠୊ীࢲ ઁҕೞח ӝמ • ৘) জ ই੉௑੄

    ࡈр࢝ ߙ૑ + ं੗ ஠਍౟ • ݽٚ ߡ੹ীࢲ زੌೞѱ ز੘ উغח Ѫ
  47. Alpha Beta RC Stable Release stage ੣਷ ѐߊ API੄ ୶о/߸҃/࢏ઁ

    рױೠ పझ౟ ాҗ ӝמ উ੿ച ױ҅ ௼ܻ౭ஸ ੉ग ഑਷ 
 ೖ٘ߔਵ۽ ੋ೧ 
 API ߸҃ оמ API ഛ੿ ױ҅
  48. ߡӒ ߊѼद ࡅܰѱ ੉ग ౟ېழী ١۾. https://issuetracker.google.com/issues

  49. Reference • Android Support Library Overview • https://www.youtube.com/watch?v=3PIc-DuEU2s • Support

    Library: Guts and Glory (Android Dev Summit 2015) • https://www.youtube.com/watch?v=ihQ16K8gSuQ • What's new in Android Support Library (Google I/O '17) • https://youtu.be/V6-roIeNUY0 • Android GitHub : https://github.com/androidx/androidx • Disabling Jetifier : https://adambennett.dev/2020/08/disabling-jetifier/ • Say bye-bye to Android Jetifier • https://medium.com/dipien/say-bye-bye-to-android-jetifier-a7e0d388f5d6
  50. Thank you! pluu (pluulove@gmail.com) Contact me