Pertanyaan Berbagi Kehidupan



Setiap pengembang muncul dengan pertanyaan tentang siklus hidup Aktivitas: apa itu layanan mengikat, bagaimana cara menyimpan keadaan antarmuka saat layar diputar, dan bagaimana Fragment berbeda dari Kegiatan.
Kami di FunCorp telah mengumpulkan daftar pertanyaan tentang topik serupa, tetapi dengan nuansa tertentu. Saya ingin membaginya dengan Anda.


1. Semua orang tahu bahwa jika Anda membuka aktivitas kedua di atas yang pertama dan memutar layar, maka rantai panggilan siklus hidup akan terlihat seperti ini:


Kegiatan Pembukaan

FirstActivity: onPause
SecondActivity: onCreate
SecondActivity: onStart
SecondActivity: onResume
FirstActivity: onSaveInstanceState
FirstActivity: onStop


Putar

SecondActivity: onPause
SecondActivity: onSaveInstanceState
SecondActivity: onStop
SecondActivity: onCreate
SecondActivity: onStart
SecondActivity: onRestoreInstanceState
SecondActivity: onResume


Kembali lagi

SecondActivity: onPause
FirstActivity: onCreate
FirstActivity: onStart
FirstActivity: onRestoreInstanceState
SecondActivity: onStop


Dan apa yang akan terjadi jika aktivitas kedua transparan?


Solusi


Dalam hal aktivitas top transparan, dalam hal logika, semuanya sedikit berbeda. Justru karena transparan, setelah rotasi, perlu untuk mengembalikan konten dan aktivitas yang langsung di bawahnya. Karenanya, urutan panggilan akan sedikit berbeda:


Kegiatan penemuan

FirstActivity: onPause
SecondActivity: onCreate
SecondActivity: onStart
SecondActivity: onResume


Putar

SecondActivity: onPause
SecondActivity: onSaveInstanceState
SecondActivity: onStop
SecondActivity: onCreate
SecondActivity: onStart
SecondActivity: onRestoreInstanceState
SecondActivity: onResume
FirstActivity: onSaveInstanceState
FirstActivity: onStop
FirstActivity: onCreate
FirstActivity: onStart
FirstActivity: onRestoreInstanceState
FirstActivity: onResume
FirstActivity: onPause


2. Tidak ada aplikasi yang dapat dilakukan tanpa menambahkan tampilan secara dinamis, tetapi terkadang Anda harus memindahkan tampilan yang sama di antara layar yang berbeda. Bisakah objek yang sama ditambahkan secara bersamaan ke dua kegiatan yang berbeda? Apa yang terjadi jika saya membuatnya dengan konteks Aplikasi dan ingin menambahkannya ke aktivitas yang berbeda secara bersamaan?


Mengapa ini dibutuhkan?
Ada pustaka "tidak terlalu menyenangkan" yang menyimpan logika bisnis penting di dalam tampilan khusus, dan menciptakan kembali pandangan ini dalam setiap aktivitas baru adalah keputusan yang buruk, karena Saya ingin memiliki satu set data.



Solusi


Tidak ada yang mencegah membuat tampilan dengan konteks Aplikasi. Ini hanya akan menerapkan gaya default yang tidak terkait dengan aktivitas apa pun. Anda juga dapat memindahkan tampilan ini di antara berbagai aktivitas tanpa masalah, tetapi Anda perlu memastikan bahwa itu ditambahkan hanya ke satu orangtua


private void addViewInner(View child, int index, LayoutParams params, boolean preventRequestLayout) { ... if (child.getParent() != null) { throw new IllegalStateException("The specified child already has a parent. " + "You must call removeView() on the child's parent first."); } ... } 

Anda dapat, misalnya, berlangganan ActivityLifecycleCallbacks, menghapus onStop (removeView) dari aktivitas saat ini, tambahkan onStart ke open berikutnya (addView).


3. Fragmen dapat ditambahkan melalui tambah dan ganti. Dan apa perbedaan antara kedua opsi ini dalam hal urutan metode siklus hidup? Apa kelebihan masing-masing?


Solusi


Bahkan jika Anda menambahkan fragmen melalui ganti, ini tidak berarti bahwa itu sepenuhnya diganti. Ini berarti bahwa pada titik ini dalam wadah tampilan akan diganti, oleh karena itu, onDestroyView akan dipanggil untuk fragmen saat ini, dan onCreateView akan dipanggil lagi ketika kembali.



Ini cukup banyak mengubah aturan permainan. Anda harus melepaskan semua pengontrol dan kelas yang terkait dengan UI di onDestroyView. Penting untuk memisahkan dengan jelas penerimaan data yang dibutuhkan oleh fragmen dan pengisian tampilan (daftar, dll.), Karena mengisi dan merusak tampilan akan terjadi jauh lebih sering daripada menerima data (membaca beberapa data dari database).


Ada juga nuansa dengan pemulihan negara: misalnya, onSaveInstanceState kadang-kadang muncul setelah onDestroyView. Selain itu, harus diingat bahwa jika null masuk pada onViewStateRestored, maka ini berarti Anda tidak perlu mengembalikan apa pun, tetapi tidak mengatur ulang ke keadaan default.


Jika kita berbicara tentang kenyamanan antara tambah dan ganti, maka ganti lebih hemat dalam memori jika Anda memiliki navigasi dalam (kami memiliki kedalaman navigasi pengguna dari salah satu KPI produk). Juga jauh lebih nyaman untuk mengganti bilah alat dengan penggantian, karena di onCreateView Anda dapat memasangnya kembali. Dari kelebihan menambahkan: lebih sedikit masalah dengan siklus hidup, ketika Anda kembali, saya tidak membuat ulang tampilan dan tidak perlu mengisi ulang apa pun.


4. Terkadang Anda masih harus bekerja secara langsung dengan layanan dan bahkan dengan layanan mengikat. Aktivitas berinteraksi dengan salah satu layanan ini (hanya satu aktivitas). Terhubung ke layanan dan transfer data ke sana. Saat Anda memutar layar, aktivitas kami hancur, dan kami berkewajiban untuk pulih dari layanan ini. Tetapi jika tidak ada koneksi, maka layanan dihancurkan, dan setelah berbalik mengikat akan menjadi layanan yang sama sekali berbeda. Bagaimana cara memastikan bahwa ketika Anda menghidupkan layanan tetap hidup?


Solusi


Jika Anda tahu solusi yang bagus, maka tulis di komentar. Hanya sesuatu yang serupa yang terlintas dalam pikiran:


  @Override protected void onDestroy() { super.onDestroy(); ThreadsUtils.postOnUiThread(new Runnable() { @Override public void run() { unbindService(mConnection); } }); } 

5. Baru-baru ini, kami redid navigasi di dalam aplikasi kami pada Kegiatan Tunggal (menggunakan salah satu perpustakaan yang tersedia). Sebelumnya, setiap layar aplikasi adalah aktivitas yang terpisah, sekarang navigasi berfungsi pada fragmen. Masalah kembali ke aktivitas di tengah tumpukan diselesaikan dengan bendera niat. Bagaimana saya bisa kembali ke fragmen di tengah tumpukan?


Solusi


Ya, FragmentManager tidak memberikan solusi di luar kotak. Cicerone melakukan sesuatu yang serupa di dalam dirinya:


  protected void backTo(BackTo command) { String key = command.getScreenKey(); if (key == null) { backToRoot(); } else { int index = localStackCopy.indexOf(key); int size = localStackCopy.size(); if (index != -1) { for (int i = 1; i < size - index; i++) { localStackCopy.pop(); } fragmentManager.popBackStack(key, 0); } else { backToUnexisting(command.getScreenKey()); } } } 

6. Juga, baru-baru ini kami menyingkirkan komponen yang tidak efisien dan kompleks seperti ViewPager, karena logika berinteraksi dengannya sangat rumit, dan perilaku fragmen tidak dapat diprediksi dalam kasus-kasus tertentu. Dalam beberapa fragmen, kami menggunakan fragmen dalam. Apa yang terjadi ketika menggunakan fragmen di dalam elemen RecycleView?


Solusi


Secara umum, tidak akan ada yang salah. Fragmen akan ditambahkan dan ditampilkan tanpa masalah. Satu-satunya hal yang kita hadapi adalah ketidakkonsistenan dengan siklus hidupnya. Implementasi pada ViewPager mengelola siklus hidup fragmen melalui setUserVisibleHint, dan RecycleView melakukan segala sesuatu di dahi tanpa memikirkan visibilitas aktual dan aksesibilitas fragmen.


7. Semua karena alasan yang sama kami beralih dari ViewPager, kami mengalami masalah memulihkan kondisi. Dalam kasus fragmen, ini diterapkan oleh kerangka kerja: di tempat yang tepat, kami cukup mendefinisikan ulang diSaveInstanceState dan menyimpan semua data yang diperlukan ke Bundle. Saat Anda membuat ulang ViewPager, semua fragmen dipulihkan oleh FragmentManager dan mengembalikan statusnya. Apa yang harus dilakukan dengan RecycleView dan ViewHolder-nya?


Solusi


"Anda harus menulis semuanya ke database dan membacanya setiap waktu," kata Anda. Atau logika untuk menyimpan keadaan harus di luar, dan daftar itu hanya tampilan. Di dunia yang ideal itu. Tetapi dalam kasus kami, setiap elemen dari daftar adalah layar yang kompleks dengan logikanya sendiri. Oleh karena itu, saya harus menciptakan sepeda saya dengan gaya "mari kita lakukan logika yang sama seperti pada ViewPager dan fragmen":


Adaptor
 public class RecycleViewGalleryAdapter extends RecyclerView.Adapter<GalleryItemViewHolder> implements GalleryAdapter { private static final String RV_STATE_KEY = "RV_STATE"; @Nullable private Bundle mSavedState; @Override public void onBindViewHolder(GalleryItemViewHolder holder, int position) { if (holder.isAttached()) { holder.detach(); } holder.attach(createArgs(position, getItemViewType(position))); restoreItemState(holder); } @Override public void saveState(Bundle bundle) { Bundle adapterState = new Bundle(); saveItemsState(adapterState); bundle.putBundle(RV_STATE_KEY, adapterState); } @Override public void restoreState(@Nullable Bundle bundle) { if (bundle == null) { return; } mSavedState = bundle.getBundle(RV_STATE_KEY); } private void restoreItemState(GalleryItemViewHolder holder) { if (mSavedState == null) { holder.restoreState(null); return; } String stateKey = String.valueOf(holder.getGalleryItemId()); Bundle state = mSavedState.getBundle(stateKey); if (state == null) { holder.restoreState(null); mSavedState = null; return; } holder.restoreState(state); mSavedState.remove(stateKey); } private void saveItemsState(Bundle outState) { GalleryItemHolder holder = getCurrentGalleryViewItem(); saveItemState(outState, (GalleryItemViewHolder) holder); } private void saveItemState(Bundle bundle, GalleryItemViewHolder holder) { Bundle itemState = new Bundle(); holder.saveState(itemState); bundle.putBundle(String.valueOf(holder.getGalleryItemId()), itemState); } } 

Di Fragment.onSaveInstanceState kita membaca status pemegang yang kita butuhkan dan memasukkannya ke dalam Bundle. Saat membuat kembali pemegang, kami mendapatkan Bundel yang disimpan dan di onBindViewHolder kami melewati negara yang ditemukan di dalam pemegang:


8. Dengan apa ini mengancam kita?


  @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity); ViewGroup root = findViewById(R.id.default_id); ViewGroup view1 = new LinearLayout(this); view1.setId(R.id.default_id); root.addView(view1); ViewGroup view2 = new FrameLayout(this); view2.setId(R.id.default_id); view1.addView(view2); ViewGroup view3 = new RelativeLayout(this); view3.setId(R.id.default_id); view2.addView(view3); } 

Solusi


Padahal, tidak ada yang salah dengan itu. Dalam RecycleView yang sama, daftar elemen dengan id yang sama disimpan. Namun, masih ada sedikit nuansa:


  @Override protected <T extends View> T findViewTraversal(@IdRes int id) { if (id == mID) { return (T) this; } final View[] where = mChildren; final int len = mChildrenCount; for (int i = 0; i < len; i++) { View v = where[i]; if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) { v = v.findViewById(id); if (v != null) { return (T) v; } } } return null; } 

Harus hati-hati jika kita memiliki elemen dengan id yang sama dalam hierarki, karena itu selalu elemen pertama yang ditemukan yang selalu dikembalikan, dan pada tingkat yang berbeda dari panggilan findViewById itu bisa menjadi objek yang berbeda.


9. Anda jatuh dari TooLargeTransaction ketika Anda memutar layar (ya, di sini ViewPager kita masih secara tidak langsung harus disalahkan). Bagaimana cara menemukan pelakunya?


Solusi


Ini cukup sederhana: menggantung ActivityLifecycleCallbacks pada Aplikasi, menangkap semua diActivitySaveInstanceState dan parsing semua yang ada di dalam Bundle. Di sana Anda bisa mendapatkan status semua tampilan dan semua fragmen di dalam aktivitas ini.


Di bawah ini adalah contoh bagaimana kita mendapatkan status fragmen dari Bundle:


 /** * Tries to find saved [FragmentState] in bundle using 'android:support:fragments' key. */ fun Bundle.getFragmentsStateList(): List<FragmentBundle>? { try { val fragmentManagerState: FragmentManagerState? = getParcelable("android:support:fragments") val active = fragmentManagerState?.mActive ?: return emptyList() return active.filter { it.mSavedFragmentState != null }.map { fragmentState -> FragmentBundle(fragmentState.mClassName, fragmentState.mSavedFragmentState) } } catch (throwable: Throwable) { Assert.fail(throwable) return null } } fun init() { application.registerActivityLifecycleCallbacks(object : SimpleActivityLifecycleCallback() { override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle?) { super.onActivitySaveInstanceState(activity, outState) outState?.let { ThreadsUtils.runOnMainThread { trackActivitySaveState(activity, outState) } } } }) } @MainThread private fun trackActivitySaveState(activity: Activity, outState: Bundle) { val sizeInBytes = outState.getSizeInBytes() val fragmentsInfos = outState.getFragmentsStateList() ?.map { mapFragmentsSaveInstanceSaveInfo(it) } ... } 

Selanjutnya, kami cukup menghitung ukuran Bundel dan mencatatnya:


  fun Bundle.getSizeInBytes(): Int { val parcel = Parcel.obtain() return try { parcel.writeValue(this) parcel.dataSize() } finally { parcel.recycle() } } 

10. Misalkan kita memiliki aktivitas dan serangkaian dependensi padanya. Dalam kondisi tertentu, kami perlu membuat ulang satu set dependensi ini (misalnya, dengan mengklik percobaan tertentu dengan UI lain). Bagaimana kita menerapkan ini?


Solusi


Tentu saja, Anda dapat mengotak-atik bendera dan menjadikannya semacam aktivitas "kruk" memulai kembali melalui peluncuran niat. Tetapi pada kenyataannya, semuanya sangat sederhana - aktivitas memiliki metode buat ulang.


Kemungkinan besar, sebagian besar pengetahuan ini tidak akan berguna bagi Anda, karena Anda tidak datang kepada mereka masing-masing dari kehidupan yang baik. Namun, beberapa dari mereka menunjukkan dengan baik bagaimana seseorang tahu bagaimana bernalar dan mengusulkan solusi. Kami menggunakan pertanyaan serupa dalam wawancara. Jika Anda memiliki tugas menarik yang diminta untuk Anda selesaikan di wawancara, atau Anda atur sendiri, tulis dalam komentar - akan menarik untuk dibahas!

Source: https://habr.com/ru/post/id426215/


All Articles