الحصول على نتيجة من نشاط

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

وفي حين أن القيمة الأساسية 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));
        }
    });
}