خدمات Services
في هذه الوثيقة
أساسيات
إعلان خدمة في البيان
إنشاء خدمة بدأت
توسيع فئة إنتنتسرفيس
توسيع فئة الخدمة
بدء الخدمة
إيقاف الخدمة
إنشاء خدمة ملزمة
إرسال إشعارات إلى المستخدم
تشغيل خدمة في المقدمة
إدارة دورة حياة الخدمة
تنفيذ استجابات دورة الحياة
الطبقات الرئيسية
Service
IntentService
عينات
ServiceStartArguments
LocalService
أنظر أيضا
خدمات ملزمة
Service هي مكون تطبيق يمكنه تنفيذ عمليات تشغيل طويلة في الخلفية، ولا يوفر واجهة مستخدم. مكون تطبيق آخر يمكن بدء تشغيل خدمة، وأنها لا تزال تعمل في الخلفية حتى لو كان المستخدم التبديل إلى تطبيق آخر. بالإضافة إلى ذلك، يمكن للمكون ربط خدمة للتفاعل معها وحتى إجراء الاتصالات إنتيربروسيس (إيبك). على سبيل المثال، يمكن للخدمة التعامل مع المعاملات الشبكة، تشغيل الموسيقى، تنفيذ ملف I / O، أو التفاعل مع موفر المحتوى، كل من الخلفية.
وهذه هي الأنواع الثلاثة المختلفة من الخدمات:
المقدمة
تقوم خدمة المقدمة بتنفيذ بعض العمليات التي يمكن ملاحظتها للمستخدم. على سبيل المثال، قد يستخدم تطبيق صوتي خدمة مقدمة لتشغيل مسار صوتي. يجب أن تعرض خدمات الواجهة رمز شريط الحالة . تستمر الخدمات الأمامية تشغيل حتى عندما يكون المستخدم لا يتفاعل مع التطبيق.
خلفية
تقوم خدمة الخلفية بتنفيذ عملية لا يلاحظها المستخدم مباشرة. على سبيل المثال، إذا كان أحد التطبيقات يستخدم خدمة لتخزين التخزين، فسيكون ذلك عادة خدمة خلفية.
ملاحظة: إذا كان تطبيقك يستهدف مستوى واجهة برمجة التطبيقات 26 أو أعلى، يفرض النظام قيودا على تشغيل خدمات الخلفية عندما يكون التطبيق نفسه ليس في المقدمة. في معظم الحالات من هذا القبيل، يجب أن يستخدم تطبيقك مهمة مجدولة بدلا من ذلك.
مقيد
لا بد من خدمة عند ربط مكون تطبيق إليه عن طريق استدعاء bindService() . تقدم خدمة ملزمة واجهة العميل-الخادم الذي يسمح المكونات للتفاعل مع الخدمة، وإرسال الطلبات وتلقي النتائج، وحتى القيام بذلك عبر العمليات مع الاتصالات إنتيربروسيس (إيبك). يتم تشغيل خدمة ملزمة فقط طالما أن مكون تطبيق آخر ملزم به. مكونات متعددة يمكن ربط الخدمة في وقت واحد، ولكن عندما كل منهم أونبيند، يتم تدمير الخدمة.
على الرغم من أن هذه الوثائق تناقش بشكل عام الخدمات التي تم البدء بها وربطها بشكل منفصل، إلا أن خدمتك يمكن أن تعمل بطريقتين - يمكن بدء تشغيلهما (للتشغيل إلى أجل غير مسمى) وأيضا السماح بالربط. انها مجرد مسألة ما إذا كنت تنفذ اثنين من أساليب الاستدعاء: onStartCommand() للسماح للمكونات لبدء تشغيله و onBind() للسماح ملزمة.
وبغض النظر عما إذا كان طلبك قد بدأ أو ملزم أو كليهما، فبإمكان أي مكون تطبيق استخدام الخدمة (حتى من تطبيق منفصل) بالطريقة نفسها التي يمكن بها لأي مكون استخدام النشاط - من خلال البدء به. ومع ذلك، يمكنك إعلان الخدمة كخاص في ملف البيان ومنع الدخول من التطبيقات الأخرى. يتم مناقشة هذا أكثر في القسم حول إعلان الخدمة في البيان .
تنبيه: يتم تشغيل خدمة في مؤشر الترابط الرئيسي لعملية استضافتها؛ لا تقوم الخدمة بإنشاء مؤشر الترابط الخاص بها ولا يتم تشغيلها في عملية منفصلة إلا إذا قمت بتحديد خلاف ذلك. إذا كانت الخدمة الخاصة بك سوف تقوم بتنفيذ أي عمل مكثف وحدة المعالجة المركزية أو عمليات حظر، مثل تشغيل MP3 أو الشبكات، يجب إنشاء مؤشر ترابط جديد داخل الخدمة لإكمال هذا العمل. باستخدام مؤشر ترابط منفصل، يمكنك تقليل مخاطر أخطاء التطبيق عدم الاستجابة (أنر)، ويمكن أن يظل الموضوع الرئيسي للتطبيق مخصصا لتفاعل المستخدم مع أنشطتك.
أساسيات
هل يجب استخدام خدمة أو مؤشر ترابط؟
الخدمة هي مجرد مكون يمكن تشغيله في الخلفية، حتى عندما يكون المستخدم لا يتفاعل مع التطبيق الخاص بك، لذلك يجب عليك إنشاء خدمة فقط إذا كان هذا هو ما تحتاجه.
إذا كان يجب إجراء عمل خارج سلسلة المحادثات الرئيسية، ولكن فقط أثناء تفاعل المستخدم مع التطبيق الخاص بك، يجب بدلا من ذلك إنشاء سلسلة محادثات جديدة. على سبيل المثال، إذا كنت تريد تشغيل بعض الموسيقى، ولكن فقط أثناء تشغيل نشاطك، يمكنك إنشاء مؤشر ترابط في onCreate() ، بدء تشغيله في onStart() ، وإيقافه في onStop() . أيضا النظر في استخدام AsyncTask أو HandlerThread بدلا من فئة Thread التقليدية. راجع وثيقة العمليات و خيوط لمزيد من المعلومات حول مؤشرات الترابط.
تذكر أنه إذا كنت تستخدم خدمة، فإنه لا يزال يعمل في مؤشر الترابط الرئيسي للتطبيق الخاص بك بشكل افتراضي، لذلك يجب عليك إنشاء سلسلة ترابط جديدة داخل الخدمة إذا كان يؤدي عمليات مكثفة أو حجب.
لإنشاء خدمة، يجب إنشاء فئة فرعية من Service أو استخدام إحدى فئاتها الفرعية الحالية. في تنفيذ الخاص بك، يجب تجاوز بعض أساليب الاستدعاء التي تعالج الجوانب الرئيسية من دورة حياة الخدمة وتوفير آلية تسمح المكونات لربط الخدمة، إذا كان ذلك مناسبا. هذه هي أهم طرق الاستدعاء التي يجب تجاوزها:
onStartCommand()
يستدعي النظام هذه الطريقة من خلال استدعاء startService() عندما startService() مكون آخر (مثل نشاط) بدء تشغيل الخدمة. عند تنفيذ هذه الطريقة، يتم تشغيل الخدمة ويمكن تشغيلها في الخلفية إلى أجل غير مسمى. إذا قمت بتنفيذ هذا، فمن مسؤوليتكم لوقف الخدمة عند اكتمال عملها عن طريق استدعاء stopSelf() أو stopService() . إذا كنت تريد فقط أن توفر ملزمة، لا تحتاج إلى تنفيذ هذه الطريقة.
onBind()
النظام استدعاء هذه الطريقة عن طريق استدعاء bindService() عندما يريد مكون آخر ربط مع الخدمة (مثل تنفيذ ريك). في تنفيذ هذا الأسلوب، يجب توفير واجهة يستخدمها العملاء للاتصال مع الخدمة عن طريق إرجاع IBinder . يجب عليك دائما تنفيذ هذه الطريقة. ومع ذلك، إذا كنت لا تريد السماح ملزمة، يجب عليك العودة فارغة.
onCreate()
النظام استدعاء هذه الطريقة لتنفيذ إجراءات الإعداد لمرة واحدة عند إنشاء الخدمة في البداية (قبل استدعاء إما على onStartCommand() أو onBind() ). إذا كانت الخدمة قيد التشغيل بالفعل، لا يتم استدعاء هذه الطريقة.
onDestroy()
ويستشهد النظام بهذه الطريقة عندما لا تعد الخدمة مستخدمة ويتم تدميرها. يجب أن تنفذ خدمتك هذا لتنظيف أي موارد مثل المواضيع أو المستمعين المسجلين أو أجهزة الاستقبال. هذه هي المكالمة الأخيرة التي تتلقاها الخدمة.
إذا كان مكون بدء تشغيل الخدمة من خلال استدعاء startService() (الذي يؤدي إلى استدعاء onStartCommand() )، يستمر تشغيل الخدمة حتى يتوقف نفسه مع stopSelf() أو مكون آخر يتوقف عن طريق استدعاء stopService() .
إذا bindService() مكون bindService() لإنشاء الخدمة و onStartCommand() لا يتم استدعاؤها، يتم تشغيل الخدمة فقط طالما أن المكون ملزم به. بعد خدمة غير منضم من جميع عملائها، النظام يدمره.
قوة نظام أندرويد توقف خدمة فقط عندما تكون الذاكرة منخفضة ويجب استعادة موارد النظام للنشاط الذي يحتوي على التركيز المستخدم. إذا كانت الخدمة ملتزمة بنشاط يركز على المستخدم، فمن المرجح أن يقتل؛ إذا تم الإعلان عن الخدمة لتشغيل في المقدمة ، ونادرا ما قتل. إذا تم بدء تشغيل الخدمة وطويلة المدى، النظام يخفض موقفها في قائمة المهام الخلفية مع مرور الوقت، والخدمة تصبح عرضة للغاية للقتل - إذا تم بدء الخدمة الخاصة بك، يجب تصميمه للتعامل مع بأمان إعادة تشغيل من قبل النظام. إذا كان النظام يقتل الخدمة الخاصة بك، فإنه إعادة تشغيله في أقرب وقت الموارد المتاحة، ولكن هذا يعتمد أيضا على القيمة التي تعود من onStartCommand() . لمزيد من المعلومات حول متى قد يقوم النظام بتدمير خدمة راجع المستند العمليات و خيوط .
في الأقسام التالية، سترى كيف يمكنك إنشاء أساليب خدمة startService() و bindService() ، فضلا عن كيفية استخدامها من مكونات التطبيق الأخرى.
إعلان خدمة في البيان
يجب عليك أن تعلن جميع الخدمات في ملف البيان الخاص بالطلب الخاص بك، تماما كما تفعل للأنشطة والمكونات الأخرى.
لإعلان الخدمة، أضف عنصر <service> كطفل من عنصر <application> . هنا مثال:
<manifest ... >
...
<application ... >
<service android:name=".ExampleService" />
...
</application>
</manifest>
راجع مرجع عنصر <service> للحصول على مزيد من المعلومات حول إعلان الخدمة في البيان.
هناك سمات أخرى يمكنك تضمينها في عنصر <service> لتعريف الخصائص مثل الأذونات المطلوبة لبدء الخدمة والعملية التي يجب تشغيل الخدمة بها. السمة android:name هي السمة المطلوبة فقط - وهي تحدد اسم الفئة للخدمة. بعد نشر التطبيق الخاص بك، وترك هذا الاسم دون تغيير لتجنب خطر كسر التعليمات البرمجية بسبب الاعتماد على النوايا صريحة لبدء أو ربط الخدمة (قراءة بلوق وظيفة، الأشياء التي لا يمكن تغيير ).
تنبيه : لضمان أن تطبيقك آمن، استخدم دائما نية صريحة عند بدء Service ولا تعلن فلاتر النوايا لخدماتك. إن استخدام نية ضمنية لبدء خدمة يشكل خطرا على الأمان لأنه لا يمكن أن تكون متأكدا من الخدمة التي سترد على القصد، ولا يمكن للمستخدم معرفة الخدمة التي تبدأ. بدءا من أندرويد 5.0 (أبي bindService() 21)، يلقي النظام استثناء إذا اتصلت bindService() مع نية ضمنية.
يمكنك التأكد من أن الخدمة متاحة فقط التطبيق الخاص بك عن طريق تضمين android:exported السمة android:exported ووضعه إلى false . يؤدي هذا إلى إيقاف التطبيقات الأخرى بشكل فعال من بدء الخدمة، حتى عند استخدام نية صريحة.
ملاحظة : يمكن للمستخدمين معرفة الخدمات التي يتم تشغيلها على أجهزتهم. إذا رأوا خدمة لا يتعرفون عليها أو يثقون بها، يمكنهم إيقاف الخدمة. ولتجنب توقف الخدمة عن طريق الخطأ من قبل المستخدمين، يلزمك إضافة السمة android:description دسكريبتيون إلى العنصر <service> في بيان التطبيق. في الوصف، قدم جملة قصيرة توضح ما تفعله الخدمة وما هي الفوائد التي تقدمها.
إنشاء خدمة بدأت
بدء تشغيل الخدمة هو بدء تشغيل مكون آخر من خلال استدعاء startService() ، مما يؤدي إلى استدعاء الأسلوب onStartCommand() الخدمة.
عند بدء تشغيل الخدمة، يكون لها دورة حياة مستقلة عن المكون الذي بدأ تشغيله. يمكن تشغيل الخدمة في الخلفية إلى أجل غير مسمى، حتى لو تم تدمير المكون الذي بدأ ذلك. على هذا النحو، يجب أن تتوقف الخدمة نفسها عند اكتمال مهمتها عن طريق استدعاء stopSelf() ، أو عنصر آخر يمكن إيقافه عن طريق استدعاء stopService() .
يمكن لمكون تطبيق مثل نشاط بدء تشغيل الخدمة عن طريق استدعاء startService() وتمرير Intent تحدد الخدمة وتتضمن أية بيانات للخدمة التي سيتم استخدامها. تتلقى الخدمة هذه Intent في الأسلوب onStartCommand() .
على سبيل المثال، لنفترض أن النشاط يحتاج إلى حفظ بعض البيانات إلى قاعدة بيانات على الإنترنت. النشاط يمكن أن تبدأ خدمة مصاحبة وتسليمها البيانات لحفظ عن طريق تمرير نية ل startService() . تتلقى الخدمة نية في onStartCommand() ، يتصل بالإنترنت، وينفذ معاملة قاعدة البيانات. عند اكتمال المعاملة، توقف الخدمة نفسها وتدمر.
تنبيه: يتم تشغيل خدمة في نفس عملية التطبيق الذي يتم الإعلان عنه وفي مؤشر الترابط الرئيسي لهذا التطبيق بشكل افتراضي. إذا كانت الخدمة تؤدي عمليات مكثفة أو حظر أثناء تفاعل المستخدم مع نشاط من نفس التطبيق، فإن الخدمة تؤدي إلى إبطاء أداء النشاط. لتجنب التأثير على أداء التطبيق بدء تشغيل مؤشر ترابط جديد داخل الخدمة.
تقليديا، هناك فئتين يمكنك تمديد لإنشاء خدمة بدأت:
Service
هذه هي الطبقة الأساسية لجميع الخدمات. عند توسيع هذه الفئة، من المهم إنشاء مؤشر ترابط جديد يمكن للخدمة إكمال جميع أعماله؛ تستخدم الخدمة الموضوع الرئيسي للتطبيق الخاص بك بشكل افتراضي، مما يمكن أن يبطئ أداء أي نشاط يتم تشغيل التطبيق الخاص بك.
IntentService
هذه فئة فرعية من Service التي تستخدم مؤشر ترابط عامل للتعامل مع كافة طلبات البدء، واحدة في المرة الواحدة. هذا هو أفضل خيار إذا كنت لا تتطلب أن الخدمة الخاصة بك التعامل مع طلبات متعددة في وقت واحد. تنفيذ onHandleIntent() ، الذي يتلقى النية لكل طلب بدء بحيث يمكنك إكمال العمل الخلفية.
تصف الأقسام التالية كيفية تنفيذ الخدمة باستخدام إحدى هذه الفئات.
توسيع فئة إنتنتسرفيس
لأن معظم الخدمات التي تم تشغيلها لا تحتاج إلى معالجة طلبات متعددة في وقت واحد (والتي يمكن أن تكون في الواقع سيناريو متعدد الخيوط الخطير)، فمن الأفضل أن تقوم بتطبيق الخدمة باستخدام فئة IntentService .
فئة IntentService يفعل ما يلي:
أنه يخلق مؤشر ترابط عامل الافتراضي الذي ينفذ كافة النوايا التي يتم تسليمها إلى onStartCommand() منفصلة عن الموضوع الرئيسي للتطبيق الخاص بك.
يخلق طابور عمل يمر نية واحدة في وقت onHandleIntent() لتنفيذ onHandleIntent() ، لذلك لا داعي للقلق حول موضوع خيوط.
توقف الخدمة بعد معالجة جميع طلبات البدء، لذلك لم يكن لديك لاستدعاء stopSelf() .
يوفر التنفيذ الافتراضي onBind() التي ترجع فارغة.
يوفر التنفيذ الافتراضي من onStartCommand() الذي يرسل نية إلى قائمة انتظار العمل ثم إلى onHandleIntent() التنفيذ.
لإكمال العمل الذي يتم توفيره من قبل العميل، تنفيذ onHandleIntent() . ومع ذلك، تحتاج أيضا إلى توفير منشئ صغير للخدمة.
وإليك مثال لتنفيذ IntentService :
public class HelloIntentService extends IntentService {
/**
* A constructor is required, and must call the super IntentService(String)
* constructor with a name for the worker thread.
*/
public HelloIntentService() {
super("HelloIntentService");
}
/**
* The IntentService calls this method from the default worker thread with
* the intent that started the service. When this method returns, IntentService
* stops the service, as appropriate.
*/
@Override
protected void onHandleIntent(Intent intent) {
// Normally we would do some work here, like download a file.
// For our sample, we just sleep for 5 seconds.
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// Restore interrupt status.
Thread.currentThread().interrupt();
}
}
}
هذا كل ما تحتاجه: منشئ وتنفيذ onHandleIntent() .
إذا قررت أيضا تجاوز أساليب رد أخرى مثل onCreate() أو onStartCommand() أو onDestroy() ، تأكد من استدعاء التنفيذ الفائق بحيث IntentService يمكن التعامل بشكل صحيح حياة مؤشر ترابط عامل.
على سبيل المثال، يجب على onStartCommand() إعادة التنفيذ الافتراضي، وهو كيفية تسليم النية إلى onHandleIntent() :
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
return super.onStartCommand(intent,flags,startId);
}
إلى جانب onHandleIntent() ، الطريقة الوحيدة التي لا تحتاج إلى استدعاء الطبقة الفائقة onBind() . تحتاج إلى تنفيذ هذا فقط إذا كانت الخدمة تسمح ملزمة.
في القسم التالي، سترى كيف يتم تنفيذ نفس النوع من الخدمة عند توسيع فئة Service الأساسية، والذي يستخدم المزيد من التعليمات البرمجية، ولكن قد يكون من المناسب إذا كنت بحاجة إلى التعامل مع طلبات البدء المتزامنة.
توسيع فئة الخدمة
استخدام IntentService يجعل تنفيذ الخاص بك خدمة بدأت بسيطة جدا. ومع ذلك، إذا كنت تحتاج إلى الخدمة الخاصة بك لتنفيذ متعدد خيوط (بدلا من معالجة طلبات البدء من خلال قائمة انتظار عمل)، يمكنك توسيع فئة Service للتعامل مع كل نية.
للمقارنة، يظهر المثال المثال التعليمات البرمجية تنفيذ فئة Service التي تؤدي نفس العمل المثال السابق باستخدام IntentService . وهذا هو، لكل طلب بدء، يستخدم مؤشر ترابط عامل لتنفيذ المهمة ويعالج طلب واحد فقط في وقت واحد.
public class HelloService extends Service {
private Looper mServiceLooper;
private ServiceHandler mServiceHandler;
// Handler that receives messages from the thread
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
// Normally we would do some work here, like download a file.
// For our sample, we just sleep for 5 seconds.
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// Restore interrupt status.
Thread.currentThread().interrupt();
}
// Stop the service using the startId, so that we don't stop
// the service in the middle of handling another job
stopSelf(msg.arg1);
}
}
@Override
public void onCreate() {
// Start up the thread running the service. Note that we create a
// separate thread because the service normally runs in the process's
// main thread, which we don't want to block. We also make it
// background priority so CPU-intensive work will not disrupt our UI.
HandlerThread thread = new HandlerThread("ServiceStartArguments",
Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
// Get the HandlerThread's Looper and use it for our Handler
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
// For each start request, send a message to start a job and deliver the
// start ID so we know which request we're stopping when we finish the job
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
mServiceHandler.sendMessage(msg);
// If we get killed, after returning from here, restart
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
// We don't provide binding, so return null
return null;
}
@Override
public void onDestroy() {
Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
}
}
كما ترون، انها الكثير من العمل من استخدام IntentService .
ومع ذلك، لأنك التعامل مع كل مكالمة إلى onStartCommand() نفسك، يمكنك تنفيذ طلبات متعددة في وقت واحد. هذا ليس ما يفعله هذا المثال، ولكن إذا كان هذا هو ما تريد، يمكنك إنشاء مؤشر ترابط جديد لكل طلب وتشغيلها على الفور بدلا من انتظار الانتهاء من الطلب السابق.
لاحظ أن الأسلوب onStartCommand() يجب أن ترجع عددا صحيحا. العدد الصحيح هو قيمة تصف كيفية استمرار النظام في الخدمة في حالة أن النظام يقتله. التنفيذ الافتراضي ل IntentService يعالج هذا لك، ولكن كنت قادرا على تعديله. يجب أن تكون قيمة الإرجاع من onStartCommand() واحدة من الثوابت التالية:
START_NOT_STICKY
إذا كان النظام يقتل الخدمة بعد إرجاع onStartCommand() ، لا إعادة إنشاء الخدمة ما لم تكن هناك نوايا معلقة لتسليم. هذا هو الخيار الأكثر أمانا لتجنب تشغيل الخدمة الخاصة بك عندما لا يكون ضروريا وعندما التطبيق الخاص بك يمكن ببساطة إعادة تشغيل أي وظائف غير مكتملة.
START_STICKY
إذا كان النظام يقتل الخدمة بعد onStartCommand() إرجاع إعادة إنشاء الخدمة واستدعاء onStartCommand() ، ولكن لا إعادة تسليم آخر نية. بدلا من ذلك، يستدعي النظام onStartCommand() مع نية خالية ما لم يكن هناك نوايا معلقة لبدء الخدمة. وفي هذه الحالة، يتم تسليم تلك النوايا. هذا هو مناسبة لاعبين وسائل الإعلام (أو خدمات مماثلة) التي لا تنفذ الأوامر ولكنها تعمل إلى أجل غير مسمى وانتظار وظيفة.
START_REDELIVER_INTENT
إذا كان النظام يقتل الخدمة بعد onStartCommand() إرجاع إعادة إنشاء الخدمة واستدعاء onStartCommand() مع آخر نية تم تسليمها إلى الخدمة. يتم تسليم أي نوايا معلقة بدورها. هذا هو مناسب للخدمات التي تؤدي بنشاط وظيفة يجب أن تستأنف فورا، مثل تحميل ملف.
لمزيد من التفاصيل حول قيم الإرجاع هذه، راجع الوثائق المرجعية المرتبطة لكل ثابت.
بدء الخدمة
يمكنك بدء تشغيل خدمة من نشاط أو مكون تطبيق آخر عن طريق تمرير Intent إلى startService() أو startForegroundService() . نظام أندرويد يدعو الأسلوب onStartCommand() الخدمة onStartCommand() ويمر عليه Intent ، الذي يحدد الخدمة التي تبدأ.
ملاحظة : إذا كان تطبيقك يستهدف مستوى واجهة برمجة التطبيقات 26 أو أعلى، يفرض النظام قيودا على استخدام أو إنشاء خدمات الخلفية ما لم يكن التطبيق نفسه في المقدمة. إذا كان التطبيق يحتاج إلى إنشاء خدمة المقدمة، يجب أن التطبيق استدعاء StartForegroundService() . هذا الأسلوب يخلق خدمة الخلفية، ولكن الأسلوب يشير إلى النظام أن الخدمة سوف تعزز نفسها إلى المقدمة. بمجرد إنشاء الخدمة، يجب أن تتصل الخدمة startForeground() في غضون خمس ثوان.
على سبيل المثال، يمكن أن يبدأ نشاط خدمة المثال في المقطع السابق ( HelloService ) باستخدام نية صريحة مع startService() ، كما هو موضح هنا:
Intent intent = new Intent(this, HelloService.class);
startService(intent);
كما ترون، انها الكثير من العمل من استخدام IntentService .
ومع ذلك، لأنك التعامل مع كل مكالمة إلى onStartCommand() نفسك، يمكنك تنفيذ طلبات متعددة في وقت واحد. هذا ليس ما يفعله هذا المثال، ولكن إذا كان هذا هو ما تريد، يمكنك إنشاء مؤشر ترابط جديد لكل طلب وتشغيلها على الفور بدلا من انتظار الانتهاء من الطلب السابق.
لاحظ أن الأسلوب onStartCommand() يجب أن ترجع عددا صحيحا. العدد الصحيح هو قيمة تصف كيفية استمرار النظام في الخدمة في حالة أن النظام يقتله. التنفيذ الافتراضي ل IntentService يعالج هذا لك، ولكن كنت قادرا على تعديله. يجب أن تكون قيمة الإرجاع من onStartCommand() واحدة من الثوابت التالية:
START_NOT_STICKY
إذا كان النظام يقتل الخدمة بعد إرجاع onStartCommand() ، لا إعادة إنشاء الخدمة ما لم تكن هناك نوايا معلقة لتسليم. هذا هو الخيار الأكثر أمانا لتجنب تشغيل الخدمة الخاصة بك عندما لا يكون ضروريا وعندما التطبيق الخاص بك يمكن ببساطة إعادة تشغيل أي وظائف غير مكتملة.
START_STICKY
إذا كان النظام يقتل الخدمة بعد onStartCommand() إرجاع إعادة إنشاء الخدمة واستدعاء onStartCommand() ، ولكن لا إعادة تسليم آخر نية. بدلا من ذلك، يستدعي النظام onStartCommand() مع نية خالية ما لم يكن هناك نوايا معلقة لبدء الخدمة. وفي هذه الحالة، يتم تسليم تلك النوايا. هذا هو مناسبة لاعبين وسائل الإعلام (أو خدمات مماثلة) التي لا تنفذ الأوامر ولكنها تعمل إلى أجل غير مسمى وانتظار وظيفة.
START_REDELIVER_INTENT
إذا كان النظام يقتل الخدمة بعد onStartCommand() إرجاع إعادة إنشاء الخدمة واستدعاء onStartCommand() مع آخر نية تم تسليمها إلى الخدمة. يتم تسليم أي نوايا معلقة بدورها. هذا هو مناسب للخدمات التي تؤدي بنشاط وظيفة يجب أن تستأنف فورا، مثل تحميل ملف.
لمزيد من التفاصيل حول قيم الإرجاع هذه، راجع الوثائق المرجعية المرتبطة لكل ثابت.
بدء الخدمة
يمكنك بدء تشغيل خدمة من نشاط أو مكون تطبيق آخر عن طريق تمرير Intent إلى startService() أو startForegroundService() . نظام أندرويد يدعو الأسلوب onStartCommand() الخدمة onStartCommand() ويمر عليه Intent ، الذي يحدد الخدمة التي تبدأ.
ملاحظة : إذا كان تطبيقك يستهدف مستوى واجهة برمجة التطبيقات 26 أو أعلى، يفرض النظام قيودا على استخدام أو إنشاء خدمات الخلفية ما لم يكن التطبيق نفسه في المقدمة. إذا كان التطبيق يحتاج إلى إنشاء خدمة المقدمة، يجب أن التطبيق استدعاء StartForegroundService() . هذا الأسلوب يخلق خدمة الخلفية، ولكن الأسلوب يشير إلى النظام أن الخدمة سوف تعزز نفسها إلى المقدمة. بمجرد إنشاء الخدمة، يجب أن تتصل الخدمة startForeground() في غضون خمس ثوان.
على سبيل المثال، يمكن أن يبدأ نشاط خدمة المثال في المقطع السابق ( HelloService ) باستخدام نية صريحة مع startService() ، كما هو موضح هنا:
Intent intent = new Intent(this, HelloService.class);
startService(intent);
يتم إرجاع الأسلوب startService() الفور، ويستدعي نظام أندرويد الأسلوب onStartCommand() . إذا لم يتم تشغيل الخدمة بالفعل، النظام يدعو أولا onCreate() ، ومن ثم استدعاء onStartCommand() .
إذا كانت الخدمة لا توفر أيضا ملزمة، النية التي يتم تسليمها مع startService() هو الأسلوب الوحيد للاتصال بين مكون التطبيق والخدمة. ومع ذلك، إذا كنت تريد أن ترسل الخدمة نتيجة إلى الوراء، العميل الذي يبدأ تشغيل الخدمة يمكن إنشاء PendingIntent للبث (مع getBroadcast() ) وتسليمها إلى الخدمة في Intent التي تبدأ الخدمة. يمكن للخدمة بعد ذلك استخدام البث لتقديم نتيجة.
تؤدي الطلبات المتعددة لبدء خدمة في مكالمات متعددة المقابلة إلى onStartCommand() . ومع ذلك، مطلوب طلب واحد فقط لوقف الخدمة (مع stopSelf() أو stopService() ) لوقفه.
إيقاف الخدمة
ويجب أن تدير الخدمة التي تبدأ الخدمة دورة حياتها الخاصة. وهذا يعني أن النظام لا تتوقف أو تدمر الخدمة إلا إذا كان يجب استعادة ذاكرة النظام وتستمر الخدمة لتشغيل بعد onStartCommand() إرجاع. يجب أن تتوقف الخدمة نفسها عن طريق استدعاء stopSelf() ، أو يمكن لمكون آخر إيقافه عن طريق استدعاء stopService() .
مرة واحدة طلب التوقف مع stopSelf() أو stopService() ، النظام يدمر الخدمة في أقرب وقت ممكن.
إذا كانت خدمتك تعالج طلبات متعددة إلى onStartCommand() بشكل متزامن، فيجب عدم إيقاف الخدمة عند الانتهاء من معالجة طلب البدء، حيث قد تكون قد تلقيت طلب بدء جديد (سيؤدي التوقف في نهاية الطلب الأول إلى إنهاء الثانية). لتجنب هذه المشكلة، يمكنك استخدام stopSelf(int) للتأكد من أن طلبك لوقف الخدمة يستند دائما إلى أحدث طلب بدء. وهذا هو، عند استدعاء stopSelf(int) ، يمكنك تمرير معرف طلب البدء ( startId تسليمها إلى onStartCommand() ) الذي يتوافق طلب التوقف الخاص بك. ثم، إذا تلقت الخدمة طلب بدء جديد قبل أن تتمكن من استدعاء stopSelf(int) ، لا يتطابق معرف ولا تتوقف الخدمة.
تنبیھ: لتجنب إهدار موارد النظام واستھلاك طاقة البطاریة، تأکد من أن طلبك یتوقف عن خدماتھ عندما ینتھي من العمل. إذا لزم الأمر، يمكن مكونات أخرى إيقاف الخدمة عن طريق استدعاء stopService() . حتى إذا قمت بتمكين الربط للخدمة، يجب دائما إيقاف الخدمة بنفسك إذا كان يتلقى أي وقت مضى مكالمة إلى onStartCommand() .
لمزيد من المعلومات حول دورة حياة الخدمة، راجع القسم أدناه حول إدارة دورة حياة إحدى الخدمات .
إنشاء خدمة ملزمة
خدمة ملزمة هي التي تسمح مكونات التطبيق لربط إليها عن طريق استدعاء bindService() لإنشاء اتصال طويل الأمد. عموما لا تسمح المكونات لبدء تشغيله عن طريق استدعاء startService() .
إنشاء خدمة ملزمة عندما تريد التفاعل مع الخدمة من الأنشطة والمكونات الأخرى في التطبيق الخاص بك أو كشف بعض وظائف التطبيق الخاص بك إلى تطبيقات أخرى من خلال الاتصالات إنتيربروسيس (إيبك).
لإنشاء خدمة ملزمة، قم بتنفيذ أسلوب الاستدعاء onBind() لإرجاع IBinder الذي يحدد واجهة الاتصال بالخدمة. مكونات التطبيق الأخرى يمكن ثم استدعاء bindService() لاسترداد واجهة وبدء طرق الاتصال على الخدمة. وتعيش الخدمة فقط لخدمة مكون التطبيق المرتبط بها، لذلك عندما لا تكون هناك مكونات مرتبطة بالخدمة، يقوم النظام بتدميرها. لا تحتاج إلى إيقاف خدمة ملزمة بنفس الطريقة التي يجب عند بدء تشغيل الخدمة من خلال onStartCommand() .
لإنشاء خدمة ملزمة، يجب تحديد الواجهة التي تحدد كيفية اتصال العميل بالخدمة. يجب أن يكون هذا السطح البيني بين الخدمة والعميل تنفيذ IBinder وهو ما يجب أن تقوم به الخدمة من أسلوب استدعاء onBind() . بعد أن يتلقى العميل IBinder ، يمكن أن يبدأ التفاعل مع الخدمة من خلال تلك الواجهة.
يمكن للعملاء متعددة ربط الخدمة في وقت واحد. عندما يتم العميل التفاعل مع الخدمة، فإنه يدعو unbindService() إلى إلغاء unbindService() . عندما لا يكون هناك عملاء ملزمون بالخدمة، يقوم النظام بتدمير الخدمة.
هناك طرق متعددة لتنفيذ خدمة ملزمة، والتنفيذ أكثر تعقيدا من بدء الخدمة. لهذه الأسباب، تظهر مناقشة الخدمة المحددة في وثيقة منفصلة حول " الخدمات الحدودية" .
إرسال إشعارات إلى المستخدم
عند تشغيل خدمة، يمكن أن يخطر المستخدم من الأحداث باستخدام الإخطارات نخب أو الإخطارات شريط الحالة .
إخطار نخب هو رسالة التي تظهر على سطح النافذة الحالية للحظة فقط قبل أن تختفي. يوفر إشعار شريط الحالة رمزا في شريط الحالة يحتوي على رسالة يمكن للمستخدم تحديدها لاتخاذ إجراء (مثل بدء النشاط).
عادة، إعلام شريط الحالة هو أفضل تقنية لاستخدامها عند الانتهاء من العمل خلفية مثل تحميل ملف، ويمكن للمستخدم الآن التصرف على ذلك. عندما يقوم المستخدم بتحديد الإخطار من العرض الموسعة، يمكن أن يبدأ الإشعار نشاطا (مثل عرض الملف الذي تم تنزيله).
انظر الإخطارات نخب أو شريط الحالة الإخطارات المطور أدلة لمزيد من المعلومات.
تشغيل خدمة في المقدمة
يجب أن تكون الخدمات الأمامية ملحوظة للمستخدم.
يجب عليك فقط استخدام خدمة المقدمة عندما يحتاج التطبيق الخاص بك لأداء مهمة التي يمكن ملاحظتها من قبل المستخدم حتى عندما لا تتفاعل مباشرة مع التطبيق. لهذا السبب، يجب أن تعرض الخدمات الأمامية إشعار شريط الحالة بأولوية PRIORITY_LOW أو أعلى، مما يساعد على التأكد من أن المستخدم على دراية بما يقوم به تطبيقك. إذا كان الإجراء منخفض الأهمية بما فيه الكفاية أنك تريد استخدام إشعار الحد الأدنى الأولوية، وربما كنت لا ينبغي أن تستخدم خدمة. بدلا من ذلك، فكر في استخدام مهمة مجدولة .
كل التطبيق الذي يقوم بتشغيل خدمة يضع حمولة إضافية على النظام، واستهلاك موارد النظام. إذا كان التطبيق يحاول إخفاء خدماتها باستخدام إشعار ذات أولوية منخفضة، وهذا يمكن أن يضعف أداء التطبيق المستخدم التفاعل بنشاط مع. لهذا السبب، إذا كان التطبيق يحاول تشغيل خدمة مع إشعار الحد الأدنى الأولوية، يستدعي النظام سلوك التطبيق في القسم السفلي درج إعلام.
خدمة المقدمة هي الخدمة التي المستخدم على علم بنشاط وليس مرشحا للنظام لقتل عندما منخفضة على الذاكرة. يجب أن تقدم خدمة المقدمة إشعارا لشريط الحالة، الذي يتم وضعه تحت العنوان الجاري. وهذا يعني أنه لا يمكن رفض الإشعار ما لم يتم إيقاف الخدمة أو إزالتها من المقدمة.
على سبيل المثال، يجب تعيين مشغل موسيقى يقوم بتشغيل الموسيقى من خدمة ليتم تشغيله في المقدمة، لأن المستخدم على دراية صريحة بتشغيله. قد يشير الإشعار في شريط الحالة إلى الأغنية الحالية ويسمح للمستخدم بإطلاق نشاط للتفاعل مع مشغل الموسيقى. وبالمثل، فإن التطبيق للسماح للمستخدمين تتبع أشواطها تحتاج إلى خدمة المقدمة لتتبع موقع المستخدم.
لطلب تشغيل الخدمة في المقدمة، اتصل startForeground() . تأخذ هذه الطريقة معلمتين: عدد صحيح يحدد بشكل فريد الإخطار Notification لشريط الحالة. يجب أن يكون PRIORITY_LOW أو أعلى. هنا مثال:
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent =
PendingIntent.getActivity(this, 0, notificationIntent, 0);
Notification notification =
new Notification.Builder(this, CHANNEL_DEFAULT_IMPORTANCE)
.setContentTitle(getText(R.string.notification_title))
.setContentText(getText(R.string.notification_message))
.setSmallIcon(R.drawable.icon)
.setContentIntent(pendingIntent)
.setTicker(getText(R.string.ticker_text))
.build();
startForeground(ONGOING_NOTIFICATION_ID, notification);
تحذير: يجب ألا يكون الرقم الصحيح الذي تعطيه ل startForeground() 0.
لإزالة الخدمة من المقدمة، اتصل stopForeground() . هذه الطريقة تأخذ منطقية، مما يشير إلى ما إذا كان لإزالة إعلام شريط الحالة كذلك. هذه الطريقة لا توقف الخدمة. ومع ذلك، إذا قمت بإيقاف الخدمة في حين أنها لا تزال قيد التشغيل في المقدمة، تتم إزالة الإخطار أيضا.
لمزيد من المعلومات حول الإشعارات، راجع إنشاء إشعارات شريط الحالة .
إدارة دورة حياة الخدمة
دورة حياة الخدمة أبسط بكثير من دورة النشاط. ومع ذلك، فمن الأهم من ذلك أن تولي اهتماما وثيقا لكيفية إنشاء خدمتك وتدميرها لأن الخدمة يمكن تشغيلها في الخلفية دون أن يكون المستخدم على بينة.
يمكن أن تتبع دورة حياة الخدمة - بدءا من إنشائها إلى وقت تدميرها - أي من المسارين التاليين:
خدمة بدأت
يتم إنشاء الخدمة عند استدعاء مكون آخر startService() . ثم تشغيل الخدمة إلى أجل غير مسمى ويجب أن تتوقف عن نفسها عن طريق استدعاء stopSelf() . عنصر آخر يمكن أيضا إيقاف الخدمة عن طريق استدعاء stopService() . عند إيقاف الخدمة، يقوم النظام بتدميره.
خدمة ملزمة
يتم إنشاء الخدمة عند مكون آخر (عميل) يدعو bindService() . العميل ثم يتصل مع الخدمة من خلال واجهة IBinder . يمكن للعميل إغلاق الاتصال عن طريق استدعاء unbindService() . يمكن للعملاء متعددة ربط نفس الخدمة وعندما كل منهم أونبيند، النظام يدمر الخدمة. الخدمة لا تحتاج إلى وقف نفسها.
وهذان المساران ليسا منفصلين تماما. يمكنك ربط خدمة التي بدأت بالفعل مع startService() . على سبيل المثال، يمكنك بدء تشغيل خدمة الموسيقى الخلفية عن طريق استدعاء startService() مع Intent التي تحدد الموسيقى للعب. في وقت لاحق، ربما عندما يريد المستخدم لممارسة بعض السيطرة على لاعب أو الحصول على معلومات حول الأغنية الحالية، يمكن أن يرتبط النشاط إلى الخدمة عن طريق الاتصال bindService() . في مثل هذه الحالات، stopService() أو stopSelf() الواقع الخدمة حتى يتم إلغاء stopSelf() كافة العملاء.
تنفيذ استجابات دورة الحياة
كما هو الحال في أي نشاط، فإن الخدمة تحتوي على أساليب استدعاء دورة الحياة التي يمكنك تنفيذها لمراقبة التغييرات في حالة الخدمة وتنفيذ العمل في الأوقات المناسبة. توضح الخدمة الهيكلية التالية كل من طرق دورة الحياة:
public class ExampleService extends Service {
int mStartMode; // indicates how to behave if the service is killed
IBinder mBinder; // interface for clients that bind
boolean mAllowRebind; // indicates whether onRebind should be used
@Override
public void onCreate() {
// The service is being created
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// The service is starting, due to a call to startService()
return mStartMode;
}
@Override
public IBinder onBind(Intent intent) {
// A client is binding to the service with bindService()
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
// All clients have unbound with unbindService()
return mAllowRebind;
}
@Override
public void onRebind(Intent intent) {
// A client is binding to the service with bindService(),
// after onUnbind() has already been called
}
@Override
public void onDestroy() {
// The service is no longer used and is being destroyed
}
}
ملاحظة: على عكس الطرق الاستدعاء النشاط دورة الحياة، كنت لا حاجة لاستدعاء تنفيذ الفائقة من هذه الأساليب رد.
الشكل 2. دورة حياة الخدمة. الرسم على اليسار يظهر دورة حياة عند إنشاء الخدمة مع startService()والرسم على اليمين يظهر دورة حياة عند إنشاء الخدمة مع bindService().
ويوضح الشكل 2 الأساليب رد نموذجي للخدمة. على الرغم من أن الرقم يفصل الخدمات التي يتم إنشاؤها من قبل startService()من تلك التي تم إنشاؤها من قبل bindService()، أن نضع في الاعتبار أن أي خدمة، بغض النظر عن كيف انها بدأت، من المحتمل أن تسمح للعملاء لربط ذلك. هذه الخدمة التي بدأت في البداية مع onStartCommand()(بواسطة الدعوة العميل startService()) لا يزال يتلقى دعوة ل onBind()(عند استدعاء العميل bindService()).
من خلال تنفيذ هذه الأساليب، يمكنك مراقبة هذه الحلقات المتداخلة اثنين من دورة حياة الخدمة:
في حياته كلها لخدمة يحدث بين الوقت الذي onCreate()يسمى والوقت الذي onDestroy()يعود. مثل هذا النشاط، وهي خدمة يفعل الإعداد الأولي في onCreate()والنشرات عن الموارد المتبقية في onDestroy(). على سبيل المثال، يمكن لخدمة تشغيل الموسيقى خلق ترابط حيث لعبت الموسيقى في onCreate()، وبعد ذلك يمكن أن يوقف موضوع في onDestroy().
ملاحظة : إن onCreate()و onDestroy()تسمى أساليب لجميع الخدمات، سواء أكانت التي أنشأتها startService()أو bindService().
في حياة نشط من خدمة يبدأ مكالمة إما onStartCommand()أو onBind(). يتم تسليم كل طريقة و Intentالتي تم تمريرها إلى إما startService()أو bindService().
إذا تم بدء تشغيل الخدمة، ينتهي عمر نشط في نفس الوقت أن تنتهي حياته بأكملها (الخدمة لا تزال نشطة حتى بعد onStartCommand()عودة). إذا لا بد من الخدمة، وحياة نشط ينتهي عندما onUnbind()يعود.
ملاحظة: على الرغم من أن يتم إيقاف خدمة التي كتبها مكالمة إما stopSelf()أو stopService()، لم يكن هناك رد منها لخدمة (لا يوجد onStop()رد). إلا إذا لا بد من الخدمة إلى العميل، ونظام يدمر عندما الخدمة stopped- onDestroy()هو رد الوحيد الواردة.
لمزيد من المعلومات حول إنشاء خدمة توفر ملزمة، راجع الخدمات ملزمة الوثيقة التي تتضمن مزيد من المعلومات حول onRebind()طريقة الاستدعاء في المقطع حول إدارة دورة حياة الخدمة المقيدة .
خدمات ملزمة
في هذه الوثيقة
أساسيات
إنشاء خدمة ملزمة
تمديد طبقة الموثق
استخدام رسول
ربط الخدمة
ملاحظات إضافية
إدارة دورة حياة خدمة ملزمة
الطبقات الرئيسية
Service
ServiceConnection
IBinder
عينات
RemoteService
LocalService
أنظر أيضا
خدمات
خدمة ملزمة هي الملقم في واجهة وحدة خدمة العميل. وهو يسمح للمكونات (مثل الأنشطة) بالالتزام بالخدمة وإرسال الطلبات وتلقي الردود وإجراء الاتصالات بين العمليات (إيبك). عادة ما تكون الخدمة الملزمة فقط في حين أنها تخدم مكون تطبيق آخر ولا تعمل في الخلفية إلى أجل غير مسمى.
ملاحظة: إذا كان تطبيقك يستهدف أندرويد 5.0 (مستوى واجهة برمجة التطبيقات 21) أو أحدث، فمن المستحسن استخدام JobScheduler لتنفيذ خدمات الخلفية. لمزيد من المعلومات حول JobScheduler ، راجع API-reference documentation لها.
تصف هذه الوثيقة كيفية إنشاء خدمة ملزمة، بما في ذلك كيفية ربط الخدمة من مكونات تطبيق أخرى. للحصول على معلومات إضافية حول الخدمات بشكل عام، مثل كيفية تقديم الإخطارات من إحدى الخدمات وتعيين الخدمة لتشغيلها في المقدمة، راجع مستند الخدمات .
أساسيات
خدمة ملزمة هي تنفيذ فئة Service التي تسمح للتطبيقات الأخرى للالتزام بها والتفاعل معها. لتوفير ملزم لخدمة يجب تنفيذ الأسلوب استدعاء onBind() . تعيد هذه الطريقة كائن IBinder الذي يحدد واجهة البرمجة التي يمكن للعملاء استخدامها للتفاعل مع الخدمة.
ملزم لخدمة بدأت
كما تمت مناقشته في وثيقة " الخدمات" ، يمكنك إنشاء خدمة يتم تشغيلها وربطها. وهذا هو، يمكنك بدء تشغيل خدمة من خلال استدعاء startService() ، والذي يسمح بتشغيل الخدمة إلى أجل غير مسمى، ويمكنك أيضا السماح للعميل لربط الخدمة عن طريق استدعاء bindService() .
إذا كنت تسمح بتشغيل الخدمة وربطها، عند بدء تشغيل الخدمة، لا يقوم النظام بتدمير الخدمة عند إلغاء ربط كافة العملاء. بدلا من ذلك، يجب إيقاف الخدمة صراحة عن طريق استدعاء stopSelf() أو stopService() .
على الرغم من أنك عادة تنفيذ إما onBind() أو onStartCommand() ، فإنه من الضروري في بعض الأحيان لتنفيذ كليهما. على سبيل المثال، قد يجد مشغل موسيقى أنه من المفيد السماح بتشغيل خدمته إلى أجل غير مسمى، وكذلك توفير الربط. وبهذه الطريقة، يمكن أن النشاط بدء تشغيل الخدمة لتشغيل بعض الموسيقى وتستمر الموسيقى للعب حتى إذا كان المستخدم يترك التطبيق. ثم، عندما يعود المستخدم إلى التطبيق، يمكن أن النشاط ربط الخدمة لاستعادة السيطرة على التشغيل.
لمزيد من المعلومات حول دورة حياة الخدمة عند إضافة ارتباط إلى خدمة تم بدء تشغيلها، راجع إدارة دورة حياة خدمة ملزمة .
عميل يربط إلى خدمة عن طريق الاتصال bindService() . عند القيام بذلك، يجب أن توفر تنفيذ ServiceConnection ، الذي يراقب الاتصال مع الخدمة. تشير قيمة الإرجاع bindService() إلى ما إذا كانت الخدمة المطلوبة موجودة وما إذا كان العميل مسموحا بالوصول إليه. عندما يقوم نظام أندرويد بإنشاء الاتصال بين العميل والخدمة، فإنه يدعو onServiceConnected() على onServiceConnected() . تتضمن الطريقة onServiceConnected() وسيطة IBinder ، التي يستخدمها العميل ثم للاتصال مع خدمة ملزمة.
يمكنك توصيل عدة عملاء بخدمة في نفس الوقت. ومع ذلك، يقوم النظام بتخزين قناة اتصال خدمة IBinder . وبعبارة أخرى، يستدعي النظام طريقة onBind() لتوليد IBinder فقط عندما يربط العميل الأول. ثم يقوم النظام بتوصيل نفس IBinder إلى كافة العملاء الإضافيين الذين onBind() بالخدمة نفسها دون استدعاء onBind() مرة أخرى.
عند إلغاء ربط العميل الأخير من الخدمة النظام يدمر الخدمة ما لم يتم بدء تشغيل الخدمة بواسطة startService() .
أهم جزء من تنفيذ الخدمة المحددة هو تعريف الواجهة التي تقوم onBind() أسلوب رد onBind() . يناقش القسم التالي عدة طرق مختلفة يمكنك من خلالها تحديد واجهة IBinder .
إنشاء خدمة ملزمة
عند إنشاء خدمة توفر IBinder ، يجب أن توفر IBinder الذي يوفر واجهة برمجة يمكن للعملاء استخدامها للتفاعل مع الخدمة. هناك ثلاث طرق يمكنك تعريف الواجهة:
تمديد طبقة الموثق
إذا كانت خدمتك خاصة onBind() الخاص وتعمل في نفس عملية العميل (وهو أمر شائع)، يجب عليك إنشاء الواجهة من خلال توسيع فئة Binder وإرجاع مثيل منه من onBind() . يتلقى العميل Binder ويمكن استخدامه للوصول مباشرة إلى الطرق العامة المتاحة في تنفيذ Binder أو Service .
هذه هي الطريقة المفضلة عندما تكون خدمتك مجرد عامل خلفية لتطبيقك الخاص. السبب الوحيد لعدم إنشاء الواجهة بهذه الطريقة هو استخدام الخدمة من خلال تطبيقات أخرى أو عبر عمليات منفصلة.
استخدام رسول
إذا كنت بحاجة إلى واجهة للعمل عبر عمليات مختلفة، يمكنك إنشاء واجهة للخدمة مع Messenger . وبهذه الطريقة، تعرف الخدمة Handler يستجيب لأنواع مختلفة من كائنات Message . هذا Handler هو الأساس Messenger التي يمكن بعد ذلك مشاركة IBinder مع العميل، مما يسمح للعميل لإرسال الأوامر إلى الخدمة باستخدام كائنات Message . بالإضافة إلى ذلك، يمكن للعميل تعريف Messenger خاص به، بحيث يمكن للخدمة إرسال الرسائل مرة أخرى.
هذا هو أبسط طريقة لإجراء اتصالات إنتيربروسيس (إيبك)، لأن طوابير Messenger كافة الطلبات في مؤشر ترابط واحد بحيث لم يكن لديك لتصميم الخدمة الخاصة بك لتكون موضوع الترابط.
استخدام إيدل
الروبوت واجهة تعريف اللغة (إيدل) تتحلل الكائنات إلى الأوليات أن نظام التشغيل يمكن فهم و مارشالس لهم عبر العمليات لأداء إيبك. الأسلوب السابق، باستخدام Messenger ، ويستند في الواقع على إيدل هيكلها الأساسي. كما ذكر أعلاه، يقوم Messenger بإنشاء قائمة انتظار من كافة طلبات العميل في مؤشر ترابط واحد، بحيث تتلقى الخدمة طلبات واحدة في كل مرة. إذا، ومع ذلك، تريد خدمتك للتعامل مع طلبات متعددة في وقت واحد، ثم يمكنك استخدام إيدل مباشرة. في هذه الحالة، يجب أن تكون الخدمة الخاصة بك آمنة وسلسلة قادرة على خيوط متعددة.
لاستخدام إيدل مباشرة، يجب إنشاء ملف .aidl الذي يحدد واجهة البرمجة. تستخدم أدوات أندرويد سك هذا الملف لإنشاء فئة مجردة تقوم بتنفيذ الواجهة والتعامل مع إيبك، والتي يمكنك بعد ذلك تمديدها ضمن خدمتك.
ملاحظة: يجب ألا تستخدم معظم التطبيقات إيدل لإنشاء خدمة ملزمة، لأنها قد تتطلب قدرات متعددة العلامات ويمكن أن تؤدي إلى تنفيذ أكثر تعقيدا. على هذا النحو، إيدل ليست مناسبة لمعظم التطبيقات وهذه الوثيقة لا يناقش كيفية استخدامها لخدمتكم. إذا كنت متأكدا من أنك بحاجة إلى استخدام إيدل مباشرة، راجع مستند إيدل .
تمديد طبقة الموثق
إذا تم استخدام الخدمة الخاصة بك فقط من قبل التطبيق المحلي ولا تحتاج إلى العمل عبر العمليات، ثم يمكنك تنفيذ الطبقة Binder الخاصة التي توفر العميل الوصول المباشر إلى الأساليب العامة في الخدمة.
ملاحظة: يعمل هذا فقط إذا كان العميل والخدمة في نفس التطبيق وعملية، وهو الأكثر شيوعا. على سبيل المثال، هذا من شأنه أن يعمل بشكل جيد لتطبيق الموسيقى التي تحتاج إلى ربط نشاط لخدمتها التي تلعب الموسيقى في الخلفية.
وإليك كيفية إعداده:
في خدمتك، إنشاء مثيل Binder أن يفعل أحد الإجراءات التالية:
يحتوي على أساليب عامة يمكن للعميل الاتصال بها.
لعرض مثيل Service الحالي الذي يحتوي على أساليب عامة يمكن للعميل الاتصال بها.
لعرض نسخة من فئة أخرى تستضيفها الخدمة باستخدام أساليب عامة يمكن للعميل الاتصال بها.
قم onBind() هذا مثيل Binder من أسلوب استدعاء onBind() .
في العميل، تلقي Binder من أسلوب استدعاء onServiceConnected() وإجراء مكالمات إلى خدمة ملزمة باستخدام الطرق المقدمة.
ملاحظة: يجب أن تكون الخدمة والعميل في نفس التطبيق بحيث يستطيع العميل إرسال الكائن الذي تم إرجاعه واستدعاء واجهات برمجة التطبيقات الخاصة به بشكل صحيح. يجب أن تكون الخدمة والعميل أيضا في نفس العملية، لأن هذه التقنية لا تؤدي أي عملية تجميع عبر العمليات.
على سبيل المثال، إليك خدمة توفر للعملاء إمكانية الوصول إلى الأساليب في الخدمة من خلال تنفيذ Binder :
public class LocalService extends Service {
// Binder given to clients
private final IBinder mBinder = new LocalBinder();
// Random number generator
private final Random mGenerator = new Random();
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
LocalService getService() {
// Return this instance of LocalService so clients can call public methods
return LocalService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
/** method for clients */
public int getRandomNumber() {
return mGenerator.nextInt(100);
}
}
يوفر getService() الأسلوب getService() للعملاء لاسترداد المثيل الحالي من LocalService . وهذا يسمح للعملاء لاستدعاء الأساليب العامة في الخدمة. على سبيل المثال، يمكن للعملاء الاتصال getRandomNumber() من الخدمة.
وهنا النشاط الذي يربط إلى LocalService ويدعو getRandomNumber() عند النقر على زر:
public class BindingActivity extends Activity {
LocalService mService;
boolean mBound = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
protected void onStart() {
super.onStart();
// Bind to LocalService
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
/** Called when a button is clicked (the button in the layout file attaches to
* this method with the android:onClick attribute) */
public void onButtonClick(View v) {
if (mBound) {
// Call a method from the LocalService.
// However, if this call were something that might hang, then this request should
// occur in a separate thread to avoid slowing down the activity performance.
int num = mService.getRandomNumber();
Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
}
}
/** Defines callbacks for service binding, passed to bindService() */
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className,
IBinder service) {
// We've bound to LocalService, cast the IBinder and get LocalService instance
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
}
توضح العينة أعلاه كيفية ربط العميل بالخدمة باستخدام تنفيذ onServiceConnected() . يقدم القسم التالي المزيد من المعلومات حول عملية الربط هذه بالخدمة.
ملاحظة: في المثال أعلاه، الأسلوب onStop() العميل من الخدمة. يجب على العملاء إلغاء الربط من الخدمات في الأوقات المناسبة، كما تمت مناقشته في الملاحظات الإضافية .
لمزيد من نموذج التعليمات البرمجية راجع فئة LocalService.java و ClassServiceActivities.java الطبقة في أبيديموس .
استخدام رسول
مقارنة مع إيدل
عندما تحتاج إلى تنفيذ إيبك، باستخدام Messenger لواجهة الخاص بك هو أبسط من استخدام إيدل، لأن Messenger طابور كافة المكالمات إلى الخدمة. واجهة إيدل نقية يرسل طلبات في وقت واحد إلى الخدمة، والتي يجب ثم التعامل مع خيوط متعددة.
بالنسبة إلى معظم التطبيقات، لا تحتاج الخدمة إلى إجراء معالجة متعددة، لذلك فإن استخدام Messenger يتيح للخدمة التعامل مع مكالمة واحدة في المرة الواحدة. إذا كان من المهم أن تكون خدمتك متعددة الخيوط، استخدم إيدل لتعريف الواجهة.
إذا كنت بحاجة إلى خدمتك للتواصل مع العمليات البعيدة، فيمكنك استخدام Messenger لتوفير الواجهة لخدمتك. هذه التقنية تسمح لك لإجراء الاتصالات إنتيربروسيس (إيبك) دون الحاجة إلى استخدام إيدل.
في ما يلي ملخص لكيفية استخدام Messenger :
تقوم الخدمة بتنفيذ Handler يتلقى رد اتصال لكل مكالمة من عميل.
تستخدم الخدمة Handler لإنشاء كائن Messenger (وهو مرجع إلى Handler ).
Messenger يخلق IBinder أن الخدمة تعود للعملاء من onBind() .
يستخدم العملاء IBinder Messenger (الذي يشير إلى Handler الخدمة)، والذي يستخدمه العميل لإرسال كائنات Message إلى الخدمة.
تتلقى الخدمة كل Message في handleMessage() على وجه التحديد، في أسلوب handleMessage() .
وبهذه الطريقة، لا توجد طرق للعميل للاتصال بالخدمة. بدلا من ذلك، يقوم العميل بتوصيل الرسائل (كائنات Message ) التي تتلقاها الخدمة في Handler .
في ما يلي مثال على خدمة بسيطة تستخدم واجهة Messenger :
public class MessengerService extends Service {
/** Command to the service to display a message */
static final int MSG_SAY_HELLO = 1;
/**
* Handler of incoming messages from clients.
*/
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SAY_HELLO:
Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
break;
default:
super.handleMessage(msg);
}
}
}
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
final Messenger mMessenger = new Messenger(new IncomingHandler());
/**
* When binding to the service, we return an interface to our messenger
* for sending messages to the service.
*/
@Override
public IBinder onBind(Intent intent) {
Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
return mMessenger.getBinder();
}
}
لاحظ أن الأسلوب handleMessage() في Handler هو حيث تتلقى الخدمة Message واردة وتقرر ما يجب القيام به، استنادا إلى what عضو.
كل ما يحتاج العميل القيام به هو إنشاء Messenger على أساس IBinder عاد من قبل الخدمة وإرسال رسالة باستخدام send() . على سبيل المثال، في ما يلي نشاط بسيط يربط بالخدمة ويقدم رسالة MSG_SAY_HELLO إلى الخدمة:
public class ActivityMessenger extends Activity {
/** Messenger for communicating with the service. */
Messenger mService = null;
/** Flag indicating whether we have called bind on the service. */
boolean mBound;
/**
* Class for interacting with the main interface of the service.
*/
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the object we can use to
// interact with the service. We are communicating with the
// service using a Messenger, so here we get a client-side
// representation of that from the raw IBinder object.
mService = new Messenger(service);
mBound = true;
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mService = null;
mBound = false;
}
};
public void sayHello(View v) {
if (!mBound) return;
// Create and send a message to the service, using a supported 'what' value
Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
protected void onStart() {
super.onStart();
// Bind to the service
bindService(new Intent(this, MessengerService.class), mConnection,
Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
}
لاحظ أن هذا المثال لا يظهر كيف يمكن للخدمة الاستجابة إلى العميل. إذا كنت تريد الخدمة للرد، تحتاج أيضا إلى إنشاء Messenger في العميل. عندما يتلقى العميل onServiceConnected() ، يرسل Message إلى الخدمة التي تتضمن Messenger العميل في المعلمة replyTo من أسلوب send() .
يمكنك الاطلاع على مثال لكيفية تقديم الرسائل ثنائية الاتجاه في عينات (خدمة) MessengerService.java (خدمة) و MessengerServiceActivities.java (العميل).
ربط الخدمة
مكونات التطبيق (العملاء) يمكن ربط خدمة عن طريق الاتصال bindService() . ثم يقوم نظام أندرويد باستدعاء الأسلوب onBind() ، الذي يعود IBinder للتفاعل مع الخدمة.
الارتباط غير متزامن، و bindService() يعود على الفور دون عدم IBinder إلى العميل. للحصول على IBinder يجب على العميل إنشاء مثيل bindService() إلى bindService() . يتضمن IBinder أسلوب الاستدعاء الذي يدعو النظام لتقديم IBinder .
ملاحظة: يمكن فقط للأنشطة والخدمات ومزودي المحتوى ربط الخدمة - لا يمكنك ربط خدمة من جهاز استقبال البث.
للالتزام بخدمة من عميلك، اتبع الخطوات التالية:
تنفيذ ServiceConnection .
يجب أن يتجاوز تنفيذك طريقتي رد اتصال:
onServiceConnected()
ويدعو النظام هذا إلى تسليم IBinder عاد من قبل onBind() طريقة الخدمة.
onServiceDisconnected()
يستدعي نظام أندرويد هذا عند فقدان الاتصال بالخدمة بشكل غير متوقع، مثل عندما تحطمت الخدمة أو تم قتلها. لا يتم استدعاء هذا عندما يلغي العميل.
استدعاء bindService() ، تمرير تنفيذ bindService() .
ملاحظة: إذا كانت الطريقة ترجع كاذبة، ليس لدى العميل اتصال صالح بالخدمة. ومع ذلك، يجب أن عميلك الاتصال unbindService() ؛ وإلا، العميل الخاص بك وسوف تبقي الخدمة من اغلاق عندما يكون خاملا.
عندما يستدعي النظام أسلوب رد الاتصال onServiceConnected() ، يمكنك البدء في إجراء مكالمات إلى الخدمة باستخدام الطرق المعرفة بواسطة الواجهة.
لقطع الاتصال من الخدمة، استدعاء unbindService() .
إذا كان العميل لا يزال مرتبطا بخدمة عندما يقوم التطبيق الخاص بك بتدمير العميل، يؤدي التدمير العميل إلى إلغاء الربط. فمن الأفضل أن إلغاء ربط العميل بمجرد أن يتم التفاعل مع الخدمة. ويؤدي ذلك إلى إيقاف تشغيل الخدمة الخاملة. لمزيد من المعلومات حول الأوقات المناسبة للربط وإلغاء الربط، راجع ملاحظات إضافية .
المثال التالي يربط العميل إلى الخدمة التي تم إنشاؤها أعلاه عن طريق توسيع فئة بيندر ، لذلك كل ما يجب القيام به هو IBinder إرجاع IBinder إلى فئة IBinder وطلب مثيل IBinder :
LocalService mService;
private ServiceConnection mConnection = new ServiceConnection() {
// Called when the connection with the service is established
public void onServiceConnected(ComponentName className, IBinder service) {
// Because we have bound to an explicit
// service that is running in our own process, we can
// cast its IBinder to a concrete class and directly access it.
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
// Called when the connection with the service disconnects unexpectedly
public void onServiceDisconnected(ComponentName className) {
Log.e(TAG, "onServiceDisconnected");
mBound = false;
}
};
مع هذا ServiceConnection ، يمكن للعميل ربط خدمة عن طريق تمريرها إلى bindService() ، كما هو موضح في المثال التالي:
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
المعلمة الأولى من bindService() هي Intent تسمي صراحة الخدمة لربط.
تحذير: إذا كنت تستخدم نية للالتزام Service ، فتأكد من أن تطبيقك آمن باستخدام نية صريحة . إن استخدام نية ضمنية لبدء خدمة يشكل خطرا على الأمان لأنه لا يمكنك التأكد من الخدمة التي سترد على النية، ولا يمكن للمستخدم معرفة الخدمة التي تبدأ. بدءا من أندرويد 5.0 (أبي bindService() 21)، يلقي النظام استثناء إذا اتصلت bindService() مع نية ضمنية.
المعلمة الثانية هو كائن ServiceConnection .
المعلمة الثالثة هي علامة تشير إلى خيارات الربط. يجب أن يكون عادة BIND_AUTO_CREATE أجل إنشاء الخدمة إذا لم تكن بالفعل على قيد الحياة. القيم المحتملة الأخرى هي BIND_DEBUG_UNBIND و BIND_NOT_FOREGROUND أو 0 أي.
ملاحظات إضافية
في ما يلي بعض الملاحظات المهمة حول الربط بالخدمة:
يجب دائما فخ استثناءات DeadObjectException ، التي يتم طرحها عند كسر الاتصال. هذا هو الاستثناء الوحيد الذي ألقاه الطرق النائية.
يتم إحضار الكائنات المرجعية عبر العمليات.
عادة ما تقوم بإقران الربط وغير الملزم أثناء التطابق بين اللحظات والدموع لحظات دورة حياة العميل، كما هو موضح في الأمثلة التالية:
إذا كنت بحاجة إلى التفاعل مع الخدمة فقط أثناء نشاطك مرئيا، يجب ربط أثناء onStart() و onStop() أثناء onStop() .
إذا كنت تريد نشاطك لتلقي الردود حتى في حين يتم إيقاف في الخلفية، ثم يمكنك ربط أثناء onCreate() و onDestroy() خلال onDestroy() . احذر من أن هذا يعني أن نشاطك يحتاج إلى استخدام الخدمة طوال الوقت الذي يتم تشغيله (حتى في الخلفية)، لذلك إذا كانت الخدمة في عملية أخرى، فإنك تزيد من وزن العملية ويصبح أكثر احتمالا أن النظام سوف اقتله.
ملاحظة: لا ترتبط عادة و onPause() onResume() و onPause() نشاط) على النشاط الخاص بك، لأن تحدث هذه الاستدعاءات في كل عملية نقل دورة حياة ويجب أن تبقي المعالجة التي تحدث عند هذه الانتقالات إلى الحد الأدنى. أيضا، إذا ربطت أنشطة متعددة في التطبيق الخاص بك نفس الخدمة وهناك انتقال بين اثنين من تلك الأنشطة، قد يتم تدمير الخدمة وإعادة إنشائها كما النشاط الحالي أونبيندس (خلال وقفة) قبل ربط واحد (خلال استئناف). ويرد وصف لعملية الانتقال هذه لكيفية تنسيق الأنشطة لدورات حياتها في وثيقة الأنشطة .
لمزيد من نموذج التعليمات البرمجية، تبين كيفية ربط خدمة، راجع فئة RemoteService.java في أبيديموس .
إدارة دورة حياة خدمة ملزمة
عندما تكون الخدمة غير منضم من كافة العملاء، يقوم نظام أندرويد onStartCommand() ما لم يكن قد بدأ أيضا مع onStartCommand() ). على هذا النحو، لم يكن لديك لإدارة دورة حياة الخدمة الخاصة بك إذا كان مجرد خدمة ملزمة - نظام أندرويد يدير لانها لكم على أساس ما إذا كان ملزما إلى أي عملاء.
ومع ذلك، إذا اخترت تطبيق أسلوب الاستدعاء onStartCommand() ، يجب إيقاف الخدمة بشكل صريح، لأن الخدمة تعتبر الآن أن تبدأ . في هذه الحالة، يتم تشغيل الخدمة حتى تتوقف الخدمة نفسها مع stopSelf() أو مكون آخر يدعو stopService() ، بغض النظر عما إذا كانت ملزمة لأي عملاء.
بالإضافة إلى ذلك، إذا تم بدء تشغيل الخدمة الخاصة بك وتقبل ملزمة، ثم عندما يستدعي النظام الأسلوب onUnbind() ، يمكنك اختياريا اختيار true إذا كنت ترغب في تلقي مكالمة إلى onRebind() في المرة التالية يربط العميل إلى الخدمة. onRebind() إرجاع الفراغ، ولكن العميل لا يزال يتلقى IBinder في onServiceConnected() . يوضح الشكل التالي منطق هذا النوع من دورة الحياة.
الشكل 1. دورة حياة الخدمة التي يتم تشغيلها كما تسمح بالربط.
للحصول على مزيد من المعلومات حول دورة حياة الخدمة التي تم تشغيلها، راجع وثيقة الخدمات .
الروبوت واجهة تعريف اللغة (إيدل)
في هذه الوثيقة
تعريف واجهة إيدل
قم بإنشاء ملف .aidl
تنفيذ واجهة
فضح واجهة للعملاء
تمرير الكائنات فوق إيبك
استدعاء طريقة إيبك
أنظر أيضا
خدمات ملزمة
إيدل (الروبوت واجهة تعريف اللغة) تشبه إدلز الأخرى التي قد عملت مع. انها تسمح لك لتحديد واجهة البرمجة التي كل من العميل والخدمة توافق على من أجل التواصل مع بعضها البعض باستخدام الاتصالات إنتيربروسيس (إيبك). على الروبوت، عملية واحدة لا يمكن عادة الوصول إلى الذاكرة من عملية أخرى. لذلك للحديث، فإنها تحتاج إلى تحلل الكائنات إلى الأولية التي يمكن لنظام التشغيل فهم، و مارشال الكائنات عبر هذا الحد بالنسبة لك. رمز للقيام بذلك حراسة مملة للكتابة، لذلك الروبوت يعالج ذلك بالنسبة لك مع إيدل.
ملاحظة: استخدام إيدل ضروري فقط إذا كنت تسمح للعملاء من تطبيقات مختلفة للوصول إلى الخدمة الخاصة بك ل إيبك وتريد التعامل مع تعدد العلامات التجارية في الخدمة الخاصة بك. إذا كنت لا تحتاج إلى تنفيذ إيبك المتزامنة عبر التطبيقات المختلفة، يجب إنشاء واجهة من خلال تنفيذ بيندر أو، إذا كنت ترغب في تنفيذ إيبك، ولكن لا تحتاج إلى التعامل مع تعدد العلامات التجارية، وتنفيذ واجهة باستخدام رسول . بغض النظر، تأكد من أنك تفهم خدمات ملزمة قبل تنفيذ إيدل.
قبل البدء في تصميم واجهة إيدل، كن على علم بأن المكالمات إلى واجهة إيدل هي استدعاءات دالة مباشرة. يجب عدم إجراء افتراضات حول مؤشر الترابط الذي تحدث فيه المكالمة. ما يحدث يختلف اعتمادا على ما إذا كانت المكالمة من مؤشر ترابط في العملية المحلية أو عملية بعيدة. على وجه التحديد:
يتم تنفيذ المكالمات التي يتم إجراؤها من العملية المحلية في نفس سلسلة المحادثات التي يتم إجراء المكالمة. إذا كان هذا هو مؤشر ترابط واجهة المستخدم الرئيسي الخاص بك، يستمر هذا الموضوع لتنفيذ في واجهة إيدل. إذا كان مؤشر ترابط آخر، وهذا هو الذي ينفذ التعليمات البرمجية الخاصة بك في الخدمة. وبالتالي، إذا كانت المواضيع المحلية فقط الوصول إلى الخدمة، يمكنك التحكم تماما المواضيع التي يتم تنفيذها في ذلك (ولكن إذا كان هذا هو الحال، ثم يجب أن لا تستخدم إيدل على الإطلاق، ولكن يجب بدلا من ذلك إنشاء واجهة بتنفيذ بيندر ).
يتم إرسال المكالمات من عملية بعيدة من تجمع ترابط يحتفظ النظام الأساسي داخل العملية الخاصة بك. يجب أن تكون مستعدا للمكالمات الواردة من المواضيع غير معروف، مع مكالمات متعددة يحدث في نفس الوقت. وبعبارة أخرى، يجب أن يكون تنفيذ واجهة إيدل موضوعا آمنا تماما.
تعمل الكلمة الرئيسية على تعديل سلوك المكالمات عن بعد. عند استخدامها، مكالمة نائية لا يمنع؛ فإنه ببساطة يرسل بيانات الصفقة ويعود على الفور. تنفيذ واجهة يتلقى في نهاية المطاف هذا كنداء منتظم من تجمع موضوع Binder كما مكالمة عن بعد العادية. إذا كان يستخدم على الطريق مع مكالمة محلية، ليس هناك أي تأثير والدعوة لا تزال متزامنة.
تعريف واجهة إيدل
يجب تعريف واجهة إيدل في ملف .aidl باستخدام بناء لغة البرمجة جافا ثم حفظه في التعليمات البرمجية المصدر (في دليل src/ ) لكل من تطبيق استضافة الخدمة وأي تطبيق آخر يربط بالخدمة.
عند إنشاء كل تطبيق يحتوي على ملف .aidl أدوات أندرويد سك إنشاء واجهة IBinder استنادا إلى ملف .aidl وحفظه في المشروع gen/ الدليل. يجب على الخدمة تنفيذ واجهة IBinder حسب الاقتضاء. يمكن للتطبيقات العميل ثم ربط الخدمة وطرق الاتصال من IBinder لأداء إيبك.
لإنشاء خدمة محدودة باستخدام إيدل، اتبع الخطوات التالية:
قم بإنشاء ملف .aidl
يحدد هذا الملف واجهة البرمجة مع تواقيع الطريقة.
تنفيذ واجهة
أدوات أندرويد سك إنشاء واجهة بلغة البرمجة جافا، استنادا إلى ملف. .aidl الخاص بك. هذه الواجهة لديها فئة مجردة الداخلية اسمه Stub الذي يمتد Binder وينفذ أساليب من واجهة إيدل الخاص بك. يجب توسيع فئة Stub وتنفيذ الطرق.
فضح واجهة للعملاء
تنفيذ Service وتجاوز onBind() لإرجاع تنفيذ فئة Stub .
تحذير: يجب أن تظل أية تغييرات تجريها على واجهة إيدل بعد الإصدار الأول متوافقة مع الإصدارات السابقة من أجل تجنب كسر التطبيقات الأخرى التي تستخدم خدمتك. وهذا يعني أنه يجب نسخ ملف .aidl الخاص بك إلى تطبيقات أخرى لكي يتمكنوا من الوصول إلى واجهة الخدمة الخاصة بك، يجب الحفاظ على دعم الواجهة الأصلية.
1. إنشاء ملف .aidl
يستخدم إيدل بنية بسيطة تسمح لك بإعلان واجهة مع واحد أو أكثر من الأساليب التي يمكن أن تأخذ المعلمات والعودة القيم. يمكن أن تكون المعلمات وقيم العودة من أي نوع، حتى واجهات إيد الأخرى التي تم إنشاؤها.
يجب إنشاء ملف .aidl باستخدام لغة برمجة جافا. يجب أن يحدد كل ملف .aidl واجهة واحدة ويتطلب فقط تعريف الواجهة وتوقيعات الأسلوب.
بشكل افتراضي، يدعم إيدل أنواع البيانات التالية:
جميع الأنواع البدائية في لغة برمجة جافا (مثل int ، long ، char ، boolean ، وهكذا)
String
CharSequence
List
يجب أن تكون جميع العناصر في List واحدة من أنواع البيانات المدعومة في هذه القائمة أو واحدة من الواجهات الأخرى التي تم إنشاؤها بواسطة إيدل أو الطرود التي قمت بإعلانها. يمكن أن تستخدم List اختياريا كفئة "عامة" (على سبيل المثال، List<String> ). الطبقة الفعلية الفعلية التي يتلقىها الجانب الآخر هو دائما ArrayList ، على الرغم من أن يتم إنشاء الطريقة لاستخدام واجهة List .
Map
يجب أن تكون جميع العناصر الموجودة في Map أحد أنواع البيانات المدعومة في هذه القائمة أو إحدى الواجهات الأخرى التي تم إنشاؤها بواسطة إيدل أو الطرود التي أعلنتها. لا يتم دعم الخرائط العامة (مثل تلك الموجودة في نموذج Map<String,Integer> فئة الخرسانة الفعلية التي HashMap الجانب الآخر هو دائما HashMap ، على الرغم من أن يتم إنشاء طريقة لاستخدام واجهة Map .
يجب تضمين عبارة import لكل نوع إضافي غير مدرج أعلاه، حتى إذا تم تعريفها في نفس الحزمة مثل الواجهة.
عند تحديد واجهة الخدمة، يجب الانتباه إلى ما يلي:
يمكن أن تأخذ الأساليب معلمات صفر أو أكثر، وإرجاع قيمة أو باطلة.
جميع المعلمات غير بدائية تتطلب علامة الاتجاه تشير إلى الطريقة التي يذهب البيانات. سواء in أو out أو inout (انظر المثال أدناه).
البدائل هي in الافتراضي، ولا يمكن أن يكون خلاف ذلك.
تنبيه: يجب أن تحد من الاتجاه إلى ما هو مطلوب حقا، لأن معلمات الاستغناء مكلفة.
يتم تضمين كافة التعليقات رمز المدرجة في ملف .aidl في واجهة IBinder ولدت (باستثناء التعليقات قبل استيراد وحزمة البيانات).
يتم دعم الطرق فقط. لا يمكنك كشف الحقول الثابتة في إيدل.
في ما يلي مثال لملف .aidl :
// IRemoteService.aidl
package com.example.android;
// Declare any non-default types here with import statements
/** Example service interface */
interface IRemoteService {
/** Request the process ID of this service, to do evil things with it. */
int getPid();
/** Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
ببساطة حفظ ملف .aidl الخاص بك في الدليل src/ المشروع الخاص بك وعند بناء التطبيق الخاص بك، أدوات سك توليد ملف واجهة IBinder في gen/ المشروع الخاص بك المشروع. اسم الملف الذي تم إنشاؤه يطابق اسم ملف .aidl ولكن مع ملحق .java (على سبيل المثال، IRemoteService.aidl النتائج في IRemoteService.java ).
إذا كنت تستخدم الروبوت ستوديو، بناء تزايدي يولد الطبقة الموثق على الفور تقريبا. إذا كنت لا تستخدم الروبوت ستوديو، ثم أداة غرادل يولد فئة الموثق في المرة القادمة التي بناء التطبيق الخاص بك، يجب عليك بناء المشروع الخاص بك مع gradle assembleDebug (أو gradle assembleRelease ) بمجرد الانتهاء من كتابة ملف .aidl ، لذلك أن التعليمات البرمجية الخاصة بك يمكن أن تربط ضد فئة ولدت.
2. تنفيذ واجهة
عند إنشاء التطبيق الخاص بك، أدوات سك الروبوت إنشاء ملف واجهة .java اسمه بعد ملف .aidl الخاص بك. تتضمن الواجهة التي تم إنشاؤها فئة فرعية اسمها Stub هو تنفيذ مجرد واجهة تعامل الأصل (على سبيل المثال، YourInterface.Stub ) ويعلن كافة الأساليب من ملف .aidl .
ملاحظة: يحدد Stub أيضا بعض الأساليب المساعد، وعلى الأخص asInterface() ، الذي يأخذ IBinder (عادة ما تم تمريره إلى أسلوب استدعاء onServiceConnected() العميل وإرجاع مثيل واجهة كعب. راجع القسم الاتصال بأسلوب إيبك للحصول على مزيد من التفاصيل حول كيفية جعل هذا الإرسال.
لتنفيذ واجهة التي تم إنشاؤها من .aidl ، قم بتوسيع واجهة Binder ولدت (على سبيل المثال، YourInterface.Stub ) وتنفيذ الطرق الموروثة من الملف .aidl .
في ما يلي مثال لتنفيذ واجهة تسمى IRemoteService (يعرفها مثال IRemoteService.aidl أعلاه) باستخدام مثيل مجهول:
private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
public int getPid(){
return Process.myPid();
}
public void basicTypes(int anInt, long aLong, boolean aBoolean,
float aFloat, double aDouble, String aString) {
// Does nothing
}
};
الآن mBinder هو مثيل من فئة Stub ( Binder )، الذي يحدد واجهة ريك للخدمة. في الخطوة التالية، يتعرض هذا المثال للعملاء حتى يتمكنوا من التفاعل مع الخدمة.
هناك بعض القواعد التي يجب أن تكون على علم بها عند تنفيذ واجهة إيدل:
ليست مضمونة المكالمات الواردة ليتم تنفيذها على مؤشر الترابط الرئيسي، لذلك تحتاج إلى التفكير في تعدد العلامات التجارية من البداية وبناء بشكل صحيح الخدمة الخاصة بك لتكون موضوع آمنة.
بشكل افتراضي، ريك المكالمات متزامنة. إذا كنت تعرف أن الخدمة تستغرق أكثر من بضعة ميلي ثانية لإكمال الطلب، يجب أن لا تتصل به من مؤشر الترابط الرئيسي للنشاط، لأنه قد يعلق التطبيق (قد يعرض أندرويد حوار "لا يستجيب") - يجب عليك وعادة ما يطلق عليها من مؤشر ترابط منفصل في العميل.
لا يتم إرسال أي استثناءات التي رمي مرة أخرى إلى المتصل.
3. فضح واجهة للعملاء
بعد تنفيذ واجهة خدمتك، يجب أن تعرضها للعملاء حتى يتمكنوا من الارتباط بها. لفضح الواجهة onBind() ، قم بتوسيع Service وتنفيذ onBind() لإرجاع مثيل من الفصل الخاص بك الذي يقوم بتنفيذ Stub إنشاؤه (كما تمت مناقشته في المقطع السابق). وإليك مثال خدمة يعرض واجهة مثال IRemoteService للعملاء.
public class RemoteService extends Service {
@Override
public void onCreate() {
super.onCreate();
}
@Override
public IBinder onBind(Intent intent) {
// Return the interface
return mBinder;
}
private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
public int getPid(){
return Process.myPid();
}
public void basicTypes(int anInt, long aLong, boolean aBoolean,
float aFloat, double aDouble, String aString) {
// Does nothing
}
};
}
الآن، عندما يقوم عميل (مثل نشاط) باستدعاء bindService() للاتصال بهذه الخدمة، يتلقى استدعاء onServiceConnected() mBinder مثيل mBinder إرجاع بواسطة طريقة onBind() .
يجب أن يكون العميل أيضا الوصول إلى فئة الواجهة، لذلك إذا كان العميل والخدمة في تطبيقات منفصلة، ثم يجب أن يكون التطبيق العميل نسخة من ملف .aidl في src/ الدليل (الذي يولد واجهة android.os.Binder - توفير وصول العميل إلى أساليب إيدل).
عندما يتلقى العميل IBinder في onServiceConnected() ، يجب استدعاء YourServiceInterface .Stub.asInterface(service) المعلمة التي تم إرجاعها إلى نوع YourServiceInterface . فمثلا:
IRemoteService mIRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
// Called when the connection with the service is established
public void onServiceConnected(ComponentName className, IBinder service) {
// Following the example above for an AIDL interface,
// this gets an instance of the IRemoteInterface, which we can use to call on the service
mIRemoteService = IRemoteService.Stub.asInterface(service);
}
// Called when the connection with the service disconnects unexpectedly
public void onServiceDisconnected(ComponentName className) {
Log.e(TAG, "Service has unexpectedly disconnected");
mIRemoteService = null;
}
};
لمزيد من نموذج التعليمات البرمجية، راجع فئة RemoteService.java في أبيديموس .
تمرير الكائنات فوق إيبك
إذا كان لديك فئة تريد إرسالها من عملية إلى أخرى من خلال واجهة إيبك، يمكنك القيام بذلك. ومع ذلك، يجب التأكد من أن التعليمات البرمجية Parcelable متاحة للجانب الآخر من قناة إيبك ويجب أن يدعم الفصل الخاص بك واجهة Parcelable . دعم واجهة Parcelable مهم لأنه يسمح لنظام أندرويد لتفكيك الكائنات إلى البدائية التي يمكن أن يكون مارشاليد عبر العمليات.
لإنشاء فئة تدعم بروتوكول Parcelable ، يجب عليك القيام بما يلي:
جعل صفك تنفيذ واجهة Parcelable .
تنفيذ writeToParcel ، الذي يأخذ الحالة الراهنة للكائن ويكتب إلى Parcel .
إضافة حقل ثابت يسمى CREATOR إلى الفصل الخاص بك الذي هو كائن تنفيذ واجهة Parcelable.Creator .
أخيرا، قم بإنشاء ملف .aidl الذي يعلن فئة الطرود الخاصة بك (كما هو موضح لملف Rect.aidl ، أدناه).
إذا كنت تستخدم عملية إنشاء مخصص لا تقم بإضافة ملف .aidl إلى بناء الخاص بك. على غرار ملف رأس في لغة C، لا يتم تجميع هذا الملف .aidl .
يستخدم إيدل هذه الأساليب والحقول في التعليمات البرمجية أنه يولد إلى مارشال و أونمارشال الكائنات الخاصة بك.
على سبيل المثال، هنا ملف Rect.aidl لإنشاء فئة مستقيمة التي يمكن طيها:
package android.graphics;
// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect;
And here is an example of how the Rect class implements the Parcelable protocol.
import android.os.Parcel;
import android.os.Parcelable;
public final class Rect implements Parcelable {
public int left;
public int top;
public int right;
public int bottom;
public static final Parcelable.Creator<Rect> CREATOR = new
Parcelable.Creator<Rect>() {
public Rect createFromParcel(Parcel in) {
return new Rect(in);
}
public Rect[] newArray(int size) {
return new Rect[size];
}
};
public Rect() {
}
private Rect(Parcel in) {
readFromParcel(in);
}
public void writeToParcel(Parcel out, int flags) {
out.writeInt(left);
out.writeInt(top);
out.writeInt(right);
out.writeInt(bottom);
}
public void readFromParcel(Parcel in) {
left = in.readInt();
top = in.readInt();
right = in.readInt();
bottom = in.readInt();
}
public int describeContents() {
return 0;
}
}
التحطيم في الطبقة Rect بسيط جدا. نلقي نظرة على الطرق الأخرى على Parcel لرؤية أنواع أخرى من القيم يمكنك الكتابة إلى الطرود.
تحذير: لا تنسى الآثار الأمنية لتلقي البيانات من العمليات الأخرى. في هذه الحالة، يقرأ Rect أربعة أرقام من Parcel ، ولكن الأمر متروك لكم للتأكد من أن هذه هي ضمن نطاق مقبول من القيم لأي ما يحاول المتصل القيام به. راجع الأمان والأذونات لمزيد من المعلومات حول كيفية الحفاظ على أمان التطبيق من البرامج الضارة.
استدعاء طريقة إيبك
فيما يلي الخطوات التي يجب أن تتخذها فئة الاتصال لاستدعاء واجهة بعيدة تعرف مع إيدل:
تضمين ملف .aidl في دليل src/ المشروع.
إعلان مثيل واجهة IBinder (ولدت استنادا إلى إيدل).
تنفيذ ServiceConnection .
استدعاء Context.bindService() ، تمر في تنفيذ ServiceConnection الخاص بك.
في تنفيذ onServiceConnected() ، سوف تتلقى مثيل IBinder (تسمى service ). استدعاء YourInterfaceName .Stub.asInterface((IBinder) service ) لإلقاء المعلمة التي تم إرجاعها إلى نوع يورينتيرفاس .
اتصل بالطرق التي حددتها على الواجهة. يجب دائما فخ استثناءات DeadObjectException ، التي يتم طرحها عند كسر الاتصال. يجب عليك أيضا استثناء استثناءات SecurityException ، التي يتم طرحها عندما يكون العمليتين المتضمنتين في استدعاء الأسلوب إيبك تعريفات إيدل متضاربة.
لقطع الاتصال، اتصل Context.unbindService() مع مثيل الواجهة.
بعض التعليقات على استدعاء خدمة إيبك:
يتم إحضار الكائنات المرجعية عبر العمليات.
يمكنك إرسال كائنات مجهولة كوسيطة الأسلوب.
لمزيد من المعلومات حول الربط إلى خدمة قراءة المستند بوند سيرفيسز .
هنا بعض نموذج التعليمات البرمجية يدل على استدعاء خدمة إنشاء إيدل، مأخوذة من عينة الخدمة عن بعد في مشروع أبيديموس.
public static class Binding extends Activity {
/** The primary interface we will be calling on the service. */
IRemoteService mService = null;
/** Another interface we use on the service. */
ISecondary mSecondaryService = null;
Button mKillButton;
TextView mCallbackText;
private boolean mIsBound;
/**
* Standard initialization of this activity. Set up the UI, then wait
* for the user to poke it before doing anything.
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.remote_service_binding);
// Watch for button clicks.
Button button = (Button)findViewById(R.id.bind);
button.setOnClickListener(mBindListener);
button = (Button)findViewById(R.id.unbind);
button.setOnClickListener(mUnbindListener);
mKillButton = (Button)findViewById(R.id.kill);
mKillButton.setOnClickListener(mKillListener);
mKillButton.setEnabled(false);
mCallbackText = (TextView)findViewById(R.id.callback);
mCallbackText.setText("Not attached.");
}
/**
* Class for interacting with the main interface of the service.
*/
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder service) {
// This is called when the connection with the service has been
// established, giving us the service object we can use to
// interact with the service. We are communicating with our
// service through an IDL interface, so get a client-side
// representation of that from the raw service object.
mService = IRemoteService.Stub.asInterface(service);
mKillButton.setEnabled(true);
mCallbackText.setText("Attached.");
// We want to monitor the service for as long as we are
// connected to it.
try {
mService.registerCallback(mCallback);
} catch (RemoteException e) {
// In this case the service has crashed before we could even
// do anything with it; we can count on soon being
// disconnected (and then reconnected if it can be restarted)
// so there is no need to do anything here.
}
// As part of the sample, tell the user what happened.
Toast.makeText(Binding.this, R.string.remote_service_connected,
Toast.LENGTH_SHORT).show();
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mService = null;
mKillButton.setEnabled(false);
mCallbackText.setText("Disconnected.");
// As part of the sample, tell the user what happened.
Toast.makeText(Binding.this, R.string.remote_service_disconnected,
Toast.LENGTH_SHORT).show();
}
};
/**
* Class for interacting with the secondary interface of the service.
*/
private ServiceConnection mSecondaryConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder service) {
// Connecting to a secondary interface is the same as any
// other interface.
mSecondaryService = ISecondary.Stub.asInterface(service);
mKillButton.setEnabled(true);
}
public void onServiceDisconnected(ComponentName className) {
mSecondaryService = null;
mKillButton.setEnabled(false);
}
};
private OnClickListener mBindListener = new OnClickListener() {
public void onClick(View v) {
// Establish a couple connections with the service, binding
// by interface names. This allows other applications to be
// installed that replace the remote service by implementing
// the same interface.
Intent intent = new Intent(Binding.this, RemoteService.class);
intent.setAction(IRemoteService.class.getName());
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
intent.setAction(ISecondary.class.getName());
bindService(intent, mSecondaryConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
mCallbackText.setText("Binding.");
}
};
private OnClickListener mUnbindListener = new OnClickListener() {
public void onClick(View v) {
if (mIsBound) {
// If we have received the service, and hence registered with
// it, then now is the time to unregister.
if (mService != null) {
try {
mService.unregisterCallback(mCallback);
} catch (RemoteException e) {
// There is nothing special we need to do if the service
// has crashed.
}
}
// Detach our existing connection.
unbindService(mConnection);
unbindService(mSecondaryConnection);
mKillButton.setEnabled(false);
mIsBound = false;
mCallbackText.setText("Unbinding.");
}
}
};
private OnClickListener mKillListener = new OnClickListener() {
public void onClick(View v) {
// To kill the process hosting our service, we need to know its
// PID. Conveniently our service has a call that will return
// to us that information.
if (mSecondaryService != null) {
try {
int pid = mSecondaryService.getPid();
// Note that, though this API allows us to request to
// kill any process based on its PID, the kernel will
// still impose standard restrictions on which PIDs you
// are actually able to kill. Typically this means only
// the process running your application and any additional
// processes created by that app as shown here; packages
// sharing a common UID will also be able to kill each
// other's processes.
Process.killProcess(pid);
mCallbackText.setText("Killed service process.");
} catch (RemoteException ex) {
// Recover gracefully from the process hosting the
// server dying.
// Just for purposes of the sample, put up a notification.
Toast.makeText(Binding.this,
R.string.remote_call_failed,
Toast.LENGTH_SHORT).show();
}
}
}
};
// ----------------------------------------------------------------------
// Code showing how to deal with callbacks.
// ----------------------------------------------------------------------
/**
* This implementation is used to receive callbacks from the remote
* service.
*/
private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
/**
* This is called by the remote service regularly to tell us about
* new values. Note that IPC calls are dispatched through a thread
* pool running in each process, so the code executing here will
* NOT be running in our main thread like most other things -- so,
* to update the UI, we need to use a Handler to hop over there.
*/
public void valueChanged(int value) {
mHandler.sendMessage(mHandler.obtainMessage(BUMP_MSG, value, 0));
}
};
private static final int BUMP_MSG = 1;
private Handler mHandler = new Handler() {
@Override public void handleMessage(Message msg) {
switch (msg.what) {
case BUMP_MSG:
mCallbackText.setText("Received from service: " + msg.arg1);
break;
default:
super.handleMessage(msg);
}
}
};
}
أساسيات
إعلان خدمة في البيان
إنشاء خدمة بدأت
توسيع فئة إنتنتسرفيس
توسيع فئة الخدمة
بدء الخدمة
إيقاف الخدمة
إنشاء خدمة ملزمة
إرسال إشعارات إلى المستخدم
تشغيل خدمة في المقدمة
إدارة دورة حياة الخدمة
تنفيذ استجابات دورة الحياة
الطبقات الرئيسية
Service
IntentService
عينات
ServiceStartArguments
LocalService
أنظر أيضا
خدمات ملزمة
Service هي مكون تطبيق يمكنه تنفيذ عمليات تشغيل طويلة في الخلفية، ولا يوفر واجهة مستخدم. مكون تطبيق آخر يمكن بدء تشغيل خدمة، وأنها لا تزال تعمل في الخلفية حتى لو كان المستخدم التبديل إلى تطبيق آخر. بالإضافة إلى ذلك، يمكن للمكون ربط خدمة للتفاعل معها وحتى إجراء الاتصالات إنتيربروسيس (إيبك). على سبيل المثال، يمكن للخدمة التعامل مع المعاملات الشبكة، تشغيل الموسيقى، تنفيذ ملف I / O، أو التفاعل مع موفر المحتوى، كل من الخلفية.
وهذه هي الأنواع الثلاثة المختلفة من الخدمات:
المقدمة
تقوم خدمة المقدمة بتنفيذ بعض العمليات التي يمكن ملاحظتها للمستخدم. على سبيل المثال، قد يستخدم تطبيق صوتي خدمة مقدمة لتشغيل مسار صوتي. يجب أن تعرض خدمات الواجهة رمز شريط الحالة . تستمر الخدمات الأمامية تشغيل حتى عندما يكون المستخدم لا يتفاعل مع التطبيق.
خلفية
تقوم خدمة الخلفية بتنفيذ عملية لا يلاحظها المستخدم مباشرة. على سبيل المثال، إذا كان أحد التطبيقات يستخدم خدمة لتخزين التخزين، فسيكون ذلك عادة خدمة خلفية.
ملاحظة: إذا كان تطبيقك يستهدف مستوى واجهة برمجة التطبيقات 26 أو أعلى، يفرض النظام قيودا على تشغيل خدمات الخلفية عندما يكون التطبيق نفسه ليس في المقدمة. في معظم الحالات من هذا القبيل، يجب أن يستخدم تطبيقك مهمة مجدولة بدلا من ذلك.
مقيد
لا بد من خدمة عند ربط مكون تطبيق إليه عن طريق استدعاء bindService() . تقدم خدمة ملزمة واجهة العميل-الخادم الذي يسمح المكونات للتفاعل مع الخدمة، وإرسال الطلبات وتلقي النتائج، وحتى القيام بذلك عبر العمليات مع الاتصالات إنتيربروسيس (إيبك). يتم تشغيل خدمة ملزمة فقط طالما أن مكون تطبيق آخر ملزم به. مكونات متعددة يمكن ربط الخدمة في وقت واحد، ولكن عندما كل منهم أونبيند، يتم تدمير الخدمة.
على الرغم من أن هذه الوثائق تناقش بشكل عام الخدمات التي تم البدء بها وربطها بشكل منفصل، إلا أن خدمتك يمكن أن تعمل بطريقتين - يمكن بدء تشغيلهما (للتشغيل إلى أجل غير مسمى) وأيضا السماح بالربط. انها مجرد مسألة ما إذا كنت تنفذ اثنين من أساليب الاستدعاء: onStartCommand() للسماح للمكونات لبدء تشغيله و onBind() للسماح ملزمة.
وبغض النظر عما إذا كان طلبك قد بدأ أو ملزم أو كليهما، فبإمكان أي مكون تطبيق استخدام الخدمة (حتى من تطبيق منفصل) بالطريقة نفسها التي يمكن بها لأي مكون استخدام النشاط - من خلال البدء به. ومع ذلك، يمكنك إعلان الخدمة كخاص في ملف البيان ومنع الدخول من التطبيقات الأخرى. يتم مناقشة هذا أكثر في القسم حول إعلان الخدمة في البيان .
تنبيه: يتم تشغيل خدمة في مؤشر الترابط الرئيسي لعملية استضافتها؛ لا تقوم الخدمة بإنشاء مؤشر الترابط الخاص بها ولا يتم تشغيلها في عملية منفصلة إلا إذا قمت بتحديد خلاف ذلك. إذا كانت الخدمة الخاصة بك سوف تقوم بتنفيذ أي عمل مكثف وحدة المعالجة المركزية أو عمليات حظر، مثل تشغيل MP3 أو الشبكات، يجب إنشاء مؤشر ترابط جديد داخل الخدمة لإكمال هذا العمل. باستخدام مؤشر ترابط منفصل، يمكنك تقليل مخاطر أخطاء التطبيق عدم الاستجابة (أنر)، ويمكن أن يظل الموضوع الرئيسي للتطبيق مخصصا لتفاعل المستخدم مع أنشطتك.
أساسيات
هل يجب استخدام خدمة أو مؤشر ترابط؟
الخدمة هي مجرد مكون يمكن تشغيله في الخلفية، حتى عندما يكون المستخدم لا يتفاعل مع التطبيق الخاص بك، لذلك يجب عليك إنشاء خدمة فقط إذا كان هذا هو ما تحتاجه.
إذا كان يجب إجراء عمل خارج سلسلة المحادثات الرئيسية، ولكن فقط أثناء تفاعل المستخدم مع التطبيق الخاص بك، يجب بدلا من ذلك إنشاء سلسلة محادثات جديدة. على سبيل المثال، إذا كنت تريد تشغيل بعض الموسيقى، ولكن فقط أثناء تشغيل نشاطك، يمكنك إنشاء مؤشر ترابط في onCreate() ، بدء تشغيله في onStart() ، وإيقافه في onStop() . أيضا النظر في استخدام AsyncTask أو HandlerThread بدلا من فئة Thread التقليدية. راجع وثيقة العمليات و خيوط لمزيد من المعلومات حول مؤشرات الترابط.
تذكر أنه إذا كنت تستخدم خدمة، فإنه لا يزال يعمل في مؤشر الترابط الرئيسي للتطبيق الخاص بك بشكل افتراضي، لذلك يجب عليك إنشاء سلسلة ترابط جديدة داخل الخدمة إذا كان يؤدي عمليات مكثفة أو حجب.
لإنشاء خدمة، يجب إنشاء فئة فرعية من Service أو استخدام إحدى فئاتها الفرعية الحالية. في تنفيذ الخاص بك، يجب تجاوز بعض أساليب الاستدعاء التي تعالج الجوانب الرئيسية من دورة حياة الخدمة وتوفير آلية تسمح المكونات لربط الخدمة، إذا كان ذلك مناسبا. هذه هي أهم طرق الاستدعاء التي يجب تجاوزها:
onStartCommand()
يستدعي النظام هذه الطريقة من خلال استدعاء startService() عندما startService() مكون آخر (مثل نشاط) بدء تشغيل الخدمة. عند تنفيذ هذه الطريقة، يتم تشغيل الخدمة ويمكن تشغيلها في الخلفية إلى أجل غير مسمى. إذا قمت بتنفيذ هذا، فمن مسؤوليتكم لوقف الخدمة عند اكتمال عملها عن طريق استدعاء stopSelf() أو stopService() . إذا كنت تريد فقط أن توفر ملزمة، لا تحتاج إلى تنفيذ هذه الطريقة.
onBind()
النظام استدعاء هذه الطريقة عن طريق استدعاء bindService() عندما يريد مكون آخر ربط مع الخدمة (مثل تنفيذ ريك). في تنفيذ هذا الأسلوب، يجب توفير واجهة يستخدمها العملاء للاتصال مع الخدمة عن طريق إرجاع IBinder . يجب عليك دائما تنفيذ هذه الطريقة. ومع ذلك، إذا كنت لا تريد السماح ملزمة، يجب عليك العودة فارغة.
onCreate()
النظام استدعاء هذه الطريقة لتنفيذ إجراءات الإعداد لمرة واحدة عند إنشاء الخدمة في البداية (قبل استدعاء إما على onStartCommand() أو onBind() ). إذا كانت الخدمة قيد التشغيل بالفعل، لا يتم استدعاء هذه الطريقة.
onDestroy()
ويستشهد النظام بهذه الطريقة عندما لا تعد الخدمة مستخدمة ويتم تدميرها. يجب أن تنفذ خدمتك هذا لتنظيف أي موارد مثل المواضيع أو المستمعين المسجلين أو أجهزة الاستقبال. هذه هي المكالمة الأخيرة التي تتلقاها الخدمة.
إذا كان مكون بدء تشغيل الخدمة من خلال استدعاء startService() (الذي يؤدي إلى استدعاء onStartCommand() )، يستمر تشغيل الخدمة حتى يتوقف نفسه مع stopSelf() أو مكون آخر يتوقف عن طريق استدعاء stopService() .
إذا bindService() مكون bindService() لإنشاء الخدمة و onStartCommand() لا يتم استدعاؤها، يتم تشغيل الخدمة فقط طالما أن المكون ملزم به. بعد خدمة غير منضم من جميع عملائها، النظام يدمره.
قوة نظام أندرويد توقف خدمة فقط عندما تكون الذاكرة منخفضة ويجب استعادة موارد النظام للنشاط الذي يحتوي على التركيز المستخدم. إذا كانت الخدمة ملتزمة بنشاط يركز على المستخدم، فمن المرجح أن يقتل؛ إذا تم الإعلان عن الخدمة لتشغيل في المقدمة ، ونادرا ما قتل. إذا تم بدء تشغيل الخدمة وطويلة المدى، النظام يخفض موقفها في قائمة المهام الخلفية مع مرور الوقت، والخدمة تصبح عرضة للغاية للقتل - إذا تم بدء الخدمة الخاصة بك، يجب تصميمه للتعامل مع بأمان إعادة تشغيل من قبل النظام. إذا كان النظام يقتل الخدمة الخاصة بك، فإنه إعادة تشغيله في أقرب وقت الموارد المتاحة، ولكن هذا يعتمد أيضا على القيمة التي تعود من onStartCommand() . لمزيد من المعلومات حول متى قد يقوم النظام بتدمير خدمة راجع المستند العمليات و خيوط .
في الأقسام التالية، سترى كيف يمكنك إنشاء أساليب خدمة startService() و bindService() ، فضلا عن كيفية استخدامها من مكونات التطبيق الأخرى.
إعلان خدمة في البيان
يجب عليك أن تعلن جميع الخدمات في ملف البيان الخاص بالطلب الخاص بك، تماما كما تفعل للأنشطة والمكونات الأخرى.
لإعلان الخدمة، أضف عنصر <service> كطفل من عنصر <application> . هنا مثال:
<manifest ... >
...
<application ... >
<service android:name=".ExampleService" />
...
</application>
</manifest>
راجع مرجع عنصر <service> للحصول على مزيد من المعلومات حول إعلان الخدمة في البيان.
هناك سمات أخرى يمكنك تضمينها في عنصر <service> لتعريف الخصائص مثل الأذونات المطلوبة لبدء الخدمة والعملية التي يجب تشغيل الخدمة بها. السمة android:name هي السمة المطلوبة فقط - وهي تحدد اسم الفئة للخدمة. بعد نشر التطبيق الخاص بك، وترك هذا الاسم دون تغيير لتجنب خطر كسر التعليمات البرمجية بسبب الاعتماد على النوايا صريحة لبدء أو ربط الخدمة (قراءة بلوق وظيفة، الأشياء التي لا يمكن تغيير ).
تنبيه : لضمان أن تطبيقك آمن، استخدم دائما نية صريحة عند بدء Service ولا تعلن فلاتر النوايا لخدماتك. إن استخدام نية ضمنية لبدء خدمة يشكل خطرا على الأمان لأنه لا يمكن أن تكون متأكدا من الخدمة التي سترد على القصد، ولا يمكن للمستخدم معرفة الخدمة التي تبدأ. بدءا من أندرويد 5.0 (أبي bindService() 21)، يلقي النظام استثناء إذا اتصلت bindService() مع نية ضمنية.
يمكنك التأكد من أن الخدمة متاحة فقط التطبيق الخاص بك عن طريق تضمين android:exported السمة android:exported ووضعه إلى false . يؤدي هذا إلى إيقاف التطبيقات الأخرى بشكل فعال من بدء الخدمة، حتى عند استخدام نية صريحة.
ملاحظة : يمكن للمستخدمين معرفة الخدمات التي يتم تشغيلها على أجهزتهم. إذا رأوا خدمة لا يتعرفون عليها أو يثقون بها، يمكنهم إيقاف الخدمة. ولتجنب توقف الخدمة عن طريق الخطأ من قبل المستخدمين، يلزمك إضافة السمة android:description دسكريبتيون إلى العنصر <service> في بيان التطبيق. في الوصف، قدم جملة قصيرة توضح ما تفعله الخدمة وما هي الفوائد التي تقدمها.
إنشاء خدمة بدأت
بدء تشغيل الخدمة هو بدء تشغيل مكون آخر من خلال استدعاء startService() ، مما يؤدي إلى استدعاء الأسلوب onStartCommand() الخدمة.
عند بدء تشغيل الخدمة، يكون لها دورة حياة مستقلة عن المكون الذي بدأ تشغيله. يمكن تشغيل الخدمة في الخلفية إلى أجل غير مسمى، حتى لو تم تدمير المكون الذي بدأ ذلك. على هذا النحو، يجب أن تتوقف الخدمة نفسها عند اكتمال مهمتها عن طريق استدعاء stopSelf() ، أو عنصر آخر يمكن إيقافه عن طريق استدعاء stopService() .
يمكن لمكون تطبيق مثل نشاط بدء تشغيل الخدمة عن طريق استدعاء startService() وتمرير Intent تحدد الخدمة وتتضمن أية بيانات للخدمة التي سيتم استخدامها. تتلقى الخدمة هذه Intent في الأسلوب onStartCommand() .
على سبيل المثال، لنفترض أن النشاط يحتاج إلى حفظ بعض البيانات إلى قاعدة بيانات على الإنترنت. النشاط يمكن أن تبدأ خدمة مصاحبة وتسليمها البيانات لحفظ عن طريق تمرير نية ل startService() . تتلقى الخدمة نية في onStartCommand() ، يتصل بالإنترنت، وينفذ معاملة قاعدة البيانات. عند اكتمال المعاملة، توقف الخدمة نفسها وتدمر.
تنبيه: يتم تشغيل خدمة في نفس عملية التطبيق الذي يتم الإعلان عنه وفي مؤشر الترابط الرئيسي لهذا التطبيق بشكل افتراضي. إذا كانت الخدمة تؤدي عمليات مكثفة أو حظر أثناء تفاعل المستخدم مع نشاط من نفس التطبيق، فإن الخدمة تؤدي إلى إبطاء أداء النشاط. لتجنب التأثير على أداء التطبيق بدء تشغيل مؤشر ترابط جديد داخل الخدمة.
تقليديا، هناك فئتين يمكنك تمديد لإنشاء خدمة بدأت:
Service
هذه هي الطبقة الأساسية لجميع الخدمات. عند توسيع هذه الفئة، من المهم إنشاء مؤشر ترابط جديد يمكن للخدمة إكمال جميع أعماله؛ تستخدم الخدمة الموضوع الرئيسي للتطبيق الخاص بك بشكل افتراضي، مما يمكن أن يبطئ أداء أي نشاط يتم تشغيل التطبيق الخاص بك.
IntentService
هذه فئة فرعية من Service التي تستخدم مؤشر ترابط عامل للتعامل مع كافة طلبات البدء، واحدة في المرة الواحدة. هذا هو أفضل خيار إذا كنت لا تتطلب أن الخدمة الخاصة بك التعامل مع طلبات متعددة في وقت واحد. تنفيذ onHandleIntent() ، الذي يتلقى النية لكل طلب بدء بحيث يمكنك إكمال العمل الخلفية.
تصف الأقسام التالية كيفية تنفيذ الخدمة باستخدام إحدى هذه الفئات.
توسيع فئة إنتنتسرفيس
لأن معظم الخدمات التي تم تشغيلها لا تحتاج إلى معالجة طلبات متعددة في وقت واحد (والتي يمكن أن تكون في الواقع سيناريو متعدد الخيوط الخطير)، فمن الأفضل أن تقوم بتطبيق الخدمة باستخدام فئة IntentService .
فئة IntentService يفعل ما يلي:
أنه يخلق مؤشر ترابط عامل الافتراضي الذي ينفذ كافة النوايا التي يتم تسليمها إلى onStartCommand() منفصلة عن الموضوع الرئيسي للتطبيق الخاص بك.
يخلق طابور عمل يمر نية واحدة في وقت onHandleIntent() لتنفيذ onHandleIntent() ، لذلك لا داعي للقلق حول موضوع خيوط.
توقف الخدمة بعد معالجة جميع طلبات البدء، لذلك لم يكن لديك لاستدعاء stopSelf() .
يوفر التنفيذ الافتراضي onBind() التي ترجع فارغة.
يوفر التنفيذ الافتراضي من onStartCommand() الذي يرسل نية إلى قائمة انتظار العمل ثم إلى onHandleIntent() التنفيذ.
لإكمال العمل الذي يتم توفيره من قبل العميل، تنفيذ onHandleIntent() . ومع ذلك، تحتاج أيضا إلى توفير منشئ صغير للخدمة.
وإليك مثال لتنفيذ IntentService :
public class HelloIntentService extends IntentService {
/**
* A constructor is required, and must call the super IntentService(String)
* constructor with a name for the worker thread.
*/
public HelloIntentService() {
super("HelloIntentService");
}
/**
* The IntentService calls this method from the default worker thread with
* the intent that started the service. When this method returns, IntentService
* stops the service, as appropriate.
*/
@Override
protected void onHandleIntent(Intent intent) {
// Normally we would do some work here, like download a file.
// For our sample, we just sleep for 5 seconds.
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// Restore interrupt status.
Thread.currentThread().interrupt();
}
}
}
هذا كل ما تحتاجه: منشئ وتنفيذ onHandleIntent() .
إذا قررت أيضا تجاوز أساليب رد أخرى مثل onCreate() أو onStartCommand() أو onDestroy() ، تأكد من استدعاء التنفيذ الفائق بحيث IntentService يمكن التعامل بشكل صحيح حياة مؤشر ترابط عامل.
على سبيل المثال، يجب على onStartCommand() إعادة التنفيذ الافتراضي، وهو كيفية تسليم النية إلى onHandleIntent() :
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
return super.onStartCommand(intent,flags,startId);
}
إلى جانب onHandleIntent() ، الطريقة الوحيدة التي لا تحتاج إلى استدعاء الطبقة الفائقة onBind() . تحتاج إلى تنفيذ هذا فقط إذا كانت الخدمة تسمح ملزمة.
في القسم التالي، سترى كيف يتم تنفيذ نفس النوع من الخدمة عند توسيع فئة Service الأساسية، والذي يستخدم المزيد من التعليمات البرمجية، ولكن قد يكون من المناسب إذا كنت بحاجة إلى التعامل مع طلبات البدء المتزامنة.
توسيع فئة الخدمة
استخدام IntentService يجعل تنفيذ الخاص بك خدمة بدأت بسيطة جدا. ومع ذلك، إذا كنت تحتاج إلى الخدمة الخاصة بك لتنفيذ متعدد خيوط (بدلا من معالجة طلبات البدء من خلال قائمة انتظار عمل)، يمكنك توسيع فئة Service للتعامل مع كل نية.
للمقارنة، يظهر المثال المثال التعليمات البرمجية تنفيذ فئة Service التي تؤدي نفس العمل المثال السابق باستخدام IntentService . وهذا هو، لكل طلب بدء، يستخدم مؤشر ترابط عامل لتنفيذ المهمة ويعالج طلب واحد فقط في وقت واحد.
public class HelloService extends Service {
private Looper mServiceLooper;
private ServiceHandler mServiceHandler;
// Handler that receives messages from the thread
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
// Normally we would do some work here, like download a file.
// For our sample, we just sleep for 5 seconds.
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// Restore interrupt status.
Thread.currentThread().interrupt();
}
// Stop the service using the startId, so that we don't stop
// the service in the middle of handling another job
stopSelf(msg.arg1);
}
}
@Override
public void onCreate() {
// Start up the thread running the service. Note that we create a
// separate thread because the service normally runs in the process's
// main thread, which we don't want to block. We also make it
// background priority so CPU-intensive work will not disrupt our UI.
HandlerThread thread = new HandlerThread("ServiceStartArguments",
Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
// Get the HandlerThread's Looper and use it for our Handler
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
// For each start request, send a message to start a job and deliver the
// start ID so we know which request we're stopping when we finish the job
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
mServiceHandler.sendMessage(msg);
// If we get killed, after returning from here, restart
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
// We don't provide binding, so return null
return null;
}
@Override
public void onDestroy() {
Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
}
}
كما ترون، انها الكثير من العمل من استخدام IntentService .
ومع ذلك، لأنك التعامل مع كل مكالمة إلى onStartCommand() نفسك، يمكنك تنفيذ طلبات متعددة في وقت واحد. هذا ليس ما يفعله هذا المثال، ولكن إذا كان هذا هو ما تريد، يمكنك إنشاء مؤشر ترابط جديد لكل طلب وتشغيلها على الفور بدلا من انتظار الانتهاء من الطلب السابق.
لاحظ أن الأسلوب onStartCommand() يجب أن ترجع عددا صحيحا. العدد الصحيح هو قيمة تصف كيفية استمرار النظام في الخدمة في حالة أن النظام يقتله. التنفيذ الافتراضي ل IntentService يعالج هذا لك، ولكن كنت قادرا على تعديله. يجب أن تكون قيمة الإرجاع من onStartCommand() واحدة من الثوابت التالية:
START_NOT_STICKY
إذا كان النظام يقتل الخدمة بعد إرجاع onStartCommand() ، لا إعادة إنشاء الخدمة ما لم تكن هناك نوايا معلقة لتسليم. هذا هو الخيار الأكثر أمانا لتجنب تشغيل الخدمة الخاصة بك عندما لا يكون ضروريا وعندما التطبيق الخاص بك يمكن ببساطة إعادة تشغيل أي وظائف غير مكتملة.
START_STICKY
إذا كان النظام يقتل الخدمة بعد onStartCommand() إرجاع إعادة إنشاء الخدمة واستدعاء onStartCommand() ، ولكن لا إعادة تسليم آخر نية. بدلا من ذلك، يستدعي النظام onStartCommand() مع نية خالية ما لم يكن هناك نوايا معلقة لبدء الخدمة. وفي هذه الحالة، يتم تسليم تلك النوايا. هذا هو مناسبة لاعبين وسائل الإعلام (أو خدمات مماثلة) التي لا تنفذ الأوامر ولكنها تعمل إلى أجل غير مسمى وانتظار وظيفة.
START_REDELIVER_INTENT
إذا كان النظام يقتل الخدمة بعد onStartCommand() إرجاع إعادة إنشاء الخدمة واستدعاء onStartCommand() مع آخر نية تم تسليمها إلى الخدمة. يتم تسليم أي نوايا معلقة بدورها. هذا هو مناسب للخدمات التي تؤدي بنشاط وظيفة يجب أن تستأنف فورا، مثل تحميل ملف.
لمزيد من التفاصيل حول قيم الإرجاع هذه، راجع الوثائق المرجعية المرتبطة لكل ثابت.
بدء الخدمة
يمكنك بدء تشغيل خدمة من نشاط أو مكون تطبيق آخر عن طريق تمرير Intent إلى startService() أو startForegroundService() . نظام أندرويد يدعو الأسلوب onStartCommand() الخدمة onStartCommand() ويمر عليه Intent ، الذي يحدد الخدمة التي تبدأ.
ملاحظة : إذا كان تطبيقك يستهدف مستوى واجهة برمجة التطبيقات 26 أو أعلى، يفرض النظام قيودا على استخدام أو إنشاء خدمات الخلفية ما لم يكن التطبيق نفسه في المقدمة. إذا كان التطبيق يحتاج إلى إنشاء خدمة المقدمة، يجب أن التطبيق استدعاء StartForegroundService() . هذا الأسلوب يخلق خدمة الخلفية، ولكن الأسلوب يشير إلى النظام أن الخدمة سوف تعزز نفسها إلى المقدمة. بمجرد إنشاء الخدمة، يجب أن تتصل الخدمة startForeground() في غضون خمس ثوان.
على سبيل المثال، يمكن أن يبدأ نشاط خدمة المثال في المقطع السابق ( HelloService ) باستخدام نية صريحة مع startService() ، كما هو موضح هنا:
Intent intent = new Intent(this, HelloService.class);
startService(intent);
كما ترون، انها الكثير من العمل من استخدام IntentService .
ومع ذلك، لأنك التعامل مع كل مكالمة إلى onStartCommand() نفسك، يمكنك تنفيذ طلبات متعددة في وقت واحد. هذا ليس ما يفعله هذا المثال، ولكن إذا كان هذا هو ما تريد، يمكنك إنشاء مؤشر ترابط جديد لكل طلب وتشغيلها على الفور بدلا من انتظار الانتهاء من الطلب السابق.
لاحظ أن الأسلوب onStartCommand() يجب أن ترجع عددا صحيحا. العدد الصحيح هو قيمة تصف كيفية استمرار النظام في الخدمة في حالة أن النظام يقتله. التنفيذ الافتراضي ل IntentService يعالج هذا لك، ولكن كنت قادرا على تعديله. يجب أن تكون قيمة الإرجاع من onStartCommand() واحدة من الثوابت التالية:
START_NOT_STICKY
إذا كان النظام يقتل الخدمة بعد إرجاع onStartCommand() ، لا إعادة إنشاء الخدمة ما لم تكن هناك نوايا معلقة لتسليم. هذا هو الخيار الأكثر أمانا لتجنب تشغيل الخدمة الخاصة بك عندما لا يكون ضروريا وعندما التطبيق الخاص بك يمكن ببساطة إعادة تشغيل أي وظائف غير مكتملة.
START_STICKY
إذا كان النظام يقتل الخدمة بعد onStartCommand() إرجاع إعادة إنشاء الخدمة واستدعاء onStartCommand() ، ولكن لا إعادة تسليم آخر نية. بدلا من ذلك، يستدعي النظام onStartCommand() مع نية خالية ما لم يكن هناك نوايا معلقة لبدء الخدمة. وفي هذه الحالة، يتم تسليم تلك النوايا. هذا هو مناسبة لاعبين وسائل الإعلام (أو خدمات مماثلة) التي لا تنفذ الأوامر ولكنها تعمل إلى أجل غير مسمى وانتظار وظيفة.
START_REDELIVER_INTENT
إذا كان النظام يقتل الخدمة بعد onStartCommand() إرجاع إعادة إنشاء الخدمة واستدعاء onStartCommand() مع آخر نية تم تسليمها إلى الخدمة. يتم تسليم أي نوايا معلقة بدورها. هذا هو مناسب للخدمات التي تؤدي بنشاط وظيفة يجب أن تستأنف فورا، مثل تحميل ملف.
لمزيد من التفاصيل حول قيم الإرجاع هذه، راجع الوثائق المرجعية المرتبطة لكل ثابت.
بدء الخدمة
يمكنك بدء تشغيل خدمة من نشاط أو مكون تطبيق آخر عن طريق تمرير Intent إلى startService() أو startForegroundService() . نظام أندرويد يدعو الأسلوب onStartCommand() الخدمة onStartCommand() ويمر عليه Intent ، الذي يحدد الخدمة التي تبدأ.
ملاحظة : إذا كان تطبيقك يستهدف مستوى واجهة برمجة التطبيقات 26 أو أعلى، يفرض النظام قيودا على استخدام أو إنشاء خدمات الخلفية ما لم يكن التطبيق نفسه في المقدمة. إذا كان التطبيق يحتاج إلى إنشاء خدمة المقدمة، يجب أن التطبيق استدعاء StartForegroundService() . هذا الأسلوب يخلق خدمة الخلفية، ولكن الأسلوب يشير إلى النظام أن الخدمة سوف تعزز نفسها إلى المقدمة. بمجرد إنشاء الخدمة، يجب أن تتصل الخدمة startForeground() في غضون خمس ثوان.
على سبيل المثال، يمكن أن يبدأ نشاط خدمة المثال في المقطع السابق ( HelloService ) باستخدام نية صريحة مع startService() ، كما هو موضح هنا:
Intent intent = new Intent(this, HelloService.class);
startService(intent);
يتم إرجاع الأسلوب startService() الفور، ويستدعي نظام أندرويد الأسلوب onStartCommand() . إذا لم يتم تشغيل الخدمة بالفعل، النظام يدعو أولا onCreate() ، ومن ثم استدعاء onStartCommand() .
إذا كانت الخدمة لا توفر أيضا ملزمة، النية التي يتم تسليمها مع startService() هو الأسلوب الوحيد للاتصال بين مكون التطبيق والخدمة. ومع ذلك، إذا كنت تريد أن ترسل الخدمة نتيجة إلى الوراء، العميل الذي يبدأ تشغيل الخدمة يمكن إنشاء PendingIntent للبث (مع getBroadcast() ) وتسليمها إلى الخدمة في Intent التي تبدأ الخدمة. يمكن للخدمة بعد ذلك استخدام البث لتقديم نتيجة.
تؤدي الطلبات المتعددة لبدء خدمة في مكالمات متعددة المقابلة إلى onStartCommand() . ومع ذلك، مطلوب طلب واحد فقط لوقف الخدمة (مع stopSelf() أو stopService() ) لوقفه.
إيقاف الخدمة
ويجب أن تدير الخدمة التي تبدأ الخدمة دورة حياتها الخاصة. وهذا يعني أن النظام لا تتوقف أو تدمر الخدمة إلا إذا كان يجب استعادة ذاكرة النظام وتستمر الخدمة لتشغيل بعد onStartCommand() إرجاع. يجب أن تتوقف الخدمة نفسها عن طريق استدعاء stopSelf() ، أو يمكن لمكون آخر إيقافه عن طريق استدعاء stopService() .
مرة واحدة طلب التوقف مع stopSelf() أو stopService() ، النظام يدمر الخدمة في أقرب وقت ممكن.
إذا كانت خدمتك تعالج طلبات متعددة إلى onStartCommand() بشكل متزامن، فيجب عدم إيقاف الخدمة عند الانتهاء من معالجة طلب البدء، حيث قد تكون قد تلقيت طلب بدء جديد (سيؤدي التوقف في نهاية الطلب الأول إلى إنهاء الثانية). لتجنب هذه المشكلة، يمكنك استخدام stopSelf(int) للتأكد من أن طلبك لوقف الخدمة يستند دائما إلى أحدث طلب بدء. وهذا هو، عند استدعاء stopSelf(int) ، يمكنك تمرير معرف طلب البدء ( startId تسليمها إلى onStartCommand() ) الذي يتوافق طلب التوقف الخاص بك. ثم، إذا تلقت الخدمة طلب بدء جديد قبل أن تتمكن من استدعاء stopSelf(int) ، لا يتطابق معرف ولا تتوقف الخدمة.
تنبیھ: لتجنب إهدار موارد النظام واستھلاك طاقة البطاریة، تأکد من أن طلبك یتوقف عن خدماتھ عندما ینتھي من العمل. إذا لزم الأمر، يمكن مكونات أخرى إيقاف الخدمة عن طريق استدعاء stopService() . حتى إذا قمت بتمكين الربط للخدمة، يجب دائما إيقاف الخدمة بنفسك إذا كان يتلقى أي وقت مضى مكالمة إلى onStartCommand() .
لمزيد من المعلومات حول دورة حياة الخدمة، راجع القسم أدناه حول إدارة دورة حياة إحدى الخدمات .
إنشاء خدمة ملزمة
خدمة ملزمة هي التي تسمح مكونات التطبيق لربط إليها عن طريق استدعاء bindService() لإنشاء اتصال طويل الأمد. عموما لا تسمح المكونات لبدء تشغيله عن طريق استدعاء startService() .
إنشاء خدمة ملزمة عندما تريد التفاعل مع الخدمة من الأنشطة والمكونات الأخرى في التطبيق الخاص بك أو كشف بعض وظائف التطبيق الخاص بك إلى تطبيقات أخرى من خلال الاتصالات إنتيربروسيس (إيبك).
لإنشاء خدمة ملزمة، قم بتنفيذ أسلوب الاستدعاء onBind() لإرجاع IBinder الذي يحدد واجهة الاتصال بالخدمة. مكونات التطبيق الأخرى يمكن ثم استدعاء bindService() لاسترداد واجهة وبدء طرق الاتصال على الخدمة. وتعيش الخدمة فقط لخدمة مكون التطبيق المرتبط بها، لذلك عندما لا تكون هناك مكونات مرتبطة بالخدمة، يقوم النظام بتدميرها. لا تحتاج إلى إيقاف خدمة ملزمة بنفس الطريقة التي يجب عند بدء تشغيل الخدمة من خلال onStartCommand() .
لإنشاء خدمة ملزمة، يجب تحديد الواجهة التي تحدد كيفية اتصال العميل بالخدمة. يجب أن يكون هذا السطح البيني بين الخدمة والعميل تنفيذ IBinder وهو ما يجب أن تقوم به الخدمة من أسلوب استدعاء onBind() . بعد أن يتلقى العميل IBinder ، يمكن أن يبدأ التفاعل مع الخدمة من خلال تلك الواجهة.
يمكن للعملاء متعددة ربط الخدمة في وقت واحد. عندما يتم العميل التفاعل مع الخدمة، فإنه يدعو unbindService() إلى إلغاء unbindService() . عندما لا يكون هناك عملاء ملزمون بالخدمة، يقوم النظام بتدمير الخدمة.
هناك طرق متعددة لتنفيذ خدمة ملزمة، والتنفيذ أكثر تعقيدا من بدء الخدمة. لهذه الأسباب، تظهر مناقشة الخدمة المحددة في وثيقة منفصلة حول " الخدمات الحدودية" .
إرسال إشعارات إلى المستخدم
عند تشغيل خدمة، يمكن أن يخطر المستخدم من الأحداث باستخدام الإخطارات نخب أو الإخطارات شريط الحالة .
إخطار نخب هو رسالة التي تظهر على سطح النافذة الحالية للحظة فقط قبل أن تختفي. يوفر إشعار شريط الحالة رمزا في شريط الحالة يحتوي على رسالة يمكن للمستخدم تحديدها لاتخاذ إجراء (مثل بدء النشاط).
عادة، إعلام شريط الحالة هو أفضل تقنية لاستخدامها عند الانتهاء من العمل خلفية مثل تحميل ملف، ويمكن للمستخدم الآن التصرف على ذلك. عندما يقوم المستخدم بتحديد الإخطار من العرض الموسعة، يمكن أن يبدأ الإشعار نشاطا (مثل عرض الملف الذي تم تنزيله).
انظر الإخطارات نخب أو شريط الحالة الإخطارات المطور أدلة لمزيد من المعلومات.
تشغيل خدمة في المقدمة
يجب أن تكون الخدمات الأمامية ملحوظة للمستخدم.
يجب عليك فقط استخدام خدمة المقدمة عندما يحتاج التطبيق الخاص بك لأداء مهمة التي يمكن ملاحظتها من قبل المستخدم حتى عندما لا تتفاعل مباشرة مع التطبيق. لهذا السبب، يجب أن تعرض الخدمات الأمامية إشعار شريط الحالة بأولوية PRIORITY_LOW أو أعلى، مما يساعد على التأكد من أن المستخدم على دراية بما يقوم به تطبيقك. إذا كان الإجراء منخفض الأهمية بما فيه الكفاية أنك تريد استخدام إشعار الحد الأدنى الأولوية، وربما كنت لا ينبغي أن تستخدم خدمة. بدلا من ذلك، فكر في استخدام مهمة مجدولة .
كل التطبيق الذي يقوم بتشغيل خدمة يضع حمولة إضافية على النظام، واستهلاك موارد النظام. إذا كان التطبيق يحاول إخفاء خدماتها باستخدام إشعار ذات أولوية منخفضة، وهذا يمكن أن يضعف أداء التطبيق المستخدم التفاعل بنشاط مع. لهذا السبب، إذا كان التطبيق يحاول تشغيل خدمة مع إشعار الحد الأدنى الأولوية، يستدعي النظام سلوك التطبيق في القسم السفلي درج إعلام.
خدمة المقدمة هي الخدمة التي المستخدم على علم بنشاط وليس مرشحا للنظام لقتل عندما منخفضة على الذاكرة. يجب أن تقدم خدمة المقدمة إشعارا لشريط الحالة، الذي يتم وضعه تحت العنوان الجاري. وهذا يعني أنه لا يمكن رفض الإشعار ما لم يتم إيقاف الخدمة أو إزالتها من المقدمة.
على سبيل المثال، يجب تعيين مشغل موسيقى يقوم بتشغيل الموسيقى من خدمة ليتم تشغيله في المقدمة، لأن المستخدم على دراية صريحة بتشغيله. قد يشير الإشعار في شريط الحالة إلى الأغنية الحالية ويسمح للمستخدم بإطلاق نشاط للتفاعل مع مشغل الموسيقى. وبالمثل، فإن التطبيق للسماح للمستخدمين تتبع أشواطها تحتاج إلى خدمة المقدمة لتتبع موقع المستخدم.
لطلب تشغيل الخدمة في المقدمة، اتصل startForeground() . تأخذ هذه الطريقة معلمتين: عدد صحيح يحدد بشكل فريد الإخطار Notification لشريط الحالة. يجب أن يكون PRIORITY_LOW أو أعلى. هنا مثال:
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent =
PendingIntent.getActivity(this, 0, notificationIntent, 0);
Notification notification =
new Notification.Builder(this, CHANNEL_DEFAULT_IMPORTANCE)
.setContentTitle(getText(R.string.notification_title))
.setContentText(getText(R.string.notification_message))
.setSmallIcon(R.drawable.icon)
.setContentIntent(pendingIntent)
.setTicker(getText(R.string.ticker_text))
.build();
startForeground(ONGOING_NOTIFICATION_ID, notification);
تحذير: يجب ألا يكون الرقم الصحيح الذي تعطيه ل startForeground() 0.
لإزالة الخدمة من المقدمة، اتصل stopForeground() . هذه الطريقة تأخذ منطقية، مما يشير إلى ما إذا كان لإزالة إعلام شريط الحالة كذلك. هذه الطريقة لا توقف الخدمة. ومع ذلك، إذا قمت بإيقاف الخدمة في حين أنها لا تزال قيد التشغيل في المقدمة، تتم إزالة الإخطار أيضا.
لمزيد من المعلومات حول الإشعارات، راجع إنشاء إشعارات شريط الحالة .
إدارة دورة حياة الخدمة
دورة حياة الخدمة أبسط بكثير من دورة النشاط. ومع ذلك، فمن الأهم من ذلك أن تولي اهتماما وثيقا لكيفية إنشاء خدمتك وتدميرها لأن الخدمة يمكن تشغيلها في الخلفية دون أن يكون المستخدم على بينة.
يمكن أن تتبع دورة حياة الخدمة - بدءا من إنشائها إلى وقت تدميرها - أي من المسارين التاليين:
خدمة بدأت
يتم إنشاء الخدمة عند استدعاء مكون آخر startService() . ثم تشغيل الخدمة إلى أجل غير مسمى ويجب أن تتوقف عن نفسها عن طريق استدعاء stopSelf() . عنصر آخر يمكن أيضا إيقاف الخدمة عن طريق استدعاء stopService() . عند إيقاف الخدمة، يقوم النظام بتدميره.
خدمة ملزمة
يتم إنشاء الخدمة عند مكون آخر (عميل) يدعو bindService() . العميل ثم يتصل مع الخدمة من خلال واجهة IBinder . يمكن للعميل إغلاق الاتصال عن طريق استدعاء unbindService() . يمكن للعملاء متعددة ربط نفس الخدمة وعندما كل منهم أونبيند، النظام يدمر الخدمة. الخدمة لا تحتاج إلى وقف نفسها.
وهذان المساران ليسا منفصلين تماما. يمكنك ربط خدمة التي بدأت بالفعل مع startService() . على سبيل المثال، يمكنك بدء تشغيل خدمة الموسيقى الخلفية عن طريق استدعاء startService() مع Intent التي تحدد الموسيقى للعب. في وقت لاحق، ربما عندما يريد المستخدم لممارسة بعض السيطرة على لاعب أو الحصول على معلومات حول الأغنية الحالية، يمكن أن يرتبط النشاط إلى الخدمة عن طريق الاتصال bindService() . في مثل هذه الحالات، stopService() أو stopSelf() الواقع الخدمة حتى يتم إلغاء stopSelf() كافة العملاء.
تنفيذ استجابات دورة الحياة
كما هو الحال في أي نشاط، فإن الخدمة تحتوي على أساليب استدعاء دورة الحياة التي يمكنك تنفيذها لمراقبة التغييرات في حالة الخدمة وتنفيذ العمل في الأوقات المناسبة. توضح الخدمة الهيكلية التالية كل من طرق دورة الحياة:
public class ExampleService extends Service {
int mStartMode; // indicates how to behave if the service is killed
IBinder mBinder; // interface for clients that bind
boolean mAllowRebind; // indicates whether onRebind should be used
@Override
public void onCreate() {
// The service is being created
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// The service is starting, due to a call to startService()
return mStartMode;
}
@Override
public IBinder onBind(Intent intent) {
// A client is binding to the service with bindService()
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
// All clients have unbound with unbindService()
return mAllowRebind;
}
@Override
public void onRebind(Intent intent) {
// A client is binding to the service with bindService(),
// after onUnbind() has already been called
}
@Override
public void onDestroy() {
// The service is no longer used and is being destroyed
}
}
ملاحظة: على عكس الطرق الاستدعاء النشاط دورة الحياة، كنت لا حاجة لاستدعاء تنفيذ الفائقة من هذه الأساليب رد.
الشكل 2. دورة حياة الخدمة. الرسم على اليسار يظهر دورة حياة عند إنشاء الخدمة مع startService()والرسم على اليمين يظهر دورة حياة عند إنشاء الخدمة مع bindService().
ويوضح الشكل 2 الأساليب رد نموذجي للخدمة. على الرغم من أن الرقم يفصل الخدمات التي يتم إنشاؤها من قبل startService()من تلك التي تم إنشاؤها من قبل bindService()، أن نضع في الاعتبار أن أي خدمة، بغض النظر عن كيف انها بدأت، من المحتمل أن تسمح للعملاء لربط ذلك. هذه الخدمة التي بدأت في البداية مع onStartCommand()(بواسطة الدعوة العميل startService()) لا يزال يتلقى دعوة ل onBind()(عند استدعاء العميل bindService()).
من خلال تنفيذ هذه الأساليب، يمكنك مراقبة هذه الحلقات المتداخلة اثنين من دورة حياة الخدمة:
في حياته كلها لخدمة يحدث بين الوقت الذي onCreate()يسمى والوقت الذي onDestroy()يعود. مثل هذا النشاط، وهي خدمة يفعل الإعداد الأولي في onCreate()والنشرات عن الموارد المتبقية في onDestroy(). على سبيل المثال، يمكن لخدمة تشغيل الموسيقى خلق ترابط حيث لعبت الموسيقى في onCreate()، وبعد ذلك يمكن أن يوقف موضوع في onDestroy().
ملاحظة : إن onCreate()و onDestroy()تسمى أساليب لجميع الخدمات، سواء أكانت التي أنشأتها startService()أو bindService().
في حياة نشط من خدمة يبدأ مكالمة إما onStartCommand()أو onBind(). يتم تسليم كل طريقة و Intentالتي تم تمريرها إلى إما startService()أو bindService().
إذا تم بدء تشغيل الخدمة، ينتهي عمر نشط في نفس الوقت أن تنتهي حياته بأكملها (الخدمة لا تزال نشطة حتى بعد onStartCommand()عودة). إذا لا بد من الخدمة، وحياة نشط ينتهي عندما onUnbind()يعود.
ملاحظة: على الرغم من أن يتم إيقاف خدمة التي كتبها مكالمة إما stopSelf()أو stopService()، لم يكن هناك رد منها لخدمة (لا يوجد onStop()رد). إلا إذا لا بد من الخدمة إلى العميل، ونظام يدمر عندما الخدمة stopped- onDestroy()هو رد الوحيد الواردة.
لمزيد من المعلومات حول إنشاء خدمة توفر ملزمة، راجع الخدمات ملزمة الوثيقة التي تتضمن مزيد من المعلومات حول onRebind()طريقة الاستدعاء في المقطع حول إدارة دورة حياة الخدمة المقيدة .
خدمات ملزمة
في هذه الوثيقة
أساسيات
إنشاء خدمة ملزمة
تمديد طبقة الموثق
استخدام رسول
ربط الخدمة
ملاحظات إضافية
إدارة دورة حياة خدمة ملزمة
الطبقات الرئيسية
Service
ServiceConnection
IBinder
عينات
RemoteService
LocalService
أنظر أيضا
خدمات
خدمة ملزمة هي الملقم في واجهة وحدة خدمة العميل. وهو يسمح للمكونات (مثل الأنشطة) بالالتزام بالخدمة وإرسال الطلبات وتلقي الردود وإجراء الاتصالات بين العمليات (إيبك). عادة ما تكون الخدمة الملزمة فقط في حين أنها تخدم مكون تطبيق آخر ولا تعمل في الخلفية إلى أجل غير مسمى.
ملاحظة: إذا كان تطبيقك يستهدف أندرويد 5.0 (مستوى واجهة برمجة التطبيقات 21) أو أحدث، فمن المستحسن استخدام JobScheduler لتنفيذ خدمات الخلفية. لمزيد من المعلومات حول JobScheduler ، راجع API-reference documentation لها.
تصف هذه الوثيقة كيفية إنشاء خدمة ملزمة، بما في ذلك كيفية ربط الخدمة من مكونات تطبيق أخرى. للحصول على معلومات إضافية حول الخدمات بشكل عام، مثل كيفية تقديم الإخطارات من إحدى الخدمات وتعيين الخدمة لتشغيلها في المقدمة، راجع مستند الخدمات .
أساسيات
خدمة ملزمة هي تنفيذ فئة Service التي تسمح للتطبيقات الأخرى للالتزام بها والتفاعل معها. لتوفير ملزم لخدمة يجب تنفيذ الأسلوب استدعاء onBind() . تعيد هذه الطريقة كائن IBinder الذي يحدد واجهة البرمجة التي يمكن للعملاء استخدامها للتفاعل مع الخدمة.
ملزم لخدمة بدأت
كما تمت مناقشته في وثيقة " الخدمات" ، يمكنك إنشاء خدمة يتم تشغيلها وربطها. وهذا هو، يمكنك بدء تشغيل خدمة من خلال استدعاء startService() ، والذي يسمح بتشغيل الخدمة إلى أجل غير مسمى، ويمكنك أيضا السماح للعميل لربط الخدمة عن طريق استدعاء bindService() .
إذا كنت تسمح بتشغيل الخدمة وربطها، عند بدء تشغيل الخدمة، لا يقوم النظام بتدمير الخدمة عند إلغاء ربط كافة العملاء. بدلا من ذلك، يجب إيقاف الخدمة صراحة عن طريق استدعاء stopSelf() أو stopService() .
على الرغم من أنك عادة تنفيذ إما onBind() أو onStartCommand() ، فإنه من الضروري في بعض الأحيان لتنفيذ كليهما. على سبيل المثال، قد يجد مشغل موسيقى أنه من المفيد السماح بتشغيل خدمته إلى أجل غير مسمى، وكذلك توفير الربط. وبهذه الطريقة، يمكن أن النشاط بدء تشغيل الخدمة لتشغيل بعض الموسيقى وتستمر الموسيقى للعب حتى إذا كان المستخدم يترك التطبيق. ثم، عندما يعود المستخدم إلى التطبيق، يمكن أن النشاط ربط الخدمة لاستعادة السيطرة على التشغيل.
لمزيد من المعلومات حول دورة حياة الخدمة عند إضافة ارتباط إلى خدمة تم بدء تشغيلها، راجع إدارة دورة حياة خدمة ملزمة .
عميل يربط إلى خدمة عن طريق الاتصال bindService() . عند القيام بذلك، يجب أن توفر تنفيذ ServiceConnection ، الذي يراقب الاتصال مع الخدمة. تشير قيمة الإرجاع bindService() إلى ما إذا كانت الخدمة المطلوبة موجودة وما إذا كان العميل مسموحا بالوصول إليه. عندما يقوم نظام أندرويد بإنشاء الاتصال بين العميل والخدمة، فإنه يدعو onServiceConnected() على onServiceConnected() . تتضمن الطريقة onServiceConnected() وسيطة IBinder ، التي يستخدمها العميل ثم للاتصال مع خدمة ملزمة.
يمكنك توصيل عدة عملاء بخدمة في نفس الوقت. ومع ذلك، يقوم النظام بتخزين قناة اتصال خدمة IBinder . وبعبارة أخرى، يستدعي النظام طريقة onBind() لتوليد IBinder فقط عندما يربط العميل الأول. ثم يقوم النظام بتوصيل نفس IBinder إلى كافة العملاء الإضافيين الذين onBind() بالخدمة نفسها دون استدعاء onBind() مرة أخرى.
عند إلغاء ربط العميل الأخير من الخدمة النظام يدمر الخدمة ما لم يتم بدء تشغيل الخدمة بواسطة startService() .
أهم جزء من تنفيذ الخدمة المحددة هو تعريف الواجهة التي تقوم onBind() أسلوب رد onBind() . يناقش القسم التالي عدة طرق مختلفة يمكنك من خلالها تحديد واجهة IBinder .
إنشاء خدمة ملزمة
عند إنشاء خدمة توفر IBinder ، يجب أن توفر IBinder الذي يوفر واجهة برمجة يمكن للعملاء استخدامها للتفاعل مع الخدمة. هناك ثلاث طرق يمكنك تعريف الواجهة:
تمديد طبقة الموثق
إذا كانت خدمتك خاصة onBind() الخاص وتعمل في نفس عملية العميل (وهو أمر شائع)، يجب عليك إنشاء الواجهة من خلال توسيع فئة Binder وإرجاع مثيل منه من onBind() . يتلقى العميل Binder ويمكن استخدامه للوصول مباشرة إلى الطرق العامة المتاحة في تنفيذ Binder أو Service .
هذه هي الطريقة المفضلة عندما تكون خدمتك مجرد عامل خلفية لتطبيقك الخاص. السبب الوحيد لعدم إنشاء الواجهة بهذه الطريقة هو استخدام الخدمة من خلال تطبيقات أخرى أو عبر عمليات منفصلة.
استخدام رسول
إذا كنت بحاجة إلى واجهة للعمل عبر عمليات مختلفة، يمكنك إنشاء واجهة للخدمة مع Messenger . وبهذه الطريقة، تعرف الخدمة Handler يستجيب لأنواع مختلفة من كائنات Message . هذا Handler هو الأساس Messenger التي يمكن بعد ذلك مشاركة IBinder مع العميل، مما يسمح للعميل لإرسال الأوامر إلى الخدمة باستخدام كائنات Message . بالإضافة إلى ذلك، يمكن للعميل تعريف Messenger خاص به، بحيث يمكن للخدمة إرسال الرسائل مرة أخرى.
هذا هو أبسط طريقة لإجراء اتصالات إنتيربروسيس (إيبك)، لأن طوابير Messenger كافة الطلبات في مؤشر ترابط واحد بحيث لم يكن لديك لتصميم الخدمة الخاصة بك لتكون موضوع الترابط.
استخدام إيدل
الروبوت واجهة تعريف اللغة (إيدل) تتحلل الكائنات إلى الأوليات أن نظام التشغيل يمكن فهم و مارشالس لهم عبر العمليات لأداء إيبك. الأسلوب السابق، باستخدام Messenger ، ويستند في الواقع على إيدل هيكلها الأساسي. كما ذكر أعلاه، يقوم Messenger بإنشاء قائمة انتظار من كافة طلبات العميل في مؤشر ترابط واحد، بحيث تتلقى الخدمة طلبات واحدة في كل مرة. إذا، ومع ذلك، تريد خدمتك للتعامل مع طلبات متعددة في وقت واحد، ثم يمكنك استخدام إيدل مباشرة. في هذه الحالة، يجب أن تكون الخدمة الخاصة بك آمنة وسلسلة قادرة على خيوط متعددة.
لاستخدام إيدل مباشرة، يجب إنشاء ملف .aidl الذي يحدد واجهة البرمجة. تستخدم أدوات أندرويد سك هذا الملف لإنشاء فئة مجردة تقوم بتنفيذ الواجهة والتعامل مع إيبك، والتي يمكنك بعد ذلك تمديدها ضمن خدمتك.
ملاحظة: يجب ألا تستخدم معظم التطبيقات إيدل لإنشاء خدمة ملزمة، لأنها قد تتطلب قدرات متعددة العلامات ويمكن أن تؤدي إلى تنفيذ أكثر تعقيدا. على هذا النحو، إيدل ليست مناسبة لمعظم التطبيقات وهذه الوثيقة لا يناقش كيفية استخدامها لخدمتكم. إذا كنت متأكدا من أنك بحاجة إلى استخدام إيدل مباشرة، راجع مستند إيدل .
تمديد طبقة الموثق
إذا تم استخدام الخدمة الخاصة بك فقط من قبل التطبيق المحلي ولا تحتاج إلى العمل عبر العمليات، ثم يمكنك تنفيذ الطبقة Binder الخاصة التي توفر العميل الوصول المباشر إلى الأساليب العامة في الخدمة.
ملاحظة: يعمل هذا فقط إذا كان العميل والخدمة في نفس التطبيق وعملية، وهو الأكثر شيوعا. على سبيل المثال، هذا من شأنه أن يعمل بشكل جيد لتطبيق الموسيقى التي تحتاج إلى ربط نشاط لخدمتها التي تلعب الموسيقى في الخلفية.
وإليك كيفية إعداده:
في خدمتك، إنشاء مثيل Binder أن يفعل أحد الإجراءات التالية:
يحتوي على أساليب عامة يمكن للعميل الاتصال بها.
لعرض مثيل Service الحالي الذي يحتوي على أساليب عامة يمكن للعميل الاتصال بها.
لعرض نسخة من فئة أخرى تستضيفها الخدمة باستخدام أساليب عامة يمكن للعميل الاتصال بها.
قم onBind() هذا مثيل Binder من أسلوب استدعاء onBind() .
في العميل، تلقي Binder من أسلوب استدعاء onServiceConnected() وإجراء مكالمات إلى خدمة ملزمة باستخدام الطرق المقدمة.
ملاحظة: يجب أن تكون الخدمة والعميل في نفس التطبيق بحيث يستطيع العميل إرسال الكائن الذي تم إرجاعه واستدعاء واجهات برمجة التطبيقات الخاصة به بشكل صحيح. يجب أن تكون الخدمة والعميل أيضا في نفس العملية، لأن هذه التقنية لا تؤدي أي عملية تجميع عبر العمليات.
على سبيل المثال، إليك خدمة توفر للعملاء إمكانية الوصول إلى الأساليب في الخدمة من خلال تنفيذ Binder :
public class LocalService extends Service {
// Binder given to clients
private final IBinder mBinder = new LocalBinder();
// Random number generator
private final Random mGenerator = new Random();
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
LocalService getService() {
// Return this instance of LocalService so clients can call public methods
return LocalService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
/** method for clients */
public int getRandomNumber() {
return mGenerator.nextInt(100);
}
}
يوفر getService() الأسلوب getService() للعملاء لاسترداد المثيل الحالي من LocalService . وهذا يسمح للعملاء لاستدعاء الأساليب العامة في الخدمة. على سبيل المثال، يمكن للعملاء الاتصال getRandomNumber() من الخدمة.
وهنا النشاط الذي يربط إلى LocalService ويدعو getRandomNumber() عند النقر على زر:
public class BindingActivity extends Activity {
LocalService mService;
boolean mBound = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
protected void onStart() {
super.onStart();
// Bind to LocalService
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
/** Called when a button is clicked (the button in the layout file attaches to
* this method with the android:onClick attribute) */
public void onButtonClick(View v) {
if (mBound) {
// Call a method from the LocalService.
// However, if this call were something that might hang, then this request should
// occur in a separate thread to avoid slowing down the activity performance.
int num = mService.getRandomNumber();
Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
}
}
/** Defines callbacks for service binding, passed to bindService() */
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className,
IBinder service) {
// We've bound to LocalService, cast the IBinder and get LocalService instance
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
}
توضح العينة أعلاه كيفية ربط العميل بالخدمة باستخدام تنفيذ onServiceConnected() . يقدم القسم التالي المزيد من المعلومات حول عملية الربط هذه بالخدمة.
ملاحظة: في المثال أعلاه، الأسلوب onStop() العميل من الخدمة. يجب على العملاء إلغاء الربط من الخدمات في الأوقات المناسبة، كما تمت مناقشته في الملاحظات الإضافية .
لمزيد من نموذج التعليمات البرمجية راجع فئة LocalService.java و ClassServiceActivities.java الطبقة في أبيديموس .
استخدام رسول
مقارنة مع إيدل
عندما تحتاج إلى تنفيذ إيبك، باستخدام Messenger لواجهة الخاص بك هو أبسط من استخدام إيدل، لأن Messenger طابور كافة المكالمات إلى الخدمة. واجهة إيدل نقية يرسل طلبات في وقت واحد إلى الخدمة، والتي يجب ثم التعامل مع خيوط متعددة.
بالنسبة إلى معظم التطبيقات، لا تحتاج الخدمة إلى إجراء معالجة متعددة، لذلك فإن استخدام Messenger يتيح للخدمة التعامل مع مكالمة واحدة في المرة الواحدة. إذا كان من المهم أن تكون خدمتك متعددة الخيوط، استخدم إيدل لتعريف الواجهة.
إذا كنت بحاجة إلى خدمتك للتواصل مع العمليات البعيدة، فيمكنك استخدام Messenger لتوفير الواجهة لخدمتك. هذه التقنية تسمح لك لإجراء الاتصالات إنتيربروسيس (إيبك) دون الحاجة إلى استخدام إيدل.
في ما يلي ملخص لكيفية استخدام Messenger :
تقوم الخدمة بتنفيذ Handler يتلقى رد اتصال لكل مكالمة من عميل.
تستخدم الخدمة Handler لإنشاء كائن Messenger (وهو مرجع إلى Handler ).
Messenger يخلق IBinder أن الخدمة تعود للعملاء من onBind() .
يستخدم العملاء IBinder Messenger (الذي يشير إلى Handler الخدمة)، والذي يستخدمه العميل لإرسال كائنات Message إلى الخدمة.
تتلقى الخدمة كل Message في handleMessage() على وجه التحديد، في أسلوب handleMessage() .
وبهذه الطريقة، لا توجد طرق للعميل للاتصال بالخدمة. بدلا من ذلك، يقوم العميل بتوصيل الرسائل (كائنات Message ) التي تتلقاها الخدمة في Handler .
في ما يلي مثال على خدمة بسيطة تستخدم واجهة Messenger :
public class MessengerService extends Service {
/** Command to the service to display a message */
static final int MSG_SAY_HELLO = 1;
/**
* Handler of incoming messages from clients.
*/
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SAY_HELLO:
Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
break;
default:
super.handleMessage(msg);
}
}
}
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
final Messenger mMessenger = new Messenger(new IncomingHandler());
/**
* When binding to the service, we return an interface to our messenger
* for sending messages to the service.
*/
@Override
public IBinder onBind(Intent intent) {
Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
return mMessenger.getBinder();
}
}
لاحظ أن الأسلوب handleMessage() في Handler هو حيث تتلقى الخدمة Message واردة وتقرر ما يجب القيام به، استنادا إلى what عضو.
كل ما يحتاج العميل القيام به هو إنشاء Messenger على أساس IBinder عاد من قبل الخدمة وإرسال رسالة باستخدام send() . على سبيل المثال، في ما يلي نشاط بسيط يربط بالخدمة ويقدم رسالة MSG_SAY_HELLO إلى الخدمة:
public class ActivityMessenger extends Activity {
/** Messenger for communicating with the service. */
Messenger mService = null;
/** Flag indicating whether we have called bind on the service. */
boolean mBound;
/**
* Class for interacting with the main interface of the service.
*/
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the object we can use to
// interact with the service. We are communicating with the
// service using a Messenger, so here we get a client-side
// representation of that from the raw IBinder object.
mService = new Messenger(service);
mBound = true;
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mService = null;
mBound = false;
}
};
public void sayHello(View v) {
if (!mBound) return;
// Create and send a message to the service, using a supported 'what' value
Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
protected void onStart() {
super.onStart();
// Bind to the service
bindService(new Intent(this, MessengerService.class), mConnection,
Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
}
لاحظ أن هذا المثال لا يظهر كيف يمكن للخدمة الاستجابة إلى العميل. إذا كنت تريد الخدمة للرد، تحتاج أيضا إلى إنشاء Messenger في العميل. عندما يتلقى العميل onServiceConnected() ، يرسل Message إلى الخدمة التي تتضمن Messenger العميل في المعلمة replyTo من أسلوب send() .
يمكنك الاطلاع على مثال لكيفية تقديم الرسائل ثنائية الاتجاه في عينات (خدمة) MessengerService.java (خدمة) و MessengerServiceActivities.java (العميل).
ربط الخدمة
مكونات التطبيق (العملاء) يمكن ربط خدمة عن طريق الاتصال bindService() . ثم يقوم نظام أندرويد باستدعاء الأسلوب onBind() ، الذي يعود IBinder للتفاعل مع الخدمة.
الارتباط غير متزامن، و bindService() يعود على الفور دون عدم IBinder إلى العميل. للحصول على IBinder يجب على العميل إنشاء مثيل bindService() إلى bindService() . يتضمن IBinder أسلوب الاستدعاء الذي يدعو النظام لتقديم IBinder .
ملاحظة: يمكن فقط للأنشطة والخدمات ومزودي المحتوى ربط الخدمة - لا يمكنك ربط خدمة من جهاز استقبال البث.
للالتزام بخدمة من عميلك، اتبع الخطوات التالية:
تنفيذ ServiceConnection .
يجب أن يتجاوز تنفيذك طريقتي رد اتصال:
onServiceConnected()
ويدعو النظام هذا إلى تسليم IBinder عاد من قبل onBind() طريقة الخدمة.
onServiceDisconnected()
يستدعي نظام أندرويد هذا عند فقدان الاتصال بالخدمة بشكل غير متوقع، مثل عندما تحطمت الخدمة أو تم قتلها. لا يتم استدعاء هذا عندما يلغي العميل.
استدعاء bindService() ، تمرير تنفيذ bindService() .
ملاحظة: إذا كانت الطريقة ترجع كاذبة، ليس لدى العميل اتصال صالح بالخدمة. ومع ذلك، يجب أن عميلك الاتصال unbindService() ؛ وإلا، العميل الخاص بك وسوف تبقي الخدمة من اغلاق عندما يكون خاملا.
عندما يستدعي النظام أسلوب رد الاتصال onServiceConnected() ، يمكنك البدء في إجراء مكالمات إلى الخدمة باستخدام الطرق المعرفة بواسطة الواجهة.
لقطع الاتصال من الخدمة، استدعاء unbindService() .
إذا كان العميل لا يزال مرتبطا بخدمة عندما يقوم التطبيق الخاص بك بتدمير العميل، يؤدي التدمير العميل إلى إلغاء الربط. فمن الأفضل أن إلغاء ربط العميل بمجرد أن يتم التفاعل مع الخدمة. ويؤدي ذلك إلى إيقاف تشغيل الخدمة الخاملة. لمزيد من المعلومات حول الأوقات المناسبة للربط وإلغاء الربط، راجع ملاحظات إضافية .
المثال التالي يربط العميل إلى الخدمة التي تم إنشاؤها أعلاه عن طريق توسيع فئة بيندر ، لذلك كل ما يجب القيام به هو IBinder إرجاع IBinder إلى فئة IBinder وطلب مثيل IBinder :
LocalService mService;
private ServiceConnection mConnection = new ServiceConnection() {
// Called when the connection with the service is established
public void onServiceConnected(ComponentName className, IBinder service) {
// Because we have bound to an explicit
// service that is running in our own process, we can
// cast its IBinder to a concrete class and directly access it.
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
// Called when the connection with the service disconnects unexpectedly
public void onServiceDisconnected(ComponentName className) {
Log.e(TAG, "onServiceDisconnected");
mBound = false;
}
};
مع هذا ServiceConnection ، يمكن للعميل ربط خدمة عن طريق تمريرها إلى bindService() ، كما هو موضح في المثال التالي:
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
المعلمة الأولى من bindService() هي Intent تسمي صراحة الخدمة لربط.
تحذير: إذا كنت تستخدم نية للالتزام Service ، فتأكد من أن تطبيقك آمن باستخدام نية صريحة . إن استخدام نية ضمنية لبدء خدمة يشكل خطرا على الأمان لأنه لا يمكنك التأكد من الخدمة التي سترد على النية، ولا يمكن للمستخدم معرفة الخدمة التي تبدأ. بدءا من أندرويد 5.0 (أبي bindService() 21)، يلقي النظام استثناء إذا اتصلت bindService() مع نية ضمنية.
المعلمة الثانية هو كائن ServiceConnection .
المعلمة الثالثة هي علامة تشير إلى خيارات الربط. يجب أن يكون عادة BIND_AUTO_CREATE أجل إنشاء الخدمة إذا لم تكن بالفعل على قيد الحياة. القيم المحتملة الأخرى هي BIND_DEBUG_UNBIND و BIND_NOT_FOREGROUND أو 0 أي.
ملاحظات إضافية
في ما يلي بعض الملاحظات المهمة حول الربط بالخدمة:
يجب دائما فخ استثناءات DeadObjectException ، التي يتم طرحها عند كسر الاتصال. هذا هو الاستثناء الوحيد الذي ألقاه الطرق النائية.
يتم إحضار الكائنات المرجعية عبر العمليات.
عادة ما تقوم بإقران الربط وغير الملزم أثناء التطابق بين اللحظات والدموع لحظات دورة حياة العميل، كما هو موضح في الأمثلة التالية:
إذا كنت بحاجة إلى التفاعل مع الخدمة فقط أثناء نشاطك مرئيا، يجب ربط أثناء onStart() و onStop() أثناء onStop() .
إذا كنت تريد نشاطك لتلقي الردود حتى في حين يتم إيقاف في الخلفية، ثم يمكنك ربط أثناء onCreate() و onDestroy() خلال onDestroy() . احذر من أن هذا يعني أن نشاطك يحتاج إلى استخدام الخدمة طوال الوقت الذي يتم تشغيله (حتى في الخلفية)، لذلك إذا كانت الخدمة في عملية أخرى، فإنك تزيد من وزن العملية ويصبح أكثر احتمالا أن النظام سوف اقتله.
ملاحظة: لا ترتبط عادة و onPause() onResume() و onPause() نشاط) على النشاط الخاص بك، لأن تحدث هذه الاستدعاءات في كل عملية نقل دورة حياة ويجب أن تبقي المعالجة التي تحدث عند هذه الانتقالات إلى الحد الأدنى. أيضا، إذا ربطت أنشطة متعددة في التطبيق الخاص بك نفس الخدمة وهناك انتقال بين اثنين من تلك الأنشطة، قد يتم تدمير الخدمة وإعادة إنشائها كما النشاط الحالي أونبيندس (خلال وقفة) قبل ربط واحد (خلال استئناف). ويرد وصف لعملية الانتقال هذه لكيفية تنسيق الأنشطة لدورات حياتها في وثيقة الأنشطة .
لمزيد من نموذج التعليمات البرمجية، تبين كيفية ربط خدمة، راجع فئة RemoteService.java في أبيديموس .
إدارة دورة حياة خدمة ملزمة
عندما تكون الخدمة غير منضم من كافة العملاء، يقوم نظام أندرويد onStartCommand() ما لم يكن قد بدأ أيضا مع onStartCommand() ). على هذا النحو، لم يكن لديك لإدارة دورة حياة الخدمة الخاصة بك إذا كان مجرد خدمة ملزمة - نظام أندرويد يدير لانها لكم على أساس ما إذا كان ملزما إلى أي عملاء.
ومع ذلك، إذا اخترت تطبيق أسلوب الاستدعاء onStartCommand() ، يجب إيقاف الخدمة بشكل صريح، لأن الخدمة تعتبر الآن أن تبدأ . في هذه الحالة، يتم تشغيل الخدمة حتى تتوقف الخدمة نفسها مع stopSelf() أو مكون آخر يدعو stopService() ، بغض النظر عما إذا كانت ملزمة لأي عملاء.
بالإضافة إلى ذلك، إذا تم بدء تشغيل الخدمة الخاصة بك وتقبل ملزمة، ثم عندما يستدعي النظام الأسلوب onUnbind() ، يمكنك اختياريا اختيار true إذا كنت ترغب في تلقي مكالمة إلى onRebind() في المرة التالية يربط العميل إلى الخدمة. onRebind() إرجاع الفراغ، ولكن العميل لا يزال يتلقى IBinder في onServiceConnected() . يوضح الشكل التالي منطق هذا النوع من دورة الحياة.
الشكل 1. دورة حياة الخدمة التي يتم تشغيلها كما تسمح بالربط.
للحصول على مزيد من المعلومات حول دورة حياة الخدمة التي تم تشغيلها، راجع وثيقة الخدمات .
الروبوت واجهة تعريف اللغة (إيدل)
في هذه الوثيقة
تعريف واجهة إيدل
قم بإنشاء ملف .aidl
تنفيذ واجهة
فضح واجهة للعملاء
تمرير الكائنات فوق إيبك
استدعاء طريقة إيبك
أنظر أيضا
خدمات ملزمة
إيدل (الروبوت واجهة تعريف اللغة) تشبه إدلز الأخرى التي قد عملت مع. انها تسمح لك لتحديد واجهة البرمجة التي كل من العميل والخدمة توافق على من أجل التواصل مع بعضها البعض باستخدام الاتصالات إنتيربروسيس (إيبك). على الروبوت، عملية واحدة لا يمكن عادة الوصول إلى الذاكرة من عملية أخرى. لذلك للحديث، فإنها تحتاج إلى تحلل الكائنات إلى الأولية التي يمكن لنظام التشغيل فهم، و مارشال الكائنات عبر هذا الحد بالنسبة لك. رمز للقيام بذلك حراسة مملة للكتابة، لذلك الروبوت يعالج ذلك بالنسبة لك مع إيدل.
ملاحظة: استخدام إيدل ضروري فقط إذا كنت تسمح للعملاء من تطبيقات مختلفة للوصول إلى الخدمة الخاصة بك ل إيبك وتريد التعامل مع تعدد العلامات التجارية في الخدمة الخاصة بك. إذا كنت لا تحتاج إلى تنفيذ إيبك المتزامنة عبر التطبيقات المختلفة، يجب إنشاء واجهة من خلال تنفيذ بيندر أو، إذا كنت ترغب في تنفيذ إيبك، ولكن لا تحتاج إلى التعامل مع تعدد العلامات التجارية، وتنفيذ واجهة باستخدام رسول . بغض النظر، تأكد من أنك تفهم خدمات ملزمة قبل تنفيذ إيدل.
قبل البدء في تصميم واجهة إيدل، كن على علم بأن المكالمات إلى واجهة إيدل هي استدعاءات دالة مباشرة. يجب عدم إجراء افتراضات حول مؤشر الترابط الذي تحدث فيه المكالمة. ما يحدث يختلف اعتمادا على ما إذا كانت المكالمة من مؤشر ترابط في العملية المحلية أو عملية بعيدة. على وجه التحديد:
يتم تنفيذ المكالمات التي يتم إجراؤها من العملية المحلية في نفس سلسلة المحادثات التي يتم إجراء المكالمة. إذا كان هذا هو مؤشر ترابط واجهة المستخدم الرئيسي الخاص بك، يستمر هذا الموضوع لتنفيذ في واجهة إيدل. إذا كان مؤشر ترابط آخر، وهذا هو الذي ينفذ التعليمات البرمجية الخاصة بك في الخدمة. وبالتالي، إذا كانت المواضيع المحلية فقط الوصول إلى الخدمة، يمكنك التحكم تماما المواضيع التي يتم تنفيذها في ذلك (ولكن إذا كان هذا هو الحال، ثم يجب أن لا تستخدم إيدل على الإطلاق، ولكن يجب بدلا من ذلك إنشاء واجهة بتنفيذ بيندر ).
يتم إرسال المكالمات من عملية بعيدة من تجمع ترابط يحتفظ النظام الأساسي داخل العملية الخاصة بك. يجب أن تكون مستعدا للمكالمات الواردة من المواضيع غير معروف، مع مكالمات متعددة يحدث في نفس الوقت. وبعبارة أخرى، يجب أن يكون تنفيذ واجهة إيدل موضوعا آمنا تماما.
تعمل الكلمة الرئيسية على تعديل سلوك المكالمات عن بعد. عند استخدامها، مكالمة نائية لا يمنع؛ فإنه ببساطة يرسل بيانات الصفقة ويعود على الفور. تنفيذ واجهة يتلقى في نهاية المطاف هذا كنداء منتظم من تجمع موضوع Binder كما مكالمة عن بعد العادية. إذا كان يستخدم على الطريق مع مكالمة محلية، ليس هناك أي تأثير والدعوة لا تزال متزامنة.
تعريف واجهة إيدل
يجب تعريف واجهة إيدل في ملف .aidl باستخدام بناء لغة البرمجة جافا ثم حفظه في التعليمات البرمجية المصدر (في دليل src/ ) لكل من تطبيق استضافة الخدمة وأي تطبيق آخر يربط بالخدمة.
عند إنشاء كل تطبيق يحتوي على ملف .aidl أدوات أندرويد سك إنشاء واجهة IBinder استنادا إلى ملف .aidl وحفظه في المشروع gen/ الدليل. يجب على الخدمة تنفيذ واجهة IBinder حسب الاقتضاء. يمكن للتطبيقات العميل ثم ربط الخدمة وطرق الاتصال من IBinder لأداء إيبك.
لإنشاء خدمة محدودة باستخدام إيدل، اتبع الخطوات التالية:
قم بإنشاء ملف .aidl
يحدد هذا الملف واجهة البرمجة مع تواقيع الطريقة.
تنفيذ واجهة
أدوات أندرويد سك إنشاء واجهة بلغة البرمجة جافا، استنادا إلى ملف. .aidl الخاص بك. هذه الواجهة لديها فئة مجردة الداخلية اسمه Stub الذي يمتد Binder وينفذ أساليب من واجهة إيدل الخاص بك. يجب توسيع فئة Stub وتنفيذ الطرق.
فضح واجهة للعملاء
تنفيذ Service وتجاوز onBind() لإرجاع تنفيذ فئة Stub .
تحذير: يجب أن تظل أية تغييرات تجريها على واجهة إيدل بعد الإصدار الأول متوافقة مع الإصدارات السابقة من أجل تجنب كسر التطبيقات الأخرى التي تستخدم خدمتك. وهذا يعني أنه يجب نسخ ملف .aidl الخاص بك إلى تطبيقات أخرى لكي يتمكنوا من الوصول إلى واجهة الخدمة الخاصة بك، يجب الحفاظ على دعم الواجهة الأصلية.
1. إنشاء ملف .aidl
يستخدم إيدل بنية بسيطة تسمح لك بإعلان واجهة مع واحد أو أكثر من الأساليب التي يمكن أن تأخذ المعلمات والعودة القيم. يمكن أن تكون المعلمات وقيم العودة من أي نوع، حتى واجهات إيد الأخرى التي تم إنشاؤها.
يجب إنشاء ملف .aidl باستخدام لغة برمجة جافا. يجب أن يحدد كل ملف .aidl واجهة واحدة ويتطلب فقط تعريف الواجهة وتوقيعات الأسلوب.
بشكل افتراضي، يدعم إيدل أنواع البيانات التالية:
جميع الأنواع البدائية في لغة برمجة جافا (مثل int ، long ، char ، boolean ، وهكذا)
String
CharSequence
List
يجب أن تكون جميع العناصر في List واحدة من أنواع البيانات المدعومة في هذه القائمة أو واحدة من الواجهات الأخرى التي تم إنشاؤها بواسطة إيدل أو الطرود التي قمت بإعلانها. يمكن أن تستخدم List اختياريا كفئة "عامة" (على سبيل المثال، List<String> ). الطبقة الفعلية الفعلية التي يتلقىها الجانب الآخر هو دائما ArrayList ، على الرغم من أن يتم إنشاء الطريقة لاستخدام واجهة List .
Map
يجب أن تكون جميع العناصر الموجودة في Map أحد أنواع البيانات المدعومة في هذه القائمة أو إحدى الواجهات الأخرى التي تم إنشاؤها بواسطة إيدل أو الطرود التي أعلنتها. لا يتم دعم الخرائط العامة (مثل تلك الموجودة في نموذج Map<String,Integer> فئة الخرسانة الفعلية التي HashMap الجانب الآخر هو دائما HashMap ، على الرغم من أن يتم إنشاء طريقة لاستخدام واجهة Map .
يجب تضمين عبارة import لكل نوع إضافي غير مدرج أعلاه، حتى إذا تم تعريفها في نفس الحزمة مثل الواجهة.
عند تحديد واجهة الخدمة، يجب الانتباه إلى ما يلي:
يمكن أن تأخذ الأساليب معلمات صفر أو أكثر، وإرجاع قيمة أو باطلة.
جميع المعلمات غير بدائية تتطلب علامة الاتجاه تشير إلى الطريقة التي يذهب البيانات. سواء in أو out أو inout (انظر المثال أدناه).
البدائل هي in الافتراضي، ولا يمكن أن يكون خلاف ذلك.
تنبيه: يجب أن تحد من الاتجاه إلى ما هو مطلوب حقا، لأن معلمات الاستغناء مكلفة.
يتم تضمين كافة التعليقات رمز المدرجة في ملف .aidl في واجهة IBinder ولدت (باستثناء التعليقات قبل استيراد وحزمة البيانات).
يتم دعم الطرق فقط. لا يمكنك كشف الحقول الثابتة في إيدل.
في ما يلي مثال لملف .aidl :
// IRemoteService.aidl
package com.example.android;
// Declare any non-default types here with import statements
/** Example service interface */
interface IRemoteService {
/** Request the process ID of this service, to do evil things with it. */
int getPid();
/** Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
ببساطة حفظ ملف .aidl الخاص بك في الدليل src/ المشروع الخاص بك وعند بناء التطبيق الخاص بك، أدوات سك توليد ملف واجهة IBinder في gen/ المشروع الخاص بك المشروع. اسم الملف الذي تم إنشاؤه يطابق اسم ملف .aidl ولكن مع ملحق .java (على سبيل المثال، IRemoteService.aidl النتائج في IRemoteService.java ).
إذا كنت تستخدم الروبوت ستوديو، بناء تزايدي يولد الطبقة الموثق على الفور تقريبا. إذا كنت لا تستخدم الروبوت ستوديو، ثم أداة غرادل يولد فئة الموثق في المرة القادمة التي بناء التطبيق الخاص بك، يجب عليك بناء المشروع الخاص بك مع gradle assembleDebug (أو gradle assembleRelease ) بمجرد الانتهاء من كتابة ملف .aidl ، لذلك أن التعليمات البرمجية الخاصة بك يمكن أن تربط ضد فئة ولدت.
2. تنفيذ واجهة
عند إنشاء التطبيق الخاص بك، أدوات سك الروبوت إنشاء ملف واجهة .java اسمه بعد ملف .aidl الخاص بك. تتضمن الواجهة التي تم إنشاؤها فئة فرعية اسمها Stub هو تنفيذ مجرد واجهة تعامل الأصل (على سبيل المثال، YourInterface.Stub ) ويعلن كافة الأساليب من ملف .aidl .
ملاحظة: يحدد Stub أيضا بعض الأساليب المساعد، وعلى الأخص asInterface() ، الذي يأخذ IBinder (عادة ما تم تمريره إلى أسلوب استدعاء onServiceConnected() العميل وإرجاع مثيل واجهة كعب. راجع القسم الاتصال بأسلوب إيبك للحصول على مزيد من التفاصيل حول كيفية جعل هذا الإرسال.
لتنفيذ واجهة التي تم إنشاؤها من .aidl ، قم بتوسيع واجهة Binder ولدت (على سبيل المثال، YourInterface.Stub ) وتنفيذ الطرق الموروثة من الملف .aidl .
في ما يلي مثال لتنفيذ واجهة تسمى IRemoteService (يعرفها مثال IRemoteService.aidl أعلاه) باستخدام مثيل مجهول:
private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
public int getPid(){
return Process.myPid();
}
public void basicTypes(int anInt, long aLong, boolean aBoolean,
float aFloat, double aDouble, String aString) {
// Does nothing
}
};
الآن mBinder هو مثيل من فئة Stub ( Binder )، الذي يحدد واجهة ريك للخدمة. في الخطوة التالية، يتعرض هذا المثال للعملاء حتى يتمكنوا من التفاعل مع الخدمة.
هناك بعض القواعد التي يجب أن تكون على علم بها عند تنفيذ واجهة إيدل:
ليست مضمونة المكالمات الواردة ليتم تنفيذها على مؤشر الترابط الرئيسي، لذلك تحتاج إلى التفكير في تعدد العلامات التجارية من البداية وبناء بشكل صحيح الخدمة الخاصة بك لتكون موضوع آمنة.
بشكل افتراضي، ريك المكالمات متزامنة. إذا كنت تعرف أن الخدمة تستغرق أكثر من بضعة ميلي ثانية لإكمال الطلب، يجب أن لا تتصل به من مؤشر الترابط الرئيسي للنشاط، لأنه قد يعلق التطبيق (قد يعرض أندرويد حوار "لا يستجيب") - يجب عليك وعادة ما يطلق عليها من مؤشر ترابط منفصل في العميل.
لا يتم إرسال أي استثناءات التي رمي مرة أخرى إلى المتصل.
3. فضح واجهة للعملاء
بعد تنفيذ واجهة خدمتك، يجب أن تعرضها للعملاء حتى يتمكنوا من الارتباط بها. لفضح الواجهة onBind() ، قم بتوسيع Service وتنفيذ onBind() لإرجاع مثيل من الفصل الخاص بك الذي يقوم بتنفيذ Stub إنشاؤه (كما تمت مناقشته في المقطع السابق). وإليك مثال خدمة يعرض واجهة مثال IRemoteService للعملاء.
public class RemoteService extends Service {
@Override
public void onCreate() {
super.onCreate();
}
@Override
public IBinder onBind(Intent intent) {
// Return the interface
return mBinder;
}
private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
public int getPid(){
return Process.myPid();
}
public void basicTypes(int anInt, long aLong, boolean aBoolean,
float aFloat, double aDouble, String aString) {
// Does nothing
}
};
}
الآن، عندما يقوم عميل (مثل نشاط) باستدعاء bindService() للاتصال بهذه الخدمة، يتلقى استدعاء onServiceConnected() mBinder مثيل mBinder إرجاع بواسطة طريقة onBind() .
يجب أن يكون العميل أيضا الوصول إلى فئة الواجهة، لذلك إذا كان العميل والخدمة في تطبيقات منفصلة، ثم يجب أن يكون التطبيق العميل نسخة من ملف .aidl في src/ الدليل (الذي يولد واجهة android.os.Binder - توفير وصول العميل إلى أساليب إيدل).
عندما يتلقى العميل IBinder في onServiceConnected() ، يجب استدعاء YourServiceInterface .Stub.asInterface(service) المعلمة التي تم إرجاعها إلى نوع YourServiceInterface . فمثلا:
IRemoteService mIRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
// Called when the connection with the service is established
public void onServiceConnected(ComponentName className, IBinder service) {
// Following the example above for an AIDL interface,
// this gets an instance of the IRemoteInterface, which we can use to call on the service
mIRemoteService = IRemoteService.Stub.asInterface(service);
}
// Called when the connection with the service disconnects unexpectedly
public void onServiceDisconnected(ComponentName className) {
Log.e(TAG, "Service has unexpectedly disconnected");
mIRemoteService = null;
}
};
لمزيد من نموذج التعليمات البرمجية، راجع فئة RemoteService.java في أبيديموس .
تمرير الكائنات فوق إيبك
إذا كان لديك فئة تريد إرسالها من عملية إلى أخرى من خلال واجهة إيبك، يمكنك القيام بذلك. ومع ذلك، يجب التأكد من أن التعليمات البرمجية Parcelable متاحة للجانب الآخر من قناة إيبك ويجب أن يدعم الفصل الخاص بك واجهة Parcelable . دعم واجهة Parcelable مهم لأنه يسمح لنظام أندرويد لتفكيك الكائنات إلى البدائية التي يمكن أن يكون مارشاليد عبر العمليات.
لإنشاء فئة تدعم بروتوكول Parcelable ، يجب عليك القيام بما يلي:
جعل صفك تنفيذ واجهة Parcelable .
تنفيذ writeToParcel ، الذي يأخذ الحالة الراهنة للكائن ويكتب إلى Parcel .
إضافة حقل ثابت يسمى CREATOR إلى الفصل الخاص بك الذي هو كائن تنفيذ واجهة Parcelable.Creator .
أخيرا، قم بإنشاء ملف .aidl الذي يعلن فئة الطرود الخاصة بك (كما هو موضح لملف Rect.aidl ، أدناه).
إذا كنت تستخدم عملية إنشاء مخصص لا تقم بإضافة ملف .aidl إلى بناء الخاص بك. على غرار ملف رأس في لغة C، لا يتم تجميع هذا الملف .aidl .
يستخدم إيدل هذه الأساليب والحقول في التعليمات البرمجية أنه يولد إلى مارشال و أونمارشال الكائنات الخاصة بك.
على سبيل المثال، هنا ملف Rect.aidl لإنشاء فئة مستقيمة التي يمكن طيها:
package android.graphics;
// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect;
And here is an example of how the Rect class implements the Parcelable protocol.
import android.os.Parcel;
import android.os.Parcelable;
public final class Rect implements Parcelable {
public int left;
public int top;
public int right;
public int bottom;
public static final Parcelable.Creator<Rect> CREATOR = new
Parcelable.Creator<Rect>() {
public Rect createFromParcel(Parcel in) {
return new Rect(in);
}
public Rect[] newArray(int size) {
return new Rect[size];
}
};
public Rect() {
}
private Rect(Parcel in) {
readFromParcel(in);
}
public void writeToParcel(Parcel out, int flags) {
out.writeInt(left);
out.writeInt(top);
out.writeInt(right);
out.writeInt(bottom);
}
public void readFromParcel(Parcel in) {
left = in.readInt();
top = in.readInt();
right = in.readInt();
bottom = in.readInt();
}
public int describeContents() {
return 0;
}
}
التحطيم في الطبقة Rect بسيط جدا. نلقي نظرة على الطرق الأخرى على Parcel لرؤية أنواع أخرى من القيم يمكنك الكتابة إلى الطرود.
تحذير: لا تنسى الآثار الأمنية لتلقي البيانات من العمليات الأخرى. في هذه الحالة، يقرأ Rect أربعة أرقام من Parcel ، ولكن الأمر متروك لكم للتأكد من أن هذه هي ضمن نطاق مقبول من القيم لأي ما يحاول المتصل القيام به. راجع الأمان والأذونات لمزيد من المعلومات حول كيفية الحفاظ على أمان التطبيق من البرامج الضارة.
استدعاء طريقة إيبك
فيما يلي الخطوات التي يجب أن تتخذها فئة الاتصال لاستدعاء واجهة بعيدة تعرف مع إيدل:
تضمين ملف .aidl في دليل src/ المشروع.
إعلان مثيل واجهة IBinder (ولدت استنادا إلى إيدل).
تنفيذ ServiceConnection .
استدعاء Context.bindService() ، تمر في تنفيذ ServiceConnection الخاص بك.
في تنفيذ onServiceConnected() ، سوف تتلقى مثيل IBinder (تسمى service ). استدعاء YourInterfaceName .Stub.asInterface((IBinder) service ) لإلقاء المعلمة التي تم إرجاعها إلى نوع يورينتيرفاس .
اتصل بالطرق التي حددتها على الواجهة. يجب دائما فخ استثناءات DeadObjectException ، التي يتم طرحها عند كسر الاتصال. يجب عليك أيضا استثناء استثناءات SecurityException ، التي يتم طرحها عندما يكون العمليتين المتضمنتين في استدعاء الأسلوب إيبك تعريفات إيدل متضاربة.
لقطع الاتصال، اتصل Context.unbindService() مع مثيل الواجهة.
بعض التعليقات على استدعاء خدمة إيبك:
يتم إحضار الكائنات المرجعية عبر العمليات.
يمكنك إرسال كائنات مجهولة كوسيطة الأسلوب.
لمزيد من المعلومات حول الربط إلى خدمة قراءة المستند بوند سيرفيسز .
هنا بعض نموذج التعليمات البرمجية يدل على استدعاء خدمة إنشاء إيدل، مأخوذة من عينة الخدمة عن بعد في مشروع أبيديموس.
public static class Binding extends Activity {
/** The primary interface we will be calling on the service. */
IRemoteService mService = null;
/** Another interface we use on the service. */
ISecondary mSecondaryService = null;
Button mKillButton;
TextView mCallbackText;
private boolean mIsBound;
/**
* Standard initialization of this activity. Set up the UI, then wait
* for the user to poke it before doing anything.
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.remote_service_binding);
// Watch for button clicks.
Button button = (Button)findViewById(R.id.bind);
button.setOnClickListener(mBindListener);
button = (Button)findViewById(R.id.unbind);
button.setOnClickListener(mUnbindListener);
mKillButton = (Button)findViewById(R.id.kill);
mKillButton.setOnClickListener(mKillListener);
mKillButton.setEnabled(false);
mCallbackText = (TextView)findViewById(R.id.callback);
mCallbackText.setText("Not attached.");
}
/**
* Class for interacting with the main interface of the service.
*/
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder service) {
// This is called when the connection with the service has been
// established, giving us the service object we can use to
// interact with the service. We are communicating with our
// service through an IDL interface, so get a client-side
// representation of that from the raw service object.
mService = IRemoteService.Stub.asInterface(service);
mKillButton.setEnabled(true);
mCallbackText.setText("Attached.");
// We want to monitor the service for as long as we are
// connected to it.
try {
mService.registerCallback(mCallback);
} catch (RemoteException e) {
// In this case the service has crashed before we could even
// do anything with it; we can count on soon being
// disconnected (and then reconnected if it can be restarted)
// so there is no need to do anything here.
}
// As part of the sample, tell the user what happened.
Toast.makeText(Binding.this, R.string.remote_service_connected,
Toast.LENGTH_SHORT).show();
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mService = null;
mKillButton.setEnabled(false);
mCallbackText.setText("Disconnected.");
// As part of the sample, tell the user what happened.
Toast.makeText(Binding.this, R.string.remote_service_disconnected,
Toast.LENGTH_SHORT).show();
}
};
/**
* Class for interacting with the secondary interface of the service.
*/
private ServiceConnection mSecondaryConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder service) {
// Connecting to a secondary interface is the same as any
// other interface.
mSecondaryService = ISecondary.Stub.asInterface(service);
mKillButton.setEnabled(true);
}
public void onServiceDisconnected(ComponentName className) {
mSecondaryService = null;
mKillButton.setEnabled(false);
}
};
private OnClickListener mBindListener = new OnClickListener() {
public void onClick(View v) {
// Establish a couple connections with the service, binding
// by interface names. This allows other applications to be
// installed that replace the remote service by implementing
// the same interface.
Intent intent = new Intent(Binding.this, RemoteService.class);
intent.setAction(IRemoteService.class.getName());
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
intent.setAction(ISecondary.class.getName());
bindService(intent, mSecondaryConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
mCallbackText.setText("Binding.");
}
};
private OnClickListener mUnbindListener = new OnClickListener() {
public void onClick(View v) {
if (mIsBound) {
// If we have received the service, and hence registered with
// it, then now is the time to unregister.
if (mService != null) {
try {
mService.unregisterCallback(mCallback);
} catch (RemoteException e) {
// There is nothing special we need to do if the service
// has crashed.
}
}
// Detach our existing connection.
unbindService(mConnection);
unbindService(mSecondaryConnection);
mKillButton.setEnabled(false);
mIsBound = false;
mCallbackText.setText("Unbinding.");
}
}
};
private OnClickListener mKillListener = new OnClickListener() {
public void onClick(View v) {
// To kill the process hosting our service, we need to know its
// PID. Conveniently our service has a call that will return
// to us that information.
if (mSecondaryService != null) {
try {
int pid = mSecondaryService.getPid();
// Note that, though this API allows us to request to
// kill any process based on its PID, the kernel will
// still impose standard restrictions on which PIDs you
// are actually able to kill. Typically this means only
// the process running your application and any additional
// processes created by that app as shown here; packages
// sharing a common UID will also be able to kill each
// other's processes.
Process.killProcess(pid);
mCallbackText.setText("Killed service process.");
} catch (RemoteException ex) {
// Recover gracefully from the process hosting the
// server dying.
// Just for purposes of the sample, put up a notification.
Toast.makeText(Binding.this,
R.string.remote_call_failed,
Toast.LENGTH_SHORT).show();
}
}
}
};
// ----------------------------------------------------------------------
// Code showing how to deal with callbacks.
// ----------------------------------------------------------------------
/**
* This implementation is used to receive callbacks from the remote
* service.
*/
private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
/**
* This is called by the remote service regularly to tell us about
* new values. Note that IPC calls are dispatched through a thread
* pool running in each process, so the code executing here will
* NOT be running in our main thread like most other things -- so,
* to update the UI, we need to use a Handler to hop over there.
*/
public void valueChanged(int value) {
mHandler.sendMessage(mHandler.obtainMessage(BUMP_MSG, value, 0));
}
};
private static final int BUMP_MSG = 1;
private Handler mHandler = new Handler() {
@Override public void handleMessage(Message msg) {
switch (msg.what) {
case BUMP_MSG:
mCallbackText.setText("Received from service: " + msg.arg1);
break;
default:
super.handleMessage(msg);
}
}
};
}