سؤال

أقوم بإنشاء تطبيق يتطلب تسجيل الدخول.لقد قمت بإنشاء النشاط الرئيسي وتسجيل الدخول.

في النشاط الرئيسي onCreate الطريقة أضفت الشرط التالي:

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    ...

    loadSettings();
    if(strSessionString == null)
    {
        login();
    }
    ...
}

ال onActivityResult تبدو الطريقة التي يتم تنفيذها عند انتهاء نموذج تسجيل الدخول كما يلي:

@Override
public void onActivityResult(int requestCode,
                             int resultCode,
                             Intent data)
{
    super.onActivityResult(requestCode, resultCode, data);
    switch(requestCode)
    {
        case(SHOW_SUBACTICITY_LOGIN):
        {
            if(resultCode == Activity.RESULT_OK)
            {

                strSessionString = data.getStringExtra(Login.SESSIONSTRING);
                connectionAvailable = true;
                strUsername = data.getStringExtra(Login.USERNAME);
            }
        }
    }

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

هل يعرف أحد كيفية تعيين المتغير عموميًا لتجنب ظهور نموذج تسجيل الدخول بعد مصادقة المستخدم بنجاح؟

هل كانت مفيدة؟

المحلول

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

الإجابة الأصلية:

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

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

طريقة القيام بذلك هي إنشاء فئة فرعية خاصة بك من android.app.Application, ، ثم حدد تلك الفئة في علامة التطبيق في البيان الخاص بك.الآن سيقوم Android تلقائيًا بإنشاء مثيل لهذه الفئة وإتاحتها لتطبيقك بالكامل.يمكنك الوصول إليه من أي context باستخدام Context.getApplicationContext() طريقة (Activity كما يوفر طريقة getApplication() الذي له نفس التأثير بالضبط).وفيما يلي مثال مبسط للغاية، مع التحذيرات التي يجب اتباعها:

class MyApp extends Application {

  private String myState;

  public String getState(){
    return myState;
  }
  public void setState(String s){
    myState = s;
  }
}

class Blah extends Activity {

  @Override
  public void onCreate(Bundle b){
    ...
    MyApp appState = ((MyApp)getApplicationContext());
    String state = appState.getState();
    ...
  }
}

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

شيء يجب ملاحظته من المثال أعلاه؛لنفترض أننا قمنا بدلاً من ذلك بشيء مثل:

class MyApp extends Application {

  private String myState = /* complicated and slow initialization */;

  public String getState(){
    return myState;
  }
}

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

class MyApp extends Application {

  private MyStateManager myStateManager = new MyStateManager();

  public MyStateManager getStateManager(){
    return myStateManager ;
  }
}

class MyStateManager {

  MyStateManager() {
    /* this should be fast */
  }

  String getState() {
    /* if necessary, perform blocking calls here */
    /* make sure to deal with any multithreading/synchronicity issues */

    ...

    return state;
  }
}

class Blah extends Activity {

  @Override
  public void onCreate(Bundle b){
    ...
    MyStateManager stateManager = ((MyApp)getApplicationContext()).getStateManager();
    String state = stateManager.getState();
    ...
  }
}

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

ملاحظة 1: كما علق anticafe أيضًا، من أجل ربط تجاوز التطبيق الخاص بك بالتطبيق الخاص بك بشكل صحيح، من الضروري وجود علامة في ملف البيان.مرة أخرى، راجع مستندات Android لمزيد من المعلومات.مثال:

<application
     android:name="my.application.MyApp" 
     android:icon="..."
     android:label="...">
</application>

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

إضافة:

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

لقد كان دايرمان لطيفًا بما يكفي للإشارة إلى أمر مثير للاهتمام محادثة مع ريتو ماير وديان هاكبورن حيث لا يُنصح باستخدام الفئات الفرعية للتطبيق لصالح أنماط Singleton.وقد أشار سوماتيك أيضًا إلى شيء من هذا القبيل سابقًا، على الرغم من أنني لم أره في ذلك الوقت.نظرًا لدور Reto وDianne في الحفاظ على نظام Android الأساسي، لا يمكنني أن أوصي بحسن نية بتجاهل نصائحهما.ما يقولون، يذهب.أود أن أختلف مع الآراء التي تم التعبير عنها فيما يتعلق بتفضيل Singleton على الفئات الفرعية للتطبيق.في خلافي سأستخدم المفاهيم التي تم شرحها بشكل أفضل هذا شرح StackExchange لنمط تصميم Singleton, ، حتى لا أضطر إلى تحديد المصطلحات في هذه الإجابة.أنا أشجع بشدة على قشط الرابط قبل المتابعة.نقطة بنقطة:

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

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

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

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

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

أترككم مع القائمة التالية من الجوانب السلبية لـ Singletons، كما تمت سرقتها من رابط StackExchange السابق:

  • عدم القدرة على استخدام الفئات المجردة أو الواجهة؛
  • عدم القدرة على فئة فرعية.
  • اقتران عالي عبر التطبيق (يصعب تعديله)؛
  • من الصعب الاختبار (لا يمكن التزييف/السخرية في اختبارات الوحدة)؛
  • من الصعب موازنتها في حالة الحالة القابلة للتغيير (تتطلب قفلًا واسع النطاق)؛

وأضيف خاصتي:

  • عقد غير واضح وغير قابل للإدارة مدى الحياة وغير مناسب لتطوير Android (أو معظم الأنظمة الأخرى)؛

نصائح أخرى

قم بإنشاء هذه الفئة الفرعية

public class MyApp extends Application {
  String foo;
}

في AndroidManifest.xml أضف android:name

مثال

<application android:name=".MyApp" 
       android:icon="@drawable/icon" 
       android:label="@string/app_name">

وهذا اقترحه Soonil سيلة للابقاء على الدولة لتطبيق أمر جيد، ولكن لديها نقطة ضعف واحدة - هناك حالات عندما يقتل OS عملية التطبيق بالكامل. هنا هو وثائق حول هذا - العمليات ودورات حياة

والنظر في القضية - التطبيق يذهب إلى الخلف ليتصل بك شخص ما (التطبيق الهاتف في المقدمة الآن). في هذه الحالة && تحت بعض الشروط الأخرى (مراجعة الرابط أعلاه للحصول على ما يمكن أن يكون) نظام التشغيل قد قتل عملية التطبيق الخاص بك، بما في ذلك سبيل المثال Application فرعية. ونتيجة لذلك فقدت الدولة. عندما لاحقا العودة إلى التطبيق، ثم OS يعيد كومة نشاطها والمثال Application فرعية، ولكن مجال myState سيتم null.

وAFAIK، والطريقة الوحيدة لضمان سلامة الدولة إلى استخدام أي نوع من استمرار الدولة، على سبيل المثال، استخدام الخاص من أجل ملف التطبيق أو SharedPrefernces (ويستخدم في نهاية المطاف الخاص لملف التطبيق في الملفات الداخلية).

مجرد ملاحظة ..

يضيف:

android:name=".Globals"

أو أيًا كان ما قمت بتسميته بفئتك الفرعية موجود <application> بطاقة شعار.ظللت أحاول إضافة آخر <application> علامة إلى البيان وسوف تحصل على استثناء.

ولم استطع العثور على كيفية تحديد العلامة التطبيق سواء، ولكن بعد الكثير من غوغلينغ، أصبح من الواضح من المستندات ملف واضح: استخدام الروبوت: الاسم، بالإضافة إلى الرمز الافتراضي والتسمية في مقطع التطبيق.

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

وفئة فرعية اختيارية. ومعظم البرامج لا تحتاج إلى واحد. في حالة عدم وجود فئة فرعية، يستخدم الروبوت مثيل من الفئة الأساسية التطبيق.

وماذا عن ضمان جمع الذاكرة الأم مع هذه الهياكل العالمية؟

والأنشطة لديها طريقة onPause/onDestroy() هذا ما دعا الدمار، ولكن فئة التطبيق ليس لديه حكمه. ما هي الآلية ينصح للتأكد من أن الهياكل العالمية (وخاصة تلك التي تحتوي على كل ما يشير إلى الذاكرة الأم) والقمامة التي تم جمعها بشكل مناسب عندما يتم تطبيق إما قتلوا أو يتم وضع كومة مهمة في الخلفية؟

وفقط تحتاج إلى تعريف اسم تطبيق مثل أدناه والتي ستعمل:

<application
  android:name="ApplicationName" android:icon="@drawable/icon">
</application>

وكأنه كان هناك نوقشت أعلاه OS يمكن أن تقتل التطبيق دون أي إشعار (هناك أي حال من onDestroy) حتى لا يكون هناك أي وسيلة لحفظ هذه المتغيرات العالمية.

وSharedPreferences يمكن أن يكون حلا باستثناء لديك المتغيرات المهيكلة المعقدة (في حالتي كان لي مجموعة صحيحة لتخزين معرفات التي قام المستخدم التعامل معها بالفعل). المشكلة مع SharedPreferences هو أنه من الصعب لتخزين واسترجاع هذه الهياكل في كل مرة القيم اللازمة.

في حالتي كان لي خدمة الخلفية حتى أتمكن من نقل هذه المتغيرات إلى هناك، ولأن الخدمة لديها الحدث onDestroy، وأنا يمكن أن تنقذ تلك القيم بسهولة.

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

وهكذا العثور على الطريق الصحيح، وليس هناك أفضل وسيلة.

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

لا استخدم آخر <application> ضع علامة في ملف البيان. فقط قم بإجراء تغيير واحد في الملف الموجود <application> العلامة، أضف هذا السطر android:name=".ApplicationName" أين، ApplicationName سيكون اسم فئتك الفرعية (تستخدم للتخزين العمومي) التي أنت على وشك إنشائها.

لذلك، أخيرا الخاص بك وحيد <application> يجب أن تبدو العلامة في ملف البيان كما يلي: -

<application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/Theme.AppCompat.NoActionBar"
        android:name=".ApplicationName"
        >

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

يمكنك القيام بذلك باستخدام طريقتين:

  1. باستخدام فئة التطبيق
  2. استخدام التفضيلات المشتركة

  3. باستخدام فئة التطبيق

مثال:

class SessionManager extends Application{

  String sessionKey;

  setSessionKey(String key){
    this.sessionKey=key;
  }

  String getSessisonKey(){
    return this.sessionKey;
  }
}

يمكنك استخدام الفئة أعلاه لتنفيذ تسجيل الدخول في MainActivity الخاص بك على النحو التالي.سيبدو الكود كالتالي:

@override 
public void onCreate (Bundle savedInstanceState){
  // you will this key when first time login is successful.
  SessionManager session= (SessionManager)getApplicationContext();
  String key=getSessisonKey.getKey();
  //Use this key to identify whether session is alive or not.
}

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

  1. استخدام التفضيلات المشتركة.

    String MYPREF="com.your.application.session"
    
    SharedPreferences pref= context.getSharedPreferences(MyPREF,MODE_PRIVATE);
    
    //Insert key as below:
    
    Editot editor= pref.edit();
    
    editor.putString("key","value");
    
    editor.commit();
    
    //Get key as below.
    
    SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
    
    String key= getResources().getString("key");
    

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

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

انظر هذا المثال للحصول على التفاصيل

لماذا تعمل يدويًا إذا كان بإمكانك الحصول على المزيد؟

class GlobaleVariableDemo extends Application {

    private String myGlobalState;

    public String getGlobalState(){
     return myGlobalState;
    }
    public void setGlobalState(String s){
     myGlobalState = s;
    }
}

class Demo extends Activity {

@Override
public void onCreate(Bundle b){
    ...
    GlobaleVariableDemo appState = ((GlobaleVariableDemo)getApplicationContext());
    String state = appState.getGlobalState();
    ...
    }
}

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

public class MyApplication extends Application {
    private String str = "My String";

    synchronized public String getMyString {
        return str;
    }
}

وبعد ذلك للوصول إلى هذا المتغير في آخر، استخدم هذا:

MyApplication application = (MyApplication) getApplication();
String myVar = application.getMyString();
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top