Flutter: لم يتم حفظ حالة المثيل عند إيقاف تشغيل التطبيق بواسطة نظام التشغيل

تم إنشاؤها على ١٢ نوفمبر ٢٠١٦  ·  139تعليقات  ·  مصدر: flutter/flutter

ما هي حالة المثيل ولماذا توجد

على Android ، يمكن للنظام قتل أي نشاط في أي وقت. يحدث هذا عادةً عندما يحتاج Android إلى ذاكرة عندما لا يكون نشاطك في المقدمة ، أو بسبب تغيير تكوين لم تتم معالجته ، مثل تغيير اللغة.
لتجنب اضطرار المستخدم إلى إعادة تشغيل ما فعله من نقطة الصفر عندما قام Android بإيقاف النشاط ، يقوم النظام باستدعاء onSaveInstanceState(…) عند توقف النشاط مؤقتًا ، حيث من المفترض أن يحفظ التطبيق بياناته في Bundle ، ويمرر الحزمة المحفوظة في كل من onCreate(…) و onRestoreInstanceState(…) عند استئناف المهمة إذا تم إنهاء النشاط بواسطة النظام.

القضية في رفرفة

في تطبيقات الرفرفة التي جربتها (معرض Flutter ، والمشروع الأساسي باستخدام عداد نقر FAB) ، إذا فتحت تطبيقات كافية لجعل Android يقتل نشاط تطبيق flutter ، فستفقد كل الحالة عندما أعود إلى نشاط الرفرفة (أثناء عدم إزالة المهمة من الأخيرة).

خطوات التكاثر

  1. قم بتثبيت معرض Flutter
  2. افتح إعدادات الجهاز ، وفي خيارات المطور ، شغّل "عدم الاحتفاظ بالأنشطة". (انظر لقطة الشاشة)
    dev_option
    سيسمح هذا بالمحاكاة عندما يقتل Android الأنشطة لأنه يفتقر إلى الذاكرة ولست في المقدمة.
  3. افتح تطبيق Flutter Gallery وانتقل إلى أي مكان بخلاف الشاشة الرئيسية.
  4. انتقل إلى المشغل عن طريق الضغط على زر الصفحة الرئيسية للجهاز.
  5. اضغط على زر النظرة العامة وارجع إلى Flutter Gallery. هذا هو الخطأ.

ما هو متوقع: التطبيق في نفس الحالة التي توقفنا عندها ، مع واجهة مستخدم لم يمسها أحد.
ماذا يحدث: يُعاد النشاط من البداية ، ويفقد كل حالة واجهة المستخدم ، حتى الأشكال الطويلة حقًا.

P2 annoyance crowd routes framework new feature

التعليق الأكثر فائدة

تحديث سريع: أعمل حاليًا بنشاط على استكشاف حلول لهذه المشكلة.

ال 139 كومينتر

https://github.com/flutter/flutter/issues/3427 من المحتمل أيضًا أن يكون مرتبطًا.

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

في الوقت الحالي لا نفعل أي شيء لإنقاذ أي شيء.

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

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

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

لكن على أي حال ، يجب علينا بالتأكيد أن نفعل ما هو أفضل من اليوم.

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

ماذا لم أدرك أن هذا هو الحال؟ هذا أمر بالغ الأهمية بالفعل ، ومن الجدير بالذكر أنه على الأجهزة ذات الذاكرة الأقل ، يمكن إنهاء العملية بسهولة تامة!

ماذا عن حفظ PageStore (أو ذات الصلة) على هيئة دفق بايت من خلال التسلسل. يبدأ بـ onSaveInstanceState ؟

Takhion هل يمكنك ربط "PageStore" الذي أشرت إليه؟ أنا مهتم بمحاولة حل هذه المشكلة لأنه يبدو أنه لن يتم إصلاحها قريبًا (31 ديسمبر من عام 2029 هو القليل من IMHO بعيدًا)

LouisCAD تأكد من وجوده هنا: https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/widgets/page_storage.dart

أعتقد أنك ستحتاج إلى توفير أكثر من ذلك: على الأقل المسار (المسارات) الحالية وربما بعض الحالات؟
ما رأيك @ Hixie؟

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

هل نكشف عن أحداث دورة الحياة الضرورية ("أنت على وشك الموت!" ، "تهانينا ، لقد تمت استعادتك الآن") في كود Dart ، حتى يتمكن المطورون من التعامل مع الحالة المستمرة؟ يبدو أن هذا هو القدرات الكافية لإطلاق العنان للمطورين لاستكشاف الحلول. أفكار؟

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

LouisCAD شكرا على ردود الفعل. أفكر في خطوات ... ما هي الخطوة الأولى؟ هل تم الكشف عن أحداث دورة الحياة حتى الآن؟

sethladd كل ما يمكنني العثور عليه هو هذا ، والذي لا يعرض أي رد اتصال لحفظ / استعادة حالة المثيل

Hixie الذي ذكرته في # 3427 أن لدينا خطافات لدورة الحياة. هل تقصد https://docs.flutter.io/flutter/widgets/WidgetsBindingObserver-class.html ؟

هل يستخدم Flutter أنشطة متعددة بشكل افتراضي؟ (على سبيل المثال ، إذا ذهبت إلى شاشة جديدة).

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

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

هذا يعني أنه إذا لم يتم تنفيذ توفير الحالة ، فسيعيد النظام إنشاء الأنشطة ولكن يتم فقد كل شيء آخر (تم إيقاف العملية) ، مما يؤدي إلى عدم الاتساق والتعطل.

لقد كتبت مقالًا عن عملية الموت على Android https://medium.com/inloop/android-process-kill-and-the-big-implications-for-your-app-1ecbed4921cb

لا توجد أيضًا طريقة (نظيفة) لمنع Android من قتل تطبيقك بمجرد تجاوزه لحالة onStop () (بعد النقر فوق الصفحة الرئيسية أو إذا لم يكن التطبيق هو التطبيق الحالي فقط). لذا بطريقة ما عليك التعامل معها. تعمل أدوات Android الافتراضية على حفظ حالتها (على سبيل المثال ، إدخال نص في EditText) تلقائيًا في نسخة الحزمة. سيُعلمك نشاطك أنه من الضروري تخزين ولايتك عن طريق الاتصال بـ Flutter قادرًا على إعادة توجيه رد شاشاتك ". ستحصل على الحزمة مع الحالة المحفوظة مرة أخرى في onCreate (Bundle saveInstanceState ) أو onRestoreInstanceState (Bundle saveInstanceState) في نشاطك.

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

حظا جيدا في ذلك :-). يرجى توخي الحذر وعدم تقديم الكثير من حالة Android / دورة الحياة هذه إلى Flutter.

لست متأكدًا من كيفية عمل ذلك على iOS - لكنني أعتقد أن هناك شيئًا مشابهًا ولكن ليس من "الضروري" استخدامه (؟).

سيكون رد الاتصال لدورة الحياة أمرًا جيدًا بالنسبة لي. أود فقط تخزين / تحميل حالة الإعادة المتسلسلة.

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

هل يستخدم Flutter أنشطة متعددة بشكل افتراضي؟ (على سبيل المثال ، إذا انتقلت إلى شاشة جديدة)

يستخدم DanielNova Flutter نظام "التوجيه" الخاص به ويتكامل افتراضيًا مع Android من خلال عرض واحد في نشاط واحد. يمكن أن يكون لديك تطبيق Android / Flutter مختلط ومن المحتمل أن يكون هناك العديد من الأنشطة وطرق عرض Flutter ، ولكن في هذه الحالة يمكنك بسهولة حفظ / استعادة حالة المثيل من خلال Android مباشرة.

أود فقط تخزين / تحميل حالة الإعادة المتسلسلة

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

يعرض Flutter حدث دورة حياة Android onPause . لم يتم عرض حدث دورة حياة Android onDestroy () . أعتقد أن النهج الصحيح هو الارتباط بحدث onPause () لتخزين الحالة ذات الصلة بالمثيل.
لكي تكون متوافقًا مع onSaveInstanceState () و onRestoreInstanceState () الخاصين بنظام Android ، ربما يكون من المنطقي إضافة أساليب مشابهة إلى أدوات Flutter.
sethladdLouisCAD هل لأي شخص إنشاء اقتراح التصميم؟

@ raju-bitter onPause () ليس هو الأمثل ، فمن الأفضل ربطه بـ onSaveInstanceState (). هنا هو javadoc من onSaveInstanceState الذي يذكر أن onPause () يُستدعى بانتظام أكثر من onSaveInstanceState (سوف تقوم بتشغيل حفظ الحالة أكثر من اللازم أو عندما لا يكون ذلك ضروريًا على الإطلاق):

لا تخلط بين هذه الطريقة مع عمليات استدعاء دورة حياة النشاط مثل onPause () ، والتي تُستدعى دائمًا عندما يتم وضع نشاط في الخلفية أو في طريقه إلى التدمير ، أو onStop () الذي يُستدعى قبل التدمير. أحد الأمثلة على وقت استدعاء onPause () و onStop () وليس هذه الطريقة عندما ينتقل المستخدم مرة أخرى من النشاط B إلى النشاط A: ليست هناك حاجة لاستدعاء onSaveInstanceState (Bundle) على B لأن هذا المثيل المحدد لن يتم استعادته أبدًا ، لذلك يتجنب النظام تسميته. مثال عندما يتم استدعاء onPause () وليس onSaveInstanceState (الحزمة) عندما يتم بدء النشاط B أمام النشاط A: قد يتجنب النظام استدعاء onSaveInstanceState (الحزمة) في النشاط A إذا لم يتم إيقافه خلال عمر B منذ ذلك الحين ستبقى حالة واجهة المستخدم A سليمة.

تضمين التغريدة

يجب أن تمر حزمة حالة المثيل عبر IPC بحد أقصى 1 ميجابايت لحالة تطبيق Android بالكامل

هذه ليست مشكلتي.
أنا فقط بحاجة إلى معرفة متى يجب الاستمرار / الاستعادة ، فالإصرار على استخدام القرص أمر جيد بالنسبة لي.

إن نشر onSaveInstanceState / onRestoreInstanceState هو الجزء السهل: يتم تضمين Flutter في طريقة عرض وله حق الوصول المناسب إلى رد الاتصال. ما أحاول القيام به هو إنشاء اقتراح تصميم ونموذج أولي لإجراء تسلسل تلقائي للكائنات الموجودة في "مستودع" معين ، بحيث يصبح استخدامه غير مؤلم (على عكس الاضطرار إلى القيام بكل شيء يدويًا).

Takhion أنت الريح تحت جناحي. لا استطيع الانتظار لرؤية الاقتراح!

حسنًا ، لذلك أقوم ببناء علاقات عامة لهذا الأمر وواجهت حاجزًا: يتطلب [ View.onSaveInstanceState ] (أو [ Activity.onSaveInstanceState ] ، لهذه المسألة) تقديم حالة المثيل المحفوظة بشكل متزامن ، ومن تجربتي (وهذا [التعليق]) يبدو أن رسائل المضيف / النظام الأساسي لا يمكن

sethladdHixieeseidelGoogle @ جايسون-سيمونز من فضلك قل لي هناك طريقة بسيطة لجعل متزامن / حجب مكالمة واحدة من الروبوت لالرفرفة التي لا تنطوي مخصصة C / C ++ التعليمات البرمجية، منذ [ dart:jni تقاعد]؟

نسخة إلى @ mravn-google

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

final MethodChannel channel = new MethodChannel(messenger, someName);
final CountDownLatch latch = new CountDownLatch(1);
channel.invokeMethod("someMethod", someArguments, new MethodChannel.Result() {
  <strong i="6">@Override</strong>
  public void success(Object result) {
    // handle successful invocation
    latch.countDown();
  }
  <strong i="7">@Override</strong>
  public void error(String errorCode, String errorMessage, Object errorDetails) {
    // handle failed invocation
    latch.countDown();
  }
  <strong i="8">@Override</strong>
  public void notImplemented() {
    // handle invocation of unimplemented method
    latch.countDown();
  }
});
try {
  latch.await();
} catch (InterruptedException e) {
  // handle interruption
}

يمكنك إعلان متغير final AtomicReference<SomeType> (أو ما شابه) بجوار المزلاج ، إذا كنت بحاجة إلى نقل القيم من مواقع // handle xxx إلى الكود الذي يتم تشغيله بعد إرجاع latch.await() .

شكرا @ mravn-google!

@ mravn-google هذا هو النهج الأول الذي جربته بالضبط ، ولكن الموضوع الرئيسي على Android معلق على المكالمة await() إلى الأبد :(

Takhion صحيح ، بالطبع.

يمكنك دائمًا تحويل استدعاء API غير متزامن إلى استدعاء متزامن باستخدام مزلاج

... ما لم يتم تسليم الاستجابة غير المتزامنة إلى سلسلة الرسائل التي أنت عليها بالفعل: - /

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

@ mravn-google للأسف ، Android _full_ من هذه "النوافذ المتزامنة للفرص" السيئة ، حيث يجب أن يحدث كل شيء قبل العودة من استدعاء الأسلوب! في الجزء العلوي من رأسي: العديد من أحداث دورة الحياة Activity ، طرق في Broadcast Receiver ، SyncAdapter ، إلخ. أنا متفاجئ بصراحة أنها لم تكن مشكلة أكبر من قبل!

في رأيي ، إذا أردنا أن يحقق Flutter تكافؤًا حقيقيًا في الميزات مع ما هو ممكن من خلال واجهات برمجة التطبيقات "الأصلية" ، فإن الاتصال برسالة متزامن أمر لا بد منه.

@ mit-mit كنت أعني SystemChannels.lifecycle وأي شيء يستخدم ذلك ، نعم.

هل هذا هو نفسه https://github.com/flutter/flutter/issues/7560 ؟

sethladd أعتقد ذلك.

@ mravn-google شكرا! لقد أغلقت # 7560 لصالح هذا ، فقط لإزالة الخداع وجعل المحادثة مركزية.

أميل إلى إضافة نظير متزامن إلى MethodChannel لهذا النوع من اتصالات النظام الأساسي إلى Dart.

@ mravn-google كيف الحال؟ هل يمكنني المساعدة؟

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

@ mravn-google رائع! ليس لدي تطبيق لأنني أقوم بتعديل إطار العمل مباشرة ، لكن يمكنني بالتأكيد تقديم عينة. راجع للشغل أنا أستخدم BinaryMessages مباشرةً نظرًا لعدم وجود حاجة إلى برنامج ترميز على جانب Android (يتم تمرير بايت فقط) ، فهل هذا جيد؟

يا رفاق صخرة.

Takhion الاستخدام المباشر لـ BinaryMessages جيد ، إذا لم تكن بحاجة إلى برنامج ترميز. سأقوم بإضافة

void setSynchronousMessageHandler(String channel, ByteData handler(ByteData message))

لتلك الدرجة.

Takhion اضرب ذلك. لا يمكننا إجراء مكالمات متزامنة في Dart VM لاستدعاء handler . لذلك لن يتغير BinaryMessages . بدلاً من ذلك ، سيكون كل شيء غير متزامن من وجهة نظر رمز Dart ، كالمعتاد. سيتم إجراء التغيير إلى BinaryMessenger وتطبيقاته على جانب النظام الأساسي. سأضيف

ByteBuffer sendSynchronous(String channel, ByteBuffer message);

رمز عرض النظام الأساسي الذي يدعم FlutterView سينفذ ذلك عن طريق إرسال رسالة منصة غير متزامنة ثم الانتظار على مزلاج. لن يقوم كائن الاستجابة المضمن في رسالة النظام الأساسي هذه بنشر إغلاق معالجة الرد على مؤشر ترابط النظام الأساسي (الذي تم حظره الآن) ، ولكنه بدلاً من ذلك سيخزن الرد ويحرر المزلاج.

يعمل @ mravn-google بالنسبة لي ، لأنه لا يمنعنا من إرسال البيانات مرة أخرى من جانب Dart ، حتى لو كانت مكالمة غير متزامنة: +1:
أعتقد أنه لن يكون هناك أي تحسينات إذا استخدم المرء Future.value(...) في استجابة Dart غير المتزامنة؟

Takhion يرجى إلقاء نظرة على https://github.com/flutter/engine/pull/4358

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

يتوقع مطورو iOS أن يكون إجراء الحفظ / الاستعادة هذا تلقائيًا تقريبًا. أعتقد ، إذا كان هدفك هو إنشاء إطار عمل عبر الأنظمة الأساسية فعليك بالتأكيد أن تأخذ ذلك في الاعتبار.

كنت تعتقد أن شيئًا ما ينوي استهداف Android كمنصة يحاول على الأقل الامتثال لأساسيات عقد النشاط ...

Zhuinden دعونا لا نشكو من الأشياء التي لا تزال في مرحلة تجريبية؟ :)

واجهات برمجة التطبيقات المتزامنة الأخرى هي onLowMemory و onTrimMemory و onConfigurationChanged لا تعرف ما إذا كان لدى IOS أيضًا واجهات برمجة تطبيقات أو آليات مماثلة

قد لا نرغب في إعادة إنتاج آلية حفظ واستعادة android المعيبة على Flutter: لا يمكنك حفظ الحالة بأكملها في الحزمة بسبب حد حجم المعاملة.

على نظام Android ، ينتهي بك الأمر إلى وجود العديد من منطق الحفظ / الاستعادة:

  • الحالة المحفوظة للمطور ، ولكن لا يمكنك تخزين كائنات كبيرة بالداخل ، مثل قائمة العناصر
  • قاعدة البيانات ، حيث يمكنك تخزين العناصر الكبيرة
  • تدير معظم الأدوات أيضًا دولتها الخاصة
  • النشاط / كومة شظية

من السهل حقًا على المطورين تنفيذ منطق فوضوي وعربات التي تجرها الدواب. في تجربتي الخاصة ، من النادر حقًا رؤية حفظ الحالة واستعادتها بشكل صحيح.

على الرفرفة ، فإن حالة المطور هي المصدر الوحيد للحقيقة.
يجب أن يستمر المطورون في استمرار هذه الحالة في القرص عندما يتغير ، دون المرور بميكانيكا النظام الأساسي المحدود.

بالتأكيد ، لن تتمكن من حفظ موضع التمرير بسهولة ، ولكن من يهتم.
في معظم الأوقات ، تعني تجربة المستخدم الجيدة للاستعادة أن المستخدم يجب أن يرى نفس الشاشة عما كان عليه سابقًا. يمكنك فعل ذلك مع Flutter اليوم.

النقطة الأساسية: هناك مجال للتحسين على جانب Flutter لجعل حفظ / استعادة الحالة أقل نموذجية (لأشياء مثل Navigator). لكنني لا أعتقد أنه يجب أن يوفر ربطًا لمنصة حفظ / استعادة الحالة.

بالتأكيد ، لن تتمكن من حفظ موضع التمرير بسهولة ، ولكن من يهتم.

الجميع؟ سأشعر بالإحباط حقًا إذا أعاد التطبيق تعيين قائمته في كل مرة أقوم فيها بطريق الخطأ بإجراء تغيير في الاتجاه. : ص

Saketme FWIW ، لا يقوم

يجب أن يكون استخدام تطبيق Flutter مع القليل من المهام المتعددة أمرًا ممتعًا على جهاز Android Go! يتم فقد التقدم في كل مرة تتعرض فيها ذاكرة الوصول العشوائي سعة 1 جيجابايت أو أقل للضغط

Saketme : كما قال @ mravn-google ، لا يرتبط Flutter بتغييرات التكوين مثل أنشطة android.
إدارة الحالة الوحيدة المطلوبة هي عندما يتم إيقاف التطبيق بواسطة نظام التشغيل أثناء وجوده في الخلفية.

LouisCAD من المؤكد أن الحفظ والاستعادة مهم.
يمكن لمطوري Flutter بالفعل تنفيذ معظم هذا المنطق بأنفسهم ، ومن المحتمل أن يوفر Flutter طرقًا لجعله أقل نموذجية. ومع ذلك ، فإن الآلية التي يقدمها Android معيبة ويمكننا اغتنام الفرصة مع Flutter لتصميم شيء أفضل ، بدلاً من اختراق النشاط onSaveInstanceState / onRestoreInstanceState .

ما هو الوضع الحالي لهذه المشكلة؟

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

بخلاف موضع التمرير والمسار ، ما هي "الحالات" الأخرى التي لا يديرها عادةً المطورون؟

nsreenath يجب أن

لقد نمت بالفعل على هذا وأدركت أنه من السهل جدًا على مطوري Flutter اكتشاف ما إذا كان التطبيق قد عاد من عملية الموت أم أنه مثيل جديد تمامًا.

بسيط جدًا ، طالما أنهم يبحثون عن savedInstanceState == null (بافتراض أنهم وضعوا عنصرًا واحدًا على الأقل في onSaveInstanceState(bundle) ). إذا كانت العلامة المنطقية الثابتة isFirstInit == false و savedInstanceState != null ، فهذا بعد موت العملية.

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

بسيط جدًا ، طالما أنهم يبحثون عن saveInstanceState == null (بافتراض أنهم وضعوا عنصرًا واحدًا على الأقل في onSaveInstanceState (حزمة)).

لا يوجد savedInstanceState و onSaveInstanceState على iOS. ولكن لا يزال من الممكن قتل التطبيق بواسطة نظام التشغيل.

ضع في اعتبارك سير عمل نموذجي:

  • ينتقل المستخدم إلى شاشة معينة داخل التطبيق
  • ثم ينتقل المستخدم إلى تطبيق آخر
  • فجأة قرر نظام التشغيل الحكيم قتل التطبيق السابق للحفاظ على الذاكرة
  • يعود المستخدم إلى التطبيق السابق لكنه يرى الشاشة الرئيسية. ليست الشاشة التي كانت مفتوحة عندما قررت تبديل التطبيق

إذن ، ما هي الطريقة الموصى بها حاليًا للحفاظ على سجل الشاشة في Flutter؟

ملاحظة: بالطبع ، هذا ليس مهمًا إذا كان تطبيقك يحتوي على شاشة واحدة فقط: ابتسامة:

Zhuinden أخفق في رؤية العديد من السيناريوهات حيث يكون ذلك مطلوبًا على أي حال.

أ) مثال على الدردشة:
احفظ دائمًا واستعد المحتوى السابق المكتوب في حقل النص الذي لم يتم إرساله.

ب) مثال طويل:

  1. احفظ المحتوى دائمًا
  2. عندما يعود المستخدم إلى شاشة النموذج ، أخبر المستخدم أن هناك نسخة مسودة تم حفظها ولكن لم يتم إرسالها ، وسؤاله عما إذا كان يريد استعادتها. لا يحدث أي فرق إذا خرج المستخدم من النموذج يدويًا (ربما يسأل عما إذا كان المستخدم يريد حفظ المسودة) أو إذا تم إيقاف التطبيق أثناء وجوده في الخلفية.

ربما تكون هناك بعض الحالات التي تكون فيها المعرفة المحددة بـ "شاشة جديدة جديدة" مقابل "شاشة جديدة بعد انتهاء العملية" مفيدة ، ولكن قد لا يكون ذلك شائعًا جدًا عند تصميم محتوى تطبيقك ليتم دعمه دائمًا بواسطة قاعدة بيانات.

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

Zhuinden ، https://github.com/flutter/flutter/issues/6827#issuecomment -335160555 لتطبيق الدردشة الخاص بي. أقوم فقط بحفظ بعض الحالات في كل مرة يتم فيها تحويل التطبيق إلى الخلفية. راجع أيضًا https://docs.flutter.io/flutter/dart-ui/AppLifecycleState-class.html

أود أن أحث فريق Flutter على عدم الكشف عن أساليب دورة حياة Android / iOS كطريقة أساسية لاستمرار / استعادة حالة واجهة المستخدم. حتى في التطبيقات الأصلية ، كان من الصعب التملص منها والتشفير بشكل صحيح ، وغالبًا ما كان الخط الفاصل بين واجهة المستخدم وحالة التطبيق ضبابيًا ، مع توفير آليات مختلفة لكل منهما. سيكون من العار نشر كل واجهات برمجة التطبيقات المتباينة هذه في Flutter (خاصة إذا تم استهداف منصات إضافية في المستقبل).

لقد قامت أدوات Flutter عديمة الحالة _بالفعل_ بترحيل مسؤولية حالة واجهة المستخدم من الأدوات إلى التطبيق ، لذلك تتم تغطية مشكلة استمرار / استعادة حالة واجهة المستخدم بشكل متزايد من خلال استمرار / استعادة حالة _app_ بدلاً من ذلك. أرى هذا أمرًا جيدًا ، بدلاً من التعامل مع حالة واجهة المستخدم والتطبيق بشكل مختلف ، كما هو الحال في iOS و Android.

لقد لاحظت أن بعض أدوات Flutter ، مثل ScrollView و TextField ، توفر فئات "تحكم" تغلف الحالة الديناميكية للأداة. في بعض النواحي ، يعملون ككائنات "تذكارية" - لقطة لحالة عنصر واجهة المستخدم - والتي يمكن إعادتها مرة أخرى إلى عنصر واجهة المستخدم في وقت لاحق لاستعادة حالتها إلى بعض التهيئة السابقة. يبدوا مألوفا. إذا كان من السهل استمرار / استعادة هذه "التذكارات" ، فقد يتحمل التطبيق المسؤولية عنها كجزء من إدارة حالته الخاصة. سيؤدي ذلك إلى إنهاء انقسام حالة التطبيق / واجهة المستخدم ، وسيكون خيارًا مشتركًا ، وسيغني عن الحاجة إلى إطار عمل Flutter لتوفير خطافات الحفاظ على حالة واجهة المستخدم (الأقل هو الأكثر!)

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

هل حدث أي تقدم في هذا الشأن؟ أعتقد أن هناك حاجة حقيقية للتمييز بين حالة العملية "التشغيل الأول" و "المستعادة". وإلا كيف يمكنك البرمجة لإظهار الشاشة الصحيحة عندما ينتقل المستخدم مرة أخرى إلى عملية قتل؟

lukaspili كتبت: "الإدارة الحكومية الوحيدة المطلوبة هي عندما يتم

حسنًا ، ماذا عن جهاز التوجيه ، حفظ أيضًا على القرص؟ أيضا أنا لا t find proper solution to determine that application was restored. Every time when app goes to background I need to save router stack, app data, state to disc? What if I don ر استخدام مسترجع؟

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

هل كان هناك تحديث لهذه القضية؟

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

هناك مشكلة أكبر هنا. كان لي بعض ذهابا وإيابا مع @ ماثيو كارول منذ شهرين وأنا واثق تماما هذا هو جارية.

لقد بدأت في إنشاء مكتبة تتعامل مع هذا . لا أوصي باستخدامه في الواقع ، لكنها فكرة واحدة عن تطبيق Dart-only. # 6225 جعل الأمور صعبة بعض الشيء.

لقد لاحظت أن تطبيق إعلانات Google ، الذي يُقال أنه صُنع باستخدام Flutter ، يحافظ على حالة التنقل بعد إعادة تشغيل النشاط. سيكون مرجعًا جيدًا لمعرفة كيفية تنفيذه ، حتى لو كان حلاً.

aletorrado هل يحتفظ تطبيق إعلانات Google بحالة التنقل في حالة تجاهل المستخدم للتطبيق من "مدير المهام" لنظام التشغيل Android / IOS ثم أطلقه مرة أخرى لاحقًا؟

gitspeak لا ، إنه يحافظ فقط على الحالة إذا تم

aletorrado إذن نعم ربما هذا هو السبيل الوحيد ...

لقد حاولت بالفعل الدخول إلى تطبيق إعلانات Google ولكن يجب أن يكون لديك حساب نشط للوصول إلى أي شاشة بخلاف الشاشة الأولى: لا يمكن لشركة DI اختبار ما إذا كانت تعمل حقًا أم لا

نعم هو كذلك. مضمون. كما أنه يحافظ على حالة أخرى مثل موضع التمرير.

لتوضيح بعض الالتباس:

  • نعم ، تطبيق إعلانات Google مكتوب باستخدام Flutter.
  • لكونهم من أوائل المتبنين ، كان عليهم أن يفعلوا شيئًا غير عادي لتنفيذ الخلفية. ينشئون ويحافظون على عزلهم الرئيسي عند بدء التطبيق (وليس النشاط). بعد ذلك ، يقومون بتمرير هذا العزل إلى عرض Flutter عند إنشائه.
  • ما تراه على أنه "حفظ دولة" هو أحد الآثار الجانبية للمنهجية الموصوفة أعلاه. عندما تقتل النشاط في إعلانات Google ، لا تموت "واجهة المستخدم" الخاصة بـ Flutter. يتم الاحتفاظ بها في الذاكرة مرتبطة بسياق التطبيق. عند إعادة تشغيل النشاط ، يتم تحميل نفس العزل والفيولا ، وستستعيد واجهة المستخدم الخاصة بك.

لا نوصي بهذا الأسلوب لحل هذه المشكلة لأنه لا يحل المشكلة على الإطلاق. إذا ماتت عملية التقديم ، فلا تزال الحالة بأكملها مفقودة. يمكنك تجربة ذلك مع إعلانات Google عن طريق الذهاب وإيقاف العملية من الإعدادات> التطبيقات. ستفقد واجهة المستخدم.

قد تكون هناك طريقة لحفظ حالة العزل محليًا ولكنها تحتاج إلى مزيد من التحقيق لأننا لسنا متأكدين من مدى إمكانية / أمانها.

في الوقت الحالي ، الحلول الموضحة أعلاه هي الأفضل: اربط بين الأساليب الأصلية واحفظ حالة تطبيقك بشكل انتقائي حتى تتمكن من استعادتها لاحقًا. لا يزال الحل التلقائي الذي يدعمه إطار العمل على كل من iOS و Android بعيدًا.

يمكنك تجربة ذلك مع إعلانات Google عن طريق الذهاب وإيقاف العملية من الإعدادات> التطبيقات. ستفقد واجهة المستخدم.

لا ، هذا جيد تمامًا. يجب أن يفقد فرض إيقاف التطبيق الحالة.

يجب ألا يتم إنهاء حالة الذاكرة المنخفضة.

في كلتا الحالتين ، إذا احتفظوا بالحالة في التطبيق باعتبارها واحدة في الذاكرة دون الاستمرار في أي شيء على القرص / الحزمة ، فسيتم فقدها.

إنه لأمر مخز أنه لا يمكنني اختبار التطبيق بدون حساب ، لأنني لن أقوم بإنشاء حساب إعلانات Google لمجرد العبث بالتطبيق نفسه. : التفكير_الوجه:

اربط بين الأساليب الأصلية واحفظ حالة تطبيقك بشكل انتقائي حتى تتمكن من استعادتها لاحقًا.

في الواقع ، سيكون الحل الأفضل هو استمرار الحالة النشطة على القرص ، واكتشاف موت العملية على الجانب الأصلي لنظام Android ؛ إذا اكتشفت موت العملية ، فأعد التحميل من القرص ، وإلا قم بمسحها وإعادة التشغيل من نقطة الصفر.

لا أعرف ما يكفي عن ملاحي Flutter لإخبارك ما إذا كان بإمكانك إعادة بناء سجل التنقل باستخدام طريقة setHistory أو setBackstack كما تفعل مع كومة خلفية أو موصل أو شيء من هذا القبيل.

تتعامل الأنشطة / الأجزاء مع هذه الأشياء داخليًا باستخدام ActivityRecord / FragmentRecord ، لذلك لم يتم ذكرها بعد ذلك.

شكرا لك على كل تلك الأفكارmehmetf. من المحزن أن نسمع أن دعم إنقاذ / استعادة الحالة بعيد كل البعد عن التنفيذ.

ربما يمكن أن يساعد flutter_redux في إجراء تسلسل لبعض حالات التطبيق.

حسنًا ، إذا واصلت الاحتفاظ بنقطة تخزين Redux على القرص. فقط احذر من مشكلة "مربع حوار التحميل اللانهائي بعد موت العملية". : التفكير_الوجه:

لتوضيح بعض الالتباس:

في الواقع ، أنا مرتبك إلى حد ما ..

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

كيف يرتبط ذلك بالقضية المطروحة؟ هل تعمل عزلات Dart في عملية منفصلة؟

ما تراه على أنه "حفظ دولة" هو أحد الآثار الجانبية للمنهجية الموصوفة أعلاه. عندما تقتل النشاط في إعلانات Google ، لا تموت "واجهة المستخدم" الخاصة بـ Flutter. يتم الاحتفاظ بها في الذاكرة مرتبطة بسياق التطبيق. عند إعادة تشغيل النشاط ، يتم تحميل نفس العزل والفيولا ، وستستعيد واجهة المستخدم الخاصة بك.

ما أفهمه هو أن aletorrado لاحظ "حفظ الحالة" بعد إنهاء العملية ، ربما عندما تم نقل التطبيق إلى الخلفية (ضغط ag على زر الصفحة الرئيسية).

لمعلوماتك: عندما "يموت النشاط" في وقت التشغيل ، فسيكون متبوعًا دائمًا بإنهاء العملية.

لا نوصي بهذا الأسلوب لحل هذه المشكلة لأنه لا يحل المشكلة على الإطلاق. إذا ماتت عملية التقديم ، فلا تزال الحالة بأكملها مفقودة. يمكنك تجربة ذلك مع إعلانات Google عن طريق الذهاب وإيقاف العملية من الإعدادات> التطبيقات. ستفقد واجهة المستخدم.

كما ذكر Zhuinden ، هذا ليس سيناريو "حفظ حالة" صالحًا ، لذلك تجاهل التطبيق من "مدير المهام"

قد تكون هناك طريقة لحفظ حالة العزل محليًا ولكنها تحتاج إلى مزيد من التحقيق لأننا لسنا متأكدين من مدى إمكانية / أمانها.

ليس هذا هو السبيل للقيام بذلك. بالنسبة لنظام التشغيل Android ، يجب عليك الاتصال بآلية onSaveInstanceState حيث تم إجراؤها صراحةً لحفظ حالة النشاط لتلك السيناريوهات التي تتطلب "حفظ الحالة" بعد إنهاء العملية. لست على دراية بدورة حياة تطبيق IOS.

في الوقت الحالي ، الحلول الموضحة أعلاه هي الأفضل: اربط بين الأساليب الأصلية واحفظ حالة تطبيقك بشكل انتقائي حتى تتمكن من استعادتها لاحقًا. لا يزال الحل التلقائي الذي يدعمه إطار العمل على كل من iOS و Android بعيدًا.

لم يتم وصف أي حل بدلاً من الاعتراف بمنشأة Android المناسبة للاحتفاظ بالحالة لتلك الحالات التي يقرر فيها وقت التشغيل إنهاء العملية.

لم يتم وصف أي حل بدلاً من الاعتراف بمنشأة Android المناسبة للاحتفاظ بالحالة لتلك الحالات التي يقرر فيها وقت التشغيل إنهاء العملية.

لقد وصفت نوعًا ما ما يمكننا القيام به ولكني لا أعرف ما يكفي عن Flutter لأخبرك ما إذا كان يدعم ما هو ضروري له (القدرة على إعداد سجل التنقل التعسفي في أي وقت معين 😄)

Zhuinden ، هناك

... وللتأكيد على موقفي: تعد مهام / أنشطة Android ، والخدمات ، ودورة حياة التطبيق (بما في ذلك onSaveInstanceState) جزءًا من عقد برمجة Android "عرضيًا" يتم تحقيقها لأول مرة في Java ولكن الفكرة الأساسية هي أنها وسيلة لفرض قيود على مستوى النظام على عملية التطبيق وبما أن شفرة Flutter تعيش داخل عملية تطبيق Android ، فلن يكون من الحكمة لـ Flutter تجاوز هذه القيود.

أعزائي مطوري Android ،

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

لماذا لا يمكنني استخدام onSavedInstanceState ؟

يتطلب onSavedInstanceState المطورين حفظ حالة التطبيق بشكل متزامن. عليك العودة على الفور. لكن الاتصال بـ Flutter غير متزامن افتراضيًا ( Future ). لذلك لا يمكنك طلب الحالة المحفوظة من تطبيق Flutter عندما يطلب Android ذلك.
_ ولكن يمكنك إعادة الحالة المحفوظة إذا كنت قد قمت بإنشائها مسبقًا ، أليس كذلك؟ _

لا تكمن المشكلة في عدم إمكانية الوصول إلى onSavedInstanceState ، فالمشكلة تكمن في التوقيت. عندما يطلب Android حفظ الحالة ، لن تضطر إلى البيانات.

حل الاسترداد أ: ملف حالة المثيل المحفوظ الخاص بك

يمكنك الاستماع إلى onSavedInstanceState وإبلاغ تطبيق flutter بهذا الحدث. ثم احفظ حالة التطبيق واكتبها بـ file . عندما يتم استرداد تطبيقك من موت العملية (يمكنك اكتشاف ذلك عبر savedInstanceState على جانب Android) ، قم بتحميل الملف من نظام الملفات ، وقم بتحليله واستعد حالتك. (تلميح: استخدم قنوات النظام الأساسي ، فهي أسهل مما تعتقد).

يمكنك حتى استعادة الحالة عند قيام المستخدمين بقتل التطبيق ، أو رفض الحالة المحفوظة إذا كانت قديمة جدًا!

حل الاسترداد ب: حفظ الحالة باستمرار

يمكننا تشغيل مؤقت وحفظ حالة التطبيق باستمرار على جانب النظام الأساسي في الذاكرة. دعنا نقول كل 3 ثوان. عندما يتصل Android بعد ذلك بـ onSavedInstanceState يمكننا إرجاع الحالة المحفوظة مسبقًا. (نعم ، قد نفقد التغييرات التي حدثت في آخر 3 ثوانٍ)

حل الاسترداد ج: حفظ الحالة عند النقاط الرئيسية

مثل الحل B ، سيتم الاحتفاظ بالحالة المحفوظة في الذاكرة على جانب النظام الأساسي. ولكن بدلاً من حفظ الحالة كل بضع ثوانٍ ، فأنت تطلب يدويًا حفظ الحالة بعد قيام المستخدمين ببعض الإجراءات. (فتح الدرج ، أدخل النص في TextField ، سمها ما شئت).


جميع الحلول الثلاثة قابلة للجمع ، استخدم ما يناسب تطبيقك بشكل أفضل.

ولكن كيف يمكنني حفظ حالة التطبيق الخاص بي؟

على عكس Android View s ، لا يحتوي Widget s على رد اتصال onSaveInstanceState . لا يتم حفظ شجرة الأدوات تلقائيًا. وهذا شيء جيد! تم فصل واجهة المستخدم والحالة أخيرًا وليس هناك سحر انعكاس عند استعادة حالة العرض أو إنشاء مثيل للأنشطة والأجزاء.

لحفظ الحالة ، يجب عليك تكرار كل شاشة في تطبيقك وحفظ جميع المعلومات المطلوبة لاستعادتها. أحب القيام بذلك في json ولكن يمكنك اختيار protobuf للحصول على أداء أفضل (قم بالقياس قبل أن تقوم بالتحسين!).

عند النظر إلى تطبيق العداد المحبوب لدينا ، يمكن أن يكون هذا سهلاً للغاية:

// complete app state of sample application
{
  "counter": 24,
}

إذا كان لديك شاشة متعددة أو مربعات حوار ، فقد ينتهي بك الأمر مع حالة التطبيق التالية json:

{
    "currentRoute": "/todo-detail/241",
    "routes": {
        "todo-detail/241": {
            "edited": "true",
            "title": "Buy bananas",
            "picture": "http://....",
            "show_confirm_delete_dialog": "false"
        },
        "todo-list": {
            "current_scroll_position": "365",
            "filter": "Filter.TODAY"
        }
    }
}

يعتمد التنفيذ الفعلي بشكل كبير على بنية التطبيق الخاص بك.

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

أفضل النصائح التي يمكنني تقديمها لك:

  • احتفظ بفصل صارم بين واجهة المستخدم ونماذجك.
  • انظر إلى كل شاشة ، واسأل نفسك ما الذي تحتاجه حقًا لاستعادة الحالة. في معظم الأحيان لا يكون كثيرًا.
  • تحدث إلى iOS colleagues وسيخبرونك أنهم لم ينفذوا استعادة الحالة عبر encodeRestorableState في سنوات. Android 1M + مقابل iOS 974

باسكال

قم بتشغيل إعداد "عدم الاحتفاظ بالأنشطة".

لا ، هذا لا يختبر بشكل صحيح ضد عملية الموت. لا يحدث هذا أبدًا ما لم تقم بتمكين هذا الإعداد صراحة. 😞

إذا كان لديك شاشة متعددة أو مربعات حوار ، فقد ينتهي بك الأمر مع حالة التطبيق التالية json:

{
  "currentRoute": "/todo-detail/241",
  "routes": {
      "todo-detail/241": {
          "edited": "true",
          "title": "Buy bananas",
          "picture": "http://....",
          "show_confirm_delete_dialog": "false"
      },
      "todo-list": {
          "current_scroll_position": "365",
          "filter": "Filter.TODAY"
      }
  }
 }

يا فتى ، لقد وصلنا إلى "حالة التنقل جزء من حالة التطبيق باعتبارها حالة آلة الحالة المحدودة" لكي يعمل هذا ، لكني أحب الطريقة التي يبدو بها 😄

يا فتى ، لقد وصلنا إلى "حالة الملاحة جزء من حالة التطبيق باعتبارها حالة آلة الحالة المحدودة" لكي يعمل هذا

😂 لا تجعلني أبدأ.

يجب حفظ بقية حالة التطبيق في قاعدة بيانات / على الخادم. الحالة المؤقتة الوحيدة التي تستحق الحفظ في onSaveInstanceState هي حزمة التنقل وبعض مدخلات المستخدم. هذا أيضًا ما يطلبه معظم الناس في هذا الموضوع.

يجب حفظ بقية حالة التطبيق في قاعدة بيانات / على الخادم. الحالة المؤقتة الوحيدة التي تستحق الحفظ في onSaveInstanceState هي حزمة التنقل وبعض مدخلات المستخدم. هذا أيضًا ما يطلبه معظم الناس في هذا الموضوع.

وهذا مفتاح! (بالإضافة إلى دعم "خدمة Android")

gitspeak ، آخر تبادل لا علاقة لنا به بهذه المشكلة. سأخفي كليهما. إذا كنت ترغب في مواصلة المناقشة ، يرجى PM لي باستخدام github handle @ gmail.com. من الواضح أن هناك سوء فهم هنا خارج نطاق هذه المشكلة.

بصفتي أحد المطورين الذين يعملون على الواجهة بين Flutter و Android ، أود أن أشكر passsy على تقديم نظرة عامة رائعة على المشكلات المتعلقة بحفظ الحالة.

أود أيضًا أن أضيف أنني مهتم بفهم أي حالات استخدام محددة يتعامل معها المطورون في هذا الموضوع. دولة الادخار هو مفهوم يمتد على الأرجح إلى العديد من الأهداف المختلفة. ما يسميه أحد التطبيقات حالة ، والآخر لا. يجب أن يكون أحد التطبيقات مضمونًا على الإطلاق بالحالة المحفوظة ، بينما يفضل الآخر ببساطة حفظ بعض الحالة ولكن لن ينكسر بدونها. تم تصميم بعض التطبيقات بالكامل باستخدام Flutter ، بينما يجب أن تقوم التطبيقات الأخرى بمزامنة المعلومات بين واجهة مستخدم Android / iOS التقليدية و Flutter.

إذا رغب أي منكم في وصف حالة الاستخدام المعينة التي يحتاج تطبيقك إلى دعمها فيما يتعلق بحالة الحفظ ، فلا تتردد في [email protected] - أنا أقوم بتجميع حالات الاستخدام بنشاط.

passsy يرجى اعتبار تعليقاتي أدناه محاولة مخلصة لتقديم ملاحظات بناءة

حل الاسترداد أ: ملف حالة المثيل المحفوظ الخاص بك
يمكنك الاستماع إلى onSavedInstanceState وإبلاغ تطبيق flutter الخاص بك بهذا الحدث. ثم احفظ حالة التطبيق واكتبها في ملف. عندما يتم استرداد تطبيقك من موت العملية (يمكنك اكتشاف ذلك عبر saveInstanceState على جانب Android) ، قم بتحميل الملف من نظام الملفات ، وقم بتحليله واستعد حالتك. (تلميح: استخدم قنوات النظام الأساسي ، فهي أسهل مما تعتقد).
يمكنك حتى استعادة الحالة عند قيام المستخدمين بقتل التطبيق ، أو رفض الحالة المحفوظة إذا كانت قديمة جدًا!

عدة قضايا:

1) هناك حالة عرقية متأصلة مع هذا النهج. قد يتجاوز مؤشر ترابط "الحالة المستمرة" نطاق تطبيق Android الرئيسي الذي يستدعي أحداث دورة الحياة ، وفي هذه الحالة ، قد يتم إنهاء العملية أثناء كتابة الملف. يمكنك محاولة تنسيق السلاسل عن طريق الحجب في طريقة دورة الحياة على إشارة اكتمال من مؤشر ترابط "توفير الحالة" ولكن هذا ، بالإضافة إلى إدخال تعقيد مزامنة إضافي ، لا يمكن أن يضمن الصحة لأن التأخير الطويل بما يكفي يمكن أن يؤدي إلى ANR. (https://developer.android.com/topic/performance/vitals/anr)
قد تجادل بأن "حفظ الحالة" في الملف سريع جدًا لدرجة أن هذا لن يحدث على الأرجح ولكن ضع في اعتبارك التأخيرات الفعلية في جدولة سلسلة الرسائل ووقت استجابة التخزين أثناء تشغيل العمليات الأخرى مثل التنزيلات في الخلفية وتحديثات متجر الألعاب ومشغل الوسائط وما إلى ذلك ... ليس في مناقشة هذا ، لكن تجربتي الشخصية (التي يشاركها الآخرون) هي أن أجهزة Android ليست محصنة ضد "الأنفلونزا" نفسها التي تصيب أجهزة الكمبيوتر التي تعمل بنظام Windows - كما أنها تصبح أبطأ بمرور الوقت. لقد قمت مؤخرًا بمسح جهاز Nexus 7 الخاص بي ولم يستغرق الأمر فقط HOURS لتنزيل وتثبيت جميع التطبيقات عبر Wifi ، ولكن استجابة الجهاز أصبحت الآن مزحة كاملة للإشارة إلى حيث أشك في التحلي بالصبر عند استخدامه حتى للبحث عن الوصفات.

2) لا يمكنك الحصول على تعريف موثوق به لـ "قديم جدًا". إذا قمت بتجاهل التطبيق من مدير المهام ، أتوقع أن تختفي كل الحالات على الفور ، بينما إذا قمت بالتبديل إلى تطبيق آخر ، أتوقع أن يتم الاحتفاظ بالحالة إلى أجل غير مسمى طالما كان الهاتف قيد التشغيل. النقطة المهمة هي أن Android لا يوفر لك طريقة للتمييز بين هاتين الحالتين.

3) لا تعرض جميع عناصر التحكم في واجهة المستخدم حالتها من خلال واجهة برمجة التطبيقات التي تسمح للمستخدم بتكوين عنصر التحكم لحالة معينة. إن واجهة برمجة التطبيقات هذه ستتحمل بلا شك جهود تطوير إضافية نيابة عن مطور التحكم. في نظام التشغيل android ، من المتوقع أن تعمل عناصر التحكم في واجهة المستخدم التي تعمل بشكل جيد على تجريد مشكلات "الحفاظ على الحالة" من المطور من خلال معالجة إشعارات onSave / Restore InstanceState داخليًا وبالتالي التحكم في الحالة التي يتم الاحتفاظ بها. إذا كنت أتذكر بشكل صحيح ، فإن عنصر تحكم الإكمال التلقائي يحتفظ بمصطلح الاستعلام وليس قائمة النتائج.

حل الاسترداد ب: حفظ الحالة باستمرار
يمكننا تشغيل مؤقت وحفظ حالة التطبيق باستمرار على جانب النظام الأساسي في الذاكرة. دعنا نقول كل 3 ثوان. عندما يستدعي Android الحالة SavedInstanceState ، يمكننا إعادة الحالة المحفوظة مسبقًا. (نعم ، قد نفقد التغييرات التي حدثت في آخر 3 ثوانٍ)

لا أوافق كثيرًا ، إلى جانب وجود نفس المشكلات المتعلقة بـ "الحل أ" ، فإنه يضيف خطرًا جديدًا لفتح علبة من الفيروسات المتنقلة لتكرار البيانات.

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

وجود نفس المشكلات المحيطة بـ "الحل أ".

لا يتم حفظ شجرة الأدوات تلقائيًا. وهذا شيء جيد! تم فصل واجهة المستخدم والحالة أخيرًا وليس هناك سحر انعكاس عند استعادة حالة العرض أو إنشاء مثيل للأنشطة والأجزاء.

تعارض. يعد الاحتفاظ التلقائي لـ Android بشجرة Widget Tree في تلك الحالات التي تخضع فيها حالتها للتعرض مفيدًا للغاية! لماذا على الأرض تريد نقل هذه المسؤولية بالكامل إلى مطور التطبيق ؟؟ لماذا يقف هذا على النقيض من فصل الدولة عن واجهة المستخدم ؟؟ في الواقع ، تبدو هذه الطريقة التلقائية لحفظ / استعادة شجرة عنصر واجهة المستخدم مثالًا مثاليًا لفصل واجهة المستخدم / الحالة ..

يبدو "الحل أ" معقولًا تمامًا بالنسبة لي عند استخدام الأقفال المناسبة ، ولا ينبغي تشغيل ANR إذا كان مقدار العمل معقولاً. أيضًا ، من خلال تبني آلية إعلام النظام الأساسي ، فإنه يتجنب القيام بعمل تسلسل غير ضروري طوال الوقت ، والذي قد يكون مكلفًا لكل ضغطة مفتاح.

فيما يتعلق بإمكانية أتمتة هذا السلوك ، يبدو واضحًا بالنسبة لي أن عدم وجود آلية انعكاس في وقت تشغيل Dart ، وعدم وجود طريقة منظمة لتخزين الحالة في الذاكرة في Flutter ، هذا غير ممكن بأي حال من الأحوال. في الوقت الحالي ، فإن أبسط نهج يمكنني التفكير فيه للقيام بذلك هو الاشتراك في الاحتفاظ بالحالة في كائن ديناميكي واحد (تمامًا مثل this.props و this.state على React).

تضمين التغريدة

يبدو "الحل أ" معقولًا تمامًا بالنسبة لي عند استخدام الأقفال المناسبة ، ولا ينبغي تشغيل ANR إذا كان مقدار العمل معقولاً.

أنا موافق. عندما ... حيث يكون نموذج Android أكثر بساطة وقوة نظرًا لأنه لا يتطلب من المطور التعامل مع الأقفال ، فإنه لا يستمر في الحالة المؤقتة للقرص ويحتفظ بالحالة فقط في الحالات التي يكون فيها مناسبًا لشيء ما (رفرفة) لا يمكنك الاستغناء عن الاشتراك في أحداث دورة حياة Android ومرفق onSaveInstance - بادئ ذي بدء ، فما فائدة ذلك؟

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

ليس هناك شرط للاستمرار في كل ضغطة مفتاح بل لقطة تتكون من حالات محددة

يجب فقط تخزين البيانات في الحزمة كملف ByteArray لتجنب ذلك
تشغيل أي ANR أو تأخر ناتج عن الإدخال / الإخراج في سلسلة المحادثات الرئيسية.

يوم الأربعاء ، 19 ديسمبر 2018 ، 6:08 مساءً Alejandro Torrado [email protected]
كتب:

يبدو "الحل أ" معقولًا تمامًا بالنسبة لي عند استخدام مناسب
أقفال ، ولا يجب تشغيل ANR إذا كان حجم العمل معقولاً.
أيضًا ، من خلال تبني آلية الإخطار الخاصة بالمنصة ، فإنها تتجنب القيام بذلك
يعمل التسلسل غير الضروري طوال الوقت ، وقد يكون ذلك مكلفًا
لكل ضغطة مفتاح.

فيما يتعلق بإمكانية أتمتة هذا السلوك ، يبدو ذلك واضحًا بالنسبة لي
عدم وجود آلية انعكاس في وقت تشغيل Dart ، وعدم وجود
طريقة منظمة لتخزين الحالة في الذاكرة في Flutter ، هذا غير ممكن
بأي وسيلة. في الوقت الحالي ، أبسط نهج يمكنني التفكير فيه
سيكون هذا هو الاشتراك في الاحتفاظ بالحالة في كائن ديناميكي واحد (تمامًا مثل
this.props و this.state على React).

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/flutter/flutter/issues/6827#issuecomment-448671264 ،
أو كتم الخيط
https://github.com/notifications/unsubscribe-auth/AGpvBUQk17YOsjLMHTWcdJHL9rR71u6lks5u6nKGgaJpZM4KwSuX
.

الخداع الحقيقي هو بناء ByteArray الذي تتحدث عنه:

أفضل أن أقول إن الخداع الحقيقي هو أن تكون قادرًا على استعادة ذلك
ByteArray بشكل صحيح.

في الأربعاء ، 19 ديسمبر 2018 ، 6:41 مساءً كتب Gabor Varadi [email protected] :

الخداع الحقيقي هو بناء ByteArray الذي تتحدث عنه 🙂

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/flutter/flutter/issues/6827#issuecomment-448682229 ،
أو كتم الخيط
https://github.com/notifications/unsubscribe-auth/AGpvBep8AW2xkKECt6uyTjqRGwri9Pmoks5u6npKgaJpZM4KwSuX
.

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

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

بسبب هذه المشكلة ، لا يمكن دمج تطبيقات Flutter بشكل صحيح في Android مثل
لا يتصرفون بشكل صحيح عندما يكون هناك ضغط ذاكرة الوصول العشوائي ويقوم المستخدم بالتبديل
التطبيقات في هذه الظروف قبل العودة.

في الخميس ، 20 ديسمبر 2018 ، 2:42 صباحًا كتب Gabor Varadi [email protected] :

لا ، بمجرد حصولك على مجموعة البايت ، يصبح الأمر سهلاً للغاية. فقط أظهر التحميل
مؤشر أثناء تحميله (على افتراض أنك أخبرت نفسك بـ
القنوات التي تحتاجها لتحميله).

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/flutter/flutter/issues/6827#issuecomment-448827537 ،
أو كتم الخيط
https://github.com/notifications/unsubscribe-auth/AGpvBR4rmSxelodAjZvcQ6kOwPd7yxKPks5u6ushgaJpZM4KwSuX
.

ناقش حفنة منا هذا الأمر أكثر قليلاً هنا: https://github.com/mehmetf/flutter/issues/1. النقاط ذات الصلة بهذه المشكلة:

  • للتصحيح (https://github.com/flutter/flutter/issues/6827#issuecomment-447488177): تم اختبار إعلانات Google مع تشغيل "عدم الاحتفاظ بالأنشطة". لا تموت عملية التطبيق في هذه الحالة ، لذا فإن توضيحي على (https://github.com/flutter/flutter/issues/6827#issuecomment-447955562) قائم. كما أشارgitspeak و Zhuinden ، هذه ليست طريقة جيدة لاختبار السيناريوهات التي

  • عندما يكون التطبيق في الخلفية ، يتم استدعاء View.onSaveInstanceState في مؤشر ترابط واجهة المستخدم لكل عرض في التسلسل الهرمي الذي تم تمكين الحفظ فيه. لذا ، فإن الحل الجيد من وجهة نظر المطور هو أن يقوم FlutterView بتنفيذ ذلك. لمنع ظروف السباق باستخدام مؤشر ترابط واجهة المستخدم (الذي لا يمكنك / لا يجب حظره) ، يجب ألا تصل هذه الطريقة إلى تطبيق Flutter حيث لا توجد طريقة متزامنة للقيام بذلك.

  • أيًا كان ما يحفظه onSaveInstanceState لا يمكن أن يكون أكبر من 1 ميجابايت ، لذا فإن حفظ حالة العزل بالكامل أمر غير وارد. بدلاً من ذلك ، يمكننا التوصل إلى طريقة لتقسيم الكود والأصول والبيانات الموجودة في الذاكرة المعزولة وتفريغ جزء البيانات فقط المتسلسل في جزء لا يتجزأ.

  • عندما يتم استعادة العرض لاحقًا ، يقوم FlutterView بإنشاء عزل وفك جزء البيانات المحفوظة مسبقًا إن وجد.

  • عندما يكون التطبيق في الخلفية ، من المفترض أن تتوقف الحالة عن التغيير على جانب Flutter. ستكون هناك دائمًا حالات ركنية ، لذا سيكون من الجيد الخروج بقائمة بأفضل الممارسات لتطبيقات Flutter لمرافقة هذا الحل (مع الأخذ في الاعتبار كل من دورة الحياة وضغط الذاكرة ).

  • أتخيل أن هذه ستكون مشكلة أكثر إلحاحًا عندما يتعلق الأمر بالأسواق الناشئة مثل الهند حيث من المتوقع أن يعمل Flutter على الهواتف المنخفضة الجودة. يشاع أيضًا أن Android Go لديه إستراتيجية أكثر قوة لإدارة الذاكرة.

  • إنهم ينشئون ويحافظون على عزلهم الرئيسي عند بدء التطبيق (وليس النشاط). بعد ذلك ، يقومون بتمرير هذا العزل إلى عرض Flutter عند إنشائه.

هل يمكنك تقديم مثال على هذا الرمز؟

ينشئون ويحافظون على عزلهم الرئيسي عند بدء التطبيق (وليس النشاط). بعد ذلك ، يقومون بتمرير هذا العزل إلى عرض Flutter عند إنشائه.

هل يمكنك تقديم مثال على هذا الرمز؟

لا يمكنني تقديم مثال من نوع نسخ / لصق ، ولكن إليك ما يحدث:

تطبيق

  • في Application.onCreate ، وظائف تهيئة المكالمة لـ FlutterMain . ( startInitialization و ensureInitializationComplete ). يمكنك العثور على مراجع لهذه في رفرفة / ريبو المحرك.
  • قم بإنشاء كائن FlutterNativeView جديد ، قم بتمرير مثيل التطبيق كسياق.
  • قم بتشغيل الحزمة الخاصة بك في هذا العرض الأصلي (ابحث عن أمثلة runFromBundle ).
  • قم بتنفيذ واجهة في هذا التطبيق تتيح لك إرجاع هذا العرض الأصلي (شيء مثل FlutterNativeViewProvider ).

نشاط

  • قم بتوسيع FlutterFragmentActivity وتجاوز هاتين الطريقتين:
  <strong i="26">@Override</strong>
  public FlutterNativeView createFlutterNativeView() {
    FlutterNativeViewProvider provider = (FlutterNativeViewProvider) getApplication();
    return provider.getFlutterNativeView();
  }

  <strong i="27">@Override</strong>
  public boolean retainFlutterNativeView() {
    return true;
  }

يجب أن يدفعك شيء ما على هذا المنوال. لاحظ أن تطبيق Flutter الخاص بك سيبدأ في العمل قبل أن يتوفر نشاط (وبالتالي نافذة). لذلك يجب ألا تتصل بـ runApp() في وظيفتك الرئيسية حتى تتلقى إشارة من النشاط تفيد بأنه جاهز. يمكنك القيام بذلك عبر PlatformChannels ومكان واحد للإشارة إلى أنه سيكون في createFlutterNativeView ().

إخلاء المسؤولية

  1. كما ذكرت أعلاه ، نحن لا نتغاضى تمامًا عن هذا النمط لأنه لا يحل هذه المشكلة بالذات وهو فوضوي تمامًا.

  2. لاحظ أنه من المقرر تغيير العديد من واجهات برمجة التطبيقات هذه. نظرًا لأن حالات استخدام add2app لا تزال تتطور ، فإن واجهات برمجة تطبيقات تضمين Android و iOS ليست مستقرة تمامًا بعد. سنعلن عن كسر التغييرات على flutter-dev @ كالمعتاد.

تضمين التغريدة

حل الاسترداد أ: ملف حالة المثيل المحفوظ الخاص بك

للتأكد من أن لديك ملف حالة صالح ، يمكنك الكتابة إلى ملف .tmp ثم إعادة التسمية عند الانتهاء من الكتابة. إذا احتفظت أيضًا بملف الحالة السابق ، فسينتهي بك الأمر دائمًا بالحالة الصالحة. أيضًا عند قتل العملية أثناء إنقاذ الدولة.

تضمين التغريدة

ستنتهي دائمًا بحالة صالحة.

ولكن ليس بالضرورة الحالة الصحيحة ، وهو نوع من بيت القصيد ..

لا توجد أيضًا متطلبات لاستمرار الحالة في الذاكرة غير المتطايرة لتبدأ ..

تضمين التغريدة

ستنتهي دائمًا بحالة صالحة.

ولكن ليس بالضرورة الحالة الصحيحة ، وهو نوع من بيت القصيد ..

لا توجد أيضًا متطلبات لاستمرار الحالة في الذاكرة غير المتطايرة لتبدأ ..

في كثير من الحالات ، يكون وجود حالة متسقة أكثر أهمية من امتلاك "معظم القيم الأحدث". قواعد البيانات العلائقية المستخدمة مع المعاملات هي مثال على ذلك. وهو في رأيي أيضًا مكان رائع لتخزين الدولة.

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

jsroest الحجج الخاصة بك صالحة ولكن IMHO لا ينطبق على هذه المشكلة بالذات لأن "الحالة" المشار إليها هنا هي حالة واجهة المستخدم المؤقتة (مثل موضع التمرير ، التحديد (التحديدات) النشط ، الإدخال المؤقت للنص ، إلخ ...). ضع في اعتبارك أن استعادة هذا النوع من البيانات مطلوبة فقط في المواقف التي لا يتجاهل فيها المستخدم "الشاشة" بشكل صريح ولكن لا يزال من الممكن فقد إدخال المستخدم ، مثل عندما ينتقل المستخدم إلى تطبيق آخر. لا يوجد توقع للمستخدم لاستعادة الحالة المؤقتة عند فقد الطاقة مثلما لا يتوقع المستخدم أن يقوم المستعرض بتمرير آخر صفحة ويب معروضة إلى الإزاحة الدقيقة قبل فقد طاقة النظام. النقطة المهمة هي أن طبيعة البيانات التي تعتبر "حالة عابرة" مهمة والمناقشة هنا (منشأة Android onSaveInsatnceState على وجه الخصوص) تتعلق بمعالجة هذا النوع من البيانات.

تضمين التغريدة
أكتب برامج للمؤسسة. على سبيل المثال البرامج التي يتم استخدامها في المستودعات من قبل ملتقطي الطلبات ، أو مثال آخر في تجارة التجزئة ، حيث يقوم صاحب المتجر بمسح العناصر لطلبها من مورده. يتوقع زبائني أن يبدأ التطبيق من حيث تركوه ، بغض النظر عما حدث. يتضمن ذلك حالة عابرة ، ولكن أيضًا مكدس التنقل. هل يمكن أن تشرح لماذا هذا ليس مناسبًا تمامًا لمنشأة OnSaveInstanceState كما تمت مناقشته هنا؟ ربما تختلف توقعات المستخدم في السوق الاستهلاكية ، ولكن يمكن استخدام نفس المنطق لحفظ واستعادة الحالة IMHO.

تضمين التغريدة

يتوقع زبائني أن يبدأ التطبيق من حيث تركوه ، بغض النظر عما حدث.

عذرًا ، لا أعرف كيفية إنشاء تطبيقات لهؤلاء العملاء ، ربما يمكن لشخص آخر المساعدة ...

jsroest هذا سؤال رائع ولكنه لا يدخل في نطاق هذه المشكلة. يقتصر Flutter على ما يمكن أن تفعله المنصة نفسها ، وكما يذكر onSaveInstanceState يتعلق بحفظ حالة واجهة المستخدم العابرة حيث تختلف دورة حياة البيانات عما تريد. التخزين المحلي أو التخزين عن بُعد (إذا كنت تريد بقاء الحالة عبر عمليات إلغاء التثبيت والأجهزة) سيمنحك ما تريده ولكن به محاذير ، والتي تعد مرة أخرى خارج نطاق هذه المشكلة.

يوصى بقراءة إضافية:

https://developer.android.com/topic/libraries/architecture/saving-states [ملاحظة على سبيل المثال كيف تفصل هذه المقالة التخزين المحلي عن onSaveInstanceState]
https://developer.android.com/guide/topics/data/data-storage
https://firebase.google.com/docs/firestore/

jsroest إذا كان حتى إذا تم إيقافه بالقوة أو مسح المهام أو إعادة تشغيل نظام التشغيل ، فهذا شيء مخصص تمامًا وليس ما يتحدث عنه الناس هنا.

كان onSaveInstanceState يحفظ حالة المهمة لمهمة معينة ، ولكن تم إسقاطها عند مسح المهمة أو فرض الإيقاف أو إعادة تشغيل نظام التشغيل.

mehmetf أعتقد أن هذه المشكلة قد وصلت بالفعل إلى مستوى من الإسهاب يمكن أن يكون من الصعب جدًا طرحه. بمباركة LouisCAD ، أقترح عليك إغلاق هذه المشكلة وفتح واحدة جديدة تبدأ بأحدث ملخص لك (ربما تم تكييفه إلى حد ما للتأكيد على بيان المشكلة ونطاقها) بالإضافة إلى المراجع الخلفية للمناقشات السابقة.

أتفق معك ولكن سأترك هذا الأمر يصل إلى @ Matthew-carroll ؛ من المحتمل أن يمتلك هذه المشكلة.

شكرا على ملاحظاتك.

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

تتكون برامجي عادة من الأجزاء التالية:

  • الواجهة الخلفية لدفع البيانات من الحالة المستقرة عندما ينتهي المستخدم من مهمة.
  • قاعدة بيانات sqlite المحلية للحالة المستقرة
  • في بنية بيانات الذاكرة تسمى "المتغيرات" للحالة العابرة التي يمكن تحويلها إلى ذاكرة غير متطايرة بحيث يمكن أن تدوم أكثر من العملية.

بنية بيانات "المتغيرات" لا تحتوي إلا على فئات بسيطة ذات خصائص قابلة للتسلسل. جزء مهم من بنية بيانات "المتغيرات" هو كومة الشاشة. (ببساطة قائمة). الشاشة الموجودة في الأعلى هي آخر شاشة تفاعل معها المستخدم. واحد أدناه هو الذي ينتقل إليه المستخدم عند الضغط على "رجوع".

أستطيع أن أتخيل أنه يمكن استخدام OnSaveInstanceState على نظام Android الأساسي لتشغيل تسلسل بنية البيانات هذه. يجب العثور على شيء مشابه على الأنظمة الأساسية الأخرى (iOS ، المستقبل: win ، mac ، الويب) ، ولكن كما يقترح passy ، قد تؤدي النقاط الرئيسية الأخرى أيضًا إلى حدوث ذلك وقد يكون هذا جيدًا بما فيه الكفاية.

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

إذا زاد كل هذا ، فسيتم إلغاء تسلسل بنية البيانات وإتاحتها في الذاكرة. يقوم البرنامج بتحميل الشاشة الموجودة أعلى المكدس. تقوم الشاشة نفسها بتحميل حالتها المؤقتة من بنية البيانات. الآن لدينا برنامج ينجو من معالجة الموت. (أو هل فاتني شيء ما؟ هذا بالتأكيد يعمل في مشاريعي السابقة على windows mobile ، وأشكال Xamarin و asp.net. أعتقد أنه يجب أن يعمل أيضًا في Flutter).

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

S100 Main menu
  S200 Order pick
    S210 Select order
      S220 Confirm pick location
        S230 Pick nr of pieces
          S240 Report shortage
        S250 Scan dropoff location
    S300 Cycle count
      S210 XXXXXX
        S220 XXXXXX
      S230 XXXXXX
    S300 Reprint labels
      S310 XXXXXX

متغيرات عينة هيكل البيانات

Class variables {
    Class AmbientVariables {
        ….
    }

    Class ScreenStack{
        List<string> ScreenStack;
    }

    Class Orderpick {
        int selected_order;
        string comfirmed_location;
        string nr_picked;
        ….
    }

    Class CycleCount {
        ….
    }

    Class ReprintLabels {
        ….
    }
}

كل ما أود رؤيته في الرفرفة ربما يكون موجودًا بالفعل ، باستثناء إعادة إنشاء screenstack المعروف أيضًا باسم navigationstack. ليس لدي أي فكرة عن كيفية إعادة إنشاء شجرة الكائن هذه في الذاكرة. أنا أيضًا لست متأكدًا مما إذا كان ينبغي عليّ إعادة إنشائه. يمكنني أيضًا إنشاء حزمة تنقل خاصة بي وتنفيذ ذلك في رفرفة كتطبيق صفحة واحدة. نفس ما فعلت في المشاريع السابقة. ولكن بعد ذلك سأفقد الكثير من الأشياء الجيدة المضمنة من فئتي MaterialApp و Scaffold ، حيث يكون السهم / الزر الخلفي هو الأكثر وضوحًا.

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

هل يجب أن أقوم بإصدار عدد جديد أم أن هذا يتناسب مع هذا الموضوع؟

شكرًا مرة أخرى على التعليقات ، أنا أقدر ذلك حقًا.

jsroest شكرا لك على الشرح التفصيلي. الرجاء نشر هذا على StackOverflow مع علامة رفرفة. أنت تطلب بشكل أساسي توصية حول كيفية هيكلة وبناء مساراتك وكيفية حفظ آخر مسار تمت زيارته في التخزين الدائم.

قد تعتقد أن حل هذه المشكلة بالذات يحل مشكلتك أيضًا ولكن هذا ليس صحيحًا. يمكنك PM لي في github الخاص بي التعامل مع @ gmail.com إذا كنت لا تفهم السبب.

أي تقدم في هذه المسألة؟

vanlooverenkoen نظريًا إذا بمسحها عندما تحصل على savedInstanceState == null على الجانب الأصلي من Android ، فمن المحتمل أن تقوم بذلك مستدام وقابل للترميم ، الأمر ليس بهذه السهولة.

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

jsroestclaudiofelbermehmetf، هل الإجابة على هذا السؤال عنوان ستاكوفيرفلوو هذه القضايا؟ https://stackoverflow.com/questions/54015775/what-is-the-best-way-to-pass-data-or-arguments-between-flutter-app-screens/54015932

لقد كنت أتابع هذا منذ البداية ، لكن كان علي إعادة قراءة مجموعة من الرسائل لأنني فقدت السياق.

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

لقد أنشأت المكون الإضافي native_state للاستفادة من آليات Android و iOS لحفظ حالة التطبيق عند إنهاء العملية.

يمكن لـ Navigator بالفعل استعادة مسارات التطبيق من خلال الخاصية initialRoute كما اتضح ، _ إذا كنت تتبع بعض القواعد المحددة ، والتي قمت بتوثيقها وإضافة أمثلة لها ، وربما تريد دائمًا استخدامها المكون الإضافي مع استعادة المسار (إما باستخدام SavedStateRouteObserver أو بعض الآليات الأخرى).

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

eseidelGoogle أنا لا أتفق مع

تحتوي تطبيقات Android على موضوعين رئيسيين:

1) آلة حالة العملية (المعروفة أيضًا باسم أحداث دورة الحياة).
2) مجموعة أدوات واجهة المستخدم.

يقوم Flutter بـ (2) ولكن ليس (1). يحتاج إلى معالجة كليهما.

3 سنوات...

تحديث سريع: أعمل حاليًا بنشاط على استكشاف حلول لهذه المشكلة.

goderbauer فقط للتأكد من أن هذه ليست مشكلة Android فقط ، فإن iOS لديها نفس الآليات المعمول بها ، على الرغم من أن التأثير على تطبيقات iOS ربما يكون أقل. لقد قمت بإنشاء مكون إضافي يسمح بحفظ الحالة من خلال آليات نظام التشغيل والتي قد تكون شيئًا يجب النظر إليه أيضًا: https://github.com/littlerobots/flutter-native-state

hvisser كيف توصي بحفظ حالة التنقل؟ لا يحتوي المستكشف من Flutter على طريقة getRoutes .

hvisser نعم ، أنا أنظر إلى هذا لكلا

hvisser كيف توصي بحفظ حالة التنقل؟ لا يحتوي المستكشف من Flutter على طريقة getRoutes .

Zhuinden الملاح لديه بالفعل دعم له (على الرغم من أنني لا أعتقد أنه معروف جيدًا). لدي مثال على كيفية إعداده في مشروع الولاية الأصلية ، ولكن TL ؛ DR

  • يجب أن تستخدم المسارات المسماة
  • يجب أن تكون المسارات هرمية ، على سبيل المثال ، يتبع /screen1/screen2 على /screen1 على /
  • يتم دفع المسارات إلى حزمة التنقل عندما تقوم بتعيين خاصية initialRoutes ويتم تنظيم مساراتك بهذه الطريقة
  • أعتقد أنك بحاجة أيضًا إلى GlobalKey لـ navigatorKey على MaterialApp لكن هذا قد يكون خاصًا بحالتي.

لذلك إذا قمت بتعيين initialRoutes على /screen1/screen2 وكان لديك إعداد مساراتك بشكل صحيح ، فستعمل استعادة التنقل أيضًا.

حالة أخرى على الأجهزة القديمة التي تحتوي على بطاقتي sim عند التبديل بين تشغيل وضع الطائرة وإيقاف تشغيله على youtube الأصلي وستتم إعادة تشغيل تطبيقات flutter # 49057
تم إرفاق الفيديو في مشكلتي

حاول تعطيل وضع تصحيح أخطاء الرفرفة. إذا تم تمكين وضع التصحيح ، فأعد تشغيل التطبيق دائمًا.

debugShowCheckedModeBanner: خطأ ،

لقد كنت أعمل في رفرفة منذ 2018 ، يونيو / يوليو ربما وعرفت أن هذه كانت مشكلة بعد 2-3 أشهر. لم أكن أعلم أنها أصبحت قضية كبيرة. كنت أصنع تطبيقًا لمشغل الموسيقى ، وإذا ضغطت على زر الرجوع ، فإن الرفرفة تستخدم لتدمير كل حالة أثناء استمرار تشغيل الموسيقى في الخلفية. لحل هذا الأمر ، قمت بإنشاء مكتبة
أضف المكون الإضافي إلى مشروعك في pubspec.yaml _ [للمبتدئين :)] _

dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2
  system_shortcuts:

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

الاستخدام -

مهما كانت الأداة التي تعمل عليها وعلى زر الرجوع ، فأنت تعلم أن التطبيق سيغلق (أي يترك المستخدم التطبيق) أضف WidgetsBindingObserver Mixin في فئة الحالة مثل هذا

class _MusicPlayerState extends State<MusicPlayer>
    with WidgetsBindingObserver{
...
}

بعد القيام بذلك تجاوز طريقة didPopRoute وأضف الكود التالي في الطريقة

<strong i="21">@override</strong>
  Future<bool> didPopRoute() async {
    await SystemShortcuts.home();
  return true;
  }

_ لنفهم كيف يعمل هذا _:
التطبيق قيد التشغيل >> يضغط المستخدم على زر الرجوع >> يتم الضغط على زر الصفحة الرئيسية ولا يتم الضغط على زر الرجوع >> ينتقل التطبيق إلى الخلفية ولا يتم إتلاف الحالة على الإطلاق

عينة العمل -

_ تحرير _
_ هذا التطبيق مكتوب باستخدام إدارة حالة الموفر ولكن لا يوجد رمز مستخدم لإدارة أي حالة بعد إغلاق التطبيق وإعادة فتحه من الأحدث. إنه مجرد الكود الذي أضفناه يعمل هنا.

ezgif com-video-to-gif

ezgif com-video-to-gif (1)

مشاكل -

إذا كنت قد أتيت إلى تطبيقك من تطبيق آخر ، فكلما رجعت من أداة المنزل ، ستنتقل دائمًا إلى الشاشة الرئيسية وليس إلى التطبيق السابق الذي أتيت منه.
مثال :-
المستخدم موجود في WhatsApp (أو أي تطبيق آخر) >> يتلقون إشعارًا من تطبيقك >> يفتحون تطبيقك من الإشعار >> الآن يضغطون على زر الرجوع >> سيعودون إلى الشاشة الرئيسية للهاتف وليس WhatsApp (والذي يجب أن يكون السيناريو الافتراضي)
_ لم تعطيني هذه المشكلة أي مشكلة كبيرة حتى الآن وأعتقد أن جميع المستخدمين والمطورين العاديين سيكونون على ما يرام معها لأنه في حالات نادرة تأتي إلى تطبيقك من تطبيق آخر. أنا لا أتجاهل أنه يمكن أن يحدث ذلك بهدوء عدة مرات ولكنه يعمل بالنسبة لي وهو مجرد حل بديل حتى يحصل على أي مساعدة فعلية من فريق flutter الأساسي.

حقيقة أن هذه المشكلة تبلغ من العمر 3 سنوات ونصف تقريبًا ولا يزال هناك شيء مزعج للغاية.

على ما يبدو ، من المقرر العمل على هذا بحلول مايو 2020 انطلاقًا من المعلم المحدد حاليًا ... في الوقت المناسب لـ Google I / O؟

لقد نجح hvisser أعلاه في حل المشكلة ، ولكن الآن يحتاج الأشخاص فقط إلى كتابة الكود الذي يجعله يعمل

أنا أحقق تقدمًا في توفير حل لهذا. للحصول على معاينة مبكرة لكيفية تمكن التطبيق من استعادة حالة المثيل بمجرد الانتهاء من ذلك ، تحقق من هذا PR: https://github.com/flutter/samples/pull/433.

لسوء الحظ ، سيستغرق الأمر مزيدًا من الوقت حتى يتم الانتهاء من كل شيء.

وثيقة التصميم التي تحتوي على تفاصيل حول كيفية إجراء استعادة الحالة في Flutter متاحة هنا: http://flutter.dev/go/state-restoration-design

تم نشر العلاقات العامة مع إطار استعادة الحالة العامة كما هو موضح في التصميم أعلاه: https://github.com/flutter/flutter/pull/60375. ما زلت أعمل على إضافة المزيد من التغطية الاختبارية.

تم تقديم إطار العمل العام لاستعادة الحالة (https://github.com/flutter/flutter/pull/60375). أنا الآن أعمل على دمجه في أدواتنا. سنبدأ مع التمرير والحقول النصية والملاح.

يمكنك تنفيذ ذلك بنفسك عن طريق تخزين البيانات التي تريد استعادتها داخل قاعدة بيانات sqlite . هذه تقنية تُستخدم عند كتابة الخوادم.

هذا له فائدة استعادة التطبيق حتى إذا ماتت البطارية على الهاتف أو كان هناك ذعر من النواة أو أي شيء آخر. أوصي بعمل التفاف OO حول مكالمات SQLite. ليس لدى Dart شيء مثل SQLite.Net والذي يقوم بذلك تلقائيًا من أجلك للأسف.

تحرير: إنه أيضًا شيء قمنا به في الأيام الأولى من تطوير الأجهزة المحمولة لأن الذاكرة كانت محدودة للغاية و sqlite طريقة رائعة لمبادلة الأشياء بين الذاكرة والقرص.

أقوم بإغلاق هذه المشكلة منذ هبوط إطار العمل العام لاستعادة الحالة (وتضمين Android). يتم تعقب العمل المتبقي في قضايا منفصلة:

أتقنه!!!!! شكرا رفرفة !!!

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات