ActivityLifecycleCallbacks - نقطة عمياء في واجهة برمجة التطبيقات العامة



منذ الطفولة ، أحب قراءة التعليمات. لقد نشأت ، لكن ما زال يذهلني كيف يهتم البالغون بالتعليمات: يعتقد الكثير منهم أن الجميع يعرفون ، وفي الوقت نفسه يستخدمون وظيفة أو وظيفتين ، بينما يوجد الكثير منهم! كم كنت تستخدم للحفاظ على درجة الحرارة في الميكروويف؟ وهي في كل شخص تقريبًا.

بمجرد أن قررت قراءة الوثائق لفئات مختلفة من إطار أندرويد. ركضت خلال الفئات الرئيسية: عرض ، نشاط ، جزء ، تطبيق ، وكنت مهتمًا جدًا بطريقة Application.registerActivityLifecycleCallbacks () وواجهة ActivityLifecycleCallbacks . من أمثلة استخدامه على الإنترنت ، لم يكن هناك شيء أفضل من تسجيل دورة حياة النشاط. ثم بدأت تجربتي بنفسي ، والآن نحن في Yandex.Money نستخدمها بنشاط لحل مجموعة كاملة من المهام المتعلقة بتأثير كائنات النشاط من الخارج.

ما هي ActivityLifecycleCallbacks؟


انظر إلى هذه الواجهة ، إليك ما بدا عليه عندما ظهر في API 14:

public interface ActivityLifecycleCallbacks {    void onActivityCreated(Activity activity, Bundle savedInstanceState);    void onActivityStarted(Activity activity);    void onActivityResumed(Activity activity);    void onActivityPaused(Activity activity);    void onActivityStopped(Activity activity);    void onActivitySaveInstanceState(Activity activity, Bundle outState);    void onActivityDestroyed(Activity activity); } 

بدءًا من API 29 ، تمت إضافة عدة طرق جديدة إليه
 public interface ActivityLifecycleCallbacks {   default void onActivityPreCreated( @NonNull Activity activity, @Nullable Bundle savedInstanceState) { }   void onActivityCreated( @NonNull Activity activity, @Nullable Bundle savedInstanceState);   default void onActivityPostCreated( @NonNull Activity activity, @Nullable Bundle savedInstanceState) { }   default void onActivityPreStarted(@NonNull Activity activity) { }   void onActivityStarted(@NonNull Activity activity);   default void onActivityPostStarted(@NonNull Activity activity) { }   default void onActivityPreResumed(@NonNull Activity activity) { }   void onActivityResumed(@NonNull Activity activity);   default void onActivityPostResumed(@NonNull Activity activity) { }   default void onActivityPrePaused(@NonNull Activity activity) { }   void onActivityPaused(@NonNull Activity activity);   default void onActivityPostPaused(@NonNull Activity activity) { }   default void onActivityPreStopped(@NonNull Activity activity) { }   void onActivityStopped(@NonNull Activity activity);   default void onActivityPostStopped(@NonNull Activity activity) { }   default void onActivityPreSaveInstanceState( @NonNull Activity activity, @NonNull Bundle outState) { }   void onActivitySaveInstanceState( @NonNull Activity activity, @NonNull Bundle outState);   default void onActivityPostSaveInstanceState( @NonNull Activity activity, @NonNull Bundle outState) { }   default void onActivityPreDestroyed(@NonNull Activity activity) { }   void onActivityDestroyed(@NonNull Activity activity);   default void onActivityPostDestroyed(@NonNull Activity activity) { } } 


ربما يتم إعطاء هذه الواجهة القليل من الاهتمام لأنها ظهرت فقط في Android 4.0 ICS. ولكن دون جدوى ، لأنه يتيح لك القيام بشيء مثير للاهتمام للغاية: التأثير على جميع كائنات النشاط من الخارج. ولكن المزيد عن ذلك لاحقًا ، وألقِ نظرة فاحصة على الأساليب.

تعرض كل طريقة طريقة مماثلة لدورة حياة النشاط ويتم استدعاؤها في الوقت الذي يتم فيه تشغيل الطريقة على أي نشاط في التطبيق. وهذا هو ، إذا بدأ التطبيق مع MainActivity ، ثم الأول سوف نتلقى مكالمة إلى ActivityLifecycleCallback.onActivityCreated (MainActivity ، خالية).

عظيم ، ولكن كيف يعمل؟ لا يوجد سحر هنا: النشاط نفسه يبلغ عن الحالة فيه. فيما يلي جزء من التعليمات البرمجية من Activity.onCreate ():

  mFragments.restoreAllState(p, mLastNonConfigurationInstances != null           ? mLastNonConfigurationInstances.fragments : null); } mFragments.dispatchCreate(); getApplication().dispatchActivityCreated(this, savedInstanceState); if (mVoiceInteractor != null) { 

يبدو كما لو أننا أنفسنا فعلنا BaseActivity. قام بذلك الزملاء من Android فقط ، كما ألزم الجميع باستخدامه. وهذا جيد جدا!

في API 29 ، تعمل هذه الطرق بالطريقة نفسها تقريبًا ، ولكن يتم استدعاء نسخ Pre و Post الخاصة بهم بصدق قبل وبعد أساليب محددة. من المحتمل أن يتم التحكم به الآن بواسطة ActivityManager ، لكن هذا مجرد تخميني ، لأنني لم أذهب إلى المصدر بما فيه الكفاية لمعرفة ذلك.

كيفية جعل ActivityLifecycleCallbacks تعمل؟


مثل كل عمليات الاسترجاعات ، يجب عليك أولاً تسجيلها. نقوم بتسجيل جميع ActivityLifecycleCallbacks في Application.onCreate () ، حتى نحصل على معلومات حول كل النشاط والقدرة على إدارتها.

 class MyApplication : Application() {   override fun onCreate() { super.onCreate() registerActivityLifecycleCallbacks(MyCallbacks()) } } 

استطراد صغير: بدءًا من API 29 ، يمكن أيضًا تسجيل ActivityLifecycleCallbacks من داخل النشاط. سيكون هذا رد اتصال محلي يعمل فقط لهذا النشاط.

هذا كل شيء. ولكن يمكنك العثور على هذا ببساطة عن طريق إدخال اسم ActivityLifecycleCallbacks في مربع البحث. سيكون هناك العديد من الأمثلة لتسجيل دورة حياة النشاط ، ولكن هل هذا مثير للاهتمام؟ يحتوي النشاط على العديد من الطرق العامة (حوالي 400) ، ويمكن استخدام كل هذا للقيام بالعديد من الأشياء المثيرة للاهتمام والمفيدة.

ما الذي يمكن القيام به مع هذا؟


ماذا تريد؟ هل تريد تغيير السمة ديناميكيًا في كل نشاط في التطبيق؟ من فضلك: طريقة setTheme () عامة ، مما يعني أنه يمكنك الاتصال بها من ActivityLifecycleCallback:

 class ThemeCallback( @StyleRes val myTheme: Int ) : ActivityLifecycleCallbacks { override fun onActivityCreated( activity: Activity, savedInstanceState: Bundle? ) { activity.setTheme(myTheme) } } 

كرر هذه الخدعة فقط في المنزل
يمكن لبعض الأنشطة من المكتبات المتصلة استخدام سماتها المخصصة. لذلك ، تحقق من الحزمة أو أي علامة أخرى يمكن من خلالها تحديد أن موضوع هذا النشاط يمكن تغييره بأمان. على سبيل المثال ، نتحقق من الحزمة مثل هذه (في Kotlinovsky =)):

 class ThemeCallback( @StyleRes val myTheme: Int ) : ActivityLifecycleCallbacks { override fun onActivityCreated( activity: Activity, savedInstanceState: Bundle? ) { val myPackage = "my.cool.application" activity .takeIf { it.javaClass.name.startsWith(myPackage) } ?.setTheme(myTheme) } } 

هل المثال لا يعمل؟ ربما نسيت تسجيل ThemeCallback في التطبيق أو التطبيق في AndroidManifest.

تريد مثالا آخر للاهتمام؟ يمكنك إظهار مربعات الحوار على أي نشاط في التطبيق.

 class DialogCallback( val dialogFragment: DialogFragment ) : Application.ActivityLifecycleCallbacks { override fun onActivityCreated( activity: Activity, savedInstanceState: Bundle? ) { if (savedInstanceState == null) { val tag = dialogFragment.javaClass.name (activity as? AppCompatActivity) ?.supportFragmentManager ?.also { fragmentManager -> if (fragmentManager.findFragmentByTag(tag) == null) { dialogFragment.show(fragmentManager, tag) } } } } } 

كرر هذه الخدعة فقط في المنزل
بالطبع ، يجب ألا تظهر حوارًا على كل شاشة - فلن يحبنا مستخدمونا لهذا الغرض. لكن في بعض الأحيان قد يكون من المفيد إظهار شيء مثل هذا على بعض الشاشات المحددة.

وإليك مثال آخر: ما إذا كنا بحاجة إلى بدء نشاط ما ، كل شيء بسيط هنا: Activity.startActivity () - وقادته. ولكن ماذا لو كنا بحاجة إلى انتظار النتيجة بعد استدعاء Activity.startActivityForResult ()؟ لدي وصفة واحدة:

 class StartingActivityCallback : Application.ActivityLifecycleCallbacks { override fun onActivityCreated( activity: Activity, savedInstanceState: Bundle? ) { if (savedInstanceState == null) { (activity as? AppCompatActivity) ?.supportFragmentManager ?.also { fragmentManager -> val startingFragment = findOrCreateFragment(fragmentManager) startingFragment.listener = { resultCode, data -> // handle response here } // start Activity inside StartingFragment } } } private fun findOrCreateFragment( fragmentManager: FragmentManager ): StartingFragment { val tag = StartingFragment::class.java.name return fragmentManager .findFragmentByTag(tag) as StartingFragment? ?: StartingFragment().apply { fragmentManager .beginTransaction() .add(this, tag) .commit() } } } 

في المثال ، نقوم فقط بإسقاط Fragment ، الذي يبدأ النشاط ويحصل على النتيجة ، ثم يفوض معالجته إلينا. كن حذرًا: هنا نتحقق من أن نشاطنا هو AppCompatActivity ، مما قد يؤدي إلى حلقة لا نهائية. استخدام شروط أخرى.

دعونا تعقيد الأمثلة. حتى تلك اللحظة ، استخدمنا فقط تلك الأساليب الموجودة بالفعل في النشاط. ماذا عن إضافة الخاصة بك؟ لنفترض أننا نريد إرسال تحليلات حول فتح الشاشة. في الوقت نفسه ، فإن شاشاتنا لها أسماء خاصة بها. كيفية حل هذه المشكلة؟ بسيط جدا قم بإنشاء واجهة شاشة يمكنها إعطاء اسم الشاشة:

 interface Screen { val screenName: String } 

الآن ننفذه في النشاط المطلوب:

 class NamedActivity : Activity(), Screen { override val screenName: String = "First screen" } 

بعد ذلك ، قمنا بإعداد ActivityLifecycleCallbacks خاص لمثل هذا النشاط:

 class AnalyticsActivityCallback( val sendAnalytics: (String) -> Unit ) : Application.ActivityLifecycleCallbacks { override fun onActivityCreated( activity: Activity, savedInstanceState: Bundle? ) { if (savedInstanceState == null) { (activity as? Screen)?.screenName?.let(sendAnalytics) } } } 

ترى؟ نحن فقط نتحقق من الواجهة ونرسل التحليلات إذا تم تنفيذها.

كرر لإصلاح. ماذا تفعل إذا كنت بحاجة لرمي بعض المعلمات؟ تمديد الواجهة:

 interface ScreenWithParameters : Screen { val parameters: Map<String, String> } 

ننفذ:

 class NamedActivity : Activity(), ScreenWithParameters { override val screenName: String = "First screen" override val parameters: Map<String, String> = mapOf("key" to "value") } 

نحن نشحن:

 class AnalyticsActivityCallback( val sendAnalytics: (String, Map<String, String>?) -> Unit ) : Application.ActivityLifecycleCallbacks { override fun onActivityCreated( activity: Activity, savedInstanceState: Bundle? ) { if (savedInstanceState == null) { (activity as? Screen)?.screenName?.let { name -> sendAnalytics( name, (activity as? ScreenWithParameters)?.parameters ) } } } } 

لكنها لا تزال سهلة. كل هذا كان فقط لإحضارك إلى موضوع مثير للاهتمام: حقن التبعية الأصلية. نعم ، لدينا خنجر ، كوين ، Guice ، Kodein وأكثر من ذلك. ولكن في المشاريع الصغيرة ، فهي زائدة عن الحاجة. ولكن لدي حل ... تخمين أي واحد؟

دعنا نقول أن لدينا بعض الأدوات مثل هذا:

 class CoolToolImpl { val extraInfo = "i am dependency" } 

أغلقه مع الواجهة ، مثل المبرمجين البالغين:

 interface CoolTool { val extraInfo: String } class CoolToolImpl : CoolTool { override val extraInfo = "i am dependency" } 

والآن بسحر شارع صغير من ActivityLifecycleCallbacks: سنقوم بإنشاء واجهة لتطبيق هذه التبعية وتنفيذها في الأنشطة الضرورية ، وباستخدام ActivityLifecycleCallbacks سنجده وننفذ تطبيق CoolToolImpl.

 interface RequireCoolTool { var coolTool: CoolTool } class CoolToolActivity : Activity(), RequireCoolTool { override lateinit var coolTool: CoolTool } class InjectingLifecycleCallbacks : ActivityLifecycleCallbacks { override fun onActivityCreated( activity: Activity, savedInstanceState: Bundle? ) { (activity as? RequireCoolTool)?.coolTool = CoolToolImpl() } } 

تذكر تسجيل InjectingLifecycleCallbacks في التطبيق الخاص بك ، قم بتشغيله ويعمل.

ولا تنسى اختبار:

 @RunWith(AndroidJUnit4::class) class DIActivityTest { @Test fun `should access extraInfo when created`() { // prepare val mockTool: CoolTool = mock() val application = getApplicationContext<android.app.Application>() application.registerActivityLifecycleCallbacks( object : Application.ActivityLifecycleCallbacks { override fun onActivityCreated( activity: Activity, savedInstanceState: Bundle? ) { (activity as? RequireCoolTool)?.coolTool = mockTool } }) // invoke launch<DIActivity>(Intent(application, DIActivity::class.java)) // assert verify(mockTool).extraInfo } } 

ولكن في المشروعات الكبيرة ، لن يتطور هذا النهج بشكل جيد ، لذلك لن أسلب أي أطر عمل DI من أي شخص. حيث من الأفضل الجمع بين الجهود وتحقيق التآزر. سأريكم مثال Dagger2. إذا كان لديك بعض النشاط الأساسي في المشروع يقوم بشيء مثل AndroidInjection.inject (هذا) ، فعندئذ حان الوقت لرميها بعيدًا. بدلاً من ذلك ، قم بما يلي:
  1. وفقًا للتعليمات ، ننفذ تطبيق DispatchingAndroidInjector ؛
  2. إنشاء ActivityLifecycleCallbacks يستدعي DispatchingAndroidInjector.maybeInject () على كل نشاط ؛
  3. تسجيل ActivityLifecycleCallbacks في التطبيق.


 class MyApplication : Application() { @Inject lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any> override fun onCreate() { super.onCreate() DaggerYourApplicationComponent.create().inject(this); registerActivityLifecycleCallbacks( InjectingLifecycleCallbacks( dispatchingAndroidInjector ) ) } } class InjectingLifecycleCallbacks( val dispatchingAndroidInjector: DispatchingAndroidInjector<Any> ) : ActivityLifecycleCallbacks { override fun onActivityCreated( activity: Activity, savedInstanceState: Bundle? ) {       dispatchingAndroidInjector.maybeInject(activity) } } 

ويمكن تحقيق نفس التأثير مع أطر عمل DI الأخرى. حاول أن تكتب في التعليقات ما حدث.

لتلخيص


ActivityLifecycleCallbacks هي أداة قوية وقليلة. جرب أحد هذه الأمثلة ، واسمح لهم بمساعدتك في مشاريعك بالطريقة نفسها التي يساعد بها Yandex.Money على تحسين تطبيقاتنا.

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


All Articles