لا يلزم بدء نشاط آخر، سواء كان واحدًا داخل تطبيقك أو من تطبيق آخر، أن يكون عملية أحادية الاتجاه. يمكنك أيضًا بدء نشاط والحصول على نتيجة. على سبيل المثال، يمكن لتطبيقك تشغيل تطبيق كاميرا وتلقي الصورة التي تم التقاطها كنتيجة لذلك. أو يمكنك تشغيل تطبيق جهات الاتصال ليتمكن المستخدم من تحديد جهة اتصال، ثم تلقي تفاصيل جهة الاتصال كنتيجة لذلك.
على الرغم من توفُّر واجهات برمجة التطبيقات
startActivityForResult()
وonActivityResult()
الأساسية في صف Activity
على جميع مستويات واجهة برمجة التطبيقات، تنصح Google
بشدّة باستخدام واجهات برمجة تطبيقات نتائج الأنشطة التي تم تقديمها في صفَّي AndroidX
Activity
وFragment
.
توفر واجهات برمجة التطبيقات لنتائج الأنشطة مكونات للتسجيل للنتيجة وإطلاق النتيجة والتعامل معها بمجرد إرسالها بواسطة النظام.
تسجيل معاودة الاتصال لنتيجة نشاط
عند بدء نشاط ما من نتيجة، من الممكن - وفي حالات العمليات التي تستهلك قدرًا كبيرًا من الذاكرة مثل استخدام الكاميرا - من المؤكد تقريبًا أن يتم إتلاف عمليتك ونشاطك بسبب انخفاض الذاكرة.
ولهذا السبب، تعمل واجهات برمجة التطبيقات لنتائج النشاط على فصل استدعاء النتيجة عن المكان الموجود في الرمز الذي تبدأ فيه النشاط الآخر. نظرًا لأن معاودة الاتصال بالنتيجة ينبغي أن تكون متاحة عند إعادة إنشاء عمليتك ونشاطك، يجب أن يتم تسجيل معاودة الاتصال بدون شروط في كل مرة يتم فيها إنشاء نشاطك، حتى إذا كان منطق بدء النشاط الآخر يحدث فقط استنادًا إلى إدخالات المستخدم أو منطق عمل آخر.
عند استخدام واجهة
ComponentActivity
أو
Fragment
، توفّر واجهات برمجة التطبيقات لنتائج النشاط
registerForActivityResult()
واجهة برمجة تطبيقات لتسجيل طلب معاودة الاتصال بالنتيجة. تأخذ registerForActivityResult()
علامة
ActivityResultContract
وActivityResultCallback
وتعرض علامة
ActivityResultLauncher
،
التي تستخدمها لتشغيل النشاط الآخر.
تحدد ActivityResultContract
نوع الإدخال المطلوب للحصول على نتيجة إلى جانب نوع الإخراج للنتيجة. توفّر واجهات برمجة التطبيقات
العقود التلقائية
لالإجراءات الأساسية المستندة إلى الأهداف، مثل التقاط صورة وطلب الأذونات وما إلى ذلك. يمكنك أيضًا
إنشاء عقد مخصّص.
ActivityResultCallback
هي واجهة ذات طريقة أحادية تتضمّن الطريقة
onActivityResult()
التي تأخذ كائنًا من نوع الإخراج المحدّد في
ActivityResultContract
:
Kotlin
val getContent = registerForActivityResult(GetContent()) { uri: Uri? -> // Handle the returned Uri }
Java
// GetContent creates an ActivityResultLauncher<String> to let you pass // in the mime type you want to let the user select ActivityResultLauncher<String> mGetContent = registerForActivityResult(new GetContent(), new ActivityResultCallback<Uri>() { @Override public void onActivityResult(Uri uri) { // Handle the returned Uri } });
إذا كان لديك استدعاءات متعددة لنتائج النشاط وكنت إما تستخدم عقودًا مختلفة
أو تريد معاودة اتصال منفصلة، يمكنك استدعاء "registerForActivityResult()
"
عدة مرات لتسجيل حالات ActivityResultLauncher
متعددة. عليك طلب الرمز registerForActivityResult()
بنفس الترتيب لكل عملية إنشاء للجزء أو النشاط حتى يتم تسليم نتائج البحث أثناء الطيران إلى معاودة الاتصال الصحيحة.
من الآمن الاتصال بـ "registerForActivityResult()
" قبل إنشاء الجزء أو النشاط،
ما يسمح باستخدامه مباشرةً عند تعريف متغيّرات الأعضاء في مثيلات ActivityResultLauncher
التي يتم عرضها.
تشغيل نشاط للنتائج
أثناء تسجيل registerForActivityResult()
لمعاودة الاتصال، لا
يطلق النشاط الآخر ويبدأ الطلب للحصول على نتيجة. بدلاً من ذلك، هذه
مسؤولية مثيل ActivityResultLauncher
الذي تم عرضه.
في حال توفُّر إدخال، سيحصل مشغّل التطبيقات على الإدخال الذي يتطابق مع نوع
ActivityResultContract
. يؤدي استدعاء
launch()
إلى بدء عملية الحصول على النتيجة. عندما ينتهي المستخدم من النشاط التالي ثم يعود، يتم تنفيذ onActivityResult()
من ActivityResultCallback
، كما هو موضّح في المثال التالي:
Kotlin
val getContent = registerForActivityResult(GetContent()) { uri: Uri? -> // Handle the returned Uri } override fun onCreate(savedInstanceState: Bundle?) { // ... val selectButton = findViewById<Button>(R.id.select_button) selectButton.setOnClickListener { // Pass in the mime type you want to let the user select // as the input getContent.launch("image/*") } }
Java
ActivityResultLauncher<String> mGetContent = registerForActivityResult(new GetContent(), new ActivityResultCallback<Uri>() { @Override public void onActivityResult(Uri uri) { // Handle the returned Uri } }); @Override public void onCreate(@Nullable Bundle savedInstanceState) { // ... Button selectButton = findViewById(R.id.select_button); selectButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { // Pass in the mime type you want to let the user select // as the input mGetContent.launch("image/*"); } }); }
يتيح لك إصدار launch()
ذي التحميل الزائد
تمرير ActivityOptionsCompat
بالإضافة إلى الإدخال.
تلقّي نتيجة نشاط في صف منفصل
على الرغم من أنّ الصفَّين ComponentActivity
وFragment
يستخدمان واجهة
ActivityResultCaller
للسماح لك باستخدام واجهات برمجة تطبيقات registerForActivityResult()
، يمكنك أيضًا
تلقّي نتيجة النشاط في صف منفصل لا ينفِّذ
ActivityResultCaller
باستخدام
ActivityResultRegistry
مباشرةً.
على سبيل المثال، قد ترغب في تنفيذ LifecycleObserver
الذي يعالج تسجيل عقد إلى جانب تشغيل مشغّل التطبيقات:
Kotlin
class MyLifecycleObserver(private val registry : ActivityResultRegistry) : DefaultLifecycleObserver { lateinit var getContent : ActivityResultLauncher<String> override fun onCreate(owner: LifecycleOwner) { getContent = registry.register("key", owner, GetContent()) { uri -> // Handle the returned Uri } } fun selectImage() { getContent.launch("image/*") } } class MyFragment : Fragment() { lateinit var observer : MyLifecycleObserver override fun onCreate(savedInstanceState: Bundle?) { // ... observer = MyLifecycleObserver(requireActivity().activityResultRegistry) lifecycle.addObserver(observer) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val selectButton = view.findViewById<Button>(R.id.select_button) selectButton.setOnClickListener { // Open the activity to select an image observer.selectImage() } } }
Java
class MyLifecycleObserver implements DefaultLifecycleObserver { private final ActivityResultRegistry mRegistry; private ActivityResultLauncher<String> mGetContent; MyLifecycleObserver(@NonNull ActivityResultRegistry registry) { mRegistry = registry; } public void onCreate(@NonNull LifecycleOwner owner) { // ... mGetContent = mRegistry.register(“key”, owner, new GetContent(), new ActivityResultCallback<Uri>() { @Override public void onActivityResult(Uri uri) { // Handle the returned Uri } }); } public void selectImage() { // Open the activity to select an image mGetContent.launch("image/*"); } } class MyFragment extends Fragment { private MyLifecycleObserver mObserver; @Override void onCreate(Bundle savedInstanceState) { // ... mObserver = new MyLifecycleObserver(requireActivity().getActivityResultRegistry()); getLifecycle().addObserver(mObserver); } @Override void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { Button selectButton = findViewById(R.id.select_button); selectButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { mObserver.selectImage(); } }); } }
عند استخدام واجهات برمجة تطبيقات ActivityResultRegistry
، تنصح Google بشدة باستخدام
واجهات برمجة التطبيقات التي تأخذ LifecycleOwner
، لأنّ LifecycleOwner
تزيل المشغّل المسجّل تلقائيًا عند تدمير Lifecycle
. مع ذلك،
في الحالات التي لا تتوفّر فيها السمة LifecycleOwner
، تتيح لك كل صف في ActivityResultLauncher
استدعاء
unregister()
يدويًا كبديل.
الاختبار
بشكل تلقائي، يستخدم "registerForActivityResult()
" تلقائيًا
سمة ActivityResultRegistry
التي يوفّرها النشاط. وتوفر لك هذه الطريقة أيضًا حملاً زائدًا يتيح لك تمرير مثيل ActivityResultRegistry
الخاص بك والذي يمكنك استخدامه لاختبار استدعاءات نتائج النشاط بدون بدء أي نشاط آخر.
عند اختبار أجزاء تطبيقك، يمكنك
تقديم اختبار ActivityResultRegistry
باستخدام
FragmentFactory
لتمرير
ActivityResultRegistry
إلى الدالة الإنشائية للجزء.
على سبيل المثال، يمكن أن تتم كتابة الجزء الذي يستخدم عقد TakePicturePreview
للحصول
على صورة مصغّرة
للصورة على النحو التالي:
Kotlin
class MyFragment( private val registry: ActivityResultRegistry ) : Fragment() { val thumbnailLiveData = MutableLiveData<Bitmap?> val takePicture = registerForActivityResult(TakePicturePreview(), registry) { bitmap: Bitmap? -> thumbnailLiveData.setValue(bitmap) } // ... }
Java
public class MyFragment extends Fragment { private final ActivityResultRegistry mRegistry; private final MutableLiveData<Bitmap> mThumbnailLiveData = new MutableLiveData(); private final ActivityResultLauncher<Void> mTakePicture = registerForActivityResult(new TakePicturePreview(), mRegistry, new ActivityResultCallback<Bitmap>() { @Override public void onActivityResult(Bitmap thumbnail) { mThumbnailLiveData.setValue(thumbnail); } }); public MyFragment(@NonNull ActivityResultRegistry registry) { super(); mRegistry = registry; } @VisibleForTesting @NonNull ActivityResultLauncher<Void> getTakePicture() { return mTakePicture; } @VisibleForTesting @NonNull LiveData<Bitmap> getThumbnailLiveData() { return mThumbnailLiveData; } // ... }
عند إنشاء ActivityResultRegistry
خاص بالاختبار، يجب تنفيذ
طريقة
onLaunch()
. بدلاً من طلب الرقم startActivityForResult()
، يمكن لتنفيذ
الاختبار الاتصال بـ
dispatchResult()
مباشرة، لتقديم النتائج الدقيقة التي تريد استخدامها في الاختبار:
val testRegistry = object : ActivityResultRegistry() {
override fun <I, O> onLaunch(
requestCode: Int,
contract: ActivityResultContract<I, O>,
input: I,
options: ActivityOptionsCompat?
) {
dispatchResult(requestCode, expectedResult)
}
}
ينشئ الاختبار الكامل النتيجة المتوقعة، وينشئ اختبارًا
ActivityResultRegistry
، ويمرره إلى الجزء، ويشغِّل مشغِّل التطبيقات
إما بشكلٍ مباشر أو باستخدام واجهات برمجة تطبيقات اختبارية أخرى، مثل Espresso، ثم يتحقّق من النتائج:
@Test
fun activityResultTest {
// Create an expected result Bitmap
val expectedResult = Bitmap.createBitmap(1, 1, Bitmap.Config.RGBA_F16)
// Create the test ActivityResultRegistry
val testRegistry = object : ActivityResultRegistry() {
override fun <I, O> onLaunch(
requestCode: Int,
contract: ActivityResultContract<I, O>,
input: I,
options: ActivityOptionsCompat?
) {
dispatchResult(requestCode, expectedResult)
}
}
// Use the launchFragmentInContainer method that takes a
// lambda to construct the Fragment with the testRegistry
with(launchFragmentInContainer { MyFragment(testRegistry) }) {
onFragment { fragment ->
// Trigger the ActivityResultLauncher
fragment.takePicture()
// Verify the result is set
assertThat(fragment.thumbnailLiveData.value)
.isSameInstanceAs(expectedResult)
}
}
}
إنشاء عقد مخصّص
على الرغم من أنّ ActivityResultContracts
يحتوي على عدد من فئات ActivityResultContract
المصمّمة مسبقًا للاستخدام، يمكنك
تقديم عقودك الخاصة التي توفّر واجهة برمجة التطبيقات المناسبة للنوع والتوافق مع معايير الكتابة التي تحتاجها.
يتطلب كل ActivityResultContract
فئات إدخال وإخراج محددة
وتستخدم Void
كنوع إدخال إذا لم تكن بحاجة إلى أي إدخال (في Kotlin، يمكنك استخدام Void?
أو Unit
).
يجب أن ينفّذ كل عقد طريقة
createIntent()
التي تستخدم Context
والإدخال وتنشئ Intent
المستخدمة مع startActivityForResult()
.
يجب أيضًا في كل عقد تنفيذ السمة
parseResult()
،
التي تنتج الناتج من resultCode
المحدّد، مثل
Activity.RESULT_OK
أو Activity.RESULT_CANCELED
وIntent
.
يمكن تطبيق العقود بشكل اختياري
getSynchronousResult()
إذا كان من الممكن تحديد النتيجة لإدخال معيّن بدون
الحاجة إلى طلب createIntent()
وبدء النشاط الآخر واستخدام
parseResult()
لإنشاء النتيجة.
يوضّح المثال التالي كيفية إنشاء ActivityResultContract
:
Kotlin
class PickRingtone : ActivityResultContract<Int, Uri?>() { override fun createIntent(context: Context, ringtoneType: Int) = Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply { putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, ringtoneType) } override fun parseResult(resultCode: Int, result: Intent?) : Uri? { if (resultCode != Activity.RESULT_OK) { return null } return result?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) } }
Java
public class PickRingtone extends ActivityResultContract<Integer, Uri> { @NonNull @Override public Intent createIntent(@NonNull Context context, @NonNull Integer ringtoneType) { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, ringtoneType.intValue()); return intent; } @Override public Uri parseResult(int resultCode, @Nullable Intent result) { if (resultCode != Activity.RESULT_OK || result == null) { return null; } return result.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); } }
إذا لم تكن بحاجة إلى عقد مخصّص، يمكنك استخدام العقد
StartActivityForResult
. هذا عقد عام يتم فيه استخدام أي Intent
كإدخال ويعرض ActivityResult
، ما يتيح لك استخراج resultCode
وIntent
كجزء من معاودة الاتصال، كما هو موضّح في المثال التالي:
Kotlin
val startForResult = registerForActivityResult(StartActivityForResult()) { result: ActivityResult -> if (result.resultCode == Activity.RESULT_OK) { val intent = result.data // Handle the Intent } } override fun onCreate(savedInstanceState: Bundle) { // ... val startButton = findViewById(R.id.start_button) startButton.setOnClickListener { // Use the Kotlin extension in activity-ktx // passing it the Intent you want to start startForResult.launch(Intent(this, ResultProducingActivity::class.java)) } }
Java
ActivityResultLauncher<Intent> mStartForResult = registerForActivityResult(new StartActivityForResult(), new ActivityResultCallback<ActivityResult>() { @Override public void onActivityResult(ActivityResult result) { if (result.getResultCode() == Activity.RESULT_OK) { Intent intent = result.getData(); // Handle the Intent } } }); @Override public void onCreate(@Nullable savedInstanceState: Bundle) { // ... Button startButton = findViewById(R.id.start_button); startButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { // The launcher with the Intent you want to start mStartForResult.launch(new Intent(this, ResultProducingActivity.class)); } }); }