验证码: 看不清楚,换一张 查询 注册会员,免验证
  • {{ basic.site_slogan }}
  • 打开微信扫一扫,
    您还可以在这里找到我们哟

    关注我们

Android startActivityForResult怎么调用与封装

阅读:1035 来源:乙速云 作者:代码code

Android startActivityForResult怎么调用与封装

      前言

      startActivityForResult 可以说是我们常用的一种操作了,用于启动新页面并拿到这个页面返回的数据,是两个 Activity 交互的基本操作。

      虽然可以通过接口,消息总线,单例池,ViewModel 等多种方法来间接的实现这样一个功能,但是 startActivityForResult 还是使用最方便的。

      目前有哪些方式实现 startActivityForResult 的功能呢?

      有新老两种方式,过时的方法是原生Activity/Fragment的 startActivityForResult 方法。另一种方法是 Activity Result API 通过 registerForActivityResult 来注册回调。

      一、原生的使用

      不管是Activity还是Fragment,我们都可以使用 startActivityForResult

      Android startActivityForResult怎么调用与封装

          override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
              super.onActivityResult(requestCode, resultCode, data)
              if (requestCode == 120 && resultCode == -1) {
                  toast("接收到返回的数据:" + data?.getStringExtra("text"))
              }
          }

      可以看到虽然标记过时了,但是 startActivityForResult 这种方法是可以用的,我们一直这么用的,老项目中有很多页面都是这么定义的。也并没有什么问题。

      不过既然谷歌推荐我们使用 Result Api 我们在以后使用 startActivityForResult 的时候还是推荐使用新的方式。

      二、对原生的封装Ghost

      在之前我们使用 startActivityForResult 这种方式的时候,为了更加方便的私有,有一种很流行的方式 Ghost 。

      它使用一种 GhostFragment 的空视图当做一次中转,这种思路在现在看来已经不稀奇了,很多框架如Glide,权限申请等都是用的这种方案。

      它的大致实现流程为:

      Activty/Fragment -> add GhostFragment -> onAttach 中 startActivityForResult -> GhostFragment onActivityResult接收结果 -> callback回调给Activty/Fragment

      总体需要两个类就可以完成这个逻辑,一个是中转Fragment,一个是管理类:

      /**
       * 封装Activity Result的API
       * 使用空Fragemnt的形式调用startActivityForResult并返回回调
       *
       * Activty/Fragment——>add GhostFragment——>onAttach中startActivityForResult
       * ——>GhostFragment onActivityResult接收结果——>callback回调给Activty/Fragment
       */
      class GhostFragment : Fragment() {
      
          private var requestCode = -1
          private var intent: Intent? = null
          private var callback: ((result: Intent?) -> Unit)? = null
      
          fun init(requestCode: Int, intent: Intent, callback: ((result: Intent?) -> Unit)) {
              this.requestCode = requestCode
              this.intent = intent
              this.callback = callback
          }
      
          private var activityStarted = false
      
          override fun onAttach(activity: Activity) {
              super.onAttach(activity)
              if (!activityStarted) {
                  activityStarted = true
                  intent?.let { startActivityForResult(it, requestCode) }
              }
          }
      
          override fun onAttach(context: Context) {
              super.onAttach(context)
              if (!activityStarted) {
                  activityStarted = true
                  intent?.let { startActivityForResult(it, requestCode) }
              }
          }
      
          override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
              super.onActivityResult(requestCode, resultCode, data)
              if (resultCode == Activity.RESULT_OK && requestCode == this.requestCode) {
                  callback?.let { it1 -> it1(data) }
              }
          }
      
          override fun onDetach() {
              super.onDetach()
              intent = null
              callback = null
          }
      
      }
      /**
       * 管理GhostFragment用于StartActivityForResult
       * 启动的时候添加Fragment 返回的时移除Fragment
       */
      object Ghost {
          var requestCode = 0
              set(value) {
                  field = if (value >= Integer.MAX_VALUE) 1 else value
              }
      
          inline fun launchActivityForResult(
              starter: FragmentActivity?,
              intent: Intent,
              crossinline callback: ((result: Intent?) -> Unit)
          ) {
              starter ?: return
              val fm = starter.supportFragmentManager
              val fragment = GhostFragment()
              fragment.init(++requestCode, intent) { result ->
                  callback(result)
                  fm.beginTransaction().remove(fragment).commitAllowingStateLoss()
              }
              fm.beginTransaction().add(fragment, GhostFragment::class.java.simpleName)
                  .commitAllowingStateLoss()
          }
      
      }

      如此我们就可以使用Kotlin的扩展方法来对它进行进一步的封装

      //真正执行AcytivityForResult的方法,使用Ghost的方式执行
      inline fun  FragmentActivity.gotoActivityForResult(
          flag: Int = -1,
          bundle: Array>? = null,
          crossinline callback: ((result: Intent?) -> Unit)
      ) {
          val intent = Intent(this, T::class.java).apply {
              if (flag != -1) {
                  this.addFlags(flag)
              }
              if (bundle != null) {
                  //调用自己的扩展方法-数组转Bundle
                  putExtras(bundle.toBundle()!!)
              }
          }
          Ghost.launchActivityForResult(this, intent, callback)
      }

      使用起来就超级简单了:

          gotoActivityForResult {
              val text = it?.getStringExtra("text")
              toast("拿到返回数据:$text")
          }
      
          gotoActivityForResult(bundle = arrayOf("id" to "123", "name" to "zhangsan")) {
              val text = it?.getStringExtra("text")
              toast("拿到返回数据:$text")
          }

      三、Result Api 的使用

      其实看Ghost的原来就看得出,他本质上还是对 startActivityForResult 的调用与封装,还是过期的方法,那么如何使用新的方式,谷歌推荐我们怎么用?

      Activity Result API :

      它是 Jetpack 的一个组件,这是官方用于替代 startActivityForResult() 和 onActivityResult() 的工具,我们以Activity 1.2.4版本为例:

      implementation "androidx.activity:activity-ktx:1.2.4"

      那么如何基础的使用它呢:

        
          private val safLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
              if (result.resultCode == RESULT_OK) {
                  val data = result.data?.getStringExtra("text")
                  toast("拿到返回数据:$data")
              }
          }
          
          //在方法中使用
          safLauncher?.launch(Intent(mActivity, Demo10Activity::class.java))

      看起来实现很简单,但是有几点要注意,Launcher 的创建需要在onStart生命周期之前,并且回调是在 Launcher 中处理的。并且 这些 Launcher 并不是只能返回Activity的Result的,还有其他的启动方式:

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

      可以看到这些方式其实对我们来说很多没必要,在真正的开发中只有 StartActivityForResult 这一种方式是我们的刚需。

      为什么?毕竟现在谁还用这种方式申请权限,操作多媒体文件。相信大家也都是使用框架来处理了,所以我们这里只对 StartActivityForResult 这一种方式做处理。毕竟这才是我们使用场景最多的,也是我们比较需要的。

      经过分析,对Result Api的封装,我们就剩下的两个重点问题:

      • 我们把 Launcher 的回调能在启动的方法中触发。

      • 实现 Launcher 在 Activity/Fragment 中的自动注册。

      下面我们就来实现吧。

      四、Result Api 的封装

      我们需要做的是:

      第一步我们把回调封装到launch方法中,并简化创建的对象方式

      第二步我们尝试自动注册的功能

      4.1 封装简化创建方式

      首先第一步,我们对 Launcher 对象做一个封装, 把 ActivityResultCallback 回调方法在 launch 方法中调用。

      /**
       * 对Result-Api的封装,支持各种输入与输出,使用泛型定义
       */
      @SuppressWarnings("unused")
      public class BaseResultLauncher {
      
          private final androidx.activity.result.ActivityResultLauncher launcher;
          private final ActivityResultCaller caller;
          private ActivityResultCallback callback;
          private MutableLiveData unprocessedResult;
      
          public BaseResultLauncher(@NonNull ActivityResultCaller caller, @NonNull ActivityResultContract contract) {
              this.caller = caller;
              launcher = caller.registerForActivityResult(contract, (result) -> {
                  if (callback != null) {
                      callback.onActivityResult(result);
                      callback = null;
                  }
              });
          }
      
          public void launch(@SuppressLint("UnknownNullness") I input, @NonNull ActivityResultCallback callback) {
              launch(input, null, callback);
          }
      
          public void launch(@SuppressLint("UnknownNullness") I input, @Nullable ActivityOptionsCompat options, @NonNull ActivityResultCallback callback) {
              this.callback = callback;
              launcher.launch(input, options);
          }
      
      }

      上门是对Result的基本封装,由于我们只想要 StartActivityForResult 这一种方式,所以我们定义一个特定的 GetSAFLauncher

      /**
       * 一般我们用这一个-StartActivityForResult 的 Launcher
       */
      class GetSAFLauncher(caller: ActivityResultCaller) :
          BaseResultLauncher(caller, ActivityResultContracts.StartActivityForResult()) {
      
          //封装另一种Intent的启动方式
          inline fun  launch(
              bundle: Array>? = null,
              @NonNull callback: ActivityResultCallback
          ) {
      
              val intent = Intent(commContext(), T::class.java).apply {
                  if (bundle != null) {
                      //调用自己的扩展方法-数组转Bundle
                      putExtras(bundle.toBundle()!!)
                  }
              }
      
              launch(intent, null, callback)
      
          }
      
      }

      注意这里调用的是 ActivityResultContracts.StartActivityForResult() 并且泛型的两个参数是 Intent 和 ActivityResult。

      如果大家想获取文件,可以使用 GetContent() 泛型的参数就要变成 String 和 Uri 。由于我们通常不使用这种方式,所以这里不做演示。

      封装第一步之后我们就能这么使用了。

          var safLauncher: GetSAFLauncher? = null
      
          //其实就是 onCreate 方法
          override fun init() {
              safLauncher = GetSAFLauncher(this@Demo16RecordActivity)
          }
      
          //AFR
          fun resultTest() {
      
              safLauncher?.launch(Intent(mActivity, Demo10Activity::class.java)) { result ->
                  val data = result.data?.getStringExtra("text")
                  toast("拿到返回数据:$data")
              }
          }

      //或者使用我们自定义的简洁方式

          fun resultTest() {
      
             safLauncher?.launch { result ->
                  val data = result.data?.getStringExtra("text")
                  toast("拿到返回数据:$data")
              }
      
              safLauncher?.launch(arrayOf("id" to "123", "name" to "zhangsan")) { result ->
                  val data = result.data?.getStringExtra("text")
                  toast("拿到返回数据:$data")
              }
          }

      使用下来是不是简单了很多了,我们只需要创建一个对象就可以了,拿到这个对象调用launch即可实现 startActivityForResult 的功能呢!

      4.2 自动注册/按需注册

      可以看到相比原始的用法,虽然我们现在的用法就简单了很多,但是我们还是要在oncreate生命周期中创建 Launcher 对象,不然会报错:

      LifecycleOwners must call register before they are STARTED.

      那我们有哪些方法处理这个问题?

      1)基类定义

      我们都已经封装成对象使用了,我们把创建的逻辑定义到BaseActivity/BaseFragment不就行了吗?

      abstract class AbsActivity() : AppCompatActivity(){
      
          protected var safLauncher: GetSAFLauncher? = null
      
          ...
      
          override fun onCreate(savedInstanceState: Bundle?) {
              super.onCreate(savedInstanceState)
              setContentView()
      
              //Result-Api
              safLauncher = GetSAFLauncher(this)
      
              ...
          }
      
      }

      这样不就行了吗?可以正常使用的。那有人可能说,你这个对象可能用不到,又不是每一个Activity都会用到 Launcher 对象,你这么无脑创建出来消耗内存。

      有办法,按需加载!

      2).懒加载

      懒加载可以吧,我需要的时候就创建。

      abstract class AbsActivity() : AppCompatActivity(){
      
          val safLauncher by lazy { GetSAFLauncher(this) }
      
          ...
      }

      额,等等,这样的懒加载貌似是不行的,这在用的时候才初始化,一样会报错:

      LifecycleOwners must call register before they are STARTED.

      我们只能在页面创建的时候就要明确,这个页面是否需要这个 Launcher 对象,如果要就要在onCreate中创建对象,如果确定不要 Launcher 对象,那么就不必创建对象。

      那我们就这么做:

      abstract class AbsActivity() : AppCompatActivity(){
      
          protected var safLauncher: GetSAFLauncher? = null
      
          ...
      
          override fun onCreate(savedInstanceState: Bundle?) {
              super.onCreate(savedInstanceState)
              setContentView()
      
              if (needLauncher()) {
                  //Result-Api
                  safLauncher = GetSAFLauncher(this)
              }
      
              ...
          }
      
          open protected fun needLauncher(): Boolean = false
      
      }

      我们使用一个flag判断不就行了吗?这个页面如果需要 Launcher 对象,重写方法返回true就行了。默认是不创建这个对象的。

      3).Kotlin委托

      我们可以使用Kotlin的委托方式,把初始化的代码和 Launcher 的对象获取用接口封装,然后提供对应的实现类,不就可以完成按需添加 Launcher 的效果了吗?

      我们定义一个接口,由于逻辑都封装在了别处,这里就尽量不改动之前的代码,只是定义初始化和提供对象两种方法。

      /**
       * 定义是否需要SAFLauncher
       */
      interface ISAFLauncher {
      
          fun  T.initLauncher()
      
          fun getLauncher(): GetSAFLauncher?
      
      }

      接着定义这个实现类

      class SAFLauncher : ISAFLauncher {
      
          private var safLauncher: GetSAFLauncher? = null
      
          override fun  T.initLauncher() {
              safLauncher = GetSAFLauncher(this)
          }
      
          override fun getLauncher(): GetSAFLauncher? = safLauncher
      
      }

      然后我们就可以使用了:

      class Demo16RecordActivity : BaseActivity, ISAFLauncher by SAFLauncher() {
      
          //onCreate中直接初始化对象
          override fun init() {
              initLauncher()
          }
      
          
          //获取到对象直接用即可,还是之前的几个方法,没有变。
          fun resultTest() {
      
             getLauncher()?.launch { result ->
                  val data = result.data?.getStringExtra("text")
                  toast("拿到返回数据:$data")
              }
          }
      
      }

      效果都是一样的:

      Android startActivityForResult怎么调用与封装

      这样通过委托的方式,我们就能自己管理初始化,自己随时获取到对象调用launch方法。

      如果你当前的Activity不需要 startActivityForResult 这种功能,那么你不实现这个接口即可,如果想要 startActivityForResult 的功能,就实现接口委托实现,从而实现按需加载的逻辑。

      我们再回顾一下 Result Api 需要封装的两个痛点与优化步骤:

      • 第一步我们把回调封装到launch方法中,并简化创建的对象方式

      • 第二步我们尝试自动注册的功能

      同时我们还对一些步骤做了更多的可能性分析,对主动注册的方式我们有三种方式,(当然其实还有更多别的方式来实现,我只写了我认为比较简单方便的几种方式)。

      到此对 Result Api的封装就此结束。

    分享到:
    *特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们: hlamps#outlook.com (#换成@)。
    相关文章
    {{ v.title }}
    {{ v.description||(cleanHtml(v.content)).substr(0,100)+'···' }}
    你可能感兴趣
    推荐阅读 更多>

    {{ basic.bottom_text }}