Android Result API详解

旧版本startActivityForResult使用方式(传统方式)

在两个Activity之间交换数据

class FirstActivity : AppCompatActivity() {
    //传统方式
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val firstButton = findViewById<Button>(R.id.button2)
        firstButton.setOnClickListener {
            val intent = Intent(this, SecondActivity::class.java)
            startActivityForResult(intent, 1)
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        when(resultCode){
            1 -> {
                if (resultCode == RESULT_OK){
                    val data = data?.getStringExtra("data")
                }
            }
        }
    }
}

调用statActivityForResult()方法去向SecondActivity请求数据,然后在onActivityResult()方法去解析SecondActivity返回的结果。

另一个SecondActivity

class SecondActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val secondButton = findViewById<Button>(R.id.button2)
        secondButton.setOnClickListener {
            val intent = Intent()
            intent.putExtra("data","data from SecondActivity")
            setResult(RESULT_OK,intent)
            finish()
        }
    }
}

使用Activity Result API来实现同样的功能

class FirstActivity : AppCompatActivity() {
    //传统方式
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val firstButton = findViewById<Button>(R.id.button2)
        firstButton.setOnClickListener {
            //传统方式
            val intent = Intent(this, SecondActivity::class.java)
            requestDataLauncher.launch(intent)
        }
    }

    //使用 Activity Result API来实现同样的功能
    private val requestDataLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
        if(result.resultCode == RESULT_OK){
            val data = result.data?.getStringExtra("data")
        }
    }
}

首先移除来对onActivityResult()方法的重写,通过调用registerForActivityResult()方法注册一个对Activity结果的监听。

registerForActivityResult():接收两个参数,第一个参数是一种Contract类型;第二个是一个Lambda表达式,当有结果返回时会回调到这里,然后我们在这里获取并处理数据。

registerForActivityResult()方法的返回值是一个ActivityResultLauncher对象,这个对象当中有一个launch()方法可以用于去启用Intent。这样我们就不需要再调用startActivityForResult()方法了,而是直接调用launch()方法,并把Intent传入即可。

请求运行时权限

class FirstActivity : AppCompatActivity() {
    
    private val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
        if (granted) {
            // User allow the permission.
        } else {
            // User deny the permission.
        }
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_first)
        val firstButton = findViewById<Button>(R.id.first_button)
        firstButton.setOnClickListener {
            requestPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
        }
    }
    
}

请求运行时的权限,需要使用RequestPermission这种Contract

指定了不同的Contract类型,Lambda表达式的参数也会发生变化,现在Lambda表达式会传入一个布尔型的参数,用于告诉我们用户是否允许了我们的请求的权限。

launch()方法的参数也发生了变化,需要传入要请求的权限名即可。

内置Contract

StartActivityForResult()
StartIntentSenderForResult()
RequestMultiplePermissions()
RequestPermission()
TakePicturePreview()
TakePicture()
TakeVideo()
PickContact()
GetContent()
GetMultipleContents()
OpenDocument()
OpenMultipleDocuments()
OpenDocumentTree()
CreateDocument()

自定义Contract

除了以上内置Contract之外,还可以自定义自己的Conteact类型。

首先继承ActivityResultContract,它的内部定义了两个抽象方法,如下所示:

public abstract class ActivityResultContract<I, O> {

    public abstract @NonNull Intent createIntent(@NonNull Context context, I input);

    public abstract O parseResult(int resultCode, @Nullable Intent intent);
    ...
}

也就是说,任何一个继承自ActivityResultContractContract,都需要重写createIntent()parseResult()这两个方法。

而这两个方法的作用也非常明显。createIntent()就是用于创建一个Intent,后续会使用这个Intent来发起动作,比如启动另外一个Activity去获取数据,或者打开相机去拍照等等。而parseResult()则是用于解析响应的结果,并把解析出来的结果作为输出参数返回到Lambda表达式当中。

每一个内置的Contract都是使用的这种规则来封装的自己的逻辑。

那么我们要自定义一个什么样的Contract来进行演示呢?

我想了一下,刚才在编写两个Activity之间交换数据的时候,我们需要显示地启动SecondActivity,并手动将SecondActivity返回的数据从Intent中解析出来,这就稍微有些麻烦。而借助自定义Contract就可以对此处进行优化。

新建一个叫做GetDataFromSecondActivityContract,代码如下所示:

class GetDataFromSecondActivity : ActivityResultContract<Void, String?>() {

    override fun createIntent(context: Context, input: Void?): Intent {
        return Intent(context, SecondActivity::class.java)
    }

    override fun parseResult(resultCode: Int, intent: Intent?): String? {
        if (resultCode == Activity.RESULT_OK) {
            if (intent != null) {
                return intent.getStringExtra("data")
            }
        }
        return null
    }
    
}

我们通过泛型指定,这个Contract的输入参数是Void,输出参数是一个字符串。

然后在createIntent()方法中,我们手动创建了一个Intent,并将它的用途设置为打开SecondActivity

最后在parseResult()方法中,我们对SecondActivity返回的结果进行解析,并将解析出来的字符串作为输出参数返回。

这样一个自定义的Contract就完成了,而我们使用这个Contract再去实现最开始的在两个Activity之间交换数据的功能,就会变得更加简单:

class FirstActivity : AppCompatActivity() {

    private val getDataLauncher = registerForActivityResult(GetDataFromSecondActivity()) { data ->
        // Handle data from SecondActivity
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_first)
        val firstButton = findViewById<Button>(R.id.first_button)
        firstButton.setOnClickListener {
            getDataLauncher.launch(null)
        }
    }

}

可以看到,借助GetDataFromSecondActivity这个Contract,我们不需要再显式地声明去启动SecondActivitylaunch()方法直接传入null即可。另外,我们也不需要再去手动解析SecondActivity返回的数据,lambda表达式上的参数就是解析出来的结果了。

小提示:

在多任务的场景中,我们调用startActivityForResult()方式时,除了传入Intent之外,还需要传入一个requestCode,用于进行区分。例如:

class FirstActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_first)
        val firstButton = findViewById<Button>(R.id.first_button)
        val secondButton = findViewById<Button>(R.id.second_button)
        firstButton.setOnClickListener {
            val intent = Intent(Intent.ACTION_VIEW)
            startActivityForResult(intent, 1)
        }
        secondButton.setOnClickListener {
            val intent = Intent(Intent.ACTION_DIAL)
            startActivityForResult(intent, 2)
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        when (requestCode) {
            1 -> {
                // Handle result for ACTION_VIEW
            }
            2 -> {
                // Handle result for ACTION_DIAL
            }
        }
    }

}

这里我们分别在两处调用了startActivityForResult()方法,它们各自用于处理不同的任务,因此需要给它们设置不同的requestCode。

在onActivityResult()方法当中,我们为了区分这个结果是来自之前的哪个任务的,所以要在这里再对requestCode进行判断。

这是以前使用startActivityForResult()方法时的传统写法。

而Activity Result API是没有地方让你传入requestCode的。

我在刚接触Activity Result API的时候受思维惯性的影响被这个问题困扰了一下,没有地方传入requestCode该怎么办呢?

后来思维转过来弯之后发现,原来Activity Result API根本就不需要requestCode这种东西,我们可以使用如下写法来实现和刚才完全一样的功能:

class FirstActivity : AppCompatActivity() {

    private val actionViewLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
        // Handle result for ACTION_VIEW
    }

    private val actionDialLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
        // Handle result for ACTION_DIAL
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_first)
        val firstButton = findViewById<Button>(R.id.first_button)
        val secondButton = findViewById<Button>(R.id.second_button)
        firstButton.setOnClickListener {
            val intent = Intent(Intent.ACTION_VIEW)
            actionViewLauncher.launch(intent)
        }
        secondButton.setOnClickListener {
            val intent = Intent(Intent.ACTION_DIAL)
            actionDialLauncher.launch(intent)
        }
    }

}

由此也可以看出,Activity Result API的设计更加合理,不需要借助requestCode这种魔术数字也能对多个任务进行区分。

一切都更加简单和清晰。

最后修改:2022 年 11 月 12 日
如果觉得我的文章对你有用,请随意赞赏