OnActivityResult方法已被弃用,有什么替代方法吗?

536

我最近发现onActivityResult已经被弃用了,我们应该如何处理?

有没有其他替代方案呢?

显示已划掉的带有onActivityResult代码的图像,表示已弃用


46
我不知道是否曾经有一个已被废弃的功能被取消废弃,但我还是希望startActivityForResult能够保留。这种新的方式使代码过于复杂,降低了可读性。 - spartygw
82
谷歌是老大,但是他们在短时间内频繁更改东西让人感到沮丧。 - AtomX
1
现在很难测试这个东西 :( - acmpo6ou
5
我能理解为什么Google决定采用这种方式,它试图将“startActivityForResult”与视图生命周期解耦。我只是希望有一种更优雅的方法来实现这一点。 - Morgan Koh
1
startActivityForResult 似乎也已经被弃用了。是吗? - Mansi Raval
显示剩余6条评论
30个回答

766

developer.android.com上提供了基础培训。

下面是一个示例,演示如何将现有代码转换为新代码:

旧方式:

public void openSomeActivityForResult() {
    Intent intent = new Intent(this, SomeActivity.class);
    startActivityForResult(intent, 123);
}

@Override
protected void onActivityResult (int requestCode, int resultCode, Intent data) {
    if (resultCode == Activity.RESULT_OK && requestCode == 123) {
        doSomeOperations();
    }
}

新的方式(Java):

public void openSomeActivityForResult() {
    Intent intent = new Intent(this, SomeActivity.class);
    someActivityResultLauncher.launch(intent);
}

// You can do the assignment inside onAttach or onCreate, i.e, before the activity is displayed
ActivityResultLauncher<Intent> someActivityResultLauncher = registerForActivityResult(
        new ActivityResultContracts.StartActivityForResult(),
        new ActivityResultCallback<ActivityResult>() {
            @Override
            public void onActivityResult(ActivityResult result) {
                if (result.getResultCode() == Activity.RESULT_OK) {
                    // There are no request codes
                    Intent data = result.getData();
                    doSomeOperations();
                }
            }
        });

新的方法(Kotlin):

fun openSomeActivityForResult() {
    val intent = Intent(this, SomeActivity::class.java)
    resultLauncher.launch(intent)
}

var resultLauncher = registerForActivityResult(StartActivityForResult()) { result ->
    if (result.resultCode == Activity.RESULT_OK) {
        // There are no request codes
        val data: Intent? = result.data
        doSomeOperations()
    }
}

编辑。更好的方法是使其更通用,这样我们可以重复使用它。下面的片段在我的一个项目中使用,但请注意,它没有经过充分测试,可能无法涵盖所有情况。

BetterActivityResult.java

import android.content.Intent;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCaller;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContract;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

public class BetterActivityResult<Input, Result> {
    /**
     * Register activity result using a {@link ActivityResultContract} and an in-place activity result callback like
     * the default approach. You can still customise callback using {@link #launch(Object, OnActivityResult)}.
     */
    @NonNull
    public static <Input, Result> BetterActivityResult<Input, Result> registerForActivityResult(
            @NonNull ActivityResultCaller caller,
            @NonNull ActivityResultContract<Input, Result> contract,
            @Nullable OnActivityResult<Result> onActivityResult) {
        return new BetterActivityResult<>(caller, contract, onActivityResult);
    }

    /**
     * Same as {@link #registerForActivityResult(ActivityResultCaller, ActivityResultContract, OnActivityResult)} except
     * the last argument is set to {@code null}.
     */
    @NonNull
    public static <Input, Result> BetterActivityResult<Input, Result> registerForActivityResult(
            @NonNull ActivityResultCaller caller,
            @NonNull ActivityResultContract<Input, Result> contract) {
        return registerForActivityResult(caller, contract, null);
    }

    /**
     * Specialised method for launching new activities.
     */
    @NonNull
    public static BetterActivityResult<Intent, ActivityResult> registerActivityForResult(
            @NonNull ActivityResultCaller caller) {
        return registerForActivityResult(caller, new ActivityResultContracts.StartActivityForResult());
    }

    /**
     * Callback interface
     */
    public interface OnActivityResult<O> {
        /**
         * Called after receiving a result from the target activity
         */
        void onActivityResult(O result);
    }

    private final ActivityResultLauncher<Input> launcher;
    @Nullable
    private OnActivityResult<Result> onActivityResult;

    private BetterActivityResult(@NonNull ActivityResultCaller caller,
                                 @NonNull ActivityResultContract<Input, Result> contract,
                                 @Nullable OnActivityResult<Result> onActivityResult) {
        this.onActivityResult = onActivityResult;
        this.launcher = caller.registerForActivityResult(contract, this::callOnActivityResult);
    }

    public void setOnActivityResult(@Nullable OnActivityResult<Result> onActivityResult) {
        this.onActivityResult = onActivityResult;
    }

    /**
     * Launch activity, same as {@link ActivityResultLauncher#launch(Object)} except that it allows a callback
     * executed after receiving a result from the target activity.
     */
    public void launch(Input input, @Nullable OnActivityResult<Result> onActivityResult) {
        if (onActivityResult != null) {
            this.onActivityResult = onActivityResult;
        }
        launcher.launch(input);
    }

    /**
     * Same as {@link #launch(Object, OnActivityResult)} with last parameter set to {@code null}.
     */
    public void launch(Input input) {
        launch(input, this.onActivityResult);
    }

    private void callOnActivityResult(Result result) {
        if (onActivityResult != null) onActivityResult.onActivityResult(result);
    }
}

使用上述方法,您仍然需要在启动活动或片段附加之前或期间进行注册。一旦定义,它可以在活动或片段中重复使用。例如,如果您需要在大多数活动中启动新活动,则可以定义一个名为BaseActivity的基础活动,并像这样注册一个新的BetterActivityResult

BaseActivity.java

public class BaseActivity extends AppCompatActivity {
    protected final BetterActivityResult<Intent, ActivityResult> activityLauncher = BetterActivityResult.registerActivityForResult(this);
}

之后,您可以像这样从任何子活动启动活动:

public void openSomeActivityForResult() {
    Intent intent = new Intent(this, SomeActivity.class);
    activityLauncher.launch(intent, result -> {
        if (result.getResultCode() == Activity.RESULT_OK) {
            // There are no request codes
            Intent data = result.getData();
            doSomeOperations();
        }
    })
}

由于您可以在Intent中设置回调函数,因此您可以将其重用于任何活动。

同样,您还可以使用其他两个构造函数来使用其他活动契约。


573
与旧方法相比,新的方式看起来过于复杂,似乎并不必要。 - drmrbrewer
129
新的方式比旧的方式更糟糕。它破坏了代码的模块性,迫使您使用更多的代码行来覆盖以前版本的用例。当您提供更好的 API 设计时,应使用“弃用”,但在谷歌,他们基于少数用例的可争议决策进行“弃用”,这些决策在技术上没有被证明是合理的。 - AndreaF
91
这个戏剧性的改变真的有必要吗?谷歌一直像换婴儿尿布一样频繁更改东西! - AtomX
25
请注意,根据文档,“BetterActivityResult”解决方案绝对不应该使用——onActivityResult是一个独立的回调函数,而不是一个在“launch”时设置的lambda表达式。原因在于它需要在配置更改或进程死亡/重建后存在(这两种情况都可能发生在其他活动打开时——只需旋转设备即可)。您基于lambda的方法永远无法正确处理这些情况。 - ianhanniballake
18
我很惊讶这个API没有被称为 startActivityForResult2。如果你认为使用结果码很繁琐,那么等你接触到这个混乱的烂摊子时,你就会更加失望了。 - rmirabelle
显示剩余38条评论

55

从现在开始,startActivityForResult()已经被弃用,请改用新的方法。

Kotlin示例

    fun openActivityForResult() {
        startForResult.launch(Intent(this, AnotherActivity::class.java))
    }


    val startForResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { 
    result: ActivityResult ->
        if (result.resultCode == Activity.RESULT_OK) {
            val intent = result.data
            // Handle the Intent
            //do stuff here
        }
    }

40

替换已弃用的方法startActivityForResult(...)有4个简单步骤。

  1. 在重写方法onActivityResult(..)的位置 -

 ActivityResultLauncher<Intent> activityResultLaunch = registerForActivityResult(
         new ActivityResultContracts.StartActivityForResult(),
         new ActivityResultCallback<ActivityResult>() {
             @Override
             public void onActivityResult(ActivityResult result) {
                 if (result.getResultCode() == 123) {
                     // ToDo : Do your stuff...
                 } else if(result.getResultCode() == 321) {
                     // ToDo : Do your stuff...
                 }
             }
});

对于多个自定义请求,请将条件追加为:

if (result.getResultCode() == 123) {
..
} else if(result.getResultCode() == 131){
..
} // so on..
  1. 导入:

 import androidx.activity.result.ActivityResult;
 import androidx.activity.result.ActivityResultCallback;
 import androidx.activity.result.ActivityResultLauncher;
 import androidx.activity.result.contract.ActivityResultContracts;
  • 使用startActivityForResult(intent, 123)替代

  •  Intent intent = new Intent(this, SampleActivity.class);
     activityResultLaunch.launch(intent);
    
  • 在SampleActivity.java类中,返回源活动时,代码将保持不变,如下所示 -

  • Intent intent = new Intent();
    setResult(123, intent);
    finish();
    

    开心编码! :)


    1
    非常感谢,您的指南非常详细。 - Steve

    22

    在Java 8中,它可以像这样编写:

    ActivityResultLauncher<Intent> startActivityForResult = registerForActivityResult(
        new ActivityResultContracts.StartActivityForResult(),
        result -> {
            if (result.getResultCode() == AppCompatActivity.RESULT_OK) {
                Intent data = result.getData();
                // ...
            }
        }
    );
    
    Intent intent = new Intent( ... );
    startActivityForResult.launch(intent);
    

    6
    我该如何处理多个requestCode?请帮忙。谢谢。 - hetsgandhi
    2
    一个Intent显然只有一个请求码。 - Martin Zeitler
    1
    我喜欢这种方法! - Marius Razvan Varvarei

    21

    在Kotlin中,我改变了我的代码。

    startActivityForResult(intent, Constants.MY_CODE_REQUEST)
    

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
        super.onActivityResult(requestCode, resultCode, data)
        if (resultCode == Activity.RESULT_OK) {
            when (requestCode) {
                Constants.MY_CODE_REQUEST -> {
                ...
    }
    

    registerForActivityResult(StartActivityForResult()) { result ->
        onActivityResult(Constants.MY_CODE_REQUEST, result)
    }.launch(intent)
    

    private fun onActivityResult(requestCode: Int, result: ActivityResult) {
        if(result.resultCode == Activity.RESULT_OK) {
            val intent = result.data
            when (requestCode) {
                Constants.MY_CODE_REQUEST -> {
                ...
    

    希望它对你有用。 :D


    3
    你第三段代码片段中的onActivityResult已经被弃用。在registerForActivityResult方法中需要进行更新。 - Filipe Brito
    2
    @FilipeBrito onActivityResult不是被覆盖的方法,它是我自己的方法,名称可以随便取 ;) - Luis Moreno
    8
    新方式中的requestCode似乎几乎没有用处。 - ThanosFisherman
    3
    这不是正确的方法。如果您将启动与registerForActivityResult一起设置,可能会遇到初始化错误。最好先创建一个变量,在那里进行注册。 - Narendra_Nath

    10

    对于那些具有多个 "requestCode" 片段的人,如果您不确定如何处理这些 "requestCode" 的多个结果,则需要了解在新方法中 "requestCode" 是无用的。

    我想像你以前是这样编写代码的:

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (resultCode == Activity.RESULT_CODE) {
            when (requestCode) {
                REQUEST_TAKE_PHOTO -> {
                    // handle photo from camera
                }
                REQUEST_PICK_IMAGE_FROM_GALLERY -> {
                    // handle image from gallery
                }
            }
        }
    }
    

    在新的API中,您需要在单独的ActivityResultContract中实现每个请求的结果:
    val takePhotoForResult = registerForActivityResult(StartActivityForResult()) { result: ActivityResult ->
        if (result.resultCode == Activity.RESULT_OK) {
            val intent = result.data
            // handle photo from camera
        }
    }
    
    val pickImageFromGalleryForResult = registerForActivityResult(StartActivityForResult()) { result: ActivityResult ->
        if (result.resultCode == Activity.RESULT_OK) {
            val intent = result.data
            // handle image from gallery
        }
    }
    

    然后你需要像这样启动那些活动/意图:

    private fun startTakePhotoActivity() {
        takePhotoForResult.launch(Intent(requireActivity(), TakePhotoActivity::class.java))
    }
    
    private fun pickImageFromGallery() {
        val pickIntent = Intent(Intent.ACTION_PICK)
        pickIntent.setDataAndType(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            "image/*"
        )
        pickImageFromGalleryForResult.launch(pickIntent)
    }
    

    通过这样做,您可以在项目中消除数百个const val REQUEST_值。


    3
    通过将数百个const val REQUEST_值替换为数百个ActivityResultContracts,您可以消除项目中的大量代码。这样做有什么好处? - The incredible Jan
    管理这些REQUEST_整数的值需要时间,而且整数可以用于不同的目的,从而搞乱项目。然而,ActivityResultContracts仅用于指导开发人员到达正确的点,只有一个目的。这就是区别所在。@TheincredibleJan - Egemen Hamutçu

    9

    onActivityResult, startActivityForResult, requestPermissions, 和 onRequestPermissionsResultandroidx.fragment1.3.0-alpha04版本中被废弃,而不是在android.app.Activity中。
    相反,你可以使用Activity Result APIsregisterForActivityResult


    你是从哪里了解到使用 "registerForActivityResult" 的 "Activity Result APIs" 的呢?这个信息的来源是什么? - Sumit
    @Sumit,当我在文本“_deprecated_”上链接更改日志时,Android团队表示:“请使用Activity Result API”。 - khcpietro

    7

    参考: Kotlin - 从图库选择图片

    到目前为止,我找到的最简单的替代方法。

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.id.activity_main)
    
        var ivPhoto = findViewById<ImageView>(R.id.ivPhoto)
        var btnChoosePhoto = findViewById<Button>(R.id.btnChoosePhoto)
    
        
    
    val getContent = registerForActivityResult(ActivityResultContracts.GetContent())  { uri: Uri? ->
                ivPhoto.setImageURI(uri)    // Handle the returned Uri
            }
    
    
        btnChoose.setOnClickListener {
            getContent.launch("image/*")
        }
        
        }
    

    为什么你的链接标签是“Kotlin - 从相册选择图像”,但指向的却是“启动一个带返回结果的活动”? - The incredible Jan

    6

    在这里我解释新的方法

    private val scan =
            registerForActivityResult(ActivityResultContracts.StartActivityForResult())
            { result: ActivityResult ->
                if (result.resultCode == AppCompatActivity.RESULT_OK && result.data != null) {
    
                    var selected_hub = result!!.data!!.getParcelableExtra<ExtendedBluetoothDevice>(Utils.EXTRA_DEVICE)
                    Log.d(TAG,"RECONNECT PROCESS "+selected_hub!!.name)
                    reconnect(selected_hub!!)
    
                }
            }
    

    从活动或片段调用此函数

    private fun callScan() {
            val intent = Intent(requireActivity(), ScanningMeshDevices::class.java)
            scan.launch(intent)
        }
    

    5
    以下代码适用于 Kotlin 片段,用于检查蓝牙权限。
    val intent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
    
    registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
        if (result.resultCode == Activity.RESULT_OK) {
            // There are no request codes
            val data: Intent? = result.data
            bluetoothAdapter.enable()
            Toast.makeText(context, "Permission Granted: ", Toast.LENGTH_SHORT).show()
            dynamicButton()
        }
        else{Toast.makeText(context, "You have to enable bluetooth to use this app.", Toast.LENGTH_SHORT).show()}
        
    }.launch(intent)
    

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接