【问题标题】:How to manage startActivityForResult on Android如何在 Android 上管理 startActivityForResult
【发布时间】:2012-05-11 12:55:19
【问题描述】:

在我的活动中,我通过startActivityForResult 从主要活动中调用第二个活动。在我的第二个活动中,有一些方法可以完成此活动(可能没有结果),但是,其中只有一个返回结果。

例如,从主要活动中,我调用了第二个活动。在这个活动中,我正在检查手机的一些功能,例如它是否有摄像头。如果没有,那么我将关闭此活动。另外,在准备MediaRecorderMediaPlayer的过程中如果出现问题我会关闭这个活动。

如果它的设备有摄像头并且录制完成,那么在录制视频后,如果用户点击完成按钮,我会将结果(录制视频的地址)发送回主活动。

如何检查主要活动的结果?

【问题讨论】:

标签: android android-intent android-activity startactivityforresult


【解决方案1】:

startActivityForResult:在 Android X 中已弃用

对于方式,我们有registerForActivityResult

在 Java 中:

 // You need to create a launcher variable inside onAttach or onCreate or global, i.e, before the activity is displayed
 ActivityResultLauncher<Intent> launchSomeActivity = registerForActivityResult(
     new ActivityResultContracts.StartActivityForResult(),
     new ActivityResultCallback<ActivityResult>() {
              @Override
              public void onActivityResult(ActivityResult result) {
                   if (result.getResultCode() == Activity.RESULT_OK) {
                         Intent data = result.getData();
                         // your operation....
                    }
               }
      });

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

在 Kotlin 中:

var resultLauncher = registerForActivityResult(StartActivityForResult()) { result ->
    if (result.resultCode == Activity.RESULT_OK) {
        val data: Intent? = result.data
        // your operation...
    }
}

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

优势:

  1. 新方法是降低我们在从片段或另一个活动中调用活动时所面临的复杂性
  2. 轻松请求任何许可并获得回调

【讨论】:

  • 在 Android API 29 上等待 -1 (Activity.RESULT_OK) 时,result.resultCode 仍然为 0。
  • 在 Kotlin 中全局初始化它,这样写:private lateinit var startActivityForResult:ActivityResultLauncher
【解决方案2】:

我将在简短的回答中使用Android X 发布新的“方式”(因为在某些情况下您不需要自定义注册表或合同)。如果您想了解更多信息,请参阅:Getting a result from an activity

重要提示:Android X 的向后兼容性实际上存在一个错误,因此您必须在 Gradle 文件中添加fragment_version否则会出现异常“New result API error : Can only use lower 16 bits for requestCode”

dependencies {

    def activity_version = "1.2.0-beta01"
    // Java language implementation
    implementation "androidx.activity:activity:$activity_version"
    // Kotlin
    implementation "androidx.activity:activity-ktx:$activity_version"

    def fragment_version = "1.3.0-beta02"
    // Java language implementation
    implementation "androidx.fragment:fragment:$fragment_version"
    // Kotlin
    implementation "androidx.fragment:fragment-ktx:$fragment_version"
    // Testing Fragments in Isolation
    debugImplementation "androidx.fragment:fragment-testing:$fragment_version"
}

现在您只需添加您的活动的这个成员变量。这使用预定义的注册表和通用合同。

public class MyActivity extends AppCompatActivity{

   ...

    /**
     * Activity callback API.
     */
    // https://developer.android.com/training/basics/intents/result
    private ActivityResultLauncher<Intent> mStartForResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),

            new ActivityResultCallback<ActivityResult>() {

                @Override
                public void onActivityResult(ActivityResult result) {
                    switch (result.getResultCode()) {
                        case Activity.RESULT_OK:
                            Intent intent = result.getData();
                            // Handle the Intent
                            Toast.makeText(MyActivity.this, "Activity returned ok", Toast.LENGTH_SHORT).show();
                            break;
                        case Activity.RESULT_CANCELED:
                            Toast.makeText(MyActivity.this, "Activity canceled", Toast.LENGTH_SHORT).show();
                            break;
                    }
                }
            });

在您拥有新 API 之前:

btn.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MyActivity .this, EditActivity.class);
                startActivityForResult(intent, Constants.INTENT_EDIT_REQUEST_CODE);
            }
        });

您可能会注意到请求代码现在由 Google 框架生成(并保存)。 你的代码变成:

 btn.setOnClickListener(new View.OnClickListener() {

                @Override
                public void onClick(View v) {
                    Intent intent = new Intent(MyActivity .this, EditActivity.class);
                    mStartForResult.launch(intent);
                }
            });

【讨论】:

    【解决方案3】:

    ActivityResultRegistry 是推荐的方法

    ComponentActivity 现在提供了一个ActivityResultRegistry,让您可以处理startActivityForResult()+onActivityResult() 以及requestPermissions()+onRequestPermissionsResult() 流,而无需覆盖ActivityFragment 中的方法,带来更多通过ActivityResultContract 进行类型安全,并提供用于测试这些流程的挂钩。

    强烈建议使用 Android 10 Activity 1.2.0-alpha02 和 Fragment 1.3.0-alpha02 中引入的 Activity Result API。

    将此添加到您的build.gradle

    def activity_version = "1.2.0-beta01"
    
    // Java language implementation
    implementation "androidx.activity:activity:$activity_version"
    // Kotlin
    implementation "androidx.activity:activity-ktx:$activity_version"
    

    如何使用预建合约

    这个新的 API 具有以下预建功能

    1. 拍摄视频
    2. 挑选联系人
    3. 获取内容
    4. 获取内容
    5. 打开文档
    6. 打开文档
    7. 打开文档树
    8. 创建文档
    9. 拨号
    10. 拍照
    11. 请求权限
    12. 请求权限

    使用 takePicture 合约的示例:

    private val takePicture = prepareCall(ActivityResultContracts.TakePicture()) { bitmap: Bitmap? ->
        // Do something with the Bitmap, if present
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    
        button.setOnClickListener { takePicture() }
    }
    

    那么这里发生了什么?让我们稍微分解一下。 takePicture 只是一个回调,它返回一个可为空的位图——它是否为空取决于 onActivityResult 进程是否成功。 prepareCall 然后将此调用注册到ComponentActivity 上的一个新功能中,称为ActivityResultRegistry - 我们稍后再讨论。 ActivityResultContracts.TakePicture() 是 Google 为我们创建的内置助手之一,最后调用 takePicture 实际上会触发 Intent,就像您之前使用 Activity.startActivityForResult(intent, REQUEST_CODE) 一样。

    如何编写自定义合同

    一个简单的契约,它接受一个 Int 作为 input 并返回一个字符串,请求的 Activity 在结果 Intent 中返回该字符串。

    class MyContract : ActivityResultContract<Int, String>() {
    
        companion object {
            const val ACTION = "com.myapp.action.MY_ACTION"
            const val INPUT_INT = "input_int"
            const val OUTPUT_STRING = "output_string"
        }
    
        override fun createIntent(input: Int): Intent {
            return Intent(ACTION)
                .apply { putExtra(INPUT_INT, input) }
        }
    
        override fun parseResult(resultCode: Int, intent: Intent?): String? {
            return when (resultCode) {
                Activity.RESULT_OK -> intent?.getStringExtra(OUTPUT_STRING)
                else -> null
            }
        }
    }
    
    class MyActivity : AppCompatActivity() {
    
        private val myActionCall = prepareCall(MyContract()) { result ->
            Log.i("MyActivity", "Obtained result: $result")
        }
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            ...
            button.setOnClickListener {
                myActionCall(500)
            }
        }
    }
    

    查看this official documentation了解更多信息。

    【讨论】:

    • 抱歉,我没有收到prepareCall
    【解决方案4】:

    你需要重写Activity.onActivityResult():

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
    
        if (resultCode == RESULT_CODE_ONE) {
    
           String a = data.getStringExtra("RESULT_CODE_ONE");
    
        }
        else if(resultCode == RESULT_CODE_TWO){
    
           // b was clicked
        }
        else{
    
        }
    }
    

    【讨论】:

    • 感谢您的回答,但您的回答与批准的回答有什么区别?
    【解决方案5】:

    这是安卓上很常见的问题

    可以分为三部分

    1. 开始活动 B(在活动 A 中发生)
    2. 设置请求的数据(在活动 B 中发生)
    3. 接收请求的数据(在活动 A 中发生)
    1. 开始活动 B
    Intent i = new Intent(A.this, B.class);
    startActivity(i);
    
    1. 设置请求的数据

    在这部分中,您决定是否要在特定事件发生时发回数据。

    例如:在活动 B 中有一个 EditText 和两个按钮 b1、b2。 单击按钮 b1 将数据发送回活动 A。 点击按钮 b2 不会发送任何数据。

    发送数据

    b1......clickListener
    {
        Intent resultIntent = new Intent();
        resultIntent.putExtra("Your_key", "Your_value");
        setResult(RES_CODE_A, resultIntent);
        finish();
    }
    

    不发送数据

    b2......clickListener
    {
       setResult(RES_CODE_B, new Intent());
       finish();
    }
    

    用户点击后退按钮

    默认情况下,使用 Activity.RESULT_CANCEL 响应代码设置结果

    1. 检索结果

    对于那个重写 onActivityResult 方法

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
    
        if (resultCode == RES_CODE_A) {
    
           // b1 was clicked
           String x = data.getStringExtra("RES_CODE_A");
    
        }
        else if(resultCode == RES_CODE_B){
    
           // b2 was clicked
        }
        else{
           // The back button was clicked
        }
    }
    

    【讨论】:

      【解决方案6】:

      补充the answer from Nishant,返回活动结果的最佳方式是:

      Intent returnIntent = getIntent();
      returnIntent.putExtra("result",result);
      setResult(RESULT_OK,returnIntent);
      finish();
      

      我遇到了问题

      new Intent();
      

      然后我发现正确的方法是使用

      getIntent();
      

      获取当前意图。

      【讨论】:

      • 创建一个新的Intent 感觉有点奇怪,它只是为了保存一个Bundle 而没有像动作或组件这样的正常值。但是修改用于启动当前活动的Intent 也感觉有点奇怪(并且有潜在危险?)。所以我搜索了Android本身的源代码,发现他们总是创建一个新的Intent作为结果使用。例如,github.com/aosp-mirror/platform_frameworks_base/blob/…
      • 您好 spaaarky21,感谢您的评论。很抱歉,我不太清楚地解释我是如何最终得到这个解决方案的。那是三年前,我只记得我的应用程序因为“新意图”而崩溃,这就是我说“我遇到问题”时的意思。实际上,我只是尝试使用“getIntent”,因为它在当时是有意义的,并且有效!因此,我决定分享我的解决方案。也许不是说“最佳方式”或“正确方式”的最佳选择,但我坚持我的解决方案。它解决了我的问题,显然也解决了其他人的问题。谢谢
      • 哇!效果很好。 getIntent() 似乎是将数据返回到未知活动的完美方式,从那里调用活动。谢谢!
      【解决方案7】:

      首先,您使用startActivityForResult() 和第一个Activity 中的参数,如果您想将数据从第二个Activity 发送到第一个Activity,然后使用IntentsetResult() 方法传递值,然后在第一个 ActivityonActivityResult() 方法中获取该数据。

      【讨论】:

        【解决方案8】:

        如果你想用活动结果更新用户界面,你不能使用this.runOnUiThread(new Runnable() {}。这样做,UI 不会用新值刷新。相反,您可以这样做:

        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
        
            if (resultCode == RESULT_CANCELED) {
                return;
            }
        
            global_lat = data.getDoubleExtra("LATITUDE", 0);
            global_lng = data.getDoubleExtra("LONGITUDE", 0);
            new_latlng = true;
        }
        
        @Override
        protected void onResume() {
            super.onResume();
        
            if(new_latlng)
            {
                PhysicalTagProperties.this.setLocation(global_lat, global_lng);
                new_latlng=false;
            }
        }
        

        这看起来很傻,但效果很好。

        【讨论】:

          【解决方案9】:

          如何查看主要活动的结果?

          你需要覆盖Activity.onActivityResult(),然后检查它的参数:

          • requestCode 标识哪个应用程序返回了这些结果。这是您在致电startActivityForResult() 时定义的。
          • resultCode 通知您此应用是成功、失败还是其他情况
          • data 保存此应用返回的任何信息。这可能是null

          【讨论】:

          • 就是说requestCode只用在第一个activity,从不用于第2个activity?如果第二个活动有不同的方法,它会改变,但基于意图附加而不是请求代码,对吧? 编辑:是的,*.com/questions/5104269/…
          【解决方案10】:

          从您的FirstActivity,使用startActivityForResult() 方法调用SecondActivity

          例如:

          int LAUNCH_SECOND_ACTIVITY = 1
          Intent i = new Intent(this, SecondActivity.class);
          startActivityForResult(i, LAUNCH_SECOND_ACTIVITY);
          

          在您的SecondActivity 中,将要返回的数据设置为FirstActivity。如果不想返回,就不要设置。

          例如:在SecondActivity如果要发回数据:

          Intent returnIntent = new Intent();
          returnIntent.putExtra("result",result);
          setResult(Activity.RESULT_OK,returnIntent);
          finish();
          

          如果不想返回数据:

          Intent returnIntent = new Intent();
          setResult(Activity.RESULT_CANCELED, returnIntent);
          finish();
          

          现在在您的FirstActivity 类中,为onActivityResult() 方法编写以下代码。

          @Override
          protected void onActivityResult(int requestCode, int resultCode, Intent data) {
              super.onActivityResult(requestCode, resultCode, data);
          
              if (requestCode == LAUNCH_SECOND_ACTIVITY) {
                  if(resultCode == Activity.RESULT_OK){
                      String result=data.getStringExtra("result");
                  }
                  if (resultCode == Activity.RESULT_CANCELED) {
                      // Write your code if there's no result
                  }
              }
          } //onActivityResult
          

          要在 Kotlin 中更好地实现两个 Activity 之间的数据传递,请通过 'A better way to pass data between Activities'

          【讨论】:

          • 在setResult(RESULT_CANCELLED, returnIntent)中放置RESUT_CANCELLED时intent的目的是什么
          • @ismail 假设在SecondActivity 中发生了一些异常,这种情况下还需要将结果返回给FirstActivity,因此可以在catch 块中将结果设置为"RESULT_CANCELLED"并返回FirstActivtyFirstActivity's' 'onActivityResult() 你可以检查你是否得到了成功或失败的结果。
          • 所以这取决于你,如果你不需要知道取消的原因,你可以使用 setResult(RESULT_CANCELED);没有任何意图
          • @Lei Leyba No finish() 在调用 startActivityForResult() 后没有被调用。First Activity 将进入暂停状态。
          • 对我来说它不起作用 -.- 这是我非常讨厌 Android 的地方 - 这个系统太不可靠了 :-/
          【解决方案11】:

          在您的主要活动中

          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
          
              findViewById(R.id.takeCam).setOnClickListener(new View.OnClickListener() {
                  @Override
                  public void onClick(View view) {
                      Intent intent=new Intent(getApplicationContext(),TakePhotoActivity.class);
                      intent.putExtra("Mode","Take");
                      startActivity(intent);
                  }
              });
              findViewById(R.id.selectGal).setOnClickListener(new View.OnClickListener() {
                  @Override
                  public void onClick(View view) {
                      Intent intent=new Intent(getApplicationContext(),TakePhotoActivity.class);
                      intent.putExtra("Mode","Gallery");
                      startActivity(intent);
                  }
              });
          }
          
          @Override
          public void onActivityResult(int requestCode, int resultCode, Intent data) {
              super.onActivityResult(requestCode, resultCode, data);
          
          }
          

          在要显示的第二个活动中

          private static final int CAMERA_REQUEST = 1888;
          private ImageView imageView;
          private static final int MY_CAMERA_PERMISSION_CODE = 100;
          private static final int PICK_PHOTO_FOR_AVATAR = 0;
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_take_photo);
          
              imageView=findViewById(R.id.imageView);
          
              if(getIntent().getStringExtra("Mode").equals("Gallery"))
              {
                  pickImage();
              }
              else {
                  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                      if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                          requestPermissions(new String[]{Manifest.permission.CAMERA}, MY_CAMERA_PERMISSION_CODE);
                      } else {
                          Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                          startActivityForResult(cameraIntent, CAMERA_REQUEST);
                      }
                  }
              }
          }
          public void pickImage() {
              Intent intent = new Intent(Intent.ACTION_PICK);
              intent.setType("image/*");
              startActivityForResult(intent, PICK_PHOTO_FOR_AVATAR);
          }
          @Override
          public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
          {
              super.onRequestPermissionsResult(requestCode, permissions, grantResults);
              if (requestCode == MY_CAMERA_PERMISSION_CODE)
              {
                  if (grantResults[0] == PackageManager.PERMISSION_GRANTED)
                  {
                      Intent cameraIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
                      startActivityForResult(cameraIntent, CAMERA_REQUEST);
                  }
                  else
                  {
                      Toast.makeText(this, "Camera Permission Denied..", Toast.LENGTH_LONG).show();
                  }
              }
          }
          
          @Override
          protected void onActivityResult(int requestCode, int resultCode, Intent data) {
              super.onActivityResult(requestCode, resultCode, data);
              if (requestCode == CAMERA_REQUEST && resultCode == Activity.RESULT_OK) {
                  Bitmap photo = (Bitmap) data.getExtras().get("data");
                  imageView.setImageBitmap(photo);
              }
                  if (requestCode == PICK_PHOTO_FOR_AVATAR && resultCode == Activity.RESULT_OK) {
                      if (data == null) {
                          Log.d("ABC","No Such Image Selected");
                          return;
                      }
                      try {
                          Uri selectedData=data.getData();
                          Log.d("ABC","Image Pick-Up");
                          imageView.setImageURI(selectedData);
                          InputStream inputStream = getApplicationContext().getContentResolver().openInputStream(selectedData);
                          Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
                          Bitmap bmp=MediaStore.Images.Media.getBitmap(getContentResolver(),selectedData);
                      } catch (FileNotFoundException e) {
                          e.printStackTrace();
                      } catch(IOException e){
          
                      }
              }
          }
          

          【讨论】:

            【解决方案12】:

            示例

            要在上下文中查看整个过程,这里有一个补充答案。更多解释见my fuller answer

            MainActivity.java

            public class MainActivity extends AppCompatActivity {
            
                // Add a different request code for every activity you are starting from here
                private static final int SECOND_ACTIVITY_REQUEST_CODE = 0;
            
                @Override
                protected void onCreate(Bundle savedInstanceState) {
                    super.onCreate(savedInstanceState);
                    setContentView(R.layout.activity_main);
                }
            
                // "Go to Second Activity" button click
                public void onButtonClick(View view) {
            
                    // Start the SecondActivity
                    Intent intent = new Intent(this, SecondActivity.class);
                    startActivityForResult(intent, SECOND_ACTIVITY_REQUEST_CODE);
                }
            
                // This method is called when the second activity finishes
                @Override
                protected void onActivityResult(int requestCode, int resultCode, Intent data) {
                    super.onActivityResult(requestCode, resultCode, data);
            
                    // check that it is the SecondActivity with an OK result
                    if (requestCode == SECOND_ACTIVITY_REQUEST_CODE) {
                        if (resultCode == RESULT_OK) { // Activity.RESULT_OK
            
                            // get String data from Intent
                            String returnString = data.getStringExtra("keyName");
            
                            // set text view with string
                            TextView textView = (TextView) findViewById(R.id.textView);
                            textView.setText(returnString);
                        }
                    }
                }
            }
            

            SecondActivity.java

            public class SecondActivity extends AppCompatActivity {
            
                @Override
                protected void onCreate(Bundle savedInstanceState) {
                    super.onCreate(savedInstanceState);
                    setContentView(R.layout.activity_second);
                }
            
                // "Send text back" button click
                public void onButtonClick(View view) {
            
                    // get the text from the EditText
                    EditText editText = (EditText) findViewById(R.id.editText);
                    String stringToPassBack = editText.getText().toString();
            
                    // put the String to pass back into an Intent and close this activity
                    Intent intent = new Intent();
                    intent.putExtra("keyName", stringToPassBack);
                    setResult(RESULT_OK, intent);
                    finish();
                }
            }
            

            【讨论】:

            【解决方案13】:

            对于wrong requestCode in onActivityResult有问题的人

            如果您从 Fragment 调用 startActivityForResult(),则 requestCode 将由拥有 Fragment 的 Activity 更改。

            如果您想在活动中获得正确的结果代码,请尝试以下操作:

            变化:

            startActivityForResult(intent, 1);收件人:

            getActivity().startActivityForResult(intent, 1);

            【讨论】: