شروع یک فعالیت دیگر، چه در برنامه شما باشد یا از یک برنامه دیگر، نیازی به یک عملیات یک طرفه ندارد. همچنین می توانید یک فعالیت را شروع کنید و نتیجه را پس بگیرید. به عنوان مثال، برنامه شما می تواند یک برنامه دوربین را راه اندازی کند و در نتیجه عکس گرفته شده را دریافت کند. یا ممکن است برنامه مخاطبین را برای کاربر راه اندازی کنید تا یک مخاطب را انتخاب کند و سپس جزئیات تماس را در نتیجه دریافت کنید.
در حالی که APIهای زیربنایی startActivityForResult()
و onActivityResult()
در کلاس Activity
در تمام سطوح API موجود هستند، Google قویاً استفاده از APIهای Activity Result معرفی شده در کلاسهای Activity
و Fragment
AndroidX را توصیه میکند.
APIهای Activity Result مؤلفههایی را برای ثبت نتیجه، راهاندازی فعالیتی که نتیجه را تولید میکند، و پس از ارسال نتیجه توسط سیستم، مدیریت میکنند.
برای نتیجه فعالیت یک تماس پاسخ ثبت کنید
هنگام شروع یک فعالیت برای یک نتیجه، ممکن است - و در موارد عملیات فشرده حافظه مانند استفاده از دوربین، تقریباً مطمئن است - که فرآیند شما و فعالیت شما به دلیل حافظه کم از بین برود.
به همین دلیل، APIهای Activity Result نتیجه تماس را از جایی در کد شما که در آن فعالیت دیگر را راه اندازی می کنید، جدا می کنند. از آنجایی که هنگام ایجاد مجدد فرآیند و فعالیت شما، پاسخ تماس نتیجه باید در دسترس باشد، هر بار که فعالیت شما ایجاد میشود، پاسخ تماس باید بدون قید و شرط ثبت شود، حتی اگر منطق راهاندازی فعالیت دیگر فقط بر اساس ورودی کاربر یا منطق تجاری دیگر اتفاق بیفتد.
هنگامی که در یک ComponentActivity
یا یک Fragment
هستید، API های Activity Result یک API registerForActivityResult()
برای ثبت پاسخ تماس نتیجه ارائه می کنند. registerForActivityResult()
یک ActivityResultContract
و یک ActivityResultCallback
می گیرد و یک ActivityResultLauncher
را برمی گرداند که از آن برای راه اندازی فعالیت دیگر استفاده می کنید.
ActivityResultContract
نوع ورودی مورد نیاز برای تولید یک نتیجه را به همراه نوع خروجی نتیجه تعریف می کند. APIها قراردادهای پیش فرض را برای اقدامات هدف اصلی مانند گرفتن عکس، درخواست مجوز و غیره ارائه می دهند. شما همچنین می توانید یک قرارداد سفارشی ایجاد کنید .
ActivityResultCallback
یک رابط متد واحد با متد onActivityResult()
است که یک شی از نوع خروجی تعریف شده در ActivityResultContract
را می گیرد:
کاتلین
val getContent = registerForActivityResult(GetContent()) { uri: Uri? -> // Handle the returned Uri }
جاوا
// 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()
را به همان ترتیب برای هر ایجاد فرگمنت یا اکتیویتی خود فراخوانی کنید تا نتایج inflight به callback صحیح تحویل داده شود.
registerForActivityResult()
برای فراخوانی قبل از ایجاد قطعه یا اکتیویتی شما بی خطر است، و اجازه می دهد که مستقیماً هنگام اعلام متغیرهای عضو برای نمونه های ActivityResultLauncher
برگشتی استفاده شود.
برای نتیجه یک فعالیت راه اندازی کنید
در حالی که registerForActivityResult()
callback شما را ثبت میکند، فعالیت دیگر را راهاندازی نمیکند و درخواست برای نتیجه را آغاز نمیکند. در عوض، این مسئولیت بر عهده نمونه بازگشتی ActivityResultLauncher
است.
اگر ورودی وجود داشته باشد، راهانداز ورودی را میگیرد که با نوع ActivityResultContract
مطابقت دارد. فراخوانی launch()
فرآیند تولید نتیجه را شروع می کند. هنگامی که کاربر با اکتیویتی بعدی تمام شد و برگرداند، onActivityResult()
از ActivityResultCallback
اجرا می شود، همانطور که در مثال زیر نشان داده شده است:
کاتلین
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/*") } }
جاوا
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
را پیاده سازی می کنند تا به شما امکان استفاده از API های registerForActivityResult()
را بدهد، همچنین می توانید نتیجه فعالیت را در یک کلاس جداگانه که ActivityResultCaller
با استفاده مستقیم از ActivityResultRegistry
پیاده سازی نمی کند، دریافت کنید.
به عنوان مثال، ممکن است بخواهید LifecycleObserver
را پیاده سازی کنید که ثبت قرارداد را همراه با راه اندازی راه اندازی انجام می دهد:
کاتلین
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() } } }
جاوا
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(); } }); } }
هنگام استفاده از APIهای ActivityResultRegistry
، Google قویاً توصیه میکند از APIهایی استفاده کنید که LifecycleOwner
را میگیرند، زیرا LifecycleOwner
بهطور خودکار راهانداز ثبتشده شما را هنگامی که Lifecycle
نابود میشود حذف میکند. با این حال، در مواردی که LifecycleOwner
در دسترس نیست، هر کلاس ActivityResultLauncher
به شما امکان میدهد بهعنوان جایگزین، unregister()
به صورت دستی فراخوانی کنید.
تست کنید
به طور پیش فرض، registerForActivityResult()
به طور خودکار از ActivityResultRegistry
ارائه شده توسط اکتیویتی استفاده می کند. همچنین اضافهباری را فراهم میکند که به شما امکان میدهد نمونهای از ActivityResultRegistry
را بگذرانید که میتوانید از آن برای آزمایش تماسهای نتیجه فعالیتتان استفاده کنید، بدون اینکه واقعاً فعالیت دیگری را راهاندازی کنید.
هنگام آزمایش قطعات برنامه خود ، یک ActivityResultRegistry
آزمایشی را با استفاده از FragmentFactory
ارائه می دهید تا در ActivityResultRegistry
به سازنده قطعه ارسال شود.
به عنوان مثال، قطعه ای که از قرارداد TakePicturePreview
برای دریافت یک تصویر کوچک از تصویر استفاده می کند، ممکن است شبیه به زیر نوشته شود:
کاتلین
class MyFragment( private val registry: ActivityResultRegistry ) : Fragment() { val thumbnailLiveData = MutableLiveData<Bitmap?> val takePicture = registerForActivityResult(TakePicturePreview(), registry) { bitmap: Bitmap? -> thumbnailLiveData.setValue(bitmap) } // ... }
جاوا
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
آزمایشی میسازد، آن را به قطعه ارسال میکند، راهانداز را مستقیماً یا با استفاده از سایر APIهای آزمایشی مانند 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
از پیش ساخته شده برای استفاده است، شما می توانید قراردادهای خود را ارائه دهید که API دقیق و ایمن مورد نیاز خود را ارائه می دهد.
هر ActivityResultContract
به کلاسهای ورودی و خروجی تعریفشده نیاز دارد، اگر به ورودی نیاز ندارید، Void
به عنوان نوع ورودی استفاده کنید (در Kotlin، از Void?
یا Unit
استفاده کنید).
هر قرارداد باید متد createIntent()
پیاده سازی کند که یک Context
و ورودی را می گیرد و Intent
را می سازد که با startActivityForResult()
استفاده می شود.
هر قرارداد همچنین باید parseResult()
را پیاده سازی کند، که خروجی را از resultCode
داده شده، مانند Activity.RESULT_OK
یا Activity.RESULT_CANCELED
، و Intent
تولید می کند.
قراردادها می توانند به صورت اختیاری getSynchronousResult()
را پیاده سازی کنند، اگر امکان تعیین نتیجه برای یک ورودی داده شده بدون نیاز به فراخوانی createIntent()
وجود داشته باشد، فعالیت دیگر را شروع کرده و از parseResult()
برای ساخت نتیجه استفاده کند.
مثال زیر نحوه ایجاد یک ActivityResultContract
را نشان می دهد:
کاتلین
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) } }
جاوا
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
به عنوان بخشی از callback خود استخراج کنید، همانطور که در مثال زیر نشان داده شده است:
کاتلین
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)) } }
جاوا
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)); } }); }